Skip to content

Instantly share code, notes, and snippets.

@adderly
Created May 13, 2015 18:58
Show Gist options
  • Save adderly/48e570ba900765d5ce60 to your computer and use it in GitHub Desktop.
Save adderly/48e570ba900765d5ce60 to your computer and use it in GitHub Desktop.
ImageRenderer localBox modification.
/*
* Copyright (C) 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <qmath.h>
#include "Actor.h"
#include "Body.h"
#include "Camera.h"
#include "Engine.h"
#include "ImageRenderer.h"
#include "renderer/Texture.h"
#include "TextureManager.h"
#include "renderer/RenderList.h"
#include "renderer/Renderer.h"
#include "utils/Util.h"
ImageRenderer::ImageRenderer(QQuickItem* parent) : Graphic(parent) {
setFlag(ItemHasContents, true);
mRenderNode.callback = this;
setCulled(true);
mTextures.resize(2);
}
ImageRenderer::~ImageRenderer() {
}
void ImageRenderer::setVisible(bool value) {
mVisible = value;
updateVisibility();
emit visibleChanged();
}
void ImageRenderer::setSizeScale(float value) {
mSizeScale = value;
mTransformDirty = true;
emit sizeScaleChanged();
}
void ImageRenderer::setAspectRatio(float value) {
mAspectRatio = value;
mTransformDirty = true;
emit aspectRatioChanged();
}
void ImageRenderer::setSource(const QString& value) {
bool sourceChanging = value != mSource;
mSource = value;
if (sourceChanging) {
mSourceDirty = true;
}
emit sourceChanged();
}
void ImageRenderer::setCacheRenderParams(bool value) {
mCacheRenderParams = value;
emit cacheRenderParamsChanged();
}
void ImageRenderer::setCullingControlsBodyActive(bool value) {
mCullingControlsBodyActive = value;
emit cullingControlsBodyActiveChanged();
}
void ImageRenderer::setCustomLocalBox(QRectF lbox){
mCustomLocalBox = lbox;
emit customLocalBoxChanged();
}
void ImageRenderer::reloadTexture() {
mTextures[mCurrentTexture] = Engine::getInstance()->getTextureManager()->getTexture(mSource);
mTransformDirty = true;
update();
}
void ImageRenderer::updateLocalBoundingBox() {
QRectF bounds;
float textureAspectRatio = 1.0f;
if (mTextures[mCurrentTexture]) {
QSizeF textureSize = QSizeF( mTextures[mCurrentTexture]->getWidth(), mTextures[mCurrentTexture]->getHeight());
computeDestTextureSize(&textureSize);
textureAspectRatio = textureSize.height() / (float) textureSize.width();
float scale = mSizeScale;
float aspectRatio = mAspectRatio;
float scaleX = scale;
float scaleY = scale * aspectRatio;
if(mCustomLocalBox.isNull()){
bounds = QRectF(-scaleX / 2, -scaleY / 2, scaleX, scaleY);
}else{
bounds = mCustomLocalBox;
textureAspectRatio = 1;
}
}
// qDebug() << mSource << " Bounds = " << bounds;
mLocalBoundingBox = bounds;
mTextureAspectRatio = textureAspectRatio;
}
void ImageRenderer::render(RenderNode*) {
Engine::getInstance()->getRenderer()->drawSprite(
mRenderTexture,
Vector2(mRenderPos.x(), mRenderPos.y()),
mRenderSize.width(), mRenderSize.height(),
Vector2(mRenderTextureSubRect.x(), mRenderTextureSubRect.y()),
mRenderTextureSubRect.width(), mRenderTextureSubRect.height(),
mRenderRotation,
mRenderOpacity);
}
void ImageRenderer::synchronizeForRendering(RenderList* renderList) {
// Update our texture.
if (mSourceDirty) {
mSourceDirty = false;
reloadTexture();
}
if (mTransformDirty) {
mTransformDirty = false;
updateLocalBoundingBox();
}
// Because most objects will be off screen, cull using the object's origin. This lets us
// check one point, instead of four.
RenderParameters renderParams;
if (!mCacheRenderParams || !mHasCachedRenderParams) {
getFlattenedRenderParameters(&renderParams);
if (mCacheRenderParams) {
mCachedRenderParams = renderParams;
mHasCachedRenderParams = true;
}
} else {
renderParams = mCachedRenderParams;
}
Camera* camera = Engine::getInstance()->getCamera();
QPointF worldPos = renderParams.worldPosition;
const QRectF& cullBounds = camera->getWorldCullBounds();
// The maximum extent of beyond the origin when this object is rotated is SQRT(2) / 2 times the
// longest dimension (ie. when the object is at 45 degree rotation). As an approximation, we'll
// just use the longest dimension since that is even longer.
float sizeX = mLocalBoundingBox.width();
float sizeY = mLocalBoundingBox.height();
float majorAxisLength = qMax(fabsf(sizeX), fabsf(sizeY * mTextureAspectRatio));
bool isCulled = (worldPos.x() + majorAxisLength) < cullBounds.left()
|| (worldPos.x() - majorAxisLength) > cullBounds.right()
|| (worldPos.y() + majorAxisLength) < cullBounds.top()
|| (worldPos.y() - majorAxisLength) > cullBounds.bottom();
// TODO: If this object has not been culled yet, we could perform a more precise test using the
// image's bounding box, but we would have to account for rotation and scaling.
if (isCulled) {
// Hide node.
setCulled(true);
} else {
// Transform node.
if (!QQuickItem::isVisible()) {
setCulled(false);
}
if (!mCulled && mVisible && parentItem()->isVisible() && opacity() > 0.0f) {
QPointF renderPos = worldPos;
// For non-square textures, anchor their bottoms to the square centered at the pivot,
// of the width of the texture.
// TODO: This is a legacy thing. When we fixed the editor to support non-square
// textures, we did it in a funny way. We should consider fixing these
// (difficult).
float offsetAmount = (1.0f - mTextureAspectRatio) * 0.5f * mSizeScale * mAspectRatio;
if (offsetAmount != 0.0f) {
// The offset we apply needs to be rotated from the y-axis into rotated space.
float angleCos = renderParams.rotation == 0.0f ? 1.0f : qCos(renderParams.rotation);
float angleSin = renderParams.rotation == 0.0f ? 0.0f : qSin(renderParams.rotation);
renderPos += QPointF(offsetAmount * -angleSin, offsetAmount * angleCos);
}
// Copy double buffered data.
mRenderPos = renderPos;
mRenderSize = QSizeF(sizeX * 0.5f, sizeY * 0.5f * mTextureAspectRatio);
mRenderRotation = renderParams.rotation;
mRenderOpacity = renderParams.opacity;
mRenderNode.zDepth = renderParams.zDepth;
mRenderTextureSubRect = QRectF(0.0f, 0.0f, 1.0f, 1.0f);
computeSourceTextureRect(&mRenderTextureSubRect);
renderList->addNode(&mRenderNode);
}
}
// Always update texture to avoid dangling pointers. Avoid excessive shared pointer copies
// though because the texture usually changes infrequently.
if (mTextures[mCurrentTexture] != mRenderTexture) {
mRenderTexture = mTextures[mCurrentTexture];
}
}
void ImageRenderer::componentComplete() {
Graphic::componentComplete();
checkBody();
}
void ImageRenderer::checkBody() {
if (mBody) {
return;
}
mActor = Util::findParentOfType<Actor>(this);
if (mActor) {
mBody = mActor->getBody();
}
}
void ImageRenderer::computeDestTextureSize(QSizeF*) const {
// Default implementation does nothing.
}
void ImageRenderer::computeSourceTextureRect(QRectF*) const {
// Default implementation does nothing.
}
void ImageRenderer::markSourceTextureRectDirty() {
mTransformDirty = true;
}
void ImageRenderer::setCulled(bool value) {
mCulled = value;
updateVisibility();
}
void ImageRenderer::updateVisibility() {
// We hid QQuickItem::setVisible, but we can still explicitly access it. Use it to hide our
// node.
bool visible = !mCulled && mVisible;
if (visible != mWasVisible) {
mWasVisible = visible;
QQuickItem::setVisible(visible);
if (mCullingControlsBodyActive) {
checkBody();
if (mBody) {
mBody->setActive(visible);
}
}
}
}
/*
* Copyright (C) 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef IMAGERENDERER_H
#define IMAGERENDERER_H
#include <memory>
#include "Graphic.h"
#include "renderer/RenderNode.h"
class Actor;
class Body;
class Texture;
class QSGTexture;
/**
* @ingroup Engine
* @ingroup QQuickItem
* @brief Graphic which displays a texture as an image.
*
* The texture is loaded and cached using TextureManager. This class also performs a crude scissor
* test to cull images from view, reducing the load on the geometry batch merger freeing up CPU
* time.
* TODO: add flexibility to render into the qml normal tree. AKA: All it in the Graphic class.
* FIXME: Fix the an weird issue with texture aspect ratio.
* FIXME: MESS with the rendering and positioning OMG. Nightmare, nah just a kind of bad dream.
*/
class ImageRenderer : public Graphic, public RenderableInterface {
Q_OBJECT
/**
* @brief Override of QQuickItem's visible property to set visibility.
* @note This is done to preserve the convention of using @c visible to control visibility.
* Internally we set @c QQuickItem's @c visible property, and @c visible is not @c virtual, so
* we cannot override it in the C++ sense. Instead we hide it.
*/
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged)
/**
* @brief Width of this image, in world coordinates.
*/
Q_PROPERTY(float sizeScale READ getSizeScale WRITE setSizeScale NOTIFY sizeScaleChanged)
/**
* @brief Height of this image, as a ratio of the width.
*/
Q_PROPERTY(float aspectRatio READ getAspectRatio WRITE setAspectRatio NOTIFY aspectRatioChanged)
/**
* @brief Path to the source texture.
*/
Q_PROPERTY(QString source READ getSource WRITE setSource NOTIFY sourceChanged)
/**
* @brief Controls whether or not the position, rotation, opacity, and z-depth are cached on
* first render, and cached copies are used subsequently.
*
* This improves performance, but creates stale data if the Actor is moving or transforming.
* @see Graphic::RenderParameters
*/
Q_PROPERTY(bool cacheRenderParams READ getCacheRenderParams WRITE setCacheRenderParams
NOTIFY cacheRenderParamsChanged)
/**
* @brief Controls whether or not the Actor's associated Body's Body#active property is linked
* to this image's visibility.
*
* If this property is @c true, when this image is culled or its #visible is set to @c false,
* the associated Body will be deactivated. The Body is found through the Actor#body property.
*/
Q_PROPERTY(bool cullingControlsBodyActive READ getCullingControlsBodyActive
WRITE setCullingControlsBodyActive NOTIFY cullingControlsBodyActiveChanged)
/**
* @brief Custom boundingbox.
*/
Q_PROPERTY(QRectF customLocalBox READ getCustomLocalBox WRITE setCustomLocalBox NOTIFY customLocalBoxChanged)
public:
/**
* @brief Construct an ImageRenderer.
* @param parent Parent item
*/
explicit ImageRenderer(QQuickItem* parent = nullptr);
virtual ~ImageRenderer();
/**
* @brief Returns #visible.
*/
bool isVisible() const { return mVisible; }
/**
* @brief Sets #visible.
* @param value Boolean to set #visible to
*/
void setVisible(bool value);
/**
* @brief Returns #sizeScale.
*/
float getSizeScale() const { return mSizeScale; }
/**
* @brief Sets #sizeScale.
* @param value Float to set #sizeScale to
*/
void setSizeScale(float value);
/**
* @brief Returns #aspectRatio.
*/
float getAspectRatio() const { return mAspectRatio; }
/**
* @brief Sets #aspectRatio.
* @param value Float to set #aspectRatio to
*/
void setAspectRatio(float value);
/**
* @brief Returns #source.
*/
const QString& getSource() const { return mSource; }
/**
* @brief Sets #source.
* @param value @c QString to set #source to
*/
void setSource(const QString& value);
/**
* @brief Returns #cacheRenderParams.
*/
bool getCacheRenderParams() const { return mCacheRenderParams; }
/**
* @brief Sets #cacheRenderParams.
* @param value Boolean to set #cacheRenderParams to
*/
void setCacheRenderParams(bool value);
/**
* @brief Returns #cullingControlsBodyActive.
*/
bool getCullingControlsBodyActive() const { return mCullingControlsBodyActive; }
/**
* @brief Sets #cullingControlsBodyActive.
* @param value Boolean to set #cullingControlsBodyActive to
*/
void setCullingControlsBodyActive(bool value);
/**
* @brief This is for external image positioning.
*/
void setCustomLocalBox(QRectF lbox);
/**
* @brief Returns the #customLocalBox.
*/
QRectF getCustomLocalBox() { return mCustomLocalBox; }
/**
* @brief Returns whether or not this image has been culled because it is off screen.
*/
bool isCulled() const { return mCulled; }
signals:
/**
* @brief Emitted when #visible changes.
*/
void visibleChanged();
/**
* @brief Emitted when #sizeScale changes.
*/
void sizeScaleChanged();
/**
* @brief Emitted when #aspectRatio changes.
*/
void aspectRatioChanged();
/**
* @brief Emitted when #source changes.
*/
void sourceChanged();
/**
* @brief Emitted when #cacheRenderParams changes.
*/
void cacheRenderParamsChanged();
/**
* @brief Emitted when #cullingControlsBodyActive changes.
*/
void cullingControlsBodyActiveChanged();
/**
* @brief Emitted when #customLocalBox changes.
*/
void customLocalBoxChanged();
protected:
virtual void synchronizeForRendering(RenderList* renderList) override;
virtual void render(RenderNode *node) override;
/**
* @brief Post-initialization that checks for the existence of a parent Actor and the parent
* Actor's Body, caching the results for use in rendering.
*/
virtual void componentComplete() override;
/**
* @brief Computes the size to treat the source Texture, for purposes of computing destination
* rectangle size.
*
* The actual texture size is passed in as @c textureSize. The desired texture size should be
* written back to @c textureSize.
* @note This can be used in conjunction with computeSourceTextureRect() to select a
* subrectangle of the source texture for rendering.
* @note The default implementation leaves @c textureSize unmodified.
* @param textureSize Actual texture size, also receiving desired texture size
*/
virtual void computeDestTextureSize(QSizeF* textureSize) const;
/**
* @brief Computes the subrectangle within the source Texture to render.
*
* @c textureSubRect initially contains a unit rectangle from <tt>(0.0f, 0.0f)</tt> to
* <tt>(1.0f, 1.0f)</tt>. The desired subrectangle should be written to @c textureSubRect by
* this function.
* @note This can be used in conjunction with computeDestTextureSize() to select a
* subrectangle of the source texture for rendering.
* @note The default implementation leaves @c textureSubRect unmodified.
* @param textureSubRect
*/
virtual void computeSourceTextureRect(QRectF* textureSubRect) const;
/**
* @brief Marks the texture rectangle as dirty, making ImageRenderer update.
*/
void markSourceTextureRectDirty();
private:
void reloadTexture();
void updateLocalBoundingBox();
void setCulled(bool value);
void updateVisibility();
void checkBody();
bool mVisible = true;
bool mWasVisible = true;
bool mCacheRenderParams = false;
bool mCullingControlsBodyActive = false;
QRectF mCustomLocalBox;
Actor* mActor = nullptr;
Body* mBody = nullptr;
float mSizeScale = 1.0f;
float mAspectRatio = 1.0f;
QRectF mLocalBoundingBox;
float mTextureAspectRatio = 1.0f;
QString mSource;
bool mCulled = false;
bool mSourceDirty = false;
bool mTransformDirty = true;
uint mCurrentTexture = 0;
std::vector<TexturePtr> mTextures;
bool mHasCachedRenderParams = false;
RenderParameters mCachedRenderParams;
// Double buffered data for threaded rendering.
RenderNode mRenderNode;
TexturePtr mRenderTexture;
QPointF mRenderPos;
QRectF mRenderTextureSubRect;
QSizeF mRenderSize;
float mRenderRotation = 0.0f;
float mRenderOpacity = 1.0f;
};
Q_DECLARE_METATYPE(ImageRenderer*)
#endif // IMAGERENDERER_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment