Mon, 21 Jul 2025 21:28:34 +0200
improve snap-to-grid-movement
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * Copyright 2023 Mike Becker. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "ascension/text.h" #include "ascension/error.h" #include "ascension/shader.h" #include "ascension/constants.h" #include <assert.h> #include <cx/printf.h> typedef struct asc_text_shader_s { AscShaderProgram program; asc_uniform_loc tex; } AscTextShader; AscShaderProgram *asc_text_shader_create(cx_attr_unused int unused) { AscShaderCodes codes; // TODO: with more advanced feature we want to create specific text shaders if (asc_shader_load_code_files((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", .files.frag = "sprite_frag.glsl", .defines.frag = "#define USE_RECT", }, &codes)) { asc_error("Loading text shader failed."); return NULL; } AscShaderProgram *shader = asc_shader_create(codes, sizeof(AscTextShader)); if (asc_shader_invalid(shader)) { asc_shader_free_codes(codes); return shader; } asc_ptr_cast(AscTextShader, text_shader, shader); text_shader->tex = asc_shader_get_uniform_loc(shader, "tex"); asc_shader_free_codes(codes); asc_error_catch_all_gl(); return shader; } static void asc_text_update(AscSceneNode *node) { asc_ptr_cast(AscText, text, node); // Render text onto a surface TTF_Font *font = asc_font_load(text->font); if (font == NULL) { // cannot load font - hide the text node to avoid errors when trying to draw asc_set_flag(node->flags, ASC_SCENE_NODE_HIDDEN); return; } static int alignments[] = { TTF_WRAPPED_ALIGN_LEFT, TTF_WRAPPED_ALIGN_CENTER, TTF_WRAPPED_ALIGN_RIGHT }; TTF_SetFontWrappedAlign( font, alignments[text->base.flags & ASC_TEXT_ALIGNMENT_MASK]); SDL_Surface *surface = TTF_RenderUTF8_Blended_Wrapped( font, text->text.ptr, asc_col_sdl(text->color), text->max_width ); if (surface == NULL) { asc_error("Rendering TTF surface failed: %s", SDL_GetError()); return; } if (asc_test_flag(text->base.flags, ASC_TEXT_CENTERED_FLAG)) { asc_scene_node_set_origin(node, ASC_VEC3F(surface->w / 2, 0, 0)); } // Transfer Image Data asc_texture_from_surface(text->texture, surface); // If dimensions changed, update the mesh if (text->dimension.width != (unsigned)surface->w || text->dimension.height != (unsigned)surface->h) { text->dimension.width = surface->w; text->dimension.height = surface->h; const asc_vec2f uv_scale = asc_texture_calculate_uv_scale(text->texture, text->dimension, ASC_VEC2F_1); asc_mesh_plane_2d(&text->mesh, .size = ASC_VEC2F(surface->w, surface->h), .uv_scale = uv_scale ); } // Free the surface SDL_FreeSurface(surface); // Schedule for transform update asc_scene_node_update_transform(node); } static void asc_text_draw(const AscCamera *camera, const AscSceneNode *node) { asc_cptr_cast(AscText, text, node); // early exit when the text has no width if (text->dimension.width == 0) return; // Activate shader // TODO: scene should know which shader we are going to activate s.t. it can pre-sort nodes const AscShaderProgram *shader = asc_shader_lookup_or_create( ASC_SHADER_TEXT, asc_text_shader_create, 0); if (asc_shader_use(shader, camera)) return; asc_cptr_cast(AscTextShader, text_shader, shader); // Upload model matrix asc_shader_upload_model_matrix(shader, node); // Bind texture asc_texture_bind(text->texture, text_shader->tex, 0); // Draw mesh asc_mesh_draw_triangle_strip(&text->mesh); } static void asc_text_destroy(AscSceneNode *node) { asc_ptr_cast(AscText, text, node); asc_mesh_destroy(&text->mesh); asc_texture_destroy(text->texture, 1); assert(text->texture->refcount == 0); cxFreeDefault(text->texture); cx_strfree(&text->text); } AscSceneNode *asc_text_create(struct asc_text_create_args args) { AscText *text = cxZallocDefault(sizeof(AscText)); asc_ptr_cast(AscSceneNode, node, text); // text properties node->flags = args.alignment; // use flags variable to save some space text->max_width = args.max_width; text->font = args.font; if (text->font.size == 0) { text->font.size = 12; } if (asc_memcmpz(&args.color, sizeof(asc_col4i))) { text->color = ASC_RGB(255, 255, 255); } else { text->color = args.color; } if (args.text == NULL) { text->text = cx_mutstr(strdup(" ")); } else { text->text = cx_mutstr(strdup(args.text)); } if (args.centered) { asc_set_flag(node->flags, ASC_TEXT_CENTERED_FLAG); } // initialize texture // mesh will be created in the update func text->texture = cxMallocDefault(sizeof(AscTexture)); asc_texture_init_rectangle(text->texture, 1); // basic node properties asc_scene_node_init(node, ASC_SCENE_NODE_FUNCS(asc_text), .name = args.name, .render_group = ASC_RENDER_GROUP_2D_BLEND, .pos2d = ASC_VEC2I(args.x, args.y) ); return node; } void asc_text_alignment( AscText *node, enum asc_text_alignment alignment ) { asc_ptr_cast(AscSceneNode, snode, node); asc_set_flag_masked(snode->flags, ASC_TEXT_ALIGNMENT_MASK, alignment); asc_scene_node_update(snode); } void asc_text_centered(AscText *node, bool centered) { asc_ptr_cast(AscSceneNode, snode, node); if (centered) { asc_clear_flag(snode->flags, ASC_TEXT_ALIGN_CENTERED); } else { asc_set_flag(snode->flags, ASC_TEXT_ALIGN_CENTERED); } asc_scene_node_update(snode); } void asc_text_max_width(AscText *node, unsigned max_width) { node->max_width = max_width; asc_scene_node_update((AscSceneNode*)node); } void asc_text_printf( AscText *node, const char *format, ... ) { va_list ap; va_start(ap, format); cx_vsprintf( &node->text.ptr, &node->text.length, format, ap ); va_end(ap); asc_scene_node_update((AscSceneNode*)node); }