From a44054f9dbf403fdef4a11696d6ff020e5d395cf Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Sat, 23 May 2015 03:24:26 +0200 Subject: [PATCH 01/38] Make TextureCache::getImageTexture static --- interface/src/audio/AudioToolBox.cpp | 6 +++--- interface/src/devices/CameraToolBox.cpp | 4 ++-- interface/src/ui/ApplicationOverlay.cpp | 7 +++---- interface/src/ui/RearMirrorTools.cpp | 7 +++---- libraries/render-utils/src/TextureCache.cpp | 2 +- libraries/render-utils/src/TextureCache.h | 2 +- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/interface/src/audio/AudioToolBox.cpp b/interface/src/audio/AudioToolBox.cpp index 85b8b19788..68328e151e 100644 --- a/interface/src/audio/AudioToolBox.cpp +++ b/interface/src/audio/AudioToolBox.cpp @@ -40,13 +40,13 @@ void AudioToolBox::render(int x, int y, int padding, bool boxed) { glEnable(GL_TEXTURE_2D); if (!_micTexture) { - _micTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/mic.svg"); + _micTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/mic.svg"); } if (!_muteTexture) { - _muteTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/mic-mute.svg"); + _muteTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/mic-mute.svg"); } if (_boxTexture) { - _boxTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/audio-box.svg"); + _boxTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/audio-box.svg"); } auto audioIO = DependencyManager::get(); diff --git a/interface/src/devices/CameraToolBox.cpp b/interface/src/devices/CameraToolBox.cpp index a1e00d7052..27cee5185b 100644 --- a/interface/src/devices/CameraToolBox.cpp +++ b/interface/src/devices/CameraToolBox.cpp @@ -76,10 +76,10 @@ void CameraToolBox::render(int x, int y, bool boxed) { glEnable(GL_TEXTURE_2D); if (!_enabledTexture) { - _enabledTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/face.svg"); + _enabledTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/face.svg"); } if (!_mutedTexture) { - _mutedTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/face-mute.svg"); + _mutedTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/face-mute.svg"); } const int MUTE_ICON_SIZE = 24; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index c65de2afb0..1e86078a8b 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -423,8 +423,8 @@ void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float }); if (!_crosshairTexture) { - _crosshairTexture = DependencyManager::get()-> - getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); + _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + + "images/sixense-reticle.png"); } //draw the mouse pointer @@ -564,8 +564,7 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, void ApplicationOverlay::renderPointers() { //lazily load crosshair texture if (_crosshairTexture == 0) { - _crosshairTexture = DependencyManager::get()-> - getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); + _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); } glEnable(GL_TEXTURE_2D); diff --git a/interface/src/ui/RearMirrorTools.cpp b/interface/src/ui/RearMirrorTools.cpp index ec73668d9e..25d37f4ef8 100644 --- a/interface/src/ui/RearMirrorTools.cpp +++ b/interface/src/ui/RearMirrorTools.cpp @@ -34,11 +34,10 @@ RearMirrorTools::RearMirrorTools(QRect& bounds) : _windowed(false), _fullScreen(false) { - auto textureCache = DependencyManager::get(); - _closeTexture = textureCache->getImageTexture(PathUtils::resourcesPath() + "images/close.svg"); + _closeTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/close.svg"); - _zoomHeadTexture = textureCache->getImageTexture(PathUtils::resourcesPath() + "images/plus.svg"); - _zoomBodyTexture = textureCache->getImageTexture(PathUtils::resourcesPath() + "images/minus.svg"); + _zoomHeadTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/plus.svg"); + _zoomBodyTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/minus.svg"); _shrinkIconRect = QRect(ICON_PADDING, ICON_PADDING, ICON_SIZE, ICON_SIZE); _closeIconRect = QRect(_bounds.left() + ICON_PADDING, _bounds.top() + ICON_PADDING, ICON_SIZE, ICON_SIZE); diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index 6facd99ff0..232e972707 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -296,7 +296,7 @@ GLuint TextureCache::getShadowDepthTextureID() { } /// Returns a texture version of an image file -gpu::TexturePointer TextureCache::getImageTexture(const QString & path) { +gpu::TexturePointer TextureCache::getImageTexture(const QString& path) { QImage image = QImage(path).mirrored(false, true); gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); diff --git a/libraries/render-utils/src/TextureCache.h b/libraries/render-utils/src/TextureCache.h index 603ab3a807..ba7176b2a4 100644 --- a/libraries/render-utils/src/TextureCache.h +++ b/libraries/render-utils/src/TextureCache.h @@ -56,7 +56,7 @@ public: const gpu::TexturePointer& getBlueTexture(); /// Returns a texture version of an image file - gpu::TexturePointer getImageTexture(const QString & path); + static gpu::TexturePointer getImageTexture(const QString& path); /// Loads a texture from the specified URL. NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, bool dilatable = false, From a52c79c378ff9f954b84145523ea6992bf3e438f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Sat, 23 May 2015 03:25:57 +0200 Subject: [PATCH 02/38] typo --- libraries/render-utils/src/TextureCache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index 232e972707..97385cb060 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -383,9 +383,9 @@ ImageReader::ImageReader(const QWeakPointer& texture, TextureType type _content(content) { } -std::once_flag onceListSuppoertedFormatsflag; +std::once_flag onceListSupportedFormatsflag; void listSupportedImageFormats() { - std::call_once(onceListSuppoertedFormatsflag, [](){ + std::call_once(onceListSupportedFormatsflag, [](){ auto supportedFormats = QImageReader::supportedImageFormats(); QString formats; foreach(const QByteArray& f, supportedFormats) { From cc7a67e05b5016bbb0e9d9683245824ae6f1a182 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Sun, 24 May 2015 19:18:33 +0200 Subject: [PATCH 03/38] Fix entityProperties.html undefined ref --- examples/html/entityProperties.html | 32 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index ece2da7760..7c214624c2 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -491,22 +491,7 @@ elModelAnimationFrame.value = properties.animationFrameIndex; elModelAnimationSettings.value = properties.animationSettings; elModelTextures.value = properties.textures; - elModelOriginalTextures.value = properties.originalTextures; - if (properties.type == "ParticleEffect") { - for (var i = 0; i < elParticleSections.length; i++) { - elParticleSections[i].style.display = 'block'; - } - - elParticleMaxParticles.value = properties.maxParticles; - elParticleLifeSpan.value = properties.lifespan.toFixed(2); - elParticleEmitRate.value = properties.emitRate.toFixed(1); - elParticleEmitDirectionX.value = properties.emitDirection.x.toFixed(2); - elParticleEmitDirectionY.value = properties.emitDirection.y.toFixed(2); - elParticleEmitDirectionZ.value = properties.emitDirection.z.toFixed(2); - elParticleEmitStrength.value = properties.emitStrength.toFixed(2); - elParticleLocalGravity.value = properties.localGravity.toFixed(2); - elParticleRadius.value = properties.particleRadius.toFixed(3); - } + elModelOriginalTextures.value = properties.originalTextures; } else if (properties.type == "Web") { for (var i = 0; i < elWebSections.length; i++) { elWebSections[i].style.display = 'block'; @@ -589,6 +574,20 @@ showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); showElements(document.getElementsByClassName('atmosphere-section'), elZoneBackgroundMode.value == 'atmosphere'); + } else if (properties.type == "ParticleEffect") { + for (var i = 0; i < elParticleSections.length; i++) { + elParticleSections[i].style.display = 'block'; + } + + elParticleMaxParticles.value = properties.maxParticles; + elParticleLifeSpan.value = properties.lifespan.toFixed(2); + elParticleEmitRate.value = properties.emitRate.toFixed(1); + elParticleEmitDirectionX.value = properties.emitDirection.x.toFixed(2); + elParticleEmitDirectionY.value = properties.emitDirection.y.toFixed(2); + elParticleEmitDirectionZ.value = properties.emitDirection.z.toFixed(2); + elParticleEmitStrength.value = properties.emitStrength.toFixed(2); + elParticleLocalGravity.value = properties.localGravity.toFixed(2); + elParticleRadius.value = properties.particleRadius.toFixed(3); } if (selected) { @@ -709,7 +708,6 @@ elParticleMaxParticles.addEventListener('change', createEmitNumberPropertyUpdateFunction('maxParticles')); elParticleLifeSpan.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifespan')); elParticleEmitRate.addEventListener('change', createEmitNumberPropertyUpdateFunction('emitRate')); - elParticleEmitDirection.addEventListener('change', createEmitNumberPropertyUpdateFunction('emitDirection')); var particleEmitDirectionChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier( 'emitDirection', elParticleEmitDirectionX, elParticleEmitDirectionY, elParticleEmitDirectionZ, DEGREES_TO_RADIANS); elParticleEmitDirectionX.addEventListener('change', particleEmitDirectionChangeFunction); From 3d7a8343567c3cd0037c43b9bb00fd483aa07639 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Sun, 24 May 2015 19:49:45 +0200 Subject: [PATCH 04/38] Ignore new entity dimensions if one of them is 0 --- libraries/entities/src/EntityItem.cpp | 7 +++++++ libraries/entities/src/EntityItem.h | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 6ad2bed291..06c9260891 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1020,6 +1020,13 @@ void EntityItem::setTranformToCenter(const Transform& transform) { setTransform(copy); } +void EntityItem::setDimensions(const glm::vec3& value) { + if (value.x == 0.0f || value.y == 0.0f || value.z == 0.0f) { + return; + } + _transform.setScale(value); +} + /// The maximum bounding cube for the entity, independent of it's rotation. /// This accounts for the registration point (upon which rotation occurs around). /// diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 8f88b6de07..0d210e5762 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -195,7 +195,7 @@ public: /// Dimensions in meters (0.0 - TREE_SCALE) inline const glm::vec3& getDimensions() const { return _transform.getScale(); } - inline virtual void setDimensions(const glm::vec3& value) { _transform.setScale(glm::abs(value)); } + virtual void setDimensions(const glm::vec3& value); float getGlowLevel() const { return _glowLevel; } From 526ec3d4894921b45c453ee5066f49c50eae762a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 25 May 2015 11:45:16 +0200 Subject: [PATCH 05/38] Move #if #else #endif for convenience This is a convenience change. The double '{' created by the #else messes up Xcode autoindentation. It won't that way and the fact that useClientState is a const set to false when SUPPORT_LEGACY_OPENGL, the code inside the if should still get optimised out by the compiler. --- libraries/gpu/src/gpu/GLBackendInput.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu/src/gpu/GLBackendInput.cpp index fde6ac40d0..4af11f4566 100755 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu/src/gpu/GLBackendInput.cpp @@ -66,22 +66,23 @@ void GLBackend::updateInput() { newActivation.set(attrib._slot); } } - + // Manage Activation what was and what is expected now for (unsigned int i = 0; i < newActivation.size(); i++) { bool newState = newActivation[i]; if (newState != _input._attributeActivation[i]) { #if defined(SUPPORT_LEGACY_OPENGL) - if (i < NUM_CLASSIC_ATTRIBS) { + const bool useClientState = i < NUM_CLASSIC_ATTRIBS; +#else + const bool useClientState = false; +#endif + if (useClientState) { if (newState) { glEnableClientState(attributeSlotToClassicAttribName[i]); } else { glDisableClientState(attributeSlotToClassicAttribName[i]); } } else { -#else - { -#endif if (newState) { glEnableVertexAttribArray(i); } else { @@ -89,7 +90,7 @@ void GLBackend::updateInput() { } } (void) CHECK_GL_ERROR(); - + _input._attributeActivation.flip(i); } } @@ -123,8 +124,12 @@ void GLBackend::updateInput() { GLenum type = _elementTypeToGLType[attrib._element.getType()]; GLuint stride = strides[bufferNum]; GLuint pointer = attrib._offset + offsets[bufferNum]; - #if defined(SUPPORT_LEGACY_OPENGL) - if (slot < NUM_CLASSIC_ATTRIBS) { +#if defined(SUPPORT_LEGACY_OPENGL) + const bool useClientState = i < NUM_CLASSIC_ATTRIBS; +#else + const bool useClientState = false; +#endif + if (useClientState) { switch (slot) { case Stream::POSITION: glVertexPointer(count, type, stride, reinterpret_cast(pointer)); @@ -140,9 +145,6 @@ void GLBackend::updateInput() { break; }; } else { - #else - { - #endif GLboolean isNormalized = attrib._element.isNormalized(); glVertexAttribPointer(slot, count, type, isNormalized, stride, reinterpret_cast(pointer)); From c0725813b6dfc2b1200b76ff8f915d03977bdf58 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 25 May 2015 12:11:24 +0200 Subject: [PATCH 06/38] typo --- libraries/render-utils/src/GeometryCache.cpp | 6 +++--- libraries/render-utils/src/GeometryCache.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index f7280fc0ec..cf5a0d58da 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -815,9 +815,9 @@ void GeometryCache::renderSolidCube(gpu::Batch& batch, float size, const glm::ve const int VERTEX_STRIDE = sizeof(GLfloat) * FLOATS_PER_VERTEX * 2; // vertices and normals const int NORMALS_OFFSET = sizeof(GLfloat) * FLOATS_PER_VERTEX; - if (!_solidCubeVerticies.contains(size)) { + if (!_solidCubeVertices.contains(size)) { gpu::BufferPointer verticesBuffer(new gpu::Buffer()); - _solidCubeVerticies[size] = verticesBuffer; + _solidCubeVertices[size] = verticesBuffer; GLfloat* vertexData = new GLfloat[vertexPoints * 2]; // vertices and normals GLfloat* vertex = vertexData; @@ -892,7 +892,7 @@ void GeometryCache::renderSolidCube(gpu::Batch& batch, float size, const glm::ve colorBuffer->append(sizeof(colors), (gpu::Byte*) colors); } - gpu::BufferPointer verticesBuffer = _solidCubeVerticies[size]; + gpu::BufferPointer verticesBuffer = _solidCubeVertices[size]; gpu::BufferPointer colorBuffer = _solidCubeColors[colorKey]; const int VERTICES_SLOT = 0; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 9b21eab2d6..b438eb2d3b 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -270,7 +270,7 @@ private: QHash _cubeColors; gpu::BufferPointer _wireCubeIndexBuffer; - QHash _solidCubeVerticies; + QHash _solidCubeVertices; QHash _solidCubeColors; gpu::BufferPointer _solidCubeIndexBuffer; From 18d683bcedfcce37a69e5fb3dca123b1d73f2135 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 25 May 2015 12:22:38 -0700 Subject: [PATCH 07/38] Particle entities should avoid resetting simulation when setMaxParticles is called. This caused all particles to disappear when a network packet was received. --- .../entities/src/ParticleEffectEntityItem.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 0879e49f03..8a9212b6f2 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -490,19 +490,21 @@ void ParticleEffectEntityItem::stepSimulation(float deltaTime) { } void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) { - _maxParticles = maxParticles; + if (_maxParticles != maxParticles) { + _maxParticles = maxParticles; - // TODO: try to do something smart here and preserve the state of existing particles. + // TODO: try to do something smart here and preserve the state of existing particles. - // resize vectors - _particleLifetimes.resize(_maxParticles); - _particlePositions.resize(_maxParticles); - _particleVelocities.resize(_maxParticles); + // resize vectors + _particleLifetimes.resize(_maxParticles); + _particlePositions.resize(_maxParticles); + _particleVelocities.resize(_maxParticles); - // effectivly clear all particles and start emitting new ones from scratch. - _particleHeadIndex = 0; - _particleTailIndex = 0; - _timeUntilNextEmit = 0.0f; + // effectivly clear all particles and start emitting new ones from scratch. + _particleHeadIndex = 0; + _particleTailIndex = 0; + _timeUntilNextEmit = 0.0f; + } } // because particles are in a ring buffer, this isn't trivial From cbf4e09a102065f017cbf8e2bb23631b299dfacc Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 16:10:50 -0700 Subject: [PATCH 08/38] Remove global collision event. --- interface/src/Application.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8e23dd8f38..79ec3b79a9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2155,9 +2155,6 @@ void Application::init() { auto entityScriptingInterface = DependencyManager::get(); - connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, - entityScriptingInterface.data(), &EntityScriptingInterface::entityCollisionWithEntity); - // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts connect(&_entitySimulation, &EntitySimulation::entityCollisionWithEntity, &_entities, &EntityTreeRenderer::entityCollisionWithEntity); From 706c837886481bd5d1cb1689bd51096f2e5ed04e Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:12:36 -0700 Subject: [PATCH 09/38] Use collisionSoundURL for billiards.js, instead of the global collision event. --- examples/example/games/billiards.js | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/examples/example/games/billiards.js b/examples/example/games/billiards.js index 5e08322c77..25ff5e7eae 100644 --- a/examples/example/games/billiards.js +++ b/examples/example/games/billiards.js @@ -33,8 +33,8 @@ var cuePosition; var startStroke = 0; // Sounds to use -hitSounds = []; -hitSounds.push(SoundCache.getSound(HIFI_PUBLIC_BUCKET + "Collisions-ballhitsandcatches/billiards/collision1.wav")); +var hitSound = HIFI_PUBLIC_BUCKET + "sounds/Collisions-ballhitsandcatches/billiards/collision1.wav"; +SoundCache.getSound(hitSound); HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var screenSize = Controller.getViewportDimensions(); @@ -127,6 +127,7 @@ function makeBalls(pos) { ignoreCollisions: false, damping: 0.50, shapeType: "sphere", + collisionSoundURL: hitSound, collisionsWillMove: true })); ballPosition.z += (BALL_SIZE + BALL_GAP) * SCALE; ballNumber++; @@ -225,26 +226,11 @@ function update(deltaTime) { } } -function entityCollisionWithEntity(entity1, entity2, collision) { - /* - NOT WORKING YET - if ((entity1.id == cueBall.id) || (entity2.id == cueBall.id)) { - print("Cue ball collision!"); - //audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - //Audio.playSound(hitSounds[0], { position: Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())) }); - } - - else if (isObjectBall(entity1.id) || isObjectBall(entity2.id)) { - print("Object ball collision"); - } */ -} - tableCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(4.0, Quat.getFront(Camera.getOrientation()))); makeTable(tableCenter); makeBalls(tableCenter); -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); Script.scriptEnding.connect(cleanup); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); From 09e9d353ead27aea0bbde5db0434738d9d876a67 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:15:55 -0700 Subject: [PATCH 10/38] Deprecate globalCollisionsExample.js, and add entityCollisionExample.js --- examples/example/entityCollisionExample.js | 53 +++++++++++++++++++++ examples/example/globalCollisionsExample.js | 17 ++----- 2 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 examples/example/entityCollisionExample.js diff --git a/examples/example/entityCollisionExample.js b/examples/example/entityCollisionExample.js new file mode 100644 index 0000000000..dcbefe1378 --- /dev/null +++ b/examples/example/entityCollisionExample.js @@ -0,0 +1,53 @@ +// +// globalCollisionsExample.js +// examples +// +// Created by Brad Hefta-Gaub on 1/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that demonstrates use of the Controller class +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function someCollisionFunction(entityA, entityB, collision) { + print("collision: " + JSON.stringify({a: entityA, b: entityB, c: collision})); +} + +var position = Vec3.sum(MyAvatar.position, Quat.getFront(MyAvatar.orientation)); +var properties = { + type: "Box", + position: position, + collisionsWillMove: true, + color: { red: 200, green: 0, blue: 0 } +}; +var collider = Entities.addEntity(properties); +var armed = false; +function togglePrinting() { + print('togglePrinting from ' + armed + ' on ' + collider); + if (armed) { + Script.removeEventHandler(collider, "collisionWithEntity", someCollisionFunction); + } else { + Script.addEventHandler(collider, "collisionWithEntity", someCollisionFunction); + } + armed = !armed; + print("Red box " + (armed ? "will" : "will not") + " print on collision."); +} +togglePrinting(); + +properties.position.y += 0.2; +properties.color.blue += 200; +// A handy target for the collider to hit. +var target = Entities.addEntity(properties); + +properties.position.y += 0.2; +properties.color.green += 200; +var button = Entities.addEntity(properties); +Script.addEventHandler(button, "clickReleaseOnEntity", togglePrinting); + +Script.scriptEnding.connect(function () { + Entities.deleteEntity(collider); + Entities.deleteEntity(target); + Entities.deleteEntity(button); +}); diff --git a/examples/example/globalCollisionsExample.js b/examples/example/globalCollisionsExample.js index 5813cb2472..99fd72696d 100644 --- a/examples/example/globalCollisionsExample.js +++ b/examples/example/globalCollisionsExample.js @@ -12,17 +12,6 @@ // -print("hello..."); - - -function entityCollisionWithEntity(entityA, entityB, collision) { - print("entityCollisionWithParticle().."); - print(" entityA.getID()=" + entityA.id); - print(" entityB.getID()=" + entityB.id); - Vec3.print('penetration=', collision.penetration); - Vec3.print('contactPoint=', collision.contactPoint); -} - -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); - -print("here... hello..."); +print("The is obsolete. Please instead use:"); +print(" the collisionSoundURL property on entities, or"); +print(" entityCollisionExample.js"); From 590ad22d89135ff75a4102126c2f97971acab16b Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:16:35 -0700 Subject: [PATCH 11/38] Update spaceInvaders to use per-entity collision handlers. Also fix bitrot: * The 'f' key triggers a profile, after which all bets are off regarding time-dependent behavior such as lifetime and update(). (I'm moving space-invaders "fire" to the space bar as a work-around.) * You don't get a collision without a non-default shapeType property (if the object type is "Model") a truthy collisionsWillMove property * Entity handles (such as returned by Entity.addEntity) no longer have a separate 'id' property, so don't reference that. Just use the handle object itself. * When an entity's lifetime expires, Entities.getEntityProperties(theEntityHandle) returns default values. It looks like the space-invaders script was written at a time when this returned falsey. (I'm changing the script to see if the properties.type === 'Unknown'.) NOT FIXED is that the level of detail is cutting out when showing all the invaders. --- .../example/games/spaceInvadersExample.js | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/examples/example/games/spaceInvadersExample.js b/examples/example/games/spaceInvadersExample.js index 819cf6b774..08ad56c04d 100644 --- a/examples/example/games/spaceInvadersExample.js +++ b/examples/example/games/spaceInvadersExample.js @@ -21,6 +21,16 @@ var gameOver = false; var invaderStepsPerCycle = 120; // the number of update steps it takes then invaders to move one column to the right var invaderStepOfCycle = 0; // current iteration in the cycle var invaderMoveDirection = 1; // 1 for moving to right, -1 for moving to left +var LEFT = ","; +var RIGHT = "."; +var FIRE = "SPACE"; +var QUIT = "q"; + +print("Use:"); +print(LEFT + " to move left"); +print(RIGHT + " to move right"); +print(FIRE + " to fire"); +print(QUIT + " to quit"); // game length... var itemLifetimes = 60; // 1 minute @@ -65,8 +75,8 @@ var myShipProperties; // create the rows of space invaders var invaders = new Array(); -var numberOfRows = 5; -var invadersPerRow = 8; +var numberOfRows = 3 // FIXME 5; +var invadersPerRow = 3 // FIXME 8; var emptyColumns = 2; // number of invader width columns not filled with invaders var invadersBottomCorner = { x: gameAt.x, y: middleY , z: gameAt.z }; var rowHeight = ((gameAt.y + gameSize.y) - invadersBottomCorner.y) / numberOfRows; @@ -80,7 +90,6 @@ var stepsToGround = (middleY - gameAt.y) / yPerStep; var maxInvaderRowOffset=stepsToGround; // missile related items -var missileFired = false; var myMissile; // sounds @@ -174,6 +183,7 @@ function initializeInvaders() { invaderPosition = getInvaderPosition(row, column); invaders[row][column] = Entities.addEntity({ type: "Model", + shapeType: "box", position: invaderPosition, velocity: { x: 0, y: 0, z: 0 }, gravity: { x: 0, y: 0, z: 0 }, @@ -181,6 +191,7 @@ function initializeInvaders() { dimensions: { x: invaderSize * 2, y: invaderSize * 2, z: invaderSize * 2 }, color: { red: 255, green: 0, blue: 0 }, modelURL: invaderModels[row].modelURL, + collisionsWillMove: true, lifetime: itemLifetimes }); } @@ -264,17 +275,17 @@ Script.update.connect(update); function cleanupGame() { print("cleaning up game..."); Entities.deleteEntity(myShip); - print("cleanupGame() ... Entities.deleteEntity(myShip)... myShip.id="+myShip.id); + print("cleanupGame() ... Entities.deleteEntity(myShip)... myShip="+myShip); for (var row = 0; row < numberOfRows; row++) { for (var column = 0; column < invadersPerRow; column++) { Entities.deleteEntity(invaders[row][column]); - print("cleanupGame() ... Entities.deleteEntity(invaders[row][column])... invaders[row][column].id=" - +invaders[row][column].id); + print("cleanupGame() ... Entities.deleteEntity(invaders[row][column])... invaders[row][column]=" + +invaders[row][column]); } } // clean up our missile - if (missileFired) { + if (myMissile) { Entities.deleteEntity(myMissile); } @@ -293,15 +304,23 @@ function moveShipTo(position) { Entities.editEntity(myShip, { position: position }); } +function entityCollisionWithEntity(entityA, entityB, collision) { + print("entityCollisionWithEntity() a="+entityA + " b=" + entityB); + Vec3.print('entityCollisionWithEntity() penetration=', collision.penetration); + Vec3.print('entityCollisionWithEntity() contactPoint=', collision.contactPoint); + + deleteIfInvader(entityB); +} + function fireMissile() { // we only allow one missile at a time... var canFire = false; // If we've fired a missile, then check to see if it's still alive - if (missileFired) { + if (myMissile) { var missileProperties = Entities.getEntityProperties(myMissile); - if (!missileProperties) { + if (!missileProperties || (missileProperties.type === 'Unknown')) { print("canFire = true"); canFire = true; } @@ -322,11 +341,12 @@ function fireMissile() { velocity: { x: 0, y: 5, z: 0}, gravity: { x: 0, y: 0, z: 0 }, damping: 0, - dimensions: { x: missileSize * 2, y: missileSize * 2, z: missileSize * 2 }, + collisionsWillMove: true, + dimensions: { x: missileSize, y: missileSize, z: missileSize }, color: { red: 0, green: 0, blue: 255 }, lifetime: 5 }); - + Script.addEventHandler(myMissile, "collisionWithEntity", entityCollisionWithEntity); var options = {} if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, @@ -335,30 +355,30 @@ function fireMissile() { } else { options.position = missilePosition; } - + Audio.playSound(shootSound, options); - missileFired = true; } } function keyPressEvent(key) { //print("keyPressEvent key.text="+key.text); - if (key.text == ",") { + + if (key.text == LEFT) { myShipProperties.position.x -= 0.1; if (myShipProperties.position.x < gameAt.x) { myShipProperties.position.x = gameAt.x; } moveShipTo(myShipProperties.position); - } else if (key.text == ".") { + } else if (key.text == RIGHT) { myShipProperties.position.x += 0.1; if (myShipProperties.position.x > gameAt.x + gameSize.x) { myShipProperties.position.x = gameAt.x + gameSize.x; } moveShipTo(myShipProperties.position); - } else if (key.text == "f") { + } else if (key.text == FIRE) { fireMissile(); - } else if (key.text == "q") { + } else if (key.text == QUIT) { endGame(); } } @@ -370,7 +390,7 @@ Controller.captureKeyEvents({text: " "}); function deleteIfInvader(possibleInvaderEntity) { for (var row = 0; row < numberOfRows; row++) { for (var column = 0; column < invadersPerRow; column++) { - if (invaders[row][column].id && invaders[row][column].id == possibleInvaderEntity.id) { + if (invaders[row][column] == possibleInvaderEntity) { Entities.deleteEntity(possibleInvaderEntity); Entities.deleteEntity(myMissile); @@ -390,20 +410,6 @@ function deleteIfInvader(possibleInvaderEntity) { } } -function entityCollisionWithEntity(entityA, entityB, collision) { - print("entityCollisionWithEntity() a.id="+entityA.id + " b.id=" + entityB.id); - Vec3.print('entityCollisionWithEntity() penetration=', collision.penetration); - Vec3.print('entityCollisionWithEntity() contactPoint=', collision.contactPoint); - if (missileFired) { - if (myMissile.id == entityA.id) { - deleteIfInvader(entityB); - } else if (myMissile.id == entityB.id) { - deleteIfInvader(entityA); - } - } -} -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); - // initialize the game... initializeMyShip(); From 050829e442dbdd84776df090b9c1d415f7b338f4 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:23:22 -0700 Subject: [PATCH 12/38] typo --- examples/example/globalCollisionsExample.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example/globalCollisionsExample.js b/examples/example/globalCollisionsExample.js index 99fd72696d..624ad43219 100644 --- a/examples/example/globalCollisionsExample.js +++ b/examples/example/globalCollisionsExample.js @@ -12,6 +12,6 @@ // -print("The is obsolete. Please instead use:"); +print("The global collision event is obsolete. Please instead use:"); print(" the collisionSoundURL property on entities, or"); print(" entityCollisionExample.js"); From d214f128cd5061c4c70a26b57b89f50501faaa09 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 25 May 2015 17:39:15 -0700 Subject: [PATCH 13/38] Miniumum change for gun to use per-entity collisions. I have *not* updated this script for other recent changes (e.g., to entityIDs). --- examples/controllers/hydra/gun.js | 43 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/examples/controllers/hydra/gun.js b/examples/controllers/hydra/gun.js index 7dd2b5974f..146f9daca3 100644 --- a/examples/controllers/hydra/gun.js +++ b/examples/controllers/hydra/gun.js @@ -153,6 +153,26 @@ if (showScore) { var BULLET_VELOCITY = 10.0; +function entityCollisionWithEntity(entity1, entity2, collision) { + if (entity2 === targetID) { + score++; + if (showScore) { + Overlays.editOverlay(text, { text: "Score: " + score } ); + } + + // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! + Script.setTimeout(deleteBulletAndTarget, 500); + + // Turn the target and the bullet white + Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); + Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); + + // play the sound near the camera so the shooter can hear it + audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + Audio.playSound(targetHitSound, audioOptions); + } +} + function shootBullet(position, velocity, grenade) { var BULLET_SIZE = 0.10; var BULLET_LIFETIME = 10.0; @@ -178,6 +198,7 @@ function shootBullet(position, velocity, grenade) { ignoreCollisions: false, collisionsWillMove: true }); + Script.addEventHandler(bulletID, "collisionWithEntity", entityCollisionWithEntity); // Play firing sounds audioOptions.position = position; @@ -310,27 +331,6 @@ function makePlatform(gravity, scale, size) { } -function entityCollisionWithEntity(entity1, entity2, collision) { - if (((entity1.id == bulletID.id) || (entity1.id == targetID.id)) && - ((entity2.id == bulletID.id) || (entity2.id == targetID.id))) { - score++; - if (showScore) { - Overlays.editOverlay(text, { text: "Score: " + score } ); - } - - // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! - Script.setTimeout(deleteBulletAndTarget, 500); - - // Turn the target and the bullet white - Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); - Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); - - // play the sound near the camera so the shooter can hear it - audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); - Audio.playSound(targetHitSound, audioOptions); - } -} - function keyPressEvent(event) { // if our tools are off, then don't do anything if (event.text == "t") { @@ -505,7 +505,6 @@ function scriptEnding() { clearPose(); } -Entities.entityCollisionWithEntity.connect(entityCollisionWithEntity); Script.scriptEnding.connect(scriptEnding); Script.update.connect(update); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); From 88d42f931edf1321eb98f2148f221d32fab95727 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 18:12:20 +0200 Subject: [PATCH 14/38] Remove unused Headers --- libraries/render-utils/src/RenderUtil.h | 2 -- tests/render-utils/src/main.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/libraries/render-utils/src/RenderUtil.h b/libraries/render-utils/src/RenderUtil.h index cc823dc177..8c1b1e12e7 100644 --- a/libraries/render-utils/src/RenderUtil.h +++ b/libraries/render-utils/src/RenderUtil.h @@ -12,8 +12,6 @@ #ifndef hifi_RenderUtil_h #define hifi_RenderUtil_h -#include - /// Renders a quad from (-1, -1, 0) to (1, 1, 0) with texture coordinates from (sMin, tMin) to (sMax, tMax). void renderFullscreenQuad(float sMin = 0.0f, float sMax = 1.0f, float tMin = 0.0f, float tMax = 1.0f); diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 0ba7416b28..87338e414b 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -9,7 +9,6 @@ // #include "TextRenderer.h" -#include "MatrixStack.h" #include #include From bcee01b3a31aa49966ed7ee09fc503a84316b9f6 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 18:13:23 +0200 Subject: [PATCH 15/38] First pass at moving TextureRender to use a batch --- libraries/render-utils/src/TextRenderer.cpp | 552 ++++++++------------ libraries/render-utils/src/TextRenderer.h | 21 +- libraries/render-utils/src/sdf_text.slf | 61 ++- libraries/render-utils/src/sdf_text.slv | 14 +- 4 files changed, 276 insertions(+), 372 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index 87cf9b2728..fe4c1e4d74 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -11,6 +11,9 @@ #include +#include +#include + #include #include #include @@ -18,18 +21,9 @@ #include #include -// FIXME, decouple from the GL headers -#include -#include -#include -#include - #include #include -#include "gpu/GLBackend.h" -#include "gpu/Stream.h" - #include "GLMHelpers.h" #include "MatrixStack.h" #include "RenderUtilsLogging.h" @@ -38,22 +32,47 @@ #include "sdf_text_vert.h" #include "sdf_text_frag.h" +const float DEFAULT_POINT_SIZE = 12; + // Helper functions for reading binary data from an IO device template -void readStream(QIODevice & in, T & t) { +void readStream(QIODevice& in, T& t) { in.read((char*) &t, sizeof(t)); } template -void readStream(QIODevice & in, T (&t)[N]) { +void readStream(QIODevice& in, T (&t)[N]) { in.read((char*) t, N); } template -void fillBuffer(QBuffer & buffer, T (&t)[N]) { +void fillBuffer(QBuffer& buffer, T (&t)[N]) { buffer.setData((const char*) t, N); } +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.topLeft(), tr.bottomLeft()); + vertices[3] = TextureVertex(r.topRight(), tr.bottomRight()); + } +}; + // 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 @@ -68,13 +87,13 @@ struct Glyph { float d; // xadvance - adjusts character positioning size_t indexOffset; - QRectF bounds() const; - QRectF textureBounds(const glm::vec2 & textureSize) const; + QRectF bounds() const { return glmToRect(offset, size); } + QRectF textureBounds() const { return glmToRect(texOffset, texSize); } - void read(QIODevice & in); + void read(QIODevice& in); }; -void Glyph::read(QIODevice & in) { +void Glyph::read(QIODevice& in) { uint16_t charcode; readStream(in, charcode); c = charcode; @@ -85,64 +104,52 @@ void Glyph::read(QIODevice & in) { texSize = size; } -const float DEFAULT_POINT_SIZE = 12; - class Font { public: - Font(); - 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 _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, + glm::vec2 computeExtent(const QString& str) const; + + // Render string to batch + void drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, TextRenderer::EffectType effectType, const glm::vec2& bound); private: - QStringList tokenizeForWrapping(const QString & str) const; - - bool _initialized; + QStringList tokenizeForWrapping(const QString& str) const; + QStringList splitLines(const QString& str) const; + glm::vec2 computeTokenExtent(const QString& str) const; + + const Glyph& getGlyph(const QChar& c) const; + + // 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 _glyphs; + + // Font characteristics + QString _family; + float _fontSize = 0.0f; + float _rowHeight = 0.0f; + float _leading = 0.0f; + float _ascent = 0.0f; + float _descent = 0.0f; + float _spaceWidth = 0.0f; + + // gpu structures + gpu::PipelinePointer _pipeline; + gpu::TexturePointer _texture; + gpu::Stream::FormatPointer _format; + gpu::BufferView _vertices; + gpu::BufferView _texCoords; + + // last string render characteristics + QString _lastStringRendered; + glm::vec2 _lastBounds; }; static QHash LOADED_FONTS; @@ -189,7 +196,7 @@ Font* loadFont(const QString& family) { return LOADED_FONTS[family]; } -Font::Font() : _initialized(false) { +Font::Font() { static bool fontResourceInitComplete = false; if (!fontResourceInitComplete) { Q_INIT_RESOURCE(fonts); @@ -198,13 +205,50 @@ Font::Font() : _initialized(false) { } // NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member -const Glyph & Font::getGlyph(const QChar & c) const { +const Glyph& Font::getGlyph(const QChar& c) const { if (!_glyphs.contains(c)) { return _glyphs[QChar('?')]; } return _glyphs[c]; } +QStringList Font::splitLines(const QString& str) const { + return str.split('\n'); +} + +QStringList Font::tokenizeForWrapping(const QString& str) const { + QStringList tokens; + for(auto line : splitLines(str)) { + if (!tokens.empty()) { + tokens << QString('\n'); + } + tokens << line.split(' '); + } + return tokens; +} + +glm::vec2 Font::computeTokenExtent(const QString& token) const { + glm::vec2 advance(0, _fontSize); + foreach(QChar c, token) { + Q_ASSERT(c != '\n'); + advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d; + } + return advance; +} + +glm::vec2 Font::computeExtent(const QString& str) const { + glm::vec2 extent = glm::vec2(0.0f, 0.0f); + + QStringList tokens = splitLines(str); + foreach(const QString& token, tokens) { + glm::vec2 tokenExtent = computeTokenExtent(token); + extent.x = std::max(tokenExtent.x, extent.x); + } + extent.y = tokens.count() * _rowHeight; + + return extent; +} + void Font::read(QIODevice& in) { uint8_t header[4]; readStream(in, header); @@ -232,7 +276,7 @@ void Font::read(QIODevice& in) { readStream(in, _descent); readStream(in, _spaceWidth); _fontSize = _ascent + _descent; - _rowHeight = _fontSize + _descent; + _rowHeight = _fontSize + _leading; // Read character count uint16_t count; @@ -240,266 +284,127 @@ void Font::read(QIODevice& in) { // read metrics data for each character QVector glyphs(count); // std::for_each instead of Qt foreach because we need non-const references - std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) { + std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph& g) { g.read(in); }); // read image data - if (!_image.loadFromData(in.readAll(), "PNG")) { + QImage image; + image.loadFromData(in.readAll(), "PNG"); + if (!image.isNull()) { qFatal("Failed to read SDFF image"); } + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + if (image.hasAlphaChannel()) { + formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); + } + _texture = gpu::TexturePointer( + gpu::Texture::create2D(formatGPU, image.width(), image.height(), + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + _texture->autoGenerateMips(-1); _glyphs.clear(); + glm::vec2 imageSize = toGlm(image.size()); 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; }; + + // Setup render pipeline + auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); + auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("Outline", 0)); + slotBindings.insert(gpu::Shader::Binding("Offset", 1)); + slotBindings.insert(gpu::Shader::Binding("Color", 2)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } -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() { - if (_initialized) { - return; - } - _initialized = true; - - _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 vertexData; - std::vector 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, 2, 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(" ")) { - bool lineFeed = false; - if (token1.isEmpty()) { - result << token1; - continue; - } - foreach(const QString & token2, token1.split("\n")) { - if (lineFeed) { - result << "\n"; +void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, + TextRenderer::EffectType effectType, const glm::vec2& bounds) { + // Top left of text + glm::vec2 advance = glm::vec2(0.0f, 0.0f); + + if (str != _lastStringRendered || bounds != _lastBounds) { + gpu::BufferPointer vertices = gpu::BufferPointer(new gpu::Buffer()); + foreach(const QString& token, tokenizeForWrapping(str)) { + bool isNewLine = token == QString('\n'); + bool forceNewLine = false; + + // Handle wrapping + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x)) { + // We are out of the x bound, force new line + forceNewLine = true; } - 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) { - - setupGL(); - - // 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 && std::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 && std::abs(advance.y) > bounds.y) { - break; + if (isNewLine || forceNewLine) { + // Character return, move the advance to a new line + advance = glm::vec2(0.0f, advance.y + _rowHeight); + if (isNewLine) { + // No need to draw anything, go directly to next token + continue; } } + if ((bounds.y != -1) && (advance.y + _fontSize > bounds.y)) { + // We are out of the y bound, stop drawing + break; + } + + // Draw the token + if (!isNewLine) { + for (auto c : token) { + auto glyph = _glyphs[c]; + + // Build translated quad and add it to the buffer + QuadBuilder qd(glyph.bounds().translated(advance.x, advance.y), + glyph.textureBounds()); + vertices->append(sizeof(QuadBuilder), (const gpu::Byte*)qd.vertices); + + // Advance by glyph size + advance.x += glyph.d; + } + + // Add space after all non return tokens + advance.x += _spaceWidth; + } } - - 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; + + // Setup rendering structures + static const int STRIDES = sizeof(TextureVertex); + static const int OFFSET = offsetof(TextureVertex, tex); + _format = gpu::Stream::FormatPointer(new gpu::Stream::Format()); + _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, + gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + _format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, + gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + + _vertices = gpu::BufferView(vertices, 0, vertices->getSize(), STRIDES, + _format->getAttributes().at(gpu::Stream::POSITION)._element); + _texCoords = gpu::BufferView(vertices, OFFSET, vertices->getSize(), STRIDES, + _format->getAttributes().at(gpu::Stream::TEXCOORD)._element); + _lastStringRendered = str; + _lastBounds = bounds; } - - _vao->release(); - _texture->release(); // TODO: Brad & Sam, let's discuss this. Without this non-textured quads get their colors borked. - _program->release(); - // FIXME, needed? - // glDisable(GL_TEXTURE_2D); - - return advance; + batch.setInputFormat(_format); + batch.setInputBuffer(0, _vertices); + batch.setInputBuffer(1, _texCoords); + batch.setUniformTexture(2, _texture); + batch._glUniform1f(0, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); + batch._glUniform2f(1, x, y); + batch._glUniform4fv(2, 4, (const GLfloat*)&color); + batch.draw(gpu::QUADS, _vertices.getNumElements()); } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -531,7 +436,7 @@ TextRenderer::TextRenderer(const char* family, float pointSize, int weight, TextRenderer::~TextRenderer() { } -glm::vec2 TextRenderer::computeExtent(const QString & str) const { +glm::vec2 TextRenderer::computeExtent(const QString& str) const { float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; if (_font) { return _font->computeExtent(str) * scale; @@ -539,32 +444,25 @@ glm::vec2 TextRenderer::computeExtent(const QString & str) const { return glm::vec2(0.1f,0.1f); } -float TextRenderer::draw(float x, float y, const QString & str, - const glm::vec4& color, const glm::vec2 & bounds) { +void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { + gpu::Batch batch; + draw(batch, x, y, str, color, bounds); + gpu::GLBackend::renderBatch(batch); +} + +void TextRenderer::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, + const glm::vec2& bounds) { + glm::vec4 actualColor(color); if (actualColor.r < 0) { actualColor = toGlm(_color); } - + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - glm::vec2 result; - - MatrixStack::withPushAll([&] { - MatrixStack & mv = MatrixStack::modelview(); - MatrixStack & pr = MatrixStack::projection(); - gpu::GLBackend::fetchMatrix(GL_MODELVIEW_MATRIX, mv.top()); - gpu::GLBackend::fetchMatrix(GL_PROJECTION_MATRIX, pr.top()); - - // 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 - if (_font) { - result = _font->drawString(x, y, str, actualColor, _effectType, bounds / scale); - } - }); - return result.x; + + // The font does all the OpenGL work + if (_font) { + _font->drawString(batch, x, y, str, actualColor, _effectType, bounds / scale); + } } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 85bb18cec9..6dbe3cbc33 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -25,9 +25,6 @@ #include #include -// a special "character" that renders as a solid block -const char SOLID_BLOCK_CHAR = 127; - // the standard sans serif font family #define SANS_FONT_FAMILY "Helvetica" @@ -46,6 +43,9 @@ const char SOLID_BLOCK_CHAR = 127; #define INCONSOLATA_FONT_WEIGHT QFont::Bold #endif +namespace gpu { +class Batch; +} class Font; // TextRenderer is actually a fairly thin wrapper around a Font class @@ -59,13 +59,12 @@ public: ~TextRenderer(); - glm::vec2 computeExtent(const QString & str) const; - - float draw( - float x, float y, - const QString & str, - const glm::vec4& color = glm::vec4(-1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); + glm::vec2 computeExtent(const QString& str) const; + + void draw(float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), + const glm::vec2& bounds = glm::vec2(-1.0f)); + void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), + const glm::vec2& bounds = glm::vec2(-1.0f)); private: TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false, @@ -82,7 +81,7 @@ private: // text color const QColor _color; - Font * _font; + Font* _font; }; diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf index 1affbe4c57..0cf6e3fb1b 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -10,11 +10,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 sampler2D Font; -uniform vec4 Color; -uniform bool Outline; +<@include DeferredBufferWrite.slh@> -varying vec2 vTexCoord; +uniform sampler2D Font; +uniform float Outline; + +// the interpolated normal +varying vec4 interpolatedNormal; const float gamma = 2.6; const float smoothing = 100.0; @@ -22,28 +24,33 @@ 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; + // retrieve signed distance + float sdf = texture2D(Font, vTexCoord).r; + if (Outline == 1.0f) { + 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); + // 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 + packDeferredFragment( + normalize(interpolatedNormal.xyz), + a, + gl_Color.rgb, + gl_FrontMaterial.specular.rgb, + gl_FrontMaterial.shininess); } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv index 27db1c4985..157efd7f4b 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -9,16 +9,16 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/Transform.slh@> -uniform mat4 Projection; -uniform mat4 ModelView; +<$declareStandardTransform()$> -attribute vec2 Position; -attribute vec2 TexCoord; +uniform float Offset[2]; -varying vec2 vTexCoord; void main() { - vTexCoord = TexCoord; - gl_Position = Projection * ModelView * vec4(Position, 0.0, 1.0); + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex + vec4(Offset[0], Offset[1], 0.0f, 0.0f), gl_Position)$> } \ No newline at end of file From 62bb1a49e416b17fbcab0c12c01ee83fdd440c78 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 18:18:50 +0200 Subject: [PATCH 16/38] Fix inverted glyphs load check --- libraries/render-utils/src/TextRenderer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index fe4c1e4d74..abb65a20d9 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -290,8 +290,7 @@ void Font::read(QIODevice& in) { // read image data QImage image; - image.loadFromData(in.readAll(), "PNG"); - if (!image.isNull()) { + if (!image.loadFromData(in.readAll(), "PNG")) { qFatal("Failed to read SDFF image"); } gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); From 5d19431d265a4a250849dafd0cb935c4e41982d7 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 20:20:39 +0200 Subject: [PATCH 17/38] Copy/paste error with useClientState --- libraries/gpu/src/gpu/GLBackendInput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu/src/gpu/GLBackendInput.cpp index 4af11f4566..bef3289161 100755 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu/src/gpu/GLBackendInput.cpp @@ -125,7 +125,7 @@ void GLBackend::updateInput() { GLuint stride = strides[bufferNum]; GLuint pointer = attrib._offset + offsets[bufferNum]; #if defined(SUPPORT_LEGACY_OPENGL) - const bool useClientState = i < NUM_CLASSIC_ATTRIBS; + const bool useClientState = slot < NUM_CLASSIC_ATTRIBS; #else const bool useClientState = false; #endif From c9022212e8a404d5681ed5848262a3578f6363a0 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 20:34:23 +0200 Subject: [PATCH 18/38] More work on the text renderer --- .../src/DeferredLightingEffect.cpp | 4 +- libraries/render-utils/src/TextRenderer.cpp | 113 +++++++++--------- libraries/render-utils/src/TextRenderer.h | 10 -- libraries/render-utils/src/sdf_text.slf | 17 +-- libraries/render-utils/src/sdf_text.slv | 8 +- 5 files changed, 66 insertions(+), 86 deletions(-) diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 8a9ee4bf6d..00c9a13387 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -62,8 +62,8 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); _simpleProgram = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); _viewState = viewState; diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index abb65a20d9..be6a847031 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -9,25 +9,19 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "TextRenderer.h" #include #include -#include -#include -#include -#include -#include #include +#include +#include #include -#include -#include - #include "GLMHelpers.h" #include "MatrixStack.h" #include "RenderUtilsLogging.h" -#include "TextRenderer.h" #include "sdf_text_vert.h" #include "sdf_text_frag.h" @@ -53,14 +47,9 @@ void fillBuffer(QBuffer& buffer, T (&t)[N]) { 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()) { - } + 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 { @@ -141,11 +130,12 @@ private: float _spaceWidth = 0.0f; // gpu structures + static const unsigned int VERTEX_BUFFER_CHANNEL = 0; gpu::PipelinePointer _pipeline; gpu::TexturePointer _texture; gpu::Stream::FormatPointer _format; - gpu::BufferView _vertices; - gpu::BufferView _texCoords; + gpu::BufferPointer _vertices; + unsigned int _numVertices = 0; // last string render characteristics QString _lastStringRendered; @@ -327,8 +317,18 @@ void Font::read(QIODevice& in) { gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + + // Setup rendering structures + static const int OFFSET = offsetof(TextureVertex, tex); + _format = gpu::Stream::FormatPointer(new gpu::Stream::Format()); + _format->setAttribute(gpu::Stream::POSITION, VERTEX_BUFFER_CHANNEL, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), 0); + _format->setAttribute(gpu::Stream::TEXCOORD, VERTEX_BUFFER_CHANNEL, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); } void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, @@ -337,7 +337,11 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c glm::vec2 advance = glm::vec2(0.0f, 0.0f); if (str != _lastStringRendered || bounds != _lastBounds) { - gpu::BufferPointer vertices = gpu::BufferPointer(new gpu::Buffer()); + _vertices = gpu::BufferPointer(new gpu::Buffer()); + _numVertices = 0; + _lastStringRendered = str; + _lastBounds = bounds; + foreach(const QString& token, tokenizeForWrapping(str)) { bool isNewLine = token == QString('\n'); bool forceNewLine = false; @@ -368,7 +372,8 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c // Build translated quad and add it to the buffer QuadBuilder qd(glyph.bounds().translated(advance.x, advance.y), glyph.textureBounds()); - vertices->append(sizeof(QuadBuilder), (const gpu::Byte*)qd.vertices); + _vertices->append(sizeof(qd.vertices), (const gpu::Byte*)qd.vertices); + _numVertices += 4; // Advance by glyph size advance.x += glyph.d; @@ -378,32 +383,19 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c advance.x += _spaceWidth; } } - - // Setup rendering structures - static const int STRIDES = sizeof(TextureVertex); - static const int OFFSET = offsetof(TextureVertex, tex); - _format = gpu::Stream::FormatPointer(new gpu::Stream::Format()); - _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - _format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - - _vertices = gpu::BufferView(vertices, 0, vertices->getSize(), STRIDES, - _format->getAttributes().at(gpu::Stream::POSITION)._element); - _texCoords = gpu::BufferView(vertices, OFFSET, vertices->getSize(), STRIDES, - _format->getAttributes().at(gpu::Stream::TEXCOORD)._element); - _lastStringRendered = str; - _lastBounds = bounds; } - batch.setInputFormat(_format); - batch.setInputBuffer(0, _vertices); - batch.setInputBuffer(1, _texCoords); + batch.setPipeline(_pipeline); batch.setUniformTexture(2, _texture); batch._glUniform1f(0, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); batch._glUniform2f(1, x, y); batch._glUniform4fv(2, 4, (const GLfloat*)&color); - batch.draw(gpu::QUADS, _vertices.getNumElements()); + QRectF rect(0.0f, 0.0f, 1.0f, 1.0f); + QuadBuilder qd(rect, rect); + _vertices->append(sizeof(QuadBuilder), (const gpu::Byte*)qd.vertices); + batch.setInputFormat(_format); + batch.setInputBuffer(VERTEX_BUFFER_CHANNEL, _vertices); + batch.draw(gpu::QUADS, _numVertices); } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -416,10 +408,13 @@ TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, 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)) { +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 (!_font) { qWarning() << "Unable to load font with family " << family; _font = loadFont("Courier"); @@ -436,32 +431,32 @@ TextRenderer::~TextRenderer() { } glm::vec2 TextRenderer::computeExtent(const QString& str) const { - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; if (_font) { + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; return _font->computeExtent(str) * scale; } return glm::vec2(0.1f,0.1f); } void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { - gpu::Batch batch; - draw(batch, x, y, str, color, bounds); - gpu::GLBackend::renderBatch(batch); + // The font does all the OpenGL work + if (_font) { +// gpu::Batch batch; +// draw(batch, x, y, str, color, bounds); +// gpu::GLBackend::renderBatch(batch, true); + } } void TextRenderer::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { - - glm::vec4 actualColor(color); - if (actualColor.r < 0) { - actualColor = toGlm(_color); - } - - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - // The font does all the OpenGL work if (_font) { - _font->drawString(batch, x, y, str, actualColor, _effectType, bounds / scale); + glm::vec4 actualColor(color); + if (actualColor.r < 0) { + actualColor = toGlm(_color); + } + + _font->drawString(batch, x, y, str, actualColor, _effectType, bounds); } } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 6dbe3cbc33..7bd0781f96 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -12,18 +12,8 @@ #ifndef hifi_TextRenderer_h #define hifi_TextRenderer_h -#include #include -#include #include -#include -#include -#include -#include -#include - -#include -#include // the standard sans serif font family #define SANS_FONT_FAMILY "Helvetica" diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf index 0cf6e3fb1b..ad5b8fe2c2 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -10,13 +10,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -<@include DeferredBufferWrite.slh@> - uniform sampler2D Font; uniform float Outline; - -// the interpolated normal -varying vec4 interpolatedNormal; +uniform vec4 Color; const float gamma = 2.6; const float smoothing = 100.0; @@ -25,7 +21,7 @@ const float outlineExpansion = 0.2; void main() { // retrieve signed distance - float sdf = texture2D(Font, vTexCoord).r; + float sdf = texture2D(Font, gl_TexCoord[0].xy).r; if (Outline == 1.0f) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; @@ -35,7 +31,7 @@ void main() { } // 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 s = smoothing * length(fwidth(gl_TexCoord[0])); float w = clamp( s, 0.0, 0.5); float a = smoothstep(0.5 - w, 0.5 + w, sdf); @@ -47,10 +43,5 @@ void main() { } // final color - packDeferredFragment( - normalize(interpolatedNormal.xyz), - a, - gl_Color.rgb, - gl_FrontMaterial.specular.rgb, - gl_FrontMaterial.shininess); + gl_FragColor = vec4(Color.rgb, a); } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv index 157efd7f4b..142d8d41d9 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -15,10 +15,14 @@ uniform float Offset[2]; - void main() { + gl_TexCoord[0] = gl_MultiTexCoord0; + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, gl_Vertex + vec4(Offset[0], Offset[1], 0.0f, 0.0f), gl_Position)$> + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + + gl_Position.x += Offset[0]; + gl_Position.y += Offset[1]; } \ No newline at end of file From ae73e68f7bdf0282bbf2eda322da8492efb25942 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 26 May 2015 13:27:37 -0700 Subject: [PATCH 19/38] Update header. --- examples/example/entityCollisionExample.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/example/entityCollisionExample.js b/examples/example/entityCollisionExample.js index dcbefe1378..de50d52753 100644 --- a/examples/example/entityCollisionExample.js +++ b/examples/example/entityCollisionExample.js @@ -1,11 +1,11 @@ // -// globalCollisionsExample.js +// entityCollisionExample.js // examples // -// Created by Brad Hefta-Gaub on 1/29/14. -// Copyright 2014 High Fidelity, Inc. +// Created by Howard Stearns on 5/25/15. +// Copyright 2015 High Fidelity, Inc. // -// This is an example script that demonstrates use of the Controller class +// This is an example script that demonstrates use of the per-entity event handlers. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -20,7 +20,7 @@ var properties = { type: "Box", position: position, collisionsWillMove: true, - color: { red: 200, green: 0, blue: 0 } + color: { red: 200, green: 0, blue: 0 } }; var collider = Entities.addEntity(properties); var armed = false; From 7194d4a4daf63fb494601792cfba3a24dfff82bd Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Tue, 26 May 2015 16:40:47 -0700 Subject: [PATCH 20/38] modified pointer script to allow users to draw on surfaces --- examples/pointer.js | 103 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/examples/pointer.js b/examples/pointer.js index cdfb93f2d3..ddaf84fcde 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -1,9 +1,50 @@ +//Dimensions property for lines is the offset of the position + var lineEntityID = null; var lineIsRezzed = false; var altHeld = false; var lineCreated = false; +var position, positionOffset, prevPosition; +var nearLinePosition; +var strokes = []; +var STROKE_ADJUST = 0.005; +var DISTANCE_DRAW_THRESHOLD = .03; +var drawDistance = 0; -function nearLinePoint(targetPosition) { +var userCanDraw = true; + +var BUTTON_SIZE = 32; +var PADDING = 3; + +var buttonOffColor = {red: 250, green: 10, blue: 10}; +var buttonOnColor = {red: 10, green: 200, blue: 100}; + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; +var screenSize = Controller.getViewportDimensions(); + +var drawButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/pencil.png?v2", + color: buttonOnColor, + alpha: 1 +}); + + + + +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(2.0, Quat.getFront(Camera.getOrientation()))); +center.y += 0.5; +var whiteBoard = Entities.addEntity({ + type: "Box", + position: center, + dimensions: {x: 1, y: 1, z: .001}, + color: {red: 255, green: 255, blue: 255} +}); + +function calculateNearLinePosition(targetPosition) { var handPosition = MyAvatar.getRightPalmPosition(); var along = Vec3.subtract(targetPosition, handPosition); along = Vec3.normalize(along); @@ -27,19 +68,27 @@ function createOrUpdateLine(event) { var props = Entities.getEntityProperties(intersection.entityID); if (intersection.intersects) { - var dim = Vec3.subtract(intersection.intersection, nearLinePoint(intersection.intersection)); + startPosition = intersection.intersection; + var subtractVec = Vec3.multiply(Vec3.normalize(pickRay.direction), STROKE_ADJUST); + startPosition = Vec3.subtract(startPosition, subtractVec); + nearLinePosition = calculateNearLinePosition(intersection.intersection); + positionOffset= Vec3.subtract(startPosition, nearLinePosition); if (lineIsRezzed) { Entities.editEntity(lineEntityID, { - position: nearLinePoint(intersection.intersection), - dimensions: dim, + position: nearLinePosition, + dimensions: positionOffset, lifetime: 15 + props.lifespan // renew lifetime }); + if(userCanDraw){ + draw(); + } } else { lineIsRezzed = true; + prevPosition = startPosition; lineEntityID = Entities.addEntity({ type: "Line", - position: nearLinePoint(intersection.intersection), - dimensions: dim, + position: nearLinePosition, + dimensions: positionOffset, color: { red: 255, green: 255, @@ -53,8 +102,39 @@ function createOrUpdateLine(event) { } } +function draw(){ + + //We only want to draw line if distance between starting and previous point is large enough + drawDistance = Vec3.distance(startPosition, prevPosition); + if( drawDistance < DISTANCE_DRAW_THRESHOLD){ + return; + } + + var offset = Vec3.subtract(startPosition, prevPosition); + strokes.push(Entities.addEntity({ + type: "Line", + position: prevPosition, + dimensions: offset, + color: {red: 200, green: 40, blue: 200}, + // lifetime: 20 + })); + prevPosition = startPosition; +} function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (clickedOverlay == drawButton) { + userCanDraw = !userCanDraw; + if(userCanDraw === true){ + Overlays.editOverlay(drawButton, {color: buttonOnColor}); + } else { + Overlays.editOverlay(drawButton, {color: buttonOffColor}); + } + } + if (!event.isLeftButton || altHeld) { return; } @@ -69,6 +149,7 @@ function mouseMoveEvent(event) { } + function mouseReleaseEvent(event) { if (!lineCreated) { return; @@ -91,7 +172,17 @@ function keyReleaseEvent(event) { } +function cleanup(){ + Entities.deleteEntity(whiteBoard); + for(var i =0; i < strokes.length; i++){ + Entities.deleteEntity(strokes[i]); + } + Overlays.deleteOverlay(drawButton); +} + + +Script.scriptEnding.connect(cleanup); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); From 579c62420d09e649c6963f23547dca42d742a4fd Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Tue, 26 May 2015 16:43:34 -0700 Subject: [PATCH 21/38] added header to pointer script --- examples/pointer.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/examples/pointer.js b/examples/pointer.js index ddaf84fcde..9ca20504b4 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -1,4 +1,14 @@ -//Dimensions property for lines is the offset of the position +// pointer.js +// examples +// +// Created by Eric Levin on May 26, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Provides a pointer with option to draw on surfaces +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var lineEntityID = null; var lineIsRezzed = false; From 3fb5476968c38f3bfd6a58b9d4f1dfa87152a6ad Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Tue, 26 May 2015 16:46:01 -0700 Subject: [PATCH 22/38] added blockWorld script to generate floor of tiles and drop jenga blocks at random points above floor --- examples/blockWorld.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/blockWorld.js diff --git a/examples/blockWorld.js b/examples/blockWorld.js new file mode 100644 index 0000000000..e69de29bb2 From efbb9b0238dfe5479b2a4fb49b4cdc09331aa06b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 26 May 2015 22:48:45 -0700 Subject: [PATCH 23/38] rewritten MassProperties with complete unit tests --- libraries/physics/src/MeshInfo.cpp | 150 ------ libraries/physics/src/MeshInfo.h | 33 -- libraries/physics/src/MeshMassProperties.cpp | 321 +++++++++++++ libraries/physics/src/MeshMassProperties.h | 60 +++ tests/physics/src/MeshInfoTests.cpp | 237 --------- tests/physics/src/MeshInfoTests.h | 21 - tests/physics/src/MeshMassPropertiesTests.cpp | 452 ++++++++++++++++++ tests/physics/src/MeshMassPropertiesTests.h | 22 + tests/physics/src/main.cpp | 4 +- 9 files changed, 857 insertions(+), 443 deletions(-) delete mode 100644 libraries/physics/src/MeshInfo.cpp delete mode 100644 libraries/physics/src/MeshInfo.h create mode 100644 libraries/physics/src/MeshMassProperties.cpp create mode 100644 libraries/physics/src/MeshMassProperties.h delete mode 100644 tests/physics/src/MeshInfoTests.cpp delete mode 100644 tests/physics/src/MeshInfoTests.h create mode 100644 tests/physics/src/MeshMassPropertiesTests.cpp create mode 100644 tests/physics/src/MeshMassPropertiesTests.h diff --git a/libraries/physics/src/MeshInfo.cpp b/libraries/physics/src/MeshInfo.cpp deleted file mode 100644 index 29ddc74a98..0000000000 --- a/libraries/physics/src/MeshInfo.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// -// MeshInfo.cpp -// libraries/physics/src -// -// Created by Virendra Singh 2015.02.28 -// 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 "MeshInfo.h" -#include -using namespace meshinfo; - -//class to compute volume, mass, center of mass, and inertia tensor of a mesh. -//origin is the default reference point for generating the tetrahedron from each triangle of the mesh. - -MeshInfo::MeshInfo(vector *vertices, vector *triangles) :\ - _vertices(vertices), - _centerOfMass(Vertex(0.0, 0.0, 0.0)), - _triangles(triangles) -{ - -} - -MeshInfo::~MeshInfo(){ - - _vertices = NULL; - _triangles = NULL; - -} - -inline float MeshInfo::getVolume(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const{ - glm::mat4 tet = { glm::vec4(p1.x, p2.x, p3.x, p4.x), glm::vec4(p1.y, p2.y, p3.y, p4.y), glm::vec4(p1.z, p2.z, p3.z, p4.z), - glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) }; - return glm::determinant(tet) / 6.0f; -} - -Vertex MeshInfo::getMeshCentroid() const{ - return _centerOfMass; -} - -vector MeshInfo::computeMassProperties(){ - vector volumeAndInertia = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; - Vertex origin(0.0, 0.0, 0.0); - glm::mat3 identity; - float meshVolume = 0.0f; - glm::mat3 globalInertiaTensors(0.0); - - for (unsigned int i = 0; i < _triangles->size(); i += 3){ - Vertex p1 = _vertices->at(_triangles->at(i)); - Vertex p2 = _vertices->at(_triangles->at(i + 1)); - Vertex p3 = _vertices->at(_triangles->at(i + 2)); - float volume = getVolume(p1, p2, p3, origin); - Vertex com = 0.25f * (p1 + p2 + p3); - meshVolume += volume; - _centerOfMass += com * volume; - - //centroid is used for calculating inertia tensor relative to center of mass. - // translate the tetrahedron to its center of mass using P = P - centroid - Vertex p0 = origin - com; - p1 = p1 - com; - p2 = p2 - com; - p3 = p3 - com; - - //Calculate inertia tensor based on Tonon's Formulae given in the paper mentioned below. - //http://docsdrive.com/pdfs/sciencepublications/jmssp/2005/8-11.pdf - //Explicit exact formulas for the 3-D tetrahedron inertia tensor in terms of its vertex coordinates - F.Tonon - - float i11 = (volume * 0.1f) * ( - p0.y*p0.y + p0.y*p1.y + p0.y*p2.y + p0.y*p3.y + - p1.y*p1.y + p1.y*p2.y + p1.y*p3.y + - p2.y*p2.y + p2.y*p3.y + - p3.y*p3.y + - p0.z*p0.z + p0.z*p1.z + p0.z*p2.z + p0.z*p3.z + - p1.z*p1.z + p1.z*p2.z + p1.z*p3.z + - p2.z*p2.z + p2.z*p3.z + - p3.z*p3.z); - - float i22 = (volume * 0.1f) * ( - p0.x*p0.x + p0.x*p1.x + p0.x*p2.x + p0.x*p3.x + - p1.x*p1.x + p1.x*p2.x + p1.x*p3.x + - p2.x*p2.x + p2.x*p3.x + - p3.x*p3.x + - p0.z*p0.z + p0.z*p1.z + p0.z*p2.z + p0.z*p3.z + - p1.z*p1.z + p1.z*p2.z + p1.z*p3.z + - p2.z*p2.z + p2.z*p3.z + - p3.z*p3.z); - - float i33 = (volume * 0.1f) * ( - p0.x*p0.x + p0.x*p1.x + p0.x*p2.x + p0.x*p3.x + - p1.x*p1.x + p1.x*p2.x + p1.x*p3.x + - p2.x*p2.x + p2.x*p3.x + - p3.x*p3.x + - p0.y*p0.y + p0.y*p1.y + p0.y*p2.y + p0.y*p3.y + - p1.y*p1.y + p1.y*p2.y + p1.y*p3.y + - p2.y*p2.y + p2.y*p3.y + - p3.y*p3.y); - - float i23 = -(volume * 0.05f) * (2.0f * (p0.y*p0.z + p1.y*p1.z + p2.y*p2.z + p3.y*p3.z) + - p0.y*p1.z + p0.y*p2.z + p0.y*p3.z + - p1.y*p0.z + p1.y*p2.z + p1.y*p3.z + - p2.y*p0.z + p2.y*p1.z + p2.y*p3.z + - p3.y*p0.z + p3.y*p1.z + p3.y*p2.z); - - float i21 = -(volume * 0.05f) * (2.0f * (p0.x*p0.z + p1.x*p1.z + p2.x*p2.z + p3.x*p3.z) + - p0.x*p1.z + p0.x*p2.z + p0.x*p3.z + - p1.x*p0.z + p1.x*p2.z + p1.x*p3.z + - p2.x*p0.z + p2.x*p1.z + p2.x*p3.z + - p3.x*p0.z + p3.x*p1.z + p3.x*p2.z); - - float i31 = -(volume * 0.05f) * (2.0f * (p0.x*p0.y + p1.x*p1.y + p2.x*p2.y + p3.x*p3.y) + - p0.x*p1.y + p0.x*p2.y + p0.x*p3.y + - p1.x*p0.y + p1.x*p2.y + p1.x*p3.y + - p2.x*p0.y + p2.x*p1.y + p2.x*p3.y + - p3.x*p0.y + p3.x*p1.y + p3.x*p2.y); - - //3x3 of local inertia tensors of each tetrahedron. Inertia tensors are the diagonal elements - // | I11 -I12 -I13 | - // I = | -I21 I22 -I23 | - // | -I31 -I32 I33 | - glm::mat3 localInertiaTensors = { Vertex(i11, i21, i31), Vertex(i21, i22, i23), - Vertex(i31, i23, i33) }; - - //Translate the inertia tensors from center of mass to origin - //Parallel axis theorem J = I * m[(R.R)*Identity - RxR] where x is outer cross product - globalInertiaTensors += localInertiaTensors + volume * ((glm::dot(com, com) * identity) - - glm::outerProduct(com, com)); - } - - //Translate accumulated center of mass from each tetrahedron to mesh's center of mass using parallel axis theorem - if (meshVolume == 0){ - return volumeAndInertia; - } - _centerOfMass = (_centerOfMass / meshVolume); - - //Translate the inertia tensors from origin to mesh's center of mass. - globalInertiaTensors = globalInertiaTensors - meshVolume * ((glm::dot(_centerOfMass, _centerOfMass) * - identity) - glm::outerProduct(_centerOfMass, _centerOfMass)); - - volumeAndInertia[0] = meshVolume; - volumeAndInertia[1] = globalInertiaTensors[0][0]; //i11 - volumeAndInertia[2] = globalInertiaTensors[1][1]; //i22 - volumeAndInertia[3] = globalInertiaTensors[2][2]; //i33 - volumeAndInertia[4] = -globalInertiaTensors[2][1]; //i23 or i32 - volumeAndInertia[5] = -globalInertiaTensors[1][0]; //i21 or i12 - volumeAndInertia[6] = -globalInertiaTensors[2][0]; //i13 or i31 - return volumeAndInertia; -} \ No newline at end of file diff --git a/libraries/physics/src/MeshInfo.h b/libraries/physics/src/MeshInfo.h deleted file mode 100644 index 67dbc8b0d6..0000000000 --- a/libraries/physics/src/MeshInfo.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// MeshInfo.h -// libraries/physics/src -// -// Created by Virendra Singh 2015.02.28 -// 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 -// -#ifndef hifi_MeshInfo_h -#define hifi_MeshInfo_h -#include -#include -#include -using namespace std; -namespace meshinfo{ - typedef glm::vec3 Vertex; - class MeshInfo{ - private: - inline float getVolume(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const; - vector computeVolumeAndInertia(const Vertex p1, const Vertex p2, const Vertex p3, const Vertex p4) const; - public: - vector *_vertices; - Vertex _centerOfMass; - vector *_triangles; - MeshInfo(vector *vertices, vector *triangles); - ~MeshInfo(); - Vertex getMeshCentroid() const; - vector computeMassProperties(); - }; -} -#endif // hifi_MeshInfo_h \ No newline at end of file diff --git a/libraries/physics/src/MeshMassProperties.cpp b/libraries/physics/src/MeshMassProperties.cpp new file mode 100644 index 0000000000..e5d4f4e5af --- /dev/null +++ b/libraries/physics/src/MeshMassProperties.cpp @@ -0,0 +1,321 @@ +// +// MeshMassProperties.cpp +// libraries/physics/src +// +// Created by Andrew Meadows 2015.05.25 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "MeshMassProperties.h" + +// this method is included for unit test verification +void computeBoxInertia(btScalar mass, const btVector3& diagonal, btMatrix3x3& inertia) { + // formula for box inertia tensor: + // + // | y^2 + z^2 0 0 | + // | | + // inertia = M/12 * | 0 z^2 + x^2 0 | + // | | + // | 0 0 x^2 + y^2 | + // + + mass = mass / btScalar(12.0f); + btScalar x = diagonal[0]; + x = mass * x * x; + btScalar y = diagonal[1]; + y = mass * y * y; + btScalar z = diagonal[2]; + z = mass * z * z; + inertia.setIdentity(); + inertia[0][0] = y + z; + inertia[1][1] = z + x; + inertia[2][2] = x + y; +} + +void computeTetrahedronInertia(btScalar mass, btVector3* points, btMatrix3x3& inertia) { + // Computes the inertia tensor of a tetrahedron about its center of mass. + // The tetrahedron is defined by array of four points in its center of mass frame. + // + // The analytic formulas were obtained from Tonon's paper: + // http://docsdrive.com/pdfs/sciencepublications/jmssp/2005/8-11.pdf + // http://thescipub.com/PDF/jmssp.2005.8.11.pdf + // + // The inertia tensor has the following form: + // + // | a f e | + // | | + // inertia = | f b d | + // | | + // | e d c | + + const btVector3& p0 = points[0]; + const btVector3& p1 = points[1]; + const btVector3& p2 = points[2]; + const btVector3& p3 = points[3]; + + for (uint32_t i = 0; i < 3; ++i ) { + uint32_t j = (i + 1) % 3; + uint32_t k = (j + 1) % 3; + + // compute diagonal + inertia[i][i] = mass * btScalar(0.1f) * + ( p0[j] * (p0[j] + p1[j] + p2[j] + p3[j]) + + p1[j] * (p1[j] + p2[j] + p3[j]) + + p2[j] * (p2[j] + p3[j]) + + p3[j] * p3[j] + + p0[k] * (p0[k] + p1[k] + p2[k] + p3[k]) + + p1[k] * (p1[k] + p2[k] + p3[k]) + + p2[k] * (p2[k] + p3[k]) + + p3[k] * p3[k] ); + + // compute off-diagonals + inertia[j][k] = inertia[k][j] = - mass * btScalar(0.05f) * + ( btScalar(2.0f) * ( p0[j] * p0[k] + p1[j] * p1[k] + p2[j] * p2[k] + p3[j] * p3[k] ) + + p0[j] * (p1[k] + p2[k] + p3[k]) + + p1[j] * (p0[k] + p2[k] + p3[k]) + + p2[j] * (p0[k] + p1[k] + p3[k]) + + p3[j] * (p0[k] + p1[k] + p2[k]) ); + } +} + +// helper function +void computePointInertia(const btVector3& point, btScalar mass, btMatrix3x3& inertia) { + btScalar distanceSquared = point.length2(); + if (distanceSquared > 0.0f) { + for (uint32_t i = 0; i < 3; ++i) { + btScalar pointi = point[i]; + inertia[i][i] = mass * (distanceSquared - (pointi * pointi)); + for (uint32_t j = i + 1; j < 3; ++j) { + btScalar offDiagonal = - mass * pointi * point[j]; + inertia[i][j] = offDiagonal; + inertia[j][i] = offDiagonal; + } + } + } +} + +// this method is included for unit test verification +void computeTetrahedronInertiaByBruteForce(btVector3* points, btMatrix3x3& inertia) { + // Computes the approximate inertia tensor of a tetrahedron (about frame's origin) + // by integration over the "point" masses. This is numerically expensive so it may + // take a while to complete. + + VectorOfIndices triangles = { + 0, 2, 1, + 0, 3, 2, + 0, 1, 3, + 1, 2, 3 }; + + for (int i = 0; i < 3; ++i) { + inertia[i].setZero(); + } + + // compute normals + btVector3 center = btScalar(0.25f) * (points[0] + points[1] + points[2] + points[3]); + btVector3 normals[4]; + btVector3 pointsOnPlane[4]; + for (int i = 0; i < 4; ++i) { + int t = 3 * i; + btVector3& p0 = points[triangles[t]]; + btVector3& p1 = points[triangles[t + 1]]; + btVector3& p2 = points[triangles[t + 2]]; + normals[i] = ((p1 - p0).cross(p2 - p1)).normalized(); + // make sure normal points away from center + if (normals[i].dot(p0 - center) < btScalar(0.0f)) { + normals[i] *= btScalar(-1.0f); + } + pointsOnPlane[i] = p0; + } + + // compute bounds of integration + btVector3 boxMax = points[0]; + btVector3 boxMin = points[0]; + for (int i = 1; i < 4; ++i) { + for(int j = 0; j < 3; ++j) { + if (points[i][j] > boxMax[j]) { + boxMax[j] = points[i][j]; + } + if (points[i][j] < boxMin[j]) { + boxMin[j] = points[i][j]; + } + } + } + + // compute step size + btVector3 diagonal = boxMax - boxMin; + btScalar maxDimension = diagonal[0]; + if (diagonal[1] > maxDimension) { + maxDimension = diagonal[1]; + } + if (diagonal[2] > maxDimension) { + maxDimension = diagonal[2]; + } + btScalar resolutionOfIntegration = btScalar(400.0f); + btScalar delta = maxDimension / resolutionOfIntegration; + btScalar deltaVolume = delta * delta * delta; + + // integrate over three dimensions + btMatrix3x3 deltaInertia; + btScalar XX = boxMax[0]; + btScalar YY = boxMax[1]; + btScalar ZZ = boxMax[2]; + btScalar x = boxMin[0]; + while(x < XX) { + btScalar y = boxMin[1]; + while (y < YY) { + btScalar z = boxMin[2]; + while (z < ZZ) { + btVector3 p(x, y, z); + // the point is inside the shape if it is behind all face planes + bool pointInside = true; + for (int i = 0; i < 4; ++i) { + if ((p - pointsOnPlane[i]).dot(normals[i]) > btScalar(0.0f)) { + pointInside = false; + break; + } + } + if (pointInside) { + // this point contributes to the total + computePointInertia(p, deltaVolume, deltaInertia); + inertia += deltaInertia; + } + z += delta; + } + y += delta; + } + x += delta; + } +} + +btScalar computeTetrahedronVolume(btVector3* points) { + // Assumes triangle {1, 2, 3} is wound according to the right-hand-rule. + // NOTE: volume may be negative, in which case the tetrahedron contributes negatively to totals + // volume = (face_area * face_normal).dot(face_to_far_point) / 3.0 + // (face_area * face_normal) = side0.cross(side1) / 2.0 + return ((points[2] - points[1]).cross(points[3] - points[2])).dot(points[3] - points[0]) / btScalar(6.0f); +} + +void applyParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { + // Parallel Axis Theorem says: + // + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // + // where R*R = inside product + // R(x)R = outside product + // E = identity matrix + + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { + btScalar shifti = shift[i]; + inertia[i][i] += mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { + btScalar offDiagonal = mass * shifti * shift[j]; + inertia[i][j] -= offDiagonal; + inertia[j][i] -= offDiagonal; + } + } + } +} + +// helper function +void applyInverseParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { + // Parallel Axis Theorem says: + // + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // + // So the inverse would be: + // + // Icm = Ishifted - M * [ (R*R)E - R(x)R ] + + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { + btScalar shifti = shift[i]; + inertia[i][i] -= mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { + btScalar offDiagonal = mass * shifti * shift[j]; + inertia[i][j] += offDiagonal; + inertia[j][i] += offDiagonal; + } + } + } +} + +MeshMassProperties::MeshMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices) { + computeMassProperties(points, triangleIndices); +} + +void MeshMassProperties::computeMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices) { + // We process the mesh one triangle at a time. Each triangle defines a tetrahedron + // relative to some local point p0 (which we chose to be the local origin for convenience). + // Each tetrahedron contributes to the three totals: volume, centerOfMass, and inertiaTensor. + // + // We assume the mesh triangles are wound using the right-hand-rule, such that the + // triangle's points circle counter-clockwise about its face normal. + // + + // initialize the totals + m_volume = btScalar(0.0f); + btVector3 weightedCenter; + weightedCenter.setZero(); + for (uint32_t i = 0; i < 3; ++i) { + m_inertia[i].setZero(); + } + + // create some variables to hold temporary results + uint32_t numPoints = points.size(); + const btVector3 p0(0.0f, 0.0f, 0.0f); + btMatrix3x3 tetraInertia; + btMatrix3x3 doubleDebugInertia; + btVector3 tetraPoints[4]; + btVector3 center; + + // loop over triangles + uint32_t numTriangles = triangleIndices.size() / 3; + for (uint32_t i = 0; i < numTriangles; ++i) { + uint32_t t = 3 * i; + assert(triangleIndices[t] < numPoints); + assert(triangleIndices[t + 1] < numPoints); + assert(triangleIndices[t + 2] < numPoints); + + // extract raw vertices + tetraPoints[0] = p0; + tetraPoints[1] = points[triangleIndices[t]]; + tetraPoints[2] = points[triangleIndices[t + 1]]; + tetraPoints[3] = points[triangleIndices[t + 2]]; + + // compute volume + btScalar volume = computeTetrahedronVolume(tetraPoints); + + // compute center + // NOTE: since tetraPoints[0] is the origin, we don't include it in the sum + center = btScalar(0.25f) * (tetraPoints[1] + tetraPoints[2] + tetraPoints[3]); + + // shift vertices so that center of mass is at origin + tetraPoints[0] -= center; + tetraPoints[1] -= center; + tetraPoints[2] -= center; + tetraPoints[3] -= center; + + // compute inertia tensor then shift it to origin-frame + computeTetrahedronInertia(volume, tetraPoints, tetraInertia); + applyParallelAxisTheorem(tetraInertia, center, volume); + + // tally results + weightedCenter += volume * center; + m_volume += volume; + m_inertia += tetraInertia; + } + + m_centerOfMass = weightedCenter / m_volume; + + applyInverseParallelAxisTheorem(m_inertia, m_centerOfMass, m_volume); +} + diff --git a/libraries/physics/src/MeshMassProperties.h b/libraries/physics/src/MeshMassProperties.h new file mode 100644 index 0000000000..a500cb28e8 --- /dev/null +++ b/libraries/physics/src/MeshMassProperties.h @@ -0,0 +1,60 @@ +// +// MeshMassProperties.h +// libraries/physics/src +// +// Created by Andrew Meadows 2015.05.25 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MeshMassProperties_h +#define hifi_MeshMassProperties_h + +#include + +#include + +typedef std::vector VectorOfPoints; +typedef std::vector VectorOfIndices; + +#define EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST +#ifdef EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST +void computeBoxInertia(btScalar mass, const btVector3& diagonal, btMatrix3x3& I); + +// mass = input mass of tetrahedron +// points = input array of four points with center of mass at origin +// I = output inertia of tetrahedron about its center of mass +void computeTetrahedronInertia(btScalar mass, btVector3* points, btMatrix3x3& I); +void computeTetrahedronInertiaByBruteForce(btVector3* points, btMatrix3x3& I); + +btScalar computeTetrahedronVolume(btVector3* points); + +void applyParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass); +#endif // EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST + +// Given a closed mesh with right-hand triangles a MeshMassProperties instance will compute +// its mass properties: +// +// volume +// center-of-mass +// normalized interia tensor about center of mass +// +class MeshMassProperties { +public: + + // the mass properties calculation is done in the constructor, so if the mesh is complex + // then the construction could be computationally expensiuve. + MeshMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices); + + // compute the mass properties of a new mesh + void computeMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices); + + // harveste the mass properties from these public data members + btScalar m_volume = 1.0f; + btVector3 m_centerOfMass = btVector3(0.0f, 0.0f, 0.0f); + btMatrix3x3 m_inertia = btMatrix3x3(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); +}; + +#endif // _hifi_MeshMassProperties_h diff --git a/tests/physics/src/MeshInfoTests.cpp b/tests/physics/src/MeshInfoTests.cpp deleted file mode 100644 index 221ffa117c..0000000000 --- a/tests/physics/src/MeshInfoTests.cpp +++ /dev/null @@ -1,237 +0,0 @@ -// -// MeshInfoTests.cpp -// tests/physics/src -// -// Created by Virendra Singh on 2015.03.02 -// 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 -#include -#include - -#include "MeshInfoTests.h" -const float epsilon = 0.015f; -void MeshInfoTests::testWithTetrahedron(){ - glm::vec3 p0(8.33220, -11.86875, 0.93355); - glm::vec3 p1(0.75523, 5.00000, 16.37072); - glm::vec3 p2(52.61236, 5.00000, -5.38580); - glm::vec3 p3(2.00000, 5.00000, 3.00000); - glm::vec3 centroid(15.92492, 0.782813, 3.72962); - - //translate the tetrahedron so that its apex is on origin - glm::vec3 p11 = p1 - p0; - glm::vec3 p22 = p2 - p0; - glm::vec3 p33 = p3 - p0; - vector vertices = { p11, p22, p33 }; - vector triangles = { 0, 1, 2 }; - - float volume = 1873.233236f; - float inertia_a = 43520.33257f; - //actual should be 194711.28938f. But for some reason it becomes 194711.296875 during - //runtime due to how floating points are stored. - float inertia_b = 194711.289f; - float inertia_c = 191168.76173f; - float inertia_aa = 4417.66150f; - float inertia_bb = -46343.16662f; - float inertia_cc = 11996.20119f; - - meshinfo::MeshInfo meshinfo(&vertices,&triangles); - vector voumeAndInertia = meshinfo.computeMassProperties(); - glm::vec3 tetCenterOfMass = meshinfo.getMeshCentroid(); - - //get original center of mass - tetCenterOfMass = tetCenterOfMass + p0; - glm::vec3 diff = centroid - tetCenterOfMass; - std::cout << std::setprecision(12); - //test if centroid is correct - if (diff.x > epsilon || diff.y > epsilon || diff.z > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Centroid is incorrect : Expected = " << centroid.x << " " << - centroid.y << " " << centroid.z << ", actual = " << tetCenterOfMass.x << " " << tetCenterOfMass.y << - " " << tetCenterOfMass.z << std::endl; - } - - //test if volume is correct - if (abs(volume - voumeAndInertia.at(0)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Volume is incorrect : Expected = " << volume << " " << - ", actual = " << voumeAndInertia.at(0) << std::endl; - } - - //test if moment of inertia with respect to x axis is correct - if (abs(inertia_a - (voumeAndInertia.at(1))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia with respect to x axis is incorrect : Expected = " << - inertia_a << " " << ", actual = " << voumeAndInertia.at(1) << std::endl; - } - - //test if moment of inertia with respect to y axis is correct - if (abs(inertia_b - voumeAndInertia.at(2)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia with respect to y axis is incorrect : Expected = " << - inertia_b << " " << ", actual = " << (voumeAndInertia.at(2)) << std::endl; - } - - //test if moment of inertia with respect to z axis is correct - if (abs(inertia_c - (voumeAndInertia.at(3))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia with respect to z axis is incorrect : Expected = " << - inertia_c << " " << ", actual = " << (voumeAndInertia.at(3)) << std::endl; - } - - //test if product of inertia with respect to x axis is correct - if (abs(inertia_aa - (voumeAndInertia.at(4))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Product of inertia with respect to x axis is incorrect : Expected = " << - inertia_aa << " " << ", actual = " << (voumeAndInertia.at(4)) << std::endl; - } - - //test if product of inertia with respect to y axis is correct - if (abs(inertia_bb - (voumeAndInertia.at(5))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Product of inertia with respect to y axis is incorrect : Expected = " << - inertia_bb << " " << ", actual = " << (voumeAndInertia.at(5)) << std::endl; - } - - //test if product of inertia with respect to z axis is correct - if (abs(inertia_cc - (voumeAndInertia.at(6))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Product of inertia with respect to z axis is incorrect : Expected = " << - inertia_cc << " " << ", actual = " << (voumeAndInertia.at(6)) << std::endl; - } - -} - -void MeshInfoTests::testWithTetrahedronAsMesh(){ - glm::vec3 p0(8.33220, -11.86875, 0.93355); - glm::vec3 p1(0.75523, 5.00000, 16.37072); - glm::vec3 p2(52.61236, 5.00000, -5.38580); - glm::vec3 p3(2.00000, 5.00000, 3.00000); - glm::vec3 centroid(15.92492, 0.782813, 3.72962); - /* TODO: actually test inertia/volume calculations here - //float volume = 1873.233236f; - //runtime due to how floating points are stored. - float inertia_a = 43520.33257f; - float inertia_b = 194711.289f; - float inertia_c = 191168.76173f; - float inertia_aa = 4417.66150f; - float inertia_bb = -46343.16662f; - float inertia_cc = 11996.20119f; - */ - std::cout << std::setprecision(12); - vector vertices = { p0, p1, p2, p3 }; - vector triangles = { 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 3 }; - meshinfo::MeshInfo massProp(&vertices, &triangles); - vector volumeAndInertia = massProp.computeMassProperties(); - std::cout << volumeAndInertia[0] << " " << volumeAndInertia[1] << " " << volumeAndInertia[2] - << " " << volumeAndInertia[3] - << " " << volumeAndInertia[4] - << " " << volumeAndInertia[5] << " " << volumeAndInertia[6] << std::endl; - - //translate the tetrahedron so that the model is placed at origin i.e. com is at origin - p0 -= centroid; - p1 -= centroid; - p2 -= centroid; - p3 -= centroid; -} - -void MeshInfoTests::testWithCube(){ - glm::vec3 p0(1.0, -1.0, -1.0); - glm::vec3 p1(1.0, -1.0, 1.0); - glm::vec3 p2(-1.0, -1.0, 1.0); - glm::vec3 p3(-1.0, -1.0, -1.0); - glm::vec3 p4(1.0, 1.0, -1.0); - glm::vec3 p5(1.0, 1.0, 1.0); - glm::vec3 p6(-1.0, 1.0, 1.0); - glm::vec3 p7(-1.0, 1.0, -1.0); - vector vertices; - vertices.push_back(p0); - vertices.push_back(p1); - vertices.push_back(p2); - vertices.push_back(p3); - vertices.push_back(p4); - vertices.push_back(p5); - vertices.push_back(p6); - vertices.push_back(p7); - std::cout << std::setprecision(10); - vector triangles = { 0, 1, 2, 0, 2, 3, 4, 7, 6, 4, 6, 5, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, - 7, 2, 7, 3, 4, 0, 3, 4, 3, 7 }; - glm::vec3 centerOfMass(0.0, 0.0, 0.0); - double volume = 8.0; - double side = 2.0; - double inertia = (volume * side * side) / 6.0; //inertia of a unit cube is (mass * side * side) /6 - - //test with origin as reference point - meshinfo::MeshInfo massProp(&vertices, &triangles); - vector volumeAndInertia = massProp.computeMassProperties(); - if (abs(centerOfMass.x - massProp.getMeshCentroid().x) > epsilon || abs(centerOfMass.y - massProp.getMeshCentroid().y) > epsilon || - abs(centerOfMass.z - massProp.getMeshCentroid().z) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Center of mass is incorrect : Expected = " << centerOfMass.x << " " << - centerOfMass.y << " " << centerOfMass.z << ", actual = " << massProp.getMeshCentroid().x << " " << - massProp.getMeshCentroid().y << " " << massProp.getMeshCentroid().z << std::endl; - } - - if (abs(volume - volumeAndInertia.at(0)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Volume is incorrect : Expected = " << volume << - ", actual = " << volumeAndInertia.at(0) << std::endl; - } - - if (abs(inertia - (volumeAndInertia.at(1))) > epsilon || abs(inertia - (volumeAndInertia.at(2))) > epsilon || - abs(inertia - (volumeAndInertia.at(3))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia is incorrect : Expected = " << inertia << " " << - inertia << " " << inertia << ", actual = " << (volumeAndInertia.at(1)) << " " << (volumeAndInertia.at(2)) << - " " << (volumeAndInertia.at(3)) << std::endl; - } -} - -void MeshInfoTests::testWithUnitCube() -{ - glm::vec3 p0(0, 0, 1); - glm::vec3 p1(1, 0, 1); - glm::vec3 p2(0, 1, 1); - glm::vec3 p3(1, 1, 1); - glm::vec3 p4(0, 0, 0); - glm::vec3 p5(1, 0, 0); - glm::vec3 p6(0, 1, 0); - glm::vec3 p7(1, 1, 0); - vector vertices; - vertices.push_back(p0); - vertices.push_back(p1); - vertices.push_back(p2); - vertices.push_back(p3); - vertices.push_back(p4); - vertices.push_back(p5); - vertices.push_back(p6); - vertices.push_back(p7); - vector triangles = { 0, 1, 2, 1, 3, 2, 2, 3, 7, 2, 7, 6, 1, 7, 3, 1, 5, 7, 6, 7, 4, 7, 5, 4, 0, 4, 1, - 1, 4, 5, 2, 6, 4, 0, 2, 4 }; - glm::vec3 centerOfMass(0.5, 0.5, 0.5); - double volume = 1.0; - double side = 1.0; - double inertia = (volume * side * side) / 6.0; //inertia of a unit cube is (mass * side * side) /6 - std::cout << std::setprecision(10); - - //test with origin as reference point - meshinfo::MeshInfo massProp(&vertices, &triangles); - vector volumeAndInertia = massProp.computeMassProperties(); - if (abs(centerOfMass.x - massProp.getMeshCentroid().x) > epsilon || abs(centerOfMass.y - massProp.getMeshCentroid().y) > - epsilon || abs(centerOfMass.z - massProp.getMeshCentroid().z) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Center of mass is incorrect : Expected = " << centerOfMass.x << - " " << centerOfMass.y << " " << centerOfMass.z << ", actual = " << massProp.getMeshCentroid().x << " " << - massProp.getMeshCentroid().y << " " << massProp.getMeshCentroid().z << std::endl; - } - - if (abs(volume - volumeAndInertia.at(0)) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Volume is incorrect : Expected = " << volume << - ", actual = " << volumeAndInertia.at(0) << std::endl; - } - - if (abs(inertia - (volumeAndInertia.at(1))) > epsilon || abs(inertia - (volumeAndInertia.at(2))) > epsilon || - abs(inertia - (volumeAndInertia.at(3))) > epsilon){ - std::cout << __FILE__ << ":" << __LINE__ << " ERROR : Moment of inertia is incorrect : Expected = " << inertia << " " << - inertia << " " << inertia << ", actual = " << (volumeAndInertia.at(1)) << " " << (volumeAndInertia.at(2)) << - " " << (volumeAndInertia.at(3)) << std::endl; - } -} -void MeshInfoTests::runAllTests(){ - testWithTetrahedron(); - testWithTetrahedronAsMesh(); - testWithUnitCube(); - testWithCube(); -} diff --git a/tests/physics/src/MeshInfoTests.h b/tests/physics/src/MeshInfoTests.h deleted file mode 100644 index 0ddd8d0944..0000000000 --- a/tests/physics/src/MeshInfoTests.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MeshInfoTests.h -// tests/physics/src -// -// Created by Virendra Singh on 2015.03.02 -// 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 -// - -#ifndef hifi_MeshInfoTests_h -#define hifi_MeshInfoTests_h -namespace MeshInfoTests{ - void testWithTetrahedron(); - void testWithUnitCube(); - void testWithCube(); - void runAllTests(); - void testWithTetrahedronAsMesh(); -} -#endif // hifi_MeshInfoTests_h \ No newline at end of file diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp new file mode 100644 index 0000000000..6434bd07ff --- /dev/null +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -0,0 +1,452 @@ +// +// MeshMassProperties.cpp +// tests/physics/src +// +// Created by Virendra Singh on 2015.03.02 +// 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 +#include + +#include "MeshMassPropertiesTests.h" + +//#define VERBOSE_UNIT_TESTS + +const btScalar acceptableRelativeError(1.0e-5f); +const btScalar acceptableAbsoluteError(1.0e-4f); + +void printMatrix(const std::string& name, const btMatrix3x3& matrix) { + std::cout << name << " = [" << std::endl; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + std::cout << " " << matrix[i][j]; + } + std::cout << std::endl; + } + std::cout << "]" << std::endl; +} + +void MeshMassPropertiesTests::testParallelAxisTheorem() { +#ifdef EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST + // verity we can compute the inertia tensor of a box in two different ways: + // (a) as one box + // (b) as a combination of two partial boxes. +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + btScalar bigBoxX = 7.0f; + btScalar bigBoxY = 9.0f; + btScalar bigBoxZ = 11.0f; + btScalar bigBoxMass = bigBoxX * bigBoxY * bigBoxZ; + btMatrix3x3 bitBoxInertia; + computeBoxInertia(bigBoxMass, btVector3(bigBoxX, bigBoxY, bigBoxZ), bitBoxInertia); + + btScalar smallBoxX = bigBoxX / 2.0f; + btScalar smallBoxY = bigBoxY; + btScalar smallBoxZ = bigBoxZ; + btScalar smallBoxMass = smallBoxX * smallBoxY * smallBoxZ; + btMatrix3x3 smallBoxI; + computeBoxInertia(smallBoxMass, btVector3(smallBoxX, smallBoxY, smallBoxZ), smallBoxI); + + btVector3 smallBoxOffset(smallBoxX / 2.0f, 0.0f, 0.0f); + + btMatrix3x3 smallBoxShiftedRight = smallBoxI; + applyParallelAxisTheorem(smallBoxShiftedRight, smallBoxOffset, smallBoxMass); + + btMatrix3x3 smallBoxShiftedLeft = smallBoxI; + applyParallelAxisTheorem(smallBoxShiftedLeft, -smallBoxOffset, smallBoxMass); + + btMatrix3x3 twoSmallBoxesInertia = smallBoxShiftedRight + smallBoxShiftedLeft; + + // verify bigBox same as twoSmallBoxes + btScalar error; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = bitBoxInertia[i][j] - twoSmallBoxesInertia[i][j]; + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : box inertia[" << i << "][" << j << "] off by = " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + printMatrix("expected inertia", bitBoxInertia); + printMatrix("computed inertia", twoSmallBoxesInertia); +#endif // VERBOSE_UNIT_TESTS +#endif // EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST +} + +void MeshMassPropertiesTests::testTetrahedron(){ + // given the four vertices of a tetrahedron verify the analytic formula for inertia + // agrees with expected results +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + // these numbers from the Tonon paper: + btVector3 points[4]; + points[0] = btVector3(8.33220f, -11.86875f, 0.93355f); + points[1] = btVector3(0.75523f, 5.00000f, 16.37072f); + points[2] = btVector3(52.61236f, 5.00000f, -5.38580f); + points[3] = btVector3(2.00000f, 5.00000f, 3.00000f); + + btScalar expectedVolume = 1873.233236f; + + btMatrix3x3 expectedInertia; + expectedInertia[0][0] = 43520.33257f; + expectedInertia[1][1] = 194711.28938f; + expectedInertia[2][2] = 191168.76173f; + expectedInertia[1][2] = -4417.66150f; + expectedInertia[2][1] = -4417.66150f; + expectedInertia[0][2] = 46343.16662f; + expectedInertia[2][0] = 46343.16662f; + expectedInertia[0][1] = -11996.20119f; + expectedInertia[1][0] = -11996.20119f; + + // compute volume + btScalar volume = computeTetrahedronVolume(points); + btScalar error = (volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + btVector3 centerOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); + + // compute inertia tensor + // (shift the points so that tetrahedron's local centerOfMass is at origin) + for (int i = 0; i < 4; ++i) { + points[i] -= centerOfMass; + } + btMatrix3x3 inertia; + computeTetrahedronInertia(volume, points, inertia); + + // verify + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", inertia); + + // when building VERBOSE you might be instrested in the results from the brute force method: + btMatrix3x3 bruteInertia; + computeTetrahedronInertiaByBruteForce(points, bruteInertia); + printMatrix("brute inertia", bruteInertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::testOpenTetrahedonMesh() { + // given the simplest possible mesh (open, with one triangle) + // verify MeshMassProperties computes the right nubers +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + // these numbers from the Tonon paper: + VectorOfPoints points; + points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f)); + points.push_back(btVector3(0.75523f, 5.00000f, 16.37072f)); + points.push_back(btVector3(52.61236f, 5.00000f, -5.38580f)); + points.push_back(btVector3(2.00000f, 5.00000f, 3.00000f)); + + btScalar expectedVolume = 1873.233236f; + + btMatrix3x3 expectedInertia; + expectedInertia[0][0] = 43520.33257f; + expectedInertia[1][1] = 194711.28938f; + expectedInertia[2][2] = 191168.76173f; + expectedInertia[1][2] = -4417.66150f; + expectedInertia[2][1] = -4417.66150f; + expectedInertia[0][2] = 46343.16662f; + expectedInertia[2][0] = 46343.16662f; + expectedInertia[0][1] = -11996.20119f; + expectedInertia[1][0] = -11996.20119f; + + // test as an open mesh with one triangle + VectorOfPoints shiftedPoints; + shiftedPoints.push_back(points[0] - points[0]); + shiftedPoints.push_back(points[1] - points[0]); + shiftedPoints.push_back(points[2] - points[0]); + shiftedPoints.push_back(points[3] - points[0]); + VectorOfIndices triangles = { 1, 2, 3 }; + btVector3 expectedCenterOfMass = 0.25f * (shiftedPoints[0] + shiftedPoints[1] + shiftedPoints[2] + shiftedPoints[3]); + + // compute mass properties + MeshMassProperties mesh(shiftedPoints, triangles); + + // verify + btScalar error = (mesh.m_volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh.m_volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh.m_inertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::testClosedTetrahedronMesh() { + // given a tetrahedron as a closed mesh of four tiangles + // verify MeshMassProperties computes the right nubers +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + // these numbers from the Tonon paper: + VectorOfPoints points; + points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f)); + points.push_back(btVector3(0.75523f, 5.00000f, 16.37072f)); + points.push_back(btVector3(52.61236f, 5.00000f, -5.38580f)); + points.push_back(btVector3(2.00000f, 5.00000f, 3.00000f)); + + btScalar expectedVolume = 1873.233236f; + + btMatrix3x3 expectedInertia; + expectedInertia[0][0] = 43520.33257f; + expectedInertia[1][1] = 194711.28938f; + expectedInertia[2][2] = 191168.76173f; + expectedInertia[1][2] = -4417.66150f; + expectedInertia[2][1] = -4417.66150f; + expectedInertia[0][2] = 46343.16662f; + expectedInertia[2][0] = 46343.16662f; + expectedInertia[0][1] = -11996.20119f; + expectedInertia[1][0] = -11996.20119f; + + btVector3 expectedCenterOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); + + VectorOfIndices triangles = { + 0, 2, 1, + 0, 3, 2, + 0, 1, 3, + 1, 2, 3 }; + + // compute mass properties + MeshMassProperties mesh(points, triangles); + + // verify + btScalar error; + error = (mesh.m_volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "(a) tetrahedron as mesh" << std::endl; + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh.m_volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh.m_inertia); +#endif // VERBOSE_UNIT_TESTS + + // test again, but this time shift the points so that the origin is definitely OUTSIDE the mesh + btVector3 shift = points[0] + expectedCenterOfMass; + for (int i = 0; i < (int)points.size(); ++i) { + points[i] += shift; + } + expectedCenterOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); + + // compute mass properties + mesh.computeMassProperties(points, triangles); + + // verify + error = (mesh.m_volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "(b) shifted tetrahedron as mesh" << std::endl; + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh.m_volume << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh.m_inertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::testBoxAsMesh() { + // verify that a mesh box produces the same mass properties as the analytic box. +#ifdef VERBOSE_UNIT_TESTS + std::cout << "\n" << __FUNCTION__ << std::endl; +#endif // VERBOSE_UNIT_TESTS + + + // build a box: + // / + // y + // / + // 6-------------------------7 + // /| /| + // / | / | + // / 2----------------------/--3 + // / / / / + // | 4-------------------------5 / --x-- + // z | / | / + // | |/ |/ + // 0 ------------------------1 + + btScalar x(5.0f); + btScalar y(3.0f); + btScalar z(2.0f); + + VectorOfPoints points; + points.reserve(8); + + points.push_back(btVector3(0.0f, 0.0f, 0.0f)); + points.push_back(btVector3(x, 0.0f, 0.0f)); + points.push_back(btVector3(0.0f, y, 0.0f)); + points.push_back(btVector3(x, y, 0.0f)); + points.push_back(btVector3(0.0f, 0.0f, z)); + points.push_back(btVector3(x, 0.0f, z)); + points.push_back(btVector3(0.0f, y, z)); + points.push_back(btVector3(x, y, z)); + + VectorOfIndices triangles = { + 0, 1, 4, + 1, 5, 4, + 1, 3, 5, + 3, 7, 5, + 2, 0, 6, + 0, 4, 6, + 3, 2, 7, + 2, 6, 7, + 4, 5, 6, + 5, 7, 6, + 0, 2, 1, + 2, 3, 1 + }; + + // compute expected mass properties analytically + btVector3 expectedCenterOfMass = 0.5f * btVector3(x, y, z); + btScalar expectedVolume = x * y * z; + btMatrix3x3 expectedInertia; + computeBoxInertia(expectedVolume, btVector3(x, y, z), expectedInertia); + + // compute the mass properties using the mesh + MeshMassProperties mesh(points, triangles); + + // verify + btScalar error; + error = (mesh.m_volume - expectedVolume) / expectedVolume; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " + << error << std::endl; + } + + error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " + << error << std::endl; + } + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + if (expectedInertia [i][j] == btScalar(0.0f)) { + error = mesh.m_inertia[i][j] - expectedInertia[i][j]; + if (fabsf(error) > acceptableAbsoluteError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << " absolute"<< std::endl; + } + } else { + error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + if (fabsf(error) > acceptableRelativeError) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " + << error << std::endl; + } + } + } + } + +#ifdef VERBOSE_UNIT_TESTS + std::cout << "expected volume = " << expectedVolume << std::endl; + std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "expected center of mass = < " + << expectedCenterOfMass[0] << ", " + << expectedCenterOfMass[1] << ", " + << expectedCenterOfMass[2] << "> " << std::endl; + std::cout << "computed center of mass = < " + << mesh.m_centerOfMass[0] << ", " + << mesh.m_centerOfMass[1] << ", " + << mesh.m_centerOfMass[2] << "> " << std::endl; + printMatrix("expected inertia", expectedInertia); + printMatrix("computed inertia", mesh.m_inertia); +#endif // VERBOSE_UNIT_TESTS +} + +void MeshMassPropertiesTests::runAllTests() { + testParallelAxisTheorem(); + testTetrahedron(); + testOpenTetrahedonMesh(); + testClosedTetrahedronMesh(); + testBoxAsMesh(); + //testWithCube(); +} diff --git a/tests/physics/src/MeshMassPropertiesTests.h b/tests/physics/src/MeshMassPropertiesTests.h new file mode 100644 index 0000000000..ab352bfce2 --- /dev/null +++ b/tests/physics/src/MeshMassPropertiesTests.h @@ -0,0 +1,22 @@ +// +// MeshMassPropertiesTests.h +// tests/physics/src +// +// Created by Virendra Singh on 2015.03.02 +// 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 +// + +#ifndef hifi_MeshMassPropertiesTests_h +#define hifi_MeshMassPropertiesTests_h +namespace MeshMassPropertiesTests{ + void testParallelAxisTheorem(); + void testTetrahedron(); + void testOpenTetrahedonMesh(); + void testClosedTetrahedronMesh(); + void testBoxAsMesh(); + void runAllTests(); +} +#endif // hifi_MeshMassPropertiesTests_h diff --git a/tests/physics/src/main.cpp b/tests/physics/src/main.cpp index 0f35ed5002..f63925bb34 100644 --- a/tests/physics/src/main.cpp +++ b/tests/physics/src/main.cpp @@ -12,13 +12,13 @@ #include "ShapeInfoTests.h" #include "ShapeManagerTests.h" #include "BulletUtilTests.h" -#include "MeshInfoTests.h" +#include "MeshMassPropertiesTests.h" int main(int argc, char** argv) { ShapeColliderTests::runAllTests(); ShapeInfoTests::runAllTests(); ShapeManagerTests::runAllTests(); BulletUtilTests::runAllTests(); - MeshInfoTests::runAllTests(); + MeshMassPropertiesTests::runAllTests(); return 0; } From 708203089c38d3f9306cc72cd34ae1493dd0ebed Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 26 May 2015 22:53:57 -0700 Subject: [PATCH 24/38] replace tabs with spaces --- libraries/physics/src/MeshMassProperties.cpp | 76 +++++++++---------- tests/physics/src/MeshMassPropertiesTests.cpp | 2 +- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/libraries/physics/src/MeshMassProperties.cpp b/libraries/physics/src/MeshMassProperties.cpp index e5d4f4e5af..721941a360 100644 --- a/libraries/physics/src/MeshMassProperties.cpp +++ b/libraries/physics/src/MeshMassProperties.cpp @@ -86,18 +86,18 @@ void computeTetrahedronInertia(btScalar mass, btVector3* points, btMatrix3x3& in // helper function void computePointInertia(const btVector3& point, btScalar mass, btMatrix3x3& inertia) { - btScalar distanceSquared = point.length2(); - if (distanceSquared > 0.0f) { - for (uint32_t i = 0; i < 3; ++i) { + btScalar distanceSquared = point.length2(); + if (distanceSquared > 0.0f) { + for (uint32_t i = 0; i < 3; ++i) { btScalar pointi = point[i]; - inertia[i][i] = mass * (distanceSquared - (pointi * pointi)); - for (uint32_t j = i + 1; j < 3; ++j) { + inertia[i][i] = mass * (distanceSquared - (pointi * pointi)); + for (uint32_t j = i + 1; j < 3; ++j) { btScalar offDiagonal = - mass * pointi * point[j]; - inertia[i][j] = offDiagonal; - inertia[j][i] = offDiagonal; - } - } - } + inertia[i][j] = offDiagonal; + inertia[j][i] = offDiagonal; + } + } + } } // this method is included for unit test verification @@ -204,48 +204,48 @@ btScalar computeTetrahedronVolume(btVector3* points) { void applyParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { // Parallel Axis Theorem says: // - // Ishifted = Icm + M * [ (R*R)E - R(x)R ] - // - // where R*R = inside product - // R(x)R = outside product - // E = identity matrix + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // + // where R*R = inside product + // R(x)R = outside product + // E = identity matrix - btScalar distanceSquared = shift.length2(); - if (distanceSquared > btScalar(0.0f)) { - for (uint32_t i = 0; i < 3; ++i) { + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { btScalar shifti = shift[i]; - inertia[i][i] += mass * (distanceSquared - (shifti * shifti)); - for (uint32_t j = i + 1; j < 3; ++j) { + inertia[i][i] += mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { btScalar offDiagonal = mass * shifti * shift[j]; - inertia[i][j] -= offDiagonal; - inertia[j][i] -= offDiagonal; - } - } - } + inertia[i][j] -= offDiagonal; + inertia[j][i] -= offDiagonal; + } + } + } } // helper function void applyInverseParallelAxisTheorem(btMatrix3x3& inertia, const btVector3& shift, btScalar mass) { // Parallel Axis Theorem says: // - // Ishifted = Icm + M * [ (R*R)E - R(x)R ] - // + // Ishifted = Icm + M * [ (R*R)E - R(x)R ] + // // So the inverse would be: // - // Icm = Ishifted - M * [ (R*R)E - R(x)R ] + // Icm = Ishifted - M * [ (R*R)E - R(x)R ] - btScalar distanceSquared = shift.length2(); - if (distanceSquared > btScalar(0.0f)) { - for (uint32_t i = 0; i < 3; ++i) { + btScalar distanceSquared = shift.length2(); + if (distanceSquared > btScalar(0.0f)) { + for (uint32_t i = 0; i < 3; ++i) { btScalar shifti = shift[i]; - inertia[i][i] -= mass * (distanceSquared - (shifti * shifti)); - for (uint32_t j = i + 1; j < 3; ++j) { + inertia[i][i] -= mass * (distanceSquared - (shifti * shifti)); + for (uint32_t j = i + 1; j < 3; ++j) { btScalar offDiagonal = mass * shifti * shift[j]; - inertia[i][j] += offDiagonal; - inertia[j][i] += offDiagonal; - } - } - } + inertia[i][j] += offDiagonal; + inertia[j][i] += offDiagonal; + } + } + } } MeshMassProperties::MeshMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices) { diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp index 6434bd07ff..442abedebe 100644 --- a/tests/physics/src/MeshMassPropertiesTests.cpp +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -446,7 +446,7 @@ void MeshMassPropertiesTests::runAllTests() { testParallelAxisTheorem(); testTetrahedron(); testOpenTetrahedonMesh(); - testClosedTetrahedronMesh(); + testClosedTetrahedronMesh(); testBoxAsMesh(); //testWithCube(); } From 39d8244255f7df518bd67ff6c450bad8519ca51c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 26 May 2015 22:59:29 -0700 Subject: [PATCH 25/38] use highfideltiy style for class data member names --- libraries/physics/src/MeshMassProperties.cpp | 12 ++--- libraries/physics/src/MeshMassProperties.h | 6 +-- tests/physics/src/MeshMassPropertiesTests.cpp | 48 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/libraries/physics/src/MeshMassProperties.cpp b/libraries/physics/src/MeshMassProperties.cpp index 721941a360..ee2945422d 100644 --- a/libraries/physics/src/MeshMassProperties.cpp +++ b/libraries/physics/src/MeshMassProperties.cpp @@ -262,11 +262,11 @@ void MeshMassProperties::computeMassProperties(const VectorOfPoints& points, con // // initialize the totals - m_volume = btScalar(0.0f); + _volume = btScalar(0.0f); btVector3 weightedCenter; weightedCenter.setZero(); for (uint32_t i = 0; i < 3; ++i) { - m_inertia[i].setZero(); + _inertia[i].setZero(); } // create some variables to hold temporary results @@ -310,12 +310,12 @@ void MeshMassProperties::computeMassProperties(const VectorOfPoints& points, con // tally results weightedCenter += volume * center; - m_volume += volume; - m_inertia += tetraInertia; + _volume += volume; + _inertia += tetraInertia; } - m_centerOfMass = weightedCenter / m_volume; + _centerOfMass = weightedCenter / _volume; - applyInverseParallelAxisTheorem(m_inertia, m_centerOfMass, m_volume); + applyInverseParallelAxisTheorem(_inertia, _centerOfMass, _volume); } diff --git a/libraries/physics/src/MeshMassProperties.h b/libraries/physics/src/MeshMassProperties.h index a500cb28e8..d78c50db00 100644 --- a/libraries/physics/src/MeshMassProperties.h +++ b/libraries/physics/src/MeshMassProperties.h @@ -52,9 +52,9 @@ public: void computeMassProperties(const VectorOfPoints& points, const VectorOfIndices& triangleIndices); // harveste the mass properties from these public data members - btScalar m_volume = 1.0f; - btVector3 m_centerOfMass = btVector3(0.0f, 0.0f, 0.0f); - btMatrix3x3 m_inertia = btMatrix3x3(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); + btScalar _volume = 1.0f; + btVector3 _centerOfMass = btVector3(0.0f, 0.0f, 0.0f); + btMatrix3x3 _inertia = btMatrix3x3(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); }; #endif // _hifi_MeshMassProperties_h diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp index 442abedebe..dac13ef4c3 100644 --- a/tests/physics/src/MeshMassPropertiesTests.cpp +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -191,13 +191,13 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() { MeshMassProperties mesh(shiftedPoints, triangles); // verify - btScalar error = (mesh.m_volume - expectedVolume) / expectedVolume; + btScalar error = (mesh._volume - expectedVolume) / expectedVolume; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " << error << std::endl; } - error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + error = (mesh._centerOfMass - expectedCenterOfMass).length(); if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " << error << std::endl; @@ -205,7 +205,7 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << std::endl; @@ -215,9 +215,9 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() { #ifdef VERBOSE_UNIT_TESTS std::cout << "expected volume = " << expectedVolume << std::endl; - std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; printMatrix("expected inertia", expectedInertia); - printMatrix("computed inertia", mesh.m_inertia); + printMatrix("computed inertia", mesh._inertia); #endif // VERBOSE_UNIT_TESTS } @@ -261,13 +261,13 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { // verify btScalar error; - error = (mesh.m_volume - expectedVolume) / expectedVolume; + error = (mesh._volume - expectedVolume) / expectedVolume; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " << error << std::endl; } - error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + error = (mesh._centerOfMass - expectedCenterOfMass).length(); if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " << error << std::endl; @@ -275,7 +275,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << std::endl; @@ -286,9 +286,9 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { #ifdef VERBOSE_UNIT_TESTS std::cout << "(a) tetrahedron as mesh" << std::endl; std::cout << "expected volume = " << expectedVolume << std::endl; - std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; printMatrix("expected inertia", expectedInertia); - printMatrix("computed inertia", mesh.m_inertia); + printMatrix("computed inertia", mesh._inertia); #endif // VERBOSE_UNIT_TESTS // test again, but this time shift the points so that the origin is definitely OUTSIDE the mesh @@ -302,13 +302,13 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { mesh.computeMassProperties(points, triangles); // verify - error = (mesh.m_volume - expectedVolume) / expectedVolume; + error = (mesh._volume - expectedVolume) / expectedVolume; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " << error << std::endl; } - error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + error = (mesh._centerOfMass - expectedCenterOfMass).length(); if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " << error << std::endl; @@ -316,7 +316,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { - error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << std::endl; @@ -327,9 +327,9 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { #ifdef VERBOSE_UNIT_TESTS std::cout << "(b) shifted tetrahedron as mesh" << std::endl; std::cout << "expected volume = " << expectedVolume << std::endl; - std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; printMatrix("expected inertia", expectedInertia); - printMatrix("computed inertia", mesh.m_inertia); + printMatrix("computed inertia", mesh._inertia); #endif // VERBOSE_UNIT_TESTS } @@ -396,13 +396,13 @@ void MeshMassPropertiesTests::testBoxAsMesh() { // verify btScalar error; - error = (mesh.m_volume - expectedVolume) / expectedVolume; + error = (mesh._volume - expectedVolume) / expectedVolume; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : volume of tetrahedron off by = " << error << std::endl; } - error = (mesh.m_centerOfMass - expectedCenterOfMass).length(); + error = (mesh._centerOfMass - expectedCenterOfMass).length(); if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : centerOfMass of tetrahedron off by = " << error << std::endl; @@ -411,13 +411,13 @@ void MeshMassPropertiesTests::testBoxAsMesh() { for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { if (expectedInertia [i][j] == btScalar(0.0f)) { - error = mesh.m_inertia[i][j] - expectedInertia[i][j]; + error = mesh._inertia[i][j] - expectedInertia[i][j]; if (fabsf(error) > acceptableAbsoluteError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << " absolute"<< std::endl; } } else { - error = (mesh.m_inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; + error = (mesh._inertia[i][j] - expectedInertia[i][j]) / expectedInertia[i][j]; if (fabsf(error) > acceptableRelativeError) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : inertia[" << i << "][" << j << "] off by " << error << std::endl; @@ -428,17 +428,17 @@ void MeshMassPropertiesTests::testBoxAsMesh() { #ifdef VERBOSE_UNIT_TESTS std::cout << "expected volume = " << expectedVolume << std::endl; - std::cout << "measured volume = " << mesh.m_volume << std::endl; + std::cout << "measured volume = " << mesh._volume << std::endl; std::cout << "expected center of mass = < " << expectedCenterOfMass[0] << ", " << expectedCenterOfMass[1] << ", " << expectedCenterOfMass[2] << "> " << std::endl; std::cout << "computed center of mass = < " - << mesh.m_centerOfMass[0] << ", " - << mesh.m_centerOfMass[1] << ", " - << mesh.m_centerOfMass[2] << "> " << std::endl; + << mesh._centerOfMass[0] << ", " + << mesh._centerOfMass[1] << ", " + << mesh._centerOfMass[2] << "> " << std::endl; printMatrix("expected inertia", expectedInertia); - printMatrix("computed inertia", mesh.m_inertia); + printMatrix("computed inertia", mesh._inertia); #endif // VERBOSE_UNIT_TESTS } From 2a6955ce1207993cfb18f7af7e36a8eecdae27d5 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 27 May 2015 09:14:11 -0700 Subject: [PATCH 26/38] fix windows build --- libraries/physics/src/MeshMassProperties.h | 1 + tests/physics/src/MeshMassPropertiesTests.cpp | 47 +++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/libraries/physics/src/MeshMassProperties.h b/libraries/physics/src/MeshMassProperties.h index d78c50db00..208955d3a6 100644 --- a/libraries/physics/src/MeshMassProperties.h +++ b/libraries/physics/src/MeshMassProperties.h @@ -13,6 +13,7 @@ #define hifi_MeshMassProperties_h #include +#include #include diff --git a/tests/physics/src/MeshMassPropertiesTests.cpp b/tests/physics/src/MeshMassPropertiesTests.cpp index dac13ef4c3..ebb762aa55 100644 --- a/tests/physics/src/MeshMassPropertiesTests.cpp +++ b/tests/physics/src/MeshMassPropertiesTests.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include "MeshMassPropertiesTests.h" @@ -30,6 +31,12 @@ void printMatrix(const std::string& name, const btMatrix3x3& matrix) { std::cout << "]" << std::endl; } +void pushTriangle(VectorOfIndices& indices, uint32_t a, uint32_t b, uint32_t c) { + indices.push_back(a); + indices.push_back(b); + indices.push_back(c); +} + void MeshMassPropertiesTests::testParallelAxisTheorem() { #ifdef EXPOSE_HELPER_FUNCTIONS_FOR_UNIT_TEST // verity we can compute the inertia tensor of a box in two different ways: @@ -184,7 +191,8 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() { shiftedPoints.push_back(points[1] - points[0]); shiftedPoints.push_back(points[2] - points[0]); shiftedPoints.push_back(points[3] - points[0]); - VectorOfIndices triangles = { 1, 2, 3 }; + VectorOfIndices triangles; + pushTriangle(triangles, 1, 2, 3); btVector3 expectedCenterOfMass = 0.25f * (shiftedPoints[0] + shiftedPoints[1] + shiftedPoints[2] + shiftedPoints[3]); // compute mass properties @@ -250,11 +258,11 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() { btVector3 expectedCenterOfMass = 0.25f * (points[0] + points[1] + points[2] + points[3]); - VectorOfIndices triangles = { - 0, 2, 1, - 0, 3, 2, - 0, 1, 3, - 1, 2, 3 }; + VectorOfIndices triangles; + pushTriangle(triangles, 0, 2, 1); + pushTriangle(triangles, 0, 3, 2); + pushTriangle(triangles, 0, 1, 3); + pushTriangle(triangles, 1, 2, 3); // compute mass properties MeshMassProperties mesh(points, triangles); @@ -370,20 +378,19 @@ void MeshMassPropertiesTests::testBoxAsMesh() { points.push_back(btVector3(0.0f, y, z)); points.push_back(btVector3(x, y, z)); - VectorOfIndices triangles = { - 0, 1, 4, - 1, 5, 4, - 1, 3, 5, - 3, 7, 5, - 2, 0, 6, - 0, 4, 6, - 3, 2, 7, - 2, 6, 7, - 4, 5, 6, - 5, 7, 6, - 0, 2, 1, - 2, 3, 1 - }; + VectorOfIndices triangles; + pushTriangle(triangles, 0, 1, 4); + pushTriangle(triangles, 1, 5, 4); + pushTriangle(triangles, 1, 3, 5); + pushTriangle(triangles, 3, 7, 5); + pushTriangle(triangles, 2, 0, 6); + pushTriangle(triangles, 0, 4, 6); + pushTriangle(triangles, 3, 2, 7); + pushTriangle(triangles, 2, 6, 7); + pushTriangle(triangles, 4, 5, 6); + pushTriangle(triangles, 5, 7, 6); + pushTriangle(triangles, 0, 2, 1); + pushTriangle(triangles, 2, 3, 1); // compute expected mass properties analytically btVector3 expectedCenterOfMass = 0.5f * btVector3(x, y, z); From eb0f35e041e391563c1985a31923c910b214582b Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Wed, 27 May 2015 11:44:37 -0700 Subject: [PATCH 27/38] removed const keyword from qinvokables for avatar getter methods such as getVelocity --- interface/src/avatar/Avatar.h | 6 +++--- libraries/avatars/src/AvatarData.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 0cdaf36099..939161327b 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -147,9 +147,9 @@ public: Q_INVOKABLE glm::vec3 getNeckPosition() const; - Q_INVOKABLE const glm::vec3& getAcceleration() const { return _acceleration; } - Q_INVOKABLE const glm::vec3& getAngularVelocity() const { return _angularVelocity; } - Q_INVOKABLE const glm::vec3& getAngularAcceleration() const { return _angularAcceleration; } + Q_INVOKABLE glm::vec3 getAcceleration() { return _acceleration; } + Q_INVOKABLE glm::vec3 getAngularVelocity() { return _angularVelocity; } + Q_INVOKABLE glm::vec3 getAngularAcceleration() { return _angularAcceleration; } /// Scales a world space position vector relative to the avatar position and scale diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 603b5d76ea..a27e4256ef 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -301,7 +301,7 @@ public: int getReceiveRate() const; void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } - Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; } + Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; } const glm::vec3& getTargetVelocity() const { return _targetVelocity; } bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } From a2272d3f42b381cecc5d721be7e5cb1b31127acf Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 27 May 2015 11:49:00 -0700 Subject: [PATCH 28/38] Fix Windows C4351 build warning VS2013 warns about default initialization of arrays because it behaved differently in previous versions. Default initialization is what we expect now that we're using VS2013 so we can disable this warning globally. --- CMakeLists.txt | 3 ++- interface/src/devices/DdeFaceTracker.cpp | 11 ----------- libraries/networking/src/Assignment.cpp | 6 ------ 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 347341efa0..e57e33e3b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,8 @@ if (WIN32) endif () message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH}) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351") # /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory. # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables # TODO: Remove when building 64-bit. diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index fba37736e3..27e15cea0e 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -141,13 +141,6 @@ static const float STARTING_DDE_MESSAGE_TIME = 0.033f; static const float DEFAULT_DDE_EYE_CLOSING_THRESHOLD = 0.8f; static const int CALIBRATION_SAMPLES = 150; -#ifdef WIN32 -// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeBlinks' will be default initialized -// warning C4351: new behavior: elements of array 'DdeFaceTracker::_filteredEyeBlinks' will be default initialized -// warning C4351: new behavior: elements of array 'DdeFaceTracker::_lastEyeCoefficients' will be default initialized -#pragma warning(disable:4351) -#endif - DdeFaceTracker::DdeFaceTracker() : DdeFaceTracker(QHostAddress::Any, DDE_SERVER_PORT, DDE_CONTROL_PORT) { @@ -214,10 +207,6 @@ DdeFaceTracker::~DdeFaceTracker() { } } -#ifdef WIN32 -#pragma warning(default:4351) -#endif - void DdeFaceTracker::init() { FaceTracker::init(); setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::UseCamera) && !_isMuted); diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index 944041730e..a4fa246c93 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -32,12 +32,6 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) { } } -#ifdef WIN32 -//warning C4351: new behavior: elements of array 'Assignment::_payload' will be default initialized -// We're disabling this warning because the new behavior which is to initialize the array with 0 is acceptable to us. -#pragma warning(disable:4351) -#endif - Assignment::Assignment() : _uuid(), _command(Assignment::RequestCommand), From ba0467aafe8359add5b4b8cc28d1c5ab56fc900b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 27 May 2015 14:10:45 -0700 Subject: [PATCH 29/38] =?UTF-8?q?Revert=20"removed=20const=20keyword=20fro?= =?UTF-8?q?m=20Q=5FINVOKABLE=20for=20avatar=20getter=20methods=20such?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interface/src/avatar/Avatar.h | 6 +++--- libraries/avatars/src/AvatarData.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 939161327b..0cdaf36099 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -147,9 +147,9 @@ public: Q_INVOKABLE glm::vec3 getNeckPosition() const; - Q_INVOKABLE glm::vec3 getAcceleration() { return _acceleration; } - Q_INVOKABLE glm::vec3 getAngularVelocity() { return _angularVelocity; } - Q_INVOKABLE glm::vec3 getAngularAcceleration() { return _angularAcceleration; } + Q_INVOKABLE const glm::vec3& getAcceleration() const { return _acceleration; } + Q_INVOKABLE const glm::vec3& getAngularVelocity() const { return _angularVelocity; } + Q_INVOKABLE const glm::vec3& getAngularAcceleration() const { return _angularAcceleration; } /// Scales a world space position vector relative to the avatar position and scale diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a27e4256ef..603b5d76ea 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -301,7 +301,7 @@ public: int getReceiveRate() const; void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } - Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; } + Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; } const glm::vec3& getTargetVelocity() const { return _targetVelocity; } bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } From ca85401b77dbfb493007ca9bc73fca6258040068 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Wed, 27 May 2015 15:08:52 -0700 Subject: [PATCH 30/38] added button so user can toggle pointer on/off --- examples/pointer.js | 101 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 20 deletions(-) diff --git a/examples/pointer.js b/examples/pointer.js index 9ca20504b4..722f1d4f11 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -18,22 +18,33 @@ var position, positionOffset, prevPosition; var nearLinePosition; var strokes = []; var STROKE_ADJUST = 0.005; -var DISTANCE_DRAW_THRESHOLD = .03; +var DISTANCE_DRAW_THRESHOLD = .02; var drawDistance = 0; +var LINE_WIDTH = 20; + var userCanDraw = true; +var userCanPoint = true; var BUTTON_SIZE = 32; var PADDING = 3; -var buttonOffColor = {red: 250, green: 10, blue: 10}; -var buttonOnColor = {red: 10, green: 200, blue: 100}; +var buttonOffColor = { + red: 250, + green: 10, + blue: 10 +}; +var buttonOnColor = { + red: 10, + green: 200, + blue: 100 +}; HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var screenSize = Controller.getViewportDimensions(); var drawButton = Overlays.addOverlay("image", { - x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, + x: screenSize.x / 2 - BUTTON_SIZE + PADDING * 2, y: screenSize.y - (BUTTON_SIZE + PADDING), width: BUTTON_SIZE, height: BUTTON_SIZE, @@ -42,6 +53,15 @@ var drawButton = Overlays.addOverlay("image", { alpha: 1 }); +var pointerButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/laser.png", + color: buttonOnColor, + alpha: 1 +}) @@ -50,8 +70,16 @@ center.y += 0.5; var whiteBoard = Entities.addEntity({ type: "Box", position: center, - dimensions: {x: 1, y: 1, z: .001}, - color: {red: 255, green: 255, blue: 255} + dimensions: { + x: 1, + y: 1, + z: .001 + }, + color: { + red: 255, + green: 255, + blue: 255 + } }); function calculateNearLinePosition(targetPosition) { @@ -73,6 +101,9 @@ function removeLine() { function createOrUpdateLine(event) { + if (!userCanPoint) { + return; + } var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking var props = Entities.getEntityProperties(intersection.entityID); @@ -82,14 +113,13 @@ function createOrUpdateLine(event) { var subtractVec = Vec3.multiply(Vec3.normalize(pickRay.direction), STROKE_ADJUST); startPosition = Vec3.subtract(startPosition, subtractVec); nearLinePosition = calculateNearLinePosition(intersection.intersection); - positionOffset= Vec3.subtract(startPosition, nearLinePosition); + positionOffset = Vec3.subtract(startPosition, nearLinePosition); if (lineIsRezzed) { Entities.editEntity(lineEntityID, { position: nearLinePosition, dimensions: positionOffset, - lifetime: 15 + props.lifespan // renew lifetime }); - if(userCanDraw){ + if (userCanDraw) { draw(); } } else { @@ -104,7 +134,6 @@ function createOrUpdateLine(event) { green: 255, blue: 255 }, - lifetime: 15 // if someone crashes while pointing, don't leave the line there forever. }); } } else { @@ -112,11 +141,11 @@ function createOrUpdateLine(event) { } } -function draw(){ +function draw() { //We only want to draw line if distance between starting and previous point is large enough drawDistance = Vec3.distance(startPosition, prevPosition); - if( drawDistance < DISTANCE_DRAW_THRESHOLD){ + if (drawDistance < DISTANCE_DRAW_THRESHOLD) { return; } @@ -125,8 +154,12 @@ function draw(){ type: "Line", position: prevPosition, dimensions: offset, - color: {red: 200, green: 40, blue: 200}, - // lifetime: 20 + color: { + red: 200, + green: 40, + blue: 200 + }, + lineWidth: LINE_WIDTH })); prevPosition = startPosition; } @@ -138,12 +171,38 @@ function mousePressEvent(event) { }); if (clickedOverlay == drawButton) { userCanDraw = !userCanDraw; - if(userCanDraw === true){ - Overlays.editOverlay(drawButton, {color: buttonOnColor}); + if (userCanDraw === true) { + Overlays.editOverlay(drawButton, { + color: buttonOnColor + }); } else { - Overlays.editOverlay(drawButton, {color: buttonOffColor}); + Overlays.editOverlay(drawButton, { + color: buttonOffColor + }); } - } + } + + if (clickedOverlay == pointerButton) { + userCanPoint = !userCanPoint; + if (userCanPoint === true) { + Overlays.editOverlay(pointerButton, { + color: buttonOnColor + }); + if (userCanDraw === true) { + + Overlays.editOverlay(drawButton, { + color: buttonOnColor + }); + } + } else { + Overlays.editOverlay(pointerButton, { + color: buttonOffColor + }); + Overlays.editOverlay(drawButton, { + color: buttonOffColor + }); + } + } if (!event.isLeftButton || altHeld) { return; @@ -154,6 +213,7 @@ function mousePressEvent(event) { } + function mouseMoveEvent(event) { createOrUpdateLine(event); } @@ -182,13 +242,14 @@ function keyReleaseEvent(event) { } -function cleanup(){ +function cleanup() { Entities.deleteEntity(whiteBoard); - for(var i =0; i < strokes.length; i++){ + for (var i = 0; i < strokes.length; i++) { Entities.deleteEntity(strokes[i]); } Overlays.deleteOverlay(drawButton); + Overlays.deleteOverlay(pointerButton); } From 07c8fb6c025b354e291dd6b221439e1856ab8994 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Wed, 27 May 2015 15:11:16 -0700 Subject: [PATCH 31/38] turned pointer and draw funcitonality off be default --- examples/pointer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/pointer.js b/examples/pointer.js index 722f1d4f11..eebe4ec5be 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -23,8 +23,8 @@ var drawDistance = 0; var LINE_WIDTH = 20; -var userCanDraw = true; -var userCanPoint = true; +var userCanPoint = false; +var userCanDraw = false; var BUTTON_SIZE = 32; var PADDING = 3; @@ -49,7 +49,7 @@ var drawButton = Overlays.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/pencil.png?v2", - color: buttonOnColor, + color: buttonOffColor, alpha: 1 }); @@ -59,7 +59,7 @@ var pointerButton = Overlays.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/laser.png", - color: buttonOnColor, + color: buttonOffColor, alpha: 1 }) From aba539928db40714e7f1f3d2d1bdfdf1bbd5c444 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 27 May 2015 18:05:59 -0700 Subject: [PATCH 32/38] fix grab glitches and failure to own simulation --- libraries/entities/src/EntityItem.cpp | 561 ++++++++++---------- libraries/physics/src/EntityMotionState.cpp | 10 +- 2 files changed, 277 insertions(+), 294 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index e8210c7e79..104ce168bb 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -318,15 +318,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef return 0; } - // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. - glm::vec3 savePosition = _position; - glm::quat saveRotation = _rotation; - // glm::vec3 saveVelocity = _velocity; - // glm::vec3 saveAngularVelocity = _angularVelocity; - // glm::vec3 saveGravity = _gravity; - // glm::vec3 saveAcceleration = _acceleration; - - // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] @@ -337,299 +328,308 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef const int MINIMUM_HEADER_BYTES = 27; int bytesRead = 0; - if (bytesLeftToRead >= MINIMUM_HEADER_BYTES) { + if (bytesLeftToRead < MINIMUM_HEADER_BYTES) { + return 0; + } - int originalLength = bytesLeftToRead; - QByteArray originalDataBuffer((const char*)data, originalLength); + // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. + glm::vec3 savePosition = _position; + glm::quat saveRotation = _rotation; + glm::vec3 saveVelocity = _velocity; + glm::vec3 saveAngularVelocity = _angularVelocity; - int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; + int originalLength = bytesLeftToRead; + QByteArray originalDataBuffer((const char*)data, originalLength); - const unsigned char* dataAt = data; + int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; - // id - QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size - _id = QUuid::fromRfc4122(encodedID); - dataAt += encodedID.size(); - bytesRead += encodedID.size(); - - // type - QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded typeCoder = encodedType; - encodedType = typeCoder; // determine true length - dataAt += encodedType.size(); - bytesRead += encodedType.size(); - quint32 type = typeCoder; - _type = (EntityTypes::EntityType)type; + const unsigned char* dataAt = data; - bool overwriteLocalData = true; // assume the new content overwrites our local data + // id + QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size + _id = QUuid::fromRfc4122(encodedID); + dataAt += encodedID.size(); + bytesRead += encodedID.size(); + + // type + QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded typeCoder = encodedType; + encodedType = typeCoder; // determine true length + dataAt += encodedType.size(); + bytesRead += encodedType.size(); + quint32 type = typeCoder; + _type = (EntityTypes::EntityType)type; - // _created - quint64 createdFromBuffer = 0; - memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer)); - dataAt += sizeof(createdFromBuffer); - bytesRead += sizeof(createdFromBuffer); + bool overwriteLocalData = true; // assume the new content overwrites our local data - quint64 now = usecTimestampNow(); - if (_created == UNKNOWN_CREATED_TIME) { - // we don't yet have a _created timestamp, so we accept this one - createdFromBuffer -= clockSkew; - if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { - createdFromBuffer = now; - } - _created = createdFromBuffer; + // _created + quint64 createdFromBuffer = 0; + memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer)); + dataAt += sizeof(createdFromBuffer); + bytesRead += sizeof(createdFromBuffer); + + quint64 now = usecTimestampNow(); + if (_created == UNKNOWN_CREATED_TIME) { + // we don't yet have a _created timestamp, so we accept this one + createdFromBuffer -= clockSkew; + if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { + createdFromBuffer = now; } + _created = createdFromBuffer; + } + #ifdef WANT_DEBUG + quint64 lastEdited = getLastEdited(); + float editedAgo = getEditedAgo(); + QString agoAsString = formatSecondsElapsed(editedAgo); + QString ageAsString = formatSecondsElapsed(getAge()); + qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; + qCDebug(entities) << "------------------------------------------"; + debugDump(); + qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << " _created =" << _created; + qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; + qCDebug(entities) << " lastEdited =" << lastEdited; + qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; + #endif + + quint64 lastEditedFromBuffer = 0; + quint64 lastEditedFromBufferAdjusted = 0; + + // TODO: we could make this encoded as a delta from _created + // _lastEdited + memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer)); + dataAt += sizeof(lastEditedFromBuffer); + bytesRead += sizeof(lastEditedFromBuffer); + lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew; + if (lastEditedFromBufferAdjusted > now) { + lastEditedFromBufferAdjusted = now; + } + + bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); + + #ifdef WANT_DEBUG + qCDebug(entities) << "data from server **************** "; + qCDebug(entities) << " entityItemID:" << getEntityItemID(); + qCDebug(entities) << " now:" << now; + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); + qCDebug(entities) << " clockSkew:" << debugTimeOnly(clockSkew); + qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); + qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); + qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; + #endif + + bool ignoreServerPacket = false; // assume we'll use this server packet + + // If this packet is from the same server edit as the last packet we accepted from the server + // we probably want to use it. + if (fromSameServerEdit) { + // If this is from the same sever packet, then check against any local changes since we got + // the most recent packet from this server time + if (_lastEdited > _lastEditedFromRemote) { + ignoreServerPacket = true; + } + } else { + // If this isn't from the same sever packet, then honor our skew adjusted times... + // If we've changed our local tree more recently than the new data from this packet + // then we will not be changing our values, instead we just read and skip the data + if (_lastEdited > lastEditedFromBufferAdjusted) { + ignoreServerPacket = true; + } + } + + if (ignoreServerPacket) { + overwriteLocalData = false; #ifdef WANT_DEBUG - quint64 lastEdited = getLastEdited(); - float editedAgo = getEditedAgo(); - QString agoAsString = formatSecondsElapsed(editedAgo); - QString ageAsString = formatSecondsElapsed(getAge()); - qCDebug(entities) << "------------------------------------------"; - qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; - qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << "IGNORING old data from server!!! ****************"; debugDump(); - qCDebug(entities) << "------------------------------------------"; - qCDebug(entities) << " _created =" << _created; - qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; - qCDebug(entities) << " lastEdited =" << lastEdited; - qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; #endif - - quint64 lastEditedFromBuffer = 0; - quint64 lastEditedFromBufferAdjusted = 0; - - // TODO: we could make this encoded as a delta from _created - // _lastEdited - memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer)); - dataAt += sizeof(lastEditedFromBuffer); - bytesRead += sizeof(lastEditedFromBuffer); - lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew; - if (lastEditedFromBufferAdjusted > now) { - lastEditedFromBufferAdjusted = now; - } - - bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); - + } else { #ifdef WANT_DEBUG - qCDebug(entities) << "data from server **************** "; - qCDebug(entities) << " entityItemID:" << getEntityItemID(); - qCDebug(entities) << " now:" << now; - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); - qCDebug(entities) << " clockSkew:" << debugTimeOnly(clockSkew); - qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); - qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); - qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); - qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; + qCDebug(entities) << "USING NEW data from server!!! ****************"; + debugDump(); #endif - bool ignoreServerPacket = false; // assume we'll use this server packet - - // If this packet is from the same server edit as the last packet we accepted from the server - // we probably want to use it. - if (fromSameServerEdit) { - // If this is from the same sever packet, then check against any local changes since we got - // the most recent packet from this server time - if (_lastEdited > _lastEditedFromRemote) { - ignoreServerPacket = true; - } - } else { - // If this isn't from the same sever packet, then honor our skew adjusted times... - // If we've changed our local tree more recently than the new data from this packet - // then we will not be changing our values, instead we just read and skip the data - if (_lastEdited > lastEditedFromBufferAdjusted) { - ignoreServerPacket = true; - } - } + // don't allow _lastEdited to be in the future + _lastEdited = lastEditedFromBufferAdjusted; + _lastEditedFromRemote = now; + _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - if (ignoreServerPacket) { - overwriteLocalData = false; - #ifdef WANT_DEBUG - qCDebug(entities) << "IGNORING old data from server!!! ****************"; - debugDump(); - #endif - } else { + // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed + // the properties out of the bitstream (see below)) + somethingChangedNotification(); // notify derived classes that something has changed + } - #ifdef WANT_DEBUG - qCDebug(entities) << "USING NEW data from server!!! ****************"; - debugDump(); - #endif - - // don't allow _lastEdited to be in the future - _lastEdited = lastEditedFromBufferAdjusted; - _lastEditedFromRemote = now; - _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - - // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed - // the properties out of the bitstream (see below)) - somethingChangedNotification(); // notify derived classes that something has changed - } - - // last updated is stored as ByteCountCoded delta from lastEdited - QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded updateDeltaCoder = encodedUpdateDelta; - quint64 updateDelta = updateDeltaCoder; + // last updated is stored as ByteCountCoded delta from lastEdited + QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded updateDeltaCoder = encodedUpdateDelta; + quint64 updateDelta = updateDeltaCoder; + if (overwriteLocalData) { + _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that + #ifdef WANT_DEBUG + qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); + qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); + qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + #endif + } + encodedUpdateDelta = updateDeltaCoder; // determine true length + dataAt += encodedUpdateDelta.size(); + bytesRead += encodedUpdateDelta.size(); + + // Newer bitstreams will have a last simulated and a last updated value + if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) { + // last simulated is stored as ByteCountCoded delta from lastEdited + QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded simulatedDeltaCoder = encodedSimulatedDelta; + quint64 simulatedDelta = simulatedDeltaCoder; if (overwriteLocalData) { - _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that + _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that #ifdef WANT_DEBUG - qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); + qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); #endif } - encodedUpdateDelta = updateDeltaCoder; // determine true length - dataAt += encodedUpdateDelta.size(); - bytesRead += encodedUpdateDelta.size(); - - // Newer bitstreams will have a last simulated and a last updated value - if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) { - // last simulated is stored as ByteCountCoded delta from lastEdited - QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded simulatedDeltaCoder = encodedSimulatedDelta; - quint64 simulatedDelta = simulatedDeltaCoder; + encodedSimulatedDelta = simulatedDeltaCoder; // determine true length + dataAt += encodedSimulatedDelta.size(); + bytesRead += encodedSimulatedDelta.size(); + } + + #ifdef WANT_DEBUG + if (overwriteLocalData) { + qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " getLastSimulated:" << debugTime(getLastSimulated(), now); + qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now); + } + #endif + + + // Property Flags + QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size + EntityPropertyFlags propertyFlags = encodedPropertyFlags; + dataAt += propertyFlags.getEncodedLength(); + bytesRead += propertyFlags.getEncodedLength(); + bool useMeters = (args.bitstreamVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + } else { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionInDomainUnits); + } + + // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS + if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { + if (propertyFlags.getHasProperty(PROP_RADIUS)) { + float fromBuffer; + memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); + dataAt += sizeof(fromBuffer); + bytesRead += sizeof(fromBuffer); if (overwriteLocalData) { - _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that - #ifdef WANT_DEBUG - qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); - qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); - qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); - #endif + setRadius(fromBuffer); } - encodedSimulatedDelta = simulatedDeltaCoder; // determine true length - dataAt += encodedSimulatedDelta.size(); - bytesRead += encodedSimulatedDelta.size(); } - - #ifdef WANT_DEBUG - if (overwriteLocalData) { - qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " getLastSimulated:" << debugTime(getLastSimulated(), now); - qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now); - } - #endif - - - // Property Flags - QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size - EntityPropertyFlags propertyFlags = encodedPropertyFlags; - dataAt += propertyFlags.getEncodedLength(); - bytesRead += propertyFlags.getEncodedLength(); - bool useMeters = (args.bitstreamVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS); + } else { if (useMeters) { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); } else { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionInDomainUnits); - } - - // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS - if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { - if (propertyFlags.getHasProperty(PROP_RADIUS)) { - float fromBuffer; - memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); - dataAt += sizeof(fromBuffer); - bytesRead += sizeof(fromBuffer); - if (overwriteLocalData) { - setRadius(fromBuffer); - } - } - } else { - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); - } else { - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensionsInDomainUnits); - } - } - - READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); - READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); - } else { - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); - } - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); - } - - READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); - READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); - READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); - READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); - READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); - READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); - } else { - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); - } - READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); - READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); - READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); - READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); - READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); - READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); - - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); - } - - if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { - READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - } - - READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); - READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); - bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); - - //////////////////////////////////// - // WARNING: Do not add stream content here after the subclass. Always add it before the subclass - // - // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover - // by doing this parsing here... but it's not likely going to fully recover the content. - // - // TODO: Remove this conde once we've sufficiently migrated content past this damaged version - if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) { - READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - } - - if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) { - // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more - // closely match where the entities should be if they'd stepped forward in time to "now". The server - // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore - // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and - // use our simulation helper routine to get a best estimate of where the entity should be. - const float MIN_TIME_SKIP = 0.0f; - const float MAX_TIME_SKIP = 1.0f; // in seconds - float skipTimeForward = glm::clamp((float)(now - _lastSimulated) / (float)(USECS_PER_SECOND), - MIN_TIME_SKIP, MAX_TIME_SKIP); - if (skipTimeForward > 0.0f) { - #ifdef WANT_DEBUG - qCDebug(entities) << "skipTimeForward:" << skipTimeForward; - #endif - - // we want to extrapolate the motion forward to compensate for packet travel time, but - // we don't want the side effect of flag setting. - simulateKinematicMotion(skipTimeForward, false); - } - _lastSimulated = now; + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensionsInDomainUnits); } } + READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); + READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); + } else { + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); + } + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); + } + + READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); + READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); + READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); + } else { + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); + } + READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); + READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); + READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); + READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); + READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); + + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + // we always accept the server's notion of simulatorID, so we fake overwriteLocalData as true + // before we try to READ_ENTITY_PROPERTY it + bool temp = overwriteLocalData; + overwriteLocalData = true; + READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); + overwriteLocalData = temp; + } + + if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { + READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + } + + READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); + READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); + bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); + + //////////////////////////////////// + // WARNING: Do not add stream content here after the subclass. Always add it before the subclass + // + // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover + // by doing this parsing here... but it's not likely going to fully recover the content. + // + // TODO: Remove this conde once we've sufficiently migrated content past this damaged version + if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) { + READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + } + + if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) { + // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more + // closely match where the entities should be if they'd stepped forward in time to "now". The server + // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore + // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and + // use our simulation helper routine to get a best estimate of where the entity should be. + const float MIN_TIME_SKIP = 0.0f; + const float MAX_TIME_SKIP = 1.0f; // in seconds + float skipTimeForward = glm::clamp((float)(now - _lastSimulated) / (float)(USECS_PER_SECOND), + MIN_TIME_SKIP, MAX_TIME_SKIP); + if (skipTimeForward > 0.0f) { + #ifdef WANT_DEBUG + qCDebug(entities) << "skipTimeForward:" << skipTimeForward; + #endif + + // we want to extrapolate the motion forward to compensate for packet travel time, but + // we don't want the side effect of flag setting. + simulateKinematicMotion(skipTimeForward, false); + } + _lastSimulated = now; + } auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); - if (_simulatorID == myNodeID && !_simulatorID.isNull()) { - // the packet that produced this bitstream originally came from physics simulations performed by - // this node, so our version has to be newer than what the packet contained. + if (overwriteLocalData && _simulatorID == myNodeID && !_simulatorID.isNull()) { + // we own the simulation, so we keep our transform+velocities and remove any related dirty flags + // rather than accept the values in the packet _position = savePosition; _rotation = saveRotation; - // _velocity = saveVelocity; - // _angularVelocity = saveAngularVelocity; - // _gravity = saveGravity; - // _acceleration = saveAcceleration; + _velocity = saveVelocity; + _angularVelocity = saveAngularVelocity; + _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); } return bytesRead; @@ -948,40 +948,25 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); if (somethingChanged) { - somethingChangedNotification(); // notify derived classes that something has changed uint64_t now = usecTimestampNow(); #ifdef WANT_DEBUG int elapsed = now - getLastEdited(); qCDebug(entities) << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed << "now=" << now << " getLastEdited()=" << getLastEdited(); #endif - if (_created != UNKNOWN_CREATED_TIME) { - setLastEdited(now); + setLastEdited(now); + somethingChangedNotification(); // notify derived classes that something has changed + if (_created == UNKNOWN_CREATED_TIME) { + _created = now; } if (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES)) { - // TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases. + // anything that sets the transform or velocity must update _lastSimulated which is used + // for kinematic extrapolation (e.g. we want to extrapolate forward from this moment + // when position and/or velocity was changed). _lastSimulated = now; } } - // timestamps - quint64 timestamp = properties.getCreated(); - if (_created == UNKNOWN_CREATED_TIME && timestamp != UNKNOWN_CREATED_TIME) { - quint64 now = usecTimestampNow(); - if (timestamp > now) { - timestamp = now; - } - _created = timestamp; - - timestamp = properties.getLastEdited(); - if (timestamp > now) { - timestamp = now; - } else if (timestamp < _created) { - timestamp = _created; - } - _lastEdited = timestamp; - } - return somethingChanged; } diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 6c4b3dad75..9a24aabb34 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -132,7 +132,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP; _entity->simulateKinematicMotion(dt); - _entity->setLastSimulated(usecTimestampNow()); // bypass const-ness so we can remember the step const_cast(this)->_lastKinematicStep = thisStep; @@ -401,13 +400,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); - // we only update lastEdited when we're sending new physics data - quint64 lastSimulated = _entity->getLastSimulated(); - _entity->setLastEdited(lastSimulated); - properties.setLastEdited(lastSimulated); + // set the LastEdited of the properties but NOT the entity itself + quint64 now = usecTimestampNow(); + properties.setLastEdited(now); #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); + quint64 lastSimulated = _entity->getLastSimulated(); qCDebug(physics) << "EntityMotionState::sendUpdate()"; qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------"; From 63c19f7c111ae36b74be771929dfc2a85b206806 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 27 May 2015 20:19:59 -0700 Subject: [PATCH 33/38] fix build buster from bad merge --- libraries/entities/src/EntityItem.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index bbf8879a5d..b4941b833b 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -333,8 +333,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. - glm::vec3 savePosition = _position; - glm::quat saveRotation = _rotation; + glm::vec3 savePosition = getPosition(); + glm::quat saveRotation = getRotation(); glm::vec3 saveVelocity = _velocity; glm::vec3 saveAngularVelocity = _angularVelocity; @@ -625,8 +625,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef if (overwriteLocalData && _simulatorID == myNodeID && !_simulatorID.isNull()) { // we own the simulation, so we keep our transform+velocities and remove any related dirty flags // rather than accept the values in the packet - _position = savePosition; - _rotation = saveRotation; + setPosition(savePosition); + setRotation(saveRotation); _velocity = saveVelocity; _angularVelocity = saveAngularVelocity; _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); From 10c2f3f561a241393ca47c72a25d2970ef1d4f4e Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 14:51:57 +0200 Subject: [PATCH 34/38] More TextRenderer work --- interface/src/ui/overlays/TextOverlay.cpp | 2 + interface/src/ui/overlays/TextOverlay.h | 2 +- .../src/RenderableTextEntityItem.cpp | 63 ++-- libraries/gpu/src/gpu/GLBackendInput.cpp | 26 +- libraries/gpu/src/gpu/Stream.cpp | 4 +- libraries/gpu/src/gpu/Texture.h | 20 +- libraries/render-utils/src/TextRenderer.cpp | 272 ++++++++++++------ libraries/render-utils/src/TextRenderer.h | 5 +- libraries/render-utils/src/sdf_text.slf | 4 +- libraries/render-utils/src/sdf_text.slv | 5 - 10 files changed, 244 insertions(+), 159 deletions(-) diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index 54156d5d2b..e709bbd9fc 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -24,6 +24,7 @@ TextOverlay::TextOverlay() : _topMargin(DEFAULT_MARGIN), _fontSize(DEFAULT_FONTSIZE) { + _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); } TextOverlay::TextOverlay(const TextOverlay* textOverlay) : @@ -35,6 +36,7 @@ TextOverlay::TextOverlay(const TextOverlay* textOverlay) : _topMargin(textOverlay->_topMargin), _fontSize(textOverlay->_fontSize) { + _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); } TextOverlay::~TextOverlay() { diff --git a/interface/src/ui/overlays/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h index 5b77be0cac..5a715ebfdf 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -60,7 +60,7 @@ public: private: - TextRenderer* _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); + TextRenderer* _textRenderer = nullptr; QString _text; xColor _backgroundColor; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index a3bfd981ec..2eeec77d68 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -20,52 +20,39 @@ #include "RenderableTextEntityItem.h" #include "GLMHelpers.h" - EntityItem* RenderableTextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return new RenderableTextEntityItem(entityID, properties); } void RenderableTextEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableTextEntityItem::render"); - assert(getType() == EntityTypes::Text); - glm::vec3 position = getPosition(); + Q_ASSERT(getType() == EntityTypes::Text); + + static const float SLIGHTLY_BEHIND = -0.005f; + glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), 1.0f); + glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), 1.0f); glm::vec3 dimensions = getDimensions(); - glm::vec3 halfDimensions = dimensions / 2.0f; - glm::quat rotation = getRotation(); - float leftMargin = 0.1f; - float topMargin = 0.1f; - - //qCDebug(entitytree) << "RenderableTextEntityItem::render() id:" << getEntityItemID() << "text:" << getText(); - - glPushMatrix(); - { - glTranslatef(position.x, position.y, position.z); - glm::vec3 axis = glm::axis(rotation); - glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - - float alpha = 1.0f; //getBackgroundAlpha(); - static const float SLIGHTLY_BEHIND = -0.005f; - - glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND); - glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND); - - // TODO: Determine if we want these entities to have the deferred lighting effect? I think we do, so that the color - // used for a sphere, or box have the same look as those used on a text entity. - //DependencyManager::get()->bindSimpleProgram(); - DependencyManager::get()->renderQuad(topLeft, bottomRight, glm::vec4(toGlm(getBackgroundColorX()), alpha)); - //DependencyManager::get()->releaseSimpleProgram(); - - glTranslatef(-(halfDimensions.x - leftMargin), halfDimensions.y - topMargin, 0.0f); - glm::vec4 textColor(toGlm(getTextColorX()), alpha); - // this is a ratio determined through experimentation - const float scaleFactor = 0.08f * _lineHeight; - glScalef(scaleFactor, -scaleFactor, scaleFactor); - glm::vec2 bounds(dimensions.x / scaleFactor, dimensions.y / scaleFactor); - _textRenderer->draw(0, 0, _text, textColor, bounds); - } - glPopMatrix(); + glm::vec2 bounds = glm::vec2(dimensions.x, dimensions.y); + + Transform transformToTopLeft = getTransformToCenter(); + transformToTopLeft.postTranslate(glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left + transformToTopLeft.setScale(1.0f); // Use a scale of one so that the text is not deformed + + // Batch render calls + Q_ASSERT(args->_batch); + gpu::Batch& batch = *args->_batch; + batch.setModelTransform(transformToTopLeft); + + // Render background + glm::vec3 minCorner = glm::vec3(0.0f, -dimensions.y, SLIGHTLY_BEHIND); + glm::vec3 maxCorner = glm::vec3(dimensions.x, 0.0f, SLIGHTLY_BEHIND); + DependencyManager::get()->renderQuad(batch, minCorner, maxCorner, backgroundColor); + + float scale = _lineHeight / _textRenderer->getRowHeight(); + transformToTopLeft.setScale(scale); + batch.setModelTransform(transformToTopLeft); + _textRenderer->draw3D(batch, 0.0f, 0.0f, _text, textColor, bounds / scale); } - diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu/src/gpu/GLBackendInput.cpp index bef3289161..aac7b56bc2 100755 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu/src/gpu/GLBackendInput.cpp @@ -131,23 +131,23 @@ void GLBackend::updateInput() { #endif if (useClientState) { switch (slot) { - case Stream::POSITION: - glVertexPointer(count, type, stride, reinterpret_cast(pointer)); - break; - case Stream::NORMAL: - glNormalPointer(type, stride, reinterpret_cast(pointer)); - break; - case Stream::COLOR: - glColorPointer(count, type, stride, reinterpret_cast(pointer)); - break; - case Stream::TEXCOORD: - glTexCoordPointer(count, type, stride, reinterpret_cast(pointer)); - break; + case Stream::POSITION: + glVertexPointer(count, type, stride, reinterpret_cast(pointer)); + break; + case Stream::NORMAL: + glNormalPointer(type, stride, reinterpret_cast(pointer)); + break; + case Stream::COLOR: + glColorPointer(count, type, stride, reinterpret_cast(pointer)); + break; + case Stream::TEXCOORD: + glTexCoordPointer(count, type, stride, reinterpret_cast(pointer)); + break; }; } else { GLboolean isNormalized = attrib._element.isNormalized(); glVertexAttribPointer(slot, count, type, isNormalized, stride, - reinterpret_cast(pointer)); + reinterpret_cast(pointer)); } (void) CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/Stream.cpp b/libraries/gpu/src/gpu/Stream.cpp index e23a730370..634545b4dd 100644 --- a/libraries/gpu/src/gpu/Stream.cpp +++ b/libraries/gpu/src/gpu/Stream.cpp @@ -10,8 +10,8 @@ // #include "Stream.h" - -#include //min max and more + +#include //min max and more using namespace gpu; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 0d9664f1ab..9036f0f6db 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -17,11 +17,11 @@ namespace gpu { -// THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated -// with the cube texture -class Texture; -class SphericalHarmonics { -public: +// THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated +// with the cube texture +class Texture; +class SphericalHarmonics { +public: glm::vec3 L00 ; float spare0; glm::vec3 L1m1 ; float spare1; glm::vec3 L10 ; float spare2; @@ -44,15 +44,15 @@ public: VINE_STREET_KITCHEN, BREEZEWAY, CAMPUS_SUNSET, - FUNSTON_BEACH_SUNSET, - - NUM_PRESET, + FUNSTON_BEACH_SUNSET, + + NUM_PRESET, }; void assignPreset(int p); void evalFromTexture(const Texture& texture); -}; +}; typedef std::shared_ptr< SphericalHarmonics > SHPointer; class Sampler { @@ -438,7 +438,7 @@ public: explicit operator bool() const { return bool(_texture); } bool operator !() const { return (!_texture); } }; -typedef std::vector TextureViews; +typedef std::vector TextureViews; }; diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index be6a847031..f48dddadbe 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -26,6 +26,12 @@ #include "sdf_text_vert.h" #include "sdf_text_frag.h" +#include "GeometryCache.h" +#include "DeferredLightingEffect.h" + +// 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 const float DEFAULT_POINT_SIZE = 12; // Helper functions for reading binary data from an IO device @@ -44,28 +50,6 @@ void fillBuffer(QBuffer& buffer, T (&t)[N]) { buffer.setData((const char*) t, N); } -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.topLeft(), tr.bottomLeft()); - vertices[3] = TextureVertex(r.topRight(), tr.bottomRight()); - } -}; - -// 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; @@ -76,7 +60,8 @@ struct Glyph { float d; // xadvance - adjusts character positioning size_t indexOffset; - QRectF bounds() const { return glmToRect(offset, size); } + // We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect + QRectF bounds() const { return glmToRect(offset, size).translated(0.0f, -size.y); } QRectF textureBounds() const { return glmToRect(texOffset, texSize); } void read(QIODevice& in); @@ -93,6 +78,56 @@ void Glyph::read(QIODevice& in) { texSize = size; } +struct TextureVertex { + glm::vec2 pos; + glm::vec2 tex; + TextureVertex() {} + TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {} +}; + +struct QuadBuilder { + TextureVertex vertices[4]; + QuadBuilder(const glm::vec2& min, const glm::vec2& size, + const glm::vec2& texMin, const glm::vec2& texSize) { + // min = bottomLeft + vertices[0] = TextureVertex(min, + texMin + glm::vec2(0.0f, texSize.y)); + vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f), + texMin + texSize); + vertices[2] = TextureVertex(min + size, + texMin + glm::vec2(texSize.x, 0.0f)); + vertices[3] = TextureVertex(min + glm::vec2(0.0f, size.y), + texMin); + } + QuadBuilder(const Glyph& glyph, const glm::vec2& offset) : + QuadBuilder(offset + glyph.offset - glm::vec2(0.0f, glyph.size.y), glyph.size, + glyph.texOffset, glyph.texSize) {} + +}; + +QDebug operator<<(QDebug debug, glm::vec2& value) { + debug << value.x << value.y; + return debug; +} + +QDebug operator<<(QDebug debug, glm::vec3& value) { + debug << value.x << value.y << value.z; + return debug; +} + +QDebug operator<<(QDebug debug, TextureVertex& value) { + debug << "Pos:" << value.pos << ", Tex:" << value.tex; + return debug; +} + +QDebug operator<<(QDebug debug, QuadBuilder& value) { + debug << '\n' << value.vertices[0] + << '\n' << value.vertices[1] + << '\n' << value.vertices[2] + << '\n' << value.vertices[3]; + return debug; +} + class Font { public: Font(); @@ -100,6 +135,7 @@ public: void read(QIODevice& path); glm::vec2 computeExtent(const QString& str) const; + float getRowHeight() const { return _rowHeight; } // Render string to batch void drawString(gpu::Batch& batch, float x, float y, const QString& str, @@ -113,6 +149,8 @@ private: const Glyph& getGlyph(const QChar& c) const; + void setupGPU(); + // maps characters to cached glyph info // HACK... the operator[] const for QHash returns a // copy of the value, not a const value reference, so @@ -129,14 +167,20 @@ private: float _descent = 0.0f; float _spaceWidth = 0.0f; + bool _initialized = false; + // gpu structures - static const unsigned int VERTEX_BUFFER_CHANNEL = 0; gpu::PipelinePointer _pipeline; gpu::TexturePointer _texture; gpu::Stream::FormatPointer _format; - gpu::BufferPointer _vertices; + gpu::BufferPointer _verticesBuffer; + gpu::BufferStreamPointer _stream; unsigned int _numVertices = 0; + int _fontLoc = -1; + int _outlineLoc = -1; + int _colorLoc = -1; + // last string render characteristics QString _lastStringRendered; glm::vec2 _lastBounds; @@ -283,17 +327,6 @@ void Font::read(QIODevice& in) { if (!image.loadFromData(in.readAll(), "PNG")) { qFatal("Failed to read SDFF image"); } - gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - if (image.hasAlphaChannel()) { - formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); - formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); - } - _texture = gpu::TexturePointer( - gpu::Texture::create2D(formatGPU, image.width(), image.height(), - gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); - _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - _texture->autoGenerateMips(-1); _glyphs.clear(); glm::vec2 imageSize = toGlm(image.size()); @@ -305,61 +338,119 @@ void Font::read(QIODevice& in) { _glyphs[g.c] = g; }; - // Setup render pipeline - auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); - auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + qDebug() << _family << "size" << image.size(); + qDebug() << _family << "format" << image.format(); + image = image.convertToFormat(QImage::Format_RGBA8888); + qDebug() << _family << "size" << image.size(); + qDebug() << _family << "format" << image.format(); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("Outline", 0)); - slotBindings.insert(gpu::Shader::Binding("Offset", 1)); - slotBindings.insert(gpu::Shader::Binding("Color", 2)); - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); - - // Setup rendering structures - static const int OFFSET = offsetof(TextureVertex, tex); - _format = gpu::Stream::FormatPointer(new gpu::Stream::Format()); - _format->setAttribute(gpu::Stream::POSITION, VERTEX_BUFFER_CHANNEL, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), 0); - _format->setAttribute(gpu::Stream::TEXCOORD, VERTEX_BUFFER_CHANNEL, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + if (image.hasAlphaChannel()) { + formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); + } + _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + _texture->autoGenerateMips(-1); +} + +#include +void Font::setupGPU() { + if (!_initialized) { + _initialized = true; + + // Setup render pipeline + auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); + auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + _fontLoc = program->getTextures().findLocation("Font"); + _outlineLoc = program->getUniforms().findLocation("Outline"); + _colorLoc = program->getUniforms().findLocation("Color"); + + + auto f = [&] (QString str, const gpu::Shader::SlotSet& set) { + if (set.size() == 0) { + return; + } + qDebug() << str << set.size(); + for (auto slot : set) { + qDebug() << " " << QString::fromStdString(slot._name) << slot._location; + } + }; + f("getUniforms:", program->getUniforms()); + f("getBuffers:", program->getBuffers()); + f("getTextures:", program->getTextures()); + f("getSamplers:", program->getSamplers()); + f("getInputs:", program->getInputs()); + f("getOutputs:", program->getOutputs()); + + qDebug() << "Texture:" << _texture.get(); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + + // Sanity checks + static const int OFFSET = offsetof(TextureVertex, tex); + assert(OFFSET == sizeof(glm::vec2)); + assert(sizeof(glm::vec2) == 2 * sizeof(float)); + assert(sizeof(glm::vec3) == 3 * sizeof(float)); + assert(sizeof(TextureVertex) == sizeof(glm::vec2) + sizeof(glm::vec2)); + assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); + + // Setup rendering structures + _format.reset(new gpu::Stream::Format()); + _format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); + _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); + } } void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, TextRenderer::EffectType effectType, const glm::vec2& bounds) { - // Top left of text - glm::vec2 advance = glm::vec2(0.0f, 0.0f); + if (str == "") { + return; + } if (str != _lastStringRendered || bounds != _lastBounds) { - _vertices = gpu::BufferPointer(new gpu::Buffer()); + _verticesBuffer.reset(new gpu::Buffer()); _numVertices = 0; _lastStringRendered = str; _lastBounds = bounds; + // Top left of text + glm::vec2 advance = glm::vec2(x, y); foreach(const QString& token, tokenizeForWrapping(str)) { - bool isNewLine = token == QString('\n'); + bool isNewLine = (token == QString('\n')); bool forceNewLine = false; // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x)) { + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > bounds.x)) { // We are out of the x bound, force new line forceNewLine = true; } if (isNewLine || forceNewLine) { // Character return, move the advance to a new line - advance = glm::vec2(0.0f, advance.y + _rowHeight); + advance = glm::vec2(0.0f, advance.y - _rowHeight); + if (isNewLine) { // No need to draw anything, go directly to next token continue; + } else if (computeExtent(token).x > bounds.x) { + // token will never fit, stop drawing + break; } } - if ((bounds.y != -1) && (advance.y + _fontSize > bounds.y)) { + if ((bounds.y != -1) && (advance.y - _fontSize < -bounds.y)) { // We are out of the y bound, stop drawing break; } @@ -369,10 +460,8 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c for (auto c : token) { auto glyph = _glyphs[c]; - // Build translated quad and add it to the buffer - QuadBuilder qd(glyph.bounds().translated(advance.x, advance.y), - glyph.textureBounds()); - _vertices->append(sizeof(qd.vertices), (const gpu::Byte*)qd.vertices); + QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _fontSize)); + _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); _numVertices += 4; // Advance by glyph size @@ -385,17 +474,15 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c } } + setupGPU(); batch.setPipeline(_pipeline); - batch.setUniformTexture(2, _texture); - batch._glUniform1f(0, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); - batch._glUniform2f(1, x, y); - batch._glUniform4fv(2, 4, (const GLfloat*)&color); - QRectF rect(0.0f, 0.0f, 1.0f, 1.0f); - QuadBuilder qd(rect, rect); - _vertices->append(sizeof(QuadBuilder), (const gpu::Byte*)qd.vertices); + batch.setUniformTexture(_fontLoc, _texture); + batch._glUniform1f(_outlineLoc, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); + batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)&color); + batch.setInputFormat(_format); - batch.setInputBuffer(VERTEX_BUFFER_CHANNEL, _vertices); - batch.draw(gpu::QUADS, _numVertices); + batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); + batch.draw(gpu::QUADS, _numVertices, 0); } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -413,7 +500,7 @@ TextRenderer::TextRenderer(const char* family, float pointSize, int weight, bool _effectType(effect), _effectThickness(effectThickness), _pointSize(pointSize), - _color(color), + _color(toGlm(color)), _font(loadFont(family)) { if (!_font) { qWarning() << "Unable to load font with family " << family; @@ -438,24 +525,37 @@ glm::vec2 TextRenderer::computeExtent(const QString& str) const { return glm::vec2(0.1f,0.1f); } +float TextRenderer::getRowHeight() const { + if (_font) { + return _font->getRowHeight(); + } + return 1.0f; +} + void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { // The font does all the OpenGL work if (_font) { -// gpu::Batch batch; -// draw(batch, x, y, str, color, bounds); -// gpu::GLBackend::renderBatch(batch, true); + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; + glPushMatrix(); + gpu::Batch batch; + Transform transform; + transform.setTranslation(glm::vec3(x, y, 0.0f)); + transform.setScale(glm::vec3(scale, -scale, scale)); + batch.setModelTransform(transform); + draw3D(batch, 0.0f, 0.0f, str, color, bounds); + gpu::GLBackend::renderBatch(batch, true); + glPopMatrix(); } } -void TextRenderer::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, +void TextRenderer::draw3D(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { // The font does all the OpenGL work if (_font) { glm::vec4 actualColor(color); if (actualColor.r < 0) { - actualColor = toGlm(_color); + actualColor = _color; } - _font->drawString(batch, x, y, str, actualColor, _effectType, bounds); } } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 7bd0781f96..0c7faa9e6b 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -50,10 +50,11 @@ public: ~TextRenderer(); glm::vec2 computeExtent(const QString& str) const; + float getRowHeight() const; void draw(float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), const glm::vec2& bounds = glm::vec2(-1.0f)); - void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), + void draw3D(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), const glm::vec2& bounds = glm::vec2(-1.0f)); private: @@ -69,7 +70,7 @@ private: const float _pointSize; // text color - const QColor _color; + const glm::vec4 _color; Font* _font; }; diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf index ad5b8fe2c2..3980045d08 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -21,7 +21,7 @@ const float outlineExpansion = 0.2; void main() { // retrieve signed distance - float sdf = texture2D(Font, gl_TexCoord[0].xy).r; + float sdf = texture2D(Font, gl_TexCoord[0].xy).g; if (Outline == 1.0f) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; @@ -41,7 +41,7 @@ void main() { if (a < 0.01) { discard; } - + // final color gl_FragColor = vec4(Color.rgb, a); } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv index 142d8d41d9..f7c35a257c 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -13,8 +13,6 @@ <$declareStandardTransform()$> -uniform float Offset[2]; - void main() { gl_TexCoord[0] = gl_MultiTexCoord0; @@ -22,7 +20,4 @@ void main() { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> - - gl_Position.x += Offset[0]; - gl_Position.y += Offset[1]; } \ No newline at end of file From c4ab18736de29b3cb7fea260c603c5818c687d82 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 15:20:40 +0200 Subject: [PATCH 35/38] Restore TextRenderer and sdf_text --- libraries/render-utils/src/TextRenderer.cpp | 668 ++++++++++---------- libraries/render-utils/src/TextRenderer.h | 34 +- libraries/render-utils/src/sdf_text.slf | 48 +- libraries/render-utils/src/sdf_text.slv | 17 +- 4 files changed, 394 insertions(+), 373 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index f48dddadbe..87cf9b2728 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -9,47 +9,55 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "TextRenderer.h" #include -#include - -#include -#include +#include +#include +#include #include +#include #include +// FIXME, decouple from the GL headers +#include +#include +#include +#include + +#include +#include + +#include "gpu/GLBackend.h" +#include "gpu/Stream.h" + #include "GLMHelpers.h" #include "MatrixStack.h" #include "RenderUtilsLogging.h" +#include "TextRenderer.h" #include "sdf_text_vert.h" #include "sdf_text_frag.h" -#include "GeometryCache.h" -#include "DeferredLightingEffect.h" - -// 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 -const float DEFAULT_POINT_SIZE = 12; - // Helper functions for reading binary data from an IO device template -void readStream(QIODevice& in, T& t) { +void readStream(QIODevice & in, T & t) { in.read((char*) &t, sizeof(t)); } template -void readStream(QIODevice& in, T (&t)[N]) { +void readStream(QIODevice & in, T (&t)[N]) { in.read((char*) t, N); } template -void fillBuffer(QBuffer& buffer, T (&t)[N]) { +void fillBuffer(QBuffer & buffer, T (&t)[N]) { buffer.setData((const char*) t, N); } +// FIXME support the shadow effect, or remove it from the API +// FIXME figure out how to improve the anti-aliasing on the +// interior of the outline fonts + // stores the font metrics for a single character struct Glyph { QChar c; @@ -60,14 +68,13 @@ struct Glyph { float d; // xadvance - adjusts character positioning size_t indexOffset; - // We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect - QRectF bounds() const { return glmToRect(offset, size).translated(0.0f, -size.y); } - QRectF textureBounds() const { return glmToRect(texOffset, texSize); } + QRectF bounds() const; + QRectF textureBounds(const glm::vec2 & textureSize) const; - void read(QIODevice& in); + void read(QIODevice & in); }; -void Glyph::read(QIODevice& in) { +void Glyph::read(QIODevice & in) { uint16_t charcode; readStream(in, charcode); c = charcode; @@ -78,112 +85,64 @@ void Glyph::read(QIODevice& in) { texSize = size; } -struct TextureVertex { - glm::vec2 pos; - glm::vec2 tex; - TextureVertex() {} - TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {} -}; - -struct QuadBuilder { - TextureVertex vertices[4]; - QuadBuilder(const glm::vec2& min, const glm::vec2& size, - const glm::vec2& texMin, const glm::vec2& texSize) { - // min = bottomLeft - vertices[0] = TextureVertex(min, - texMin + glm::vec2(0.0f, texSize.y)); - vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f), - texMin + texSize); - vertices[2] = TextureVertex(min + size, - texMin + glm::vec2(texSize.x, 0.0f)); - vertices[3] = TextureVertex(min + glm::vec2(0.0f, size.y), - texMin); - } - QuadBuilder(const Glyph& glyph, const glm::vec2& offset) : - QuadBuilder(offset + glyph.offset - glm::vec2(0.0f, glyph.size.y), glyph.size, - glyph.texOffset, glyph.texSize) {} - -}; - -QDebug operator<<(QDebug debug, glm::vec2& value) { - debug << value.x << value.y; - return debug; -} - -QDebug operator<<(QDebug debug, glm::vec3& value) { - debug << value.x << value.y << value.z; - return debug; -} - -QDebug operator<<(QDebug debug, TextureVertex& value) { - debug << "Pos:" << value.pos << ", Tex:" << value.tex; - return debug; -} - -QDebug operator<<(QDebug debug, QuadBuilder& value) { - debug << '\n' << value.vertices[0] - << '\n' << value.vertices[1] - << '\n' << value.vertices[2] - << '\n' << value.vertices[3]; - return debug; -} +const float DEFAULT_POINT_SIZE = 12; class Font { public: + Font(); - void read(QIODevice& path); + using TexturePtr = QSharedPointer < QOpenGLTexture >; + using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >; + using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >; + using BufferPtr = QSharedPointer < QOpenGLBuffer >; - glm::vec2 computeExtent(const QString& str) const; - float getRowHeight() const { return _rowHeight; } - - // Render string to batch - void drawString(gpu::Batch& batch, float x, float y, const QString& str, + // 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 _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); private: - QStringList tokenizeForWrapping(const QString& str) const; - QStringList splitLines(const QString& str) const; - glm::vec2 computeTokenExtent(const QString& str) const; - - const Glyph& getGlyph(const QChar& c) const; - - void setupGPU(); - - // 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 _glyphs; - - // Font characteristics - QString _family; - float _fontSize = 0.0f; - float _rowHeight = 0.0f; - float _leading = 0.0f; - float _ascent = 0.0f; - float _descent = 0.0f; - float _spaceWidth = 0.0f; - - bool _initialized = false; - - // gpu structures - gpu::PipelinePointer _pipeline; - gpu::TexturePointer _texture; - gpu::Stream::FormatPointer _format; - gpu::BufferPointer _verticesBuffer; - gpu::BufferStreamPointer _stream; - unsigned int _numVertices = 0; - - int _fontLoc = -1; - int _outlineLoc = -1; - int _colorLoc = -1; - - // last string render characteristics - QString _lastStringRendered; - glm::vec2 _lastBounds; + QStringList tokenizeForWrapping(const QString & str) const; + + bool _initialized; }; static QHash LOADED_FONTS; @@ -230,7 +189,7 @@ Font* loadFont(const QString& family) { return LOADED_FONTS[family]; } -Font::Font() { +Font::Font() : _initialized(false) { static bool fontResourceInitComplete = false; if (!fontResourceInitComplete) { Q_INIT_RESOURCE(fonts); @@ -239,50 +198,13 @@ Font::Font() { } // NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member -const Glyph& Font::getGlyph(const QChar& c) const { +const Glyph & Font::getGlyph(const QChar & c) const { if (!_glyphs.contains(c)) { return _glyphs[QChar('?')]; } return _glyphs[c]; } -QStringList Font::splitLines(const QString& str) const { - return str.split('\n'); -} - -QStringList Font::tokenizeForWrapping(const QString& str) const { - QStringList tokens; - for(auto line : splitLines(str)) { - if (!tokens.empty()) { - tokens << QString('\n'); - } - tokens << line.split(' '); - } - return tokens; -} - -glm::vec2 Font::computeTokenExtent(const QString& token) const { - glm::vec2 advance(0, _fontSize); - foreach(QChar c, token) { - Q_ASSERT(c != '\n'); - advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d; - } - return advance; -} - -glm::vec2 Font::computeExtent(const QString& str) const { - glm::vec2 extent = glm::vec2(0.0f, 0.0f); - - QStringList tokens = splitLines(str); - foreach(const QString& token, tokens) { - glm::vec2 tokenExtent = computeTokenExtent(token); - extent.x = std::max(tokenExtent.x, extent.x); - } - extent.y = tokens.count() * _rowHeight; - - return extent; -} - void Font::read(QIODevice& in) { uint8_t header[4]; readStream(in, header); @@ -310,7 +232,7 @@ void Font::read(QIODevice& in) { readStream(in, _descent); readStream(in, _spaceWidth); _fontSize = _ascent + _descent; - _rowHeight = _fontSize + _leading; + _rowHeight = _fontSize + _descent; // Read character count uint16_t count; @@ -318,171 +240,266 @@ void Font::read(QIODevice& in) { // read metrics data for each character QVector glyphs(count); // std::for_each instead of Qt foreach because we need non-const references - std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph& g) { + std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) { g.read(in); }); // read image data - QImage image; - if (!image.loadFromData(in.readAll(), "PNG")) { + if (!_image.loadFromData(in.readAll(), "PNG")) { qFatal("Failed to read SDFF image"); } _glyphs.clear(); - glm::vec2 imageSize = toGlm(image.size()); 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; }; - - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); - image = image.convertToFormat(QImage::Format_RGBA8888); - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); - - gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - if (image.hasAlphaChannel()) { - formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); - formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); - } - _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), - gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); - _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - _texture->autoGenerateMips(-1); } -#include -void Font::setupGPU() { - if (!_initialized) { - _initialized = true; - - // Setup render pipeline - auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); - auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - _fontLoc = program->getTextures().findLocation("Font"); - _outlineLoc = program->getUniforms().findLocation("Outline"); - _colorLoc = program->getUniforms().findLocation("Color"); - - - auto f = [&] (QString str, const gpu::Shader::SlotSet& set) { - if (set.size() == 0) { - return; - } - qDebug() << str << set.size(); - for (auto slot : set) { - qDebug() << " " << QString::fromStdString(slot._name) << slot._location; - } - }; - f("getUniforms:", program->getUniforms()); - f("getBuffers:", program->getBuffers()); - f("getTextures:", program->getTextures()); - f("getSamplers:", program->getSamplers()); - f("getInputs:", program->getInputs()); - f("getOutputs:", program->getOutputs()); - - qDebug() << "Texture:" << _texture.get(); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); - - // Sanity checks - static const int OFFSET = offsetof(TextureVertex, tex); - assert(OFFSET == sizeof(glm::vec2)); - assert(sizeof(glm::vec2) == 2 * sizeof(float)); - assert(sizeof(glm::vec3) == 3 * sizeof(float)); - assert(sizeof(TextureVertex) == sizeof(glm::vec2) + sizeof(glm::vec2)); - assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); - - // Setup rendering structures - _format.reset(new gpu::Stream::Format()); - _format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); - _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); +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); } -void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - TextRenderer::EffectType effectType, const glm::vec2& bounds) { - if (str == "") { +QRectF Glyph::textureBounds(const glm::vec2 & textureSize) const { + return glmToRect(texOffset, texSize); +} + +void Font::setupGL() { + if (_initialized) { return; } - - if (str != _lastStringRendered || bounds != _lastBounds) { - _verticesBuffer.reset(new gpu::Buffer()); - _numVertices = 0; - _lastStringRendered = str; - _lastBounds = bounds; - - // Top left of text - glm::vec2 advance = glm::vec2(x, y); - foreach(const QString& token, tokenizeForWrapping(str)) { - bool isNewLine = (token == QString('\n')); - bool forceNewLine = false; - - // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > bounds.x)) { - // We are out of the x bound, force new line - forceNewLine = true; - } - if (isNewLine || forceNewLine) { - // Character return, move the advance to a new line - advance = glm::vec2(0.0f, advance.y - _rowHeight); + _initialized = true; - if (isNewLine) { - // No need to draw anything, go directly to next token - continue; - } else if (computeExtent(token).x > bounds.x) { - // token will never fit, stop drawing + _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 vertexData; + std::vector 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, 2, 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(" ")) { + bool lineFeed = false; + if (token1.isEmpty()) { + result << token1; + continue; + } + 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) { + + setupGL(); + + // 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 && std::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 && std::abs(advance.y) > bounds.y) { break; } } - if ((bounds.y != -1) && (advance.y - _fontSize < -bounds.y)) { - // We are out of the y bound, stop drawing - break; - } - - // Draw the token - if (!isNewLine) { - for (auto c : token) { - auto glyph = _glyphs[c]; - - QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _fontSize)); - _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); - _numVertices += 4; - - // Advance by glyph size - advance.x += glyph.d; - } - - // Add space after all non return tokens - advance.x += _spaceWidth; - } } + + 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(); + _texture->release(); // TODO: Brad & Sam, let's discuss this. Without this non-textured quads get their colors borked. + _program->release(); + // FIXME, needed? + // glDisable(GL_TEXTURE_2D); - setupGPU(); - batch.setPipeline(_pipeline); - batch.setUniformTexture(_fontLoc, _texture); - batch._glUniform1f(_outlineLoc, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); - batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)&color); - batch.setInputFormat(_format); - batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); - batch.draw(gpu::QUADS, _numVertices, 0); + return advance; } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -495,13 +512,10 @@ TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, 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(toGlm(color)), - _font(loadFont(family)) { +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 (!_font) { qWarning() << "Unable to load font with family " << family; _font = loadFont("Courier"); @@ -517,46 +531,40 @@ TextRenderer::TextRenderer(const char* family, float pointSize, int weight, bool TextRenderer::~TextRenderer() { } -glm::vec2 TextRenderer::computeExtent(const QString& str) const { +glm::vec2 TextRenderer::computeExtent(const QString & str) const { + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; if (_font) { - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; return _font->computeExtent(str) * scale; } return glm::vec2(0.1f,0.1f); } -float TextRenderer::getRowHeight() const { - if (_font) { - return _font->getRowHeight(); +float TextRenderer::draw(float x, float y, const QString & str, + const glm::vec4& color, const glm::vec2 & bounds) { + glm::vec4 actualColor(color); + if (actualColor.r < 0) { + actualColor = toGlm(_color); } - return 1.0f; -} -void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { - // The font does all the OpenGL work - if (_font) { - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - glPushMatrix(); - gpu::Batch batch; - Transform transform; - transform.setTranslation(glm::vec3(x, y, 0.0f)); - transform.setScale(glm::vec3(scale, -scale, scale)); - batch.setModelTransform(transform); - draw3D(batch, 0.0f, 0.0f, str, color, bounds); - gpu::GLBackend::renderBatch(batch, true); - glPopMatrix(); - } -} + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; + glm::vec2 result; -void TextRenderer::draw3D(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - const glm::vec2& bounds) { - // The font does all the OpenGL work - if (_font) { - glm::vec4 actualColor(color); - if (actualColor.r < 0) { - actualColor = _color; + MatrixStack::withPushAll([&] { + MatrixStack & mv = MatrixStack::modelview(); + MatrixStack & pr = MatrixStack::projection(); + gpu::GLBackend::fetchMatrix(GL_MODELVIEW_MATRIX, mv.top()); + gpu::GLBackend::fetchMatrix(GL_PROJECTION_MATRIX, pr.top()); + + // 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 + if (_font) { + result = _font->drawString(x, y, str, actualColor, _effectType, bounds / scale); } - _font->drawString(batch, x, y, str, actualColor, _effectType, bounds); - } + }); + return result.x; } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 0c7faa9e6b..85bb18cec9 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -12,8 +12,21 @@ #ifndef hifi_TextRenderer_h #define hifi_TextRenderer_h +#include #include +#include #include +#include +#include +#include +#include +#include + +#include +#include + +// a special "character" that renders as a solid block +const char SOLID_BLOCK_CHAR = 127; // the standard sans serif font family #define SANS_FONT_FAMILY "Helvetica" @@ -33,9 +46,6 @@ #define INCONSOLATA_FONT_WEIGHT QFont::Bold #endif -namespace gpu { -class Batch; -} class Font; // TextRenderer is actually a fairly thin wrapper around a Font class @@ -49,13 +59,13 @@ public: ~TextRenderer(); - glm::vec2 computeExtent(const QString& str) const; - float getRowHeight() const; - - void draw(float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); - void draw3D(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); + glm::vec2 computeExtent(const QString & str) const; + + float draw( + float x, float y, + const QString & str, + const glm::vec4& color = glm::vec4(-1.0f), + const glm::vec2& bounds = glm::vec2(-1.0f)); private: TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false, @@ -70,9 +80,9 @@ private: const float _pointSize; // text color - const glm::vec4 _color; + const QColor _color; - Font* _font; + Font * _font; }; diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf index 3980045d08..1affbe4c57 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -11,8 +11,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html uniform sampler2D Font; -uniform float Outline; uniform vec4 Color; +uniform bool Outline; + +varying vec2 vTexCoord; const float gamma = 2.6; const float smoothing = 100.0; @@ -20,28 +22,28 @@ const float interiorCutoff = 0.8; const float outlineExpansion = 0.2; void main() { - // retrieve signed distance - float sdf = texture2D(Font, gl_TexCoord[0].xy).g; - if (Outline == 1.0f) { - 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(gl_TexCoord[0])); - 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; + // 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); - // final color - gl_FragColor = vec4(Color.rgb, a); + // gamma correction for linear attenuation + a = pow(a, 1.0 / gamma); + + if (a < 0.01) { + discard; + } + + // final color + gl_FragColor = vec4(Color.rgb, a); } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv index f7c35a257c..27db1c4985 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -9,15 +9,16 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Transform.slh@> -<$declareStandardTransform()$> +uniform mat4 Projection; +uniform mat4 ModelView; + +attribute vec2 Position; +attribute vec2 TexCoord; + +varying vec2 vTexCoord; void main() { - gl_TexCoord[0] = gl_MultiTexCoord0; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + vTexCoord = TexCoord; + gl_Position = Projection * ModelView * vec4(Position, 0.0, 1.0); } \ No newline at end of file From 12d75481e58d04263cb9ec820a61ff7e7ccb154b Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 15:43:16 +0200 Subject: [PATCH 36/38] Introducing TextRenderer3D --- .../src/RenderableTextEntityItem.cpp | 5 +- .../src/RenderableTextEntityItem.h | 4 +- libraries/render-utils/src/TextRenderer3D.cpp | 545 ++++++++++++++++++ libraries/render-utils/src/TextRenderer3D.h | 77 +++ libraries/render-utils/src/sdf_text3D.slf | 47 ++ libraries/render-utils/src/sdf_text3D.slv | 23 + 6 files changed, 697 insertions(+), 4 deletions(-) create mode 100644 libraries/render-utils/src/TextRenderer3D.cpp create mode 100644 libraries/render-utils/src/TextRenderer3D.h create mode 100644 libraries/render-utils/src/sdf_text3D.slf create mode 100644 libraries/render-utils/src/sdf_text3D.slv diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 2eeec77d68..1e6a39d2a8 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -32,7 +32,8 @@ void RenderableTextEntityItem::render(RenderArgs* args) { glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), 1.0f); glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), 1.0f); glm::vec3 dimensions = getDimensions(); - glm::vec2 bounds = glm::vec2(dimensions.x, dimensions.y); + float leftMargin = 0.1f, topMargin = 0.1f; + glm::vec2 bounds = glm::vec2(dimensions.x - 2 * leftMargin, dimensions.y - 2 * topMargin); Transform transformToTopLeft = getTransformToCenter(); transformToTopLeft.postTranslate(glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left @@ -51,7 +52,7 @@ void RenderableTextEntityItem::render(RenderArgs* args) { float scale = _lineHeight / _textRenderer->getRowHeight(); transformToTopLeft.setScale(scale); batch.setModelTransform(transformToTopLeft); - _textRenderer->draw3D(batch, 0.0f, 0.0f, _text, textColor, bounds / scale); + _textRenderer->draw(batch, leftMargin, topMargin, _text, textColor, bounds / scale); } diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index 57a485241e..355847c300 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -13,7 +13,7 @@ #define hifi_RenderableTextEntityItem_h #include -#include +#include const int FIXED_FONT_POINT_SIZE = 40; @@ -29,7 +29,7 @@ public: virtual void render(RenderArgs* args); private: - TextRenderer* _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f); + TextRenderer3D* _textRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f); }; diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp new file mode 100644 index 0000000000..8785848cb4 --- /dev/null +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -0,0 +1,545 @@ +// +// TextRenderer3D.cpp +// interface/src/ui +// +// Created by Andrzej Kapolka on 4/24/13. +// Copyright 2013 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 "TextRenderer3D.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "GLMHelpers.h" +#include "MatrixStack.h" +#include "RenderUtilsLogging.h" + +#include "sdf_text3D_vert.h" +#include "sdf_text3D_frag.h" + +#include "GeometryCache.h" +#include "DeferredLightingEffect.h" + +// 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 +const float DEFAULT_POINT_SIZE = 12; + +// Helper functions for reading binary data from an IO device +template +void readStream(QIODevice& in, T& t) { + in.read((char*) &t, sizeof(t)); +} + +template +void readStream(QIODevice& in, T (&t)[N]) { + in.read((char*) t, N); +} + +template +void fillBuffer(QBuffer& buffer, T (&t)[N]) { + buffer.setData((const char*) t, N); +} + +// stores the font metrics for a single character +struct Glyph3D { + QChar c; + glm::vec2 texOffset; + glm::vec2 texSize; + glm::vec2 size; + glm::vec2 offset; + float d; // xadvance - adjusts character positioning + size_t indexOffset; + + // We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect + QRectF bounds() const { return glmToRect(offset, size).translated(0.0f, -size.y); } + QRectF textureBounds() const { return glmToRect(texOffset, texSize); } + + void read(QIODevice& in); +}; + +void Glyph3D::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; +} + +struct TextureVertex { + glm::vec2 pos; + glm::vec2 tex; + TextureVertex() {} + TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {} +}; + +struct QuadBuilder { + TextureVertex vertices[4]; + QuadBuilder(const glm::vec2& min, const glm::vec2& size, + const glm::vec2& texMin, const glm::vec2& texSize) { + // min = bottomLeft + vertices[0] = TextureVertex(min, + texMin + glm::vec2(0.0f, texSize.y)); + vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f), + texMin + texSize); + vertices[2] = TextureVertex(min + size, + texMin + glm::vec2(texSize.x, 0.0f)); + vertices[3] = TextureVertex(min + glm::vec2(0.0f, size.y), + texMin); + } + QuadBuilder(const Glyph3D& glyph, const glm::vec2& offset) : + QuadBuilder(offset + glyph.offset - glm::vec2(0.0f, glyph.size.y), glyph.size, + glyph.texOffset, glyph.texSize) {} + +}; + +QDebug operator<<(QDebug debug, glm::vec2& value) { + debug << value.x << value.y; + return debug; +} + +QDebug operator<<(QDebug debug, glm::vec3& value) { + debug << value.x << value.y << value.z; + return debug; +} + +QDebug operator<<(QDebug debug, TextureVertex& value) { + debug << "Pos:" << value.pos << ", Tex:" << value.tex; + return debug; +} + +QDebug operator<<(QDebug debug, QuadBuilder& value) { + debug << '\n' << value.vertices[0] + << '\n' << value.vertices[1] + << '\n' << value.vertices[2] + << '\n' << value.vertices[3]; + return debug; +} + +class Font3D { +public: + Font3D(); + + void read(QIODevice& path); + + glm::vec2 computeExtent(const QString& str) const; + float getRowHeight() const { return _rowHeight; } + + // Render string to batch + void drawString(gpu::Batch& batch, float x, float y, const QString& str, + const glm::vec4& color, TextRenderer3D::EffectType effectType, + const glm::vec2& bound); + +private: + QStringList tokenizeForWrapping(const QString& str) const; + QStringList splitLines(const QString& str) const; + glm::vec2 computeTokenExtent(const QString& str) const; + + const Glyph3D& getGlyph(const QChar& c) const; + + void setupGPU(); + + // 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 _glyphs; + + // Font characteristics + QString _family; + float _fontSize = 0.0f; + float _rowHeight = 0.0f; + float _leading = 0.0f; + float _ascent = 0.0f; + float _descent = 0.0f; + float _spaceWidth = 0.0f; + + bool _initialized = false; + + // gpu structures + gpu::PipelinePointer _pipeline; + gpu::TexturePointer _texture; + gpu::Stream::FormatPointer _format; + gpu::BufferPointer _verticesBuffer; + gpu::BufferStreamPointer _stream; + unsigned int _numVertices = 0; + + int _fontLoc = -1; + int _outlineLoc = -1; + int _colorLoc = -1; + + // last string render characteristics + QString _lastStringRendered; + glm::vec2 _lastBounds; +}; + +static QHash LOADED_FONTS; + +Font3D* loadFont3D(QIODevice& fontFile) { + Font3D* result = new Font3D(); + result->read(fontFile); + return result; +} + +Font3D* loadFont3D(const QString& family) { + if (!LOADED_FONTS.contains(family)) { + + const QString SDFF_COURIER_PRIME_FILENAME = ":/CourierPrime.sdff"; + const QString SDFF_INCONSOLATA_MEDIUM_FILENAME = ":/InconsolataMedium.sdff"; + const QString SDFF_ROBOTO_FILENAME = ":/Roboto.sdff"; + const QString SDFF_TIMELESS_FILENAME = ":/Timeless.sdff"; + + QString loadFilename; + + if (family == MONO_FONT_FAMILY) { + loadFilename = SDFF_COURIER_PRIME_FILENAME; + } else if (family == INCONSOLATA_FONT_FAMILY) { + loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME; + } else if (family == SANS_FONT_FAMILY) { + loadFilename = SDFF_ROBOTO_FILENAME; + } else { + if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) { + loadFilename = SDFF_TIMELESS_FILENAME; + } else { + LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY]; + } + } + + if (!loadFilename.isEmpty()) { + QFile fontFile(loadFilename); + fontFile.open(QIODevice::ReadOnly); + + qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System."; + + LOADED_FONTS[family] = loadFont3D(fontFile); + } + } + return LOADED_FONTS[family]; +} + +Font3D::Font3D() { + static bool fontResourceInitComplete = false; + if (!fontResourceInitComplete) { + Q_INIT_RESOURCE(fonts); + fontResourceInitComplete = true; + } +} + +// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member +const Glyph3D& Font3D::getGlyph(const QChar& c) const { + if (!_glyphs.contains(c)) { + return _glyphs[QChar('?')]; + } + return _glyphs[c]; +} + +QStringList Font3D::splitLines(const QString& str) const { + return str.split('\n'); +} + +QStringList Font3D::tokenizeForWrapping(const QString& str) const { + QStringList tokens; + for(auto line : splitLines(str)) { + if (!tokens.empty()) { + tokens << QString('\n'); + } + tokens << line.split(' '); + } + return tokens; +} + +glm::vec2 Font3D::computeTokenExtent(const QString& token) const { + glm::vec2 advance(0, _fontSize); + foreach(QChar c, token) { + Q_ASSERT(c != '\n'); + advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d; + } + return advance; +} + +glm::vec2 Font3D::computeExtent(const QString& str) const { + glm::vec2 extent = glm::vec2(0.0f, 0.0f); + + QStringList tokens = splitLines(str); + foreach(const QString& token, tokens) { + glm::vec2 tokenExtent = computeTokenExtent(token); + extent.x = std::max(tokenExtent.x, extent.x); + } + extent.y = tokens.count() * _rowHeight; + + return extent; +} + +void Font3D::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 + _leading; + + // Read character count + uint16_t count; + readStream(in, count); + // read metrics data for each character + QVector glyphs(count); + // std::for_each instead of Qt foreach because we need non-const references + std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph3D& g) { + g.read(in); + }); + + // read image data + QImage image; + if (!image.loadFromData(in.readAll(), "PNG")) { + qFatal("Failed to read SDFF image"); + } + + _glyphs.clear(); + glm::vec2 imageSize = toGlm(image.size()); + foreach(Glyph3D g, glyphs) { + // Adjust the pixel texture coordinates into UV coordinates, + g.texSize /= imageSize; + g.texOffset /= imageSize; + // store in the character to glyph hash + _glyphs[g.c] = g; + }; + + qDebug() << _family << "size" << image.size(); + qDebug() << _family << "format" << image.format(); + image = image.convertToFormat(QImage::Format_RGBA8888); + qDebug() << _family << "size" << image.size(); + qDebug() << _family << "format" << image.format(); + + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + if (image.hasAlphaChannel()) { + formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); + } + _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + _texture->autoGenerateMips(-1); +} + +void Font3D::setupGPU() { + if (!_initialized) { + _initialized = true; + + // Setup render pipeline + auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text3D_vert))); + auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text3D_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + _fontLoc = program->getTextures().findLocation("Font"); + _outlineLoc = program->getUniforms().findLocation("Outline"); + _colorLoc = program->getUniforms().findLocation("Color"); + + + auto f = [&] (QString str, const gpu::Shader::SlotSet& set) { + if (set.size() == 0) { + return; + } + qDebug() << str << set.size(); + for (auto slot : set) { + qDebug() << " " << QString::fromStdString(slot._name) << slot._location; + } + }; + f("getUniforms:", program->getUniforms()); + f("getBuffers:", program->getBuffers()); + f("getTextures:", program->getTextures()); + f("getSamplers:", program->getSamplers()); + f("getInputs:", program->getInputs()); + f("getOutputs:", program->getOutputs()); + + qDebug() << "Texture:" << _texture.get(); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setCullMode(gpu::State::CULL_BACK); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + + // Sanity checks + static const int OFFSET = offsetof(TextureVertex, tex); + assert(OFFSET == sizeof(glm::vec2)); + assert(sizeof(glm::vec2) == 2 * sizeof(float)); + assert(sizeof(glm::vec3) == 3 * sizeof(float)); + assert(sizeof(TextureVertex) == sizeof(glm::vec2) + sizeof(glm::vec2)); + assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); + + // Setup rendering structures + _format.reset(new gpu::Stream::Format()); + _format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); + _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); + } +} + +void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, + TextRenderer3D::EffectType effectType, const glm::vec2& bounds) { + if (str == "") { + return; + } + + if (str != _lastStringRendered || bounds != _lastBounds) { + _verticesBuffer.reset(new gpu::Buffer()); + _numVertices = 0; + _lastStringRendered = str; + _lastBounds = bounds; + + // Top left of text + glm::vec2 advance = glm::vec2(x, y); + foreach(const QString& token, tokenizeForWrapping(str)) { + bool isNewLine = (token == QString('\n')); + bool forceNewLine = false; + + // Handle wrapping + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > bounds.x)) { + // We are out of the x bound, force new line + forceNewLine = true; + } + if (isNewLine || forceNewLine) { + // Character return, move the advance to a new line + advance = glm::vec2(0.0f, advance.y - _rowHeight); + + if (isNewLine) { + // No need to draw anything, go directly to next token + continue; + } else if (computeExtent(token).x > bounds.x) { + // token will never fit, stop drawing + break; + } + } + if ((bounds.y != -1) && (advance.y - _fontSize < -bounds.y)) { + // We are out of the y bound, stop drawing + break; + } + + // Draw the token + if (!isNewLine) { + for (auto c : token) { + auto glyph = _glyphs[c]; + + QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _fontSize)); + _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); + _numVertices += 4; + + // Advance by glyph size + advance.x += glyph.d; + } + + // Add space after all non return tokens + advance.x += _spaceWidth; + } + } + } + + setupGPU(); + batch.setPipeline(_pipeline); + batch.setUniformTexture(_fontLoc, _texture); + batch._glUniform1f(_outlineLoc, (effectType == TextRenderer3D::OUTLINE_EFFECT) ? 1.0f : 0.0f); + batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)&color); + + batch.setInputFormat(_format); + batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); + batch.draw(gpu::QUADS, _numVertices, 0); +} + +TextRenderer3D* TextRenderer3D::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 TextRenderer3D(family, pointSize, weight, italic, effect, + effectThickness, color); +} + +TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight, bool italic, + EffectType effect, int effectThickness, const QColor& color) : + _effectType(effect), + _effectThickness(effectThickness), + _pointSize(pointSize), + _color(toGlm(color)), + _font(loadFont3D(family)) { + if (!_font) { + qWarning() << "Unable to load font with family " << family; + _font = loadFont3D("Courier"); + } + if (1 != _effectThickness) { + qWarning() << "Effect thickness not current supported"; + } + if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) { + qWarning() << "Effect thickness not current supported"; + } +} + +TextRenderer3D::~TextRenderer3D() { +} + +glm::vec2 TextRenderer3D::computeExtent(const QString& str) const { + if (_font) { + return _font->computeExtent(str); + } + return glm::vec2(0.0f, 0.0f); +} + +float TextRenderer3D::getRowHeight() const { + if (_font) { + return _font->getRowHeight(); + } + return 0.0f; +} + +void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, + const glm::vec2& bounds) { + // The font does all the OpenGL work + if (_font) { + glm::vec4 actualColor(color); + if (actualColor.r < 0) { + actualColor = _color; + } + _font->drawString(batch, x, y, str, actualColor, _effectType, bounds); + } +} + diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h new file mode 100644 index 0000000000..8f55d0c977 --- /dev/null +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -0,0 +1,77 @@ +// +// TextRenderer3D.h +// interface/src/ui +// +// Created by Andrzej Kapolka on 4/26/13. +// Copyright 2013 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 +// + +#ifndef hifi_TextRenderer3D_h +#define hifi_TextRenderer3D_h + +#include +#include + +// the standard sans serif font family +#define SANS_FONT_FAMILY "Helvetica" + +// the standard sans serif font family +#define SERIF_FONT_FAMILY "Timeless" + +// the standard mono font family +#define MONO_FONT_FAMILY "Courier" + +// the Inconsolata font family +#ifdef Q_OS_WIN +#define INCONSOLATA_FONT_FAMILY "Fixedsys" +#define INCONSOLATA_FONT_WEIGHT QFont::Normal +#else +#define INCONSOLATA_FONT_FAMILY "Inconsolata" +#define INCONSOLATA_FONT_WEIGHT QFont::Bold +#endif + +namespace gpu { +class Batch; +} +class Font3D; + +// TextRenderer3D is actually a fairly thin wrapper around a Font class +// defined in the cpp file. +class TextRenderer3D { +public: + enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT }; + + static TextRenderer3D* getInstance(const char* family, float pointSize = -1, int weight = -1, bool italic = false, + EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255)); + + ~TextRenderer3D(); + + glm::vec2 computeExtent(const QString& str) const; + float getRowHeight() const; + + void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), + const glm::vec2& bounds = glm::vec2(-1.0f)); + +private: + TextRenderer3D(const char* family, float pointSize = -1, int weight = -1, bool italic = false, + EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255)); + + // the type of effect to apply + const EffectType _effectType; + + // the thickness of the effect + const int _effectThickness; + + const float _pointSize; + + // text color + const glm::vec4 _color; + + Font3D* _font; +}; + + +#endif // hifi_TextRenderer3D_h diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf new file mode 100644 index 0000000000..3980045d08 --- /dev/null +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -0,0 +1,47 @@ +<@include gpu/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 float Outline; +uniform vec4 Color; + +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, gl_TexCoord[0].xy).g; + if (Outline == 1.0f) { + 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(gl_TexCoord[0])); + 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); +} \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv new file mode 100644 index 0000000000..f7c35a257c --- /dev/null +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -0,0 +1,23 @@ +<@include gpu/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 +// +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +void main() { + gl_TexCoord[0] = gl_MultiTexCoord0; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> +} \ No newline at end of file From ca529e4bb8a1403dc91efd2e5a77c5d87ce65901 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 15:58:40 +0200 Subject: [PATCH 37/38] Remove debug/tweak sampler --- .../src/RenderableTextEntityItem.cpp | 9 ++-- libraries/render-utils/src/TextRenderer3D.cpp | 53 ++----------------- 2 files changed, 8 insertions(+), 54 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 1e6a39d2a8..f76454e1b3 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -32,8 +32,6 @@ void RenderableTextEntityItem::render(RenderArgs* args) { glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), 1.0f); glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), 1.0f); glm::vec3 dimensions = getDimensions(); - float leftMargin = 0.1f, topMargin = 0.1f; - glm::vec2 bounds = glm::vec2(dimensions.x - 2 * leftMargin, dimensions.y - 2 * topMargin); Transform transformToTopLeft = getTransformToCenter(); transformToTopLeft.postTranslate(glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left @@ -50,9 +48,12 @@ void RenderableTextEntityItem::render(RenderArgs* args) { DependencyManager::get()->renderQuad(batch, minCorner, maxCorner, backgroundColor); float scale = _lineHeight / _textRenderer->getRowHeight(); - transformToTopLeft.setScale(scale); + transformToTopLeft.setScale(scale); // Scale to have the correct line height batch.setModelTransform(transformToTopLeft); - _textRenderer->draw(batch, leftMargin, topMargin, _text, textColor, bounds / scale); + + float leftMargin = 0.5f * _lineHeight, topMargin = 0.5f * _lineHeight; + glm::vec2 bounds = glm::vec2(dimensions.x - 2.0f * leftMargin, dimensions.y - 2.0f * topMargin); + _textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, _text, textColor, bounds / scale); } diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 8785848cb4..cfa358ffb3 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -106,29 +106,6 @@ struct QuadBuilder { }; -QDebug operator<<(QDebug debug, glm::vec2& value) { - debug << value.x << value.y; - return debug; -} - -QDebug operator<<(QDebug debug, glm::vec3& value) { - debug << value.x << value.y << value.z; - return debug; -} - -QDebug operator<<(QDebug debug, TextureVertex& value) { - debug << "Pos:" << value.pos << ", Tex:" << value.tex; - return debug; -} - -QDebug operator<<(QDebug debug, QuadBuilder& value) { - debug << '\n' << value.vertices[0] - << '\n' << value.vertices[1] - << '\n' << value.vertices[2] - << '\n' << value.vertices[3]; - return debug; -} - class Font3D { public: Font3D(); @@ -339,11 +316,7 @@ void Font3D::read(QIODevice& in) { _glyphs[g.c] = g; }; - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); image = image.convertToFormat(QImage::Format_RGBA8888); - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); @@ -352,7 +325,7 @@ void Font3D::read(QIODevice& in) { formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); } _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), - gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR))); _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); _texture->autoGenerateMips(-1); } @@ -373,25 +346,6 @@ void Font3D::setupGPU() { _outlineLoc = program->getUniforms().findLocation("Outline"); _colorLoc = program->getUniforms().findLocation("Color"); - - auto f = [&] (QString str, const gpu::Shader::SlotSet& set) { - if (set.size() == 0) { - return; - } - qDebug() << str << set.size(); - for (auto slot : set) { - qDebug() << " " << QString::fromStdString(slot._name) << slot._location; - } - }; - f("getUniforms:", program->getUniforms()); - f("getBuffers:", program->getBuffers()); - f("getTextures:", program->getTextures()); - f("getSamplers:", program->getSamplers()); - f("getInputs:", program->getInputs()); - f("getOutputs:", program->getOutputs()); - - qDebug() << "Texture:" << _texture.get(); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); @@ -404,8 +358,7 @@ void Font3D::setupGPU() { static const int OFFSET = offsetof(TextureVertex, tex); assert(OFFSET == sizeof(glm::vec2)); assert(sizeof(glm::vec2) == 2 * sizeof(float)); - assert(sizeof(glm::vec3) == 3 * sizeof(float)); - assert(sizeof(TextureVertex) == sizeof(glm::vec2) + sizeof(glm::vec2)); + assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2)); assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); // Setup rendering structures @@ -440,7 +393,7 @@ void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, } if (isNewLine || forceNewLine) { // Character return, move the advance to a new line - advance = glm::vec2(0.0f, advance.y - _rowHeight); + advance = glm::vec2(x, advance.y - _rowHeight); if (isNewLine) { // No need to draw anything, go directly to next token From b0cbf5c51a0ec3a403179cefb19bc55313a7ec71 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 16:06:18 +0200 Subject: [PATCH 38/38] Adjust wraping with offset --- libraries/render-utils/src/TextRenderer3D.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index cfa358ffb3..d081c0480a 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -387,7 +387,7 @@ void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, bool forceNewLine = false; // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > bounds.x)) { + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > x + bounds.x)) { // We are out of the x bound, force new line forceNewLine = true; } @@ -403,7 +403,7 @@ void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, break; } } - if ((bounds.y != -1) && (advance.y - _fontSize < -bounds.y)) { + if ((bounds.y != -1) && (advance.y - _fontSize < -y - bounds.y)) { // We are out of the y bound, stop drawing break; }