Initial Vectorail Core library
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
|||||||
|
/build/
|
||||||
|
/cmake-build-*/
|
||||||
|
/.cache/
|
||||||
|
/.clangd/
|
||||||
|
/compile_commands.json
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.dll
|
||||||
|
*.dylib
|
||||||
|
*.exe
|
||||||
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
|
project(vectorail-core VERSION 0.1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
find_package(PNG REQUIRED)
|
||||||
|
find_package(SDL3 CONFIG REQUIRED)
|
||||||
|
find_package(glm CONFIG REQUIRED)
|
||||||
|
|
||||||
|
add_library(vectorail-core
|
||||||
|
src/DdsTexture.cpp
|
||||||
|
src/PngTexture.cpp
|
||||||
|
src/Shader.cpp
|
||||||
|
src/Spline.cpp
|
||||||
|
src/gl_loader.cpp
|
||||||
|
)
|
||||||
|
add_library(Vectorail::Core ALIAS vectorail-core)
|
||||||
|
|
||||||
|
target_compile_features(vectorail-core PUBLIC cxx_std_23)
|
||||||
|
target_include_directories(vectorail-core
|
||||||
|
PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||||
|
)
|
||||||
|
target_link_libraries(vectorail-core
|
||||||
|
PUBLIC
|
||||||
|
SDL3::SDL3
|
||||||
|
OpenGL::GL
|
||||||
|
PNG::PNG
|
||||||
|
glm::glm
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(vectorail-core PROPERTIES
|
||||||
|
EXPORT_NAME Core
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(TARGETS vectorail-core
|
||||||
|
EXPORT VectorailCoreTargets
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||||
|
)
|
||||||
|
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
|
install(EXPORT VectorailCoreTargets
|
||||||
|
FILE VectorailCoreTargets.cmake
|
||||||
|
NAMESPACE Vectorail::
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/VectorailCore
|
||||||
|
)
|
||||||
|
|
||||||
|
configure_package_config_file(
|
||||||
|
cmake/VectorailCoreConfig.cmake.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/VectorailCoreConfig.cmake
|
||||||
|
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/VectorailCore
|
||||||
|
)
|
||||||
|
write_basic_package_version_file(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/VectorailCoreConfigVersion.cmake
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
COMPATIBILITY SameMajorVersion
|
||||||
|
)
|
||||||
|
install(FILES
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/VectorailCoreConfig.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/VectorailCoreConfigVersion.cmake
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/VectorailCore
|
||||||
|
)
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2026, Kiyooru Takasaki
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Vectorail Core
|
||||||
|
|
||||||
|
Reusable SDL 3 and OpenGL composition primitives extracted from Vectorail.
|
||||||
|
The library deliberately contains no game rules and no Groove Coaster format
|
||||||
|
knowledge.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- OpenGL function loading and shader helpers;
|
||||||
|
- spline evaluation used by rail renderers;
|
||||||
|
- PNG and uncompressed DDS texture upload helpers;
|
||||||
|
- portable SDL/OpenGL integration shared by applications built on Vectorail.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake -S . -B build
|
||||||
|
cmake --build build
|
||||||
|
cmake --install build --prefix /desired/prefix
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependencies are SDL 3, OpenGL, libpng, and GLM. Consumers link the exported
|
||||||
|
`Vectorail::Core` CMake target.
|
||||||
|
|
||||||
|
Vectorail Core is distributed under the BSD 3-Clause License.
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
include(CMakeFindDependencyMacro)
|
||||||
|
find_dependency(OpenGL)
|
||||||
|
find_dependency(PNG)
|
||||||
|
find_dependency(SDL3 CONFIG)
|
||||||
|
find_dependency(glm CONFIG)
|
||||||
|
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/VectorailCoreTargets.cmake")
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct DdsTexture {
|
||||||
|
unsigned int id = 0;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Groove Coaster's stage/menu atlases use uncompressed RGB(A) DDS surfaces.
|
||||||
|
// This intentionally small loader accepts those surfaces without introducing
|
||||||
|
// a conversion step or an external image dependency.
|
||||||
|
bool loadDdsTexture(const std::string& path, DdsTexture& out, std::string* error = nullptr);
|
||||||
|
bool loadDdsTextureBytes(const std::vector<uint8_t>& bytes, DdsTexture& out,
|
||||||
|
std::string* error = nullptr);
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "vectorail/core/DdsTexture.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// The arcade effect atlases have a .bin suffix but contain ordinary PNG
|
||||||
|
// streams. Keep loading based on the file signature rather than its suffix.
|
||||||
|
bool loadPngTexture(const std::string& path, DdsTexture& out, std::string* error = nullptr);
|
||||||
|
|
||||||
|
// MtxTexture::LoadListData format used by data/skin/skinN/img.dat: a BE
|
||||||
|
// count/offset table followed by zero or more embedded PNG streams.
|
||||||
|
bool loadPngTextureList(
|
||||||
|
const std::string& path, std::vector<DdsTexture>& out, std::string* error = nullptr);
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "vectorail/core/gl_loader.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
|
||||||
|
class Shader {
|
||||||
|
public:
|
||||||
|
unsigned int ID;
|
||||||
|
Shader(const char* vertexPath, const char* fragmentPath);
|
||||||
|
void use() const { glUseProgram(ID); }
|
||||||
|
void setBool(const std::string& name, bool value) const {
|
||||||
|
glUniform1i(glGetUniformLocation(ID, (const GLchar*)name.c_str()), (int)value);
|
||||||
|
}
|
||||||
|
void setInt(const std::string& name, int value) const {
|
||||||
|
glUniform1i(glGetUniformLocation(ID, (const GLchar*)name.c_str()), value);
|
||||||
|
}
|
||||||
|
void setFloat(const std::string& name, float value) const {
|
||||||
|
glUniform1f(glGetUniformLocation(ID, (const GLchar*)name.c_str()), value);
|
||||||
|
}
|
||||||
|
void setVec3(const std::string& name, const glm::vec3& value) const {
|
||||||
|
glUniform3fv(glGetUniformLocation(ID, (const GLchar*)name.c_str()), 1, &value[0]);
|
||||||
|
}
|
||||||
|
void setVec4(const std::string& name, const glm::vec4& value) const {
|
||||||
|
glUniform4fv(glGetUniformLocation(ID, (const GLchar*)name.c_str()), 1, &value[0]);
|
||||||
|
}
|
||||||
|
void setMat4(const std::string& name, const glm::mat4& mat) const {
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(ID, (const GLchar*)name.c_str()), 1, GL_FALSE, glm::value_ptr(mat));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
struct SplinePoint {
|
||||||
|
glm::vec3 position;
|
||||||
|
int type; // 0 = Smooth, 1 = Linear
|
||||||
|
};
|
||||||
|
|
||||||
|
class Spline {
|
||||||
|
public:
|
||||||
|
Spline() = default;
|
||||||
|
void addPoint(const glm::vec3& pos, int type = 0);
|
||||||
|
void clear() { points.clear(); LUT.clear(); totalLength = 0; }
|
||||||
|
|
||||||
|
// Получить позицию по физическому расстоянию (метрам) от начала
|
||||||
|
glm::vec3 getPositionAtDistance(float distance) const;
|
||||||
|
glm::vec3 getTangentAtDistance(float distance) const;
|
||||||
|
float getDistanceAtIndex(size_t index) const;
|
||||||
|
|
||||||
|
float getTotalLength() const { return totalLength; }
|
||||||
|
void rebuildLUT(); // Предрасчет таблицы длин для константной скорости
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<SplinePoint> points;
|
||||||
|
|
||||||
|
struct LUTEntry {
|
||||||
|
float distance;
|
||||||
|
float t;
|
||||||
|
};
|
||||||
|
std::vector<LUTEntry> LUT;
|
||||||
|
float totalLength = 0;
|
||||||
|
|
||||||
|
glm::vec3 getPositionRaw(float t) const;
|
||||||
|
glm::vec3 getTangentRaw(float t) const;
|
||||||
|
};
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#ifndef GL_LOADER_HPP
|
||||||
|
#define GL_LOADER_HPP
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_opengl.h>
|
||||||
|
|
||||||
|
#ifndef APIENTRY
|
||||||
|
#define APIENTRY
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GL_FRAGMENT_SHADER
|
||||||
|
#define GL_FRAGMENT_SHADER 0x8B30
|
||||||
|
#endif
|
||||||
|
#ifndef GL_VERTEX_SHADER
|
||||||
|
#define GL_VERTEX_SHADER 0x8B31
|
||||||
|
#endif
|
||||||
|
#ifndef GL_ARRAY_BUFFER
|
||||||
|
#define GL_ARRAY_BUFFER 0x8892
|
||||||
|
#endif
|
||||||
|
#ifndef GL_STATIC_DRAW
|
||||||
|
#define GL_STATIC_DRAW 0x88E4
|
||||||
|
#endif
|
||||||
|
#ifndef GL_DYNAMIC_DRAW
|
||||||
|
#define GL_DYNAMIC_DRAW 0x88E8
|
||||||
|
#endif
|
||||||
|
#ifndef GL_ELEMENT_ARRAY_BUFFER
|
||||||
|
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
|
||||||
|
#endif
|
||||||
|
#ifndef GL_PROGRAM_POINT_SIZE
|
||||||
|
#define GL_PROGRAM_POINT_SIZE 0x8642
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef GLuint (APIENTRY *PFNGLCREATESHADERPROC)(GLenum type);
|
||||||
|
typedef void (APIENTRY *PFNGLSHADERSOURCEPROC)(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length);
|
||||||
|
typedef void (APIENTRY *PFNGLCOMPILESHADERPROC)(GLuint shader);
|
||||||
|
typedef GLuint (APIENTRY *PFNGLCREATEPROGRAMPROC)(void);
|
||||||
|
typedef void (APIENTRY *PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader);
|
||||||
|
typedef void (APIENTRY *PFNGLLINKPROGRAMPROC)(GLuint program);
|
||||||
|
typedef void (APIENTRY *PFNGLUSEPROGRAMPROC)(GLuint program);
|
||||||
|
typedef void (APIENTRY *PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint* arrays);
|
||||||
|
typedef void (APIENTRY *PFNGLBINDVERTEXARRAYPROC)(GLuint array);
|
||||||
|
typedef void (APIENTRY *PFNGLGENBUFFERSPROC)(GLsizei n, GLuint* buffers);
|
||||||
|
typedef void (APIENTRY *PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint* buffers);
|
||||||
|
typedef void (APIENTRY *PFNGLDELETEVERTEXARRAYSPROC)(GLsizei n, const GLuint* arrays);
|
||||||
|
typedef void (APIENTRY *PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer);
|
||||||
|
typedef void (APIENTRY *PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
|
||||||
|
typedef void (APIENTRY *PFNGLBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
|
||||||
|
typedef void (APIENTRY *PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index);
|
||||||
|
typedef void (APIENTRY *PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer);
|
||||||
|
typedef GLint (APIENTRY *PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar* name);
|
||||||
|
typedef void (APIENTRY *PFNGLUNIFORM1FPROC)(GLint location, GLfloat v0);
|
||||||
|
typedef void (APIENTRY *PFNGLUNIFORM1IPROC)(GLint location, GLint v0);
|
||||||
|
typedef void (APIENTRY *PFNGLUNIFORM3FVPROC)(GLint location, GLsizei count, const GLfloat* value);
|
||||||
|
typedef void (APIENTRY *PFNGLUNIFORMMATRIX4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
|
||||||
|
|
||||||
|
#ifdef GL_LOADER_IMPLEMENTATION
|
||||||
|
#define GL_EXTERN
|
||||||
|
#else
|
||||||
|
#define GL_EXTERN extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
GL_EXTERN PFNGLCREATESHADERPROC glCreateShader;
|
||||||
|
GL_EXTERN PFNGLSHADERSOURCEPROC glShaderSource;
|
||||||
|
GL_EXTERN PFNGLCOMPILESHADERPROC glCompileShader;
|
||||||
|
GL_EXTERN PFNGLCREATEPROGRAMPROC glCreateProgram;
|
||||||
|
GL_EXTERN PFNGLATTACHSHADERPROC glAttachShader;
|
||||||
|
GL_EXTERN PFNGLLINKPROGRAMPROC glLinkProgram;
|
||||||
|
GL_EXTERN PFNGLUSEPROGRAMPROC glUseProgram;
|
||||||
|
GL_EXTERN PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
|
||||||
|
GL_EXTERN PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
|
||||||
|
GL_EXTERN PFNGLGENBUFFERSPROC glGenBuffers;
|
||||||
|
GL_EXTERN PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
||||||
|
GL_EXTERN PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
|
||||||
|
GL_EXTERN PFNGLBINDBUFFERPROC glBindBuffer;
|
||||||
|
GL_EXTERN PFNGLBUFFERDATAPROC glBufferData;
|
||||||
|
GL_EXTERN PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
||||||
|
GL_EXTERN PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
|
||||||
|
GL_EXTERN PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
|
||||||
|
GL_EXTERN PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
||||||
|
GL_EXTERN PFNGLUNIFORM1FPROC glUniform1f;
|
||||||
|
GL_EXTERN PFNGLUNIFORM1IPROC glUniform1i;
|
||||||
|
GL_EXTERN PFNGLUNIFORM3FVPROC glUniform3fv;
|
||||||
|
GL_EXTERN PFNGLUNIFORM4FVPROC glUniform4fv;
|
||||||
|
GL_EXTERN PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv;
|
||||||
|
|
||||||
|
inline void load_gl_functions() {
|
||||||
|
glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader");
|
||||||
|
glShaderSource = (PFNGLSHADERSOURCEPROC)SDL_GL_GetProcAddress("glShaderSource");
|
||||||
|
glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader");
|
||||||
|
glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram");
|
||||||
|
glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader");
|
||||||
|
glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram");
|
||||||
|
glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram");
|
||||||
|
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glGenVertexArrays");
|
||||||
|
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)SDL_GL_GetProcAddress("glBindVertexArray");
|
||||||
|
glGenBuffers = (PFNGLGENBUFFERSPROC)SDL_GL_GetProcAddress("glGenBuffers");
|
||||||
|
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteBuffers");
|
||||||
|
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glDeleteVertexArrays");
|
||||||
|
glBindBuffer = (PFNGLBINDBUFFERPROC)SDL_GL_GetProcAddress("glBindBuffer");
|
||||||
|
glBufferData = (PFNGLBUFFERDATAPROC)SDL_GL_GetProcAddress("glBufferData");
|
||||||
|
glBufferSubData = (PFNGLBUFFERSUBDATAPROC)SDL_GL_GetProcAddress("glBufferSubData");
|
||||||
|
glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)SDL_GL_GetProcAddress("glEnableVertexAttribArray");
|
||||||
|
glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)SDL_GL_GetProcAddress("glVertexAttribPointer");
|
||||||
|
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)SDL_GL_GetProcAddress("glGetUniformLocation");
|
||||||
|
glUniform1f = (PFNGLUNIFORM1FPROC)SDL_GL_GetProcAddress("glUniform1f");
|
||||||
|
glUniform1i = (PFNGLUNIFORM1IPROC)SDL_GL_GetProcAddress("glUniform1i");
|
||||||
|
glUniform3fv = (PFNGLUNIFORM3FVPROC)SDL_GL_GetProcAddress("glUniform3fv");
|
||||||
|
glUniform4fv = (PFNGLUNIFORM4FVPROC)SDL_GL_GetProcAddress("glUniform4fv");
|
||||||
|
glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)SDL_GL_GetProcAddress("glUniformMatrix4fv");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
#include "vectorail/core/DdsTexture.hpp"
|
||||||
|
|
||||||
|
#include "vectorail/core/gl_loader.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
uint32_t readU32le(const std::vector<uint8_t>& bytes, size_t offset) {
|
||||||
|
return static_cast<uint32_t>(bytes[offset + 0]) |
|
||||||
|
(static_cast<uint32_t>(bytes[offset + 1]) << 8) |
|
||||||
|
(static_cast<uint32_t>(bytes[offset + 2]) << 16) |
|
||||||
|
(static_cast<uint32_t>(bytes[offset + 3]) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t expandMasked(uint32_t pixel, uint32_t mask, uint8_t fallback) {
|
||||||
|
if (mask == 0) return fallback;
|
||||||
|
unsigned shift = 0;
|
||||||
|
while (((mask >> shift) & 1u) == 0u && shift < 31u) ++shift;
|
||||||
|
const uint32_t valueMask = mask >> shift;
|
||||||
|
const uint32_t value = (pixel & mask) >> shift;
|
||||||
|
return static_cast<uint8_t>((value * 255u + valueMask / 2u) / valueMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool loadDdsTexture(const std::string& path, DdsTexture& out, std::string* error) {
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
if (error) *error = "could not open DDS";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
const std::streamoff fileSize = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
if (fileSize < 128) {
|
||||||
|
if (error) *error = "DDS header is truncated";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<uint8_t> bytes(static_cast<size_t>(fileSize));
|
||||||
|
file.read(reinterpret_cast<char*>(bytes.data()), fileSize);
|
||||||
|
if (!file) {
|
||||||
|
if (error) *error = "could not read DDS";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return loadDdsTextureBytes(bytes, out, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadDdsTextureBytes(const std::vector<uint8_t>& bytes, DdsTexture& out, std::string* error) {
|
||||||
|
out = {};
|
||||||
|
if (bytes.size() < 128) {
|
||||||
|
if (error) *error = "DDS header is truncated";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (bytes[0] != 'D' || bytes[1] != 'D' || bytes[2] != 'S' || bytes[3] != ' ') {
|
||||||
|
if (error) *error = "not a DDS file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t headerSize = readU32le(bytes, 4);
|
||||||
|
const uint32_t height = readU32le(bytes, 12);
|
||||||
|
const uint32_t width = readU32le(bytes, 16);
|
||||||
|
const uint32_t pixelFormatSize = readU32le(bytes, 76);
|
||||||
|
const uint32_t pixelFormatFlags = readU32le(bytes, 80);
|
||||||
|
const uint32_t fourCC = readU32le(bytes, 84);
|
||||||
|
const uint32_t bitsPerPixel = readU32le(bytes, 88);
|
||||||
|
const uint32_t rMask = readU32le(bytes, 92);
|
||||||
|
const uint32_t gMask = readU32le(bytes, 96);
|
||||||
|
const uint32_t bMask = readU32le(bytes, 100);
|
||||||
|
const uint32_t aMask = readU32le(bytes, 104);
|
||||||
|
if (headerSize != 124 || pixelFormatSize != 32 || width == 0 || height == 0 ||
|
||||||
|
(bitsPerPixel != 24 && bitsPerPixel != 32) || fourCC != 0 || (pixelFormatFlags & 0x40u) == 0) {
|
||||||
|
if (error) *error = "unsupported DDS pixel format (expected uncompressed RGB24/RGBA32)";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t bytesPerPixel = bitsPerPixel / 8;
|
||||||
|
if (width > std::numeric_limits<size_t>::max() / height ||
|
||||||
|
static_cast<size_t>(width) * height > std::numeric_limits<size_t>::max() / bytesPerPixel) {
|
||||||
|
if (error) *error = "DDS dimensions overflow";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const size_t pixelCount = static_cast<size_t>(width) * height;
|
||||||
|
const size_t payloadSize = pixelCount * bytesPerPixel;
|
||||||
|
if (payloadSize > bytes.size() - 128) {
|
||||||
|
if (error) *error = "DDS pixel payload is truncated";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> rgba(pixelCount * 4);
|
||||||
|
for (size_t i = 0; i < pixelCount; ++i) {
|
||||||
|
uint32_t pixel = 0;
|
||||||
|
for (size_t byte = 0; byte < bytesPerPixel; ++byte) {
|
||||||
|
pixel |= static_cast<uint32_t>(bytes[128 + i * bytesPerPixel + byte]) << (byte * 8);
|
||||||
|
}
|
||||||
|
rgba[i * 4 + 0] = expandMasked(pixel, rMask, 0);
|
||||||
|
rgba[i * 4 + 1] = expandMasked(pixel, gMask, 0);
|
||||||
|
rgba[i * 4 + 2] = expandMasked(pixel, bMask, 0);
|
||||||
|
rgba[i * 4 + 3] = expandMasked(pixel, aMask, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
glGenTextures(1, &out.id);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, out.id);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, static_cast<GLsizei>(width), static_cast<GLsizei>(height),
|
||||||
|
0, GL_RGBA, GL_UNSIGNED_BYTE, rgba.data());
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
out.width = static_cast<int>(width);
|
||||||
|
out.height = static_cast<int>(height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
#include "vectorail/core/PngTexture.hpp"
|
||||||
|
|
||||||
|
#include "vectorail/core/gl_loader.hpp"
|
||||||
|
|
||||||
|
#include <png.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
uint16_t u16be(std::span<const uint8_t> bytes, size_t offset) {
|
||||||
|
return static_cast<uint16_t>((static_cast<uint16_t>(bytes[offset]) << 8) | bytes[offset + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t u32be(std::span<const uint8_t> bytes, size_t offset) {
|
||||||
|
return (static_cast<uint32_t>(bytes[offset]) << 24) |
|
||||||
|
(static_cast<uint32_t>(bytes[offset + 1]) << 16) |
|
||||||
|
(static_cast<uint32_t>(bytes[offset + 2]) << 8) |
|
||||||
|
static_cast<uint32_t>(bytes[offset + 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readFile(const std::string& path, std::vector<uint8_t>& bytes) {
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
if (!file) return false;
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
const std::streamoff size = file.tellg();
|
||||||
|
if (size < 0) return false;
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
bytes.assign(static_cast<size_t>(size), 0);
|
||||||
|
if (!bytes.empty()) file.read(reinterpret_cast<char*>(bytes.data()), size);
|
||||||
|
return static_cast<bool>(file) || file.eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PngMemoryReader {
|
||||||
|
std::span<const uint8_t> bytes;
|
||||||
|
size_t offset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void readPngMemory(png_structp png, png_bytep destination, png_size_t length) {
|
||||||
|
auto* reader = static_cast<PngMemoryReader*>(png_get_io_ptr(png));
|
||||||
|
if (!reader || length > reader->bytes.size() - reader->offset) {
|
||||||
|
png_error(png, "truncated PNG stream");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::memcpy(destination, reader->bytes.data() + reader->offset, length);
|
||||||
|
reader->offset += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decodePng(std::span<const uint8_t> bytes, DdsTexture& out, std::string* error) {
|
||||||
|
out = {};
|
||||||
|
if (bytes.size() < 8 || png_sig_cmp(bytes.data(), 0, 8) != 0) {
|
||||||
|
if (error) *error = "not a PNG stream";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||||
|
png_infop info = png ? png_create_info_struct(png) : nullptr;
|
||||||
|
if (!png || !info || setjmp(png_jmpbuf(png))) {
|
||||||
|
if (error) *error = "could not decode PNG";
|
||||||
|
if (png) png_destroy_read_struct(&png, info ? &info : nullptr, nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PngMemoryReader reader{bytes};
|
||||||
|
png_set_read_fn(png, &reader, readPngMemory);
|
||||||
|
png_read_info(png, info);
|
||||||
|
const png_uint_32 width = png_get_image_width(png, info);
|
||||||
|
const png_uint_32 height = png_get_image_height(png, info);
|
||||||
|
const int colorType = png_get_color_type(png, info);
|
||||||
|
const int bitDepth = png_get_bit_depth(png, info);
|
||||||
|
if (bitDepth == 16) png_set_strip_16(png);
|
||||||
|
if (colorType == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png);
|
||||||
|
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) png_set_expand_gray_1_2_4_to_8(png);
|
||||||
|
if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png);
|
||||||
|
if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png);
|
||||||
|
if ((colorType & PNG_COLOR_MASK_ALPHA) == 0) png_set_add_alpha(png, 0xff, PNG_FILLER_AFTER);
|
||||||
|
png_read_update_info(png, info);
|
||||||
|
|
||||||
|
std::vector<uint8_t> rgba(static_cast<size_t>(width) * height * 4);
|
||||||
|
std::vector<png_bytep> rows(height);
|
||||||
|
for (png_uint_32 y = 0; y < height; ++y) rows[y] = rgba.data() + static_cast<size_t>(y) * width * 4;
|
||||||
|
png_read_image(png, rows.data());
|
||||||
|
png_destroy_read_struct(&png, &info, nullptr);
|
||||||
|
|
||||||
|
glGenTextures(1, &out.id);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, out.id);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, static_cast<GLsizei>(width), static_cast<GLsizei>(height),
|
||||||
|
0, GL_RGBA, GL_UNSIGNED_BYTE, rgba.data());
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
out.width = static_cast<int>(width);
|
||||||
|
out.height = static_cast<int>(height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool loadPngTexture(const std::string& path, DdsTexture& out, std::string* error) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
if (!readFile(path, bytes)) {
|
||||||
|
if (error) *error = "could not open PNG";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return decodePng(bytes, out, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadPngTextureList(const std::string& path, std::vector<DdsTexture>& out, std::string* error) {
|
||||||
|
out.clear();
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
if (!readFile(path, bytes) || bytes.size() < 10) {
|
||||||
|
if (error) *error = "could not read texture list";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::span<const uint8_t> view(bytes);
|
||||||
|
const uint16_t count = u16be(view, 4);
|
||||||
|
if (6u + (static_cast<size_t>(count) + 1u) * 4u > bytes.size()) {
|
||||||
|
if (error) *error = "invalid texture-list offset table";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.assign(count, {});
|
||||||
|
int loaded = 0;
|
||||||
|
for (uint16_t index = 0; index < count; ++index) {
|
||||||
|
const size_t begin = u32be(view, 6 + static_cast<size_t>(index) * 4);
|
||||||
|
const size_t end = u32be(view, 10 + static_cast<size_t>(index) * 4);
|
||||||
|
if (begin == end) continue;
|
||||||
|
if (begin > end || end > bytes.size()) {
|
||||||
|
if (error) *error = "invalid embedded PNG range";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string pngError;
|
||||||
|
if (!decodePng(view.subspan(begin, end - begin), out[index], &pngError)) {
|
||||||
|
if (error) *error = "texture " + std::to_string(index) + ": " + pngError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++loaded;
|
||||||
|
}
|
||||||
|
if (loaded == 0) {
|
||||||
|
if (error) *error = "texture list has no PNG entries";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#include "vectorail/core/Shader.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Shader::Shader(const char* vertexPath, const char* fragmentPath) {
|
||||||
|
std::string vCode, fCode;
|
||||||
|
std::ifstream vFile(vertexPath), fFile(fragmentPath);
|
||||||
|
if (!vFile || !fFile) {
|
||||||
|
std::cerr << "Shader source unavailable: " << vertexPath
|
||||||
|
<< " / " << fragmentPath << std::endl;
|
||||||
|
}
|
||||||
|
std::stringstream vStr, fStr;
|
||||||
|
vStr << vFile.rdbuf(); fStr << fFile.rdbuf();
|
||||||
|
vCode = vStr.str(); fCode = fStr.str();
|
||||||
|
|
||||||
|
const char* vPtr = vCode.c_str();
|
||||||
|
const char* fPtr = fCode.c_str();
|
||||||
|
|
||||||
|
unsigned int v = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
glShaderSource(v, 1, &vPtr, NULL);
|
||||||
|
glCompileShader(v);
|
||||||
|
|
||||||
|
unsigned int f = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
glShaderSource(f, 1, &fPtr, NULL);
|
||||||
|
glCompileShader(f);
|
||||||
|
|
||||||
|
ID = glCreateProgram();
|
||||||
|
glAttachShader(ID, v);
|
||||||
|
glAttachShader(ID, f);
|
||||||
|
glLinkProgram(ID);
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#include "vectorail/core/Spline.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
void Spline::addPoint(const glm::vec3& pos, int type) {
|
||||||
|
points.push_back({pos, type});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spline::rebuildLUT() {
|
||||||
|
LUT.clear();
|
||||||
|
totalLength = 0;
|
||||||
|
if (points.size() < 2) return;
|
||||||
|
|
||||||
|
LUT.push_back({0.0f, 0.0f});
|
||||||
|
glm::vec3 prevPos = getPositionRaw(0.0f);
|
||||||
|
constexpr int samplesPerSegment = 50; // Same 0.02 resolution as before.
|
||||||
|
for (size_t segment = 0; segment + 1 < points.size(); ++segment) {
|
||||||
|
for (int sample = 1; sample <= samplesPerSegment; ++sample) {
|
||||||
|
// Sample every authored segment independently. The old cumulative
|
||||||
|
// t += 0.02 loop could step from just before an integer boundary
|
||||||
|
// to just after it and measure a chord across the corner. That
|
||||||
|
// shortened the LUT a little at every turn and moved later notes
|
||||||
|
// progressively ahead of their authored timestamp.
|
||||||
|
const float t = static_cast<float>(segment) +
|
||||||
|
static_cast<float>(sample) / static_cast<float>(samplesPerSegment);
|
||||||
|
const glm::vec3 currPos = getPositionRaw(t);
|
||||||
|
totalLength += glm::distance(prevPos, currPos);
|
||||||
|
LUT.push_back({totalLength, t});
|
||||||
|
prevPos = currPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 Spline::getPositionAtDistance(float d) const {
|
||||||
|
if (LUT.empty()) return glm::vec3(0);
|
||||||
|
d = std::clamp(d, 0.0f, totalLength);
|
||||||
|
auto it = std::lower_bound(LUT.begin(), LUT.end(), d, [](const LUTEntry& e, float val) { return e.distance < val; });
|
||||||
|
if (it == LUT.begin()) return getPositionRaw(it->t);
|
||||||
|
auto prev = std::prev(it);
|
||||||
|
float factor = (d - prev->distance) / (it->distance - prev->distance);
|
||||||
|
return getPositionRaw(glm::mix(prev->t, it->t, factor));
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 Spline::getTangentAtDistance(float d) const {
|
||||||
|
if (LUT.empty()) return glm::vec3(0, 0, -1);
|
||||||
|
d = std::clamp(d, 0.0f, totalLength);
|
||||||
|
auto it = std::lower_bound(LUT.begin(), LUT.end(), d, [](const LUTEntry& e, float val) { return e.distance < val; });
|
||||||
|
float t = (it == LUT.begin()) ? it->t : glm::mix(std::prev(it)->t, it->t, (d - std::prev(it)->distance) / (it->distance - std::prev(it)->distance));
|
||||||
|
return getTangentRaw(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Spline::getDistanceAtIndex(size_t index) const {
|
||||||
|
if (LUT.empty() || points.empty()) return 0.0f;
|
||||||
|
float t = std::clamp((float)index, 0.0f, (float)points.size() - 1.0f);
|
||||||
|
auto it = std::lower_bound(LUT.begin(), LUT.end(), t, [](const LUTEntry& e, float val) { return e.t < val; });
|
||||||
|
if (it == LUT.begin()) return it->distance;
|
||||||
|
if (it == LUT.end()) return LUT.back().distance;
|
||||||
|
auto prev = std::prev(it);
|
||||||
|
float factor = (t - prev->t) / (it->t - prev->t);
|
||||||
|
return prev->distance + (it->distance - prev->distance) * factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 Spline::getPositionRaw(float t) const {
|
||||||
|
int p1 = (int)t; int p2 = p1 + 1;
|
||||||
|
if (p2 >= points.size()) return points.back().position;
|
||||||
|
float lt = t - (float)p1;
|
||||||
|
if (points[p1].type == 1) return glm::mix(points[p1].position, points[p2].position, lt);
|
||||||
|
int p0 = std::max(0, p1 - 1), p3 = std::min((int)points.size() - 1, p2 + 1);
|
||||||
|
float t2 = lt * lt, t3 = t2 * lt;
|
||||||
|
return 0.5f * ((2.0f * points[p1].position) + (-points[p0].position + points[p2].position) * lt + (2.0f * points[p0].position - 5.0f * points[p1].position + 4.0f * points[p2].position - points[p3].position) * t2 + (-points[p0].position + 3.0f * points[p1].position - 3.0f * points[p2].position + points[p3].position) * t3);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 Spline::getTangentRaw(float t) const {
|
||||||
|
int p1 = (int)t; int p2 = p1 + 1;
|
||||||
|
if (p2 >= points.size()) return glm::normalize(points.back().position - points[std::max(0, (int)points.size()-2)].position);
|
||||||
|
float lt = t - (float)p1;
|
||||||
|
if (points[p1].type == 1) return glm::normalize(points[p2].position - points[p1].position);
|
||||||
|
int p0 = std::max(0, p1 - 1), p3 = std::min((int)points.size() - 1, p2 + 1);
|
||||||
|
float t2 = lt * lt;
|
||||||
|
glm::vec3 tangent = 0.5f * ((-points[p0].position + points[p2].position) + 2.0f * (2.0f * points[p0].position - 5.0f * points[p1].position + 4.0f * points[p2].position - points[p3].position) * lt + 3.0f * (-points[p0].position + 3.0f * points[p1].position - 3.0f * points[p2].position + points[p3].position) * t2);
|
||||||
|
return glm::normalize(tangent);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#define GL_LOADER_IMPLEMENTATION
|
||||||
|
#include "vectorail/core/gl_loader.hpp"
|
||||||
Reference in New Issue
Block a user