Files
vn/src/main.cpp
T
2025-12-17 21:34:59 +01:00

345 lines
16 KiB
C++

// this file is an absolute clusterfuck to demo the internals.
// if you know any better please make a pr. please.
// i hate lines++; but i don't know any better, do i look like i know frontend?
// branching? use arbitrary lines value, do i look like i know any better?
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <chrono>
#include <vector>
#include <memory>
#include <window.h>
#include <text.h>
#include <compositor.h>
#include <image.h>
// --- RAII wrappers for engine objects ---
struct WindowHandle {
window_t* ptr;
WindowHandle(window_t* w) : ptr(w) {}
~WindowHandle() { if (ptr) window_destroy(ptr); }
window_t* get() { return ptr; }
operator window_t* () { return ptr; }
};
struct CompositorHandle {
compositor_t* ptr;
CompositorHandle(compositor_t* c) : ptr(c) {}
~CompositorHandle() { if (ptr) compositor_destroy(ptr); }
compositor_t* get() { return ptr; }
operator compositor_t* () { return ptr; }
};
struct TextHandle {
text_t* ptr;
TextHandle(text_t* t) : ptr(t) {}
~TextHandle() { if (ptr) text_destroy(ptr); }
text_t* get() { return ptr; }
operator text_t* () { return ptr; }
};
struct ImageHandle {
uint32_t* ptr;
ImageHandle(uint32_t* p) : ptr(p) {}
~ImageHandle() { if (ptr) image_free(ptr); }
uint32_t* get() { return ptr; }
operator uint32_t* () { return ptr; }
};
int main() {
const int WIDTH = 1280;
const int HEIGHT = 720;
const int CHAR_WIDTH = 400;
const int CHAR_HEIGHT = 600;
// --- 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;
WindowHandle window(window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine"));
if (!window.get()) {
printf("Failed to create window\n");
return 1;
}
CompositorHandle compositor(compositor_init(WIDTH, HEIGHT));
if (!compositor.get()) {
printf("Failed to initialize compositor\n");
return 1;
}
ImageHandle bg(image_load_resized("assets/background.png", WIDTH, HEIGHT));
if (bg.get()) {
uint32_t bg_id = compositor_insert(compositor.get(), bg.get(), WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_alpha(compositor.get(), bg_id, 1.0f);
}
ImageHandle textbox(image_load_resized("assets/textbox.png", WIDTH, 180));
if (textbox.get()) {
// Position via the bottom-left corner for easier alignment with screen bottom
compositor_insert(compositor.get(), textbox.get(), WIDTH, 180, 0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT);
}
ImageHandle 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.get(), maksw_happy.get(),
CHAR_WIDTH, CHAR_HEIGHT, OFFSCREEN_LEFT_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
ImageHandle 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.get(), maksw_neutral.get(),
CHAR_WIDTH, CHAR_HEIGHT, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
compositor_set_visible(compositor.get(), maksw_neutral_id, false);
ImageHandle 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.get(), wikor.get(),
CHAR_WIDTH, CHAR_HEIGHT, RIGHT_CHAR_CENTER_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER);
TextHandle name_text(text_init(300, 100));
text_set_font(name_text.get(), "assets/Aller_Rg.ttf", 40.0f);
// Text is anchored at top-left, positioned relative to textbox
uint32_t name_id = compositor_insert(compositor.get(), text_getfb(name_text.get()),
300, 100, 60, HEIGHT - 180, 4, 1.0f, ANCHOR_TOP_LEFT);
TextHandle dialogue_text(text_init(WIDTH - 140, 160));
text_set_font(dialogue_text.get(), "assets/Vera.ttf", 30.0f);
// Text is anchored at top-left, positioned relative to textbox
uint32_t dialogue_id = compositor_insert(compositor.get(), text_getfb(dialogue_text.get()),
WIDTH - 140, 160, 60, HEIGHT - 130, 4, 1.0f, ANCHOR_TOP_LEFT);
ImageHandle mask(image_load_resized("assets/mask.png", 1280, 1080));
// Use top-left anchor for full-screen wipes
uint32_t mask_id = compositor_insert(compositor.get(), mask.get(), 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor.get(), mask_id, false);
ImageHandle mask2(image_load_resized("assets/mask2.png", 1280, 1080));
// Use top-left anchor for full-screen wipes
uint32_t mask2_id = compositor_insert(compositor.get(), mask2.get(), 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
compositor_set_visible(compositor.get(), mask2_id, false);
// yeah! memory savings!
//std::unique_ptr<uint32_t[]> black(new uint32_t[WIDTH * HEIGHT]);
//for (int i = 0; i < WIDTH * HEIGHT; i++) black[i] = 0xFF000000;
//// Use top-left anchor for full-screen fade
//uint32_t black_id = compositor_insert(compositor.get(), black.get(), WIDTH, HEIGHT, 0, 0, 6, 1.0f, ANCHOR_TOP_LEFT);
//compositor_set_visible(compositor.get(), black_id, false);
//compositor_set_alpha(compositor.get(), black_id, 0.0f);
// Animate character into position using the new centered coordinates
compositor_animate_move(compositor.get(), maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1.0f, EASE_OUT_CUBIC);
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, true);
text_push(name_text.get(), "maksw", 0.0f, true);
text_push(dialogue_text.get(),
"Hello and welcome to the Obwarzanek Narrative Engine demo!",
30.0f, true);
bool running = true;
auto last_frame_time = std::chrono::high_resolution_clock::now();
auto last_report_time = last_frame_time;
int frame_count = 0;
float frame_time_sum = 0.0f;
const std::chrono::duration<float> REPORT_INTERVAL = std::chrono::seconds(5);
while (running) {
if (window_pollevent(window.get())) running = false;
auto current_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> delta_duration = current_time - last_frame_time;
float delta_time_sec = delta_duration.count();
last_frame_time = current_time;
frame_count++;
frame_time_sum += delta_time_sec;
std::chrono::duration<float> time_since_last_report = current_time - last_report_time;
if (time_since_last_report >= REPORT_INTERVAL) {
if (frame_count > 0) {
float avg_delta_time = frame_time_sum / (float)frame_count;
float fps = 1.0f / avg_delta_time;
float actual_duration = time_since_last_report.count();
printf("FPS: %.2f (over %d frames in %.2f seconds)\n",
fps, frame_count, actual_duration);
}
frame_count = 0;
frame_time_sum = 0.0f;
last_report_time = current_time;
}
static bool mouse_was_down = false;
bool mouse_is_down = window_is_mouse_button_down(window.get(), MOUSE_BUTTON_LEFT);
bool mouse_clicked = mouse_is_down && !mouse_was_down;
mouse_was_down = mouse_is_down;
static bool key_was_down = false;
bool key_is_down = window_is_key_down(window.get(), KEY_SPACE);
bool key_pressed = key_is_down && !key_was_down;
key_was_down = key_is_down;
// don't judge me, it will work for now
static unsigned int lines = 2;
static bool dialog_finished = false;
static bool transitioning = false;
if (mouse_clicked || key_pressed) {
if (text_is_animating(dialogue_text.get())) {
text_show_all_current(dialogue_text.get());
}
else {
switch (lines) {
case 2:
compositor_animation_complete_all(compositor.get());
text_clear(dialogue_text.get());
text_push(dialogue_text.get(), "Today I'll present to you-", 30.0f, true);
lines++;
break;
case 3:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "wikor", 0.0f, true);
text_push(dialogue_text.get(), "Yeah, hello to you too.", 30.0f, true);
lines++;
break;
case 4:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "maksw", 0.0f, true);
text_push(dialogue_text.get(), "Oh Wiktor, what a nice suprise!", 30.0f, true);
lines++;
break;
case 5:
compositor_animation_complete_all(compositor.get());
text_clear(dialogue_text.get());
text_push(dialogue_text.get(), "Do you need something?", 30.0f, true);
lines++;
break;
case 6:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "wikor", 0.0f, true);
text_push(dialogue_text.get(), "No, i was just walking by.", 30.0f, true);
lines++;
break;
case 7:
compositor_animation_complete_all(compositor.get());
text_clear(dialogue_text.get());
text_push(dialogue_text.get(), "Cool engine though.", 30.0f, true);
lines++;
break;
case 8:
// fade transition
compositor_animation_complete_all(compositor.get());
compositor_set_visible(compositor.get(), mask_id, true);
compositor_animate_move(compositor.get(), mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC, false);
transitioning = true;
lines++;
break;
case 9:
// some fucking how?
break;
case 10:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "maksw", 0.0f, true);
text_push(dialogue_text.get(), "Can't complain, the winter break is just a week away. Plus the compositor is finally cooperating.", 30.0f, true);
lines++;
break;
case 11:
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "wikor", 0.0f, true);
text_push(dialogue_text.get(), "Sure thing..", 30.0f, true);
lines++;
break;
case 12:
compositor_animation_complete_all(compositor.get());
text_clear(dialogue_text.get());
text_push(dialogue_text.get(), "You're talking to me like i know anything about computers.", 30.0f, true);
lines++;
break;
case 13:
// fade to black and end
compositor_animation_complete_all(compositor.get());
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
//compositor_set_visible(compositor.get(), black_id, true);
//compositor_animate_fade(compositor.get(), black_id, 1.0f, 0.7f, EASE_IN_CUBIC, false);
// yeah! memory savings!
compositor_set_visible(compositor.get(), mask_id, true);
compositor_set_alpha(compositor.get(), mask_id, 0.0f);
compositor_animate_fade(compositor.get(), mask_id, 1.0f, 0.8f, EASE_IN_OUT_CUBIC, false);
dialog_finished = true;
lines++;
break;
default:
running = false;
break;
}
}
}
compositor_tick(compositor.get(), delta_time_sec);
if (transitioning) {
if (!compositor_is_element_animating(compositor.get(), mask_id)) {
compositor_set_visible(compositor.get(), maksw_happy_id, false);
compositor_set_visible(compositor.get(), maksw_neutral_id, true);
compositor_set_scale(compositor.get(), maksw_neutral_id, 1.0f);
compositor_set_scale(compositor.get(), wikor_id, 1.05f);
text_clear(dialogue_text.get());
text_clear(name_text.get());
text_push(name_text.get(), "wikor", 0.0f, true);
text_push(dialogue_text.get(), "Anyways, how's your day going?", 30.0f, true);
compositor_set_visible(compositor.get(), mask_id, false);
compositor_set_visible(compositor.get(), mask2_id, true);
compositor_animate_move(compositor.get(), mask2_id, 0, HEIGHT, 0.5f, EASE_OUT_CUBIC, false);
transitioning = false;
lines++;
}
}
if (dialog_finished) {
compositor_mark_content_dirty(compositor.get(), mask_id);
if (!compositor_is_element_animating(compositor.get(), mask_id)) {
running = false;
}
}
if (text_nextframe(name_text.get(), delta_time_sec))
compositor_mark_content_dirty(compositor.get(), name_id);
if (text_nextframe(dialogue_text.get(), delta_time_sec))
compositor_mark_content_dirty(compositor.get(), dialogue_id);
const uint32_t* fb = compositor_compose(compositor.get());
window_blit(window.get(), fb);
}
return 0;
}