/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "LyShineExamples_precompiled.h" #include "UiCustomImageComponent.h" #include #include #include #include #include #include #include #include #include #include namespace LyShineExamples { //////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC MEMBER FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// UiCustomImageComponent::UiCustomImageComponent() : m_color(1.f, 1.f, 1.f, 1.f) , m_alpha(1.f) , m_sprite(nullptr) , m_uvs(0, 0, 1, 1) , m_clamp(true) , m_overrideColor(m_color) , m_overrideAlpha(m_alpha) , m_overrideSprite(nullptr) { } //////////////////////////////////////////////////////////////////////////////////////////////////// UiCustomImageComponent::~UiCustomImageComponent() { SAFE_RELEASE(m_sprite); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::ResetOverrides() { m_overrideColor = m_color; m_overrideAlpha = m_alpha; m_overrideSprite = nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::SetOverrideColor(const AZ::Color& color) { m_overrideColor.Set(color.GetAsVector3()); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::SetOverrideAlpha(float alpha) { m_overrideAlpha = alpha; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::SetOverrideSprite(ISprite* sprite, AZ::u32 /* cellIndex */) { m_overrideSprite = sprite; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::Render(LyShine::IRenderGraph* renderGraph) { // get fade value (tracked by UiRenderer) and compute the desired alpha for the image float fade = renderGraph->GetAlphaFade(); float desiredAlpha = m_overrideAlpha * fade; uint8 desiredPackedAlpha = static_cast(desiredAlpha * 255.0f); // if desired alpha is zero then no need to do any more if (desiredPackedAlpha == 0) { return; } ISprite* sprite = (m_overrideSprite) ? m_overrideSprite : m_sprite; ITexture* texture = (sprite) ? sprite->GetTexture() : nullptr; if (!texture) { // if there is no texture we will just use a white texture texture = gEnv->pRenderer->EF_GetTextureByID(gEnv->pRenderer->GetWhiteTextureId()); } if (m_isRenderCacheDirty) { RenderToCache(renderGraph); m_isRenderCacheDirty = false; } // Render cache is now valid - render using the cache // If the fade value has changed we need to update the alpha values in the vertex colors but we do // not want to touch or recompute the RGB values if (m_cachedPrimitive.m_vertices[0].color.a != desiredPackedAlpha) { // go through all the cached vertices and update the alpha values UCol desiredPackedColor = m_cachedPrimitive.m_vertices[0].color; desiredPackedColor.a = desiredPackedAlpha; for (int i = 0; i < m_cachedPrimitive.m_numVertices; ++i) { m_cachedPrimitive.m_vertices[i].color = desiredPackedColor; } } bool isTextureSRGB = false; bool isTexturePremultipliedAlpha = false; // we are not rendering from a render target with alpha in it LyShine::BlendMode blendMode = LyShine::BlendMode::Normal; renderGraph->AddPrimitive(&m_cachedPrimitive, texture, m_clamp, isTextureSRGB, isTexturePremultipliedAlpha, blendMode); } //////////////////////////////////////////////////////////////////////////////////////////////////// AZ::Color UiCustomImageComponent::GetColor() { return AZ::Color::CreateFromVector3AndFloat(m_color.GetAsVector3(), m_alpha); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::SetColor(const AZ::Color& color) { m_color.Set(color.GetAsVector3()); m_alpha = color.GetA(); m_overrideColor = m_color; m_overrideAlpha = m_alpha; MarkRenderCacheDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// ISprite* UiCustomImageComponent::GetSprite() { return m_sprite; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::SetSprite(ISprite* sprite) { if (m_sprite) { m_sprite->Release(); m_spritePathname.SetAssetPath(""); } m_sprite = sprite; if (m_sprite) { m_sprite->AddRef(); m_spritePathname.SetAssetPath(m_sprite->GetPathname().c_str()); } MarkRenderGraphDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// AZStd::string UiCustomImageComponent::GetSpritePathname() { return m_spritePathname.GetAssetPath(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::SetSpritePathname(AZStd::string spritePath) { m_spritePathname.SetAssetPath(spritePath.c_str()); MarkRenderGraphDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// UiCustomImageInterface::UVRect UiCustomImageComponent::GetUVs() { return m_uvs; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::SetUVs(UiCustomImageInterface::UVRect uvs) { m_uvs = uvs; MarkRenderCacheDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiCustomImageComponent::GetClamp() { return m_clamp; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::SetClamp(bool clamp) { m_clamp = clamp; MarkRenderGraphDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::OnCanvasSpaceRectChanged(AZ::EntityId /*entityId*/, const UiTransformInterface::Rect& /*oldRect*/, const UiTransformInterface::Rect& /*newRect*/) { MarkRenderCacheDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::OnTransformToViewportChanged() { MarkRenderCacheDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC STATIC MEMBER FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); AZ::BehaviorContext* behaviorContext = azrtti_cast(context); // Serialize this component if (serializeContext) { serializeContext->Class() ->Field("SpritePath", &UiCustomImageComponent::m_spritePathname) ->Field("Color", &UiCustomImageComponent::m_color) ->Field("Alpha", &UiCustomImageComponent::m_alpha) ->Field("UVCoords", &UiCustomImageComponent::m_uvs) ->Field("Clamp", &UiCustomImageComponent::m_clamp); AZ::EditContext* ec = serializeContext->GetEditContext(); if (ec) { auto editInfo = ec->Class("Custom Image", "A visual component to draw a rectangle with an optional sprite/texture"); editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiImage.png") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiImage.png") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true); editInfo->DataElement("Sprite", &UiCustomImageComponent::m_spritePathname, "Sprite path", "The sprite path. Can be overridden by another component such as an interactable.") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiCustomImageComponent::OnSpritePathnameChange); editInfo->DataElement(AZ::Edit::UIHandlers::Color, &UiCustomImageComponent::m_color, "Color", "The color tint for the image. Can be overridden by another component such as an interactable.") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiCustomImageComponent::OnColorChange); editInfo->DataElement(AZ::Edit::UIHandlers::Slider, &UiCustomImageComponent::m_alpha, "Alpha", "The transparency. Can be overridden by another component such as an interactable.") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiCustomImageComponent::OnColorChange) ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Max, 1.0f); editInfo->DataElement(0, &UiCustomImageComponent::m_uvs, "UV Rect", "The UV coordinates of the rectangle for rendering the texture.") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiCustomImageComponent::OnRenderSettingChange) ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshValues", 0x28e720d4)) ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show); // needed because sub-elements are hidden editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiCustomImageComponent::m_clamp, "Clamp", "Whether the image should be clamped or not.") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiCustomImageComponent::OnRenderSettingChange) ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshValues", 0x28e720d4)); } } if (behaviorContext) { behaviorContext->EBus("UiCustomImageBus") ->Event("GetColor", &UiCustomImageBus::Events::GetColor) ->Event("SetColor", &UiCustomImageBus::Events::SetColor) ->Event("GetSpritePathname", &UiCustomImageBus::Events::GetSpritePathname) ->Event("SetSpritePathname", &UiCustomImageBus::Events::SetSpritePathname) ->Event("GetUVs", &UiCustomImageBus::Events::GetUVs) ->Event("SetUVs", &UiCustomImageBus::Events::SetUVs) ->Event("GetClamp", &UiCustomImageBus::Events::GetClamp) ->Event("SetClamp", &UiCustomImageBus::Events::SetClamp); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // PROTECTED MEMBER FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::Init() { // If this is called from RC.exe for example these pointers will not be set. In that case // we only need to be able to load, init and save the component. It will never be // activated. if (!(gEnv && gEnv->pLyShine)) { return; } // Load our sprite from the path at the beginning of the game if (!m_sprite) { if (!m_spritePathname.GetAssetPath().empty()) { m_sprite = gEnv->pLyShine->LoadSprite(m_spritePathname.GetAssetPath().c_str()); } } m_overrideColor = m_color; m_overrideAlpha = m_alpha; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::Activate() { UiVisualBus::Handler::BusConnect(m_entity->GetId()); UiRenderBus::Handler::BusConnect(m_entity->GetId()); UiCustomImageBus::Handler::BusConnect(m_entity->GetId()); UiTransformChangeNotificationBus::Handler::BusConnect(m_entity->GetId()); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::Deactivate() { UiVisualBus::Handler::BusDisconnect(); UiRenderBus::Handler::BusDisconnect(); UiCustomImageBus::Handler::BusDisconnect(); UiTransformChangeNotificationBus::Handler::BusDisconnect(); } //////////////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE MEMBER FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::RenderToCache(LyShine::IRenderGraph* renderGraph) { UiTransformInterface::RectPoints points; EBUS_EVENT_ID(GetEntityId(), UiTransformBus, GetViewportSpacePoints, points); // points are a clockwise quad const AZ::Vector2 uvs[4] = { AZ::Vector2(m_uvs.m_left, m_uvs.m_top), AZ::Vector2(m_uvs.m_right, m_uvs.m_top) , AZ::Vector2(m_uvs.m_right, m_uvs.m_bottom), AZ::Vector2(m_uvs.m_left, m_uvs.m_bottom) }; RenderSingleQuad(renderGraph, points.pt, uvs); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::RenderSingleQuad(LyShine::IRenderGraph* renderGraph, const AZ::Vector2* positions, const AZ::Vector2* uvs) { float fade = renderGraph->GetAlphaFade(); float desiredAlpha = m_overrideAlpha * fade; AZ::Color color = AZ::Color::CreateFromVector3AndFloat(m_overrideColor.GetAsVector3(), desiredAlpha); color = color.GammaToLinear(); // the colors are specified in sRGB but we want linear colors in the shader uint32 packedColor = (color.GetA8() << 24) | (color.GetR8() << 16) | (color.GetG8() << 8) | color.GetB8(); IDraw2d::Rounding pixelRounding = IsPixelAligned() ? IDraw2d::Rounding::Nearest : IDraw2d::Rounding::None; const int numVertices = 4; if (numVertices != m_cachedPrimitive.m_numVertices) { if (m_cachedPrimitive.m_vertices) { delete [] m_cachedPrimitive.m_vertices; } m_cachedPrimitive.m_vertices = new SVF_P2F_C4B_T2F_F4B[numVertices]; m_cachedPrimitive.m_numVertices = numVertices; } // points are a clockwise quad for (int i = 0; i < numVertices; ++i) { AZ::Vector2 pos = Draw2dHelper::RoundXY(positions[i], pixelRounding); m_cachedPrimitive.m_vertices[i].xy = Vec2(pos.GetX(), pos.GetY()); m_cachedPrimitive.m_vertices[i].color.dcolor = packedColor; m_cachedPrimitive.m_vertices[i].st = Vec2(uvs[i].GetX(), uvs[i].GetY()); m_cachedPrimitive.m_vertices[i].texIndex = 0; m_cachedPrimitive.m_vertices[i].texHasColorChannel = 1; m_cachedPrimitive.m_vertices[i].texIndex2 = 0; m_cachedPrimitive.m_vertices[i].pad = 0; } static uint16 indices[6] = { 0, 1, 2, 2, 3, 0 }; m_cachedPrimitive.m_numIndices = 6; m_cachedPrimitive.m_indices = indices; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiCustomImageComponent::IsPixelAligned() { AZ::EntityId canvasEntityId; EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); bool isPixelAligned = true; EBUS_EVENT_ID_RESULT(isPixelAligned, canvasEntityId, UiCanvasBus, GetIsPixelAligned); return isPixelAligned; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::OnSpritePathnameChange() { ISprite* newSprite = nullptr; if (!m_spritePathname.GetAssetPath().empty()) { // Load the new texture. newSprite = gEnv->pLyShine->LoadSprite(m_spritePathname.GetAssetPath().c_str()); } SAFE_RELEASE(m_sprite); m_sprite = newSprite; MarkRenderGraphDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::OnColorChange() { m_overrideColor = m_color; m_overrideAlpha = m_alpha; MarkRenderCacheDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::OnRenderSettingChange() { MarkRenderCacheDirty(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::MarkRenderCacheDirty() { if (!m_isRenderCacheDirty) { m_isRenderCacheDirty = true; MarkRenderGraphDirty(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCustomImageComponent::MarkRenderGraphDirty() { // tell the canvas to invalidate the render graph (never want to do this while rendering) AZ::EntityId canvasEntityId; EBUS_EVENT_ID_RESULT(canvasEntityId, GetEntityId(), UiElementBus, GetCanvasEntityId); EBUS_EVENT_ID(canvasEntityId, UiCanvasComponentImplementationBus, MarkRenderGraphDirty); } }