proper anchor points

This commit is contained in:
maksw committed 2025-12-01 21:46:36 +01:00
1 parent bdc1a4561d
commit 333ebca300
3 files changed
+108 -56

No files matched your search

+14 -1
View File
@@ -24,6 +24,18 @@ enum easing_t {
EASE_IN_OUT_CUBIC
};
enum anchor_point_t {
ANCHOR_TOP_LEFT,
ANCHOR_TOP_CENTER,
ANCHOR_TOP_RIGHT,
ANCHOR_CENTER_LEFT,
ANCHOR_CENTER,
ANCHOR_CENTER_RIGHT,
ANCHOR_BOTTOM_LEFT,
ANCHOR_BOTTOM_CENTER,
ANCHOR_BOTTOM_RIGHT,
};
extern "C" {
typedef struct compositor_t compositor_t;
@@ -39,7 +51,8 @@ uint32_t compositor_insert(compositor_t* comp,
uint32_t fb_width,
uint32_t fb_height,
int32_t x, int32_t y,
int32_t z_order, float scale = 1.0f);
int32_t z_order, float scale,
anchor_point_t anchor);
// Remove element by ID
void compositor_remove(compositor_t* comp, uint32_t id);
+58 -36
View File
@@ -70,6 +70,7 @@ struct element_t {
bool visible;
float scale;
float alpha = 1.0f;
anchor_point_t anchor; // Anchor point for positioning and scaling
uint32_t id; // Unique identifier
std::vector<uint32_t> occupied_tiles; // List of occupied tiles
animation_t anim;
@@ -154,29 +155,35 @@ static inline void get_element_bounds(const element_t* elem,
int32_t& out_x0, int32_t& out_y0,
int32_t& out_x1, int32_t& out_y1)
{
if (elem->scale == 1.0f) {
// Fast path for 1.0 scale
out_x0 = elem->x;
out_y0 = elem->y;
out_x1 = elem->x + (int32_t)elem->fb_width;
out_y1 = elem->y + (int32_t)elem->fb_height;
// Calculate scaled dimensions, rounding to nearest integer to prevent jitter
int32_t scaled_width = (int32_t)std::round(elem->fb_width * elem->scale);
int32_t scaled_height = (int32_t)std::round(elem->fb_height * elem->scale);
float anchor_x_factor = 0.0f;
float anchor_y_factor = 0.0f;
// Determine the anchor's proportional position within the element
switch (elem->anchor) {
case ANCHOR_TOP_LEFT: anchor_x_factor = 0.0f; anchor_y_factor = 0.0f; break;
case ANCHOR_TOP_CENTER: anchor_x_factor = 0.5f; anchor_y_factor = 0.0f; break;
case ANCHOR_TOP_RIGHT: anchor_x_factor = 1.0f; anchor_y_factor = 0.0f; break;
case ANCHOR_CENTER_LEFT: anchor_x_factor = 0.0f; anchor_y_factor = 0.5f; break;
case ANCHOR_CENTER: anchor_x_factor = 0.5f; anchor_y_factor = 0.5f; break;
case ANCHOR_CENTER_RIGHT: anchor_x_factor = 1.0f; anchor_y_factor = 0.5f; break;
case ANCHOR_BOTTOM_LEFT: anchor_x_factor = 0.0f; anchor_y_factor = 1.0f; break;
case ANCHOR_BOTTOM_CENTER: anchor_x_factor = 0.5f; anchor_y_factor = 1.0f; break;
case ANCHOR_BOTTOM_RIGHT: anchor_x_factor = 1.0f; anchor_y_factor = 1.0f; break;
}
else {
// Scaled path
int32_t scaled_width = (int32_t)(elem->fb_width * elem->scale);
int32_t scaled_height = (int32_t)(elem->fb_height * elem->scale);
// Apply anchor point transformation (bottom-center)
float x_offset = (elem->fb_width * 0.5f) * (1.0f - elem->scale);
float y_offset = (elem->fb_height) * (1.0f - elem->scale);
// Use std::round to avoid 1-pixel jiggle
out_x0 = elem->x + (int32_t)std::round(x_offset);
out_y0 = elem->y + (int32_t)std::round(y_offset);
// The element's (x, y) represents the anchor point's position on screen.
// We calculate the top-left corner (out_x0, out_y0) by offsetting
// from the anchor's position.
out_x0 = elem->x - (int32_t)std::round(scaled_width * anchor_x_factor);
out_y0 = elem->y - (int32_t)std::round(scaled_height * anchor_y_factor);
// The bottom-right corner is the top-left plus the scaled dimensions.
out_x1 = out_x0 + scaled_width;
out_y1 = out_y0 + scaled_height;
}
}
// --- Spatial Hashing & Dirty Rects ---
@@ -256,8 +263,6 @@ static void update_spatial_hash(compositor_t* comp, element_t* elem) {
}
}
// --- Draw Order ---
// Uses an incremental insertion sort to place an element in the correct z-order position.
static void update_draw_order(compositor_t* comp, uint32_t id) {
element_t* elem = find_element(comp, id);
@@ -286,12 +291,8 @@ static void update_draw_order(compositor_t* comp, uint32_t id) {
comp->draw_order.insert(it_insert, id);
}
// --- Public C-Style API Functions ---
extern "C" {
// --- Lifecycle ---
// Advances all animations by a given time delta.
void compositor_tick(compositor_t* comp, float delta_time_sec) {
if (!comp) return;
@@ -448,15 +449,14 @@ void compositor_destroy(compositor_t* comp) {
delete comp; // Uses the default C++ destructor for cleanup
}
// --- Element Management ---
// Inserts a new element into the compositor and returns a unique ID.
uint32_t compositor_insert(compositor_t* comp,
const uint32_t* fb,
uint32_t fb_width,
uint32_t fb_height,
int32_t x, int32_t y,
int32_t z_order, float scale) {
int32_t z_order, float scale,
anchor_point_t anchor) {
if (!comp) {
// Cannot set error if comp is NULL, must return 0
return 0;
@@ -483,6 +483,7 @@ uint32_t compositor_insert(compositor_t* comp,
.z_order = z_order,
.visible = true,
.scale = scale,
.anchor = anchor,
.id = id
};
comp->elements_map[id] = new_elem;
@@ -567,9 +568,7 @@ void compositor_update(compositor_t* comp, uint32_t id,
comp->last_error.clear();
}
// --- Optimized Setters ---
// Optimized setter for an element's (x, y) position.
// setter for an element's (x, y) position.
void compositor_set_position(compositor_t* comp, uint32_t id,
int32_t x, int32_t y) {
if (!comp) return;
@@ -589,7 +588,7 @@ void compositor_set_position(compositor_t* comp, uint32_t id,
comp->last_error.clear();
}
// Optimized setter for an element's z-order.
// setter for an element's z-order.
void compositor_set_zorder(compositor_t* comp, uint32_t id,
int32_t z_order) {
if (!comp) return;
@@ -606,7 +605,7 @@ void compositor_set_zorder(compositor_t* comp, uint32_t id,
comp->last_error.clear();
}
// Optimized setter for an element's visibility.
// setter for an element's visibility.
void compositor_set_visible(compositor_t* comp, uint32_t id,
bool visible) {
if (!comp) return;
@@ -629,7 +628,7 @@ void compositor_set_visible(compositor_t* comp, uint32_t id,
comp->last_error.clear();
}
// Optimized setter for an element's scale.
// setter for an element's scale.
void compositor_set_scale(compositor_t* comp, uint32_t id,
float scale) {
if (!comp) return;
@@ -654,7 +653,7 @@ void compositor_set_scale(compositor_t* comp, uint32_t id,
comp->last_error.clear();
}
// Optimized setter for an element's alpha.
// setter for an element's alpha.
void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha) {
if (!comp) return;
element_t* elem = find_element(comp, id);
@@ -679,7 +678,30 @@ void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha) {
}
}
// --- Animation ---
// setter for an element's anchor point.
void compositor_set_anchor(compositor_t* comp, uint32_t id, anchor_point_t anchor) {
if (!comp) return;
element_t* elem = find_element(comp, id);
if (!elem) {
set_error(comp, "Element with ID " + std::to_string(id) + " not found to set anchor.");
return;
}
if (elem->anchor == anchor) {
comp->last_error.clear();
return;
}
// Changing anchor changes bounds if scale != 1.0, so treat it like a move/scale.
mark_element_dirty(comp, elem);
clear_spatial_hash(comp, id);
elem->anchor = anchor;
update_spatial_hash(comp, elem);
// Clear last error on success
comp->last_error.clear();
}
void compositor_animate_move(compositor_t* comp, uint32_t id,
int32_t to_x, int32_t to_y,
@@ -1060,7 +1082,7 @@ const uint32_t* compositor_compose(compositor_t* comp) {
// --- Blending Loop (with SIMD) ---
int32_t x = blend_x0;
#if defined(__SSE2__) || (defined(_M_AMD64) || defined(_M_X64))
#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)
// Pre-calculate fixed values for the SIMD loop
const __m128i v_zero = _mm_setzero_si128();
const __m128i v_one = _mm_set1_epi16(1);
+36 -19
View File
@@ -13,8 +13,16 @@ int main() {
const int HEIGHT = 720;
const int CHAR_WIDTH = 400;
const int CHAR_HEIGHT = 600;
const int CHAR_Y_OFFSET = 140;
const int POS_Y = HEIGHT - CHAR_HEIGHT - CHAR_Y_OFFSET;
// --- New Character Positioning Constants ---
// The vertical baseline for character sprites, making positioning independent of sprite height.
const int CHAR_BASELINE_Y = HEIGHT - 140;
// The horizontal center for the character on the left.
const int LEFT_CHAR_CENTER_X = 60 + (CHAR_WIDTH / 2);
// The horizontal center for the character on the right.
const int RIGHT_CHAR_CENTER_X = WIDTH - 60 - (CHAR_WIDTH / 2);
// An off-screen position to the left for transitions, based on the final position.
const int OFFSCREEN_LEFT_X = LEFT_CHAR_CENTER_X - 660;
window_t* window = window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine");
if (!window) {
@@ -31,59 +39,65 @@ int main() {
uint32_t* bg = image_load_resized("assets/background.png", WIDTH, HEIGHT);
if (bg) {
uint32_t bg_id = compositor_insert(compositor, bg, WIDTH, HEIGHT, 0, 0, 0);
uint32_t bg_id = compositor_insert(compositor, bg, WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_alpha(compositor, bg_id, 1.0f);
}
uint32_t* textbox = image_load_resized("assets/textbox.png", WIDTH, 180);
if (textbox) {
compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT - 180, 3);
// Position via the bottom-left corner for easier alignment with screen bottom
compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT);
}
uint32_t* maksw_happy = image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT);
// Insert off-screen left, anchored at bottom-center for natural scaling
uint32_t maksw_happy_id = compositor_insert(compositor, maksw_happy,
CHAR_WIDTH, CHAR_HEIGHT, -600, POS_Y, 1);
CHAR_WIDTH, CHAR_HEIGHT, OFFSCREEN_LEFT_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
uint32_t* maksw_neutral = image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT);
// Insert at the final left position, anchored at bottom-center
uint32_t maksw_neutral_id = compositor_insert(compositor, maksw_neutral,
CHAR_WIDTH, CHAR_HEIGHT, 60, POS_Y, 1);
CHAR_WIDTH, CHAR_HEIGHT, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
compositor_set_visible(compositor, maksw_neutral_id, false);
uint32_t* wikor = image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT);
// Insert at the final right position, anchored at bottom-center
uint32_t wikor_id = compositor_insert(compositor, wikor,
CHAR_WIDTH, CHAR_HEIGHT, WIDTH - CHAR_WIDTH - 60, POS_Y, 2);
CHAR_WIDTH, CHAR_HEIGHT, RIGHT_CHAR_CENTER_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER);
text_t* name_text = text_init(300, 100);
text_set_font(name_text, "assets/Aller_Rg.ttf", 40.0f);
// Text is anchored at top-left, positioned relative to textbox
uint32_t name_id = compositor_insert(compositor, text_getfb(name_text),
300, 100, 60, HEIGHT - 180, 4);
300, 100, 60, HEIGHT - 180, 4, 1.0f, ANCHOR_TOP_LEFT);
text_t* dialogue_text = text_init(WIDTH - 140, 160);
text_set_font(dialogue_text, "assets/Vera.ttf", 30.0f);
// Text is anchored at top-left, positioned relative to textbox
uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text),
WIDTH - 140, 160, 60, HEIGHT - 130, 4);
WIDTH - 140, 160, 60, HEIGHT - 130, 4, 1.0f, ANCHOR_TOP_LEFT);
uint32_t* mask = image_load_resized("assets/mask.png", 1280, 1080);
// okay so this requires some math
// inserting at 0,0 (top-left anchor) means it will cover the whole screen
// we need to insert it above the screen, so -1080
uint32_t mask_id = compositor_insert(compositor, mask, 1280, 1080, 0, -1080, 5);
// Use top-left anchor for full-screen wipes
uint32_t mask_id = compositor_insert(compositor, mask, 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor, mask_id, false);
uint32_t* mask2 = image_load_resized("assets/mask2.png", 1280, 1080);
// here, we put it in the center, as this is the slide out
uint32_t mask2_id = compositor_insert(compositor, mask2, 1280, 1080, 0, 0, 5);
// Use top-left anchor for full-screen wipes
uint32_t mask2_id = compositor_insert(compositor, mask2, 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor, mask2_id, false);
uint32_t* black = (uint32_t*)malloc(WIDTH * HEIGHT * sizeof(uint32_t));
for (int i = 0; i < WIDTH * HEIGHT; i++) {
black[i] = 0xFF000000;
}
uint32_t black_id = compositor_insert(compositor, black, WIDTH, HEIGHT, 0, 0, 6);
// Use top-left anchor for full-screen fade
uint32_t black_id = compositor_insert(compositor, black, WIDTH, HEIGHT, 0, 0, 6, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor, black_id, false);
compositor_set_alpha(compositor, black_id, 0.0f);
compositor_animate_move(compositor, maksw_happy_id, 60, POS_Y, 1.0f, EASE_OUT_CUBIC);
// Animate character into position using the new centered coordinates
compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1.0f, EASE_OUT_CUBIC);
compositor_animate_scale(compositor, maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, true);
text_push(name_text, "maksw", 0.0f, true);
text_push(dialogue_text,
@@ -152,11 +166,12 @@ int main() {
text_show_all_current(dialogue_text);
}
else {
// this will bite me
switch (lines) {
case 0:
// line 2
compositor_animation_complete_all(compositor);
compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.2f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor, wikor_id, 1.1f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text);
text_clear(name_text);
@@ -177,6 +192,7 @@ int main() {
case 2:
// (line 4), fade out
compositor_animation_complete_all(compositor);
compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
compositor_set_visible(compositor, black_id, true);
compositor_animate_fade(compositor, black_id, 1.0f, 1.0f, EASE_IN_CUBIC, false);
@@ -197,7 +213,7 @@ int main() {
if (compositor_is_element_animating(compositor, mask_id) == false) {
compositor_set_visible(compositor, maksw_happy_id, false);
compositor_set_visible(compositor, maksw_neutral_id, true);
compositor_animate_scale(compositor, maksw_neutral_id, 1.05f, 0.5f, EASE_IN_OUT_CUBIC);
compositor_set_scale(compositor, maksw_neutral_id, 1.05f);
text_clear(dialogue_text);
text_clear(name_text);
text_push(name_text, "maksw", 0.0f, true);
@@ -227,6 +243,7 @@ int main() {
window_blit(window, fb);
}
// no raii because i'm tired already
if (maksw_happy) image_free(maksw_happy);
if (maksw_neutral) image_free(maksw_neutral);
if (wikor) image_free(wikor);