Initial engine repository
This commit is contained in:
@@ -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,220 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/base/RefObject.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <frostbite2D/types/uuid.h>
|
||||
#include <frostbite2D/utils/intrusive_list.hpp>
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <frostbite2D/event/key_event.h>
|
||||
#include <frostbite2D/event/mouse_event.h>
|
||||
#include <frostbite2D/event/touch_event.h>
|
||||
#include <frostbite2D/event/joystick_event.h>
|
||||
#include <frostbite2D/event/window_event.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Actor;
|
||||
class Scene;
|
||||
|
||||
using ActorList = IntrusiveList<RefPtr<Actor>>;
|
||||
|
||||
class Actor : public RefObject, protected IntrusiveListValue<RefPtr<Actor>> {
|
||||
public:
|
||||
using IntrusiveListValue<RefPtr<Actor>>::GetNext;
|
||||
using IntrusiveListValue<RefPtr<Actor>>::GetPrev;
|
||||
|
||||
Actor();
|
||||
virtual ~Actor();
|
||||
|
||||
virtual void Update(float deltaTime);
|
||||
virtual void Render();
|
||||
virtual void OnUpdate(float deltaTime) {}
|
||||
virtual void OnAdded(Actor* parent) {}
|
||||
|
||||
void AddChild(RefPtr<Actor> child);
|
||||
void RemoveChild(RefPtr<Actor> child);
|
||||
void RemoveAllChildren();
|
||||
|
||||
Actor* GetParent() const { return parent_; }
|
||||
Scene* GetScene() const { return scene_; }
|
||||
|
||||
const std::string& GetName() const { return name_; }
|
||||
void SetName(const std::string& name) { name_ = name; }
|
||||
|
||||
const UUID& GetUUID() const { return uuid_; }
|
||||
std::string GetUUIDString() const { return uuid_.toString(); }
|
||||
|
||||
/**
|
||||
* @brief 获取锚点在父坐标系中的位置
|
||||
*/
|
||||
const Vec2& GetPosition() const { return position_; }
|
||||
/**
|
||||
* @brief 设置锚点在父坐标系中的位置
|
||||
*/
|
||||
void SetPosition(const Vec2& pos);
|
||||
void SetPosition(float x, float y);
|
||||
|
||||
float GetRotation() const { return rotation_; }
|
||||
void SetRotation(float rotation);
|
||||
|
||||
const Vec2& GetScale() const { return scale_; }
|
||||
void SetScale(const Vec2& scale);
|
||||
void SetScale(float scale);
|
||||
|
||||
const Vec2& GetSkew() const { return skew_; }
|
||||
void SetSkew(const Vec2& skew);
|
||||
void SetSkew(float skewX, float skewY);
|
||||
|
||||
const Transform2D& GetLocalTransform() const;
|
||||
const Transform2D& GetWorldTransform() const;
|
||||
|
||||
const Vec2& GetSize() const { return size_; }
|
||||
void SetSize(const Vec2& size);
|
||||
void SetSize(float width, float height);
|
||||
|
||||
/**
|
||||
* @brief 获取归一化锚点,(0, 0) 为左上角,(0.5, 0.5) 为中心
|
||||
*/
|
||||
const Vec2& GetAnchor() const { return anchor_; }
|
||||
/**
|
||||
* @brief 设置归一化锚点,不会自动补偿 position
|
||||
*/
|
||||
void SetAnchor(const Vec2& anchor);
|
||||
void SetAnchor(float x, float y);
|
||||
|
||||
/**
|
||||
* @brief 获取未旋转矩形左上角在父坐标系中的位置
|
||||
*/
|
||||
Vec2 GetTopLeftPosition() const;
|
||||
/**
|
||||
* @brief 设置未旋转矩形左上角在父坐标系中的位置
|
||||
*/
|
||||
void SetTopLeftPosition(const Vec2& pos);
|
||||
void SetTopLeftPosition(float x, float y);
|
||||
Vec2 LocalToWorld(const Vec2& point) const;
|
||||
Vec2 WorldToLocal(const Vec2& point) const;
|
||||
Rect GetWorldBounds() const;
|
||||
bool ContainsWorldPoint(const Vec2& point) const;
|
||||
bool ContainsWorldPoint(float x, float y) const;
|
||||
|
||||
bool IsVisible() const { return visible_; }
|
||||
void SetVisible(bool visible) { visible_ = visible; }
|
||||
|
||||
int GetZOrder() const { return zOrder_; }
|
||||
void SetZOrder(int zOrder);
|
||||
|
||||
float GetOpacity() const { return opacity_; }
|
||||
void SetOpacity(float opacity);
|
||||
float GetWorldOpacity() const;
|
||||
|
||||
ActorList& GetChildren() { return children_; }
|
||||
const ActorList& GetChildren() const { return children_; }
|
||||
|
||||
bool IsEventReceiveEnabled() const { return eventReceiveEnabled_; }
|
||||
void EnableEventReceive() { eventReceiveEnabled_ = true; }
|
||||
void DisableEventReceive() { eventReceiveEnabled_ = false; }
|
||||
|
||||
int32 GetEventPriority() const { return eventPriority_; }
|
||||
void SetEventPriority(int32 priority) { eventPriority_ = priority; }
|
||||
|
||||
bool IsEventInterceptEnabled() const { return eventInterceptEnabled_; }
|
||||
void EnableEventIntercept() { eventInterceptEnabled_ = true; }
|
||||
void DisableEventIntercept() { eventInterceptEnabled_ = false; }
|
||||
|
||||
enum class UpdatePhase {
|
||||
BeforeChildren,
|
||||
AfterChildren
|
||||
};
|
||||
|
||||
using UpdateCallback = Function<void(Actor&, float)>;
|
||||
using EventCallback = std::function<bool(const Event&)>;
|
||||
|
||||
uint32 AddUpdateListener(UpdateCallback callback,
|
||||
UpdatePhase phase = UpdatePhase::BeforeChildren);
|
||||
bool RemoveUpdateListener(uint32 listenerId);
|
||||
void ClearUpdateListeners();
|
||||
|
||||
uint32 AddEventListener(EventType type, EventCallback callback);
|
||||
bool RemoveEventListener(uint32 listenerId);
|
||||
void ClearEventListeners();
|
||||
|
||||
virtual bool OnEvent(const Event& event);
|
||||
|
||||
bool DispatchEvent(const Event& event);
|
||||
bool BubbleEvent(const Event& event);
|
||||
|
||||
protected:
|
||||
void SetParent(Actor* parent) { parent_ = parent; }
|
||||
void SetScene(Scene* scene) { scene_ = scene; }
|
||||
|
||||
void insertChildByZOrder(RefPtr<Actor> child);
|
||||
void UpdateChildren(float deltaTime);
|
||||
void RenderChildren();
|
||||
|
||||
virtual void OnTransformChanged() {};
|
||||
|
||||
private:
|
||||
void markChildrenOrderDirty();
|
||||
void reorderChildrenByZOrder();
|
||||
|
||||
Actor* parent_;
|
||||
Scene* scene_;
|
||||
ActorList children_;
|
||||
|
||||
UUID uuid_;
|
||||
std::string name_;
|
||||
Vec2 position_;
|
||||
float rotation_;
|
||||
Vec2 scale_ = Vec2(1.0f, 1.0f);
|
||||
Vec2 skew_;
|
||||
Vec2 size_;
|
||||
Vec2 anchor_;
|
||||
bool visible_;
|
||||
int zOrder_;
|
||||
float opacity_;
|
||||
|
||||
mutable Transform2D localTransform_;
|
||||
mutable Transform2D worldTransform_;
|
||||
mutable bool transformDirty_ = true;
|
||||
|
||||
bool eventReceiveEnabled_ = false;
|
||||
int32 eventPriority_ = 0;
|
||||
bool eventInterceptEnabled_ = false;
|
||||
|
||||
struct UpdateListener {
|
||||
uint32 id;
|
||||
UpdatePhase phase;
|
||||
UpdateCallback callback;
|
||||
bool active = true;
|
||||
};
|
||||
std::vector<UpdateListener> updateListeners_;
|
||||
uint32 nextUpdateListenerId_ = 1;
|
||||
bool iteratingUpdateListeners_ = false;
|
||||
bool updateListenersDirty_ = false;
|
||||
bool iteratingChildren_ = false;
|
||||
bool childrenOrderDirty_ = false;
|
||||
|
||||
struct EventListener {
|
||||
uint32 id;
|
||||
EventType type;
|
||||
EventCallback callback;
|
||||
};
|
||||
std::vector<EventListener> eventListeners_;
|
||||
uint32 nextListenerId_ = 1;
|
||||
|
||||
void markTransformDirty();
|
||||
void updateLocalTransform() const;
|
||||
void updateWorldTransform() const;
|
||||
void executeUpdateListeners(UpdatePhase phase, float deltaTime);
|
||||
void compactUpdateListeners();
|
||||
|
||||
friend class Scene;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/2d/sprite.h>
|
||||
#include <frostbite2D/graphics/camera.h>
|
||||
#include <frostbite2D/graphics/render_texture.h>
|
||||
#include <functional>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/// @brief 离屏画布节点。
|
||||
///
|
||||
/// 子节点通过 `AddCanvasChild()` 挂到内部画布树,只会在重绘到离屏纹理时参与渲染,
|
||||
/// 不会像普通场景子节点那样直接显示到屏幕。
|
||||
class CanvasActor : public Sprite {
|
||||
public:
|
||||
CanvasActor();
|
||||
~CanvasActor() override = default;
|
||||
|
||||
bool Init(int width, int height);
|
||||
bool SetCanvasSize(int width, int height);
|
||||
Vec2 GetCanvasSize() const {
|
||||
return Vec2(static_cast<float>(canvasWidth_), static_cast<float>(canvasHeight_));
|
||||
}
|
||||
|
||||
void SetClearColor(const Color& color);
|
||||
const Color& GetClearColor() const { return clearColor_; }
|
||||
|
||||
void SetDirty();
|
||||
bool IsDirty() const { return dirty_; }
|
||||
bool Redraw();
|
||||
void SetCustomDrawCallback(std::function<void()> callback);
|
||||
void ClearCustomDrawCallback();
|
||||
|
||||
void AddCanvasChild(RefPtr<Actor> child);
|
||||
void RemoveCanvasChild(RefPtr<Actor> child);
|
||||
void RemoveAllCanvasChildren();
|
||||
Actor* GetCanvasRoot() const { return canvasRoot_.Get(); }
|
||||
ActorList& GetCanvasChildren() { return canvasRoot_->GetChildren(); }
|
||||
const ActorList& GetCanvasChildren() const { return canvasRoot_->GetChildren(); }
|
||||
|
||||
Ptr<Texture> GetOutputTexture() const;
|
||||
Ptr<RenderTexture> GetRenderTexture() const { return renderTexture_; }
|
||||
bool IsCanvasReady() const { return renderTexture_ && renderTexture_->IsValid(); }
|
||||
Camera& GetCanvasCamera() { return canvasCamera_; }
|
||||
const Camera& GetCanvasCamera() const { return canvasCamera_; }
|
||||
|
||||
void OnUpdate(float deltaTime) override;
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
bool redrawInternal();
|
||||
void syncCanvasResources();
|
||||
|
||||
RefPtr<Actor> canvasRoot_;
|
||||
Ptr<RenderTexture> renderTexture_ = nullptr;
|
||||
Camera canvasCamera_;
|
||||
Color clearColor_ = Colors::Transparent;
|
||||
int canvasWidth_ = 0;
|
||||
int canvasHeight_ = 0;
|
||||
bool dirty_ = true;
|
||||
std::function<void()> customDrawCallback_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/2d/actor.h>
|
||||
#include <frostbite2D/graphics/texture.h>
|
||||
#include <frostbite2D/graphics/shader.h>
|
||||
#include <frostbite2D/graphics/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Sprite : public Actor {
|
||||
public:
|
||||
Sprite();
|
||||
explicit Sprite(Ptr<Texture> texture);
|
||||
virtual ~Sprite();
|
||||
|
||||
static Ptr<Sprite> createFromFile(const std::string& path);
|
||||
static Ptr<Sprite> createFromMemory(const uint8* data, int width, int height, int channels);
|
||||
static Ptr<Sprite> createFromNpk(const std::string& imgPath, size_t frameIndex = 0);
|
||||
|
||||
void Render() override;
|
||||
|
||||
void SetTexture(Ptr<Texture> texture);
|
||||
Ptr<Texture> GetTexture() const { return texture_; }
|
||||
|
||||
void SetShader(const std::string& shaderName);
|
||||
void SetDefaultShader();
|
||||
const std::string& GetShaderName() const { return shaderName_; }
|
||||
bool HasCustomShader() const { return !shaderName_.empty(); }
|
||||
|
||||
void SetSourceRect(const Rect& rect);
|
||||
const Rect& GetSourceRect() const { return srcRect_; }
|
||||
|
||||
void SetColor(const Color& color);
|
||||
void SetColor(float r, float g, float b, float a = 1.0f);
|
||||
const Color& GetColor() const { return color_; }
|
||||
|
||||
void SetFlippedX(bool flipped);
|
||||
void SetFlippedY(bool flipped);
|
||||
bool IsFlippedX() const { return flippedX_; }
|
||||
bool IsFlippedY() const { return flippedY_; }
|
||||
|
||||
void SetBlendMode(BlendMode mode);
|
||||
BlendMode GetBlendMode() const { return blendMode_; }
|
||||
|
||||
void SetSizeToTexture();
|
||||
|
||||
const Vec2& GetOffset() const { return offset_; }
|
||||
void SetOffset(const Vec2& offset);
|
||||
void SetOffset(float x, float y);
|
||||
|
||||
protected:
|
||||
virtual void ConfigureShader(Shader* shader) const;
|
||||
void updateTransform();
|
||||
Shader* getActiveShader() const;
|
||||
Quad createQuad() const;
|
||||
|
||||
private:
|
||||
Ptr<Texture> texture_;
|
||||
std::string shaderName_;
|
||||
Color color_ = Color(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
Rect srcRect_;
|
||||
bool flippedX_ = false;
|
||||
bool flippedY_ = false;
|
||||
BlendMode blendMode_ = BlendMode::Normal;
|
||||
Vec2 offset_ = Vec2::Zero();
|
||||
Transform2D transform_;
|
||||
|
||||
static constexpr const char* DEFAULT_SHADER = "sprite";
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/2d/sprite.h>
|
||||
#include <frostbite2D/types/type_color.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class TextSprite : public Sprite {
|
||||
public:
|
||||
enum class Align {
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
};
|
||||
|
||||
TextSprite();
|
||||
~TextSprite();
|
||||
|
||||
static Ptr<TextSprite> create();
|
||||
static Ptr<TextSprite> create(const std::string& text, const std::string& fontName);
|
||||
|
||||
void SetText(const std::string& text);
|
||||
const std::string& GetText() const { return text_; }
|
||||
|
||||
void SetFont(const std::string& fontName);
|
||||
const std::string& GetFontName() const { return fontName_; }
|
||||
|
||||
void SetTextColor(const Color& color);
|
||||
void SetTextColor(float r, float g, float b, float a = 1.0f);
|
||||
const Color& GetTextColor() const { return textColor_; }
|
||||
|
||||
void SetAlign(Align align);
|
||||
Align GetAlign() const { return align_; }
|
||||
|
||||
void RenderText();
|
||||
Vec2 GetTextSize() const;
|
||||
|
||||
private:
|
||||
void updateTexture();
|
||||
|
||||
std::string text_;
|
||||
std::string fontName_;
|
||||
Color textColor_ = Colors::White;
|
||||
Align align_ = Align::Left;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/2d/actor.h>
|
||||
#include <frostbite2D/2d/sprite.h>
|
||||
#include <frostbite2D/animation/animation_data.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Sprite;
|
||||
|
||||
class Animation : public Actor {
|
||||
public:
|
||||
struct ReplaceData {
|
||||
int param1 = 0;
|
||||
int param2 = 0;
|
||||
ReplaceData() = default;
|
||||
ReplaceData(int p1, int p2) : param1(p1), param2(p2) {}
|
||||
};
|
||||
|
||||
public:
|
||||
Animation();
|
||||
explicit Animation(const std::string& aniPath);
|
||||
Animation(const std::string& aniPath,
|
||||
std::function<std::string(std::string, ReplaceData)> additionalOptions,
|
||||
ReplaceData data);
|
||||
~Animation();
|
||||
|
||||
void Init(const std::string& aniPath);
|
||||
|
||||
void OnUpdate(float deltaTime) override;
|
||||
void OnAdded(Actor* parent) override;
|
||||
void SetVisible(bool visible);
|
||||
|
||||
public:
|
||||
void FlushFrame(int index);
|
||||
void Reset();
|
||||
animation::AniFrame GetCurrentFrameInfo() const;
|
||||
void SetFrameIndex(int index);
|
||||
void SetDirection(int direction);
|
||||
void InterpolationLogic();
|
||||
|
||||
Vec2 GetMaxSize() const;
|
||||
bool GetStaticLocalBounds(Rect& outBounds) const;
|
||||
bool GetStaticLocalBounds(int direction, Rect& outBounds) const;
|
||||
uint64 GetRenderSignature() const;
|
||||
|
||||
bool IsUsable() const { return usable_; }
|
||||
void SetUsable(bool usable) { usable_ = usable; }
|
||||
|
||||
int GetCurrentFrameIndex() const { return currentFrameIndex_; }
|
||||
int GetTotalFrameCount() const { return totalFrameCount_; }
|
||||
|
||||
public:
|
||||
bool usable_ = true;
|
||||
int currentFrameIndex_ = 0;
|
||||
int totalFrameCount_ = 0;
|
||||
float currentFrameTime_ = 0.0f;
|
||||
Ptr<Sprite> currentFrame_ = nullptr;
|
||||
float nextFrameDelay_ = 9999999.0f;
|
||||
|
||||
bool isLooping_ = true;
|
||||
|
||||
std::function<void(int)> changeFrameCallback_;
|
||||
std::function<void()> endCallback_;
|
||||
|
||||
std::vector<animation::AniFrame> frames_;
|
||||
std::vector<Ptr<Sprite>> spriteFrames_;
|
||||
std::vector<Vec2> spriteFrameOffsets_;
|
||||
|
||||
std::unordered_map<std::string, animation::AniFlag> animationFlag_;
|
||||
|
||||
std::string type_ = "normal";
|
||||
std::string aniPath_;
|
||||
|
||||
std::function<std::string(std::string, ReplaceData)> additionalOptions_;
|
||||
ReplaceData additionalOptionsData_;
|
||||
|
||||
Vec2 maxSize_ = Vec2::Zero();
|
||||
int direction_ = 1;
|
||||
|
||||
std::vector<animation::AniFrame> interpolationData_;
|
||||
|
||||
private:
|
||||
void ApplyFramePresentation(const Vec2& framePos,
|
||||
const Vec2& imageRate,
|
||||
float rotation,
|
||||
BlendMode blendMode);
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class BinaryReader;
|
||||
|
||||
namespace animation {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 类型定义
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
using AniFlag = std::variant<
|
||||
int,
|
||||
float,
|
||||
Vec2,
|
||||
std::string,
|
||||
std::vector<int>,
|
||||
std::vector<float>>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 数据结构
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct AniFrame {
|
||||
std::string imgPath; // 图片路径
|
||||
int imgIndex = 0; // 图片索引
|
||||
Vec2 imgPos; // 图片位置
|
||||
std::vector<std::vector<int>> attackBox; // 攻击框 [x, y, w, h, type, param]
|
||||
std::vector<std::vector<int>> damageBox; // 受击框 [x, y, w, h, type, param]
|
||||
std::unordered_map<std::string, AniFlag> flag; // 帧特效数据
|
||||
int delay = 0; // 延迟(毫秒)
|
||||
};
|
||||
|
||||
struct AniInfo {
|
||||
std::vector<std::string> imgList; // 图片列表
|
||||
std::vector<AniFrame> frames; // 帧列表
|
||||
std::unordered_map<std::string, AniFlag> flag; // 动画特效数据
|
||||
};
|
||||
|
||||
struct AlsAniInfo {
|
||||
std::string path; // 路径
|
||||
std::vector<int> layer; // 图层 [zOrder, subLayer]
|
||||
};
|
||||
|
||||
struct AlsInfo {
|
||||
std::unordered_map<std::string, AlsAniInfo> aniList; // ALS 动画列表
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::string getAniFlag(int type);
|
||||
std::string getAniEffectType(int type);
|
||||
std::string getAniFlipType(int type);
|
||||
std::string getAniDamageType(int type);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 解析函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 解析 .ani 二进制数据
|
||||
* @param data 二进制数据
|
||||
* @param size 数据大小
|
||||
* @return 解析后的动画信息
|
||||
*/
|
||||
AniInfo parseAniInfo(const char* data, size_t size);
|
||||
|
||||
/**
|
||||
* @brief 从 PVF 路径解析 .ani 文件
|
||||
* @param path PVF 中的文件路径(如 "character/player/attack.ani")
|
||||
* @return 解析后的动画信息,解析失败返回 std::nullopt
|
||||
*/
|
||||
std::optional<AniInfo> parseAniFromPvf(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 解析 .als 文本数据
|
||||
* @param data 文本数据
|
||||
* @return 解析后的 ALS 信息
|
||||
*/
|
||||
AlsInfo parseAlsInfo(const std::string& data);
|
||||
|
||||
/**
|
||||
* @brief 从 PVF 路径解析 .als 文件
|
||||
* @param path PVF 中的文件路径
|
||||
* @return 解析后的 ALS 信息,解析失败返回 std::nullopt
|
||||
*/
|
||||
std::optional<AlsInfo> parseAlsFromPvf(const std::string& path);
|
||||
|
||||
} // namespace animation
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 音频系统配置
|
||||
*/
|
||||
struct AudioConfig {
|
||||
int frequency = 44100;
|
||||
uint16_t format = 0x8010;
|
||||
int channels = 2;
|
||||
int chunksize = 4096;
|
||||
int maxSoundChannels = 32;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 音频系统(单例)
|
||||
*
|
||||
* 负责初始化和管理整个音频系统,包括音量控制和声道管理。
|
||||
*/
|
||||
class AudioSystem {
|
||||
public:
|
||||
static AudioSystem& get();
|
||||
|
||||
AudioSystem(const AudioSystem&) = delete;
|
||||
AudioSystem& operator=(const AudioSystem&) = delete;
|
||||
|
||||
bool init(const AudioConfig& config = AudioConfig());
|
||||
void shutdown();
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
void setMasterVolume(float volume);
|
||||
float getMasterVolume() const;
|
||||
|
||||
void setSoundVolume(float volume);
|
||||
float getSoundVolume() const;
|
||||
|
||||
void setMusicVolume(float volume);
|
||||
float getMusicVolume() const;
|
||||
|
||||
void pauseAllSounds();
|
||||
void resumeAllSounds();
|
||||
void stopAllSounds();
|
||||
|
||||
int getMaxChannels() const { return maxChannels_; }
|
||||
|
||||
private:
|
||||
AudioSystem() = default;
|
||||
// ~AudioSystem() 在 shutdown() 中手动调用销毁
|
||||
|
||||
void updateVolumes();
|
||||
|
||||
bool initialized_ = false;
|
||||
float masterVolume_ = 1.0f;
|
||||
float soundVolume_ = 1.0f;
|
||||
float musicVolume_ = 1.0f;
|
||||
int maxChannels_ = 32;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/base/RefObject.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <string>
|
||||
#include <SDL2/SDL_mixer.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 音乐类
|
||||
*
|
||||
* 用于加载和播放长音频文件(背景音乐),支持从文件或内存加载。
|
||||
* 支持 WAV、OGG、MP3 等格式。
|
||||
*/
|
||||
class Music : public RefObject {
|
||||
public:
|
||||
static Ptr<Music> loadFromPath(const std::string& path);
|
||||
static Ptr<Music> loadFromFile(const std::string& path);
|
||||
static Ptr<Music> loadFromMemory(const uint8* data, size_t size);
|
||||
static Ptr<Music> loadFromNpk(const std::string& audioPath);
|
||||
static bool isPathPlaying(const std::string& path);
|
||||
static std::string getCurrentPlayingPath();
|
||||
|
||||
~Music();
|
||||
|
||||
bool play(int loops = -1);
|
||||
bool fadeIn(int ms, int loops = -1);
|
||||
void pause();
|
||||
void resume();
|
||||
void stop();
|
||||
bool fadeOut(int ms);
|
||||
|
||||
void setVolume(float volume);
|
||||
float getVolume() const;
|
||||
|
||||
bool isPlaying() const;
|
||||
bool isPaused() const;
|
||||
|
||||
const std::string& getPath() const { return path_; }
|
||||
|
||||
private:
|
||||
Music(Mix_Music* music, const std::string& path = "");
|
||||
Music(Mix_Music* music, std::vector<uint8> data, const std::string& path = "");
|
||||
|
||||
Mix_Music* music_ = nullptr;
|
||||
std::string path_;
|
||||
std::vector<uint8> data_;
|
||||
float volume_ = 1.0f;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/base/RefObject.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <string>
|
||||
#include <SDL2/SDL_mixer.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 音效类
|
||||
*
|
||||
* 用于加载和播放短音频文件,支持从文件或内存加载。
|
||||
* 支持 WAV、OGG 等格式。
|
||||
*/
|
||||
class Sound : public RefObject {
|
||||
public:
|
||||
static Ptr<Sound> loadFromPath(const std::string& path);
|
||||
static Ptr<Sound> loadFromFile(const std::string& path);
|
||||
static Ptr<Sound> loadFromMemory(const uint8* data, size_t size);
|
||||
static Ptr<Sound> loadFromNpk(const std::string& audioPath);
|
||||
|
||||
~Sound();
|
||||
|
||||
int play(int loops = 0, int channel = -1);
|
||||
int playLoop(int channel = -1);
|
||||
void setVolume(float volume);
|
||||
float getVolume() const;
|
||||
int getLastChannel() const;
|
||||
|
||||
static void stop(int channel);
|
||||
static void stopAll();
|
||||
static bool isPlaying(int channel);
|
||||
static void pause(int channel);
|
||||
static void resume(int channel);
|
||||
static void pauseAll();
|
||||
static void resumeAll();
|
||||
|
||||
const std::string& getPath() const { return path_; }
|
||||
|
||||
private:
|
||||
Sound(Mix_Chunk* chunk, const std::string& path = "");
|
||||
|
||||
Mix_Chunk* chunk_ = nullptr;
|
||||
std::string path_;
|
||||
float volume_ = 1.0f;
|
||||
int lastChannel_ = -1;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Noncopyable {
|
||||
protected:
|
||||
Noncopyable() = default;
|
||||
|
||||
private:
|
||||
Noncopyable(const Noncopyable&) = delete;
|
||||
Noncopyable& operator=(const Noncopyable&) = delete;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <frostbite2D/base/Noncopyable.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class RefObject : protected Noncopyable {
|
||||
public:
|
||||
void Retain();
|
||||
void Release();
|
||||
uint32_t GetRefCount() const;
|
||||
|
||||
virtual ~RefObject();
|
||||
|
||||
protected:
|
||||
RefObject();
|
||||
|
||||
private:
|
||||
std::atomic<uint32_t> ref_count_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/base/RefObject.h>
|
||||
#include <frostbite2D/core/RefBasePtr.hpp>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
struct DefaultRefPtrPolicy {
|
||||
void Retain(RefObject* ptr) {
|
||||
if (ptr) {
|
||||
ptr->Retain();
|
||||
}
|
||||
}
|
||||
|
||||
void Release(RefObject* ptr) {
|
||||
if (ptr) {
|
||||
ptr->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using RefPtr = RefBasePtr<T, DefaultRefPtrPolicy>;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
RefPtr<T> MakePtr(Args&&... args) {
|
||||
static_assert(std::is_base_of<RefObject, T>::value,
|
||||
"T must be derived from RefObject");
|
||||
return RefPtr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
RefPtr<T> MakePtr(T* ptr) {
|
||||
static_assert(std::is_base_of<RefObject, T>::value,
|
||||
"T must be derived from RefObject");
|
||||
return RefPtr<T>(ptr);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/base/RefObject.h>
|
||||
#include <frostbite2D/base/RefPtr.h>
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Actor;
|
||||
class UUID;
|
||||
|
||||
class ObjectRegistry {
|
||||
public:
|
||||
static ObjectRegistry& get();
|
||||
|
||||
ObjectRegistry(const ObjectRegistry&) = delete;
|
||||
ObjectRegistry& operator=(const ObjectRegistry&) = delete;
|
||||
|
||||
void registerObject(const UUID& uuid, Actor* actor);
|
||||
void unregisterObject(const UUID& uuid);
|
||||
|
||||
Actor* getObject(const UUID& uuid);
|
||||
Actor* getObject(const std::string& uuidStr);
|
||||
|
||||
bool hasObject(const UUID& uuid) const;
|
||||
bool hasObject(const std::string& uuidStr) const;
|
||||
size_t count() const;
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
ObjectRegistry() = default;
|
||||
~ObjectRegistry() = default;
|
||||
|
||||
std::map<std::string, Actor*> registry_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,178 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
template <typename T, typename RefPolicy>
|
||||
class RefBasePtr : protected RefPolicy {
|
||||
public:
|
||||
using value_type = T;
|
||||
using pointer_type = T*;
|
||||
using const_pointer_type = const T*;
|
||||
using reference_type = T&;
|
||||
using const_reference_type = const T&;
|
||||
|
||||
RefBasePtr() noexcept : ptr_(nullptr) {}
|
||||
RefBasePtr(std::nullptr_t) noexcept : ptr_(nullptr) {}
|
||||
|
||||
RefBasePtr(pointer_type p) : ptr_(p) {
|
||||
RefPolicy::Retain(ptr_);
|
||||
}
|
||||
|
||||
RefBasePtr(const RefBasePtr& other) : ptr_(other.ptr_) {
|
||||
RefPolicy::Retain(ptr_);
|
||||
}
|
||||
|
||||
RefBasePtr(RefBasePtr&& other) noexcept : ptr_(nullptr) {
|
||||
Swap(other);
|
||||
}
|
||||
|
||||
~RefBasePtr() {
|
||||
Tidy();
|
||||
}
|
||||
|
||||
template <typename U,
|
||||
typename std::enable_if<std::is_convertible<U*, T*>::value, int>::type = 0>
|
||||
RefBasePtr(const RefBasePtr<U, RefPolicy>& other) {
|
||||
ptr_ = dynamic_cast<pointer_type>(other.Get());
|
||||
RefPolicy::Retain(ptr_);
|
||||
}
|
||||
|
||||
pointer_type Get() const noexcept { return ptr_; }
|
||||
|
||||
pointer_type* GetAddressOfAndRelease() {
|
||||
Tidy();
|
||||
return &ptr_;
|
||||
}
|
||||
|
||||
void Reset(pointer_type ptr = nullptr) {
|
||||
if (ptr) {
|
||||
RefBasePtr(ptr).Swap(*this);
|
||||
} else {
|
||||
Tidy();
|
||||
}
|
||||
}
|
||||
|
||||
void Swap(RefBasePtr& other) noexcept {
|
||||
std::swap(ptr_, other.ptr_);
|
||||
}
|
||||
|
||||
pointer_type operator->() { return ptr_; }
|
||||
const_pointer_type operator->() const { return ptr_; }
|
||||
reference_type operator*() { return *ptr_; }
|
||||
const_reference_type operator*() const { return *ptr_; }
|
||||
pointer_type* operator&() { return this->GetAddressOfAndRelease(); }
|
||||
|
||||
operator bool() const noexcept { return ptr_ != nullptr; }
|
||||
bool operator!() const noexcept { return ptr_ == nullptr; }
|
||||
|
||||
RefBasePtr& operator=(const RefBasePtr& other) {
|
||||
if (other.ptr_ != ptr_) {
|
||||
RefBasePtr(other).Swap(*this);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
RefBasePtr& operator=(RefBasePtr&& other) noexcept {
|
||||
if (other.ptr_ != ptr_) {
|
||||
other.Swap(*this);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
RefBasePtr& operator=(pointer_type p) {
|
||||
if (p != ptr_) {
|
||||
RefBasePtr(p).Swap(*this);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U,
|
||||
typename std::enable_if<std::is_convertible<U*, T*>::value, int>::type = 0>
|
||||
RefBasePtr& operator=(const RefBasePtr<U, RefPolicy>& other) {
|
||||
if (other.Get() != ptr_) {
|
||||
RefBasePtr(dynamic_cast<pointer_type>(other.Get())).Swap(*this);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
RefBasePtr& operator=(std::nullptr_t) {
|
||||
Tidy();
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
void Tidy() {
|
||||
RefPolicy::Release(ptr_);
|
||||
ptr_ = nullptr;
|
||||
}
|
||||
|
||||
pointer_type ptr_;
|
||||
};
|
||||
|
||||
template <class T, class U, class RefPolicy>
|
||||
bool operator==(const RefBasePtr<T, RefPolicy>& lhs,
|
||||
const RefBasePtr<U, RefPolicy>& rhs) noexcept {
|
||||
return lhs.Get() == rhs.Get();
|
||||
}
|
||||
|
||||
template <class T, class RefPolicy>
|
||||
bool operator==(const RefBasePtr<T, RefPolicy>& lhs, T* rhs) noexcept {
|
||||
return lhs.Get() == rhs;
|
||||
}
|
||||
|
||||
template <class T, class RefPolicy>
|
||||
bool operator==(T* lhs, const RefBasePtr<T, RefPolicy>& rhs) noexcept {
|
||||
return lhs == rhs.Get();
|
||||
}
|
||||
|
||||
template <class T, class RefPolicy>
|
||||
bool operator==(const RefBasePtr<T, RefPolicy>& lhs, std::nullptr_t) noexcept {
|
||||
return !static_cast<bool>(lhs);
|
||||
}
|
||||
|
||||
template <class T, class RefPolicy>
|
||||
bool operator==(std::nullptr_t, const RefBasePtr<T, RefPolicy>& rhs) noexcept {
|
||||
return !static_cast<bool>(rhs);
|
||||
}
|
||||
|
||||
template <class T, class U, class RefPolicy>
|
||||
bool operator!=(const RefBasePtr<T, RefPolicy>& lhs,
|
||||
const RefBasePtr<U, RefPolicy>& rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <class T, class RefPolicy>
|
||||
bool operator!=(const RefBasePtr<T, RefPolicy>& lhs, T* rhs) noexcept {
|
||||
return lhs.Get() != rhs;
|
||||
}
|
||||
|
||||
template <class T, class RefPolicy>
|
||||
bool operator!=(T* lhs, const RefBasePtr<T, RefPolicy>& rhs) noexcept {
|
||||
return lhs != rhs.Get();
|
||||
}
|
||||
|
||||
template <class T, class RefPolicy>
|
||||
bool operator!=(const RefBasePtr<T, RefPolicy>& lhs, std::nullptr_t) noexcept {
|
||||
return static_cast<bool>(lhs);
|
||||
}
|
||||
|
||||
template <class T, class RefPolicy>
|
||||
bool operator!=(std::nullptr_t, const RefBasePtr<T, RefPolicy>& rhs) noexcept {
|
||||
return static_cast<bool>(rhs);
|
||||
}
|
||||
|
||||
template <class T, class U, class RefPolicy>
|
||||
bool operator<(const RefBasePtr<T, RefPolicy>& lhs,
|
||||
const RefBasePtr<U, RefPolicy>& rhs) noexcept {
|
||||
return lhs.Get() < rhs.Get();
|
||||
}
|
||||
|
||||
template <class T, class RefPolicy>
|
||||
void swap(RefBasePtr<T, RefPolicy>& lhs, RefBasePtr<T, RefPolicy>& rhs) noexcept {
|
||||
lhs.Swap(rhs);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/core/window.h>
|
||||
#include <frostbite2D/graphics/camera.h>
|
||||
#include <frostbite2D/graphics/render_style.h>
|
||||
#include <frostbite2D/graphics/render_resolution.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 应用配置结构体
|
||||
* 仅包含应用级别的配置项,模块配置由各模块自行管理
|
||||
*/
|
||||
struct AppConfig {
|
||||
/**
|
||||
* @brief 应用名称
|
||||
*/
|
||||
std::string appName = "frostbite2D App";
|
||||
/**
|
||||
* @brief 应用版本号
|
||||
*/
|
||||
std::string appVersion = "1.0.0";
|
||||
/**
|
||||
* @brief 组织名称
|
||||
*/
|
||||
std::string organization = "frostbite";
|
||||
|
||||
/**
|
||||
* @brief 窗口配置
|
||||
*/
|
||||
WindowConfig windowConfig;
|
||||
|
||||
/**
|
||||
* @brief 目标平台
|
||||
*/
|
||||
PlatformType targetPlatform = PlatformType::Auto;
|
||||
|
||||
/**
|
||||
* @brief 是否启用虚拟分辨率
|
||||
*/
|
||||
bool useVirtualResolution = false;
|
||||
|
||||
/**
|
||||
* @brief 虚拟分辨率宽度
|
||||
*/
|
||||
int virtualWidth = 0;
|
||||
|
||||
/**
|
||||
* @brief 虚拟分辨率高度
|
||||
*/
|
||||
int virtualHeight = 0;
|
||||
|
||||
/**
|
||||
* @brief 虚拟分辨率缩放模式
|
||||
*/
|
||||
ResolutionScaleMode resolutionMode = ResolutionScaleMode::Fit;
|
||||
bool useSeparateUIVirtualResolution = false;
|
||||
int uiVirtualWidth = 0;
|
||||
int uiVirtualHeight = 0;
|
||||
ResolutionScaleMode uiResolutionMode = ResolutionScaleMode::Fit;
|
||||
|
||||
/**
|
||||
* @brief 默认 2D 渲染风格预设
|
||||
* 启动时作为世界/UI 渲染的基础风格,Scene 可按需覆盖
|
||||
*/
|
||||
RenderStyleProfileId renderStyleProfile = RenderStyleProfileId::Hybrid2D;
|
||||
/**
|
||||
* @brief 最大帧率,0 表示不限制
|
||||
*/
|
||||
int maxFps = 0;
|
||||
|
||||
/**
|
||||
* @brief 创建默认配置
|
||||
* @return 默认的应用配置实例
|
||||
*/
|
||||
static AppConfig createDefault() {
|
||||
AppConfig config;
|
||||
config.appName = "Frostbite2D App";
|
||||
config.appVersion = "1.0.0";
|
||||
config.organization = "frostbite";
|
||||
config.targetPlatform = PlatformType::Auto;
|
||||
config.windowConfig = WindowConfig();
|
||||
config.renderStyleProfile = RenderStyleProfileId::Hybrid2D;
|
||||
config.useSeparateUIVirtualResolution = false;
|
||||
config.uiVirtualWidth = 0;
|
||||
config.uiVirtualHeight = 0;
|
||||
config.uiResolutionMode = ResolutionScaleMode::Fit;
|
||||
config.maxFps = 0;
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 应用程序类
|
||||
*/
|
||||
class Application {
|
||||
public:
|
||||
using StartCallback = Function<void()>;
|
||||
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 应用程序实例引用
|
||||
*/
|
||||
static Application& get();
|
||||
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 使用默认配置初始化
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 使用指定配置初始化
|
||||
* @param config 应用配置
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init(const AppConfig& config);
|
||||
|
||||
/**
|
||||
* @brief 关闭应用程序
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 运行主循环
|
||||
* @param callback 主循环开始前执行一次的启动回调
|
||||
*/
|
||||
void run(StartCallback callback);
|
||||
|
||||
/**
|
||||
* @brief 请求退出
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief 暂停应用程序
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* @brief 恢复应用程序
|
||||
*/
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* @brief 检查是否暂停
|
||||
* @return 暂停状态返回 true
|
||||
*/
|
||||
bool isPaused() const { return paused_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否运行中
|
||||
* @return 运行中返回 true
|
||||
*/
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
/**
|
||||
* @brief 获取总运行时间
|
||||
* @return 总运行时间(秒)
|
||||
*/
|
||||
float totalTime() const { return totalTime_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前帧率
|
||||
* @return 帧率
|
||||
*/
|
||||
int fps() const { return currentFps_; }
|
||||
|
||||
/**
|
||||
* @brief 设置最大帧率
|
||||
* @param maxFps 0 表示不限制,>0 表示帧率上限
|
||||
*/
|
||||
void setMaxFps(int maxFps);
|
||||
|
||||
/**
|
||||
* @brief 获取最大帧率设置
|
||||
* @return 0 表示不限制,>0 表示帧率上限
|
||||
*/
|
||||
int maxFps() const { return maxFps_; }
|
||||
|
||||
void setTextInputEnabled(bool enabled);
|
||||
bool isTextInputEnabled() const { return textInputEnabled_; }
|
||||
void requestTextInput();
|
||||
void releaseTextInput();
|
||||
void setImmEnabled(bool enabled);
|
||||
bool isImmEnabled() const;
|
||||
|
||||
/**
|
||||
* @brief 获取应用配置
|
||||
* @return 应用配置常量引用
|
||||
*/
|
||||
const AppConfig& getConfig() const;
|
||||
|
||||
/**
|
||||
* @brief 获取窗口
|
||||
* @return 窗口指针
|
||||
*/
|
||||
Window* getWindow() const { return window_; }
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器
|
||||
* @return 渲染器指针
|
||||
*/
|
||||
class Renderer* getRenderer() const { return renderer_; }
|
||||
|
||||
/**
|
||||
* @brief 获取 delta time
|
||||
* @return 帧间隔时间(秒)
|
||||
*/
|
||||
float deltaTime() const { return deltaTime_; }
|
||||
|
||||
private:
|
||||
Application() = default;
|
||||
~Application();
|
||||
|
||||
/**
|
||||
* @brief 初始化核心模块
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool initCoreModules();
|
||||
|
||||
/**
|
||||
* @brief 主循环
|
||||
*/
|
||||
void mainLoop();
|
||||
|
||||
/**
|
||||
* @brief 更新
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief 渲染
|
||||
*/
|
||||
void render();
|
||||
|
||||
std::unique_ptr<Event> convertSDLEvent(const SDL_Event& sdlEvent);
|
||||
void dispatchEvent(const Event& event);
|
||||
|
||||
Window* window_ = nullptr;
|
||||
class Renderer* renderer_ = nullptr;
|
||||
Camera* camera_ = nullptr;
|
||||
AppConfig config_;
|
||||
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
bool shouldQuit_ = false;
|
||||
bool textInputEnabled_ = false;
|
||||
bool immEnabled_ = false;
|
||||
int textInputRequestCount_ = 0;
|
||||
|
||||
float deltaTime_ = 0.0f;
|
||||
float totalTime_ = 0.0f;
|
||||
double lastFrameTime_ = 0.0;
|
||||
int frameCount_ = 0;
|
||||
float fpsTimer_ = 0.0f;
|
||||
int currentFps_ = 0;
|
||||
int maxFps_ = 0;
|
||||
double targetFrameDuration_ = 0.0;
|
||||
const float fpsUpdateInterval_ = 1.0f;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
struct TaskSystemConfig {
|
||||
uint32 workerCount = 0;
|
||||
uint32 maxMainThreadCallbacksPerFrame = 0;
|
||||
uint32 threadStackSize = 0;
|
||||
};
|
||||
|
||||
class TaskSystem {
|
||||
public:
|
||||
static TaskSystem& get();
|
||||
|
||||
TaskSystem(const TaskSystem&) = delete;
|
||||
TaskSystem& operator=(const TaskSystem&) = delete;
|
||||
|
||||
bool init(const TaskSystemConfig& config = {});
|
||||
void shutdown();
|
||||
|
||||
bool isInitialized() const { return initialized_; }
|
||||
bool isMainThread() const;
|
||||
size_t workerCount() const;
|
||||
uint32 maxMainThreadCallbacksPerFrame() const;
|
||||
|
||||
template <typename F>
|
||||
void post(F&& task) {
|
||||
enqueueWorkerTask(Function<void()>(std::forward<F>(task)));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
auto submit(F&& task) -> std::future<std::invoke_result_t<F>> {
|
||||
using Result = std::invoke_result_t<F>;
|
||||
|
||||
auto packagedTask =
|
||||
std::make_shared<std::packaged_task<Result()>>(std::forward<F>(task));
|
||||
std::future<Result> future = packagedTask->get_future();
|
||||
|
||||
enqueueWorkerTask([packagedTask]() mutable { (*packagedTask)(); });
|
||||
return future;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void postToMainThread(F&& callback) {
|
||||
enqueueMainThreadTask(Function<void()>(std::forward<F>(callback)));
|
||||
}
|
||||
|
||||
template <typename Task, typename OnComplete>
|
||||
void submitThen(Task&& task, OnComplete&& onComplete) {
|
||||
using Result = std::invoke_result_t<Task>;
|
||||
using Storage = std::decay_t<Result>;
|
||||
|
||||
auto taskFn = std::make_shared<std::decay_t<Task>>(std::forward<Task>(task));
|
||||
auto completeFn =
|
||||
std::make_shared<std::decay_t<OnComplete>>(std::forward<OnComplete>(onComplete));
|
||||
|
||||
enqueueWorkerTask([this, taskFn, completeFn]() mutable {
|
||||
try {
|
||||
if constexpr (std::is_void_v<Result>) {
|
||||
std::invoke(*taskFn);
|
||||
enqueueMainThreadTask([completeFn]() mutable { std::invoke(*completeFn); });
|
||||
} else {
|
||||
auto result = std::make_shared<Storage>(std::invoke(*taskFn));
|
||||
enqueueMainThreadTask([completeFn, result]() mutable {
|
||||
std::invoke(*completeFn, std::move(*result));
|
||||
});
|
||||
}
|
||||
} catch (...) {
|
||||
logUnhandledTaskException(std::current_exception());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Task, typename OnComplete, typename OnError>
|
||||
void submitThen(Task&& task, OnComplete&& onComplete, OnError&& onError) {
|
||||
using Result = std::invoke_result_t<Task>;
|
||||
using Storage = std::decay_t<Result>;
|
||||
|
||||
auto taskFn = std::make_shared<std::decay_t<Task>>(std::forward<Task>(task));
|
||||
auto completeFn =
|
||||
std::make_shared<std::decay_t<OnComplete>>(std::forward<OnComplete>(onComplete));
|
||||
auto errorFn =
|
||||
std::make_shared<std::decay_t<OnError>>(std::forward<OnError>(onError));
|
||||
|
||||
enqueueWorkerTask([this, taskFn, completeFn, errorFn]() mutable {
|
||||
try {
|
||||
if constexpr (std::is_void_v<Result>) {
|
||||
std::invoke(*taskFn);
|
||||
enqueueMainThreadTask([completeFn]() mutable { std::invoke(*completeFn); });
|
||||
} else {
|
||||
auto result = std::make_shared<Storage>(std::invoke(*taskFn));
|
||||
enqueueMainThreadTask([completeFn, result]() mutable {
|
||||
std::invoke(*completeFn, std::move(*result));
|
||||
});
|
||||
}
|
||||
} catch (...) {
|
||||
auto error = std::make_shared<std::exception_ptr>(std::current_exception());
|
||||
enqueueMainThreadTask([errorFn, error]() mutable { std::invoke(*errorFn, *error); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void drainMainThreadTasks(uint32 maxTasks = 0);
|
||||
|
||||
private:
|
||||
TaskSystem() = default;
|
||||
~TaskSystem();
|
||||
|
||||
void enqueueWorkerTask(Function<void()> task);
|
||||
void enqueueMainThreadTask(Function<void()> task);
|
||||
size_t resolveWorkerCount(uint32 requestedCount) const;
|
||||
static void logUnhandledTaskException(std::exception_ptr error);
|
||||
|
||||
TaskSystemConfig config_;
|
||||
std::vector<std::thread> workers_;
|
||||
std::queue<Function<void()>> workerTasks_;
|
||||
std::queue<Function<void()>> mainThreadTasks_;
|
||||
|
||||
mutable std::mutex workerMutex_;
|
||||
mutable std::mutex mainThreadMutex_;
|
||||
std::condition_variable workerCondition_;
|
||||
|
||||
std::atomic<bool> initialized_ = false;
|
||||
std::atomic<bool> acceptingTasks_ = false;
|
||||
std::thread::id mainThreadId_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
enum class CursorType {
|
||||
Arrow,
|
||||
TextInput,
|
||||
Hand,
|
||||
SizeAll,
|
||||
SizeWE,
|
||||
SizeNS,
|
||||
SizeNESW,
|
||||
SizeNWSE,
|
||||
No,
|
||||
};
|
||||
|
||||
struct Resolution {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t refresh_rate = 0;
|
||||
|
||||
Resolution() = default;
|
||||
|
||||
Resolution(uint32_t width, uint32_t height, uint32_t refresh_rate)
|
||||
: width(width), height(height), refresh_rate(refresh_rate) {}
|
||||
};
|
||||
|
||||
struct Icon {
|
||||
Icon() = default;
|
||||
|
||||
Icon(std::string file_path) : file_path(file_path) {}
|
||||
|
||||
std::string file_path;
|
||||
|
||||
#if defined(_WIN32)
|
||||
uint32_t resource_id = 0;
|
||||
|
||||
Icon(uint32_t resource_id) : resource_id(resource_id) {}
|
||||
#endif
|
||||
};
|
||||
|
||||
struct WindowConfig {
|
||||
uint32_t width = 640;
|
||||
uint32_t height = 480;
|
||||
std::string title = "frostbite2D Game";
|
||||
Icon icon;
|
||||
bool resizable = false;
|
||||
bool fullscreen = false;
|
||||
bool borderless = false;
|
||||
bool decorated = true;
|
||||
int multisamples = 0;
|
||||
bool centered = true;
|
||||
bool vsync = true;
|
||||
bool showCursor = true;
|
||||
};
|
||||
|
||||
class Window {
|
||||
public:
|
||||
Window() = default;
|
||||
~Window() = default;
|
||||
|
||||
virtual bool create(const WindowConfig& cfg);
|
||||
virtual void destroy();
|
||||
virtual void poll();
|
||||
virtual void swap();
|
||||
virtual void close();
|
||||
virtual void setTitle(const std::string& title);
|
||||
virtual void setSize(int w, int h);
|
||||
virtual void setPos(int x, int y);
|
||||
virtual void setFullscreen(bool fs);
|
||||
virtual void setVSync(bool vsync);
|
||||
virtual void setVisible(bool visible);
|
||||
|
||||
virtual int width() const;
|
||||
virtual int height() const;
|
||||
virtual int drawableWidth() const;
|
||||
virtual int drawableHeight() const;
|
||||
virtual Size size() const;
|
||||
virtual Vec2 pos() const;
|
||||
virtual bool fullscreen() const;
|
||||
virtual bool vsync() const;
|
||||
virtual bool focused() const;
|
||||
virtual bool minimized() const;
|
||||
virtual void setImmEnabled(bool enabled);
|
||||
virtual bool isImmEnabled() const;
|
||||
virtual float scaleX() const;
|
||||
virtual float scaleY() const;
|
||||
virtual bool refreshMetrics(bool emitResize = false);
|
||||
virtual void setCursor(CursorType cursor);
|
||||
virtual void showCursor(bool show);
|
||||
virtual void lockCursor(bool lock);
|
||||
|
||||
using ResizeCb = std::function<void(int, int)>;
|
||||
using CloseCb = std::function<void()>;
|
||||
using FocusCb = std::function<void(bool)>;
|
||||
|
||||
virtual void onResize(ResizeCb cb);
|
||||
virtual void onClose(CloseCb cb);
|
||||
virtual void onFocus(FocusCb cb);
|
||||
virtual void* native() const;
|
||||
|
||||
SDL_Window* sdlWindow() const { return sdlWindow_; }
|
||||
SDL_GLContext glContext() const { return glContext_; }
|
||||
|
||||
private:
|
||||
SDL_Window* sdlWindow_ = nullptr;
|
||||
SDL_GLContext glContext_ = nullptr;
|
||||
|
||||
int width_ = 1280;
|
||||
int height_ = 720;
|
||||
int drawableWidth_ = 1280;
|
||||
int drawableHeight_ = 720;
|
||||
bool fullscreen_ = false;
|
||||
bool vsync_ = true;
|
||||
bool focused_ = true;
|
||||
bool minimized_ = false;
|
||||
bool shouldClose_ = false;
|
||||
float scaleX_ = 1.0f;
|
||||
float scaleY_ = 1.0f;
|
||||
bool cursorVisible_ = true;
|
||||
bool cursorLocked_ = false;
|
||||
bool immEnabled_ = false;
|
||||
bool immDisabledByApp_ = false;
|
||||
void* immRestoreContext_ = nullptr;
|
||||
|
||||
ResizeCb resizeCb_;
|
||||
CloseCb closeCb_;
|
||||
FocusCb focusCb_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Actor;
|
||||
|
||||
enum class EventType : int32 {
|
||||
None = 0,
|
||||
WindowClose,
|
||||
WindowResize,
|
||||
WindowFocus,
|
||||
WindowMinimize,
|
||||
WindowMaximize,
|
||||
WindowRestore,
|
||||
KeyDown,
|
||||
KeyUp,
|
||||
KeyText,
|
||||
MouseMove,
|
||||
MouseButtonDown,
|
||||
MouseButtonUp,
|
||||
MouseWheel,
|
||||
TouchDown,
|
||||
TouchUp,
|
||||
TouchMove,
|
||||
JoystickAxis,
|
||||
JoystickBall,
|
||||
JoystickHat,
|
||||
JoystickButtonDown,
|
||||
JoystickButtonUp,
|
||||
Custom
|
||||
};
|
||||
|
||||
class Event {
|
||||
public:
|
||||
virtual ~Event() = default;
|
||||
|
||||
virtual EventType getType() const = 0;
|
||||
|
||||
bool isHandled() const { return handled_; }
|
||||
void setHandled(bool handled) { handled_ = handled; }
|
||||
|
||||
bool isPropagationStopped() const { return propagationStopped_; }
|
||||
void stopPropagation() { propagationStopped_ = true; }
|
||||
|
||||
Actor* getSource() const { return source_; }
|
||||
void setSource(Actor* actor) { source_ = actor; }
|
||||
|
||||
uint32 getTimestamp() const { return timestamp_; }
|
||||
|
||||
protected:
|
||||
Event() = default;
|
||||
Event(uint32 timestamp)
|
||||
: timestamp_(timestamp) {}
|
||||
|
||||
bool handled_ = false;
|
||||
bool propagationStopped_ = false;
|
||||
Actor* source_ = nullptr;
|
||||
uint32 timestamp_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <SDL2/SDL_joystick.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class JoystickEvent : public Event {
|
||||
public:
|
||||
JoystickEvent(EventType type, uint32 timestamp, int32 deviceId)
|
||||
: Event(timestamp)
|
||||
, type_(type)
|
||||
, deviceId_(deviceId) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return type_;
|
||||
}
|
||||
|
||||
int32 getDeviceId() const { return deviceId_; }
|
||||
|
||||
protected:
|
||||
EventType type_;
|
||||
int32 deviceId_;
|
||||
};
|
||||
|
||||
class JoystickAxisEvent : public JoystickEvent {
|
||||
public:
|
||||
JoystickAxisEvent(uint32 timestamp, int32 deviceId, uint8 axis, int16 value)
|
||||
: JoystickEvent(EventType::JoystickAxis, timestamp, deviceId)
|
||||
, axis_(axis)
|
||||
, value_(value) {}
|
||||
|
||||
uint8 getAxis() const { return axis_; }
|
||||
int16 getValue() const { return value_; }
|
||||
|
||||
float getNormalizedValue() const {
|
||||
return static_cast<float>(value_) / 32767.0f;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8 axis_;
|
||||
int16 value_;
|
||||
};
|
||||
|
||||
class JoystickBallEvent : public JoystickEvent {
|
||||
public:
|
||||
JoystickBallEvent(uint32 timestamp, int32 deviceId, uint8 ball, int16 dx, int16 dy)
|
||||
: JoystickEvent(EventType::JoystickBall, timestamp, deviceId)
|
||||
, ball_(ball)
|
||||
, dx_(dx)
|
||||
, dy_(dy) {}
|
||||
|
||||
uint8 getBall() const { return ball_; }
|
||||
int16 getDX() const { return dx_; }
|
||||
int16 getDY() const { return dy_; }
|
||||
|
||||
private:
|
||||
uint8 ball_;
|
||||
int16 dx_;
|
||||
int16 dy_;
|
||||
};
|
||||
|
||||
class JoystickHatEvent : public JoystickEvent {
|
||||
public:
|
||||
JoystickHatEvent(uint32 timestamp, int32 deviceId, uint8 hat, uint8 value)
|
||||
: JoystickEvent(EventType::JoystickHat, timestamp, deviceId)
|
||||
, hat_(hat)
|
||||
, value_(value) {}
|
||||
|
||||
uint8 getHat() const { return hat_; }
|
||||
uint8 getValue() const { return value_; }
|
||||
|
||||
bool isCentered() const { return value_ == SDL_HAT_CENTERED; }
|
||||
bool isUp() const { return (value_ & SDL_HAT_UP) != 0; }
|
||||
bool isRight() const { return (value_ & SDL_HAT_RIGHT) != 0; }
|
||||
bool isDown() const { return (value_ & SDL_HAT_DOWN) != 0; }
|
||||
bool isLeft() const { return (value_ & SDL_HAT_LEFT) != 0; }
|
||||
|
||||
private:
|
||||
uint8 hat_;
|
||||
uint8 value_;
|
||||
};
|
||||
|
||||
class JoystickButtonEvent : public JoystickEvent {
|
||||
public:
|
||||
JoystickButtonEvent(EventType type, uint32 timestamp, int32 deviceId, uint8 button)
|
||||
: JoystickEvent(type, timestamp, deviceId)
|
||||
, button_(button) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return type_;
|
||||
}
|
||||
|
||||
uint8 getButton() const { return button_; }
|
||||
|
||||
private:
|
||||
uint8 button_;
|
||||
};
|
||||
|
||||
class JoystickButtonDownEvent : public JoystickButtonEvent {
|
||||
public:
|
||||
JoystickButtonDownEvent(uint32 timestamp, int32 deviceId, uint8 button)
|
||||
: JoystickButtonEvent(EventType::JoystickButtonDown, timestamp, deviceId, button) {}
|
||||
};
|
||||
|
||||
class JoystickButtonUpEvent : public JoystickButtonEvent {
|
||||
public:
|
||||
JoystickButtonUpEvent(uint32 timestamp, int32 deviceId, uint8 button)
|
||||
: JoystickButtonEvent(EventType::JoystickButtonUp, timestamp, deviceId, button) {}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <SDL2/SDL_keyboard.h>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
enum class KeyCode {
|
||||
Unknown = SDLK_UNKNOWN,
|
||||
Return = SDLK_RETURN,
|
||||
Escape = SDLK_ESCAPE,
|
||||
Backspace = SDLK_BACKSPACE,
|
||||
Tab = SDLK_TAB,
|
||||
Space = SDLK_SPACE,
|
||||
Exclaim = SDLK_EXCLAIM,
|
||||
Quotedbl = SDLK_QUOTEDBL,
|
||||
Hash = SDLK_HASH,
|
||||
Percent = SDLK_PERCENT,
|
||||
Dollar = SDLK_DOLLAR,
|
||||
Ampersand = SDLK_AMPERSAND,
|
||||
Quote = SDLK_QUOTE,
|
||||
LeftParen = SDLK_LEFTPAREN,
|
||||
RightParen = SDLK_RIGHTPAREN,
|
||||
Asterisk = SDLK_ASTERISK,
|
||||
Plus = SDLK_PLUS,
|
||||
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,
|
||||
Colon = SDLK_COLON,
|
||||
Semicolon = SDLK_SEMICOLON,
|
||||
Less = SDLK_LESS,
|
||||
Equals = SDLK_EQUALS,
|
||||
Greater = SDLK_GREATER,
|
||||
Question = SDLK_QUESTION,
|
||||
At = SDLK_AT,
|
||||
LeftBracket = SDLK_LEFTBRACKET,
|
||||
Backslash = SDLK_BACKSLASH,
|
||||
RightBracket = SDLK_RIGHTBRACKET,
|
||||
Caret = SDLK_CARET,
|
||||
Underscore = SDLK_UNDERSCORE,
|
||||
Backquote = SDLK_BACKQUOTE,
|
||||
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,
|
||||
CapsLock = SDLK_CAPSLOCK,
|
||||
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,
|
||||
PrintScreen = SDLK_PRINTSCREEN,
|
||||
ScrollLock = SDLK_SCROLLLOCK,
|
||||
Pause = SDLK_PAUSE,
|
||||
Insert = SDLK_INSERT,
|
||||
Home = SDLK_HOME,
|
||||
PageUp = SDLK_PAGEUP,
|
||||
Delete = SDLK_DELETE,
|
||||
End = SDLK_END,
|
||||
PageDown = SDLK_PAGEDOWN,
|
||||
Right = SDLK_RIGHT,
|
||||
Left = SDLK_LEFT,
|
||||
Down = SDLK_DOWN,
|
||||
Up = SDLK_UP,
|
||||
Numpad0 = SDLK_KP_0,
|
||||
Numpad1 = SDLK_KP_1,
|
||||
Numpad2 = SDLK_KP_2,
|
||||
Numpad3 = SDLK_KP_3,
|
||||
Numpad4 = SDLK_KP_4,
|
||||
Numpad5 = SDLK_KP_5,
|
||||
Numpad6 = SDLK_KP_6,
|
||||
Numpad7 = SDLK_KP_7,
|
||||
Numpad8 = SDLK_KP_8,
|
||||
Numpad9 = SDLK_KP_9,
|
||||
NumpadDecimal = SDLK_KP_DECIMAL,
|
||||
NumpadDivide = SDLK_KP_DIVIDE,
|
||||
NumpadMultiply = SDLK_KP_MULTIPLY,
|
||||
NumpadMinus = SDLK_KP_MINUS,
|
||||
NumpadPlus = SDLK_KP_PLUS,
|
||||
NumpadEnter = SDLK_KP_ENTER,
|
||||
NumpadEquals = SDLK_KP_EQUALS,
|
||||
LShift = SDLK_LSHIFT,
|
||||
RShift = SDLK_RSHIFT,
|
||||
LCtrl = SDLK_LCTRL,
|
||||
RCtrl = SDLK_RCTRL,
|
||||
LAlt = SDLK_LALT,
|
||||
RAlt = SDLK_RALT,
|
||||
LGui = SDLK_LGUI,
|
||||
RGui = SDLK_RGUI
|
||||
};
|
||||
|
||||
enum class KeyMod {
|
||||
None = KMOD_NONE,
|
||||
LShift = KMOD_LSHIFT,
|
||||
RShift = KMOD_RSHIFT,
|
||||
Shift = KMOD_SHIFT,
|
||||
LCtrl = KMOD_LCTRL,
|
||||
RCtrl = KMOD_RCTRL,
|
||||
Ctrl = KMOD_CTRL,
|
||||
LAlt = KMOD_LALT,
|
||||
RAlt = KMOD_RALT,
|
||||
Alt = KMOD_ALT,
|
||||
LGui = KMOD_LGUI,
|
||||
RGui = KMOD_RGUI,
|
||||
Gui = KMOD_GUI,
|
||||
Num = KMOD_NUM,
|
||||
Caps = KMOD_CAPS
|
||||
};
|
||||
|
||||
class KeyEvent : public Event {
|
||||
public:
|
||||
KeyEvent(uint32 timestamp, KeyCode keyCode, KeyMod modifiers, bool repeat)
|
||||
: Event(timestamp)
|
||||
, keyCode_(keyCode)
|
||||
, modifiers_(modifiers)
|
||||
, repeat_(repeat) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return EventType::KeyDown;
|
||||
}
|
||||
|
||||
KeyCode getKeyCode() const { return keyCode_; }
|
||||
KeyMod getModifiers() const { return modifiers_; }
|
||||
bool isRepeat() const { return repeat_; }
|
||||
|
||||
private:
|
||||
KeyCode keyCode_;
|
||||
KeyMod modifiers_;
|
||||
bool repeat_;
|
||||
};
|
||||
|
||||
class KeyUpEvent : public Event {
|
||||
public:
|
||||
KeyUpEvent(uint32 timestamp, KeyCode keyCode, KeyMod modifiers)
|
||||
: Event(timestamp)
|
||||
, keyCode_(keyCode)
|
||||
, modifiers_(modifiers) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return EventType::KeyUp;
|
||||
}
|
||||
|
||||
KeyCode getKeyCode() const { return keyCode_; }
|
||||
KeyMod getModifiers() const { return modifiers_; }
|
||||
|
||||
private:
|
||||
KeyCode keyCode_;
|
||||
KeyMod modifiers_;
|
||||
};
|
||||
|
||||
class KeyTextEvent : public Event {
|
||||
public:
|
||||
KeyTextEvent(uint32 timestamp, const std::string& text)
|
||||
: Event(timestamp)
|
||||
, text_(text) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return EventType::KeyText;
|
||||
}
|
||||
|
||||
const std::string& getText() const { return text_; }
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <SDL2/SDL_mouse.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
enum class MouseButton {
|
||||
Left = SDL_BUTTON_LEFT,
|
||||
Right = SDL_BUTTON_RIGHT,
|
||||
Middle = SDL_BUTTON_MIDDLE,
|
||||
X1 = SDL_BUTTON_X1,
|
||||
X2 = SDL_BUTTON_X2
|
||||
};
|
||||
|
||||
class MouseEvent : public Event {
|
||||
public:
|
||||
MouseEvent(EventType type, uint32 timestamp, const Vec2& position)
|
||||
: Event(timestamp)
|
||||
, type_(type)
|
||||
, position_(position) {}
|
||||
|
||||
virtual ~MouseEvent() = default;
|
||||
|
||||
EventType getType() const override { return type_; }
|
||||
const Vec2& getPosition() const { return position_; }
|
||||
int32 getX() const { return static_cast<int32>(position_.x); }
|
||||
int32 getY() const { return static_cast<int32>(position_.y); }
|
||||
|
||||
protected:
|
||||
EventType type_;
|
||||
Vec2 position_;
|
||||
};
|
||||
|
||||
class MouseMoveEvent : public Event {
|
||||
public:
|
||||
MouseMoveEvent(uint32 timestamp, const Vec2& position, const Vec2& relative)
|
||||
: Event(timestamp)
|
||||
, position_(position)
|
||||
, relative_(relative) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return EventType::MouseMove;
|
||||
}
|
||||
|
||||
const Vec2& getPosition() const { return position_; }
|
||||
const Vec2& getRelative() const { return relative_; }
|
||||
|
||||
int32 getX() const { return static_cast<int32>(position_.x); }
|
||||
int32 getY() const { return static_cast<int32>(position_.y); }
|
||||
|
||||
private:
|
||||
Vec2 position_;
|
||||
Vec2 relative_;
|
||||
};
|
||||
|
||||
class MouseButtonEvent : public Event {
|
||||
public:
|
||||
MouseButtonEvent(uint32 timestamp, MouseButton button, const Vec2& position, bool down)
|
||||
: Event(timestamp)
|
||||
, button_(button)
|
||||
, position_(position)
|
||||
, down_(down) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return down_ ? EventType::MouseButtonDown : EventType::MouseButtonUp;
|
||||
}
|
||||
|
||||
MouseButton getButton() const { return button_; }
|
||||
const Vec2& getPosition() const { return position_; }
|
||||
bool isDown() const { return down_; }
|
||||
|
||||
int32 getX() const { return static_cast<int32>(position_.x); }
|
||||
int32 getY() const { return static_cast<int32>(position_.y); }
|
||||
|
||||
private:
|
||||
MouseButton button_;
|
||||
Vec2 position_;
|
||||
bool down_;
|
||||
};
|
||||
|
||||
class MouseWheelEvent : public Event {
|
||||
public:
|
||||
MouseWheelEvent(uint32 timestamp, const Vec2& position, const Vec2& direction, bool flipped)
|
||||
: Event(timestamp)
|
||||
, position_(position)
|
||||
, direction_(direction)
|
||||
, flipped_(flipped) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return EventType::MouseWheel;
|
||||
}
|
||||
|
||||
const Vec2& getPosition() const { return position_; }
|
||||
const Vec2& getDirection() const { return direction_; }
|
||||
bool isFlipped() const { return flipped_; }
|
||||
|
||||
int32 getX() const { return static_cast<int32>(direction_.x); }
|
||||
int32 getY() const { return static_cast<int32>(direction_.y); }
|
||||
|
||||
private:
|
||||
Vec2 position_;
|
||||
Vec2 direction_;
|
||||
bool flipped_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <SDL2/SDL_touch.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class TouchEvent : public Event {
|
||||
public:
|
||||
TouchEvent(EventType type, uint32 timestamp, int64 touchId, int64 fingerId,
|
||||
const Vec2& position, const Vec2& delta, float pressure)
|
||||
: Event(timestamp)
|
||||
, type_(type)
|
||||
, touchId_(touchId)
|
||||
, fingerId_(fingerId)
|
||||
, position_(position)
|
||||
, delta_(delta)
|
||||
, pressure_(pressure) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return type_;
|
||||
}
|
||||
|
||||
int64 getTouchId() const { return touchId_; }
|
||||
int64 getFingerId() const { return fingerId_; }
|
||||
const Vec2& getPosition() const { return position_; }
|
||||
const Vec2& getDelta() const { return delta_; }
|
||||
float getPressure() const { return pressure_; }
|
||||
|
||||
private:
|
||||
EventType type_;
|
||||
int64 touchId_;
|
||||
int64 fingerId_;
|
||||
Vec2 position_;
|
||||
Vec2 delta_;
|
||||
float pressure_;
|
||||
};
|
||||
|
||||
class TouchDownEvent : public TouchEvent {
|
||||
public:
|
||||
TouchDownEvent(uint32 timestamp, int64 touchId, int64 fingerId,
|
||||
const Vec2& position, float pressure)
|
||||
: TouchEvent(EventType::TouchDown, timestamp, touchId, fingerId,
|
||||
position, Vec2(0, 0), pressure) {}
|
||||
};
|
||||
|
||||
class TouchUpEvent : public TouchEvent {
|
||||
public:
|
||||
TouchUpEvent(uint32 timestamp, int64 touchId, int64 fingerId,
|
||||
const Vec2& position, float pressure)
|
||||
: TouchEvent(EventType::TouchUp, timestamp, touchId, fingerId,
|
||||
position, Vec2(0, 0), pressure) {}
|
||||
};
|
||||
|
||||
class TouchMoveEvent : public TouchEvent {
|
||||
public:
|
||||
TouchMoveEvent(uint32 timestamp, int64 touchId, int64 fingerId,
|
||||
const Vec2& position, const Vec2& delta, float pressure)
|
||||
: TouchEvent(EventType::TouchMove, timestamp, touchId, fingerId,
|
||||
position, delta, pressure) {}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class WindowEvent : public Event {
|
||||
public:
|
||||
WindowEvent(EventType type, uint32 timestamp, int32 windowId)
|
||||
: Event(timestamp)
|
||||
, type_(type)
|
||||
, windowId_(windowId) {}
|
||||
|
||||
EventType getType() const override {
|
||||
return type_;
|
||||
}
|
||||
|
||||
int32 getWindowId() const { return windowId_; }
|
||||
|
||||
protected:
|
||||
EventType type_;
|
||||
int32 windowId_;
|
||||
};
|
||||
|
||||
class WindowCloseEvent : public WindowEvent {
|
||||
public:
|
||||
WindowCloseEvent(uint32 timestamp, int32 windowId)
|
||||
: WindowEvent(EventType::WindowClose, timestamp, windowId) {}
|
||||
};
|
||||
|
||||
class WindowResizeEvent : public WindowEvent {
|
||||
public:
|
||||
WindowResizeEvent(uint32 timestamp, int32 windowId, int32 width, int32 height)
|
||||
: WindowEvent(EventType::WindowResize, timestamp, windowId)
|
||||
, width_(width)
|
||||
, height_(height) {}
|
||||
|
||||
int32 getWidth() const { return width_; }
|
||||
int32 getHeight() const { return height_; }
|
||||
|
||||
private:
|
||||
int32 width_;
|
||||
int32 height_;
|
||||
};
|
||||
|
||||
class WindowFocusEvent : public WindowEvent {
|
||||
public:
|
||||
WindowFocusEvent(uint32 timestamp, int32 windowId, bool focused)
|
||||
: WindowEvent(EventType::WindowFocus, timestamp, windowId)
|
||||
, focused_(focused) {}
|
||||
|
||||
bool isFocused() const { return focused_; }
|
||||
|
||||
private:
|
||||
bool focused_;
|
||||
};
|
||||
|
||||
class WindowMinimizeEvent : public WindowEvent {
|
||||
public:
|
||||
WindowMinimizeEvent(uint32 timestamp, int32 windowId)
|
||||
: WindowEvent(EventType::WindowMinimize, timestamp, windowId) {}
|
||||
};
|
||||
|
||||
class WindowMaximizeEvent : public WindowEvent {
|
||||
public:
|
||||
WindowMaximizeEvent(uint32 timestamp, int32 windowId)
|
||||
: WindowEvent(EventType::WindowMaximize, timestamp, windowId) {}
|
||||
};
|
||||
|
||||
class WindowRestoreEvent : public WindowEvent {
|
||||
public:
|
||||
WindowRestoreEvent(uint32 timestamp, int32 windowId)
|
||||
: WindowEvent(EventType::WindowRestore, timestamp, windowId) {}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/graphics/types.h>
|
||||
#include <frostbite2D/graphics/texture.h>
|
||||
#include <frostbite2D/graphics/shader.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
struct BatchKey {
|
||||
uint32_t shaderID;
|
||||
uint32_t textureID;
|
||||
uint32_t blendMode;
|
||||
|
||||
bool operator<(const BatchKey& other) const {
|
||||
if (shaderID != other.shaderID) return shaderID < other.shaderID;
|
||||
if (textureID != other.textureID) return textureID < other.textureID;
|
||||
return blendMode < other.blendMode;
|
||||
}
|
||||
};
|
||||
|
||||
class Batch {
|
||||
public:
|
||||
static constexpr int MAX_QUADS = 2048;
|
||||
static constexpr int MAX_VERTICES = MAX_QUADS * 4;
|
||||
static constexpr int MAX_INDICES = MAX_QUADS * 6;
|
||||
|
||||
Batch();
|
||||
~Batch();
|
||||
|
||||
bool init();
|
||||
void shutdown();
|
||||
|
||||
void begin();
|
||||
void end();
|
||||
|
||||
void submitQuad(const Quad& quad, const Transform2D& transform,
|
||||
Ptr<Texture> texture, Shader* shader,
|
||||
BlendMode blendMode = BlendMode::Normal);
|
||||
|
||||
void flush();
|
||||
void flushIfNeeded(const BatchKey& newKey, Shader* shader, Ptr<Texture> texture);
|
||||
uint32 drawCallCount() const { return drawCallCount_; }
|
||||
|
||||
private:
|
||||
void setupMesh();
|
||||
void flushCurrentBatch();
|
||||
|
||||
struct BatchData {
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<uint16> indices;
|
||||
BatchKey key;
|
||||
Shader* shader = nullptr;
|
||||
Ptr<Texture> texture;
|
||||
};
|
||||
|
||||
uint32_t vao_ = 0;
|
||||
uint32_t vbo_ = 0;
|
||||
uint32_t ibo_ = 0;
|
||||
|
||||
BatchData currentBatch_;
|
||||
std::vector<BatchData> batches_;
|
||||
|
||||
bool begun_ = false;
|
||||
uint32 drawCallCount_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Camera {
|
||||
public:
|
||||
Camera();
|
||||
|
||||
void setPosition(const Vec2& pos);
|
||||
void setZoom(float zoom);
|
||||
void setViewport(int width, int height);
|
||||
void setFlipY(bool flip) { flipY_ = flip; } // Debug Y-axis flip.
|
||||
void setPixelSnapEnabled(bool enabled) { pixelSnapEnabled_ = enabled; }
|
||||
|
||||
const Vec2& getPosition() const { return position_; }
|
||||
Vec2 getRenderPosition() const;
|
||||
Vec2 snapWorldPosition(const Vec2& position) const;
|
||||
float getZoom() const { return zoom_; }
|
||||
int getViewportWidth() const { return viewportWidth_; }
|
||||
int getViewportHeight() const { return viewportHeight_; }
|
||||
float getVisibleWidth() const;
|
||||
float getVisibleHeight() const;
|
||||
bool isPixelSnapEnabled() const { return pixelSnapEnabled_; }
|
||||
|
||||
void lookAt(const Vec2& target);
|
||||
void move(const Vec2& delta);
|
||||
void zoomAt(float factor, const Vec2& screenPos);
|
||||
|
||||
glm::mat4 getViewMatrix() const;
|
||||
glm::mat4 getProjectionMatrix() const;
|
||||
|
||||
private:
|
||||
Vec2 position_;
|
||||
float zoom_ = 1.0f;
|
||||
int viewportWidth_ = 1280;
|
||||
int viewportHeight_ = 720;
|
||||
bool flipY_ = false; // Debug Y-axis flip.
|
||||
bool pixelSnapEnabled_ = false;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class FontManager {
|
||||
public:
|
||||
static FontManager& get();
|
||||
|
||||
bool init();
|
||||
void shutdown();
|
||||
|
||||
bool registerFont(const std::string& name, const std::string& path, int fontSize);
|
||||
TTF_Font* getFont(const std::string& name);
|
||||
bool hasFont(const std::string& name) const;
|
||||
void unregisterFont(const std::string& name);
|
||||
void unloadAll();
|
||||
|
||||
struct FontInfo {
|
||||
std::string name;
|
||||
std::string path;
|
||||
int size = 0;
|
||||
};
|
||||
|
||||
std::optional<FontInfo> getFontInfo(const std::string& name) const;
|
||||
|
||||
FontManager(const FontManager&) = delete;
|
||||
FontManager& operator=(const FontManager&) = delete;
|
||||
|
||||
private:
|
||||
FontManager() = default;
|
||||
~FontManager();
|
||||
|
||||
std::unordered_map<std::string, TTF_Font*> fonts_;
|
||||
std::unordered_map<std::string, FontInfo> fontInfos_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 虚拟分辨率缩放模式
|
||||
*/
|
||||
enum class ResolutionScaleMode {
|
||||
Disabled, ///< 不保持纵横比,直接按窗口宽高分别缩放
|
||||
Fit, ///< 保持纵横比并完整显示,按较小缩放因子适配
|
||||
FitHeight, ///< 以高度优先适配,宽度不足时回退为 Fit
|
||||
};
|
||||
|
||||
struct RenderResolutionState {
|
||||
int windowWidth = 1280;
|
||||
int windowHeight = 720;
|
||||
|
||||
int drawableWidth = 1280;
|
||||
int drawableHeight = 720;
|
||||
|
||||
int windowViewportX = 0;
|
||||
int windowViewportY = 0;
|
||||
int windowViewportWidth = 1280;
|
||||
int windowViewportHeight = 720;
|
||||
|
||||
int viewportX = 0;
|
||||
int viewportY = 0;
|
||||
int viewportWidth = 1280;
|
||||
int viewportHeight = 720;
|
||||
|
||||
float contentScaleX = 1.0f;
|
||||
float contentScaleY = 1.0f;
|
||||
float scaleX = 1.0f;
|
||||
float scaleY = 1.0f;
|
||||
|
||||
Rect getWindowViewportRect() const {
|
||||
return Rect(static_cast<float>(windowViewportX),
|
||||
static_cast<float>(windowViewportY),
|
||||
static_cast<float>(windowViewportWidth),
|
||||
static_cast<float>(windowViewportHeight));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 2D 渲染风格预设 ID
|
||||
*/
|
||||
enum class RenderStyleProfileId {
|
||||
PixelArt2D, ///< 世界与 UI 都按像素风策略渲染
|
||||
Smooth2D, ///< 世界与 UI 都按平滑 2D 策略渲染
|
||||
Hybrid2D ///< 世界走像素风,UI 走平滑策略
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 渲染风格作用层级
|
||||
*/
|
||||
enum class RenderStyleLayerRole {
|
||||
World, ///< 世界层,如地图、角色、场景特效
|
||||
UI ///< UI 层,如 HUD、菜单、文字
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 单个层级的渲染风格开关集合
|
||||
*/
|
||||
struct RenderStyleLayerSettings {
|
||||
bool cameraPixelSnap = false; ///< 是否让相机位置吸附到像素网格
|
||||
bool vertexPixelSnap = false; ///< 是否让世界顶点在提交前做像素对齐
|
||||
bool pixelArtSampling = false; ///< 是否优先使用像素风采样(如 GL_NEAREST)
|
||||
bool shrinkSubTextureUVs = false; ///< 是否对子纹理 UV 做向内收缩,减轻图集边缘闪烁
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 一套完整的 2D 渲染风格设置
|
||||
*/
|
||||
struct RenderStyleSettings {
|
||||
RenderStyleLayerSettings world; ///< 世界层使用的渲染风格
|
||||
RenderStyleLayerSettings ui; ///< UI 层使用的渲染风格
|
||||
|
||||
/**
|
||||
* @brief 根据预设 ID 生成对应的渲染风格
|
||||
*/
|
||||
static RenderStyleSettings FromProfile(RenderStyleProfileId profile) {
|
||||
RenderStyleSettings settings;
|
||||
|
||||
switch (profile) {
|
||||
case RenderStyleProfileId::PixelArt2D:
|
||||
settings.world = {true, true, true, true};
|
||||
settings.ui = {true, true, true, true};
|
||||
break;
|
||||
case RenderStyleProfileId::Smooth2D:
|
||||
settings.world = {false, false, false, false};
|
||||
settings.ui = {false, false, false, false};
|
||||
break;
|
||||
case RenderStyleProfileId::Hybrid2D:
|
||||
default:
|
||||
settings.world = {true, true, true, true};
|
||||
settings.ui = {false, false, false, false};
|
||||
break;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指定层级对应的风格设置
|
||||
*/
|
||||
const RenderStyleLayerSettings& layer(RenderStyleLayerRole role) const {
|
||||
return role == RenderStyleLayerRole::UI ? ui : world;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/base/RefObject.h>
|
||||
#include <frostbite2D/graphics/texture.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Renderer;
|
||||
|
||||
/// @brief 2D 离屏渲染目标,封装颜色纹理和对应的 framebuffer。
|
||||
class RenderTexture : public RefObject {
|
||||
public:
|
||||
RenderTexture() = default;
|
||||
~RenderTexture() override;
|
||||
|
||||
bool Init(int width, int height);
|
||||
bool Resize(int width, int height);
|
||||
void Reset();
|
||||
|
||||
bool IsValid() const { return framebufferID_ != 0 && texture_ != nullptr; }
|
||||
int GetWidth() const { return width_; }
|
||||
int GetHeight() const { return height_; }
|
||||
Ptr<Texture> GetTexture() const { return texture_; }
|
||||
|
||||
private:
|
||||
Ptr<Texture> texture_ = nullptr;
|
||||
uint32 framebufferID_ = 0;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
|
||||
friend class Renderer;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,179 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/graphics/batch.h>
|
||||
#include <frostbite2D/graphics/camera.h>
|
||||
#include <frostbite2D/graphics/render_resolution.h>
|
||||
#include <frostbite2D/graphics/render_style.h>
|
||||
#include <frostbite2D/graphics/shader.h>
|
||||
#include <frostbite2D/graphics/shader_manager.h>
|
||||
#include <frostbite2D/graphics/texture.h>
|
||||
#include <frostbite2D/graphics/types.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class RenderTexture;
|
||||
|
||||
class Renderer {
|
||||
public:
|
||||
static Renderer& get();
|
||||
|
||||
bool init();
|
||||
void shutdown();
|
||||
|
||||
void beginFrame();
|
||||
void endFrame();
|
||||
void flush();
|
||||
bool beginRenderToTexture(RenderTexture& target, Camera* camera,
|
||||
const Color& clearColor = Color(0.0f, 0.0f, 0.0f, 0.0f),
|
||||
bool clear = true);
|
||||
bool endRenderToTexture();
|
||||
|
||||
void setViewport(int x, int y, int width, int height);
|
||||
void setWindowSize(int width, int height, float contentScaleX = 1.0f,
|
||||
float contentScaleY = 1.0f);
|
||||
void setVirtualResolutionEnabled(bool enabled);
|
||||
void setVirtualResolution(int width, int height);
|
||||
void setResolutionScaleMode(ResolutionScaleMode mode);
|
||||
void setSeparateUIVirtualResolutionEnabled(bool enabled);
|
||||
void setUIVirtualResolution(int width, int height);
|
||||
void setUIResolutionScaleMode(ResolutionScaleMode mode);
|
||||
void setClearColor(float r, float g, float b, float a = 1.0f);
|
||||
void setClearColor(const Color& color);
|
||||
void clear(uint32_t flags);
|
||||
/**
|
||||
* @brief 设置应用默认渲染风格预设
|
||||
*/
|
||||
void setDefaultRenderStyleProfile(RenderStyleProfileId profile);
|
||||
/**
|
||||
* @brief 获取应用默认渲染风格预设
|
||||
*/
|
||||
RenderStyleProfileId getDefaultRenderStyleProfile() const {
|
||||
return defaultRenderStyleProfile_;
|
||||
}
|
||||
/**
|
||||
* @brief 切换当前正在使用的渲染风格上下文
|
||||
*/
|
||||
void setActiveRenderStyleProfile(RenderStyleProfileId profile,
|
||||
RenderStyleLayerRole role);
|
||||
/**
|
||||
* @brief 将指定风格作用到某个相机
|
||||
*/
|
||||
void applyRenderStyleToCamera(Camera* camera, RenderStyleProfileId profile,
|
||||
RenderStyleLayerRole role) const;
|
||||
|
||||
void setCamera(Camera* camera);
|
||||
Camera* getCamera() { return camera_; }
|
||||
int getVirtualWidth() const;
|
||||
int getVirtualHeight() const;
|
||||
int getUIVirtualWidth() const;
|
||||
int getUIVirtualHeight() const;
|
||||
Rect getViewportRect() const;
|
||||
Rect getUIViewportRect() const;
|
||||
const RenderResolutionState& getResolutionState() const {
|
||||
return resolutionState_;
|
||||
}
|
||||
const RenderResolutionState& getUIResolutionState() const {
|
||||
return uiResolutionState_;
|
||||
}
|
||||
bool isInitialized() const { return initialized_; }
|
||||
bool isFrameActive() const { return frameActive_; }
|
||||
bool isRenderingToTexture() const { return !renderTargetStack_.empty(); }
|
||||
void setCullingEnabled(bool enabled) { cullingEnabled_ = enabled; }
|
||||
bool isCullingEnabled() const { return cullingEnabled_; }
|
||||
void applyWorldViewport() const;
|
||||
void applyUIViewport() const;
|
||||
Vec2 screenToVirtual(const Vec2& screenPos) const;
|
||||
Vec2 screenToUIVirtual(const Vec2& screenPos) const;
|
||||
Vec2 virtualToScreen(const Vec2& virtualPos) const;
|
||||
|
||||
void drawQuad(const Vec2& pos, const Size& size, float cr = 1.0f,
|
||||
float cg = 1.0f, float cb = 1.0f, float ca = 1.0f);
|
||||
void drawQuad(const Vec2& pos, const Size& size, Ptr<Texture> texture);
|
||||
void drawQuad(const Rect& rect, const Color& color);
|
||||
|
||||
void drawSprite(const Vec2& pos, const Size& size, Ptr<Texture> texture);
|
||||
void drawSprite(const Vec2& pos, const Rect& srcRect, const Vec2& texSize,
|
||||
Ptr<Texture> texture,
|
||||
const Color& color = Color(1, 1, 1, 1));
|
||||
void setupBlendMode(BlendMode mode);
|
||||
/**
|
||||
* @brief 获取当前激活的层级风格设置
|
||||
*/
|
||||
const RenderStyleLayerSettings& getActiveRenderStyle() const {
|
||||
return activeRenderStyle_;
|
||||
}
|
||||
/**
|
||||
* @brief 当前风格是否要求顶点像素对齐
|
||||
*/
|
||||
bool shouldSnapVertices() const { return activeRenderStyle_.vertexPixelSnap; }
|
||||
/**
|
||||
* @brief 当前风格是否优先使用像素风采样
|
||||
*/
|
||||
bool shouldUsePixelArtSampling() const {
|
||||
return activeRenderStyle_.pixelArtSampling;
|
||||
}
|
||||
/**
|
||||
* @brief 当前风格是否需要对子纹理 UV 做向内收缩
|
||||
*/
|
||||
bool shouldShrinkSubTextureUVs() const {
|
||||
return activeRenderStyle_.shrinkSubTextureUVs;
|
||||
}
|
||||
|
||||
ShaderManager& getShaderManager() { return shaderManager_; }
|
||||
Batch& getBatch() { return batch_; }
|
||||
|
||||
private:
|
||||
Renderer();
|
||||
|
||||
void updateUniforms();
|
||||
void recalculateResolutionState();
|
||||
void recalculateSingleResolutionState(RenderResolutionState& state,
|
||||
bool useVirtualResolution,
|
||||
int virtualWidth, int virtualHeight,
|
||||
ResolutionScaleMode mode) const;
|
||||
void syncCameraViewport();
|
||||
|
||||
struct RenderTargetState {
|
||||
uint32 framebuffer = 0;
|
||||
int viewportX = 0;
|
||||
int viewportY = 0;
|
||||
int viewportWidth = 1;
|
||||
int viewportHeight = 1;
|
||||
float clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
Camera* camera = nullptr;
|
||||
bool startedStandaloneBatch = false;
|
||||
bool cullingEnabled = true;
|
||||
};
|
||||
|
||||
ShaderManager shaderManager_;
|
||||
Batch batch_;
|
||||
Camera* camera_ = nullptr;
|
||||
RenderStyleProfileId defaultRenderStyleProfile_ = RenderStyleProfileId::Hybrid2D;
|
||||
RenderStyleProfileId activeRenderStyleProfile_ = RenderStyleProfileId::Hybrid2D;
|
||||
RenderStyleLayerRole activeRenderStyleRole_ = RenderStyleLayerRole::World;
|
||||
RenderStyleLayerSettings activeRenderStyle_;
|
||||
|
||||
uint32_t clearColor_[4] = {0, 0, 0, 255};
|
||||
bool useVirtualResolution_ = false;
|
||||
int virtualWidth_ = 1280;
|
||||
int virtualHeight_ = 720;
|
||||
ResolutionScaleMode resolutionMode_ = ResolutionScaleMode::Fit;
|
||||
bool useSeparateUIVirtualResolution_ = false;
|
||||
int uiVirtualWidth_ = 0;
|
||||
int uiVirtualHeight_ = 0;
|
||||
ResolutionScaleMode uiResolutionMode_ = ResolutionScaleMode::Fit;
|
||||
RenderResolutionState resolutionState_;
|
||||
RenderResolutionState uiResolutionState_;
|
||||
|
||||
bool initialized_ = false;
|
||||
bool frameActive_ = false;
|
||||
bool cullingEnabled_ = true;
|
||||
std::vector<RenderTargetState> renderTargetStack_;
|
||||
|
||||
Renderer(const Renderer&) = delete;
|
||||
Renderer& operator=(const Renderer&) = delete;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <frostbite2D/base/RefObject.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Shader : public RefObject {
|
||||
public:
|
||||
Shader() = default;
|
||||
~Shader();
|
||||
|
||||
void use();
|
||||
void unuse();
|
||||
|
||||
bool isValid() const { return programID_ != 0; }
|
||||
uint32_t getID() const { return programID_; }
|
||||
const std::string& getName() const { return name_; }
|
||||
|
||||
void setInt(const std::string& name, int value);
|
||||
void setFloat(const std::string& name, float value);
|
||||
void setVec2(const std::string& name, const Vec2& value);
|
||||
void setVec3(const std::string& name, const glm::vec3& value);
|
||||
void setVec4(const std::string& name, const glm::vec4& value);
|
||||
void setMat3(const std::string& name, const glm::mat3& value);
|
||||
void setMat4(const std::string& name, const glm::mat4& value);
|
||||
void setTexture(const std::string& name, int slot);
|
||||
|
||||
private:
|
||||
Shader(const std::string& name, uint32_t programID);
|
||||
|
||||
friend class ShaderManager;
|
||||
|
||||
bool compile(uint32_t type, const std::string& source);
|
||||
bool link(uint32_t vertexID, uint32_t fragmentID);
|
||||
int getUniformLocation(const std::string& name);
|
||||
|
||||
uint32_t programID_ = 0;
|
||||
std::string name_;
|
||||
std::unordered_map<std::string, int> uniformLocations_;
|
||||
|
||||
Shader(const Shader&) = delete;
|
||||
Shader& operator=(const Shader&) = delete;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/graphics/shader.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Renderer;
|
||||
|
||||
class ShaderManager {
|
||||
public:
|
||||
static ShaderManager& get();
|
||||
|
||||
bool init(const std::string& shadersDir);
|
||||
void shutdown();
|
||||
|
||||
Shader* getShader(const std::string& name);
|
||||
bool hasShader(const std::string& name) const;
|
||||
const std::unordered_map<std::string, Ptr<Shader>>& getLoadedShaders() const {
|
||||
return shaders_;
|
||||
}
|
||||
|
||||
~ShaderManager() = default;
|
||||
|
||||
ShaderManager() = default;
|
||||
|
||||
private:
|
||||
|
||||
std::unordered_map<std::string, Ptr<Shader>> shaders_;
|
||||
std::string shadersDir_;
|
||||
|
||||
bool loadShadersFromConfig(const std::string& configPath);
|
||||
bool loadShader(const std::string& name, const std::string& vertPath,
|
||||
const std::string& fragPath);
|
||||
|
||||
ShaderManager(const ShaderManager&) = delete;
|
||||
ShaderManager& operator=(const ShaderManager&) = delete;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/graphics/types.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/base/RefObject.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
enum class TextureSampling {
|
||||
Linear,
|
||||
PixelArt
|
||||
};
|
||||
|
||||
class Texture : public RefObject {
|
||||
public:
|
||||
static Ptr<Texture> loadFromFile(const std::string& path);
|
||||
static Ptr<Texture> createFromMemory(const uint8* data, int width, int height, int channels);
|
||||
static Ptr<Texture> createEmpty(int width, int height);
|
||||
|
||||
~Texture();
|
||||
|
||||
void bind(uint32_t slot = 0, bool preferPixelArtSampling = false);
|
||||
void unbind();
|
||||
|
||||
void setWrapMode(uint32_t wrapS, uint32_t wrapT);
|
||||
void setFilterMode(uint32_t minFilter, uint32_t magFilter);
|
||||
void setSampling(TextureSampling sampling);
|
||||
TextureSampling getSampling() const { return sampling_; }
|
||||
|
||||
int getWidth() const { return width_; }
|
||||
int getHeight() const { return height_; }
|
||||
uint32_t getID() const { return textureID_; }
|
||||
const std::string& getPath() const { return path_; }
|
||||
|
||||
private:
|
||||
Texture(int width, int height, uint32_t id);
|
||||
void applyFilter(uint32_t minFilter, uint32_t magFilter);
|
||||
void applySampling(bool preferPixelArtSampling);
|
||||
|
||||
uint32_t textureID_ = 0;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
int channels_ = 0;
|
||||
std::string path_;
|
||||
TextureSampling sampling_ = TextureSampling::Linear;
|
||||
uint32_t appliedMinFilter_ = 0;
|
||||
uint32_t appliedMagFilter_ = 0;
|
||||
|
||||
Texture() = default;
|
||||
Texture(const Texture&) = delete;
|
||||
Texture& operator=(const Texture&) = delete;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/types/type_math.h>
|
||||
#include <frostbite2D/types/type_color.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
enum class BlendMode {
|
||||
None,
|
||||
Normal,
|
||||
Additive,
|
||||
Screen,
|
||||
Premultiplied,
|
||||
Subtractive,
|
||||
Multiply
|
||||
};
|
||||
|
||||
enum class PrimitiveType {
|
||||
Triangles,
|
||||
TriangleStrip,
|
||||
TriangleFan,
|
||||
Quads
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
Vec2 position;
|
||||
Vec2 texCoord;
|
||||
float r, g, b, a;
|
||||
|
||||
Vertex() = default;
|
||||
Vertex(const Vec2& pos, const Vec2& uv, float cr, float cg, float cb, float ca)
|
||||
: position(pos), texCoord(uv), r(cr), g(cg), b(cb), a(ca) {}
|
||||
};
|
||||
|
||||
struct Quad {
|
||||
Vertex vertices[4];
|
||||
|
||||
Quad() = default;
|
||||
|
||||
static Quad create(const Rect& rect, float cr = 1.0f, float cg = 1.0f,
|
||||
float cb = 1.0f, float ca = 1.0f) {
|
||||
Quad q;
|
||||
Vec2 bl(rect.left(), rect.bottom());
|
||||
Vec2 br(rect.right(), rect.bottom());
|
||||
Vec2 tl(rect.left(), rect.top());
|
||||
Vec2 tr(rect.right(), rect.top());
|
||||
|
||||
q.vertices[0] = Vertex(bl, Vec2(0, 0), cr, cg, cb, ca);
|
||||
q.vertices[1] = Vertex(br, Vec2(1, 0), cr, cg, cb, ca);
|
||||
q.vertices[2] = Vertex(tl, Vec2(0, 1), cr, cg, cb, ca);
|
||||
q.vertices[3] = Vertex(tr, Vec2(1, 1), cr, cg, cb, ca);
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
static Quad createTextured(const Rect& destRect, const Rect& srcRect,
|
||||
const Vec2& texSize, float cr = 1.0f,
|
||||
float cg = 1.0f, float cb = 1.0f, float ca = 1.0f,
|
||||
bool shrinkSubTextureUVs = false) {
|
||||
Quad q;
|
||||
|
||||
Vec2 bl(destRect.left(), destRect.bottom());
|
||||
Vec2 br(destRect.right(), destRect.bottom());
|
||||
Vec2 tl(destRect.left(), destRect.top());
|
||||
Vec2 tr(destRect.right(), destRect.top());
|
||||
|
||||
auto resolveUvX = [&srcRect, &texSize, shrinkSubTextureUVs](bool useRightEdge) {
|
||||
float pixelInset = 0.0f;
|
||||
if (!shrinkSubTextureUVs) {
|
||||
return (useRightEdge ? srcRect.right() : srcRect.left()) / texSize.x;
|
||||
}
|
||||
if (useRightEdge && srcRect.right() < texSize.x) {
|
||||
pixelInset = -0.5f;
|
||||
} else if (!useRightEdge && srcRect.left() > 0.0f) {
|
||||
pixelInset = 0.5f;
|
||||
}
|
||||
return (useRightEdge ? srcRect.right() : srcRect.left()) / texSize.x +
|
||||
pixelInset / texSize.x;
|
||||
};
|
||||
|
||||
auto resolveUvY = [&srcRect, &texSize, shrinkSubTextureUVs](bool useBottomEdge) {
|
||||
float pixelInset = 0.0f;
|
||||
if (!shrinkSubTextureUVs) {
|
||||
return (useBottomEdge ? srcRect.bottom() : srcRect.top()) / texSize.y;
|
||||
}
|
||||
if (useBottomEdge && srcRect.bottom() < texSize.y) {
|
||||
pixelInset = -0.5f;
|
||||
} else if (!useBottomEdge && srcRect.top() > 0.0f) {
|
||||
pixelInset = 0.5f;
|
||||
}
|
||||
return (useBottomEdge ? srcRect.bottom() : srcRect.top()) / texSize.y +
|
||||
pixelInset / texSize.y;
|
||||
};
|
||||
|
||||
Vec2 uvBL(resolveUvX(false), resolveUvY(true));
|
||||
Vec2 uvBR(resolveUvX(true), resolveUvY(true));
|
||||
Vec2 uvTL(resolveUvX(false), resolveUvY(false));
|
||||
Vec2 uvTR(resolveUvX(true), resolveUvY(false));
|
||||
|
||||
q.vertices[0] = Vertex(bl, uvBL, cr, cg, cb, ca);
|
||||
q.vertices[1] = Vertex(br, uvBR, cr, cg, cb, ca);
|
||||
q.vertices[2] = Vertex(tl, uvTL, cr, cg, cb, ca);
|
||||
q.vertices[3] = Vertex(tr, uvTR, cr, cg, cb, ca);
|
||||
|
||||
return q;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __SWITCH__
|
||||
|
||||
#include <switch.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
/**
|
||||
* @brief Switch平台相关函数
|
||||
*/
|
||||
|
||||
void switchInit();
|
||||
void switchShutdown();
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
void windowsInit();
|
||||
|
||||
} // namespace frostbite2D
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,397 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/**
|
||||
* @brief 文件信息结构体
|
||||
*/
|
||||
struct FileInfo {
|
||||
std::string name; ///< 文件名(包含扩展名)
|
||||
std::string extension; ///< 文件扩展名(包含点,如 ".txt")
|
||||
std::string fullPath; ///< 完整路径
|
||||
uint64 size = 0; ///< 文件大小(字节)
|
||||
bool isDirectory = false; ///< 是否为目录
|
||||
bool isRegularFile = false; ///< 是否为普通文件
|
||||
bool exists = false; ///< 是否存在
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 资源管理类(单例)
|
||||
*
|
||||
* 提供文件读写、目录操作、路径处理等功能。
|
||||
* 支持设置工作目录,所有相对路径将基于工作目录解析。
|
||||
* 支持 UTF-8 编码的中文路径。
|
||||
*
|
||||
* @example
|
||||
* auto& asset = Asset::get();
|
||||
* asset.setWorkingDirectory("D:/游戏/资源");
|
||||
* std::string content;
|
||||
* asset.readTextFile("配置/设置.json", content);
|
||||
*/
|
||||
class Asset {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 资源管理器实例引用
|
||||
*/
|
||||
static Asset &get();
|
||||
|
||||
Asset(const Asset &) = delete;
|
||||
Asset &operator=(const Asset &) = delete;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件读写
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 读取文本文件
|
||||
* @param path 文件路径(相对或绝对路径)
|
||||
* @param outContent 输出文件内容
|
||||
* @return 读取成功返回 true
|
||||
*/
|
||||
bool readTextFile(const std::string &path, std::string &outContent);
|
||||
|
||||
/**
|
||||
* @brief 写入文本文件
|
||||
* @param path 文件路径
|
||||
* @param content 要写入的内容
|
||||
* @param append 是否追加模式,默认覆盖
|
||||
* @return 写入成功返回 true
|
||||
*/
|
||||
bool writeTextFile(const std::string &path, const std::string &content,
|
||||
bool append = false);
|
||||
|
||||
/**
|
||||
* @brief 读取二进制文件
|
||||
* @param path 文件路径
|
||||
* @param outData 输出字节数组
|
||||
* @return 读取成功返回 true
|
||||
*/
|
||||
bool readBinaryFile(const std::string &path, std::vector<uint8> &outData);
|
||||
|
||||
/**
|
||||
* @brief 写入二进制文件
|
||||
* @param path 文件路径
|
||||
* @param data 要写入的字节数组
|
||||
* @param append 是否追加模式,默认覆盖
|
||||
* @return 写入成功返回 true
|
||||
*/
|
||||
bool writeBinaryFile(const std::string &path, const std::vector<uint8> &data,
|
||||
bool append = false);
|
||||
|
||||
/**
|
||||
* @brief 读取文本文件到字符串(便捷方法)
|
||||
* @param path 文件路径
|
||||
* @return 文件内容,失败返回 std::nullopt
|
||||
*/
|
||||
std::optional<std::string> readFileToString(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 读取二进制文件到字节数组(便捷方法)
|
||||
* @param path 文件路径
|
||||
* @return 字节数组,失败返回 std::nullopt
|
||||
*/
|
||||
std::optional<std::vector<uint8>> readFileToBytes(const std::string &path);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件/目录检查
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 检查路径是否存在
|
||||
* @param path 路径
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool exists(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 检查路径是否为目录
|
||||
* @param path 路径
|
||||
* @return 是目录返回 true
|
||||
*/
|
||||
bool isDirectory(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 检查路径是否为普通文件
|
||||
* @param path 路径
|
||||
* @return 是普通文件返回 true
|
||||
*/
|
||||
bool isRegularFile(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 检查文件或目录是否为空
|
||||
* @param path 路径
|
||||
* @return 为空返回 true
|
||||
*/
|
||||
bool isEmpty(const std::string &path) const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 目录操作
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 创建单层目录
|
||||
* @param path 目录路径
|
||||
* @return 创建成功返回 true
|
||||
*/
|
||||
bool createDirectory(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 递归创建多层目录
|
||||
* @param path 目录路径
|
||||
* @return 创建成功返回 true
|
||||
*/
|
||||
bool createDirectories(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 删除文件
|
||||
* @param path 文件路径
|
||||
* @return 删除成功返回 true
|
||||
*/
|
||||
bool removeFile(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 删除目录及其所有内容
|
||||
* @param path 目录路径
|
||||
* @return 删除成功返回 true
|
||||
*/
|
||||
bool removeDirectory(const std::string &path);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件操作
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 复制文件
|
||||
* @param from 源文件路径
|
||||
* @param to 目标文件路径
|
||||
* @param overwrite 是否覆盖已存在的文件
|
||||
* @return 复制成功返回 true
|
||||
*/
|
||||
bool copyFile(const std::string &from, const std::string &to,
|
||||
bool overwrite = false);
|
||||
|
||||
/**
|
||||
* @brief 移动文件
|
||||
* @param from 源文件路径
|
||||
* @param to 目标文件路径
|
||||
* @return 移动成功返回 true
|
||||
*/
|
||||
bool moveFile(const std::string &from, const std::string &to);
|
||||
|
||||
/**
|
||||
* @brief 重命名文件或目录
|
||||
* @param oldPath 原路径
|
||||
* @param newPath 新路径
|
||||
* @return 重命名成功返回 true
|
||||
*/
|
||||
bool rename(const std::string &oldPath, const std::string &newPath);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 目录遍历
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 列出目录中的所有文件
|
||||
* @param directoryPath 目录路径
|
||||
* @param recursive 是否递归遍历子目录
|
||||
* @return 文件路径列表
|
||||
*/
|
||||
std::vector<std::string> listFiles(const std::string &directoryPath,
|
||||
bool recursive = false);
|
||||
|
||||
/**
|
||||
* @brief 列出目录中的所有子目录
|
||||
* @param directoryPath 目录路径
|
||||
* @param recursive 是否递归遍历子目录
|
||||
* @return 子目录路径列表
|
||||
*/
|
||||
std::vector<std::string> listDirectories(const std::string &directoryPath,
|
||||
bool recursive = false);
|
||||
|
||||
/**
|
||||
* @brief 列出目录中的所有文件和子目录
|
||||
* @param directoryPath 目录路径
|
||||
* @param recursive 是否递归遍历子目录
|
||||
* @return 所有项目路径列表
|
||||
*/
|
||||
std::vector<std::string> listAll(const std::string &directoryPath,
|
||||
bool recursive = false);
|
||||
|
||||
/**
|
||||
* @brief 按扩展名列出文件
|
||||
* @param directoryPath 目录路径
|
||||
* @param extension 文件扩展名(如 ".png")
|
||||
* @param recursive 是否递归遍历子目录
|
||||
* @return 匹配的文件路径列表
|
||||
*/
|
||||
std::vector<std::string>
|
||||
listFilesWithExtension(const std::string &directoryPath,
|
||||
const std::string &extension, bool recursive = false);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件信息
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取文件详细信息
|
||||
* @param path 文件路径
|
||||
* @return 文件信息结构体
|
||||
*/
|
||||
FileInfo getFileInfo(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 获取文件大小
|
||||
* @param path 文件路径
|
||||
* @return 文件大小(字节)
|
||||
*/
|
||||
uint64 getFileSize(const std::string &path) const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 路径处理
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取文件名(包含扩展名)
|
||||
* @param path 文件路径
|
||||
* @return 文件名
|
||||
*/
|
||||
std::string getFileName(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件名(不含扩展名)
|
||||
* @param path 文件路径
|
||||
* @return 文件名
|
||||
*/
|
||||
std::string getFileNameWithoutExtension(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件扩展名
|
||||
* @param path 文件路径
|
||||
* @return 扩展名(包含点,如 ".txt")
|
||||
*/
|
||||
std::string getExtension(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取父目录路径
|
||||
* @param path 文件路径
|
||||
* @return 父目录路径
|
||||
*/
|
||||
std::string getParentPath(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取绝对路径
|
||||
* @param path 相对或绝对路径
|
||||
* @return 绝对路径
|
||||
*/
|
||||
std::string getAbsolutePath(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取规范路径(解析符号链接和相对路径)
|
||||
* @param path 路径
|
||||
* @return 规范化路径
|
||||
*/
|
||||
std::string getCanonicalPath(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 合并两个路径
|
||||
* @param left 左侧路径
|
||||
* @param right 右侧路径
|
||||
* @return 合并后的路径
|
||||
*/
|
||||
std::string combinePath(const std::string &left,
|
||||
const std::string &right) const;
|
||||
|
||||
/**
|
||||
* @brief 规范化路径(转换为系统首选格式)
|
||||
* @param path 路径
|
||||
* @return 规范化路径
|
||||
*/
|
||||
std::string normalizePath(const std::string &path) const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 工作目录
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取当前工作目录
|
||||
* @return 当前工作目录路径
|
||||
*/
|
||||
std::string getCurrentPath() const;
|
||||
|
||||
/**
|
||||
* @brief 设置当前工作目录
|
||||
* @param path 目录路径
|
||||
* @return 设置成功返回 true
|
||||
*/
|
||||
bool setCurrentPath(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 设置资源工作目录
|
||||
*
|
||||
* 设置后,所有相对路径操作都将基于此目录。
|
||||
*
|
||||
* @param path 工作目录路径
|
||||
*/
|
||||
void setWorkingDirectory(const std::string &path);
|
||||
|
||||
/**
|
||||
* @brief 获取资源工作目录
|
||||
* @return 工作目录路径
|
||||
*/
|
||||
const std::string &getWorkingDirectory() const;
|
||||
|
||||
/**
|
||||
* @brief 解析相对路径为完整路径
|
||||
* @param relativePath 相对路径
|
||||
* @return 完整路径
|
||||
*/
|
||||
std::string resolvePath(const std::string &relativePath) const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 资源根目录
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 设置资源根目录
|
||||
* @param root 资源根目录路径
|
||||
*/
|
||||
void setAssetRoot(const std::string &root);
|
||||
|
||||
/**
|
||||
* @brief 获取资源根目录
|
||||
* @return 资源根目录路径
|
||||
*/
|
||||
const std::string &getAssetRoot() const;
|
||||
|
||||
/**
|
||||
* @brief 解析资源相对路径
|
||||
*
|
||||
* 将相对路径解析为:工作目录/资源根目录/相对路径
|
||||
*
|
||||
* @param relativePath 相对路径
|
||||
* @return 完整路径
|
||||
*/
|
||||
std::string resolveAssetPath(const std::string &relativePath) const;
|
||||
|
||||
private:
|
||||
Asset() = default;
|
||||
~Asset() = default;
|
||||
|
||||
fs::path toPath(const std::string &path) const;
|
||||
std::string fromPath(const fs::path &path) const;
|
||||
std::string resolveFullPath(const std::string &path) const;
|
||||
|
||||
std::string workingDirectory_;
|
||||
std::string assetRoot_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,353 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 音频项类型枚举
|
||||
*/
|
||||
enum class AudioEntryType {
|
||||
None, ///< invalid
|
||||
Effect, ///< one-shot sound effect
|
||||
Ambient, ///< looping ambient track
|
||||
Music, ///< background music
|
||||
Random ///< random sound group
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 音效配置
|
||||
*/
|
||||
struct AudioEffect {
|
||||
std::string id; ///< 音效 ID
|
||||
std::string file; ///< 文件路径
|
||||
std::string subgroup; ///< 子组(可选)
|
||||
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
|
||||
int32 loopDelayRange = -1;///< 循环延迟范围(-1 表示未设置)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 音乐配置
|
||||
*/
|
||||
struct AudioMusic {
|
||||
std::string id; ///< 音乐 ID
|
||||
std::string file; ///< 文件路径
|
||||
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ?????????
|
||||
*/
|
||||
struct AudioAmbient {
|
||||
std::string id; ///< ?????ID
|
||||
std::string file; ///< ??????
|
||||
int32 loopDelay = -1; ///< ????????1 ?????????
|
||||
int32 loopDelayRange = -1;///< ???????????1 ?????????
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 随机音效项
|
||||
*/
|
||||
struct RandomItem {
|
||||
std::string tag; ///< 音效标签(ID)
|
||||
int32 probability = 0; ///< 概率权重
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 随机音效组
|
||||
*/
|
||||
struct AudioRandom {
|
||||
std::string id; ///< 随机组 ID
|
||||
std::vector<RandomItem> items; ///< 随机项列表
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 统一的音频条目(包含类型和数据)
|
||||
*/
|
||||
struct AudioEntry {
|
||||
AudioEntryType type = AudioEntryType::None; ///< 条目类型
|
||||
|
||||
const AudioEffect* effect = nullptr; ///< 音效指针(type == Effect 时有效)
|
||||
const AudioAmbient* ambient = nullptr;///< ???????????ype == Ambient ??????
|
||||
const AudioMusic* music = nullptr; ///< 音乐指针(type == Music 时有效)
|
||||
const AudioRandom* random = nullptr; ///< 随机组指针(type == Random 时有效)
|
||||
|
||||
/// @brief 判断是否有效
|
||||
bool isValid() const { return type != AudioEntryType::None; }
|
||||
|
||||
/// @brief 判断是否为音效
|
||||
bool isEffect() const { return type == AudioEntryType::Effect; }
|
||||
|
||||
/// @brief ????????????
|
||||
bool isAmbient() const { return type == AudioEntryType::Ambient; }
|
||||
|
||||
/// @brief 判断是否为音乐
|
||||
bool isMusic() const { return type == AudioEntryType::Music; }
|
||||
|
||||
/// @brief 判断是否为随机组
|
||||
bool isRandom() const { return type == AudioEntryType::Random; }
|
||||
|
||||
/// @brief 安全获取文件路径
|
||||
std::string getFilePath() const {
|
||||
if (type == AudioEntryType::Effect && effect)
|
||||
return effect->file;
|
||||
if (type == AudioEntryType::Ambient && ambient)
|
||||
return ambient->file;
|
||||
if (type == AudioEntryType::Music && music)
|
||||
return music->file;
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @brief 安全获取子组
|
||||
std::string getSubgroup() const {
|
||||
if (type == AudioEntryType::Effect && effect)
|
||||
return effect->subgroup;
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 音频数据库(单例)
|
||||
*
|
||||
* 用于解析和访问 audio.xml 文件中的音频配置。
|
||||
* 提供统一的查询接口,自动判断音频类型。
|
||||
*
|
||||
* @example
|
||||
* auto& audioDB = AudioDatabase::get();
|
||||
* if (audioDB.loadFromFile("audio.xml")) {
|
||||
* // 统一查询
|
||||
* if (auto entry = audioDB.get("GN_FINAL_SHOT_01")) {
|
||||
* // 使用 entry
|
||||
* }
|
||||
*
|
||||
* // 获取文件路径
|
||||
* if (auto filePath = audioDB.getFilePath("GN_FINAL_SHOT_01")) {
|
||||
* // 使用 *filePath
|
||||
* }
|
||||
*
|
||||
* // 获取音效(自动处理随机组)
|
||||
* if (auto soundId = audioDB.getSound("R_GN_FINAL_SHOT")) {
|
||||
* // 使用 *soundId
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class AudioDatabase {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 音频数据库实例引用
|
||||
*/
|
||||
static AudioDatabase& get();
|
||||
|
||||
AudioDatabase(const AudioDatabase&) = delete;
|
||||
AudioDatabase& operator=(const AudioDatabase&) = delete;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 加载和解析
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从文件加载音频数据库
|
||||
* @param path XML 文件路径
|
||||
* @return 加载成功返回 true
|
||||
*/
|
||||
bool loadFromFile(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 从字符串加载音频数据库
|
||||
* @param xmlContent XML 内容字符串
|
||||
* @return 加载成功返回 true
|
||||
*/
|
||||
bool loadFromString(const std::string& xmlContent);
|
||||
|
||||
/**
|
||||
* @brief 清空数据库
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 检查数据库是否已加载
|
||||
* @return 已加载返回 true
|
||||
*/
|
||||
bool isLoaded() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 统一查询接口
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 统一查询:输入任意 ID,自动判断类型
|
||||
* @param id 音频 ID(可以是 EFFECT、MUSIC 或 RANDOM 的 ID)
|
||||
* @return 音频条目,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<AudioEntry> get(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取音效 ID(自动处理 RANDOM 类型)
|
||||
*
|
||||
* 如果 id 是 RANDOM 类型,会按概率随机选择一个音效;
|
||||
* 如果 id 是 EFFECT 类型,直接返回该 ID。
|
||||
*
|
||||
* @param id 音效 ID 或随机组 ID
|
||||
* @return 音效 ID,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<std::string> getSound(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取文件路径
|
||||
*
|
||||
* 对于 EFFECT 和 MUSIC 类型,返回对应的文件路径;
|
||||
* 对于 RANDOM 类型,先随机选择一个音效再返回其文件路径。
|
||||
*
|
||||
* @param id 音频 ID
|
||||
* @return 文件路径,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<std::string> getFilePath(const std::string& id);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 类型特定查询(高级用法)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取音效配置
|
||||
* @param id 音效 ID
|
||||
* @return 音效配置指针,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<const AudioEffect*> getEffect(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief ????????????
|
||||
* @param id ?????ID
|
||||
* @return ????????????????????? std::nullopt
|
||||
*/
|
||||
std::optional<const AudioAmbient*> getAmbient(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取音乐配置
|
||||
* @param id 音乐 ID
|
||||
* @return 音乐配置指针,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<const AudioMusic*> getMusic(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取随机音效组配置
|
||||
* @param id 随机组 ID
|
||||
* @return 随机组配置指针,未找到返回 std::nullopt
|
||||
*/
|
||||
std::optional<const AudioRandom*> getRandom(const std::string& id);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 统计信息
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取音效数量
|
||||
*/
|
||||
size_t effectCount() const;
|
||||
|
||||
/**
|
||||
* @brief ???????????
|
||||
*/
|
||||
size_t ambientCount() const;
|
||||
|
||||
/**
|
||||
* @brief 获取音乐数量
|
||||
*/
|
||||
size_t musicCount() const;
|
||||
|
||||
/**
|
||||
* @brief 获取随机组数量
|
||||
*/
|
||||
size_t randomCount() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 便捷访问方法(推荐使用)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 检查 ID 是否存在
|
||||
* @param id 音频 ID
|
||||
* @return 存在返回 true
|
||||
*/
|
||||
bool has(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取文件路径(不存在返回空字符串)
|
||||
* @param id 音频 ID
|
||||
* @return 文件路径,未找到返回空字符串
|
||||
*/
|
||||
std::string filePath(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取音效 ID(自动处理随机组,不存在返回空字符串)
|
||||
* @param idOrRandomId 音效 ID 或随机组 ID
|
||||
* @return 音效 ID,未找到返回空字符串
|
||||
*/
|
||||
std::string soundId(const std::string& idOrRandomId);
|
||||
|
||||
/**
|
||||
* @brief 获取子组(EFFECT 的 SUBGROUP 属性)
|
||||
* @param id 音频 ID
|
||||
* @return 子组名,未找到或不存在返回空字符串
|
||||
*/
|
||||
std::string subgroup(const std::string& id);
|
||||
|
||||
/**
|
||||
* @brief 获取条目类型
|
||||
* @param id 音频 ID
|
||||
* @return 条目类型,未找到返回 AudioEntryType::None
|
||||
*/
|
||||
AudioEntryType typeOf(const std::string& id);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 带检查的访问方法
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 尝试获取文件路径,带成功标志
|
||||
* @param id 音频 ID
|
||||
* @param outPath 输出文件路径
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool tryGetFilePath(const std::string& id, std::string& outPath);
|
||||
|
||||
/**
|
||||
* @brief 尝试获取音效 ID,带成功标志
|
||||
* @param id 音效 ID 或随机组 ID
|
||||
* @param outSoundId 输出音效 ID
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool tryGetSoundId(const std::string& id, std::string& outSoundId);
|
||||
|
||||
private:
|
||||
AudioDatabase() = default;
|
||||
~AudioDatabase() = default;
|
||||
|
||||
/**
|
||||
* @brief 初始化随机数生成器
|
||||
*/
|
||||
void initializeRNG();
|
||||
|
||||
/**
|
||||
* @brief 从随机组中按概率选择一个音效
|
||||
* @param randomGroup 随机组配置
|
||||
* @return 选中的音效 ID
|
||||
*/
|
||||
std::string selectFromRandom(const AudioRandom& randomGroup);
|
||||
|
||||
std::unordered_map<std::string, AudioEffect> effectMap_; ///< 音效映射
|
||||
std::unordered_map<std::string, AudioAmbient> ambientMap_; ///< ????????
|
||||
std::unordered_map<std::string, AudioMusic> musicMap_; ///< 音乐映射
|
||||
std::unordered_map<std::string, AudioRandom> randomMap_; ///< 随机组映射
|
||||
|
||||
std::mt19937 rng_; ///< 随机数生成器
|
||||
bool rngInitialized_ = false; ///< 随机数生成器是否已初始化
|
||||
bool loaded_ = false;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class BinaryFileStreamReader {
|
||||
public:
|
||||
BinaryFileStreamReader() = default;
|
||||
explicit BinaryFileStreamReader(const std::string& filePath);
|
||||
~BinaryFileStreamReader();
|
||||
|
||||
bool open(const std::string& filePath);
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
|
||||
size_t tell() const;
|
||||
void seek(size_t pos);
|
||||
void skip(size_t count);
|
||||
bool eof() const;
|
||||
|
||||
size_t size() const;
|
||||
size_t remaining() const;
|
||||
size_t lastReadCount() const;
|
||||
|
||||
size_t read(char* buffer, size_t size);
|
||||
std::vector<uint8> readBytes(size_t size);
|
||||
|
||||
template <typename T>
|
||||
T read() {
|
||||
T value{};
|
||||
read(reinterpret_cast<char*>(&value), sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
int8 readInt8();
|
||||
int16 readInt16();
|
||||
int32 readInt32();
|
||||
int64 readInt64();
|
||||
uint8 readUInt8();
|
||||
uint16 readUInt16();
|
||||
uint32 readUInt32();
|
||||
uint64 readUInt64();
|
||||
float readFloat();
|
||||
double readDouble();
|
||||
|
||||
std::string readString(size_t length);
|
||||
std::string readNullTerminatedString(size_t maxLength = 4096);
|
||||
|
||||
private:
|
||||
std::ifstream stream_;
|
||||
std::string filePath_;
|
||||
size_t size_ = 0;
|
||||
size_t position_ = 0;
|
||||
size_t lastReadCount_ = 0;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,269 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 二进制文件读取器
|
||||
*
|
||||
* 将整个二进制文件加载到内存中,提供便捷的读取接口。
|
||||
* 支持各种基本类型的读取、定位操作和 CRC 解码。
|
||||
*
|
||||
* @example
|
||||
* BinaryReader reader("data.bin");
|
||||
* if (reader.isOpen()) {
|
||||
* int32 value = reader.read<int32>();
|
||||
* std::string str = reader.readString(10);
|
||||
* }
|
||||
*/
|
||||
class BinaryReader {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
BinaryReader() = default;
|
||||
|
||||
/**
|
||||
* @brief 从文件构造
|
||||
* @param filePath 文件路径
|
||||
*/
|
||||
explicit BinaryReader(const std::string& filePath);
|
||||
|
||||
/**
|
||||
* @brief 从内存数据构造
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
*/
|
||||
BinaryReader(const char* data, size_t size);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~BinaryReader() = default;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 文件操作
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 打开文件
|
||||
* @param filePath 文件路径
|
||||
* @return 打开成功返回 true
|
||||
*/
|
||||
bool open(const std::string& filePath);
|
||||
|
||||
/**
|
||||
* @brief 从内存加载数据
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
*/
|
||||
void loadFromMemory(const char* data, size_t size);
|
||||
|
||||
/**
|
||||
* @brief 关闭并清空数据
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否成功打开
|
||||
* @return 已打开返回 true
|
||||
*/
|
||||
bool isOpen() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 位置操作
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取当前读取位置
|
||||
* @return 当前位置(字节偏移)
|
||||
*/
|
||||
size_t tell() const;
|
||||
|
||||
/**
|
||||
* @brief 设置读取位置
|
||||
* @param pos 目标位置(字节偏移)
|
||||
*/
|
||||
void seek(size_t pos);
|
||||
|
||||
/**
|
||||
* @brief 跳过指定字节数
|
||||
* @param count 要跳过的字节数
|
||||
*/
|
||||
void skip(size_t count);
|
||||
|
||||
/**
|
||||
* @brief 检查是否已到达文件末尾
|
||||
* @return 到达末尾返回 true
|
||||
*/
|
||||
bool eof() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 信息获取
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取数据总大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
/**
|
||||
* @brief 获取剩余可读取的字节数
|
||||
* @return 剩余字节数
|
||||
*/
|
||||
size_t remaining() const;
|
||||
|
||||
/**
|
||||
* @brief 获取上一次读取的字节数
|
||||
* @return 上次读取的字节数
|
||||
*/
|
||||
size_t lastReadCount() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 原始数据读取
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 读取原始字节数据
|
||||
* @param buffer 输出缓冲区
|
||||
* @param size 要读取的字节数
|
||||
* @return 实际读取的字节数
|
||||
*/
|
||||
size_t read(char* buffer, size_t size);
|
||||
|
||||
/**
|
||||
* @brief 读取原始字节数据到 vector
|
||||
* @param size 要读取的字节数
|
||||
* @return 读取的数据
|
||||
*/
|
||||
std::vector<uint8> readBytes(size_t size);
|
||||
|
||||
/**
|
||||
* @brief 获取数据指针(不移动读取位置)
|
||||
* @return 当前位置的数据指针
|
||||
*/
|
||||
const char* data() const;
|
||||
|
||||
/**
|
||||
* @brief 获取当前位置的数据指针(不移动读取位置)
|
||||
* @return 当前位置的数据指针
|
||||
*/
|
||||
const char* currentData() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 类型化读取(模板方法)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 读取指定类型的值
|
||||
* @tparam T 要读取的类型
|
||||
* @return 读取的值
|
||||
*/
|
||||
template <typename T>
|
||||
T read() {
|
||||
T value;
|
||||
read(reinterpret_cast<char*>(&value), sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取 int8
|
||||
* @return 读取的值
|
||||
*/
|
||||
int8 readInt8();
|
||||
|
||||
/**
|
||||
* @brief 读取 int16
|
||||
* @return 读取的值
|
||||
*/
|
||||
int16 readInt16();
|
||||
|
||||
/**
|
||||
* @brief 读取 int32
|
||||
* @return 读取的值
|
||||
*/
|
||||
int32 readInt32();
|
||||
|
||||
/**
|
||||
* @brief 读取 int64
|
||||
* @return 读取的值
|
||||
*/
|
||||
int64 readInt64();
|
||||
|
||||
/**
|
||||
* @brief 读取 uint8
|
||||
* @return 读取的值
|
||||
*/
|
||||
uint8 readUInt8();
|
||||
|
||||
/**
|
||||
* @brief 读取 uint16
|
||||
* @return 读取的值
|
||||
*/
|
||||
uint16 readUInt16();
|
||||
|
||||
/**
|
||||
* @brief 读取 uint32
|
||||
* @return 读取的值
|
||||
*/
|
||||
uint32 readUInt32();
|
||||
|
||||
/**
|
||||
* @brief 读取 uint64
|
||||
* @return 读取的值
|
||||
*/
|
||||
uint64 readUInt64();
|
||||
|
||||
/**
|
||||
* @brief 读取 float
|
||||
* @return 读取的值
|
||||
*/
|
||||
float readFloat();
|
||||
|
||||
/**
|
||||
* @brief 读取 double
|
||||
* @return 读取的值
|
||||
*/
|
||||
double readDouble();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 字符串读取
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 读取指定长度的字符串
|
||||
* @param length 字符串长度(字节)
|
||||
* @return 读取的字符串
|
||||
*/
|
||||
std::string readString(size_t length);
|
||||
|
||||
/**
|
||||
* @brief 读取以 null 结尾的字符串
|
||||
* @return 读取的字符串
|
||||
*/
|
||||
std::string readNullTerminatedString();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CRC 解码
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief CRC 解码
|
||||
* @param length 要解码的数据长度(字节)
|
||||
* @param crc32 CRC32 值
|
||||
*/
|
||||
void crcDecode(size_t length, uint32 crc32);
|
||||
|
||||
private:
|
||||
std::vector<char> data_; ///< 存储的数据
|
||||
size_t position_ = 0; ///< 当前读取位置
|
||||
size_t lastReadCount_ = 0; ///< 上一次读取的字节数
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/resource/asset.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class BinaryFileStreamReader;
|
||||
|
||||
struct ImageFrame {
|
||||
int32 type = 0;
|
||||
int32 compressionType = 0;
|
||||
int32 width = 0;
|
||||
int32 height = 0;
|
||||
int32 xPos = 0;
|
||||
int32 yPos = 0;
|
||||
int32 frameXPos = 0;
|
||||
int32 frameYPos = 0;
|
||||
uint32 offset = 0;
|
||||
int32 size = 0;
|
||||
std::vector<uint8> data;
|
||||
};
|
||||
|
||||
struct ImgRef {
|
||||
std::string path;
|
||||
std::string npkFile;
|
||||
size_t frameCount = 0;
|
||||
uint32 offset = 0;
|
||||
uint32 size = 0;
|
||||
bool loaded = false;
|
||||
};
|
||||
|
||||
struct CachedImageData {
|
||||
std::vector<ImageFrame> frames;
|
||||
uint64 lastUseTime = 0;
|
||||
size_t memoryUsage = 0;
|
||||
};
|
||||
|
||||
class NpkArchive {
|
||||
public:
|
||||
static NpkArchive& get();
|
||||
|
||||
NpkArchive(const NpkArchive&) = delete;
|
||||
NpkArchive& operator=(const NpkArchive&) = delete;
|
||||
NpkArchive(NpkArchive&&) = delete;
|
||||
NpkArchive& operator=(NpkArchive&&) = delete;
|
||||
|
||||
void setImagePackDirectory(const std::string& dir);
|
||||
const std::string& getImagePackDirectory() const;
|
||||
|
||||
void init();
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
|
||||
bool hasImg(const std::string& path) const;
|
||||
std::optional<ImgRef> getImg(const std::string& path);
|
||||
std::vector<std::string> listImgs() const;
|
||||
|
||||
std::optional<ImageFrame> getImageFrame(const ImgRef& img, size_t index);
|
||||
size_t getFrameCount(const ImgRef& img) const;
|
||||
|
||||
void setCacheSize(size_t maxBytes);
|
||||
void clearCache();
|
||||
size_t getCacheUsage() const;
|
||||
|
||||
void setDefaultImg(const std::string& imgPath, size_t frameIndex = 0);
|
||||
const std::string& getDefaultImgPath() const;
|
||||
size_t getDefaultImgFrame() const;
|
||||
|
||||
private:
|
||||
enum class IndexCacheValidationMode : uint32 {
|
||||
TrustCache = 1,
|
||||
StrictSourceState = 2
|
||||
};
|
||||
|
||||
struct SourceFileState {
|
||||
std::string fileName;
|
||||
uint64 size = 0;
|
||||
int64 writeTime = 0;
|
||||
|
||||
bool operator==(const SourceFileState& other) const {
|
||||
return fileName == other.fileName && size == other.size &&
|
||||
writeTime == other.writeTime;
|
||||
}
|
||||
};
|
||||
|
||||
NpkArchive() = default;
|
||||
~NpkArchive() = default;
|
||||
|
||||
std::string normalizePath(const std::string& path) const;
|
||||
std::string getCacheFilePath() const;
|
||||
IndexCacheValidationMode getIndexCacheValidationMode() const;
|
||||
std::vector<SourceFileState> collectSourceFileStates() const;
|
||||
bool loadIndexCache(const std::vector<SourceFileState>& sourceFiles);
|
||||
bool saveIndexCache(const std::vector<SourceFileState>& sourceFiles) const;
|
||||
void scanNpkFiles();
|
||||
bool parseNpkFile(const std::string& npkPath);
|
||||
bool loadImgData(ImgRef& img);
|
||||
void parseColor(const uint8* tab, int type, uint8* saveByte, int offset);
|
||||
void evictCacheIfNeeded(size_t requiredSize);
|
||||
void updateCacheUsage(const std::string& imgPath);
|
||||
std::string readNpkInfoString(BinaryFileStreamReader& reader);
|
||||
|
||||
static const uint8 NPK_KEY[256];
|
||||
|
||||
std::string imagePackDirectory_ = "ImagePacks2";
|
||||
bool initialized_ = false;
|
||||
std::unordered_map<std::string, ImgRef> imgIndex_;
|
||||
std::unordered_map<std::string, CachedImageData> imageCache_;
|
||||
std::list<std::string> lruList_;
|
||||
std::unordered_map<std::string, std::list<std::string>::iterator> lruLookup_;
|
||||
size_t maxCacheSize_ = 512 * 1024 * 1024;
|
||||
size_t currentCacheSize_ = 0;
|
||||
std::string defaultImgPath_;
|
||||
size_t defaultImgFrame_ = 0;
|
||||
bool verboseFallbackLog_ =
|
||||
#ifndef NDEBUG
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/resource/binary_file_stream_reader.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief PVF ???????
|
||||
*/
|
||||
struct PvfFileInfo {
|
||||
size_t offset = 0; ///< ????????????
|
||||
uint32 crc32 = 0; ///< CRC32 ???
|
||||
size_t length = 0; ///< ????????
|
||||
bool decoded = false; ///< ?????
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ?????????
|
||||
*/
|
||||
struct RawData {
|
||||
std::unique_ptr<char[]> data; ///< ??????????????????
|
||||
size_t size; ///< ????????
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PVF ??????
|
||||
*
|
||||
* ??????? PVF ???????????
|
||||
* ??????????????????????????
|
||||
*/
|
||||
class PvfArchive {
|
||||
public:
|
||||
static PvfArchive& get();
|
||||
|
||||
PvfArchive(const PvfArchive&) = delete;
|
||||
PvfArchive& operator=(const PvfArchive&) = delete;
|
||||
PvfArchive(PvfArchive&&) = delete;
|
||||
PvfArchive& operator=(PvfArchive&&) = delete;
|
||||
|
||||
bool open(const std::string& filePath = "Script.pvf");
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
|
||||
void init();
|
||||
void initHeader();
|
||||
void initBinStringTable();
|
||||
void initLoadStrings();
|
||||
|
||||
bool hasFile(const std::string& path) const;
|
||||
std::optional<PvfFileInfo> getFileInfo(const std::string& path) const;
|
||||
std::vector<std::string> listFiles() const;
|
||||
|
||||
std::optional<std::string> getFileContent(const std::string& path);
|
||||
std::optional<std::vector<uint8>> getFileBytes(const std::string& path);
|
||||
std::optional<RawData> getFileRawData(const std::string& path);
|
||||
|
||||
std::optional<std::string> getBinString(int key) const;
|
||||
std::optional<std::string> getLoadString(const std::string& type,
|
||||
const std::string& key) const;
|
||||
bool hasBinString(int key) const;
|
||||
bool hasLoadString(const std::string& type, const std::string& key) const;
|
||||
|
||||
std::string normalizePath(const std::string& path) const;
|
||||
std::string resolvePath(const std::string& baseDir,
|
||||
const std::string& path) const;
|
||||
|
||||
private:
|
||||
PvfArchive() = default;
|
||||
~PvfArchive() = default;
|
||||
|
||||
std::vector<std::string> splitString(const std::string& str,
|
||||
const std::string& delimiter) const;
|
||||
bool decodeFile(const std::string& normalizedPath, PvfFileInfo& info);
|
||||
void clearInitData();
|
||||
std::optional<std::vector<uint8>> readArchiveBytes(size_t absoluteOffset,
|
||||
size_t length);
|
||||
std::optional<std::vector<uint8>> readDecodedFileBytes(
|
||||
const PvfFileInfo& info);
|
||||
static void crcDecodeBuffer(std::vector<uint8>& data, uint32 crc32);
|
||||
|
||||
BinaryFileStreamReader reader_;
|
||||
size_t dataStartPos_ = 0;
|
||||
std::map<std::string, PvfFileInfo> fileInfo_;
|
||||
std::map<int, std::string> binStringTable_;
|
||||
std::map<std::string, std::map<std::string, std::string>> loadStrings_;
|
||||
std::map<std::string, std::vector<uint8>> decodedFileCache_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <json/json.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief Save system runtime configuration.
|
||||
*/
|
||||
struct SaveSystemConfig {
|
||||
int32 slotCount = 10;
|
||||
std::string saveRoot = ".save";
|
||||
std::string indexFile = "index.json";
|
||||
std::string slotPrefix = "slot_";
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Optional display metadata stored in each slot document.
|
||||
*/
|
||||
struct SaveMeta {
|
||||
std::string displayName;
|
||||
double playTimeSeconds = 0.0;
|
||||
std::string userTag;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Cached per-slot summary used by save slot list UI.
|
||||
*/
|
||||
struct SaveSlotInfo {
|
||||
int32 slot = -1;
|
||||
bool exists = false;
|
||||
std::string updatedAtUtc;
|
||||
uint64 fileSize = 0;
|
||||
int32 schemaVersion = 1;
|
||||
SaveMeta meta;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Manual save/load service using Asset as the only filesystem backend.
|
||||
*/
|
||||
class SaveSystem {
|
||||
public:
|
||||
/**
|
||||
* @brief Get singleton instance.
|
||||
*/
|
||||
static SaveSystem& get();
|
||||
|
||||
SaveSystem(const SaveSystem&) = delete;
|
||||
SaveSystem& operator=(const SaveSystem&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Initialize save root, slot cache and index file.
|
||||
*/
|
||||
bool init(const SaveSystemConfig& config = SaveSystemConfig{});
|
||||
|
||||
/**
|
||||
* @brief Save payload into a fixed slot and update slot index metadata.
|
||||
*/
|
||||
bool saveSlot(int32 slot, const nlohmann::json& payload,
|
||||
const SaveMeta& meta = SaveMeta{});
|
||||
|
||||
/**
|
||||
* @brief Load payload object from a slot.
|
||||
*/
|
||||
std::optional<nlohmann::json> loadSlotPayload(int32 slot) const;
|
||||
|
||||
/**
|
||||
* @brief Query summary info for one slot.
|
||||
*/
|
||||
std::optional<SaveSlotInfo> getSlotInfo(int32 slot) const;
|
||||
|
||||
/**
|
||||
* @brief List summary info of all slots.
|
||||
*/
|
||||
std::vector<SaveSlotInfo> listSlots() const;
|
||||
|
||||
/**
|
||||
* @brief Delete a slot file and clear its index entry.
|
||||
*/
|
||||
bool deleteSlot(int32 slot);
|
||||
|
||||
/**
|
||||
* @brief Check whether slot has an existing save file.
|
||||
*/
|
||||
bool hasSlot(int32 slot) const;
|
||||
|
||||
private:
|
||||
SaveSystem() = default;
|
||||
~SaveSystem() = default;
|
||||
|
||||
bool isValidSlot(int32 slot) const;
|
||||
void resetSlots();
|
||||
bool ensureSaveDirectory() const;
|
||||
bool refreshSlotsFromDisk();
|
||||
bool tryReadSlotDocument(int32 slot, nlohmann::json& outDoc) const;
|
||||
bool writeIndexFile() const;
|
||||
|
||||
std::string indexPath() const;
|
||||
std::string slotPath(int32 slot) const;
|
||||
std::string slotFileName(int32 slot) const;
|
||||
|
||||
static std::string makeUtcTimestamp();
|
||||
static nlohmann::json toJson(const SaveMeta& meta);
|
||||
static SaveMeta fromJson(const nlohmann::json& json);
|
||||
|
||||
SaveSystemConfig config_;
|
||||
std::vector<SaveSlotInfo> slots_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,208 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/resource/pvf_archive.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief 脚本指令操作码枚举
|
||||
*/
|
||||
enum class ScriptOpcode : uint8 {
|
||||
Integer = 2, ///< 整数值
|
||||
Float = 4, ///< 浮点值
|
||||
StringRef5 = 5, ///< 二进制字符串引用(类型5)
|
||||
StringRef6 = 6, ///< 二进制字符串引用(类型6)
|
||||
StringRef7 = 7, ///< 二进制字符串引用(类型7)
|
||||
StringRef8 = 8, ///< 二进制字符串引用(类型8)
|
||||
ExtendedString9 = 9, ///< 扩展字符串(带额外字节)
|
||||
ExtendedString10 = 10, ///< 扩展字符串
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 脚本值类型
|
||||
*/
|
||||
enum class ScriptValueType {
|
||||
Empty, ///< 空值
|
||||
Integer, ///< 整数值
|
||||
Float, ///< 浮点值
|
||||
String, ///< 字符串值
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 脚本解析结果值
|
||||
*/
|
||||
struct ScriptValue {
|
||||
ScriptValueType type = ScriptValueType::Empty; ///< 值类型
|
||||
int32 intValue = 0; ///< 整数值(type == Integer 时有效)
|
||||
double floatValue = 0.0; ///< 浮点值(type == Float 时有效)
|
||||
std::string stringValue; ///< 字符串值(type == String 时有效)
|
||||
|
||||
/**
|
||||
* @brief 判断是否为空值
|
||||
*/
|
||||
bool isEmpty() const { return type == ScriptValueType::Empty; }
|
||||
|
||||
/**
|
||||
* @brief 转换为字符串表示
|
||||
*/
|
||||
std::string toString() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 脚本二进制数据解析器
|
||||
*
|
||||
* 用于解析 PVF 存档中的脚本二进制数据。
|
||||
* 每条指令为 5 字节:1 字节操作码 + 4 字节数据。
|
||||
* 支持与 PvfArchive 交互获取字符串资源。
|
||||
*
|
||||
* @example
|
||||
* auto& archive = PvfArchive::get();
|
||||
* if (auto rawData = archive.getFileRawData("script/example.bin")) {
|
||||
* ScriptParser parser(*rawData, "script/example.bin");
|
||||
*
|
||||
* // 迭代解析
|
||||
* while (!parser.isEnd()) {
|
||||
* if (auto value = parser.next()) {
|
||||
* // 处理 value
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // 或者一次性解析所有
|
||||
* auto allValues = parser.parseAll();
|
||||
* }
|
||||
*/
|
||||
class ScriptParser {
|
||||
public:
|
||||
/**
|
||||
* @brief 从 RawData 构造
|
||||
* @param data 原始数据
|
||||
* @param filePath 文件路径(用于提取文件类型)
|
||||
*/
|
||||
ScriptParser(const RawData& data, const std::string& filePath);
|
||||
|
||||
/**
|
||||
* @brief 从字节数组构造
|
||||
* @param data 字节数组
|
||||
* @param filePath 文件路径(用于提取文件类型)
|
||||
*/
|
||||
ScriptParser(const std::vector<uint8>& data, const std::string& filePath);
|
||||
|
||||
/**
|
||||
* @brief 从指针和大小构造
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @param filePath 文件路径(用于提取文件类型)
|
||||
*/
|
||||
ScriptParser(const char* data, size_t size, const std::string& filePath);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 迭代器风格 API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取下一个值
|
||||
* @return 解析的值,失败或结束返回 std::nullopt
|
||||
*/
|
||||
std::optional<ScriptValue> next();
|
||||
|
||||
/**
|
||||
* @brief 回退一个指令
|
||||
*/
|
||||
void back();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已到达数据末尾
|
||||
* @return 到达末尾返回 true
|
||||
*/
|
||||
bool isEnd() const;
|
||||
|
||||
/**
|
||||
* @brief 重置解析位置到开头
|
||||
*/
|
||||
void reset();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 批量解析
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 解析所有值
|
||||
* @return 所有解析值的列表
|
||||
*/
|
||||
std::vector<ScriptValue> parseAll();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 状态查询
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取当前解析位置
|
||||
* @return 当前位置(字节偏移)
|
||||
*/
|
||||
size_t position() const;
|
||||
|
||||
/**
|
||||
* @brief 获取数据总大小
|
||||
* @return 数据大小(字节)
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件路径
|
||||
* @return 文件路径
|
||||
*/
|
||||
const std::string& filePath() const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件类型(从路径提取)
|
||||
* @return 文件类型
|
||||
*/
|
||||
const std::string& fileType() const;
|
||||
|
||||
/**
|
||||
* @brief 检查解析器是否有效(有数据)
|
||||
* @return 有效返回 true
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 从文件路径提取文件类型
|
||||
* @param filePath 文件路径
|
||||
* @return 文件类型
|
||||
*/
|
||||
std::string extractFileType(const std::string& filePath) const;
|
||||
|
||||
/**
|
||||
* @brief 读取 1 字节
|
||||
* @param offset 偏移位置
|
||||
* @return 字节值
|
||||
*/
|
||||
uint8 readByte(size_t offset) const;
|
||||
|
||||
/**
|
||||
* @brief 读取 4 字节整数
|
||||
* @param offset 偏移位置
|
||||
* @return 整数值
|
||||
*/
|
||||
int32 readInt32(size_t offset) const;
|
||||
|
||||
/**
|
||||
* @brief 解析单个值
|
||||
* @param offset 偏移位置
|
||||
* @return 解析的值
|
||||
*/
|
||||
std::optional<ScriptValue> parseValueAt(size_t offset) const;
|
||||
|
||||
std::vector<char> data_; ///< 数据副本
|
||||
size_t position_ = 2; ///< 当前解析位置(从第 2 字节开始)
|
||||
std::string filePath_; ///< 文件路径
|
||||
std::string fileType_; ///< 文件类型
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <frostbite2D/resource/binary_reader.h>
|
||||
#include <frostbite2D/resource/asset.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class BinaryFileStreamReader;
|
||||
|
||||
struct AudioRef {
|
||||
std::string path;
|
||||
std::string npkFile;
|
||||
uint32 offset = 0;
|
||||
uint32 size = 0;
|
||||
bool loaded = false;
|
||||
};
|
||||
|
||||
struct CachedAudioData {
|
||||
std::vector<uint8> data;
|
||||
uint64 lastUseTime = 0;
|
||||
size_t memoryUsage = 0;
|
||||
};
|
||||
|
||||
class SoundPackArchive {
|
||||
public:
|
||||
static SoundPackArchive& get();
|
||||
|
||||
SoundPackArchive(const SoundPackArchive&) = delete;
|
||||
SoundPackArchive& operator=(const SoundPackArchive&) = delete;
|
||||
SoundPackArchive(SoundPackArchive&&) = delete;
|
||||
SoundPackArchive& operator=(SoundPackArchive&&) = delete;
|
||||
|
||||
void setSoundPackDirectory(const std::string& dir);
|
||||
const std::string& getSoundPackDirectory() const;
|
||||
|
||||
void init();
|
||||
void close();
|
||||
bool isOpen() const;
|
||||
|
||||
bool hasAudio(const std::string& path) const;
|
||||
std::optional<AudioRef> getAudio(const std::string& path);
|
||||
std::vector<std::string> listAudios() const;
|
||||
|
||||
std::optional<std::vector<uint8>> getAudioData(const AudioRef& audio);
|
||||
|
||||
void setCacheSize(size_t maxBytes);
|
||||
void clearCache();
|
||||
size_t getCacheUsage() const;
|
||||
|
||||
void setDefaultAudio(const std::string& audioPath);
|
||||
const std::string& getDefaultAudioPath() const;
|
||||
|
||||
private:
|
||||
SoundPackArchive() = default;
|
||||
~SoundPackArchive() = default;
|
||||
|
||||
std::string normalizePath(const std::string& path) const;
|
||||
void scanNpkFiles();
|
||||
bool parseNpkFile(const std::string& npkPath);
|
||||
bool loadAudioData(AudioRef& audio);
|
||||
void evictCacheIfNeeded(size_t requiredSize);
|
||||
void updateCacheUsage(const std::string& audioPath);
|
||||
std::string readNpkInfoString(BinaryFileStreamReader& reader);
|
||||
|
||||
static const uint8 NPK_KEY[256];
|
||||
|
||||
std::string soundPackDirectory_ = "SoundPacks";
|
||||
bool initialized_ = false;
|
||||
std::map<std::string, AudioRef> audioIndex_;
|
||||
std::map<std::string, CachedAudioData> audioCache_;
|
||||
std::list<std::string> lruList_;
|
||||
size_t maxCacheSize_ = 256 * 1024 * 1024;
|
||||
size_t currentCacheSize_ = 0;
|
||||
std::string defaultAudioPath_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/2d/actor.h>
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <frostbite2D/graphics/render_style.h>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Scene : public Actor {
|
||||
public:
|
||||
Scene();
|
||||
virtual ~Scene();
|
||||
|
||||
virtual void onEnter();
|
||||
virtual void onExit();
|
||||
|
||||
void Update(float deltaTime) override;
|
||||
void Render() override;
|
||||
|
||||
bool OnEvent(const Event& event) override;
|
||||
/**
|
||||
* @brief 为当前场景指定渲染风格预设
|
||||
*/
|
||||
void SetRenderStyleProfile(RenderStyleProfileId profile);
|
||||
/**
|
||||
* @brief 清除场景级渲染风格覆盖,回退到应用默认值
|
||||
*/
|
||||
void ClearRenderStyleProfileOverride();
|
||||
bool HasRenderStyleProfileOverride() const { return hasRenderStyleProfileOverride_; }
|
||||
/**
|
||||
* @brief 解析当前场景最终应使用的渲染风格
|
||||
*/
|
||||
RenderStyleProfileId ResolveRenderStyleProfile(
|
||||
RenderStyleProfileId defaultProfile) const;
|
||||
|
||||
static Scene* GetCurrent();
|
||||
|
||||
private:
|
||||
bool dispatchToChildren(const Event& event);
|
||||
|
||||
static Scene* current_;
|
||||
bool hasRenderStyleProfileOverride_ = false;
|
||||
RenderStyleProfileId renderStyleProfileOverride_ = RenderStyleProfileId::Hybrid2D;
|
||||
|
||||
friend class SceneManager;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/event/event.h>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <vector>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Scene;
|
||||
class UIScene;
|
||||
|
||||
class SceneManager {
|
||||
public:
|
||||
static SceneManager& get();
|
||||
|
||||
SceneManager(const SceneManager&) = delete;
|
||||
SceneManager& operator=(const SceneManager&) = delete;
|
||||
|
||||
void PushScene(Ptr<Scene> scene);
|
||||
void PopScene();
|
||||
void ReplaceScene(Ptr<Scene> scene);
|
||||
|
||||
void PushUIScene(Ptr<UIScene> scene);
|
||||
void PopUIScene();
|
||||
void ReplaceUIScene(Ptr<UIScene> scene);
|
||||
bool RemoveUIScene(UIScene* scene);
|
||||
void ClearUIScenes();
|
||||
void ClearAll();
|
||||
|
||||
void Update(float deltaTime);
|
||||
void UpdateUI(float deltaTime);
|
||||
void Render();
|
||||
|
||||
bool DispatchEvent(const Event& event);
|
||||
bool DispatchUIEvent(const Event& event);
|
||||
|
||||
Scene* GetCurrentScene() const;
|
||||
UIScene* GetCurrentUIScene() const;
|
||||
bool HasActiveScene() const;
|
||||
bool HasActiveUIScene() const;
|
||||
|
||||
private:
|
||||
SceneManager() = default;
|
||||
|
||||
std::vector<Ptr<Scene>> sceneStack_;
|
||||
std::vector<Ptr<UIScene>> uiSceneStack_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/graphics/camera.h>
|
||||
#include <frostbite2D/scene/scene.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class UIScene : public Scene {
|
||||
public:
|
||||
UIScene();
|
||||
~UIScene() override = default;
|
||||
|
||||
Camera* GetCamera() { return &camera_; }
|
||||
const Camera* GetCamera() const { return &camera_; }
|
||||
|
||||
private:
|
||||
Camera camera_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
// Backward-compatible forwarding header. Prefer quickjs_bridge_base.h.
|
||||
#include <frostbite2D/script/quickjs_bridge_base.h>
|
||||
@@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/2d/actor.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace qjs {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/**
|
||||
* @brief Basic bridge object between QuickJS and engine Vec2.
|
||||
*/
|
||||
class JsVec2 {
|
||||
public:
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
|
||||
JsVec2() = default;
|
||||
JsVec2(double xValue, double yValue) : x(xValue), y(yValue) {}
|
||||
explicit JsVec2(const Vec2& value) : x(value.x), y(value.y) {}
|
||||
|
||||
Vec2 toNative() const {
|
||||
return Vec2(static_cast<float>(x), static_cast<float>(y));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Basic bridge base between QuickJS and engine Actor.
|
||||
*
|
||||
* This type only exposes the smallest stable surface that all script-side
|
||||
* Actor wrappers need, so engine bindings and Game-side bindings can share it.
|
||||
*/
|
||||
class JsActor {
|
||||
public:
|
||||
JsActor() : actor_(MakePtr<Actor>()) {}
|
||||
explicit JsActor(Ptr<Actor> actor) : actor_(std::move(actor)) {
|
||||
if (!actor_) {
|
||||
actor_ = MakePtr<Actor>();
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetName() const { return actor_->GetName(); }
|
||||
void SetName(const std::string& name) { actor_->SetName(name); }
|
||||
|
||||
std::shared_ptr<JsVec2> GetPosition() const {
|
||||
return std::make_shared<JsVec2>(actor_->GetPosition());
|
||||
}
|
||||
void SetPosition(const std::shared_ptr<JsVec2>& position) {
|
||||
if (position) {
|
||||
actor_->SetPosition(position->toNative());
|
||||
}
|
||||
}
|
||||
void SetPositionXY(double x, double y) {
|
||||
actor_->SetPosition(static_cast<float>(x), static_cast<float>(y));
|
||||
}
|
||||
|
||||
double GetRotation() const { return actor_->GetRotation(); }
|
||||
void SetRotation(double rotation) {
|
||||
actor_->SetRotation(static_cast<float>(rotation));
|
||||
}
|
||||
|
||||
std::shared_ptr<JsVec2> GetScale() const {
|
||||
return std::make_shared<JsVec2>(actor_->GetScale());
|
||||
}
|
||||
void SetScale(const std::shared_ptr<JsVec2>& scale) {
|
||||
if (scale) {
|
||||
actor_->SetScale(scale->toNative());
|
||||
}
|
||||
}
|
||||
void SetScaleValue(double scale) {
|
||||
actor_->SetScale(static_cast<float>(scale));
|
||||
}
|
||||
|
||||
bool IsVisible() const { return actor_->IsVisible(); }
|
||||
void SetVisible(bool visible) { actor_->SetVisible(visible); }
|
||||
|
||||
void AddChild(const std::shared_ptr<JsActor>& child) {
|
||||
if (!actor_ || !child) {
|
||||
return;
|
||||
}
|
||||
actor_->AddChild(child->native());
|
||||
}
|
||||
|
||||
void RemoveAllChildren() {
|
||||
if (actor_) {
|
||||
actor_->RemoveAllChildren();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<JsVec2> GetSize() const {
|
||||
return std::make_shared<JsVec2>(actor_->GetSize());
|
||||
}
|
||||
void SetSize(const std::shared_ptr<JsVec2>& size) {
|
||||
if (size) {
|
||||
actor_->SetSize(size->toNative());
|
||||
}
|
||||
}
|
||||
void SetSizeWH(double width, double height) {
|
||||
actor_->SetSize(static_cast<float>(width), static_cast<float>(height));
|
||||
}
|
||||
|
||||
std::shared_ptr<JsVec2> GetAnchor() const {
|
||||
return std::make_shared<JsVec2>(actor_->GetAnchor());
|
||||
}
|
||||
void SetAnchor(const std::shared_ptr<JsVec2>& anchor) {
|
||||
if (anchor) {
|
||||
actor_->SetAnchor(anchor->toNative());
|
||||
}
|
||||
}
|
||||
void SetAnchorXY(double x, double y) {
|
||||
actor_->SetAnchor(static_cast<float>(x), static_cast<float>(y));
|
||||
}
|
||||
|
||||
std::shared_ptr<JsActor> GetParent() const;
|
||||
std::shared_ptr<JsVec2> GetTopLeftPosition() const;
|
||||
void SetTopLeftPosition(const std::shared_ptr<JsVec2>& position);
|
||||
void SetTopLeftPositionXY(double x, double y);
|
||||
std::shared_ptr<JsVec2> LocalToWorld(const std::shared_ptr<JsVec2>& point) const;
|
||||
std::shared_ptr<JsVec2> WorldToLocal(const std::shared_ptr<JsVec2>& point) const;
|
||||
bool ContainsWorldPoint(double x, double y) const;
|
||||
|
||||
bool IsEventReceiveEnabled() const {
|
||||
return actor_ && actor_->IsEventReceiveEnabled();
|
||||
}
|
||||
void EnableEventReceive() {
|
||||
if (actor_) {
|
||||
actor_->EnableEventReceive();
|
||||
}
|
||||
}
|
||||
void DisableEventReceive() {
|
||||
if (actor_) {
|
||||
actor_->DisableEventReceive();
|
||||
}
|
||||
}
|
||||
uint32 AddEventListener(const std::string& type,
|
||||
std::function<bool(qjs::Value)> callback);
|
||||
bool RemoveEventListener(uint32 listenerId);
|
||||
void ClearEventListeners();
|
||||
|
||||
const Ptr<Actor>& native() const { return actor_; }
|
||||
|
||||
protected:
|
||||
Ptr<Actor> actor_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <frostbite2D/event/event.h>
|
||||
|
||||
namespace qjs {
|
||||
class Runtime;
|
||||
class Context;
|
||||
class Value;
|
||||
} // namespace qjs
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class Actor;
|
||||
struct Vec2;
|
||||
|
||||
class QuickJSVM {
|
||||
public:
|
||||
using BindingInstaller = std::function<bool(qjs::Context&)>;
|
||||
|
||||
static QuickJSVM& get();
|
||||
|
||||
void setScriptDirectory(const std::string& path);
|
||||
void setEntryScript(const std::string& path);
|
||||
void setDebugMode(bool enable);
|
||||
|
||||
bool init();
|
||||
void shutdown();
|
||||
bool run();
|
||||
|
||||
bool isInitialized() const { return initialized_; }
|
||||
qjs::Context* getContext() const { return context_.get(); }
|
||||
const std::string& getScriptDirectory() const { return scriptDirectory_; }
|
||||
|
||||
bool runPendingJobs();
|
||||
void logException(const char* stage);
|
||||
|
||||
qjs::Value newObject();
|
||||
qjs::Value loadModule(const std::string& relativePath);
|
||||
qjs::Value loadModule(const std::string& relativePath,
|
||||
const std::string& cacheTag);
|
||||
qjs::Value construct(const qjs::Value& constructor, const qjs::Value& argument);
|
||||
qjs::Value callMethod(const qjs::Value& object, const char* name);
|
||||
qjs::Value callMethod(const qjs::Value& object, const char* name,
|
||||
const qjs::Value& argument);
|
||||
qjs::Value callMethod(const qjs::Value& object, const char* name,
|
||||
double argument);
|
||||
qjs::Value wrapActor(const Ptr<Actor>& actor);
|
||||
qjs::Value wrapVec2(const Vec2& value);
|
||||
qjs::Value wrapEvent(const Event& event);
|
||||
std::optional<EventType> parseEventTypeName(std::string_view typeName) const;
|
||||
void RegisterBindingInstaller(BindingInstaller installer);
|
||||
void RegisterBootstrapScript(const std::string& script);
|
||||
|
||||
private:
|
||||
QuickJSVM() = default;
|
||||
~QuickJSVM();
|
||||
|
||||
QuickJSVM(const QuickJSVM&) = delete;
|
||||
QuickJSVM& operator=(const QuickJSVM&) = delete;
|
||||
|
||||
bool registerBindings();
|
||||
bool installGlobals();
|
||||
void logScriptException(const char* stage);
|
||||
std::string buildEntryPath() const;
|
||||
|
||||
bool initialized_ = false;
|
||||
bool debugMode_ = false;
|
||||
std::string scriptDirectory_ = "assets/scripts";
|
||||
std::string entryScript_ = "main.js";
|
||||
uint64 nextEvalId_ = 1;
|
||||
std::vector<BindingInstaller> bindingInstallers_;
|
||||
std::vector<std::string> bootstrapScripts_;
|
||||
|
||||
std::unique_ptr<qjs::Runtime> runtime_;
|
||||
std::unique_ptr<qjs::Context> context_;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <frostbite2D/base/base.h>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 宏定义
|
||||
// ---------------------------------------------------------------------------
|
||||
#define E2D_CONCAT_IMPL(a, b) a##b
|
||||
#define E2D_CONCAT(a, b) E2D_CONCAT_IMPL(a, b)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 智能指针别名
|
||||
// ---------------------------------------------------------------------------
|
||||
template <typename T> using Ptr = RefPtr<T>;
|
||||
template <typename T> using SharedPtr = RefPtr<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 MakePtr<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
inline SharedPtr<T> makeShared(Args &&...args) {
|
||||
return MakePtr<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;
|
||||
|
||||
/**
|
||||
* @brief 平台类型枚举
|
||||
*/
|
||||
enum class PlatformType { Auto, Windows, Switch, Linux, macOS };
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
/// RGB 颜色(字节,每通道 0-255)
|
||||
struct Color3B {
|
||||
uint8_t r = 255;
|
||||
uint8_t g = 255;
|
||||
uint8_t b = 255;
|
||||
|
||||
constexpr Color3B() = default;
|
||||
constexpr Color3B(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {}
|
||||
|
||||
constexpr bool operator==(const Color3B &other) const {
|
||||
return r == other.r && g == other.g && b == other.b;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const Color3B &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
Color3B operator+(const Color3B &other) const {
|
||||
return Color3B(
|
||||
static_cast<uint8_t>(std::min(255, static_cast<int>(r) + other.r)),
|
||||
static_cast<uint8_t>(std::min(255, static_cast<int>(g) + other.g)),
|
||||
static_cast<uint8_t>(std::min(255, static_cast<int>(b) + other.b)));
|
||||
}
|
||||
|
||||
Color3B operator-(const Color3B &other) const {
|
||||
return Color3B(
|
||||
static_cast<uint8_t>(std::max(0, static_cast<int>(r) - other.r)),
|
||||
static_cast<uint8_t>(std::max(0, static_cast<int>(g) - other.g)),
|
||||
static_cast<uint8_t>(std::max(0, static_cast<int>(b) - other.b)));
|
||||
}
|
||||
};
|
||||
|
||||
/// 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
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,552 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <frostbite2D/types/type_alias.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 常量
|
||||
// ---------------------------------------------------------------------------
|
||||
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; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 角度工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 规范化角度到 [0, 360) 范围
|
||||
* @param degrees 输入角度(度数)
|
||||
* @return 规范化后的角度,范围 [0, 360)
|
||||
*/
|
||||
inline float normalizeAngle360(float degrees) {
|
||||
degrees = std::fmod(degrees, 360.0f);
|
||||
if (degrees < 0.0f) {
|
||||
degrees += 360.0f;
|
||||
}
|
||||
return degrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 规范化角度到 [-180, 180) 范围
|
||||
* @param degrees 输入角度(度数)
|
||||
* @return 规范化后的角度,范围 [-180, 180)
|
||||
*/
|
||||
inline float normalizeAngle180(float degrees) {
|
||||
degrees = std::fmod(degrees + 180.0f, 360.0f);
|
||||
if (degrees < 0.0f) {
|
||||
degrees += 360.0f;
|
||||
}
|
||||
return degrees - 180.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两个角度之间的最短差值
|
||||
* @param from 起始角度(度数)
|
||||
* @param to 目标角度(度数)
|
||||
* @return 从 from 到 to 的最短角度差,范围 [-180, 180]
|
||||
*/
|
||||
inline float angleDifference(float from, float to) {
|
||||
float diff = normalizeAngle360(to - from);
|
||||
if (diff > 180.0f) {
|
||||
diff -= 360.0f;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 线性插值角度
|
||||
* @param from 起始角度(度数)
|
||||
* @param to 目标角度(度数)
|
||||
* @param t 插值因子 [0, 1]
|
||||
* @return 插值后的角度
|
||||
*/
|
||||
inline float lerpAngle(float from, float to, float t) {
|
||||
return from + angleDifference(from, to) * t;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 向量工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 计算方向向量(从 from 指向 to 的单位向量)
|
||||
* @param from 起始点
|
||||
* @param to 目标点
|
||||
* @return 归一化的方向向量
|
||||
*/
|
||||
inline Vec2 direction(const Vec2 &from, const Vec2 &to) {
|
||||
return (to - from).normalized();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两点之间的角度
|
||||
* @param from 起始点
|
||||
* @param to 目标点
|
||||
* @return 角度(度数),范围 [-180, 180]
|
||||
*/
|
||||
inline float angleBetween(const Vec2 &from, const Vec2 &to) {
|
||||
Vec2 dir = to - from;
|
||||
return std::atan2(dir.y, dir.x) * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据角度创建方向向量
|
||||
* @param degrees 角度(度数),0度指向右方,逆时针为正
|
||||
* @return 单位方向向量
|
||||
*/
|
||||
inline Vec2 angleToVector(float degrees) {
|
||||
float rad = degrees * DEG_TO_RAD;
|
||||
return {std::cos(rad), std::sin(rad)};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将向量旋转指定角度
|
||||
* @param v 原始向量
|
||||
* @param degrees 旋转角度(度数),正值为逆时针旋转
|
||||
* @return 旋转后的向量
|
||||
*/
|
||||
inline Vec2 rotateVector(const Vec2 &v, float degrees) {
|
||||
float rad = degrees * DEG_TO_RAD;
|
||||
float cosA = std::cos(rad);
|
||||
float sinA = std::sin(rad);
|
||||
return {v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 坐标系转换工具
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Y轴向上坐标转Y轴向下坐标
|
||||
* @param pos Y轴向上坐标系中的位置
|
||||
* @param height 画布/屏幕高度
|
||||
* @return Y轴向下坐标系中的位置
|
||||
*/
|
||||
inline Vec2 flipY(const Vec2 &pos, float height) {
|
||||
return {pos.x, height - pos.y};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Y轴向下坐标转Y轴向上坐标
|
||||
* @param pos Y轴向下坐标系中的位置
|
||||
* @param height 画布/屏幕高度
|
||||
* @return Y轴向上坐标系中的位置
|
||||
*/
|
||||
inline Vec2 unflipY(const Vec2 &pos, float height) {
|
||||
return {pos.x, height - pos.y};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 矩阵工具函数
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从变换矩阵提取位置
|
||||
* @param matrix 4x4变换矩阵
|
||||
* @return 提取的位置向量
|
||||
*/
|
||||
inline Vec2 extractPosition(const glm::mat4 &matrix) {
|
||||
return {matrix[3][0], matrix[3][1]};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从变换矩阵提取缩放
|
||||
* @param matrix 4x4变换矩阵
|
||||
* @return 提取的缩放向量
|
||||
*/
|
||||
inline Vec2 extractScale(const glm::mat4 &matrix) {
|
||||
float scaleX =
|
||||
std::sqrt(matrix[0][0] * matrix[0][0] + matrix[0][1] * matrix[0][1]);
|
||||
float scaleY =
|
||||
std::sqrt(matrix[1][0] * matrix[1][0] + matrix[1][1] * matrix[1][1]);
|
||||
return {scaleX, scaleY};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从变换矩阵提取旋转角度
|
||||
* @param matrix 4x4变换矩阵
|
||||
* @return 提取的旋转角度(度数)
|
||||
*/
|
||||
inline float extractRotation(const glm::mat4 &matrix) {
|
||||
return std::atan2(matrix[0][1], matrix[0][0]) * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 碰撞检测工具
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 判断点是否在矩形内
|
||||
* @param point 要检测的点
|
||||
* @param rect 矩形区域
|
||||
* @return 如果点在矩形内返回 true,否则返回 false
|
||||
*/
|
||||
inline bool pointInRect(const Vec2 &point, const Rect &rect) {
|
||||
return point.x >= rect.left() && point.x <= rect.right() &&
|
||||
point.y >= rect.top() && point.y <= rect.bottom();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断点是否在圆内
|
||||
* @param point 要检测的点
|
||||
* @param center 圆心
|
||||
* @param radius 圆的半径
|
||||
* @return 如果点在圆内返回 true,否则返回 false
|
||||
*/
|
||||
inline bool pointInCircle(const Vec2 &point, const Vec2 ¢er, float radius) {
|
||||
float dx = point.x - center.x;
|
||||
float dy = point.y - center.y;
|
||||
return (dx * dx + dy * dy) <= (radius * radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断两个矩形是否相交
|
||||
* @param a 第一个矩形
|
||||
* @param b 第二个矩形
|
||||
* @return 如果矩形相交返回 true,否则返回 false
|
||||
*/
|
||||
inline bool rectsIntersect(const Rect &a, const Rect &b) {
|
||||
return a.intersects(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断两个圆是否相交
|
||||
* @param center1 第一个圆的圆心
|
||||
* @param radius1 第一个圆的半径
|
||||
* @param center2 第二个圆的圆心
|
||||
* @param radius2 第二个圆的半径
|
||||
* @return 如果圆相交返回 true,否则返回 false
|
||||
*/
|
||||
inline bool circlesIntersect(const Vec2 ¢er1, float radius1,
|
||||
const Vec2 ¢er2, float radius2) {
|
||||
float dx = center2.x - center1.x;
|
||||
float dy = center2.y - center1.y;
|
||||
float distSq = dx * dx + dy * dy;
|
||||
float radiusSum = radius1 + radius2;
|
||||
return distSq <= (radiusSum * radiusSum);
|
||||
}
|
||||
|
||||
} // namespace math
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class UUID {
|
||||
public:
|
||||
static UUID generate();
|
||||
static UUID fromString(const std::string& str);
|
||||
std::string toString() const;
|
||||
|
||||
UUID() = default;
|
||||
|
||||
bool operator==(const UUID& other) const { return data_ == other.data_; }
|
||||
bool operator!=(const UUID& other) const { return data_ != other.data_; }
|
||||
|
||||
bool isValid() const {
|
||||
for (auto byte : data_) {
|
||||
if (byte != 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::array<uint8_t, 16>& data() const { return data_; }
|
||||
|
||||
private:
|
||||
explicit UUID(const std::array<uint8_t, 16>& data) : data_(data) {}
|
||||
|
||||
std::array<uint8_t, 16> data_{};
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
@@ -0,0 +1,395 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <cassert>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
template <typename _PtrTy>
|
||||
class IntrusiveListValue;
|
||||
|
||||
template <typename _PtrTy>
|
||||
class IntrusiveList {
|
||||
public:
|
||||
using value_type = typename std::pointer_traits<_PtrTy>::pointer;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
|
||||
IntrusiveList()
|
||||
: first_(nullptr)
|
||||
, last_(nullptr) {
|
||||
}
|
||||
|
||||
~IntrusiveList() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
const value_type& GetFirst() const {
|
||||
return first_;
|
||||
}
|
||||
|
||||
value_type& GetFirst() {
|
||||
return first_;
|
||||
}
|
||||
|
||||
const value_type& GetLast() const {
|
||||
return last_;
|
||||
}
|
||||
|
||||
value_type& GetLast() {
|
||||
return last_;
|
||||
}
|
||||
|
||||
inline bool IsEmpty() const {
|
||||
return first_ == nullptr;
|
||||
}
|
||||
|
||||
void PushBack(reference child) {
|
||||
if (child->GetPrev()) {
|
||||
child->GetPrev()->GetNext() = child->GetNext();
|
||||
}
|
||||
if (child->GetNext()) {
|
||||
child->GetNext()->GetPrev() = child->GetPrev();
|
||||
}
|
||||
|
||||
child->GetPrev() = last_;
|
||||
child->GetNext() = nullptr;
|
||||
|
||||
if (first_) {
|
||||
last_->GetNext() = child;
|
||||
} else {
|
||||
first_ = child;
|
||||
}
|
||||
|
||||
last_ = child;
|
||||
}
|
||||
|
||||
void PushFront(reference child) {
|
||||
if (child->GetPrev()) {
|
||||
child->GetPrev()->GetNext() = child->GetNext();
|
||||
}
|
||||
if (child->GetNext()) {
|
||||
child->GetNext()->GetPrev() = child->GetPrev();
|
||||
}
|
||||
|
||||
child->GetPrev() = nullptr;
|
||||
child->GetNext() = first_;
|
||||
|
||||
if (first_) {
|
||||
first_->GetPrev() = child;
|
||||
} else {
|
||||
last_ = child;
|
||||
}
|
||||
|
||||
first_ = child;
|
||||
}
|
||||
|
||||
void InsertBefore(reference child, reference before) {
|
||||
if (child->GetPrev()) {
|
||||
child->GetPrev()->GetNext() = child->GetNext();
|
||||
}
|
||||
if (child->GetNext()) {
|
||||
child->GetNext()->GetPrev() = child->GetPrev();
|
||||
}
|
||||
|
||||
if (before->GetPrev()) {
|
||||
before->GetPrev()->GetNext() = child;
|
||||
} else {
|
||||
first_ = child;
|
||||
}
|
||||
|
||||
child->GetPrev() = before->GetPrev();
|
||||
child->GetNext() = before;
|
||||
before->GetPrev() = child;
|
||||
}
|
||||
|
||||
void InsertAfter(reference child, reference after) {
|
||||
if (child->GetPrev()) {
|
||||
child->GetPrev()->GetNext() = child->GetNext();
|
||||
}
|
||||
if (child->GetNext()) {
|
||||
child->GetNext()->GetPrev() = child->GetPrev();
|
||||
}
|
||||
|
||||
if (after->GetNext()) {
|
||||
after->GetNext()->GetPrev() = child;
|
||||
} else {
|
||||
last_ = child;
|
||||
}
|
||||
|
||||
child->GetNext() = after->GetNext();
|
||||
child->GetPrev() = after;
|
||||
after->GetNext() = child;
|
||||
}
|
||||
|
||||
void Remove(reference child) {
|
||||
if (child->GetNext()) {
|
||||
child->GetNext()->GetPrev() = child->GetPrev();
|
||||
} else {
|
||||
last_ = child->GetPrev();
|
||||
}
|
||||
|
||||
if (child->GetPrev()) {
|
||||
child->GetPrev()->GetNext() = child->GetNext();
|
||||
} else {
|
||||
first_ = child->GetNext();
|
||||
}
|
||||
|
||||
child->GetPrev() = nullptr;
|
||||
child->GetNext() = nullptr;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
value_type p = first_;
|
||||
while (p) {
|
||||
value_type tmp = p;
|
||||
p = p->GetNext();
|
||||
if (tmp) {
|
||||
tmp->GetNext() = nullptr;
|
||||
tmp->GetPrev() = nullptr;
|
||||
}
|
||||
}
|
||||
first_ = nullptr;
|
||||
last_ = nullptr;
|
||||
}
|
||||
|
||||
bool CheckValid() {
|
||||
if (!first_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
|
||||
value_type p = first_;
|
||||
value_type tmp = p;
|
||||
do {
|
||||
tmp = p;
|
||||
p = p->GetNext();
|
||||
++pos;
|
||||
|
||||
if (p) {
|
||||
if (p->GetPrev() != tmp) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (tmp != last_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while (p);
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename _IterPtrTy>
|
||||
struct Iterator {
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using value_type = _IterPtrTy;
|
||||
using pointer = _IterPtrTy*;
|
||||
using reference = _IterPtrTy&;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
inline Iterator(value_type ptr = nullptr, bool is_end = false)
|
||||
: base_(ptr)
|
||||
, is_end_(is_end) {
|
||||
}
|
||||
|
||||
inline reference operator*() const {
|
||||
assert(base_ && !is_end_);
|
||||
return const_cast<reference>(base_);
|
||||
}
|
||||
|
||||
inline pointer operator->() const {
|
||||
return std::pointer_traits<pointer>::pointer_to(**this);
|
||||
}
|
||||
|
||||
inline Iterator& operator++() {
|
||||
assert(base_ && !is_end_);
|
||||
value_type next = base_->GetNext();
|
||||
if (next) {
|
||||
base_ = next;
|
||||
} else {
|
||||
is_end_ = true;
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
|
||||
inline Iterator operator++(int) {
|
||||
Iterator old = (*this);
|
||||
++(*this);
|
||||
return old;
|
||||
}
|
||||
|
||||
inline Iterator& operator--() {
|
||||
assert(base_);
|
||||
if (is_end_) {
|
||||
is_end_ = false;
|
||||
} else {
|
||||
base_ = base_->GetPrev();
|
||||
}
|
||||
return (*this);
|
||||
}
|
||||
|
||||
inline Iterator operator--(int) {
|
||||
Iterator old = (*this);
|
||||
--(*this);
|
||||
return old;
|
||||
}
|
||||
|
||||
inline bool operator==(const Iterator& other) const {
|
||||
return base_ == other.base_ && is_end_ == other.is_end_;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Iterator& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
inline operator bool() const {
|
||||
return base_ != nullptr && !is_end_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_end_;
|
||||
|
||||
typename std::remove_const<value_type>::type base_;
|
||||
};
|
||||
|
||||
public:
|
||||
using iterator = Iterator<value_type>;
|
||||
using const_iterator = Iterator<const value_type>;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
inline iterator begin() {
|
||||
return iterator(first_, first_ == nullptr);
|
||||
}
|
||||
|
||||
inline const_iterator begin() const {
|
||||
return const_iterator(first_, first_ == nullptr);
|
||||
}
|
||||
|
||||
inline const_iterator cbegin() const {
|
||||
return begin();
|
||||
}
|
||||
|
||||
inline iterator end() {
|
||||
return iterator(last_, true);
|
||||
}
|
||||
|
||||
inline const_iterator end() const {
|
||||
return const_iterator(last_, true);
|
||||
}
|
||||
|
||||
inline const_iterator cend() const {
|
||||
return end();
|
||||
}
|
||||
|
||||
inline reverse_iterator rbegin() {
|
||||
return reverse_iterator(end());
|
||||
}
|
||||
|
||||
inline const_reverse_iterator rbegin() const {
|
||||
return const_reverse_iterator(end());
|
||||
}
|
||||
|
||||
inline const_reverse_iterator crbegin() const {
|
||||
return rbegin();
|
||||
}
|
||||
|
||||
inline reverse_iterator rend() {
|
||||
return reverse_iterator(begin());
|
||||
}
|
||||
|
||||
inline const_reverse_iterator rend() const {
|
||||
return const_reverse_iterator(begin());
|
||||
}
|
||||
|
||||
inline const_reverse_iterator crend() const {
|
||||
return rend();
|
||||
}
|
||||
|
||||
inline value_type& front() {
|
||||
if (IsEmpty()) {
|
||||
throw std::out_of_range("front() called on empty list");
|
||||
}
|
||||
return first_;
|
||||
}
|
||||
|
||||
inline const value_type& front() const {
|
||||
if (IsEmpty()) {
|
||||
throw std::out_of_range("front() called on empty list");
|
||||
}
|
||||
return first_;
|
||||
}
|
||||
|
||||
inline value_type& back() {
|
||||
if (IsEmpty()) {
|
||||
throw std::out_of_range("back() called on empty list");
|
||||
}
|
||||
return last_;
|
||||
}
|
||||
|
||||
inline const value_type& back() const {
|
||||
if (IsEmpty()) {
|
||||
throw std::out_of_range("back() called on empty list");
|
||||
}
|
||||
return last_;
|
||||
}
|
||||
|
||||
private:
|
||||
value_type first_;
|
||||
value_type last_;
|
||||
|
||||
template <typename T>
|
||||
friend class IntrusiveListValue;
|
||||
};
|
||||
|
||||
template <typename _PtrTy>
|
||||
class IntrusiveListValue {
|
||||
public:
|
||||
using value_type = typename std::pointer_traits<_PtrTy>::pointer;
|
||||
using reference = value_type&;
|
||||
using pointer = value_type*;
|
||||
|
||||
IntrusiveListValue()
|
||||
: prev_(nullptr)
|
||||
, next_(nullptr) {
|
||||
}
|
||||
|
||||
IntrusiveListValue(value_type rhs)
|
||||
: prev_(nullptr)
|
||||
, next_(nullptr) {
|
||||
if (rhs) {
|
||||
prev_ = rhs->GetPrev();
|
||||
next_ = rhs->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
const value_type& GetPrev() const {
|
||||
return prev_;
|
||||
}
|
||||
|
||||
value_type& GetPrev() {
|
||||
return prev_;
|
||||
}
|
||||
|
||||
const value_type& GetNext() const {
|
||||
return next_;
|
||||
}
|
||||
|
||||
value_type& GetNext() {
|
||||
return next_;
|
||||
}
|
||||
|
||||
private:
|
||||
value_type prev_;
|
||||
value_type next_;
|
||||
|
||||
template <typename T>
|
||||
friend class IntrusiveList;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace frostbite2D {
|
||||
|
||||
class StartupTrace {
|
||||
public:
|
||||
static constexpr bool enabled() {
|
||||
#ifndef NDEBUG
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void reset(const char* label = nullptr) {
|
||||
if (!enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Uint64 now = SDL_GetPerformanceCounter();
|
||||
std::string line;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex());
|
||||
startCounter() = now;
|
||||
if (label && label[0] != '\0') {
|
||||
line = std::string("[Startup] reset: ") + label;
|
||||
clearLogFileUnlocked();
|
||||
writeLineUnlocked(line);
|
||||
} else {
|
||||
clearLogFileUnlocked();
|
||||
}
|
||||
}
|
||||
if (!line.empty()) {
|
||||
SDL_Log("%s", line.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static double totalElapsedMs() {
|
||||
if (!enabled()) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Uint64 start = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex());
|
||||
start = startCounter();
|
||||
}
|
||||
|
||||
if (start == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Uint64 now = SDL_GetPerformanceCounter();
|
||||
Uint64 frequency = SDL_GetPerformanceFrequency();
|
||||
if (frequency == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return static_cast<double>(now - start) * 1000.0 /
|
||||
static_cast<double>(frequency);
|
||||
}
|
||||
|
||||
static void mark(const char* label) {
|
||||
if (!enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[256] = {};
|
||||
SDL_snprintf(buffer, sizeof(buffer),
|
||||
"[Startup] %s reached at %.2f ms (thread=%u)",
|
||||
label ? label : "<unnamed>", totalElapsedMs(), SDL_ThreadID());
|
||||
logLine(buffer);
|
||||
SDL_Log("%s", buffer);
|
||||
}
|
||||
|
||||
static void logLine(const std::string& line) {
|
||||
if (!enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex());
|
||||
writeLineUnlocked(line);
|
||||
}
|
||||
|
||||
private:
|
||||
static Uint64& startCounter() {
|
||||
static Uint64 counter = 0;
|
||||
return counter;
|
||||
}
|
||||
|
||||
static std::mutex& mutex() {
|
||||
static std::mutex value;
|
||||
return value;
|
||||
}
|
||||
|
||||
static const char* logFilePath() {
|
||||
return "startup_trace.log";
|
||||
}
|
||||
|
||||
static void clearLogFileUnlocked() {
|
||||
std::ofstream stream(logFilePath(), std::ios::trunc);
|
||||
}
|
||||
|
||||
static void writeLineUnlocked(const std::string& line) {
|
||||
std::ofstream stream(logFilePath(), std::ios::app);
|
||||
if (!stream) {
|
||||
return;
|
||||
}
|
||||
stream << line << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
class ScopedStartupTrace {
|
||||
public:
|
||||
explicit ScopedStartupTrace(const char* label)
|
||||
: label_(label ? label : "<unnamed>"),
|
||||
startCounter_(SDL_GetPerformanceCounter()) {}
|
||||
|
||||
~ScopedStartupTrace() {
|
||||
if (!StartupTrace::enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Uint64 now = SDL_GetPerformanceCounter();
|
||||
Uint64 frequency = SDL_GetPerformanceFrequency();
|
||||
double elapsedMs = 0.0;
|
||||
if (frequency != 0) {
|
||||
elapsedMs = static_cast<double>(now - startCounter_) * 1000.0 /
|
||||
static_cast<double>(frequency);
|
||||
}
|
||||
|
||||
char buffer[256] = {};
|
||||
SDL_snprintf(buffer, sizeof(buffer),
|
||||
"[Startup] %s took %.2f ms (total %.2f ms, thread=%u)",
|
||||
label_, elapsedMs, StartupTrace::totalElapsedMs(),
|
||||
SDL_ThreadID());
|
||||
StartupTrace::logLine(buffer);
|
||||
SDL_Log("%s", buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* label_;
|
||||
Uint64 startCounter_ = 0;
|
||||
};
|
||||
|
||||
} // namespace frostbite2D
|
||||
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
@@ -0,0 +1,174 @@
|
||||
#ifndef RAPIDXML_ITERATORS_HPP_INCLUDED
|
||||
#define RAPIDXML_ITERATORS_HPP_INCLUDED
|
||||
|
||||
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||
// Version 1.13
|
||||
// Revision $DateTime: 2009/05/13 01:46:17 $
|
||||
//! \file rapidxml_iterators.hpp This file contains rapidxml iterators
|
||||
|
||||
#include "rapidxml.hpp"
|
||||
|
||||
namespace rapidxml
|
||||
{
|
||||
|
||||
//! Iterator of child nodes of xml_node
|
||||
template<class Ch>
|
||||
class node_iterator
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
typedef typename xml_node<Ch> value_type;
|
||||
typedef typename xml_node<Ch> &reference;
|
||||
typedef typename xml_node<Ch> *pointer;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef std::bidirectional_iterator_tag iterator_category;
|
||||
|
||||
node_iterator()
|
||||
: m_node(0)
|
||||
{
|
||||
}
|
||||
|
||||
node_iterator(xml_node<Ch> *node)
|
||||
: m_node(node->first_node())
|
||||
{
|
||||
}
|
||||
|
||||
reference operator *() const
|
||||
{
|
||||
assert(m_node);
|
||||
return *m_node;
|
||||
}
|
||||
|
||||
pointer operator->() const
|
||||
{
|
||||
assert(m_node);
|
||||
return m_node;
|
||||
}
|
||||
|
||||
node_iterator& operator++()
|
||||
{
|
||||
assert(m_node);
|
||||
m_node = m_node->next_sibling();
|
||||
return *this;
|
||||
}
|
||||
|
||||
node_iterator operator++(int)
|
||||
{
|
||||
node_iterator tmp = *this;
|
||||
++this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
node_iterator& operator--()
|
||||
{
|
||||
assert(m_node && m_node->previous_sibling());
|
||||
m_node = m_node->previous_sibling();
|
||||
return *this;
|
||||
}
|
||||
|
||||
node_iterator operator--(int)
|
||||
{
|
||||
node_iterator tmp = *this;
|
||||
++this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator ==(const node_iterator<Ch> &rhs)
|
||||
{
|
||||
return m_node == rhs.m_node;
|
||||
}
|
||||
|
||||
bool operator !=(const node_iterator<Ch> &rhs)
|
||||
{
|
||||
return m_node != rhs.m_node;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
xml_node<Ch> *m_node;
|
||||
|
||||
};
|
||||
|
||||
//! Iterator of child attributes of xml_node
|
||||
template<class Ch>
|
||||
class attribute_iterator
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
typedef typename xml_attribute<Ch> value_type;
|
||||
typedef typename xml_attribute<Ch> &reference;
|
||||
typedef typename xml_attribute<Ch> *pointer;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef std::bidirectional_iterator_tag iterator_category;
|
||||
|
||||
attribute_iterator()
|
||||
: m_attribute(0)
|
||||
{
|
||||
}
|
||||
|
||||
attribute_iterator(xml_node<Ch> *node)
|
||||
: m_attribute(node->first_attribute())
|
||||
{
|
||||
}
|
||||
|
||||
reference operator *() const
|
||||
{
|
||||
assert(m_attribute);
|
||||
return *m_attribute;
|
||||
}
|
||||
|
||||
pointer operator->() const
|
||||
{
|
||||
assert(m_attribute);
|
||||
return m_attribute;
|
||||
}
|
||||
|
||||
attribute_iterator& operator++()
|
||||
{
|
||||
assert(m_attribute);
|
||||
m_attribute = m_attribute->next_attribute();
|
||||
return *this;
|
||||
}
|
||||
|
||||
attribute_iterator operator++(int)
|
||||
{
|
||||
attribute_iterator tmp = *this;
|
||||
++this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
attribute_iterator& operator--()
|
||||
{
|
||||
assert(m_attribute && m_attribute->previous_attribute());
|
||||
m_attribute = m_attribute->previous_attribute();
|
||||
return *this;
|
||||
}
|
||||
|
||||
attribute_iterator operator--(int)
|
||||
{
|
||||
attribute_iterator tmp = *this;
|
||||
++this;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator ==(const attribute_iterator<Ch> &rhs)
|
||||
{
|
||||
return m_attribute == rhs.m_attribute;
|
||||
}
|
||||
|
||||
bool operator !=(const attribute_iterator<Ch> &rhs)
|
||||
{
|
||||
return m_attribute != rhs.m_attribute;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
xml_attribute<Ch> *m_attribute;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,421 @@
|
||||
#ifndef RAPIDXML_PRINT_HPP_INCLUDED
|
||||
#define RAPIDXML_PRINT_HPP_INCLUDED
|
||||
|
||||
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||
// Version 1.13
|
||||
// Revision $DateTime: 2009/05/13 01:46:17 $
|
||||
//! \file rapidxml_print.hpp This file contains rapidxml printer implementation
|
||||
|
||||
#include "rapidxml.hpp"
|
||||
|
||||
// Only include streams if not disabled
|
||||
#ifndef RAPIDXML_NO_STREAMS
|
||||
#include <ostream>
|
||||
#include <iterator>
|
||||
#endif
|
||||
|
||||
namespace rapidxml
|
||||
{
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Printing flags
|
||||
|
||||
const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function.
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Internal
|
||||
|
||||
//! \cond internal
|
||||
namespace internal
|
||||
{
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Internal character operations
|
||||
|
||||
// Copy characters from given range to given output iterator
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out)
|
||||
{
|
||||
while (begin != end)
|
||||
*out++ = *begin++;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Copy characters from given range to given output iterator and expand
|
||||
// characters into references (< > ' " &)
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out)
|
||||
{
|
||||
while (begin != end)
|
||||
{
|
||||
if (*begin == noexpand)
|
||||
{
|
||||
*out++ = *begin; // No expansion, copy character
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (*begin)
|
||||
{
|
||||
case Ch('<'):
|
||||
*out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||
break;
|
||||
case Ch('>'):
|
||||
*out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||
break;
|
||||
case Ch('\''):
|
||||
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';');
|
||||
break;
|
||||
case Ch('"'):
|
||||
*out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||
break;
|
||||
case Ch('&'):
|
||||
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';');
|
||||
break;
|
||||
default:
|
||||
*out++ = *begin; // No expansion, copy character
|
||||
}
|
||||
}
|
||||
++begin; // Step to next character
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fill given output iterator with repetitions of the same character
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt fill_chars(OutIt out, int n, Ch ch)
|
||||
{
|
||||
for (int i = 0; i < n; ++i)
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Find character
|
||||
template<class Ch, Ch ch>
|
||||
inline bool find_char(const Ch *begin, const Ch *end)
|
||||
{
|
||||
while (begin != end)
|
||||
if (*begin++ == ch)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Internal printing operations
|
||||
|
||||
// Print node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
// Print proper node type
|
||||
switch (node->type())
|
||||
{
|
||||
|
||||
// Document
|
||||
case node_document:
|
||||
out = print_children(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Element
|
||||
case node_element:
|
||||
out = print_element_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Data
|
||||
case node_data:
|
||||
out = print_data_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// CDATA
|
||||
case node_cdata:
|
||||
out = print_cdata_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Declaration
|
||||
case node_declaration:
|
||||
out = print_declaration_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Comment
|
||||
case node_comment:
|
||||
out = print_comment_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Doctype
|
||||
case node_doctype:
|
||||
out = print_doctype_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Pi
|
||||
case node_pi:
|
||||
out = print_pi_node(out, node, flags, indent);
|
||||
break;
|
||||
|
||||
// Unknown
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
// If indenting not disabled, add line break after node
|
||||
if (!(flags & print_no_indenting))
|
||||
*out = Ch('\n'), ++out;
|
||||
|
||||
// Return modified iterator
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print children of the node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
for (xml_node<Ch> *child = node->first_node(); child; child = child->next_sibling())
|
||||
out = print_node(out, child, flags, indent);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print attributes of the node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags)
|
||||
{
|
||||
for (xml_attribute<Ch> *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute())
|
||||
{
|
||||
if (attribute->name() && attribute->value())
|
||||
{
|
||||
// Print attribute name
|
||||
*out = Ch(' '), ++out;
|
||||
out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out);
|
||||
*out = Ch('='), ++out;
|
||||
// Print attribute value using appropriate quote type
|
||||
if (find_char<Ch, Ch('"')>(attribute->value(), attribute->value() + attribute->value_size()))
|
||||
{
|
||||
*out = Ch('\''), ++out;
|
||||
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out);
|
||||
*out = Ch('\''), ++out;
|
||||
}
|
||||
else
|
||||
{
|
||||
*out = Ch('"'), ++out;
|
||||
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out);
|
||||
*out = Ch('"'), ++out;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print data node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_data);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print data node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_cdata);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'); ++out;
|
||||
*out = Ch('!'); ++out;
|
||||
*out = Ch('['); ++out;
|
||||
*out = Ch('C'); ++out;
|
||||
*out = Ch('D'); ++out;
|
||||
*out = Ch('A'); ++out;
|
||||
*out = Ch('T'); ++out;
|
||||
*out = Ch('A'); ++out;
|
||||
*out = Ch('['); ++out;
|
||||
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||
*out = Ch(']'); ++out;
|
||||
*out = Ch(']'); ++out;
|
||||
*out = Ch('>'); ++out;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print element node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_element);
|
||||
|
||||
// Print element name and attributes, if any
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||
out = print_attributes(out, node, flags);
|
||||
|
||||
// If node is childless
|
||||
if (node->value_size() == 0 && !node->first_node())
|
||||
{
|
||||
// Print childless node tag ending
|
||||
*out = Ch('/'), ++out;
|
||||
*out = Ch('>'), ++out;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Print normal node tag ending
|
||||
*out = Ch('>'), ++out;
|
||||
|
||||
// Test if node contains a single data node only (and no other nodes)
|
||||
xml_node<Ch> *child = node->first_node();
|
||||
if (!child)
|
||||
{
|
||||
// If node has no children, only print its value without indenting
|
||||
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
|
||||
}
|
||||
else if (child->next_sibling() == 0 && child->type() == node_data)
|
||||
{
|
||||
// If node has a sole data child, only print its value without indenting
|
||||
out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Print all children with full indenting
|
||||
if (!(flags & print_no_indenting))
|
||||
*out = Ch('\n'), ++out;
|
||||
out = print_children(out, node, flags, indent + 1);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
}
|
||||
|
||||
// Print node end
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('/'), ++out;
|
||||
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||
*out = Ch('>'), ++out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print declaration node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
// Print declaration start
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('?'), ++out;
|
||||
*out = Ch('x'), ++out;
|
||||
*out = Ch('m'), ++out;
|
||||
*out = Ch('l'), ++out;
|
||||
|
||||
// Print attributes
|
||||
out = print_attributes(out, node, flags);
|
||||
|
||||
// Print declaration end
|
||||
*out = Ch('?'), ++out;
|
||||
*out = Ch('>'), ++out;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print comment node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_comment);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('!'), ++out;
|
||||
*out = Ch('-'), ++out;
|
||||
*out = Ch('-'), ++out;
|
||||
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||
*out = Ch('-'), ++out;
|
||||
*out = Ch('-'), ++out;
|
||||
*out = Ch('>'), ++out;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print doctype node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_doctype);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('!'), ++out;
|
||||
*out = Ch('D'), ++out;
|
||||
*out = Ch('O'), ++out;
|
||||
*out = Ch('C'), ++out;
|
||||
*out = Ch('T'), ++out;
|
||||
*out = Ch('Y'), ++out;
|
||||
*out = Ch('P'), ++out;
|
||||
*out = Ch('E'), ++out;
|
||||
*out = Ch(' '), ++out;
|
||||
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||
*out = Ch('>'), ++out;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Print pi node
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||
{
|
||||
assert(node->type() == node_pi);
|
||||
if (!(flags & print_no_indenting))
|
||||
out = fill_chars(out, indent, Ch('\t'));
|
||||
*out = Ch('<'), ++out;
|
||||
*out = Ch('?'), ++out;
|
||||
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||
*out = Ch(' '), ++out;
|
||||
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||
*out = Ch('?'), ++out;
|
||||
*out = Ch('>'), ++out;
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
//! \endcond
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Printing
|
||||
|
||||
//! Prints XML to given output iterator.
|
||||
//! \param out Output iterator to print to.
|
||||
//! \param node Node to be printed. Pass xml_document to print entire document.
|
||||
//! \param flags Flags controlling how XML is printed.
|
||||
//! \return Output iterator pointing to position immediately after last character of printed text.
|
||||
template<class OutIt, class Ch>
|
||||
inline OutIt print(OutIt out, const xml_node<Ch> &node, int flags = 0)
|
||||
{
|
||||
return internal::print_node(out, &node, flags, 0);
|
||||
}
|
||||
|
||||
#ifndef RAPIDXML_NO_STREAMS
|
||||
|
||||
//! Prints XML to given output stream.
|
||||
//! \param out Output stream to print to.
|
||||
//! \param node Node to be printed. Pass xml_document to print entire document.
|
||||
//! \param flags Flags controlling how XML is printed.
|
||||
//! \return Output stream.
|
||||
template<class Ch>
|
||||
inline std::basic_ostream<Ch> &print(std::basic_ostream<Ch> &out, const xml_node<Ch> &node, int flags = 0)
|
||||
{
|
||||
print(std::ostream_iterator<Ch>(out), node, flags);
|
||||
return out;
|
||||
}
|
||||
|
||||
//! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.
|
||||
//! \param out Output stream to print to.
|
||||
//! \param node Node to be printed.
|
||||
//! \return Output stream.
|
||||
template<class Ch>
|
||||
inline std::basic_ostream<Ch> &operator <<(std::basic_ostream<Ch> &out, const xml_node<Ch> &node)
|
||||
{
|
||||
return print(out, node);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,122 @@
|
||||
#ifndef RAPIDXML_UTILS_HPP_INCLUDED
|
||||
#define RAPIDXML_UTILS_HPP_INCLUDED
|
||||
|
||||
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||
// Version 1.13
|
||||
// Revision $DateTime: 2009/05/13 01:46:17 $
|
||||
//! \file rapidxml_utils.hpp This file contains high-level rapidxml utilities that can be useful
|
||||
//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective.
|
||||
|
||||
#include "rapidxml.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace rapidxml
|
||||
{
|
||||
|
||||
//! Represents data loaded from a file
|
||||
template<class Ch = char>
|
||||
class file
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
//! Loads file into the memory. Data will be automatically destroyed by the destructor.
|
||||
//! \param filename Filename to load.
|
||||
file(const char *filename)
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
// Open stream
|
||||
basic_ifstream<Ch> stream(filename, ios::binary);
|
||||
if (!stream)
|
||||
throw runtime_error(string("cannot open file ") + filename);
|
||||
stream.unsetf(ios::skipws);
|
||||
|
||||
// Determine stream size
|
||||
stream.seekg(0, ios::end);
|
||||
size_t size = stream.tellg();
|
||||
stream.seekg(0);
|
||||
|
||||
// Load data and add terminating 0
|
||||
m_data.resize(size + 1);
|
||||
stream.read(&m_data.front(), static_cast<streamsize>(size));
|
||||
m_data[size] = 0;
|
||||
}
|
||||
|
||||
//! Loads file into the memory. Data will be automatically destroyed by the destructor
|
||||
//! \param stream Stream to load from
|
||||
file(std::basic_istream<Ch> &stream)
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
// Load data and add terminating 0
|
||||
stream.unsetf(ios::skipws);
|
||||
m_data.assign(istreambuf_iterator<Ch>(stream), istreambuf_iterator<Ch>());
|
||||
if (stream.fail() || stream.bad())
|
||||
throw runtime_error("error reading stream");
|
||||
m_data.push_back(0);
|
||||
}
|
||||
|
||||
//! Gets file data.
|
||||
//! \return Pointer to data of file.
|
||||
Ch *data()
|
||||
{
|
||||
return &m_data.front();
|
||||
}
|
||||
|
||||
//! Gets file data.
|
||||
//! \return Pointer to data of file.
|
||||
const Ch *data() const
|
||||
{
|
||||
return &m_data.front();
|
||||
}
|
||||
|
||||
//! Gets file data size.
|
||||
//! \return Size of file data, in characters.
|
||||
std::size_t size() const
|
||||
{
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::vector<Ch> m_data; // File data
|
||||
|
||||
};
|
||||
|
||||
//! Counts children of node. Time complexity is O(n).
|
||||
//! \return Number of children of node
|
||||
template<class Ch>
|
||||
inline std::size_t count_children(xml_node<Ch> *node)
|
||||
{
|
||||
xml_node<Ch> *child = node->first_node();
|
||||
std::size_t count = 0;
|
||||
while (child)
|
||||
{
|
||||
++count;
|
||||
child = child->next_sibling();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//! Counts attributes of node. Time complexity is O(n).
|
||||
//! \return Number of attributes of node
|
||||
template<class Ch>
|
||||
inline std::size_t count_attributes(xml_node<Ch> *node)
|
||||
{
|
||||
xml_attribute<Ch> *attr = node->first_attribute();
|
||||
std::size_t count = 0;
|
||||
while (attr)
|
||||
{
|
||||
++count;
|
||||
attr = attr->next_attribute();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
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
Reference in New Issue
Block a user