mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-19 21:37:57 +08:00
414 lines
13 KiB
C++
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
|