Merge pull request #7487 from hyperlogic/tony/animated-culling

Improved render bounding boxes for animated models
This commit is contained in:
Brad Hefta-Gaub 2016-03-30 09:27:43 -07:00
commit d99dcf5450
17 changed files with 300 additions and 81 deletions

View file

@ -2975,6 +2975,11 @@ void Application::updateLOD() {
}
}
void Application::pushPreRenderLambda(void* key, std::function<void()> func) {
std::unique_lock<std::mutex> guard(_preRenderLambdasLock);
_preRenderLambdas[key] = func;
}
// Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone.
// (Maybe this code should be moved there?)
// The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition().
@ -3451,6 +3456,16 @@ void Application::update(float deltaTime) {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection);
}
}
{
PROFILE_RANGE_EX("PreRenderLambdas", 0xffff0000, (uint64_t)0);
std::unique_lock<std::mutex> guard(_preRenderLambdasLock);
for (auto& iter : _preRenderLambdas) {
iter.second();
}
_preRenderLambdas.clear();
}
}

View file

@ -211,6 +211,8 @@ public:
render::EnginePointer getRenderEngine() override { return _renderEngine; }
gpu::ContextPointer getGPUContext() const { return _gpuContext; }
virtual void pushPreRenderLambda(void* key, std::function<void()> func) override;
const QRect& getMirrorViewRect() const { return _mirrorViewRect; }
void updateMyAvatarLookAtPosition();
@ -510,6 +512,9 @@ private:
bool _cursorNeedsChanging { false };
QThread* _deadlockWatchdogThread;
std::map<void*, std::function<void()>> _preRenderLambdas;
std::mutex _preRenderLambdasLock;
};
#endif // hifi_Application_h

View file

@ -18,22 +18,22 @@
QString const ModelOverlay::TYPE = "model";
ModelOverlay::ModelOverlay()
: _model(std::make_shared<Rig>()),
: _model(std::make_shared<Model>(std::make_shared<Rig>())),
_modelTextures(QVariantMap()),
_updateModel(false)
{
_model.init();
_model->init();
_isLoaded = false;
}
ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
Volume3DOverlay(modelOverlay),
_model(std::make_shared<Rig>()),
_model(std::make_shared<Model>(std::make_shared<Rig>())),
_modelTextures(QVariantMap()),
_url(modelOverlay->_url),
_updateModel(false)
{
_model.init();
_model->init();
if (_url.isValid()) {
_updateModel = true;
_isLoaded = false;
@ -44,27 +44,27 @@ void ModelOverlay::update(float deltatime) {
if (_updateModel) {
_updateModel = false;
_model.setSnapModelToCenter(true);
_model.setScale(getDimensions());
_model.setRotation(getRotation());
_model.setTranslation(getPosition());
_model.setURL(_url);
_model.simulate(deltatime, true);
_model->setSnapModelToCenter(true);
_model->setScale(getDimensions());
_model->setRotation(getRotation());
_model->setTranslation(getPosition());
_model->setURL(_url);
_model->simulate(deltatime, true);
} else {
_model.simulate(deltatime);
_model->simulate(deltatime);
}
_isLoaded = _model.isActive();
_isLoaded = _model->isActive();
}
bool ModelOverlay::addToScene(Overlay::Pointer overlay, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
Volume3DOverlay::addToScene(overlay, scene, pendingChanges);
_model.addToScene(scene, pendingChanges);
_model->addToScene(scene, pendingChanges);
return true;
}
void ModelOverlay::removeFromScene(Overlay::Pointer overlay, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
Volume3DOverlay::removeFromScene(overlay, scene, pendingChanges);
_model.removeFromScene(scene, pendingChanges);
_model->removeFromScene(scene, pendingChanges);
}
void ModelOverlay::render(RenderArgs* args) {
@ -73,9 +73,9 @@ void ModelOverlay::render(RenderArgs* args) {
// fix them up in the scene
render::ScenePointer scene = qApp->getMain3DScene();
render::PendingChanges pendingChanges;
if (_model.needsFixupInScene()) {
_model.removeFromScene(scene, pendingChanges);
_model.addToScene(scene, pendingChanges);
if (_model->needsFixupInScene()) {
_model->removeFromScene(scene, pendingChanges);
_model->addToScene(scene, pendingChanges);
}
scene->enqueuePendingChanges(pendingChanges);
@ -100,7 +100,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
if (newScale.x <= 0 || newScale.y <= 0 || newScale.z <= 0) {
setDimensions(scale);
} else {
_model.setScaleToFit(true, getDimensions());
_model->setScaleToFit(true, getDimensions());
_updateModel = true;
}
}
@ -120,7 +120,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) {
QUrl newTextureURL = textureMap[key].toUrl();
qDebug() << "Updating texture named" << key << "to texture at URL" << newTextureURL;
QMetaObject::invokeMethod(&_model, "setTextureWithNameToURL", Qt::AutoConnection,
QMetaObject::invokeMethod(_model.get(), "setTextureWithNameToURL", Qt::AutoConnection,
Q_ARG(const QString&, key),
Q_ARG(const QUrl&, newTextureURL));
@ -134,7 +134,7 @@ QVariant ModelOverlay::getProperty(const QString& property) {
return _url.toString();
}
if (property == "dimensions" || property == "scale" || property == "size") {
return vec3toVariant(_model.getScaleToFitDimensions());
return vec3toVariant(_model->getScaleToFitDimensions());
}
if (property == "textures") {
if (_modelTextures.size() > 0) {
@ -155,13 +155,13 @@ bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3&
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
QString subMeshNameTemp;
return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp);
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp);
}
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
return _model.findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
}
ModelOverlay* ModelOverlay::createClone() const {

View file

@ -41,11 +41,11 @@ public:
private:
Model _model;
ModelPointer _model;
QVariantMap _modelTextures;
QUrl _url;
bool _updateModel;
};
#endif // hifi_ModelOverlay_h
#endif // hifi_ModelOverlay_h

View file

@ -1056,7 +1056,9 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm
// limit rotation
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat));
if (fabsf(glm::angle(deltaQuat)) > MAX_ANGLE) {
deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat));
}
// directly set absolutePose rotation
_internalPoseSet._absolutePoses[index].rot = deltaQuat * headQuat;

View file

@ -221,6 +221,8 @@ public:
void setEnableInverseKinematics(bool enable);
const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; }
protected:
bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); }
void updateAnimationStateHandlers();

View file

@ -111,7 +111,7 @@ Box Mesh::evalPartBound(int partNum) const {
return box;
}
Box Mesh::evalPartBounds(int partStart, int partEnd, Boxes& bounds) const {
Box Mesh::evalPartsBound(int partStart, int partEnd) const {
Box totalBound;
auto part = _partBuffer.cbegin<Part>() + partStart;
auto partItEnd = _partBuffer.cbegin<Part>() + partEnd;

View file

@ -108,9 +108,9 @@ public:
// evaluate the bounding box of A part
Box evalPartBound(int partNum) const;
// evaluate the bounding boxes of the parts in the range [start, end[ and fill the bounds parameter
// the returned box is the bounding box of ALL the evaluated part bounds.
Box evalPartBounds(int partStart, int partEnd, Boxes& bounds) const;
// evaluate the bounding boxes of the parts in the range [start, end]
// the returned box is the bounding box of ALL the evaluated parts bound.
Box evalPartsBound(int partStart, int partEnd) const;
static gpu::Primitive topologyToPrimitive(Topology topo) { return static_cast<gpu::Primitive>(topo); }

View file

@ -46,6 +46,8 @@ public:
virtual render::ScenePointer getMain3DScene() = 0;
virtual render::EnginePointer getRenderEngine() = 0;
virtual void pushPreRenderLambda(void* key, std::function<void()> func) = 0;
// FIXME - we shouldn't assume that there's a single instance of an AbstractViewStateInterface
static AbstractViewStateInterface* instance();
static void setInstance(AbstractViewStateInterface* instance);

View file

@ -58,7 +58,6 @@ void MeshPartPayload::updateMeshPart(const std::shared_ptr<const model::Mesh>& d
auto vertexFormat = _drawMesh->getVertexFormat();
_hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR);
_drawPart = _drawMesh->getPartBuffer().get<model::Mesh::Part>(partIndex);
_localBound = _drawMesh->evalPartBound(partIndex);
}
}
@ -352,7 +351,23 @@ void ModelMeshPartPayload::initCache() {
void ModelMeshPartPayload::notifyLocationChanged() {
_model->_needsUpdateClusterMatrices = true;
}
void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector<glm::mat4>& clusterMatrices) {
ModelMeshPartPayload::updateTransform(transform, offsetTransform);
if (clusterMatrices.size() > 0) {
_worldBound = AABox();
for (auto& clusterMatrix : clusterMatrices) {
AABox clusterBound = _localBound;
clusterBound.transform(clusterMatrix);
_worldBound += clusterBound;
}
// clusterMatrix has world rotation but not world translation.
_worldBound.translate(transform.getTranslation());
}
}
ItemKey ModelMeshPartPayload::getKey() const {
@ -377,12 +392,6 @@ ItemKey ModelMeshPartPayload::getKey() const {
return builder.build();
}
Item::Bound ModelMeshPartPayload::getBound() const {
// NOTE: we can't cache this bounds because we need to handle the case of a moving
// entity or mesh part.
return _model->getPartBounds(_meshIndex, _partIndex, _transform.getTranslation(), _transform.getRotation());
}
ShapeKey ModelMeshPartPayload::getShapeKey() const {
assert(_model->isLoaded());
const FBXGeometry& geometry = _model->getFBXGeometry();

View file

@ -74,15 +74,15 @@ namespace render {
class ModelMeshPartPayload : public MeshPartPayload {
public:
ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
typedef render::Payload<ModelMeshPartPayload> Payload;
typedef Payload::DataPointer Pointer;
void notifyLocationChanged() override;
void updateTransformForSkinnedMesh(const Transform& transform, const Transform& offsetTransform, const QVector<glm::mat4>& clusterMatrices);
// Render Item interface
render::ItemKey getKey() const override;
render::Item::Bound getBound() const override;
render::ShapeKey getShapeKey() const override; // shape interface
void render(RenderArgs* args) const override;
@ -101,4 +101,11 @@ public:
bool _isBlendShaped{ false };
};
namespace render {
template <> const ItemKey payloadGetKey(const ModelMeshPartPayload::Pointer& payload);
template <> const Item::Bound payloadGetBound(const ModelMeshPartPayload::Pointer& payload);
template <> const ShapeKey shapeGetShapeKey(const ModelMeshPartPayload::Pointer& payload);
template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, RenderArgs* args);
}
#endif // hifi_MeshPartPayload_h

View file

@ -130,25 +130,60 @@ void Model::setOffset(const glm::vec3& offset) {
}
void Model::enqueueLocationChange() {
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
Transform transform;
transform.setTranslation(_translation);
transform.setRotation(_rotation);
// queue up this work for later processing, at the end of update and just before rendering.
// the application will ensure only the last lambda is actually invoked.
void* key = (void*)this;
std::weak_ptr<Model> weakSelf = shared_from_this();
AbstractViewStateInterface::instance()->pushPreRenderLambda(key, [weakSelf]() {
Transform offset;
offset.setScale(_scale);
offset.postTranslate(_offset);
// do nothing, if the model has already been destroyed.
auto self = weakSelf.lock();
if (!self) {
return;
}
render::PendingChanges pendingChanges;
foreach (auto itemID, _renderItems.keys()) {
pendingChanges.updateItem<MeshPartPayload>(itemID, [transform, offset](MeshPartPayload& data) {
data.updateTransform(transform, offset);
data.notifyLocationChanged();
});
}
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
scene->enqueuePendingChanges(pendingChanges);
Transform modelTransform;
modelTransform.setScale(self->_scale);
modelTransform.setTranslation(self->_translation);
modelTransform.setRotation(self->_rotation);
Transform modelMeshOffset;
if (self->isLoaded()) {
// includes model offset and unitScale.
modelMeshOffset = Transform(self->_rig->getGeometryToRigTransform());
} else {
modelMeshOffset.postTranslate(self->_offset);
}
// only apply offset only, collision mesh does not share the same unit scale as the FBX file's mesh.
Transform collisionMeshOffset;
collisionMeshOffset.postTranslate(self->_offset);
render::PendingChanges pendingChanges;
foreach (auto itemID, self->_modelMeshRenderItems.keys()) {
pendingChanges.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, modelMeshOffset](ModelMeshPartPayload& data) {
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation());
// update the model transform and bounding box for this render item.
const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);
data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices);
});
}
foreach (auto itemID, self->_collisionRenderItems.keys()) {
pendingChanges.updateItem<MeshPartPayload>(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) {
// update the model transform for this render item.
data.updateTransform(modelTransform, collisionMeshOffset);
});
}
scene->enqueuePendingChanges(pendingChanges);
});
}
void Model::initJointTransforms() {
@ -497,8 +532,11 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scen
_isVisible = newValue;
render::PendingChanges pendingChanges;
foreach (auto item, _renderItems.keys()) {
pendingChanges.resetItem(item, _renderItems[item]);
foreach (auto item, _modelMeshRenderItems.keys()) {
pendingChanges.resetItem(item, _modelMeshRenderItems[item]);
}
foreach (auto item, _collisionRenderItems.keys()) {
pendingChanges.resetItem(item, _modelMeshRenderItems[item]);
}
scene->enqueuePendingChanges(pendingChanges);
}
@ -514,14 +552,25 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan
bool somethingAdded = false;
foreach (auto renderItem, _renderItemsSet) {
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
data.notifyLocationChanged();
});
_modelMeshRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
data.notifyLocationChanged();
});
_renderItems.insert(item, renderPayload);
_collisionRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
@ -541,7 +590,19 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
bool somethingAdded = false;
foreach (auto renderItem, _renderItemsSet) {
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
renderPayload->addStatusGetters(statusGetters);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
data.notifyLocationChanged();
});
_modelMeshRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
renderPayload->addStatusGetters(statusGetters);
@ -549,7 +610,7 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
data.notifyLocationChanged();
});
_renderItems.insert(item, renderPayload);
_collisionRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
@ -559,11 +620,16 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
}
void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) {
foreach (auto item, _renderItems.keys()) {
foreach (auto item, _modelMeshRenderItems.keys()) {
pendingChanges.removeItem(item);
}
_renderItems.clear();
_renderItemsSet.clear();
_modelMeshRenderItems.clear();
_modelMeshRenderItemsSet.clear();
foreach (auto item, _collisionRenderItems.keys()) {
pendingChanges.removeItem(item);
}
_collisionRenderItems.clear();
_collisionRenderItemsSet.clear();
_meshGroupsKnown = false;
_readyWhenAdded = false;
}
@ -1175,10 +1241,14 @@ void Model::segregateMeshGroups() {
return;
}
Q_ASSERT(_renderItems.isEmpty()); // We should not have any existing renderItems if we enter this section of code
Q_ASSERT(_renderItemsSet.isEmpty()); // We should not have any existing renderItemsSet if we enter this section of code
// We should not have any existing renderItems if we enter this section of code
Q_ASSERT(_modelMeshRenderItems.isEmpty());
Q_ASSERT(_modelMeshRenderItemsSet.isEmpty());
Q_ASSERT(_collisionRenderItems.isEmpty());
Q_ASSERT(_collisionRenderItemsSet.isEmpty());
_renderItemsSet.clear();
_modelMeshRenderItemsSet.clear();
_collisionRenderItemsSet.clear();
Transform transform;
transform.setTranslation(_translation);
@ -1204,9 +1274,9 @@ void Model::segregateMeshGroups() {
_collisionHullMaterial->setMetallic(0.02f);
_collisionHullMaterial->setRoughness(0.5f);
}
_renderItemsSet << std::make_shared<MeshPartPayload>(networkMesh, partIndex, _collisionHullMaterial, transform, offset);
_collisionRenderItemsSet << std::make_shared<MeshPartPayload>(networkMesh, partIndex, _collisionHullMaterial, transform, offset);
} else {
_renderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
_modelMeshRenderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
}
shapeID++;
@ -1229,13 +1299,21 @@ bool Model::initWhenReady(render::ScenePointer scene) {
offset.setScale(_scale);
offset.postTranslate(_offset);
foreach (auto renderItem, _renderItemsSet) {
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
_modelMeshRenderItems.insert(item, renderPayload);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<ModelMeshPartPayload>(item, [transform, offset](MeshPartPayload& data) {
data.notifyLocationChanged();
});
}
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
_renderItems.insert(item, renderPayload);
_collisionRenderItems.insert(item, renderPayload);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<MeshPartPayload>(item, [transform,offset](MeshPartPayload& data) {
data.updateTransform(transform, offset);
pendingChanges.updateItem<MeshPartPayload>(item, [transform, offset](MeshPartPayload& data) {
data.notifyLocationChanged();
});
}

View file

@ -44,6 +44,7 @@ namespace render {
typedef unsigned int ItemID;
}
class MeshPartPayload;
class ModelMeshPartPayload;
class ModelRenderLocations;
inline uint qHash(const std::shared_ptr<MeshPartPayload>& a, uint seed) {
@ -375,8 +376,12 @@ protected:
bool _renderCollisionHull;
QSet<std::shared_ptr<MeshPartPayload>> _renderItemsSet;
QMap<render::ItemID, render::PayloadPointer> _renderItems;
QSet<std::shared_ptr<MeshPartPayload>> _collisionRenderItemsSet;
QMap<render::ItemID, render::PayloadPointer> _collisionRenderItems;
QSet<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItemsSet;
QMap<render::ItemID, render::PayloadPointer> _modelMeshRenderItems;
bool _readyWhenAdded { false };
bool _needsReload { true };
bool _needsUpdateClusterMatrices { true };

View file

@ -17,6 +17,8 @@
#include "GeometryUtil.h"
#include "NumericalConstants.h"
const glm::vec3 AABox::INFINITY_VECTOR(std::numeric_limits<float>::infinity());
AABox::AABox(const AACube& other) :
_corner(other.getCorner()), _scale(other.getScale(), other.getScale(), other.getScale()) {
}
@ -34,7 +36,7 @@ AABox::AABox(const glm::vec3& corner, const glm::vec3& dimensions) :
_corner(corner), _scale(dimensions) {
};
AABox::AABox() : _corner(std::numeric_limits<float>::infinity()), _scale(0.0f) {
AABox::AABox() : _corner(INFINITY_VECTOR), _scale(0.0f) {
};
glm::vec3 AABox::calcCenter() const {
@ -475,8 +477,15 @@ AABox AABox::clamp(float min, float max) const {
}
AABox& AABox::operator += (const glm::vec3& point) {
_corner = glm::min(_corner, point);
_scale = glm::max(_scale, point - _corner);
if (isInvalid()) {
_corner = glm::min(_corner, point);
} else {
glm::vec3 maximum(_corner + _scale);
_corner = glm::min(_corner, point);
maximum = glm::max(maximum, point);
_scale = maximum - _corner;
}
return (*this);
}
@ -484,17 +493,31 @@ AABox& AABox::operator += (const glm::vec3& point) {
AABox& AABox::operator += (const AABox& box) {
if (!box.isInvalid()) {
(*this) += box._corner;
_scale = glm::max(_scale, box.calcTopFarLeft() - _corner);
(*this) += box.calcTopFarLeft();
}
return (*this);
}
void AABox::embiggen(float scale) {
_corner += scale * (-0.5f * _scale);
_scale *= scale;
}
void AABox::embiggen(const glm::vec3& scale) {
_corner += scale * (-0.5f * _scale);
_scale *= scale;
}
void AABox::scale(float scale) {
_corner *= scale;
_scale *= scale;
}
void AABox::scale(const glm::vec3& scale) {
_corner *= scale;
_scale *= scale;
}
void AABox::rotate(const glm::quat& rotation) {
auto minimum = _corner;
auto maximum = _corner + _scale;
@ -544,3 +567,47 @@ void AABox::transform(const Transform& transform) {
rotate(transform.getRotation());
translate(transform.getTranslation());
}
void AABox::transform(const glm::mat4& matrix) {
auto minimum = _corner;
auto maximum = _corner + _scale;
glm::vec3 bottomLeftNear(minimum.x, minimum.y, minimum.z);
glm::vec3 bottomRightNear(maximum.x, minimum.y, minimum.z);
glm::vec3 bottomLeftFar(minimum.x, minimum.y, maximum.z);
glm::vec3 bottomRightFar(maximum.x, minimum.y, maximum.z);
glm::vec3 topLeftNear(minimum.x, maximum.y, minimum.z);
glm::vec3 topRightNear(maximum.x, maximum.y, minimum.z);
glm::vec3 topLeftFar(minimum.x, maximum.y, maximum.z);
glm::vec3 topRightFar(maximum.x, maximum.y, maximum.z);
glm::vec3 bottomLeftNearTransformed = transformPoint(matrix, bottomLeftNear);
glm::vec3 bottomRightNearTransformed = transformPoint(matrix, bottomRightNear);
glm::vec3 bottomLeftFarTransformed = transformPoint(matrix, bottomLeftFar);
glm::vec3 bottomRightFarTransformed = transformPoint(matrix, bottomRightFar);
glm::vec3 topLeftNearTransformed = transformPoint(matrix, topLeftNear);
glm::vec3 topRightNearTransformed = transformPoint(matrix, topRightNear);
glm::vec3 topLeftFarTransformed = transformPoint(matrix, topLeftFar);
glm::vec3 topRightFarTransformed = transformPoint(matrix, topRightFar);
minimum = glm::min(bottomLeftNearTransformed,
glm::min(bottomRightNearTransformed,
glm::min(bottomLeftFarTransformed,
glm::min(bottomRightFarTransformed,
glm::min(topLeftNearTransformed,
glm::min(topRightNearTransformed,
glm::min(topLeftFarTransformed,
topRightFarTransformed)))))));
maximum = glm::max(bottomLeftNearTransformed,
glm::max(bottomRightNearTransformed,
glm::max(bottomLeftFarTransformed,
glm::max(bottomRightFarTransformed,
glm::max(topLeftNearTransformed,
glm::max(topRightNearTransformed,
glm::max(topLeftFarTransformed,
topRightFarTransformed)))))));
_corner = minimum;
_scale = maximum - minimum;
}

View file

@ -58,7 +58,6 @@ public:
const glm::vec3& getMinimumPoint() const { return _corner; }
glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); }
bool contains(const glm::vec3& point) const;
bool contains(const AABox& otherBox) const;
bool touches(const AABox& otherBox) const;
@ -93,10 +92,19 @@ public:
void scale(float scale);
void scale(const glm::vec3& scale);
/// make the AABox bigger (scale about it's center)
void embiggen(float scale);
void embiggen(const glm::vec3& scale);
// Transform the extents with transform
void transform(const Transform& transform);
bool isInvalid() const { return _corner == glm::vec3(std::numeric_limits<float>::infinity()); }
// Transform the extents with matrix
void transform(const glm::mat4& matrix);
static const glm::vec3 INFINITY_VECTOR;
bool isInvalid() const { return _corner == INFINITY_VECTOR; }
private:
glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const;

View file

@ -151,3 +151,21 @@ void AABoxTests::testTouchesSphere() {
}
}
void AABoxTests::testScale() {
AABox box1(glm::vec3(2.0f), glm::vec3(1.0f));
QCOMPARE(box1.contains(glm::vec3(0.0f)), false);
box1.scale(glm::vec3(10.0f));
QCOMPARE(box1.contains(glm::vec3(0.0f)), false);
QCOMPARE(box1.contains(glm::vec3(2.0f * 10.0f)), true);
AABox box2(glm::vec3(2.0f), glm::vec3(1.0f));
QCOMPARE(box2.contains(glm::vec3(0.0f)), false);
box2.embiggen(glm::vec3(10.0f));
QCOMPARE(box2.contains(glm::vec3(0.0f)), true);
AABox box3;
box3 += glm::vec3(0.0f, 0.0f, 0.0f);
box3 += glm::vec3(1.0f, 1.0f, 1.0f);
box3 += glm::vec3(-1.0f, -1.0f, -1.0f);
QCOMPARE(box3.contains(glm::vec3(0.5f, 0.5f, 0.5f)), true);
}

View file

@ -23,6 +23,7 @@ private slots:
void testCtorsAndSetters();
void testContainsPoint();
void testTouchesSphere();
void testScale();
};
#endif // hifi_AABoxTests_h