414 lines
13 KiB
C++

// Released under the MIT License. See LICENSE for details.
#include "ballistica/scene_v1/node/image_node.h"
#include "ballistica/base/graphics/component/simple_component.h"
#include "ballistica/core/core.h"
#include "ballistica/scene_v1/node/node_attribute.h"
#include "ballistica/scene_v1/node/node_type.h"
#include "ballistica/scene_v1/support/scene.h"
namespace ballistica::scene_v1 {
class ImageNodeType : public NodeType {
public:
#define BA_NODE_TYPE_CLASS ImageNode
BA_NODE_CREATE_CALL(CreateImage);
BA_FLOAT_ARRAY_ATTR(scale, scale, SetScale);
BA_FLOAT_ARRAY_ATTR(position, position, SetPosition);
BA_FLOAT_ATTR(opacity, opacity, set_opacity);
BA_FLOAT_ARRAY_ATTR(color, color, SetColor);
BA_FLOAT_ARRAY_ATTR(tint_color, tint_color, SetTintColor);
BA_FLOAT_ARRAY_ATTR(tint2_color, tint2_color, SetTint2Color);
BA_BOOL_ATTR(fill_screen, fill_screen, SetFillScreen);
BA_BOOL_ATTR(has_alpha_channel, has_alpha_channel, set_has_alpha_channel);
BA_BOOL_ATTR(absolute_scale, absolute_scale, set_absolute_scale);
BA_FLOAT_ATTR(tilt_translate, tilt_translate, set_tilt_translate);
BA_FLOAT_ATTR(rotate, rotate, set_rotate);
BA_BOOL_ATTR(premultiplied, premultiplied, set_premultiplied);
BA_STRING_ATTR(attach, GetAttach, SetAttach);
BA_TEXTURE_ATTR(texture, texture, set_texture);
BA_TEXTURE_ATTR(tint_texture, tint_texture, set_tint_texture);
BA_TEXTURE_ATTR(mask_texture, mask_texture, set_mask_texture);
BA_MESH_ATTR(mesh_opaque, mesh_opaque, set_mesh_opaque);
BA_MESH_ATTR(mesh_transparent, mesh_transparent, set_mesh_transparent);
BA_FLOAT_ATTR(vr_depth, vr_depth, set_vr_depth);
BA_BOOL_ATTR(host_only, host_only, set_host_only);
BA_BOOL_ATTR(front, front, set_front);
#undef BA_NODE_TYPE_CLASS
ImageNodeType()
: NodeType("image", CreateImage),
scale(this),
position(this),
opacity(this),
color(this),
tint_color(this),
tint2_color(this),
fill_screen(this),
has_alpha_channel(this),
absolute_scale(this),
tilt_translate(this),
rotate(this),
premultiplied(this),
attach(this),
texture(this),
tint_texture(this),
mask_texture(this),
mesh_opaque(this),
mesh_transparent(this),
vr_depth(this),
host_only(this),
front(this) {}
};
static NodeType* node_type{};
auto ImageNode::InitType() -> NodeType* {
node_type = new ImageNodeType();
return node_type;
}
ImageNode::ImageNode(Scene* scene) : Node(scene, node_type) {}
ImageNode::~ImageNode() {
if (fill_screen_) {
scene()->decrement_bg_cover_count();
}
}
auto ImageNode::GetAttach() const -> std::string {
switch (attach_) {
case Attach::CENTER:
return "center";
case Attach::TOP_LEFT:
return "topLeft";
case Attach::TOP_CENTER:
return "topCenter";
case Attach::TOP_RIGHT:
return "topRight";
case Attach::CENTER_RIGHT:
return "centerRight";
case Attach::BOTTOM_RIGHT:
return "bottomRight";
case Attach::BOTTOM_CENTER:
return "bottomCenter";
case Attach::BOTTOM_LEFT:
return "bottomLeft";
case Attach::CENTER_LEFT:
return "centerLeft";
}
// This should be unreachable, but most compilers complain about
// control reaching the end of non-void function without it.
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnreachableCode"
throw Exception();
#pragma clang diagnostic pop
}
void ImageNode::SetAttach(const std::string& val) {
dirty_ = true;
if (val == "center") {
attach_ = Attach::CENTER;
} else if (val == "topLeft") {
attach_ = Attach::TOP_LEFT;
} else if (val == "topCenter") {
attach_ = Attach::TOP_CENTER;
} else if (val == "topRight") {
attach_ = Attach::TOP_RIGHT;
} else if (val == "centerRight") {
attach_ = Attach::CENTER_RIGHT;
} else if (val == "bottomRight") {
attach_ = Attach::BOTTOM_RIGHT;
} else if (val == "bottomCenter") {
attach_ = Attach::BOTTOM_CENTER;
} else if (val == "bottomLeft") {
attach_ = Attach::BOTTOM_LEFT;
} else if (val == "centerLeft") {
attach_ = Attach::CENTER_LEFT;
} else {
throw Exception("Invalid attach value for ImageNode: " + val);
}
}
void ImageNode::SetTint2Color(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of size 3 for tint2_color",
PyExcType::kValue);
}
tint2_color_ = vals;
tint2_red_ = tint2_color_[0];
tint2_green_ = tint2_color_[1];
tint2_blue_ = tint2_color_[2];
}
void ImageNode::SetTintColor(const std::vector<float>& vals) {
if (vals.size() != 3) {
throw Exception("Expected float array of size 3 for tint_color",
PyExcType::kValue);
}
tint_color_ = vals;
tint_red_ = tint_color_[0];
tint_green_ = tint_color_[1];
tint_blue_ = tint_color_[2];
}
void ImageNode::SetColor(const std::vector<float>& vals) {
if (vals.size() != 3 && vals.size() != 4) {
throw Exception("Got " + std::to_string(vals.size())
+ " values for 'color'; expected 3 or 4.");
}
red_ = vals[0];
green_ = vals[1];
blue_ = vals[2];
if (vals.size() == 4) {
alpha_ = vals[3];
} else {
alpha_ = 1.0f;
}
color_ = vals;
}
void ImageNode::SetScale(const std::vector<float>& vals) {
if (vals.size() != 1 && vals.size() != 2) {
throw Exception("Expected float array of length 1 or 2 for scale",
PyExcType::kValue);
}
dirty_ = true;
scale_ = vals;
}
void ImageNode::SetPosition(const std::vector<float>& vals) {
if (vals.size() != 2) {
throw Exception("Expected float array of length 2 for position",
PyExcType::kValue);
}
dirty_ = true;
position_ = vals;
}
void ImageNode::OnScreenSizeChange() { dirty_ = true; }
void ImageNode::SetFillScreen(bool val) {
bool old = fill_screen_;
fill_screen_ = val;
dirty_ = true;
// Help the scene keep track of stuff that covers the whole background
// (so it knows it doesnt have to clear).
if (!old && fill_screen_) {
scene()->increment_bg_cover_count();
}
if (old && !fill_screen_) {
scene()->decrement_bg_cover_count();
}
// We keep track of how many full-screen images are present at any given time.
// vr-mode uses this to lock down the overlay layer's position in that case.
}
void ImageNode::Draw(base::FrameDef* frame_def) {
if (host_only_ && !context_ref().GetHostSession()) {
return;
}
bool vr = g_core->IsVRMode();
// In vr mode we use the fixed overlay position if our scene
// is set for that.
bool vr_use_fixed = scene()->use_fixed_vr_overlay();
// Currently front and vr-fixed are mutually-exclusive.. need to fix.
if (front_) {
vr_use_fixed = false;
}
base::RenderPass& pass(*(vr_use_fixed ? frame_def->GetOverlayFixedPass()
: front_ ? frame_def->overlay_front_pass()
: frame_def->overlay_pass()));
// If the pass we're drawing into changes dimensions, recalc.
// Otherwise we break if a window is resized.
float screen_width = pass.virtual_width();
float screen_height = pass.virtual_height();
if (dirty_) {
float width = absolute_scale_ ? scale_[0] : screen_height * scale_[0];
float height =
(scale_.size() > 1)
? (absolute_scale_ ? scale_[1] : screen_height * scale_[1])
: width;
float center_x, center_y;
float scale_mult_x = absolute_scale_ ? 1.0f : screen_width;
float scale_mult_y = absolute_scale_ ? 1.0f : screen_height;
float screen_center_x = screen_width / 2;
float screen_center_y = screen_height / 2;
float tx = position_[0];
float ty = position_[1];
if (!absolute_scale_) {
tx *= scale_mult_x;
ty *= scale_mult_y;
}
switch (attach_) {
case Attach::BOTTOM_LEFT:
case Attach::BOTTOM_CENTER:
case Attach::BOTTOM_RIGHT: {
center_y = ty;
break;
}
case Attach::TOP_LEFT:
case Attach::TOP_CENTER:
case Attach::TOP_RIGHT: {
center_y = screen_height + ty;
break;
}
case Attach::CENTER_LEFT:
case Attach::CENTER_RIGHT:
case Attach::CENTER: {
center_y = screen_center_y + ty;
break;
}
}
switch (attach_) {
case Attach::TOP_LEFT:
case Attach::CENTER_LEFT:
case Attach::BOTTOM_LEFT: {
center_x = tx;
break;
}
case Attach::TOP_CENTER:
case Attach::CENTER:
case Attach::BOTTOM_CENTER: {
center_x = screen_center_x + tx;
break;
}
case Attach::TOP_RIGHT:
case Attach::CENTER_RIGHT:
case Attach::BOTTOM_RIGHT: {
center_x = screen_width + tx;
break;
}
}
if (fill_screen_) {
width_ = screen_width;
height_ = screen_height;
center_x_ = width_ * 0.5f;
center_y_ = height_ * 0.5f;
} else {
center_x_ = center_x;
center_y_ = center_y;
width_ = width;
height_ = height;
}
dirty_ = false;
}
float fin_center_x = center_x_;
float fin_center_y = center_y_;
float fin_width = width_;
float fin_height = height_;
// Tilt-translate doesn't happen in vr mode.
if (tilt_translate_ != 0.0f && !vr) {
Vector3f tilt = g_base->graphics->tilt();
fin_center_x -= tilt.y * tilt_translate_;
fin_center_y += tilt.x * tilt_translate_;
// If we're fullscreen and are tilting, crank our dimensions up
// slightly to account for tiltage.
#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
if (fill_screen_) {
float s = 1.0f - tilt_translate_ * 0.2f;
fin_width *= s;
fin_height *= s;
}
#endif // BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
}
bool has_alpha_channel = has_alpha_channel_;
float alpha = opacity_ * alpha_;
if (alpha < 0) {
alpha = 0;
}
base::MeshAsset* mesh_opaque_used = nullptr;
if (mesh_opaque_.Exists()) mesh_opaque_used = mesh_opaque_->mesh_data();
base::MeshAsset* mesh_transparent_used = nullptr;
if (mesh_transparent_.Exists()) {
mesh_transparent_used = mesh_transparent_->mesh_data();
}
// If no meshes were provided, use default image meshes.
if (!mesh_opaque_.Exists() && !mesh_transparent_.Exists()) {
if (vr && fill_screen_) {
#if BA_VR_BUILD
mesh_opaque_used =
g_base->assets->SysMesh(base::SysMeshID::kImage1x1VRFullScreen);
#else
throw Exception();
#endif // BA_VR_BUILD
} else {
base::SysMeshID m = fill_screen_ ? base::SysMeshID::kImage1x1FullScreen
: base::SysMeshID::kImage1x1;
if (has_alpha_channel) {
mesh_transparent_used = g_base->assets->SysMesh(m);
} else {
mesh_opaque_used = g_base->assets->SysMesh(m);
}
}
}
// Draw opaque portion either opaque or transparent depending on our
// global opacity.
if (mesh_opaque_used) {
// Draw in opaque pass if possible.
base::SimpleComponent c(&pass);
bool draw_transparent = (alpha < 0.999f);
// Stuff in the fixed vr overlay pass may inadvertently
// obscure the non-fixed overlay pass, so lets just always draw
// transparent to avoid that.
c.SetTransparent(draw_transparent);
c.SetPremultiplied(premultiplied_);
c.SetTexture(texture_.Exists() ? texture_->texture_data() : nullptr);
c.SetColor(red_, green_, blue_, alpha);
if (tint_texture_.Exists()) {
c.SetColorizeTexture(tint_texture_->texture_data());
c.SetColorizeColor(tint_red_, tint_green_, tint_blue_);
c.SetColorizeColor2(tint2_red_, tint2_green_, tint2_blue_);
}
c.SetMaskTexture(mask_texture_.Exists() ? mask_texture_->texture_data()
: nullptr);
c.PushTransform();
c.Translate(fin_center_x, fin_center_y,
vr ? vr_depth_ : g_base->graphics->overlay_node_z_depth());
if (rotate_ != 0.0f) c.Rotate(rotate_, 0, 0, 1);
c.Scale(fin_width, fin_height, fin_width);
c.DrawMeshAsset(mesh_opaque_used);
c.PopTransform();
c.Submit();
}
// Transparent portion.
if (mesh_transparent_used) {
base::SimpleComponent c(&pass);
c.SetTransparent(true);
c.SetPremultiplied(premultiplied_);
c.SetTexture(texture_.Exists() ? texture_->texture_data() : nullptr);
c.SetColor(red_, green_, blue_, alpha);
if (tint_texture_.Exists()) {
c.SetColorizeTexture(tint_texture_->texture_data());
c.SetColorizeColor(tint_red_, tint_green_, tint_blue_);
c.SetColorizeColor2(tint2_red_, tint2_green_, tint2_blue_);
}
c.SetMaskTexture(mask_texture_.Exists() ? mask_texture_->texture_data()
: nullptr);
c.PushTransform();
c.Translate(fin_center_x, fin_center_y,
vr ? vr_depth_ : g_base->graphics->overlay_node_z_depth());
if (rotate_ != 0.0f) c.Rotate(rotate_, 0, 0, 1);
c.Scale(fin_width, fin_height, fin_width);
c.DrawMeshAsset(mesh_transparent_used);
c.PopTransform();
c.Submit();
}
}
} // namespace ballistica::scene_v1