First commit
This commit is contained in:
11
.clang-format
Normal file
11
.clang-format
Normal file
@@ -0,0 +1,11 @@
|
||||
ColumnLimit: 120
|
||||
PointerAlignment: Left
|
||||
ReferenceAlignment: Left
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
CommentPragmas: '^'
|
||||
BreakBeforeBraces: Allman
|
||||
NamespaceIndentation: All
|
||||
FixNamespaceComments: false
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: BeforeHash
|
||||
3
.clangd
Normal file
3
.clangd
Normal file
@@ -0,0 +1,3 @@
|
||||
CompileFlags:
|
||||
CompilationDatabase: build
|
||||
Add: [-std=c++20]
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
resources/
|
||||
18
.gitmodules
vendored
Normal file
18
.gitmodules
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
[submodule "external/SDL"]
|
||||
path = external/SDL
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
[submodule "external/SDL_mixer"]
|
||||
path = external/SDL_mixer
|
||||
url = https://github.com/libsdl-org/SDL_mixer
|
||||
[submodule "external/imgui"]
|
||||
path = external/imgui
|
||||
url = https://github.com/ocornut/imgui
|
||||
[submodule "external/glm"]
|
||||
path = external/glm
|
||||
url = https://github.com/g-truc/glm
|
||||
[submodule "external/tinyxml2"]
|
||||
path = external/tinyxml2
|
||||
url = https://github.com/leethomason/tinyxml2
|
||||
[submodule "external/libanm2"]
|
||||
path = external/libanm2
|
||||
url = https://github.com/shweetsstuff/libanm2
|
||||
28
.vscode/launch.json
vendored
Normal file
28
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/snivy",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "/usr/bin/gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing"
|
||||
},
|
||||
{
|
||||
"description": "Set disassembly flavor to Intel",
|
||||
"text": "-gdb-set disassembly-flavor intel"
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
44
.vscode/tasks.json
vendored
Normal file
44
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "shell",
|
||||
"command": "cmake --build build",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "start-wasm-devserver",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/scripts/start_wasm_dev.sh",
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
"panel": "dedicated"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "stop-wasm-devserver",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/scripts/stop_wasm_dev.sh",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"type": "cmake",
|
||||
"label": "CMake: build",
|
||||
"command": "build",
|
||||
"targets": [
|
||||
"[N/A - Select Kit]"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"detail": "CMake template build task"
|
||||
}
|
||||
]
|
||||
}
|
||||
115
CMakeLists.txt
Normal file
115
CMakeLists.txt
Normal file
@@ -0,0 +1,115 @@
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
project(snivy LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
set(CMAKE_C_FLAGS_DEBUG "-O0 -g" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g" CACHE STRING "" FORCE)
|
||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(SDL_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(external/SDL)
|
||||
set(SDL3_DIR "${CMAKE_CURRENT_BINARY_DIR}/external/SDL" CACHE PATH "" FORCE)
|
||||
|
||||
set(SDLMIXER_VORBIS_STB ON CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_VORBIS_VORBISFILE OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_VORBIS_TREMOR OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(external/SDL_mixer)
|
||||
|
||||
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/imgui)
|
||||
set(TINYXML2_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/tinyxml2)
|
||||
set(GLM_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/glm)
|
||||
|
||||
set(IMGUI_SOURCES
|
||||
${IMGUI_DIR}/imgui.cpp
|
||||
${IMGUI_DIR}/imgui_draw.cpp
|
||||
${IMGUI_DIR}/imgui_tables.cpp
|
||||
${IMGUI_DIR}/imgui_widgets.cpp
|
||||
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp
|
||||
${IMGUI_DIR}/backends/imgui_impl_sdl3.cpp
|
||||
)
|
||||
|
||||
set (TINYXML2_SOURCES ${TINYXML2_DIR}/tinyxml2.cpp)
|
||||
|
||||
file(GLOB PROJECT_SRC CONFIGURE_DEPENDS
|
||||
include/*.cpp
|
||||
src/*.cpp
|
||||
src/resource/*.cpp
|
||||
src/util/*.cpp
|
||||
src/util/*.h
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME}
|
||||
${PROJECT_SRC}
|
||||
${IMGUI_SOURCES}
|
||||
${TINYXML2_SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/glad/glad.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/external/SDL/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/external/SDL_mixer/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/external # tinyxml2 headers are included as <tinyxml2/tinyxml2.h>
|
||||
${IMGUI_DIR}
|
||||
${IMGUI_DIR}/backends
|
||||
${GLM_DIR}
|
||||
${TINYXML2_DIR}
|
||||
include/glad
|
||||
include/KHR
|
||||
include
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3-static SDL3_mixer::SDL3_mixer-static)
|
||||
|
||||
set(PROJECT_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/resources")
|
||||
set(PROJECT_RESOURCES_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources")
|
||||
if(EXISTS "${PROJECT_RESOURCES_DIR}")
|
||||
file(GLOB_RECURSE PROJECT_RESOURCE_FILES CONFIGURE_DEPENDS
|
||||
"${PROJECT_RESOURCES_DIR}/*")
|
||||
add_custom_target(copy_resources ALL
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_RESOURCES_DIR}" "${PROJECT_RESOURCES_BINARY_DIR}"
|
||||
DEPENDS ${PROJECT_RESOURCE_FILES}
|
||||
COMMENT "Copying resources directory")
|
||||
add_dependencies(${PROJECT_NAME} copy_resources)
|
||||
set(HAS_PROJECT_RESOURCES TRUE)
|
||||
else()
|
||||
set(HAS_PROJECT_RESOURCES FALSE)
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
"-sMIN_WEBGL_VERSION=2"
|
||||
"-sMAX_WEBGL_VERSION=2"
|
||||
"-sFULL_ES2=1"
|
||||
"-sUSE_OGG=1"
|
||||
"-sUSE_VORBIS=1"
|
||||
"-sALLOW_MEMORY_GROWTH=1"
|
||||
)
|
||||
if(HAS_PROJECT_RESOURCES)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
"--preload-file"
|
||||
"${PROJECT_RESOURCES_BINARY_DIR}@resources"
|
||||
)
|
||||
endif()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
OUTPUT_NAME "index"
|
||||
SUFFIX ".html")
|
||||
else()
|
||||
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL)
|
||||
endif()
|
||||
|
||||
message(STATUS "System: ${CMAKE_SYSTEM_NAME}")
|
||||
message(STATUS "Project: ${PROJECT_NAME}")
|
||||
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER}")
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
1
external/SDL
vendored
Submodule
1
external/SDL
vendored
Submodule
Submodule external/SDL added at 770b38b4f1
1
external/SDL_mixer
vendored
Submodule
1
external/SDL_mixer
vendored
Submodule
Submodule external/SDL_mixer added at 4182794ea4
1
external/glm
vendored
Submodule
1
external/glm
vendored
Submodule
Submodule external/glm added at a583c59e16
1
external/imgui
vendored
Submodule
1
external/imgui
vendored
Submodule
Submodule external/imgui added at c254db7637
1
external/libanm2
vendored
Submodule
1
external/libanm2
vendored
Submodule
Submodule external/libanm2 added at 623c67edbd
1
external/tinyxml2
vendored
Submodule
1
external/tinyxml2
vendored
Submodule
Submodule external/tinyxml2 added at 36ff404c34
311
include/KHR/khrplatform.h
Normal file
311
include/KHR/khrplatform.h
Normal file
@@ -0,0 +1,311 @@
|
||||
#ifndef __khrplatform_h_
|
||||
#define __khrplatform_h_
|
||||
|
||||
/*
|
||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
||||
** copy of this software and/or associated documentation files (the
|
||||
** "Materials"), to deal in the Materials without restriction, including
|
||||
** without limitation the rights to use, copy, modify, merge, publish,
|
||||
** distribute, sublicense, and/or sell copies of the Materials, and to
|
||||
** permit persons to whom the Materials are furnished to do so, subject to
|
||||
** the following conditions:
|
||||
**
|
||||
** The above copyright notice and this permission notice shall be included
|
||||
** in all copies or substantial portions of the Materials.
|
||||
**
|
||||
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||
*/
|
||||
|
||||
/* Khronos platform-specific types and definitions.
|
||||
*
|
||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
||||
* The last semantic modification to khrplatform.h was at commit ID:
|
||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
||||
*
|
||||
* Adopters may modify this file to suit their platform. Adopters are
|
||||
* encouraged to submit platform specific modifications to the Khronos
|
||||
* group so that they can be included in future versions of this file.
|
||||
* Please submit changes by filing pull requests or issues on
|
||||
* the EGL Registry repository linked above.
|
||||
*
|
||||
*
|
||||
* See the Implementer's Guidelines for information about where this file
|
||||
* should be located on your system and for more details of its use:
|
||||
* http://www.khronos.org/registry/implementers_guide.pdf
|
||||
*
|
||||
* This file should be included as
|
||||
* #include <KHR/khrplatform.h>
|
||||
* by Khronos client API header files that use its types and defines.
|
||||
*
|
||||
* The types in khrplatform.h should only be used to define API-specific types.
|
||||
*
|
||||
* Types defined in khrplatform.h:
|
||||
* khronos_int8_t signed 8 bit
|
||||
* khronos_uint8_t unsigned 8 bit
|
||||
* khronos_int16_t signed 16 bit
|
||||
* khronos_uint16_t unsigned 16 bit
|
||||
* khronos_int32_t signed 32 bit
|
||||
* khronos_uint32_t unsigned 32 bit
|
||||
* khronos_int64_t signed 64 bit
|
||||
* khronos_uint64_t unsigned 64 bit
|
||||
* khronos_intptr_t signed same number of bits as a pointer
|
||||
* khronos_uintptr_t unsigned same number of bits as a pointer
|
||||
* khronos_ssize_t signed size
|
||||
* khronos_usize_t unsigned size
|
||||
* khronos_float_t signed 32 bit floating point
|
||||
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
|
||||
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
|
||||
* nanoseconds
|
||||
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
|
||||
* khronos_boolean_enum_t enumerated boolean type. This should
|
||||
* only be used as a base type when a client API's boolean type is
|
||||
* an enum. Client APIs which use an integer or other type for
|
||||
* booleans cannot use this as the base type for their boolean.
|
||||
*
|
||||
* Tokens defined in khrplatform.h:
|
||||
*
|
||||
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
|
||||
*
|
||||
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
|
||||
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
|
||||
*
|
||||
* Calling convention macros defined in this file:
|
||||
* KHRONOS_APICALL
|
||||
* KHRONOS_APIENTRY
|
||||
* KHRONOS_APIATTRIBUTES
|
||||
*
|
||||
* These may be used in function prototypes as:
|
||||
*
|
||||
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
|
||||
* int arg1,
|
||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
||||
*/
|
||||
|
||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
||||
# define KHRONOS_STATIC 1
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APICALL
|
||||
*-------------------------------------------------------------------------
|
||||
* This precedes the return type of the function in the function prototype.
|
||||
*/
|
||||
#if defined(KHRONOS_STATIC)
|
||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
||||
* header compatible with static linking. */
|
||||
# define KHRONOS_APICALL
|
||||
#elif defined(_WIN32)
|
||||
# define KHRONOS_APICALL __declspec(dllimport)
|
||||
#elif defined (__SYMBIAN32__)
|
||||
# define KHRONOS_APICALL IMPORT_C
|
||||
#elif defined(__ANDROID__)
|
||||
# define KHRONOS_APICALL __attribute__((visibility("default")))
|
||||
#else
|
||||
# define KHRONOS_APICALL
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIENTRY
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the return type of the function and precedes the function
|
||||
* name in the function prototype.
|
||||
*/
|
||||
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
|
||||
/* Win32 but not WinCE */
|
||||
# define KHRONOS_APIENTRY __stdcall
|
||||
#else
|
||||
# define KHRONOS_APIENTRY
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIATTRIBUTES
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the closing parenthesis of the function prototype arguments.
|
||||
*/
|
||||
#if defined (__ARMCC_2__)
|
||||
#define KHRONOS_APIATTRIBUTES __softfp
|
||||
#else
|
||||
#define KHRONOS_APIATTRIBUTES
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* basic type definitions
|
||||
*-----------------------------------------------------------------------*/
|
||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
||||
|
||||
|
||||
/*
|
||||
* Using <stdint.h>
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
/*
|
||||
* To support platform where unsigned long cannot be used interchangeably with
|
||||
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
|
||||
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
|
||||
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
|
||||
* unsigned long long or similar (this results in different C++ name mangling).
|
||||
* To avoid changes for existing platforms, we restrict usage of intptr_t to
|
||||
* platforms where the size of a pointer is larger than the size of long.
|
||||
*/
|
||||
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
|
||||
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
|
||||
#define KHRONOS_USE_INTPTR_T
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif defined(__VMS ) || defined(__sgi)
|
||||
|
||||
/*
|
||||
* Using <inttypes.h>
|
||||
*/
|
||||
#include <inttypes.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
||||
|
||||
/*
|
||||
* Win32
|
||||
*/
|
||||
typedef __int32 khronos_int32_t;
|
||||
typedef unsigned __int32 khronos_uint32_t;
|
||||
typedef __int64 khronos_int64_t;
|
||||
typedef unsigned __int64 khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(__sun__) || defined(__digital__)
|
||||
|
||||
/*
|
||||
* Sun or Digital
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#if defined(__arch64__) || defined(_LP64)
|
||||
typedef long int khronos_int64_t;
|
||||
typedef unsigned long int khronos_uint64_t;
|
||||
#else
|
||||
typedef long long int khronos_int64_t;
|
||||
typedef unsigned long long int khronos_uint64_t;
|
||||
#endif /* __arch64__ */
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif 0
|
||||
|
||||
/*
|
||||
* Hypothetical platform with no float or int64 support
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#define KHRONOS_SUPPORT_INT64 0
|
||||
#define KHRONOS_SUPPORT_FLOAT 0
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* Generic fallback
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Types that are (so far) the same on all platforms
|
||||
*/
|
||||
typedef signed char khronos_int8_t;
|
||||
typedef unsigned char khronos_uint8_t;
|
||||
typedef signed short int khronos_int16_t;
|
||||
typedef unsigned short int khronos_uint16_t;
|
||||
|
||||
/*
|
||||
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
||||
* to be the only LLP64 architecture in current use.
|
||||
*/
|
||||
#ifdef KHRONOS_USE_INTPTR_T
|
||||
typedef intptr_t khronos_intptr_t;
|
||||
typedef uintptr_t khronos_uintptr_t;
|
||||
#elif defined(_WIN64)
|
||||
typedef signed long long int khronos_intptr_t;
|
||||
typedef unsigned long long int khronos_uintptr_t;
|
||||
#else
|
||||
typedef signed long int khronos_intptr_t;
|
||||
typedef unsigned long int khronos_uintptr_t;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN64)
|
||||
typedef signed long long int khronos_ssize_t;
|
||||
typedef unsigned long long int khronos_usize_t;
|
||||
#else
|
||||
typedef signed long int khronos_ssize_t;
|
||||
typedef unsigned long int khronos_usize_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_FLOAT
|
||||
/*
|
||||
* Float type
|
||||
*/
|
||||
typedef float khronos_float_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_INT64
|
||||
/* Time types
|
||||
*
|
||||
* These types can be used to represent a time interval in nanoseconds or
|
||||
* an absolute Unadjusted System Time. Unadjusted System Time is the number
|
||||
* of nanoseconds since some arbitrary system event (e.g. since the last
|
||||
* time the system booted). The Unadjusted System Time is an unsigned
|
||||
* 64 bit value that wraps back to 0 every 584 years. Time intervals
|
||||
* may be either signed or unsigned.
|
||||
*/
|
||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
||||
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Dummy value used to pad enum types to 32 bits.
|
||||
*/
|
||||
#ifndef KHRONOS_MAX_ENUM
|
||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Enumerated boolean type
|
||||
*
|
||||
* Values other than zero should be considered to be true. Therefore
|
||||
* comparisons should not be made against KHRONOS_TRUE.
|
||||
*/
|
||||
typedef enum {
|
||||
KHRONOS_FALSE = 0,
|
||||
KHRONOS_TRUE = 1,
|
||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
||||
} khronos_boolean_enum_t;
|
||||
|
||||
#endif /* __khrplatform_h_ */
|
||||
9045
include/glad/glad.cpp
Normal file
9045
include/glad/glad.cpp
Normal file
File diff suppressed because one or more lines are too long
15776
include/glad/glad.h
Normal file
15776
include/glad/glad.h
Normal file
File diff suppressed because one or more lines are too long
8881
include/stb_image.h
Normal file
8881
include/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
146
src/canvas.cpp
Normal file
146
src/canvas.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
#include "canvas.h"
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
using namespace glm;
|
||||
using namespace game::resource;
|
||||
|
||||
namespace game
|
||||
{
|
||||
GLuint Canvas::textureVAO = 0;
|
||||
GLuint Canvas::textureVBO = 0;
|
||||
GLuint Canvas::textureEBO = 0;
|
||||
bool Canvas::isStaticInit = false;
|
||||
|
||||
Canvas::Canvas(vec2 size, bool isDefault)
|
||||
{
|
||||
this->size = size;
|
||||
|
||||
if (isDefault)
|
||||
{
|
||||
fbo = 0;
|
||||
rbo = 0;
|
||||
texture = 0;
|
||||
this->isDefault = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glGenRenderbuffers(1, &rbo);
|
||||
glGenTextures(1, &texture);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x, size.y);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
if (!isStaticInit)
|
||||
{
|
||||
glGenVertexArrays(1, &textureVAO);
|
||||
glGenBuffers(1, &textureVBO);
|
||||
glGenBuffers(1, &textureEBO);
|
||||
|
||||
glBindVertexArray(textureVAO);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, textureVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 4 * 4, nullptr, GL_DYNAMIC_DRAW);
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, textureEBO);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(TEXTURE_INDICES), TEXTURE_INDICES, GL_DYNAMIC_DRAW);
|
||||
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
|
||||
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
Canvas::~Canvas()
|
||||
{
|
||||
if (!isDefault)
|
||||
{
|
||||
if (fbo) glDeleteFramebuffers(1, &fbo);
|
||||
if (rbo) glDeleteRenderbuffers(1, &rbo);
|
||||
if (texture) glDeleteTextures(1, &texture);
|
||||
}
|
||||
}
|
||||
|
||||
mat4 Canvas::view_get() const { return mat4{1.0f}; }
|
||||
mat4 Canvas::projection_get() const
|
||||
{
|
||||
if (isDefault) return glm::ortho(0.0f, (float)size.x, (float)size.y, 0.0f, -1.0f, 1.0f);
|
||||
return glm::ortho(0.0f, (float)size.x, 0.0f, (float)size.y, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
void Canvas::texture_render(Shader& shader, GLuint textureId, mat4& model, vec4 tint, vec3 colorOffset,
|
||||
float* vertices) const
|
||||
{
|
||||
auto view = view_get();
|
||||
auto projection = projection_get();
|
||||
|
||||
glUseProgram(shader.id);
|
||||
|
||||
glUniform1i(glGetUniformLocation(shader.id, shader::UNIFORM_TEXTURE), 0);
|
||||
glUniform3fv(glGetUniformLocation(shader.id, shader::UNIFORM_COLOR_OFFSET), 1, value_ptr(colorOffset));
|
||||
glUniform4fv(glGetUniformLocation(shader.id, shader::UNIFORM_TINT), 1, value_ptr(tint));
|
||||
|
||||
glUniformMatrix4fv(glGetUniformLocation(shader.id, shader::UNIFORM_MODEL), 1, GL_FALSE, value_ptr(model));
|
||||
glUniformMatrix4fv(glGetUniformLocation(shader.id, shader::UNIFORM_VIEW), 1, GL_FALSE, value_ptr(view));
|
||||
glUniformMatrix4fv(glGetUniformLocation(shader.id, shader::UNIFORM_PROJECTION), 1, GL_FALSE, value_ptr(projection));
|
||||
|
||||
glBindVertexArray(textureVAO);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, textureVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(TEXTURE_VERTICES), vertices, GL_DYNAMIC_DRAW);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void Canvas::render(Shader& shader, mat4& model, vec4 tint, vec3 colorOffset) const
|
||||
{
|
||||
texture_render(shader, texture, model, tint, colorOffset);
|
||||
}
|
||||
|
||||
void Canvas::bind() const
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
|
||||
glViewport(0, 0, size.x, size.y);
|
||||
}
|
||||
|
||||
void Canvas::clear(vec4 color) const
|
||||
{
|
||||
glClearColor(color.r, color.g, color.b, color.a);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void Canvas::unbind() const
|
||||
{
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
bool Canvas::is_valid() const { return fbo != 0 || isDefault; };
|
||||
}
|
||||
51
src/canvas.h
Normal file
51
src/canvas.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GLES3/gl3.h>
|
||||
#else
|
||||
#include <glad/glad.h>
|
||||
#endif
|
||||
|
||||
#include "resource/shader.h"
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace game
|
||||
{
|
||||
class Canvas
|
||||
{
|
||||
static constexpr float TEXTURE_VERTICES[] = {0, 0, 0.0f, 0.0f, 1, 0, 1.0f, 0.0f,
|
||||
1, 1, 1.0f, 1.0f, 0, 1, 0.0f, 1.0f};
|
||||
|
||||
static constexpr GLuint TEXTURE_INDICES[] = {0, 1, 2, 2, 3, 0};
|
||||
|
||||
static GLuint textureVAO;
|
||||
static GLuint textureVBO;
|
||||
static GLuint textureEBO;
|
||||
static bool isStaticInit;
|
||||
|
||||
public:
|
||||
GLuint fbo{};
|
||||
GLuint rbo{};
|
||||
GLuint texture{};
|
||||
|
||||
glm::vec2 size{};
|
||||
|
||||
bool isDefault{};
|
||||
|
||||
Canvas() = default;
|
||||
Canvas(glm::vec2, bool isDefault = false);
|
||||
~Canvas();
|
||||
glm::mat4 transform_get() const;
|
||||
glm::mat4 view_get() const;
|
||||
glm::mat4 projection_get() const;
|
||||
void texture_render(resource::Shader&, GLuint, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {},
|
||||
float* = (float*)TEXTURE_VERTICES) const;
|
||||
void render(resource::Shader&, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {}) const;
|
||||
void bind() const;
|
||||
void unbind() const;
|
||||
void clear(glm::vec4 color = glm::vec4(0, 0, 0, 1)) const;
|
||||
bool is_valid() const;
|
||||
};
|
||||
}
|
||||
173
src/loader.cpp
Normal file
173
src/loader.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#include "loader.h"
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GLES3/gl3.h>
|
||||
#else
|
||||
#include <glad/glad.h>
|
||||
#endif
|
||||
|
||||
#include <backends/imgui_impl_opengl3.h>
|
||||
#include <backends/imgui_impl_sdl3.h>
|
||||
#include <imgui.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
constexpr auto GL_VERSION_MAJOR = 3;
|
||||
constexpr auto GL_VERSION_MINOR = 0;
|
||||
constexpr auto GLSL_VERSION = "#version 300 es";
|
||||
#else
|
||||
constexpr auto GL_VERSION_MAJOR = 3;
|
||||
constexpr auto GL_VERSION_MINOR = 3;
|
||||
constexpr auto GLSL_VERSION = "#version 330";
|
||||
#endif
|
||||
|
||||
constexpr auto WINDOW_ROUNDING = 6.0f;
|
||||
constexpr auto WINDOW_COLOR = ImVec4(0.05f, 0.35f, 0.08f, 1.0f);
|
||||
|
||||
namespace game
|
||||
{
|
||||
Loader::Loader()
|
||||
{
|
||||
if (!SDL_Init(SDL_INIT_VIDEO))
|
||||
{
|
||||
std::cout << "Failed to initialize SDL: " << SDL_GetError();
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Initialized SDL" << "\n";
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, GL_VERSION_MAJOR);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, GL_VERSION_MINOR);
|
||||
#ifdef __EMSCRIPTEN__
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
#endif
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
||||
window = SDL_CreateWindow("Snivy", SIZE.x, SIZE.y, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
||||
|
||||
if (!window)
|
||||
{
|
||||
std::cout << "Failed to initialize window: " << SDL_GetError();
|
||||
;
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Initialized window" << "\n";
|
||||
|
||||
context = SDL_GL_CreateContext(window);
|
||||
|
||||
if (!context)
|
||||
{
|
||||
std::cout << "Failed to initialize GL context: " << SDL_GetError();
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SDL_GL_MakeCurrent(window, context))
|
||||
{
|
||||
std::cout << "Failed to make GL context current: " << SDL_GetError();
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
|
||||
{
|
||||
std::cout << "Failed to initialize GLAD" << "\n";
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Initialized GLAD" << "\n";
|
||||
#endif
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
std::cout << "Initialized GL context: " << glGetString(GL_VERSION) << "\n";
|
||||
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
|
||||
if (!MIX_Init())
|
||||
{
|
||||
std::cout << "Failed to initialize SDL mixer: " << SDL_GetError();
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Initialized SDL mixer" << "\n";
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGuiContext* imguiContext = ImGui::CreateContext();
|
||||
|
||||
if (!imguiContext)
|
||||
{
|
||||
std::cout << "Failed to initialize Dear ImGui" << "\n";
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Initialized Dear ImGui" << "\n";
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
io.Fonts->AddFontFromFileTTF("resources/font/pmd.ttf");
|
||||
style.WindowRounding = WINDOW_ROUNDING;
|
||||
style.ChildRounding = style.WindowRounding;
|
||||
style.FrameRounding = style.WindowRounding;
|
||||
style.GrabRounding = style.WindowRounding;
|
||||
style.PopupRounding = style.WindowRounding;
|
||||
style.ScrollbarRounding = style.WindowRounding;
|
||||
style.Colors[ImGuiCol_TitleBg] = WINDOW_COLOR;
|
||||
style.Colors[ImGuiCol_TitleBgActive] = WINDOW_COLOR;
|
||||
style.Colors[ImGuiCol_TitleBgCollapsed] = WINDOW_COLOR;
|
||||
|
||||
io.IniFilename = nullptr;
|
||||
|
||||
if (!ImGui_ImplSDL3_InitForOpenGL(window, context))
|
||||
{
|
||||
std::cout << "Failed to initialize Dear ImGui SDL3 backend" << "\n";
|
||||
ImGui::DestroyContext(imguiContext);
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Initialize Dear ImGui SDL3 backend" << "\n";
|
||||
|
||||
if (!ImGui_ImplOpenGL3_Init(GLSL_VERSION))
|
||||
{
|
||||
std::cout << "Failed to initialize Dear ImGui OpenGL backend" << "\n";
|
||||
ImGui_ImplSDL3_Shutdown();
|
||||
ImGui::DestroyContext(imguiContext);
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Initialize Dear ImGui OpenGL backend" << "\n";
|
||||
}
|
||||
|
||||
Loader::~Loader()
|
||||
{
|
||||
if (ImGui::GetCurrentContext())
|
||||
{
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplSDL3_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
if (context) SDL_GL_DestroyContext(context);
|
||||
if (window) SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
|
||||
std::cout << "Exiting..." << "\n";
|
||||
}
|
||||
};
|
||||
21
src/loader.h
Normal file
21
src/loader.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "glm/ext/vector_float2.hpp"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace game
|
||||
{
|
||||
class Loader
|
||||
{
|
||||
public:
|
||||
static constexpr glm::vec2 SIZE = {1080, 720};
|
||||
|
||||
SDL_Window* window{};
|
||||
SDL_GLContext context{};
|
||||
bool isError{};
|
||||
|
||||
Loader();
|
||||
~Loader();
|
||||
};
|
||||
|
||||
};
|
||||
38
src/main.cpp
Normal file
38
src/main.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten/emscripten.h>
|
||||
#endif
|
||||
|
||||
#include "loader.h"
|
||||
#include "state.h"
|
||||
|
||||
using namespace game;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
static void emscripten_loop(void* arg)
|
||||
{
|
||||
auto* state = (State*)(arg);
|
||||
state->loop();
|
||||
|
||||
if (!state->isRunning) emscripten_cancel_main_loop();
|
||||
}
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
Loader loader;
|
||||
|
||||
if (loader.isError) return EXIT_FAILURE;
|
||||
|
||||
State state(loader.window, loader.context, Loader::SIZE);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
emscripten_set_main_loop_arg(emscripten_loop, &state, 0, true);
|
||||
#else
|
||||
while (state.isRunning)
|
||||
state.loop();
|
||||
#endif
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
197
src/resource/actor.cpp
Normal file
197
src/resource/actor.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
#include "actor.h"
|
||||
|
||||
#include "../util/map_.h"
|
||||
#include "../util/math_.h"
|
||||
#include "../util/unordered_map_.h"
|
||||
#include "../util/vector_.h"
|
||||
|
||||
#include "../resource/audio.h"
|
||||
#include "../resource/texture.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
using namespace glm;
|
||||
using namespace game::util;
|
||||
using namespace game::anm2;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
std::shared_ptr<void> texture_callback(const std::filesystem::path& path) { return std::make_shared<Texture>(path); }
|
||||
std::shared_ptr<void> sound_callback(const std::filesystem::path& path) { return std::make_shared<Audio>(path); }
|
||||
|
||||
Actor::Actor(const std::filesystem::path& path, vec2 position) : anm2(path, texture_callback, sound_callback)
|
||||
{
|
||||
this->position = position;
|
||||
play(anm2.animations.defaultAnimation);
|
||||
}
|
||||
|
||||
anm2::Animation* Actor::animation_get() { return vector::find(anm2.animations.items, animationIndex); }
|
||||
|
||||
anm2::Item* Actor::item_get(anm2::Type type, int id)
|
||||
{
|
||||
if (auto animation = animation_get())
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case anm2::ROOT:
|
||||
return &animation->rootAnimation;
|
||||
break;
|
||||
case anm2::LAYER:
|
||||
return unordered_map::find(animation->layerAnimations, id);
|
||||
case anm2::NULL_:
|
||||
return map::find(animation->nullAnimations, id);
|
||||
break;
|
||||
case anm2::TRIGGER:
|
||||
return &animation->triggers;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
anm2::Frame* Actor::trigger_get(int atFrame)
|
||||
{
|
||||
if (auto item = item_get(anm2::TRIGGER))
|
||||
for (auto& trigger : item->frames)
|
||||
if (trigger.atFrame == atFrame) return &trigger;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
anm2::Frame* Actor::frame_get(int index, anm2::Type type, int id)
|
||||
{
|
||||
if (auto item = item_get(type, id)) return vector::find(item->frames, index);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
anm2::Frame Actor::frame_generate(anm2::Item& item, float time)
|
||||
{
|
||||
anm2::Frame frame{};
|
||||
frame.isVisible = false;
|
||||
|
||||
if (item.frames.empty()) return frame;
|
||||
|
||||
time = time < 0.0f ? 0.0f : time;
|
||||
|
||||
anm2::Frame* frameNext = nullptr;
|
||||
int durationCurrent = 0;
|
||||
int durationNext = 0;
|
||||
|
||||
for (int i = 0; i < item.frames.size(); i++)
|
||||
{
|
||||
anm2::Frame& checkFrame = item.frames[i];
|
||||
|
||||
frame = checkFrame;
|
||||
|
||||
durationNext += frame.duration;
|
||||
|
||||
if (time >= durationCurrent && time < durationNext)
|
||||
{
|
||||
if (i + 1 < (int)item.frames.size())
|
||||
frameNext = &item.frames[i + 1];
|
||||
else
|
||||
frameNext = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
durationCurrent += frame.duration;
|
||||
}
|
||||
|
||||
if (frame.isInterpolated && frameNext && frame.duration > 1)
|
||||
{
|
||||
auto interpolation = (time - durationCurrent) / (durationNext - durationCurrent);
|
||||
|
||||
frame.rotation = glm::mix(frame.rotation, frameNext->rotation, interpolation);
|
||||
frame.position = glm::mix(frame.position, frameNext->position, interpolation);
|
||||
frame.scale = glm::mix(frame.scale, frameNext->scale, interpolation);
|
||||
frame.colorOffset = glm::mix(frame.colorOffset, frameNext->colorOffset, interpolation);
|
||||
frame.tint = glm::mix(frame.tint, frameNext->tint, interpolation);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void Actor::play(const std::string& name)
|
||||
{
|
||||
for (int i = 0; i < anm2.animations.items.size(); i++)
|
||||
{
|
||||
if (anm2.animations.items[i].name == name)
|
||||
{
|
||||
animationIndex = i;
|
||||
time = 0.0f;
|
||||
isPlaying = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Actor::tick()
|
||||
{
|
||||
if (!isPlaying) return;
|
||||
auto animation = animation_get();
|
||||
if (!animation) return;
|
||||
|
||||
time += anm2.info.fps / 30.0f;
|
||||
|
||||
auto intTime = (int)time;
|
||||
|
||||
if (auto trigger = trigger_get(intTime))
|
||||
{
|
||||
if (!playedTriggers.contains(intTime))
|
||||
{
|
||||
if (auto sound = map::find(anm2.content.sounds, trigger->soundID)) sound->audio.play();
|
||||
playedTriggers.insert(intTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (time >= animation->frameNum)
|
||||
{
|
||||
if (animation->isLoop)
|
||||
time = 0.0f;
|
||||
else
|
||||
isPlaying = false;
|
||||
|
||||
playedTriggers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Actor::render(Shader& shader, Canvas& canvas)
|
||||
{
|
||||
auto animation = animation_get();
|
||||
if (!animation) return;
|
||||
|
||||
auto root = frame_generate(animation->rootAnimation, time);
|
||||
auto rootModel = math::quad_model_parent_get(root.position + position, root.pivot,
|
||||
math::percent_to_unit(root.scale), root.rotation);
|
||||
|
||||
for (auto& i : animation->layerOrder)
|
||||
{
|
||||
auto& layerAnimation = animation->layerAnimations[i];
|
||||
if (!layerAnimation.isVisible) continue;
|
||||
|
||||
auto layer = map::find(anm2.content.layers, i);
|
||||
if (!layer) continue;
|
||||
|
||||
auto spritesheet = map::find(anm2.content.spritesheets, layer->spritesheetID);
|
||||
if (!spritesheet) continue;
|
||||
|
||||
auto frame = frame_generate(layerAnimation, time);
|
||||
if (!frame.isVisible) continue;
|
||||
|
||||
auto model = math::quad_model_get(frame.size, frame.position, frame.pivot, math::percent_to_unit(frame.scale),
|
||||
frame.rotation);
|
||||
model = rootModel * model;
|
||||
|
||||
auto& texture = spritesheet->texture;
|
||||
if (!texture.is_valid()) return;
|
||||
|
||||
auto uvMin = frame.crop / vec2(texture.size);
|
||||
auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
|
||||
auto uvVertices = math::uv_vertices_get(uvMin, uvMax);
|
||||
|
||||
canvas.texture_render(shader, texture.id, model, frame.tint, frame.colorOffset, uvVertices.data());
|
||||
}
|
||||
}
|
||||
};
|
||||
33
src/resource/actor.h
Normal file
33
src/resource/actor.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "../canvas.h"
|
||||
#include "anm2.h"
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Actor
|
||||
{
|
||||
|
||||
public:
|
||||
anm2::Anm2 anm2{};
|
||||
glm::vec2 position{};
|
||||
float time{};
|
||||
bool isPlaying{};
|
||||
int animationIndex{-1};
|
||||
std::unordered_set<int> playedTriggers{};
|
||||
|
||||
Actor(const std::filesystem::path&, glm::vec2);
|
||||
anm2::Animation* animation_get();
|
||||
anm2::Animation* animation_get(std::string&);
|
||||
int animation_index_get(anm2::Animation&);
|
||||
anm2::Item* item_get(anm2::Type, int = -1);
|
||||
anm2::Frame* trigger_get(int);
|
||||
anm2::Frame* frame_get(int, anm2::Type, int = -1);
|
||||
anm2::Frame frame_generate(anm2::Item&, float);
|
||||
void play(const std::string&);
|
||||
void tick();
|
||||
void render(Shader&, Canvas&);
|
||||
};
|
||||
}
|
||||
248
src/resource/anm2.cpp
Normal file
248
src/resource/anm2.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
#include "anm2.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace game::resource;
|
||||
|
||||
namespace game::anm2
|
||||
{
|
||||
XMLError query_string_attribute(XMLElement* element, const char* attribute, std::string* value)
|
||||
{
|
||||
const char* temp = nullptr;
|
||||
auto result = element->QueryStringAttribute(attribute, &temp);
|
||||
if (result == XML_SUCCESS && temp && value) *value = temp;
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_path_attribute(XMLElement* element, const char* attribute, std::filesystem::path* value)
|
||||
{
|
||||
std::string temp{};
|
||||
auto result = query_string_attribute(element, attribute, &temp);
|
||||
if (value) *value = std::filesystem::path(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
XMLError query_color_attribute(XMLElement* element, const char* attribute, float* value)
|
||||
{
|
||||
int temp{};
|
||||
auto result = element->QueryIntAttribute(attribute, &temp);
|
||||
if (result == XML_SUCCESS && value) *value = (temp / 255.0f);
|
||||
return result;
|
||||
}
|
||||
|
||||
Info::Info(XMLElement* element)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Fps", &fps);
|
||||
}
|
||||
|
||||
Spritesheet::Spritesheet(XMLElement* element, int& id, TextureCallback textureCallback)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_path_attribute(element, "Path", &path);
|
||||
texture = Texture(path);
|
||||
}
|
||||
|
||||
Layer::Layer(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_string_attribute(element, "Name", &name);
|
||||
element->QueryIntAttribute("SpritesheetId", &spritesheetID);
|
||||
}
|
||||
|
||||
Null::Null(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_string_attribute(element, "Name", &name);
|
||||
element->QueryBoolAttribute("ShowRect", &isShowRect);
|
||||
}
|
||||
|
||||
Event::Event(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_string_attribute(element, "Name", &name);
|
||||
}
|
||||
|
||||
Sound::Sound(XMLElement* element, int& id, SoundCallback soundCallback)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
query_path_attribute(element, "Path", &path);
|
||||
audio = Audio(path);
|
||||
}
|
||||
|
||||
Content::Content(XMLElement* element, TextureCallback textureCallback, SoundCallback soundCallback)
|
||||
{
|
||||
if (auto spritesheetsElement = element->FirstChildElement("Spritesheets"))
|
||||
{
|
||||
for (auto child = spritesheetsElement->FirstChildElement("Spritesheet"); child;
|
||||
child = child->NextSiblingElement("Spritesheet"))
|
||||
{
|
||||
int spritesheetId{};
|
||||
Spritesheet spritesheet(child, spritesheetId, textureCallback);
|
||||
spritesheets.emplace(spritesheetId, std::move(spritesheet));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto layersElement = element->FirstChildElement("Layers"))
|
||||
{
|
||||
for (auto child = layersElement->FirstChildElement("Layer"); child; child = child->NextSiblingElement("Layer"))
|
||||
{
|
||||
int layerId{};
|
||||
Layer layer(child, layerId);
|
||||
layers.emplace(layerId, std::move(layer));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto nullsElement = element->FirstChildElement("Nulls"))
|
||||
{
|
||||
for (auto child = nullsElement->FirstChildElement("Null"); child; child = child->NextSiblingElement("Null"))
|
||||
{
|
||||
int nullId{};
|
||||
Null null(child, nullId);
|
||||
nulls.emplace(nullId, std::move(null));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto eventsElement = element->FirstChildElement("Events"))
|
||||
{
|
||||
for (auto child = eventsElement->FirstChildElement("Event"); child; child = child->NextSiblingElement("Event"))
|
||||
{
|
||||
int eventId{};
|
||||
Event event(child, eventId);
|
||||
events.emplace(eventId, std::move(event));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto soundsElement = element->FirstChildElement("Sounds"))
|
||||
{
|
||||
for (auto child = soundsElement->FirstChildElement("Sound"); child; child = child->NextSiblingElement("Sound"))
|
||||
{
|
||||
int soundId{};
|
||||
Sound sound(child, soundId, soundCallback);
|
||||
sounds.emplace(soundId, std::move(sound));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Frame::Frame(XMLElement* element, Type type)
|
||||
{
|
||||
if (type != TRIGGER)
|
||||
{
|
||||
element->QueryFloatAttribute("XPosition", &position.x);
|
||||
element->QueryFloatAttribute("YPosition", &position.y);
|
||||
if (type == LAYER)
|
||||
{
|
||||
element->QueryFloatAttribute("XPivot", &pivot.x);
|
||||
element->QueryFloatAttribute("YPivot", &pivot.y);
|
||||
element->QueryFloatAttribute("XCrop", &crop.x);
|
||||
element->QueryFloatAttribute("YCrop", &crop.y);
|
||||
element->QueryFloatAttribute("Width", &size.x);
|
||||
element->QueryFloatAttribute("Height", &size.y);
|
||||
}
|
||||
element->QueryFloatAttribute("XScale", &scale.x);
|
||||
element->QueryFloatAttribute("YScale", &scale.y);
|
||||
element->QueryIntAttribute("Delay", &duration);
|
||||
element->QueryBoolAttribute("Visible", &isVisible);
|
||||
query_color_attribute(element, "RedTint", &tint.r);
|
||||
query_color_attribute(element, "GreenTint", &tint.g);
|
||||
query_color_attribute(element, "BlueTint", &tint.b);
|
||||
query_color_attribute(element, "AlphaTint", &tint.a);
|
||||
query_color_attribute(element, "RedOffset", &colorOffset.r);
|
||||
query_color_attribute(element, "GreenOffset", &colorOffset.g);
|
||||
query_color_attribute(element, "BlueOffset", &colorOffset.b);
|
||||
element->QueryFloatAttribute("Rotation", &rotation);
|
||||
element->QueryBoolAttribute("Interpolated", &isInterpolated);
|
||||
}
|
||||
else
|
||||
{
|
||||
element->QueryIntAttribute("EventId", &eventID);
|
||||
element->QueryIntAttribute("SoundId", &soundID);
|
||||
element->QueryIntAttribute("AtFrame", &atFrame);
|
||||
}
|
||||
}
|
||||
|
||||
Item::Item(XMLElement* element, Type type, int& id)
|
||||
{
|
||||
if (type == LAYER) element->QueryIntAttribute("LayerId", &id);
|
||||
if (type == NULL_) element->QueryIntAttribute("NullId", &id);
|
||||
|
||||
element->QueryBoolAttribute("Visible", &isVisible);
|
||||
|
||||
for (auto child = type == TRIGGER ? element->FirstChildElement("Trigger") : element->FirstChildElement("Frame");
|
||||
child; child = type == TRIGGER ? child->NextSiblingElement("Trigger") : child->NextSiblingElement("Frame"))
|
||||
frames.emplace_back(Frame(child, type));
|
||||
}
|
||||
|
||||
Animation::Animation(XMLElement* element)
|
||||
{
|
||||
query_string_attribute(element, "Name", &name);
|
||||
element->QueryIntAttribute("FrameNum", &frameNum);
|
||||
element->QueryBoolAttribute("Loop", &isLoop);
|
||||
|
||||
int id{-1};
|
||||
|
||||
if (auto rootAnimationElement = element->FirstChildElement("RootAnimation"))
|
||||
rootAnimation = Item(rootAnimationElement, ROOT, id);
|
||||
|
||||
if (auto layerAnimationsElement = element->FirstChildElement("LayerAnimations"))
|
||||
{
|
||||
for (auto child = layerAnimationsElement->FirstChildElement("LayerAnimation"); child;
|
||||
child = child->NextSiblingElement("LayerAnimation"))
|
||||
{
|
||||
Item layerAnimation(child, LAYER, id);
|
||||
layerOrder.push_back(id);
|
||||
layerAnimations.emplace(id, std::move(layerAnimation));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto nullAnimationsElement = element->FirstChildElement("NullAnimations"))
|
||||
{
|
||||
for (auto child = nullAnimationsElement->FirstChildElement("NullAnimation"); child;
|
||||
child = child->NextSiblingElement("NullAnimation"))
|
||||
{
|
||||
Item nullAnimation(child, NULL_, id);
|
||||
nullAnimations.emplace(id, std::move(nullAnimation));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto triggersElement = element->FirstChildElement("Triggers")) triggers = Item(triggersElement, TRIGGER, id);
|
||||
}
|
||||
|
||||
Animations::Animations(XMLElement* element)
|
||||
{
|
||||
query_string_attribute(element, "DefaultAnimation", &defaultAnimation);
|
||||
|
||||
for (auto child = element->FirstChildElement("Animation"); child; child = child->NextSiblingElement("Animation"))
|
||||
items.emplace_back(Animation(child));
|
||||
}
|
||||
|
||||
Anm2::Anm2(const std::filesystem::path& path, TextureCallback textureCallback, SoundCallback soundCallback)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
if (document.LoadFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
std::cout << "Failed to initialize anm2: " << document.ErrorStr() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Initialzed anm2: " << path.string() << "\n";
|
||||
|
||||
auto previousPath = std::filesystem::current_path();
|
||||
std::filesystem::current_path(path.parent_path());
|
||||
|
||||
auto element = document.RootElement();
|
||||
|
||||
if (auto infoElement = element->FirstChildElement("Info")) info = Info(infoElement);
|
||||
if (auto contentElement = element->FirstChildElement("Content"))
|
||||
content = Content(contentElement, textureCallback, soundCallback);
|
||||
if (auto animationsElement = element->FirstChildElement("Animations")) animations = Animations(animationsElement);
|
||||
|
||||
std::filesystem::current_path(previousPath);
|
||||
}
|
||||
}
|
||||
163
src/resource/anm2.h
Normal file
163
src/resource/anm2.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "audio.h"
|
||||
#include "texture.h"
|
||||
|
||||
namespace game::anm2
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
NONE,
|
||||
ROOT,
|
||||
LAYER,
|
||||
NULL_,
|
||||
TRIGGER
|
||||
};
|
||||
|
||||
using TextureCallback = std::function<std::shared_ptr<void>(const std::filesystem::path&)>;
|
||||
using SoundCallback = std::function<std::shared_ptr<void>(const std::filesystem::path&)>;
|
||||
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
int fps = 30;
|
||||
|
||||
Info() = default;
|
||||
Info(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Spritesheet
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path{};
|
||||
resource::Texture texture{};
|
||||
|
||||
Spritesheet(tinyxml2::XMLElement*, int&, TextureCallback = nullptr);
|
||||
};
|
||||
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
std::string name{"New Layer"};
|
||||
int spritesheetID{-1};
|
||||
Layer(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Null
|
||||
{
|
||||
public:
|
||||
std::string name{"New Null"};
|
||||
bool isShowRect{};
|
||||
Null(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Event
|
||||
{
|
||||
public:
|
||||
std::string name{"New Event"};
|
||||
Event(tinyxml2::XMLElement*, int&);
|
||||
};
|
||||
|
||||
class Sound
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path{};
|
||||
resource::Audio audio{};
|
||||
|
||||
Sound(tinyxml2::XMLElement*, int&, SoundCallback = nullptr);
|
||||
};
|
||||
|
||||
class Content
|
||||
{
|
||||
public:
|
||||
std::map<int, Spritesheet> spritesheets{};
|
||||
std::map<int, Layer> layers{};
|
||||
std::map<int, Null> nulls{};
|
||||
std::map<int, Event> events{};
|
||||
std::map<int, Sound> sounds{};
|
||||
|
||||
Content() = default;
|
||||
Content(tinyxml2::XMLElement*, TextureCallback = nullptr, SoundCallback = nullptr);
|
||||
};
|
||||
|
||||
struct Frame
|
||||
{
|
||||
glm::vec2 crop{};
|
||||
glm::vec2 position{};
|
||||
glm::vec2 pivot{};
|
||||
glm::vec2 size{};
|
||||
glm::vec2 scale{100, 100};
|
||||
float rotation{};
|
||||
int duration{};
|
||||
glm::vec4 tint{1.0f, 1.0f, 1.0f, 1.0f};
|
||||
glm::vec3 colorOffset{};
|
||||
bool isInterpolated{};
|
||||
int eventID{-1};
|
||||
int soundID{-1};
|
||||
int atFrame{-1};
|
||||
|
||||
bool isVisible{true};
|
||||
|
||||
Frame() = default;
|
||||
Frame(tinyxml2::XMLElement*, Type);
|
||||
};
|
||||
|
||||
class Item
|
||||
{
|
||||
public:
|
||||
std::vector<Frame> frames{};
|
||||
bool isVisible{};
|
||||
|
||||
Item() = default;
|
||||
Item(tinyxml2::XMLElement*, Type, int&);
|
||||
};
|
||||
|
||||
class Animation
|
||||
{
|
||||
public:
|
||||
std::string name{"New Animation"};
|
||||
int frameNum{};
|
||||
bool isLoop{};
|
||||
|
||||
Item rootAnimation{};
|
||||
std::unordered_map<int, Item> layerAnimations{};
|
||||
std::vector<int> layerOrder{};
|
||||
std::map<int, Item> nullAnimations{};
|
||||
Item triggers{};
|
||||
|
||||
Animation() = default;
|
||||
Animation(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Animations
|
||||
{
|
||||
public:
|
||||
std::string defaultAnimation{};
|
||||
std::vector<Animation> items{};
|
||||
|
||||
Animations() = default;
|
||||
Animations(tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Anm2
|
||||
{
|
||||
public:
|
||||
Info info;
|
||||
Content content{};
|
||||
Animations animations{};
|
||||
|
||||
Anm2() = default;
|
||||
Anm2(const std::filesystem::path&, TextureCallback = nullptr, SoundCallback = nullptr);
|
||||
};
|
||||
}
|
||||
147
src/resource/audio.cpp
Normal file
147
src/resource/audio.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "audio.h"
|
||||
|
||||
#include <SDL3/SDL_properties.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
MIX_Mixer* Audio::mixer_get()
|
||||
{
|
||||
static auto mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
|
||||
return mixer;
|
||||
}
|
||||
|
||||
void Audio::retain()
|
||||
{
|
||||
if (refCount) ++(*refCount);
|
||||
}
|
||||
|
||||
void Audio::release()
|
||||
{
|
||||
if (refCount)
|
||||
{
|
||||
if (--(*refCount) == 0)
|
||||
{
|
||||
if (internal) MIX_DestroyAudio(internal);
|
||||
delete refCount;
|
||||
}
|
||||
refCount = nullptr;
|
||||
}
|
||||
internal = nullptr;
|
||||
}
|
||||
|
||||
Audio::Audio(const std::filesystem::path& path)
|
||||
{
|
||||
internal = MIX_LoadAudio(mixer_get(), path.c_str(), true);
|
||||
if (internal)
|
||||
{
|
||||
refCount = new int(1);
|
||||
std::cout << "Initialized audio: '" << path.string() << "'\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Failed to initialize audio: '" << path.string() << "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
Audio::Audio(const Audio& other)
|
||||
{
|
||||
internal = other.internal;
|
||||
refCount = other.refCount;
|
||||
retain();
|
||||
track = nullptr;
|
||||
}
|
||||
|
||||
Audio::Audio(Audio&& other) noexcept
|
||||
{
|
||||
internal = other.internal;
|
||||
track = other.track;
|
||||
refCount = other.refCount;
|
||||
|
||||
other.internal = nullptr;
|
||||
other.track = nullptr;
|
||||
other.refCount = nullptr;
|
||||
}
|
||||
|
||||
Audio& Audio::operator=(const Audio& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
unload();
|
||||
internal = other.internal;
|
||||
refCount = other.refCount;
|
||||
retain();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Audio& Audio::operator=(Audio&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
unload();
|
||||
internal = other.internal;
|
||||
track = other.track;
|
||||
refCount = other.refCount;
|
||||
|
||||
other.internal = nullptr;
|
||||
other.track = nullptr;
|
||||
other.refCount = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Audio::unload()
|
||||
{
|
||||
if (track)
|
||||
{
|
||||
MIX_DestroyTrack(track);
|
||||
track = nullptr;
|
||||
}
|
||||
release();
|
||||
}
|
||||
|
||||
void Audio::play(bool isLoop)
|
||||
{
|
||||
if (!internal) return;
|
||||
|
||||
auto mixer = mixer_get();
|
||||
|
||||
if (track && MIX_GetTrackMixer(track) != mixer)
|
||||
{
|
||||
MIX_DestroyTrack(track);
|
||||
track = nullptr;
|
||||
}
|
||||
|
||||
if (!track)
|
||||
{
|
||||
track = MIX_CreateTrack(mixer);
|
||||
if (!track) return;
|
||||
}
|
||||
|
||||
MIX_SetTrackAudio(track, internal);
|
||||
|
||||
SDL_PropertiesID options = 0;
|
||||
|
||||
if (isLoop)
|
||||
{
|
||||
options = SDL_CreateProperties();
|
||||
if (options) SDL_SetNumberProperty(options, MIX_PROP_PLAY_LOOPS_NUMBER, -1);
|
||||
}
|
||||
|
||||
MIX_PlayTrack(track, options);
|
||||
|
||||
if (options) SDL_DestroyProperties(options);
|
||||
}
|
||||
|
||||
void Audio::stop()
|
||||
{
|
||||
if (track) MIX_StopTrack(track, 0);
|
||||
}
|
||||
|
||||
bool Audio::is_playing() const { return track && MIX_TrackPlaying(track); }
|
||||
|
||||
Audio::~Audio() { unload(); }
|
||||
bool Audio::is_valid() const { return internal != nullptr; }
|
||||
}
|
||||
31
src/resource/audio.h
Normal file
31
src/resource/audio.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
#include <filesystem>
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Audio
|
||||
{
|
||||
MIX_Audio* internal{nullptr};
|
||||
MIX_Track* track{nullptr};
|
||||
int* refCount{nullptr};
|
||||
MIX_Mixer* mixer_get();
|
||||
void unload();
|
||||
void retain();
|
||||
void release();
|
||||
|
||||
public:
|
||||
Audio() = default;
|
||||
Audio(const std::filesystem::path&);
|
||||
Audio(const Audio&);
|
||||
Audio(Audio&&) noexcept;
|
||||
Audio& operator=(const Audio&);
|
||||
Audio& operator=(Audio&&) noexcept;
|
||||
~Audio();
|
||||
bool is_valid() const;
|
||||
void play(bool isLoop = false);
|
||||
void stop();
|
||||
bool is_playing() const;
|
||||
};
|
||||
}
|
||||
79
src/resource/shader.cpp
Normal file
79
src/resource/shader.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "shader.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
Shader::Shader(const char* vertex, const char* fragment)
|
||||
{
|
||||
id = glCreateProgram();
|
||||
|
||||
auto compile = [&](const GLuint& shaderHandle, const char* text, const char* stage)
|
||||
{
|
||||
int isCompile{};
|
||||
glShaderSource(shaderHandle, 1, &text, nullptr);
|
||||
glCompileShader(shaderHandle);
|
||||
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &isCompile);
|
||||
if (!isCompile)
|
||||
{
|
||||
GLint logLength = 0;
|
||||
glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &logLength);
|
||||
std::string log(logLength, '\0');
|
||||
if (logLength > 0) glGetShaderInfoLog(shaderHandle, logLength, nullptr, log.data());
|
||||
std::cout << "Failed to compile shader: " << log << '\n';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto vertexHandle = glCreateShader(GL_VERTEX_SHADER);
|
||||
auto fragmentHandle = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
|
||||
if (!(compile(vertexHandle, vertex, "vertex") && compile(fragmentHandle, fragment, "fragment")))
|
||||
{
|
||||
glDeleteShader(vertexHandle);
|
||||
glDeleteShader(fragmentHandle);
|
||||
glDeleteProgram(id);
|
||||
id = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
glAttachShader(id, vertexHandle);
|
||||
glAttachShader(id, fragmentHandle);
|
||||
|
||||
glLinkProgram(id);
|
||||
|
||||
auto isLinked = GL_FALSE;
|
||||
glGetProgramiv(id, GL_LINK_STATUS, &isLinked);
|
||||
|
||||
if (!isLinked)
|
||||
{
|
||||
glDeleteProgram(id);
|
||||
id = 0;
|
||||
std::cout << "Failed to link shader: " << id << "\n";
|
||||
}
|
||||
else
|
||||
std::cout << "Initialized shader: " << id << "\n";
|
||||
|
||||
glDeleteShader(vertexHandle);
|
||||
glDeleteShader(fragmentHandle);
|
||||
}
|
||||
|
||||
Shader::~Shader()
|
||||
{
|
||||
if (is_valid()) glDeleteProgram(id);
|
||||
}
|
||||
|
||||
bool Shader::is_valid() const { return id != 0; }
|
||||
|
||||
Shader& Shader::operator=(Shader&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (is_valid()) glDeleteProgram(id);
|
||||
id = other.id;
|
||||
other.id = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
118
src/resource/shader.h
Normal file
118
src/resource/shader.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GLES3/gl3.h>
|
||||
#else
|
||||
#include <glad/glad.h>
|
||||
#endif
|
||||
|
||||
namespace game::resource::shader
|
||||
{
|
||||
struct Info
|
||||
{
|
||||
const char* vertex{};
|
||||
const char* fragment{};
|
||||
};
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
constexpr auto VERTEX = R"(#version 300 es
|
||||
layout (location = 0) in vec2 i_position;
|
||||
layout (location = 1) in vec2 i_uv;
|
||||
out vec2 v_uv;
|
||||
uniform mat4 u_model;
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_projection;
|
||||
void main()
|
||||
{
|
||||
v_uv = i_uv;
|
||||
mat4 transform = u_projection * u_view * u_model;
|
||||
gl_Position = transform * vec4(i_position, 0.0, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr auto FRAGMENT = R"(#version 300 es
|
||||
precision mediump float;
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec4 u_tint;
|
||||
uniform vec3 u_color_offset;
|
||||
out vec4 o_fragColor;
|
||||
void main()
|
||||
{
|
||||
vec4 texColor = texture(u_texture, v_uv);
|
||||
texColor *= u_tint;
|
||||
texColor.rgb += u_color_offset;
|
||||
o_fragColor = texColor;
|
||||
}
|
||||
)";
|
||||
#else
|
||||
constexpr auto VERTEX = R"(#version 330 core
|
||||
layout (location = 0) in vec2 i_position;
|
||||
layout (location = 1) in vec2 i_uv;
|
||||
out vec2 v_uv;
|
||||
uniform mat4 u_model;
|
||||
uniform mat4 u_view;
|
||||
uniform mat4 u_projection;
|
||||
void main()
|
||||
{
|
||||
v_uv = i_uv;
|
||||
mat4 transform = u_projection * u_view * u_model;
|
||||
gl_Position = transform * vec4(i_position, 0.0, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr auto FRAGMENT = R"(#version 330 core
|
||||
in vec2 v_uv;
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec4 u_tint;
|
||||
uniform vec3 u_color_offset;
|
||||
out vec4 o_fragColor;
|
||||
void main()
|
||||
{
|
||||
vec4 texColor = texture(u_texture, v_uv);
|
||||
texColor *= u_tint;
|
||||
texColor.rgb += u_color_offset;
|
||||
o_fragColor = texColor;
|
||||
}
|
||||
)";
|
||||
#endif
|
||||
|
||||
constexpr auto UNIFORM_MODEL = "u_model";
|
||||
constexpr auto UNIFORM_VIEW = "u_view";
|
||||
constexpr auto UNIFORM_PROJECTION = "u_projection";
|
||||
constexpr auto UNIFORM_TEXTURE = "u_texture";
|
||||
constexpr auto UNIFORM_TINT = "u_tint";
|
||||
constexpr auto UNIFORM_COLOR_OFFSET = "u_color_offset";
|
||||
|
||||
#define SHADERS X(TEXTURE, VERTEX, FRAGMENT)
|
||||
|
||||
enum Type
|
||||
{
|
||||
#define X(symbol, vertex, fragment) symbol,
|
||||
SHADERS
|
||||
#undef X
|
||||
COUNT
|
||||
};
|
||||
|
||||
constexpr Info INFO[] = {
|
||||
#define X(symbol, vertex, fragment) {vertex, fragment},
|
||||
SHADERS
|
||||
#undef X
|
||||
};
|
||||
}
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Shader
|
||||
{
|
||||
public:
|
||||
GLint id{};
|
||||
|
||||
Shader() = default;
|
||||
Shader(const char*, const char*);
|
||||
bool is_valid() const;
|
||||
~Shader();
|
||||
|
||||
Shader& operator=(Shader&&) noexcept;
|
||||
};
|
||||
}
|
||||
132
src/resource/texture.cpp
Normal file
132
src/resource/texture.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#include "texture.h"
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
#endif
|
||||
|
||||
#define STBI_ONLY_PNG
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace glm;
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
bool Texture::is_valid() const { return id != 0; }
|
||||
|
||||
void Texture::retain()
|
||||
{
|
||||
if (refCount) ++(*refCount);
|
||||
}
|
||||
|
||||
void Texture::release()
|
||||
{
|
||||
if (refCount)
|
||||
{
|
||||
if (--(*refCount) == 0)
|
||||
{
|
||||
if (is_valid()) glDeleteTextures(1, &id);
|
||||
delete refCount;
|
||||
}
|
||||
refCount = nullptr;
|
||||
}
|
||||
else if (is_valid())
|
||||
{
|
||||
glDeleteTextures(1, &id);
|
||||
}
|
||||
|
||||
id = 0;
|
||||
}
|
||||
|
||||
Texture::~Texture() { release(); }
|
||||
|
||||
Texture::Texture(const Texture& other)
|
||||
{
|
||||
id = other.id;
|
||||
size = other.size;
|
||||
channels = other.channels;
|
||||
refCount = other.refCount;
|
||||
retain();
|
||||
}
|
||||
|
||||
Texture::Texture(Texture&& other) noexcept
|
||||
{
|
||||
id = other.id;
|
||||
size = other.size;
|
||||
channels = other.channels;
|
||||
refCount = other.refCount;
|
||||
|
||||
other.id = 0;
|
||||
other.size = {};
|
||||
other.channels = 0;
|
||||
other.refCount = nullptr;
|
||||
}
|
||||
|
||||
Texture& Texture::operator=(const Texture& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
release();
|
||||
id = other.id;
|
||||
size = other.size;
|
||||
channels = other.channels;
|
||||
refCount = other.refCount;
|
||||
retain();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Texture& Texture::operator=(Texture&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
release();
|
||||
id = other.id;
|
||||
size = other.size;
|
||||
channels = other.channels;
|
||||
refCount = other.refCount;
|
||||
|
||||
other.id = 0;
|
||||
other.size = {};
|
||||
other.channels = 0;
|
||||
other.refCount = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Texture::Texture(const std::filesystem::path& path)
|
||||
{
|
||||
if (auto data = stbi_load(path.c_str(), &size.x, &size.y, nullptr, CHANNELS); data)
|
||||
{
|
||||
glGenTextures(1, &id);
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
stbi_image_free(data);
|
||||
channels = CHANNELS;
|
||||
refCount = new int(1);
|
||||
std::cout << "Initialized texture: '" << path.string() << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
id = 0;
|
||||
size = {};
|
||||
channels = 0;
|
||||
refCount = nullptr;
|
||||
std::cout << "Failed to initialize texture: '" << path.string() << "'\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/resource/texture.h
Normal file
38
src/resource/texture.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GLES3/gl3.h>
|
||||
#else
|
||||
#include <glad/glad.h>
|
||||
#endif
|
||||
|
||||
#include <filesystem>
|
||||
#include <glm/ext/vector_int2.hpp>
|
||||
|
||||
namespace game::resource
|
||||
{
|
||||
class Texture
|
||||
{
|
||||
public:
|
||||
static constexpr auto CHANNELS = 4;
|
||||
|
||||
GLuint id{};
|
||||
glm::ivec2 size{};
|
||||
int channels{};
|
||||
|
||||
bool is_valid() const;
|
||||
|
||||
Texture() = default;
|
||||
~Texture();
|
||||
Texture(const Texture&);
|
||||
Texture(Texture&&) noexcept;
|
||||
Texture& operator=(const Texture&);
|
||||
Texture& operator=(Texture&&) noexcept;
|
||||
Texture(const std::filesystem::path&);
|
||||
|
||||
private:
|
||||
int* refCount{nullptr};
|
||||
void retain();
|
||||
void release();
|
||||
};
|
||||
}
|
||||
12
src/resources.cpp
Normal file
12
src/resources.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "resources.h"
|
||||
|
||||
using namespace game::resource;
|
||||
|
||||
namespace game
|
||||
{
|
||||
Resources::Resources()
|
||||
{
|
||||
for (int i = 0; i < shader::COUNT; i++)
|
||||
shaders[i] = Shader(shader::INFO[i].vertex, shader::INFO[i].fragment);
|
||||
}
|
||||
}
|
||||
14
src/resources.h
Normal file
14
src/resources.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "resource/shader.h"
|
||||
|
||||
namespace game
|
||||
{
|
||||
class Resources
|
||||
{
|
||||
public:
|
||||
resource::Shader shaders[resource::shader::COUNT];
|
||||
|
||||
Resources();
|
||||
};
|
||||
}
|
||||
87
src/state.cpp
Normal file
87
src/state.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "state.h"
|
||||
|
||||
#include <backends/imgui_impl_opengl3.h>
|
||||
#include <backends/imgui_impl_sdl3.h>
|
||||
#include <imgui.h>
|
||||
|
||||
using namespace glm;
|
||||
using namespace game::resource;
|
||||
|
||||
namespace game
|
||||
{
|
||||
constexpr auto TICK_RATE = 30;
|
||||
constexpr auto TICK_INTERVAL = (1000 / TICK_RATE);
|
||||
constexpr auto UPDATE_RATE = 120;
|
||||
constexpr auto UPDATE_INTERVAL = (1000 / UPDATE_RATE);
|
||||
|
||||
State::State(SDL_Window* inWindow, SDL_GLContext inContext, vec2 size)
|
||||
: window(inWindow), context(inContext), canvas(size, true)
|
||||
{
|
||||
}
|
||||
|
||||
void State::tick() { actor.tick(); }
|
||||
|
||||
void State::update()
|
||||
{
|
||||
SDL_Event event;
|
||||
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
ImGui_ImplSDL3_ProcessEvent(&event);
|
||||
if (event.type == SDL_EVENT_QUIT) isRunning = false;
|
||||
}
|
||||
if (!isRunning) return;
|
||||
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplSDL3_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
ImGui::Begin("Metrics");
|
||||
ImGui::Text("Time: %f", actor.time);
|
||||
ImGui::Text("IS Playing: %s", actor.isPlaying ? "true" : "false");
|
||||
|
||||
auto animation = actor.animation_get();
|
||||
ImGui::Text("Animation: %s", animation ? animation->name.c_str() : "null");
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Render();
|
||||
}
|
||||
|
||||
void State::render()
|
||||
{
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
|
||||
int width{};
|
||||
int height{};
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
|
||||
canvas.bind();
|
||||
canvas.clear(glm::vec4(0, 0, 0, 1));
|
||||
actor.render(resources.shaders[shader::TEXTURE], canvas);
|
||||
canvas.unbind();
|
||||
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
void State::loop()
|
||||
{
|
||||
auto currentTick = SDL_GetTicks();
|
||||
auto currentUpdate = SDL_GetTicks();
|
||||
|
||||
if (currentUpdate - previousUpdate >= UPDATE_INTERVAL)
|
||||
{
|
||||
update();
|
||||
render();
|
||||
previousUpdate = currentUpdate;
|
||||
}
|
||||
|
||||
if (currentTick - previousTick >= TICK_INTERVAL)
|
||||
{
|
||||
tick();
|
||||
previousTick = currentTick;
|
||||
}
|
||||
|
||||
SDL_Delay(1);
|
||||
}
|
||||
}
|
||||
35
src/state.h
Normal file
35
src/state.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "resource/actor.h"
|
||||
|
||||
#include "canvas.h"
|
||||
#include "resources.h"
|
||||
|
||||
namespace game
|
||||
{
|
||||
class State
|
||||
{
|
||||
SDL_Window* window{};
|
||||
SDL_GLContext context{};
|
||||
|
||||
Resources resources;
|
||||
|
||||
resource::Actor actor{"resources/anm2/snivy.anm2", glm::vec2(400, 400)};
|
||||
|
||||
long previousUpdate{};
|
||||
long previousTick{};
|
||||
|
||||
void tick();
|
||||
void update();
|
||||
void render();
|
||||
|
||||
public:
|
||||
bool isRunning{true};
|
||||
Canvas canvas{};
|
||||
|
||||
State(SDL_Window*, SDL_GLContext, glm::vec2);
|
||||
void loop();
|
||||
};
|
||||
};
|
||||
13
src/util/map_.h
Normal file
13
src/util/map_.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace game::util::map
|
||||
{
|
||||
template <typename T0, typename T1> T1* find(std::map<T0, T1>& map, T0 key)
|
||||
{
|
||||
auto it = map.find(key);
|
||||
if (it != map.end()) return &it->second;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
41
src/util/math_.cpp
Normal file
41
src/util/math_.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "math_.h"
|
||||
#include "glm/ext/matrix_transform.hpp"
|
||||
|
||||
using namespace glm;
|
||||
|
||||
namespace game::util::math
|
||||
{
|
||||
mat4 quad_model_get(vec2 size, vec2 position, vec2 pivot, vec2 scale, float rotation)
|
||||
{
|
||||
vec2 scaleAbsolute = glm::abs(scale);
|
||||
vec2 scaleSign = glm::sign(scale);
|
||||
vec2 pivotScaled = pivot * scaleAbsolute;
|
||||
vec2 sizeScaled = size * scaleAbsolute;
|
||||
|
||||
mat4 model(1.0f);
|
||||
model = glm::translate(model, vec3(position - pivotScaled, 0.0f));
|
||||
model = glm::translate(model, vec3(pivotScaled, 0.0f));
|
||||
model = glm::scale(model, vec3(scaleSign, 1.0f));
|
||||
model = glm::rotate(model, glm::radians(rotation), vec3(0, 0, 1));
|
||||
model = glm::translate(model, vec3(-pivotScaled, 0.0f));
|
||||
model = glm::scale(model, vec3(sizeScaled, 1.0f));
|
||||
return model;
|
||||
}
|
||||
|
||||
mat4 quad_model_parent_get(vec2 position, vec2 pivot, vec2 scale, float rotation)
|
||||
{
|
||||
vec2 scaleSign = glm::sign(scale);
|
||||
vec2 scaleAbsolute = glm::abs(scale);
|
||||
float handedness = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f;
|
||||
|
||||
mat4 local(1.0f);
|
||||
local = glm::translate(local, vec3(pivot, 0.0f));
|
||||
local = glm::scale(local, vec3(scaleSign, 1.0f));
|
||||
local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1));
|
||||
local = glm::translate(local, vec3(-pivot, 0.0f));
|
||||
local = glm::scale(local, vec3(scaleAbsolute, 1.0f));
|
||||
|
||||
return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local;
|
||||
}
|
||||
|
||||
}
|
||||
19
src/util/math_.h
Normal file
19
src/util/math_.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "glm/ext/matrix_float4x4.hpp"
|
||||
#include "glm/ext/vector_float2.hpp"
|
||||
|
||||
namespace game::util::math
|
||||
{
|
||||
glm::mat4 quad_model_get(glm::vec2, glm::vec2, glm::vec2, glm::vec2, float);
|
||||
glm::mat4 quad_model_parent_get(glm::vec2 position, glm::vec2 pivot, glm::vec2, float);
|
||||
|
||||
template <typename T> constexpr T percent_to_unit(T value) { return value / 100.0f; }
|
||||
template <typename T> constexpr T unit_to_percent(T value) { return value * 100.0f; }
|
||||
|
||||
constexpr std::array<float, 16> uv_vertices_get(glm::vec2 uvMin, glm::vec2 uvMax)
|
||||
{
|
||||
return {0.0f, 0.0f, uvMin.x, uvMin.y, 1.0f, 0.0f, uvMax.x, uvMin.y,
|
||||
1.0f, 1.0f, uvMax.x, uvMax.y, 0.0f, 1.0f, uvMin.x, uvMax.y};
|
||||
}
|
||||
}
|
||||
13
src/util/unordered_map_.h
Normal file
13
src/util/unordered_map_.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace game::util::unordered_map
|
||||
{
|
||||
template <typename T0, typename T1> T1* find(std::unordered_map<T0, T1>& map, T0 key)
|
||||
{
|
||||
auto it = map.find(key);
|
||||
if (it != map.end()) return &it->second;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
17
src/util/vector_.h
Normal file
17
src/util/vector_.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace game::util::vector
|
||||
{
|
||||
template <typename T> bool in_bounds(std::vector<T>& vector, int index)
|
||||
{
|
||||
return (index >= 0 && index < vector.size());
|
||||
}
|
||||
|
||||
template <typename T> T* find(std::vector<T>& vector, int index)
|
||||
{
|
||||
if (!in_bounds(vector, index)) return nullptr;
|
||||
return &vector[index];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user