Added proper hit test with rotation and pivoting

This commit is contained in:
2026-05-20 20:56:25 +01:00
parent 75b1bc5dbd
commit 6ff16e3db4
7 changed files with 181 additions and 35 deletions
+37 -17
View File
@@ -15,6 +15,7 @@ struct Element {
Type type;
Vec2<float> position = {0, 0};
float rotation = 0;
Vec2<float> pivot = {0, 0};
Vec2<float> scale = {1, 1};
float alpha = 1.0f;
float z = 0;
@@ -24,7 +25,7 @@ struct Element {
Element(Type type) : type(type) {}
virtual ~Element() = default;
virtual void render(Window &window, uint64_t abs_time) = 0;
virtual Vec2<float> size(Window *window) = 0;
virtual Vec2<float> size() = 0;
void click() {
on_click.call();
@@ -43,11 +44,11 @@ struct EImage : Element {
animation_pool.release(animation_id);
}
Vec2<float> size(Window *window) override {
Vec2<float> size() override {
Animation &animation = *animation_pool[animation_id];
if (animation.frames.empty())
return {0, 0};
return window->get_image_size(animation.frames[0]);
return window.get_image_size(animation.frames[0]);
}
void render(Window &window, uint64_t abs_time) override {
@@ -55,7 +56,7 @@ struct EImage : Element {
if (animation.frames.empty())
return;
int frame_index = (int)((abs_time * animation.frame_rate) / 1e9) % animation.frames.size();
window.draw(animation.frames[frame_index], position, z, rotation, scale, alpha);
window.draw(animation.frames[frame_index], position, z, rotation, pivot, scale, alpha);
}
};
@@ -86,10 +87,10 @@ struct EText : Element {
last_key = UINT16_MAX;
}
Vec2<float> size(Window *window) override {
Vec2<float> size() override {
if (text_id == UINT64_MAX)
return {0, 0};
return window->get_text_size(text_id);
return window.get_text_size(text_id);
};
void render(Window &window, uint64_t) override {
@@ -106,7 +107,7 @@ struct EText : Element {
last_key = key;
last_color = color;
}
window.write(text_id, position, z, rotation, scale, alpha);
window.write(text_id, position, z, rotation, pivot, scale, alpha);
}
private:
@@ -119,14 +120,14 @@ private:
struct ERect : Element {
Vec2<float> shape = {0, 0};
Color color = {255, 255, 255};
Vec2<float> size(Window *) override {
Vec2<float> size() override {
return shape;
};
ERect(Vec2<float> shape, Color color) : Element(Type::RECT), shape(shape), color(color) {}
void render(Window &window, uint64_t) override {
window.draw_rect(Rect::from_size(position, shape), color, z, rotation, scale, alpha);
window.draw_rect(Rect::from(position, shape), color, z, rotation, pivot, scale, alpha);
}
};
@@ -177,16 +178,19 @@ struct Scene {
element_pool[id]->click();
}
#warning "This does not take rotation into account \
reference and use reverse-transform on point then AABB check instead"
void render(Window &window, uint64_t abs_time) {
for (const uint64_t &e : element_ids)
element_pool[e]->render(window, abs_time);
}
std::vector<uint64_t> elements_at(Vec2<float> position) {
std::vector<uint64_t> result;
for (auto it = element_ids.rbegin(); it != element_ids.rend(); ++it) {
Element *e = element_pool[*it];
Vec2<float> size = e->size(&window);
Rect rect = Rect::from_size(e->position, size * e->scale);
if (rect.contains(position)) {
Vec2<float> size = e->size();
Rect rect = Rect::from(size);
if (rect.contains(world_to_local(position, e))) {
result.push_back(*it);
if (e->click_through)
continue;
@@ -198,9 +202,25 @@ reference and use reverse-transform on point then AABB check instead"
return result;
}
void render(Window &window, uint64_t abs_time) {
for (const uint64_t &e : element_ids)
element_pool[e]->render(window, abs_time);
Vec2<float> world_to_local(Vec2<float> p, Element *e) {
Vec2<float> size = e->size();
Vec2<float> pivot = e->pivot * size * e->scale;
p = p - e->position;
float rad = -e->rotation * (M_PI / 180.0f);
float c = cos(rad);
float s = sin(rad);
p = p - pivot;
p = {
p.x * c - p.y * s,
p.x * s + p.y * c
};
p = p + pivot;
p = p / e->scale;
return p;
}
};
+104 -5
View File
@@ -317,7 +317,9 @@ static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) {
return mrb_nil_value();
}
float x = 0, y = 0, z = 0, rotation = 0, scale_x = 1, scale_y = 1, alpha = 1;
float x = 0, y = 0, z = 0, rotation = 0;
float scale_x = 1, scale_y = 1, alpha = 1;
float pivot_x = 0.5, pivot_y = 0.5;
bool click_through = false;
if (!mrb_nil_p(kwargs)) {
mrb_value pos_hash = HASH_GET(kwargs, position);
@@ -332,6 +334,12 @@ static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) {
HASH_FLOAT(scale_hash, y, scale_y);
}
mrb_value pivot_hash = HASH_GET(kwargs, pivot);
if (!mrb_nil_p(pivot_hash)) {
HASH_FLOAT(pivot_hash, x, pivot_x);
HASH_FLOAT(pivot_hash, y, pivot_y);
}
HASH_FLOAT(kwargs, z, z);
HASH_FLOAT(kwargs, rotation, rotation);
HASH_FLOAT(kwargs, alpha, alpha);
@@ -351,6 +359,7 @@ static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) {
element->position = {x, y};
element->scale = {scale_x, scale_y};
element->rotation = rotation;
element->pivot = {pivot_x, pivot_y};
element->alpha = alpha;
element->z = z;
element->click_through = click_through;
@@ -533,6 +542,28 @@ static mrb_value element_text_rotation_set(mrb_state *mrb, mrb_value self) {
return mrb_nil_value();
}
static mrb_value element_text_pivot_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementText(mrb, self);
app::EText *element = (app::EText *)app::element_pool[id];
mrb_value pivot_hash = mrb_hash_new(mrb);
HASH_SET(pivot_hash, x, mrb_float_value(mrb, element->pivot.x));
HASH_SET(pivot_hash, y, mrb_float_value(mrb, element->pivot.y));
return pivot_hash;
}
static mrb_value element_text_pivot_set(mrb_state *mrb, mrb_value self) {
mrb_value pivot_hash;
mrb_get_args(mrb, "H", &pivot_hash);
uint64_t id = get_id_ElementText(mrb, self);
app::EText *element = (app::EText *)app::element_pool[id];
HASH_FLOAT(pivot_hash, x, element->pivot.x);
HASH_FLOAT(pivot_hash, y, element->pivot.y);
return mrb_nil_value();
}
static mrb_value element_text_scale_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementText(mrb, self);
app::EText *element = (app::EText *)app::element_pool[id];
@@ -609,13 +640,13 @@ static mrb_value element_text_click_through_set(mrb_state *mrb, mrb_value self)
static mrb_value element_text_width(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementText(mrb, self);
app::EText *element = (app::EText *)app::element_pool[id];
return mrb_float_value(mrb, element->size(&window).x);
return mrb_float_value(mrb, element->size().x);
}
static mrb_value element_text_height(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementText(mrb, self);
app::EText *element = (app::EText *)app::element_pool[id];
return mrb_float_value(mrb, element->size(&window).y);
return mrb_float_value(mrb, element->size().y);
}
static void ElementRect_free(mrb_state *mrb, void *ptr) {
@@ -643,7 +674,8 @@ static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) {
mrb_value kwargs;
mrb_get_args(mrb, "ffi|H", &w, &h, &color_i, &kwargs);
float x = 0, y = 0, z = 0, rotation = 0, scale_x = 1, scale_y = 1, alpha = 1;
float x = 0, y = 0, z = 0, rotation = 0;
float scale_x = 1, scale_y = 1, alpha = 1, pivot_x = 0.5, pivot_y = 0.5;
bool click_through = false;
if (!mrb_nil_p(kwargs)) {
mrb_value pos_hash = HASH_GET(kwargs, position);
@@ -658,6 +690,12 @@ static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) {
HASH_FLOAT(scale_hash, y, scale_y);
}
mrb_value pivot_hash = HASH_GET(kwargs, pivot);
if (!mrb_nil_p(pivot_hash)) {
HASH_FLOAT(pivot_hash, x, pivot_x);
HASH_FLOAT(pivot_hash, y, pivot_y);
}
HASH_FLOAT(kwargs, z, z);
HASH_FLOAT(kwargs, rotation, rotation);
HASH_FLOAT(kwargs, alpha, alpha);
@@ -679,6 +717,7 @@ static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) {
element->position = {x, y};
element->scale = {scale_x, scale_y};
element->rotation = rotation;
element->pivot = {pivot_x, pivot_y};
element->alpha = alpha;
element->z = z;
element->click_through = click_through;
@@ -829,6 +868,28 @@ static mrb_value element_rect_rotation_set(mrb_state *mrb, mrb_value self) {
return mrb_nil_value();
}
static mrb_value element_rect_pivot_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementRect(mrb, self);
app::ERect *element = (app::ERect *)app::element_pool[id];
mrb_value pivot_hash = mrb_hash_new(mrb);
HASH_SET(pivot_hash, x, mrb_float_value(mrb, element->pivot.x));
HASH_SET(pivot_hash, y, mrb_float_value(mrb, element->pivot.y));
return pivot_hash;
}
static mrb_value element_rect_pivot_set(mrb_state *mrb, mrb_value self) {
mrb_value pivot_hash;
mrb_get_args(mrb, "H", &pivot_hash);
uint64_t id = get_id_ElementRect(mrb, self);
app::ERect *element = (app::ERect *)app::element_pool[id];
HASH_FLOAT(pivot_hash, x, element->pivot.x);
HASH_FLOAT(pivot_hash, y, element->pivot.y);
return mrb_nil_value();
}
static mrb_value element_rect_scale_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementRect(mrb, self);
app::ERect *element = (app::ERect *)app::element_pool[id];
@@ -938,7 +999,8 @@ static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) {
return mrb_nil_value();
}
float x = 0, y = 0, z = 0, rotation = 0, scale_x = 1, scale_y = 1, alpha = 1;
float x = 0, y = 0, z = 0, rotation = 0;
float scale_x = 1, scale_y = 1, alpha = 1, pivot_x = 0.5, pivot_y = 0.5;
bool click_through = false;
if (!mrb_nil_p(kwargs)) {
mrb_value pos_hash = HASH_GET(kwargs, position);
@@ -953,6 +1015,12 @@ static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) {
HASH_FLOAT(scale_hash, y, scale_y);
}
mrb_value pivot_hash = HASH_GET(kwargs, pivot);
if (!mrb_nil_p(pivot_hash)) {
HASH_FLOAT(pivot_hash, x, pivot_x);
HASH_FLOAT(pivot_hash, y, pivot_y);
}
HASH_FLOAT(kwargs, z, z);
HASH_FLOAT(kwargs, rotation, rotation);
HASH_FLOAT(kwargs, alpha, alpha);
@@ -965,6 +1033,7 @@ static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) {
element->position = {x, y};
element->scale = {scale_x, scale_y};
element->rotation = rotation;
element->pivot = {pivot_x, pivot_y};
element->alpha = alpha;
element->z = z;
element->click_through = click_through;
@@ -1098,6 +1167,28 @@ static mrb_value element_image_rotation_set(mrb_state *mrb, mrb_value self) {
return mrb_nil_value();
}
static mrb_value element_image_pivot_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementImage(mrb, self);
app::EImage *element = (app::EImage *)app::element_pool[id];
mrb_value pivot_hash = mrb_hash_new(mrb);
HASH_SET(pivot_hash, x, mrb_float_value(mrb, element->pivot.x));
HASH_SET(pivot_hash, y, mrb_float_value(mrb, element->pivot.y));
return pivot_hash;
}
static mrb_value element_image_pivot_set(mrb_state *mrb, mrb_value self) {
mrb_value pivot_hash;
mrb_get_args(mrb, "H", &pivot_hash);
uint64_t id = get_id_ElementImage(mrb, self);
app::EImage *element = (app::EImage *)app::element_pool[id];
HASH_FLOAT(pivot_hash, x, element->pivot.x);
HASH_FLOAT(pivot_hash, y, element->pivot.y);
return mrb_nil_value();
}
static mrb_value element_image_scale_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementImage(mrb, self);
app::EImage *element = (app::EImage *)app::element_pool[id];
@@ -1572,14 +1663,18 @@ inline void setup() {
ADD_METHOD(ElementText, "color", element_text_color_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "font=", element_text_font_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "font", element_text_font_get, MRB_ARGS_NONE());
#warning "Make text params a proper class instead of using a hash for text params"
ADD_METHOD(ElementText, "params", element_text_params, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "params!", element_text_params_load, MRB_ARGS_NONE());
#warning "Make vector a proper class instead of using a hash for vectors"
ADD_METHOD(ElementText, "position=", element_text_position_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "position", element_text_position_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "scale=", element_text_scale_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "scale", element_text_scale_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "rotation=", element_text_rotation_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "rotation", element_text_rotation_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "pivot=", element_text_pivot_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "pivot", element_text_pivot_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "alpha=", element_text_alpha_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "alpha", element_text_alpha_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "z=", element_text_z_set, MRB_ARGS_REQ(1));
@@ -1608,6 +1703,8 @@ inline void setup() {
ADD_METHOD(ElementRect, "scale", element_rect_scale_get, MRB_ARGS_NONE());
ADD_METHOD(ElementRect, "rotation=", element_rect_rotation_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementRect, "rotation", element_rect_rotation_get, MRB_ARGS_NONE());
ADD_METHOD(ElementRect, "pivot=", element_rect_pivot_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementRect, "pivot", element_rect_pivot_get, MRB_ARGS_NONE());
ADD_METHOD(ElementRect, "alpha=", element_rect_alpha_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementRect, "alpha", element_rect_alpha_get, MRB_ARGS_NONE());
ADD_METHOD(ElementRect, "z=", element_rect_z_set, MRB_ARGS_REQ(1));
@@ -1630,6 +1727,8 @@ inline void setup() {
ADD_METHOD(ElementImage, "scale", element_image_scale_get, MRB_ARGS_NONE());
ADD_METHOD(ElementImage, "rotation=", element_image_rotation_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementImage, "rotation", element_image_rotation_get, MRB_ARGS_NONE());
ADD_METHOD(ElementImage, "pivot=", element_image_pivot_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementImage, "pivot", element_image_pivot_get, MRB_ARGS_NONE());
ADD_METHOD(ElementImage, "alpha=", element_image_alpha_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementImage, "alpha", element_image_alpha_get, MRB_ARGS_NONE());
ADD_METHOD(ElementImage, "z=", element_image_z_set, MRB_ARGS_REQ(1));
+1
View File
@@ -103,6 +103,7 @@ DEF_SYM(italic)
DEF_SYM(underline)
DEF_SYM(position)
DEF_SYM(rotation)
DEF_SYM(pivot)
DEF_SYM(scale)
DEF_SYM(alpha)
DEF_SYM(fps)
+7 -2
View File
@@ -80,6 +80,7 @@ struct Line {
Vec2<float> start;
Vec2<float> end;
Line() = default;
Line(Vec2<float> start, Vec2<float> end) : start(start), end(end) {}
bool intersects(const Line &other) const {
@@ -98,7 +99,7 @@ struct Line {
struct Rect {
Line diagonal; // from top-left to bottom-right
Rect(Vec2<float> a, Vec2<float> b) : diagonal(a, b) {
Rect(Vec2<float> a, Vec2<float> b) {
diagonal.start = {
MIN(a.x, b.x),
MIN(a.y, b.y)
@@ -109,7 +110,11 @@ struct Rect {
};
}
static Rect from_size(Vec2<float> position, Vec2<float> size) {
static Rect from(Vec2<float> size) {
return Rect({0, 0}, size);
}
static Rect from(Vec2<float> position, Vec2<float> size) {
return Rect(position, position + size);
}
+4 -3
View File
@@ -320,11 +320,11 @@ public:
uint64_t create_text(uint64_t font_id, const char *text, uint32_t length, Color color);
Vec2<float> get_text_size(uint64_t text_id);
bool write(uint64_t text_id, Vec2<float> position, float z, float angle, Vec2<float> scale, float alpha);
bool write(uint64_t text_id, Vec2<float> position, float z, float angle, Vec2<float> pivot, Vec2<float> scale, float alpha);
bool draw(uint64_t image_id, Vec2<float> position, float z, float angle, Vec2<float> scale, float alpha);
bool draw(uint64_t image_id, Vec2<float> position, float z, float angle, Vec2<float> pivot, Vec2<float> scale, float alpha);
bool draw_rect(Rect rect, Color color, float z, float angle, Vec2<float> scale, float alpha);
bool draw_rect(Rect rect, Color color, float z, float angle, Vec2<float> pivot, Vec2<float> scale, float alpha);
void render(); // the rest of the calls only build the instruction list, this actually renders everything added to the screen (following z order)
@@ -366,6 +366,7 @@ private:
float z;
float angle;
Vec2<float> pivot;
Vec2<float> scale;
float alpha;
+4 -2
View File
@@ -14,10 +14,12 @@ App.localize(:en, :btn_text, "Click %{name}!")
text = ElementText.new :btn_text, default_font, 0xFFFF00, {name: "Me"}, position: {x: 50, y: 50}, z: 1, click_through: true, alpha: 1
rect = ElementRect.new 100, 25, 0xFF0000, position: {x: 50, y: 50}, z: 0, rotation: 60, scale: { x: 1.5, y: 1 }, alpha: 0.4
rect = ElementRect.new 100, 25, 0xFF0000, position: {x: 50, y: 50}, z: 0, rotation: 60, pivot: {x: 0.5, y: 0.5}, scale: { x: 1, y: 1 }, alpha: 0.4
image_element = ElementImage.new bg_animation, position: {x: 0, y: 0}, z: -1, click_through: true, alpha: 1
pp text, rect, image_element
rect.on_click do
rect.color = rect.color == 0xFF0000 ?
0x00FF00 :
@@ -47,7 +49,7 @@ main_scene.on_update do |delta_time|
pp App.text_input if App.text_input != ""
next unless App.pressed?(:press)
next unless App.down?(:press)
pp "#{App.pressed?(:press)}, #{App.down?(:press)}, #{App.released?(:press)}"
+24 -6
View File
@@ -155,7 +155,7 @@ Vec2<float> Window::get_text_size(uint64_t text_id) {
return text_pool[text_id]->size;
}
bool Window::write(uint64_t text_id, Vec2<float> position, float z, float angle, Vec2<float> scale, float alpha) {
bool Window::write(uint64_t text_id, Vec2<float> position, float z, float angle, Vec2<float> pivot, Vec2<float> scale, float alpha) {
if (!text_pool.is_valid(text_id)) {
fprintf(stderr, "(Write) Invalid text ID: %llu\n", (unsigned long long)text_id);
return false;
@@ -165,6 +165,7 @@ bool Window::write(uint64_t text_id, Vec2<float> position, float z, float angle,
{.type = RenderInstruction::DRAW_TEXT,
.z = z,
.angle = angle,
.pivot = pivot,
.scale = scale,
.alpha = alpha,
.draw_text = {
@@ -176,7 +177,7 @@ bool Window::write(uint64_t text_id, Vec2<float> position, float z, float angle,
return true;
};
bool Window::draw(uint64_t image_id, Vec2<float> position, float z, float angle, Vec2<float> scale, float alpha) {
bool Window::draw(uint64_t image_id, Vec2<float> position, float z, float angle, Vec2<float> pivot, Vec2<float> scale, float alpha) {
if (!image_pool.is_valid(image_id)) {
fprintf(stderr, "Invalid image ID: %llu\n", (unsigned long long)image_id);
return false;
@@ -186,6 +187,7 @@ bool Window::draw(uint64_t image_id, Vec2<float> position, float z, float angle,
{.type = RenderInstruction::DRAW_IMAGE,
.z = z,
.angle = angle,
.pivot = pivot,
.scale = scale,
.alpha = alpha,
.draw_image = {
@@ -197,11 +199,12 @@ bool Window::draw(uint64_t image_id, Vec2<float> position, float z, float angle,
return true;
};
bool Window::draw_rect(Rect rect, Color color, float z, float angle, Vec2<float> scale, float alpha) {
bool Window::draw_rect(Rect rect, Color color, float z, float angle, Vec2<float> pivot, Vec2<float> scale, float alpha) {
render_instructions.push_back(
{.type = RenderInstruction::DRAW_RECT,
.z = z,
.angle = angle,
.pivot = pivot,
.scale = scale,
.alpha = alpha,
.draw_rect = {
@@ -279,13 +282,18 @@ void Window::render() {
SDL_SetTextureAlphaModFloat(texture, instruction.alpha);
SDL_FPoint pivot_point = {
instruction.pivot.x * scale_x,
instruction.pivot.y * scale_y
};
SDL_RenderTextureRotated(
renderer,
texture,
nullptr,
&dst_rect,
instruction.angle,
nullptr,
&pivot_point,
flip
);
} break;
@@ -311,13 +319,18 @@ void Window::render() {
SDL_SetTextureAlphaModFloat(image->texture, instruction.alpha);
SDL_FPoint pivot_point = {
instruction.pivot.x * scale_x,
instruction.pivot.y * scale_y
};
SDL_RenderTextureRotated(
renderer,
image->texture,
nullptr,
&dst_rect,
instruction.angle,
nullptr,
&pivot_point,
flip
);
} break;
@@ -338,13 +351,18 @@ void Window::render() {
SDL_SetTextureAlphaModFloat(white_tex, instruction.alpha);
SDL_FPoint pivot_point = {
instruction.pivot.x * dst_rect.w,
instruction.pivot.y * dst_rect.h
};
SDL_RenderTextureRotated(
renderer,
white_tex,
nullptr,
&dst_rect,
instruction.angle,
nullptr,
&pivot_point,
SDL_FLIP_NONE
);
} break;