Initial commit
This commit is contained in:
commit
f299d9e765
|
|
@ -0,0 +1,141 @@
|
||||||
|
# ============================================
|
||||||
|
# Easy2D 项目 Git 忽略配置
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 构建输出目录
|
||||||
|
# --------------------------------------------
|
||||||
|
/build/
|
||||||
|
/bin/
|
||||||
|
/obj/
|
||||||
|
/out/
|
||||||
|
/Debug/
|
||||||
|
/Release/
|
||||||
|
/x64/
|
||||||
|
/x86/
|
||||||
|
/.build/
|
||||||
|
/.trae/
|
||||||
|
# --------------------------------------------
|
||||||
|
# xmake 构建系统
|
||||||
|
# --------------------------------------------
|
||||||
|
/.xmake/
|
||||||
|
/.xmake.conf
|
||||||
|
/xmake.config
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# IDE 和编辑器配置
|
||||||
|
# --------------------------------------------
|
||||||
|
# Visual Studio
|
||||||
|
.vs/
|
||||||
|
*.sln
|
||||||
|
*.vcxproj
|
||||||
|
*.vcxproj.filters
|
||||||
|
*.vcxproj.user
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.VC.db
|
||||||
|
*.VC.opendb
|
||||||
|
ipch/
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# CLion / IntelliJ
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 编译缓存和索引
|
||||||
|
# --------------------------------------------
|
||||||
|
/.cache/
|
||||||
|
/.clangd/
|
||||||
|
/compile_commands.json
|
||||||
|
.clang-format
|
||||||
|
.clang-tidy
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 依赖和包管理
|
||||||
|
# --------------------------------------------
|
||||||
|
/packages/
|
||||||
|
/deps/
|
||||||
|
/third_party/
|
||||||
|
/vendor/
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 生成的文件
|
||||||
|
# --------------------------------------------
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.pdb
|
||||||
|
*.ilk
|
||||||
|
*.exp
|
||||||
|
*.manifest
|
||||||
|
*.res
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 日志和临时文件
|
||||||
|
# --------------------------------------------
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 测试和覆盖率
|
||||||
|
# --------------------------------------------
|
||||||
|
/test-results/
|
||||||
|
/coverage/
|
||||||
|
*.gcov
|
||||||
|
*.gcda
|
||||||
|
*.gcno
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 文档生成
|
||||||
|
# --------------------------------------------
|
||||||
|
/docs/html/
|
||||||
|
/docs/latex/
|
||||||
|
/doxygen/
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 平台特定
|
||||||
|
# --------------------------------------------
|
||||||
|
# Windows
|
||||||
|
*.bat
|
||||||
|
*.cmd
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# --------------------------------------------
|
||||||
|
# 其他
|
||||||
|
# --------------------------------------------
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
*.7z
|
||||||
|
|
@ -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_ */
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
class Node;
|
||||||
|
|
||||||
|
enum class ActionState { Idle, Running, Paused, Completed };
|
||||||
|
|
||||||
|
class Action {
|
||||||
|
public:
|
||||||
|
using ProgressCallback = std::function<void(float)>;
|
||||||
|
using CompletionCallback = std::function<void()>;
|
||||||
|
|
||||||
|
Action();
|
||||||
|
virtual ~Action() = default;
|
||||||
|
|
||||||
|
Action(const Action &) = delete;
|
||||||
|
Action &operator=(const Action &) = delete;
|
||||||
|
Action(Action &&) = default;
|
||||||
|
Action &operator=(Action &&) = default;
|
||||||
|
|
||||||
|
virtual void start(Node *target);
|
||||||
|
virtual void stop();
|
||||||
|
virtual void update(float dt);
|
||||||
|
virtual void step(float dt);
|
||||||
|
|
||||||
|
virtual bool isDone() const = 0;
|
||||||
|
virtual Action *clone() const = 0;
|
||||||
|
virtual Action *reverse() const = 0;
|
||||||
|
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void restart();
|
||||||
|
|
||||||
|
ActionState getState() const { return state_; }
|
||||||
|
float getElapsed() const { return elapsed_; }
|
||||||
|
float getDuration() const { return duration_; }
|
||||||
|
Node *getTarget() const { return target_; }
|
||||||
|
Node *getOriginalTarget() const { return originalTarget_; }
|
||||||
|
|
||||||
|
void setDuration(float duration) { duration_ = duration; }
|
||||||
|
void setSpeed(float speed) { speed_ = speed; }
|
||||||
|
float getSpeed() const { return speed_; }
|
||||||
|
|
||||||
|
void setProgressCallback(ProgressCallback callback) {
|
||||||
|
progressCallback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
void setCompletionCallback(CompletionCallback callback) {
|
||||||
|
completionCallback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTag(int tag) { tag_ = tag; }
|
||||||
|
int getTag() const { return tag_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void onStart() {}
|
||||||
|
virtual void onUpdate(float progress) = 0;
|
||||||
|
virtual void onComplete() {}
|
||||||
|
|
||||||
|
void setDone() { state_ = ActionState::Completed; }
|
||||||
|
|
||||||
|
Node *target_ = nullptr;
|
||||||
|
Node *originalTarget_ = nullptr;
|
||||||
|
ActionState state_ = ActionState::Idle;
|
||||||
|
float elapsed_ = 0.0f;
|
||||||
|
float duration_ = 0.0f;
|
||||||
|
float speed_ = 1.0f;
|
||||||
|
int tag_ = -1;
|
||||||
|
ProgressCallback progressCallback_;
|
||||||
|
CompletionCallback completionCallback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ActionPtr = std::unique_ptr<Action>;
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,278 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "extra2d/action/action.h"
|
||||||
|
#include "extra2d/core/math_types.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
// Interval Action Base
|
||||||
|
class IntervalAction : public Action {
|
||||||
|
public:
|
||||||
|
explicit IntervalAction(float duration);
|
||||||
|
bool isDone() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Instant Action Base
|
||||||
|
class InstantAction : public Action {
|
||||||
|
public:
|
||||||
|
InstantAction();
|
||||||
|
bool isDone() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Move Actions
|
||||||
|
class MoveBy : public IntervalAction {
|
||||||
|
public:
|
||||||
|
MoveBy(float duration, const Vec2 &delta);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 delta_;
|
||||||
|
Vec2 startPosition_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MoveTo : public IntervalAction {
|
||||||
|
public:
|
||||||
|
MoveTo(float duration, const Vec2 &position);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 endPosition_;
|
||||||
|
Vec2 startPosition_;
|
||||||
|
Vec2 delta_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scale Actions
|
||||||
|
class ScaleBy : public IntervalAction {
|
||||||
|
public:
|
||||||
|
ScaleBy(float duration, float scale);
|
||||||
|
ScaleBy(float duration, float scaleX, float scaleY);
|
||||||
|
ScaleBy(float duration, const Vec2 &scale);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 deltaScale_;
|
||||||
|
Vec2 startScale_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScaleTo : public IntervalAction {
|
||||||
|
public:
|
||||||
|
ScaleTo(float duration, float scale);
|
||||||
|
ScaleTo(float duration, float scaleX, float scaleY);
|
||||||
|
ScaleTo(float duration, const Vec2 &scale);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 endScale_;
|
||||||
|
Vec2 startScale_;
|
||||||
|
Vec2 delta_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rotate Actions
|
||||||
|
class RotateBy : public IntervalAction {
|
||||||
|
public:
|
||||||
|
RotateBy(float duration, float deltaAngle);
|
||||||
|
RotateBy(float duration, float deltaAngleX, float deltaAngleY);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float deltaAngle_ = 0.0f;
|
||||||
|
float startAngle_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RotateTo : public IntervalAction {
|
||||||
|
public:
|
||||||
|
RotateTo(float duration, float angle);
|
||||||
|
RotateTo(float duration, float angleX, float angleY);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float endAngle_ = 0.0f;
|
||||||
|
float startAngle_ = 0.0f;
|
||||||
|
float deltaAngle_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fade Actions
|
||||||
|
class FadeIn : public IntervalAction {
|
||||||
|
public:
|
||||||
|
explicit FadeIn(float duration);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float startOpacity_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FadeOut : public IntervalAction {
|
||||||
|
public:
|
||||||
|
explicit FadeOut(float duration);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float startOpacity_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FadeTo : public IntervalAction {
|
||||||
|
public:
|
||||||
|
FadeTo(float duration, float opacity);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float endOpacity_ = 0.0f;
|
||||||
|
float startOpacity_ = 0.0f;
|
||||||
|
float deltaOpacity_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Composite Actions
|
||||||
|
class Sequence : public IntervalAction {
|
||||||
|
public:
|
||||||
|
Sequence(const std::vector<Action *> &actions);
|
||||||
|
~Sequence();
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Action *> actions_;
|
||||||
|
int currentIndex_ = 0;
|
||||||
|
float split_ = 0.0f;
|
||||||
|
float last_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Spawn : public IntervalAction {
|
||||||
|
public:
|
||||||
|
Spawn(const std::vector<Action *> &actions);
|
||||||
|
~Spawn();
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Action *> actions_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loop Action
|
||||||
|
class Loop : public Action {
|
||||||
|
public:
|
||||||
|
Loop(Action *action, int times = -1);
|
||||||
|
~Loop();
|
||||||
|
|
||||||
|
bool isDone() const override;
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Action *action_ = nullptr;
|
||||||
|
int times_ = 1;
|
||||||
|
int currentTimes_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delay Action
|
||||||
|
class Delay : public IntervalAction {
|
||||||
|
public:
|
||||||
|
explicit Delay(float duration);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CallFunc Action
|
||||||
|
class CallFunc : public InstantAction {
|
||||||
|
public:
|
||||||
|
using Callback = std::function<void()>;
|
||||||
|
|
||||||
|
explicit CallFunc(Callback callback);
|
||||||
|
|
||||||
|
Action *clone() const override;
|
||||||
|
Action *reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Callback callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
inline Sequence *sequence(const std::vector<Action *> &actions) {
|
||||||
|
return new Sequence(actions);
|
||||||
|
}
|
||||||
|
inline Spawn *spawn(const std::vector<Action *> &actions) {
|
||||||
|
return new Spawn(actions);
|
||||||
|
}
|
||||||
|
inline Loop *loop(Action *action, int times = -1) {
|
||||||
|
return new Loop(action, times);
|
||||||
|
}
|
||||||
|
inline Delay *delay(float duration) { return new Delay(duration); }
|
||||||
|
inline CallFunc *callFunc(CallFunc::Callback callback) {
|
||||||
|
return new CallFunc(std::move(callback));
|
||||||
|
}
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
// Easing function type
|
||||||
|
using EaseFunction = float (*)(float);
|
||||||
|
|
||||||
|
// Linear (no easing)
|
||||||
|
float easeLinear(float t);
|
||||||
|
|
||||||
|
// Quadratic
|
||||||
|
float easeInQuad(float t);
|
||||||
|
float easeOutQuad(float t);
|
||||||
|
float easeInOutQuad(float t);
|
||||||
|
|
||||||
|
// Cubic
|
||||||
|
float easeInCubic(float t);
|
||||||
|
float easeOutCubic(float t);
|
||||||
|
float easeInOutCubic(float t);
|
||||||
|
|
||||||
|
// Quartic
|
||||||
|
float easeInQuart(float t);
|
||||||
|
float easeOutQuart(float t);
|
||||||
|
float easeInOutQuart(float t);
|
||||||
|
|
||||||
|
// Quintic
|
||||||
|
float easeInQuint(float t);
|
||||||
|
float easeOutQuint(float t);
|
||||||
|
float easeInOutQuint(float t);
|
||||||
|
|
||||||
|
// Sine
|
||||||
|
float easeInSine(float t);
|
||||||
|
float easeOutSine(float t);
|
||||||
|
float easeInOutSine(float t);
|
||||||
|
|
||||||
|
// Exponential
|
||||||
|
float easeInExpo(float t);
|
||||||
|
float easeOutExpo(float t);
|
||||||
|
float easeInOutExpo(float t);
|
||||||
|
|
||||||
|
// Circular
|
||||||
|
float easeInCirc(float t);
|
||||||
|
float easeOutCirc(float t);
|
||||||
|
float easeInOutCirc(float t);
|
||||||
|
|
||||||
|
// Back
|
||||||
|
float easeInBack(float t);
|
||||||
|
float easeOutBack(float t);
|
||||||
|
float easeInOutBack(float t);
|
||||||
|
|
||||||
|
// Elastic
|
||||||
|
float easeInElastic(float t);
|
||||||
|
float easeOutElastic(float t);
|
||||||
|
float easeInOutElastic(float t);
|
||||||
|
|
||||||
|
// Bounce
|
||||||
|
float easeInBounce(float t);
|
||||||
|
float easeOutBounce(float t);
|
||||||
|
float easeInOutBounce(float t);
|
||||||
|
|
||||||
|
// Ease Action wrapper
|
||||||
|
class Action;
|
||||||
|
|
||||||
|
class EaseAction {
|
||||||
|
public:
|
||||||
|
static Action *create(Action *action, EaseFunction easeFunc);
|
||||||
|
};
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ALS 图层信息
|
||||||
|
// ============================================================================
|
||||||
|
struct AlsLayerInfo {
|
||||||
|
std::string aniPath; // 子动画的 ANI 文件路径
|
||||||
|
int zOrder = 0; // 层级顺序
|
||||||
|
Vec2 offset; // 层偏移
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ALS 解析结果
|
||||||
|
// ============================================================================
|
||||||
|
struct AlsParseResult {
|
||||||
|
bool success = false;
|
||||||
|
std::string errorMessage;
|
||||||
|
std::vector<AlsLayerInfo> layers;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AlsParser - ALS 复合动画文件解析器
|
||||||
|
// 解析 .als 文件获取多层动画的图层信息
|
||||||
|
// ============================================================================
|
||||||
|
class AlsParser {
|
||||||
|
public:
|
||||||
|
AlsParser() = default;
|
||||||
|
|
||||||
|
/// 从文件解析
|
||||||
|
AlsParseResult parse(const std::string &filePath);
|
||||||
|
|
||||||
|
/// 从内存内容解析
|
||||||
|
AlsParseResult parseFromMemory(const std::string &content,
|
||||||
|
const std::string &basePath = "");
|
||||||
|
|
||||||
|
/// 设置基础路径
|
||||||
|
void setBasePath(const std::string &basePath) { basePath_ = basePath; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string basePath_;
|
||||||
|
|
||||||
|
std::string resolvePath(const std::string &relativePath) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <extra2d/animation/ani_parser.h>
|
||||||
|
#include <extra2d/animation/animation_cache.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DNF ANI 二进制格式中的节点类型枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class AniNodeType : uint16_t {
|
||||||
|
Loop = 0,
|
||||||
|
Shadow = 1,
|
||||||
|
Coord = 3,
|
||||||
|
ImageRate = 7,
|
||||||
|
ImageRotate = 8,
|
||||||
|
RGBA = 9,
|
||||||
|
Interpolation = 10,
|
||||||
|
GraphicEffect = 11,
|
||||||
|
Delay = 12,
|
||||||
|
DamageType = 13,
|
||||||
|
DamageBox = 14,
|
||||||
|
AttackBox = 15,
|
||||||
|
PlaySound = 16,
|
||||||
|
Preload = 17,
|
||||||
|
Spectrum = 18,
|
||||||
|
SetFlag = 23,
|
||||||
|
FlipType = 24,
|
||||||
|
LoopStart = 25,
|
||||||
|
LoopEnd = 26,
|
||||||
|
Clip = 27,
|
||||||
|
Operation = 28,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AniBinaryParser - ANI 二进制格式解析器
|
||||||
|
// 参考 DNF-Porting 的 PvfAnimation 实现
|
||||||
|
// ============================================================================
|
||||||
|
class AniBinaryParser {
|
||||||
|
public:
|
||||||
|
AniBinaryParser() = default;
|
||||||
|
|
||||||
|
/// 从二进制数据解析
|
||||||
|
AniParseResult parse(const uint8_t *data, size_t length);
|
||||||
|
|
||||||
|
/// 从文件解析
|
||||||
|
AniParseResult parseFromFile(const std::string &filePath);
|
||||||
|
|
||||||
|
/// 设置路径替换回调
|
||||||
|
void setPathResolver(PathResolveCallback callback) {
|
||||||
|
pathResolver_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置基础路径
|
||||||
|
void setBasePath(const std::string &basePath) { basePath_ = basePath; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
PathResolveCallback pathResolver_;
|
||||||
|
std::string basePath_;
|
||||||
|
|
||||||
|
std::string resolvePath(const std::string &relativePath) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/animation/animation_cache.h>
|
||||||
|
#include <extra2d/animation/animation_clip.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ANI 文件解析结果
|
||||||
|
// ============================================================================
|
||||||
|
struct AniParseResult {
|
||||||
|
bool success = false;
|
||||||
|
std::string errorMessage;
|
||||||
|
Ptr<AnimationClip> clip;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AniParser - ANI 脚本文件解析器
|
||||||
|
// 将原始 ANI 文件格式解析为 AnimationClip 数据
|
||||||
|
// ============================================================================
|
||||||
|
class AniParser {
|
||||||
|
public:
|
||||||
|
AniParser() = default;
|
||||||
|
|
||||||
|
/// 从文件解析
|
||||||
|
AniParseResult parse(const std::string &filePath);
|
||||||
|
|
||||||
|
/// 从内存内容解析
|
||||||
|
AniParseResult parseFromMemory(const std::string &content,
|
||||||
|
const std::string &basePath = "");
|
||||||
|
|
||||||
|
/// 设置路径替换回调(对应原始 AdditionalOptions)
|
||||||
|
void setPathResolver(PathResolveCallback callback) {
|
||||||
|
pathResolver_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置基础路径(用于解析相对路径)
|
||||||
|
void setBasePath(const std::string &basePath) { basePath_ = basePath; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
PathResolveCallback pathResolver_;
|
||||||
|
std::string basePath_;
|
||||||
|
|
||||||
|
std::string resolvePath(const std::string &relativePath) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <extra2d/animation/animation_cache.h>
|
||||||
|
#include <extra2d/animation/animation_controller.h>
|
||||||
|
#include <extra2d/scene/sprite.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimatedSprite - 动画精灵节点
|
||||||
|
// 将 AnimationController 与 Sprite 渲染桥接,接入场景图
|
||||||
|
// ============================================================================
|
||||||
|
class AnimatedSprite : public Sprite {
|
||||||
|
public:
|
||||||
|
AnimatedSprite();
|
||||||
|
~AnimatedSprite() override = default;
|
||||||
|
|
||||||
|
// ------ 静态工厂 ------
|
||||||
|
static Ptr<AnimatedSprite> create();
|
||||||
|
static Ptr<AnimatedSprite> create(Ptr<AnimationClip> clip);
|
||||||
|
static Ptr<AnimatedSprite> create(const std::string &aniFilePath);
|
||||||
|
|
||||||
|
// ------ 动画绑定 ------
|
||||||
|
void setAnimationClip(Ptr<AnimationClip> clip);
|
||||||
|
void loadAnimation(const std::string &aniFilePath);
|
||||||
|
Ptr<AnimationClip> getAnimationClip() const;
|
||||||
|
|
||||||
|
// ------ 动画字典 ------
|
||||||
|
void addAnimation(const std::string &name, Ptr<AnimationClip> clip);
|
||||||
|
void play(const std::string &name, bool loop = true);
|
||||||
|
bool hasAnimation(const std::string &name) const;
|
||||||
|
Ptr<AnimationClip> getAnimation(const std::string &name) const;
|
||||||
|
const std::string &getCurrentAnimationName() const;
|
||||||
|
|
||||||
|
// ------ 播放控制(委托 controller_)------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
bool isPlaying() const;
|
||||||
|
bool isPaused() const;
|
||||||
|
bool isStopped() const;
|
||||||
|
|
||||||
|
// ------ 属性控制 ------
|
||||||
|
void setLooping(bool loop);
|
||||||
|
bool isLooping() const;
|
||||||
|
void setPlaybackSpeed(float speed);
|
||||||
|
float getPlaybackSpeed() const;
|
||||||
|
|
||||||
|
// ------ 帧控制 ------
|
||||||
|
void setFrameIndex(size_t index);
|
||||||
|
size_t getCurrentFrameIndex() const;
|
||||||
|
size_t getTotalFrames() const;
|
||||||
|
void nextFrame();
|
||||||
|
void prevFrame();
|
||||||
|
|
||||||
|
// ------ 帧范围限制 ------
|
||||||
|
/// 设置帧播放范围(用于精灵图动画,限制在指定范围内循环)
|
||||||
|
/// @param start 起始帧索引(包含)
|
||||||
|
/// @param end 结束帧索引(包含),-1表示不限制
|
||||||
|
void setFrameRange(int start, int end = -1);
|
||||||
|
|
||||||
|
/// 获取当前帧范围
|
||||||
|
/// @return pair<起始帧, 结束帧>,结束帧为-1表示不限制
|
||||||
|
std::pair<int, int> getFrameRange() const;
|
||||||
|
|
||||||
|
/// 清除帧范围限制(恢复播放所有帧)
|
||||||
|
void clearFrameRange();
|
||||||
|
|
||||||
|
/// 检查是否设置了帧范围限制
|
||||||
|
bool hasFrameRange() const;
|
||||||
|
|
||||||
|
// ------ 回调 ------
|
||||||
|
void setCompletionCallback(AnimationController::CompletionCallback cb);
|
||||||
|
void setKeyframeCallback(AnimationController::KeyframeCallback cb);
|
||||||
|
void setSoundTriggerCallback(AnimationController::SoundTriggerCallback cb);
|
||||||
|
|
||||||
|
// ------ 碰撞盒访问(当前帧)------
|
||||||
|
const std::vector<std::array<int32_t, 6>> &getCurrentDamageBoxes() const;
|
||||||
|
const std::vector<std::array<int32_t, 6>> &getCurrentAttackBoxes() const;
|
||||||
|
|
||||||
|
// ------ 帧变换控制 ------
|
||||||
|
/// 设置是否由动画帧数据覆盖节点的 position/scale/rotation
|
||||||
|
/// ANI 动画需要开启(默认),精灵图动画应关闭
|
||||||
|
void setApplyFrameTransform(bool apply) { applyFrameTransform_ = apply; }
|
||||||
|
bool isApplyFrameTransform() const { return applyFrameTransform_; }
|
||||||
|
|
||||||
|
// ------ 自动播放 ------
|
||||||
|
void setAutoPlay(bool autoPlay) { autoPlay_ = autoPlay; }
|
||||||
|
bool isAutoPlay() const { return autoPlay_; }
|
||||||
|
|
||||||
|
// ------ 直接控制器访问 ------
|
||||||
|
AnimationController &getController() { return controller_; }
|
||||||
|
const AnimationController &getController() const { return controller_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onUpdate(float dt) override;
|
||||||
|
void onEnter() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
AnimationController controller_;
|
||||||
|
bool autoPlay_ = false;
|
||||||
|
bool applyFrameTransform_ = true;
|
||||||
|
|
||||||
|
// 动画字典
|
||||||
|
std::unordered_map<std::string, Ptr<AnimationClip>> animations_;
|
||||||
|
std::string currentAnimationName_;
|
||||||
|
static const std::string emptyString_;
|
||||||
|
|
||||||
|
// 帧范围限制(用于精灵图动画)
|
||||||
|
int frameRangeStart_ = 0; // 起始帧索引
|
||||||
|
int frameRangeEnd_ = -1; // 结束帧索引,-1表示不限制
|
||||||
|
|
||||||
|
// 空碰撞盒列表(用于无帧时返回引用)
|
||||||
|
static const std::vector<std::array<int32_t, 6>> emptyBoxes_;
|
||||||
|
|
||||||
|
void applyFrame(const AnimationFrame &frame);
|
||||||
|
void onFrameChanged(size_t oldIdx, size_t newIdx,
|
||||||
|
const AnimationFrame &frame);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/animation/animation_clip.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 路径替换回调(对应原始 AdditionalOptions)
|
||||||
|
using PathResolveCallback = std::function<std::string(const std::string &)>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationCache - 动画片段全局缓存(借鉴 Cocos AnimationCache)
|
||||||
|
// 同一 ANI 文件只解析一次,后续直接复用数据
|
||||||
|
// ============================================================================
|
||||||
|
class AnimationCache {
|
||||||
|
public:
|
||||||
|
static AnimationCache &getInstance() {
|
||||||
|
static AnimationCache instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 加载与获取 ------
|
||||||
|
|
||||||
|
/// 从文件加载(自动缓存),已缓存则直接返回
|
||||||
|
/// 注意:实际的 ANI 解析逻辑在 AniParser 中实现
|
||||||
|
/// 此方法在 animation_cache.cpp 中实现,依赖 AniParser
|
||||||
|
Ptr<AnimationClip> loadClip(const std::string &aniFilePath);
|
||||||
|
|
||||||
|
/// 从缓存获取(不触发加载)
|
||||||
|
Ptr<AnimationClip> getClip(const std::string &name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = clips_.find(name);
|
||||||
|
if (it != clips_.end())
|
||||||
|
return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 手动添加到缓存
|
||||||
|
void addClip(Ptr<AnimationClip> clip, const std::string &name) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
clips_[name] = std::move(clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 缓存管理 ------
|
||||||
|
|
||||||
|
bool has(const std::string &name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return clips_.find(name) != clips_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeClip(const std::string &name) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
clips_.erase(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 移除未被外部引用的动画片段
|
||||||
|
void removeUnusedClips() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
for (auto it = clips_.begin(); it != clips_.end();) {
|
||||||
|
if (it->second.use_count() == 1) {
|
||||||
|
it = clips_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
clips_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t count() const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return clips_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 路径配置 ------
|
||||||
|
void setPathResolver(PathResolveCallback resolver) {
|
||||||
|
pathResolver_ = std::move(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathResolveCallback getPathResolver() const { return pathResolver_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
AnimationCache() = default;
|
||||||
|
~AnimationCache() = default;
|
||||||
|
AnimationCache(const AnimationCache &) = delete;
|
||||||
|
AnimationCache &operator=(const AnimationCache &) = delete;
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
std::unordered_map<std::string, Ptr<AnimationClip>> clips_;
|
||||||
|
PathResolveCallback pathResolver_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 便捷宏
|
||||||
|
#define E2D_ANIMATION_CACHE() ::extra2d::AnimationCache::getInstance()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <extra2d/animation/animation_frame.h>
|
||||||
|
#include <extra2d/animation/sprite_frame_cache.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationClip - 动画片段(纯数据,可复用)
|
||||||
|
// 借鉴 Cocos:一份 AnimationClip 可被多个 AnimationNode 同时使用
|
||||||
|
// ============================================================================
|
||||||
|
class AnimationClip {
|
||||||
|
public:
|
||||||
|
AnimationClip() = default;
|
||||||
|
|
||||||
|
explicit AnimationClip(const std::string &name) : name_(name) {}
|
||||||
|
|
||||||
|
// ------ 帧管理 ------
|
||||||
|
void addFrame(const AnimationFrame &frame) { frames_.push_back(frame); }
|
||||||
|
|
||||||
|
void addFrame(AnimationFrame &&frame) { frames_.push_back(std::move(frame)); }
|
||||||
|
|
||||||
|
void insertFrame(size_t index, const AnimationFrame &frame) {
|
||||||
|
assert(index <= frames_.size());
|
||||||
|
frames_.insert(frames_.begin() + static_cast<ptrdiff_t>(index), frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeFrame(size_t index) {
|
||||||
|
assert(index < frames_.size());
|
||||||
|
frames_.erase(frames_.begin() + static_cast<ptrdiff_t>(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearFrames() { frames_.clear(); }
|
||||||
|
|
||||||
|
const AnimationFrame &getFrame(size_t index) const {
|
||||||
|
assert(index < frames_.size());
|
||||||
|
return frames_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationFrame &getFrame(size_t index) {
|
||||||
|
assert(index < frames_.size());
|
||||||
|
return frames_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getFrameCount() const { return frames_.size(); }
|
||||||
|
bool empty() const { return frames_.empty(); }
|
||||||
|
|
||||||
|
// ------ 全局属性(对应原始 AnimationFlag)------
|
||||||
|
FramePropertySet &globalProperties() { return globalProperties_; }
|
||||||
|
const FramePropertySet &globalProperties() const { return globalProperties_; }
|
||||||
|
|
||||||
|
bool isLooping() const {
|
||||||
|
return globalProperties_.getOr<bool>(FramePropertyKey::Loop, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLooping(bool loop) { globalProperties_.withLoop(loop); }
|
||||||
|
|
||||||
|
// ------ 时间信息 ------
|
||||||
|
float getTotalDuration() const {
|
||||||
|
float total = 0.0f;
|
||||||
|
for (const auto &frame : frames_) {
|
||||||
|
total += frame.delay;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 预计算最大帧尺寸 ------
|
||||||
|
Size getMaxFrameSize() const {
|
||||||
|
Size maxSize;
|
||||||
|
for (const auto &frame : frames_) {
|
||||||
|
if (frame.spriteFrame && frame.spriteFrame->isValid()) {
|
||||||
|
const auto &rect = frame.spriteFrame->getRect();
|
||||||
|
if (rect.size.width > maxSize.width)
|
||||||
|
maxSize.width = rect.size.width;
|
||||||
|
if (rect.size.height > maxSize.height)
|
||||||
|
maxSize.height = rect.size.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 元数据 ------
|
||||||
|
void setName(const std::string &name) { name_ = name; }
|
||||||
|
const std::string &getName() const { return name_; }
|
||||||
|
|
||||||
|
void setSourcePath(const std::string &path) { sourcePath_ = path; }
|
||||||
|
const std::string &getSourcePath() const { return sourcePath_; }
|
||||||
|
|
||||||
|
// ------ 静态工厂 ------
|
||||||
|
static Ptr<AnimationClip> create(const std::string &name = "") {
|
||||||
|
return makePtr<AnimationClip>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从精灵图网格创建(所有帧按顺序)
|
||||||
|
static Ptr<AnimationClip> createFromGrid(Ptr<Texture> texture, int frameWidth,
|
||||||
|
int frameHeight,
|
||||||
|
float frameDurationMs = 100.0f,
|
||||||
|
int frameCount = -1, int spacing = 0,
|
||||||
|
int margin = 0) {
|
||||||
|
if (!texture)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
int texW = texture->getWidth();
|
||||||
|
int texH = texture->getHeight();
|
||||||
|
int usableW = texW - 2 * margin;
|
||||||
|
int usableH = texH - 2 * margin;
|
||||||
|
int cols = (usableW + spacing) / (frameWidth + spacing);
|
||||||
|
int rows = (usableH + spacing) / (frameHeight + spacing);
|
||||||
|
int total = (frameCount > 0) ? frameCount : cols * rows;
|
||||||
|
|
||||||
|
auto clip = makePtr<AnimationClip>();
|
||||||
|
for (int i = 0; i < total; ++i) {
|
||||||
|
int col = i % cols;
|
||||||
|
int row = i / cols;
|
||||||
|
if (row >= rows)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 翻转行顺序:精灵图第0行在顶部,但OpenGL纹理V坐标从底部开始
|
||||||
|
// 所以将行索引翻转,使第0行对应纹理底部(V=1.0),第3行对应纹理顶部(V=0.0)
|
||||||
|
int flippedRow = (rows - 1) - row;
|
||||||
|
|
||||||
|
Rect rect(
|
||||||
|
static_cast<float>(margin + col * (frameWidth + spacing)),
|
||||||
|
static_cast<float>(margin + flippedRow * (frameHeight + spacing)),
|
||||||
|
static_cast<float>(frameWidth), static_cast<float>(frameHeight));
|
||||||
|
|
||||||
|
auto sf = SpriteFrame::create(texture, rect);
|
||||||
|
AnimationFrame frame;
|
||||||
|
frame.spriteFrame = std::move(sf);
|
||||||
|
frame.delay = frameDurationMs;
|
||||||
|
clip->addFrame(std::move(frame));
|
||||||
|
}
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从精灵图网格创建(指定帧索引列表)
|
||||||
|
static Ptr<AnimationClip>
|
||||||
|
createFromGridIndices(Ptr<Texture> texture, int frameWidth, int frameHeight,
|
||||||
|
const std::vector<int> &frameIndices,
|
||||||
|
float frameDurationMs = 100.0f, int spacing = 0,
|
||||||
|
int margin = 0) {
|
||||||
|
if (!texture)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
int texW = texture->getWidth();
|
||||||
|
int texH = texture->getHeight();
|
||||||
|
int usableW = texW - 2 * margin;
|
||||||
|
int usableH = texH - 2 * margin;
|
||||||
|
int cols = (usableW + spacing) / (frameWidth + spacing);
|
||||||
|
int rows = (usableH + spacing) / (frameHeight + spacing);
|
||||||
|
|
||||||
|
auto clip = makePtr<AnimationClip>();
|
||||||
|
for (int idx : frameIndices) {
|
||||||
|
int col = idx % cols;
|
||||||
|
int row = idx / cols;
|
||||||
|
|
||||||
|
// 翻转行顺序:精灵图第0行在顶部,但OpenGL纹理V坐标从底部开始
|
||||||
|
int flippedRow = (rows - 1) - row;
|
||||||
|
|
||||||
|
Rect rect(
|
||||||
|
static_cast<float>(margin + col * (frameWidth + spacing)),
|
||||||
|
static_cast<float>(margin + flippedRow * (frameHeight + spacing)),
|
||||||
|
static_cast<float>(frameWidth), static_cast<float>(frameHeight));
|
||||||
|
|
||||||
|
auto sf = SpriteFrame::create(texture, rect);
|
||||||
|
AnimationFrame frame;
|
||||||
|
frame.spriteFrame = std::move(sf);
|
||||||
|
frame.delay = frameDurationMs;
|
||||||
|
clip->addFrame(std::move(frame));
|
||||||
|
}
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
std::string sourcePath_;
|
||||||
|
std::vector<AnimationFrame> frames_;
|
||||||
|
FramePropertySet globalProperties_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/animation/animation_clip.h>
|
||||||
|
#include <extra2d/animation/interpolation_engine.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 动画播放状态
|
||||||
|
// ============================================================================
|
||||||
|
enum class AnimPlayState : uint8 { Stopped, Playing, Paused };
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationController - 动画播放控制器
|
||||||
|
// 借鉴 Cocos Creator 的 AnimationState:纯播放逻辑,不持有渲染资源
|
||||||
|
// ============================================================================
|
||||||
|
class AnimationController {
|
||||||
|
public:
|
||||||
|
// 回调类型定义
|
||||||
|
using FrameChangeCallback = std::function<void(size_t oldIdx, size_t newIdx,
|
||||||
|
const AnimationFrame &frame)>;
|
||||||
|
using KeyframeCallback = std::function<void(int flagIndex)>;
|
||||||
|
using SoundTriggerCallback = std::function<void(const std::string &path)>;
|
||||||
|
using CompletionCallback = std::function<void()>;
|
||||||
|
|
||||||
|
AnimationController() = default;
|
||||||
|
|
||||||
|
// ------ 绑定动画数据 ------
|
||||||
|
void setClip(Ptr<AnimationClip> clip);
|
||||||
|
Ptr<AnimationClip> getClip() const { return clip_; }
|
||||||
|
|
||||||
|
// ------ 播放控制 ------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
// ------ 帧控制 ------
|
||||||
|
void setFrameIndex(size_t index);
|
||||||
|
void nextFrame();
|
||||||
|
void prevFrame();
|
||||||
|
|
||||||
|
// ------ 核心更新(每帧调用)------
|
||||||
|
void update(float dt);
|
||||||
|
|
||||||
|
// ------ 状态查询 ------
|
||||||
|
AnimPlayState getState() const { return state_; }
|
||||||
|
bool isPlaying() const { return state_ == AnimPlayState::Playing; }
|
||||||
|
bool isPaused() const { return state_ == AnimPlayState::Paused; }
|
||||||
|
bool isStopped() const { return state_ == AnimPlayState::Stopped; }
|
||||||
|
|
||||||
|
size_t getCurrentFrameIndex() const { return currentFrameIndex_; }
|
||||||
|
size_t getTotalFrames() const;
|
||||||
|
const AnimationFrame &getCurrentFrame() const;
|
||||||
|
|
||||||
|
float getPlaybackSpeed() const { return playbackSpeed_; }
|
||||||
|
void setPlaybackSpeed(float speed) { playbackSpeed_ = speed; }
|
||||||
|
|
||||||
|
bool isLooping() const;
|
||||||
|
void setLooping(bool loop);
|
||||||
|
|
||||||
|
// ------ 插值状态 ------
|
||||||
|
float getInterpolationFactor() const { return interpolationFactor_; }
|
||||||
|
bool isInterpolating() const { return interpolating_; }
|
||||||
|
|
||||||
|
// ------ 回调注册 ------
|
||||||
|
void setFrameChangeCallback(FrameChangeCallback cb) {
|
||||||
|
onFrameChange_ = std::move(cb);
|
||||||
|
}
|
||||||
|
void setKeyframeCallback(KeyframeCallback cb) { onKeyframe_ = std::move(cb); }
|
||||||
|
void setSoundTriggerCallback(SoundTriggerCallback cb) {
|
||||||
|
onSoundTrigger_ = std::move(cb);
|
||||||
|
}
|
||||||
|
void setCompletionCallback(CompletionCallback cb) {
|
||||||
|
onComplete_ = std::move(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<AnimationClip> clip_;
|
||||||
|
AnimPlayState state_ = AnimPlayState::Stopped;
|
||||||
|
|
||||||
|
size_t currentFrameIndex_ = 0;
|
||||||
|
float accumulatedTime_ = 0.0f; // 当前帧已累积时间 (ms)
|
||||||
|
float playbackSpeed_ = 1.0f;
|
||||||
|
bool loopOverride_ = false; // 外部循环覆盖值
|
||||||
|
bool hasLoopOverride_ = false; // 是否使用外部循环覆盖
|
||||||
|
|
||||||
|
// 插值状态
|
||||||
|
bool interpolating_ = false;
|
||||||
|
float interpolationFactor_ = 0.0f;
|
||||||
|
|
||||||
|
// 回调
|
||||||
|
FrameChangeCallback onFrameChange_;
|
||||||
|
KeyframeCallback onKeyframe_;
|
||||||
|
SoundTriggerCallback onSoundTrigger_;
|
||||||
|
CompletionCallback onComplete_;
|
||||||
|
|
||||||
|
// 内部方法
|
||||||
|
void advanceFrame(size_t newIndex);
|
||||||
|
void processFrameProperties(const AnimationFrame &frame);
|
||||||
|
void updateInterpolation();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Node;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 动画事件类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class AnimationEventType : uint32_t {
|
||||||
|
FrameChanged = 0x2001, // 帧切换
|
||||||
|
KeyframeHit = 0x2002, // 关键帧触发
|
||||||
|
SoundTrigger = 0x2003, // 音效触发
|
||||||
|
AnimationStart = 0x2004, // 动画开始播放
|
||||||
|
AnimationEnd = 0x2005, // 动画播放结束
|
||||||
|
AnimationLoop = 0x2006, // 动画循环一轮
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 动画事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct AnimationEvent {
|
||||||
|
AnimationEventType type;
|
||||||
|
size_t frameIndex = 0;
|
||||||
|
size_t previousFrameIndex = 0;
|
||||||
|
int keyframeFlag = -1;
|
||||||
|
std::string soundPath;
|
||||||
|
Node *source = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 动画事件回调类型
|
||||||
|
// ============================================================================
|
||||||
|
using AnimationEventCallback = std::function<void(const AnimationEvent &)>;
|
||||||
|
using KeyframeHitCallback = std::function<void(int flagIndex)>;
|
||||||
|
using AnimationCompleteCallback = std::function<void()>;
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <extra2d/animation/frame_property.h>
|
||||||
|
#include <extra2d/animation/sprite_frame.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationFrame - 单帧数据
|
||||||
|
// 引用 SpriteFrame 而非直接持有纹理(借鉴 Cocos 模式)
|
||||||
|
// 通过 FramePropertySet 支持不固定数据(ANI Flag 系统增强版)
|
||||||
|
// ============================================================================
|
||||||
|
struct AnimationFrame {
|
||||||
|
// ------ 核心数据(固定部分)------
|
||||||
|
Ptr<SpriteFrame> spriteFrame; // 精灵帧引用(Cocos 模式)
|
||||||
|
std::string texturePath; // 原始图片路径(用于解析时定位资源)
|
||||||
|
int textureIndex = 0; // 精灵图集索引
|
||||||
|
Vec2 offset; // 位置偏移
|
||||||
|
float delay = 100.0f; // 帧延迟(毫秒)
|
||||||
|
|
||||||
|
// ------ 碰撞盒数据(DNF ANI 格式)------
|
||||||
|
std::vector<std::array<int32_t, 6>> damageBoxes; // 伤害碰撞盒
|
||||||
|
std::vector<std::array<int32_t, 6>> attackBoxes; // 攻击碰撞盒
|
||||||
|
|
||||||
|
// ------ 不固定数据(属性集合)------
|
||||||
|
FramePropertySet properties; // 类型安全的 Flag 系统
|
||||||
|
|
||||||
|
// ------ 便捷方法 ------
|
||||||
|
bool hasTexture() const {
|
||||||
|
return spriteFrame != nullptr && spriteFrame->isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasInterpolation() const {
|
||||||
|
return properties.getOr<bool>(FramePropertyKey::Interpolation, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasKeyframeCallback() const {
|
||||||
|
return properties.has(FramePropertyKey::SetFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getKeyframeIndex() const {
|
||||||
|
return properties.getOr<int>(FramePropertyKey::SetFlag, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 getEffectiveScale() const {
|
||||||
|
return properties.getOr<Vec2>(FramePropertyKey::ImageRate, Vec2::One());
|
||||||
|
}
|
||||||
|
|
||||||
|
float getEffectiveRotation() const {
|
||||||
|
return properties.getOr<float>(FramePropertyKey::ImageRotate, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getEffectiveColor() const {
|
||||||
|
return properties.getOr<Color>(FramePropertyKey::ColorTint, Colors::White);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/animation/animation_cache.h>
|
||||||
|
#include <extra2d/animation/animation_clip.h>
|
||||||
|
#include <extra2d/animation/animation_controller.h>
|
||||||
|
#include <extra2d/animation/animation_event.h>
|
||||||
|
#include <extra2d/animation/frame_renderer.h>
|
||||||
|
#include <extra2d/scene/node.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationNode - 动画节点(继承 Node)
|
||||||
|
// 使用 FrameRenderer 单渲染器策略,不依赖 Sprite 基类
|
||||||
|
// 适用于需要独立渲染控制的动画(如特效、复合动画图层)
|
||||||
|
// ============================================================================
|
||||||
|
class AnimationNode : public Node {
|
||||||
|
public:
|
||||||
|
AnimationNode();
|
||||||
|
~AnimationNode() override = default;
|
||||||
|
|
||||||
|
// ------ 静态工厂(Cocos 风格)------
|
||||||
|
static Ptr<AnimationNode> create();
|
||||||
|
static Ptr<AnimationNode> create(Ptr<AnimationClip> clip);
|
||||||
|
static Ptr<AnimationNode> create(const std::string &aniFilePath);
|
||||||
|
|
||||||
|
// ------ 动画数据 ------
|
||||||
|
void setClip(Ptr<AnimationClip> clip);
|
||||||
|
Ptr<AnimationClip> getClip() const;
|
||||||
|
bool loadFromFile(const std::string &aniFilePath);
|
||||||
|
|
||||||
|
// ------ 播放控制 ------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
bool isPlaying() const;
|
||||||
|
bool isPaused() const;
|
||||||
|
bool isStopped() const;
|
||||||
|
|
||||||
|
void setPlaybackSpeed(float speed);
|
||||||
|
float getPlaybackSpeed() const;
|
||||||
|
void setLooping(bool loop);
|
||||||
|
bool isLooping() const;
|
||||||
|
|
||||||
|
// ------ 帧控制 ------
|
||||||
|
void setFrameIndex(size_t index);
|
||||||
|
size_t getCurrentFrameIndex() const;
|
||||||
|
size_t getTotalFrames() const;
|
||||||
|
|
||||||
|
// ------ 事件回调 ------
|
||||||
|
void setKeyframeCallback(KeyframeHitCallback callback);
|
||||||
|
void setCompletionCallback(AnimationCompleteCallback callback);
|
||||||
|
void
|
||||||
|
setFrameChangeCallback(AnimationController::FrameChangeCallback callback);
|
||||||
|
void addEventListener(AnimationEventCallback callback);
|
||||||
|
|
||||||
|
// ------ 视觉属性 ------
|
||||||
|
void setTintColor(const Color &color);
|
||||||
|
Color getTintColor() const { return tintColor_; }
|
||||||
|
void setFlipX(bool flip) { flipX_ = flip; }
|
||||||
|
void setFlipY(bool flip) { flipY_ = flip; }
|
||||||
|
bool isFlipX() const { return flipX_; }
|
||||||
|
bool isFlipY() const { return flipY_; }
|
||||||
|
|
||||||
|
// ------ 自动播放 ------
|
||||||
|
void setAutoPlay(bool autoPlay) { autoPlay_ = autoPlay; }
|
||||||
|
bool isAutoPlay() const { return autoPlay_; }
|
||||||
|
|
||||||
|
// ------ 碰撞盒访问 ------
|
||||||
|
const std::vector<std::array<int32_t, 6>> &getCurrentDamageBoxes() const;
|
||||||
|
const std::vector<std::array<int32_t, 6>> &getCurrentAttackBoxes() const;
|
||||||
|
|
||||||
|
// ------ 查询 ------
|
||||||
|
Size getMaxFrameSize() const;
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
// ------ 直接访问 ------
|
||||||
|
AnimationController &getController() { return controller_; }
|
||||||
|
const AnimationController &getController() const { return controller_; }
|
||||||
|
FrameRenderer &getFrameRenderer() { return frameRenderer_; }
|
||||||
|
const FrameRenderer &getFrameRenderer() const { return frameRenderer_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onUpdate(float dt) override;
|
||||||
|
void onDraw(RenderBackend &renderer) override;
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
AnimationController controller_;
|
||||||
|
FrameRenderer frameRenderer_;
|
||||||
|
Color tintColor_ = Colors::White;
|
||||||
|
bool flipX_ = false;
|
||||||
|
bool flipY_ = false;
|
||||||
|
bool autoPlay_ = false;
|
||||||
|
std::vector<AnimationEventCallback> eventListeners_;
|
||||||
|
|
||||||
|
static const std::vector<std::array<int32_t, 6>> emptyBoxes_;
|
||||||
|
|
||||||
|
void setupControllerCallbacks();
|
||||||
|
void dispatchEvent(const AnimationEvent &event);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/animation/als_parser.h>
|
||||||
|
#include <extra2d/animation/animation_event.h>
|
||||||
|
#include <extra2d/animation/animation_node.h>
|
||||||
|
#include <extra2d/scene/node.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CompositeAnimation - ALS 多层复合动画节点
|
||||||
|
// 管理多个 AnimationNode 图层,统一控制播放
|
||||||
|
// 对应 DNF 的 ALS 格式(多层动画叠加)
|
||||||
|
// ============================================================================
|
||||||
|
class CompositeAnimation : public Node {
|
||||||
|
public:
|
||||||
|
CompositeAnimation() = default;
|
||||||
|
~CompositeAnimation() override = default;
|
||||||
|
|
||||||
|
// ------ 静态工厂 ------
|
||||||
|
static Ptr<CompositeAnimation> create();
|
||||||
|
static Ptr<CompositeAnimation> create(const std::string &alsFilePath);
|
||||||
|
|
||||||
|
// ------ 加载 ------
|
||||||
|
bool loadFromFile(const std::string &alsFilePath);
|
||||||
|
|
||||||
|
// ------ 图层管理 ------
|
||||||
|
void addLayer(Ptr<AnimationNode> node, int zOrder = 0);
|
||||||
|
void removeLayer(size_t index);
|
||||||
|
Ptr<AnimationNode> getLayer(size_t index) const;
|
||||||
|
Ptr<AnimationNode> getMainLayer() const;
|
||||||
|
size_t getLayerCount() const;
|
||||||
|
|
||||||
|
// ------ 统一播放控制 ------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
void setPlaybackSpeed(float speed);
|
||||||
|
void setLooping(bool loop);
|
||||||
|
|
||||||
|
bool isPlaying() const;
|
||||||
|
bool isStopped() const;
|
||||||
|
|
||||||
|
// ------ 事件回调(绑定到主图层)------
|
||||||
|
void setKeyframeCallback(KeyframeHitCallback callback);
|
||||||
|
void setCompletionCallback(AnimationCompleteCallback callback);
|
||||||
|
void addEventListener(AnimationEventCallback callback);
|
||||||
|
|
||||||
|
// ------ 视觉属性(应用到所有图层)------
|
||||||
|
void setTintColor(const Color &color);
|
||||||
|
void setFlipX(bool flip);
|
||||||
|
void setFlipY(bool flip);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct LayerEntry {
|
||||||
|
Ptr<AnimationNode> node;
|
||||||
|
int zOrder = 0;
|
||||||
|
};
|
||||||
|
std::vector<LayerEntry> layers_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 帧属性键 - 强类型枚举替代原始 ANI 的字符串键
|
||||||
|
// ============================================================================
|
||||||
|
enum class FramePropertyKey : uint32 {
|
||||||
|
// 事件触发
|
||||||
|
SetFlag = 0x0001, // int: 关键帧回调索引
|
||||||
|
PlaySound = 0x0002, // string: 音效路径
|
||||||
|
|
||||||
|
// 变换属性
|
||||||
|
ImageRate = 0x0010, // Vec2: 缩放比例
|
||||||
|
ImageRotate = 0x0011, // float: 旋转角度(度)
|
||||||
|
ImageOffset = 0x0012, // Vec2: 额外位置偏移
|
||||||
|
|
||||||
|
// 视觉效果
|
||||||
|
BlendLinearDodge = 0x0020, // bool: 线性减淡
|
||||||
|
BlendAdditive = 0x0021, // bool: 加法混合
|
||||||
|
ColorTint = 0x0022, // Color: RGBA 颜色
|
||||||
|
|
||||||
|
// 控制标记
|
||||||
|
Interpolation = 0x0030, // bool: 启用到下一帧的插值
|
||||||
|
Loop = 0x0031, // bool: 全局循环标记
|
||||||
|
|
||||||
|
// DNF ANI 扩展属性
|
||||||
|
DamageType = 0x0040, // int: 伤害类型 (0=Normal, 1=SuperArmor, 2=Unbreakable)
|
||||||
|
Shadow = 0x0041, // bool: 阴影
|
||||||
|
FlipType = 0x0042, // int: 翻转类型 (1=Horizon, 2=Vertical, 3=All)
|
||||||
|
Coord = 0x0043, // int: 坐标系
|
||||||
|
LoopStart = 0x0044, // bool: 循环起始标记
|
||||||
|
LoopEnd = 0x0045, // int: 循环结束帧数
|
||||||
|
GraphicEffect = 0x0046, // int: 图形特效类型
|
||||||
|
ClipRegion = 0x0047, // vector<int>: 裁剪区域 [4个int16]
|
||||||
|
|
||||||
|
// 用户自定义扩展区间 (0x1000+)
|
||||||
|
UserDefined = 0x1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 帧属性值 - variant 多态值(优化版本)
|
||||||
|
// 使用紧凑存储,常用小类型直接内联,大类型使用索引引用
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
struct FramePropertyValue;
|
||||||
|
|
||||||
|
// 属性存储类型枚举
|
||||||
|
enum class PropertyValueType : uint8_t {
|
||||||
|
Empty = 0,
|
||||||
|
Bool = 1,
|
||||||
|
Int = 2,
|
||||||
|
Float = 3,
|
||||||
|
Vec2 = 4,
|
||||||
|
Color = 5,
|
||||||
|
String = 6, // 字符串使用索引引用
|
||||||
|
IntVector = 7, // vector<int> 使用索引引用
|
||||||
|
};
|
||||||
|
|
||||||
|
// 紧凑的属性值结构(16字节)
|
||||||
|
struct FramePropertyValue {
|
||||||
|
PropertyValueType type = PropertyValueType::Empty;
|
||||||
|
uint8_t padding[3] = {0};
|
||||||
|
|
||||||
|
// 使用结构体包装非平凡类型,使其可以在union中使用
|
||||||
|
struct Vec2Storage {
|
||||||
|
float x, y;
|
||||||
|
Vec2Storage() = default;
|
||||||
|
Vec2Storage(const Vec2& v) : x(v.x), y(v.y) {}
|
||||||
|
operator Vec2() const { return Vec2(x, y); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ColorStorage {
|
||||||
|
float r, g, b, a;
|
||||||
|
ColorStorage() = default;
|
||||||
|
ColorStorage(const Color& c) : r(c.r), g(c.g), b(c.b), a(c.a) {}
|
||||||
|
operator Color() const { return Color(r, g, b, a); }
|
||||||
|
};
|
||||||
|
|
||||||
|
union Data {
|
||||||
|
bool boolValue;
|
||||||
|
int intValue;
|
||||||
|
float floatValue;
|
||||||
|
Vec2Storage vec2Value;
|
||||||
|
ColorStorage colorValue;
|
||||||
|
uint32_t stringIndex; // 字符串池索引
|
||||||
|
uint32_t vectorIndex; // vector池索引
|
||||||
|
|
||||||
|
Data() : intValue(0) {} // 默认构造函数
|
||||||
|
~Data() {} // 析构函数
|
||||||
|
} data;
|
||||||
|
|
||||||
|
FramePropertyValue() : type(PropertyValueType::Empty) {}
|
||||||
|
explicit FramePropertyValue(bool v) : type(PropertyValueType::Bool) { data.boolValue = v; }
|
||||||
|
explicit FramePropertyValue(int v) : type(PropertyValueType::Int) { data.intValue = v; }
|
||||||
|
explicit FramePropertyValue(float v) : type(PropertyValueType::Float) { data.floatValue = v; }
|
||||||
|
explicit FramePropertyValue(const Vec2& v) : type(PropertyValueType::Vec2) { data.vec2Value = v; }
|
||||||
|
explicit FramePropertyValue(const Color& v) : type(PropertyValueType::Color) { data.colorValue = v; }
|
||||||
|
|
||||||
|
bool isInline() const {
|
||||||
|
return type <= PropertyValueType::Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isString() const { return type == PropertyValueType::String; }
|
||||||
|
bool isIntVector() const { return type == PropertyValueType::IntVector; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FramePropertyKey 的 hash 支持
|
||||||
|
// ============================================================================
|
||||||
|
struct FramePropertyKeyHash {
|
||||||
|
size_t operator()(FramePropertyKey key) const noexcept {
|
||||||
|
return std::hash<uint32>{}(static_cast<uint32>(key));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FramePropertySet - 单帧属性集合(优化版本)
|
||||||
|
// 使用紧凑存储和线性探测哈希表,提高缓存命中率
|
||||||
|
// ============================================================================
|
||||||
|
class FramePropertySet {
|
||||||
|
public:
|
||||||
|
FramePropertySet() = default;
|
||||||
|
|
||||||
|
// ------ 设置属性 ------
|
||||||
|
void set(FramePropertyKey key, FramePropertyValue value);
|
||||||
|
void set(FramePropertyKey key, bool value) { set(key, FramePropertyValue(value)); }
|
||||||
|
void set(FramePropertyKey key, int value) { set(key, FramePropertyValue(value)); }
|
||||||
|
void set(FramePropertyKey key, float value) { set(key, FramePropertyValue(value)); }
|
||||||
|
void set(FramePropertyKey key, const Vec2& value) { set(key, FramePropertyValue(value)); }
|
||||||
|
void set(FramePropertyKey key, const Color& value) { set(key, FramePropertyValue(value)); }
|
||||||
|
void set(FramePropertyKey key, const std::string& value);
|
||||||
|
void set(FramePropertyKey key, const std::vector<int>& value);
|
||||||
|
|
||||||
|
void setCustom(const std::string &key, std::any value);
|
||||||
|
|
||||||
|
// ------ 类型安全获取 ------
|
||||||
|
template <typename T> std::optional<T> get(FramePropertyKey key) const;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T getOr(FramePropertyKey key, const T &defaultValue) const {
|
||||||
|
auto result = get<T>(key);
|
||||||
|
return result.value_or(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::any> getCustom(const std::string &key) const;
|
||||||
|
|
||||||
|
// ------ 查询 ------
|
||||||
|
bool has(FramePropertyKey key) const;
|
||||||
|
bool hasCustom(const std::string &key) const;
|
||||||
|
bool empty() const { return properties_.empty() && customProperties_.empty(); }
|
||||||
|
size_t count() const { return properties_.size() + customProperties_.size(); }
|
||||||
|
|
||||||
|
// ------ 移除 ------
|
||||||
|
void remove(FramePropertyKey key);
|
||||||
|
void removeCustom(const std::string &key);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// ------ 迭代 ------
|
||||||
|
using PropertyMap = std::unordered_map<FramePropertyKey, FramePropertyValue,
|
||||||
|
FramePropertyKeyHash>;
|
||||||
|
const PropertyMap &properties() const { return properties_; }
|
||||||
|
|
||||||
|
// ------ 链式 API ------
|
||||||
|
FramePropertySet &withSetFlag(int index);
|
||||||
|
FramePropertySet &withPlaySound(const std::string &path);
|
||||||
|
FramePropertySet &withImageRate(const Vec2 &scale);
|
||||||
|
FramePropertySet &withImageRotate(float degrees);
|
||||||
|
FramePropertySet &withColorTint(const Color &color);
|
||||||
|
FramePropertySet &withInterpolation(bool enabled = true);
|
||||||
|
FramePropertySet &withBlendLinearDodge(bool enabled = true);
|
||||||
|
FramePropertySet &withLoop(bool enabled = true);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PropertyMap properties_;
|
||||||
|
std::unordered_map<std::string, std::any> customProperties_;
|
||||||
|
|
||||||
|
// 字符串池和vector池,用于存储大对象
|
||||||
|
mutable std::vector<std::string> stringPool_;
|
||||||
|
mutable std::vector<std::vector<int>> vectorPool_;
|
||||||
|
mutable uint32_t nextStringIndex_ = 0;
|
||||||
|
mutable uint32_t nextVectorIndex_ = 0;
|
||||||
|
|
||||||
|
uint32_t allocateString(const std::string& str);
|
||||||
|
uint32_t allocateVector(const std::vector<int>& vec);
|
||||||
|
const std::string* getString(uint32_t index) const;
|
||||||
|
const std::vector<int>* getVector(uint32_t index) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模板特化声明
|
||||||
|
template <> std::optional<bool> FramePropertySet::get<bool>(FramePropertyKey key) const;
|
||||||
|
template <> std::optional<int> FramePropertySet::get<int>(FramePropertyKey key) const;
|
||||||
|
template <> std::optional<float> FramePropertySet::get<float>(FramePropertyKey key) const;
|
||||||
|
template <> std::optional<Vec2> FramePropertySet::get<Vec2>(FramePropertyKey key) const;
|
||||||
|
template <> std::optional<Color> FramePropertySet::get<Color>(FramePropertyKey key) const;
|
||||||
|
template <> std::optional<std::string> FramePropertySet::get<std::string>(FramePropertyKey key) const;
|
||||||
|
template <> std::optional<std::vector<int>> FramePropertySet::get<std::vector<int>>(FramePropertyKey key) const;
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/animation/animation_frame.h>
|
||||||
|
#include <extra2d/animation/interpolation_engine.h>
|
||||||
|
#include <extra2d/animation/sprite_frame.h>
|
||||||
|
#include <extra2d/animation/sprite_frame_cache.h>
|
||||||
|
#include <extra2d/graphics/render_backend.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FrameRenderer - 帧渲染器
|
||||||
|
// 单渲染器 + SpriteFrame 引用策略,替代 N帧=N个Sprite 的旧设计
|
||||||
|
// 负责预加载帧的 SpriteFrame、渲染当前帧、处理混合模式
|
||||||
|
// ============================================================================
|
||||||
|
class FrameRenderer {
|
||||||
|
public:
|
||||||
|
FrameRenderer() = default;
|
||||||
|
|
||||||
|
// ------ 预加载 ------
|
||||||
|
// 解析所有帧的 SpriteFrame(通过 SpriteFrameCache)
|
||||||
|
bool preloadFrames(const std::vector<AnimationFrame> &frames);
|
||||||
|
void releaseFrames();
|
||||||
|
|
||||||
|
// ------ 渲染当前帧 ------
|
||||||
|
void renderFrame(RenderBackend &renderer, const AnimationFrame &frame,
|
||||||
|
size_t frameIndex, const Vec2 &position, float nodeOpacity,
|
||||||
|
const Color &tintColor, bool flipX, bool flipY);
|
||||||
|
|
||||||
|
// ------ 渲染插值帧 ------
|
||||||
|
void renderInterpolated(RenderBackend &renderer,
|
||||||
|
const AnimationFrame &fromFrame, size_t fromIndex,
|
||||||
|
const InterpolatedProperties &props,
|
||||||
|
const Vec2 &position, float nodeOpacity,
|
||||||
|
const Color &tintColor, bool flipX, bool flipY);
|
||||||
|
|
||||||
|
// ------ 混合模式映射 ------
|
||||||
|
static BlendMode mapBlendMode(const FramePropertySet &props);
|
||||||
|
|
||||||
|
// ------ 查询 ------
|
||||||
|
Ptr<SpriteFrame> getSpriteFrame(size_t frameIndex) const;
|
||||||
|
Size getMaxFrameSize() const { return maxFrameSize_; }
|
||||||
|
bool isLoaded() const { return !spriteFrames_.empty(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Ptr<SpriteFrame>> spriteFrames_;
|
||||||
|
Size maxFrameSize_;
|
||||||
|
|
||||||
|
void drawSpriteFrame(RenderBackend &renderer, Ptr<SpriteFrame> sf,
|
||||||
|
const Vec2 &position, const Vec2 &offset,
|
||||||
|
const Vec2 &scale, float rotation, float opacity,
|
||||||
|
const Color &tint, bool flipX, bool flipY,
|
||||||
|
BlendMode blend);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <extra2d/animation/animation_frame.h>
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 插值结果 - 两帧之间的插值后属性
|
||||||
|
// ============================================================================
|
||||||
|
struct InterpolatedProperties {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 scale = Vec2::One();
|
||||||
|
float rotation = 0.0f;
|
||||||
|
Color color = Colors::White;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 插值曲线类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class InterpolationCurve : uint8 {
|
||||||
|
Linear, // 线性(原始系统的 uniform velocity)
|
||||||
|
EaseIn, // 缓入
|
||||||
|
EaseOut, // 缓出
|
||||||
|
EaseInOut, // 缓入缓出
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// InterpolationEngine - 帧间属性插值计算(静态方法,无状态)
|
||||||
|
// 独立于 AnimationController,可复用于其他系统
|
||||||
|
// ============================================================================
|
||||||
|
class InterpolationEngine {
|
||||||
|
public:
|
||||||
|
/// 核心插值计算:根据 t 因子 (0~1) 计算两帧之间的插值属性
|
||||||
|
static InterpolatedProperties
|
||||||
|
interpolate(const AnimationFrame &from, const AnimationFrame &to, float t,
|
||||||
|
InterpolationCurve curve = InterpolationCurve::Linear) {
|
||||||
|
float curvedT = applyCurve(t, curve);
|
||||||
|
|
||||||
|
InterpolatedProperties result;
|
||||||
|
result.position = lerpPosition(from, to, curvedT);
|
||||||
|
result.scale = lerpScale(from, to, curvedT);
|
||||||
|
result.rotation = lerpRotation(from, to, curvedT);
|
||||||
|
result.color = lerpColor(from, to, curvedT);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 位置插值
|
||||||
|
static Vec2 lerpPosition(const AnimationFrame &from, const AnimationFrame &to,
|
||||||
|
float t) {
|
||||||
|
return Vec2::lerp(from.offset, to.offset, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 缩放插值
|
||||||
|
static Vec2 lerpScale(const AnimationFrame &from, const AnimationFrame &to,
|
||||||
|
float t) {
|
||||||
|
Vec2 fromScale = from.getEffectiveScale();
|
||||||
|
Vec2 toScale = to.getEffectiveScale();
|
||||||
|
return Vec2::lerp(fromScale, toScale, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 旋转插值
|
||||||
|
static float lerpRotation(const AnimationFrame &from,
|
||||||
|
const AnimationFrame &to, float t) {
|
||||||
|
float fromRot = from.getEffectiveRotation();
|
||||||
|
float toRot = to.getEffectiveRotation();
|
||||||
|
return math::lerp(fromRot, toRot, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 颜色插值
|
||||||
|
static Color lerpColor(const AnimationFrame &from, const AnimationFrame &to,
|
||||||
|
float t) {
|
||||||
|
Color fromColor = from.getEffectiveColor();
|
||||||
|
Color toColor = to.getEffectiveColor();
|
||||||
|
return Color::lerp(fromColor, toColor, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 应用曲线函数
|
||||||
|
static float applyCurve(float t, InterpolationCurve curve) {
|
||||||
|
t = math::clamp(t, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
switch (curve) {
|
||||||
|
case InterpolationCurve::Linear:
|
||||||
|
return t;
|
||||||
|
|
||||||
|
case InterpolationCurve::EaseIn:
|
||||||
|
return t * t;
|
||||||
|
|
||||||
|
case InterpolationCurve::EaseOut:
|
||||||
|
return t * (2.0f - t);
|
||||||
|
|
||||||
|
case InterpolationCurve::EaseInOut:
|
||||||
|
if (t < 0.5f)
|
||||||
|
return 2.0f * t * t;
|
||||||
|
else
|
||||||
|
return -1.0f + (4.0f - 2.0f * t) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SpriteFrame - 精灵帧(纹理 + 区域 + 偏移的中间抽象)
|
||||||
|
// 借鉴 Cocos2d-x SpriteFrame:解耦纹理物理存储与逻辑帧
|
||||||
|
// 一个纹理图集可包含多个 SpriteFrame,减少纹理切换提升渲染性能
|
||||||
|
// ============================================================================
|
||||||
|
class SpriteFrame {
|
||||||
|
public:
|
||||||
|
SpriteFrame() = default;
|
||||||
|
|
||||||
|
SpriteFrame(Ptr<Texture> texture, const Rect &rect)
|
||||||
|
: texture_(std::move(texture)), rect_(rect), originalSize_(rect.size) {}
|
||||||
|
|
||||||
|
SpriteFrame(Ptr<Texture> texture, const Rect &rect, const Vec2 &offset,
|
||||||
|
const Size &originalSize)
|
||||||
|
: texture_(std::move(texture)), rect_(rect), offset_(offset),
|
||||||
|
originalSize_(originalSize) {}
|
||||||
|
|
||||||
|
// ------ 静态创建 ------
|
||||||
|
static Ptr<SpriteFrame> create(Ptr<Texture> texture, const Rect &rect) {
|
||||||
|
return makePtr<SpriteFrame>(std::move(texture), rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Ptr<SpriteFrame> create(Ptr<Texture> texture, const Rect &rect,
|
||||||
|
const Vec2 &offset, const Size &originalSize) {
|
||||||
|
return makePtr<SpriteFrame>(std::move(texture), rect, offset, originalSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 纹理信息 ------
|
||||||
|
void setTexture(Ptr<Texture> texture) { texture_ = std::move(texture); }
|
||||||
|
Ptr<Texture> getTexture() const { return texture_; }
|
||||||
|
|
||||||
|
// ------ 矩形区域(在纹理图集中的位置)------
|
||||||
|
void setRect(const Rect &rect) { rect_ = rect; }
|
||||||
|
const Rect &getRect() const { return rect_; }
|
||||||
|
|
||||||
|
// ------ 偏移(图集打包时的裁剪偏移)------
|
||||||
|
void setOffset(const Vec2 &offset) { offset_ = offset; }
|
||||||
|
const Vec2 &getOffset() const { return offset_; }
|
||||||
|
|
||||||
|
// ------ 原始尺寸(裁剪前的完整尺寸)------
|
||||||
|
void setOriginalSize(const Size &size) { originalSize_ = size; }
|
||||||
|
const Size &getOriginalSize() const { return originalSize_; }
|
||||||
|
|
||||||
|
// ------ 旋转标志(图集工具可能旋转90度)------
|
||||||
|
void setRotated(bool rotated) { rotated_ = rotated; }
|
||||||
|
bool isRotated() const { return rotated_; }
|
||||||
|
|
||||||
|
// ------ 名称(用于缓存索引)------
|
||||||
|
void setName(const std::string &name) { name_ = name; }
|
||||||
|
const std::string &getName() const { return name_; }
|
||||||
|
|
||||||
|
// ------ 有效性检查 ------
|
||||||
|
bool isValid() const { return texture_ != nullptr; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<Texture> texture_;
|
||||||
|
Rect rect_;
|
||||||
|
Vec2 offset_;
|
||||||
|
Size originalSize_;
|
||||||
|
bool rotated_ = false;
|
||||||
|
std::string name_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/animation/sprite_frame.h>
|
||||||
|
#include <extra2d/graphics/texture_pool.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SpriteFrameCache - 精灵帧全局缓存(借鉴 Cocos SpriteFrameCache)
|
||||||
|
// 全局单例管理所有精灵帧,避免重复创建,支持图集自动切割
|
||||||
|
// ============================================================================
|
||||||
|
class SpriteFrameCache {
|
||||||
|
public:
|
||||||
|
static SpriteFrameCache &getInstance() {
|
||||||
|
static SpriteFrameCache instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 添加帧 ------
|
||||||
|
|
||||||
|
/// 添加单个精灵帧
|
||||||
|
void addSpriteFrame(Ptr<SpriteFrame> frame, const std::string &name) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
frames_[name] = std::move(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从纹理和矩形区域创建并添加帧
|
||||||
|
void addSpriteFrameFromTexture(Ptr<Texture> texture, const Rect &rect,
|
||||||
|
const std::string &name) {
|
||||||
|
auto frame = SpriteFrame::create(std::move(texture), rect);
|
||||||
|
frame->setName(name);
|
||||||
|
addSpriteFrame(std::move(frame), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从纹理图集批量切割添加(等宽等高网格)
|
||||||
|
void addSpriteFramesFromGrid(const std::string &texturePath, int frameWidth,
|
||||||
|
int frameHeight, int frameCount = -1,
|
||||||
|
int spacing = 0, int margin = 0) {
|
||||||
|
auto texture = TexturePool::getInstance().get(texturePath);
|
||||||
|
if (!texture)
|
||||||
|
return;
|
||||||
|
addSpriteFramesFromGrid(texture, texturePath, frameWidth, frameHeight,
|
||||||
|
frameCount, spacing, margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从纹理对象批量切割添加(等宽等高网格,无需走 TexturePool)
|
||||||
|
void addSpriteFramesFromGrid(Ptr<Texture> texture,
|
||||||
|
const std::string &keyPrefix, int frameWidth,
|
||||||
|
int frameHeight, int frameCount = -1,
|
||||||
|
int spacing = 0, int margin = 0) {
|
||||||
|
if (!texture)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int texW = texture->getWidth();
|
||||||
|
int texH = texture->getHeight();
|
||||||
|
int usableW = texW - 2 * margin;
|
||||||
|
int usableH = texH - 2 * margin;
|
||||||
|
int cols = (usableW + spacing) / (frameWidth + spacing);
|
||||||
|
int rows = (usableH + spacing) / (frameHeight + spacing);
|
||||||
|
int total = (frameCount > 0) ? frameCount : cols * rows;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
for (int i = 0; i < total; ++i) {
|
||||||
|
int col = i % cols;
|
||||||
|
int row = i / cols;
|
||||||
|
if (row >= rows)
|
||||||
|
break;
|
||||||
|
|
||||||
|
Rect rect(static_cast<float>(margin + col * (frameWidth + spacing)),
|
||||||
|
static_cast<float>(margin + row * (frameHeight + spacing)),
|
||||||
|
static_cast<float>(frameWidth),
|
||||||
|
static_cast<float>(frameHeight));
|
||||||
|
|
||||||
|
std::string name = keyPrefix + "#" + std::to_string(i);
|
||||||
|
auto frame = SpriteFrame::create(texture, rect);
|
||||||
|
frame->setName(name);
|
||||||
|
frames_[name] = std::move(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 获取帧 ------
|
||||||
|
|
||||||
|
/// 按名称获取
|
||||||
|
Ptr<SpriteFrame> getSpriteFrame(const std::string &name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = frames_.find(name);
|
||||||
|
if (it != frames_.end())
|
||||||
|
return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 通过路径+索引获取或创建(ANI 格式的定位方式)
|
||||||
|
Ptr<SpriteFrame> getOrCreateFromFile(const std::string &texturePath,
|
||||||
|
int index = 0) {
|
||||||
|
std::string key = texturePath + "#" + std::to_string(index);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = frames_.find(key);
|
||||||
|
if (it != frames_.end())
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存未命中,从 TexturePool 加载纹理并创建 SpriteFrame
|
||||||
|
auto texture = TexturePool::getInstance().get(texturePath);
|
||||||
|
if (!texture)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// 默认整张纹理作为一帧(index=0),或用整张纹理
|
||||||
|
Rect rect(0.0f, 0.0f, static_cast<float>(texture->getWidth()),
|
||||||
|
static_cast<float>(texture->getHeight()));
|
||||||
|
|
||||||
|
auto frame = SpriteFrame::create(texture, rect);
|
||||||
|
frame->setName(key);
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
frames_[key] = frame;
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 缓存管理 ------
|
||||||
|
|
||||||
|
bool has(const std::string &name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return frames_.find(name) != frames_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeSpriteFrame(const std::string &name) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
frames_.erase(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 移除未被外部引用的精灵帧(use_count == 1 表示仅缓存自身持有)
|
||||||
|
void removeUnusedSpriteFrames() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
for (auto it = frames_.begin(); it != frames_.end();) {
|
||||||
|
if (it->second.use_count() == 1) {
|
||||||
|
it = frames_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
frames_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t count() const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return frames_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SpriteFrameCache() = default;
|
||||||
|
~SpriteFrameCache() = default;
|
||||||
|
SpriteFrameCache(const SpriteFrameCache &) = delete;
|
||||||
|
SpriteFrameCache &operator=(const SpriteFrameCache &) = delete;
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
std::unordered_map<std::string, Ptr<SpriteFrame>> frames_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 便捷宏
|
||||||
|
#define E2D_SPRITE_FRAME_CACHE() ::extra2d::SpriteFrameCache::getInstance()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/render_backend.h>
|
||||||
|
#include <extra2d/platform/window.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Input;
|
||||||
|
class AudioEngine;
|
||||||
|
class SceneManager;
|
||||||
|
class ResourceManager;
|
||||||
|
class TimerManager;
|
||||||
|
class EventQueue;
|
||||||
|
class EventDispatcher;
|
||||||
|
class Camera;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Application 配置
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
enum class PlatformType {
|
||||||
|
Auto = 0,
|
||||||
|
PC,
|
||||||
|
Switch
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppConfig {
|
||||||
|
std::string title = "Easy2D Application";
|
||||||
|
int width = 800;
|
||||||
|
int height = 600;
|
||||||
|
bool fullscreen = false;
|
||||||
|
bool resizable = true;
|
||||||
|
bool vsync = true;
|
||||||
|
int fpsLimit = 0;
|
||||||
|
BackendType renderBackend = BackendType::OpenGL;
|
||||||
|
int msaaSamples = 0;
|
||||||
|
PlatformType platform = PlatformType::Auto;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Application 单例 - 应用主控
|
||||||
|
// ============================================================================
|
||||||
|
class Application {
|
||||||
|
public:
|
||||||
|
// Meyer's 单例
|
||||||
|
static Application &instance();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
Application(const Application &) = delete;
|
||||||
|
Application &operator=(const Application &) = delete;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init(const AppConfig &config);
|
||||||
|
void shutdown();
|
||||||
|
void run();
|
||||||
|
void quit();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 状态控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
bool isPaused() const { return paused_; }
|
||||||
|
bool isRunning() const { return running_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 子系统访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Window &window() { return *window_; }
|
||||||
|
RenderBackend &renderer() { return *renderer_; }
|
||||||
|
Input &input();
|
||||||
|
AudioEngine &audio();
|
||||||
|
SceneManager &scenes();
|
||||||
|
ResourceManager &resources();
|
||||||
|
TimerManager &timers();
|
||||||
|
EventQueue &eventQueue();
|
||||||
|
EventDispatcher &eventDispatcher();
|
||||||
|
Camera &camera();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 便捷方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void enterScene(Ptr<class Scene> scene);
|
||||||
|
void enterScene(Ptr<class Scene> scene, Ptr<class Transition> transition);
|
||||||
|
|
||||||
|
float deltaTime() const { return deltaTime_; }
|
||||||
|
float totalTime() const { return totalTime_; }
|
||||||
|
int fps() const { return currentFps_; }
|
||||||
|
|
||||||
|
// 获取配置
|
||||||
|
const AppConfig &getConfig() const { return config_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Application() = default;
|
||||||
|
~Application();
|
||||||
|
|
||||||
|
void mainLoop();
|
||||||
|
void update();
|
||||||
|
void render();
|
||||||
|
|
||||||
|
// 配置
|
||||||
|
AppConfig config_;
|
||||||
|
|
||||||
|
// 子系统
|
||||||
|
UniquePtr<Window> window_;
|
||||||
|
UniquePtr<RenderBackend> renderer_;
|
||||||
|
UniquePtr<SceneManager> sceneManager_;
|
||||||
|
UniquePtr<ResourceManager> resourceManager_;
|
||||||
|
UniquePtr<TimerManager> timerManager_;
|
||||||
|
UniquePtr<EventQueue> eventQueue_;
|
||||||
|
UniquePtr<EventDispatcher> eventDispatcher_;
|
||||||
|
UniquePtr<Camera> camera_;
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
bool initialized_ = false;
|
||||||
|
bool running_ = false;
|
||||||
|
bool paused_ = false;
|
||||||
|
bool shouldQuit_ = false;
|
||||||
|
|
||||||
|
// 时间
|
||||||
|
float deltaTime_ = 0.0f;
|
||||||
|
float totalTime_ = 0.0f;
|
||||||
|
double lastFrameTime_ = 0.0;
|
||||||
|
int frameCount_ = 0;
|
||||||
|
float fpsTimer_ = 0.0f;
|
||||||
|
int currentFps_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class Sound;
|
||||||
|
|
||||||
|
class AudioEngine {
|
||||||
|
public:
|
||||||
|
static AudioEngine& getInstance();
|
||||||
|
|
||||||
|
AudioEngine(const AudioEngine&) = delete;
|
||||||
|
AudioEngine& operator=(const AudioEngine&) = delete;
|
||||||
|
AudioEngine(AudioEngine&&) = delete;
|
||||||
|
AudioEngine& operator=(AudioEngine&&) = delete;
|
||||||
|
|
||||||
|
bool initialize();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
std::shared_ptr<Sound> loadSound(const std::string& filePath);
|
||||||
|
std::shared_ptr<Sound> loadSound(const std::string& name, const std::string& filePath);
|
||||||
|
|
||||||
|
std::shared_ptr<Sound> getSound(const std::string& name);
|
||||||
|
void unloadSound(const std::string& name);
|
||||||
|
void unloadAllSounds();
|
||||||
|
|
||||||
|
void setMasterVolume(float volume);
|
||||||
|
float getMasterVolume() const;
|
||||||
|
|
||||||
|
void pauseAll();
|
||||||
|
void resumeAll();
|
||||||
|
void stopAll();
|
||||||
|
|
||||||
|
private:
|
||||||
|
AudioEngine() = default;
|
||||||
|
~AudioEngine();
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<Sound>> sounds_;
|
||||||
|
float masterVolume_ = 1.0f;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
|
struct Mix_Chunk;
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class AudioEngine;
|
||||||
|
|
||||||
|
class Sound {
|
||||||
|
public:
|
||||||
|
~Sound();
|
||||||
|
|
||||||
|
Sound(const Sound&) = delete;
|
||||||
|
Sound& operator=(const Sound&) = delete;
|
||||||
|
|
||||||
|
bool play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
bool isPlaying() const;
|
||||||
|
bool isPaused() const;
|
||||||
|
|
||||||
|
void setVolume(float volume);
|
||||||
|
float getVolume() const { return volume_; }
|
||||||
|
|
||||||
|
void setLooping(bool looping);
|
||||||
|
bool isLooping() const { return looping_; }
|
||||||
|
|
||||||
|
void setPitch(float pitch);
|
||||||
|
float getPitch() const { return pitch_; }
|
||||||
|
|
||||||
|
float getDuration() const;
|
||||||
|
float getCursor() const;
|
||||||
|
void setCursor(float seconds);
|
||||||
|
|
||||||
|
const std::string& getFilePath() const { return filePath_; }
|
||||||
|
const std::string& getName() const { return name_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class AudioEngine;
|
||||||
|
|
||||||
|
Sound(const std::string& name, const std::string& filePath, Mix_Chunk* chunk);
|
||||||
|
|
||||||
|
std::string name_;
|
||||||
|
std::string filePath_;
|
||||||
|
Mix_Chunk* chunk_ = nullptr;
|
||||||
|
int channel_ = -1; // SDL_mixer 分配的通道,-1 表示未播放
|
||||||
|
float volume_ = 1.0f;
|
||||||
|
float pitch_ = 1.0f;
|
||||||
|
bool looping_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/// RGBA 颜色(浮点数,每通道 0.0 - 1.0)
|
||||||
|
struct Color {
|
||||||
|
float r = 0.0f;
|
||||||
|
float g = 0.0f;
|
||||||
|
float b = 0.0f;
|
||||||
|
float a = 1.0f;
|
||||||
|
|
||||||
|
constexpr Color() = default;
|
||||||
|
|
||||||
|
constexpr Color(float r, float g, float b, float a = 1.0f)
|
||||||
|
: r(r), g(g), b(b), a(a) {}
|
||||||
|
|
||||||
|
/// 从 0xRRGGBB 整数构造
|
||||||
|
constexpr explicit Color(uint32_t rgb, float a = 1.0f)
|
||||||
|
: r(static_cast<float>((rgb >> 16) & 0xFF) / 255.0f),
|
||||||
|
g(static_cast<float>((rgb >> 8) & 0xFF) / 255.0f),
|
||||||
|
b(static_cast<float>((rgb) & 0xFF) / 255.0f), a(a) {}
|
||||||
|
|
||||||
|
/// 从 0-255 整数构造
|
||||||
|
static constexpr Color fromRGBA(uint8_t r, uint8_t g, uint8_t b,
|
||||||
|
uint8_t a = 255) {
|
||||||
|
return Color(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 转换为 glm::vec4
|
||||||
|
glm::vec4 toVec4() const { return {r, g, b, a}; }
|
||||||
|
|
||||||
|
/// 线性插值
|
||||||
|
static Color lerp(const Color &a, const Color &b, float t) {
|
||||||
|
t = std::clamp(t, 0.0f, 1.0f);
|
||||||
|
return Color(a.r + (b.r - a.r) * t, a.g + (b.g - a.g) * t,
|
||||||
|
a.b + (b.b - a.b) * t, a.a + (b.a - a.a) * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Color &other) const {
|
||||||
|
return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const Color &other) const { return !(*this == other); }
|
||||||
|
|
||||||
|
// 算术运算符
|
||||||
|
Color operator+(const Color &other) const {
|
||||||
|
return Color(r + other.r, g + other.g, b + other.b, a + other.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator-(const Color &other) const {
|
||||||
|
return Color(r - other.r, g - other.g, b - other.b, a - other.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator*(float scalar) const {
|
||||||
|
return Color(r * scalar, g * scalar, b * scalar, a * scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator/(float scalar) const {
|
||||||
|
return Color(r / scalar, g / scalar, b / scalar, a / scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &operator+=(const Color &other) {
|
||||||
|
r += other.r;
|
||||||
|
g += other.g;
|
||||||
|
b += other.b;
|
||||||
|
a += other.a;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &operator-=(const Color &other) {
|
||||||
|
r -= other.r;
|
||||||
|
g -= other.g;
|
||||||
|
b -= other.b;
|
||||||
|
a -= other.a;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &operator*=(float scalar) {
|
||||||
|
r *= scalar;
|
||||||
|
g *= scalar;
|
||||||
|
b *= scalar;
|
||||||
|
a *= scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color &operator/=(float scalar) {
|
||||||
|
r /= scalar;
|
||||||
|
g /= scalar;
|
||||||
|
b /= scalar;
|
||||||
|
a /= scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 命名颜色常量
|
||||||
|
namespace Colors {
|
||||||
|
inline constexpr Color White{1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
inline constexpr Color Black{0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Red{1.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Green{0.0f, 1.0f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Blue{0.0f, 0.0f, 1.0f, 1.0f};
|
||||||
|
inline constexpr Color Yellow{1.0f, 1.0f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Cyan{0.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
inline constexpr Color Magenta{1.0f, 0.0f, 1.0f, 1.0f};
|
||||||
|
inline constexpr Color Orange{1.0f, 0.647f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Purple{0.502f, 0.0f, 0.502f, 1.0f};
|
||||||
|
inline constexpr Color Pink{1.0f, 0.753f, 0.796f, 1.0f};
|
||||||
|
inline constexpr Color Gray{0.502f, 0.502f, 0.502f, 1.0f};
|
||||||
|
inline constexpr Color LightGray{0.827f, 0.827f, 0.827f, 1.0f};
|
||||||
|
inline constexpr Color DarkGray{0.412f, 0.412f, 0.412f, 1.0f};
|
||||||
|
inline constexpr Color Brown{0.647f, 0.165f, 0.165f, 1.0f};
|
||||||
|
inline constexpr Color Gold{1.0f, 0.843f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Silver{0.753f, 0.753f, 0.753f, 1.0f};
|
||||||
|
inline constexpr Color SkyBlue{0.529f, 0.808f, 0.922f, 1.0f};
|
||||||
|
inline constexpr Color LimeGreen{0.196f, 0.804f, 0.196f, 1.0f};
|
||||||
|
inline constexpr Color Coral{1.0f, 0.498f, 0.314f, 1.0f};
|
||||||
|
inline constexpr Color Transparent{0.0f, 0.0f, 0.0f, 0.0f};
|
||||||
|
} // namespace Colors
|
||||||
|
|
||||||
|
// 为了向后兼容,在 Color 结构体内提供静态引用
|
||||||
|
struct ColorConstants {
|
||||||
|
static const Color &White;
|
||||||
|
static const Color &Black;
|
||||||
|
static const Color &Red;
|
||||||
|
static const Color &Green;
|
||||||
|
static const Color &Blue;
|
||||||
|
static const Color &Yellow;
|
||||||
|
static const Color &Cyan;
|
||||||
|
static const Color &Magenta;
|
||||||
|
static const Color &Transparent;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,335 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 常量
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
constexpr float PI_F = 3.14159265358979323846f;
|
||||||
|
constexpr float DEG_TO_RAD = PI_F / 180.0f;
|
||||||
|
constexpr float RAD_TO_DEG = 180.0f / PI_F;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2D 向量
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Vec2 {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
|
||||||
|
constexpr Vec2() = default;
|
||||||
|
constexpr Vec2(float x, float y) : x(x), y(y) {}
|
||||||
|
explicit Vec2(const glm::vec2 &v) : x(v.x), y(v.y) {}
|
||||||
|
|
||||||
|
glm::vec2 toGlm() const { return {x, y}; }
|
||||||
|
static Vec2 fromGlm(const glm::vec2 &v) { return {v.x, v.y}; }
|
||||||
|
|
||||||
|
// 基础运算
|
||||||
|
Vec2 operator+(const Vec2 &v) const { return {x + v.x, y + v.y}; }
|
||||||
|
Vec2 operator-(const Vec2 &v) const { return {x - v.x, y - v.y}; }
|
||||||
|
Vec2 operator*(float s) const { return {x * s, y * s}; }
|
||||||
|
Vec2 operator/(float s) const { return {x / s, y / s}; }
|
||||||
|
Vec2 operator-() const { return {-x, -y}; }
|
||||||
|
|
||||||
|
Vec2 &operator+=(const Vec2 &v) {
|
||||||
|
x += v.x;
|
||||||
|
y += v.y;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Vec2 &operator-=(const Vec2 &v) {
|
||||||
|
x -= v.x;
|
||||||
|
y -= v.y;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Vec2 &operator*=(float s) {
|
||||||
|
x *= s;
|
||||||
|
y *= s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Vec2 &operator/=(float s) {
|
||||||
|
x /= s;
|
||||||
|
y /= s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Vec2 &v) const { return x == v.x && y == v.y; }
|
||||||
|
bool operator!=(const Vec2 &v) const { return !(*this == v); }
|
||||||
|
|
||||||
|
// 向量运算
|
||||||
|
float length() const { return std::sqrt(x * x + y * y); }
|
||||||
|
float lengthSquared() const { return x * x + y * y; }
|
||||||
|
|
||||||
|
Vec2 normalized() const {
|
||||||
|
float len = length();
|
||||||
|
if (len > 0.0f)
|
||||||
|
return {x / len, y / len};
|
||||||
|
return {0.0f, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
float dot(const Vec2 &v) const { return x * v.x + y * v.y; }
|
||||||
|
float cross(const Vec2 &v) const { return x * v.y - y * v.x; }
|
||||||
|
|
||||||
|
float distance(const Vec2 &v) const { return (*this - v).length(); }
|
||||||
|
float angle() const { return std::atan2(y, x) * RAD_TO_DEG; }
|
||||||
|
|
||||||
|
static Vec2 lerp(const Vec2 &a, const Vec2 &b, float t) {
|
||||||
|
return a + (b - a) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Vec2 Zero() { return {0.0f, 0.0f}; }
|
||||||
|
static constexpr Vec2 One() { return {1.0f, 1.0f}; }
|
||||||
|
static constexpr Vec2 UnitX() { return {1.0f, 0.0f}; }
|
||||||
|
static constexpr Vec2 UnitY() { return {0.0f, 1.0f}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Vec2 operator*(float s, const Vec2 &v) { return v * s; }
|
||||||
|
|
||||||
|
using Point = Vec2;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 3D 向量 (用于3D动作)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Vec3 {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float z = 0.0f;
|
||||||
|
|
||||||
|
constexpr Vec3() = default;
|
||||||
|
constexpr Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
|
||||||
|
explicit Vec3(const glm::vec3 &v) : x(v.x), y(v.y), z(v.z) {}
|
||||||
|
|
||||||
|
glm::vec3 toGlm() const { return {x, y, z}; }
|
||||||
|
static Vec3 fromGlm(const glm::vec3 &v) { return {v.x, v.y, v.z}; }
|
||||||
|
|
||||||
|
Vec3 operator+(const Vec3 &v) const { return {x + v.x, y + v.y, z + v.z}; }
|
||||||
|
Vec3 operator-(const Vec3 &v) const { return {x - v.x, y - v.y, z - v.z}; }
|
||||||
|
Vec3 operator*(float s) const { return {x * s, y * s, z * s}; }
|
||||||
|
Vec3 operator/(float s) const { return {x / s, y / s, z / s}; }
|
||||||
|
Vec3 operator-() const { return {-x, -y, -z}; }
|
||||||
|
|
||||||
|
Vec3 &operator+=(const Vec3 &v) {
|
||||||
|
x += v.x;
|
||||||
|
y += v.y;
|
||||||
|
z += v.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Vec3 &operator-=(const Vec3 &v) {
|
||||||
|
x -= v.x;
|
||||||
|
y -= v.y;
|
||||||
|
z -= v.z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Vec3 &operator*=(float s) {
|
||||||
|
x *= s;
|
||||||
|
y *= s;
|
||||||
|
z *= s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Vec3 &operator/=(float s) {
|
||||||
|
x /= s;
|
||||||
|
y /= s;
|
||||||
|
z /= s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Vec3 &v) const {
|
||||||
|
return x == v.x && y == v.y && z == v.z;
|
||||||
|
}
|
||||||
|
bool operator!=(const Vec3 &v) const { return !(*this == v); }
|
||||||
|
|
||||||
|
float length() const { return std::sqrt(x * x + y * y + z * z); }
|
||||||
|
float lengthSquared() const { return x * x + y * y + z * z; }
|
||||||
|
|
||||||
|
Vec3 normalized() const {
|
||||||
|
float len = length();
|
||||||
|
if (len > 0.0f)
|
||||||
|
return {x / len, y / len, z / len};
|
||||||
|
return {0.0f, 0.0f, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
float dot(const Vec3 &v) const { return x * v.x + y * v.y + z * v.z; }
|
||||||
|
|
||||||
|
static Vec3 lerp(const Vec3 &a, const Vec3 &b, float t) {
|
||||||
|
return a + (b - a) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Vec3 Zero() { return {0.0f, 0.0f, 0.0f}; }
|
||||||
|
static constexpr Vec3 One() { return {1.0f, 1.0f, 1.0f}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Vec3 operator*(float s, const Vec3 &v) { return v * s; }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2D 尺寸
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Size {
|
||||||
|
float width = 0.0f;
|
||||||
|
float height = 0.0f;
|
||||||
|
|
||||||
|
constexpr Size() = default;
|
||||||
|
constexpr Size(float w, float h) : width(w), height(h) {}
|
||||||
|
|
||||||
|
bool operator==(const Size &s) const {
|
||||||
|
return width == s.width && height == s.height;
|
||||||
|
}
|
||||||
|
bool operator!=(const Size &s) const { return !(*this == s); }
|
||||||
|
|
||||||
|
float area() const { return width * height; }
|
||||||
|
bool empty() const { return width <= 0.0f || height <= 0.0f; }
|
||||||
|
|
||||||
|
static constexpr Size Zero() { return {0.0f, 0.0f}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2D 矩形
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Rect {
|
||||||
|
Point origin;
|
||||||
|
Size size;
|
||||||
|
|
||||||
|
constexpr Rect() = default;
|
||||||
|
constexpr Rect(float x, float y, float w, float h)
|
||||||
|
: origin(x, y), size(w, h) {}
|
||||||
|
constexpr Rect(const Point &o, const Size &s) : origin(o), size(s) {}
|
||||||
|
|
||||||
|
float left() const { return origin.x; }
|
||||||
|
float top() const { return origin.y; }
|
||||||
|
float right() const { return origin.x + size.width; }
|
||||||
|
float bottom() const { return origin.y + size.height; }
|
||||||
|
float width() const { return size.width; }
|
||||||
|
float height() const { return size.height; }
|
||||||
|
Point center() const {
|
||||||
|
return {origin.x + size.width * 0.5f, origin.y + size.height * 0.5f};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const { return size.empty(); }
|
||||||
|
|
||||||
|
bool containsPoint(const Point &p) const {
|
||||||
|
return p.x >= left() && p.x <= right() && p.y >= top() && p.y <= bottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const Rect &r) const {
|
||||||
|
return r.left() >= left() && r.right() <= right() && r.top() >= top() &&
|
||||||
|
r.bottom() <= bottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool intersects(const Rect &r) const {
|
||||||
|
return !(left() > r.right() || right() < r.left() || top() > r.bottom() ||
|
||||||
|
bottom() < r.top());
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect intersection(const Rect &r) const {
|
||||||
|
float l = std::max(left(), r.left());
|
||||||
|
float t = std::max(top(), r.top());
|
||||||
|
float ri = std::min(right(), r.right());
|
||||||
|
float b = std::min(bottom(), r.bottom());
|
||||||
|
if (l < ri && t < b)
|
||||||
|
return {l, t, ri - l, b - t};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect unionWith(const Rect &r) const {
|
||||||
|
if (empty())
|
||||||
|
return r;
|
||||||
|
if (r.empty())
|
||||||
|
return *this;
|
||||||
|
float l = std::min(left(), r.left());
|
||||||
|
float t = std::min(top(), r.top());
|
||||||
|
float ri = std::max(right(), r.right());
|
||||||
|
float b = std::max(bottom(), r.bottom());
|
||||||
|
return {l, t, ri - l, b - t};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Rect &r) const {
|
||||||
|
return origin == r.origin && size == r.size;
|
||||||
|
}
|
||||||
|
bool operator!=(const Rect &r) const { return !(*this == r); }
|
||||||
|
|
||||||
|
static constexpr Rect Zero() { return {0, 0, 0, 0}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2D 变换矩阵(基于 glm::mat4,兼容 OpenGL)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Transform2D {
|
||||||
|
glm::mat4 matrix{1.0f}; // 单位矩阵
|
||||||
|
|
||||||
|
Transform2D() = default;
|
||||||
|
explicit Transform2D(const glm::mat4 &m) : matrix(m) {}
|
||||||
|
|
||||||
|
static Transform2D identity() { return Transform2D{}; }
|
||||||
|
|
||||||
|
static Transform2D translation(float x, float y) {
|
||||||
|
Transform2D t;
|
||||||
|
t.matrix = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, 0.0f));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform2D translation(const Vec2 &v) {
|
||||||
|
return translation(v.x, v.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform2D rotation(float degrees) {
|
||||||
|
Transform2D t;
|
||||||
|
t.matrix = glm::rotate(glm::mat4(1.0f), degrees * DEG_TO_RAD,
|
||||||
|
glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform2D scaling(float sx, float sy) {
|
||||||
|
Transform2D t;
|
||||||
|
t.matrix = glm::scale(glm::mat4(1.0f), glm::vec3(sx, sy, 1.0f));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform2D scaling(float s) { return scaling(s, s); }
|
||||||
|
|
||||||
|
static Transform2D skewing(float skewX, float skewY) {
|
||||||
|
Transform2D t;
|
||||||
|
t.matrix = glm::mat4(1.0f);
|
||||||
|
t.matrix[1][0] = std::tan(skewX * DEG_TO_RAD);
|
||||||
|
t.matrix[0][1] = std::tan(skewY * DEG_TO_RAD);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform2D operator*(const Transform2D &other) const {
|
||||||
|
return Transform2D(matrix * other.matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform2D &operator*=(const Transform2D &other) {
|
||||||
|
matrix *= other.matrix;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 transformPoint(const Vec2 &p) const {
|
||||||
|
glm::vec4 result = matrix * glm::vec4(p.x, p.y, 0.0f, 1.0f);
|
||||||
|
return {result.x, result.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform2D inverse() const { return Transform2D(glm::inverse(matrix)); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 数学工具函数
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
namespace math {
|
||||||
|
|
||||||
|
inline float clamp(float value, float minVal, float maxVal) {
|
||||||
|
return std::clamp(value, minVal, maxVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float lerp(float a, float b, float t) { return a + (b - a) * t; }
|
||||||
|
|
||||||
|
inline float degrees(float radians) { return radians * RAD_TO_DEG; }
|
||||||
|
|
||||||
|
inline float radians(float degrees) { return degrees * DEG_TO_RAD; }
|
||||||
|
|
||||||
|
} // namespace math
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 字符串编码转换工具函数
|
||||||
|
// 统一使用 std::string (UTF-8) 作为项目标准字符串类型
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// UTF-8 ↔ UTF-16 转换
|
||||||
|
std::u16string utf8ToUtf16(const std::string& utf8);
|
||||||
|
std::string utf16ToUtf8(const std::u16string& utf16);
|
||||||
|
|
||||||
|
// UTF-8 ↔ UTF-32 转换
|
||||||
|
std::u32string utf8ToUtf32(const std::string& utf8);
|
||||||
|
std::string utf32ToUtf8(const std::u32string& utf32);
|
||||||
|
|
||||||
|
// UTF-8 ↔ Wide String 转换
|
||||||
|
std::wstring utf8ToWide(const std::string& utf8);
|
||||||
|
std::string wideToUtf8(const std::wstring& wide);
|
||||||
|
|
||||||
|
// UTF-8 ↔ GBK/GB2312 转换(Windows 中文系统常用)
|
||||||
|
std::string utf8ToGbk(const std::string& utf8);
|
||||||
|
std::string gbkToUtf8(const std::string& gbk);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 内联实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
inline std::u16string utf8ToUtf16(const std::string& utf8) {
|
||||||
|
if (utf8.empty()) return std::u16string();
|
||||||
|
|
||||||
|
// UTF-8 → UTF-32 → UTF-16 (with surrogate pairs)
|
||||||
|
std::u32string u32 = utf8ToUtf32(utf8);
|
||||||
|
std::u16string result;
|
||||||
|
result.reserve(u32.size());
|
||||||
|
|
||||||
|
for (char32_t ch : u32) {
|
||||||
|
if (ch <= 0xFFFF) {
|
||||||
|
result.push_back(static_cast<char16_t>(ch));
|
||||||
|
} else if (ch <= 0x10FFFF) {
|
||||||
|
// Surrogate pair
|
||||||
|
ch -= 0x10000;
|
||||||
|
result.push_back(static_cast<char16_t>(0xD800 | (ch >> 10)));
|
||||||
|
result.push_back(static_cast<char16_t>(0xDC00 | (ch & 0x3FF)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string utf16ToUtf8(const std::u16string& utf16) {
|
||||||
|
if (utf16.empty()) return std::string();
|
||||||
|
|
||||||
|
// UTF-16 → UTF-32 → UTF-8
|
||||||
|
std::u32string u32;
|
||||||
|
u32.reserve(utf16.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < utf16.size(); ++i) {
|
||||||
|
char16_t cu = utf16[i];
|
||||||
|
char32_t ch;
|
||||||
|
if (cu >= 0xD800 && cu <= 0xDBFF && i + 1 < utf16.size()) {
|
||||||
|
// High surrogate
|
||||||
|
char16_t cl = utf16[i + 1];
|
||||||
|
if (cl >= 0xDC00 && cl <= 0xDFFF) {
|
||||||
|
ch = 0x10000 + ((static_cast<char32_t>(cu - 0xD800) << 10) |
|
||||||
|
(cl - 0xDC00));
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
ch = cu; // Invalid, pass through
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ch = cu;
|
||||||
|
}
|
||||||
|
u32.push_back(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return utf32ToUtf8(u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::u32string utf8ToUtf32(const std::string& utf8) {
|
||||||
|
std::u32string result;
|
||||||
|
result.reserve(utf8.size());
|
||||||
|
|
||||||
|
const char* ptr = utf8.c_str();
|
||||||
|
const char* end = ptr + utf8.size();
|
||||||
|
|
||||||
|
while (ptr < end) {
|
||||||
|
char32_t ch = 0;
|
||||||
|
unsigned char byte = static_cast<unsigned char>(*ptr);
|
||||||
|
|
||||||
|
if ((byte & 0x80) == 0) {
|
||||||
|
// 1-byte sequence
|
||||||
|
ch = byte;
|
||||||
|
ptr += 1;
|
||||||
|
} else if ((byte & 0xE0) == 0xC0) {
|
||||||
|
// 2-byte sequence
|
||||||
|
ch = (byte & 0x1F) << 6;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F);
|
||||||
|
ptr += 2;
|
||||||
|
} else if ((byte & 0xF0) == 0xE0) {
|
||||||
|
// 3-byte sequence
|
||||||
|
ch = (byte & 0x0F) << 12;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 6;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F);
|
||||||
|
ptr += 3;
|
||||||
|
} else if ((byte & 0xF8) == 0xF0) {
|
||||||
|
// 4-byte sequence
|
||||||
|
ch = (byte & 0x07) << 18;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 12;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F) << 6;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[3]) & 0x3F);
|
||||||
|
ptr += 4;
|
||||||
|
} else {
|
||||||
|
// Invalid UTF-8, skip
|
||||||
|
ptr += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_back(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string utf32ToUtf8(const std::u32string& utf32) {
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
for (char32_t ch : utf32) {
|
||||||
|
if (ch <= 0x7F) {
|
||||||
|
// 1-byte
|
||||||
|
result.push_back(static_cast<char>(ch));
|
||||||
|
} else if (ch <= 0x7FF) {
|
||||||
|
// 2-byte
|
||||||
|
result.push_back(static_cast<char>(0xC0 | ((ch >> 6) & 0x1F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
|
||||||
|
} else if (ch <= 0xFFFF) {
|
||||||
|
// 3-byte
|
||||||
|
result.push_back(static_cast<char>(0xE0 | ((ch >> 12) & 0x0F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
|
||||||
|
} else if (ch <= 0x10FFFF) {
|
||||||
|
// 4-byte
|
||||||
|
result.push_back(static_cast<char>(0xF0 | ((ch >> 18) & 0x07)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | ((ch >> 12) & 0x3F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::wstring utf8ToWide(const std::string& utf8) {
|
||||||
|
if (utf8.empty()) return std::wstring();
|
||||||
|
|
||||||
|
if constexpr (sizeof(wchar_t) == 4) {
|
||||||
|
// wchar_t is 32-bit (Linux/Switch): same as UTF-32
|
||||||
|
std::u32string u32 = utf8ToUtf32(utf8);
|
||||||
|
return std::wstring(u32.begin(), u32.end());
|
||||||
|
} else {
|
||||||
|
// wchar_t is 16-bit (Windows): same as UTF-16
|
||||||
|
std::u16string u16 = utf8ToUtf16(utf8);
|
||||||
|
return std::wstring(u16.begin(), u16.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string wideToUtf8(const std::wstring& wide) {
|
||||||
|
if (wide.empty()) return std::string();
|
||||||
|
|
||||||
|
if constexpr (sizeof(wchar_t) == 4) {
|
||||||
|
std::u32string u32(wide.begin(), wide.end());
|
||||||
|
return utf32ToUtf8(u32);
|
||||||
|
} else {
|
||||||
|
std::u16string u16(wide.begin(), wide.end());
|
||||||
|
return utf16ToUtf8(u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GBK/GB2312 转换(Windows 平台实现)
|
||||||
|
// 注意:Windows 实现在 .cpp 文件中,避免头文件包含 windows.h 导致冲突
|
||||||
|
#ifdef _WIN32
|
||||||
|
// 前向声明,实现在 .cpp 文件中
|
||||||
|
std::string utf8ToGbkImpl(const std::string& utf8);
|
||||||
|
std::string gbkToUtf8Impl(const std::string& gbk);
|
||||||
|
|
||||||
|
inline std::string utf8ToGbk(const std::string& utf8) {
|
||||||
|
return utf8ToGbkImpl(utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string gbkToUtf8(const std::string& gbk) {
|
||||||
|
return gbkToUtf8Impl(gbk);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// 非 Windows 平台,GBK 转换使用 iconv 或返回原字符串
|
||||||
|
inline std::string utf8ToGbk(const std::string& utf8) {
|
||||||
|
// TODO: 使用 iconv 实现
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string gbkToUtf8(const std::string& gbk) {
|
||||||
|
// TODO: 使用 iconv 实现
|
||||||
|
return gbk;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 智能指针别名
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
template <typename T> using Ptr = std::shared_ptr<T>;
|
||||||
|
|
||||||
|
template <typename T> using UniquePtr = std::unique_ptr<T>;
|
||||||
|
|
||||||
|
template <typename T> using WeakPtr = std::weak_ptr<T>;
|
||||||
|
|
||||||
|
/// 创建 shared_ptr 的便捷函数
|
||||||
|
template <typename T, typename... Args> inline Ptr<T> makePtr(Args &&...args) {
|
||||||
|
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建 unique_ptr 的便捷函数
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
inline UniquePtr<T> makeUnique(Args &&...args) {
|
||||||
|
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 函数别名
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
template <typename Sig> using Function = std::function<Sig>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 基础类型别名
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
using int8 = std::int8_t;
|
||||||
|
using int16 = std::int16_t;
|
||||||
|
using int32 = std::int32_t;
|
||||||
|
using int64 = std::int64_t;
|
||||||
|
using uint8 = std::uint8_t;
|
||||||
|
using uint16 = std::uint16_t;
|
||||||
|
using uint32 = std::uint32_t;
|
||||||
|
using uint64 = std::uint64_t;
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/effects/particle_system.h>
|
||||||
|
#include <extra2d/effects/post_process.h>
|
||||||
|
#include <extra2d/graphics/shader_system.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class CustomEffectType {
|
||||||
|
Particle, // 粒子特效
|
||||||
|
PostProcess, // 后处理特效
|
||||||
|
Shader, // Shader特效
|
||||||
|
Combined // 组合特效
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效配置
|
||||||
|
// ============================================================================
|
||||||
|
struct CustomEffectConfig {
|
||||||
|
std::string name; // 特效名称
|
||||||
|
CustomEffectType type; // 特效类型
|
||||||
|
std::string description; // 描述
|
||||||
|
|
||||||
|
// 粒子特效配置
|
||||||
|
EmitterConfig emitterConfig;
|
||||||
|
|
||||||
|
// 后处理特效配置
|
||||||
|
std::string shaderVertPath; // 顶点着色器路径
|
||||||
|
std::string shaderFragPath; // 片段着色器路径
|
||||||
|
std::unordered_map<std::string, float> shaderParams; // Shader参数
|
||||||
|
|
||||||
|
// 通用配置
|
||||||
|
float duration; // 持续时间(-1表示无限)
|
||||||
|
bool loop; // 是否循环
|
||||||
|
float delay; // 延迟启动时间
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效基类
|
||||||
|
// ============================================================================
|
||||||
|
class CustomEffect {
|
||||||
|
public:
|
||||||
|
explicit CustomEffect(const CustomEffectConfig &config);
|
||||||
|
virtual ~CustomEffect() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual bool init();
|
||||||
|
virtual void update(float dt);
|
||||||
|
virtual void render(RenderBackend &renderer);
|
||||||
|
virtual void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
bool isPlaying() const { return playing_; }
|
||||||
|
bool isFinished() const { return finished_; }
|
||||||
|
float getElapsedTime() const { return elapsedTime_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 配置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
const std::string &getName() const { return config_.name; }
|
||||||
|
const CustomEffectConfig &getConfig() const { return config_; }
|
||||||
|
|
||||||
|
void setPosition(const Vec2 &pos) { position_ = pos; }
|
||||||
|
void setRotation(float rot) { rotation_ = rot; }
|
||||||
|
void setScale(float scale) { scale_ = scale; }
|
||||||
|
|
||||||
|
Vec2 getPosition() const { return position_; }
|
||||||
|
float getRotation() const { return rotation_; }
|
||||||
|
float getScale() const { return scale_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CustomEffectConfig config_;
|
||||||
|
Vec2 position_ = Vec2::Zero();
|
||||||
|
float rotation_ = 0.0f;
|
||||||
|
float scale_ = 1.0f;
|
||||||
|
|
||||||
|
bool playing_ = false;
|
||||||
|
bool paused_ = false;
|
||||||
|
bool finished_ = false;
|
||||||
|
float elapsedTime_ = 0.0f;
|
||||||
|
float delayTimer_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义粒子特效
|
||||||
|
// ============================================================================
|
||||||
|
class CustomParticleEffect : public CustomEffect {
|
||||||
|
public:
|
||||||
|
explicit CustomParticleEffect(const CustomEffectConfig &config);
|
||||||
|
|
||||||
|
bool init() override;
|
||||||
|
void update(float dt) override;
|
||||||
|
void render(RenderBackend &renderer) override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void play();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
Ptr<ParticleEmitter> getEmitter() { return emitter_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<ParticleSystem> particleSystem_;
|
||||||
|
Ptr<ParticleEmitter> emitter_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义后处理特效
|
||||||
|
// ============================================================================
|
||||||
|
class CustomPostProcessEffect : public CustomEffect, public PostProcessEffect {
|
||||||
|
public:
|
||||||
|
explicit CustomPostProcessEffect(const CustomEffectConfig &config);
|
||||||
|
|
||||||
|
bool init() override;
|
||||||
|
void update(float dt) override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void onShaderBind(GLShader &shader) override;
|
||||||
|
|
||||||
|
void setParam(const std::string &name, float value);
|
||||||
|
float getParam(const std::string &name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, float> runtimeParams_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效工厂
|
||||||
|
// ============================================================================
|
||||||
|
class CustomEffectFactory {
|
||||||
|
public:
|
||||||
|
using EffectCreator =
|
||||||
|
std::function<Ptr<CustomEffect>(const CustomEffectConfig &)>;
|
||||||
|
|
||||||
|
static CustomEffectFactory &getInstance();
|
||||||
|
|
||||||
|
// 注册自定义特效创建器
|
||||||
|
void registerEffect(const std::string &typeName, EffectCreator creator);
|
||||||
|
|
||||||
|
// 创建特效
|
||||||
|
Ptr<CustomEffect> create(const std::string &typeName,
|
||||||
|
const CustomEffectConfig &config);
|
||||||
|
|
||||||
|
// 检查是否已注册
|
||||||
|
bool isRegistered(const std::string &typeName) const;
|
||||||
|
|
||||||
|
// 获取所有已注册的类型
|
||||||
|
std::vector<std::string> getRegisteredTypes() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CustomEffectFactory() = default;
|
||||||
|
~CustomEffectFactory() = default;
|
||||||
|
CustomEffectFactory(const CustomEffectFactory &) = delete;
|
||||||
|
CustomEffectFactory &operator=(const CustomEffectFactory &) = delete;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, EffectCreator> creators_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效管理器
|
||||||
|
// ============================================================================
|
||||||
|
class CustomEffectManager {
|
||||||
|
public:
|
||||||
|
static CustomEffectManager &getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 特效管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件加载特效配置(支持JSON和文本格式)
|
||||||
|
* 自动检测格式:JSON格式优先,失败则回退到文本格式
|
||||||
|
*/
|
||||||
|
bool loadFromFile(const std::string &filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文本文件加载特效配置(简化格式)
|
||||||
|
* 格式:每行一个参数,如 EMISSION 100
|
||||||
|
*/
|
||||||
|
bool loadFromTextFile(const std::string &filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存特效配置到文件
|
||||||
|
* @param useJson true=JSON格式, false=文本格式
|
||||||
|
*/
|
||||||
|
bool saveToFile(const std::string &name, const std::string &filepath,
|
||||||
|
bool useJson = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存所有特效配置到一个JSON文件
|
||||||
|
*/
|
||||||
|
bool saveAllToFile(const std::string &filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册特效配置
|
||||||
|
*/
|
||||||
|
void registerConfig(const std::string &name,
|
||||||
|
const CustomEffectConfig &config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取特效配置
|
||||||
|
*/
|
||||||
|
CustomEffectConfig *getConfig(const std::string &name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移除特效配置
|
||||||
|
*/
|
||||||
|
void removeConfig(const std::string &name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取所有配置名称
|
||||||
|
*/
|
||||||
|
std::vector<std::string> getConfigNames() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 特效实例管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建特效实例
|
||||||
|
*/
|
||||||
|
Ptr<CustomEffect> createEffect(const std::string &name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从配置直接创建特效
|
||||||
|
*/
|
||||||
|
Ptr<CustomEffect> createEffectFromConfig(const CustomEffectConfig &config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁特效实例
|
||||||
|
*/
|
||||||
|
void destroyEffect(Ptr<CustomEffect> effect);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新所有特效
|
||||||
|
*/
|
||||||
|
void update(float dt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染所有特效
|
||||||
|
*/
|
||||||
|
void render(RenderBackend &renderer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 停止所有特效
|
||||||
|
*/
|
||||||
|
void stopAll();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 便捷方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 播放特效(简写)
|
||||||
|
*/
|
||||||
|
Ptr<CustomEffect> play(const std::string &name, const Vec2 &position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 播放特效并自动销毁
|
||||||
|
*/
|
||||||
|
void playOneShot(const std::string &name, const Vec2 &position);
|
||||||
|
|
||||||
|
private:
|
||||||
|
CustomEffectManager() = default;
|
||||||
|
~CustomEffectManager() = default;
|
||||||
|
CustomEffectManager(const CustomEffectManager &) = delete;
|
||||||
|
CustomEffectManager &operator=(const CustomEffectManager &) = delete;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, CustomEffectConfig> configs_;
|
||||||
|
std::vector<Ptr<CustomEffect>> activeEffects_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_CUSTOM_EFFECT_MANAGER() \
|
||||||
|
::extra2d::CustomEffectManager::getInstance()
|
||||||
|
#define E2D_CUSTOM_EFFECT_FACTORY() \
|
||||||
|
::extra2d::CustomEffectFactory::getInstance()
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 预设特效快速创建
|
||||||
|
// ============================================================================
|
||||||
|
class EffectBuilder {
|
||||||
|
public:
|
||||||
|
// 粒子特效
|
||||||
|
static CustomEffectConfig Particle(const std::string &name);
|
||||||
|
static CustomEffectConfig Fire(const std::string &name);
|
||||||
|
static CustomEffectConfig Smoke(const std::string &name);
|
||||||
|
static CustomEffectConfig Explosion(const std::string &name);
|
||||||
|
static CustomEffectConfig Magic(const std::string &name);
|
||||||
|
static CustomEffectConfig Sparkle(const std::string &name);
|
||||||
|
|
||||||
|
// 后处理特效
|
||||||
|
static CustomEffectConfig Bloom(const std::string &name);
|
||||||
|
static CustomEffectConfig Blur(const std::string &name);
|
||||||
|
static CustomEffectConfig Vignette(const std::string &name);
|
||||||
|
static CustomEffectConfig ColorGrading(const std::string &name);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,266 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <extra2d/scene/node.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <random>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 粒子数据
|
||||||
|
// ============================================================================
|
||||||
|
struct Particle {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 velocity;
|
||||||
|
Vec2 acceleration;
|
||||||
|
float rotation;
|
||||||
|
float angularVelocity;
|
||||||
|
float size;
|
||||||
|
float sizeDelta;
|
||||||
|
Color color;
|
||||||
|
Color colorDelta;
|
||||||
|
float life;
|
||||||
|
float maxLife;
|
||||||
|
bool active;
|
||||||
|
|
||||||
|
Particle()
|
||||||
|
: position(Vec2::Zero()), velocity(Vec2::Zero()),
|
||||||
|
acceleration(Vec2::Zero()), rotation(0.0f), angularVelocity(0.0f),
|
||||||
|
size(1.0f), sizeDelta(0.0f), color(Colors::White),
|
||||||
|
colorDelta(Colors::Transparent), life(0.0f), maxLife(1.0f),
|
||||||
|
active(false) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 发射器配置
|
||||||
|
// ============================================================================
|
||||||
|
struct EmitterConfig {
|
||||||
|
// 发射速率
|
||||||
|
float emissionRate = 100.0f; // 每秒发射粒子数
|
||||||
|
float emissionDuration = -1.0f; // 发射持续时间(-1表示无限)
|
||||||
|
|
||||||
|
// 粒子生命周期
|
||||||
|
float minLife = 1.0f;
|
||||||
|
float maxLife = 2.0f;
|
||||||
|
|
||||||
|
// 粒子大小
|
||||||
|
float minStartSize = 10.0f;
|
||||||
|
float maxStartSize = 20.0f;
|
||||||
|
float minEndSize = 0.0f;
|
||||||
|
float maxEndSize = 5.0f;
|
||||||
|
|
||||||
|
// 粒子速度
|
||||||
|
Vec2 minVelocity = Vec2(-50.0f, -50.0f);
|
||||||
|
Vec2 maxVelocity = Vec2(50.0f, 50.0f);
|
||||||
|
|
||||||
|
// 粒子加速度
|
||||||
|
Vec2 acceleration = Vec2(0.0f, -100.0f); // 重力
|
||||||
|
|
||||||
|
// 粒子旋转
|
||||||
|
float minRotation = 0.0f;
|
||||||
|
float maxRotation = 360.0f;
|
||||||
|
float minAngularVelocity = -90.0f;
|
||||||
|
float maxAngularVelocity = 90.0f;
|
||||||
|
|
||||||
|
// 颜色
|
||||||
|
Color startColor = Colors::White;
|
||||||
|
Color endColor = Colors::Transparent;
|
||||||
|
|
||||||
|
// 发射形状
|
||||||
|
enum class Shape {
|
||||||
|
Point, // 点发射
|
||||||
|
Circle, // 圆形区域
|
||||||
|
Rectangle, // 矩形区域
|
||||||
|
Cone // 锥形
|
||||||
|
};
|
||||||
|
Shape shape = Shape::Point;
|
||||||
|
float shapeRadius = 50.0f; // 圆形/锥形半径
|
||||||
|
Vec2 shapeSize = Vec2(100.0f, 100.0f); // 矩形大小
|
||||||
|
float coneAngle = 45.0f; // 锥形角度
|
||||||
|
|
||||||
|
// 纹理
|
||||||
|
Ptr<Texture> texture = nullptr;
|
||||||
|
|
||||||
|
// 混合模式
|
||||||
|
BlendMode blendMode = BlendMode::Additive;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 粒子发射器
|
||||||
|
// ============================================================================
|
||||||
|
class ParticleEmitter {
|
||||||
|
public:
|
||||||
|
ParticleEmitter();
|
||||||
|
~ParticleEmitter() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init(size_t maxParticles);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 配置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setConfig(const EmitterConfig &config) { config_ = config; }
|
||||||
|
const EmitterConfig &getConfig() const { return config_; }
|
||||||
|
|
||||||
|
// 链式配置API
|
||||||
|
ParticleEmitter &withEmissionRate(float rate) {
|
||||||
|
config_.emissionRate = rate;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter &withLife(float minLife, float maxLife) {
|
||||||
|
config_.minLife = minLife;
|
||||||
|
config_.maxLife = maxLife;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter &withSize(float minStart, float maxStart, float minEnd = 0.0f,
|
||||||
|
float maxEnd = 0.0f) {
|
||||||
|
config_.minStartSize = minStart;
|
||||||
|
config_.maxStartSize = maxStart;
|
||||||
|
config_.minEndSize = minEnd;
|
||||||
|
config_.maxEndSize = maxEnd;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter &withVelocity(const Vec2 &minVel, const Vec2 &maxVel) {
|
||||||
|
config_.minVelocity = minVel;
|
||||||
|
config_.maxVelocity = maxVel;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter &withAcceleration(const Vec2 &accel) {
|
||||||
|
config_.acceleration = accel;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter &withColor(const Color &start, const Color &end) {
|
||||||
|
config_.startColor = start;
|
||||||
|
config_.endColor = end;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter &withTexture(Ptr<Texture> texture) {
|
||||||
|
config_.texture = texture;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter &withBlendMode(BlendMode mode) {
|
||||||
|
config_.blendMode = mode;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 发射控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
void burst(int count); // 爆发发射
|
||||||
|
void reset();
|
||||||
|
bool isEmitting() const { return emitting_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 更新和渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void update(float dt);
|
||||||
|
void render(RenderBackend &renderer);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 状态查询
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
size_t getActiveParticleCount() const { return activeCount_; }
|
||||||
|
size_t getMaxParticles() const { return particles_.size(); }
|
||||||
|
bool isActive() const { return activeCount_ > 0 || emitting_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 变换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPosition(const Vec2 &pos) { position_ = pos; }
|
||||||
|
void setRotation(float rot) { rotation_ = rot; }
|
||||||
|
Vec2 getPosition() const { return position_; }
|
||||||
|
float getRotation() const { return rotation_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
EmitterConfig config_;
|
||||||
|
std::vector<Particle> particles_;
|
||||||
|
size_t activeCount_ = 0;
|
||||||
|
|
||||||
|
Vec2 position_ = Vec2::Zero();
|
||||||
|
float rotation_ = 0.0f;
|
||||||
|
|
||||||
|
bool emitting_ = false;
|
||||||
|
float emissionTimer_ = 0.0f;
|
||||||
|
float emissionTime_ = 0.0f;
|
||||||
|
|
||||||
|
std::mt19937 rng_;
|
||||||
|
|
||||||
|
void emitParticle();
|
||||||
|
float randomFloat(float min, float max);
|
||||||
|
Vec2 randomPointInShape();
|
||||||
|
Vec2 randomVelocity();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 粒子系统 - 管理多个发射器
|
||||||
|
// ============================================================================
|
||||||
|
class ParticleSystem : public Node {
|
||||||
|
public:
|
||||||
|
ParticleSystem();
|
||||||
|
~ParticleSystem() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<ParticleSystem> create();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 发射器管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Ptr<ParticleEmitter> addEmitter(const EmitterConfig &config = {});
|
||||||
|
void removeEmitter(Ptr<ParticleEmitter> emitter);
|
||||||
|
void removeAllEmitters();
|
||||||
|
size_t getEmitterCount() const { return emitters_.size(); }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 全局控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void startAll();
|
||||||
|
void stopAll();
|
||||||
|
void resetAll();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 预设
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static EmitterConfig PresetFire();
|
||||||
|
static EmitterConfig PresetSmoke();
|
||||||
|
static EmitterConfig PresetExplosion();
|
||||||
|
static EmitterConfig PresetSparkle();
|
||||||
|
static EmitterConfig PresetRain();
|
||||||
|
static EmitterConfig PresetSnow();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 重写Node方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void onUpdate(float dt) override;
|
||||||
|
void onDraw(RenderBackend &renderer) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Ptr<ParticleEmitter>> emitters_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 粒子预设(便捷类)
|
||||||
|
// ============================================================================
|
||||||
|
class ParticlePreset {
|
||||||
|
public:
|
||||||
|
static EmitterConfig Fire();
|
||||||
|
static EmitterConfig Smoke();
|
||||||
|
static EmitterConfig Explosion();
|
||||||
|
static EmitterConfig Sparkle();
|
||||||
|
static EmitterConfig Rain();
|
||||||
|
static EmitterConfig Snow();
|
||||||
|
static EmitterConfig Magic();
|
||||||
|
static EmitterConfig Bubbles();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 前向声明
|
||||||
|
// ============================================================================
|
||||||
|
class RenderTarget;
|
||||||
|
class RenderBackend;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 后处理效果基类
|
||||||
|
// ============================================================================
|
||||||
|
class PostProcessEffect {
|
||||||
|
public:
|
||||||
|
PostProcessEffect(const std::string &name);
|
||||||
|
virtual ~PostProcessEffect() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化效果
|
||||||
|
*/
|
||||||
|
virtual bool init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭效果
|
||||||
|
*/
|
||||||
|
virtual void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用效果
|
||||||
|
* @param source 输入纹理
|
||||||
|
* @param target 输出渲染目标
|
||||||
|
* @param renderer 渲染后端
|
||||||
|
*/
|
||||||
|
virtual void apply(const Texture &source, RenderTarget &target,
|
||||||
|
RenderBackend &renderer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置Shader参数(子类重写)
|
||||||
|
*/
|
||||||
|
virtual void onShaderBind(GLShader &shader) {}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 状态
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
const std::string &getName() const { return name_; }
|
||||||
|
bool isEnabled() const { return enabled_; }
|
||||||
|
void setEnabled(bool enabled) { enabled_ = enabled; }
|
||||||
|
bool isValid() const { return valid_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式API
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
PostProcessEffect &withEnabled(bool enabled) {
|
||||||
|
enabled_ = enabled;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string name_;
|
||||||
|
bool enabled_ = true;
|
||||||
|
bool valid_ = false;
|
||||||
|
Ptr<GLShader> shader_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载自定义Shader
|
||||||
|
*/
|
||||||
|
bool loadShader(const std::string &vertSource, const std::string &fragSource);
|
||||||
|
bool loadShaderFromFile(const std::string &vertPath,
|
||||||
|
const std::string &fragPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染全屏四边形
|
||||||
|
*/
|
||||||
|
void renderFullscreenQuad();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static GLuint quadVao_;
|
||||||
|
static GLuint quadVbo_;
|
||||||
|
static bool quadInitialized_;
|
||||||
|
|
||||||
|
void initQuad();
|
||||||
|
void destroyQuad();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 后处理栈 - 管理多个后处理效果
|
||||||
|
// ============================================================================
|
||||||
|
class PostProcessStack {
|
||||||
|
public:
|
||||||
|
PostProcessStack();
|
||||||
|
~PostProcessStack();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init(int width, int height);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 效果管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加效果到栈
|
||||||
|
*/
|
||||||
|
void addEffect(Ptr<PostProcessEffect> effect);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 插入效果到指定位置
|
||||||
|
*/
|
||||||
|
void insertEffect(size_t index, Ptr<PostProcessEffect> effect);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移除效果
|
||||||
|
*/
|
||||||
|
void removeEffect(const std::string &name);
|
||||||
|
void removeEffect(size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取效果
|
||||||
|
*/
|
||||||
|
Ptr<PostProcessEffect> getEffect(const std::string &name);
|
||||||
|
Ptr<PostProcessEffect> getEffect(size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空所有效果
|
||||||
|
*/
|
||||||
|
void clearEffects();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取效果数量
|
||||||
|
*/
|
||||||
|
size_t getEffectCount() const { return effects_.size(); }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始捕获场景
|
||||||
|
*/
|
||||||
|
void beginCapture();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束捕获并应用所有效果
|
||||||
|
*/
|
||||||
|
void endCapture(RenderBackend &renderer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 直接应用效果到纹理
|
||||||
|
*/
|
||||||
|
void process(const Texture &source, RenderTarget &target,
|
||||||
|
RenderBackend &renderer);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 配置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void resize(int width, int height);
|
||||||
|
bool isValid() const { return valid_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 便捷方法 - 添加内置效果
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
PostProcessStack &addBloom(float intensity = 1.0f, float threshold = 0.8f);
|
||||||
|
PostProcessStack &addBlur(float radius = 2.0f);
|
||||||
|
PostProcessStack &addColorGrading(const Color &tint);
|
||||||
|
PostProcessStack &addVignette(float intensity = 0.5f);
|
||||||
|
PostProcessStack &addChromaticAberration(float amount = 1.0f);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Ptr<PostProcessEffect>> effects_;
|
||||||
|
Ptr<RenderTarget> renderTargetA_;
|
||||||
|
Ptr<RenderTarget> renderTargetB_;
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
bool valid_ = false;
|
||||||
|
bool capturing_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 全局后处理管理
|
||||||
|
// ============================================================================
|
||||||
|
class PostProcessManager {
|
||||||
|
public:
|
||||||
|
static PostProcessManager &getInstance();
|
||||||
|
|
||||||
|
void init(int width, int height);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
PostProcessStack &getMainStack() { return mainStack_; }
|
||||||
|
|
||||||
|
void resize(int width, int height);
|
||||||
|
void beginFrame();
|
||||||
|
void endFrame(RenderBackend &renderer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PostProcessManager() = default;
|
||||||
|
~PostProcessManager() = default;
|
||||||
|
PostProcessManager(const PostProcessManager &) = delete;
|
||||||
|
PostProcessManager &operator=(const PostProcessManager &) = delete;
|
||||||
|
|
||||||
|
PostProcessStack mainStack_;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_POST_PROCESS() ::extra2d::PostProcessManager::getInstance()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件类型枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class EventType {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// 窗口事件
|
||||||
|
WindowClose,
|
||||||
|
WindowResize,
|
||||||
|
WindowFocus,
|
||||||
|
WindowLostFocus,
|
||||||
|
WindowMoved,
|
||||||
|
|
||||||
|
// 键盘事件
|
||||||
|
KeyPressed,
|
||||||
|
KeyReleased,
|
||||||
|
KeyRepeat,
|
||||||
|
|
||||||
|
// 鼠标事件
|
||||||
|
MouseButtonPressed,
|
||||||
|
MouseButtonReleased,
|
||||||
|
MouseMoved,
|
||||||
|
MouseScrolled,
|
||||||
|
|
||||||
|
// UI 事件
|
||||||
|
UIHoverEnter,
|
||||||
|
UIHoverExit,
|
||||||
|
UIPressed,
|
||||||
|
UIReleased,
|
||||||
|
UIClicked,
|
||||||
|
|
||||||
|
// 游戏手柄事件
|
||||||
|
GamepadConnected,
|
||||||
|
GamepadDisconnected,
|
||||||
|
GamepadButtonPressed,
|
||||||
|
GamepadButtonReleased,
|
||||||
|
GamepadAxisMoved,
|
||||||
|
|
||||||
|
// 触摸事件 (移动端)
|
||||||
|
TouchBegan,
|
||||||
|
TouchMoved,
|
||||||
|
TouchEnded,
|
||||||
|
TouchCancelled,
|
||||||
|
|
||||||
|
// 自定义事件
|
||||||
|
Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 键盘事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct KeyEvent {
|
||||||
|
int keyCode;
|
||||||
|
int scancode;
|
||||||
|
int mods; // 修饰键 (Shift, Ctrl, Alt, etc.)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 鼠标事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct MouseButtonEvent {
|
||||||
|
int button;
|
||||||
|
int mods;
|
||||||
|
Vec2 position;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MouseMoveEvent {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 delta;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MouseScrollEvent {
|
||||||
|
Vec2 offset;
|
||||||
|
Vec2 position;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 窗口事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct WindowResizeEvent {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WindowMoveEvent {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 游戏手柄事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct GamepadButtonEvent {
|
||||||
|
int gamepadId;
|
||||||
|
int button;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GamepadAxisEvent {
|
||||||
|
int gamepadId;
|
||||||
|
int axis;
|
||||||
|
float value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 触摸事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct TouchEvent {
|
||||||
|
int touchId;
|
||||||
|
Vec2 position;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct CustomEvent {
|
||||||
|
uint32_t id;
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件结构
|
||||||
|
// ============================================================================
|
||||||
|
struct Event {
|
||||||
|
EventType type = EventType::None;
|
||||||
|
double timestamp = 0.0;
|
||||||
|
bool handled = false;
|
||||||
|
|
||||||
|
// 事件数据联合体
|
||||||
|
std::variant<std::monostate, KeyEvent, MouseButtonEvent, MouseMoveEvent,
|
||||||
|
MouseScrollEvent, WindowResizeEvent, WindowMoveEvent,
|
||||||
|
GamepadButtonEvent, GamepadAxisEvent, TouchEvent, CustomEvent>
|
||||||
|
data;
|
||||||
|
|
||||||
|
// 便捷访问方法
|
||||||
|
bool isWindowEvent() const {
|
||||||
|
return type == EventType::WindowClose || type == EventType::WindowResize ||
|
||||||
|
type == EventType::WindowFocus ||
|
||||||
|
type == EventType::WindowLostFocus || type == EventType::WindowMoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isKeyboardEvent() const {
|
||||||
|
return type == EventType::KeyPressed || type == EventType::KeyReleased ||
|
||||||
|
type == EventType::KeyRepeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isMouseEvent() const {
|
||||||
|
return type == EventType::MouseButtonPressed ||
|
||||||
|
type == EventType::MouseButtonReleased ||
|
||||||
|
type == EventType::MouseMoved || type == EventType::MouseScrolled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态工厂方法
|
||||||
|
static Event createWindowResize(int width, int height);
|
||||||
|
static Event createWindowClose();
|
||||||
|
static Event createKeyPress(int keyCode, int scancode, int mods);
|
||||||
|
static Event createKeyRelease(int keyCode, int scancode, int mods);
|
||||||
|
static Event createMouseButtonPress(int button, int mods, const Vec2 &pos);
|
||||||
|
static Event createMouseButtonRelease(int button, int mods, const Vec2 &pos);
|
||||||
|
static Event createMouseMove(const Vec2 &pos, const Vec2 &delta);
|
||||||
|
static Event createMouseScroll(const Vec2 &offset, const Vec2 &pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/event/event.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件监听器 ID
|
||||||
|
// ============================================================================
|
||||||
|
using ListenerId = uint64_t;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件分发器
|
||||||
|
// ============================================================================
|
||||||
|
class EventDispatcher {
|
||||||
|
public:
|
||||||
|
using EventCallback = std::function<void(Event &)>;
|
||||||
|
|
||||||
|
EventDispatcher();
|
||||||
|
~EventDispatcher() = default;
|
||||||
|
|
||||||
|
// 添加监听器
|
||||||
|
ListenerId addListener(EventType type, EventCallback callback);
|
||||||
|
|
||||||
|
// 移除监听器
|
||||||
|
void removeListener(ListenerId id);
|
||||||
|
void removeAllListeners(EventType type);
|
||||||
|
void removeAllListeners();
|
||||||
|
|
||||||
|
// 分发事件
|
||||||
|
void dispatch(Event &event);
|
||||||
|
void dispatch(const Event &event);
|
||||||
|
|
||||||
|
// 处理事件队列
|
||||||
|
void processQueue(class EventQueue &queue);
|
||||||
|
|
||||||
|
// 统计
|
||||||
|
size_t getListenerCount(EventType type) const;
|
||||||
|
size_t getTotalListenerCount() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Listener {
|
||||||
|
ListenerId id;
|
||||||
|
EventType type;
|
||||||
|
EventCallback callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<EventType, std::vector<Listener>> listeners_;
|
||||||
|
ListenerId nextId_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/event/event.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件队列 - 线程安全的事件队列
|
||||||
|
// ============================================================================
|
||||||
|
class EventQueue {
|
||||||
|
public:
|
||||||
|
EventQueue();
|
||||||
|
~EventQueue() = default;
|
||||||
|
|
||||||
|
// 添加事件到队列
|
||||||
|
void push(const Event &event);
|
||||||
|
void push(Event &&event);
|
||||||
|
|
||||||
|
// 从队列取出事件
|
||||||
|
bool poll(Event &event);
|
||||||
|
|
||||||
|
// 查看队列头部事件(不移除)
|
||||||
|
bool peek(Event &event) const;
|
||||||
|
|
||||||
|
// 清空队列
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// 队列状态
|
||||||
|
bool empty() const;
|
||||||
|
size_t size() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::queue<Event> queue_;
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// SDL2 键码定义
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 键盘按键码 (基于 SDL2)
|
||||||
|
// ============================================================================
|
||||||
|
namespace Key {
|
||||||
|
enum : int {
|
||||||
|
Unknown = SDLK_UNKNOWN,
|
||||||
|
Space = SDLK_SPACE,
|
||||||
|
Apostrophe = SDLK_QUOTE,
|
||||||
|
Comma = SDLK_COMMA,
|
||||||
|
Minus = SDLK_MINUS,
|
||||||
|
Period = SDLK_PERIOD,
|
||||||
|
Slash = SDLK_SLASH,
|
||||||
|
Num0 = SDLK_0,
|
||||||
|
Num1 = SDLK_1,
|
||||||
|
Num2 = SDLK_2,
|
||||||
|
Num3 = SDLK_3,
|
||||||
|
Num4 = SDLK_4,
|
||||||
|
Num5 = SDLK_5,
|
||||||
|
Num6 = SDLK_6,
|
||||||
|
Num7 = SDLK_7,
|
||||||
|
Num8 = SDLK_8,
|
||||||
|
Num9 = SDLK_9,
|
||||||
|
Semicolon = SDLK_SEMICOLON,
|
||||||
|
Equal = SDLK_EQUALS,
|
||||||
|
A = SDLK_a,
|
||||||
|
B = SDLK_b,
|
||||||
|
C = SDLK_c,
|
||||||
|
D = SDLK_d,
|
||||||
|
E = SDLK_e,
|
||||||
|
F = SDLK_f,
|
||||||
|
G = SDLK_g,
|
||||||
|
H = SDLK_h,
|
||||||
|
I = SDLK_i,
|
||||||
|
J = SDLK_j,
|
||||||
|
K = SDLK_k,
|
||||||
|
L = SDLK_l,
|
||||||
|
M = SDLK_m,
|
||||||
|
N = SDLK_n,
|
||||||
|
O = SDLK_o,
|
||||||
|
P = SDLK_p,
|
||||||
|
Q = SDLK_q,
|
||||||
|
R = SDLK_r,
|
||||||
|
S = SDLK_s,
|
||||||
|
T = SDLK_t,
|
||||||
|
U = SDLK_u,
|
||||||
|
V = SDLK_v,
|
||||||
|
W = SDLK_w,
|
||||||
|
X = SDLK_x,
|
||||||
|
Y = SDLK_y,
|
||||||
|
Z = SDLK_z,
|
||||||
|
LeftBracket = SDLK_LEFTBRACKET,
|
||||||
|
Backslash = SDLK_BACKSLASH,
|
||||||
|
RightBracket = SDLK_RIGHTBRACKET,
|
||||||
|
GraveAccent = SDLK_BACKQUOTE,
|
||||||
|
Escape = SDLK_ESCAPE,
|
||||||
|
Enter = SDLK_RETURN,
|
||||||
|
Tab = SDLK_TAB,
|
||||||
|
Backspace = SDLK_BACKSPACE,
|
||||||
|
Insert = SDLK_INSERT,
|
||||||
|
Delete = SDLK_DELETE,
|
||||||
|
Right = SDLK_RIGHT,
|
||||||
|
Left = SDLK_LEFT,
|
||||||
|
Down = SDLK_DOWN,
|
||||||
|
Up = SDLK_UP,
|
||||||
|
PageUp = SDLK_PAGEUP,
|
||||||
|
PageDown = SDLK_PAGEDOWN,
|
||||||
|
Home = SDLK_HOME,
|
||||||
|
End = SDLK_END,
|
||||||
|
CapsLock = SDLK_CAPSLOCK,
|
||||||
|
ScrollLock = SDLK_SCROLLLOCK,
|
||||||
|
NumLock = SDLK_NUMLOCKCLEAR,
|
||||||
|
PrintScreen = SDLK_PRINTSCREEN,
|
||||||
|
Pause = SDLK_PAUSE,
|
||||||
|
F1 = SDLK_F1,
|
||||||
|
F2 = SDLK_F2,
|
||||||
|
F3 = SDLK_F3,
|
||||||
|
F4 = SDLK_F4,
|
||||||
|
F5 = SDLK_F5,
|
||||||
|
F6 = SDLK_F6,
|
||||||
|
F7 = SDLK_F7,
|
||||||
|
F8 = SDLK_F8,
|
||||||
|
F9 = SDLK_F9,
|
||||||
|
F10 = SDLK_F10,
|
||||||
|
F11 = SDLK_F11,
|
||||||
|
F12 = SDLK_F12,
|
||||||
|
F13 = SDLK_F13,
|
||||||
|
F14 = SDLK_F14,
|
||||||
|
F15 = SDLK_F15,
|
||||||
|
F16 = SDLK_F16,
|
||||||
|
F17 = SDLK_F17,
|
||||||
|
F18 = SDLK_F18,
|
||||||
|
F19 = SDLK_F19,
|
||||||
|
F20 = SDLK_F20,
|
||||||
|
F21 = SDLK_F21,
|
||||||
|
F22 = SDLK_F22,
|
||||||
|
F23 = SDLK_F23,
|
||||||
|
F24 = SDLK_F24,
|
||||||
|
KP0 = SDLK_KP_0,
|
||||||
|
KP1 = SDLK_KP_1,
|
||||||
|
KP2 = SDLK_KP_2,
|
||||||
|
KP3 = SDLK_KP_3,
|
||||||
|
KP4 = SDLK_KP_4,
|
||||||
|
KP5 = SDLK_KP_5,
|
||||||
|
KP6 = SDLK_KP_6,
|
||||||
|
KP7 = SDLK_KP_7,
|
||||||
|
KP8 = SDLK_KP_8,
|
||||||
|
KP9 = SDLK_KP_9,
|
||||||
|
KPDecimal = SDLK_KP_PERIOD,
|
||||||
|
KPDivide = SDLK_KP_DIVIDE,
|
||||||
|
KPMultiply = SDLK_KP_MULTIPLY,
|
||||||
|
KPSubtract = SDLK_KP_MINUS,
|
||||||
|
KPAdd = SDLK_KP_PLUS,
|
||||||
|
KPEnter = SDLK_KP_ENTER,
|
||||||
|
KPEqual = SDLK_KP_EQUALS,
|
||||||
|
LeftShift = SDLK_LSHIFT,
|
||||||
|
LeftControl = SDLK_LCTRL,
|
||||||
|
LeftAlt = SDLK_LALT,
|
||||||
|
LeftSuper = SDLK_LGUI,
|
||||||
|
RightShift = SDLK_RSHIFT,
|
||||||
|
RightControl = SDLK_RCTRL,
|
||||||
|
RightAlt = SDLK_RALT,
|
||||||
|
RightSuper = SDLK_RGUI,
|
||||||
|
Menu = SDLK_MENU,
|
||||||
|
Last = SDLK_MENU
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 修饰键
|
||||||
|
// ============================================================================
|
||||||
|
namespace Mod {
|
||||||
|
enum : int {
|
||||||
|
Shift = KMOD_SHIFT,
|
||||||
|
Control = KMOD_CTRL,
|
||||||
|
Alt = KMOD_ALT,
|
||||||
|
Super = KMOD_GUI,
|
||||||
|
CapsLock = KMOD_CAPS,
|
||||||
|
NumLock = KMOD_NUM
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 鼠标按键码
|
||||||
|
// ============================================================================
|
||||||
|
namespace Mouse {
|
||||||
|
enum : int {
|
||||||
|
Button1 = 0,
|
||||||
|
Button2 = 1,
|
||||||
|
Button3 = 2,
|
||||||
|
Button4 = 3,
|
||||||
|
Button5 = 4,
|
||||||
|
Button6 = 5,
|
||||||
|
Button7 = 6,
|
||||||
|
Button8 = 7,
|
||||||
|
ButtonLast = Button8,
|
||||||
|
ButtonLeft = Button1,
|
||||||
|
ButtonRight = Button2,
|
||||||
|
ButtonMiddle = Button3
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 游戏手柄按键
|
||||||
|
// ============================================================================
|
||||||
|
namespace GamepadButton {
|
||||||
|
enum : int {
|
||||||
|
A = SDL_CONTROLLER_BUTTON_A,
|
||||||
|
B = SDL_CONTROLLER_BUTTON_B,
|
||||||
|
X = SDL_CONTROLLER_BUTTON_X,
|
||||||
|
Y = SDL_CONTROLLER_BUTTON_Y,
|
||||||
|
LeftBumper = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
||||||
|
RightBumper = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
||||||
|
Back = SDL_CONTROLLER_BUTTON_BACK,
|
||||||
|
Start = SDL_CONTROLLER_BUTTON_START,
|
||||||
|
Guide = SDL_CONTROLLER_BUTTON_GUIDE,
|
||||||
|
LeftThumb = SDL_CONTROLLER_BUTTON_LEFTSTICK,
|
||||||
|
RightThumb = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
|
||||||
|
DPadUp = SDL_CONTROLLER_BUTTON_DPAD_UP,
|
||||||
|
DPadRight = SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
||||||
|
DPadDown = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
|
||||||
|
DPadLeft = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
||||||
|
Last = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
||||||
|
Cross = A,
|
||||||
|
Circle = B,
|
||||||
|
Square = X,
|
||||||
|
Triangle = Y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 游戏手柄轴
|
||||||
|
// ============================================================================
|
||||||
|
namespace GamepadAxis {
|
||||||
|
enum : int {
|
||||||
|
LeftX = SDL_CONTROLLER_AXIS_LEFTX,
|
||||||
|
LeftY = SDL_CONTROLLER_AXIS_LEFTY,
|
||||||
|
RightX = SDL_CONTROLLER_AXIS_RIGHTX,
|
||||||
|
RightY = SDL_CONTROLLER_AXIS_RIGHTY,
|
||||||
|
LeftTrigger = SDL_CONTROLLER_AXIS_TRIGGERLEFT,
|
||||||
|
RightTrigger = SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
||||||
|
Last = SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Easy2D v3.0 - 统一入口头文件
|
||||||
|
// 包含所有公共 API
|
||||||
|
|
||||||
|
// Core
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
|
||||||
|
// Platform
|
||||||
|
#include <extra2d/platform/window.h>
|
||||||
|
#include <extra2d/platform/input.h>
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
#include <extra2d/graphics/render_backend.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/graphics/camera.h>
|
||||||
|
#include <extra2d/graphics/shader_system.h>
|
||||||
|
#include <extra2d/graphics/texture_pool.h>
|
||||||
|
#include <extra2d/graphics/render_target.h>
|
||||||
|
#include <extra2d/graphics/vram_manager.h>
|
||||||
|
|
||||||
|
// Scene
|
||||||
|
#include <extra2d/scene/node.h>
|
||||||
|
#include <extra2d/scene/scene.h>
|
||||||
|
#include <extra2d/scene/sprite.h>
|
||||||
|
#include <extra2d/scene/shape_node.h>
|
||||||
|
#include <extra2d/scene/scene_manager.h>
|
||||||
|
#include <extra2d/scene/transition.h>
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
#include <extra2d/animation/sprite_frame.h>
|
||||||
|
#include <extra2d/animation/sprite_frame_cache.h>
|
||||||
|
#include <extra2d/animation/frame_property.h>
|
||||||
|
#include <extra2d/animation/animation_frame.h>
|
||||||
|
#include <extra2d/animation/animation_clip.h>
|
||||||
|
#include <extra2d/animation/animation_controller.h>
|
||||||
|
#include <extra2d/animation/animation_cache.h>
|
||||||
|
#include <extra2d/animation/interpolation_engine.h>
|
||||||
|
#include <extra2d/animation/animated_sprite.h>
|
||||||
|
#include <extra2d/animation/frame_renderer.h>
|
||||||
|
#include <extra2d/animation/animation_event.h>
|
||||||
|
#include <extra2d/animation/animation_node.h>
|
||||||
|
#include <extra2d/animation/composite_animation.h>
|
||||||
|
#include <extra2d/animation/ani_parser.h>
|
||||||
|
#include <extra2d/animation/ani_binary_parser.h>
|
||||||
|
#include <extra2d/animation/als_parser.h>
|
||||||
|
|
||||||
|
// UI
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
#include <extra2d/ui/button.h>
|
||||||
|
#include <extra2d/ui/text.h>
|
||||||
|
#include <extra2d/ui/label.h>
|
||||||
|
#include <extra2d/ui/progress_bar.h>
|
||||||
|
#include <extra2d/ui/check_box.h>
|
||||||
|
#include <extra2d/ui/radio_button.h>
|
||||||
|
#include <extra2d/ui/slider.h>
|
||||||
|
|
||||||
|
// Action
|
||||||
|
#include <extra2d/action/action.h>
|
||||||
|
#include <extra2d/action/actions.h>
|
||||||
|
#include <extra2d/action/ease.h>
|
||||||
|
|
||||||
|
// Event
|
||||||
|
#include <extra2d/event/event.h>
|
||||||
|
#include <extra2d/event/event_queue.h>
|
||||||
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
|
#include <extra2d/event/input_codes.h>
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
#include <extra2d/audio/audio_engine.h>
|
||||||
|
#include <extra2d/audio/sound.h>
|
||||||
|
|
||||||
|
// Resource
|
||||||
|
#include <extra2d/resource/resource_manager.h>
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <extra2d/utils/timer.h>
|
||||||
|
#include <extra2d/utils/data.h>
|
||||||
|
#include <extra2d/utils/random.h>
|
||||||
|
|
||||||
|
// Spatial
|
||||||
|
#include <extra2d/spatial/spatial_index.h>
|
||||||
|
#include <extra2d/spatial/quadtree.h>
|
||||||
|
#include <extra2d/spatial/spatial_hash.h>
|
||||||
|
#include <extra2d/spatial/spatial_manager.h>
|
||||||
|
|
||||||
|
// Effects
|
||||||
|
#include <extra2d/effects/post_process.h>
|
||||||
|
#include <extra2d/effects/particle_system.h>
|
||||||
|
#include <extra2d/effects/custom_effect_manager.h>
|
||||||
|
|
||||||
|
// Application
|
||||||
|
#include <extra2d/app/application.h>
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Alpha 遮罩 - 存储图片的非透明区域信息
|
||||||
|
// ============================================================================
|
||||||
|
class AlphaMask {
|
||||||
|
public:
|
||||||
|
AlphaMask() = default;
|
||||||
|
AlphaMask(int width, int height);
|
||||||
|
|
||||||
|
/// 从像素数据创建遮罩
|
||||||
|
static AlphaMask createFromPixels(const uint8_t *pixels, int width,
|
||||||
|
int height, int channels);
|
||||||
|
|
||||||
|
/// 获取指定位置的透明度(0-255)
|
||||||
|
uint8_t getAlpha(int x, int y) const;
|
||||||
|
|
||||||
|
/// 检查指定位置是否不透明
|
||||||
|
bool isOpaque(int x, int y, uint8_t threshold = 128) const;
|
||||||
|
|
||||||
|
/// 检查指定位置是否在遮罩范围内
|
||||||
|
bool isValid(int x, int y) const;
|
||||||
|
|
||||||
|
/// 获取遮罩尺寸
|
||||||
|
int getWidth() const { return width_; }
|
||||||
|
int getHeight() const { return height_; }
|
||||||
|
Size getSize() const {
|
||||||
|
return Size(static_cast<float>(width_), static_cast<float>(height_));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取原始数据
|
||||||
|
const std::vector<uint8_t> &getData() const { return data_; }
|
||||||
|
|
||||||
|
/// 检查遮罩是否有效
|
||||||
|
bool isValid() const { return !data_.empty() && width_ > 0 && height_ > 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
std::vector<uint8_t> data_; // Alpha值数组
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 2D 正交相机
|
||||||
|
// ============================================================================
|
||||||
|
class Camera {
|
||||||
|
public:
|
||||||
|
Camera();
|
||||||
|
Camera(float left, float right, float bottom, float top);
|
||||||
|
Camera(const Size &viewport);
|
||||||
|
~Camera() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 位置和变换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPosition(const Vec2 &position);
|
||||||
|
void setPosition(float x, float y);
|
||||||
|
Vec2 getPosition() const { return position_; }
|
||||||
|
|
||||||
|
void setRotation(float degrees);
|
||||||
|
float getRotation() const { return rotation_; }
|
||||||
|
|
||||||
|
void setZoom(float zoom);
|
||||||
|
float getZoom() const { return zoom_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 视口设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setViewport(float left, float right, float bottom, float top);
|
||||||
|
void setViewport(const Rect &rect);
|
||||||
|
Rect getViewport() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 矩阵获取
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
glm::mat4 getViewMatrix() const;
|
||||||
|
glm::mat4 getProjectionMatrix() const;
|
||||||
|
glm::mat4 getViewProjectionMatrix() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 坐标转换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Vec2 screenToWorld(const Vec2 &screenPos) const;
|
||||||
|
Vec2 worldToScreen(const Vec2 &worldPos) const;
|
||||||
|
Vec2 screenToWorld(float x, float y) const;
|
||||||
|
Vec2 worldToScreen(float x, float y) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 移动相机
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void move(const Vec2 &offset);
|
||||||
|
void move(float x, float y);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 边界限制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBounds(const Rect &bounds);
|
||||||
|
void clearBounds();
|
||||||
|
void clampToBounds();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 快捷方法:看向某点
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void lookAt(const Vec2 &target);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 position_ = Vec2::Zero();
|
||||||
|
float rotation_ = 0.0f;
|
||||||
|
float zoom_ = 1.0f;
|
||||||
|
|
||||||
|
float left_ = -1.0f;
|
||||||
|
float right_ = 1.0f;
|
||||||
|
float bottom_ = -1.0f;
|
||||||
|
float top_ = 1.0f;
|
||||||
|
|
||||||
|
Rect bounds_;
|
||||||
|
bool hasBounds_ = false;
|
||||||
|
|
||||||
|
mutable glm::mat4 viewMatrix_;
|
||||||
|
mutable glm::mat4 projMatrix_;
|
||||||
|
mutable glm::mat4 vpMatrix_;
|
||||||
|
mutable bool viewDirty_ = true;
|
||||||
|
mutable bool projDirty_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 字形信息
|
||||||
|
// ============================================================================
|
||||||
|
struct Glyph {
|
||||||
|
float u0, v0; // 纹理坐标左下角
|
||||||
|
float u1, v1; // 纹理坐标右上角
|
||||||
|
float width; // 字形宽度(像素)
|
||||||
|
float height; // 字形高度(像素)
|
||||||
|
float bearingX; // 水平偏移
|
||||||
|
float bearingY; // 垂直偏移
|
||||||
|
float advance; // 前进距离
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 字体图集接口
|
||||||
|
// ============================================================================
|
||||||
|
class FontAtlas {
|
||||||
|
public:
|
||||||
|
virtual ~FontAtlas() = default;
|
||||||
|
|
||||||
|
// 获取字形信息
|
||||||
|
virtual const Glyph *getGlyph(char32_t codepoint) const = 0;
|
||||||
|
|
||||||
|
// 获取纹理
|
||||||
|
virtual class Texture *getTexture() const = 0;
|
||||||
|
|
||||||
|
// 获取字体大小
|
||||||
|
virtual int getFontSize() const = 0;
|
||||||
|
|
||||||
|
virtual float getAscent() const = 0;
|
||||||
|
virtual float getDescent() const = 0;
|
||||||
|
virtual float getLineGap() const = 0;
|
||||||
|
virtual float getLineHeight() const = 0;
|
||||||
|
|
||||||
|
// 计算文字尺寸
|
||||||
|
virtual Vec2 measureText(const std::string &text) = 0;
|
||||||
|
|
||||||
|
// 是否支持 SDF 渲染
|
||||||
|
virtual bool isSDF() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <stb/stb_rect_pack.h>
|
||||||
|
#include <stb/stb_truetype.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 字体图集实现 - 使用 stb_rect_pack 进行矩形打包
|
||||||
|
// ============================================================================
|
||||||
|
class GLFontAtlas : public FontAtlas {
|
||||||
|
public:
|
||||||
|
GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF = false);
|
||||||
|
~GLFontAtlas();
|
||||||
|
|
||||||
|
// FontAtlas 接口实现
|
||||||
|
const Glyph *getGlyph(char32_t codepoint) const override;
|
||||||
|
Texture *getTexture() const override { return texture_.get(); }
|
||||||
|
int getFontSize() const override { return fontSize_; }
|
||||||
|
float getAscent() const override { return ascent_; }
|
||||||
|
float getDescent() const override { return descent_; }
|
||||||
|
float getLineGap() const override { return lineGap_; }
|
||||||
|
float getLineHeight() const override { return ascent_ - descent_ + lineGap_; }
|
||||||
|
Vec2 measureText(const std::string &text) override;
|
||||||
|
bool isSDF() const override { return useSDF_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 图集配置
|
||||||
|
static constexpr int ATLAS_WIDTH = 512;
|
||||||
|
static constexpr int ATLAS_HEIGHT = 512;
|
||||||
|
static constexpr int PADDING = 2; // 字形之间的间距
|
||||||
|
|
||||||
|
int fontSize_;
|
||||||
|
bool useSDF_;
|
||||||
|
mutable std::unique_ptr<GLTexture> texture_;
|
||||||
|
mutable std::unordered_map<char32_t, Glyph> glyphs_;
|
||||||
|
|
||||||
|
// stb_rect_pack 上下文
|
||||||
|
mutable stbrp_context packContext_;
|
||||||
|
mutable std::vector<stbrp_node> packNodes_;
|
||||||
|
mutable int currentY_;
|
||||||
|
|
||||||
|
std::vector<unsigned char> fontData_;
|
||||||
|
stbtt_fontinfo fontInfo_;
|
||||||
|
float scale_;
|
||||||
|
float ascent_;
|
||||||
|
float descent_;
|
||||||
|
float lineGap_;
|
||||||
|
|
||||||
|
void createAtlas();
|
||||||
|
void cacheGlyph(char32_t codepoint) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/render_backend.h>
|
||||||
|
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
||||||
|
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class Window;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 渲染器实现
|
||||||
|
// ============================================================================
|
||||||
|
class GLRenderer : public RenderBackend {
|
||||||
|
public:
|
||||||
|
GLRenderer();
|
||||||
|
~GLRenderer() override;
|
||||||
|
|
||||||
|
// RenderBackend 接口实现
|
||||||
|
bool init(Window* window) override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void beginFrame(const Color& clearColor) override;
|
||||||
|
void endFrame() override;
|
||||||
|
void setViewport(int x, int y, int width, int height) override;
|
||||||
|
void setVSync(bool enabled) override;
|
||||||
|
|
||||||
|
void setBlendMode(BlendMode mode) override;
|
||||||
|
void setViewProjection(const glm::mat4& matrix) override;
|
||||||
|
|
||||||
|
Ptr<Texture> createTexture(int width, int height, const uint8_t* pixels, int channels) override;
|
||||||
|
Ptr<Texture> loadTexture(const std::string& filepath) override;
|
||||||
|
|
||||||
|
void beginSpriteBatch() override;
|
||||||
|
void drawSprite(const Texture& texture, const Rect& destRect, const Rect& srcRect,
|
||||||
|
const Color& tint, float rotation, const Vec2& anchor) override;
|
||||||
|
void drawSprite(const Texture& texture, const Vec2& position, const Color& tint) override;
|
||||||
|
void endSpriteBatch() override;
|
||||||
|
|
||||||
|
void drawLine(const Vec2& start, const Vec2& end, const Color& color, float width) override;
|
||||||
|
void drawRect(const Rect& rect, const Color& color, float width) override;
|
||||||
|
void fillRect(const Rect& rect, const Color& color) override;
|
||||||
|
void drawCircle(const Vec2& center, float radius, const Color& color, int segments, float width) override;
|
||||||
|
void fillCircle(const Vec2& center, float radius, const Color& color, int segments) override;
|
||||||
|
void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color, float width) override;
|
||||||
|
void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color) override;
|
||||||
|
void drawPolygon(const std::vector<Vec2>& points, const Color& color, float width) override;
|
||||||
|
void fillPolygon(const std::vector<Vec2>& points, const Color& color) override;
|
||||||
|
|
||||||
|
Ptr<FontAtlas> createFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false) override;
|
||||||
|
void drawText(const FontAtlas& font, const std::string& text, const Vec2& position, const Color& color) override;
|
||||||
|
void drawText(const FontAtlas& font, const std::string& text, float x, float y, const Color& color) override;
|
||||||
|
|
||||||
|
Stats getStats() const override { return stats_; }
|
||||||
|
void resetStats() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Window* window_;
|
||||||
|
GLSpriteBatch spriteBatch_;
|
||||||
|
GLShader shapeShader_;
|
||||||
|
|
||||||
|
GLuint shapeVao_;
|
||||||
|
GLuint shapeVbo_;
|
||||||
|
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
Stats stats_;
|
||||||
|
bool vsync_;
|
||||||
|
|
||||||
|
void initShapeRendering();
|
||||||
|
void setupBlendMode(BlendMode mode);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/vec3.hpp>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL Shader 程序
|
||||||
|
// ============================================================================
|
||||||
|
class GLShader {
|
||||||
|
public:
|
||||||
|
GLShader();
|
||||||
|
~GLShader();
|
||||||
|
|
||||||
|
// 从源码编译
|
||||||
|
bool compileFromSource(const char* vertexSource, const char* fragmentSource);
|
||||||
|
|
||||||
|
// 从文件加载并编译
|
||||||
|
bool compileFromFile(const std::string& vertexPath, const std::string& fragmentPath);
|
||||||
|
|
||||||
|
// 使用/激活
|
||||||
|
void bind() const;
|
||||||
|
void unbind() const;
|
||||||
|
|
||||||
|
// Uniform 设置
|
||||||
|
void setBool(const std::string& name, bool value);
|
||||||
|
void setInt(const std::string& name, int value);
|
||||||
|
void setFloat(const std::string& name, float value);
|
||||||
|
void setVec2(const std::string& name, const glm::vec2& value);
|
||||||
|
void setVec3(const std::string& name, const glm::vec3& value);
|
||||||
|
void setVec4(const std::string& name, const glm::vec4& value);
|
||||||
|
void setMat4(const std::string& name, const glm::mat4& value);
|
||||||
|
|
||||||
|
// 获取程序 ID
|
||||||
|
GLuint getProgramID() const { return programID_; }
|
||||||
|
|
||||||
|
// 检查是否有效
|
||||||
|
bool isValid() const { return programID_ != 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint programID_;
|
||||||
|
std::unordered_map<std::string, GLint> uniformCache_;
|
||||||
|
|
||||||
|
GLuint compileShader(GLenum type, const char* source);
|
||||||
|
GLint getUniformLocation(const std::string& name);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 精灵批渲染器
|
||||||
|
// ============================================================================
|
||||||
|
class GLSpriteBatch {
|
||||||
|
public:
|
||||||
|
static constexpr size_t MAX_SPRITES = 10000;
|
||||||
|
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
||||||
|
static constexpr size_t INDICES_PER_SPRITE = 6;
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
glm::vec2 position;
|
||||||
|
glm::vec2 texCoord;
|
||||||
|
glm::vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpriteData {
|
||||||
|
glm::vec2 position;
|
||||||
|
glm::vec2 size;
|
||||||
|
glm::vec2 texCoordMin;
|
||||||
|
glm::vec2 texCoordMax;
|
||||||
|
glm::vec4 color;
|
||||||
|
float rotation;
|
||||||
|
glm::vec2 anchor;
|
||||||
|
bool isSDF = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
GLSpriteBatch();
|
||||||
|
~GLSpriteBatch();
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
void begin(const glm::mat4 &viewProjection);
|
||||||
|
void draw(const Texture &texture, const SpriteData &data);
|
||||||
|
void end();
|
||||||
|
|
||||||
|
// 统计
|
||||||
|
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
||||||
|
uint32_t getSpriteCount() const { return spriteCount_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint vao_;
|
||||||
|
GLuint vbo_;
|
||||||
|
GLuint ibo_;
|
||||||
|
GLShader shader_;
|
||||||
|
|
||||||
|
std::vector<Vertex> vertices_;
|
||||||
|
std::vector<GLuint> indices_;
|
||||||
|
|
||||||
|
const Texture *currentTexture_;
|
||||||
|
bool currentIsSDF_;
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
|
||||||
|
uint32_t drawCallCount_;
|
||||||
|
uint32_t spriteCount_;
|
||||||
|
|
||||||
|
void flush();
|
||||||
|
void setupShader();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <extra2d/graphics/alpha_mask.h>
|
||||||
|
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 纹理实现
|
||||||
|
// ============================================================================
|
||||||
|
class GLTexture : public Texture {
|
||||||
|
public:
|
||||||
|
GLTexture(int width, int height, const uint8_t* pixels, int channels);
|
||||||
|
GLTexture(const std::string& filepath);
|
||||||
|
~GLTexture();
|
||||||
|
|
||||||
|
// Texture 接口实现
|
||||||
|
int getWidth() const override { return width_; }
|
||||||
|
int getHeight() const override { return height_; }
|
||||||
|
Size getSize() const override { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
|
||||||
|
int getChannels() const override { return channels_; }
|
||||||
|
PixelFormat getFormat() const override;
|
||||||
|
void* getNativeHandle() const override { return reinterpret_cast<void*>(static_cast<uintptr_t>(textureID_)); }
|
||||||
|
bool isValid() const override { return textureID_ != 0; }
|
||||||
|
void setFilter(bool linear) override;
|
||||||
|
void setWrap(bool repeat) override;
|
||||||
|
|
||||||
|
// 从参数创建纹理的工厂方法
|
||||||
|
static Ptr<Texture> create(int width, int height, PixelFormat format);
|
||||||
|
|
||||||
|
// 加载压缩纹理(KTX/DDS 格式)
|
||||||
|
bool loadCompressed(const std::string& filepath);
|
||||||
|
|
||||||
|
// OpenGL 特定
|
||||||
|
GLuint getTextureID() const { return textureID_; }
|
||||||
|
void bind(unsigned int slot = 0) const;
|
||||||
|
void unbind() const;
|
||||||
|
|
||||||
|
// 获取纹理数据大小(字节),用于 VRAM 跟踪
|
||||||
|
size_t getDataSize() const { return dataSize_; }
|
||||||
|
|
||||||
|
// Alpha 遮罩
|
||||||
|
bool hasAlphaMask() const { return alphaMask_ != nullptr && alphaMask_->isValid(); }
|
||||||
|
const AlphaMask* getAlphaMask() const { return alphaMask_.get(); }
|
||||||
|
void generateAlphaMask(); // 从当前纹理数据生成遮罩
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint textureID_;
|
||||||
|
int width_;
|
||||||
|
int height_;
|
||||||
|
int channels_;
|
||||||
|
PixelFormat format_;
|
||||||
|
size_t dataSize_;
|
||||||
|
|
||||||
|
// 原始像素数据(用于生成遮罩)
|
||||||
|
std::vector<uint8_t> pixelData_;
|
||||||
|
std::unique_ptr<AlphaMask> alphaMask_;
|
||||||
|
|
||||||
|
void createTexture(const uint8_t* pixels);
|
||||||
|
|
||||||
|
// KTX 文件加载
|
||||||
|
bool loadKTX(const std::string& filepath);
|
||||||
|
// DDS 文件加载
|
||||||
|
bool loadDDS(const std::string& filepath);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Window;
|
||||||
|
class Texture;
|
||||||
|
class FontAtlas;
|
||||||
|
class Shader;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染后端类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class BackendType {
|
||||||
|
OpenGL,
|
||||||
|
// Vulkan,
|
||||||
|
// Metal,
|
||||||
|
// D3D11,
|
||||||
|
// D3D12
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 混合模式
|
||||||
|
// ============================================================================
|
||||||
|
enum class BlendMode {
|
||||||
|
None, // 不混合
|
||||||
|
Alpha, // 标准 Alpha 混合
|
||||||
|
Additive, // 加法混合
|
||||||
|
Multiply // 乘法混合
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染后端抽象接口
|
||||||
|
// ============================================================================
|
||||||
|
class RenderBackend {
|
||||||
|
public:
|
||||||
|
virtual ~RenderBackend() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual bool init(Window *window) = 0;
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 帧管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void beginFrame(const Color &clearColor) = 0;
|
||||||
|
virtual void endFrame() = 0;
|
||||||
|
virtual void setViewport(int x, int y, int width, int height) = 0;
|
||||||
|
virtual void setVSync(bool enabled) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 状态设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void setBlendMode(BlendMode mode) = 0;
|
||||||
|
virtual void setViewProjection(const glm::mat4 &matrix) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 纹理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual Ptr<Texture> createTexture(int width, int height,
|
||||||
|
const uint8_t *pixels, int channels) = 0;
|
||||||
|
virtual Ptr<Texture> loadTexture(const std::string &filepath) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 精灵批渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void beginSpriteBatch() = 0;
|
||||||
|
virtual void drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
|
const Rect &srcRect, const Color &tint,
|
||||||
|
float rotation, const Vec2 &anchor) = 0;
|
||||||
|
virtual void drawSprite(const Texture &texture, const Vec2 &position,
|
||||||
|
const Color &tint) = 0;
|
||||||
|
virtual void endSpriteBatch() = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 形状渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
||||||
|
float width = 1.0f) = 0;
|
||||||
|
virtual void drawRect(const Rect &rect, const Color &color,
|
||||||
|
float width = 1.0f) = 0;
|
||||||
|
virtual void fillRect(const Rect &rect, const Color &color) = 0;
|
||||||
|
virtual void drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments = 32, float width = 1.0f) = 0;
|
||||||
|
virtual void fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments = 32) = 0;
|
||||||
|
virtual void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color, float width = 1.0f) = 0;
|
||||||
|
virtual void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color) = 0;
|
||||||
|
virtual void drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
||||||
|
float width = 1.0f) = 0;
|
||||||
|
virtual void fillPolygon(const std::vector<Vec2> &points,
|
||||||
|
const Color &color) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文字渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual Ptr<FontAtlas> createFontAtlas(const std::string &filepath,
|
||||||
|
int fontSize, bool useSDF = false) = 0;
|
||||||
|
virtual void drawText(const FontAtlas &font, const std::string &text,
|
||||||
|
const Vec2 &position, const Color &color) = 0;
|
||||||
|
virtual void drawText(const FontAtlas &font, const std::string &text, float x,
|
||||||
|
float y, const Color &color) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 统计信息
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
struct Stats {
|
||||||
|
uint32_t drawCalls = 0;
|
||||||
|
uint32_t triangleCount = 0;
|
||||||
|
uint32_t textureBinds = 0;
|
||||||
|
uint32_t shaderBinds = 0;
|
||||||
|
};
|
||||||
|
virtual Stats getStats() const = 0;
|
||||||
|
virtual void resetStats() = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 工厂方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static UniquePtr<RenderBackend> create(BackendType type);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Texture;
|
||||||
|
class FontAtlas;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染命令类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class RenderCommandType {
|
||||||
|
Sprite,
|
||||||
|
Line,
|
||||||
|
Rect,
|
||||||
|
FilledRect,
|
||||||
|
Circle,
|
||||||
|
FilledCircle,
|
||||||
|
Triangle,
|
||||||
|
FilledTriangle,
|
||||||
|
Polygon,
|
||||||
|
FilledPolygon,
|
||||||
|
Text
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 精灵数据
|
||||||
|
// ============================================================================
|
||||||
|
struct SpriteData {
|
||||||
|
Ptr<Texture> texture;
|
||||||
|
Rect destRect;
|
||||||
|
Rect srcRect;
|
||||||
|
Color tint;
|
||||||
|
float rotation;
|
||||||
|
Vec2 anchor;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 直线数据
|
||||||
|
// ============================================================================
|
||||||
|
struct LineData {
|
||||||
|
Vec2 start;
|
||||||
|
Vec2 end;
|
||||||
|
Color color;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 矩形数据
|
||||||
|
// ============================================================================
|
||||||
|
struct RectData {
|
||||||
|
Rect rect;
|
||||||
|
Color color;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 圆形数据
|
||||||
|
// ============================================================================
|
||||||
|
struct CircleData {
|
||||||
|
Vec2 center;
|
||||||
|
float radius;
|
||||||
|
Color color;
|
||||||
|
int segments;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 三角形数据
|
||||||
|
// ============================================================================
|
||||||
|
struct TriangleData {
|
||||||
|
Vec2 p1;
|
||||||
|
Vec2 p2;
|
||||||
|
Vec2 p3;
|
||||||
|
Color color;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 多边形数据
|
||||||
|
// ============================================================================
|
||||||
|
struct PolygonData {
|
||||||
|
std::vector<Vec2> points;
|
||||||
|
Color color;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 文字数据
|
||||||
|
// ============================================================================
|
||||||
|
struct TextData {
|
||||||
|
Ptr<FontAtlas> font;
|
||||||
|
std::string text;
|
||||||
|
Vec2 position;
|
||||||
|
Color color;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染命令
|
||||||
|
// ============================================================================
|
||||||
|
struct RenderCommand {
|
||||||
|
RenderCommandType type;
|
||||||
|
int zOrder;
|
||||||
|
|
||||||
|
std::variant<SpriteData, LineData, RectData, CircleData, TriangleData,
|
||||||
|
PolygonData, TextData>
|
||||||
|
data;
|
||||||
|
|
||||||
|
// 用于排序
|
||||||
|
bool operator<(const RenderCommand &other) const {
|
||||||
|
return zOrder < other.zOrder;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,333 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染目标配置
|
||||||
|
// ============================================================================
|
||||||
|
struct RenderTargetConfig {
|
||||||
|
int width = 800; // 宽度
|
||||||
|
int height = 600; // 高度
|
||||||
|
PixelFormat colorFormat = PixelFormat::RGBA8; // 颜色格式
|
||||||
|
bool hasDepth = true; // 是否包含深度缓冲
|
||||||
|
bool hasDepthBuffer = true; // 兼容旧API的别名 (同hasDepth)
|
||||||
|
bool hasStencil = false; // 是否包含模板缓冲
|
||||||
|
int samples = 1; // 多重采样数 (1 = 无MSAA)
|
||||||
|
bool autoResize = true; // 是否自动调整大小
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染目标 - 基于FBO的离屏渲染
|
||||||
|
// ============================================================================
|
||||||
|
class RenderTarget {
|
||||||
|
public:
|
||||||
|
RenderTarget();
|
||||||
|
~RenderTarget();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
RenderTarget(const RenderTarget &) = delete;
|
||||||
|
RenderTarget &operator=(const RenderTarget &) = delete;
|
||||||
|
|
||||||
|
// 允许移动
|
||||||
|
RenderTarget(RenderTarget &&other) noexcept;
|
||||||
|
RenderTarget &operator=(RenderTarget &&other) noexcept;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 创建和销毁
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建渲染目标
|
||||||
|
*/
|
||||||
|
bool create(const RenderTargetConfig &config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染目标(create的别名,兼容旧API)
|
||||||
|
*/
|
||||||
|
bool init(const RenderTargetConfig &config) { return create(config); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从现有纹理创建渲染目标
|
||||||
|
*/
|
||||||
|
bool createFromTexture(Ptr<Texture> texture, bool hasDepth = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁渲染目标
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染目标(destroy的别名,兼容旧API)
|
||||||
|
*/
|
||||||
|
void shutdown() { destroy(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有效
|
||||||
|
*/
|
||||||
|
bool isValid() const { return fbo_ != 0; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 尺寸和格式
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
int getWidth() const { return width_; }
|
||||||
|
int getHeight() const { return height_; }
|
||||||
|
Vec2 getSize() const {
|
||||||
|
return Vec2(static_cast<float>(width_), static_cast<float>(height_));
|
||||||
|
}
|
||||||
|
PixelFormat getColorFormat() const { return colorFormat_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 绑定和解绑
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定为当前渲染目标
|
||||||
|
*/
|
||||||
|
void bind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑(恢复默认渲染目标)
|
||||||
|
*/
|
||||||
|
void unbind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除渲染目标
|
||||||
|
*/
|
||||||
|
void clear(const Color &color = Colors::Transparent);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 纹理访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取颜色纹理
|
||||||
|
*/
|
||||||
|
Ptr<Texture> getColorTexture() const { return colorTexture_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取深度纹理(如果有)
|
||||||
|
*/
|
||||||
|
Ptr<Texture> getDepthTexture() const { return depthTexture_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 视口和裁剪
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口(相对于渲染目标)
|
||||||
|
*/
|
||||||
|
void setViewport(int x, int y, int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取完整视口
|
||||||
|
*/
|
||||||
|
void getFullViewport(int &x, int &y, int &width, int &height) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 工具方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 调整大小(会销毁并重新创建)
|
||||||
|
*/
|
||||||
|
bool resize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 复制到另一个渲染目标
|
||||||
|
*/
|
||||||
|
void copyTo(RenderTarget &target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 复制到另一个渲染目标(blitTo的别名,兼容旧API)
|
||||||
|
* @param target 目标渲染目标
|
||||||
|
* @param color 是否复制颜色缓冲
|
||||||
|
* @param depth 是否复制深度缓冲
|
||||||
|
*/
|
||||||
|
void blitTo(RenderTarget &target, bool color = true, bool depth = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 复制到屏幕
|
||||||
|
*/
|
||||||
|
void copyToScreen(int screenWidth, int screenHeight);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存为图像文件
|
||||||
|
*/
|
||||||
|
bool saveToFile(const std::string &filepath);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建渲染目标的静态工厂方法
|
||||||
|
*/
|
||||||
|
static Ptr<RenderTarget> createFromConfig(const RenderTargetConfig &config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前绑定的渲染目标ID
|
||||||
|
*/
|
||||||
|
static GLuint getCurrentFBO();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定默认渲染目标(屏幕)
|
||||||
|
*/
|
||||||
|
static void bindDefault();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取FBO ID(供内部使用)
|
||||||
|
*/
|
||||||
|
GLuint getFBO() const { return fbo_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
GLuint fbo_ = 0; // 帧缓冲对象
|
||||||
|
GLuint rbo_ = 0; // 渲染缓冲对象(深度/模板)
|
||||||
|
|
||||||
|
Ptr<Texture> colorTexture_; // 颜色纹理
|
||||||
|
Ptr<Texture> depthTexture_; // 深度纹理(可选)
|
||||||
|
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
PixelFormat colorFormat_ = PixelFormat::RGBA8;
|
||||||
|
bool hasDepth_ = false;
|
||||||
|
bool hasStencil_ = false;
|
||||||
|
int samples_ = 1;
|
||||||
|
|
||||||
|
bool createFBO();
|
||||||
|
void deleteFBO();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 多重采样渲染目标(用于MSAA)
|
||||||
|
// ============================================================================
|
||||||
|
class MultisampleRenderTarget : public RenderTarget {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 创建多重采样渲染目标
|
||||||
|
*/
|
||||||
|
bool create(int width, int height, int samples = 4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解析到普通渲染目标(用于显示)
|
||||||
|
*/
|
||||||
|
void resolveTo(RenderTarget &target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁渲染目标
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint colorRBO_ = 0; // 多重采样颜色渲染缓冲
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染目标栈(用于嵌套渲染)
|
||||||
|
// ============================================================================
|
||||||
|
class RenderTargetStack {
|
||||||
|
public:
|
||||||
|
static RenderTargetStack &getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 压入渲染目标
|
||||||
|
*/
|
||||||
|
void push(RenderTarget *target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 弹出渲染目标
|
||||||
|
*/
|
||||||
|
void pop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前渲染目标
|
||||||
|
*/
|
||||||
|
RenderTarget *getCurrent() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取栈大小
|
||||||
|
*/
|
||||||
|
size_t size() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空栈
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderTargetStack() = default;
|
||||||
|
~RenderTargetStack() = default;
|
||||||
|
|
||||||
|
std::vector<RenderTarget *> stack_;
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染目标管理器 - 全局渲染目标管理
|
||||||
|
// ============================================================================
|
||||||
|
class RenderTargetManager {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取单例实例
|
||||||
|
*/
|
||||||
|
static RenderTargetManager &getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染目标管理器
|
||||||
|
* @param width 默认宽度
|
||||||
|
* @param height 默认高度
|
||||||
|
*/
|
||||||
|
bool init(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染目标管理器
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建新的渲染目标
|
||||||
|
*/
|
||||||
|
Ptr<RenderTarget> createRenderTarget(const RenderTargetConfig &config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取默认渲染目标
|
||||||
|
*/
|
||||||
|
RenderTarget *getDefaultRenderTarget() const {
|
||||||
|
return defaultRenderTarget_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 调整所有受管渲染目标的大小
|
||||||
|
*/
|
||||||
|
void resize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否已初始化
|
||||||
|
*/
|
||||||
|
bool isInitialized() const { return initialized_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderTargetManager() = default;
|
||||||
|
~RenderTargetManager() = default;
|
||||||
|
RenderTargetManager(const RenderTargetManager &) = delete;
|
||||||
|
RenderTargetManager &operator=(const RenderTargetManager &) = delete;
|
||||||
|
|
||||||
|
Ptr<RenderTarget> defaultRenderTarget_;
|
||||||
|
std::vector<Ptr<RenderTarget>> renderTargets_;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_RENDER_TARGET_STACK() ::extra2d::RenderTargetStack::getInstance()
|
||||||
|
#define E2D_RENDER_TARGET_MANAGER() \
|
||||||
|
::extra2d::RenderTargetManager::getInstance()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,319 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
struct WaterParams {
|
||||||
|
float waveSpeed = 1.0f;
|
||||||
|
float waveAmplitude = 0.02f;
|
||||||
|
float waveFrequency = 4.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OutlineParams {
|
||||||
|
Color color = Colors::Black;
|
||||||
|
float thickness = 2.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DistortionParams {
|
||||||
|
float distortionAmount = 0.02f;
|
||||||
|
float timeScale = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PixelateParams {
|
||||||
|
float pixelSize = 8.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InvertParams {
|
||||||
|
float strength = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GrayscaleParams {
|
||||||
|
float intensity = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BlurParams {
|
||||||
|
float radius = 5.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace ShaderSource {
|
||||||
|
|
||||||
|
static const char* StandardVert = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec2 a_texCoord;
|
||||||
|
layout(location = 2) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_viewProjection;
|
||||||
|
uniform mat4 u_model;
|
||||||
|
|
||||||
|
out vec2 v_texCoord;
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
|
v_color = a_color;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* StandardFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture(u_texture, v_texCoord);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* WaterFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_waveSpeed;
|
||||||
|
uniform float u_waveAmplitude;
|
||||||
|
uniform float u_waveFrequency;
|
||||||
|
uniform float u_time;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = v_texCoord;
|
||||||
|
|
||||||
|
// 水波纹效果
|
||||||
|
float wave = sin(uv.y * u_waveFrequency + u_time * u_waveSpeed) * u_waveAmplitude;
|
||||||
|
uv.x += wave;
|
||||||
|
|
||||||
|
vec4 texColor = texture(u_texture, uv);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* OutlineFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform vec4 u_outlineColor;
|
||||||
|
uniform float u_thickness;
|
||||||
|
uniform vec2 u_textureSize;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 color = texture(u_texture, v_texCoord);
|
||||||
|
|
||||||
|
// 简单的描边检测
|
||||||
|
float alpha = 0.0;
|
||||||
|
vec2 offset = u_thickness / u_textureSize;
|
||||||
|
|
||||||
|
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
|
||||||
|
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
|
||||||
|
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
|
||||||
|
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
|
||||||
|
|
||||||
|
if (color.a < 0.1 && alpha > 0.0) {
|
||||||
|
fragColor = u_outlineColor;
|
||||||
|
} else {
|
||||||
|
fragColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* DistortionFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_distortionAmount;
|
||||||
|
uniform float u_time;
|
||||||
|
uniform float u_timeScale;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = v_texCoord;
|
||||||
|
|
||||||
|
// 扭曲效果
|
||||||
|
float t = u_time * u_timeScale;
|
||||||
|
float dx = sin(uv.y * 10.0 + t) * u_distortionAmount;
|
||||||
|
float dy = cos(uv.x * 10.0 + t) * u_distortionAmount;
|
||||||
|
uv += vec2(dx, dy);
|
||||||
|
|
||||||
|
vec4 texColor = texture(u_texture, uv);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* PixelateFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_pixelSize;
|
||||||
|
uniform vec2 u_textureSize;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 pixel = u_pixelSize / u_textureSize;
|
||||||
|
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
|
||||||
|
|
||||||
|
vec4 texColor = texture(u_texture, uv);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* InvertFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_strength;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
||||||
|
vec3 inverted = vec3(1.0) - texColor.rgb;
|
||||||
|
texColor.rgb = mix(texColor.rgb, inverted, u_strength);
|
||||||
|
|
||||||
|
fragColor = texColor;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* GrayscaleFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_intensity;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
||||||
|
|
||||||
|
float gray = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
texColor.rgb = mix(texColor.rgb, vec3(gray), u_intensity);
|
||||||
|
|
||||||
|
fragColor = texColor;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* BlurFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_radius;
|
||||||
|
uniform vec2 u_textureSize;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 texel = u_radius / u_textureSize;
|
||||||
|
|
||||||
|
vec4 sum = vec4(0.0);
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, -1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, -1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, -1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 0.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 0.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 0.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 1.0));
|
||||||
|
|
||||||
|
vec4 texColor = sum / 9.0;
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
} // namespace ShaderSource
|
||||||
|
|
||||||
|
class ShaderPreset {
|
||||||
|
public:
|
||||||
|
static Ptr<GLShader> Water(const WaterParams& params);
|
||||||
|
static Ptr<GLShader> Outline(const OutlineParams& params);
|
||||||
|
static Ptr<GLShader> Distortion(const DistortionParams& params);
|
||||||
|
static Ptr<GLShader> Pixelate(const PixelateParams& params);
|
||||||
|
static Ptr<GLShader> Invert(const InvertParams& params);
|
||||||
|
static Ptr<GLShader> Grayscale(const GrayscaleParams& params);
|
||||||
|
static Ptr<GLShader> Blur(const BlurParams& params);
|
||||||
|
|
||||||
|
static Ptr<GLShader> GrayscaleOutline(const GrayscaleParams& grayParams,
|
||||||
|
const OutlineParams& outlineParams);
|
||||||
|
static Ptr<GLShader> PixelateInvert(const PixelateParams& pixParams,
|
||||||
|
const InvertParams& invParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Shader参数绑定回调
|
||||||
|
// ============================================================================
|
||||||
|
using ShaderBindCallback = std::function<void(GLShader &)>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Shader系统 - 管理所有Shader的加载、缓存和热重载
|
||||||
|
// ============================================================================
|
||||||
|
class ShaderSystem {
|
||||||
|
public:
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 单例访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static ShaderSystem &getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Shader加载
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件加载Shader
|
||||||
|
* @param name Shader名称(用于缓存)
|
||||||
|
* @param vertPath 顶点着色器文件路径
|
||||||
|
* @param fragPath 片段着色器文件路径
|
||||||
|
* @return 加载的Shader,失败返回nullptr
|
||||||
|
*/
|
||||||
|
Ptr<GLShader> loadFromFile(const std::string &name,
|
||||||
|
const std::string &vertPath,
|
||||||
|
const std::string &fragPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从源码字符串加载Shader
|
||||||
|
* @param name Shader名称(用于缓存)
|
||||||
|
* @param vertSource 顶点着色器源码
|
||||||
|
* @param fragSource 片段着色器源码
|
||||||
|
* @return 加载的Shader,失败返回nullptr
|
||||||
|
*/
|
||||||
|
Ptr<GLShader> loadFromSource(const std::string &name,
|
||||||
|
const std::string &vertSource,
|
||||||
|
const std::string &fragSource);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从已编译的程序获取Shader
|
||||||
|
* @param name Shader名称
|
||||||
|
* @return 缓存的Shader,不存在返回nullptr
|
||||||
|
*/
|
||||||
|
Ptr<GLShader> get(const std::string &name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查Shader是否存在
|
||||||
|
*/
|
||||||
|
bool has(const std::string &name) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Shader移除
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void remove(const std::string &name);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 热重载支持
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 启用/禁用文件监视
|
||||||
|
*/
|
||||||
|
void setFileWatching(bool enable);
|
||||||
|
bool isFileWatching() const { return fileWatching_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新文件监视(在主循环中调用)
|
||||||
|
*/
|
||||||
|
void updateFileWatching();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重载指定Shader
|
||||||
|
*/
|
||||||
|
bool reload(const std::string &name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重载所有Shader
|
||||||
|
*/
|
||||||
|
void reloadAll();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 内置Shader获取
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Ptr<GLShader> getBuiltinSpriteShader();
|
||||||
|
Ptr<GLShader> getBuiltinParticleShader();
|
||||||
|
Ptr<GLShader> getBuiltinPostProcessShader();
|
||||||
|
Ptr<GLShader> getBuiltinShapeShader();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 工具方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件读取文本内容
|
||||||
|
*/
|
||||||
|
static std::string readFile(const std::string &filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取Shader文件的最后修改时间
|
||||||
|
*/
|
||||||
|
static uint64_t getFileModifiedTime(const std::string &filepath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
ShaderSystem() = default;
|
||||||
|
~ShaderSystem() = default;
|
||||||
|
ShaderSystem(const ShaderSystem &) = delete;
|
||||||
|
ShaderSystem &operator=(const ShaderSystem &) = delete;
|
||||||
|
|
||||||
|
struct ShaderInfo {
|
||||||
|
Ptr<GLShader> shader;
|
||||||
|
std::string vertPath;
|
||||||
|
std::string fragPath;
|
||||||
|
uint64_t vertModifiedTime;
|
||||||
|
uint64_t fragModifiedTime;
|
||||||
|
bool isBuiltin;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, ShaderInfo> shaders_;
|
||||||
|
bool fileWatching_ = false;
|
||||||
|
float watchTimer_ = 0.0f;
|
||||||
|
static constexpr float WATCH_INTERVAL = 1.0f; // 检查间隔(秒)
|
||||||
|
|
||||||
|
// 内置Shader缓存
|
||||||
|
Ptr<GLShader> builtinSpriteShader_;
|
||||||
|
Ptr<GLShader> builtinParticleShader_;
|
||||||
|
Ptr<GLShader> builtinPostProcessShader_;
|
||||||
|
Ptr<GLShader> builtinShapeShader_;
|
||||||
|
|
||||||
|
bool loadBuiltinShaders();
|
||||||
|
void checkAndReload();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Shader参数包装器 - 简化Uniform设置
|
||||||
|
// ============================================================================
|
||||||
|
class ShaderParams {
|
||||||
|
public:
|
||||||
|
explicit ShaderParams(GLShader &shader);
|
||||||
|
|
||||||
|
ShaderParams &setBool(const std::string &name, bool value);
|
||||||
|
ShaderParams &setInt(const std::string &name, int value);
|
||||||
|
ShaderParams &setFloat(const std::string &name, float value);
|
||||||
|
ShaderParams &setVec2(const std::string &name, const glm::vec2 &value);
|
||||||
|
ShaderParams &setVec3(const std::string &name, const glm::vec3 &value);
|
||||||
|
ShaderParams &setVec4(const std::string &name, const glm::vec4 &value);
|
||||||
|
ShaderParams &setMat4(const std::string &name, const glm::mat4 &value);
|
||||||
|
ShaderParams &setColor(const std::string &name, const Color &color);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLShader &shader_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_SHADER_SYSTEM() ::extra2d::ShaderSystem::getInstance()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 像素格式枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class PixelFormat {
|
||||||
|
R8, // 单通道灰度
|
||||||
|
RG8, // 双通道
|
||||||
|
RGB8, // RGB 24位
|
||||||
|
RGBA8, // RGBA 32位(默认)
|
||||||
|
RGB16F, // RGB 半精度浮点
|
||||||
|
RGBA16F, // RGBA 半精度浮点
|
||||||
|
RGB32F, // RGB 全精度浮点
|
||||||
|
RGBA32F, // RGBA 全精度浮点
|
||||||
|
Depth16, // 16位深度
|
||||||
|
Depth24, // 24位深度
|
||||||
|
Depth32F, // 32位浮点深度
|
||||||
|
Depth24Stencil8, // 24位深度 + 8位模板
|
||||||
|
|
||||||
|
// 压缩纹理格式
|
||||||
|
ETC2_RGB8, // ETC2 RGB 压缩
|
||||||
|
ETC2_RGBA8, // ETC2 RGBA 压缩
|
||||||
|
ASTC_4x4, // ASTC 4x4 压缩
|
||||||
|
ASTC_6x6, // ASTC 6x6 压缩
|
||||||
|
ASTC_8x8 // ASTC 8x8 压缩
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 纹理接口
|
||||||
|
// ============================================================================
|
||||||
|
class Texture {
|
||||||
|
public:
|
||||||
|
virtual ~Texture() = default;
|
||||||
|
|
||||||
|
// 获取尺寸
|
||||||
|
virtual int getWidth() const = 0;
|
||||||
|
virtual int getHeight() const = 0;
|
||||||
|
virtual Size getSize() const = 0;
|
||||||
|
|
||||||
|
// 获取通道数
|
||||||
|
virtual int getChannels() const = 0;
|
||||||
|
|
||||||
|
// 获取像素格式
|
||||||
|
virtual PixelFormat getFormat() const = 0;
|
||||||
|
|
||||||
|
// 获取原始句柄(用于底层渲染)
|
||||||
|
virtual void* getNativeHandle() const = 0;
|
||||||
|
|
||||||
|
// 是否有效
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
|
||||||
|
// 设置过滤模式
|
||||||
|
virtual void setFilter(bool linear) = 0;
|
||||||
|
|
||||||
|
// 设置环绕模式
|
||||||
|
virtual void setWrap(bool repeat) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 纹理池配置
|
||||||
|
// ============================================================================
|
||||||
|
struct TexturePoolConfig {
|
||||||
|
size_t maxCacheSize = 64 * 1024 * 1024; // 最大缓存大小 (64MB)
|
||||||
|
size_t maxTextureCount = 256; // 最大纹理数量
|
||||||
|
float unloadInterval = 30.0f; // 自动清理间隔 (秒)
|
||||||
|
bool enableAsyncLoad = true; // 启用异步加载
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 纹理池 - 使用LRU缓存策略管理纹理复用
|
||||||
|
// ============================================================================
|
||||||
|
class TexturePool {
|
||||||
|
public:
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 单例访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static TexturePool &getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init(const TexturePoolConfig &config = TexturePoolConfig{});
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 纹理获取
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件获取纹理(带缓存)
|
||||||
|
* @param filepath 纹理文件路径
|
||||||
|
* @return 纹理对象,失败返回nullptr
|
||||||
|
*/
|
||||||
|
Ptr<Texture> get(const std::string &filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 异步加载纹理
|
||||||
|
* @param filepath 纹理文件路径
|
||||||
|
* @param callback 加载完成回调
|
||||||
|
*/
|
||||||
|
void getAsync(const std::string &filepath,
|
||||||
|
std::function<void(Ptr<Texture>)> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从内存数据创建纹理(自动缓存)
|
||||||
|
* @param name 纹理名称(用于缓存键)
|
||||||
|
* @param data 图像数据
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
* @param format 像素格式
|
||||||
|
* @return 纹理对象
|
||||||
|
*/
|
||||||
|
Ptr<Texture> createFromData(const std::string &name, const uint8_t *data,
|
||||||
|
int width, int height,
|
||||||
|
PixelFormat format = PixelFormat::RGBA8);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 缓存管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 手动添加纹理到缓存
|
||||||
|
*/
|
||||||
|
void add(const std::string &key, Ptr<Texture> texture);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从缓存移除纹理
|
||||||
|
*/
|
||||||
|
void remove(const std::string &key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查纹理是否在缓存中
|
||||||
|
*/
|
||||||
|
bool has(const std::string &key) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空所有缓存
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清理未使用的纹理(LRU策略)
|
||||||
|
* @param targetSize 目标缓存大小
|
||||||
|
*/
|
||||||
|
void trim(size_t targetSize);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 统计信息
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前缓存的纹理数量
|
||||||
|
*/
|
||||||
|
size_t getTextureCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前缓存的总大小(字节)
|
||||||
|
*/
|
||||||
|
size_t getCacheSize() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓存命中率
|
||||||
|
*/
|
||||||
|
float getHitRate() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打印统计信息
|
||||||
|
*/
|
||||||
|
void printStats() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 自动清理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新(在主循环中调用,用于自动清理)
|
||||||
|
*/
|
||||||
|
void update(float dt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置自动清理间隔
|
||||||
|
*/
|
||||||
|
void setAutoUnloadInterval(float interval);
|
||||||
|
|
||||||
|
private:
|
||||||
|
TexturePool() = default;
|
||||||
|
~TexturePool() = default;
|
||||||
|
TexturePool(const TexturePool &) = delete;
|
||||||
|
TexturePool &operator=(const TexturePool &) = delete;
|
||||||
|
|
||||||
|
// 侵入式LRU节点 - 减少内存分配和指针跳转
|
||||||
|
struct LRUNode {
|
||||||
|
std::string key;
|
||||||
|
uint32_t prev; // 数组索引,0表示无效
|
||||||
|
uint32_t next; // 数组索引,0表示无效
|
||||||
|
bool valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 缓存项 - 紧凑存储
|
||||||
|
struct CacheEntry {
|
||||||
|
Ptr<Texture> texture;
|
||||||
|
size_t size; // 纹理大小(字节)
|
||||||
|
float lastAccessTime; // 最后访问时间
|
||||||
|
uint32_t accessCount; // 访问次数
|
||||||
|
uint32_t lruIndex; // LRU节点索引
|
||||||
|
};
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
TexturePoolConfig config_;
|
||||||
|
|
||||||
|
// 缓存存储
|
||||||
|
std::unordered_map<std::string, CacheEntry> cache_;
|
||||||
|
|
||||||
|
// 侵入式LRU链表 - 使用数组索引代替指针,提高缓存局部性
|
||||||
|
std::vector<LRUNode> lruNodes_;
|
||||||
|
uint32_t lruHead_ = 0; // 最近使用
|
||||||
|
uint32_t lruTail_ = 0; // 最久未使用
|
||||||
|
uint32_t freeList_ = 0; // 空闲节点链表
|
||||||
|
|
||||||
|
// 统计
|
||||||
|
size_t totalSize_ = 0;
|
||||||
|
uint64_t hitCount_ = 0;
|
||||||
|
uint64_t missCount_ = 0;
|
||||||
|
float autoUnloadTimer_ = 0.0f;
|
||||||
|
bool initialized_ = false;
|
||||||
|
|
||||||
|
// 异步加载队列
|
||||||
|
struct AsyncLoadTask {
|
||||||
|
std::string filepath;
|
||||||
|
std::function<void(Ptr<Texture>)> callback;
|
||||||
|
};
|
||||||
|
std::vector<AsyncLoadTask> asyncTasks_;
|
||||||
|
|
||||||
|
// LRU操作
|
||||||
|
uint32_t allocateLRUNode(const std::string& key);
|
||||||
|
void freeLRUNode(uint32_t index);
|
||||||
|
void moveToFront(uint32_t index);
|
||||||
|
void removeFromList(uint32_t index);
|
||||||
|
std::string evictLRU();
|
||||||
|
|
||||||
|
// 内部方法
|
||||||
|
void touch(const std::string &key);
|
||||||
|
void evict();
|
||||||
|
Ptr<Texture> loadTexture(const std::string &filepath);
|
||||||
|
size_t calculateTextureSize(int width, int height, PixelFormat format);
|
||||||
|
void processAsyncTasks();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_TEXTURE_POOL() ::extra2d::TexturePool::getInstance()
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// VRAM 管理器 - 跟踪显存使用情况
|
||||||
|
// ============================================================================
|
||||||
|
class VRAMManager {
|
||||||
|
public:
|
||||||
|
static VRAMManager& getInstance();
|
||||||
|
|
||||||
|
// 纹理显存跟踪
|
||||||
|
void allocTexture(size_t size);
|
||||||
|
void freeTexture(size_t size);
|
||||||
|
|
||||||
|
// VBO/FBO 显存跟踪
|
||||||
|
void allocBuffer(size_t size);
|
||||||
|
void freeBuffer(size_t size);
|
||||||
|
|
||||||
|
// 查询显存使用情况
|
||||||
|
size_t getUsedVRAM() const;
|
||||||
|
size_t getTextureVRAM() const;
|
||||||
|
size_t getBufferVRAM() const;
|
||||||
|
size_t getAvailableVRAM() const;
|
||||||
|
|
||||||
|
// 显存预算管理
|
||||||
|
void setVRAMBudget(size_t budget);
|
||||||
|
size_t getVRAMBudget() const;
|
||||||
|
bool isOverBudget() const;
|
||||||
|
|
||||||
|
// 统计信息
|
||||||
|
void printStats() const;
|
||||||
|
|
||||||
|
// 重置计数器
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
private:
|
||||||
|
VRAMManager();
|
||||||
|
~VRAMManager() = default;
|
||||||
|
VRAMManager(const VRAMManager&) = delete;
|
||||||
|
VRAMManager& operator=(const VRAMManager&) = delete;
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
|
||||||
|
size_t textureVRAM_;
|
||||||
|
size_t bufferVRAM_;
|
||||||
|
size_t vramBudget_;
|
||||||
|
|
||||||
|
// 统计
|
||||||
|
uint32_t textureAllocCount_;
|
||||||
|
uint32_t textureFreeCount_;
|
||||||
|
uint32_t bufferAllocCount_;
|
||||||
|
uint32_t bufferFreeCount_;
|
||||||
|
size_t peakTextureVRAM_;
|
||||||
|
size_t peakBufferVRAM_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/event/input_codes.h>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 鼠标按钮枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class MouseButton {
|
||||||
|
Left = 0,
|
||||||
|
Right = 1,
|
||||||
|
Middle = 2,
|
||||||
|
Button4 = 3,
|
||||||
|
Button5 = 4,
|
||||||
|
Button6 = 5,
|
||||||
|
Button7 = 6,
|
||||||
|
Button8 = 7,
|
||||||
|
Count = 8
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Input 类 - 跨平台输入管理
|
||||||
|
// 支持: 键盘、鼠标、手柄、触摸屏
|
||||||
|
// ============================================================================
|
||||||
|
class Input {
|
||||||
|
public:
|
||||||
|
Input();
|
||||||
|
~Input();
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
void init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// 每帧更新
|
||||||
|
void update();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 键盘输入
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isKeyDown(int keyCode) const;
|
||||||
|
bool isKeyPressed(int keyCode) const;
|
||||||
|
bool isKeyReleased(int keyCode) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 手柄按钮
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isButtonDown(int button) const;
|
||||||
|
bool isButtonPressed(int button) const;
|
||||||
|
bool isButtonReleased(int button) const;
|
||||||
|
|
||||||
|
// 摇杆
|
||||||
|
Vec2 getLeftStick() const;
|
||||||
|
Vec2 getRightStick() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 鼠标输入
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isMouseDown(MouseButton button) const;
|
||||||
|
bool isMousePressed(MouseButton button) const;
|
||||||
|
bool isMouseReleased(MouseButton button) const;
|
||||||
|
|
||||||
|
Vec2 getMousePosition() const;
|
||||||
|
Vec2 getMouseDelta() const;
|
||||||
|
float getMouseScroll() const { return mouseScroll_; }
|
||||||
|
float getMouseScrollDelta() const { return mouseScroll_ - prevMouseScroll_; }
|
||||||
|
|
||||||
|
void setMousePosition(const Vec2 &position);
|
||||||
|
void setMouseVisible(bool visible);
|
||||||
|
void setMouseLocked(bool locked);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 触摸屏 (Switch 原生支持,PC 端模拟或禁用)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isTouching() const { return touching_; }
|
||||||
|
Vec2 getTouchPosition() const { return touchPosition_; }
|
||||||
|
int getTouchCount() const { return touchCount_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 便捷方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isAnyKeyDown() const;
|
||||||
|
bool isAnyMouseDown() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr int MAX_BUTTONS = SDL_CONTROLLER_BUTTON_MAX;
|
||||||
|
static constexpr int MAX_KEYS = SDL_NUM_SCANCODES;
|
||||||
|
|
||||||
|
SDL_GameController *controller_;
|
||||||
|
|
||||||
|
// 键盘状态 (PC 端使用)
|
||||||
|
std::array<bool, MAX_KEYS> keysDown_;
|
||||||
|
std::array<bool, MAX_KEYS> prevKeysDown_;
|
||||||
|
|
||||||
|
// 手柄按钮状态
|
||||||
|
std::array<bool, MAX_BUTTONS> buttonsDown_;
|
||||||
|
std::array<bool, MAX_BUTTONS> prevButtonsDown_;
|
||||||
|
|
||||||
|
// 摇杆状态
|
||||||
|
float leftStickX_;
|
||||||
|
float leftStickY_;
|
||||||
|
float rightStickX_;
|
||||||
|
float rightStickY_;
|
||||||
|
|
||||||
|
// 鼠标状态 (PC 端使用)
|
||||||
|
Vec2 mousePosition_;
|
||||||
|
Vec2 prevMousePosition_;
|
||||||
|
float mouseScroll_;
|
||||||
|
float prevMouseScroll_;
|
||||||
|
std::array<bool, 8> mouseButtonsDown_;
|
||||||
|
std::array<bool, 8> prevMouseButtonsDown_;
|
||||||
|
|
||||||
|
// 触摸屏状态 (Switch 原生)
|
||||||
|
bool touching_;
|
||||||
|
bool prevTouching_;
|
||||||
|
Vec2 touchPosition_;
|
||||||
|
Vec2 prevTouchPosition_;
|
||||||
|
int touchCount_;
|
||||||
|
|
||||||
|
// 映射键盘 keyCode 到 SDL GameController 按钮 (Switch 兼容模式)
|
||||||
|
SDL_GameControllerButton mapKeyToButton(int keyCode) const;
|
||||||
|
|
||||||
|
// 更新键盘状态
|
||||||
|
void updateKeyboard();
|
||||||
|
|
||||||
|
// 更新鼠标状态
|
||||||
|
void updateMouse();
|
||||||
|
|
||||||
|
// 更新手柄状态
|
||||||
|
void updateGamepad();
|
||||||
|
|
||||||
|
// 更新触摸屏状态
|
||||||
|
void updateTouch();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class EventQueue;
|
||||||
|
class Input;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 窗口配置
|
||||||
|
// ============================================================================
|
||||||
|
struct WindowConfig {
|
||||||
|
std::string title = "Extra2D Application";
|
||||||
|
int width = 1280;
|
||||||
|
int height = 720;
|
||||||
|
bool fullscreen = true;
|
||||||
|
bool resizable = false;
|
||||||
|
bool vsync = true;
|
||||||
|
int msaaSamples = 0;
|
||||||
|
bool centerWindow = true;
|
||||||
|
bool enableCursors = true;
|
||||||
|
bool enableDpiScale = true;
|
||||||
|
bool fullscreenDesktop = true; // true: SDL_WINDOW_FULLSCREEN_DESKTOP, false: SDL_WINDOW_FULLSCREEN
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 鼠标光标形状枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class CursorShape {
|
||||||
|
Arrow,
|
||||||
|
IBeam,
|
||||||
|
Crosshair,
|
||||||
|
Hand,
|
||||||
|
HResize,
|
||||||
|
VResize,
|
||||||
|
ResizeAll,
|
||||||
|
ResizeNWSE,
|
||||||
|
ResizeNESW
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Window 类 - SDL2 Window + GLES 3.2 封装
|
||||||
|
// 支持平台: Nintendo Switch, Windows, Linux, macOS
|
||||||
|
// ============================================================================
|
||||||
|
class Window {
|
||||||
|
public:
|
||||||
|
Window();
|
||||||
|
~Window();
|
||||||
|
|
||||||
|
// 创建窗口
|
||||||
|
bool create(const WindowConfig& config);
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
// 窗口操作
|
||||||
|
void pollEvents();
|
||||||
|
void swapBuffers();
|
||||||
|
bool shouldClose() const;
|
||||||
|
void setShouldClose(bool close);
|
||||||
|
|
||||||
|
// 窗口属性
|
||||||
|
void setTitle(const std::string& title);
|
||||||
|
void setSize(int width, int height);
|
||||||
|
void setPosition(int x, int y);
|
||||||
|
void setFullscreen(bool fullscreen);
|
||||||
|
void setVSync(bool enabled);
|
||||||
|
void setResizable(bool resizable);
|
||||||
|
|
||||||
|
// 获取窗口属性
|
||||||
|
int getWidth() const { return width_; }
|
||||||
|
int getHeight() const { return height_; }
|
||||||
|
Size getSize() const { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
|
||||||
|
Vec2 getPosition() const;
|
||||||
|
bool isFullscreen() const { return fullscreen_; }
|
||||||
|
bool isVSync() const { return vsync_; }
|
||||||
|
|
||||||
|
// DPI 缩放 (PC 端自动检测,Switch 固定 1.0)
|
||||||
|
float getContentScaleX() const;
|
||||||
|
float getContentScaleY() const;
|
||||||
|
Vec2 getContentScale() const;
|
||||||
|
|
||||||
|
// 窗口状态
|
||||||
|
bool isFocused() const { return focused_; }
|
||||||
|
bool isMinimized() const;
|
||||||
|
bool isMaximized() const;
|
||||||
|
|
||||||
|
// 获取 SDL2 窗口和 GL 上下文
|
||||||
|
SDL_Window* getSDLWindow() const { return sdlWindow_; }
|
||||||
|
SDL_GLContext getGLContext() const { return glContext_; }
|
||||||
|
|
||||||
|
// 设置/获取用户数据
|
||||||
|
void setUserData(void* data) { userData_ = data; }
|
||||||
|
void* getUserData() const { return userData_; }
|
||||||
|
|
||||||
|
// 事件队列
|
||||||
|
void setEventQueue(EventQueue* queue) { eventQueue_ = queue; }
|
||||||
|
EventQueue* getEventQueue() const { return eventQueue_; }
|
||||||
|
|
||||||
|
// 获取输入管理器
|
||||||
|
Input* getInput() const { return input_.get(); }
|
||||||
|
|
||||||
|
// 光标操作 (PC 端有效,Switch 上为空操作)
|
||||||
|
void setCursor(CursorShape shape);
|
||||||
|
void resetCursor();
|
||||||
|
void setMouseVisible(bool visible);
|
||||||
|
|
||||||
|
// 窗口回调
|
||||||
|
using ResizeCallback = std::function<void(int width, int height)>;
|
||||||
|
using FocusCallback = std::function<void(bool focused)>;
|
||||||
|
using CloseCallback = std::function<void()>;
|
||||||
|
|
||||||
|
void setResizeCallback(ResizeCallback callback) { resizeCallback_ = callback; }
|
||||||
|
void setFocusCallback(FocusCallback callback) { focusCallback_ = callback; }
|
||||||
|
void setCloseCallback(CloseCallback callback) { closeCallback_ = callback; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// SDL2 状态
|
||||||
|
SDL_Window* sdlWindow_;
|
||||||
|
SDL_GLContext glContext_;
|
||||||
|
SDL_Cursor* sdlCursors_[9]; // 光标缓存
|
||||||
|
SDL_Cursor* currentCursor_;
|
||||||
|
|
||||||
|
int width_;
|
||||||
|
int height_;
|
||||||
|
bool vsync_;
|
||||||
|
bool shouldClose_;
|
||||||
|
bool fullscreen_;
|
||||||
|
bool focused_;
|
||||||
|
float contentScaleX_;
|
||||||
|
float contentScaleY_;
|
||||||
|
bool enableDpiScale_;
|
||||||
|
void* userData_;
|
||||||
|
EventQueue* eventQueue_;
|
||||||
|
UniquePtr<Input> input_;
|
||||||
|
|
||||||
|
ResizeCallback resizeCallback_;
|
||||||
|
FocusCallback focusCallback_;
|
||||||
|
CloseCallback closeCallback_;
|
||||||
|
|
||||||
|
bool initSDL(const WindowConfig& config);
|
||||||
|
void deinitSDL();
|
||||||
|
void initCursors();
|
||||||
|
void deinitCursors();
|
||||||
|
void updateContentScale();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/audio/sound.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/alpha_mask.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 资源管理器 - 统一管理纹理、字体、音效等资源
|
||||||
|
// ============================================================================
|
||||||
|
class ResourceManager {
|
||||||
|
public:
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 单例访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static ResourceManager &getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 纹理资源
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 加载纹理(带缓存)
|
||||||
|
Ptr<Texture> loadTexture(const std::string &filepath);
|
||||||
|
|
||||||
|
/// 加载纹理并生成Alpha遮罩(用于不规则形状图片)
|
||||||
|
Ptr<Texture> loadTextureWithAlphaMask(const std::string &filepath);
|
||||||
|
|
||||||
|
/// 通过key获取已缓存的纹理
|
||||||
|
Ptr<Texture> getTexture(const std::string &key) const;
|
||||||
|
|
||||||
|
/// 检查纹理是否已缓存
|
||||||
|
bool hasTexture(const std::string &key) const;
|
||||||
|
|
||||||
|
/// 卸载指定纹理
|
||||||
|
void unloadTexture(const std::string &key);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Alpha遮罩资源
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 获取纹理的Alpha遮罩(如果已生成)
|
||||||
|
const AlphaMask *getAlphaMask(const std::string &textureKey) const;
|
||||||
|
|
||||||
|
/// 为已加载的纹理生成Alpha遮罩
|
||||||
|
bool generateAlphaMask(const std::string &textureKey);
|
||||||
|
|
||||||
|
/// 检查纹理是否有Alpha遮罩
|
||||||
|
bool hasAlphaMask(const std::string &textureKey) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 字体图集资源
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 加载字体图集(带缓存)
|
||||||
|
Ptr<FontAtlas> loadFont(const std::string &filepath, int fontSize,
|
||||||
|
bool useSDF = false);
|
||||||
|
|
||||||
|
/// 通过key获取已缓存的字体图集
|
||||||
|
Ptr<FontAtlas> getFont(const std::string &key) const;
|
||||||
|
|
||||||
|
/// 检查字体是否已缓存
|
||||||
|
bool hasFont(const std::string &key) const;
|
||||||
|
|
||||||
|
/// 卸载指定字体
|
||||||
|
void unloadFont(const std::string &key);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 音效资源
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 加载音效(带缓存)
|
||||||
|
Ptr<Sound> loadSound(const std::string &filepath);
|
||||||
|
Ptr<Sound> loadSound(const std::string &name, const std::string &filepath);
|
||||||
|
|
||||||
|
/// 通过key获取已缓存的音效
|
||||||
|
Ptr<Sound> getSound(const std::string &key) const;
|
||||||
|
|
||||||
|
/// 检查音效是否已缓存
|
||||||
|
bool hasSound(const std::string &key) const;
|
||||||
|
|
||||||
|
/// 卸载指定音效
|
||||||
|
void unloadSound(const std::string &key);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 缓存清理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 清理所有失效的弱引用(自动清理已释放的资源)
|
||||||
|
void purgeUnused();
|
||||||
|
|
||||||
|
/// 清理指定类型的所有缓存
|
||||||
|
void clearTextureCache();
|
||||||
|
void clearFontCache();
|
||||||
|
void clearSoundCache();
|
||||||
|
|
||||||
|
/// 清理所有资源缓存
|
||||||
|
void clearAllCaches();
|
||||||
|
|
||||||
|
/// 获取各类资源的缓存数量
|
||||||
|
size_t getTextureCacheSize() const;
|
||||||
|
size_t getFontCacheSize() const;
|
||||||
|
size_t getSoundCacheSize() const;
|
||||||
|
|
||||||
|
ResourceManager();
|
||||||
|
~ResourceManager();
|
||||||
|
ResourceManager(const ResourceManager &) = delete;
|
||||||
|
ResourceManager &operator=(const ResourceManager &) = delete;
|
||||||
|
|
||||||
|
// 生成字体缓存key
|
||||||
|
std::string makeFontKey(const std::string &filepath, int fontSize,
|
||||||
|
bool useSDF) const;
|
||||||
|
|
||||||
|
// 互斥锁保护缓存
|
||||||
|
mutable std::mutex textureMutex_;
|
||||||
|
mutable std::mutex fontMutex_;
|
||||||
|
mutable std::mutex soundMutex_;
|
||||||
|
|
||||||
|
// 资源缓存 - 使用弱指针实现自动清理
|
||||||
|
std::unordered_map<std::string, WeakPtr<Texture>> textureCache_;
|
||||||
|
std::unordered_map<std::string, WeakPtr<FontAtlas>> fontCache_;
|
||||||
|
std::unordered_map<std::string, WeakPtr<Sound>> soundCache_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
|
#include <extra2d/graphics/render_backend.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Scene;
|
||||||
|
class Action;
|
||||||
|
class RenderBackend;
|
||||||
|
struct RenderCommand;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 节点基类 - 场景图的基础
|
||||||
|
// ============================================================================
|
||||||
|
class Node : public std::enable_shared_from_this<Node> {
|
||||||
|
public:
|
||||||
|
Node();
|
||||||
|
virtual ~Node();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 层级管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void addChild(Ptr<Node> child);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量添加子节点
|
||||||
|
* @param children 子节点列表
|
||||||
|
*/
|
||||||
|
void addChildren(std::vector<Ptr<Node>> &&children);
|
||||||
|
|
||||||
|
void removeChild(Ptr<Node> child);
|
||||||
|
void removeChildByName(const std::string &name);
|
||||||
|
void removeFromParent();
|
||||||
|
void removeAllChildren();
|
||||||
|
|
||||||
|
Ptr<Node> getParent() const { return parent_.lock(); }
|
||||||
|
const std::vector<Ptr<Node>> &getChildren() const { return children_; }
|
||||||
|
Ptr<Node> getChildByName(const std::string &name) const;
|
||||||
|
Ptr<Node> getChildByTag(int tag) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 变换属性
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPosition(const Vec2 &pos);
|
||||||
|
void setPosition(float x, float y);
|
||||||
|
Vec2 getPosition() const { return position_; }
|
||||||
|
|
||||||
|
void setRotation(float degrees);
|
||||||
|
float getRotation() const { return rotation_; }
|
||||||
|
|
||||||
|
void setScale(const Vec2 &scale);
|
||||||
|
void setScale(float scale);
|
||||||
|
void setScale(float x, float y);
|
||||||
|
Vec2 getScale() const { return scale_; }
|
||||||
|
|
||||||
|
void setAnchor(const Vec2 &anchor);
|
||||||
|
void setAnchor(float x, float y);
|
||||||
|
Vec2 getAnchor() const { return anchor_; }
|
||||||
|
|
||||||
|
void setSkew(const Vec2 &skew);
|
||||||
|
void setSkew(float x, float y);
|
||||||
|
Vec2 getSkew() const { return skew_; }
|
||||||
|
|
||||||
|
void setOpacity(float opacity);
|
||||||
|
float getOpacity() const { return opacity_; }
|
||||||
|
|
||||||
|
void setVisible(bool visible);
|
||||||
|
bool isVisible() const { return visible_; }
|
||||||
|
|
||||||
|
void setZOrder(int zOrder);
|
||||||
|
int getZOrder() const { return zOrder_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 世界变换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Vec2 convertToWorldSpace(const Vec2 &localPos) const;
|
||||||
|
Vec2 convertToNodeSpace(const Vec2 &worldPos) const;
|
||||||
|
|
||||||
|
glm::mat4 getLocalTransform() const;
|
||||||
|
glm::mat4 getWorldTransform() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 标记变换矩阵为脏状态,并传播到所有子节点
|
||||||
|
*/
|
||||||
|
void markTransformDirty();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 名称和标签
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setName(const std::string &name) { name_ = name; }
|
||||||
|
const std::string &getName() const { return name_; }
|
||||||
|
|
||||||
|
void setTag(int tag) { tag_ = tag; }
|
||||||
|
int getTag() const { return tag_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期回调
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void onEnter();
|
||||||
|
virtual void onExit();
|
||||||
|
virtual void onUpdate(float dt);
|
||||||
|
virtual void onRender(RenderBackend &renderer);
|
||||||
|
virtual void onAttachToScene(Scene *scene);
|
||||||
|
virtual void onDetachFromScene();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 边界框(用于空间索引)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual Rect getBoundingBox() const;
|
||||||
|
|
||||||
|
// 是否需要参与空间索引(默认 true)
|
||||||
|
void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; }
|
||||||
|
bool isSpatialIndexed() const { return spatialIndexed_; }
|
||||||
|
|
||||||
|
// 更新空间索引(手动调用,通常在边界框变化后)
|
||||||
|
void updateSpatialIndex();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 动作系统
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void runAction(Ptr<Action> action);
|
||||||
|
void stopAllActions();
|
||||||
|
void stopAction(Ptr<Action> action);
|
||||||
|
void stopActionByTag(int tag);
|
||||||
|
Ptr<Action> getActionByTag(int tag) const;
|
||||||
|
size_t getActionCount() const { return actions_.size(); }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 事件系统
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 内部方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void update(float dt);
|
||||||
|
void render(RenderBackend &renderer);
|
||||||
|
void sortChildren();
|
||||||
|
|
||||||
|
bool isRunning() const { return running_; }
|
||||||
|
Scene *getScene() const { return scene_; }
|
||||||
|
|
||||||
|
// 多线程渲染命令收集
|
||||||
|
virtual void collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||||
|
int parentZOrder = 0);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// 子类重写
|
||||||
|
virtual void onDraw(RenderBackend &renderer) {}
|
||||||
|
virtual void onUpdateNode(float dt) {}
|
||||||
|
virtual void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
|
int zOrder) {};
|
||||||
|
|
||||||
|
// 供子类访问的内部状态
|
||||||
|
Vec2 &getPositionRef() { return position_; }
|
||||||
|
Vec2 &getScaleRef() { return scale_; }
|
||||||
|
Vec2 &getAnchorRef() { return anchor_; }
|
||||||
|
float getRotationRef() { return rotation_; }
|
||||||
|
float getOpacityRef() { return opacity_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// ==========================================================================
|
||||||
|
// 成员变量按类型大小降序排列,减少内存对齐填充
|
||||||
|
// 64位系统对齐:std::string(32) > glm::mat4(64) > std::vector(24) >
|
||||||
|
// double(8) > float(4) > int(4) > bool(1)
|
||||||
|
// ==========================================================================
|
||||||
|
|
||||||
|
// 1. 大块内存(64字节)
|
||||||
|
mutable glm::mat4 localTransform_; // 64 bytes
|
||||||
|
mutable glm::mat4 worldTransform_; // 64 bytes
|
||||||
|
|
||||||
|
// 2. 字符串和容器(24-32字节)
|
||||||
|
std::string name_; // 32 bytes
|
||||||
|
std::vector<Ptr<Node>> children_; // 24 bytes
|
||||||
|
|
||||||
|
// 3. 子节点索引(加速查找)
|
||||||
|
std::unordered_map<std::string, WeakPtr<Node>> nameIndex_; // 56 bytes
|
||||||
|
std::unordered_map<int, WeakPtr<Node>> tagIndex_; // 56 bytes
|
||||||
|
|
||||||
|
// 4. 动作系统(使用 unordered_map 加速 tag 查找)
|
||||||
|
std::unordered_map<int, Ptr<Action>> actionByTag_; // 56 bytes
|
||||||
|
std::vector<Ptr<Action>> actions_; // 24 bytes(无 tag 的 Action)
|
||||||
|
|
||||||
|
// 5. 事件分发器
|
||||||
|
EventDispatcher eventDispatcher_; // 大小取决于实现
|
||||||
|
|
||||||
|
// 6. 父节点引用
|
||||||
|
WeakPtr<Node> parent_; // 16 bytes
|
||||||
|
|
||||||
|
// 7. 变换属性(按访问频率分组)
|
||||||
|
Vec2 position_ = Vec2::Zero(); // 8 bytes
|
||||||
|
Vec2 scale_ = Vec2(1.0f, 1.0f); // 8 bytes
|
||||||
|
Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes
|
||||||
|
Vec2 skew_ = Vec2::Zero(); // 8 bytes
|
||||||
|
|
||||||
|
// 8. 边界框(用于空间索引)
|
||||||
|
Rect lastSpatialBounds_; // 16 bytes
|
||||||
|
|
||||||
|
// 9. 浮点属性
|
||||||
|
float rotation_ = 0.0f; // 4 bytes
|
||||||
|
float opacity_ = 1.0f; // 4 bytes
|
||||||
|
|
||||||
|
// 10. 整数属性
|
||||||
|
int zOrder_ = 0; // 4 bytes
|
||||||
|
int tag_ = -1; // 4 bytes
|
||||||
|
|
||||||
|
// 11. 场景指针
|
||||||
|
Scene *scene_ = nullptr; // 8 bytes
|
||||||
|
|
||||||
|
// 12. 布尔标志(打包在一起)
|
||||||
|
mutable bool transformDirty_ = true; // 1 byte
|
||||||
|
mutable bool worldTransformDirty_ = true; // 1 byte
|
||||||
|
bool childrenOrderDirty_ = false; // 1 byte
|
||||||
|
bool visible_ = true; // 1 byte
|
||||||
|
bool running_ = false; // 1 byte
|
||||||
|
bool spatialIndexed_ = true; // 1 byte
|
||||||
|
// 填充 2 bytes 到 8 字节对齐
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/graphics/camera.h>
|
||||||
|
#include <extra2d/scene/node.h>
|
||||||
|
#include <extra2d/spatial/spatial_manager.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
struct RenderCommand;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 场景类 - 节点容器,管理整个场景图
|
||||||
|
// ============================================================================
|
||||||
|
class Scene : public Node {
|
||||||
|
public:
|
||||||
|
Scene();
|
||||||
|
~Scene() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 场景属性
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBackgroundColor(const Color &color) { backgroundColor_ = color; }
|
||||||
|
Color getBackgroundColor() const { return backgroundColor_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 摄像机
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setCamera(Ptr<Camera> camera);
|
||||||
|
Ptr<Camera> getCamera() const { return camera_; }
|
||||||
|
|
||||||
|
Camera *getActiveCamera() const {
|
||||||
|
return camera_ ? camera_.get() : defaultCamera_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 视口和尺寸
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setViewportSize(float width, float height);
|
||||||
|
void setViewportSize(const Size &size);
|
||||||
|
Size getViewportSize() const { return viewportSize_; }
|
||||||
|
|
||||||
|
float getWidth() const { return viewportSize_.width; }
|
||||||
|
float getHeight() const { return viewportSize_.height; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 场景状态
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isPaused() const { return paused_; }
|
||||||
|
void pause() { paused_ = true; }
|
||||||
|
void resume() { paused_ = false; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 渲染和更新
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void renderScene(RenderBackend &renderer);
|
||||||
|
void renderContent(RenderBackend &renderer);
|
||||||
|
void updateScene(float dt);
|
||||||
|
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||||
|
int parentZOrder = 0) override;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 空间索引系统
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
SpatialManager &getSpatialManager() { return spatialManager_; }
|
||||||
|
const SpatialManager &getSpatialManager() const { return spatialManager_; }
|
||||||
|
|
||||||
|
// 启用/禁用空间索引
|
||||||
|
void setSpatialIndexingEnabled(bool enabled) {
|
||||||
|
spatialIndexingEnabled_ = enabled;
|
||||||
|
}
|
||||||
|
bool isSpatialIndexingEnabled() const { return spatialIndexingEnabled_; }
|
||||||
|
|
||||||
|
// 节点空间索引管理(内部使用)
|
||||||
|
void updateNodeInSpatialIndex(Node *node, const Rect &oldBounds,
|
||||||
|
const Rect &newBounds);
|
||||||
|
void removeNodeFromSpatialIndex(Node *node);
|
||||||
|
|
||||||
|
// 碰撞检测查询
|
||||||
|
std::vector<Node *> queryNodesInArea(const Rect &area) const;
|
||||||
|
std::vector<Node *> queryNodesAtPoint(const Vec2 &point) const;
|
||||||
|
std::vector<std::pair<Node *, Node *>> queryCollisions() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<Scene> create();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
|
||||||
|
friend class SceneManager;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Color backgroundColor_ = Colors::Black;
|
||||||
|
Size viewportSize_ = Size::Zero();
|
||||||
|
|
||||||
|
Ptr<Camera> camera_;
|
||||||
|
Ptr<Camera> defaultCamera_;
|
||||||
|
|
||||||
|
bool paused_ = false;
|
||||||
|
|
||||||
|
// 空间索引系统
|
||||||
|
SpatialManager spatialManager_;
|
||||||
|
bool spatialIndexingEnabled_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/scene/scene.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <stack>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
struct RenderCommand;
|
||||||
|
class Transition;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 场景切换特效类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class TransitionType {
|
||||||
|
None,
|
||||||
|
Fade,
|
||||||
|
SlideLeft,
|
||||||
|
SlideRight,
|
||||||
|
SlideUp,
|
||||||
|
SlideDown,
|
||||||
|
Scale,
|
||||||
|
Flip
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 场景管理器 - 管理场景的生命周期和切换
|
||||||
|
// ============================================================================
|
||||||
|
class SceneManager {
|
||||||
|
public:
|
||||||
|
using TransitionCallback = std::function<void()>;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 单例访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static SceneManager &getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 场景栈操作
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// 运行第一个场景
|
||||||
|
void runWithScene(Ptr<Scene> scene);
|
||||||
|
|
||||||
|
// 替换当前场景
|
||||||
|
void replaceScene(Ptr<Scene> scene);
|
||||||
|
void replaceScene(Ptr<Scene> scene, TransitionType transition,
|
||||||
|
float duration = 0.5f);
|
||||||
|
|
||||||
|
// 压入新场景(当前场景暂停)
|
||||||
|
void pushScene(Ptr<Scene> scene);
|
||||||
|
void pushScene(Ptr<Scene> scene, TransitionType transition,
|
||||||
|
float duration = 0.5f);
|
||||||
|
|
||||||
|
// 弹出当前场景(恢复上一个场景)
|
||||||
|
void popScene();
|
||||||
|
void popScene(TransitionType transition, float duration = 0.5f);
|
||||||
|
|
||||||
|
// 弹出到根场景
|
||||||
|
void popToRootScene();
|
||||||
|
void popToRootScene(TransitionType transition, float duration = 0.5f);
|
||||||
|
|
||||||
|
// 弹出到指定场景
|
||||||
|
void popToScene(const std::string &name);
|
||||||
|
void popToScene(const std::string &name, TransitionType transition,
|
||||||
|
float duration = 0.5f);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 获取场景
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Ptr<Scene> getCurrentScene() const;
|
||||||
|
Ptr<Scene> getPreviousScene() const;
|
||||||
|
Ptr<Scene> getRootScene() const;
|
||||||
|
|
||||||
|
// 通过名称获取场景
|
||||||
|
Ptr<Scene> getSceneByName(const std::string &name) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 查询
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
size_t getSceneCount() const { return sceneStack_.size(); }
|
||||||
|
bool isEmpty() const { return sceneStack_.empty(); }
|
||||||
|
bool hasScene(const std::string &name) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 更新和渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void update(float dt);
|
||||||
|
void render(RenderBackend &renderer);
|
||||||
|
void collectRenderCommands(std::vector<RenderCommand> &commands);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 过渡控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isTransitioning() const { return isTransitioning_; }
|
||||||
|
void setTransitionCallback(TransitionCallback callback) {
|
||||||
|
transitionCallback_ = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 清理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void end();
|
||||||
|
void purgeCachedScenes();
|
||||||
|
|
||||||
|
public:
|
||||||
|
SceneManager() = default;
|
||||||
|
~SceneManager() = default;
|
||||||
|
SceneManager(const SceneManager &) = delete;
|
||||||
|
SceneManager &operator=(const SceneManager &) = delete;
|
||||||
|
|
||||||
|
// 场景切换(供 Application 使用)
|
||||||
|
void enterScene(Ptr<Scene> scene);
|
||||||
|
void enterScene(Ptr<Scene> scene, Ptr<class Transition> transition);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void doSceneSwitch();
|
||||||
|
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type,
|
||||||
|
float duration, Function<void()> stackAction);
|
||||||
|
void updateTransition(float dt);
|
||||||
|
void finishTransition();
|
||||||
|
void dispatchPointerEvents(Scene &scene);
|
||||||
|
|
||||||
|
std::stack<Ptr<Scene>> sceneStack_;
|
||||||
|
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
||||||
|
|
||||||
|
// Transition state
|
||||||
|
bool isTransitioning_ = false;
|
||||||
|
TransitionType currentTransition_ = TransitionType::None;
|
||||||
|
float transitionDuration_ = 0.0f;
|
||||||
|
float transitionElapsed_ = 0.0f;
|
||||||
|
Ptr<Scene> outgoingScene_;
|
||||||
|
Ptr<Scene> incomingScene_;
|
||||||
|
Ptr<Transition> activeTransition_;
|
||||||
|
Function<void()> transitionStackAction_;
|
||||||
|
TransitionCallback transitionCallback_;
|
||||||
|
|
||||||
|
// Next scene to switch to (queued during transition)
|
||||||
|
Ptr<Scene> nextScene_;
|
||||||
|
bool sendCleanupToScene_ = false;
|
||||||
|
|
||||||
|
Node *hoverTarget_ = nullptr;
|
||||||
|
Node *captureTarget_ = nullptr;
|
||||||
|
Vec2 lastPointerWorld_ = Vec2::Zero();
|
||||||
|
bool hasLastPointerWorld_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/scene/node.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 形状类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class ShapeType { Point, Line, Rect, Circle, Triangle, Polygon };
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 形状节点 - 用于绘制几何形状
|
||||||
|
// ============================================================================
|
||||||
|
class ShapeNode : public Node {
|
||||||
|
public:
|
||||||
|
ShapeNode();
|
||||||
|
~ShapeNode() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<ShapeNode> create();
|
||||||
|
|
||||||
|
// 点
|
||||||
|
static Ptr<ShapeNode> createPoint(const Vec2 &pos, const Color &color);
|
||||||
|
|
||||||
|
// 线
|
||||||
|
static Ptr<ShapeNode> createLine(const Vec2 &start, const Vec2 &end,
|
||||||
|
const Color &color, float width = 1.0f);
|
||||||
|
|
||||||
|
// 矩形
|
||||||
|
static Ptr<ShapeNode> createRect(const Rect &rect, const Color &color,
|
||||||
|
float width = 1.0f);
|
||||||
|
static Ptr<ShapeNode> createFilledRect(const Rect &rect, const Color &color);
|
||||||
|
|
||||||
|
// 圆形
|
||||||
|
static Ptr<ShapeNode> createCircle(const Vec2 ¢er, float radius,
|
||||||
|
const Color &color, int segments = 32,
|
||||||
|
float width = 1.0f);
|
||||||
|
static Ptr<ShapeNode> createFilledCircle(const Vec2 ¢er, float radius,
|
||||||
|
const Color &color,
|
||||||
|
int segments = 32);
|
||||||
|
|
||||||
|
// 三角形
|
||||||
|
static Ptr<ShapeNode> createTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||||
|
const Vec2 &p3, const Color &color,
|
||||||
|
float width = 1.0f);
|
||||||
|
static Ptr<ShapeNode> createFilledTriangle(const Vec2 &p1, const Vec2 &p2,
|
||||||
|
const Vec2 &p3,
|
||||||
|
const Color &color);
|
||||||
|
|
||||||
|
// 多边形
|
||||||
|
static Ptr<ShapeNode> createPolygon(const std::vector<Vec2> &points,
|
||||||
|
const Color &color, float width = 1.0f);
|
||||||
|
static Ptr<ShapeNode> createFilledPolygon(const std::vector<Vec2> &points,
|
||||||
|
const Color &color);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 属性设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setShapeType(ShapeType type) { shapeType_ = type; }
|
||||||
|
ShapeType getShapeType() const { return shapeType_; }
|
||||||
|
|
||||||
|
void setColor(const Color &color) { color_ = color; }
|
||||||
|
Color getColor() const { return color_; }
|
||||||
|
|
||||||
|
void setFilled(bool filled) { filled_ = filled; }
|
||||||
|
bool isFilled() const { return filled_; }
|
||||||
|
|
||||||
|
void setLineWidth(float width) { lineWidth_ = width; }
|
||||||
|
float getLineWidth() const { return lineWidth_; }
|
||||||
|
|
||||||
|
void setSegments(int segments) { segments_ = segments; }
|
||||||
|
int getSegments() const { return segments_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 点设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPoints(const std::vector<Vec2> &points);
|
||||||
|
const std::vector<Vec2> &getPoints() const { return points_; }
|
||||||
|
void addPoint(const Vec2 &point);
|
||||||
|
void clearPoints();
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDraw(RenderBackend &renderer) override;
|
||||||
|
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
|
int zOrder) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ShapeType shapeType_ = ShapeType::Rect;
|
||||||
|
Color color_ = Colors::White;
|
||||||
|
bool filled_ = false;
|
||||||
|
float lineWidth_ = 1.0f;
|
||||||
|
int segments_ = 32;
|
||||||
|
std::vector<Vec2> points_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <extra2d/scene/node.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 精灵节点
|
||||||
|
// ============================================================================
|
||||||
|
class Sprite : public Node {
|
||||||
|
public:
|
||||||
|
Sprite();
|
||||||
|
explicit Sprite(Ptr<Texture> texture);
|
||||||
|
~Sprite() override = default;
|
||||||
|
|
||||||
|
// 纹理
|
||||||
|
void setTexture(Ptr<Texture> texture);
|
||||||
|
Ptr<Texture> getTexture() const { return texture_; }
|
||||||
|
|
||||||
|
// 纹理矩形 (用于图集)
|
||||||
|
void setTextureRect(const Rect &rect);
|
||||||
|
Rect getTextureRect() const { return textureRect_; }
|
||||||
|
|
||||||
|
// 颜色混合
|
||||||
|
void setColor(const Color &color);
|
||||||
|
Color getColor() const { return color_; }
|
||||||
|
|
||||||
|
// 翻转
|
||||||
|
void setFlipX(bool flip);
|
||||||
|
void setFlipY(bool flip);
|
||||||
|
bool isFlipX() const { return flipX_; }
|
||||||
|
bool isFlipY() const { return flipY_; }
|
||||||
|
|
||||||
|
// 静态创建方法
|
||||||
|
static Ptr<Sprite> create();
|
||||||
|
static Ptr<Sprite> create(Ptr<Texture> texture);
|
||||||
|
static Ptr<Sprite> create(Ptr<Texture> texture, const Rect &rect);
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDraw(RenderBackend &renderer) override;
|
||||||
|
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
||||||
|
int zOrder) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<Texture> texture_;
|
||||||
|
Rect textureRect_;
|
||||||
|
Color color_ = Colors::White;
|
||||||
|
bool flipX_ = false;
|
||||||
|
bool flipY_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/scene/scene.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 过渡方向
|
||||||
|
// ============================================================================
|
||||||
|
enum class TransitionDirection { Left, Right, Up, Down };
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 过渡效果基类
|
||||||
|
// ============================================================================
|
||||||
|
class Transition : public std::enable_shared_from_this<Transition> {
|
||||||
|
public:
|
||||||
|
using FinishCallback = std::function<void()>;
|
||||||
|
|
||||||
|
Transition(float duration);
|
||||||
|
virtual ~Transition() = default;
|
||||||
|
|
||||||
|
// 开始过渡
|
||||||
|
void start(Ptr<Scene> from, Ptr<Scene> to);
|
||||||
|
|
||||||
|
// 更新过渡进度
|
||||||
|
void update(float dt);
|
||||||
|
|
||||||
|
// 渲染过渡效果
|
||||||
|
virtual void render(RenderBackend &renderer);
|
||||||
|
|
||||||
|
// 是否完成
|
||||||
|
bool isFinished() const { return isFinished_; }
|
||||||
|
|
||||||
|
// 获取进度 [0, 1]
|
||||||
|
float getProgress() const { return progress_; }
|
||||||
|
|
||||||
|
// 获取淡入淡出进度 (0->1 for fade in, 1->0 for fade out)
|
||||||
|
float getFadeInAlpha() const;
|
||||||
|
float getFadeOutAlpha() const;
|
||||||
|
|
||||||
|
// 设置完成回调
|
||||||
|
void setFinishCallback(FinishCallback callback) {
|
||||||
|
finishCallback_ = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取源场景和目标场景
|
||||||
|
Ptr<Scene> getOutgoingScene() const { return outgoingScene_; }
|
||||||
|
Ptr<Scene> getIncomingScene() const { return incomingScene_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// 子类实现具体的渲染效果
|
||||||
|
virtual void onRenderTransition(RenderBackend &renderer, float progress) = 0;
|
||||||
|
|
||||||
|
// 过渡完成时调用
|
||||||
|
virtual void onFinish();
|
||||||
|
|
||||||
|
float duration_;
|
||||||
|
float elapsed_;
|
||||||
|
float progress_;
|
||||||
|
bool isFinished_;
|
||||||
|
bool isStarted_;
|
||||||
|
|
||||||
|
Ptr<Scene> outgoingScene_;
|
||||||
|
Ptr<Scene> incomingScene_;
|
||||||
|
FinishCallback finishCallback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 淡入淡出过渡
|
||||||
|
// ============================================================================
|
||||||
|
class FadeTransition : public Transition {
|
||||||
|
public:
|
||||||
|
FadeTransition(float duration);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onRenderTransition(RenderBackend &renderer, float progress) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 滑动过渡
|
||||||
|
// ============================================================================
|
||||||
|
class SlideTransition : public Transition {
|
||||||
|
public:
|
||||||
|
SlideTransition(float duration, TransitionDirection direction);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onRenderTransition(RenderBackend &renderer, float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TransitionDirection direction_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 缩放过渡
|
||||||
|
// ============================================================================
|
||||||
|
class ScaleTransition : public Transition {
|
||||||
|
public:
|
||||||
|
ScaleTransition(float duration);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onRenderTransition(RenderBackend &renderer, float progress) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 翻页过渡
|
||||||
|
// ============================================================================
|
||||||
|
class FlipTransition : public Transition {
|
||||||
|
public:
|
||||||
|
enum class Axis { Horizontal, Vertical };
|
||||||
|
|
||||||
|
FlipTransition(float duration, Axis axis = Axis::Horizontal);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onRenderTransition(RenderBackend &renderer, float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Axis axis_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 马赛克/方块过渡
|
||||||
|
// ============================================================================
|
||||||
|
class BoxTransition : public Transition {
|
||||||
|
public:
|
||||||
|
BoxTransition(float duration, int divisions = 8);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onRenderTransition(RenderBackend &renderer, float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int divisions_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <extra2d/spatial/spatial_index.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class QuadTree : public ISpatialIndex {
|
||||||
|
public:
|
||||||
|
static constexpr int MAX_OBJECTS = 10;
|
||||||
|
static constexpr int MAX_LEVELS = 5;
|
||||||
|
|
||||||
|
struct QuadTreeNode {
|
||||||
|
Rect bounds;
|
||||||
|
int level;
|
||||||
|
std::vector<std::pair<Node *, Rect>> objects;
|
||||||
|
std::array<std::unique_ptr<QuadTreeNode>, 4> children;
|
||||||
|
|
||||||
|
QuadTreeNode(const Rect &bounds, int level);
|
||||||
|
bool contains(const Rect &rect) const;
|
||||||
|
bool intersects(const Rect &rect) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit QuadTree(const Rect &worldBounds);
|
||||||
|
~QuadTree() override = default;
|
||||||
|
|
||||||
|
void insert(Node *node, const Rect &bounds) override;
|
||||||
|
void remove(Node *node) override;
|
||||||
|
void update(Node *node, const Rect &newBounds) override;
|
||||||
|
|
||||||
|
std::vector<Node *> query(const Rect &area) const override;
|
||||||
|
std::vector<Node *> query(const Vec2 &point) const override;
|
||||||
|
std::vector<std::pair<Node *, Node *>> queryCollisions() const override;
|
||||||
|
|
||||||
|
void clear() override;
|
||||||
|
size_t size() const override;
|
||||||
|
bool empty() const override;
|
||||||
|
|
||||||
|
void rebuild() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void split(QuadTreeNode *node);
|
||||||
|
void insertIntoNode(QuadTreeNode *node, Node *object, const Rect &bounds);
|
||||||
|
void queryNode(const QuadTreeNode *node, const Rect &area,
|
||||||
|
std::vector<Node *> &results) const;
|
||||||
|
void queryNode(const QuadTreeNode *node, const Vec2 &point,
|
||||||
|
std::vector<Node *> &results) const;
|
||||||
|
void
|
||||||
|
collectCollisions(const QuadTreeNode *node,
|
||||||
|
std::vector<std::pair<Node *, Node *>> &collisions) const;
|
||||||
|
bool removeFromNode(QuadTreeNode *node, Node *object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 使用扫描线算法检测节点内对象的碰撞
|
||||||
|
* @param objects 对象列表
|
||||||
|
* @param collisions 输出碰撞对
|
||||||
|
*/
|
||||||
|
void detectCollisionsInNode(
|
||||||
|
const std::vector<std::pair<Node *, Rect>> &objects,
|
||||||
|
std::vector<std::pair<Node *, Node *>> &collisions) const;
|
||||||
|
|
||||||
|
std::unique_ptr<QuadTreeNode> root_;
|
||||||
|
Rect worldBounds_;
|
||||||
|
size_t objectCount_ = 0;
|
||||||
|
|
||||||
|
// 碰撞检测用的临时缓冲区,避免重复分配
|
||||||
|
mutable std::vector<std::pair<Node *, Rect>> collisionBuffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/spatial/spatial_index.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 空间哈希实现 - 优化内存布局版本
|
||||||
|
* 使用连续内存存储单元格内容,减少内存碎片
|
||||||
|
*/
|
||||||
|
class SpatialHash : public ISpatialIndex {
|
||||||
|
public:
|
||||||
|
using CellKey = std::pair<int64_t, int64_t>;
|
||||||
|
|
||||||
|
struct CellKeyHash {
|
||||||
|
size_t operator()(const CellKey &key) const {
|
||||||
|
return std::hash<int64_t>()(key.first) ^
|
||||||
|
(std::hash<int64_t>()(key.second) << 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit SpatialHash(float cellSize = 64.0f);
|
||||||
|
~SpatialHash() override = default;
|
||||||
|
|
||||||
|
void insert(Node *node, const Rect &bounds) override;
|
||||||
|
void remove(Node *node) override;
|
||||||
|
void update(Node *node, const Rect &newBounds) override;
|
||||||
|
|
||||||
|
std::vector<Node *> query(const Rect &area) const override;
|
||||||
|
std::vector<Node *> query(const Vec2 &point) const override;
|
||||||
|
std::vector<std::pair<Node *, Node *>> queryCollisions() const override;
|
||||||
|
|
||||||
|
void clear() override;
|
||||||
|
size_t size() const override;
|
||||||
|
bool empty() const override;
|
||||||
|
|
||||||
|
void rebuild() override;
|
||||||
|
|
||||||
|
void setCellSize(float cellSize);
|
||||||
|
float getCellSize() const { return cellSize_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief 单元格数据 - 使用vector代替unordered_set减少内存开销
|
||||||
|
*/
|
||||||
|
struct Cell {
|
||||||
|
std::vector<Node *> objects;
|
||||||
|
|
||||||
|
void insert(Node *node);
|
||||||
|
void remove(Node *node);
|
||||||
|
bool contains(Node *node) const;
|
||||||
|
void clear() { objects.clear(); }
|
||||||
|
size_t size() const { return objects.size(); }
|
||||||
|
bool empty() const { return objects.empty(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
CellKey getCellKey(float x, float y) const;
|
||||||
|
void getCellsForRect(const Rect &rect, std::vector<CellKey> &cells) const;
|
||||||
|
void insertIntoCells(Node *node, const Rect &bounds);
|
||||||
|
void removeFromCells(Node *node, const Rect &bounds);
|
||||||
|
|
||||||
|
float cellSize_;
|
||||||
|
// 使用vector存储对象列表代替unordered_set,内存更紧凑
|
||||||
|
std::unordered_map<CellKey, Cell, CellKeyHash> grid_;
|
||||||
|
std::unordered_map<Node *, Rect> objectBounds_;
|
||||||
|
size_t objectCount_ = 0;
|
||||||
|
|
||||||
|
// 查询用的临时缓冲区,避免重复分配
|
||||||
|
mutable std::vector<Node *> queryBuffer_;
|
||||||
|
mutable std::vector<std::pair<Node *, Node *>> collisionBuffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class Node;
|
||||||
|
|
||||||
|
enum class SpatialStrategy { Auto, QuadTree, SpatialHash };
|
||||||
|
|
||||||
|
struct SpatialQueryResult {
|
||||||
|
Node *node;
|
||||||
|
Rect bounds;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ISpatialIndex {
|
||||||
|
public:
|
||||||
|
virtual ~ISpatialIndex() = default;
|
||||||
|
|
||||||
|
virtual void insert(Node *node, const Rect &bounds) = 0;
|
||||||
|
virtual void remove(Node *node) = 0;
|
||||||
|
virtual void update(Node *node, const Rect &newBounds) = 0;
|
||||||
|
|
||||||
|
virtual std::vector<Node *> query(const Rect &area) const = 0;
|
||||||
|
virtual std::vector<Node *> query(const Vec2 &point) const = 0;
|
||||||
|
virtual std::vector<std::pair<Node *, Node *>> queryCollisions() const = 0;
|
||||||
|
|
||||||
|
virtual void clear() = 0;
|
||||||
|
virtual size_t size() const = 0;
|
||||||
|
virtual bool empty() const = 0;
|
||||||
|
|
||||||
|
virtual void rebuild() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
using SpatialIndexPtr = std::unique_ptr<ISpatialIndex>;
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/spatial/spatial_index.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class SpatialManager {
|
||||||
|
public:
|
||||||
|
using QueryCallback = std::function<bool(Node *)>;
|
||||||
|
|
||||||
|
SpatialManager();
|
||||||
|
explicit SpatialManager(const Rect &worldBounds);
|
||||||
|
~SpatialManager() = default;
|
||||||
|
|
||||||
|
void setStrategy(SpatialStrategy strategy);
|
||||||
|
void setAutoThresholds(size_t quadTreeThreshold, size_t hashThreshold);
|
||||||
|
|
||||||
|
void setWorldBounds(const Rect &bounds);
|
||||||
|
Rect getWorldBounds() const { return worldBounds_; }
|
||||||
|
|
||||||
|
void insert(Node *node, const Rect &bounds);
|
||||||
|
void remove(Node *node);
|
||||||
|
void update(Node *node, const Rect &newBounds);
|
||||||
|
|
||||||
|
std::vector<Node *> query(const Rect &area) const;
|
||||||
|
std::vector<Node *> query(const Vec2 &point) const;
|
||||||
|
std::vector<std::pair<Node *, Node *>> queryCollisions() const;
|
||||||
|
|
||||||
|
void query(const Rect &area, const QueryCallback &callback) const;
|
||||||
|
void query(const Vec2 &point, const QueryCallback &callback) const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
size_t size() const;
|
||||||
|
bool empty() const;
|
||||||
|
|
||||||
|
void rebuild();
|
||||||
|
void optimize();
|
||||||
|
|
||||||
|
SpatialStrategy getCurrentStrategy() const;
|
||||||
|
const char *getStrategyName() const;
|
||||||
|
|
||||||
|
static std::unique_ptr<ISpatialIndex> createIndex(SpatialStrategy strategy,
|
||||||
|
const Rect &bounds);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void selectOptimalStrategy();
|
||||||
|
|
||||||
|
SpatialStrategy currentStrategy_ = SpatialStrategy::Auto;
|
||||||
|
SpatialStrategy activeStrategy_ = SpatialStrategy::QuadTree;
|
||||||
|
std::unique_ptr<ISpatialIndex> index_;
|
||||||
|
Rect worldBounds_;
|
||||||
|
|
||||||
|
size_t quadTreeThreshold_ = 1000;
|
||||||
|
size_t hashThreshold_ = 5000;
|
||||||
|
|
||||||
|
mutable size_t queryCount_ = 0;
|
||||||
|
mutable size_t totalQueryTime_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,242 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/graphics/texture.h>
|
||||||
|
#include <extra2d/platform/window.h>
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 图片缩放模式
|
||||||
|
enum class ImageScaleMode {
|
||||||
|
Original, // 使用原图大小
|
||||||
|
Stretch, // 拉伸填充
|
||||||
|
ScaleFit, // 等比缩放,保持完整显示
|
||||||
|
ScaleFill // 等比缩放,填充整个区域(可能裁剪)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 基础按钮类
|
||||||
|
// ============================================================================
|
||||||
|
class Button : public Widget {
|
||||||
|
public:
|
||||||
|
Button();
|
||||||
|
explicit Button(const std::string &text);
|
||||||
|
~Button() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<Button> create();
|
||||||
|
static Ptr<Button> create(const std::string &text);
|
||||||
|
static Ptr<Button> create(const std::string &text, Ptr<FontAtlas> font);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用构建器方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Button *withPosition(float x, float y);
|
||||||
|
Button *withPosition(const Vec2 &pos);
|
||||||
|
Button *withAnchor(float x, float y);
|
||||||
|
Button *withAnchor(const Vec2 &anchor);
|
||||||
|
Button *withText(const std::string &text);
|
||||||
|
Button *withFont(Ptr<FontAtlas> font);
|
||||||
|
Button *withTextColor(const Color &color);
|
||||||
|
Button *withBackgroundColor(const Color &normal, const Color &hover,
|
||||||
|
const Color &pressed);
|
||||||
|
Button *withSize(float width, float height);
|
||||||
|
Button *withPadding(const Vec2 &padding);
|
||||||
|
Button *withPadding(float x, float y);
|
||||||
|
Button *withBorder(const Color &color, float width);
|
||||||
|
Button *withCornerRadius(float radius);
|
||||||
|
Button *withRoundedCornersEnabled(bool enabled);
|
||||||
|
Button *withHoverCursor(CursorShape cursor);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用 - 坐标空间设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Button *withCoordinateSpace(CoordinateSpace space);
|
||||||
|
Button *withScreenPosition(float x, float y);
|
||||||
|
Button *withScreenPosition(const Vec2 &pos);
|
||||||
|
Button *withCameraOffset(float x, float y);
|
||||||
|
Button *withCameraOffset(const Vec2 &offset);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文字内容
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setText(const std::string &text);
|
||||||
|
const std::string &getText() const { return text_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 字体
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setFont(Ptr<FontAtlas> font);
|
||||||
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 内边距
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPadding(const Vec2 &padding);
|
||||||
|
Vec2 getPadding() const { return padding_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文字颜色
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setTextColor(const Color &color);
|
||||||
|
Color getTextColor() const { return textColor_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 纯色背景设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBackgroundColor(const Color &normal, const Color &hover,
|
||||||
|
const Color &pressed);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 边框设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBorder(const Color &color, float width);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 图片背景设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr,
|
||||||
|
Ptr<Texture> pressed = nullptr);
|
||||||
|
void setBackgroundImageScaleMode(ImageScaleMode mode);
|
||||||
|
void setCustomSize(const Vec2 &size);
|
||||||
|
void setCustomSize(float width, float height);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 圆角矩形设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setCornerRadius(float radius);
|
||||||
|
float getCornerRadius() const { return cornerRadius_; }
|
||||||
|
void setRoundedCornersEnabled(bool enabled);
|
||||||
|
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 鼠标光标设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setHoverCursor(CursorShape cursor);
|
||||||
|
CursorShape getHoverCursor() const { return hoverCursor_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Alpha遮罩点击检测
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setUseAlphaMaskForHitTest(bool enabled);
|
||||||
|
bool isUseAlphaMaskForHitTest() const { return useAlphaMaskForHitTest_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 点击回调
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setOnClick(Function<void()> callback);
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
|
void drawBackgroundImage(RenderBackend &renderer, const Rect &rect);
|
||||||
|
void drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
|
const Color &color, float radius);
|
||||||
|
void fillRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
|
const Color &color, float radius);
|
||||||
|
Vec2 calculateImageSize(const Vec2 &buttonSize, const Vec2 &imageSize);
|
||||||
|
|
||||||
|
// 状态访问(供子类使用)
|
||||||
|
bool isHovered() const { return hovered_; }
|
||||||
|
bool isPressed() const { return pressed_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string text_;
|
||||||
|
Ptr<FontAtlas> font_;
|
||||||
|
Vec2 padding_ = Vec2(10.0f, 6.0f);
|
||||||
|
|
||||||
|
// 文字颜色
|
||||||
|
Color textColor_ = Colors::White;
|
||||||
|
|
||||||
|
// 纯色背景
|
||||||
|
Color bgNormal_ = Color(0.2f, 0.2f, 0.2f, 1.0f);
|
||||||
|
Color bgHover_ = Color(0.28f, 0.28f, 0.28f, 1.0f);
|
||||||
|
Color bgPressed_ = Color(0.15f, 0.15f, 0.15f, 1.0f);
|
||||||
|
|
||||||
|
// 图片背景
|
||||||
|
Ptr<Texture> imgNormal_;
|
||||||
|
Ptr<Texture> imgHover_;
|
||||||
|
Ptr<Texture> imgPressed_;
|
||||||
|
ImageScaleMode scaleMode_ = ImageScaleMode::Original;
|
||||||
|
bool useImageBackground_ = false;
|
||||||
|
|
||||||
|
// 边框
|
||||||
|
Color borderColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f);
|
||||||
|
float borderWidth_ = 1.0f;
|
||||||
|
|
||||||
|
// 圆角矩形
|
||||||
|
float cornerRadius_ = 8.0f;
|
||||||
|
bool roundedCornersEnabled_ = false;
|
||||||
|
|
||||||
|
// 鼠标光标
|
||||||
|
CursorShape hoverCursor_ = CursorShape::Hand;
|
||||||
|
bool cursorChanged_ = false;
|
||||||
|
|
||||||
|
// Alpha遮罩点击检测
|
||||||
|
bool useAlphaMaskForHitTest_ = false;
|
||||||
|
|
||||||
|
bool hovered_ = false;
|
||||||
|
bool pressed_ = false;
|
||||||
|
|
||||||
|
Function<void()> onClick_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 切换按钮 - 点击切换两种状态(支持图片和文字)
|
||||||
|
// ============================================================================
|
||||||
|
class ToggleImageButton : public Button {
|
||||||
|
public:
|
||||||
|
ToggleImageButton();
|
||||||
|
~ToggleImageButton() override = default;
|
||||||
|
|
||||||
|
static Ptr<ToggleImageButton> create();
|
||||||
|
|
||||||
|
// 设置两种状态的图片
|
||||||
|
void setStateImages(Ptr<Texture> stateOffNormal, Ptr<Texture> stateOnNormal,
|
||||||
|
Ptr<Texture> stateOffHover = nullptr,
|
||||||
|
Ptr<Texture> stateOnHover = nullptr,
|
||||||
|
Ptr<Texture> stateOffPressed = nullptr,
|
||||||
|
Ptr<Texture> stateOnPressed = nullptr);
|
||||||
|
|
||||||
|
// 设置两种状态的文字
|
||||||
|
void setStateText(const std::string &textOff, const std::string &textOn);
|
||||||
|
|
||||||
|
// 设置两种状态的文字颜色
|
||||||
|
void setStateTextColor(const Color &colorOff, const Color &colorOn);
|
||||||
|
|
||||||
|
// 获取/设置当前状态
|
||||||
|
bool isOn() const { return isOn_; }
|
||||||
|
void setOn(bool on);
|
||||||
|
void toggle();
|
||||||
|
|
||||||
|
// 设置状态改变回调
|
||||||
|
void setOnStateChange(Function<void(bool)> callback);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 状态图片
|
||||||
|
Ptr<Texture> imgOffNormal_, imgOnNormal_;
|
||||||
|
Ptr<Texture> imgOffHover_, imgOnHover_;
|
||||||
|
Ptr<Texture> imgOffPressed_, imgOnPressed_;
|
||||||
|
|
||||||
|
// 状态文字
|
||||||
|
std::string textOff_, textOn_;
|
||||||
|
bool useStateText_ = false;
|
||||||
|
|
||||||
|
// 状态文字颜色
|
||||||
|
Color textColorOff_ = Colors::White;
|
||||||
|
Color textColorOn_ = Colors::White;
|
||||||
|
bool useStateTextColor_ = false;
|
||||||
|
|
||||||
|
bool isOn_ = false;
|
||||||
|
Function<void(bool)> onStateChange_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 复选框组件
|
||||||
|
// ============================================================================
|
||||||
|
class CheckBox : public Widget {
|
||||||
|
public:
|
||||||
|
CheckBox();
|
||||||
|
~CheckBox() override = default;
|
||||||
|
|
||||||
|
static Ptr<CheckBox> create();
|
||||||
|
static Ptr<CheckBox> create(const std::string &label);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用构建器方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
CheckBox *withPosition(float x, float y);
|
||||||
|
CheckBox *withPosition(const Vec2 &pos);
|
||||||
|
CheckBox *withAnchor(float x, float y);
|
||||||
|
CheckBox *withAnchor(const Vec2 &anchor);
|
||||||
|
CheckBox *withText(const std::string &text);
|
||||||
|
CheckBox *withFont(Ptr<FontAtlas> font);
|
||||||
|
CheckBox *withTextColor(const Color &color);
|
||||||
|
CheckBox *withSize(float width, float height);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用 - 坐标空间设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
CheckBox *withCoordinateSpace(CoordinateSpace space);
|
||||||
|
CheckBox *withScreenPosition(float x, float y);
|
||||||
|
CheckBox *withScreenPosition(const Vec2 &pos);
|
||||||
|
CheckBox *withCameraOffset(float x, float y);
|
||||||
|
CheckBox *withCameraOffset(const Vec2 &offset);
|
||||||
|
|
||||||
|
void setChecked(bool checked);
|
||||||
|
bool isChecked() const { return checked_; }
|
||||||
|
void toggle();
|
||||||
|
|
||||||
|
void setLabel(const std::string &label);
|
||||||
|
const std::string &getLabel() const { return label_; }
|
||||||
|
|
||||||
|
void setFont(Ptr<FontAtlas> font);
|
||||||
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
void setTextColor(const Color &color);
|
||||||
|
Color getTextColor() const { return textColor_; }
|
||||||
|
|
||||||
|
void setBoxSize(float size);
|
||||||
|
float getBoxSize() const { return boxSize_; }
|
||||||
|
|
||||||
|
void setSpacing(float spacing);
|
||||||
|
float getSpacing() const { return spacing_; }
|
||||||
|
|
||||||
|
void setCheckedColor(const Color &color);
|
||||||
|
Color getCheckedColor() const { return checkedColor_; }
|
||||||
|
|
||||||
|
void setUncheckedColor(const Color &color);
|
||||||
|
Color getUncheckedColor() const { return uncheckedColor_; }
|
||||||
|
|
||||||
|
void setCheckMarkColor(const Color &color);
|
||||||
|
Color getCheckMarkColor() const { return checkMarkColor_; }
|
||||||
|
|
||||||
|
void setOnStateChange(Function<void(bool)> callback);
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
|
bool onMousePress(const MouseEvent &event) override;
|
||||||
|
bool onMouseRelease(const MouseEvent &event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool checked_ = false;
|
||||||
|
std::string label_;
|
||||||
|
Ptr<FontAtlas> font_;
|
||||||
|
Color textColor_ = Colors::White;
|
||||||
|
|
||||||
|
float boxSize_ = 20.0f;
|
||||||
|
float spacing_ = 8.0f;
|
||||||
|
|
||||||
|
Color checkedColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f);
|
||||||
|
Color uncheckedColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f);
|
||||||
|
Color checkMarkColor_ = Colors::White;
|
||||||
|
|
||||||
|
bool pressed_ = false;
|
||||||
|
Function<void(bool)> onStateChange_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 文本标签组件 - 用于显示静态文本
|
||||||
|
// 支持多行、对齐、阴影、描边等游戏常用效果
|
||||||
|
// ============================================================================
|
||||||
|
class Label : public Widget {
|
||||||
|
public:
|
||||||
|
Label();
|
||||||
|
explicit Label(const std::string &text);
|
||||||
|
~Label() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<Label> create();
|
||||||
|
static Ptr<Label> create(const std::string &text);
|
||||||
|
static Ptr<Label> create(const std::string &text, Ptr<FontAtlas> font);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用构建器方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Label *withPosition(float x, float y);
|
||||||
|
Label *withPosition(const Vec2 &pos);
|
||||||
|
Label *withAnchor(float x, float y);
|
||||||
|
Label *withAnchor(const Vec2 &anchor);
|
||||||
|
Label *withText(const std::string &text);
|
||||||
|
Label *withFont(Ptr<FontAtlas> font);
|
||||||
|
Label *withTextColor(const Color &color);
|
||||||
|
Label *withFontSize(int size);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 坐标空间设置(链式调用)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Label *withCoordinateSpace(CoordinateSpace space);
|
||||||
|
Label *withScreenPosition(float x, float y);
|
||||||
|
Label *withScreenPosition(const Vec2 &pos);
|
||||||
|
Label *withCameraOffset(float x, float y);
|
||||||
|
Label *withCameraOffset(const Vec2 &offset);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文本内容
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setText(const std::string &text);
|
||||||
|
const std::string &getText() const { return text_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 字体设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setFont(Ptr<FontAtlas> font);
|
||||||
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文字颜色
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setTextColor(const Color &color);
|
||||||
|
Color getTextColor() const { return textColor_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 字体大小
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setFontSize(int size);
|
||||||
|
int getFontSize() const { return fontSize_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 水平对齐方式
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
enum class HorizontalAlign { Left, Center, Right };
|
||||||
|
|
||||||
|
void setHorizontalAlign(HorizontalAlign align);
|
||||||
|
HorizontalAlign getHorizontalAlign() const { return hAlign_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 垂直对齐方式
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
enum class VerticalAlign { Top, Middle, Bottom };
|
||||||
|
|
||||||
|
void setVerticalAlign(VerticalAlign align);
|
||||||
|
VerticalAlign getVerticalAlign() const { return vAlign_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 阴影效果
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setShadowEnabled(bool enabled);
|
||||||
|
bool isShadowEnabled() const { return shadowEnabled_; }
|
||||||
|
|
||||||
|
void setShadowColor(const Color &color);
|
||||||
|
Color getShadowColor() const { return shadowColor_; }
|
||||||
|
|
||||||
|
void setShadowOffset(const Vec2 &offset);
|
||||||
|
Vec2 getShadowOffset() const { return shadowOffset_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 描边效果
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setOutlineEnabled(bool enabled);
|
||||||
|
bool isOutlineEnabled() const { return outlineEnabled_; }
|
||||||
|
|
||||||
|
void setOutlineColor(const Color &color);
|
||||||
|
Color getOutlineColor() const { return outlineColor_; }
|
||||||
|
|
||||||
|
void setOutlineWidth(float width);
|
||||||
|
float getOutlineWidth() const { return outlineWidth_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 多行文本
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setMultiLine(bool multiLine);
|
||||||
|
bool isMultiLine() const { return multiLine_; }
|
||||||
|
|
||||||
|
void setLineSpacing(float spacing);
|
||||||
|
float getLineSpacing() const { return lineSpacing_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 最大宽度(用于自动换行)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setMaxWidth(float maxWidth);
|
||||||
|
float getMaxWidth() const { return maxWidth_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 尺寸计算
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Vec2 getTextSize() const;
|
||||||
|
float getLineHeight() const;
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string text_;
|
||||||
|
Ptr<FontAtlas> font_;
|
||||||
|
Color textColor_ = Colors::White;
|
||||||
|
int fontSize_ = 16;
|
||||||
|
|
||||||
|
HorizontalAlign hAlign_ = HorizontalAlign::Left;
|
||||||
|
VerticalAlign vAlign_ = VerticalAlign::Top;
|
||||||
|
|
||||||
|
// 阴影
|
||||||
|
bool shadowEnabled_ = false;
|
||||||
|
Color shadowColor_ = Color(0.0f, 0.0f, 0.0f, 0.5f);
|
||||||
|
Vec2 shadowOffset_ = Vec2(2.0f, 2.0f);
|
||||||
|
|
||||||
|
// 描边
|
||||||
|
bool outlineEnabled_ = false;
|
||||||
|
Color outlineColor_ = Colors::Black;
|
||||||
|
float outlineWidth_ = 1.0f;
|
||||||
|
|
||||||
|
// 多行
|
||||||
|
bool multiLine_ = false;
|
||||||
|
float lineSpacing_ = 1.0f;
|
||||||
|
float maxWidth_ = 0.0f;
|
||||||
|
|
||||||
|
mutable Vec2 cachedSize_ = Vec2::Zero();
|
||||||
|
mutable bool sizeDirty_ = true;
|
||||||
|
|
||||||
|
void updateCache() const;
|
||||||
|
void drawText(RenderBackend &renderer, const Vec2 &position, const Color &color);
|
||||||
|
Vec2 calculateDrawPosition() const;
|
||||||
|
std::vector<std::string> splitLines() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 进度条组件 - 用于显示进度/百分比
|
||||||
|
// 适用于血条、能量条、加载进度、经验条等游戏场景
|
||||||
|
// ============================================================================
|
||||||
|
class ProgressBar : public Widget {
|
||||||
|
public:
|
||||||
|
ProgressBar();
|
||||||
|
~ProgressBar() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<ProgressBar> create();
|
||||||
|
static Ptr<ProgressBar> create(float min, float max, float value);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用构建器方法 - 坐标空间支持
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
ProgressBar *withPosition(float x, float y);
|
||||||
|
ProgressBar *withPosition(const Vec2 &pos);
|
||||||
|
ProgressBar *withAnchor(float x, float y);
|
||||||
|
ProgressBar *withAnchor(const Vec2 &anchor);
|
||||||
|
ProgressBar *withSize(float width, float height);
|
||||||
|
ProgressBar *withProgress(float progress);
|
||||||
|
ProgressBar *withCoordinateSpace(CoordinateSpace space);
|
||||||
|
ProgressBar *withScreenPosition(float x, float y);
|
||||||
|
ProgressBar *withScreenPosition(const Vec2 &pos);
|
||||||
|
ProgressBar *withCameraOffset(float x, float y);
|
||||||
|
ProgressBar *withCameraOffset(const Vec2 &offset);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 数值范围
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setRange(float min, float max);
|
||||||
|
float getMin() const { return min_; }
|
||||||
|
float getMax() const { return max_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 当前值
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setValue(float value);
|
||||||
|
float getValue() const { return value_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 获取百分比 (0.0 - 1.0)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
float getPercent() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 进度条方向
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
enum class Direction { LeftToRight, RightToLeft, BottomToTop, TopToBottom };
|
||||||
|
|
||||||
|
void setDirection(Direction dir);
|
||||||
|
Direction getDirection() const { return direction_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 颜色设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBackgroundColor(const Color &color);
|
||||||
|
Color getBackgroundColor() const { return bgColor_; }
|
||||||
|
|
||||||
|
void setFillColor(const Color &color);
|
||||||
|
Color getFillColor() const { return fillColor_; }
|
||||||
|
|
||||||
|
// 渐变填充色(从 fillColor_ 到 fillColorEnd_)
|
||||||
|
void setGradientFillEnabled(bool enabled);
|
||||||
|
bool isGradientFillEnabled() const { return gradientEnabled_; }
|
||||||
|
|
||||||
|
void setFillColorEnd(const Color &color);
|
||||||
|
Color getFillColorEnd() const { return fillColorEnd_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 分段颜色(根据百分比自动切换颜色)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setSegmentedColorsEnabled(bool enabled);
|
||||||
|
bool isSegmentedColorsEnabled() const { return segmentedColorsEnabled_; }
|
||||||
|
|
||||||
|
// 设置分段阈值和颜色,例如:>70%绿色, >30%黄色, 其他红色
|
||||||
|
void addColorSegment(float percentThreshold, const Color &color);
|
||||||
|
void clearColorSegments();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 圆角设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setCornerRadius(float radius);
|
||||||
|
float getCornerRadius() const { return cornerRadius_; }
|
||||||
|
|
||||||
|
void setRoundedCornersEnabled(bool enabled);
|
||||||
|
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 边框设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBorderEnabled(bool enabled);
|
||||||
|
bool isBorderEnabled() const { return borderEnabled_; }
|
||||||
|
|
||||||
|
void setBorderColor(const Color &color);
|
||||||
|
Color getBorderColor() const { return borderColor_; }
|
||||||
|
|
||||||
|
void setBorderWidth(float width);
|
||||||
|
float getBorderWidth() const { return borderWidth_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 内边距(填充与边框的距离)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPadding(float padding);
|
||||||
|
float getPadding() const { return padding_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文本显示
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setTextEnabled(bool enabled);
|
||||||
|
bool isTextEnabled() const { return textEnabled_; }
|
||||||
|
|
||||||
|
void setFont(Ptr<FontAtlas> font);
|
||||||
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
void setTextColor(const Color &color);
|
||||||
|
Color getTextColor() const { return textColor_; }
|
||||||
|
|
||||||
|
// 文本格式:"{value}/{max}", "{percent}%", "{value:.1f}" 等
|
||||||
|
void setTextFormat(const std::string &format);
|
||||||
|
const std::string &getTextFormat() const { return textFormat_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 动画效果
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setAnimatedChangeEnabled(bool enabled);
|
||||||
|
bool isAnimatedChangeEnabled() const { return animatedChangeEnabled_; }
|
||||||
|
|
||||||
|
void setAnimationSpeed(float speed); // 每秒变化量
|
||||||
|
float getAnimationSpeed() const { return animationSpeed_; }
|
||||||
|
|
||||||
|
// 延迟显示效果(如LOL血条)
|
||||||
|
void setDelayedDisplayEnabled(bool enabled);
|
||||||
|
bool isDelayedDisplayEnabled() const { return delayedDisplayEnabled_; }
|
||||||
|
|
||||||
|
void setDelayTime(float seconds);
|
||||||
|
float getDelayTime() const { return delayTime_; }
|
||||||
|
|
||||||
|
void setDelayedFillColor(const Color &color);
|
||||||
|
Color getDelayedFillColor() const { return delayedFillColor_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 条纹效果
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setStripedEnabled(bool enabled);
|
||||||
|
bool isStripedEnabled() const { return stripedEnabled_; }
|
||||||
|
|
||||||
|
void setStripeColor(const Color &color);
|
||||||
|
Color getStripeColor() const { return stripeColor_; }
|
||||||
|
|
||||||
|
void setStripeSpeed(float speed); // 条纹移动速度
|
||||||
|
float getStripeSpeed() const { return stripeSpeed_; }
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onUpdate(float deltaTime) override;
|
||||||
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 数值
|
||||||
|
float min_ = 0.0f;
|
||||||
|
float max_ = 100.0f;
|
||||||
|
float value_ = 50.0f;
|
||||||
|
|
||||||
|
// 方向
|
||||||
|
Direction direction_ = Direction::LeftToRight;
|
||||||
|
|
||||||
|
// 颜色
|
||||||
|
Color bgColor_ = Color(0.2f, 0.2f, 0.2f, 1.0f);
|
||||||
|
Color fillColor_ = Color(0.0f, 0.8f, 0.2f, 1.0f);
|
||||||
|
Color fillColorEnd_ = Color(0.0f, 0.6f, 0.1f, 1.0f);
|
||||||
|
bool gradientEnabled_ = false;
|
||||||
|
|
||||||
|
// 分段颜色
|
||||||
|
bool segmentedColorsEnabled_ = false;
|
||||||
|
std::vector<std::pair<float, Color>> colorSegments_;
|
||||||
|
|
||||||
|
// 圆角
|
||||||
|
float cornerRadius_ = 4.0f;
|
||||||
|
bool roundedCornersEnabled_ = true;
|
||||||
|
|
||||||
|
// 边框
|
||||||
|
bool borderEnabled_ = false;
|
||||||
|
Color borderColor_ = Colors::White;
|
||||||
|
float borderWidth_ = 1.0f;
|
||||||
|
|
||||||
|
// 内边距
|
||||||
|
float padding_ = 2.0f;
|
||||||
|
|
||||||
|
// 文本
|
||||||
|
bool textEnabled_ = false;
|
||||||
|
Ptr<FontAtlas> font_;
|
||||||
|
Color textColor_ = Colors::White;
|
||||||
|
std::string textFormat_ = "{percent:.0f}%";
|
||||||
|
|
||||||
|
// 动画
|
||||||
|
bool animatedChangeEnabled_ = false;
|
||||||
|
float animationSpeed_ = 100.0f;
|
||||||
|
float displayValue_ = 50.0f; // 用于动画的显示值
|
||||||
|
|
||||||
|
// 延迟显示
|
||||||
|
bool delayedDisplayEnabled_ = false;
|
||||||
|
float delayTime_ = 0.3f;
|
||||||
|
float delayTimer_ = 0.0f;
|
||||||
|
float delayedValue_ = 50.0f;
|
||||||
|
Color delayedFillColor_ = Color(1.0f, 0.0f, 0.0f, 0.5f);
|
||||||
|
|
||||||
|
// 条纹
|
||||||
|
bool stripedEnabled_ = false;
|
||||||
|
Color stripeColor_ = Color(1.0f, 1.0f, 1.0f, 0.2f);
|
||||||
|
float stripeSpeed_ = 50.0f;
|
||||||
|
float stripeOffset_ = 0.0f;
|
||||||
|
|
||||||
|
Color getCurrentFillColor() const;
|
||||||
|
std::string formatText() const;
|
||||||
|
void drawRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius);
|
||||||
|
void fillRoundedRect(RenderBackend &renderer, const Rect &rect, const Color &color, float radius);
|
||||||
|
void drawStripes(RenderBackend &renderer, const Rect &rect);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 单选按钮组件
|
||||||
|
// ============================================================================
|
||||||
|
class RadioButton : public Widget {
|
||||||
|
public:
|
||||||
|
RadioButton();
|
||||||
|
~RadioButton() override = default;
|
||||||
|
|
||||||
|
static Ptr<RadioButton> create();
|
||||||
|
static Ptr<RadioButton> create(const std::string &label);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用构建器方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
RadioButton *withPosition(float x, float y);
|
||||||
|
RadioButton *withPosition(const Vec2 &pos);
|
||||||
|
RadioButton *withAnchor(float x, float y);
|
||||||
|
RadioButton *withAnchor(const Vec2 &anchor);
|
||||||
|
RadioButton *withText(const std::string &text);
|
||||||
|
RadioButton *withFont(Ptr<FontAtlas> font);
|
||||||
|
RadioButton *withTextColor(const Color &color);
|
||||||
|
RadioButton *withSize(float width, float height);
|
||||||
|
RadioButton *withCoordinateSpace(CoordinateSpace space);
|
||||||
|
RadioButton *withScreenPosition(float x, float y);
|
||||||
|
RadioButton *withScreenPosition(const Vec2 &pos);
|
||||||
|
RadioButton *withCameraOffset(float x, float y);
|
||||||
|
RadioButton *withCameraOffset(const Vec2 &offset);
|
||||||
|
|
||||||
|
void setSelected(bool selected);
|
||||||
|
bool isSelected() const { return selected_; }
|
||||||
|
|
||||||
|
void setLabel(const std::string &label);
|
||||||
|
const std::string &getLabel() const { return label_; }
|
||||||
|
|
||||||
|
void setFont(Ptr<FontAtlas> font);
|
||||||
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
void setTextColor(const Color &color);
|
||||||
|
Color getTextColor() const { return textColor_; }
|
||||||
|
|
||||||
|
void setCircleSize(float size);
|
||||||
|
float getCircleSize() const { return circleSize_; }
|
||||||
|
|
||||||
|
void setSpacing(float spacing);
|
||||||
|
float getSpacing() const { return spacing_; }
|
||||||
|
|
||||||
|
void setSelectedColor(const Color &color);
|
||||||
|
Color getSelectedColor() const { return selectedColor_; }
|
||||||
|
|
||||||
|
void setUnselectedColor(const Color &color);
|
||||||
|
Color getUnselectedColor() const { return unselectedColor_; }
|
||||||
|
|
||||||
|
void setDotColor(const Color &color);
|
||||||
|
Color getDotColor() const { return dotColor_; }
|
||||||
|
|
||||||
|
void setGroupId(int groupId);
|
||||||
|
int getGroupId() const { return groupId_; }
|
||||||
|
|
||||||
|
void setOnStateChange(Function<void(bool)> callback);
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
|
bool onMousePress(const MouseEvent &event) override;
|
||||||
|
bool onMouseRelease(const MouseEvent &event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool selected_ = false;
|
||||||
|
std::string label_;
|
||||||
|
Ptr<FontAtlas> font_;
|
||||||
|
Color textColor_ = Colors::White;
|
||||||
|
|
||||||
|
float circleSize_ = 20.0f;
|
||||||
|
float spacing_ = 8.0f;
|
||||||
|
|
||||||
|
Color selectedColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f);
|
||||||
|
Color unselectedColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f);
|
||||||
|
Color dotColor_ = Colors::White;
|
||||||
|
|
||||||
|
int groupId_ = 0; // 用于分组,同组内只能选一个
|
||||||
|
|
||||||
|
bool pressed_ = false;
|
||||||
|
Function<void(bool)> onStateChange_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 单选按钮组管理器
|
||||||
|
// ============================================================================
|
||||||
|
class RadioButtonGroup {
|
||||||
|
public:
|
||||||
|
void addButton(RadioButton *button);
|
||||||
|
void removeButton(RadioButton *button);
|
||||||
|
void selectButton(RadioButton *button);
|
||||||
|
RadioButton *getSelectedButton() const { return selectedButton_; }
|
||||||
|
|
||||||
|
void setOnSelectionChange(Function<void(RadioButton*)> callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<RadioButton*> buttons_;
|
||||||
|
RadioButton *selectedButton_ = nullptr;
|
||||||
|
Function<void(RadioButton*)> onSelectionChange_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 滑动条组件
|
||||||
|
// ============================================================================
|
||||||
|
class Slider : public Widget {
|
||||||
|
public:
|
||||||
|
Slider();
|
||||||
|
~Slider() override = default;
|
||||||
|
|
||||||
|
static Ptr<Slider> create();
|
||||||
|
static Ptr<Slider> create(float min, float max, float value);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用构建器方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Slider *withPosition(float x, float y);
|
||||||
|
Slider *withPosition(const Vec2 &pos);
|
||||||
|
Slider *withAnchor(float x, float y);
|
||||||
|
Slider *withAnchor(const Vec2 &anchor);
|
||||||
|
Slider *withSize(float width, float height);
|
||||||
|
Slider *withMinValue(float min);
|
||||||
|
Slider *withMaxValue(float max);
|
||||||
|
Slider *withValue(float value);
|
||||||
|
Slider *withCoordinateSpace(CoordinateSpace space);
|
||||||
|
Slider *withScreenPosition(float x, float y);
|
||||||
|
Slider *withScreenPosition(const Vec2 &pos);
|
||||||
|
Slider *withCameraOffset(float x, float y);
|
||||||
|
Slider *withCameraOffset(const Vec2 &offset);
|
||||||
|
|
||||||
|
void setRange(float min, float max);
|
||||||
|
float getMin() const { return min_; }
|
||||||
|
float getMax() const { return max_; }
|
||||||
|
|
||||||
|
void setValue(float value);
|
||||||
|
float getValue() const { return value_; }
|
||||||
|
|
||||||
|
void setStep(float step);
|
||||||
|
float getStep() const { return step_; }
|
||||||
|
|
||||||
|
void setVertical(bool vertical);
|
||||||
|
bool isVertical() const { return vertical_; }
|
||||||
|
|
||||||
|
void setTrackSize(float size);
|
||||||
|
float getTrackSize() const { return trackSize_; }
|
||||||
|
|
||||||
|
void setThumbSize(float size);
|
||||||
|
float getThumbSize() const { return thumbSize_; }
|
||||||
|
|
||||||
|
void setTrackColor(const Color &color);
|
||||||
|
Color getTrackColor() const { return trackColor_; }
|
||||||
|
|
||||||
|
void setFillColor(const Color &color);
|
||||||
|
Color getFillColor() const { return fillColor_; }
|
||||||
|
|
||||||
|
void setThumbColor(const Color &color);
|
||||||
|
Color getThumbColor() const { return thumbColor_; }
|
||||||
|
|
||||||
|
void setThumbHoverColor(const Color &color);
|
||||||
|
Color getThumbHoverColor() const { return thumbHoverColor_; }
|
||||||
|
|
||||||
|
void setThumbPressedColor(const Color &color);
|
||||||
|
Color getThumbPressedColor() const { return thumbPressedColor_; }
|
||||||
|
|
||||||
|
void setShowThumb(bool show);
|
||||||
|
bool isShowThumb() const { return showThumb_; }
|
||||||
|
|
||||||
|
void setShowFill(bool show);
|
||||||
|
bool isShowFill() const { return showFill_; }
|
||||||
|
|
||||||
|
void setTextEnabled(bool enabled);
|
||||||
|
bool isTextEnabled() const { return textEnabled_; }
|
||||||
|
|
||||||
|
void setFont(Ptr<FontAtlas> font);
|
||||||
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
void setTextColor(const Color &color);
|
||||||
|
Color getTextColor() const { return textColor_; }
|
||||||
|
|
||||||
|
void setTextFormat(const std::string &format);
|
||||||
|
const std::string &getTextFormat() const { return textFormat_; }
|
||||||
|
|
||||||
|
void setOnValueChange(Function<void(float)> callback);
|
||||||
|
void setOnDragStart(Function<void()> callback);
|
||||||
|
void setOnDragEnd(Function<void()> callback);
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
|
bool onMousePress(const MouseEvent &event) override;
|
||||||
|
bool onMouseRelease(const MouseEvent &event) override;
|
||||||
|
bool onMouseMove(const MouseEvent &event) override;
|
||||||
|
void onMouseEnter() override;
|
||||||
|
void onMouseLeave() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float min_ = 0.0f;
|
||||||
|
float max_ = 100.0f;
|
||||||
|
float value_ = 50.0f;
|
||||||
|
float step_ = 0.0f; // 0表示无步进
|
||||||
|
|
||||||
|
bool vertical_ = false;
|
||||||
|
float trackSize_ = 6.0f;
|
||||||
|
float thumbSize_ = 16.0f;
|
||||||
|
|
||||||
|
Color trackColor_ = Color(0.3f, 0.3f, 0.3f, 1.0f);
|
||||||
|
Color fillColor_ = Color(0.2f, 0.6f, 1.0f, 1.0f);
|
||||||
|
Color thumbColor_ = Color(0.8f, 0.8f, 0.8f, 1.0f);
|
||||||
|
Color thumbHoverColor_ = Color(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
Color thumbPressedColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f);
|
||||||
|
|
||||||
|
bool showThumb_ = true;
|
||||||
|
bool showFill_ = true;
|
||||||
|
|
||||||
|
bool textEnabled_ = false;
|
||||||
|
Ptr<FontAtlas> font_;
|
||||||
|
Color textColor_ = Colors::White;
|
||||||
|
std::string textFormat_ = "{value:.0f}";
|
||||||
|
|
||||||
|
bool dragging_ = false;
|
||||||
|
bool hovered_ = false;
|
||||||
|
|
||||||
|
Function<void(float)> onValueChange_;
|
||||||
|
Function<void()> onDragStart_;
|
||||||
|
Function<void()> onDragEnd_;
|
||||||
|
|
||||||
|
float valueToPosition(float value) const;
|
||||||
|
float positionToValue(float pos) const;
|
||||||
|
Rect getThumbRect() const;
|
||||||
|
Rect getTrackRect() const;
|
||||||
|
std::string formatText() const;
|
||||||
|
float snapToStep(float value) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/font.h>
|
||||||
|
#include <extra2d/ui/widget.h>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 文本组件 - 继承自 Widget 的 UI 组件
|
||||||
|
// ============================================================================
|
||||||
|
class Text : public Widget {
|
||||||
|
public:
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 对齐方式枚举
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
enum class Alignment { Left, Center, Right };
|
||||||
|
enum class VerticalAlignment { Top, Middle, Bottom };
|
||||||
|
|
||||||
|
Text();
|
||||||
|
explicit Text(const std::string &text);
|
||||||
|
~Text() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<Text> create();
|
||||||
|
static Ptr<Text> create(const std::string &text);
|
||||||
|
static Ptr<Text> create(const std::string &text, Ptr<FontAtlas> font);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 格式化创建方法(类似 printf)
|
||||||
|
// 使用示例:
|
||||||
|
// auto text = Text::createFormat("FPS: %d", 60);
|
||||||
|
// auto text = Text::createFormat(font, "得分: %d", score);
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<Text> createFormat(const char *fmt, ...);
|
||||||
|
static Ptr<Text> createFormat(Ptr<FontAtlas> font, const char *fmt, ...);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用构建器方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Text *withPosition(float x, float y);
|
||||||
|
Text *withPosition(const Vec2 &pos);
|
||||||
|
Text *withAnchor(float x, float y);
|
||||||
|
Text *withAnchor(const Vec2 &anchor);
|
||||||
|
Text *withTextColor(const Color &color);
|
||||||
|
Text *withFont(Ptr<FontAtlas> font);
|
||||||
|
Text *withFontSize(int size);
|
||||||
|
Text *withAlignment(Alignment align);
|
||||||
|
Text *withVerticalAlignment(VerticalAlignment align);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文字内容
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setText(const std::string &text);
|
||||||
|
const std::string &getText() const { return text_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 格式化文本设置(类似 printf)
|
||||||
|
// 使用示例:
|
||||||
|
// text->setFormat("FPS: %d", 60);
|
||||||
|
// text->setFormat("位置: (%.1f, %.1f)", x, y);
|
||||||
|
// text->setFormat("生命值: %d/100", hp);
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setFormat(const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
char buffer[256];
|
||||||
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
text_ = buffer;
|
||||||
|
sizeDirty_ = true;
|
||||||
|
updateSpatialIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 链式调用的格式化方法
|
||||||
|
// 注意:由于C++可变参数的限制,链式调用需要单独设置格式和值
|
||||||
|
Text *withFormattedText(const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
char buffer[256];
|
||||||
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
text_ = buffer;
|
||||||
|
sizeDirty_ = true;
|
||||||
|
updateSpatialIndex();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 字体
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setFont(Ptr<FontAtlas> font);
|
||||||
|
Ptr<FontAtlas> getFont() const { return font_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文字属性
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setTextColor(const Color &color);
|
||||||
|
Color getTextColor() const { return color_; }
|
||||||
|
|
||||||
|
void setFontSize(int size);
|
||||||
|
int getFontSize() const { return fontSize_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 对齐方式
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setAlignment(Alignment align);
|
||||||
|
Alignment getAlignment() const { return alignment_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 垂直对齐方式
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setVerticalAlignment(VerticalAlignment align);
|
||||||
|
VerticalAlignment getVerticalAlignment() const { return verticalAlignment_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 尺寸计算
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Vec2 getTextSize() const;
|
||||||
|
float getLineHeight() const;
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式调用 - 坐标空间设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Text *withCoordinateSpace(CoordinateSpace space);
|
||||||
|
Text *withScreenPosition(float x, float y);
|
||||||
|
Text *withScreenPosition(const Vec2 &pos);
|
||||||
|
Text *withCameraOffset(float x, float y);
|
||||||
|
Text *withCameraOffset(const Vec2 &offset);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onDrawWidget(RenderBackend &renderer) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string text_;
|
||||||
|
Ptr<FontAtlas> font_;
|
||||||
|
Color color_ = Colors::White;
|
||||||
|
int fontSize_ = 16;
|
||||||
|
Alignment alignment_ = Alignment::Left;
|
||||||
|
VerticalAlignment verticalAlignment_ = VerticalAlignment::Top;
|
||||||
|
|
||||||
|
mutable Vec2 cachedSize_ = Vec2::Zero();
|
||||||
|
mutable bool sizeDirty_ = true;
|
||||||
|
|
||||||
|
void updateCache() const;
|
||||||
|
Vec2 calculateDrawPosition() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/platform/input.h>
|
||||||
|
#include <extra2d/scene/node.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 鼠标事件结构
|
||||||
|
// ============================================================================
|
||||||
|
struct MouseEvent {
|
||||||
|
MouseButton button;
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
int mods; // 修饰键状态
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 坐标空间枚举 - 定义 UI 组件的渲染坐标空间
|
||||||
|
// ============================================================================
|
||||||
|
enum class CoordinateSpace {
|
||||||
|
Screen, // 屏幕空间 - 固定位置,不随相机移动
|
||||||
|
World, // 世界空间 - 随相机移动(默认行为)
|
||||||
|
Camera, // 相机空间 - 相对于相机位置的偏移
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Widget 基类 - UI 组件的基础
|
||||||
|
// ============================================================================
|
||||||
|
class Widget : public Node {
|
||||||
|
public:
|
||||||
|
Widget();
|
||||||
|
~Widget() override = default;
|
||||||
|
|
||||||
|
void setSize(const Size &size);
|
||||||
|
void setSize(float width, float height);
|
||||||
|
Size getSize() const { return size_; }
|
||||||
|
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 坐标空间设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setCoordinateSpace(CoordinateSpace space);
|
||||||
|
CoordinateSpace getCoordinateSpace() const { return coordinateSpace_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 屏幕空间位置设置(仅在 Screen 空间下有效)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setScreenPosition(const Vec2 &pos);
|
||||||
|
void setScreenPosition(float x, float y);
|
||||||
|
Vec2 getScreenPosition() const { return screenPosition_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 相机空间偏移设置(仅在 Camera 空间下有效)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setCameraOffset(const Vec2 &offset);
|
||||||
|
void setCameraOffset(float x, float y);
|
||||||
|
Vec2 getCameraOffset() const { return cameraOffset_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 鼠标事件处理(子类可重写)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual bool onMousePress(const MouseEvent &event) { return false; }
|
||||||
|
virtual bool onMouseRelease(const MouseEvent &event) { return false; }
|
||||||
|
virtual bool onMouseMove(const MouseEvent &event) { return false; }
|
||||||
|
virtual void onMouseEnter() {}
|
||||||
|
virtual void onMouseLeave() {}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 启用/禁用状态
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setEnabled(bool enabled) { enabled_ = enabled; }
|
||||||
|
bool isEnabled() const { return enabled_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 焦点状态
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setFocused(bool focused) { focused_ = focused; }
|
||||||
|
bool isFocused() const { return focused_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// 供子类使用的辅助方法
|
||||||
|
bool isPointInside(float x, float y) const {
|
||||||
|
return getBoundingBox().containsPoint(Point(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取实际渲染位置(根据坐标空间计算)
|
||||||
|
Vec2 getRenderPosition() const;
|
||||||
|
|
||||||
|
// 子类重写此方法以支持自定义渲染
|
||||||
|
virtual void onDrawWidget(RenderBackend &renderer) {}
|
||||||
|
|
||||||
|
// 重写 Node 的 onDraw 以处理坐标空间
|
||||||
|
void onDraw(RenderBackend &renderer) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Size size_ = Size::Zero();
|
||||||
|
bool enabled_ = true;
|
||||||
|
bool focused_ = false;
|
||||||
|
bool hovered_ = false;
|
||||||
|
|
||||||
|
// 坐标空间相关
|
||||||
|
CoordinateSpace coordinateSpace_ = CoordinateSpace::World;
|
||||||
|
Vec2 screenPosition_ = Vec2::Zero(); // 屏幕空间位置
|
||||||
|
Vec2 cameraOffset_ = Vec2::Zero(); // 相机空间偏移
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 存档类型枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class SaveDataType {
|
||||||
|
Account, // 用户存档(与特定用户关联)
|
||||||
|
Common, // 公共存档(所有用户共享)
|
||||||
|
Cache, // 缓存数据(可删除)
|
||||||
|
Device, // 设备存档
|
||||||
|
Temporary, // 临时数据
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 用户ID结构(封装 Switch AccountUid)
|
||||||
|
// ============================================================================
|
||||||
|
struct UserId {
|
||||||
|
uint64_t uid[2] = {0, 0};
|
||||||
|
|
||||||
|
bool isValid() const { return uid[0] != 0 || uid[1] != 0; }
|
||||||
|
bool operator==(const UserId &other) const {
|
||||||
|
return uid[0] == other.uid[0] && uid[1] == other.uid[1];
|
||||||
|
}
|
||||||
|
bool operator!=(const UserId &other) const { return !(*this == other); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DataStore 类 - 数据持久化(支持 Switch 存档系统)
|
||||||
|
// ============================================================================
|
||||||
|
class DataStore {
|
||||||
|
public:
|
||||||
|
DataStore();
|
||||||
|
~DataStore();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文件操作
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 加载 INI 文件
|
||||||
|
bool load(const std::string &filename);
|
||||||
|
|
||||||
|
/// 保存到 INI 文件
|
||||||
|
bool save(const std::string &filename);
|
||||||
|
|
||||||
|
/// 获取当前文件名
|
||||||
|
const std::string &getFilename() const { return filename_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Switch 存档系统支持
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 挂载 Switch 存档数据
|
||||||
|
* @param type 存档类型
|
||||||
|
* @param userId 用户ID(Account 类型需要)
|
||||||
|
* @param mountName 挂载点名称(默认 "save")
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
bool mountSaveData(SaveDataType type = SaveDataType::Account,
|
||||||
|
const UserId &userId = UserId(),
|
||||||
|
const std::string &mountName = "save");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 卸载存档挂载
|
||||||
|
* @param mountName 挂载点名称
|
||||||
|
*/
|
||||||
|
void unmountSaveData(const std::string &mountName = "save");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提交存档更改(重要:修改后必须调用)
|
||||||
|
* @param mountName 挂载点名称
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
bool commitSaveData(const std::string &mountName = "save");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查存档是否已挂载
|
||||||
|
*/
|
||||||
|
bool isSaveDataMounted() const { return saveDataMounted_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取挂载点路径
|
||||||
|
*/
|
||||||
|
std::string getSaveDataPath(const std::string &path = "") const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 用户账户管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前预选用户ID
|
||||||
|
* @return 用户ID(无效时返回空ID)
|
||||||
|
*/
|
||||||
|
static UserId getCurrentUserId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置默认用户ID
|
||||||
|
*/
|
||||||
|
void setDefaultUserId(const UserId &userId) { defaultUserId_ = userId; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取默认用户ID
|
||||||
|
*/
|
||||||
|
UserId getDefaultUserId() const { return defaultUserId_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 数据读写
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 获取字符串值
|
||||||
|
std::string getString(const std::string §ion, const std::string &key,
|
||||||
|
const std::string &defaultValue = "");
|
||||||
|
|
||||||
|
/// 获取整数值
|
||||||
|
int getInt(const std::string §ion, const std::string &key,
|
||||||
|
int defaultValue = 0);
|
||||||
|
|
||||||
|
/// 获取浮点数值
|
||||||
|
float getFloat(const std::string §ion, const std::string &key,
|
||||||
|
float defaultValue = 0.0f);
|
||||||
|
|
||||||
|
/// 获取布尔值
|
||||||
|
bool getBool(const std::string §ion, const std::string &key,
|
||||||
|
bool defaultValue = false);
|
||||||
|
|
||||||
|
/// 设置字符串值
|
||||||
|
void setString(const std::string §ion, const std::string &key,
|
||||||
|
const std::string &value);
|
||||||
|
|
||||||
|
/// 设置整数值
|
||||||
|
void setInt(const std::string §ion, const std::string &key, int value);
|
||||||
|
|
||||||
|
/// 设置浮点数值
|
||||||
|
void setFloat(const std::string §ion, const std::string &key,
|
||||||
|
float value);
|
||||||
|
|
||||||
|
/// 设置布尔值
|
||||||
|
void setBool(const std::string §ion, const std::string &key, bool value);
|
||||||
|
|
||||||
|
/// 删除键
|
||||||
|
void removeKey(const std::string §ion, const std::string &key);
|
||||||
|
|
||||||
|
/// 删除整个 section
|
||||||
|
void removeSection(const std::string §ion);
|
||||||
|
|
||||||
|
/// 检查键是否存在
|
||||||
|
bool hasKey(const std::string §ion, const std::string &key);
|
||||||
|
|
||||||
|
/// 检查 section 是否存在
|
||||||
|
bool hasSection(const std::string §ion);
|
||||||
|
|
||||||
|
/// 清除所有数据
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 事务支持
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始事务(批量操作,延迟写入)
|
||||||
|
*/
|
||||||
|
void beginTransaction();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提交事务(写入文件)
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
bool commit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 回滚事务(放弃更改)
|
||||||
|
*/
|
||||||
|
void rollback();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否在事务中
|
||||||
|
*/
|
||||||
|
bool isInTransaction() const { return inTransaction_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 工具方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 获取所有 section 名称
|
||||||
|
std::vector<std::string> getAllSections() const;
|
||||||
|
|
||||||
|
/// 获取指定 section 的所有 key
|
||||||
|
std::vector<std::string> getAllKeys(const std::string §ion) const;
|
||||||
|
|
||||||
|
/// 从存档加载(自动处理挂载路径)
|
||||||
|
bool loadFromSave(const std::string &path);
|
||||||
|
|
||||||
|
/// 保存到存档(自动处理挂载路径和提交)
|
||||||
|
bool saveToSave(const std::string &path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Impl;
|
||||||
|
UniquePtr<Impl> impl_;
|
||||||
|
std::string filename_;
|
||||||
|
std::string mountName_;
|
||||||
|
UserId defaultUserId_;
|
||||||
|
bool saveDataMounted_ = false;
|
||||||
|
bool inTransaction_ = false;
|
||||||
|
bool dirty_ = false;
|
||||||
|
|
||||||
|
// 内部辅助方法
|
||||||
|
bool internalSave(const std::string &filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,270 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
// SDL2 日志头文件
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 日志级别枚举 - 映射到 SDL_LogPriority
|
||||||
|
// ============================================================================
|
||||||
|
enum class LogLevel {
|
||||||
|
Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志
|
||||||
|
Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志
|
||||||
|
Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志
|
||||||
|
Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志
|
||||||
|
Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志
|
||||||
|
Fatal = SDL_LOG_PRIORITY_CRITICAL, // SDL 严重日志
|
||||||
|
Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 (使用 Critical+1 作为关闭标记)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 简单的 fmt-style {} 格式化器
|
||||||
|
// ============================================================================
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
// 将单个参数转为字符串
|
||||||
|
template <typename T> inline std::string to_string_arg(const T &value) {
|
||||||
|
if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
return value;
|
||||||
|
} else if constexpr (std::is_same_v<T, const char *> ||
|
||||||
|
std::is_same_v<T, char *>) {
|
||||||
|
return value ? std::string(value) : std::string("(null)");
|
||||||
|
} else if constexpr (std::is_same_v<T, bool>) {
|
||||||
|
return value ? "true" : "false";
|
||||||
|
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||||
|
// 对浮点数使用特殊格式
|
||||||
|
if constexpr (std::is_floating_point_v<T>) {
|
||||||
|
char buf[64];
|
||||||
|
snprintf(buf, sizeof(buf), "%.2f", static_cast<double>(value));
|
||||||
|
return buf;
|
||||||
|
} else {
|
||||||
|
return std::to_string(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << value;
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化基础情况:没有更多参数
|
||||||
|
inline std::string format_impl(const char *fmt) {
|
||||||
|
std::string result;
|
||||||
|
while (*fmt) {
|
||||||
|
if (*fmt == '{' && *(fmt + 1) == '}') {
|
||||||
|
result += "{}"; // 无参数可替换,保留原样
|
||||||
|
fmt += 2;
|
||||||
|
} else {
|
||||||
|
result += *fmt;
|
||||||
|
++fmt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化递归:替换第一个 {} 并递归处理剩余
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
inline std::string format_impl(const char *fmt, const T &first,
|
||||||
|
const Args &...rest) {
|
||||||
|
std::string result;
|
||||||
|
while (*fmt) {
|
||||||
|
if (*fmt == '{') {
|
||||||
|
// 检查 {:#x} 等格式说明符
|
||||||
|
if (*(fmt + 1) == '}') {
|
||||||
|
result += to_string_arg(first);
|
||||||
|
fmt += 2;
|
||||||
|
result += format_impl(fmt, rest...);
|
||||||
|
return result;
|
||||||
|
} else if (*(fmt + 1) == ':') {
|
||||||
|
// 跳过格式说明符直到 }
|
||||||
|
const char *end = fmt + 2;
|
||||||
|
while (*end && *end != '}')
|
||||||
|
++end;
|
||||||
|
if (*end == '}') {
|
||||||
|
// 检查是否是十六进制格式
|
||||||
|
std::string spec(fmt + 2, end);
|
||||||
|
if (spec.find('x') != std::string::npos ||
|
||||||
|
spec.find('X') != std::string::npos) {
|
||||||
|
if constexpr (std::is_integral_v<T>) {
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "0x%x",
|
||||||
|
static_cast<unsigned int>(first));
|
||||||
|
result += buf;
|
||||||
|
} else {
|
||||||
|
result += to_string_arg(first);
|
||||||
|
}
|
||||||
|
} else if (spec.find('f') != std::string::npos ||
|
||||||
|
spec.find('.') != std::string::npos) {
|
||||||
|
if constexpr (std::is_arithmetic_v<T>) {
|
||||||
|
// 解析精度
|
||||||
|
int precision = 2;
|
||||||
|
auto dot = spec.find('.');
|
||||||
|
if (dot != std::string::npos) {
|
||||||
|
precision = 0;
|
||||||
|
for (size_t i = dot + 1;
|
||||||
|
i < spec.size() && spec[i] >= '0' && spec[i] <= '9'; ++i) {
|
||||||
|
precision = precision * 10 + (spec[i] - '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char fmtbuf[16];
|
||||||
|
snprintf(fmtbuf, sizeof(fmtbuf), "%%.%df", precision);
|
||||||
|
char buf[64];
|
||||||
|
snprintf(buf, sizeof(buf), fmtbuf, static_cast<double>(first));
|
||||||
|
result += buf;
|
||||||
|
} else {
|
||||||
|
result += to_string_arg(first);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result += to_string_arg(first);
|
||||||
|
}
|
||||||
|
fmt = end + 1;
|
||||||
|
result += format_impl(fmt, rest...);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += *fmt;
|
||||||
|
++fmt;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// 顶层格式化函数
|
||||||
|
template <typename... Args>
|
||||||
|
inline std::string e2d_format(const char *fmt, const Args &...args) {
|
||||||
|
return detail::format_impl(fmt, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无参数版本
|
||||||
|
inline std::string e2d_format(const char *fmt) { return std::string(fmt); }
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Logger 类 - 使用 SDL2 日志系统
|
||||||
|
// ============================================================================
|
||||||
|
class Logger {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 初始化日志系统
|
||||||
|
*/
|
||||||
|
static void init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭日志系统
|
||||||
|
*/
|
||||||
|
static void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置日志级别
|
||||||
|
* @param level 日志级别
|
||||||
|
*/
|
||||||
|
static void setLevel(LogLevel level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置是否输出到控制台
|
||||||
|
* @param enable 是否启用
|
||||||
|
*/
|
||||||
|
static void setConsoleOutput(bool enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置日志输出到文件
|
||||||
|
* @param filename 日志文件名
|
||||||
|
*/
|
||||||
|
static void setFileOutput(const std::string &filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前日志级别
|
||||||
|
* @return 当前日志级别
|
||||||
|
*/
|
||||||
|
static LogLevel getLevel() { return level_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 日志记录模板函数
|
||||||
|
* @param level 日志级别
|
||||||
|
* @param fmt 格式化字符串
|
||||||
|
* @param args 可变参数
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
static void log(LogLevel level, const char *fmt, const Args &...args) {
|
||||||
|
if (static_cast<int>(level) < static_cast<int>(level_))
|
||||||
|
return;
|
||||||
|
std::string msg = e2d_format(fmt, args...);
|
||||||
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
static_cast<SDL_LogPriority>(level), "[%s] %s",
|
||||||
|
getLevelString(level), msg.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 日志记录无参数版本
|
||||||
|
* @param level 日志级别
|
||||||
|
* @param msg 日志消息
|
||||||
|
*/
|
||||||
|
static void log(LogLevel level, const char *msg) {
|
||||||
|
if (static_cast<int>(level) < static_cast<int>(level_))
|
||||||
|
return;
|
||||||
|
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
static_cast<SDL_LogPriority>(level), "[%s] %s",
|
||||||
|
getLevelString(level), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static LogLevel level_; // 当前日志级别
|
||||||
|
static bool initialized_; // 是否已初始化
|
||||||
|
static bool consoleOutput_; // 是否输出到控制台
|
||||||
|
static bool fileOutput_; // 是否输出到文件
|
||||||
|
static std::string logFile_; // 日志文件路径
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取日志级别字符串
|
||||||
|
* @param level 日志级别
|
||||||
|
* @return 级别字符串
|
||||||
|
*/
|
||||||
|
static const char *getLevelString(LogLevel level);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 日志宏
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifdef E2D_DEBUG
|
||||||
|
#define E2D_LOG_TRACE(...) \
|
||||||
|
::extra2d::Logger::log(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
||||||
|
#define E2D_LOG_DEBUG(...) \
|
||||||
|
::extra2d::Logger::log(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define E2D_LOG_TRACE(...)
|
||||||
|
#define E2D_LOG_DEBUG(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define E2D_LOG_INFO(...) \
|
||||||
|
::extra2d::Logger::log(::extra2d::LogLevel::Info, __VA_ARGS__)
|
||||||
|
#define E2D_LOG_WARN(...) \
|
||||||
|
::extra2d::Logger::log(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
||||||
|
#define E2D_LOG_ERROR(...) \
|
||||||
|
::extra2d::Logger::log(::extra2d::LogLevel::Error, __VA_ARGS__)
|
||||||
|
#define E2D_LOG_FATAL(...) \
|
||||||
|
::extra2d::Logger::log(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
||||||
|
|
||||||
|
// 简化的日志宏
|
||||||
|
#define E2D_INFO(...) E2D_LOG_INFO(__VA_ARGS__)
|
||||||
|
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
|
||||||
|
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
|
||||||
|
#define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__)
|
||||||
|
#ifdef E2D_DEBUG
|
||||||
|
#define E2D_DEBUG_LOG(...) E2D_LOG_DEBUG(__VA_ARGS__)
|
||||||
|
#define E2D_TRACE(...) E2D_LOG_TRACE(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define E2D_DEBUG_LOG(...)
|
||||||
|
#define E2D_TRACE(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Random 类 - 随机数生成器
|
||||||
|
// ============================================================================
|
||||||
|
class Random {
|
||||||
|
public:
|
||||||
|
/// 获取单例实例
|
||||||
|
static Random &getInstance();
|
||||||
|
|
||||||
|
/// 设置随机种子
|
||||||
|
void setSeed(uint32 seed);
|
||||||
|
|
||||||
|
/// 使用当前时间作为种子
|
||||||
|
void randomize();
|
||||||
|
|
||||||
|
/// 获取 [0, 1) 范围内的随机浮点数
|
||||||
|
float getFloat();
|
||||||
|
|
||||||
|
/// 获取 [min, max] 范围内的随机浮点数
|
||||||
|
float getFloat(float min, float max);
|
||||||
|
|
||||||
|
/// 获取 [0, max] 范围内的随机整数
|
||||||
|
int getInt(int max);
|
||||||
|
|
||||||
|
/// 获取 [min, max] 范围内的随机整数
|
||||||
|
int getInt(int min, int max);
|
||||||
|
|
||||||
|
/// 获取随机布尔值
|
||||||
|
bool getBool();
|
||||||
|
|
||||||
|
/// 获取随机布尔值(带概率)
|
||||||
|
bool getBool(float probability);
|
||||||
|
|
||||||
|
/// 获取指定范围内的随机角度(弧度)
|
||||||
|
float getAngle();
|
||||||
|
|
||||||
|
/// 获取 [-1, 1] 范围内的随机数(用于方向)
|
||||||
|
float getSigned();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Random();
|
||||||
|
~Random() = default;
|
||||||
|
|
||||||
|
Random(const Random &) = delete;
|
||||||
|
Random &operator=(const Random &) = delete;
|
||||||
|
|
||||||
|
std::mt19937 generator_;
|
||||||
|
std::uniform_real_distribution<float> floatDist_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷函数
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// 获取 [0, 1) 范围内的随机浮点数
|
||||||
|
inline float randomFloat() { return Random::getInstance().getFloat(); }
|
||||||
|
|
||||||
|
/// 获取 [min, max] 范围内的随机浮点数
|
||||||
|
inline float randomFloat(float min, float max) {
|
||||||
|
return Random::getInstance().getFloat(min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取 [0, max] 范围内的随机整数
|
||||||
|
inline int randomInt(int max) { return Random::getInstance().getInt(max); }
|
||||||
|
|
||||||
|
/// 获取 [min, max] 范围内的随机整数
|
||||||
|
inline int randomInt(int min, int max) {
|
||||||
|
return Random::getInstance().getInt(min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取随机布尔值
|
||||||
|
inline bool randomBool() { return Random::getInstance().getBool(); }
|
||||||
|
|
||||||
|
/// 获取随机布尔值(带概率)
|
||||||
|
inline bool randomBool(float probability) {
|
||||||
|
return Random::getInstance().getBool(probability);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Timer 类 - 单次/重复计时器
|
||||||
|
// ============================================================================
|
||||||
|
class Timer {
|
||||||
|
public:
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
using TimePoint = Clock::time_point;
|
||||||
|
using Duration = Clock::duration;
|
||||||
|
using Callback = Function<void()>;
|
||||||
|
|
||||||
|
Timer(float interval, bool repeat, Callback callback);
|
||||||
|
|
||||||
|
/// 更新计时器,返回 true 如果触发了回调
|
||||||
|
bool update(float deltaTime);
|
||||||
|
|
||||||
|
/// 重置计时器
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/// 暂停计时器
|
||||||
|
void pause();
|
||||||
|
|
||||||
|
/// 恢复计时器
|
||||||
|
void resume();
|
||||||
|
|
||||||
|
/// 取消计时器(标记为无效)
|
||||||
|
void cancel();
|
||||||
|
|
||||||
|
/// 是否有效
|
||||||
|
bool isValid() const { return valid_; }
|
||||||
|
|
||||||
|
/// 是否暂停
|
||||||
|
bool isPaused() const { return paused_; }
|
||||||
|
|
||||||
|
/// 获取剩余时间(秒)
|
||||||
|
float getRemaining() const;
|
||||||
|
|
||||||
|
/// 获取唯一ID
|
||||||
|
uint32 getId() const { return id_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32 id_;
|
||||||
|
float interval_;
|
||||||
|
float elapsed_;
|
||||||
|
bool repeat_;
|
||||||
|
bool paused_;
|
||||||
|
bool valid_;
|
||||||
|
Callback callback_;
|
||||||
|
|
||||||
|
static uint32 nextId_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TimerManager 类 - 管理所有计时器
|
||||||
|
// ============================================================================
|
||||||
|
class TimerManager {
|
||||||
|
public:
|
||||||
|
TimerManager() = default;
|
||||||
|
~TimerManager() = default;
|
||||||
|
|
||||||
|
/// 创建单次计时器,返回计时器ID
|
||||||
|
uint32 addTimer(float delay, Timer::Callback callback);
|
||||||
|
|
||||||
|
/// 创建重复计时器,返回计时器ID
|
||||||
|
uint32 addRepeatingTimer(float interval, Timer::Callback callback);
|
||||||
|
|
||||||
|
/// 取消指定ID的计时器
|
||||||
|
void cancelTimer(uint32 timerId);
|
||||||
|
|
||||||
|
/// 暂停指定ID的计时器
|
||||||
|
void pauseTimer(uint32 timerId);
|
||||||
|
|
||||||
|
/// 恢复指定ID的计时器
|
||||||
|
void resumeTimer(uint32 timerId);
|
||||||
|
|
||||||
|
/// 更新所有计时器(每帧调用)
|
||||||
|
void update(float deltaTime);
|
||||||
|
|
||||||
|
/// 清除所有计时器
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/// 获取计时器数量
|
||||||
|
size_t getTimerCount() const { return timers_.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<uint32, std::unique_ptr<Timer>> timers_;
|
||||||
|
std::vector<uint32> timersToRemove_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,428 @@
|
||||||
|
// stb_perlin.h - v0.5 - perlin noise
|
||||||
|
// public domain single-file C implementation by Sean Barrett
|
||||||
|
//
|
||||||
|
// LICENSE
|
||||||
|
//
|
||||||
|
// See end of file.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// to create the implementation,
|
||||||
|
// #define STB_PERLIN_IMPLEMENTATION
|
||||||
|
// in *one* C/CPP file that includes this file.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Documentation:
|
||||||
|
//
|
||||||
|
// float stb_perlin_noise3( float x,
|
||||||
|
// float y,
|
||||||
|
// float z,
|
||||||
|
// int x_wrap=0,
|
||||||
|
// int y_wrap=0,
|
||||||
|
// int z_wrap=0)
|
||||||
|
//
|
||||||
|
// This function computes a random value at the coordinate (x,y,z).
|
||||||
|
// Adjacent random values are continuous but the noise fluctuates
|
||||||
|
// its randomness with period 1, i.e. takes on wholly unrelated values
|
||||||
|
// at integer points. Specifically, this implements Ken Perlin's
|
||||||
|
// revised noise function from 2002.
|
||||||
|
//
|
||||||
|
// The "wrap" parameters can be used to create wraparound noise that
|
||||||
|
// wraps at powers of two. The numbers MUST be powers of two. Specify
|
||||||
|
// 0 to mean "don't care". (The noise always wraps every 256 due
|
||||||
|
// details of the implementation, even if you ask for larger or no
|
||||||
|
// wrapping.)
|
||||||
|
//
|
||||||
|
// float stb_perlin_noise3_seed( float x,
|
||||||
|
// float y,
|
||||||
|
// float z,
|
||||||
|
// int x_wrap=0,
|
||||||
|
// int y_wrap=0,
|
||||||
|
// int z_wrap=0,
|
||||||
|
// int seed)
|
||||||
|
//
|
||||||
|
// As above, but 'seed' selects from multiple different variations of the
|
||||||
|
// noise function. The current implementation only uses the bottom 8 bits
|
||||||
|
// of 'seed', but possibly in the future more bits will be used.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Fractal Noise:
|
||||||
|
//
|
||||||
|
// Three common fractal noise functions are included, which produce
|
||||||
|
// a wide variety of nice effects depending on the parameters
|
||||||
|
// provided. Note that each function will call stb_perlin_noise3
|
||||||
|
// 'octaves' times, so this parameter will affect runtime.
|
||||||
|
//
|
||||||
|
// float stb_perlin_ridge_noise3(float x, float y, float z,
|
||||||
|
// float lacunarity, float gain, float offset, int octaves)
|
||||||
|
//
|
||||||
|
// float stb_perlin_fbm_noise3(float x, float y, float z,
|
||||||
|
// float lacunarity, float gain, int octaves)
|
||||||
|
//
|
||||||
|
// float stb_perlin_turbulence_noise3(float x, float y, float z,
|
||||||
|
// float lacunarity, float gain, int octaves)
|
||||||
|
//
|
||||||
|
// Typical values to start playing with:
|
||||||
|
// octaves = 6 -- number of "octaves" of noise3() to sum
|
||||||
|
// lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output)
|
||||||
|
// gain = 0.5 -- relative weighting applied to each successive octave
|
||||||
|
// offset = 1.0? -- used to invert the ridges, may need to be larger, not sure
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Contributors:
|
||||||
|
// Jack Mott - additional noise functions
|
||||||
|
// Jordan Peck - seeded noise
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
extern float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap);
|
||||||
|
extern float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed);
|
||||||
|
extern float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves);
|
||||||
|
extern float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves);
|
||||||
|
extern float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves);
|
||||||
|
extern float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed);
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef STB_PERLIN_IMPLEMENTATION
|
||||||
|
|
||||||
|
#include <math.h> // fabs()
|
||||||
|
|
||||||
|
// not same permutation table as Perlin's reference to avoid copyright issues;
|
||||||
|
// Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/
|
||||||
|
static unsigned char stb__perlin_randtab[512] =
|
||||||
|
{
|
||||||
|
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
|
||||||
|
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
|
||||||
|
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
|
||||||
|
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
|
||||||
|
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
|
||||||
|
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
|
||||||
|
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
|
||||||
|
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
|
||||||
|
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
|
||||||
|
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
|
||||||
|
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
|
||||||
|
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
|
||||||
|
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
|
||||||
|
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
|
||||||
|
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
|
||||||
|
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
|
||||||
|
|
||||||
|
// and a second copy so we don't need an extra mask or static initializer
|
||||||
|
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
|
||||||
|
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
|
||||||
|
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
|
||||||
|
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
|
||||||
|
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
|
||||||
|
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
|
||||||
|
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
|
||||||
|
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
|
||||||
|
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
|
||||||
|
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
|
||||||
|
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
|
||||||
|
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
|
||||||
|
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
|
||||||
|
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
|
||||||
|
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
|
||||||
|
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// perlin's gradient has 12 cases so some get used 1/16th of the time
|
||||||
|
// and some 2/16ths. We reduce bias by changing those fractions
|
||||||
|
// to 5/64ths and 6/64ths
|
||||||
|
|
||||||
|
// this array is designed to match the previous implementation
|
||||||
|
// of gradient hash: indices[stb__perlin_randtab[i]&63]
|
||||||
|
static unsigned char stb__perlin_randtab_grad_idx[512] =
|
||||||
|
{
|
||||||
|
7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7,
|
||||||
|
8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8,
|
||||||
|
7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8,
|
||||||
|
8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5,
|
||||||
|
5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1,
|
||||||
|
2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4,
|
||||||
|
9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11,
|
||||||
|
1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6,
|
||||||
|
10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0,
|
||||||
|
6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2,
|
||||||
|
4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3,
|
||||||
|
11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11,
|
||||||
|
10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1,
|
||||||
|
3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10,
|
||||||
|
11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7,
|
||||||
|
9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5,
|
||||||
|
|
||||||
|
// and a second copy so we don't need an extra mask or static initializer
|
||||||
|
7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7,
|
||||||
|
8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8,
|
||||||
|
7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8,
|
||||||
|
8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5,
|
||||||
|
5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1,
|
||||||
|
2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4,
|
||||||
|
9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11,
|
||||||
|
1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6,
|
||||||
|
10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0,
|
||||||
|
6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2,
|
||||||
|
4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3,
|
||||||
|
11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11,
|
||||||
|
10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1,
|
||||||
|
3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10,
|
||||||
|
11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7,
|
||||||
|
9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
static float stb__perlin_lerp(float a, float b, float t)
|
||||||
|
{
|
||||||
|
return a + (b-a) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stb__perlin_fastfloor(float a)
|
||||||
|
{
|
||||||
|
int ai = (int) a;
|
||||||
|
return (a < ai) ? ai-1 : ai;
|
||||||
|
}
|
||||||
|
|
||||||
|
// different grad function from Perlin's, but easy to modify to match reference
|
||||||
|
static float stb__perlin_grad(int grad_idx, float x, float y, float z)
|
||||||
|
{
|
||||||
|
static float basis[12][4] =
|
||||||
|
{
|
||||||
|
{ 1, 1, 0 },
|
||||||
|
{ -1, 1, 0 },
|
||||||
|
{ 1,-1, 0 },
|
||||||
|
{ -1,-1, 0 },
|
||||||
|
{ 1, 0, 1 },
|
||||||
|
{ -1, 0, 1 },
|
||||||
|
{ 1, 0,-1 },
|
||||||
|
{ -1, 0,-1 },
|
||||||
|
{ 0, 1, 1 },
|
||||||
|
{ 0,-1, 1 },
|
||||||
|
{ 0, 1,-1 },
|
||||||
|
{ 0,-1,-1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
float *grad = basis[grad_idx];
|
||||||
|
return grad[0]*x + grad[1]*y + grad[2]*z;
|
||||||
|
}
|
||||||
|
|
||||||
|
float stb_perlin_noise3_internal(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed)
|
||||||
|
{
|
||||||
|
float u,v,w;
|
||||||
|
float n000,n001,n010,n011,n100,n101,n110,n111;
|
||||||
|
float n00,n01,n10,n11;
|
||||||
|
float n0,n1;
|
||||||
|
|
||||||
|
unsigned int x_mask = (x_wrap-1) & 255;
|
||||||
|
unsigned int y_mask = (y_wrap-1) & 255;
|
||||||
|
unsigned int z_mask = (z_wrap-1) & 255;
|
||||||
|
int px = stb__perlin_fastfloor(x);
|
||||||
|
int py = stb__perlin_fastfloor(y);
|
||||||
|
int pz = stb__perlin_fastfloor(z);
|
||||||
|
int x0 = px & x_mask, x1 = (px+1) & x_mask;
|
||||||
|
int y0 = py & y_mask, y1 = (py+1) & y_mask;
|
||||||
|
int z0 = pz & z_mask, z1 = (pz+1) & z_mask;
|
||||||
|
int r0,r1, r00,r01,r10,r11;
|
||||||
|
|
||||||
|
#define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a)
|
||||||
|
|
||||||
|
x -= px; u = stb__perlin_ease(x);
|
||||||
|
y -= py; v = stb__perlin_ease(y);
|
||||||
|
z -= pz; w = stb__perlin_ease(z);
|
||||||
|
|
||||||
|
r0 = stb__perlin_randtab[x0+seed];
|
||||||
|
r1 = stb__perlin_randtab[x1+seed];
|
||||||
|
|
||||||
|
r00 = stb__perlin_randtab[r0+y0];
|
||||||
|
r01 = stb__perlin_randtab[r0+y1];
|
||||||
|
r10 = stb__perlin_randtab[r1+y0];
|
||||||
|
r11 = stb__perlin_randtab[r1+y1];
|
||||||
|
|
||||||
|
n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z );
|
||||||
|
n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 );
|
||||||
|
n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z );
|
||||||
|
n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 );
|
||||||
|
n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z );
|
||||||
|
n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 );
|
||||||
|
n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z );
|
||||||
|
n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 );
|
||||||
|
|
||||||
|
n00 = stb__perlin_lerp(n000,n001,w);
|
||||||
|
n01 = stb__perlin_lerp(n010,n011,w);
|
||||||
|
n10 = stb__perlin_lerp(n100,n101,w);
|
||||||
|
n11 = stb__perlin_lerp(n110,n111,w);
|
||||||
|
|
||||||
|
n0 = stb__perlin_lerp(n00,n01,v);
|
||||||
|
n1 = stb__perlin_lerp(n10,n11,v);
|
||||||
|
|
||||||
|
return stb__perlin_lerp(n0,n1,u);
|
||||||
|
}
|
||||||
|
|
||||||
|
float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap)
|
||||||
|
{
|
||||||
|
return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed)
|
||||||
|
{
|
||||||
|
return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap, (unsigned char) seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
float frequency = 1.0f;
|
||||||
|
float prev = 1.0f;
|
||||||
|
float amplitude = 0.5f;
|
||||||
|
float sum = 0.0f;
|
||||||
|
|
||||||
|
for (i = 0; i < octaves; i++) {
|
||||||
|
float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i);
|
||||||
|
r = offset - (float) fabs(r);
|
||||||
|
r = r*r;
|
||||||
|
sum += r*amplitude*prev;
|
||||||
|
prev = r;
|
||||||
|
frequency *= lacunarity;
|
||||||
|
amplitude *= gain;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
float frequency = 1.0f;
|
||||||
|
float amplitude = 1.0f;
|
||||||
|
float sum = 0.0f;
|
||||||
|
|
||||||
|
for (i = 0; i < octaves; i++) {
|
||||||
|
sum += stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude;
|
||||||
|
frequency *= lacunarity;
|
||||||
|
amplitude *= gain;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
float frequency = 1.0f;
|
||||||
|
float amplitude = 1.0f;
|
||||||
|
float sum = 0.0f;
|
||||||
|
|
||||||
|
for (i = 0; i < octaves; i++) {
|
||||||
|
float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude;
|
||||||
|
sum += (float) fabs(r);
|
||||||
|
frequency *= lacunarity;
|
||||||
|
amplitude *= gain;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed)
|
||||||
|
{
|
||||||
|
float u,v,w;
|
||||||
|
float n000,n001,n010,n011,n100,n101,n110,n111;
|
||||||
|
float n00,n01,n10,n11;
|
||||||
|
float n0,n1;
|
||||||
|
|
||||||
|
int px = stb__perlin_fastfloor(x);
|
||||||
|
int py = stb__perlin_fastfloor(y);
|
||||||
|
int pz = stb__perlin_fastfloor(z);
|
||||||
|
int x_wrap2 = (x_wrap ? x_wrap : 256);
|
||||||
|
int y_wrap2 = (y_wrap ? y_wrap : 256);
|
||||||
|
int z_wrap2 = (z_wrap ? z_wrap : 256);
|
||||||
|
int x0 = px % x_wrap2, x1;
|
||||||
|
int y0 = py % y_wrap2, y1;
|
||||||
|
int z0 = pz % z_wrap2, z1;
|
||||||
|
int r0,r1, r00,r01,r10,r11;
|
||||||
|
|
||||||
|
if (x0 < 0) x0 += x_wrap2;
|
||||||
|
if (y0 < 0) y0 += y_wrap2;
|
||||||
|
if (z0 < 0) z0 += z_wrap2;
|
||||||
|
x1 = (x0+1) % x_wrap2;
|
||||||
|
y1 = (y0+1) % y_wrap2;
|
||||||
|
z1 = (z0+1) % z_wrap2;
|
||||||
|
|
||||||
|
#define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a)
|
||||||
|
|
||||||
|
x -= px; u = stb__perlin_ease(x);
|
||||||
|
y -= py; v = stb__perlin_ease(y);
|
||||||
|
z -= pz; w = stb__perlin_ease(z);
|
||||||
|
|
||||||
|
r0 = stb__perlin_randtab[x0];
|
||||||
|
r0 = stb__perlin_randtab[r0+seed];
|
||||||
|
r1 = stb__perlin_randtab[x1];
|
||||||
|
r1 = stb__perlin_randtab[r1+seed];
|
||||||
|
|
||||||
|
r00 = stb__perlin_randtab[r0+y0];
|
||||||
|
r01 = stb__perlin_randtab[r0+y1];
|
||||||
|
r10 = stb__perlin_randtab[r1+y0];
|
||||||
|
r11 = stb__perlin_randtab[r1+y1];
|
||||||
|
|
||||||
|
n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z );
|
||||||
|
n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 );
|
||||||
|
n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z );
|
||||||
|
n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 );
|
||||||
|
n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z );
|
||||||
|
n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 );
|
||||||
|
n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z );
|
||||||
|
n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 );
|
||||||
|
|
||||||
|
n00 = stb__perlin_lerp(n000,n001,w);
|
||||||
|
n01 = stb__perlin_lerp(n010,n011,w);
|
||||||
|
n10 = stb__perlin_lerp(n100,n101,w);
|
||||||
|
n11 = stb__perlin_lerp(n110,n111,w);
|
||||||
|
|
||||||
|
n0 = stb__perlin_lerp(n00,n01,v);
|
||||||
|
n1 = stb__perlin_lerp(n10,n11,v);
|
||||||
|
|
||||||
|
return stb__perlin_lerp(n0,n1,u);
|
||||||
|
}
|
||||||
|
#endif // STB_PERLIN_IMPLEMENTATION
|
||||||
|
|
||||||
|
/*
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
This software is available under 2 licenses -- choose whichever you prefer.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
ALTERNATIVE A - MIT License
|
||||||
|
Copyright (c) 2017 Sean Barrett
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is 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 Software.
|
||||||
|
THE SOFTWARE IS 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||||
|
software, either in source code form or as a compiled binary, for any purpose,
|
||||||
|
commercial or non-commercial, and by any means.
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||||
|
software dedicate any and all copyright interest in the software to the public
|
||||||
|
domain. We make this dedication for the benefit of the public at large and to
|
||||||
|
the detriment of our heirs and successors. We intend this dedication to be an
|
||||||
|
overt act of relinquishment in perpetuity of all present and future rights to
|
||||||
|
this software under copyright law.
|
||||||
|
THE SOFTWARE IS 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 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
@ -0,0 +1,623 @@
|
||||||
|
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
|
||||||
|
// Sean Barrett 2014
|
||||||
|
//
|
||||||
|
// Useful for e.g. packing rectangular textures into an atlas.
|
||||||
|
// Does not do rotation.
|
||||||
|
//
|
||||||
|
// Before #including,
|
||||||
|
//
|
||||||
|
// #define STB_RECT_PACK_IMPLEMENTATION
|
||||||
|
//
|
||||||
|
// in the file that you want to have the implementation.
|
||||||
|
//
|
||||||
|
// Not necessarily the awesomest packing method, but better than
|
||||||
|
// the totally naive one in stb_truetype (which is primarily what
|
||||||
|
// this is meant to replace).
|
||||||
|
//
|
||||||
|
// Has only had a few tests run, may have issues.
|
||||||
|
//
|
||||||
|
// More docs to come.
|
||||||
|
//
|
||||||
|
// No memory allocations; uses qsort() and assert() from stdlib.
|
||||||
|
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
|
||||||
|
//
|
||||||
|
// This library currently uses the Skyline Bottom-Left algorithm.
|
||||||
|
//
|
||||||
|
// Please note: better rectangle packers are welcome! Please
|
||||||
|
// implement them to the same API, but with a different init
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// Credits
|
||||||
|
//
|
||||||
|
// Library
|
||||||
|
// Sean Barrett
|
||||||
|
// Minor features
|
||||||
|
// Martins Mozeiko
|
||||||
|
// github:IntellectualKitty
|
||||||
|
//
|
||||||
|
// Bugfixes / warning fixes
|
||||||
|
// Jeremy Jaussaud
|
||||||
|
// Fabian Giesen
|
||||||
|
//
|
||||||
|
// Version history:
|
||||||
|
//
|
||||||
|
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
|
||||||
|
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
|
||||||
|
// 0.99 (2019-02-07) warning fixes
|
||||||
|
// 0.11 (2017-03-03) return packing success/fail result
|
||||||
|
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
|
||||||
|
// 0.09 (2016-08-27) fix compiler warnings
|
||||||
|
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
|
||||||
|
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
|
||||||
|
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
|
||||||
|
// 0.05: added STBRP_ASSERT to allow replacing assert
|
||||||
|
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
|
||||||
|
// 0.01: initial release
|
||||||
|
//
|
||||||
|
// LICENSE
|
||||||
|
//
|
||||||
|
// See end of file for license information.
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// INCLUDE SECTION
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef STB_INCLUDE_STB_RECT_PACK_H
|
||||||
|
#define STB_INCLUDE_STB_RECT_PACK_H
|
||||||
|
|
||||||
|
#define STB_RECT_PACK_VERSION 1
|
||||||
|
|
||||||
|
#ifdef STBRP_STATIC
|
||||||
|
#define STBRP_DEF static
|
||||||
|
#else
|
||||||
|
#define STBRP_DEF extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct stbrp_context stbrp_context;
|
||||||
|
typedef struct stbrp_node stbrp_node;
|
||||||
|
typedef struct stbrp_rect stbrp_rect;
|
||||||
|
|
||||||
|
typedef int stbrp_coord;
|
||||||
|
|
||||||
|
#define STBRP__MAXVAL 0x7fffffff
|
||||||
|
// Mostly for internal use, but this is the maximum supported coordinate value.
|
||||||
|
|
||||||
|
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
|
||||||
|
// Assign packed locations to rectangles. The rectangles are of type
|
||||||
|
// 'stbrp_rect' defined below, stored in the array 'rects', and there
|
||||||
|
// are 'num_rects' many of them.
|
||||||
|
//
|
||||||
|
// Rectangles which are successfully packed have the 'was_packed' flag
|
||||||
|
// set to a non-zero value and 'x' and 'y' store the minimum location
|
||||||
|
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
|
||||||
|
// if you imagine y increasing downwards). Rectangles which do not fit
|
||||||
|
// have the 'was_packed' flag set to 0.
|
||||||
|
//
|
||||||
|
// You should not try to access the 'rects' array from another thread
|
||||||
|
// while this function is running, as the function temporarily reorders
|
||||||
|
// the array while it executes.
|
||||||
|
//
|
||||||
|
// To pack into another rectangle, you need to call stbrp_init_target
|
||||||
|
// again. To continue packing into the same rectangle, you can call
|
||||||
|
// this function again. Calling this multiple times with multiple rect
|
||||||
|
// arrays will probably produce worse packing results than calling it
|
||||||
|
// a single time with the full rectangle array, but the option is
|
||||||
|
// available.
|
||||||
|
//
|
||||||
|
// The function returns 1 if all of the rectangles were successfully
|
||||||
|
// packed and 0 otherwise.
|
||||||
|
|
||||||
|
struct stbrp_rect
|
||||||
|
{
|
||||||
|
// reserved for your use:
|
||||||
|
int id;
|
||||||
|
|
||||||
|
// input:
|
||||||
|
stbrp_coord w, h;
|
||||||
|
|
||||||
|
// output:
|
||||||
|
stbrp_coord x, y;
|
||||||
|
int was_packed; // non-zero if valid packing
|
||||||
|
|
||||||
|
}; // 16 bytes, nominally
|
||||||
|
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
|
||||||
|
// Initialize a rectangle packer to:
|
||||||
|
// pack a rectangle that is 'width' by 'height' in dimensions
|
||||||
|
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
|
||||||
|
//
|
||||||
|
// You must call this function every time you start packing into a new target.
|
||||||
|
//
|
||||||
|
// There is no "shutdown" function. The 'nodes' memory must stay valid for
|
||||||
|
// the following stbrp_pack_rects() call (or calls), but can be freed after
|
||||||
|
// the call (or calls) finish.
|
||||||
|
//
|
||||||
|
// Note: to guarantee best results, either:
|
||||||
|
// 1. make sure 'num_nodes' >= 'width'
|
||||||
|
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
|
||||||
|
//
|
||||||
|
// If you don't do either of the above things, widths will be quantized to multiples
|
||||||
|
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
|
||||||
|
//
|
||||||
|
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
|
||||||
|
// may run out of temporary storage and be unable to pack some rectangles.
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
|
||||||
|
// Optionally call this function after init but before doing any packing to
|
||||||
|
// change the handling of the out-of-temp-memory scenario, described above.
|
||||||
|
// If you call init again, this will be reset to the default (false).
|
||||||
|
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
|
||||||
|
// Optionally select which packing heuristic the library should use. Different
|
||||||
|
// heuristics will produce better/worse results for different data sets.
|
||||||
|
// If you call init again, this will be reset to the default.
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
STBRP_HEURISTIC_Skyline_default=0,
|
||||||
|
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
|
||||||
|
STBRP_HEURISTIC_Skyline_BF_sortHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// the details of the following structures don't matter to you, but they must
|
||||||
|
// be visible so you can handle the memory allocations for them
|
||||||
|
|
||||||
|
struct stbrp_node
|
||||||
|
{
|
||||||
|
stbrp_coord x,y;
|
||||||
|
stbrp_node *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct stbrp_context
|
||||||
|
{
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int align;
|
||||||
|
int init_mode;
|
||||||
|
int heuristic;
|
||||||
|
int num_nodes;
|
||||||
|
stbrp_node *active_head;
|
||||||
|
stbrp_node *free_head;
|
||||||
|
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// IMPLEMENTATION SECTION
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifdef STB_RECT_PACK_IMPLEMENTATION
|
||||||
|
#ifndef STBRP_SORT
|
||||||
|
#include <stdlib.h>
|
||||||
|
#define STBRP_SORT qsort
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef STBRP_ASSERT
|
||||||
|
#include <assert.h>
|
||||||
|
#define STBRP_ASSERT assert
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define STBRP__NOTUSED(v) (void)(v)
|
||||||
|
#define STBRP__CDECL __cdecl
|
||||||
|
#else
|
||||||
|
#define STBRP__NOTUSED(v) (void)sizeof(v)
|
||||||
|
#define STBRP__CDECL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
STBRP__INIT_skyline = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
|
||||||
|
{
|
||||||
|
switch (context->init_mode) {
|
||||||
|
case STBRP__INIT_skyline:
|
||||||
|
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
|
||||||
|
context->heuristic = heuristic;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
STBRP_ASSERT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
|
||||||
|
{
|
||||||
|
if (allow_out_of_mem)
|
||||||
|
// if it's ok to run out of memory, then don't bother aligning them;
|
||||||
|
// this gives better packing, but may fail due to OOM (even though
|
||||||
|
// the rectangles easily fit). @TODO a smarter approach would be to only
|
||||||
|
// quantize once we've hit OOM, then we could get rid of this parameter.
|
||||||
|
context->align = 1;
|
||||||
|
else {
|
||||||
|
// if it's not ok to run out of memory, then quantize the widths
|
||||||
|
// so that num_nodes is always enough nodes.
|
||||||
|
//
|
||||||
|
// I.e. num_nodes * align >= width
|
||||||
|
// align >= width / num_nodes
|
||||||
|
// align = ceil(width/num_nodes)
|
||||||
|
|
||||||
|
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i=0; i < num_nodes-1; ++i)
|
||||||
|
nodes[i].next = &nodes[i+1];
|
||||||
|
nodes[i].next = NULL;
|
||||||
|
context->init_mode = STBRP__INIT_skyline;
|
||||||
|
context->heuristic = STBRP_HEURISTIC_Skyline_default;
|
||||||
|
context->free_head = &nodes[0];
|
||||||
|
context->active_head = &context->extra[0];
|
||||||
|
context->width = width;
|
||||||
|
context->height = height;
|
||||||
|
context->num_nodes = num_nodes;
|
||||||
|
stbrp_setup_allow_out_of_mem(context, 0);
|
||||||
|
|
||||||
|
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
|
||||||
|
context->extra[0].x = 0;
|
||||||
|
context->extra[0].y = 0;
|
||||||
|
context->extra[0].next = &context->extra[1];
|
||||||
|
context->extra[1].x = (stbrp_coord) width;
|
||||||
|
context->extra[1].y = (1<<30);
|
||||||
|
context->extra[1].next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find minimum y position if it starts at x1
|
||||||
|
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
|
||||||
|
{
|
||||||
|
stbrp_node *node = first;
|
||||||
|
int x1 = x0 + width;
|
||||||
|
int min_y, visited_width, waste_area;
|
||||||
|
|
||||||
|
STBRP__NOTUSED(c);
|
||||||
|
|
||||||
|
STBRP_ASSERT(first->x <= x0);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// skip in case we're past the node
|
||||||
|
while (node->next->x <= x0)
|
||||||
|
++node;
|
||||||
|
#else
|
||||||
|
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
|
||||||
|
#endif
|
||||||
|
|
||||||
|
STBRP_ASSERT(node->x <= x0);
|
||||||
|
|
||||||
|
min_y = 0;
|
||||||
|
waste_area = 0;
|
||||||
|
visited_width = 0;
|
||||||
|
while (node->x < x1) {
|
||||||
|
if (node->y > min_y) {
|
||||||
|
// raise min_y higher.
|
||||||
|
// we've accounted for all waste up to min_y,
|
||||||
|
// but we'll now add more waste for everything we've visted
|
||||||
|
waste_area += visited_width * (node->y - min_y);
|
||||||
|
min_y = node->y;
|
||||||
|
// the first time through, visited_width might be reduced
|
||||||
|
if (node->x < x0)
|
||||||
|
visited_width += node->next->x - x0;
|
||||||
|
else
|
||||||
|
visited_width += node->next->x - node->x;
|
||||||
|
} else {
|
||||||
|
// add waste area
|
||||||
|
int under_width = node->next->x - node->x;
|
||||||
|
if (under_width + visited_width > width)
|
||||||
|
under_width = width - visited_width;
|
||||||
|
waste_area += under_width * (min_y - node->y);
|
||||||
|
visited_width += under_width;
|
||||||
|
}
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pwaste = waste_area;
|
||||||
|
return min_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int x,y;
|
||||||
|
stbrp_node **prev_link;
|
||||||
|
} stbrp__findresult;
|
||||||
|
|
||||||
|
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
|
||||||
|
{
|
||||||
|
int best_waste = (1<<30), best_x, best_y = (1 << 30);
|
||||||
|
stbrp__findresult fr;
|
||||||
|
stbrp_node **prev, *node, *tail, **best = NULL;
|
||||||
|
|
||||||
|
// align to multiple of c->align
|
||||||
|
width = (width + c->align - 1);
|
||||||
|
width -= width % c->align;
|
||||||
|
STBRP_ASSERT(width % c->align == 0);
|
||||||
|
|
||||||
|
// if it can't possibly fit, bail immediately
|
||||||
|
if (width > c->width || height > c->height) {
|
||||||
|
fr.prev_link = NULL;
|
||||||
|
fr.x = fr.y = 0;
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = c->active_head;
|
||||||
|
prev = &c->active_head;
|
||||||
|
while (node->x + width <= c->width) {
|
||||||
|
int y,waste;
|
||||||
|
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
|
||||||
|
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
|
||||||
|
// bottom left
|
||||||
|
if (y < best_y) {
|
||||||
|
best_y = y;
|
||||||
|
best = prev;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// best-fit
|
||||||
|
if (y + height <= c->height) {
|
||||||
|
// can only use it if it first vertically
|
||||||
|
if (y < best_y || (y == best_y && waste < best_waste)) {
|
||||||
|
best_y = y;
|
||||||
|
best_waste = waste;
|
||||||
|
best = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev = &node->next;
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
best_x = (best == NULL) ? 0 : (*best)->x;
|
||||||
|
|
||||||
|
// if doing best-fit (BF), we also have to try aligning right edge to each node position
|
||||||
|
//
|
||||||
|
// e.g, if fitting
|
||||||
|
//
|
||||||
|
// ____________________
|
||||||
|
// |____________________|
|
||||||
|
//
|
||||||
|
// into
|
||||||
|
//
|
||||||
|
// | |
|
||||||
|
// | ____________|
|
||||||
|
// |____________|
|
||||||
|
//
|
||||||
|
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
|
||||||
|
//
|
||||||
|
// This makes BF take about 2x the time
|
||||||
|
|
||||||
|
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
|
||||||
|
tail = c->active_head;
|
||||||
|
node = c->active_head;
|
||||||
|
prev = &c->active_head;
|
||||||
|
// find first node that's admissible
|
||||||
|
while (tail->x < width)
|
||||||
|
tail = tail->next;
|
||||||
|
while (tail) {
|
||||||
|
int xpos = tail->x - width;
|
||||||
|
int y,waste;
|
||||||
|
STBRP_ASSERT(xpos >= 0);
|
||||||
|
// find the left position that matches this
|
||||||
|
while (node->next->x <= xpos) {
|
||||||
|
prev = &node->next;
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
|
||||||
|
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
|
||||||
|
if (y + height <= c->height) {
|
||||||
|
if (y <= best_y) {
|
||||||
|
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
|
||||||
|
best_x = xpos;
|
||||||
|
STBRP_ASSERT(y <= best_y);
|
||||||
|
best_y = y;
|
||||||
|
best_waste = waste;
|
||||||
|
best = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tail = tail->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fr.prev_link = best;
|
||||||
|
fr.x = best_x;
|
||||||
|
fr.y = best_y;
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
|
||||||
|
{
|
||||||
|
// find best position according to heuristic
|
||||||
|
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
|
||||||
|
stbrp_node *node, *cur;
|
||||||
|
|
||||||
|
// bail if:
|
||||||
|
// 1. it failed
|
||||||
|
// 2. the best node doesn't fit (we don't always check this)
|
||||||
|
// 3. we're out of memory
|
||||||
|
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
|
||||||
|
res.prev_link = NULL;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// on success, create new node
|
||||||
|
node = context->free_head;
|
||||||
|
node->x = (stbrp_coord) res.x;
|
||||||
|
node->y = (stbrp_coord) (res.y + height);
|
||||||
|
|
||||||
|
context->free_head = node->next;
|
||||||
|
|
||||||
|
// insert the new node into the right starting point, and
|
||||||
|
// let 'cur' point to the remaining nodes needing to be
|
||||||
|
// stiched back in
|
||||||
|
|
||||||
|
cur = *res.prev_link;
|
||||||
|
if (cur->x < res.x) {
|
||||||
|
// preserve the existing one, so start testing with the next one
|
||||||
|
stbrp_node *next = cur->next;
|
||||||
|
cur->next = node;
|
||||||
|
cur = next;
|
||||||
|
} else {
|
||||||
|
*res.prev_link = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// from here, traverse cur and free the nodes, until we get to one
|
||||||
|
// that shouldn't be freed
|
||||||
|
while (cur->next && cur->next->x <= res.x + width) {
|
||||||
|
stbrp_node *next = cur->next;
|
||||||
|
// move the current node to the free list
|
||||||
|
cur->next = context->free_head;
|
||||||
|
context->free_head = cur;
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stitch the list back in
|
||||||
|
node->next = cur;
|
||||||
|
|
||||||
|
if (cur->x < res.x + width)
|
||||||
|
cur->x = (stbrp_coord) (res.x + width);
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
cur = context->active_head;
|
||||||
|
while (cur->x < context->width) {
|
||||||
|
STBRP_ASSERT(cur->x < cur->next->x);
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
STBRP_ASSERT(cur->next == NULL);
|
||||||
|
|
||||||
|
{
|
||||||
|
int count=0;
|
||||||
|
cur = context->active_head;
|
||||||
|
while (cur) {
|
||||||
|
cur = cur->next;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
cur = context->free_head;
|
||||||
|
while (cur) {
|
||||||
|
cur = cur->next;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
STBRP_ASSERT(count == context->num_nodes+2);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||||
|
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||||
|
if (p->h > q->h)
|
||||||
|
return -1;
|
||||||
|
if (p->h < q->h)
|
||||||
|
return 1;
|
||||||
|
return (p->w > q->w) ? -1 : (p->w < q->w);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||||
|
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||||
|
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
|
||||||
|
}
|
||||||
|
|
||||||
|
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
|
||||||
|
{
|
||||||
|
int i, all_rects_packed = 1;
|
||||||
|
|
||||||
|
// we use the 'was_packed' field internally to allow sorting/unsorting
|
||||||
|
for (i=0; i < num_rects; ++i) {
|
||||||
|
rects[i].was_packed = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort according to heuristic
|
||||||
|
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
|
||||||
|
|
||||||
|
for (i=0; i < num_rects; ++i) {
|
||||||
|
if (rects[i].w == 0 || rects[i].h == 0) {
|
||||||
|
rects[i].x = rects[i].y = 0; // empty rect needs no space
|
||||||
|
} else {
|
||||||
|
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
|
||||||
|
if (fr.prev_link) {
|
||||||
|
rects[i].x = (stbrp_coord) fr.x;
|
||||||
|
rects[i].y = (stbrp_coord) fr.y;
|
||||||
|
} else {
|
||||||
|
rects[i].x = rects[i].y = STBRP__MAXVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsort
|
||||||
|
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
|
||||||
|
|
||||||
|
// set was_packed flags and all_rects_packed status
|
||||||
|
for (i=0; i < num_rects; ++i) {
|
||||||
|
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
|
||||||
|
if (!rects[i].was_packed)
|
||||||
|
all_rects_packed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the all_rects_packed status
|
||||||
|
return all_rects_packed;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
This software is available under 2 licenses -- choose whichever you prefer.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
ALTERNATIVE A - MIT License
|
||||||
|
Copyright (c) 2017 Sean Barrett
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is 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 Software.
|
||||||
|
THE SOFTWARE IS 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||||
|
software, either in source code form or as a compiled binary, for any purpose,
|
||||||
|
commercial or non-commercial, and by any means.
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||||
|
software dedicate any and all copyright interest in the software to the public
|
||||||
|
domain. We make this dedication for the benefit of the public at large and to
|
||||||
|
the detriment of our heirs and successors. We intend this dedication to be an
|
||||||
|
overt act of relinquishment in perpetuity of all present and future rights to
|
||||||
|
this software under copyright law.
|
||||||
|
THE SOFTWARE IS 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 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,68 @@
|
||||||
|
#include "extra2d/action/action.h"
|
||||||
|
#include "extra2d/scene/node.h"
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
Action::Action() : elapsed_(0.0f), duration_(0.0f), speed_(1.0f), tag_(-1) {}
|
||||||
|
|
||||||
|
void Action::start(Node *target) {
|
||||||
|
target_ = target;
|
||||||
|
originalTarget_ = target;
|
||||||
|
elapsed_ = 0.0f;
|
||||||
|
state_ = ActionState::Running;
|
||||||
|
onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::stop() {
|
||||||
|
target_ = nullptr;
|
||||||
|
state_ = ActionState::Completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::update(float dt) {
|
||||||
|
if (state_ != ActionState::Running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
step(dt);
|
||||||
|
|
||||||
|
if (isDone()) {
|
||||||
|
state_ = ActionState::Completed;
|
||||||
|
onComplete();
|
||||||
|
if (completionCallback_)
|
||||||
|
completionCallback_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::step(float dt) {
|
||||||
|
if (state_ != ActionState::Running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
elapsed_ += dt * speed_;
|
||||||
|
|
||||||
|
float progress = 0.0f;
|
||||||
|
if (duration_ > 0.0f) {
|
||||||
|
progress = std::min(1.0f, elapsed_ / duration_);
|
||||||
|
} else {
|
||||||
|
progress = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressCallback_)
|
||||||
|
progressCallback_(progress);
|
||||||
|
|
||||||
|
onUpdate(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::pause() {
|
||||||
|
if (state_ == ActionState::Running)
|
||||||
|
state_ = ActionState::Paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::resume() {
|
||||||
|
if (state_ == ActionState::Paused)
|
||||||
|
state_ = ActionState::Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Action::restart() {
|
||||||
|
elapsed_ = 0.0f;
|
||||||
|
state_ = ActionState::Running;
|
||||||
|
onStart();
|
||||||
|
}
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,405 @@
|
||||||
|
#include "extra2d/action/actions.h"
|
||||||
|
#include "extra2d/scene/node.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
// IntervalAction
|
||||||
|
IntervalAction::IntervalAction(float duration) { duration_ = duration; }
|
||||||
|
|
||||||
|
bool IntervalAction::isDone() const { return elapsed_ >= duration_; }
|
||||||
|
|
||||||
|
// InstantAction
|
||||||
|
InstantAction::InstantAction() { duration_ = 0.0f; }
|
||||||
|
|
||||||
|
bool InstantAction::isDone() const { return true; }
|
||||||
|
|
||||||
|
// MoveBy
|
||||||
|
MoveBy::MoveBy(float duration, const Vec2 &delta)
|
||||||
|
: IntervalAction(duration), delta_(delta) {}
|
||||||
|
|
||||||
|
void MoveBy::onStart() { startPosition_ = target_->getPosition(); }
|
||||||
|
|
||||||
|
void MoveBy::onUpdate(float progress) {
|
||||||
|
Vec2 newPos = startPosition_ + delta_ * progress;
|
||||||
|
target_->setPosition(newPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *MoveBy::clone() const { return new MoveBy(duration_, delta_); }
|
||||||
|
|
||||||
|
Action *MoveBy::reverse() const { return new MoveBy(duration_, -delta_); }
|
||||||
|
|
||||||
|
// MoveTo
|
||||||
|
MoveTo::MoveTo(float duration, const Vec2 &position)
|
||||||
|
: IntervalAction(duration), endPosition_(position) {}
|
||||||
|
|
||||||
|
void MoveTo::onStart() {
|
||||||
|
startPosition_ = target_->getPosition();
|
||||||
|
delta_ = endPosition_ - startPosition_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MoveTo::onUpdate(float progress) {
|
||||||
|
Vec2 newPos = startPosition_ + delta_ * progress;
|
||||||
|
target_->setPosition(newPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *MoveTo::clone() const { return new MoveTo(duration_, endPosition_); }
|
||||||
|
|
||||||
|
Action *MoveTo::reverse() const {
|
||||||
|
return new MoveTo(duration_, startPosition_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScaleBy
|
||||||
|
ScaleBy::ScaleBy(float duration, float scale)
|
||||||
|
: IntervalAction(duration), deltaScale_(scale - 1.0f, scale - 1.0f) {}
|
||||||
|
|
||||||
|
ScaleBy::ScaleBy(float duration, float scaleX, float scaleY)
|
||||||
|
: IntervalAction(duration), deltaScale_(scaleX - 1.0f, scaleY - 1.0f) {}
|
||||||
|
|
||||||
|
ScaleBy::ScaleBy(float duration, const Vec2 &scale)
|
||||||
|
: IntervalAction(duration), deltaScale_(scale.x - 1.0f, scale.y - 1.0f) {}
|
||||||
|
|
||||||
|
void ScaleBy::onStart() { startScale_ = target_->getScale(); }
|
||||||
|
|
||||||
|
void ScaleBy::onUpdate(float progress) {
|
||||||
|
Vec2 newScale = startScale_ + deltaScale_ * progress;
|
||||||
|
target_->setScale(newScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *ScaleBy::clone() const {
|
||||||
|
return new ScaleBy(duration_, Vec2(startScale_.x + deltaScale_.x,
|
||||||
|
startScale_.y + deltaScale_.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *ScaleBy::reverse() const {
|
||||||
|
return new ScaleBy(duration_, Vec2(startScale_.x - deltaScale_.x,
|
||||||
|
startScale_.y - deltaScale_.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScaleTo
|
||||||
|
ScaleTo::ScaleTo(float duration, float scale)
|
||||||
|
: IntervalAction(duration), endScale_(scale, scale) {}
|
||||||
|
|
||||||
|
ScaleTo::ScaleTo(float duration, float scaleX, float scaleY)
|
||||||
|
: IntervalAction(duration), endScale_(scaleX, scaleY) {}
|
||||||
|
|
||||||
|
ScaleTo::ScaleTo(float duration, const Vec2 &scale)
|
||||||
|
: IntervalAction(duration), endScale_(scale) {}
|
||||||
|
|
||||||
|
void ScaleTo::onStart() {
|
||||||
|
startScale_ = target_->getScale();
|
||||||
|
delta_ = endScale_ - startScale_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScaleTo::onUpdate(float progress) {
|
||||||
|
Vec2 newScale = startScale_ + delta_ * progress;
|
||||||
|
target_->setScale(newScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *ScaleTo::clone() const { return new ScaleTo(duration_, endScale_); }
|
||||||
|
|
||||||
|
Action *ScaleTo::reverse() const { return new ScaleTo(duration_, startScale_); }
|
||||||
|
|
||||||
|
// RotateBy
|
||||||
|
RotateBy::RotateBy(float duration, float deltaAngle)
|
||||||
|
: IntervalAction(duration), deltaAngle_(deltaAngle) {}
|
||||||
|
|
||||||
|
RotateBy::RotateBy(float duration, float deltaAngleX, float deltaAngleY)
|
||||||
|
: IntervalAction(duration), deltaAngle_(deltaAngleX) {
|
||||||
|
(void)deltaAngleY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotateBy::onStart() { startAngle_ = target_->getRotation(); }
|
||||||
|
|
||||||
|
void RotateBy::onUpdate(float progress) {
|
||||||
|
float newAngle = startAngle_ + deltaAngle_ * progress;
|
||||||
|
target_->setRotation(newAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *RotateBy::clone() const { return new RotateBy(duration_, deltaAngle_); }
|
||||||
|
|
||||||
|
Action *RotateBy::reverse() const {
|
||||||
|
return new RotateBy(duration_, -deltaAngle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RotateTo
|
||||||
|
RotateTo::RotateTo(float duration, float angle)
|
||||||
|
: IntervalAction(duration), endAngle_(angle) {}
|
||||||
|
|
||||||
|
RotateTo::RotateTo(float duration, float angleX, float angleY)
|
||||||
|
: IntervalAction(duration), endAngle_(angleX) {
|
||||||
|
(void)angleY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotateTo::onStart() {
|
||||||
|
startAngle_ = target_->getRotation();
|
||||||
|
deltaAngle_ = endAngle_ - startAngle_;
|
||||||
|
|
||||||
|
// Shortest path
|
||||||
|
if (deltaAngle_ > 180.0f)
|
||||||
|
deltaAngle_ -= 360.0f;
|
||||||
|
if (deltaAngle_ < -180.0f)
|
||||||
|
deltaAngle_ += 360.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotateTo::onUpdate(float progress) {
|
||||||
|
float newAngle = startAngle_ + deltaAngle_ * progress;
|
||||||
|
target_->setRotation(newAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *RotateTo::clone() const { return new RotateTo(duration_, endAngle_); }
|
||||||
|
|
||||||
|
Action *RotateTo::reverse() const {
|
||||||
|
return new RotateTo(duration_, startAngle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FadeIn
|
||||||
|
FadeIn::FadeIn(float duration) : IntervalAction(duration) {}
|
||||||
|
|
||||||
|
void FadeIn::onStart() {
|
||||||
|
startOpacity_ = target_->getOpacity();
|
||||||
|
target_->setOpacity(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FadeIn::onUpdate(float progress) { target_->setOpacity(progress); }
|
||||||
|
|
||||||
|
Action *FadeIn::clone() const { return new FadeIn(duration_); }
|
||||||
|
|
||||||
|
Action *FadeIn::reverse() const { return new FadeOut(duration_); }
|
||||||
|
|
||||||
|
// FadeOut
|
||||||
|
FadeOut::FadeOut(float duration) : IntervalAction(duration) {}
|
||||||
|
|
||||||
|
void FadeOut::onStart() {
|
||||||
|
startOpacity_ = target_->getOpacity();
|
||||||
|
target_->setOpacity(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FadeOut::onUpdate(float progress) { target_->setOpacity(1.0f - progress); }
|
||||||
|
|
||||||
|
Action *FadeOut::clone() const { return new FadeOut(duration_); }
|
||||||
|
|
||||||
|
Action *FadeOut::reverse() const { return new FadeIn(duration_); }
|
||||||
|
|
||||||
|
// FadeTo
|
||||||
|
FadeTo::FadeTo(float duration, float opacity)
|
||||||
|
: IntervalAction(duration), endOpacity_(opacity) {}
|
||||||
|
|
||||||
|
void FadeTo::onStart() {
|
||||||
|
startOpacity_ = target_->getOpacity();
|
||||||
|
deltaOpacity_ = endOpacity_ - startOpacity_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FadeTo::onUpdate(float progress) {
|
||||||
|
target_->setOpacity(startOpacity_ + deltaOpacity_ * progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *FadeTo::clone() const { return new FadeTo(duration_, endOpacity_); }
|
||||||
|
|
||||||
|
Action *FadeTo::reverse() const { return new FadeTo(duration_, startOpacity_); }
|
||||||
|
|
||||||
|
// Sequence
|
||||||
|
Sequence::Sequence(const std::vector<Action *> &actions)
|
||||||
|
: IntervalAction(0.0f) {
|
||||||
|
for (auto *action : actions) {
|
||||||
|
if (action) {
|
||||||
|
actions_.push_back(action->clone());
|
||||||
|
duration_ += action->getDuration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sequence::~Sequence() {
|
||||||
|
for (auto *action : actions_)
|
||||||
|
delete action;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequence::onStart() {
|
||||||
|
currentIndex_ = 0;
|
||||||
|
split_ = 0.0f;
|
||||||
|
last_ = -1.0f;
|
||||||
|
|
||||||
|
if (!actions_.empty()) {
|
||||||
|
actions_[0]->start(target_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequence::onUpdate(float progress) {
|
||||||
|
int found = 0;
|
||||||
|
float newTime = progress * duration_;
|
||||||
|
|
||||||
|
if (newTime < last_) {
|
||||||
|
// Rewind
|
||||||
|
for (auto *action : actions_) {
|
||||||
|
action->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_ = newTime;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < actions_.size(); ++i) {
|
||||||
|
split_ += actions_[i]->getDuration();
|
||||||
|
|
||||||
|
if (split_ > newTime) {
|
||||||
|
found = static_cast<int>(i);
|
||||||
|
break;
|
||||||
|
} else if (split_ == newTime) {
|
||||||
|
found = static_cast<int>(i) + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found != currentIndex_) {
|
||||||
|
if (currentIndex_ >= 0 &&
|
||||||
|
currentIndex_ < static_cast<int>(actions_.size())) {
|
||||||
|
actions_[currentIndex_]->update(actions_[currentIndex_]->getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found >= 0 && found < static_cast<int>(actions_.size())) {
|
||||||
|
actions_[found]->start(target_);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIndex_ = found;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIndex_ >= 0 && currentIndex_ < static_cast<int>(actions_.size())) {
|
||||||
|
float localProgress = 0.0f;
|
||||||
|
if (actions_[currentIndex_]->getDuration() > 0.0f) {
|
||||||
|
localProgress =
|
||||||
|
(newTime - (split_ - actions_[currentIndex_]->getDuration())) /
|
||||||
|
actions_[currentIndex_]->getDuration();
|
||||||
|
}
|
||||||
|
actions_[currentIndex_]->step(actions_[currentIndex_]->getDuration() *
|
||||||
|
localProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *Sequence::clone() const { return new Sequence(actions_); }
|
||||||
|
|
||||||
|
Action *Sequence::reverse() const {
|
||||||
|
std::vector<Action *> rev;
|
||||||
|
for (auto it = actions_.rbegin(); it != actions_.rend(); ++it) {
|
||||||
|
rev.push_back((*it)->reverse());
|
||||||
|
}
|
||||||
|
return new Sequence(rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn
|
||||||
|
Spawn::Spawn(const std::vector<Action *> &actions) : IntervalAction(0.0f) {
|
||||||
|
for (auto *action : actions) {
|
||||||
|
if (action) {
|
||||||
|
actions_.push_back(action->clone());
|
||||||
|
duration_ = std::max(duration_, action->getDuration());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spawn::~Spawn() {
|
||||||
|
for (auto *action : actions_)
|
||||||
|
delete action;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spawn::onStart() {
|
||||||
|
for (auto *action : actions_) {
|
||||||
|
action->start(target_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spawn::onUpdate(float progress) {
|
||||||
|
for (auto *action : actions_) {
|
||||||
|
float localProgress = 0.0f;
|
||||||
|
if (action->getDuration() > 0.0f) {
|
||||||
|
localProgress =
|
||||||
|
std::min(1.0f, (progress * duration_) / action->getDuration());
|
||||||
|
} else {
|
||||||
|
localProgress = 1.0f;
|
||||||
|
}
|
||||||
|
action->step(action->getDuration() * localProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *Spawn::clone() const { return new Spawn(actions_); }
|
||||||
|
|
||||||
|
Action *Spawn::reverse() const {
|
||||||
|
std::vector<Action *> rev;
|
||||||
|
for (auto *action : actions_) {
|
||||||
|
rev.push_back(action->reverse());
|
||||||
|
}
|
||||||
|
return new Spawn(rev);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop
|
||||||
|
Loop::Loop(Action *action, int times)
|
||||||
|
: action_(action ? action->clone() : nullptr), times_(times),
|
||||||
|
currentTimes_(0) {
|
||||||
|
if (action_) {
|
||||||
|
duration_ = times < 0 ? -1.0f : action_->getDuration() * times;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loop::~Loop() { delete action_; }
|
||||||
|
|
||||||
|
bool Loop::isDone() const {
|
||||||
|
if (times_ < 0)
|
||||||
|
return false;
|
||||||
|
return currentTimes_ >= times_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loop::onStart() {
|
||||||
|
currentTimes_ = 0;
|
||||||
|
if (action_) {
|
||||||
|
action_->start(target_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loop::onUpdate(float progress) {
|
||||||
|
if (!action_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float actionDuration = action_->getDuration();
|
||||||
|
float dt = progress * duration_ - elapsed_;
|
||||||
|
|
||||||
|
while (dt > 0.0f) {
|
||||||
|
float localProgress = std::min(1.0f, dt / actionDuration);
|
||||||
|
action_->step(actionDuration * localProgress);
|
||||||
|
|
||||||
|
if (action_->isDone()) {
|
||||||
|
currentTimes_++;
|
||||||
|
if (times_ > 0 && currentTimes_ >= times_)
|
||||||
|
break;
|
||||||
|
action_->restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
dt -= actionDuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *Loop::clone() const { return new Loop(action_, times_); }
|
||||||
|
|
||||||
|
Action *Loop::reverse() const {
|
||||||
|
return new Loop(action_ ? action_->reverse() : nullptr, times_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay
|
||||||
|
Delay::Delay(float duration) : IntervalAction(duration) {}
|
||||||
|
|
||||||
|
void Delay::onUpdate(float progress) {
|
||||||
|
// No update needed, just wait
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *Delay::clone() const { return new Delay(duration_); }
|
||||||
|
|
||||||
|
Action *Delay::reverse() const { return new Delay(duration_); }
|
||||||
|
|
||||||
|
// CallFunc
|
||||||
|
CallFunc::CallFunc(Callback callback) : callback_(std::move(callback)) {}
|
||||||
|
|
||||||
|
void CallFunc::onUpdate(float progress) {
|
||||||
|
(void)progress;
|
||||||
|
if (callback_)
|
||||||
|
callback_();
|
||||||
|
}
|
||||||
|
|
||||||
|
Action *CallFunc::clone() const { return new CallFunc(callback_); }
|
||||||
|
|
||||||
|
Action *CallFunc::reverse() const { return new CallFunc(callback_); }
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
#include <cmath>
|
||||||
|
#include <extra2d/action/ease.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
// Linear
|
||||||
|
float easeLinear(float t) { return t; }
|
||||||
|
|
||||||
|
// Quadratic
|
||||||
|
float easeInQuad(float t) { return t * t; }
|
||||||
|
|
||||||
|
float easeOutQuad(float t) { return 1.0f - (1.0f - t) * (1.0f - t); }
|
||||||
|
|
||||||
|
float easeInOutQuad(float t) {
|
||||||
|
return t < 0.5f ? 2.0f * t * t
|
||||||
|
: 1.0f - std::pow(-2.0f * t + 2.0f, 2.0f) / 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cubic
|
||||||
|
float easeInCubic(float t) { return t * t * t; }
|
||||||
|
|
||||||
|
float easeOutCubic(float t) { return 1.0f - std::pow(1.0f - t, 3.0f); }
|
||||||
|
|
||||||
|
float easeInOutCubic(float t) {
|
||||||
|
return t < 0.5f ? 4.0f * t * t * t
|
||||||
|
: 1.0f - std::pow(-2.0f * t + 2.0f, 3.0f) / 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quartic
|
||||||
|
float easeInQuart(float t) { return t * t * t * t; }
|
||||||
|
|
||||||
|
float easeOutQuart(float t) { return 1.0f - std::pow(1.0f - t, 4.0f); }
|
||||||
|
|
||||||
|
float easeInOutQuart(float t) {
|
||||||
|
return t < 0.5f ? 8.0f * t * t * t * t
|
||||||
|
: 1.0f - std::pow(-2.0f * t + 2.0f, 4.0f) / 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quintic
|
||||||
|
float easeInQuint(float t) { return t * t * t * t * t; }
|
||||||
|
|
||||||
|
float easeOutQuint(float t) { return 1.0f - std::pow(1.0f - t, 5.0f); }
|
||||||
|
|
||||||
|
float easeInOutQuint(float t) {
|
||||||
|
return t < 0.5f ? 16.0f * t * t * t * t * t
|
||||||
|
: 1.0f - std::pow(-2.0f * t + 2.0f, 5.0f) / 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sine
|
||||||
|
float easeInSine(float t) {
|
||||||
|
return 1.0f - std::cos((t * 3.14159265359f) / 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutSine(float t) { return std::sin((t * 3.14159265359f) / 2.0f); }
|
||||||
|
|
||||||
|
float easeInOutSine(float t) {
|
||||||
|
return -(std::cos(3.14159265359f * t) - 1.0f) / 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponential
|
||||||
|
float easeInExpo(float t) {
|
||||||
|
return t == 0.0f ? 0.0f : std::pow(2.0f, 10.0f * (t - 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutExpo(float t) {
|
||||||
|
return t == 1.0f ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutExpo(float t) {
|
||||||
|
if (t == 0.0f)
|
||||||
|
return 0.0f;
|
||||||
|
if (t == 1.0f)
|
||||||
|
return 1.0f;
|
||||||
|
return t < 0.5f ? std::pow(2.0f, 20.0f * t - 10.0f) / 2.0f
|
||||||
|
: (2.0f - std::pow(2.0f, -20.0f * t + 10.0f)) / 2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Circular
|
||||||
|
float easeInCirc(float t) { return 1.0f - std::sqrt(1.0f - std::pow(t, 2.0f)); }
|
||||||
|
|
||||||
|
float easeOutCirc(float t) {
|
||||||
|
return std::sqrt(1.0f - std::pow(t - 1.0f, 2.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutCirc(float t) {
|
||||||
|
return t < 0.5f
|
||||||
|
? (1.0f - std::sqrt(1.0f - std::pow(2.0f * t, 2.0f))) / 2.0f
|
||||||
|
: (std::sqrt(1.0f - std::pow(-2.0f * t + 2.0f, 2.0f)) + 1.0f) /
|
||||||
|
2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back
|
||||||
|
float easeInBack(float t) {
|
||||||
|
const float c1 = 1.70158f;
|
||||||
|
const float c3 = c1 + 1.0f;
|
||||||
|
return c3 * t * t * t - c1 * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutBack(float t) {
|
||||||
|
const float c1 = 1.70158f;
|
||||||
|
const float c3 = c1 + 1.0f;
|
||||||
|
return 1.0f + c3 * std::pow(t - 1.0f, 3.0f) + c1 * std::pow(t - 1.0f, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutBack(float t) {
|
||||||
|
const float c1 = 1.70158f;
|
||||||
|
const float c2 = c1 * 1.525f;
|
||||||
|
return t < 0.5f
|
||||||
|
? (std::pow(2.0f * t, 2.0f) * ((c2 + 1.0f) * 2.0f * t - c2)) / 2.0f
|
||||||
|
: (std::pow(2.0f * t - 2.0f, 2.0f) *
|
||||||
|
((c2 + 1.0f) * (t * 2.0f - 2.0f) + c2) +
|
||||||
|
2.0f) /
|
||||||
|
2.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elastic
|
||||||
|
float easeInElastic(float t) {
|
||||||
|
const float c4 = (2.0f * 3.14159265359f) / 3.0f;
|
||||||
|
if (t == 0.0f)
|
||||||
|
return 0.0f;
|
||||||
|
if (t == 1.0f)
|
||||||
|
return 1.0f;
|
||||||
|
return -std::pow(2.0f, 10.0f * t - 10.0f) *
|
||||||
|
std::sin((t * 10.0f - 10.75f) * c4);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutElastic(float t) {
|
||||||
|
const float c4 = (2.0f * 3.14159265359f) / 3.0f;
|
||||||
|
if (t == 0.0f)
|
||||||
|
return 0.0f;
|
||||||
|
if (t == 1.0f)
|
||||||
|
return 1.0f;
|
||||||
|
return std::pow(2.0f, -10.0f * t) * std::sin((t * 10.0f - 0.75f) * c4) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutElastic(float t) {
|
||||||
|
const float c5 = (2.0f * 3.14159265359f) / 4.5f;
|
||||||
|
if (t == 0.0f)
|
||||||
|
return 0.0f;
|
||||||
|
if (t == 1.0f)
|
||||||
|
return 1.0f;
|
||||||
|
return t < 0.5f ? -(std::pow(2.0f, 20.0f * t - 10.0f) *
|
||||||
|
std::sin((20.0f * t - 11.125f) * c5)) /
|
||||||
|
2.0f
|
||||||
|
: (std::pow(2.0f, -20.0f * t + 10.0f) *
|
||||||
|
std::sin((20.0f * t - 11.125f) * c5)) /
|
||||||
|
2.0f +
|
||||||
|
1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounce
|
||||||
|
namespace {
|
||||||
|
float easeOutBounceInternal(float t) {
|
||||||
|
const float n1 = 7.5625f;
|
||||||
|
const float d1 = 2.75f;
|
||||||
|
|
||||||
|
if (t < 1.0f / d1) {
|
||||||
|
return n1 * t * t;
|
||||||
|
} else if (t < 2.0f / d1) {
|
||||||
|
t -= 1.5f / d1;
|
||||||
|
return n1 * t * t + 0.75f;
|
||||||
|
} else if (t < 2.5f / d1) {
|
||||||
|
t -= 2.25f / d1;
|
||||||
|
return n1 * t * t + 0.9375f;
|
||||||
|
} else {
|
||||||
|
t -= 2.625f / d1;
|
||||||
|
return n1 * t * t + 0.984375f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
float easeInBounce(float t) { return 1.0f - easeOutBounceInternal(1.0f - t); }
|
||||||
|
|
||||||
|
float easeOutBounce(float t) { return easeOutBounceInternal(t); }
|
||||||
|
|
||||||
|
float easeInOutBounce(float t) {
|
||||||
|
return t < 0.5f ? (1.0f - easeOutBounceInternal(1.0f - 2.0f * t)) / 2.0f
|
||||||
|
: (1.0f + easeOutBounceInternal(2.0f * t - 1.0f)) / 2.0f;
|
||||||
|
}
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#include <extra2d/animation/als_parser.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
AlsParseResult AlsParser::parse(const std::string &filePath) {
|
||||||
|
AlsParseResult result;
|
||||||
|
|
||||||
|
// TODO: 实现实际的 ALS 文件格式解析
|
||||||
|
// 当前为框架实现,需要根据实际 ALS 文件格式补充解析逻辑
|
||||||
|
//
|
||||||
|
// 解析流程:
|
||||||
|
// 1. 读取 ALS 文件内容
|
||||||
|
// 2. 解析子动画列表:
|
||||||
|
// - 子动画 ANI 文件路径
|
||||||
|
// - 层级 zOrder
|
||||||
|
// - 偏移量
|
||||||
|
// 3. 组装 AlsLayerInfo 数组
|
||||||
|
|
||||||
|
result.success = true;
|
||||||
|
result.errorMessage =
|
||||||
|
"ALS parser framework ready - actual format parsing to be implemented";
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlsParseResult AlsParser::parseFromMemory(const std::string &content,
|
||||||
|
const std::string &basePath) {
|
||||||
|
AlsParseResult result;
|
||||||
|
|
||||||
|
// TODO: 从内存内容解析
|
||||||
|
result.success = true;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AlsParser::resolvePath(const std::string &relativePath) const {
|
||||||
|
if (!basePath_.empty() && !relativePath.empty() && relativePath[0] != '/') {
|
||||||
|
return basePath_ + "/" + relativePath;
|
||||||
|
}
|
||||||
|
return relativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,313 @@
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
#include <extra2d/animation/ani_binary_parser.h>
|
||||||
|
#include <extra2d/animation/sprite_frame_cache.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// 简易二进制缓冲区读取器
|
||||||
|
class BufferReader {
|
||||||
|
public:
|
||||||
|
BufferReader(const uint8_t *data, size_t length)
|
||||||
|
: data_(data), length_(length), pos_(0) {}
|
||||||
|
|
||||||
|
template <typename T> T read() {
|
||||||
|
if (pos_ + sizeof(T) > length_) {
|
||||||
|
return T{};
|
||||||
|
}
|
||||||
|
T value;
|
||||||
|
std::memcpy(&value, data_ + pos_, sizeof(T));
|
||||||
|
pos_ += sizeof(T);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string readAsciiString(int32_t len) {
|
||||||
|
if (len <= 0 || pos_ + static_cast<size_t>(len) > length_) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
std::string result(reinterpret_cast<const char *>(data_ + pos_), len);
|
||||||
|
pos_ += len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasRemaining() const { return pos_ < length_; }
|
||||||
|
size_t remaining() const { return length_ - pos_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const uint8_t *data_;
|
||||||
|
size_t length_;
|
||||||
|
size_t pos_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 字符串转小写
|
||||||
|
void toLower(std::string &s) {
|
||||||
|
for (auto &c : s) {
|
||||||
|
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
AniParseResult AniBinaryParser::parse(const uint8_t *data, size_t length) {
|
||||||
|
AniParseResult result;
|
||||||
|
result.clip = AnimationClip::create();
|
||||||
|
|
||||||
|
if (!data || length < 4) {
|
||||||
|
result.success = false;
|
||||||
|
result.errorMessage = "Invalid binary ANI data";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferReader reader(data, length);
|
||||||
|
|
||||||
|
// 读取帧数和资源数
|
||||||
|
uint16_t frameCount = reader.read<uint16_t>();
|
||||||
|
uint16_t resourceCount = reader.read<uint16_t>();
|
||||||
|
|
||||||
|
// 读取精灵路径列表
|
||||||
|
std::vector<std::string> sprites;
|
||||||
|
sprites.reserve(resourceCount);
|
||||||
|
for (uint16_t i = 0; i < resourceCount; ++i) {
|
||||||
|
int32_t len = reader.read<int32_t>();
|
||||||
|
std::string path = reader.readAsciiString(len);
|
||||||
|
toLower(path);
|
||||||
|
sprites.push_back(std::move(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取全局参数
|
||||||
|
uint16_t globalParamCount = reader.read<uint16_t>();
|
||||||
|
for (uint16_t j = 0; j < globalParamCount; ++j) {
|
||||||
|
uint16_t type = reader.read<uint16_t>();
|
||||||
|
switch (type) {
|
||||||
|
case static_cast<uint16_t>(AniNodeType::Loop):
|
||||||
|
if (reader.read<int8_t>()) {
|
||||||
|
result.clip->setLooping(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case static_cast<uint16_t>(AniNodeType::Shadow):
|
||||||
|
if (reader.read<int8_t>()) {
|
||||||
|
result.clip->globalProperties().set(FramePropertyKey::Shadow, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐帧解析
|
||||||
|
for (uint16_t i = 0; i < frameCount; ++i) {
|
||||||
|
AnimationFrame frame;
|
||||||
|
|
||||||
|
// 碰撞盒
|
||||||
|
uint16_t boxCount = reader.read<uint16_t>();
|
||||||
|
for (uint16_t j = 0; j < boxCount; ++j) {
|
||||||
|
uint16_t boxType = reader.read<uint16_t>();
|
||||||
|
std::array<int32_t, 6> box;
|
||||||
|
for (int m = 0; m < 6; ++m) {
|
||||||
|
box[m] = reader.read<int32_t>();
|
||||||
|
}
|
||||||
|
if (boxType == static_cast<uint16_t>(AniNodeType::DamageBox)) {
|
||||||
|
frame.damageBoxes.push_back(box);
|
||||||
|
} else if (boxType == static_cast<uint16_t>(AniNodeType::AttackBox)) {
|
||||||
|
frame.attackBoxes.push_back(box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片 ID 和参数
|
||||||
|
uint16_t imgId = reader.read<uint16_t>();
|
||||||
|
uint16_t imgParam = reader.read<uint16_t>();
|
||||||
|
(void)imgParam;
|
||||||
|
|
||||||
|
if (imgId < sprites.size()) {
|
||||||
|
frame.texturePath = sprites[imgId];
|
||||||
|
frame.textureIndex = imgId;
|
||||||
|
|
||||||
|
std::string resolvedPath = resolvePath(frame.texturePath);
|
||||||
|
auto spriteFrame = SpriteFrameCache::getInstance().getOrCreateFromFile(
|
||||||
|
resolvedPath, frame.textureIndex);
|
||||||
|
frame.spriteFrame = spriteFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 位置
|
||||||
|
frame.offset = Vec2(static_cast<float>(reader.read<int32_t>()),
|
||||||
|
static_cast<float>(reader.read<int32_t>()));
|
||||||
|
|
||||||
|
// 帧属性
|
||||||
|
uint16_t propertyCount = reader.read<uint16_t>();
|
||||||
|
for (uint16_t m = 0; m < propertyCount; ++m) {
|
||||||
|
uint16_t propType = reader.read<uint16_t>();
|
||||||
|
AniNodeType nodeType = static_cast<AniNodeType>(propType);
|
||||||
|
|
||||||
|
switch (nodeType) {
|
||||||
|
case AniNodeType::Loop:
|
||||||
|
frame.properties.set(FramePropertyKey::Loop,
|
||||||
|
static_cast<bool>(reader.read<int8_t>()));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::Shadow:
|
||||||
|
frame.properties.set(FramePropertyKey::Shadow,
|
||||||
|
static_cast<bool>(reader.read<int8_t>()));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::Interpolation:
|
||||||
|
frame.properties.withInterpolation(
|
||||||
|
static_cast<bool>(reader.read<int8_t>()));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::Coord:
|
||||||
|
frame.properties.set(FramePropertyKey::Coord,
|
||||||
|
static_cast<int>(reader.read<uint16_t>()));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::ImageRate: {
|
||||||
|
float rateX = reader.read<float>();
|
||||||
|
float rateY = reader.read<float>();
|
||||||
|
frame.properties.withImageRate(Vec2(rateX, rateY));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AniNodeType::ImageRotate:
|
||||||
|
frame.properties.withImageRotate(
|
||||||
|
static_cast<float>(reader.read<int32_t>()));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::RGBA: {
|
||||||
|
uint32_t rgba = reader.read<uint32_t>();
|
||||||
|
uint8_t a = static_cast<uint8_t>((rgba >> 24) & 0xFF);
|
||||||
|
uint8_t r = static_cast<uint8_t>((rgba >> 16) & 0xFF);
|
||||||
|
uint8_t g = static_cast<uint8_t>((rgba >> 8) & 0xFF);
|
||||||
|
uint8_t b = static_cast<uint8_t>((rgba) & 0xFF);
|
||||||
|
frame.properties.withColorTint(Color::fromRGBA(r, g, b, a));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AniNodeType::GraphicEffect: {
|
||||||
|
uint16_t effectType = reader.read<uint16_t>();
|
||||||
|
frame.properties.set(FramePropertyKey::GraphicEffect,
|
||||||
|
static_cast<int>(effectType));
|
||||||
|
// MONOCHROME 额外读取 rgb
|
||||||
|
if (effectType == 5) {
|
||||||
|
reader.read<uint8_t>(); // r
|
||||||
|
reader.read<uint8_t>(); // g
|
||||||
|
reader.read<uint8_t>(); // b
|
||||||
|
}
|
||||||
|
// SPACEDISTORT 额外读取 pos
|
||||||
|
else if (effectType == 6) {
|
||||||
|
reader.read<uint16_t>(); // x
|
||||||
|
reader.read<uint16_t>(); // y
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AniNodeType::Delay:
|
||||||
|
frame.delay = static_cast<float>(reader.read<int32_t>());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::DamageType:
|
||||||
|
frame.properties.set(FramePropertyKey::DamageType,
|
||||||
|
static_cast<int>(reader.read<uint16_t>()));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::PlaySound: {
|
||||||
|
int32_t len = reader.read<int32_t>();
|
||||||
|
std::string soundPath = reader.readAsciiString(len);
|
||||||
|
frame.properties.withPlaySound(resolvePath(soundPath));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AniNodeType::SetFlag:
|
||||||
|
frame.properties.withSetFlag(reader.read<int32_t>());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::FlipType:
|
||||||
|
frame.properties.set(FramePropertyKey::FlipType,
|
||||||
|
static_cast<int>(reader.read<uint16_t>()));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::LoopStart:
|
||||||
|
frame.properties.set(FramePropertyKey::LoopStart, true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::LoopEnd:
|
||||||
|
frame.properties.set(FramePropertyKey::LoopEnd, reader.read<int32_t>());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AniNodeType::Clip: {
|
||||||
|
std::vector<int> clipRegion = {
|
||||||
|
static_cast<int>(reader.read<int16_t>()),
|
||||||
|
static_cast<int>(reader.read<int16_t>()),
|
||||||
|
static_cast<int>(reader.read<int16_t>()),
|
||||||
|
static_cast<int>(reader.read<int16_t>())};
|
||||||
|
frame.properties.set(FramePropertyKey::ClipRegion, clipRegion);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 未知类型 - 跳过
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.clip->addFrame(std::move(frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AniParseResult AniBinaryParser::parseFromFile(const std::string &filePath) {
|
||||||
|
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
AniParseResult result;
|
||||||
|
result.success = false;
|
||||||
|
result.errorMessage = "Cannot open binary ANI file: " + filePath;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
std::vector<uint8_t> buffer(static_cast<size_t>(size));
|
||||||
|
file.read(reinterpret_cast<char *>(buffer.data()), size);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// 提取基础路径
|
||||||
|
auto lastSlash = filePath.find_last_of("/\\");
|
||||||
|
if (lastSlash != std::string::npos && basePath_.empty()) {
|
||||||
|
basePath_ = filePath.substr(0, lastSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = parse(buffer.data(), buffer.size());
|
||||||
|
|
||||||
|
if (result.clip) {
|
||||||
|
result.clip->setSourcePath(filePath);
|
||||||
|
std::string name = (lastSlash != std::string::npos)
|
||||||
|
? filePath.substr(lastSlash + 1)
|
||||||
|
: filePath;
|
||||||
|
result.clip->setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
AniBinaryParser::resolvePath(const std::string &relativePath) const {
|
||||||
|
std::string resolved = relativePath;
|
||||||
|
if (pathResolver_) {
|
||||||
|
resolved = pathResolver_(relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!basePath_.empty() && !resolved.empty() && resolved[0] != '/') {
|
||||||
|
if (resolved.size() < 2 || resolved[1] != ':') {
|
||||||
|
resolved = basePath_ + "/" + resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,430 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <extra2d/animation/ani_parser.h>
|
||||||
|
#include <extra2d/animation/sprite_frame_cache.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string trim(const std::string &s) {
|
||||||
|
auto start = s.find_first_not_of(" \t\r\n");
|
||||||
|
if (start == std::string::npos)
|
||||||
|
return "";
|
||||||
|
auto end = s.find_last_not_of(" \t\r\n");
|
||||||
|
return s.substr(start, end - start + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string stripBackticks(const std::string &s) {
|
||||||
|
std::string t = trim(s);
|
||||||
|
if (t.size() >= 2 && t.front() == '`' && t.back() == '`') {
|
||||||
|
return t.substr(1, t.size() - 2);
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> splitWhitespace(const std::string &s) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
std::istringstream iss(s);
|
||||||
|
std::string token;
|
||||||
|
while (iss >> token) {
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lineStartsWith(const std::string &trimmed, const std::string &tag) {
|
||||||
|
return trimmed.find(tag) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string valueAfterTag(const std::string &trimmed, const std::string &tag) {
|
||||||
|
auto pos = trimmed.find(tag);
|
||||||
|
if (pos == std::string::npos)
|
||||||
|
return "";
|
||||||
|
return trim(trimmed.substr(pos + tag.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取标签后的值,如果同行没有则从流中读取下一行
|
||||||
|
std::string readValue(const std::string &trimmed, const std::string &tag,
|
||||||
|
std::istringstream &stream) {
|
||||||
|
std::string val = valueAfterTag(trimmed, tag);
|
||||||
|
if (val.empty()) {
|
||||||
|
std::string nextLine;
|
||||||
|
if (std::getline(stream, nextLine)) {
|
||||||
|
val = trim(nextLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
AniParseResult AniParser::parse(const std::string &filePath) {
|
||||||
|
AniParseResult result;
|
||||||
|
|
||||||
|
std::ifstream file(filePath);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
result.success = false;
|
||||||
|
result.errorMessage = "Cannot open ANI file: " + filePath;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string content((std::istreambuf_iterator<char>(file)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// 提取基础路径
|
||||||
|
auto lastSlash = filePath.find_last_of("/\\");
|
||||||
|
std::string basePath;
|
||||||
|
if (lastSlash != std::string::npos) {
|
||||||
|
basePath = filePath.substr(0, lastSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basePath_.empty() && !basePath.empty()) {
|
||||||
|
basePath_ = basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = parseFromMemory(content, basePath_.empty() ? basePath : basePath_);
|
||||||
|
|
||||||
|
if (result.clip) {
|
||||||
|
result.clip->setSourcePath(filePath);
|
||||||
|
std::string name = (lastSlash != std::string::npos)
|
||||||
|
? filePath.substr(lastSlash + 1)
|
||||||
|
: filePath;
|
||||||
|
result.clip->setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AniParseResult AniParser::parseFromMemory(const std::string &content,
|
||||||
|
const std::string &basePath) {
|
||||||
|
AniParseResult result;
|
||||||
|
result.clip = AnimationClip::create();
|
||||||
|
|
||||||
|
if (!basePath.empty() && basePath_.empty()) {
|
||||||
|
basePath_ = basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istringstream stream(content);
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
AnimationFrame currentFrame;
|
||||||
|
bool inFrame = false;
|
||||||
|
bool hasCurrentFrame = false;
|
||||||
|
|
||||||
|
// [IMAGE] 标签的多行读取状态
|
||||||
|
bool pendingImage = false;
|
||||||
|
std::string imagePath;
|
||||||
|
|
||||||
|
auto finalizeFrame = [&]() {
|
||||||
|
if (!hasCurrentFrame)
|
||||||
|
return;
|
||||||
|
if (!currentFrame.texturePath.empty()) {
|
||||||
|
std::string resolvedPath = resolvePath(currentFrame.texturePath);
|
||||||
|
auto spriteFrame = SpriteFrameCache::getInstance().getOrCreateFromFile(
|
||||||
|
resolvedPath, currentFrame.textureIndex);
|
||||||
|
currentFrame.spriteFrame = spriteFrame;
|
||||||
|
}
|
||||||
|
result.clip->addFrame(std::move(currentFrame));
|
||||||
|
currentFrame = AnimationFrame();
|
||||||
|
hasCurrentFrame = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
while (std::getline(stream, line)) {
|
||||||
|
std::string trimmed = trim(line);
|
||||||
|
|
||||||
|
if (trimmed.empty())
|
||||||
|
continue;
|
||||||
|
if (trimmed[0] == '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// [FRAME MAX]
|
||||||
|
if (lineStartsWith(trimmed, "[FRAME MAX]")) {
|
||||||
|
readValue(trimmed, "[FRAME MAX]", stream);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [FRAMEXXX] - 新帧开始
|
||||||
|
if (trimmed.size() >= 7 && trimmed.substr(0, 6) == "[FRAME" &&
|
||||||
|
trimmed.back() == ']' && !lineStartsWith(trimmed, "[FRAME MAX]")) {
|
||||||
|
finalizeFrame();
|
||||||
|
currentFrame = AnimationFrame();
|
||||||
|
inFrame = true;
|
||||||
|
hasCurrentFrame = true;
|
||||||
|
pendingImage = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inFrame) {
|
||||||
|
// 全局属性
|
||||||
|
if (lineStartsWith(trimmed, "[LOOP]")) {
|
||||||
|
result.clip->setLooping(true);
|
||||||
|
} else if (lineStartsWith(trimmed, "[SHADOW]")) {
|
||||||
|
result.clip->globalProperties().set(FramePropertyKey::Shadow, true);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 帧内属性解析 ===
|
||||||
|
|
||||||
|
// [IMAGE] - 图片路径和索引(跨行)
|
||||||
|
if (lineStartsWith(trimmed, "[IMAGE]")) {
|
||||||
|
std::string val = valueAfterTag(trimmed, "[IMAGE]");
|
||||||
|
if (!val.empty()) {
|
||||||
|
auto tokens = splitWhitespace(val);
|
||||||
|
imagePath = stripBackticks(tokens[0]);
|
||||||
|
if (tokens.size() >= 2) {
|
||||||
|
currentFrame.texturePath = imagePath;
|
||||||
|
currentFrame.textureIndex = std::stoi(tokens[1]);
|
||||||
|
pendingImage = false;
|
||||||
|
} else {
|
||||||
|
pendingImage = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pendingImage = true;
|
||||||
|
imagePath.clear();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [IMAGE] 后续行
|
||||||
|
if (pendingImage) {
|
||||||
|
if (imagePath.empty()) {
|
||||||
|
imagePath = stripBackticks(trimmed);
|
||||||
|
} else {
|
||||||
|
currentFrame.texturePath = imagePath;
|
||||||
|
currentFrame.textureIndex = std::stoi(trimmed);
|
||||||
|
pendingImage = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [IMAGE POS]
|
||||||
|
if (lineStartsWith(trimmed, "[IMAGE POS]")) {
|
||||||
|
std::string val = readValue(trimmed, "[IMAGE POS]", stream);
|
||||||
|
auto tokens = splitWhitespace(val);
|
||||||
|
if (tokens.size() >= 2) {
|
||||||
|
currentFrame.offset = Vec2(static_cast<float>(std::stoi(tokens[0])),
|
||||||
|
static_cast<float>(std::stoi(tokens[1])));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [DELAY]
|
||||||
|
if (lineStartsWith(trimmed, "[DELAY]")) {
|
||||||
|
std::string val = readValue(trimmed, "[DELAY]", stream);
|
||||||
|
currentFrame.delay = static_cast<float>(std::stoi(trim(val)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [DAMAGE TYPE]
|
||||||
|
if (lineStartsWith(trimmed, "[DAMAGE TYPE]")) {
|
||||||
|
std::string val = readValue(trimmed, "[DAMAGE TYPE]", stream);
|
||||||
|
val = stripBackticks(val);
|
||||||
|
int damageType = 0;
|
||||||
|
if (val == "SUPERARMOR")
|
||||||
|
damageType = 1;
|
||||||
|
else if (val == "UNBREAKABLE")
|
||||||
|
damageType = 2;
|
||||||
|
currentFrame.properties.set(FramePropertyKey::DamageType, damageType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [DAMAGE BOX]
|
||||||
|
if (lineStartsWith(trimmed, "[DAMAGE BOX]")) {
|
||||||
|
std::string val = readValue(trimmed, "[DAMAGE BOX]", stream);
|
||||||
|
auto tokens = splitWhitespace(val);
|
||||||
|
if (tokens.size() >= 6) {
|
||||||
|
std::array<int32_t, 6> box;
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
box[i] = std::stoi(tokens[i]);
|
||||||
|
}
|
||||||
|
currentFrame.damageBoxes.push_back(box);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [ATTACK BOX]
|
||||||
|
if (lineStartsWith(trimmed, "[ATTACK BOX]")) {
|
||||||
|
std::string val = readValue(trimmed, "[ATTACK BOX]", stream);
|
||||||
|
auto tokens = splitWhitespace(val);
|
||||||
|
if (tokens.size() >= 6) {
|
||||||
|
std::array<int32_t, 6> box;
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
box[i] = std::stoi(tokens[i]);
|
||||||
|
}
|
||||||
|
currentFrame.attackBoxes.push_back(box);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [SET FLAG]
|
||||||
|
if (lineStartsWith(trimmed, "[SET FLAG]")) {
|
||||||
|
std::string val = readValue(trimmed, "[SET FLAG]", stream);
|
||||||
|
currentFrame.properties.withSetFlag(std::stoi(trim(val)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [PLAY SOUND]
|
||||||
|
if (lineStartsWith(trimmed, "[PLAY SOUND]")) {
|
||||||
|
std::string val = readValue(trimmed, "[PLAY SOUND]", stream);
|
||||||
|
currentFrame.properties.withPlaySound(resolvePath(stripBackticks(val)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [IMAGE RATE]
|
||||||
|
if (lineStartsWith(trimmed, "[IMAGE RATE]")) {
|
||||||
|
std::string val = readValue(trimmed, "[IMAGE RATE]", stream);
|
||||||
|
auto tokens = splitWhitespace(val);
|
||||||
|
if (tokens.size() >= 2) {
|
||||||
|
currentFrame.properties.withImageRate(
|
||||||
|
Vec2(std::stof(tokens[0]), std::stof(tokens[1])));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [IMAGE ROTATE]
|
||||||
|
if (lineStartsWith(trimmed, "[IMAGE ROTATE]")) {
|
||||||
|
std::string val = readValue(trimmed, "[IMAGE ROTATE]", stream);
|
||||||
|
currentFrame.properties.withImageRotate(std::stof(trim(val)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [RGBA]
|
||||||
|
if (lineStartsWith(trimmed, "[RGBA]")) {
|
||||||
|
std::string val = readValue(trimmed, "[RGBA]", stream);
|
||||||
|
auto tokens = splitWhitespace(val);
|
||||||
|
if (tokens.size() >= 4) {
|
||||||
|
Color color =
|
||||||
|
Color::fromRGBA(static_cast<uint8_t>(std::stoi(tokens[0])),
|
||||||
|
static_cast<uint8_t>(std::stoi(tokens[1])),
|
||||||
|
static_cast<uint8_t>(std::stoi(tokens[2])),
|
||||||
|
static_cast<uint8_t>(std::stoi(tokens[3])));
|
||||||
|
currentFrame.properties.withColorTint(color);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [INTERPOLATION]
|
||||||
|
if (lineStartsWith(trimmed, "[INTERPOLATION]")) {
|
||||||
|
currentFrame.properties.withInterpolation(true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [LOOP]
|
||||||
|
if (lineStartsWith(trimmed, "[LOOP]")) {
|
||||||
|
currentFrame.properties.set(FramePropertyKey::Loop, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [SHADOW]
|
||||||
|
if (lineStartsWith(trimmed, "[SHADOW]")) {
|
||||||
|
currentFrame.properties.set(FramePropertyKey::Shadow, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [FLIP TYPE]
|
||||||
|
if (lineStartsWith(trimmed, "[FLIP TYPE]")) {
|
||||||
|
std::string val = readValue(trimmed, "[FLIP TYPE]", stream);
|
||||||
|
val = stripBackticks(val);
|
||||||
|
int flipType = 0;
|
||||||
|
if (val == "HORIZON")
|
||||||
|
flipType = 1;
|
||||||
|
else if (val == "VERTICAL")
|
||||||
|
flipType = 2;
|
||||||
|
else if (val == "ALL")
|
||||||
|
flipType = 3;
|
||||||
|
else
|
||||||
|
flipType = std::stoi(val);
|
||||||
|
currentFrame.properties.set(FramePropertyKey::FlipType, flipType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [COORD]
|
||||||
|
if (lineStartsWith(trimmed, "[COORD]")) {
|
||||||
|
std::string val = readValue(trimmed, "[COORD]", stream);
|
||||||
|
currentFrame.properties.set(FramePropertyKey::Coord,
|
||||||
|
std::stoi(trim(val)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [GRAPHIC EFFECT]
|
||||||
|
if (lineStartsWith(trimmed, "[GRAPHIC EFFECT]")) {
|
||||||
|
std::string val = readValue(trimmed, "[GRAPHIC EFFECT]", stream);
|
||||||
|
val = stripBackticks(val);
|
||||||
|
int effectType = 0;
|
||||||
|
if (val == "DODGE")
|
||||||
|
effectType = 1;
|
||||||
|
else if (val == "LINEARDODGE")
|
||||||
|
effectType = 2;
|
||||||
|
else if (val == "DARK")
|
||||||
|
effectType = 3;
|
||||||
|
else if (val == "XOR")
|
||||||
|
effectType = 4;
|
||||||
|
else if (val == "MONOCHROME")
|
||||||
|
effectType = 5;
|
||||||
|
else if (val == "SPACEDISTORT")
|
||||||
|
effectType = 6;
|
||||||
|
else
|
||||||
|
effectType = std::stoi(val);
|
||||||
|
currentFrame.properties.set(FramePropertyKey::GraphicEffect, effectType);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [CLIP]
|
||||||
|
if (lineStartsWith(trimmed, "[CLIP]")) {
|
||||||
|
std::string val = readValue(trimmed, "[CLIP]", stream);
|
||||||
|
auto tokens = splitWhitespace(val);
|
||||||
|
if (tokens.size() >= 4) {
|
||||||
|
std::vector<int> clipRegion = {
|
||||||
|
std::stoi(tokens[0]), std::stoi(tokens[1]), std::stoi(tokens[2]),
|
||||||
|
std::stoi(tokens[3])};
|
||||||
|
currentFrame.properties.set(FramePropertyKey::ClipRegion, clipRegion);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [LOOP START]
|
||||||
|
if (lineStartsWith(trimmed, "[LOOP START]")) {
|
||||||
|
currentFrame.properties.set(FramePropertyKey::LoopStart, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [LOOP END]
|
||||||
|
if (lineStartsWith(trimmed, "[LOOP END]")) {
|
||||||
|
std::string val = readValue(trimmed, "[LOOP END]", stream);
|
||||||
|
currentFrame.properties.set(FramePropertyKey::LoopEnd,
|
||||||
|
std::stoi(trim(val)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未识别标签 - 静默忽略
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存最后一帧
|
||||||
|
finalizeFrame();
|
||||||
|
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AniParser::resolvePath(const std::string &relativePath) const {
|
||||||
|
std::string resolved = relativePath;
|
||||||
|
if (pathResolver_) {
|
||||||
|
resolved = pathResolver_(relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!basePath_.empty() && !resolved.empty() && resolved[0] != '/') {
|
||||||
|
if (resolved.size() < 2 || resolved[1] != ':') {
|
||||||
|
resolved = basePath_ + "/" + resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,290 @@
|
||||||
|
#include <extra2d/animation/animated_sprite.h>
|
||||||
|
#include <extra2d/animation/interpolation_engine.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
const std::vector<std::array<int32_t, 6>> AnimatedSprite::emptyBoxes_;
|
||||||
|
const std::string AnimatedSprite::emptyString_;
|
||||||
|
|
||||||
|
AnimatedSprite::AnimatedSprite() {
|
||||||
|
controller_.setFrameChangeCallback(
|
||||||
|
[this](size_t oldIdx, size_t newIdx, const AnimationFrame &frame) {
|
||||||
|
onFrameChanged(oldIdx, newIdx, frame);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 静态工厂 ------
|
||||||
|
|
||||||
|
Ptr<AnimatedSprite> AnimatedSprite::create() {
|
||||||
|
return makePtr<AnimatedSprite>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<AnimatedSprite> AnimatedSprite::create(Ptr<AnimationClip> clip) {
|
||||||
|
auto sprite = makePtr<AnimatedSprite>();
|
||||||
|
sprite->setAnimationClip(std::move(clip));
|
||||||
|
return sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<AnimatedSprite> AnimatedSprite::create(const std::string &aniFilePath) {
|
||||||
|
auto sprite = makePtr<AnimatedSprite>();
|
||||||
|
sprite->loadAnimation(aniFilePath);
|
||||||
|
return sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 动画绑定 ------
|
||||||
|
|
||||||
|
void AnimatedSprite::setAnimationClip(Ptr<AnimationClip> clip) {
|
||||||
|
controller_.setClip(clip);
|
||||||
|
if (clip && !clip->empty()) {
|
||||||
|
applyFrame(clip->getFrame(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimatedSprite::loadAnimation(const std::string &aniFilePath) {
|
||||||
|
auto clip = AnimationCache::getInstance().loadClip(aniFilePath);
|
||||||
|
if (clip) {
|
||||||
|
setAnimationClip(clip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<AnimationClip> AnimatedSprite::getAnimationClip() const {
|
||||||
|
return controller_.getClip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 动画字典 ------
|
||||||
|
|
||||||
|
void AnimatedSprite::addAnimation(const std::string &name,
|
||||||
|
Ptr<AnimationClip> clip) {
|
||||||
|
if (clip) {
|
||||||
|
animations_[name] = std::move(clip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimatedSprite::play(const std::string &name, bool loop) {
|
||||||
|
auto it = animations_.find(name);
|
||||||
|
if (it == animations_.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
currentAnimationName_ = name;
|
||||||
|
// 精灵图动画不应覆盖节点的 position/scale/rotation
|
||||||
|
applyFrameTransform_ = false;
|
||||||
|
setAnimationClip(it->second);
|
||||||
|
setLooping(loop);
|
||||||
|
play();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnimatedSprite::hasAnimation(const std::string &name) const {
|
||||||
|
return animations_.find(name) != animations_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<AnimationClip> AnimatedSprite::getAnimation(const std::string &name) const {
|
||||||
|
auto it = animations_.find(name);
|
||||||
|
if (it != animations_.end())
|
||||||
|
return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &AnimatedSprite::getCurrentAnimationName() const {
|
||||||
|
return currentAnimationName_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 播放控制 ------
|
||||||
|
|
||||||
|
void AnimatedSprite::play() { controller_.play(); }
|
||||||
|
void AnimatedSprite::pause() { controller_.pause(); }
|
||||||
|
void AnimatedSprite::resume() { controller_.resume(); }
|
||||||
|
void AnimatedSprite::stop() { controller_.stop(); }
|
||||||
|
void AnimatedSprite::reset() { controller_.reset(); }
|
||||||
|
bool AnimatedSprite::isPlaying() const { return controller_.isPlaying(); }
|
||||||
|
bool AnimatedSprite::isPaused() const { return controller_.isPaused(); }
|
||||||
|
bool AnimatedSprite::isStopped() const { return controller_.isStopped(); }
|
||||||
|
|
||||||
|
// ------ 属性控制 ------
|
||||||
|
|
||||||
|
void AnimatedSprite::setLooping(bool loop) { controller_.setLooping(loop); }
|
||||||
|
bool AnimatedSprite::isLooping() const { return controller_.isLooping(); }
|
||||||
|
void AnimatedSprite::setPlaybackSpeed(float speed) {
|
||||||
|
controller_.setPlaybackSpeed(speed);
|
||||||
|
}
|
||||||
|
float AnimatedSprite::getPlaybackSpeed() const {
|
||||||
|
return controller_.getPlaybackSpeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 帧控制 ------
|
||||||
|
|
||||||
|
void AnimatedSprite::setFrameIndex(size_t index) {
|
||||||
|
controller_.setFrameIndex(index);
|
||||||
|
}
|
||||||
|
size_t AnimatedSprite::getCurrentFrameIndex() const {
|
||||||
|
return controller_.getCurrentFrameIndex();
|
||||||
|
}
|
||||||
|
size_t AnimatedSprite::getTotalFrames() const {
|
||||||
|
return controller_.getTotalFrames();
|
||||||
|
}
|
||||||
|
void AnimatedSprite::nextFrame() { controller_.nextFrame(); }
|
||||||
|
void AnimatedSprite::prevFrame() { controller_.prevFrame(); }
|
||||||
|
|
||||||
|
// ------ 帧范围限制 ------
|
||||||
|
|
||||||
|
void AnimatedSprite::setFrameRange(int start, int end) {
|
||||||
|
frameRangeStart_ = start;
|
||||||
|
frameRangeEnd_ = end;
|
||||||
|
|
||||||
|
// 确保当前帧在新的范围内
|
||||||
|
auto clip = controller_.getClip();
|
||||||
|
if (clip && !clip->empty()) {
|
||||||
|
size_t currentFrame = controller_.getCurrentFrameIndex();
|
||||||
|
size_t minFrame = static_cast<size_t>(start);
|
||||||
|
size_t maxFrame =
|
||||||
|
(end < 0) ? clip->getFrameCount() - 1 : static_cast<size_t>(end);
|
||||||
|
|
||||||
|
if (currentFrame < minFrame || currentFrame > maxFrame) {
|
||||||
|
controller_.setFrameIndex(minFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> AnimatedSprite::getFrameRange() const {
|
||||||
|
return {frameRangeStart_, frameRangeEnd_};
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimatedSprite::clearFrameRange() {
|
||||||
|
frameRangeStart_ = 0;
|
||||||
|
frameRangeEnd_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnimatedSprite::hasFrameRange() const { return frameRangeEnd_ >= 0; }
|
||||||
|
|
||||||
|
// ------ 回调 ------
|
||||||
|
|
||||||
|
void AnimatedSprite::setCompletionCallback(
|
||||||
|
AnimationController::CompletionCallback cb) {
|
||||||
|
controller_.setCompletionCallback(std::move(cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimatedSprite::setKeyframeCallback(
|
||||||
|
AnimationController::KeyframeCallback cb) {
|
||||||
|
controller_.setKeyframeCallback(std::move(cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimatedSprite::setSoundTriggerCallback(
|
||||||
|
AnimationController::SoundTriggerCallback cb) {
|
||||||
|
controller_.setSoundTriggerCallback(std::move(cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 碰撞盒访问 ------
|
||||||
|
|
||||||
|
const std::vector<std::array<int32_t, 6>> &
|
||||||
|
AnimatedSprite::getCurrentDamageBoxes() const {
|
||||||
|
auto clip = controller_.getClip();
|
||||||
|
if (!clip || clip->empty())
|
||||||
|
return emptyBoxes_;
|
||||||
|
return clip->getFrame(controller_.getCurrentFrameIndex()).damageBoxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::array<int32_t, 6>> &
|
||||||
|
AnimatedSprite::getCurrentAttackBoxes() const {
|
||||||
|
auto clip = controller_.getClip();
|
||||||
|
if (!clip || clip->empty())
|
||||||
|
return emptyBoxes_;
|
||||||
|
return clip->getFrame(controller_.getCurrentFrameIndex()).attackBoxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 生命周期 ------
|
||||||
|
|
||||||
|
void AnimatedSprite::onEnter() {
|
||||||
|
Sprite::onEnter();
|
||||||
|
if (autoPlay_ && controller_.getClip() && !controller_.getClip()->empty()) {
|
||||||
|
play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimatedSprite::onUpdate(float dt) {
|
||||||
|
Sprite::onUpdate(dt);
|
||||||
|
|
||||||
|
// 保存更新前的帧索引
|
||||||
|
size_t prevFrameIdx = controller_.getCurrentFrameIndex();
|
||||||
|
|
||||||
|
controller_.update(dt);
|
||||||
|
|
||||||
|
// 应用帧范围限制
|
||||||
|
if (hasFrameRange() && controller_.isPlaying()) {
|
||||||
|
size_t currentFrame = controller_.getCurrentFrameIndex();
|
||||||
|
size_t minFrame = static_cast<size_t>(frameRangeStart_);
|
||||||
|
size_t maxFrame = static_cast<size_t>(frameRangeEnd_);
|
||||||
|
|
||||||
|
auto clip = controller_.getClip();
|
||||||
|
if (clip && !clip->empty()) {
|
||||||
|
// 确保范围有效
|
||||||
|
if (maxFrame >= clip->getFrameCount()) {
|
||||||
|
maxFrame = clip->getFrameCount() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果超出范围,回到起始帧
|
||||||
|
if (currentFrame < minFrame || currentFrame > maxFrame) {
|
||||||
|
controller_.setFrameIndex(minFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插值处理
|
||||||
|
if (controller_.isInterpolating()) {
|
||||||
|
auto clip = controller_.getClip();
|
||||||
|
if (clip) {
|
||||||
|
size_t idx = controller_.getCurrentFrameIndex();
|
||||||
|
if (idx + 1 < clip->getFrameCount()) {
|
||||||
|
auto props = InterpolationEngine::interpolate(
|
||||||
|
clip->getFrame(idx), clip->getFrame(idx + 1),
|
||||||
|
controller_.getInterpolationFactor());
|
||||||
|
// 仅更新插值属性,不覆盖帧纹理
|
||||||
|
Sprite::setColor(props.color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 内部方法 ------
|
||||||
|
|
||||||
|
void AnimatedSprite::onFrameChanged(size_t /*oldIdx*/, size_t /*newIdx*/,
|
||||||
|
const AnimationFrame &frame) {
|
||||||
|
applyFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimatedSprite::applyFrame(const AnimationFrame &frame) {
|
||||||
|
// 更新纹理
|
||||||
|
if (frame.spriteFrame && frame.spriteFrame->isValid()) {
|
||||||
|
Sprite::setTexture(frame.spriteFrame->getTexture());
|
||||||
|
Sprite::setTextureRect(frame.spriteFrame->getRect());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帧变换仅在 ANI 动画模式下应用;精灵图模式跳过,避免覆盖节点世界坐标
|
||||||
|
if (applyFrameTransform_) {
|
||||||
|
// 应用帧偏移(作为精灵位置)
|
||||||
|
Node::setPosition(frame.offset);
|
||||||
|
|
||||||
|
// 应用缩放
|
||||||
|
Vec2 scale = frame.getEffectiveScale();
|
||||||
|
Node::setScale(scale);
|
||||||
|
|
||||||
|
// 应用旋转
|
||||||
|
float rotation = frame.getEffectiveRotation();
|
||||||
|
Node::setRotation(rotation);
|
||||||
|
|
||||||
|
// 应用翻转
|
||||||
|
auto flipType = frame.properties.get<int>(FramePropertyKey::FlipType);
|
||||||
|
if (flipType.has_value()) {
|
||||||
|
int flip = flipType.value();
|
||||||
|
Sprite::setFlipX(flip == 1 || flip == 3);
|
||||||
|
Sprite::setFlipY(flip == 2 || flip == 3);
|
||||||
|
} else {
|
||||||
|
Sprite::setFlipX(false);
|
||||||
|
Sprite::setFlipY(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 颜色始终应用
|
||||||
|
Color color = frame.getEffectiveColor();
|
||||||
|
Sprite::setColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
#include <extra2d/animation/ani_binary_parser.h>
|
||||||
|
#include <extra2d/animation/ani_parser.h>
|
||||||
|
#include <extra2d/animation/animation_cache.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// 检测文件是否为文本格式 ANI
|
||||||
|
// 文本格式以 '#' 或 '[' 开头(跳过空白后)
|
||||||
|
bool isTextFormat(const std::string &filePath) {
|
||||||
|
std::ifstream file(filePath, std::ios::binary);
|
||||||
|
if (!file.is_open())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 读取前几个字节判断
|
||||||
|
char buf[16] = {};
|
||||||
|
file.read(buf, sizeof(buf));
|
||||||
|
auto bytesRead = file.gcount();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// 跳过 BOM 和空白
|
||||||
|
size_t pos = 0;
|
||||||
|
// UTF-8 BOM
|
||||||
|
if (bytesRead >= 3 && static_cast<uint8_t>(buf[0]) == 0xEF &&
|
||||||
|
static_cast<uint8_t>(buf[1]) == 0xBB &&
|
||||||
|
static_cast<uint8_t>(buf[2]) == 0xBF) {
|
||||||
|
pos = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过空白
|
||||||
|
while (pos < static_cast<size_t>(bytesRead) &&
|
||||||
|
(buf[pos] == ' ' || buf[pos] == '\t' || buf[pos] == '\r' ||
|
||||||
|
buf[pos] == '\n')) {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos >= static_cast<size_t>(bytesRead))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 文本 ANI 文件以 '#' (注释/PVF_File) 或 '[' (标签) 开头
|
||||||
|
return buf[pos] == '#' || buf[pos] == '[';
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
Ptr<AnimationClip> AnimationCache::loadClip(const std::string &aniFilePath) {
|
||||||
|
// 先检查缓存
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = clips_.find(aniFilePath);
|
||||||
|
if (it != clips_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取基础路径
|
||||||
|
std::string basePath;
|
||||||
|
auto lastSlash = aniFilePath.find_last_of("/\\");
|
||||||
|
if (lastSlash != std::string::npos) {
|
||||||
|
basePath = aniFilePath.substr(0, lastSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
AniParseResult result;
|
||||||
|
|
||||||
|
if (isTextFormat(aniFilePath)) {
|
||||||
|
// 文本格式
|
||||||
|
AniParser parser;
|
||||||
|
if (pathResolver_) {
|
||||||
|
parser.setPathResolver(pathResolver_);
|
||||||
|
}
|
||||||
|
if (!basePath.empty()) {
|
||||||
|
parser.setBasePath(basePath);
|
||||||
|
}
|
||||||
|
result = parser.parse(aniFilePath);
|
||||||
|
} else {
|
||||||
|
// 二进制格式
|
||||||
|
AniBinaryParser parser;
|
||||||
|
if (pathResolver_) {
|
||||||
|
parser.setPathResolver(pathResolver_);
|
||||||
|
}
|
||||||
|
if (!basePath.empty()) {
|
||||||
|
parser.setBasePath(basePath);
|
||||||
|
}
|
||||||
|
result = parser.parseFromFile(aniFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.success || !result.clip) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到缓存
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
clips_[aniFilePath] = result.clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
#include <extra2d/animation/animation_clip.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// AnimationClip 的实现全部在头文件中以内联方式完成
|
||||||
|
// 此文件保留用于未来可能需要的非内联实现
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
#include <cassert>
|
||||||
|
#include <extra2d/animation/animation_controller.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
void AnimationController::setClip(Ptr<AnimationClip> clip) {
|
||||||
|
clip_ = std::move(clip);
|
||||||
|
currentFrameIndex_ = 0;
|
||||||
|
accumulatedTime_ = 0.0f;
|
||||||
|
interpolating_ = false;
|
||||||
|
interpolationFactor_ = 0.0f;
|
||||||
|
state_ = AnimPlayState::Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::play() {
|
||||||
|
if (!clip_ || clip_->empty())
|
||||||
|
return;
|
||||||
|
state_ = AnimPlayState::Playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::pause() {
|
||||||
|
if (state_ == AnimPlayState::Playing) {
|
||||||
|
state_ = AnimPlayState::Paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::resume() {
|
||||||
|
if (state_ == AnimPlayState::Paused) {
|
||||||
|
state_ = AnimPlayState::Playing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::stop() {
|
||||||
|
state_ = AnimPlayState::Stopped;
|
||||||
|
accumulatedTime_ = 0.0f;
|
||||||
|
interpolating_ = false;
|
||||||
|
interpolationFactor_ = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::reset() {
|
||||||
|
stop();
|
||||||
|
if (clip_ && !clip_->empty()) {
|
||||||
|
advanceFrame(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::setFrameIndex(size_t index) {
|
||||||
|
if (!clip_ || index >= clip_->getFrameCount())
|
||||||
|
return;
|
||||||
|
accumulatedTime_ = 0.0f;
|
||||||
|
advanceFrame(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::nextFrame() {
|
||||||
|
if (!clip_ || clip_->empty())
|
||||||
|
return;
|
||||||
|
size_t next = currentFrameIndex_ + 1;
|
||||||
|
if (next >= clip_->getFrameCount()) {
|
||||||
|
if (isLooping()) {
|
||||||
|
next = 0;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accumulatedTime_ = 0.0f;
|
||||||
|
advanceFrame(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::prevFrame() {
|
||||||
|
if (!clip_ || clip_->empty())
|
||||||
|
return;
|
||||||
|
if (currentFrameIndex_ > 0) {
|
||||||
|
accumulatedTime_ = 0.0f;
|
||||||
|
advanceFrame(currentFrameIndex_ - 1);
|
||||||
|
} else if (isLooping()) {
|
||||||
|
accumulatedTime_ = 0.0f;
|
||||||
|
advanceFrame(clip_->getFrameCount() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::update(float dt) {
|
||||||
|
if (state_ != AnimPlayState::Playing)
|
||||||
|
return;
|
||||||
|
if (!clip_ || clip_->empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 累加时间(转换为毫秒)
|
||||||
|
float dt_ms = dt * 1000.0f * playbackSpeed_;
|
||||||
|
accumulatedTime_ += dt_ms;
|
||||||
|
|
||||||
|
const AnimationFrame ¤tFrame = clip_->getFrame(currentFrameIndex_);
|
||||||
|
float frameDelay = currentFrame.delay;
|
||||||
|
|
||||||
|
// 更新插值状态
|
||||||
|
updateInterpolation();
|
||||||
|
|
||||||
|
// 循环处理:支持一次跳过多帧(与原始 ANI 系统行为一致)
|
||||||
|
while (accumulatedTime_ >= frameDelay) {
|
||||||
|
accumulatedTime_ -= frameDelay;
|
||||||
|
|
||||||
|
size_t totalFrames = clip_->getFrameCount();
|
||||||
|
|
||||||
|
if (currentFrameIndex_ < totalFrames - 1) {
|
||||||
|
// 推进到下一帧
|
||||||
|
advanceFrame(currentFrameIndex_ + 1);
|
||||||
|
} else {
|
||||||
|
// 最后一帧播放完毕
|
||||||
|
if (isLooping()) {
|
||||||
|
advanceFrame(0);
|
||||||
|
} else {
|
||||||
|
// 动画结束
|
||||||
|
state_ = AnimPlayState::Stopped;
|
||||||
|
if (onComplete_) {
|
||||||
|
onComplete_();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新下一帧的延迟
|
||||||
|
frameDelay = clip_->getFrame(currentFrameIndex_).delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新插值因子
|
||||||
|
updateInterpolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AnimationController::getTotalFrames() const {
|
||||||
|
return clip_ ? clip_->getFrameCount() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnimationFrame &AnimationController::getCurrentFrame() const {
|
||||||
|
assert(clip_ && currentFrameIndex_ < clip_->getFrameCount());
|
||||||
|
return clip_->getFrame(currentFrameIndex_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnimationController::isLooping() const {
|
||||||
|
if (hasLoopOverride_)
|
||||||
|
return loopOverride_;
|
||||||
|
return clip_ ? clip_->isLooping() : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::setLooping(bool loop) {
|
||||||
|
hasLoopOverride_ = true;
|
||||||
|
loopOverride_ = loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::advanceFrame(size_t newIndex) {
|
||||||
|
if (!clip_ || newIndex >= clip_->getFrameCount())
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t oldIndex = currentFrameIndex_;
|
||||||
|
currentFrameIndex_ = newIndex;
|
||||||
|
|
||||||
|
const AnimationFrame &frame = clip_->getFrame(newIndex);
|
||||||
|
|
||||||
|
// 触发帧变更回调
|
||||||
|
if (onFrameChange_) {
|
||||||
|
onFrameChange_(oldIndex, newIndex, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理帧属性(关键帧、音效等)
|
||||||
|
processFrameProperties(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::processFrameProperties(const AnimationFrame &frame) {
|
||||||
|
const auto &props = frame.properties;
|
||||||
|
|
||||||
|
// 关键帧回调
|
||||||
|
if (props.has(FramePropertyKey::SetFlag)) {
|
||||||
|
auto flagIndex = props.get<int>(FramePropertyKey::SetFlag);
|
||||||
|
if (flagIndex.has_value() && onKeyframe_) {
|
||||||
|
onKeyframe_(flagIndex.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 音效触发
|
||||||
|
if (props.has(FramePropertyKey::PlaySound)) {
|
||||||
|
auto soundPath = props.get<std::string>(FramePropertyKey::PlaySound);
|
||||||
|
if (soundPath.has_value() && onSoundTrigger_) {
|
||||||
|
onSoundTrigger_(soundPath.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationController::updateInterpolation() {
|
||||||
|
if (!clip_ || clip_->empty()) {
|
||||||
|
interpolating_ = false;
|
||||||
|
interpolationFactor_ = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnimationFrame ¤tFrame = clip_->getFrame(currentFrameIndex_);
|
||||||
|
|
||||||
|
if (currentFrame.hasInterpolation() &&
|
||||||
|
currentFrameIndex_ + 1 < clip_->getFrameCount()) {
|
||||||
|
interpolating_ = true;
|
||||||
|
float frameDelay = currentFrame.delay;
|
||||||
|
interpolationFactor_ =
|
||||||
|
(frameDelay > 0.0f)
|
||||||
|
? math::clamp(accumulatedTime_ / frameDelay, 0.0f, 1.0f)
|
||||||
|
: 0.0f;
|
||||||
|
} else {
|
||||||
|
interpolating_ = false;
|
||||||
|
interpolationFactor_ = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
#include <extra2d/animation/animation_frame.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// AnimationFrame 的实现全部在头文件中以内联方式完成
|
||||||
|
// 此文件保留用于未来可能需要的非内联实现
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,266 @@
|
||||||
|
#include <extra2d/animation/animation_node.h>
|
||||||
|
#include <extra2d/animation/interpolation_engine.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
const std::vector<std::array<int32_t, 6>> AnimationNode::emptyBoxes_;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 构造
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
AnimationNode::AnimationNode() { setupControllerCallbacks(); }
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 静态工厂
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
Ptr<AnimationNode> AnimationNode::create() { return makePtr<AnimationNode>(); }
|
||||||
|
|
||||||
|
Ptr<AnimationNode> AnimationNode::create(Ptr<AnimationClip> clip) {
|
||||||
|
auto node = makePtr<AnimationNode>();
|
||||||
|
node->setClip(std::move(clip));
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<AnimationNode> AnimationNode::create(const std::string &aniFilePath) {
|
||||||
|
auto node = makePtr<AnimationNode>();
|
||||||
|
node->loadFromFile(aniFilePath);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 动画数据
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void AnimationNode::setClip(Ptr<AnimationClip> clip) {
|
||||||
|
controller_.setClip(clip);
|
||||||
|
if (clip && !clip->empty()) {
|
||||||
|
// 预加载所有帧的 SpriteFrame
|
||||||
|
std::vector<AnimationFrame> frames;
|
||||||
|
frames.reserve(clip->getFrameCount());
|
||||||
|
for (size_t i = 0; i < clip->getFrameCount(); ++i) {
|
||||||
|
frames.push_back(clip->getFrame(i));
|
||||||
|
}
|
||||||
|
frameRenderer_.preloadFrames(frames);
|
||||||
|
} else {
|
||||||
|
frameRenderer_.releaseFrames();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<AnimationClip> AnimationNode::getClip() const {
|
||||||
|
return controller_.getClip();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnimationNode::loadFromFile(const std::string &aniFilePath) {
|
||||||
|
auto clip = AnimationCache::getInstance().loadClip(aniFilePath);
|
||||||
|
if (clip) {
|
||||||
|
setClip(clip);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 播放控制
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void AnimationNode::play() { controller_.play(); }
|
||||||
|
void AnimationNode::pause() { controller_.pause(); }
|
||||||
|
void AnimationNode::resume() { controller_.resume(); }
|
||||||
|
void AnimationNode::stop() { controller_.stop(); }
|
||||||
|
void AnimationNode::reset() { controller_.reset(); }
|
||||||
|
|
||||||
|
bool AnimationNode::isPlaying() const { return controller_.isPlaying(); }
|
||||||
|
bool AnimationNode::isPaused() const { return controller_.isPaused(); }
|
||||||
|
bool AnimationNode::isStopped() const { return controller_.isStopped(); }
|
||||||
|
|
||||||
|
void AnimationNode::setPlaybackSpeed(float speed) {
|
||||||
|
controller_.setPlaybackSpeed(speed);
|
||||||
|
}
|
||||||
|
float AnimationNode::getPlaybackSpeed() const {
|
||||||
|
return controller_.getPlaybackSpeed();
|
||||||
|
}
|
||||||
|
void AnimationNode::setLooping(bool loop) { controller_.setLooping(loop); }
|
||||||
|
bool AnimationNode::isLooping() const { return controller_.isLooping(); }
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 帧控制
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void AnimationNode::setFrameIndex(size_t index) {
|
||||||
|
controller_.setFrameIndex(index);
|
||||||
|
}
|
||||||
|
size_t AnimationNode::getCurrentFrameIndex() const {
|
||||||
|
return controller_.getCurrentFrameIndex();
|
||||||
|
}
|
||||||
|
size_t AnimationNode::getTotalFrames() const {
|
||||||
|
return controller_.getTotalFrames();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件回调
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void AnimationNode::setKeyframeCallback(KeyframeHitCallback callback) {
|
||||||
|
controller_.setKeyframeCallback(
|
||||||
|
[this, cb = std::move(callback)](int flagIndex) {
|
||||||
|
if (cb)
|
||||||
|
cb(flagIndex);
|
||||||
|
AnimationEvent evt;
|
||||||
|
evt.type = AnimationEventType::KeyframeHit;
|
||||||
|
evt.frameIndex = controller_.getCurrentFrameIndex();
|
||||||
|
evt.keyframeFlag = flagIndex;
|
||||||
|
evt.source = this;
|
||||||
|
dispatchEvent(evt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationNode::setCompletionCallback(AnimationCompleteCallback callback) {
|
||||||
|
controller_.setCompletionCallback([this, cb = std::move(callback)]() {
|
||||||
|
if (cb)
|
||||||
|
cb();
|
||||||
|
AnimationEvent evt;
|
||||||
|
evt.type = AnimationEventType::AnimationEnd;
|
||||||
|
evt.frameIndex = controller_.getCurrentFrameIndex();
|
||||||
|
evt.source = this;
|
||||||
|
dispatchEvent(evt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationNode::setFrameChangeCallback(
|
||||||
|
AnimationController::FrameChangeCallback callback) {
|
||||||
|
// 保存外部回调,在 setupControllerCallbacks 中已设置内部回调
|
||||||
|
// 需要重新绑定,将两者合并
|
||||||
|
controller_.setFrameChangeCallback(
|
||||||
|
[this, cb = std::move(callback)](size_t oldIdx, size_t newIdx,
|
||||||
|
const AnimationFrame &frame) {
|
||||||
|
if (cb)
|
||||||
|
cb(oldIdx, newIdx, frame);
|
||||||
|
AnimationEvent evt;
|
||||||
|
evt.type = AnimationEventType::FrameChanged;
|
||||||
|
evt.frameIndex = newIdx;
|
||||||
|
evt.previousFrameIndex = oldIdx;
|
||||||
|
evt.source = this;
|
||||||
|
dispatchEvent(evt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationNode::addEventListener(AnimationEventCallback callback) {
|
||||||
|
eventListeners_.push_back(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 视觉属性
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void AnimationNode::setTintColor(const Color &color) { tintColor_ = color; }
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 碰撞盒
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
const std::vector<std::array<int32_t, 6>> &
|
||||||
|
AnimationNode::getCurrentDamageBoxes() const {
|
||||||
|
auto clip = controller_.getClip();
|
||||||
|
if (!clip || clip->empty())
|
||||||
|
return emptyBoxes_;
|
||||||
|
return clip->getFrame(controller_.getCurrentFrameIndex()).damageBoxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::array<int32_t, 6>> &
|
||||||
|
AnimationNode::getCurrentAttackBoxes() const {
|
||||||
|
auto clip = controller_.getClip();
|
||||||
|
if (!clip || clip->empty())
|
||||||
|
return emptyBoxes_;
|
||||||
|
return clip->getFrame(controller_.getCurrentFrameIndex()).attackBoxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 查询
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
Size AnimationNode::getMaxFrameSize() const {
|
||||||
|
return frameRenderer_.getMaxFrameSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect AnimationNode::getBoundingBox() const {
|
||||||
|
Size size = frameRenderer_.getMaxFrameSize();
|
||||||
|
Vec2 pos = getPosition();
|
||||||
|
Vec2 anchor = getAnchor();
|
||||||
|
return Rect{{pos.x - size.width * anchor.x, pos.y - size.height * anchor.y},
|
||||||
|
size};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 生命周期
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void AnimationNode::onEnter() {
|
||||||
|
Node::onEnter();
|
||||||
|
if (autoPlay_ && controller_.getClip() && !controller_.getClip()->empty()) {
|
||||||
|
play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationNode::onExit() { Node::onExit(); }
|
||||||
|
|
||||||
|
void AnimationNode::onUpdate(float dt) {
|
||||||
|
Node::onUpdate(dt);
|
||||||
|
controller_.update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationNode::onDraw(RenderBackend &renderer) {
|
||||||
|
auto clip = controller_.getClip();
|
||||||
|
if (!clip || clip->empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t idx = controller_.getCurrentFrameIndex();
|
||||||
|
const auto &frame = clip->getFrame(idx);
|
||||||
|
Vec2 pos = getPosition();
|
||||||
|
|
||||||
|
if (controller_.isInterpolating() && idx + 1 < clip->getFrameCount()) {
|
||||||
|
auto props = InterpolationEngine::interpolate(
|
||||||
|
frame, clip->getFrame(idx + 1), controller_.getInterpolationFactor());
|
||||||
|
|
||||||
|
frameRenderer_.renderInterpolated(renderer, frame, idx, props, pos,
|
||||||
|
getOpacity(), tintColor_, flipX_, flipY_);
|
||||||
|
} else {
|
||||||
|
frameRenderer_.renderFrame(renderer, frame, idx, pos, getOpacity(),
|
||||||
|
tintColor_, flipX_, flipY_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 内部方法
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void AnimationNode::setupControllerCallbacks() {
|
||||||
|
controller_.setFrameChangeCallback(
|
||||||
|
[this](size_t oldIdx, size_t newIdx, const AnimationFrame &frame) {
|
||||||
|
AnimationEvent evt;
|
||||||
|
evt.type = AnimationEventType::FrameChanged;
|
||||||
|
evt.frameIndex = newIdx;
|
||||||
|
evt.previousFrameIndex = oldIdx;
|
||||||
|
evt.source = this;
|
||||||
|
dispatchEvent(evt);
|
||||||
|
});
|
||||||
|
|
||||||
|
controller_.setSoundTriggerCallback([this](const std::string &path) {
|
||||||
|
AnimationEvent evt;
|
||||||
|
evt.type = AnimationEventType::SoundTrigger;
|
||||||
|
evt.frameIndex = controller_.getCurrentFrameIndex();
|
||||||
|
evt.soundPath = path;
|
||||||
|
evt.source = this;
|
||||||
|
dispatchEvent(evt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationNode::dispatchEvent(const AnimationEvent &event) {
|
||||||
|
for (auto &listener : eventListeners_) {
|
||||||
|
if (listener)
|
||||||
|
listener(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
#include <extra2d/animation/animation_cache.h>
|
||||||
|
#include <extra2d/animation/composite_animation.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 静态工厂
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
Ptr<CompositeAnimation> CompositeAnimation::create() {
|
||||||
|
return makePtr<CompositeAnimation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<CompositeAnimation>
|
||||||
|
CompositeAnimation::create(const std::string &alsFilePath) {
|
||||||
|
auto comp = makePtr<CompositeAnimation>();
|
||||||
|
comp->loadFromFile(alsFilePath);
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 加载 ALS 文件
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
bool CompositeAnimation::loadFromFile(const std::string &alsFilePath) {
|
||||||
|
AlsParser parser;
|
||||||
|
auto result = parser.parse(alsFilePath);
|
||||||
|
if (!result.success)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 清除现有图层
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node) {
|
||||||
|
removeChild(entry.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layers_.clear();
|
||||||
|
|
||||||
|
// 创建每个图层
|
||||||
|
for (const auto &layer : result.layers) {
|
||||||
|
auto node = AnimationNode::create();
|
||||||
|
if (node->loadFromFile(layer.aniPath)) {
|
||||||
|
node->setPosition(layer.offset);
|
||||||
|
addLayer(node, layer.zOrder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !layers_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 图层管理
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void CompositeAnimation::addLayer(Ptr<AnimationNode> node, int zOrder) {
|
||||||
|
if (!node)
|
||||||
|
return;
|
||||||
|
|
||||||
|
node->setZOrder(zOrder);
|
||||||
|
addChild(node);
|
||||||
|
layers_.push_back({node, zOrder});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::removeLayer(size_t index) {
|
||||||
|
if (index >= layers_.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto &entry = layers_[index];
|
||||||
|
if (entry.node) {
|
||||||
|
removeChild(entry.node);
|
||||||
|
}
|
||||||
|
layers_.erase(layers_.begin() + static_cast<ptrdiff_t>(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<AnimationNode> CompositeAnimation::getLayer(size_t index) const {
|
||||||
|
if (index >= layers_.size())
|
||||||
|
return nullptr;
|
||||||
|
return layers_[index].node;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<AnimationNode> CompositeAnimation::getMainLayer() const {
|
||||||
|
if (layers_.empty())
|
||||||
|
return nullptr;
|
||||||
|
return layers_[0].node;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CompositeAnimation::getLayerCount() const { return layers_.size(); }
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 统一播放控制
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void CompositeAnimation::play() {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::pause() {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::resume() {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::stop() {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::reset() {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::setPlaybackSpeed(float speed) {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->setPlaybackSpeed(speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::setLooping(bool loop) {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->setLooping(loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CompositeAnimation::isPlaying() const {
|
||||||
|
auto main = getMainLayer();
|
||||||
|
return main ? main->isPlaying() : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CompositeAnimation::isStopped() const {
|
||||||
|
auto main = getMainLayer();
|
||||||
|
return main ? main->isStopped() : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件回调(绑定到主图层)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void CompositeAnimation::setKeyframeCallback(KeyframeHitCallback callback) {
|
||||||
|
auto main = getMainLayer();
|
||||||
|
if (main)
|
||||||
|
main->setKeyframeCallback(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::setCompletionCallback(
|
||||||
|
AnimationCompleteCallback callback) {
|
||||||
|
auto main = getMainLayer();
|
||||||
|
if (main)
|
||||||
|
main->setCompletionCallback(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::addEventListener(AnimationEventCallback callback) {
|
||||||
|
auto main = getMainLayer();
|
||||||
|
if (main)
|
||||||
|
main->addEventListener(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 视觉属性(应用到所有图层)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void CompositeAnimation::setTintColor(const Color &color) {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->setTintColor(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::setFlipX(bool flip) {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->setFlipX(flip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompositeAnimation::setFlipY(bool flip) {
|
||||||
|
for (auto &entry : layers_) {
|
||||||
|
if (entry.node)
|
||||||
|
entry.node->setFlipY(flip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
#include <extra2d/animation/frame_property.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FramePropertySet 实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void FramePropertySet::set(FramePropertyKey key, FramePropertyValue value) {
|
||||||
|
properties_[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FramePropertySet::set(FramePropertyKey key, const std::string& value) {
|
||||||
|
FramePropertyValue pv;
|
||||||
|
pv.type = PropertyValueType::String;
|
||||||
|
pv.data.stringIndex = allocateString(value);
|
||||||
|
properties_[key] = pv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FramePropertySet::set(FramePropertyKey key, const std::vector<int>& value) {
|
||||||
|
FramePropertyValue pv;
|
||||||
|
pv.type = PropertyValueType::IntVector;
|
||||||
|
pv.data.vectorIndex = allocateVector(value);
|
||||||
|
properties_[key] = pv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FramePropertySet::setCustom(const std::string &key, std::any value) {
|
||||||
|
customProperties_[key] = std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FramePropertySet::has(FramePropertyKey key) const {
|
||||||
|
return properties_.find(key) != properties_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FramePropertySet::hasCustom(const std::string &key) const {
|
||||||
|
return customProperties_.find(key) != customProperties_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FramePropertySet::remove(FramePropertyKey key) {
|
||||||
|
properties_.erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FramePropertySet::removeCustom(const std::string &key) {
|
||||||
|
customProperties_.erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FramePropertySet::clear() {
|
||||||
|
properties_.clear();
|
||||||
|
customProperties_.clear();
|
||||||
|
stringPool_.clear();
|
||||||
|
vectorPool_.clear();
|
||||||
|
nextStringIndex_ = 0;
|
||||||
|
nextVectorIndex_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::any> FramePropertySet::getCustom(const std::string &key) const {
|
||||||
|
auto it = customProperties_.find(key);
|
||||||
|
if (it == customProperties_.end())
|
||||||
|
return std::nullopt;
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 字符串池和vector池管理
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
uint32_t FramePropertySet::allocateString(const std::string& str) {
|
||||||
|
// 查找是否已存在相同字符串
|
||||||
|
for (uint32_t i = 0; i < stringPool_.size(); ++i) {
|
||||||
|
if (stringPool_[i] == str) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 分配新字符串
|
||||||
|
uint32_t index = static_cast<uint32_t>(stringPool_.size());
|
||||||
|
stringPool_.push_back(str);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t FramePropertySet::allocateVector(const std::vector<int>& vec) {
|
||||||
|
// 查找是否已存在相同vector
|
||||||
|
for (uint32_t i = 0; i < vectorPool_.size(); ++i) {
|
||||||
|
if (vectorPool_[i] == vec) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 分配新vector
|
||||||
|
uint32_t index = static_cast<uint32_t>(vectorPool_.size());
|
||||||
|
vectorPool_.push_back(vec);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string* FramePropertySet::getString(uint32_t index) const {
|
||||||
|
if (index < stringPool_.size()) {
|
||||||
|
return &stringPool_[index];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<int>* FramePropertySet::getVector(uint32_t index) const {
|
||||||
|
if (index < vectorPool_.size()) {
|
||||||
|
return &vectorPool_[index];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 模板特化实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
template <> std::optional<bool> FramePropertySet::get<bool>(FramePropertyKey key) const {
|
||||||
|
auto it = properties_.find(key);
|
||||||
|
if (it == properties_.end()) return std::nullopt;
|
||||||
|
if (it->second.type == PropertyValueType::Bool) {
|
||||||
|
return it->second.data.boolValue;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::optional<int> FramePropertySet::get<int>(FramePropertyKey key) const {
|
||||||
|
auto it = properties_.find(key);
|
||||||
|
if (it == properties_.end()) return std::nullopt;
|
||||||
|
if (it->second.type == PropertyValueType::Int) {
|
||||||
|
return it->second.data.intValue;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::optional<float> FramePropertySet::get<float>(FramePropertyKey key) const {
|
||||||
|
auto it = properties_.find(key);
|
||||||
|
if (it == properties_.end()) return std::nullopt;
|
||||||
|
if (it->second.type == PropertyValueType::Float) {
|
||||||
|
return it->second.data.floatValue;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::optional<Vec2> FramePropertySet::get<Vec2>(FramePropertyKey key) const {
|
||||||
|
auto it = properties_.find(key);
|
||||||
|
if (it == properties_.end()) return std::nullopt;
|
||||||
|
if (it->second.type == PropertyValueType::Vec2) {
|
||||||
|
return it->second.data.vec2Value;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::optional<Color> FramePropertySet::get<Color>(FramePropertyKey key) const {
|
||||||
|
auto it = properties_.find(key);
|
||||||
|
if (it == properties_.end()) return std::nullopt;
|
||||||
|
if (it->second.type == PropertyValueType::Color) {
|
||||||
|
return it->second.data.colorValue;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::optional<std::string> FramePropertySet::get<std::string>(FramePropertyKey key) const {
|
||||||
|
auto it = properties_.find(key);
|
||||||
|
if (it == properties_.end()) return std::nullopt;
|
||||||
|
if (it->second.type == PropertyValueType::String) {
|
||||||
|
const std::string* str = getString(it->second.data.stringIndex);
|
||||||
|
if (str) return *str;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> std::optional<std::vector<int>> FramePropertySet::get<std::vector<int>>(FramePropertyKey key) const {
|
||||||
|
auto it = properties_.find(key);
|
||||||
|
if (it == properties_.end()) return std::nullopt;
|
||||||
|
if (it->second.type == PropertyValueType::IntVector) {
|
||||||
|
const std::vector<int>* vec = getVector(it->second.data.vectorIndex);
|
||||||
|
if (vec) return *vec;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 链式 API 实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
FramePropertySet &FramePropertySet::withSetFlag(int index) {
|
||||||
|
set(FramePropertyKey::SetFlag, FramePropertyValue(index));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet &FramePropertySet::withPlaySound(const std::string &path) {
|
||||||
|
set(FramePropertyKey::PlaySound, path);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet &FramePropertySet::withImageRate(const Vec2 &scale) {
|
||||||
|
set(FramePropertyKey::ImageRate, FramePropertyValue(scale));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet &FramePropertySet::withImageRotate(float degrees) {
|
||||||
|
set(FramePropertyKey::ImageRotate, FramePropertyValue(degrees));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet &FramePropertySet::withColorTint(const Color &color) {
|
||||||
|
set(FramePropertyKey::ColorTint, FramePropertyValue(color));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet &FramePropertySet::withInterpolation(bool enabled) {
|
||||||
|
set(FramePropertyKey::Interpolation, FramePropertyValue(enabled));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet &FramePropertySet::withBlendLinearDodge(bool enabled) {
|
||||||
|
set(FramePropertyKey::BlendLinearDodge, FramePropertyValue(enabled));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet &FramePropertySet::withLoop(bool enabled) {
|
||||||
|
set(FramePropertyKey::Loop, FramePropertyValue(enabled));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue