Files
o3de/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/GraphicsItems/AnimatedPulse.cpp
T
Steve Pham 38261d0800 Shorten copyright headers by splitting into 2 lines (#2213)
* Updated all copyright headers to split the longer original copyright line into 2 shorter lines

Signed-off-by: Steve Pham <spham@amazon.com>
2021-07-16 15:25:48 -07:00

240 lines
8.1 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/PlatformDef.h>
AZ_PUSH_DISABLE_WARNING(4251 4800 4244, "-Wunknown-warning-option")
#include <QGraphicsScene>
#include <QTimer>
#include <QPainter>
#include <QPainterPath>
AZ_POP_DISABLE_WARNING
#include <AzCore/Component/Entity.h>
#include <GraphCanvas/GraphicsItems/AnimatedPulse.h>
#include <GraphCanvas/Utils/QtVectorMath.h>
namespace GraphCanvas
{
//////////////////////////////
// AnimatedPulseControlPoint
//////////////////////////////
AnimatedPulseControlPoint::AnimatedPulseControlPoint(const QPointF& startPoint, const QPointF& endPoint)
: m_startPoint(startPoint)
{
m_delta = endPoint - startPoint;
}
QPointF AnimatedPulseControlPoint::GetPoint(float percent) const
{
return m_startPoint + m_delta * percent;
}
///////////////////////////////
// AnimatedPulseConfiguration
///////////////////////////////
AnimatedPulseConfiguration::AnimatedPulseConfiguration()
: m_enableGradient(false)
, m_drawColor(0,0,0)
, m_durationSec(1.0f)
{
}
//////////////////
// AnimatedPulse
//////////////////
AnimatedPulse::AnimatedPulse(const AnimatedPulseConfiguration& pulseConfiguration)
: GraphicsEffect<QGraphicsItem>()
, m_elapsedDuration(0)
, m_configuration(pulseConfiguration)
{
PulseRequestBus::Handler::BusConnect(GetEffectId());
AZ::TickBus::Handler::BusConnect();
setAcceptHoverEvents(false);
setAcceptDrops(false);
setAcceptTouchEvents(false);
setFlag(QGraphicsItem::ItemIsMovable, false);
setFlag(QGraphicsItem::ItemIsFocusable, false);
if (!m_configuration.m_controlPoints.empty())
{
QPointF testPoint = m_configuration.m_controlPoints.front().GetPoint(1.0f);
m_boundingRect.setTopLeft(testPoint);
m_boundingRect.setBottomRight(testPoint);
}
for (const AnimatedPulseControlPoint& controlPoint : m_configuration.m_controlPoints)
{
for (float controlValue : {0.0f, 1.0f})
{
QPointF testPoint = controlPoint.GetPoint(controlValue);
if (m_boundingRect.left() > testPoint.x())
{
m_boundingRect.setLeft(testPoint.x());
}
else if (m_boundingRect.right() < testPoint.x())
{
m_boundingRect.setRight(testPoint.x());
}
if (m_boundingRect.top() > testPoint.y())
{
m_boundingRect.setTop(testPoint.y());
}
else if (m_boundingRect.bottom() < testPoint.y())
{
m_boundingRect.setBottom(testPoint.y());
}
}
}
setPos(m_boundingRect.center());
setZValue(m_configuration.m_zValue);
// Points are given to use in absolute coordinates, and we need them to be in relative coordinates.
// we are positioned at the center of the to allow for the drawing to make sense. So we need to offset out bounding box
// accordingly.
m_boundingRect.moveTo(-m_boundingRect.width() * 0.5f, -m_boundingRect.height() * 0.5f);
}
AnimatedPulse::~AnimatedPulse()
{
AZ::SystemTickBus::Handler::BusDisconnect();
}
void AnimatedPulse::OnSystemTick()
{
AZ::SystemTickBus::Handler::BusDisconnect();
PulseNotificationBus::Event(GetEffectId(), &PulseNotifications::OnPulseComplete);
QGraphicsScene* graphicsScene = this->scene();
graphicsScene->removeItem(this);
delete this;
}
void AnimatedPulse::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
{
m_elapsedDuration += deltaTime;
if (m_elapsedDuration >= m_configuration.m_durationSec)
{
AZ::TickBus::Handler::BusDisconnect();
AZ::SystemTickBus::Handler::BusConnect();
}
update();
}
QRectF AnimatedPulse::boundingRect() const
{
return m_boundingRect;
}
void AnimatedPulse::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
{
if (m_configuration.m_controlPoints.empty())
{
return;
}
static const float k_pulseWidth = 60.0f;
painter->save();
float percent = m_elapsedDuration / m_configuration.m_durationSec;
if (percent >= 1.0f)
{
percent = 1.0f;
}
painter->setPen(Qt::PenStyle::NoPen);
if (m_configuration.m_enableGradient)
{
QColor fullColor = m_configuration.m_drawColor;
fullColor.setAlpha(aznumeric_cast<int>(255 - (255 * percent)));
QColor transparentColor(0,0,0,0);
// This is pretty bad perf wise(causes a lot of unnecessary redrawing if the pulse is large enough).
// This case could be cleaned up, but since we never use it, it's not really worth it.
// This is also still not perfect, as there is a slight visible seam between the edges on hard corners.
//
// But to make this more performant in general will probably require a different approach here.
// - A Radial Gradient looks really bad on oblong shapes, and bad on pretty much anything non-circular(which we don't really support here)
for (int i = 0; i < m_configuration.m_controlPoints.size(); ++i)
{
QPointF sceneStartPoint = m_configuration.m_controlPoints[i].GetPoint(percent);
QPointF sceneStartPointOffset = m_configuration.m_controlPoints[i].GetPoint(0.0f);
int elementIndex = (i + 1) % m_configuration.m_controlPoints.size();
QPointF sceneEndPoint = m_configuration.m_controlPoints[elementIndex].GetPoint(percent);
QPointF sceneEndPointOffset = m_configuration.m_controlPoints[elementIndex].GetPoint(0.0f);
QPointF endCenter = (sceneStartPoint + sceneEndPoint) * 0.5f;
QPointF offsetCenter = (sceneStartPointOffset + sceneEndPointOffset) * 0.5f;
float distance = QtVectorMath::GetLength(offsetCenter - endCenter);
QLinearGradient linearGradient(offsetCenter - pos(), endCenter - pos());
linearGradient.setColorAt(0.0, transparentColor);
if (distance > k_pulseWidth)
{
linearGradient.setColorAt((distance - k_pulseWidth) / distance, transparentColor);
}
linearGradient.setColorAt(1.0, fullColor);
QPainterPath painterPath;
painterPath.moveTo(sceneStartPoint - pos());
painterPath.lineTo(sceneEndPoint - pos());
painterPath.lineTo(sceneEndPointOffset - pos());
painterPath.lineTo(sceneStartPointOffset - pos());
painterPath.closeSubpath();
painter->setBrush(linearGradient);
painter->drawPath(painterPath);
}
}
else
{
QPainterPath painterPath;
painterPath.moveTo(m_configuration.m_controlPoints.front().GetPoint(percent) - pos());
for (int i = 1; i < m_configuration.m_controlPoints.size(); ++i)
{
painterPath.lineTo(m_configuration.m_controlPoints[i].GetPoint(percent) - pos());
}
painterPath.closeSubpath();
QColor drawColor = m_configuration.m_drawColor;
drawColor.setAlpha(aznumeric_cast<int>(255 - 192 * percent));
painter->setBrush(drawColor);
painter->drawPath(painterPath);
}
painter->restore();
}
void AnimatedPulse::OnGraphicsEffectCancelled()
{
PulseNotificationBus::Event(GetEffectId(), &PulseNotifications::OnPulseCanceled);
}
}