Refactor code structure for improved readability and maintainability
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
compile_commands.json
|
||||||
|
CMakeUserPresets.json
|
||||||
|
cmake/
|
||||||
24
.vscode/launch.json
vendored
Normal file
24
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug live-wallpaper",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/build/debug/live-wallpaper",
|
||||||
|
"args": [
|
||||||
|
"${workspaceFolder}/samples/dry_rocky_gorge.lua"
|
||||||
|
],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"environment": [],
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Enable pretty-printing for gdb",
|
||||||
|
"text": "-enable-pretty-printing",
|
||||||
|
"ignoreFailures": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"C_Cpp.errorSquiggles": "disabled"
|
||||||
|
}
|
||||||
30
.vscode/tasks.json
vendored
Normal file
30
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "CMake: configure debug",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cmake",
|
||||||
|
"args": ["--preset", "debug"],
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "CMake: build debug",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cmake",
|
||||||
|
"args": ["--build", "--preset", "debug"],
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"problemMatcher": "$gcc",
|
||||||
|
"dependsOn": "CMake: configure debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
92
CMakeLists.txt
Normal file
92
CMakeLists.txt
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.25)
|
||||||
|
project(live-wallpaper LANGUAGES C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
# ── CPM ──────────────────────────────────────────────────────────────────────
|
||||||
|
set(CPM_DOWNLOAD_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CPM.cmake")
|
||||||
|
|
||||||
|
if(NOT EXISTS "${CPM_DOWNLOAD_LOCATION}")
|
||||||
|
message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}")
|
||||||
|
file(DOWNLOAD
|
||||||
|
"https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/CPM.cmake"
|
||||||
|
"${CPM_DOWNLOAD_LOCATION}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include("${CPM_DOWNLOAD_LOCATION}")
|
||||||
|
|
||||||
|
# ── Dependencies ─────────────────────────────────────────────────────────────
|
||||||
|
CPMAddPackage(
|
||||||
|
NAME SDL3
|
||||||
|
GITHUB_REPOSITORY libsdl-org/SDL
|
||||||
|
GIT_TAG main
|
||||||
|
OPTIONS
|
||||||
|
"SDL_SHARED OFF"
|
||||||
|
"SDL_STATIC ON"
|
||||||
|
"SDL_TEST_LIBRARY OFF"
|
||||||
|
"SDL_TESTS OFF"
|
||||||
|
"SDL_EXAMPLES OFF"
|
||||||
|
"SDL_X11_XSCRNSAVER OFF"
|
||||||
|
)
|
||||||
|
CPMAddPackage(
|
||||||
|
NAME lua
|
||||||
|
GIT_TAG v5.4.7
|
||||||
|
GITHUB_REPOSITORY walterschell/Lua
|
||||||
|
OPTIONS
|
||||||
|
"LUA_BUILD_COMPILER OFF"
|
||||||
|
"LUA_BUILD_INTERPRETER OFF"
|
||||||
|
)
|
||||||
|
CPMAddPackage(
|
||||||
|
NAME sol2
|
||||||
|
GITHUB_REPOSITORY ThePhD/sol2
|
||||||
|
GIT_TAG v3.3.0
|
||||||
|
OPTIONS
|
||||||
|
"SOL2_LUA_VERSION 5.4.7"
|
||||||
|
)
|
||||||
|
CPMAddPackage(
|
||||||
|
NAME stb
|
||||||
|
GITHUB_REPOSITORY nothings/stb
|
||||||
|
GIT_TAG master
|
||||||
|
DOWNLOAD_ONLY YES
|
||||||
|
)
|
||||||
|
if(stb_ADDED)
|
||||||
|
add_library(stb INTERFACE)
|
||||||
|
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ── Local dependencies ───────────────────────────────────────────────────────
|
||||||
|
add_subdirectory(external/glad)
|
||||||
|
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
find_package(X11 REQUIRED)
|
||||||
|
|
||||||
|
# ── Source Files ─────────────────────────────────────────────────────────────
|
||||||
|
file(GLOB_RECURSE SOURCE_FILES
|
||||||
|
src/*.cpp
|
||||||
|
src/*.hpp
|
||||||
|
src/*.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Executable ───────────────────────────────────────────────────────────────
|
||||||
|
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME}
|
||||||
|
PRIVATE
|
||||||
|
SDL3::SDL3-static
|
||||||
|
OpenGL::GL
|
||||||
|
X11::X11
|
||||||
|
Xext
|
||||||
|
Lua::Library
|
||||||
|
sol2
|
||||||
|
stb
|
||||||
|
glad
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME}
|
||||||
|
PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
|
)
|
||||||
32
CMakePresets.json
Normal file
32
CMakePresets.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"version": 6,
|
||||||
|
"cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 },
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"displayName": "Debug",
|
||||||
|
"binaryDir": "${sourceDir}/build/debug",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Debug"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "release",
|
||||||
|
"displayName": "Release",
|
||||||
|
"binaryDir": "${sourceDir}/build/release",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Release"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"configurePreset": "debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "release",
|
||||||
|
"configurePreset": "release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
2
external/glad/CMakeLists.txt
vendored
Normal file
2
external/glad/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
add_library(glad STATIC src/gl.c)
|
||||||
|
target_include_directories(glad PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
311
external/glad/include/KHR/khrplatform.h
vendored
Normal file
311
external/glad/include/KHR/khrplatform.h
vendored
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
#ifndef __khrplatform_h_
|
||||||
|
#define __khrplatform_h_
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
||||||
|
**
|
||||||
|
** Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
** copy of this software and/or associated documentation files (the
|
||||||
|
** "Materials"), to deal in the Materials without restriction, including
|
||||||
|
** without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
** distribute, sublicense, and/or sell copies of the Materials, and to
|
||||||
|
** permit persons to whom the Materials are furnished to do so, subject to
|
||||||
|
** the following conditions:
|
||||||
|
**
|
||||||
|
** The above copyright notice and this permission notice shall be included
|
||||||
|
** in all copies or substantial portions of the Materials.
|
||||||
|
**
|
||||||
|
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Khronos platform-specific types and definitions.
|
||||||
|
*
|
||||||
|
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
||||||
|
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
||||||
|
* The last semantic modification to khrplatform.h was at commit ID:
|
||||||
|
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
||||||
|
*
|
||||||
|
* Adopters may modify this file to suit their platform. Adopters are
|
||||||
|
* encouraged to submit platform specific modifications to the Khronos
|
||||||
|
* group so that they can be included in future versions of this file.
|
||||||
|
* Please submit changes by filing pull requests or issues on
|
||||||
|
* the EGL Registry repository linked above.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* See the Implementer's Guidelines for information about where this file
|
||||||
|
* should be located on your system and for more details of its use:
|
||||||
|
* http://www.khronos.org/registry/implementers_guide.pdf
|
||||||
|
*
|
||||||
|
* This file should be included as
|
||||||
|
* #include <KHR/khrplatform.h>
|
||||||
|
* by Khronos client API header files that use its types and defines.
|
||||||
|
*
|
||||||
|
* The types in khrplatform.h should only be used to define API-specific types.
|
||||||
|
*
|
||||||
|
* Types defined in khrplatform.h:
|
||||||
|
* khronos_int8_t signed 8 bit
|
||||||
|
* khronos_uint8_t unsigned 8 bit
|
||||||
|
* khronos_int16_t signed 16 bit
|
||||||
|
* khronos_uint16_t unsigned 16 bit
|
||||||
|
* khronos_int32_t signed 32 bit
|
||||||
|
* khronos_uint32_t unsigned 32 bit
|
||||||
|
* khronos_int64_t signed 64 bit
|
||||||
|
* khronos_uint64_t unsigned 64 bit
|
||||||
|
* khronos_intptr_t signed same number of bits as a pointer
|
||||||
|
* khronos_uintptr_t unsigned same number of bits as a pointer
|
||||||
|
* khronos_ssize_t signed size
|
||||||
|
* khronos_usize_t unsigned size
|
||||||
|
* khronos_float_t signed 32 bit floating point
|
||||||
|
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
|
||||||
|
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
|
||||||
|
* nanoseconds
|
||||||
|
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
|
||||||
|
* khronos_boolean_enum_t enumerated boolean type. This should
|
||||||
|
* only be used as a base type when a client API's boolean type is
|
||||||
|
* an enum. Client APIs which use an integer or other type for
|
||||||
|
* booleans cannot use this as the base type for their boolean.
|
||||||
|
*
|
||||||
|
* Tokens defined in khrplatform.h:
|
||||||
|
*
|
||||||
|
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
|
||||||
|
*
|
||||||
|
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
|
||||||
|
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
|
||||||
|
*
|
||||||
|
* Calling convention macros defined in this file:
|
||||||
|
* KHRONOS_APICALL
|
||||||
|
* KHRONOS_APIENTRY
|
||||||
|
* KHRONOS_APIATTRIBUTES
|
||||||
|
*
|
||||||
|
* These may be used in function prototypes as:
|
||||||
|
*
|
||||||
|
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
|
||||||
|
* int arg1,
|
||||||
|
* int arg2) KHRONOS_APIATTRIBUTES;
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
||||||
|
# define KHRONOS_STATIC 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* Definition of KHRONOS_APICALL
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
* This precedes the return type of the function in the function prototype.
|
||||||
|
*/
|
||||||
|
#if defined(KHRONOS_STATIC)
|
||||||
|
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
||||||
|
* header compatible with static linking. */
|
||||||
|
# define KHRONOS_APICALL
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
# define KHRONOS_APICALL __declspec(dllimport)
|
||||||
|
#elif defined (__SYMBIAN32__)
|
||||||
|
# define KHRONOS_APICALL IMPORT_C
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
|
# define KHRONOS_APICALL __attribute__((visibility("default")))
|
||||||
|
#else
|
||||||
|
# define KHRONOS_APICALL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* Definition of KHRONOS_APIENTRY
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
* This follows the return type of the function and precedes the function
|
||||||
|
* name in the function prototype.
|
||||||
|
*/
|
||||||
|
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
|
||||||
|
/* Win32 but not WinCE */
|
||||||
|
# define KHRONOS_APIENTRY __stdcall
|
||||||
|
#else
|
||||||
|
# define KHRONOS_APIENTRY
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* Definition of KHRONOS_APIATTRIBUTES
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
* This follows the closing parenthesis of the function prototype arguments.
|
||||||
|
*/
|
||||||
|
#if defined (__ARMCC_2__)
|
||||||
|
#define KHRONOS_APIATTRIBUTES __softfp
|
||||||
|
#else
|
||||||
|
#define KHRONOS_APIATTRIBUTES
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* basic type definitions
|
||||||
|
*-----------------------------------------------------------------------*/
|
||||||
|
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Using <stdint.h>
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef int32_t khronos_int32_t;
|
||||||
|
typedef uint32_t khronos_uint32_t;
|
||||||
|
typedef int64_t khronos_int64_t;
|
||||||
|
typedef uint64_t khronos_uint64_t;
|
||||||
|
#define KHRONOS_SUPPORT_INT64 1
|
||||||
|
#define KHRONOS_SUPPORT_FLOAT 1
|
||||||
|
/*
|
||||||
|
* To support platform where unsigned long cannot be used interchangeably with
|
||||||
|
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
|
||||||
|
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
|
||||||
|
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
|
||||||
|
* unsigned long long or similar (this results in different C++ name mangling).
|
||||||
|
* To avoid changes for existing platforms, we restrict usage of intptr_t to
|
||||||
|
* platforms where the size of a pointer is larger than the size of long.
|
||||||
|
*/
|
||||||
|
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
|
||||||
|
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
|
||||||
|
#define KHRONOS_USE_INTPTR_T
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif defined(__VMS ) || defined(__sgi)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Using <inttypes.h>
|
||||||
|
*/
|
||||||
|
#include <inttypes.h>
|
||||||
|
typedef int32_t khronos_int32_t;
|
||||||
|
typedef uint32_t khronos_uint32_t;
|
||||||
|
typedef int64_t khronos_int64_t;
|
||||||
|
typedef uint64_t khronos_uint64_t;
|
||||||
|
#define KHRONOS_SUPPORT_INT64 1
|
||||||
|
#define KHRONOS_SUPPORT_FLOAT 1
|
||||||
|
|
||||||
|
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Win32
|
||||||
|
*/
|
||||||
|
typedef __int32 khronos_int32_t;
|
||||||
|
typedef unsigned __int32 khronos_uint32_t;
|
||||||
|
typedef __int64 khronos_int64_t;
|
||||||
|
typedef unsigned __int64 khronos_uint64_t;
|
||||||
|
#define KHRONOS_SUPPORT_INT64 1
|
||||||
|
#define KHRONOS_SUPPORT_FLOAT 1
|
||||||
|
|
||||||
|
#elif defined(__sun__) || defined(__digital__)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sun or Digital
|
||||||
|
*/
|
||||||
|
typedef int khronos_int32_t;
|
||||||
|
typedef unsigned int khronos_uint32_t;
|
||||||
|
#if defined(__arch64__) || defined(_LP64)
|
||||||
|
typedef long int khronos_int64_t;
|
||||||
|
typedef unsigned long int khronos_uint64_t;
|
||||||
|
#else
|
||||||
|
typedef long long int khronos_int64_t;
|
||||||
|
typedef unsigned long long int khronos_uint64_t;
|
||||||
|
#endif /* __arch64__ */
|
||||||
|
#define KHRONOS_SUPPORT_INT64 1
|
||||||
|
#define KHRONOS_SUPPORT_FLOAT 1
|
||||||
|
|
||||||
|
#elif 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hypothetical platform with no float or int64 support
|
||||||
|
*/
|
||||||
|
typedef int khronos_int32_t;
|
||||||
|
typedef unsigned int khronos_uint32_t;
|
||||||
|
#define KHRONOS_SUPPORT_INT64 0
|
||||||
|
#define KHRONOS_SUPPORT_FLOAT 0
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generic fallback
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef int32_t khronos_int32_t;
|
||||||
|
typedef uint32_t khronos_uint32_t;
|
||||||
|
typedef int64_t khronos_int64_t;
|
||||||
|
typedef uint64_t khronos_uint64_t;
|
||||||
|
#define KHRONOS_SUPPORT_INT64 1
|
||||||
|
#define KHRONOS_SUPPORT_FLOAT 1
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Types that are (so far) the same on all platforms
|
||||||
|
*/
|
||||||
|
typedef signed char khronos_int8_t;
|
||||||
|
typedef unsigned char khronos_uint8_t;
|
||||||
|
typedef signed short int khronos_int16_t;
|
||||||
|
typedef unsigned short int khronos_uint16_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
||||||
|
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
||||||
|
* to be the only LLP64 architecture in current use.
|
||||||
|
*/
|
||||||
|
#ifdef KHRONOS_USE_INTPTR_T
|
||||||
|
typedef intptr_t khronos_intptr_t;
|
||||||
|
typedef uintptr_t khronos_uintptr_t;
|
||||||
|
#elif defined(_WIN64)
|
||||||
|
typedef signed long long int khronos_intptr_t;
|
||||||
|
typedef unsigned long long int khronos_uintptr_t;
|
||||||
|
#else
|
||||||
|
typedef signed long int khronos_intptr_t;
|
||||||
|
typedef unsigned long int khronos_uintptr_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN64)
|
||||||
|
typedef signed long long int khronos_ssize_t;
|
||||||
|
typedef unsigned long long int khronos_usize_t;
|
||||||
|
#else
|
||||||
|
typedef signed long int khronos_ssize_t;
|
||||||
|
typedef unsigned long int khronos_usize_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if KHRONOS_SUPPORT_FLOAT
|
||||||
|
/*
|
||||||
|
* Float type
|
||||||
|
*/
|
||||||
|
typedef float khronos_float_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if KHRONOS_SUPPORT_INT64
|
||||||
|
/* Time types
|
||||||
|
*
|
||||||
|
* These types can be used to represent a time interval in nanoseconds or
|
||||||
|
* an absolute Unadjusted System Time. Unadjusted System Time is the number
|
||||||
|
* of nanoseconds since some arbitrary system event (e.g. since the last
|
||||||
|
* time the system booted). The Unadjusted System Time is an unsigned
|
||||||
|
* 64 bit value that wraps back to 0 every 584 years. Time intervals
|
||||||
|
* may be either signed or unsigned.
|
||||||
|
*/
|
||||||
|
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
||||||
|
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dummy value used to pad enum types to 32 bits.
|
||||||
|
*/
|
||||||
|
#ifndef KHRONOS_MAX_ENUM
|
||||||
|
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enumerated boolean type
|
||||||
|
*
|
||||||
|
* Values other than zero should be considered to be true. Therefore
|
||||||
|
* comparisons should not be made against KHRONOS_TRUE.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
KHRONOS_FALSE = 0,
|
||||||
|
KHRONOS_TRUE = 1,
|
||||||
|
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
||||||
|
} khronos_boolean_enum_t;
|
||||||
|
|
||||||
|
#endif /* __khrplatform_h_ */
|
||||||
3637
external/glad/include/glad/gl.h
vendored
Normal file
3637
external/glad/include/glad/gl.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1620
external/glad/src/gl.c
vendored
Normal file
1620
external/glad/src/gl.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
156
samples/auroras.lua
Normal file
156
samples/auroras.lua
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
-- Auroras by nimitz 2017 (twitter: @stormoid)
|
||||||
|
-- License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
|
||||||
|
-- Converted from ShaderToy to live-wallpaper system
|
||||||
|
|
||||||
|
effect = nil
|
||||||
|
|
||||||
|
function _create()
|
||||||
|
local fxSrc = [[
|
||||||
|
in vec2 vPosition;
|
||||||
|
|
||||||
|
#define time uTime
|
||||||
|
|
||||||
|
mat2 mm2(in float a){float c = cos(a), s = sin(a);return mat2(c,s,-s,c);}
|
||||||
|
mat2 m2 = mat2(0.95534, 0.29552, -0.29552, 0.95534);
|
||||||
|
float tri(in float x){return clamp(abs(fract(x)-.5),0.01,0.49);}
|
||||||
|
vec2 tri2(in vec2 p){return vec2(tri(p.x)+tri(p.y),tri(p.y+tri(p.x)));}
|
||||||
|
|
||||||
|
float triNoise2d(in vec2 p, float spd)
|
||||||
|
{
|
||||||
|
float z=1.8;
|
||||||
|
float z2=2.5;
|
||||||
|
float rz = 0.;
|
||||||
|
p *= mm2(p.x*0.06);
|
||||||
|
vec2 bp = p;
|
||||||
|
for (float i=0.; i<5.; i++ )
|
||||||
|
{
|
||||||
|
vec2 dg = tri2(bp*1.85)*.75;
|
||||||
|
dg *= mm2(time*spd);
|
||||||
|
p -= dg/z2;
|
||||||
|
|
||||||
|
bp *= 1.3;
|
||||||
|
z2 *= .45;
|
||||||
|
z *= .42;
|
||||||
|
p *= 1.21 + (rz-1.0)*.02;
|
||||||
|
|
||||||
|
rz += tri(p.x+tri(p.y))*z;
|
||||||
|
p*= -m2;
|
||||||
|
}
|
||||||
|
return clamp(1./pow(rz*29., 1.3),0.,.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
float hash21(in vec2 n){ return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); }
|
||||||
|
|
||||||
|
vec4 aurora(vec3 ro, vec3 rd)
|
||||||
|
{
|
||||||
|
vec4 col = vec4(0);
|
||||||
|
vec4 avgCol = vec4(0);
|
||||||
|
|
||||||
|
for(float i=0.;i<50.;i++)
|
||||||
|
{
|
||||||
|
float of = 0.006*hash21(gl_FragCoord.xy)*smoothstep(0.,15., i);
|
||||||
|
float pt = ((.8+pow(i,1.4)*.002)-ro.y)/(rd.y*2.+0.4);
|
||||||
|
pt -= of;
|
||||||
|
vec3 bpos = ro + pt*rd;
|
||||||
|
vec2 p = bpos.zx;
|
||||||
|
float rzt = triNoise2d(p, 0.06);
|
||||||
|
vec4 col2 = vec4(0,0,0, rzt);
|
||||||
|
col2.rgb = (sin(1.-vec3(2.15,-.5, 1.2)+i*0.043)*0.5+0.5)*rzt;
|
||||||
|
avgCol = mix(avgCol, col2, .5);
|
||||||
|
col += avgCol*exp2(-i*0.065 - 2.5)*smoothstep(0.,5., i);
|
||||||
|
}
|
||||||
|
|
||||||
|
col *= (clamp(rd.y*15.+.4,0.,1.));
|
||||||
|
|
||||||
|
return col*1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------Background and Stars--------------------
|
||||||
|
|
||||||
|
vec3 nmzHash33(vec3 q)
|
||||||
|
{
|
||||||
|
uvec3 p = uvec3(ivec3(q));
|
||||||
|
p = p*uvec3(374761393U, 1103515245U, 668265263U) + p.zxy + p.yzx;
|
||||||
|
p = p.yzx*(p.zxy^(p >> 3U));
|
||||||
|
return vec3(p^(p >> 16U))*(1.0/vec3(0xffffffffU));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 stars(in vec3 p)
|
||||||
|
{
|
||||||
|
vec3 c = vec3(0.);
|
||||||
|
float res = uDesktopSize.x*1.;
|
||||||
|
|
||||||
|
for (float i=0.;i<4.;i++)
|
||||||
|
{
|
||||||
|
vec3 q = fract(p*(.15*res))-0.5;
|
||||||
|
vec3 id = floor(p*(.15*res));
|
||||||
|
vec2 rn = nmzHash33(id).xy;
|
||||||
|
float c2 = 1.-smoothstep(0.,.6,length(q));
|
||||||
|
c2 *= step(rn.x,.0005+i*i*0.001);
|
||||||
|
c += c2*(mix(vec3(1.0,0.49,0.1),vec3(0.75,0.9,1.),rn.y)*0.1+0.9);
|
||||||
|
p *= 1.3;
|
||||||
|
}
|
||||||
|
return c*c*.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 bg(in vec3 rd)
|
||||||
|
{
|
||||||
|
float sd = dot(normalize(vec3(-0.5, -0.6, 0.9)), rd)*0.5+0.5;
|
||||||
|
sd = pow(sd, 5.);
|
||||||
|
vec3 col = mix(vec3(0.05,0.1,0.2), vec3(0.1,0.05,0.2), sd);
|
||||||
|
return col*.63;
|
||||||
|
}
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec2 fragCoord = (vPosition * 0.5 + 0.5) * uDesktopSize;
|
||||||
|
|
||||||
|
vec2 q = fragCoord.xy / uDesktopSize.xy;
|
||||||
|
vec2 p = q - 0.5;
|
||||||
|
p.x *= uDesktopSize.x / uDesktopSize.y;
|
||||||
|
|
||||||
|
vec3 ro = vec3(0,0,-6.7);
|
||||||
|
vec3 rd = normalize(vec3(p,1.3));
|
||||||
|
vec2 mo = uMouse.xy / uDesktopSize.xy-.5;
|
||||||
|
mo = (mo==vec2(-.5))?mo=vec2(-0.1,0.1):mo;
|
||||||
|
mo.x *= uDesktopSize.x / uDesktopSize.y;
|
||||||
|
rd.yz *= mm2(mo.y);
|
||||||
|
rd.xz *= mm2(mo.x + sin(time*0.05)*0.2);
|
||||||
|
|
||||||
|
vec3 col = vec3(0.);
|
||||||
|
vec3 brd = rd;
|
||||||
|
float fade = smoothstep(0.,0.01,abs(brd.y))*0.1+0.9;
|
||||||
|
|
||||||
|
col = bg(rd)*fade;
|
||||||
|
|
||||||
|
if (rd.y > 0.){
|
||||||
|
vec4 aur = smoothstep(0.,1.5,aurora(ro,rd))*fade;
|
||||||
|
col += stars(rd);
|
||||||
|
col = col*(1.-aur.a) + aur.rgb;
|
||||||
|
}
|
||||||
|
else //Reflections
|
||||||
|
{
|
||||||
|
rd.y = abs(rd.y);
|
||||||
|
col = bg(rd)*fade*0.6;
|
||||||
|
vec4 aur = smoothstep(0.0,2.5,aurora(ro,rd));
|
||||||
|
col += stars(rd)*0.1;
|
||||||
|
col = col*(1.-aur.a) + aur.rgb;
|
||||||
|
vec3 pos = ro + ((0.5-ro.y)/rd.y)*rd;
|
||||||
|
float nz2 = triNoise2d(pos.xz*vec2(.5,.7), 0.);
|
||||||
|
col += mix(vec3(0.2,0.25,0.5)*0.08,vec3(0.3,0.3,0.5)*0.7, nz2*0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
FragColor = vec4(col, 1.);
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
effect = Effect.new(fxSrc)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _update(dt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _render()
|
||||||
|
gl.Clear(0, 0, 0, 1.0)
|
||||||
|
effect:Render()
|
||||||
|
end
|
||||||
290
samples/dry_rocky_gorge.lua
Normal file
290
samples/dry_rocky_gorge.lua
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
-- Dry Rocky Gorge by Shane
|
||||||
|
-- License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
|
||||||
|
-- Converted from ShaderToy to live-wallpaper system
|
||||||
|
|
||||||
|
effect = nil
|
||||||
|
rockTexture = nil
|
||||||
|
|
||||||
|
function _create()
|
||||||
|
rockTexture = Texture.LoadFromFile("rock_texture.jpg")
|
||||||
|
rockTexture:Bind()
|
||||||
|
rockTexture:SetFilter(TextureFilter.LinearMipmapLinear, TextureFilter.Linear)
|
||||||
|
rockTexture:SetWrap(TextureWrap.Repeat, TextureWrap.Repeat)
|
||||||
|
rockTexture:GenerateMipmaps()
|
||||||
|
|
||||||
|
local fxSrc = [[
|
||||||
|
in vec2 vPosition;
|
||||||
|
|
||||||
|
uniform sampler2D iChannel0;
|
||||||
|
|
||||||
|
#define FAR 80.
|
||||||
|
|
||||||
|
mat2 r2(in float a){ float c = cos(a), s = sin(a); return mat2(c, s, -s, c); }
|
||||||
|
|
||||||
|
float hash31( vec3 p ){ return fract(sin(dot(p, vec3(157, 113, 7)))*45758.5453); }
|
||||||
|
|
||||||
|
float hash21( vec2 p ){ return fract(sin(dot(p, vec2(41, 289)))*45758.5453); }
|
||||||
|
|
||||||
|
vec3 hash33(vec3 p){
|
||||||
|
float n = sin(dot(p, vec3(7, 157, 113)));
|
||||||
|
return fract(vec3(2097152, 262144, 32768)*n);
|
||||||
|
}
|
||||||
|
|
||||||
|
float n3D(vec3 p){
|
||||||
|
const vec3 s = vec3(7, 157, 113);
|
||||||
|
vec3 ip = floor(p); p -= ip;
|
||||||
|
vec4 h = vec4(0., s.yz, s.y + s.z) + dot(ip, s);
|
||||||
|
p = p*p*(3. - 2.*p);
|
||||||
|
h = mix(fract(sin(h)*43758.5453), fract(sin(h + s.x)*43758.5453), p.x);
|
||||||
|
h.xy = mix(h.xz, h.yw, p.y);
|
||||||
|
return mix(h.x, h.y, p.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
float n2D(vec2 p) {
|
||||||
|
vec2 i = floor(p); p -= i; p *= p*(3. - p*2.);
|
||||||
|
return dot(mat2(fract(sin(vec4(0, 41, 289, 330) + dot(i, vec2(41, 289)))*43758.5453))*
|
||||||
|
vec2(1. - p.y, p.y), vec2(1. - p.x, p.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 tex3D( sampler2D t, in vec3 p, in vec3 n ){
|
||||||
|
n = max(abs(n), 0.001);
|
||||||
|
n /= dot(n, vec3(1));
|
||||||
|
vec3 tx = texture(t, p.yz).xyz;
|
||||||
|
vec3 ty = texture(t, p.zx).xyz;
|
||||||
|
vec3 tz = texture(t, p.xy).xyz;
|
||||||
|
return (tx*tx*n.x + ty*ty*n.y + tz*tz*n.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 path(in float z){
|
||||||
|
return vec2(sin(z*.075)*8., cos(z*.1)*.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
float smax(float a, float b, float s){
|
||||||
|
float h = clamp(.5 + .5*(a - b)/s, 0., 1.);
|
||||||
|
return mix(b, a, h) + h*(1. - h)*s;
|
||||||
|
}
|
||||||
|
|
||||||
|
float terrain(vec2 p){
|
||||||
|
p /= 8.;
|
||||||
|
p += .5;
|
||||||
|
float a = 1., sum = 0., res = 0.;
|
||||||
|
for (int i=0; i<5; i++){
|
||||||
|
res += n2D(p)*a;
|
||||||
|
p = mat2(1, -.75, .75, 1)*p*2.72;
|
||||||
|
sum += a;
|
||||||
|
a *= -.5/1.7;
|
||||||
|
}
|
||||||
|
return res/sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
float map(vec3 p){
|
||||||
|
float trSf = terrain(p.xz);
|
||||||
|
p.xy -= path(p.z);
|
||||||
|
vec2 ca = abs(p.xy*vec2(1, .7) + vec2(0, -2.75));
|
||||||
|
float n = smax(6. - mix(length(ca), max(ca.x, ca.y), .25), p.y - 1.75, 2.) + (.5 - trSf)*4.;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 texBump( sampler2D tx, in vec3 p, in vec3 n, float bf){
|
||||||
|
const vec2 e = vec2(.001, 0);
|
||||||
|
mat3 m = mat3( tex3D(tx, p - e.xyy, n), tex3D(tx, p - e.yxy, n), tex3D(tx, p - e.yyx, n));
|
||||||
|
vec3 g = vec3(.299, .587, .114)*m;
|
||||||
|
g = (g - dot(tex3D(tx, p , n), vec3(.299, .587, .114)) )/e.x;
|
||||||
|
g -= n*dot(n, g);
|
||||||
|
return normalize( n + g*bf );
|
||||||
|
}
|
||||||
|
|
||||||
|
float trace(vec3 ro, vec3 rd){
|
||||||
|
float t = 0., d;
|
||||||
|
for (int i=0; i<160; i++){
|
||||||
|
d = map(ro + rd*t);
|
||||||
|
if(abs(d)<.001*(t*.025 + 1.) || t>FAR) break;
|
||||||
|
t += d*.7;
|
||||||
|
}
|
||||||
|
return min(t, FAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
float softShadow(vec3 ro, vec3 n, vec3 lp, float k, float t){
|
||||||
|
const int maxIterationsShad = 48;
|
||||||
|
ro += n*.0015;
|
||||||
|
vec3 rd = lp - ro;
|
||||||
|
float shade = 1.;
|
||||||
|
float dist = .0;
|
||||||
|
float end = max(length(rd), 0.0001);
|
||||||
|
rd /= end;
|
||||||
|
for (int i=0; i<maxIterationsShad; i++){
|
||||||
|
float h = map(ro + rd*dist);
|
||||||
|
shade = min(shade, smoothstep(0.0, 1.0, k*h/dist));
|
||||||
|
dist += clamp(h, .02, .25);
|
||||||
|
if (h<0. || dist > end) break;
|
||||||
|
}
|
||||||
|
return min(max(shade, 0.) + .15, 1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 getNormal( in vec3 p ){
|
||||||
|
vec2 e = vec2(.001, -.001);
|
||||||
|
return normalize(e.xyy*map(p + e.xyy) + e.yyx*map(p + e.yyx) + e.yxy*map(p + e.yxy) + e.xxx*map(p + e.xxx));
|
||||||
|
}
|
||||||
|
|
||||||
|
float calcAO(in vec3 p, in vec3 nor){
|
||||||
|
float sca = 1.5, occ = 0.;
|
||||||
|
for(float i=0.; i<5.; i++){
|
||||||
|
float hr = .01 + i*.5/4.;
|
||||||
|
float dd = map(nor*hr + p);
|
||||||
|
occ += (hr - dd)*sca;
|
||||||
|
sca *= .7;
|
||||||
|
}
|
||||||
|
return clamp(1. - occ, 0., 1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fmap(vec3 p){
|
||||||
|
p *= vec3(1, 4, 1)/400.;
|
||||||
|
return n3D(p)*0.57 + n3D(p*4.)*0.28 + n3D(p*8.)*0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 cloudLayers(vec3 ro, vec3 rd, vec3 lp, float far){
|
||||||
|
rd = (rd + (hash33(rd.zyx)*0.004-0.002));
|
||||||
|
rd *= (1. + fract(sin(dot(vec3(7, 157, 113), rd.zyx))*43758.5453)*0.04-0.02);
|
||||||
|
|
||||||
|
float localDensity=0., td=0., w=0.;
|
||||||
|
float d=1., t=0.;
|
||||||
|
const float h = .5;
|
||||||
|
vec3 col = vec3(0), sp;
|
||||||
|
vec4 d4 = vec4(1, 0, 0, 0);
|
||||||
|
vec3 sn = normalize(hash33(rd.yxz)*.03-rd);
|
||||||
|
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
|
if(td>1. || t>far)break;
|
||||||
|
sp = ro + rd*t;
|
||||||
|
d = fmap(sp);
|
||||||
|
|
||||||
|
localDensity = (h - d) * step(d, h);
|
||||||
|
w = (1. - td) * localDensity;
|
||||||
|
td += w*.5 + 1./65.;
|
||||||
|
|
||||||
|
vec3 lightDir = lp-sp;
|
||||||
|
float lDist = max(length(lightDir), 0.001);
|
||||||
|
lightDir /= lDist;
|
||||||
|
|
||||||
|
float atten = 100./(1. + lDist*0.005 + lDist*lDist*0.00005);
|
||||||
|
float diff = max(dot( sn, lightDir ), 0.);
|
||||||
|
float spec = pow(max( dot( reflect(-lightDir, sn), -rd ), 0. ), 4.);
|
||||||
|
|
||||||
|
col += w*(diff + vec3(1, .75, .5)*spec + .5)*atten;
|
||||||
|
|
||||||
|
t += max(d4.x*.5, 0.25)*100.;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec4(col, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 getSky(in vec3 ro, in vec3 rd, vec3 lp, float t){
|
||||||
|
float sun = max(dot(rd, normalize(lp - ro)), 0.0);
|
||||||
|
float horiz = pow(1.0-max(rd.y, 0.0), 3.)*.25;
|
||||||
|
|
||||||
|
vec3 col = mix(vec3(.25, .5, 1)*.8, vec3(.8, .75, .7), sun*.5);
|
||||||
|
col = mix(col, vec3(1, .5, .25), horiz);
|
||||||
|
|
||||||
|
col += 0.25*vec3(1, .7, .4)*pow(sun, 5.0);
|
||||||
|
col += 0.25*vec3(1, .8, .6)*pow(sun, 64.0);
|
||||||
|
col += 0.15*vec3(1, .9, .7)*max(pow(sun, 512.0), .25);
|
||||||
|
|
||||||
|
col = clamp(col + hash31(rd)*0.04 - 0.02, 0., 1.);
|
||||||
|
|
||||||
|
float tt = (1000. - ro.y)/(rd.y + .2);
|
||||||
|
if(t>=FAR && tt>0.){
|
||||||
|
vec4 cl = cloudLayers(ro + rd*tt, rd, lp, FAR*3.);
|
||||||
|
vec3 clouds = cl.xyz;
|
||||||
|
col = mix( col, vec3(1), clouds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 getObjectColor(vec3 p, vec3 n){
|
||||||
|
vec3 tx = tex3D(iChannel0, p/8., n );
|
||||||
|
vec3 gr = mix(vec3(1), vec3(.8, 1.3, .2), smoothstep(.5, 1., n.y));
|
||||||
|
return mix(tx, tx*gr, smoothstep(.7, 1., (n.y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 doColor(in vec3 ro, in vec3 rd, in vec3 lp, float t){
|
||||||
|
vec3 sceneCol = vec3(0);
|
||||||
|
|
||||||
|
if(t<FAR){
|
||||||
|
vec3 sp = ro + rd*t;
|
||||||
|
vec3 sn = getNormal(sp);
|
||||||
|
|
||||||
|
vec3 tx = sp;
|
||||||
|
sn = texBump(iChannel0, tx/2., sn, .15);
|
||||||
|
|
||||||
|
float sh = softShadow(sp, sn, lp, 16., t);
|
||||||
|
float ao = calcAO(sp, sn);
|
||||||
|
sh = (sh + ao*.25)*ao;
|
||||||
|
|
||||||
|
vec3 ld = lp - sp;
|
||||||
|
float lDist = max(length(ld), 0.001);
|
||||||
|
ld /= lDist;
|
||||||
|
|
||||||
|
float atten = 3./(1. + lDist*0.005 + lDist*lDist*0.00005);
|
||||||
|
float diff = max(dot(sn, ld), 0.);
|
||||||
|
float spec = pow(max( dot( reflect(-ld, sn), -rd ), 0.0 ), 64.0);
|
||||||
|
|
||||||
|
vec3 objCol = getObjectColor(sp, sn);
|
||||||
|
sceneCol = objCol*(diff + ao*.5 + vec3(1, .7, .5)*spec);
|
||||||
|
sceneCol *= atten*sh;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sceneCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
vec2 fragCoord = (vPosition * 0.5 + 0.5) * uDesktopSize;
|
||||||
|
vec2 uv = (fragCoord - uDesktopSize.xy*.5)/uDesktopSize.y;
|
||||||
|
|
||||||
|
vec3 ro = vec3(0, 0, uTime*5.);
|
||||||
|
vec3 lk = ro + vec3(0, -.04, .25);
|
||||||
|
|
||||||
|
vec3 lp = ro + vec3(8, FAR*.26, FAR*.52)*3.;
|
||||||
|
|
||||||
|
ro.xy += path(ro.z);
|
||||||
|
lk.xy += path(lk.z);
|
||||||
|
lp.xy += path(lp.z);
|
||||||
|
|
||||||
|
float FOV = 3.14159/3.;
|
||||||
|
vec3 forward = normalize(lk-ro);
|
||||||
|
vec3 right = normalize(vec3(forward.z, 0., -forward.x ));
|
||||||
|
vec3 up = cross(forward, right);
|
||||||
|
|
||||||
|
vec3 rd = normalize(uv.x*right + uv.y*up + forward/FOV);
|
||||||
|
|
||||||
|
vec2 sw = path(lk.z);
|
||||||
|
rd.xy *= r2(-sw.x/24.);
|
||||||
|
rd.yz *= r2(-sw.y/16.);
|
||||||
|
|
||||||
|
float t = trace(ro, rd);
|
||||||
|
|
||||||
|
vec3 sky = getSky(ro, rd, lp, t);
|
||||||
|
vec3 sceneColor = doColor(ro, rd, lp, t);
|
||||||
|
|
||||||
|
float fog = smoothstep(0., .95, t/FAR);
|
||||||
|
vec3 fogCol = sky;
|
||||||
|
sceneColor = mix(sceneColor, fogCol, fog);
|
||||||
|
|
||||||
|
uv = fragCoord/uDesktopSize.xy;
|
||||||
|
sceneColor *= pow(16.*uv.x*uv.y*(1. - uv.x)*(1. - uv.y) , .125)*.75 + .25;
|
||||||
|
|
||||||
|
FragColor = vec4(sqrt(clamp(sceneColor, 0.0, 1.0)), 1.0);
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
effect = Effect.new(fxSrc)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _update(dt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _render()
|
||||||
|
gl.Clear(0, 0, 0, 1.0)
|
||||||
|
effect:Use()
|
||||||
|
effect:SetTexture("iChannel0", rockTexture, 0)
|
||||||
|
effect:Render()
|
||||||
|
end
|
||||||
BIN
samples/rock_texture.jpg
Normal file
BIN
samples/rock_texture.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 723 KiB |
21
samples/simple.lua
Normal file
21
samples/simple.lua
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
effect = nil
|
||||||
|
|
||||||
|
function _create()
|
||||||
|
local fxSrc = [[in vec2 vPosition;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = vPosition * 0.5 + 0.5;
|
||||||
|
FragColor = vec4(uv, 0.5 + 0.5 * sin(uTime), 1.0);
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
effect = Effect.new(fxSrc)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _update(dt)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function _render()
|
||||||
|
gl.Clear(0, 0, 0, 1.0)
|
||||||
|
effect:Render()
|
||||||
|
end
|
||||||
169
samples/sunset_sea.lua
Normal file
169
samples/sunset_sea.lua
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
-- Sunset on the sea v.1.0.1 - Ray Marching & Ray Tracing experiment by Riccardo Gerosa aka h3r3
|
||||||
|
-- Converted from ShaderToy to live-wallpaper system
|
||||||
|
|
||||||
|
effect = nil
|
||||||
|
|
||||||
|
function _create()
|
||||||
|
local fxSrc = [[
|
||||||
|
in vec2 vPosition;
|
||||||
|
|
||||||
|
const bool USE_MOUSE = false;
|
||||||
|
|
||||||
|
const float PI = 3.14159265;
|
||||||
|
const float MAX_RAYMARCH_DIST = 150.0;
|
||||||
|
const float MIN_RAYMARCH_DELTA = 0.00015;
|
||||||
|
const float GRADIENT_DELTA = 0.015;
|
||||||
|
float waveHeight1 = 0.005;
|
||||||
|
float waveHeight2 = 0.004;
|
||||||
|
float waveHeight3 = 0.001;
|
||||||
|
vec2 mouse;
|
||||||
|
|
||||||
|
// --------------------- START of SIMPLEX NOISE
|
||||||
|
vec3 mod289(vec3 x) {
|
||||||
|
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 mod289(vec2 x) {
|
||||||
|
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 permute(vec3 x) {
|
||||||
|
return mod289(((x * 34.0) + 1.0) * x);
|
||||||
|
}
|
||||||
|
|
||||||
|
float snoise(vec2 v) {
|
||||||
|
const vec4 C = vec4(0.211324865405187,
|
||||||
|
0.366025403784439,
|
||||||
|
-0.577350269189626,
|
||||||
|
0.024390243902439);
|
||||||
|
vec2 i = floor(v + dot(v, C.yy));
|
||||||
|
vec2 x0 = v - i + dot(i, C.xx);
|
||||||
|
|
||||||
|
vec2 i1;
|
||||||
|
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
|
||||||
|
vec4 x12 = x0.xyxy + C.xxzz;
|
||||||
|
x12.xy -= i1;
|
||||||
|
|
||||||
|
i = mod289(i);
|
||||||
|
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0))
|
||||||
|
+ i.x + vec3(0.0, i1.x, 1.0));
|
||||||
|
|
||||||
|
vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
|
||||||
|
m = m * m;
|
||||||
|
m = m * m;
|
||||||
|
|
||||||
|
vec3 x = 2.0 * fract(p * C.www) - 1.0;
|
||||||
|
vec3 h = abs(x) - 0.5;
|
||||||
|
vec3 ox = floor(x + 0.5);
|
||||||
|
vec3 a0 = x - ox;
|
||||||
|
|
||||||
|
m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
|
||||||
|
|
||||||
|
vec3 g;
|
||||||
|
g.x = a0.x * x0.x + h.x * x0.y;
|
||||||
|
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
|
||||||
|
return 130.0 * dot(m, g);
|
||||||
|
}
|
||||||
|
// --------------------- END of SIMPLEX NOISE
|
||||||
|
|
||||||
|
float map(vec3 p) {
|
||||||
|
return p.y + (0.5 + waveHeight1 + waveHeight2 + waveHeight3)
|
||||||
|
+ snoise(vec2(p.x + uTime * 0.4, p.z + uTime * 0.6)) * waveHeight1
|
||||||
|
+ snoise(vec2(p.x * 1.6 - uTime * 0.4, p.z * 1.7 - uTime * 0.6)) * waveHeight2
|
||||||
|
+ snoise(vec2(p.x * 6.6 - uTime * 1.0, p.z * 2.7 + uTime * 1.176)) * waveHeight3;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 gradientNormalFast(vec3 p, float map_p) {
|
||||||
|
return normalize(vec3(
|
||||||
|
map_p - map(p - vec3(GRADIENT_DELTA, 0, 0)),
|
||||||
|
map_p - map(p - vec3(0, GRADIENT_DELTA, 0)),
|
||||||
|
map_p - map(p - vec3(0, 0, GRADIENT_DELTA))));
|
||||||
|
}
|
||||||
|
|
||||||
|
float intersect(vec3 p, vec3 ray_dir, out float map_p, out int iterations) {
|
||||||
|
iterations = 0;
|
||||||
|
if (ray_dir.y >= 0.0) { return -1.0; }
|
||||||
|
|
||||||
|
float distMin = (-0.5 - p.y) / ray_dir.y;
|
||||||
|
float distMid = distMin;
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
distMid += max(0.05 + float(i) * 0.002, map_p);
|
||||||
|
map_p = map(p + ray_dir * distMid);
|
||||||
|
if (map_p > 0.0) {
|
||||||
|
distMin = distMid + map_p;
|
||||||
|
} else {
|
||||||
|
float distMax = distMid + map_p;
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
distMid = distMin + (distMax - distMin) / 2.0;
|
||||||
|
map_p = map(p + ray_dir * distMid);
|
||||||
|
if (abs(map_p) < MIN_RAYMARCH_DELTA) return distMid;
|
||||||
|
if (map_p > 0.0) {
|
||||||
|
distMin = distMid + map_p;
|
||||||
|
} else {
|
||||||
|
distMax = distMid + map_p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return distMid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return distMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 fragCoord = (vPosition * 0.5 + 0.5) * uDesktopSize;
|
||||||
|
|
||||||
|
mouse = vec2(uMouse.x / uDesktopSize.x, uMouse.y / uDesktopSize.y);
|
||||||
|
float waveHeight = USE_MOUSE ? mouse.x * 5.0 : cos(uTime * 0.03) * 1.2 + 1.6;
|
||||||
|
waveHeight1 *= waveHeight;
|
||||||
|
waveHeight2 *= waveHeight;
|
||||||
|
waveHeight3 *= waveHeight;
|
||||||
|
|
||||||
|
vec2 position = vec2(
|
||||||
|
(fragCoord.x - uDesktopSize.x / 2.0) / uDesktopSize.y,
|
||||||
|
(fragCoord.y - uDesktopSize.y / 2.0) / uDesktopSize.y
|
||||||
|
);
|
||||||
|
vec3 ray_start = vec3(0, 0.2, -2);
|
||||||
|
vec3 ray_dir = normalize(vec3(position, 0) - ray_start);
|
||||||
|
ray_start.y = cos(uTime * 0.5) * 0.2 - 0.25 + sin(uTime * 2.0) * 0.05;
|
||||||
|
|
||||||
|
const float dayspeed = 0.04;
|
||||||
|
float subtime = max(-0.16, sin(uTime * dayspeed) * 0.2);
|
||||||
|
float middayperc = USE_MOUSE ? mouse.y * 0.3 - 0.15 : max(0.0, sin(subtime));
|
||||||
|
vec3 light1_pos = vec3(0.0, middayperc * 200.0, USE_MOUSE ? 200.0 : cos(subtime * dayspeed) * 200.0);
|
||||||
|
float sunperc = pow(max(0.0, min(dot(ray_dir, normalize(light1_pos)), 1.0)), 190.0 + max(0.0, light1_pos.y * 4.3));
|
||||||
|
vec3 suncolor = (1.0 - max(0.0, middayperc)) * vec3(1.5, 1.2, middayperc + 0.5)
|
||||||
|
+ max(0.0, middayperc) * vec3(1.0, 1.0, 1.0) * 4.0;
|
||||||
|
vec3 skycolor = vec3(middayperc + 0.8, middayperc + 0.7, middayperc + 0.5);
|
||||||
|
vec3 skycolor_now = suncolor * sunperc + (skycolor * (middayperc * 1.6 + 0.5)) * (1.0 - sunperc);
|
||||||
|
|
||||||
|
vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
float map_p;
|
||||||
|
int iterations;
|
||||||
|
float dist = intersect(ray_start, ray_dir, map_p, iterations);
|
||||||
|
if (dist > 0.0) {
|
||||||
|
vec3 p = ray_start + ray_dir * dist;
|
||||||
|
vec3 light1_dir = normalize(light1_pos - p);
|
||||||
|
vec3 n = gradientNormalFast(p, map_p);
|
||||||
|
vec3 ambient = skycolor_now * 0.1;
|
||||||
|
vec3 diffuse1 = vec3(1.1, 1.1, 0.6) * max(0.0, dot(light1_dir, n) * 2.8);
|
||||||
|
vec3 r = reflect(light1_dir, n);
|
||||||
|
vec3 specular1 = vec3(1.5, 1.2, 0.6) * (0.8 * pow(max(0.0, dot(r, ray_dir)), 200.0));
|
||||||
|
float fog = min(max(p.z * 0.07, 0.0), 1.0);
|
||||||
|
color.rgb = (vec3(0.6, 0.6, 1.0) * diffuse1 + specular1 + ambient) * (1.0 - fog) + skycolor_now * fog;
|
||||||
|
} else {
|
||||||
|
color.rgb = skycolor_now.rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
FragColor = color;
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
effect = Effect.new(fxSrc)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _update(dt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function _render()
|
||||||
|
gl.Clear(0, 0, 0, 1.0)
|
||||||
|
effect:Render()
|
||||||
|
end
|
||||||
237
src/effect.cpp
Normal file
237
src/effect.cpp
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
#include "effect.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <regex>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
#include "texture.h"
|
||||||
|
|
||||||
|
GLuint Effect::g_dummyVAO = 0;
|
||||||
|
|
||||||
|
static std::vector<std::string> SplitLines(const std::string& str) {
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
size_t start = 0, end = 0;
|
||||||
|
while ((end = str.find_first_of("\r\n", start)) != std::string::npos) {
|
||||||
|
lines.push_back(str.substr(start, end - start));
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
if (start < str.size()) {
|
||||||
|
lines.push_back(str.substr(start));
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect::Effect(const std::string& fragmentShaderSource)
|
||||||
|
{
|
||||||
|
const std::string vertexShaderSource =
|
||||||
|
#include "fx_vertex.glsl.h"
|
||||||
|
;
|
||||||
|
|
||||||
|
std::string code =
|
||||||
|
"#version 460 core\n\n"
|
||||||
|
"out vec4 FragColor;\n\n"
|
||||||
|
"uniform float uTime;\n"
|
||||||
|
"uniform vec4 uMouse;\n"
|
||||||
|
"uniform vec2 uDesktopSize;\n";
|
||||||
|
|
||||||
|
for (size_t i = 0; i < g_Displays.size(); i++) {
|
||||||
|
code += "uniform vec4 uDisplay" + std::to_string(i) + ";\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset line number
|
||||||
|
code += "#line 1\n";
|
||||||
|
|
||||||
|
GLuint vertexShader = CreateShader(vertexShaderSource, GL_VERTEX_SHADER);
|
||||||
|
if (!vertexShader) {
|
||||||
|
throw std::runtime_error("Failed to compile vertex shader");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint fragmentShader = CreateShader(code + fragmentShaderSource, GL_FRAGMENT_SHADER);
|
||||||
|
if (!fragmentShader) {
|
||||||
|
glDeleteShader(vertexShader);
|
||||||
|
throw std::runtime_error("Failed to compile fragment shader");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_program = glCreateProgram();
|
||||||
|
glAttachShader(m_program, vertexShader);
|
||||||
|
glAttachShader(m_program, fragmentShader);
|
||||||
|
glLinkProgram(m_program);
|
||||||
|
|
||||||
|
GLint success;
|
||||||
|
glGetProgramiv(m_program, GL_LINK_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
GLint logLength;
|
||||||
|
glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, &logLength);
|
||||||
|
std::string log(logLength, '\0');
|
||||||
|
glGetProgramInfoLog(m_program, logLength, nullptr, log.data());
|
||||||
|
throw std::runtime_error("Failed to link shader program: " + log);
|
||||||
|
}
|
||||||
|
|
||||||
|
glDeleteShader(vertexShader);
|
||||||
|
glDeleteShader(fragmentShader);
|
||||||
|
|
||||||
|
// Create a dummy VAO to allow rendering without a real VAO bound
|
||||||
|
if (!g_dummyVAO) {
|
||||||
|
glGenVertexArrays(1, &g_dummyVAO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect::~Effect()
|
||||||
|
{
|
||||||
|
if (m_program) {
|
||||||
|
glDeleteProgram(m_program);
|
||||||
|
m_program = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Effect::Use() const
|
||||||
|
{
|
||||||
|
glUseProgram(m_program);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint Effect::GetUniformLocation(const std::string& name)
|
||||||
|
{
|
||||||
|
auto it = m_uniformLocations.find(name);
|
||||||
|
if (it != m_uniformLocations.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint location = glGetUniformLocation(m_program, name.c_str());
|
||||||
|
m_uniformLocations[name] = location;
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Effect::SetSampler(const std::string &name, GLuint textureUnit)
|
||||||
|
{
|
||||||
|
glUniform1i(GetUniformLocation(name), textureUnit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Effect::SetTexture(const std::string &name, const Texture &texture, size_t textureUnit)
|
||||||
|
{
|
||||||
|
texture.Bind();
|
||||||
|
glActiveTexture(GL_TEXTURE0 + static_cast<GLuint>(textureUnit));
|
||||||
|
SetSampler(name, static_cast<GLuint>(textureUnit));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Effect::SetFloat(const std::string &name, float value)
|
||||||
|
{
|
||||||
|
glUniform1f(GetUniformLocation(name), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Effect::SetVector2(const std::string &name, const Vector2 &value)
|
||||||
|
{
|
||||||
|
glUniform2f(GetUniformLocation(name), value.x, value.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Effect::SetVector3(const std::string &name, const Vector3 &value)
|
||||||
|
{
|
||||||
|
glUniform3f(GetUniformLocation(name), value.x, value.y, value.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Effect::SetVector4(const std::string &name, const Vector4 &value)
|
||||||
|
{
|
||||||
|
glUniform4f(GetUniformLocation(name), value.x, value.y, value.z, value.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Effect::SetMatrix4(const std::string &name, const Matrix4 &value)
|
||||||
|
{
|
||||||
|
glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, value.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Effect::Render()
|
||||||
|
{
|
||||||
|
Use();
|
||||||
|
|
||||||
|
// Set default uniforms
|
||||||
|
SetFloat("uTime", g_Time);
|
||||||
|
SetVector4("uMouse", g_Mouse);
|
||||||
|
SetVector2("uDesktopSize", g_DesktopSize);
|
||||||
|
for (size_t i = 0; i < g_Displays.size(); i++) {
|
||||||
|
SetVector4("uDisplay" + std::to_string(i), g_Displays[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(g_dummyVAO);
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint Effect::CreateShader(const std::string &source, GLenum shaderType)
|
||||||
|
{
|
||||||
|
GLuint shader = glCreateShader(shaderType);
|
||||||
|
|
||||||
|
const char* src = source.c_str();
|
||||||
|
glShaderSource(shader, 1, &src, nullptr);
|
||||||
|
glCompileShader(shader);
|
||||||
|
GLint success;
|
||||||
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
std::vector<std::string> sourceLines = SplitLines(source);
|
||||||
|
|
||||||
|
GLint logLength;
|
||||||
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
|
||||||
|
std::string log(logLength, '\0');
|
||||||
|
glGetShaderInfoLog(shader, logLength, nullptr, log.data());
|
||||||
|
|
||||||
|
std::string logText = "";
|
||||||
|
std::string shaderTypeText = "??";
|
||||||
|
|
||||||
|
switch (shaderType) {
|
||||||
|
case GL_VERTEX_SHADER: shaderTypeText = "Vertex Shader"; break;
|
||||||
|
case GL_FRAGMENT_SHADER: shaderTypeText = "Fragment Shader"; break;
|
||||||
|
default: shaderTypeText = "Shader"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::smatch match;
|
||||||
|
int lineNumber = 0;
|
||||||
|
int column = -1;
|
||||||
|
std::string errorMessage;
|
||||||
|
|
||||||
|
// Mesa/Intel: "0:15(10): error: msg" or "ERROR: 0:15: msg"
|
||||||
|
std::regex mesaRegex(R"((?:ERROR: )?\d+:(\d+)(?:\((\d+)\))?:\s*(.*))");
|
||||||
|
// NVIDIA: "0(11) : error C7530: msg"
|
||||||
|
std::regex nvidiaRegex(R"(\d+\((\d+)\)\s*:\s*(.*))");
|
||||||
|
|
||||||
|
if (std::regex_search(log, match, mesaRegex)) {
|
||||||
|
lineNumber = std::stoi(match[1].str());
|
||||||
|
column = match[2].matched ? std::stoi(match[2].str()) : -1;
|
||||||
|
errorMessage = match[3].str();
|
||||||
|
} else if (std::regex_search(log, match, nvidiaRegex)) {
|
||||||
|
lineNumber = std::stoi(match[1].str());
|
||||||
|
errorMessage = match[2].str();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineNumber > 0) {
|
||||||
|
logText = std::format("{} error at line {}: {}\nOffending code:\n", shaderTypeText, lineNumber, errorMessage);
|
||||||
|
if (lineNumber <= static_cast<int>(sourceLines.size())) {
|
||||||
|
std::string line = sourceLines[lineNumber - 1];
|
||||||
|
|
||||||
|
// add a ^ at column in a new line
|
||||||
|
std::string arrow(line.size(), ' ');
|
||||||
|
if (column >= 0 && column < static_cast<int>(arrow.size())) {
|
||||||
|
arrow[column] = '^';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineNumber - 2 >= 0) {
|
||||||
|
logText += std::format("{}\n", sourceLines[lineNumber - 2]);
|
||||||
|
}
|
||||||
|
logText += std::format("{}\n{}", line, arrow);
|
||||||
|
if (lineNumber < static_cast<int>(sourceLines.size())) {
|
||||||
|
logText += std::format("\n{}", sourceLines[lineNumber]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << logText << std::endl;
|
||||||
|
|
||||||
|
glDeleteShader(shader);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
38
src/effect.h
Normal file
38
src/effect.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "glad/gl.h"
|
||||||
|
#include "tmath.hpp"
|
||||||
|
|
||||||
|
class Texture;
|
||||||
|
class Effect {
|
||||||
|
public:
|
||||||
|
Effect() = default;
|
||||||
|
|
||||||
|
Effect(const std::string& fragmentShaderSource);
|
||||||
|
~Effect();
|
||||||
|
|
||||||
|
void Use() const;
|
||||||
|
GLuint GetUniformLocation(const std::string& name);
|
||||||
|
|
||||||
|
void SetSampler(const std::string& name, GLuint textureUnit);
|
||||||
|
void SetTexture(const std::string& name, const Texture& texture, size_t textureUnit);
|
||||||
|
void SetFloat(const std::string& name, float value);
|
||||||
|
void SetVector2(const std::string& name, const Vector2& value);
|
||||||
|
void SetVector3(const std::string& name, const Vector3& value);
|
||||||
|
void SetVector4(const std::string& name, const Vector4& value);
|
||||||
|
void SetMatrix4(const std::string& name, const Matrix4& value);
|
||||||
|
|
||||||
|
void Render();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static GLuint g_dummyVAO;
|
||||||
|
|
||||||
|
GLuint m_program = 0;
|
||||||
|
std::map<std::string, GLuint> m_uniformLocations;
|
||||||
|
|
||||||
|
GLuint CreateShader(const std::string& source, GLenum shaderType);
|
||||||
|
};
|
||||||
166
src/framebuffer.cpp
Normal file
166
src/framebuffer.cpp
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#include "framebuffer.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
FrameBuffer::FrameBuffer(uint32_t width, uint32_t height)
|
||||||
|
: m_width(width), m_height(height)
|
||||||
|
{
|
||||||
|
glGenFramebuffers(1, &m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameBuffer::~FrameBuffer()
|
||||||
|
{
|
||||||
|
m_colorTextures.clear();
|
||||||
|
DestroyDepthRenderbuffer();
|
||||||
|
if (m_id) {
|
||||||
|
glDeleteFramebuffers(1, &m_id);
|
||||||
|
m_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameBuffer::FrameBuffer(FrameBuffer&& other) noexcept
|
||||||
|
: m_width(other.m_width), m_height(other.m_height),
|
||||||
|
m_attachments(std::move(other.m_attachments)),
|
||||||
|
m_colorTextures(std::move(other.m_colorTextures)),
|
||||||
|
m_depthRenderbuffer(other.m_depthRenderbuffer)
|
||||||
|
{
|
||||||
|
m_id = other.m_id;
|
||||||
|
other.m_id = 0;
|
||||||
|
other.m_depthRenderbuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameBuffer& FrameBuffer::operator=(FrameBuffer&& other) noexcept
|
||||||
|
{
|
||||||
|
if (this != &other) {
|
||||||
|
m_colorTextures.clear();
|
||||||
|
DestroyDepthRenderbuffer();
|
||||||
|
if (m_id) glDeleteFramebuffers(1, &m_id);
|
||||||
|
|
||||||
|
m_id = other.m_id;
|
||||||
|
m_width = other.m_width;
|
||||||
|
m_height = other.m_height;
|
||||||
|
m_attachments = std::move(other.m_attachments);
|
||||||
|
m_colorTextures = std::move(other.m_colorTextures);
|
||||||
|
m_depthRenderbuffer = other.m_depthRenderbuffer;
|
||||||
|
|
||||||
|
other.m_id = 0;
|
||||||
|
other.m_depthRenderbuffer = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::Bind() const
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, m_id);
|
||||||
|
glViewport(0, 0, m_width, m_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::Unbind() const
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::AddColorAttachment(const ColorAttachment& attachment)
|
||||||
|
{
|
||||||
|
m_attachments.push_back(attachment);
|
||||||
|
|
||||||
|
auto texture = CreateColorTexture(m_attachments.back());
|
||||||
|
uint32_t index = static_cast<uint32_t>(m_colorTextures.size());
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, m_id);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index,
|
||||||
|
GL_TEXTURE_2D, texture->GetID(), 0);
|
||||||
|
m_colorTextures.push_back(std::move(texture));
|
||||||
|
|
||||||
|
UpdateDrawBuffers();
|
||||||
|
|
||||||
|
// Create or recreate depth renderbuffer to match
|
||||||
|
DestroyDepthRenderbuffer();
|
||||||
|
CreateDepthRenderbuffer();
|
||||||
|
|
||||||
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||||
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
throw std::runtime_error("Framebuffer is not complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::Resize(uint32_t width, uint32_t height)
|
||||||
|
{
|
||||||
|
if (m_width == width && m_height == height) return;
|
||||||
|
|
||||||
|
m_width = width;
|
||||||
|
m_height = height;
|
||||||
|
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Texture2D& FrameBuffer::GetColorTexture(uint32_t index) const
|
||||||
|
{
|
||||||
|
return *m_colorTextures[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::Rebuild()
|
||||||
|
{
|
||||||
|
m_colorTextures.clear();
|
||||||
|
DestroyDepthRenderbuffer();
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, m_id);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < m_attachments.size(); ++i) {
|
||||||
|
auto texture = CreateColorTexture(m_attachments[i]);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
|
||||||
|
GL_TEXTURE_2D, texture->GetID(), 0);
|
||||||
|
m_colorTextures.push_back(std::move(texture));
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateDrawBuffers();
|
||||||
|
CreateDepthRenderbuffer();
|
||||||
|
|
||||||
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||||
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
throw std::runtime_error("Framebuffer is not complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Texture2D> FrameBuffer::CreateColorTexture(const ColorAttachment& attachment) const
|
||||||
|
{
|
||||||
|
auto tex = std::make_unique<Texture2D>();
|
||||||
|
tex->m_width = m_width;
|
||||||
|
tex->m_height = m_height;
|
||||||
|
tex->Bind();
|
||||||
|
tex->LoadData(attachment.format, nullptr);
|
||||||
|
tex->SetFilter(attachment.minFilter, attachment.magFilter);
|
||||||
|
tex->SetWrap(attachment.wrapS, attachment.wrapT);
|
||||||
|
tex->Unbind();
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::CreateDepthRenderbuffer()
|
||||||
|
{
|
||||||
|
glGenRenderbuffers(1, &m_depthRenderbuffer);
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, m_depthRenderbuffer);
|
||||||
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, m_width, m_height);
|
||||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
||||||
|
GL_RENDERBUFFER, m_depthRenderbuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::DestroyDepthRenderbuffer()
|
||||||
|
{
|
||||||
|
if (m_depthRenderbuffer) {
|
||||||
|
glDeleteRenderbuffers(1, &m_depthRenderbuffer);
|
||||||
|
m_depthRenderbuffer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FrameBuffer::UpdateDrawBuffers() const
|
||||||
|
{
|
||||||
|
std::vector<GLenum> drawBuffers(m_colorTextures.size());
|
||||||
|
for (uint32_t i = 0; i < drawBuffers.size(); ++i) {
|
||||||
|
drawBuffers[i] = GL_COLOR_ATTACHMENT0 + i;
|
||||||
|
}
|
||||||
|
glDrawBuffers(static_cast<GLsizei>(drawBuffers.size()), drawBuffers.data());
|
||||||
|
}
|
||||||
52
src/framebuffer.h
Normal file
52
src/framebuffer.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "gpu.hpp"
|
||||||
|
#include "texture.h"
|
||||||
|
|
||||||
|
struct ColorAttachment {
|
||||||
|
TextureFormatType format = TextureFormatType::RGBA8;
|
||||||
|
TextureFilter minFilter = TextureFilter::Linear;
|
||||||
|
TextureFilter magFilter = TextureFilter::Linear;
|
||||||
|
TextureWrap wrapS = TextureWrap::ClampToEdge;
|
||||||
|
TextureWrap wrapT = TextureWrap::ClampToEdge;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrameBuffer : public IGPUObject<GLuint> {
|
||||||
|
public:
|
||||||
|
FrameBuffer(uint32_t width, uint32_t height);
|
||||||
|
~FrameBuffer();
|
||||||
|
|
||||||
|
FrameBuffer(const FrameBuffer&) = delete;
|
||||||
|
FrameBuffer& operator=(const FrameBuffer&) = delete;
|
||||||
|
FrameBuffer(FrameBuffer&& other) noexcept;
|
||||||
|
FrameBuffer& operator=(FrameBuffer&& other) noexcept;
|
||||||
|
|
||||||
|
void Bind() const override;
|
||||||
|
void Unbind() const override;
|
||||||
|
|
||||||
|
void AddColorAttachment(const ColorAttachment& attachment = {});
|
||||||
|
void Resize(uint32_t width, uint32_t height);
|
||||||
|
|
||||||
|
const Texture2D& GetColorTexture(uint32_t index) const;
|
||||||
|
uint32_t GetColorAttachmentCount() const { return static_cast<uint32_t>(m_colorTextures.size()); }
|
||||||
|
|
||||||
|
uint32_t GetWidth() const { return m_width; }
|
||||||
|
uint32_t GetHeight() const { return m_height; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t m_width;
|
||||||
|
uint32_t m_height;
|
||||||
|
std::vector<ColorAttachment> m_attachments;
|
||||||
|
std::vector<std::unique_ptr<Texture2D>> m_colorTextures;
|
||||||
|
GLuint m_depthRenderbuffer = 0;
|
||||||
|
|
||||||
|
void Rebuild();
|
||||||
|
std::unique_ptr<Texture2D> CreateColorTexture(const ColorAttachment& attachment) const;
|
||||||
|
void CreateDepthRenderbuffer();
|
||||||
|
void DestroyDepthRenderbuffer();
|
||||||
|
void UpdateDrawBuffers() const;
|
||||||
|
};
|
||||||
14
src/fx_vertex.glsl.h
Normal file
14
src/fx_vertex.glsl.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
R"(#version 460 core
|
||||||
|
out vec2 vPosition;
|
||||||
|
|
||||||
|
vec2 coords[] = vec2[](
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2( 1.0, -1.0),
|
||||||
|
vec2(-1.0, 1.0),
|
||||||
|
vec2( 1.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vPosition = coords[gl_VertexID];
|
||||||
|
gl_Position = vec4(vPosition, 0.0, 1.0);
|
||||||
|
})"
|
||||||
12
src/globals.h
Normal file
12
src/globals.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tmath.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Some globals that are passed to the Effects as uniforms
|
||||||
|
extern float g_Time;
|
||||||
|
extern Vector4 g_Mouse; // x, y position, z = left button, w = right button
|
||||||
|
extern std::vector<Vector4> g_Displays; // x, y, w, h
|
||||||
|
extern Vector2 g_DesktopSize; // width, height
|
||||||
|
extern std::string g_ScriptDir; // directory containing the loaded script
|
||||||
19
src/gpu.hpp
Normal file
19
src/gpu.hpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "glad/gl.h"
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
requires std::is_integral_v<T>
|
||||||
|
class IGPUObject {
|
||||||
|
public:
|
||||||
|
virtual ~IGPUObject() = default;
|
||||||
|
|
||||||
|
virtual void Bind() const = 0;
|
||||||
|
virtual void Unbind() const = 0;
|
||||||
|
|
||||||
|
T GetID() const { return m_id; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
T m_id;
|
||||||
|
};
|
||||||
450
src/lua.cpp
Normal file
450
src/lua.cpp
Normal file
@@ -0,0 +1,450 @@
|
|||||||
|
#include "lua.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "tmath.hpp"
|
||||||
|
#include "texture.h"
|
||||||
|
#include "framebuffer.h"
|
||||||
|
#include "effect.h"
|
||||||
|
#include "opengl.h"
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
static void RegisterTMath(sol::state& lua) {
|
||||||
|
// ── Free functions ──────────────────────────────────────────────────
|
||||||
|
lua.set_function("radians", &radians);
|
||||||
|
lua.set_function("degrees", °rees);
|
||||||
|
lua.set_function("clamp", &clamp);
|
||||||
|
lua.set_function("lerp", static_cast<float(*)(float, float, float)>(&lerp));
|
||||||
|
|
||||||
|
// ── Vector2 ─────────────────────────────────────────────────────────
|
||||||
|
auto vec2 = lua.new_usertype<Vector2>("Vector2",
|
||||||
|
sol::constructors<Vector2(), Vector2(float), Vector2(float, float)>(),
|
||||||
|
"x", &Vector2::x,
|
||||||
|
"y", &Vector2::y,
|
||||||
|
"dot", &Vector2::dot,
|
||||||
|
"cross", &Vector2::cross,
|
||||||
|
"length", &Vector2::length,
|
||||||
|
"lengthSquared", &Vector2::lengthSquared,
|
||||||
|
"normalized", &Vector2::normalized,
|
||||||
|
"distance", &Vector2::distance,
|
||||||
|
"lerp", &Vector2::lerp,
|
||||||
|
"reflect", &Vector2::reflect,
|
||||||
|
sol::meta_function::unary_minus, sol::resolve<Vector2() const>(&Vector2::operator-),
|
||||||
|
sol::meta_function::addition, sol::resolve<Vector2(const Vector2&) const>(&Vector2::operator+),
|
||||||
|
sol::meta_function::subtraction, sol::resolve<Vector2(const Vector2&) const>(&Vector2::operator-),
|
||||||
|
sol::meta_function::multiplication, sol::overload(
|
||||||
|
sol::resolve<Vector2(const Vector2&) const>(&Vector2::operator*),
|
||||||
|
sol::resolve<Vector2(float) const>(&Vector2::operator*)
|
||||||
|
),
|
||||||
|
sol::meta_function::division, sol::overload(
|
||||||
|
sol::resolve<Vector2(const Vector2&) const>(&Vector2::operator/),
|
||||||
|
sol::resolve<Vector2(float) const>(&Vector2::operator/)
|
||||||
|
),
|
||||||
|
sol::meta_function::equal_to, sol::resolve<bool(const Vector2&) const>(&Vector2::operator==),
|
||||||
|
sol::meta_function::to_string, [](const Vector2& v) {
|
||||||
|
return "Vector2(" + std::to_string(v.x) + ", " + std::to_string(v.y) + ")";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Vector3 ─────────────────────────────────────────────────────────
|
||||||
|
auto vec3 = lua.new_usertype<Vector3>("Vector3",
|
||||||
|
sol::constructors<Vector3(), Vector3(float), Vector3(float, float, float), Vector3(const Vector2&, float)>(),
|
||||||
|
"x", &Vector3::x,
|
||||||
|
"y", &Vector3::y,
|
||||||
|
"z", &Vector3::z,
|
||||||
|
"toVector2", &Vector3::toVector2,
|
||||||
|
"dot", &Vector3::dot,
|
||||||
|
"cross", &Vector3::cross,
|
||||||
|
"lerp", &Vector3::lerp,
|
||||||
|
"length", &Vector3::length,
|
||||||
|
"lengthSquared", &Vector3::lengthSquared,
|
||||||
|
"normalized", &Vector3::normalized,
|
||||||
|
"distance", &Vector3::distance,
|
||||||
|
"reflect", &Vector3::reflect,
|
||||||
|
"refract", &Vector3::refract,
|
||||||
|
sol::meta_function::unary_minus, sol::resolve<Vector3() const>(&Vector3::operator-),
|
||||||
|
sol::meta_function::addition, sol::resolve<Vector3(const Vector3&) const>(&Vector3::operator+),
|
||||||
|
sol::meta_function::subtraction, sol::resolve<Vector3(const Vector3&) const>(&Vector3::operator-),
|
||||||
|
sol::meta_function::multiplication, sol::overload(
|
||||||
|
sol::resolve<Vector3(const Vector3&) const>(&Vector3::operator*),
|
||||||
|
sol::resolve<Vector3(float) const>(&Vector3::operator*)
|
||||||
|
),
|
||||||
|
sol::meta_function::division, sol::overload(
|
||||||
|
sol::resolve<Vector3(const Vector3&) const>(&Vector3::operator/),
|
||||||
|
sol::resolve<Vector3(float) const>(&Vector3::operator/)
|
||||||
|
),
|
||||||
|
sol::meta_function::equal_to, sol::resolve<bool(const Vector3&) const>(&Vector3::operator==),
|
||||||
|
sol::meta_function::to_string, [](const Vector3& v) {
|
||||||
|
return "Vector3(" + std::to_string(v.x) + ", " + std::to_string(v.y) + ", " + std::to_string(v.z) + ")";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Vector4 ─────────────────────────────────────────────────────────
|
||||||
|
auto vec4 = lua.new_usertype<Vector4>("Vector4",
|
||||||
|
sol::constructors<Vector4(), Vector4(float), Vector4(float, float, float, float), Vector4(const Vector2&, float, float), Vector4(const Vector3&, float)>(),
|
||||||
|
"x", &Vector4::x,
|
||||||
|
"y", &Vector4::y,
|
||||||
|
"z", &Vector4::z,
|
||||||
|
"w", &Vector4::w,
|
||||||
|
"toVector2", &Vector4::toVector2,
|
||||||
|
"toVector3", &Vector4::toVector3,
|
||||||
|
"dot", &Vector4::dot,
|
||||||
|
"length", &Vector4::length,
|
||||||
|
"lengthSquared", &Vector4::lengthSquared,
|
||||||
|
"normalized", &Vector4::normalized,
|
||||||
|
"lerp", &Vector4::lerp,
|
||||||
|
sol::meta_function::unary_minus, sol::resolve<Vector4() const>(&Vector4::operator-),
|
||||||
|
sol::meta_function::addition, sol::resolve<Vector4(const Vector4&) const>(&Vector4::operator+),
|
||||||
|
sol::meta_function::subtraction, sol::resolve<Vector4(const Vector4&) const>(&Vector4::operator-),
|
||||||
|
sol::meta_function::multiplication, sol::overload(
|
||||||
|
sol::resolve<Vector4(const Vector4&) const>(&Vector4::operator*),
|
||||||
|
sol::resolve<Vector4(float) const>(&Vector4::operator*)
|
||||||
|
),
|
||||||
|
sol::meta_function::division, sol::overload(
|
||||||
|
sol::resolve<Vector4(const Vector4&) const>(&Vector4::operator/),
|
||||||
|
sol::resolve<Vector4(float) const>(&Vector4::operator/)
|
||||||
|
),
|
||||||
|
sol::meta_function::equal_to, sol::resolve<bool(const Vector4&) const>(&Vector4::operator==),
|
||||||
|
sol::meta_function::to_string, [](const Vector4& v) {
|
||||||
|
return "Vector4(" + std::to_string(v.x) + ", " + std::to_string(v.y) + ", " + std::to_string(v.z) + ", " + std::to_string(v.w) + ")";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Matrix4 ─────────────────────────────────────────────────────────
|
||||||
|
auto mat4 = lua.new_usertype<Matrix4>("Matrix4",
|
||||||
|
sol::constructors<Matrix4()>(),
|
||||||
|
"identity", sol::var(Matrix4::identity()),
|
||||||
|
"translation", &Matrix4::translation,
|
||||||
|
"scale", &Matrix4::scale,
|
||||||
|
"uniformScale", &Matrix4::uniformScale,
|
||||||
|
"rotationX", &Matrix4::rotationX,
|
||||||
|
"rotationY", &Matrix4::rotationY,
|
||||||
|
"rotationZ", &Matrix4::rotationZ,
|
||||||
|
"angleAxis", &Matrix4::angleAxis,
|
||||||
|
"ortho", &Matrix4::ortho,
|
||||||
|
"perspective", &Matrix4::perspective,
|
||||||
|
"lookAt", &Matrix4::lookAt,
|
||||||
|
"transposed", &Matrix4::transposed,
|
||||||
|
"determinant", &Matrix4::determinant,
|
||||||
|
"inverse", &Matrix4::inverse,
|
||||||
|
sol::meta_function::multiplication, sol::overload(
|
||||||
|
sol::resolve<Matrix4(const Matrix4&) const>(&Matrix4::operator*),
|
||||||
|
sol::resolve<Vector4(const Vector4&) const>(&Matrix4::operator*),
|
||||||
|
sol::resolve<Vector3(const Vector3&) const>(&Matrix4::operator*)
|
||||||
|
),
|
||||||
|
sol::meta_function::to_string, [](const Matrix4& m) {
|
||||||
|
std::string s = "Matrix4(\n";
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
s += " " + std::to_string(m[i].x) + ", " + std::to_string(m[i].y) + ", "
|
||||||
|
+ std::to_string(m[i].z) + ", " + std::to_string(m[i].w) + "\n";
|
||||||
|
}
|
||||||
|
s += ")";
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Quaternion ──────────────────────────────────────────────────────
|
||||||
|
auto quat = lua.new_usertype<Quaternion>("Quaternion",
|
||||||
|
sol::constructors<Quaternion(), Quaternion(float), Quaternion(float, float, float, float), Quaternion(const Vector3&, float)>(),
|
||||||
|
"x", &Quaternion::x,
|
||||||
|
"y", &Quaternion::y,
|
||||||
|
"z", &Quaternion::z,
|
||||||
|
"w", &Quaternion::w,
|
||||||
|
"axisAngle", &Quaternion::axisAngle,
|
||||||
|
"fromEuler", &Quaternion::fromEuler,
|
||||||
|
"toEuler", &Quaternion::toEuler,
|
||||||
|
"fromMatrix", &Quaternion::fromMatrix,
|
||||||
|
"toVector2", &Quaternion::toVector2,
|
||||||
|
"toVector3", &Quaternion::toVector3,
|
||||||
|
"toVector4", &Quaternion::toVector4,
|
||||||
|
"conjugated", &Quaternion::conjugated,
|
||||||
|
"inversed", &Quaternion::inversed,
|
||||||
|
"dot", &Quaternion::dot,
|
||||||
|
"length", &Quaternion::length,
|
||||||
|
"lengthSquared", &Quaternion::lengthSquared,
|
||||||
|
"normalized", &Quaternion::normalized,
|
||||||
|
"rotate", &Quaternion::rotate,
|
||||||
|
"toMatrix4", &Quaternion::toMatrix4,
|
||||||
|
"nLerp", &Quaternion::nLerp,
|
||||||
|
"sLerp", &Quaternion::sLerp,
|
||||||
|
sol::meta_function::unary_minus, sol::resolve<Quaternion() const>(&Quaternion::operator-),
|
||||||
|
sol::meta_function::multiplication, sol::overload(
|
||||||
|
sol::resolve<Quaternion(const Quaternion&) const>(&Quaternion::operator*),
|
||||||
|
sol::resolve<Quaternion(const Vector3&) const>(&Quaternion::operator*),
|
||||||
|
sol::resolve<Quaternion(float) const>(&Quaternion::operator*)
|
||||||
|
),
|
||||||
|
sol::meta_function::addition, sol::resolve<Quaternion(const Quaternion&) const>(&Quaternion::operator+),
|
||||||
|
sol::meta_function::subtraction, sol::resolve<Quaternion(const Quaternion&) const>(&Quaternion::operator-),
|
||||||
|
sol::meta_function::equal_to, sol::resolve<bool(const Quaternion&) const>(&Quaternion::operator==),
|
||||||
|
sol::meta_function::to_string, [](const Quaternion& q) {
|
||||||
|
return "Quaternion(" + std::to_string(q.x) + ", " + std::to_string(q.y) + ", " + std::to_string(q.z) + ", " + std::to_string(q.w) + ")";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Constants ───────────────────────────────────────────────────────
|
||||||
|
auto consts_table = lua.create_named_table("consts");
|
||||||
|
consts_table["Pi"] = consts::Pi;
|
||||||
|
consts_table["InvPi"] = consts::InvPi;
|
||||||
|
consts_table["InvTwoPi"] = consts::InvTwoPi;
|
||||||
|
consts_table["HalfPi"] = consts::HalfPi;
|
||||||
|
consts_table["QuarPi"] = consts::QuarPi;
|
||||||
|
consts_table["TwoPi"] = consts::TwoPi;
|
||||||
|
consts_table["Epsilon"] = consts::Epsilon;
|
||||||
|
consts_table["E"] = consts::E;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterTexture(sol::state& lua) {
|
||||||
|
// ── Enums ───────────────────────────────────────────────────────────
|
||||||
|
lua.new_enum<TextureFormatType>("TextureFormatType", {
|
||||||
|
{ "R8", TextureFormatType::R8 },
|
||||||
|
{ "RG8", TextureFormatType::RG8 },
|
||||||
|
{ "RGB8", TextureFormatType::RGB8 },
|
||||||
|
{ "RGBA8", TextureFormatType::RGBA8 },
|
||||||
|
{ "R16F", TextureFormatType::R16F },
|
||||||
|
{ "RG16F", TextureFormatType::RG16F },
|
||||||
|
{ "RGB16F", TextureFormatType::RGB16F },
|
||||||
|
{ "RGBA16F", TextureFormatType::RGBA16F },
|
||||||
|
{ "R32F", TextureFormatType::R32F },
|
||||||
|
{ "RG32F", TextureFormatType::RG32F },
|
||||||
|
{ "RGB32F", TextureFormatType::RGB32F },
|
||||||
|
{ "RGBA32F", TextureFormatType::RGBA32F }
|
||||||
|
});
|
||||||
|
|
||||||
|
lua.new_enum<TextureFilter>("TextureFilter", {
|
||||||
|
{ "Nearest", TextureFilter::Nearest },
|
||||||
|
{ "Linear", TextureFilter::Linear },
|
||||||
|
{ "NearestMipmapNearest", TextureFilter::NearestMipmapNearest },
|
||||||
|
{ "LinearMipmapNearest", TextureFilter::LinearMipmapNearest },
|
||||||
|
{ "NearestMipmapLinear", TextureFilter::NearestMipmapLinear },
|
||||||
|
{ "LinearMipmapLinear", TextureFilter::LinearMipmapLinear }
|
||||||
|
});
|
||||||
|
|
||||||
|
lua.new_enum<TextureWrap>("TextureWrap", {
|
||||||
|
{ "Repeat", TextureWrap::Repeat },
|
||||||
|
{ "MirroredRepeat", TextureWrap::MirroredRepeat },
|
||||||
|
{ "ClampToEdge", TextureWrap::ClampToEdge },
|
||||||
|
{ "ClampToBorder", TextureWrap::ClampToBorder }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Texture1D ───────────────────────────────────────────────────────
|
||||||
|
lua.new_usertype<Texture1D>("Texture1D",
|
||||||
|
sol::no_constructor,
|
||||||
|
sol::base_classes, sol::bases<Texture>(),
|
||||||
|
"Bind", &Texture1D::Bind,
|
||||||
|
"Unbind", &Texture1D::Unbind,
|
||||||
|
"GenerateMipmaps", &Texture1D::GenerateMipmaps,
|
||||||
|
"SetFilter", &Texture1D::SetFilter,
|
||||||
|
"SetWrap", sol::overload(
|
||||||
|
sol::resolve<void(TextureWrap, TextureWrap) const>(&Texture1D::SetWrap),
|
||||||
|
sol::resolve<void(TextureWrap, TextureWrap, TextureWrap) const>(&Texture1D::SetWrap)
|
||||||
|
),
|
||||||
|
"GetWidth", &Texture1D::GetWidth,
|
||||||
|
"GetID", &Texture1D::GetID
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Texture2D ───────────────────────────────────────────────────────
|
||||||
|
lua.new_usertype<Texture2D>("Texture2D",
|
||||||
|
sol::no_constructor,
|
||||||
|
sol::base_classes, sol::bases<Texture>(),
|
||||||
|
"Bind", &Texture2D::Bind,
|
||||||
|
"Unbind", &Texture2D::Unbind,
|
||||||
|
"GenerateMipmaps", &Texture2D::GenerateMipmaps,
|
||||||
|
"SetFilter", &Texture2D::SetFilter,
|
||||||
|
"SetWrap", sol::overload(
|
||||||
|
sol::resolve<void(TextureWrap, TextureWrap) const>(&Texture2D::SetWrap),
|
||||||
|
sol::resolve<void(TextureWrap, TextureWrap, TextureWrap) const>(&Texture2D::SetWrap)
|
||||||
|
),
|
||||||
|
"GetWidth", &Texture2D::GetWidth,
|
||||||
|
"GetHeight", &Texture2D::GetHeight,
|
||||||
|
"GetID", &Texture2D::GetID
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Texture3D ───────────────────────────────────────────────────────
|
||||||
|
lua.new_usertype<Texture3D>("Texture3D",
|
||||||
|
sol::no_constructor,
|
||||||
|
sol::base_classes, sol::bases<Texture>(),
|
||||||
|
"Bind", &Texture3D::Bind,
|
||||||
|
"Unbind", &Texture3D::Unbind,
|
||||||
|
"GenerateMipmaps", &Texture3D::GenerateMipmaps,
|
||||||
|
"SetFilter", &Texture3D::SetFilter,
|
||||||
|
"SetWrap", sol::overload(
|
||||||
|
sol::resolve<void(TextureWrap, TextureWrap) const>(&Texture3D::SetWrap),
|
||||||
|
sol::resolve<void(TextureWrap, TextureWrap, TextureWrap) const>(&Texture3D::SetWrap)
|
||||||
|
),
|
||||||
|
"GetWidth", &Texture3D::GetWidth,
|
||||||
|
"GetHeight", &Texture3D::GetHeight,
|
||||||
|
"GetDepth", &Texture3D::GetDepth,
|
||||||
|
"GetID", &Texture3D::GetID
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── TextureCubeMap ──────────────────────────────────────────────────
|
||||||
|
lua.new_enum<TextureCubeMap::Face>("CubeMapFace", {
|
||||||
|
{ "PositiveX", TextureCubeMap::Face::PositiveX },
|
||||||
|
{ "NegativeX", TextureCubeMap::Face::NegativeX },
|
||||||
|
{ "PositiveY", TextureCubeMap::Face::PositiveY },
|
||||||
|
{ "NegativeY", TextureCubeMap::Face::NegativeY },
|
||||||
|
{ "PositiveZ", TextureCubeMap::Face::PositiveZ },
|
||||||
|
{ "NegativeZ", TextureCubeMap::Face::NegativeZ }
|
||||||
|
});
|
||||||
|
|
||||||
|
lua.new_usertype<TextureCubeMap>("TextureCubeMap",
|
||||||
|
sol::no_constructor,
|
||||||
|
sol::base_classes, sol::bases<Texture>(),
|
||||||
|
"Bind", &TextureCubeMap::Bind,
|
||||||
|
"Unbind", &TextureCubeMap::Unbind,
|
||||||
|
"GenerateMipmaps", &TextureCubeMap::GenerateMipmaps,
|
||||||
|
"SetFilter", &TextureCubeMap::SetFilter,
|
||||||
|
"SetWrap", sol::overload(
|
||||||
|
sol::resolve<void(TextureWrap, TextureWrap) const>(&TextureCubeMap::SetWrap),
|
||||||
|
sol::resolve<void(TextureWrap, TextureWrap, TextureWrap) const>(&TextureCubeMap::SetWrap)
|
||||||
|
),
|
||||||
|
"LoadFaceDataFromFile", &TextureCubeMap::LoadFaceDataFromFile,
|
||||||
|
"GetSize", &TextureCubeMap::GetSize,
|
||||||
|
"GetID", &TextureCubeMap::GetID
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Texture static factories ────────────────────────────────────────
|
||||||
|
auto tex = lua.create_named_table("Texture");
|
||||||
|
tex["CreateTexture1D"] = static_cast<std::unique_ptr<Texture1D>(*)(uint32_t, TextureFormatType)>(&Texture::CreateTexture);
|
||||||
|
tex["CreateTexture2D"] = static_cast<std::unique_ptr<Texture2D>(*)(uint32_t, uint32_t, TextureFormatType)>(&Texture::CreateTexture);
|
||||||
|
tex["CreateTexture3D"] = static_cast<std::unique_ptr<Texture3D>(*)(uint32_t, uint32_t, uint32_t, TextureFormatType)>(&Texture::CreateTexture);
|
||||||
|
tex["CreateCubeMap"] = &Texture::CreateCubeMap;
|
||||||
|
tex["LoadFromFile"] = [](const std::string& filepath) -> std::unique_ptr<Texture2D> {
|
||||||
|
std::filesystem::path p(filepath);
|
||||||
|
if (p.is_relative() && !g_ScriptDir.empty()) {
|
||||||
|
p = std::filesystem::path(g_ScriptDir) / p;
|
||||||
|
}
|
||||||
|
return Texture::LoadFromFile(p.string());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterFrameBuffer(sol::state& lua) {
|
||||||
|
// ── ColorAttachment ─────────────────────────────────────────────────
|
||||||
|
auto ca = lua.new_usertype<ColorAttachment>("ColorAttachment",
|
||||||
|
sol::constructors<ColorAttachment()>(),
|
||||||
|
"format", &ColorAttachment::format,
|
||||||
|
"minFilter", &ColorAttachment::minFilter,
|
||||||
|
"magFilter", &ColorAttachment::magFilter,
|
||||||
|
"wrapS", &ColorAttachment::wrapS,
|
||||||
|
"wrapT", &ColorAttachment::wrapT
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── FrameBuffer ─────────────────────────────────────────────────────
|
||||||
|
auto fb = lua.new_usertype<FrameBuffer>("FrameBuffer",
|
||||||
|
sol::constructors<FrameBuffer(uint32_t, uint32_t)>(),
|
||||||
|
"Bind", &FrameBuffer::Bind,
|
||||||
|
"Unbind", &FrameBuffer::Unbind,
|
||||||
|
"AddColorAttachment", &FrameBuffer::AddColorAttachment,
|
||||||
|
"Resize", &FrameBuffer::Resize,
|
||||||
|
"GetColorTexture", &FrameBuffer::GetColorTexture,
|
||||||
|
"GetColorAttachmentCount", &FrameBuffer::GetColorAttachmentCount,
|
||||||
|
"GetWidth", &FrameBuffer::GetWidth,
|
||||||
|
"GetHeight", &FrameBuffer::GetHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterEffect(sol::state& lua) {
|
||||||
|
auto fx = lua.new_usertype<Effect>("Effect",
|
||||||
|
sol::constructors<Effect(), Effect(const std::string&)>(),
|
||||||
|
"Use", &Effect::Use,
|
||||||
|
"SetSampler", &Effect::SetSampler,
|
||||||
|
"SetTexture", &Effect::SetTexture,
|
||||||
|
"SetFloat", &Effect::SetFloat,
|
||||||
|
"SetVector2", &Effect::SetVector2,
|
||||||
|
"SetVector3", &Effect::SetVector3,
|
||||||
|
"SetVector4", &Effect::SetVector4,
|
||||||
|
"SetMatrix4", &Effect::SetMatrix4,
|
||||||
|
"Render", &Effect::Render
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterOpenGL(sol::state& lua) {
|
||||||
|
auto gl = lua.create_named_table("gl");
|
||||||
|
|
||||||
|
// ── Clear ───────────────────────────────────────────────────────────
|
||||||
|
gl["Clear"] = sol::overload(
|
||||||
|
static_cast<void(*)(float, float, float, float)>(&OpenGL::Clear),
|
||||||
|
static_cast<void(*)(const Vector3&)>(&OpenGL::Clear),
|
||||||
|
static_cast<void(*)(const Vector4&)>(&OpenGL::Clear)
|
||||||
|
);
|
||||||
|
gl["ClearDepth"] = &OpenGL::ClearDepth;
|
||||||
|
|
||||||
|
// ── Viewport ────────────────────────────────────────────────────────
|
||||||
|
gl["SetViewport"] = &OpenGL::SetViewport;
|
||||||
|
|
||||||
|
// ── Blending ────────────────────────────────────────────────────────
|
||||||
|
gl["EnableBlending"] = &OpenGL::EnableBlending;
|
||||||
|
gl["DisableBlending"] = &OpenGL::DisableBlending;
|
||||||
|
gl["SetBlendFunc"] = &OpenGL::SetBlendFunc;
|
||||||
|
|
||||||
|
// ── Depth ───────────────────────────────────────────────────────────
|
||||||
|
gl["EnableDepthTest"] = &OpenGL::EnableDepthTest;
|
||||||
|
gl["DisableDepthTest"] = &OpenGL::DisableDepthTest;
|
||||||
|
gl["SetDepthFunc"] = &OpenGL::SetDepthFunc;
|
||||||
|
gl["SetDepthMask"] = &OpenGL::SetDepthMask;
|
||||||
|
|
||||||
|
// ── Culling ─────────────────────────────────────────────────────────
|
||||||
|
gl["EnableCullFace"] = &OpenGL::EnableCullFace;
|
||||||
|
gl["DisableCullFace"] = &OpenGL::DisableCullFace;
|
||||||
|
gl["SetCullFace"] = &OpenGL::SetCullFace;
|
||||||
|
|
||||||
|
// ── Scissor ─────────────────────────────────────────────────────────
|
||||||
|
gl["EnableScissorTest"] = &OpenGL::EnableScissorTest;
|
||||||
|
gl["DisableScissorTest"] = &OpenGL::DisableScissorTest;
|
||||||
|
gl["SetScissor"] = &OpenGL::SetScissor;
|
||||||
|
|
||||||
|
// ── Wireframe ───────────────────────────────────────────────────────
|
||||||
|
gl["SetWireframe"] = &OpenGL::SetWireframe;
|
||||||
|
|
||||||
|
// ── Color Mask ──────────────────────────────────────────────────────
|
||||||
|
gl["SetColorMask"] = &OpenGL::SetColorMask;
|
||||||
|
|
||||||
|
// ── Textures ─────────────────────────────────────────────────────────
|
||||||
|
gl["ActiveTexture"] = &OpenGL::ActiveTexture;
|
||||||
|
|
||||||
|
// ── GL enum constants ───────────────────────────────────────────────
|
||||||
|
auto glEnum = lua.create_named_table("GL");
|
||||||
|
|
||||||
|
// Blend factors
|
||||||
|
glEnum["ZERO"] = GL_ZERO;
|
||||||
|
glEnum["ONE"] = GL_ONE;
|
||||||
|
glEnum["SRC_COLOR"] = GL_SRC_COLOR;
|
||||||
|
glEnum["ONE_MINUS_SRC_COLOR"] = GL_ONE_MINUS_SRC_COLOR;
|
||||||
|
glEnum["DST_COLOR"] = GL_DST_COLOR;
|
||||||
|
glEnum["ONE_MINUS_DST_COLOR"] = GL_ONE_MINUS_DST_COLOR;
|
||||||
|
glEnum["SRC_ALPHA"] = GL_SRC_ALPHA;
|
||||||
|
glEnum["ONE_MINUS_SRC_ALPHA"] = GL_ONE_MINUS_SRC_ALPHA;
|
||||||
|
glEnum["DST_ALPHA"] = GL_DST_ALPHA;
|
||||||
|
glEnum["ONE_MINUS_DST_ALPHA"] = GL_ONE_MINUS_DST_ALPHA;
|
||||||
|
|
||||||
|
// Depth functions
|
||||||
|
glEnum["NEVER"] = GL_NEVER;
|
||||||
|
glEnum["LESS"] = GL_LESS;
|
||||||
|
glEnum["EQUAL"] = GL_EQUAL;
|
||||||
|
glEnum["LEQUAL"] = GL_LEQUAL;
|
||||||
|
glEnum["GREATER"] = GL_GREATER;
|
||||||
|
glEnum["NOTEQUAL"] = GL_NOTEQUAL;
|
||||||
|
glEnum["GEQUAL"] = GL_GEQUAL;
|
||||||
|
glEnum["ALWAYS"] = GL_ALWAYS;
|
||||||
|
|
||||||
|
// Cull face
|
||||||
|
glEnum["FRONT"] = GL_FRONT;
|
||||||
|
glEnum["BACK"] = GL_BACK;
|
||||||
|
glEnum["FRONT_AND_BACK"] = GL_FRONT_AND_BACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterLuaBindings(sol::state& lua) {
|
||||||
|
lua.open_libraries(
|
||||||
|
sol::lib::base,
|
||||||
|
sol::lib::math,
|
||||||
|
sol::lib::string,
|
||||||
|
sol::lib::table,
|
||||||
|
sol::lib::package
|
||||||
|
);
|
||||||
|
|
||||||
|
RegisterTMath(lua);
|
||||||
|
RegisterTexture(lua);
|
||||||
|
RegisterFrameBuffer(lua);
|
||||||
|
RegisterEffect(lua);
|
||||||
|
RegisterOpenGL(lua);
|
||||||
|
}
|
||||||
5
src/lua.h
Normal file
5
src/lua.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sol/sol.hpp>
|
||||||
|
|
||||||
|
void RegisterLuaBindings(sol::state& lua);
|
||||||
217
src/main.cpp
Normal file
217
src/main.cpp
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_main.h>
|
||||||
|
#include "glad/gl.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xatom.h>
|
||||||
|
#include <X11/extensions/shape.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "lua.h"
|
||||||
|
#include "script.h"
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
// definition of globals
|
||||||
|
float g_Time = 0.0f;
|
||||||
|
Vector4 g_Mouse = Vector4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
std::vector<Vector4> g_Displays = {};
|
||||||
|
Vector2 g_DesktopSize = Vector2(0.0f, 0.0f);
|
||||||
|
std::string g_ScriptDir;
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
// we need a wallpaper file (.lua) to be able to run
|
||||||
|
if (argc < 2) {
|
||||||
|
SDL_Log("Usage: %s <wallpaper.lua>", argv[0]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
|
SDL_Log("SDL_Init failed: %s", SDL_GetError());
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||||
|
|
||||||
|
// Compute bounding box spanning all monitors
|
||||||
|
int numDisplays = 0;
|
||||||
|
SDL_DisplayID* displays = SDL_GetDisplays(&numDisplays);
|
||||||
|
int minX = INT_MAX, minY = INT_MAX, maxX = INT_MIN, maxY = INT_MIN;
|
||||||
|
for (int i = 0; i < numDisplays; ++i) {
|
||||||
|
SDL_Rect bounds;
|
||||||
|
if (SDL_GetDisplayBounds(displays[i], &bounds)) {
|
||||||
|
minX = std::min(minX, bounds.x);
|
||||||
|
minY = std::min(minY, bounds.y);
|
||||||
|
maxX = std::max(maxX, bounds.x + bounds.w);
|
||||||
|
maxY = std::max(maxY, bounds.y + bounds.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_Displays.push_back(Vector4(
|
||||||
|
static_cast<float>(bounds.x),
|
||||||
|
static_cast<float>(bounds.y),
|
||||||
|
static_cast<float>(bounds.w),
|
||||||
|
static_cast<float>(bounds.h)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
g_DesktopSize.x = static_cast<float>(maxX - minX);
|
||||||
|
g_DesktopSize.y = static_cast<float>(maxY - minY);
|
||||||
|
|
||||||
|
SDL_free(displays);
|
||||||
|
SDL_Log("Spanning %d display(s): %dx%d at (%d,%d)",
|
||||||
|
numDisplays, maxX - minX, maxY - minY, minX, minY);
|
||||||
|
|
||||||
|
SDL_PropertiesID props = SDL_CreateProperties();
|
||||||
|
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, "live-wallpaper");
|
||||||
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, minX);
|
||||||
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, minY);
|
||||||
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, maxX - minX);
|
||||||
|
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, maxY - minY);
|
||||||
|
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
|
||||||
|
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
|
||||||
|
|
||||||
|
SDL_SetHint(SDL_HINT_X11_WINDOW_TYPE, "_NET_WM_WINDOW_TYPE_DESKTOP");
|
||||||
|
|
||||||
|
SDL_Window* window = SDL_CreateWindowWithProperties(props);
|
||||||
|
if (!window) {
|
||||||
|
SDL_Log("SDL_CreateWindow failed: %s", SDL_GetError());
|
||||||
|
SDL_Quit();
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GLContext glContext = SDL_GL_CreateContext(window);
|
||||||
|
if (!glContext) {
|
||||||
|
SDL_Log("SDL_GL_CreateContext failed: %s", SDL_GetError());
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GL_SetSwapInterval(1); // vsync
|
||||||
|
|
||||||
|
// Load GLAD
|
||||||
|
if (!gladLoadGL((GLADloadfunc)SDL_GL_GetProcAddress)) {
|
||||||
|
SDL_Log("Failed to initialize GLAD");
|
||||||
|
SDL_GL_DestroyContext(glContext);
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetWindowParent(window, NULL);
|
||||||
|
SDL_SetWindowFocusable(window, false);
|
||||||
|
|
||||||
|
// Get the native X11 handles from SDL3 properties
|
||||||
|
SDL_PropertiesID windowProps = SDL_GetWindowProperties(window);
|
||||||
|
Display* x11Display = (Display*)SDL_GetPointerProperty(windowProps, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
|
||||||
|
Window x11Window = (Window)SDL_GetNumberProperty(windowProps, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
|
||||||
|
|
||||||
|
if (x11Display && x11Window) {
|
||||||
|
// Make the window completely click-through
|
||||||
|
Region region = XCreateRegion();
|
||||||
|
XShapeCombineRegion(x11Display, x11Window, ShapeInput, 0, 0, region, ShapeSet);
|
||||||
|
XDestroyRegion(region);
|
||||||
|
|
||||||
|
// Force window below everything (including the file manager desktop)
|
||||||
|
XLowerWindow(x11Display, x11Window);
|
||||||
|
|
||||||
|
// Set _NET_WM_STATE_BELOW so the WM keeps it at the bottom
|
||||||
|
Atom wmState = XInternAtom(x11Display, "_NET_WM_STATE", False);
|
||||||
|
Atom wmStateBelow = XInternAtom(x11Display, "_NET_WM_STATE_BELOW", False);
|
||||||
|
XChangeProperty(x11Display, x11Window, wmState, XA_ATOM, 32,
|
||||||
|
PropModeAppend, (unsigned char*)&wmStateBelow, 1);
|
||||||
|
|
||||||
|
XFlush(x11Display);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Log("OpenGL Vendor: %s", glGetString(GL_VENDOR));
|
||||||
|
SDL_Log("OpenGL Renderer: %s", glGetString(GL_RENDERER));
|
||||||
|
SDL_Log("OpenGL Version: %s", glGetString(GL_VERSION));
|
||||||
|
|
||||||
|
// OpenGL configuration
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||||
|
|
||||||
|
// Script setup
|
||||||
|
g_ScriptDir = std::filesystem::absolute(std::filesystem::path(argv[1]).parent_path()).string();
|
||||||
|
|
||||||
|
sol::state lua;
|
||||||
|
RegisterLuaBindings(lua);
|
||||||
|
|
||||||
|
Script script(lua, argv[1]);
|
||||||
|
script.Create();
|
||||||
|
|
||||||
|
double lastTime = SDL_GetTicks() / 1000.0;
|
||||||
|
double accumulatedTime = 0.0;
|
||||||
|
const double fixedTimeStep = 1.0 / 60.0;
|
||||||
|
|
||||||
|
bool running = true;
|
||||||
|
while (running) {
|
||||||
|
double currentTime = SDL_GetTicks() / 1000.0;
|
||||||
|
double deltaTime = currentTime - lastTime;
|
||||||
|
lastTime = currentTime;
|
||||||
|
accumulatedTime += deltaTime;
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_EVENT_QUIT) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse position and button state
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_EVENT_MOUSE_MOTION:
|
||||||
|
g_Mouse.x = static_cast<float>(event.motion.x);
|
||||||
|
g_Mouse.y = static_cast<float>(event.motion.y);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||||
|
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||||
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||||
|
g_Mouse.z = (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? 1.0f : 0.0f;
|
||||||
|
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||||
|
g_Mouse.w = (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canRender = false;
|
||||||
|
while (accumulatedTime >= fixedTimeStep) {
|
||||||
|
script.Update(static_cast<float>(fixedTimeStep));
|
||||||
|
accumulatedTime -= fixedTimeStep;
|
||||||
|
canRender = true;
|
||||||
|
|
||||||
|
g_Time += static_cast<float>(fixedTimeStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canRender) {
|
||||||
|
script.Render();
|
||||||
|
SDL_GL_SwapWindow(window);
|
||||||
|
} else {
|
||||||
|
SDL_Delay(16); // Sleep briefly to avoid busy-waiting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
script.Destroy();
|
||||||
|
|
||||||
|
SDL_GL_DestroyContext(glContext);
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
100
src/opengl.cpp
Normal file
100
src/opengl.cpp
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#include "opengl.h"
|
||||||
|
|
||||||
|
namespace OpenGL {
|
||||||
|
|
||||||
|
// ── Clear ───────────────────────────────────────────────────────────────
|
||||||
|
void Clear(float r, float g, float b, float a) {
|
||||||
|
glClearColor(r, g, b, a);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear(const Vector3& color) {
|
||||||
|
Clear(color.x, color.y, color.z, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear(const Vector4& color) {
|
||||||
|
Clear(color.x, color.y, color.z, color.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearDepth(float depth) {
|
||||||
|
glClearDepth(depth);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Viewport ────────────────────────────────────────────────────────────
|
||||||
|
void SetViewport(int x, int y, int width, int height) {
|
||||||
|
glViewport(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Blending ────────────────────────────────────────────────────────────
|
||||||
|
void EnableBlending() {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisableBlending() {
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetBlendFunc(GLenum src, GLenum dst) {
|
||||||
|
glBlendFunc(src, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Depth ───────────────────────────────────────────────────────────────
|
||||||
|
void EnableDepthTest() {
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisableDepthTest() {
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetDepthFunc(GLenum func) {
|
||||||
|
glDepthFunc(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetDepthMask(bool write) {
|
||||||
|
glDepthMask(write ? GL_TRUE : GL_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Culling ─────────────────────────────────────────────────────────────
|
||||||
|
void EnableCullFace() {
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisableCullFace() {
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCullFace(GLenum face) {
|
||||||
|
glCullFace(face);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Scissor ─────────────────────────────────────────────────────────────
|
||||||
|
void EnableScissorTest() {
|
||||||
|
glEnable(GL_SCISSOR_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisableScissorTest() {
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetScissor(int x, int y, int width, int height) {
|
||||||
|
glScissor(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Wireframe ───────────────────────────────────────────────────────────
|
||||||
|
void SetWireframe(bool enabled) {
|
||||||
|
glPolygonMode(GL_FRONT_AND_BACK, enabled ? GL_LINE : GL_FILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Color Mask ──────────────────────────────────────────────────────────
|
||||||
|
void SetColorMask(bool r, bool g, bool b, bool a) {
|
||||||
|
glColorMask(r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActiveTexture(size_t textureUnit)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE0 + static_cast<GLenum>(textureUnit));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace OpenGL
|
||||||
47
src/opengl.h
Normal file
47
src/opengl.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "glad/gl.h"
|
||||||
|
#include "tmath.hpp"
|
||||||
|
|
||||||
|
namespace OpenGL {
|
||||||
|
|
||||||
|
// ── Clear ───────────────────────────────────────────────────────────────
|
||||||
|
void Clear(float r, float g, float b, float a = 1.0f);
|
||||||
|
void Clear(const Vector3& color);
|
||||||
|
void Clear(const Vector4& color);
|
||||||
|
void ClearDepth(float depth = 1.0f);
|
||||||
|
|
||||||
|
// ── Viewport ────────────────────────────────────────────────────────────
|
||||||
|
void SetViewport(int x, int y, int width, int height);
|
||||||
|
|
||||||
|
// ── Blending ────────────────────────────────────────────────────────────
|
||||||
|
void EnableBlending();
|
||||||
|
void DisableBlending();
|
||||||
|
void SetBlendFunc(GLenum src, GLenum dst);
|
||||||
|
|
||||||
|
// ── Depth ───────────────────────────────────────────────────────────────
|
||||||
|
void EnableDepthTest();
|
||||||
|
void DisableDepthTest();
|
||||||
|
void SetDepthFunc(GLenum func);
|
||||||
|
void SetDepthMask(bool write);
|
||||||
|
|
||||||
|
// ── Culling ─────────────────────────────────────────────────────────────
|
||||||
|
void EnableCullFace();
|
||||||
|
void DisableCullFace();
|
||||||
|
void SetCullFace(GLenum face);
|
||||||
|
|
||||||
|
// ── Scissor ─────────────────────────────────────────────────────────────
|
||||||
|
void EnableScissorTest();
|
||||||
|
void DisableScissorTest();
|
||||||
|
void SetScissor(int x, int y, int width, int height);
|
||||||
|
|
||||||
|
// ── Wireframe ───────────────────────────────────────────────────────────
|
||||||
|
void SetWireframe(bool enabled);
|
||||||
|
|
||||||
|
// ── Color Mask ──────────────────────────────────────────────────────────
|
||||||
|
void SetColorMask(bool r, bool g, bool b, bool a);
|
||||||
|
|
||||||
|
// ── Textures ────────────────────────────────────────────────────────────
|
||||||
|
void ActiveTexture(size_t textureUnit);
|
||||||
|
|
||||||
|
} // namespace OpenGL
|
||||||
83
src/script.cpp
Normal file
83
src/script.cpp
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#include "script.h"
|
||||||
|
|
||||||
|
#include <SDL3/SDL_log.h>
|
||||||
|
|
||||||
|
Script::Script(sol::state& lua, const std::string& filepath) : m_lua(lua) {
|
||||||
|
auto result = m_lua.safe_script_file(filepath, sol::script_pass_on_error);
|
||||||
|
if (!result.valid()) {
|
||||||
|
sol::error err = result;
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load script '%s': %s",
|
||||||
|
filepath.c_str(), err.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind lifecycle functions
|
||||||
|
sol::object createObj = m_lua["_create"];
|
||||||
|
if (createObj.is<sol::protected_function>()) {
|
||||||
|
m_create = createObj.as<sol::protected_function>();
|
||||||
|
m_hasCreate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sol::object updateObj = m_lua["_update"];
|
||||||
|
if (updateObj.is<sol::protected_function>()) {
|
||||||
|
m_update = updateObj.as<sol::protected_function>();
|
||||||
|
} else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Script '%s' missing required '_update(dt)' function",
|
||||||
|
filepath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
sol::object renderObj = m_lua["_render"];
|
||||||
|
if (renderObj.is<sol::protected_function>()) {
|
||||||
|
m_render = renderObj.as<sol::protected_function>();
|
||||||
|
} else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Script '%s' missing required '_render' function",
|
||||||
|
filepath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
sol::object destroyObj = m_lua["_destroy"];
|
||||||
|
if (destroyObj.is<sol::protected_function>()) {
|
||||||
|
m_destroy = destroyObj.as<sol::protected_function>();
|
||||||
|
m_hasDestroy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Script::~Script() {
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Script::Create() {
|
||||||
|
if (!m_hasCreate) return;
|
||||||
|
auto result = m_create();
|
||||||
|
if (!result.valid()) {
|
||||||
|
sol::error err = result;
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "_create error: %s", err.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Script::Update(float dt) {
|
||||||
|
if (!m_update.valid()) return;
|
||||||
|
auto result = m_update(dt);
|
||||||
|
if (!result.valid()) {
|
||||||
|
sol::error err = result;
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "_update error: %s", err.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Script::Render() {
|
||||||
|
if (!m_render.valid()) return;
|
||||||
|
auto result = m_render();
|
||||||
|
if (!result.valid()) {
|
||||||
|
sol::error err = result;
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "_render error: %s", err.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Script::Destroy() {
|
||||||
|
if (!m_hasDestroy) return;
|
||||||
|
auto result = m_destroy();
|
||||||
|
if (!result.valid()) {
|
||||||
|
sol::error err = result;
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "_destroy error: %s", err.what());
|
||||||
|
}
|
||||||
|
m_hasDestroy = false; // Prevent double-destroy
|
||||||
|
}
|
||||||
24
src/script.h
Normal file
24
src/script.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <sol/sol.hpp>
|
||||||
|
|
||||||
|
class Script {
|
||||||
|
public:
|
||||||
|
Script(sol::state& lua, const std::string& filepath);
|
||||||
|
~Script();
|
||||||
|
|
||||||
|
void Create();
|
||||||
|
void Update(float dt);
|
||||||
|
void Render();
|
||||||
|
void Destroy();
|
||||||
|
|
||||||
|
private:
|
||||||
|
sol::state& m_lua;
|
||||||
|
sol::protected_function m_create;
|
||||||
|
sol::protected_function m_update;
|
||||||
|
sol::protected_function m_render;
|
||||||
|
sol::protected_function m_destroy;
|
||||||
|
bool m_hasCreate = false;
|
||||||
|
bool m_hasDestroy = false;
|
||||||
|
};
|
||||||
2
src/stb.cpp
Normal file
2
src/stb.cpp
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
253
src/texture.cpp
Normal file
253
src/texture.cpp
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
#include "texture.h"
|
||||||
|
|
||||||
|
#include "stb_image.h"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
struct TextureFormat {
|
||||||
|
GLint internalFormat;
|
||||||
|
GLenum format;
|
||||||
|
GLenum type;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TextureFormatType => TextureFormat mapping
|
||||||
|
static const TextureFormat textureFormatMap[] = {
|
||||||
|
{ GL_R8, GL_RED, GL_UNSIGNED_BYTE }, // R8
|
||||||
|
{ GL_RG8, GL_RG, GL_UNSIGNED_BYTE }, // RG8
|
||||||
|
{ GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE }, // RGB8
|
||||||
|
{ GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE }, // RGBA8
|
||||||
|
{ GL_R16F, GL_RED, GL_HALF_FLOAT }, // R16F
|
||||||
|
{ GL_RG16F, GL_RG, GL_HALF_FLOAT }, // RG16F
|
||||||
|
{ GL_RGB16F, GL_RGB, GL_HALF_FLOAT }, // RGB16F
|
||||||
|
{ GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT }, // RGBA16F
|
||||||
|
{ GL_R32F, GL_RED, GL_FLOAT }, // R32F
|
||||||
|
{ GL_RG32F, GL_RG, GL_FLOAT }, // RG32F
|
||||||
|
{ GL_RGB32F, GL_RGB, GL_FLOAT }, // RGB32F
|
||||||
|
{ GL_RGBA32F, GL_RGBA, GL_FLOAT } // RGBA32F
|
||||||
|
};
|
||||||
|
|
||||||
|
void Texture::Bind() const
|
||||||
|
{
|
||||||
|
glBindTexture(m_target, m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::Unbind() const
|
||||||
|
{
|
||||||
|
glBindTexture(m_target, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::GenerateMipmaps() const
|
||||||
|
{
|
||||||
|
glGenerateMipmap(m_target);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLenum ToGLFilter(TextureFilter filter)
|
||||||
|
{
|
||||||
|
switch (filter) {
|
||||||
|
case TextureFilter::Nearest: return GL_NEAREST;
|
||||||
|
case TextureFilter::Linear: return GL_LINEAR;
|
||||||
|
case TextureFilter::NearestMipmapNearest: return GL_NEAREST_MIPMAP_NEAREST;
|
||||||
|
case TextureFilter::LinearMipmapNearest: return GL_LINEAR_MIPMAP_NEAREST;
|
||||||
|
case TextureFilter::NearestMipmapLinear: return GL_NEAREST_MIPMAP_LINEAR;
|
||||||
|
case TextureFilter::LinearMipmapLinear: return GL_LINEAR_MIPMAP_LINEAR;
|
||||||
|
}
|
||||||
|
return GL_LINEAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLenum ToGLWrap(TextureWrap wrap)
|
||||||
|
{
|
||||||
|
switch (wrap) {
|
||||||
|
case TextureWrap::Repeat: return GL_REPEAT;
|
||||||
|
case TextureWrap::MirroredRepeat: return GL_MIRRORED_REPEAT;
|
||||||
|
case TextureWrap::ClampToEdge: return GL_CLAMP_TO_EDGE;
|
||||||
|
case TextureWrap::ClampToBorder: return GL_CLAMP_TO_BORDER;
|
||||||
|
}
|
||||||
|
return GL_REPEAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::SetFilter(TextureFilter min, TextureFilter mag) const
|
||||||
|
{
|
||||||
|
glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, ToGLFilter(min));
|
||||||
|
glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, ToGLFilter(mag));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::SetWrap(TextureWrap s, TextureWrap t) const
|
||||||
|
{
|
||||||
|
glTexParameteri(m_target, GL_TEXTURE_WRAP_S, ToGLWrap(s));
|
||||||
|
glTexParameteri(m_target, GL_TEXTURE_WRAP_T, ToGLWrap(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::SetWrap(TextureWrap s, TextureWrap t, TextureWrap r) const
|
||||||
|
{
|
||||||
|
glTexParameteri(m_target, GL_TEXTURE_WRAP_S, ToGLWrap(s));
|
||||||
|
glTexParameteri(m_target, GL_TEXTURE_WRAP_T, ToGLWrap(t));
|
||||||
|
glTexParameteri(m_target, GL_TEXTURE_WRAP_R, ToGLWrap(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Texture1D> Texture::CreateTexture(uint32_t width, TextureFormatType format)
|
||||||
|
{
|
||||||
|
auto tex = std::make_unique<Texture1D>();
|
||||||
|
tex->m_width = width;
|
||||||
|
tex->Bind();
|
||||||
|
tex->LoadData(format, nullptr);
|
||||||
|
tex->Unbind();
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Texture2D> Texture::CreateTexture(uint32_t width, uint32_t height, TextureFormatType format)
|
||||||
|
{
|
||||||
|
auto tex = std::make_unique<Texture2D>();
|
||||||
|
tex->m_width = width;
|
||||||
|
tex->m_height = height;
|
||||||
|
tex->Bind();
|
||||||
|
tex->LoadData(format, nullptr);
|
||||||
|
tex->Unbind();
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Texture3D> Texture::CreateTexture(uint32_t width, uint32_t height, uint32_t depth, TextureFormatType format)
|
||||||
|
{
|
||||||
|
auto tex = std::make_unique<Texture3D>();
|
||||||
|
tex->m_width = width;
|
||||||
|
tex->m_height = height;
|
||||||
|
tex->m_depth = depth;
|
||||||
|
tex->Bind();
|
||||||
|
tex->LoadData(format, nullptr);
|
||||||
|
tex->Unbind();
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<TextureCubeMap> Texture::CreateCubeMap(uint32_t size, TextureFormatType format)
|
||||||
|
{
|
||||||
|
auto tex = std::make_unique<TextureCubeMap>();
|
||||||
|
tex->m_size = size;
|
||||||
|
tex->Bind();
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
tex->LoadFaceData(static_cast<TextureCubeMap::Face>(i), format, nullptr);
|
||||||
|
}
|
||||||
|
tex->Unbind();
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Texture2D> Texture::LoadFromFile(const std::string &filepath)
|
||||||
|
{
|
||||||
|
int width, height, channels;
|
||||||
|
stbi_set_flip_vertically_on_load(true);
|
||||||
|
stbi_uc* data = stbi_load(filepath.c_str(), &width, &height, &channels, 0);
|
||||||
|
if (!data) {
|
||||||
|
throw std::runtime_error("Failed to load texture: " + filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureFormatType format;
|
||||||
|
switch (channels) {
|
||||||
|
case 1: format = TextureFormatType::R8; break;
|
||||||
|
case 2: format = TextureFormatType::RG8; break;
|
||||||
|
case 3: format = TextureFormatType::RGB8; break;
|
||||||
|
case 4: format = TextureFormatType::RGBA8; break;
|
||||||
|
default:
|
||||||
|
stbi_image_free(data);
|
||||||
|
throw std::runtime_error("Unsupported number of channels in texture: " + filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tex = CreateTexture(width, height, format);
|
||||||
|
tex->Bind();
|
||||||
|
tex->LoadData(format, data);
|
||||||
|
tex->Unbind();
|
||||||
|
|
||||||
|
stbi_image_free(data);
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture::Texture(GLenum target)
|
||||||
|
: m_target(target)
|
||||||
|
{
|
||||||
|
glGenTextures(1, &m_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture::~Texture()
|
||||||
|
{
|
||||||
|
if (m_id) {
|
||||||
|
glDeleteTextures(1, &m_id);
|
||||||
|
m_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureCubeMap::LoadFaceData(Face face, TextureFormatType format, const void* data) const
|
||||||
|
{
|
||||||
|
const TextureFormat& fmt = textureFormatMap[static_cast<int>(format)];
|
||||||
|
glTexImage2D(
|
||||||
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast<GLenum>(face),
|
||||||
|
0,
|
||||||
|
fmt.internalFormat,
|
||||||
|
m_size, m_size, 0,
|
||||||
|
fmt.format, fmt.type,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureCubeMap::LoadFaceDataFromFile(Face face, const std::string &filepath) const
|
||||||
|
{
|
||||||
|
int width, height, channels;
|
||||||
|
stbi_set_flip_vertically_on_load(false); // Cube maps typically expect unflipped images
|
||||||
|
stbi_uc* data = stbi_load(filepath.c_str(), &width, &height, &channels, 0);
|
||||||
|
if (!data) {
|
||||||
|
throw std::runtime_error("Failed to load cube map face texture: " + filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width != m_size || height != m_size) {
|
||||||
|
stbi_image_free(data);
|
||||||
|
throw std::runtime_error("Cube map face texture size mismatch: " + filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureFormatType format;
|
||||||
|
switch (channels) {
|
||||||
|
case 1: format = TextureFormatType::R8; break;
|
||||||
|
case 2: format = TextureFormatType::RG8; break;
|
||||||
|
case 3: format = TextureFormatType::RGB8; break;
|
||||||
|
case 4: format = TextureFormatType::RGBA8; break;
|
||||||
|
default:
|
||||||
|
stbi_image_free(data);
|
||||||
|
throw std::runtime_error("Unsupported number of channels in cube map face texture: " + filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadFaceData(face, format, data);
|
||||||
|
stbi_image_free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture1D::LoadData(TextureFormatType format, const void *data) const
|
||||||
|
{
|
||||||
|
const TextureFormat& fmt = textureFormatMap[static_cast<int>(format)];
|
||||||
|
glTexImage1D(
|
||||||
|
m_target,
|
||||||
|
0,
|
||||||
|
fmt.internalFormat,
|
||||||
|
m_width, 0,
|
||||||
|
fmt.format, fmt.type,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture2D::LoadData(TextureFormatType format, const void *data) const
|
||||||
|
{
|
||||||
|
const TextureFormat& fmt = textureFormatMap[static_cast<int>(format)];
|
||||||
|
glTexImage2D(
|
||||||
|
m_target,
|
||||||
|
0,
|
||||||
|
fmt.internalFormat,
|
||||||
|
m_width, m_height, 0,
|
||||||
|
fmt.format, fmt.type,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture3D::LoadData(TextureFormatType format, const void *data) const
|
||||||
|
{
|
||||||
|
const TextureFormat& fmt = textureFormatMap[static_cast<int>(format)];
|
||||||
|
glTexImage3D(
|
||||||
|
m_target,
|
||||||
|
0,
|
||||||
|
fmt.internalFormat,
|
||||||
|
m_width, m_height, m_depth, 0,
|
||||||
|
fmt.format, fmt.type,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
165
src/texture.h
Normal file
165
src/texture.h
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "gpu.hpp"
|
||||||
|
|
||||||
|
enum class TextureFormatType {
|
||||||
|
R8 = 0,
|
||||||
|
RG8,
|
||||||
|
RGB8,
|
||||||
|
RGBA8,
|
||||||
|
R16F,
|
||||||
|
RG16F,
|
||||||
|
RGB16F,
|
||||||
|
RGBA16F,
|
||||||
|
R32F,
|
||||||
|
RG32F,
|
||||||
|
RGB32F,
|
||||||
|
RGBA32F
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TextureFilter {
|
||||||
|
Nearest,
|
||||||
|
Linear,
|
||||||
|
NearestMipmapNearest,
|
||||||
|
LinearMipmapNearest,
|
||||||
|
NearestMipmapLinear,
|
||||||
|
LinearMipmapLinear
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TextureWrap {
|
||||||
|
Repeat,
|
||||||
|
MirroredRepeat,
|
||||||
|
ClampToEdge,
|
||||||
|
ClampToBorder
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward-declare texture types
|
||||||
|
class Texture1D;
|
||||||
|
class Texture2D;
|
||||||
|
class Texture3D;
|
||||||
|
class TextureCubeMap;
|
||||||
|
|
||||||
|
// RAII wrapper base class for OpenGL textures
|
||||||
|
class Texture : public IGPUObject<GLuint> {
|
||||||
|
public:
|
||||||
|
Texture(GLenum target);
|
||||||
|
virtual ~Texture();
|
||||||
|
|
||||||
|
void Bind() const override;
|
||||||
|
void Unbind() const override;
|
||||||
|
void GenerateMipmaps() const;
|
||||||
|
|
||||||
|
void SetFilter(TextureFilter min, TextureFilter mag) const;
|
||||||
|
void SetWrap(TextureWrap s, TextureWrap t) const;
|
||||||
|
void SetWrap(TextureWrap s, TextureWrap t, TextureWrap r) const;
|
||||||
|
|
||||||
|
static std::unique_ptr<Texture1D> CreateTexture(uint32_t width, TextureFormatType format);
|
||||||
|
static std::unique_ptr<Texture2D> CreateTexture(uint32_t width, uint32_t height, TextureFormatType format);
|
||||||
|
static std::unique_ptr<Texture3D> CreateTexture(uint32_t width, uint32_t height, uint32_t depth, TextureFormatType format);
|
||||||
|
static std::unique_ptr<TextureCubeMap> CreateCubeMap(uint32_t size, TextureFormatType format);
|
||||||
|
static std::unique_ptr<Texture2D> LoadFromFile(const std::string& filepath);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
GLenum m_target;
|
||||||
|
|
||||||
|
virtual void LoadData(
|
||||||
|
TextureFormatType format,
|
||||||
|
const void* data
|
||||||
|
) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Texture1D : public Texture {
|
||||||
|
friend class Texture;
|
||||||
|
public:
|
||||||
|
Texture1D() : Texture(GL_TEXTURE_1D) {}
|
||||||
|
|
||||||
|
uint32_t GetWidth() const { return m_width; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t m_width;
|
||||||
|
|
||||||
|
void LoadData(
|
||||||
|
TextureFormatType format,
|
||||||
|
const void* data
|
||||||
|
) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Texture2D : public Texture {
|
||||||
|
friend class Texture;
|
||||||
|
friend class FrameBuffer;
|
||||||
|
public:
|
||||||
|
Texture2D() : Texture(GL_TEXTURE_2D) {}
|
||||||
|
|
||||||
|
uint32_t GetWidth() const { return m_width; }
|
||||||
|
uint32_t GetHeight() const { return m_height; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t m_width;
|
||||||
|
uint32_t m_height;
|
||||||
|
|
||||||
|
void LoadData(
|
||||||
|
TextureFormatType format,
|
||||||
|
const void* data
|
||||||
|
) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Texture3D : public Texture {
|
||||||
|
friend class Texture;
|
||||||
|
public:
|
||||||
|
Texture3D() : Texture(GL_TEXTURE_3D) {}
|
||||||
|
|
||||||
|
uint32_t GetWidth() const { return m_width; }
|
||||||
|
uint32_t GetHeight() const { return m_height; }
|
||||||
|
uint32_t GetDepth() const { return m_depth; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t m_width;
|
||||||
|
uint32_t m_height;
|
||||||
|
uint32_t m_depth;
|
||||||
|
|
||||||
|
void LoadData(
|
||||||
|
TextureFormatType format,
|
||||||
|
const void* data
|
||||||
|
) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextureCubeMap : public Texture {
|
||||||
|
friend class Texture;
|
||||||
|
public:
|
||||||
|
TextureCubeMap() : Texture(GL_TEXTURE_CUBE_MAP) {}
|
||||||
|
|
||||||
|
uint32_t GetSize() const { return m_size; }
|
||||||
|
|
||||||
|
enum class Face {
|
||||||
|
PositiveX = 0,
|
||||||
|
NegativeX,
|
||||||
|
PositiveY,
|
||||||
|
NegativeY,
|
||||||
|
PositiveZ,
|
||||||
|
NegativeZ
|
||||||
|
};
|
||||||
|
|
||||||
|
void LoadFaceData(
|
||||||
|
Face face,
|
||||||
|
TextureFormatType format,
|
||||||
|
const void* data
|
||||||
|
) const;
|
||||||
|
|
||||||
|
void LoadFaceDataFromFile(
|
||||||
|
Face face,
|
||||||
|
const std::string& filepath
|
||||||
|
) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t m_size;
|
||||||
|
|
||||||
|
void LoadData(
|
||||||
|
TextureFormatType format,
|
||||||
|
const void* data
|
||||||
|
) const override {}
|
||||||
|
};
|
||||||
2
src/tmath.cpp
Normal file
2
src/tmath.cpp
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#define TMATH_IMPLEMENTATION
|
||||||
|
#include "tmath.hpp"
|
||||||
965
src/tmath.hpp
Normal file
965
src/tmath.hpp
Normal file
@@ -0,0 +1,965 @@
|
|||||||
|
#ifndef TMATH_HPP
|
||||||
|
#define TMATH_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cmath>
|
||||||
|
#include <numbers>
|
||||||
|
#include <array>
|
||||||
|
#include <ostream>
|
||||||
|
#include <concepts>
|
||||||
|
|
||||||
|
#if defined(__SSE__) || (defined(_M_IX86_FP) && _M_IX86_FP > 0) || defined(_M_X64)
|
||||||
|
#define TMATH_SSE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TMATH_SSE)
|
||||||
|
# if defined(_M_IX86_FP)
|
||||||
|
# if _M_IX86_FP >=2
|
||||||
|
# define TMATH_SSE4_1
|
||||||
|
# endif
|
||||||
|
# elif defined(__SSE4_1__)
|
||||||
|
# define TMATH_SSE4_1
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# include <intrin.h>
|
||||||
|
#elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))
|
||||||
|
# include <x86intrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
# include <xmmintrin.h>
|
||||||
|
|
||||||
|
# if defined(TMATH_SSE4_1)
|
||||||
|
# include <smmintrin.h>
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef TMATH_USE_NAMESPACE
|
||||||
|
namespace tm {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace consts {
|
||||||
|
inline constexpr float Pi = std::numbers::pi_v<float>;
|
||||||
|
inline constexpr float InvPi = std::numbers::inv_pi_v<float>;
|
||||||
|
inline constexpr float InvTwoPi = 1.0f / (Pi * 2.0f);
|
||||||
|
inline constexpr float HalfPi = Pi / 2.0f;
|
||||||
|
inline constexpr float QuarPi = Pi / 4.0f;
|
||||||
|
inline constexpr float TwoPi = Pi * 2.0f;
|
||||||
|
inline constexpr float Epsilon = 1e-5f;
|
||||||
|
inline constexpr float E = std::numbers::e_v<float>;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline constexpr float radians(float degrees) {
|
||||||
|
return degrees * (consts::Pi / 180.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline constexpr float degrees(float radians) {
|
||||||
|
return radians * (180.0f / consts::Pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline constexpr float clamp(float v, float lo, float hi) {
|
||||||
|
return v < lo ? lo : (v > hi ? hi : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline constexpr float lerp(float a, float b, float t) {
|
||||||
|
return a * (1.0f - t) + b * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Vector2 {
|
||||||
|
public:
|
||||||
|
constexpr Vector2() = default;
|
||||||
|
constexpr Vector2(float v) : x(v), y(v) {}
|
||||||
|
constexpr Vector2(float x, float y) : x(x), y(y) {}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float dot(const Vector2& v) const {
|
||||||
|
return x * v.x + y * v.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2D cross product (returns scalar: the z-component of the 3D cross)
|
||||||
|
[[nodiscard]] constexpr float cross(const Vector2& v) const {
|
||||||
|
return x * v.y - y * v.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline float length() const {
|
||||||
|
return std::sqrt(this->dot(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float lengthSquared() const {
|
||||||
|
return this->dot(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vector2 normalized() const {
|
||||||
|
float nlen = 1.0f / length();
|
||||||
|
return (*this) * nlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline float distance(const Vector2& v) const {
|
||||||
|
return (*this - v).length();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 lerp(const Vector2& to, float factor) const {
|
||||||
|
return (*this) * (1.0f - factor) + to * factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 reflect(const Vector2& normal) const {
|
||||||
|
return *this - normal * (2.0f * dot(normal));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 operator -() const {
|
||||||
|
return Vector2(-x, -y);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 operator +(const Vector2& o) const {
|
||||||
|
return Vector2(x + o.x, y + o.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 operator -(const Vector2& o) const {
|
||||||
|
return Vector2(x - o.x, y - o.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 operator *(const Vector2& o) const {
|
||||||
|
return Vector2(x * o.x, y * o.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 operator /(const Vector2& o) const {
|
||||||
|
return Vector2(x / o.x, y / o.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 operator *(float o) const {
|
||||||
|
return Vector2(x * o, y * o);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 operator /(float o) const {
|
||||||
|
return Vector2(x / o, y / o);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Vector2& operator +=(const Vector2& o) { x += o.x; y += o.y; return *this; }
|
||||||
|
constexpr Vector2& operator -=(const Vector2& o) { x -= o.x; y -= o.y; return *this; }
|
||||||
|
constexpr Vector2& operator *=(const Vector2& o) { x *= o.x; y *= o.y; return *this; }
|
||||||
|
constexpr Vector2& operator /=(const Vector2& o) { x /= o.x; y /= o.y; return *this; }
|
||||||
|
constexpr Vector2& operator *=(float o) { x *= o; y *= o; return *this; }
|
||||||
|
constexpr Vector2& operator /=(float o) { x /= o; y /= o; return *this; }
|
||||||
|
|
||||||
|
constexpr bool operator ==(const Vector2&) const = default;
|
||||||
|
|
||||||
|
[[nodiscard]] friend constexpr Vector2 operator *(float s, const Vector2& v) { return v * s; }
|
||||||
|
|
||||||
|
float x = 0.0f, y = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TMATH_IMPLEMENTATION
|
||||||
|
std::ostream& operator<<(std::ostream& o, const Vector2& v) {
|
||||||
|
return o << "Vector2(" << v.x << ", " << v.y << ")";
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline std::ostream& operator<<(std::ostream& o, const Vector2& v) {
|
||||||
|
return o << "Vector2(" << v.x << ", " << v.y << ")";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class Vector3 {
|
||||||
|
public:
|
||||||
|
constexpr Vector3() = default;
|
||||||
|
constexpr Vector3(float v) : x(v), y(v), z(v) {}
|
||||||
|
constexpr Vector3(float x, float y, float z) : x(x), y(y), z(z) {}
|
||||||
|
constexpr Vector3(const Vector2& v, float z = 0.0f) : x(v.x), y(v.y), z(z) {}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 toVector2() const { return Vector2(x, y); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float dot(const Vector3& v) const {
|
||||||
|
return x * v.x + y * v.y + z * v.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 cross(const Vector3& v) const {
|
||||||
|
return Vector3(
|
||||||
|
y * v.z - z * v.y,
|
||||||
|
z * v.x - x * v.z,
|
||||||
|
x * v.y - y * v.x
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 lerp(const Vector3& to, float factor) const {
|
||||||
|
return (*this) * (1.0f - factor) + to * factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline float length() const {
|
||||||
|
return std::sqrt(this->dot(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float lengthSquared() const {
|
||||||
|
return this->dot(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vector3 normalized() const {
|
||||||
|
float nlen = 1.0f / length();
|
||||||
|
return (*this) * nlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline float distance(const Vector3& v) const {
|
||||||
|
return (*this - v).length();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 reflect(const Vector3& normal) const {
|
||||||
|
return *this - normal * (2.0f * dot(normal));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vector3 refract(const Vector3& normal, float eta) const {
|
||||||
|
const float cosI = -dot(normal);
|
||||||
|
const float sinT2 = eta * eta * (1.0f - cosI * cosI);
|
||||||
|
if (sinT2 > 1.0f) return Vector3(0.0f);
|
||||||
|
const float cosT = std::sqrt(1.0f - sinT2);
|
||||||
|
return (*this) * eta + normal * (eta * cosI - cosT);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 operator -() const {
|
||||||
|
return Vector3(-x, -y, -z);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 operator +(const Vector3& o) const {
|
||||||
|
return Vector3(x + o.x, y + o.y, z + o.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 operator -(const Vector3& o) const {
|
||||||
|
return Vector3(x - o.x, y - o.y, z - o.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 operator *(const Vector3& o) const {
|
||||||
|
return Vector3(x * o.x, y * o.y, z * o.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 operator /(const Vector3& o) const {
|
||||||
|
return Vector3(x / o.x, y / o.y, z / o.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 operator *(float o) const {
|
||||||
|
return Vector3(x * o, y * o, z * o);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector3 operator /(float o) const {
|
||||||
|
return Vector3(x / o, y / o, z / o);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Vector3& operator +=(const Vector3& o) { x += o.x; y += o.y; z += o.z; return *this; }
|
||||||
|
constexpr Vector3& operator -=(const Vector3& o) { x -= o.x; y -= o.y; z -= o.z; return *this; }
|
||||||
|
constexpr Vector3& operator *=(const Vector3& o) { x *= o.x; y *= o.y; z *= o.z; return *this; }
|
||||||
|
constexpr Vector3& operator /=(const Vector3& o) { x /= o.x; y /= o.y; z /= o.z; return *this; }
|
||||||
|
constexpr Vector3& operator *=(float o) { x *= o; y *= o; z *= o; return *this; }
|
||||||
|
constexpr Vector3& operator /=(float o) { x /= o; y /= o; z /= o; return *this; }
|
||||||
|
|
||||||
|
constexpr bool operator ==(const Vector3&) const = default;
|
||||||
|
|
||||||
|
[[nodiscard]] friend constexpr Vector3 operator *(float s, const Vector3& v) { return v * s; }
|
||||||
|
|
||||||
|
float x = 0.0f, y = 0.0f, z = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TMATH_IMPLEMENTATION
|
||||||
|
std::ostream& operator<<(std::ostream& o, const Vector3& v) {
|
||||||
|
return o << "Vector3(" << v.x << ", " << v.y << ", " << v.z << ")";
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline std::ostream& operator<<(std::ostream& o, const Vector3& v) {
|
||||||
|
return o << "Vector3(" << v.x << ", " << v.y << ", " << v.z << ")";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class Vector4 {
|
||||||
|
public:
|
||||||
|
constexpr Vector4() = default;
|
||||||
|
constexpr Vector4(float v) : x(v), y(v), z(v), w(v) {}
|
||||||
|
constexpr Vector4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {}
|
||||||
|
constexpr Vector4(const Vector2& v, float z = 0.0f, float w = 0.0f) : x(v.x), y(v.y), z(z), w(w) {}
|
||||||
|
constexpr Vector4(const Vector3& v, float w = 0.0f) : x(v.x), y(v.y), z(v.z), w(w) {}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 toVector2() const { return Vector2(x, y); }
|
||||||
|
[[nodiscard]] constexpr Vector3 toVector3() const { return Vector3(x, y, z); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float dot(const Vector4& v) const {
|
||||||
|
return x * v.x + y * v.y + z * v.z + w * v.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline float length() const {
|
||||||
|
return std::sqrt(this->dot(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float lengthSquared() const {
|
||||||
|
return this->dot(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vector4 normalized() const {
|
||||||
|
float nlen = 1.0f / length();
|
||||||
|
return (*this) * nlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector4 lerp(const Vector4& to, float factor) const {
|
||||||
|
return (*this) * (1.0f - factor) + to * factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector4 operator -() const {
|
||||||
|
return Vector4(-x, -y, -z, -w);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector4 operator +(const Vector4& o) const {
|
||||||
|
return Vector4(x + o.x, y + o.y, z + o.z, w + o.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector4 operator -(const Vector4& o) const {
|
||||||
|
return Vector4(x - o.x, y - o.y, z - o.z, w - o.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector4 operator *(const Vector4& o) const {
|
||||||
|
return Vector4(x * o.x, y * o.y, z * o.z, w * o.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector4 operator /(const Vector4& o) const {
|
||||||
|
return Vector4(x / o.x, y / o.y, z / o.z, w / o.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector4 operator *(float o) const {
|
||||||
|
return Vector4(x * o, y * o, z * o, w * o);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector4 operator /(float o) const {
|
||||||
|
return Vector4(x / o, y / o, z / o, w / o);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Vector4& operator +=(const Vector4& o) { x += o.x; y += o.y; z += o.z; w += o.w; return *this; }
|
||||||
|
constexpr Vector4& operator -=(const Vector4& o) { x -= o.x; y -= o.y; z -= o.z; w -= o.w; return *this; }
|
||||||
|
constexpr Vector4& operator *=(const Vector4& o) { x *= o.x; y *= o.y; z *= o.z; w *= o.w; return *this; }
|
||||||
|
constexpr Vector4& operator /=(const Vector4& o) { x /= o.x; y /= o.y; z /= o.z; w /= o.w; return *this; }
|
||||||
|
constexpr Vector4& operator *=(float o) { x *= o; y *= o; z *= o; w *= o; return *this; }
|
||||||
|
constexpr Vector4& operator /=(float o) { x /= o; y /= o; z /= o; w /= o; return *this; }
|
||||||
|
|
||||||
|
constexpr bool operator ==(const Vector4&) const = default;
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float& operator [](size_t i) {
|
||||||
|
switch (i) {
|
||||||
|
default:
|
||||||
|
case 0: return x;
|
||||||
|
case 1: return y;
|
||||||
|
case 2: return z;
|
||||||
|
case 3: return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr const float& operator [](size_t i) const {
|
||||||
|
switch (i) {
|
||||||
|
default:
|
||||||
|
case 0: return x;
|
||||||
|
case 1: return y;
|
||||||
|
case 2: return z;
|
||||||
|
case 3: return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] friend constexpr Vector4 operator *(float s, const Vector4& v) { return v * s; }
|
||||||
|
|
||||||
|
float x = 0.0f, y = 0.0f, z = 0.0f, w = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TMATH_IMPLEMENTATION
|
||||||
|
std::ostream& operator<<(std::ostream& o, const Vector4& v) {
|
||||||
|
return o << "Vector4(" << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ")";
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline std::ostream& operator<<(std::ostream& o, const Vector4& v) {
|
||||||
|
return o << "Vector4(" << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ")";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class Matrix4 {
|
||||||
|
public:
|
||||||
|
constexpr Matrix4() : m_rows{} {}
|
||||||
|
constexpr Matrix4(const std::array<float, 16>& m) {
|
||||||
|
m_rows[0] = Vector4(m[0], m[1], m[2], m[3]);
|
||||||
|
m_rows[1] = Vector4(m[4], m[5], m[6], m[7]);
|
||||||
|
m_rows[2] = Vector4(m[8], m[9], m[10], m[11]);
|
||||||
|
m_rows[3] = Vector4(m[12], m[13], m[14], m[15]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] float* data() { return &m_rows[0][0]; }
|
||||||
|
[[nodiscard]] const float* data() const { return &m_rows[0][0]; }
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr Matrix4 identity() {
|
||||||
|
return Matrix4({
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr Matrix4 translation(const Vector3& t) {
|
||||||
|
return Matrix4({
|
||||||
|
1, 0, 0, t.x,
|
||||||
|
0, 1, 0, t.y,
|
||||||
|
0, 0, 1, t.z,
|
||||||
|
0, 0, 0, 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr Matrix4 scale(const Vector3& s) {
|
||||||
|
return Matrix4({
|
||||||
|
s.x, 0, 0, 0,
|
||||||
|
0, s.y, 0, 0,
|
||||||
|
0, 0, s.z, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr Matrix4 uniformScale(float s) {
|
||||||
|
return Matrix4({
|
||||||
|
s, 0, 0, 0,
|
||||||
|
0, s, 0, 0,
|
||||||
|
0, 0, s, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Matrix4 rotationX(float angle) {
|
||||||
|
const float s = std::sin(angle), c = std::cos(angle);
|
||||||
|
return Matrix4({
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, c, -s, 0,
|
||||||
|
0, s, c, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Matrix4 rotationY(float angle) {
|
||||||
|
const float s = std::sin(angle), c = std::cos(angle);
|
||||||
|
return Matrix4({
|
||||||
|
c, 0, s, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
-s, 0, c, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Matrix4 rotationZ(float angle) {
|
||||||
|
const float s = std::sin(angle), c = std::cos(angle);
|
||||||
|
return Matrix4({
|
||||||
|
c, -s, 0, 0,
|
||||||
|
s, c, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr Matrix4 rotation(const Vector3& forward, const Vector3& up, const Vector3& right) {
|
||||||
|
const Vector3& f = forward, u = up, r = right;
|
||||||
|
return Matrix4({
|
||||||
|
r.x, r.y, r.z, 0.0f,
|
||||||
|
u.x, u.y, u.z, 0.0f,
|
||||||
|
f.x, f.y, f.z, 0.0f,
|
||||||
|
0.0f, 0.0f, 0.0f, 1.0f
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Matrix4 rotation(const Vector3& forward, const Vector3& up) {
|
||||||
|
const Vector3 f = forward.normalized();
|
||||||
|
Vector3 r = up.normalized();
|
||||||
|
r = r.cross(f);
|
||||||
|
|
||||||
|
Vector3 u = f.cross(r);
|
||||||
|
return rotation(f, u, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Matrix4 angleAxis(float angle, const Vector3& axis) {
|
||||||
|
const float s = std::sin(angle),
|
||||||
|
c = std::cos(angle),
|
||||||
|
t = 1.0f - c;
|
||||||
|
|
||||||
|
Vector3 ax = axis.normalized();
|
||||||
|
float x = ax.x, y = ax.y, z = ax.z;
|
||||||
|
return Matrix4({
|
||||||
|
t * x * x + c, t * x * y - z * s, t * x * z + y * s, 0,
|
||||||
|
t * x * y + z * s, t * y * y + c, t * y * z - x * s, 0,
|
||||||
|
t * x * z - y * s, t * y * z + x * s, t * z * z + c, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr Matrix4 ortho(float left, float right, float bottom, float top, float near, float far) {
|
||||||
|
const float w = right - left;
|
||||||
|
const float h = top - bottom;
|
||||||
|
const float d = far - near;
|
||||||
|
return Matrix4({
|
||||||
|
2.0f / w, 0.0f, 0.0f, -((right + left) / w),
|
||||||
|
0.0f, 2.0f / h, 0.0f, -((top + bottom) / h),
|
||||||
|
0.0f, 0.0f, -2.0f / d, -((far + near) / d),
|
||||||
|
0.0f, 0.0f, 0.0f, 1.0f
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Matrix4 perspective(float fov, float aspect, float near, float far) {
|
||||||
|
const float thf = std::tan(fov / 2.0f);
|
||||||
|
return Matrix4({
|
||||||
|
1.0f / (aspect * thf), 0.0f, 0.0f, 0.0f,
|
||||||
|
0.0f, 1.0f / thf, 0.0f, 0.0f,
|
||||||
|
0.0f, 0.0f, -((far + near) / (far - near)), -((2.0f * far * near) / (far - near)),
|
||||||
|
0.0f, 0.0f, -1.0f, 0.0f
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Matrix4 lookAt(const Vector3& eye, const Vector3& target, const Vector3& up) {
|
||||||
|
const Vector3 f = (target - eye).normalized();
|
||||||
|
const Vector3 r = f.cross(up.normalized()).normalized();
|
||||||
|
const Vector3 u = r.cross(f);
|
||||||
|
return Matrix4({
|
||||||
|
r.x, r.y, r.z, -r.dot(eye),
|
||||||
|
u.x, u.y, u.z, -u.dot(eye),
|
||||||
|
-f.x, -f.y, -f.z, f.dot(eye),
|
||||||
|
0.0f, 0.0f, 0.0f, 1.0f
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Matrix4 operator *(const Matrix4& mat) const {
|
||||||
|
Matrix4 ret{};
|
||||||
|
#if defined(TMATH_SSE)
|
||||||
|
__m128 out0x = lincomb_SSE(s_rows[0], mat);
|
||||||
|
__m128 out1x = lincomb_SSE(s_rows[1], mat);
|
||||||
|
__m128 out2x = lincomb_SSE(s_rows[2], mat);
|
||||||
|
__m128 out3x = lincomb_SSE(s_rows[3], mat);
|
||||||
|
ret.s_rows[0] = out0x;
|
||||||
|
ret.s_rows[1] = out1x;
|
||||||
|
ret.s_rows[2] = out2x;
|
||||||
|
ret.s_rows[3] = out3x;
|
||||||
|
#else
|
||||||
|
for (size_t i = 0; i < 4; i++) {
|
||||||
|
for (size_t j = 0; j < 4; j++) {
|
||||||
|
ret[i][j] = 0.0f;
|
||||||
|
for (size_t k = 0; k < 4; k++) {
|
||||||
|
ret[i][j] += (*this)[i][k] * mat[k][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vector4 operator *(const Vector4& v) const {
|
||||||
|
const Matrix4& m = (*this);
|
||||||
|
return Vector4(
|
||||||
|
m[0][0] * v.x + m[0][1] * v.y + m[0][2] * v.z + m[0][3] * v.w,
|
||||||
|
m[1][0] * v.x + m[1][1] * v.y + m[1][2] * v.z + m[1][3] * v.w,
|
||||||
|
m[2][0] * v.x + m[2][1] * v.y + m[2][2] * v.z + m[2][3] * v.w,
|
||||||
|
m[3][0] * v.x + m[3][1] * v.y + m[3][2] * v.z + m[3][3] * v.w
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vector3 operator *(const Vector3& v) const {
|
||||||
|
return ((*this) * Vector4(v, 1.0f)).toVector3();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Matrix4 transposed() const {
|
||||||
|
return Matrix4({
|
||||||
|
m_rows[0].x, m_rows[1].x, m_rows[2].x, m_rows[3].x,
|
||||||
|
m_rows[0].y, m_rows[1].y, m_rows[2].y, m_rows[3].y,
|
||||||
|
m_rows[0].z, m_rows[1].z, m_rows[2].z, m_rows[3].z,
|
||||||
|
m_rows[0].w, m_rows[1].w, m_rows[2].w, m_rows[3].w
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float determinant() const {
|
||||||
|
const float* m = &m_rows[0].x;
|
||||||
|
float a0 = m[0] * (m[5] * (m[10] * m[15] - m[11] * m[14]) -
|
||||||
|
m[6] * (m[9] * m[15] - m[11] * m[13]) +
|
||||||
|
m[7] * (m[9] * m[14] - m[10] * m[13]));
|
||||||
|
float a1 = m[1] * (m[4] * (m[10] * m[15] - m[11] * m[14]) -
|
||||||
|
m[6] * (m[8] * m[15] - m[11] * m[12]) +
|
||||||
|
m[7] * (m[8] * m[14] - m[10] * m[12]));
|
||||||
|
float a2 = m[2] * (m[4] * (m[9] * m[15] - m[11] * m[13]) -
|
||||||
|
m[5] * (m[8] * m[15] - m[11] * m[12]) +
|
||||||
|
m[7] * (m[8] * m[13] - m[9] * m[12]));
|
||||||
|
float a3 = m[3] * (m[4] * (m[9] * m[14] - m[10] * m[13]) -
|
||||||
|
m[5] * (m[8] * m[14] - m[10] * m[12]) +
|
||||||
|
m[6] * (m[8] * m[13] - m[9] * m[12]));
|
||||||
|
return a0 - a1 + a2 - a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Matrix4 inverse() const {
|
||||||
|
Matrix4 res{};
|
||||||
|
Matrix4 tmp = *this;
|
||||||
|
float* m = tmp.data();
|
||||||
|
float inv[16], det;
|
||||||
|
|
||||||
|
inv[0] = m[5] * m[10] * m[15] -
|
||||||
|
m[5] * m[11] * m[14] -
|
||||||
|
m[9] * m[6] * m[15] +
|
||||||
|
m[9] * m[7] * m[14] +
|
||||||
|
m[13] * m[6] * m[11] -
|
||||||
|
m[13] * m[7] * m[10];
|
||||||
|
|
||||||
|
inv[4] = -m[4] * m[10] * m[15] +
|
||||||
|
m[4] * m[11] * m[14] +
|
||||||
|
m[8] * m[6] * m[15] -
|
||||||
|
m[8] * m[7] * m[14] -
|
||||||
|
m[12] * m[6] * m[11] +
|
||||||
|
m[12] * m[7] * m[10];
|
||||||
|
|
||||||
|
inv[8] = m[4] * m[9] * m[15] -
|
||||||
|
m[4] * m[11] * m[13] -
|
||||||
|
m[8] * m[5] * m[15] +
|
||||||
|
m[8] * m[7] * m[13] +
|
||||||
|
m[12] * m[5] * m[11] -
|
||||||
|
m[12] * m[7] * m[9];
|
||||||
|
|
||||||
|
inv[12] = -m[4] * m[9] * m[14] +
|
||||||
|
m[4] * m[10] * m[13] +
|
||||||
|
m[8] * m[5] * m[14] -
|
||||||
|
m[8] * m[6] * m[13] -
|
||||||
|
m[12] * m[5] * m[10] +
|
||||||
|
m[12] * m[6] * m[9];
|
||||||
|
|
||||||
|
inv[1] = -m[1] * m[10] * m[15] +
|
||||||
|
m[1] * m[11] * m[14] +
|
||||||
|
m[9] * m[2] * m[15] -
|
||||||
|
m[9] * m[3] * m[14] -
|
||||||
|
m[13] * m[2] * m[11] +
|
||||||
|
m[13] * m[3] * m[10];
|
||||||
|
|
||||||
|
inv[5] = m[0] * m[10] * m[15] -
|
||||||
|
m[0] * m[11] * m[14] -
|
||||||
|
m[8] * m[2] * m[15] +
|
||||||
|
m[8] * m[3] * m[14] +
|
||||||
|
m[12] * m[2] * m[11] -
|
||||||
|
m[12] * m[3] * m[10];
|
||||||
|
|
||||||
|
inv[9] = -m[0] * m[9] * m[15] +
|
||||||
|
m[0] * m[11] * m[13] +
|
||||||
|
m[8] * m[1] * m[15] -
|
||||||
|
m[8] * m[3] * m[13] -
|
||||||
|
m[12] * m[1] * m[11] +
|
||||||
|
m[12] * m[3] * m[9];
|
||||||
|
|
||||||
|
inv[13] = m[0] * m[9] * m[14] -
|
||||||
|
m[0] * m[10] * m[13] -
|
||||||
|
m[8] * m[1] * m[14] +
|
||||||
|
m[8] * m[2] * m[13] +
|
||||||
|
m[12] * m[1] * m[10] -
|
||||||
|
m[12] * m[2] * m[9];
|
||||||
|
|
||||||
|
inv[2] = m[1] * m[6] * m[15] -
|
||||||
|
m[1] * m[7] * m[14] -
|
||||||
|
m[5] * m[2] * m[15] +
|
||||||
|
m[5] * m[3] * m[14] +
|
||||||
|
m[13] * m[2] * m[7] -
|
||||||
|
m[13] * m[3] * m[6];
|
||||||
|
|
||||||
|
inv[6] = -m[0] * m[6] * m[15] +
|
||||||
|
m[0] * m[7] * m[14] +
|
||||||
|
m[4] * m[2] * m[15] -
|
||||||
|
m[4] * m[3] * m[14] -
|
||||||
|
m[12] * m[2] * m[7] +
|
||||||
|
m[12] * m[3] * m[6];
|
||||||
|
|
||||||
|
inv[10] = m[0] * m[5] * m[15] -
|
||||||
|
m[0] * m[7] * m[13] -
|
||||||
|
m[4] * m[1] * m[15] +
|
||||||
|
m[4] * m[3] * m[13] +
|
||||||
|
m[12] * m[1] * m[7] -
|
||||||
|
m[12] * m[3] * m[5];
|
||||||
|
|
||||||
|
inv[14] = -m[0] * m[5] * m[14] +
|
||||||
|
m[0] * m[6] * m[13] +
|
||||||
|
m[4] * m[1] * m[14] -
|
||||||
|
m[4] * m[2] * m[13] -
|
||||||
|
m[12] * m[1] * m[6] +
|
||||||
|
m[12] * m[2] * m[5];
|
||||||
|
|
||||||
|
inv[3] = -m[1] * m[6] * m[11] +
|
||||||
|
m[1] * m[7] * m[10] +
|
||||||
|
m[5] * m[2] * m[11] -
|
||||||
|
m[5] * m[3] * m[10] -
|
||||||
|
m[9] * m[2] * m[7] +
|
||||||
|
m[9] * m[3] * m[6];
|
||||||
|
|
||||||
|
inv[7] = m[0] * m[6] * m[11] -
|
||||||
|
m[0] * m[7] * m[10] -
|
||||||
|
m[4] * m[2] * m[11] +
|
||||||
|
m[4] * m[3] * m[10] +
|
||||||
|
m[8] * m[2] * m[7] -
|
||||||
|
m[8] * m[3] * m[6];
|
||||||
|
|
||||||
|
inv[11] = -m[0] * m[5] * m[11] +
|
||||||
|
m[0] * m[7] * m[9] +
|
||||||
|
m[4] * m[1] * m[11] -
|
||||||
|
m[4] * m[3] * m[9] -
|
||||||
|
m[8] * m[1] * m[7] +
|
||||||
|
m[8] * m[3] * m[5];
|
||||||
|
|
||||||
|
inv[15] = m[0] * m[5] * m[10] -
|
||||||
|
m[0] * m[6] * m[9] -
|
||||||
|
m[4] * m[1] * m[10] +
|
||||||
|
m[4] * m[2] * m[9] +
|
||||||
|
m[8] * m[1] * m[6] -
|
||||||
|
m[8] * m[2] * m[5];
|
||||||
|
|
||||||
|
det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
|
||||||
|
|
||||||
|
if (det == 0)
|
||||||
|
return (*this);
|
||||||
|
|
||||||
|
det = 1.0f / det;
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++) res.data()[i] = inv[i] * det;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Vector4& operator [](size_t i) { return m_rows[i]; }
|
||||||
|
constexpr const Vector4& operator [](size_t i) const { return m_rows[i]; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
union {
|
||||||
|
Vector4 m_rows[4];
|
||||||
|
#if defined(TMATH_SSE)
|
||||||
|
__m128 s_rows[4];
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(TMATH_SSE)
|
||||||
|
inline __m128 lincomb_SSE(const __m128 &a, const Matrix4& b) const {
|
||||||
|
__m128 result;
|
||||||
|
result = _mm_mul_ps(_mm_shuffle_ps(a, a, 0x00), b.s_rows[0]);
|
||||||
|
result = _mm_add_ps(result, _mm_mul_ps(_mm_shuffle_ps(a, a, 0x55), b.s_rows[1]));
|
||||||
|
result = _mm_add_ps(result, _mm_mul_ps(_mm_shuffle_ps(a, a, 0xaa), b.s_rows[2]));
|
||||||
|
result = _mm_add_ps(result, _mm_mul_ps(_mm_shuffle_ps(a, a, 0xff), b.s_rows[3]));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TMATH_IMPLEMENTATION
|
||||||
|
std::ostream& operator<<(std::ostream& o, const Matrix4& v) {
|
||||||
|
return o << "Matrix4(\n" <<
|
||||||
|
"\t" << v[0] << "\n" <<
|
||||||
|
"\t" << v[1] << "\n" <<
|
||||||
|
"\t" << v[2] << "\n" <<
|
||||||
|
"\t" << v[3] << "\n)";
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline std::ostream& operator<<(std::ostream& o, const Matrix4& v) {
|
||||||
|
return o << "Matrix4(\n" <<
|
||||||
|
"\t" << v[0] << "\n" <<
|
||||||
|
"\t" << v[1] << "\n" <<
|
||||||
|
"\t" << v[2] << "\n" <<
|
||||||
|
"\t" << v[3] << "\n)";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class Quaternion {
|
||||||
|
public:
|
||||||
|
constexpr Quaternion() = default;
|
||||||
|
constexpr Quaternion(float v) : x(v), y(v), z(v), w(v) {}
|
||||||
|
constexpr Quaternion(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {}
|
||||||
|
constexpr Quaternion(const Vector2& v, float z = 0.0f, float w = 1.0f) : x(v.x), y(v.y), z(z), w(w) {}
|
||||||
|
constexpr Quaternion(const Vector3& v, float w = 1.0f) : x(v.x), y(v.y), z(v.z), w(w) {}
|
||||||
|
constexpr Quaternion(const Vector4& v) : x(v.x), y(v.y), z(v.z), w(v.w) {}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Quaternion axisAngle(const Vector3& axis, float angle) {
|
||||||
|
const float hs = std::sin(angle / 2.0f),
|
||||||
|
hc = std::cos(angle / 2.0f);
|
||||||
|
Vector3 a = axis.normalized();
|
||||||
|
return Quaternion(hs * a.x, hs * a.y, hs * a.z, hc);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Quaternion fromEuler(float pitch, float yaw, float roll) {
|
||||||
|
const float cp = std::cos(pitch * 0.5f), sp = std::sin(pitch * 0.5f);
|
||||||
|
const float cy = std::cos(yaw * 0.5f), sy = std::sin(yaw * 0.5f);
|
||||||
|
const float cr = std::cos(roll * 0.5f), sr = std::sin(roll * 0.5f);
|
||||||
|
return Quaternion(
|
||||||
|
sr * cp * cy - cr * sp * sy,
|
||||||
|
cr * sp * cy + sr * cp * sy,
|
||||||
|
cr * cp * sy - sr * sp * cy,
|
||||||
|
cr * cp * cy + sr * sp * sy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vector3 toEuler() const {
|
||||||
|
// Returns (pitch, yaw, roll) in radians
|
||||||
|
float sinp = 2.0f * (w * y - z * x);
|
||||||
|
float pitch;
|
||||||
|
if (std::abs(sinp) >= 1.0f)
|
||||||
|
pitch = std::copysign(consts::HalfPi, sinp);
|
||||||
|
else
|
||||||
|
pitch = std::asin(sinp);
|
||||||
|
|
||||||
|
float siny_cosp = 2.0f * (w * z + x * y);
|
||||||
|
float cosy_cosp = 1.0f - 2.0f * (y * y + z * z);
|
||||||
|
float yaw = std::atan2(siny_cosp, cosy_cosp);
|
||||||
|
|
||||||
|
float sinr_cosp = 2.0f * (w * x + y * z);
|
||||||
|
float cosr_cosp = 1.0f - 2.0f * (x * x + y * y);
|
||||||
|
float roll = std::atan2(sinr_cosp, cosr_cosp);
|
||||||
|
|
||||||
|
return Vector3(pitch, yaw, roll);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline static Quaternion fromMatrix(const Matrix4& m) {
|
||||||
|
float trace = m[0][0] + m[1][1] + m[2][2];
|
||||||
|
if (trace > 0.0f) {
|
||||||
|
float s = 0.5f / std::sqrt(trace + 1.0f);
|
||||||
|
return Quaternion(
|
||||||
|
(m[2][1] - m[1][2]) * s,
|
||||||
|
(m[0][2] - m[2][0]) * s,
|
||||||
|
(m[1][0] - m[0][1]) * s,
|
||||||
|
0.25f / s
|
||||||
|
);
|
||||||
|
} else if (m[0][0] > m[1][1] && m[0][0] > m[2][2]) {
|
||||||
|
float s = 2.0f * std::sqrt(1.0f + m[0][0] - m[1][1] - m[2][2]);
|
||||||
|
return Quaternion(
|
||||||
|
0.25f * s,
|
||||||
|
(m[0][1] + m[1][0]) / s,
|
||||||
|
(m[0][2] + m[2][0]) / s,
|
||||||
|
(m[2][1] - m[1][2]) / s
|
||||||
|
);
|
||||||
|
} else if (m[1][1] > m[2][2]) {
|
||||||
|
float s = 2.0f * std::sqrt(1.0f + m[1][1] - m[0][0] - m[2][2]);
|
||||||
|
return Quaternion(
|
||||||
|
(m[0][1] + m[1][0]) / s,
|
||||||
|
0.25f * s,
|
||||||
|
(m[1][2] + m[2][1]) / s,
|
||||||
|
(m[0][2] - m[2][0]) / s
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
float s = 2.0f * std::sqrt(1.0f + m[2][2] - m[0][0] - m[1][1]);
|
||||||
|
return Quaternion(
|
||||||
|
(m[0][2] + m[2][0]) / s,
|
||||||
|
(m[1][2] + m[2][1]) / s,
|
||||||
|
0.25f * s,
|
||||||
|
(m[1][0] - m[0][1]) / s
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Vector2 toVector2() const { return Vector2(x, y); }
|
||||||
|
[[nodiscard]] constexpr Vector3 toVector3() const { return Vector3(x, y, z); }
|
||||||
|
[[nodiscard]] constexpr Vector4 toVector4() const { return Vector4(x, y, z, w); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Quaternion conjugated() const {
|
||||||
|
return Quaternion(-x, -y, -z, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Quaternion inversed() const {
|
||||||
|
float lenSq = dot(*this);
|
||||||
|
return conjugated() * (1.0f / lenSq);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float dot(const Quaternion& q) const {
|
||||||
|
return x * q.x + y * q.y + z * q.z + w * q.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline float length() const {
|
||||||
|
return std::sqrt(this->dot(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr float lengthSquared() const {
|
||||||
|
return this->dot(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Quaternion normalized() const {
|
||||||
|
float nlen = 1.0f / length();
|
||||||
|
return (*this) * nlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Quaternion operator -() const {
|
||||||
|
return Quaternion(-x, -y, -z, -w);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Quaternion operator *(const Quaternion& r) const {
|
||||||
|
float w_ = w * r.w - x * r.x - y * r.y - z * r.z;
|
||||||
|
float x_ = x * r.w + w * r.x + y * r.z - z * r.y;
|
||||||
|
float y_ = y * r.w + w * r.y + z * r.x - x * r.z;
|
||||||
|
float z_ = z * r.w + w * r.z + x * r.y - y * r.x;
|
||||||
|
return Quaternion(x_, y_, z_, w_);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Quaternion operator *(const Vector3& r) const {
|
||||||
|
float w_ = -x * r.x - y * r.y - z * r.z;
|
||||||
|
float x_ = w * r.x + y * r.z - z * r.y;
|
||||||
|
float y_ = w * r.y + z * r.x - x * r.z;
|
||||||
|
float z_ = w * r.z + x * r.y - y * r.x;
|
||||||
|
return Quaternion(x_, y_, z_, w_);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Quaternion operator *(float r) const {
|
||||||
|
return Quaternion(x * r, y * r, z * r, w * r);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Quaternion operator +(const Quaternion& r) const {
|
||||||
|
return Quaternion(x + r.x, y + r.y, z + r.z, w + r.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr Quaternion operator -(const Quaternion& r) const {
|
||||||
|
return Quaternion(x - r.x, y - r.y, z - r.z, w - r.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator ==(const Quaternion&) const = default;
|
||||||
|
|
||||||
|
[[nodiscard]] inline Vector3 rotate(const Vector3& v) const {
|
||||||
|
const Quaternion cq = conjugated();
|
||||||
|
const Quaternion r = ((*this) * v) * cq;
|
||||||
|
return r.toVector3();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Matrix4 toMatrix4() const {
|
||||||
|
const Vector3 forward = Vector3(2.0f * (x * z - w * y), 2.0f * (y * z + w * x), 1.0f - 2.0f * (x * x + y * y));
|
||||||
|
const Vector3 up = Vector3(2.0f * (x * y + w * z), 1.0f - 2.0f * (x * x + z * z), 2.0f * (y * z - w * x));
|
||||||
|
const Vector3 right = Vector3(1.0f - 2.0f * (y * y + z * z), 2.0f * (x * y - w * z), 2.0f * (x * z + w * y));
|
||||||
|
return Matrix4::rotation(forward, up, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Quaternion nLerp(const Quaternion& to, float factor, bool shortest = true) const {
|
||||||
|
Quaternion correctTo = to;
|
||||||
|
if (shortest && dot(to) < 0.0f) {
|
||||||
|
correctTo = Quaternion(-to.x, -to.y, -to.z, -to.w);
|
||||||
|
}
|
||||||
|
return ((*this) * (1.0f - factor) + correctTo * factor).normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline Quaternion sLerp(const Quaternion& to, float factor, bool shortest = true) const {
|
||||||
|
const float THRESHOLD = 1e-3f;
|
||||||
|
|
||||||
|
float cos = dot(to);
|
||||||
|
Quaternion correctTo = to;
|
||||||
|
|
||||||
|
if (shortest && cos < 0.0f) {
|
||||||
|
cos = -cos;
|
||||||
|
correctTo = Quaternion(-to.x, -to.y, -to.z, -to.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::abs(cos) >= 1.0f - THRESHOLD)
|
||||||
|
return nLerp(to, factor, shortest);
|
||||||
|
|
||||||
|
const float sin = std::sqrt(1.0f - cos * cos);
|
||||||
|
const float angle = std::atan2(sin, cos);
|
||||||
|
const float invSin = 1.0f / sin;
|
||||||
|
|
||||||
|
const float srcFactor = std::sin((1.0f - factor) * angle) * invSin;
|
||||||
|
const float destFactor = std::sin(factor * angle) * invSin;
|
||||||
|
|
||||||
|
return (*this) * srcFactor + correctTo * destFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] friend constexpr Quaternion operator *(float s, const Quaternion& q) { return q * s; }
|
||||||
|
|
||||||
|
float x = 0.0f, y = 0.0f, z = 0.0f, w = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef TMATH_IMPLEMENTATION
|
||||||
|
std::ostream& operator<<(std::ostream& o, const Quaternion& v) {
|
||||||
|
return o << "Quaternion(" << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ")";
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline std::ostream& operator<<(std::ostream& o, const Quaternion& v) {
|
||||||
|
return o << "Quaternion(" << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ")";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef TMATH_USE_NAMESPACE
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // TMATH_HPP
|
||||||
Reference in New Issue
Block a user