Refactor code structure for improved readability and maintainability

This commit is contained in:
Diego Lopes
2026-03-18 00:25:31 -04:00
commit e538df3673
34 changed files with 9243 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
build/
.cache/
compile_commands.json
CMakeUserPresets.json
cmake/

24
.vscode/launch.json vendored Normal file
View 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
View File

@@ -0,0 +1,3 @@
{
"C_Cpp.errorSquiggles": "disabled"
}

30
.vscode/tasks.json vendored Normal file
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 KiB

21
samples/simple.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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", &degrees);
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
View File

@@ -0,0 +1,5 @@
#pragma once
#include <sol/sol.hpp>
void RegisterLuaBindings(sol::state& lua);

217
src/main.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

253
src/texture.cpp Normal file
View 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
View 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
View File

@@ -0,0 +1,2 @@
#define TMATH_IMPLEMENTATION
#include "tmath.hpp"

965
src/tmath.hpp Normal file
View 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