basic glfw + math stuff

master
sam lovelace 2021-11-27 00:23:19 +00:00
commit a6a83e546e
36 changed files with 25444 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
**/zig-cache/
**/zig-out/
/release/
/debug/
/build/
/build-*/
/docgen_tmp/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "zalgebra"]
path = src/math/zalgebra
url = git@github.com:digitalcreature/zalgebra.git

51
build.zig Normal file
View File

@ -0,0 +1,51 @@
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
b.installBinFile("deps/lib/glfw3.dll", "glfw3.dll");
const exe = b.addExecutable("musileko", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(mode);
exe.addIncludeDir("deps/inc");
// exe.addIncludeDir("C:/Users/sam/zig-windows-x86_64-0.8.0/lib/libc/include/any-windows-any");
// exe.addIncludeDir("C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.11.25503/include");
exe.addCSourceFile("deps/src/glad.c", &[_][]const u8{"-std=c99"});
exe.addCSourceFile("deps/src/stb_image.c", &[_][]const u8{"-std=c99"});
// exe.addIncludeDir("GLFW/include/GLFW");
exe.addLibPath("deps/lib");
exe.linkSystemLibrary("glfw3");
exe.linkSystemLibrary("user32");
exe.linkSystemLibrary("gdi32");
exe.linkSystemLibrary("shell32");
exe.linkSystemLibrary("opengl32");
// exe.linkSystemLibrary("deps/lib/cimguid");
exe.linkLibC();
exe.install();
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const exe_tests = b.addTest("src/main.zig");
exe_tests.setBuildMode(mode);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&exe_tests.step);
}

290
deps/inc/KHR/khrplatform.h vendored Normal file
View File

@ -0,0 +1,290 @@
#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
#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 _WIN64
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
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_ */

5131
deps/inc/glad/glad.h vendored Normal file

File diff suppressed because it is too large Load Diff

6062
deps/inc/glfw3.h vendored Normal file

File diff suppressed because it is too large Load Diff

525
deps/inc/glfw3native.h vendored Normal file
View File

@ -0,0 +1,525 @@
/*************************************************************************
* GLFW 3.4 - www.glfw.org
* A library for OpenGL, window and input
*------------------------------------------------------------------------
* Copyright (c) 2002-2006 Marcus Geelnard
* Copyright (c) 2006-2018 Camilla Löwy <elmindreda@glfw.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would
* be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not
* be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
*************************************************************************/
#ifndef _glfw3_native_h_
#define _glfw3_native_h_
#ifdef __cplusplus
extern "C" {
#endif
/*************************************************************************
* Doxygen documentation
*************************************************************************/
/*! @file glfw3native.h
* @brief The header of the native access functions.
*
* This is the header file of the native access functions. See @ref native for
* more information.
*/
/*! @defgroup native Native access
* @brief Functions related to accessing native handles.
*
* **By using the native access functions you assert that you know what you're
* doing and how to fix problems caused by using them. If you don't, you
* shouldn't be using them.**
*
* Before the inclusion of @ref glfw3native.h, you may define zero or more
* window system API macro and zero or more context creation API macros.
*
* The chosen backends must match those the library was compiled for. Failure
* to do this will cause a link-time error.
*
* The available window API macros are:
* * `GLFW_EXPOSE_NATIVE_WIN32`
* * `GLFW_EXPOSE_NATIVE_COCOA`
* * `GLFW_EXPOSE_NATIVE_X11`
* * `GLFW_EXPOSE_NATIVE_WAYLAND`
*
* The available context API macros are:
* * `GLFW_EXPOSE_NATIVE_WGL`
* * `GLFW_EXPOSE_NATIVE_NSGL`
* * `GLFW_EXPOSE_NATIVE_GLX`
* * `GLFW_EXPOSE_NATIVE_EGL`
* * `GLFW_EXPOSE_NATIVE_OSMESA`
*
* These macros select which of the native access functions that are declared
* and which platform-specific headers to include. It is then up your (by
* definition platform-specific) code to handle which of these should be
* defined.
*/
/*************************************************************************
* System headers and types
*************************************************************************/
#if defined(GLFW_EXPOSE_NATIVE_WIN32) || defined(GLFW_EXPOSE_NATIVE_WGL)
// This is a workaround for the fact that glfw3.h needs to export APIENTRY (for
// example to allow applications to correctly declare a GL_ARB_debug_output
// callback) but windows.h assumes no one will define APIENTRY before it does
#if defined(GLFW_APIENTRY_DEFINED)
#undef APIENTRY
#undef GLFW_APIENTRY_DEFINED
#endif
#include <windows.h>
#elif defined(GLFW_EXPOSE_NATIVE_COCOA) || defined(GLFW_EXPOSE_NATIVE_NSGL)
#if defined(__OBJC__)
#import <Cocoa/Cocoa.h>
#else
#include <ApplicationServices/ApplicationServices.h>
typedef void* id;
#endif
#elif defined(GLFW_EXPOSE_NATIVE_X11) || defined(GLFW_EXPOSE_NATIVE_GLX)
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#elif defined(GLFW_EXPOSE_NATIVE_WAYLAND)
#include <wayland-client.h>
#endif
#if defined(GLFW_EXPOSE_NATIVE_WGL)
/* WGL is declared by windows.h */
#endif
#if defined(GLFW_EXPOSE_NATIVE_NSGL)
/* NSGL is declared by Cocoa.h */
#endif
#if defined(GLFW_EXPOSE_NATIVE_GLX)
#include <GL/glx.h>
#endif
#if defined(GLFW_EXPOSE_NATIVE_EGL)
#include <EGL/egl.h>
#endif
#if defined(GLFW_EXPOSE_NATIVE_OSMESA)
#include <GL/osmesa.h>
#endif
/*************************************************************************
* Functions
*************************************************************************/
#if defined(GLFW_EXPOSE_NATIVE_WIN32)
/*! @brief Returns the adapter device name of the specified monitor.
*
* @return The UTF-8 encoded adapter device name (for example `\\.\DISPLAY1`)
* of the specified monitor, or `NULL` if an [error](@ref error_handling)
* occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.1.
*
* @ingroup native
*/
GLFWAPI const char* glfwGetWin32Adapter(GLFWmonitor* monitor);
/*! @brief Returns the display device name of the specified monitor.
*
* @return The UTF-8 encoded display device name (for example
* `\\.\DISPLAY1\Monitor0`) of the specified monitor, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.1.
*
* @ingroup native
*/
GLFWAPI const char* glfwGetWin32Monitor(GLFWmonitor* monitor);
/*! @brief Returns the `HWND` of the specified window.
*
* @return The `HWND` of the specified window, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI HWND glfwGetWin32Window(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_WGL)
/*! @brief Returns the `HGLRC` of the specified window.
*
* @return The `HGLRC` of the specified window, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI HGLRC glfwGetWGLContext(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_COCOA)
/*! @brief Returns the `CGDirectDisplayID` of the specified monitor.
*
* @return The `CGDirectDisplayID` of the specified monitor, or
* `kCGNullDirectDisplay` if an [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.1.
*
* @ingroup native
*/
GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* monitor);
/*! @brief Returns the `NSWindow` of the specified window.
*
* @return The `NSWindow` of the specified window, or `nil` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI id glfwGetCocoaWindow(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_NSGL)
/*! @brief Returns the `NSOpenGLContext` of the specified window.
*
* @return The `NSOpenGLContext` of the specified window, or `nil` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI id glfwGetNSGLContext(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_X11)
/*! @brief Returns the `Display` used by GLFW.
*
* @return The `Display` used by GLFW, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI Display* glfwGetX11Display(void);
/*! @brief Returns the `RRCrtc` of the specified monitor.
*
* @return The `RRCrtc` of the specified monitor, or `None` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.1.
*
* @ingroup native
*/
GLFWAPI RRCrtc glfwGetX11Adapter(GLFWmonitor* monitor);
/*! @brief Returns the `RROutput` of the specified monitor.
*
* @return The `RROutput` of the specified monitor, or `None` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.1.
*
* @ingroup native
*/
GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* monitor);
/*! @brief Returns the `Window` of the specified window.
*
* @return The `Window` of the specified window, or `None` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI Window glfwGetX11Window(GLFWwindow* window);
/*! @brief Sets the current primary selection to the specified string.
*
* @param[in] string A UTF-8 encoded string.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @pointer_lifetime The specified string is copied before this function
* returns.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref clipboard
* @sa glfwGetX11SelectionString
* @sa glfwSetClipboardString
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI void glfwSetX11SelectionString(const char* string);
/*! @brief Returns the contents of the current primary selection as a string.
*
* If the selection is empty or if its contents cannot be converted, `NULL`
* is returned and a @ref GLFW_FORMAT_UNAVAILABLE error is generated.
*
* @return The contents of the selection as a UTF-8 encoded string, or `NULL`
* if an [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @pointer_lifetime The returned string is allocated and freed by GLFW. You
* should not free it yourself. It is valid until the next call to @ref
* glfwGetX11SelectionString or @ref glfwSetX11SelectionString, or until the
* library is terminated.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref clipboard
* @sa glfwSetX11SelectionString
* @sa glfwGetClipboardString
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI const char* glfwGetX11SelectionString(void);
#endif
#if defined(GLFW_EXPOSE_NATIVE_GLX)
/*! @brief Returns the `GLXContext` of the specified window.
*
* @return The `GLXContext` of the specified window, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* window);
/*! @brief Returns the `GLXWindow` of the specified window.
*
* @return The `GLXWindow` of the specified window, or `None` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.2.
*
* @ingroup native
*/
GLFWAPI GLXWindow glfwGetGLXWindow(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_WAYLAND)
/*! @brief Returns the `struct wl_display*` used by GLFW.
*
* @return The `struct wl_display*` used by GLFW, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.2.
*
* @ingroup native
*/
GLFWAPI struct wl_display* glfwGetWaylandDisplay(void);
/*! @brief Returns the `struct wl_output*` of the specified monitor.
*
* @return The `struct wl_output*` of the specified monitor, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.2.
*
* @ingroup native
*/
GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* monitor);
/*! @brief Returns the main `struct wl_surface*` of the specified window.
*
* @return The main `struct wl_surface*` of the specified window, or `NULL` if
* an [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.2.
*
* @ingroup native
*/
GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_EGL)
/*! @brief Returns the `EGLDisplay` used by GLFW.
*
* @return The `EGLDisplay` used by GLFW, or `EGL_NO_DISPLAY` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI EGLDisplay glfwGetEGLDisplay(void);
/*! @brief Returns the `EGLContext` of the specified window.
*
* @return The `EGLContext` of the specified window, or `EGL_NO_CONTEXT` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* window);
/*! @brief Returns the `EGLSurface` of the specified window.
*
* @return The `EGLSurface` of the specified window, or `EGL_NO_SURFACE` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI EGLSurface glfwGetEGLSurface(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_OSMESA)
/*! @brief Retrieves the color buffer associated with the specified window.
*
* @param[in] window The window whose color buffer to retrieve.
* @param[out] width Where to store the width of the color buffer, or `NULL`.
* @param[out] height Where to store the height of the color buffer, or `NULL`.
* @param[out] format Where to store the OSMesa pixel format of the color
* buffer, or `NULL`.
* @param[out] buffer Where to store the address of the color buffer, or
* `NULL`.
* @return `GLFW_TRUE` if successful, or `GLFW_FALSE` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI int glfwGetOSMesaColorBuffer(GLFWwindow* window, int* width, int* height, int* format, void** buffer);
/*! @brief Retrieves the depth buffer associated with the specified window.
*
* @param[in] window The window whose depth buffer to retrieve.
* @param[out] width Where to store the width of the depth buffer, or `NULL`.
* @param[out] height Where to store the height of the depth buffer, or `NULL`.
* @param[out] bytesPerValue Where to store the number of bytes per depth
* buffer element, or `NULL`.
* @param[out] buffer Where to store the address of the depth buffer, or
* `NULL`.
* @return `GLFW_TRUE` if successful, or `GLFW_FALSE` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI int glfwGetOSMesaDepthBuffer(GLFWwindow* window, int* width, int* height, int* bytesPerValue, void** buffer);
/*! @brief Returns the `OSMesaContext` of the specified window.
*
* @return The `OSMesaContext` of the specified window, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* window);
#endif
#ifdef __cplusplus
}
#endif
#endif /* _glfw3_native_h_ */

7765
deps/inc/stb_image.h vendored Normal file

File diff suppressed because it is too large Load Diff

BIN
deps/lib/cimgui.lib vendored Normal file

Binary file not shown.

BIN
deps/lib/cimguid.lib vendored Normal file

Binary file not shown.

BIN
deps/lib/cimguid.pdb vendored Normal file

Binary file not shown.

BIN
deps/lib/glfw3.dll vendored Normal file

Binary file not shown.

BIN
deps/lib/glfw3.lib vendored Normal file

Binary file not shown.

2518
deps/src/glad.c vendored Normal file

File diff suppressed because it is too large Load Diff

2
deps/src/stb_image.c vendored Normal file
View File

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

1
readme.md Normal file
View File

@ -0,0 +1 @@
# musi leko

8
src/c.zig Normal file
View File

@ -0,0 +1,8 @@
// pub const c = @cImport({
pub usingnamespace @cImport({
@cInclude("glad/glad.h");
@cInclude("glfw3.h");
// @cDefine("GLFW_EXPOSE_NATIVE_WIN32", "true");
// @cInclude("glfw3native.h");
@cInclude("stb_image.h");
});

19
src/glfw.zig Normal file
View File

@ -0,0 +1,19 @@
const panic = @import("std").debug.panic;
const c = @import("c.zig");
const window = @import("glfw/window.zig");
pub const Window = window.Window;
// pub usingnamespace @import("window.zig");
// pub usingnamespace @import("mouse.zig");
// pub usingnamespace @import("keyboard.zig");
// pub usingnamespace @import("time.zig");
pub fn init() void {
if (c.glfwInit() == 0) {
panic("Failed to initialise GLFW\n", .{});
}
}
pub fn deinit() void {
c.glfwTerminate();
}

259
src/glfw/keyboard.zig Normal file
View File

@ -0,0 +1,259 @@
const std = @import("std");
usingnamespace @import("c");
const math = @import("math");
usingnamespace math.glm;
const Window = @import("window.zig").Window;
pub const KeyState = enum(u1) {
down = GLFW_PRESS,
up = GLFW_RELEASE,
pub fn fromCInt(i: c_int) KeyState {
const u = @bitCast(c_uint, i) & 1;
return @intToEnum(KeyState, @truncate(u1, u));
}
};
pub const Keyboard = struct {
window: Window.Handle,
prev_state_index: usize,
curr_state_index: usize,
_states: [2]State,
const Self = *@This();
const SelfConst = * const @This();
pub fn init(window: Window.Handle) Keyboard {
var state: State = undefined;
pollState(window, &state);
return .{
.window = window,
.prev_state_index = 0,
.curr_state_index = 1,
._states = .{ state, state },
};
}
pub fn update(self: Self) void {
const tmp = self.prev_state_index;
self.prev_state_index = self.curr_state_index;
self.curr_state_index = tmp;
pollState(self.window, &(self._states[self.curr_state_index]));
}
pub fn didKeyChange(self: Self, comptime code: KeyCode, comptime state: KeyState) ?bool {
const prev = self.previousKeyState(code);
const curr = self.currentKeyState(code);
return (prev != curr and curr == state);
}
pub fn wasKeyReleased(self: Self, comptime code: KeyCode) ?bool {
return self.didKeyChange(code, .up);
}
pub fn wasKeyPressed(self: Self, comptime code: KeyCode) ?bool {
return self.didKeyChange(code, .down);
}
pub fn isKey(self: Self, comptime code: KeyCode, comptime state: KeyState) ?bool {
return self.currentKeyState(code) == state;
}
pub fn isKeyDown(self: Self, comptime code: KeyCode) ?bool {
return self.isKey(code, .down);
}
pub fn isKeyUp(self: Self, comptime code: KeyCode) ?bool {
return self.isKey(code, .up);
}
fn getKeyState(self: Self, comptime code: KeyCode, index: usize) KeyState {
return self._states[index].get(code);
}
pub fn previousKeyState(self: Self, comptime code: KeyCode) KeyState {
return self.getKeyState(code, self.prev_state_index);
}
pub fn currentKeyState(self: Self, comptime code: KeyCode) KeyState {
return self.getKeyState(code, self.curr_state_index);
}
pub fn currState(self: Self) *State {
return &self._states[self.curr_state_index];
}
pub fn prevState(self: Self) *State {
return &self._states[self.prev_state_index];
}
fn pollState(window: Window.Handle, state: *State) void {
comptime var i: usize = 0;
inline while (i < KeyCode.count) : (i += 1) {
const key_state = glfwGetKey(window, KeyCode.fields[i].value);
state.keys[i] = KeyState.fromCInt(key_state);
}
}
pub const State = struct {
keys: StateArray = std.mem.zeroes(StateArray),
pub fn get(self: State, comptime code: KeyCode) KeyState {
return self.keys[comptime code.index()];
}
pub const StateArray = [KeyCode.count]KeyState;
};
};
pub const KeyCode = enum(c_int) {
pub const fields = @typeInfo(KeyCode).Enum.fields;
pub const count = fields.len - 1; // subtract one for .unkown
// yeah this is suboptimal, but i dont know how to do some sort of "inline switch"
// this is only to be used at runtime, as there isnt really a better option i can think of.
// TODO: improve keycode indexing with runtime keycodes, it could cause issues with rebindable controls
pub fn index(comptime code: KeyCode) usize {
if (code == .unknown) {
@compileError("cannot index with KeyCode.unknown");
}
comptime var i: usize = 0;
comptime var match: ?usize = null;
inline while (i < count) : (i += 1) {
if (match == null and @as(c_int, fields[i].value) == @enumToInt(code)) {
match = i;
}
}
return match.?;
}
space = GLFW_KEY_SPACE,
apostrophe = GLFW_KEY_APOSTROPHE,
comma = GLFW_KEY_COMMA,
minus = GLFW_KEY_MINUS,
period = GLFW_KEY_PERIOD,
slash = GLFW_KEY_SLASH,
alpha_0 = GLFW_KEY_0,
alpha_1 = GLFW_KEY_1,
alpha_2 = GLFW_KEY_2,
alpha_3 = GLFW_KEY_3,
alpha_4 = GLFW_KEY_4,
alpha_5 = GLFW_KEY_5,
alpha_6 = GLFW_KEY_6,
alpha_7 = GLFW_KEY_7,
alpha_8 = GLFW_KEY_8,
alpha_9 = GLFW_KEY_9,
semicolon = GLFW_KEY_SEMICOLON,
equal = GLFW_KEY_EQUAL,
a = GLFW_KEY_A,
b = GLFW_KEY_B,
c = GLFW_KEY_C,
d = GLFW_KEY_D,
e = GLFW_KEY_E,
f = GLFW_KEY_F,
g = GLFW_KEY_G,
h = GLFW_KEY_H,
i = GLFW_KEY_I,
j = GLFW_KEY_J,
k = GLFW_KEY_K,
l = GLFW_KEY_L,
m = GLFW_KEY_M,
n = GLFW_KEY_N,
o = GLFW_KEY_O,
p = GLFW_KEY_P,
q = GLFW_KEY_Q,
r = GLFW_KEY_R,
s = GLFW_KEY_S,
t = GLFW_KEY_T,
u = GLFW_KEY_U,
v = GLFW_KEY_V,
w = GLFW_KEY_W,
x = GLFW_KEY_X,
y = GLFW_KEY_Y,
z = GLFW_KEY_Z,
left_bracket = GLFW_KEY_LEFT_BRACKET,
backslash = GLFW_KEY_BACKSLASH,
right_bracket = GLFW_KEY_RIGHT_BRACKET,
grave = GLFW_KEY_GRAVE_ACCENT,
world_1 = GLFW_KEY_WORLD_1,
world_2 = GLFW_KEY_WORLD_2,
escape = GLFW_KEY_ESCAPE,
enter = GLFW_KEY_ENTER,
tab = GLFW_KEY_TAB,
backspace = GLFW_KEY_BACKSPACE,
insert = GLFW_KEY_INSERT,
delete = GLFW_KEY_DELETE,
right = GLFW_KEY_RIGHT,
left = GLFW_KEY_LEFT,
down = GLFW_KEY_DOWN,
up = GLFW_KEY_UP,
page_up = GLFW_KEY_PAGE_UP,
page_down = GLFW_KEY_PAGE_DOWN,
home = GLFW_KEY_HOME,
end = GLFW_KEY_END,
caps_lock = GLFW_KEY_CAPS_LOCK,
scroll_lock = GLFW_KEY_SCROLL_LOCK,
num_lock = GLFW_KEY_NUM_LOCK,
print_screen = GLFW_KEY_PRINT_SCREEN,
pause = GLFW_KEY_PAUSE,
f_1 = GLFW_KEY_F1,
f_2 = GLFW_KEY_F2,
f_3 = GLFW_KEY_F3,
f_4 = GLFW_KEY_F4,
f_5 = GLFW_KEY_F5,
f_6 = GLFW_KEY_F6,
f_7 = GLFW_KEY_F7,
f_8 = GLFW_KEY_F8,
f_9 = GLFW_KEY_F9,
f_10 = GLFW_KEY_F10,
f_11 = GLFW_KEY_F11,
f_12 = GLFW_KEY_F12,
f_13 = GLFW_KEY_F13,
f_14 = GLFW_KEY_F14,
f_15 = GLFW_KEY_F15,
f_16 = GLFW_KEY_F16,
f_17 = GLFW_KEY_F17,
f_18 = GLFW_KEY_F18,
f_19 = GLFW_KEY_F19,
f_20 = GLFW_KEY_F20,
f_21 = GLFW_KEY_F21,
f_22 = GLFW_KEY_F22,
f_23 = GLFW_KEY_F23,
f_24 = GLFW_KEY_F24,
f_25 = GLFW_KEY_F25,
kp_0 = GLFW_KEY_KP_0,
kp_1 = GLFW_KEY_KP_1,
kp_2 = GLFW_KEY_KP_2,
kp_3 = GLFW_KEY_KP_3,
kp_4 = GLFW_KEY_KP_4,
kp_5 = GLFW_KEY_KP_5,
kp_6 = GLFW_KEY_KP_6,
kp_7 = GLFW_KEY_KP_7,
kp_8 = GLFW_KEY_KP_8,
kp_9 = GLFW_KEY_KP_9,
kp_decimal = GLFW_KEY_KP_DECIMAL,
kp_divide = GLFW_KEY_KP_DIVIDE,
kp_multiply = GLFW_KEY_KP_MULTIPLY,
kp_subtract = GLFW_KEY_KP_SUBTRACT,
kp_add = GLFW_KEY_KP_ADD,
kp_enter = GLFW_KEY_KP_ENTER,
kp_equal = GLFW_KEY_KP_EQUAL,
left_shift = GLFW_KEY_LEFT_SHIFT,
left_control = GLFW_KEY_LEFT_CONTROL,
left_alt = GLFW_KEY_LEFT_ALT,
left_super = GLFW_KEY_LEFT_SUPER,
right_shift = GLFW_KEY_RIGHT_SHIFT,
right_control = GLFW_KEY_RIGHT_CONTROL,
right_alt = GLFW_KEY_RIGHT_ALT,
right_super = GLFW_KEY_RIGHT_SUPER,
menu = GLFW_KEY_MENU,
unknown = GLFW_KEY_UNKNOWN,
};

75
src/glfw/mouse.zig Normal file
View File

@ -0,0 +1,75 @@
usingnamespace @import("c");
const math = @import("math");
usingnamespace math.glm;
const Window = @import("window.zig").Window;
pub const Mouse = struct {
window: Window.Handle,
raw_input_supported: bool,
last_cursor_position: Position = Position.zero,
cursor_position_delta: Position = Position.zero,
cursor_mode: CursorMode = .enabled,
const Self = @This();
pub const Position = DVec2;
pub fn init(window: Window.Handle) Self {
var self = .{
.window = window,
.raw_input_supported = glfwRawMouseMotionSupported() != GLFW_FALSE,
};
return self;
}
pub fn deinit(self: *Self) void {}
pub fn update(self: *Self) void {
var cursorPosition = self.getCursorPosition();
self.cursor_position_delta = cursorPosition.sub(self.last_cursor_position);
self.last_cursor_position = cursorPosition;
}
pub fn resetCursorPositionDelta(self: *Self) void {
self.last_cursor_position = self.getCursorPosition();
self.cursor_position_delta = Position.zero;
}
pub fn getCursorPosition(self: *Self) Position {
var x: f64 = undefined;
var y: f64 = undefined;
glfwGetCursorPos(self.window, &x, &y);
return Position.init(x, y);
}
pub fn setCursorMode(self: *Self, mode: CursorMode) void {
self.cursor_mode = mode;
glfwSetInputMode(self.window, GLFW_CURSOR, @enumToInt(mode));
}
pub fn setRawInputMode(self: *Self, mode: RawInputMode) void {
if (self.raw_input_supported) {
const cursor_mode: CursorMode = switch (mode) {
.enabled => .disabled,
.disabled => .enabled,
};
self.setCursorMode(cursor_mode);
glfwSetInputMode(self.window, GLFW_RAW_MOUSE_MOTION, @enumToInt(mode));
glfwPollEvents();
self.resetCursorPositionDelta();
}
}
pub const RawInputMode = enum(c_int) {
enabled = GLFW_TRUE,
disabled = GLFW_FALSE,
};
pub const CursorMode = enum(c_int) {
enabled = GLFW_CURSOR_NORMAL,
// hidden = GLFW_CURSOR_HIDDEN,
disabled = GLFW_CURSOR_DISABLED,
};
};

36
src/glfw/time.zig Normal file
View File

@ -0,0 +1,36 @@
const std = @import("std");
usingnamespace @import("c");
// const math = @import("math");
// usingnamespace math.glm;
// const Window = @import("window.zig").Window;
pub const Time = f64;
pub const FrameTimer = struct {
previous_time: Time,
current_time: Time,
frame_time: Time,
const Self = *@This();
pub fn init() FrameTimer {
const time = getTime();
return .{
.previous_time = time,
.current_time = time,
.frame_time = 0,
};
}
pub fn update(self: Self) void {
self.previous_time = self.current_time;
self.current_time = getTime();
self.frame_time = self.current_time - self.previous_time;
}
pub fn getTime() Time {
return glfwGetTime();
}
};

147
src/glfw/window.zig Normal file
View File

@ -0,0 +1,147 @@
const panic = @import("std").debug.panic;
const c =@import("../c.zig");
// usingnamespace @import("mouse.zig");
// usingnamespace @import("keyboard.zig");
// usingnamespace @import("time.zig");
const math = @import("../math.zig");
pub const Vec2i = math.Vec2i;
pub const Window = struct {
handle: Handle,
// mouse: Mouse,
// keyboard: Keyboard,
// time: FrameTimer,
display_mode: DisplayMode,
windowed_pos: Vec2i,
windowed_size: Vec2i,
pub const Handle = *c.GLFWwindow;
const Self = @This();
pub fn init(width: c_int, height: c_int, title: [:0]const u8) Self {
// yeah its hardcoded eat my ass
c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 4);
c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MINOR, 5);
c.glfwWindowHint(c.GLFW_OPENGL_PROFILE, c.GLFW_OPENGL_CORE_PROFILE);
c.glfwWindowHint(c.GLFW_OPENGL_FORWARD_COMPAT, c.GL_TRUE);
// c.glfwWindowHint(c.GLFW_SAMPLES, 4);
const window_opt: ?Handle = c.glfwCreateWindow(width, height, title, null, null);
if (window_opt == null) {
panic("Failed to create GLFW window\n", .{});
}
const window = window_opt.?;
c.glfwMakeContextCurrent(window);
_ = c.glfwSetFramebufferSizeCallback(window, frameBufferSizeCallback);
c.glfwPollEvents();
var self = Self{
.handle = window,
// .mouse = Mouse.init(window),
// .keyboard = Keyboard.init(window),
// .time = FrameTimer.init(),
.display_mode = .windowed,
.windowed_pos = Vec2i.zero(),
.windowed_size = Vec2i.zero(),
};
self.saveWindowedShape();
return self;
}
pub fn deinit(self: *Self) void {
_ = self;
}
fn saveWindowedShape(self: *Self) void {
var pos: Vec2i = undefined;
c.glfwGetWindowPos(self.handle, &pos.x, &pos.y);
const size: Vec2i = self.getFrameBufferSize();
self.windowed_pos = pos;
self.windowed_size = size;
}
pub fn update(self: *Self) void {
_ = self;
c.glfwPollEvents();
// self.mouse.update();
// self.keyboard.update();
// self.time.update();
}
pub fn shouldClose(self: Self) bool {
return c.glfwWindowShouldClose(self.handle) != 0;
}
pub fn setShouldClose(self: Self, should_close: bool) void {
c.glfwSetWindowShouldClose(self.handle, @boolToInt(should_close));
}
pub fn swapBuffers(self: Self) void {
c.glfwSwapBuffers(self.handle);
}
pub fn setVsyncMode(self: Self, mode: VsyncMode) void {
_ = self;
c.glfwSwapInterval(@enumToInt(mode));
}
pub const VsyncMode = enum(c_int) {
disabled = 0,
enabled = 1,
};
pub fn setDisplayMode(self: *Self, mode: DisplayMode, vsync_mode: VsyncMode) void {
var monitor: *c.GLFWmonitor = c.glfwGetPrimaryMonitor().?;
var vidmode: *const c.GLFWvidmode = c.glfwGetVideoMode(monitor);
switch(mode) {
// .windowed => {
// c.glfwRestoreWindow(self.handle);
// c.glfwSetWindowAttrib(self.handle, c.GLFW_FLOATING, c.GLFW_FALSE);
// c.glfwSetWindowAttrib(self.handle, c.GLFW_DECORATED, c.GLFW_TRUE);
// },
// .borderless => {
// c.glfwSetWindowAttrib(self.handle, c.GLFW_DECORATED, c.GLFW_FALSE);
// c.glfwMaximizeWindow(self.handle);
// c.glfwSetWindowAttrib(self.handle, c.GLFW_FLOATING, c.GLFW_TRUE);
// },
.windowed => {
const pos = self.windowed_pos;
const size = self.windowed_size;
c.glfwSetWindowMonitor(self.handle, null, pos.x, pos.y, size.x, size.y, 0);
},
.borderless => {
self.saveWindowedShape();
c.glfwSetWindowMonitor(self.handle, monitor, 0, 0, vidmode.width, vidmode.height, vidmode.refreshRate);
},
}
self.display_mode = mode;
self.setVsyncMode(vsync_mode);
}
pub const DisplayMode = enum {
windowed,
borderless,
};
pub fn getFrameBufferSize(self: Self) Vec2i {
var frame_buffer_size: Vec2i = undefined;
c.glfwGetFramebufferSize(self.handle, &frame_buffer_size.x, &frame_buffer_size.y);
return frame_buffer_size;
}
fn frameBufferSizeCallback(window: ?Handle, width: c_int, height: c_int) callconv(.C) void {
_ = window;
// make sure the viewport matches the new window dimensions; note that width and
// height will be significantly larger than specified on retina displays.
c.glViewport(0, 0, width, height);
}
};

21
src/main.zig Normal file
View File

@ -0,0 +1,21 @@
const std = @import("std");
const builtin = @import("builtin");
const glfw = @import("glfw.zig");
const math = @import("math.zig");
pub fn main() anyerror!void {
glfw.init();
defer glfw.deinit();
var window = glfw.Window.init(640, 360, "a toki ma!");
defer window.deinit();
while (!window.shouldClose()) {
window.update();
window.swapBuffers();
}
}
test "basic test" {
try std.testing.expectEqual(10, 3 + 7);
}

71
src/math.zig Normal file
View File

@ -0,0 +1,71 @@
const std = @import("std");
const math = std.math;
const mat4 = @import("math/zalgebra/src/mat4.zig");
const quaternion = @import("math/zalgebra/src/quaternion.zig");
const vec2 = @import("math/zalgebra/src/vec2.zig");
const vec3 = @import("math/zalgebra/src/vec3.zig");
const vec4 = @import("math/zalgebra/src/vec4.zig");
pub const Quaternion = quaternion.Quaternion;
pub fn Matrix(comptime T: type, comptime dimensions: u32) type {
return switch (dimensions) {
4 => mat4.Mat4x4(T),
else => @compileError("unsupported matrix size"),
};
}
pub fn Vector(comptime T: type, comptime dimensions: u32) type {
return switch(dimensions) {
2 => vec2.Vector2(T),
3 => vec3.Vector3(T),
4 => vec4.Vector4(T),
else => @compileError("unsupported vector size"),
};
}
pub const Vec2 = Vector(f32, 2);
pub const Vec3 = Vector(f32, 3);
pub const Vec4 = Vector(f32, 4);
pub const Vec2i = Vector(i32, 2);
pub const Vec3i = Vector(i32, 3);
pub const Vec4i = Vector(i32, 4);
pub const Vec2u = Vector(u32, 2);
pub const Vec3u = Vector(u32, 3);
pub const Vec4u = Vector(u32, 4);
pub const Mat4 = Matrix(f32, 4);
pub const Quat = Quaternion(f32);
/// Convert degrees to radians.
pub fn toRadians(degrees: anytype) @TypeOf(degrees) {
const T = @TypeOf(degrees);
return switch (@typeInfo(T)) {
.Float => degrees * (math.pi / 180.0),
else => @compileError("Radians not implemented for " ++ @typeName(T)),
};
}
/// Convert radians to degrees.
pub fn toDegrees(radians: anytype) @TypeOf(radians) {
const T = @TypeOf(radians);
return switch (@typeInfo(T)) {
.Float => radians * (180.0 / math.pi),
else => @compileError("Degrees not implemented for " ++ @typeName(T)),
};
}
/// Linear interpolation between two floats.
/// `t` is used to interpolate between `from` and `to`.
pub fn lerp(comptime T: type, from: T, to: T, t: T) T {
return switch (@typeInfo(T)) {
.Float => (1 - t) * from + t * to,
else => @compileError("Lerp not implemented for " ++ @typeName(T)),
};
}

View File

@ -0,0 +1,22 @@
name: CI
on: push
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v2
- uses: goto-bus-stop/setup-zig@v1
with:
version: 0.8.0
- run: zig build test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: goto-bus-stop/setup-zig@v1
with:
version: 0.8.0
- run: zig fmt --check src/*.zig

4
src/math/zalgebra/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
zig-cache/
build_runner.zig
deps.zig
gyro.lock

21
src/math/zalgebra/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Alexandre Chêne
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

154
src/math/zalgebra/README.md Normal file
View File

@ -0,0 +1,154 @@
# zalgebra
![CI](https://github.com/kooparse/zalgebra/workflows/CI/badge.svg)
<br/>
<br/>
Linear algebra library for games and computer graphics.
The goal is to become as complete and useful as the Unity one. I'm currently using it for my projects and will continue to update it as new needs are coming.
If you would like to contribute, don't hesitate! ;)
## Examples
```zig
const za = @import("zalgebra");
const Vec3 = za.Vec3;
const Mat4 = za.Mat4;
pub fn main () void {
var projection = za.perspective(45.0, 800.0 / 600.0, 0.1, 100.0);
var view = za.lookAt(Vec3.new(0., 0., -3.), Vec3.zero(), Vec3.up());
var model = Mat4.fromTranslate(Vec3.new(0.2, 0.5, 0.0));
var mvp = Mat4.mult(projection, view.mult(model));
}
```
## Quick reference
### Aliases
Type | Description
------------ | -------------
Vec2 | Two dimensional vector for `f32`
Vec2_f64 | Two dimensional vector for `f64`
Vec2_i32 | Two dimensional vector for `i32`
Vec3 | Three dimensional vector for `f32`
Vec3_f64 | Three dimensional vector for `f64`
Vec3_i32 | Three dimensional vector for `i32`
Vec4 | Four dimensional vector for `f32`
Vec4_f64 | Four dimensional vector for `f64`
Vec4_i32 | Four dimensional vector for `i32`
Mat4 | 4x4 matrix for `f32`
Mat4_f64 | 4x4 matrix for `f64`
Quat | Quaternion for `f32`
Quat_f64 | Quaternion for `f64`
perspective | Perspective function for `f32` 4x4 mat4
orthographic | Orthographic function for `f32` 4x4 mat4
lookAt | LookAt function for `f32` 4x4 mat4
### Vectors
Methods | Description
------------ | -------------
new | Construct a vector from 2 to 4 components
at | Return component from given index
set | Set all components to the same given value
negate | Scale all components by -1
cast | Cast a type to another type
fromSlice | Construct new vectors from slice
zero | Shorthand for `(0, 0, 0)`
one | Shorthand for `(1, 1, 1)`
up | Shorthand for `(0, 1, 0)` (only for vec3)
down | Shorthand for `(0, -1, 0)` (only for vec3)
right | Shorthand for `(1, 0, 0)` (only for vec3)
left | Shorthand for `(-1, 0, 0)` (only for vec3)
forward | Shorthand for `(0, 0, 1)` (only for vec3)
back | Shorthand for `(0, 0, -1)` (only for vec3)
toArray | Return an array of same size.
getAngle | Return angle in degrees between two vectors (only for vec2 and vec3)
length | Return the magnitude of the current vector
distance | Return the distance between two points
norm | Construct a new normalized vector based on the given one
eql | Return `true` if two vectors are equals
sub | Construct new vector resulting from the substraction between two vectors
add | Construct new vector resulting from the addition between two vectors
scale | Construct new vector after multiplying each components by the given scalar
cross | Construct the cross product (as vector) from two vectors (only for vec3)
dot | Return the dot product between two vectors
lerp | Linear interpolation between two vectors
min | Construct vector from the min components between two vectors
max | Construct vector from the min components between two vectors
### Matrices
Note: All matrices are column-major.
Methods | Description
------------ | -------------
identity | Construct an identity matrix
fromSlice | Construct new matrix from given slice of data
getData | Return a pointer to the inner data
transpose | Return the transpose matrix
negate | Scale all components by -1
eql | Return `true` if two matrices are equals
multByVec4 | Multiply a given vec4 by matrix (only for mat4)
fromTranslate | Construct a translation matrix
translate | Construct a translation from the given matrix according to given axis (vec3)
fromRotation | Construct a rotation matrix
fromEulerAngle | Construct a rotation matrix from pitch/yaw/roll in degrees (X * Y * Z)
rotate | Construct a rotation from the given matrix according to given axis (vec3)
fromScale | Construct a scale matrix
scale | Construct a scale from the given matrix according to given axis (vec3)
extractTranslation | Return a vector with proper translation
orthoNormalize | Ortho normalize the given matrix.
extractRotation | Return a vector with proper pitch/yaw/roll
extractScale | Return a vector with proper scale
perspective | Construct a perspective matrix from given fovy, aspect ratio, near/far inputs
orthographic| Construct an orthographic matrix from given left, right, bottom, top, near/far inputs
lookAt | Construct a right-handed lookAt matrix from given position (eye) and target
mult | Multiply two matrices
inv | Inverse the given matrix
recompose | Return matrix from given `translation`, `rotation` and `scale` components
decompose | Return components `translation`, `rotation` and `scale` from given matrix.
debugPrint | Print the matrix data for debug purpose
### Quaternions
Methods | Description
------------ | -------------
new| Construct new quat from given floats
zero| Construct quat as `(1, 0, 0, 0)`
fromSlice | Construct new quaternion from slice
fromVec3 | Construct quaternion from vec3
eql | Return `true` if two quaternions are equal
norm | Normalize given quaternion
length | Return the magniture of the given quaternion
sub | Construct quaternion resulting from the subtraction of two given ones
add | Construct quaternion resulting from the addition of two given ones
mult | Construct quaternion resulting from the multiplication of two given ones
scale | Construct quaternion resulting from the multiplication of all components of the given quat
dot | Return the dot product between two quaternions
toMat4 | Convert given quat to rotation 4x4 matrix
fromEulerAngle | Construct quaternion from Euler angle
fromAxis | Construct quat from angle around specified axis
extractRotation | Get euler angles from given quaternion
rotateVec | Rotate given vector
### Utilities
Methods | Description
------------ | -------------
toRadians | Convert degrees to radians
toDegrees | Convert radians to degrees
lerp | Linear interpolation between two floats
## Contributing to the project
Dont be shy about shooting any questions you may have. If you are a beginner/junior, dont hesitate, I will always encourage you. Its a safe place here. Also, I would be very happy to receive any kind of pull requests, you will have (at least) some feedback/guidance rapidly.
Behind screens, there are human beings, living any sort of story. So be always kind and respectful, because we all sheer to learn new things.
## Thanks
This project is inspired by [Handmade Math](https://github.com/HandmadeMath/Handmade-Math), [nalgebra](https://nalgebra.org/) and [Unity](https://unity.com/).

View File

@ -0,0 +1,12 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
var tests = b.addTest("src/main.zig");
tests.setBuildMode(mode);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests.step);
}

View File

@ -0,0 +1,20 @@
pkgs:
zalgebra:
version: 0.1.0
description: "Linear algebra library for games and real-time computer graphics."
license: MIT
source_url: "https://github.com/kooparse/zalgebra"
tags:
math
linear-algebra
graphics
gamedev
matrix
quaternion
root: src/main.zig
files:
README.md
LICENSE
build.zig
src/*.zig

View File

@ -0,0 +1,62 @@
//! Math utilities for graphics.
//!
const std = @import("std");
const testing = std.testing;
const math = std.math;
pub usingnamespace @import("vec2.zig");
pub usingnamespace @import("vec3.zig");
pub usingnamespace @import("vec4.zig");
pub usingnamespace @import("mat4.zig");
pub usingnamespace @import("quaternion.zig");
/// Convert degrees to radians.
pub fn toRadians(degrees: anytype) @TypeOf(degrees) {
const T = @TypeOf(degrees);
return switch (@typeInfo(T)) {
.Float => degrees * (math.pi / 180.0),
else => @compileError("Radians not implemented for " ++ @typeName(T)),
};
}
/// Convert radians to degrees.
pub fn toDegrees(radians: anytype) @TypeOf(radians) {
const T = @TypeOf(radians);
return switch (@typeInfo(T)) {
.Float => radians * (180.0 / math.pi),
else => @compileError("Degrees not implemented for " ++ @typeName(T)),
};
}
/// Linear interpolation between two floats.
/// `t` is used to interpolate between `from` and `to`.
pub fn lerp(comptime T: type, from: T, to: T, t: T) T {
return switch (@typeInfo(T)) {
.Float => (1 - t) * from + t * to,
else => @compileError("Lerp not implemented for " ++ @typeName(T)),
};
}
test "zalgebra.toRadians" {
try testing.expectEqual(toRadians(@as(f32, 90)), 1.57079637);
try testing.expectEqual(toRadians(@as(f32, 45)), 0.785398185);
try testing.expectEqual(toRadians(@as(f32, 360)), 6.28318548);
try testing.expectEqual(toRadians(@as(f32, 0)), 0.0);
}
test "zalgebra.toDegrees" {
try testing.expectEqual(toDegrees(@as(f32, 0.5)), 28.6478900);
try testing.expectEqual(toDegrees(@as(f32, 1.0)), 57.2957801);
try testing.expectEqual(toDegrees(@as(f32, 0.0)), 0.0);
}
test "zalgebra.lerp" {
const from: f32 = 0;
const to: f32 = 10;
try testing.expectEqual(lerp(f32, from, to, 0), 0);
try testing.expectEqual(lerp(f32, from, to, 0.5), 5);
try testing.expectEqual(lerp(f32, from, to, 1), 10);
}

View File

@ -0,0 +1,651 @@
const std = @import("std");
const math = std.math;
const testing = std.testing;
const print = std.debug.print;
const root = @import("main.zig");
const vec4 = @import("vec4.zig");
const vec3 = @import("vec3.zig");
const quat = @import("quaternion.zig");
const Vec3 = vec3.Vec3;
const Vector3 = vec3.Vector3;
const Vector4 = vec4.Vector4;
const Quaternion = quat.Quaternion;
const Quat = quat.Quat;
pub const Mat4 = Mat4x4(f32);
pub const Mat4_f64 = Mat4x4(f64);
pub const perspective = Mat4.perspective;
pub const orthographic = Mat4.orthographic;
pub const lookAt = Mat4.lookAt;
/// A column-major 4x4 matrix
/// Note: Column-major means accessing data like m.data[COLUMN][ROW].
pub fn Mat4x4(comptime T: type) type {
if (@typeInfo(T) != .Float) {
@compileError("Mat4x4 not implemented for " ++ @typeName(T));
}
return extern struct {
data: [4][4]T,
const Self = @This();
pub fn identity() Self {
return .{
.data = .{
.{ 1, 0, 0, 0 },
.{ 0, 1, 0, 0 },
.{ 0, 0, 1, 0 },
.{ 0, 0, 0, 1 },
},
};
}
/// Construct new 4x4 matrix from given slice.
pub fn fromSlice(data: *const [16]T) Self {
return .{
.data = .{
data[0..4].*,
data[4..8].*,
data[8..12].*,
data[12..16].*,
},
};
}
/// Negate the given matrix.
pub fn negate(mat: Self) Self {
var result = mat;
var col: usize = 0;
while (col < 4) : (col += 1) {
var row: usize = 0;
while (row < 4) : (row += 1) {
result.data[col][row] = -mat.data[col][row];
}
}
return result;
}
/// Transpose the given matrix.
pub fn transpose(mat: Self) Self {
var result = mat;
var col: usize = 0;
while (col < 4) : (col += 1) {
var row: usize = col;
while (row < 4) : (row += 1) {
std.mem.swap(T, &result.data[col][row], &result.data[row][col]);
}
}
return result;
}
/// Return a pointer to the inner data of the matrix.
pub fn getData(mat: *const Self) *const T {
return @ptrCast(*const T, &mat.data);
}
pub fn eql(left: Self, right: Self) bool {
var col: usize = 0;
while (col < 4) : (col += 1) {
var row: usize = 0;
while (row < 4) : (row += 1) {
if (left.data[col][row] != right.data[col][row]) {
return false;
}
}
}
return true;
}
pub fn multByVec4(mat: Self, v: Vector4(T)) Vector4(T) {
var result: Vector4(T) = undefined;
result.x = (mat.data[0][0] * v.x) + (mat.data[1][0] * v.y) + (mat.data[2][0] * v.z) + (mat.data[3][0] * v.w);
result.y = (mat.data[0][1] * v.x) + (mat.data[1][1] * v.y) + (mat.data[2][1] * v.z) + (mat.data[3][1] * v.w);
result.z = (mat.data[0][2] * v.x) + (mat.data[1][2] * v.y) + (mat.data[2][2] * v.z) + (mat.data[3][2] * v.w);
result.w = (mat.data[0][3] * v.x) + (mat.data[1][3] * v.y) + (mat.data[2][3] * v.z) + (mat.data[3][3] * v.w);
return result;
}
/// Construct 4x4 translation matrix by multiplying identity matrix and
/// given translation vector.
pub fn fromTranslate(axis: Vector3(T)) Self {
var mat = Self.identity();
mat.data[3][0] = axis.x;
mat.data[3][1] = axis.y;
mat.data[3][2] = axis.z;
return mat;
}
/// Make a translation between the given matrix and the given axis.
pub fn translate(mat: Self, axis: Vector3(T)) Self {
const trans_mat = Self.fromTranslate(axis);
return Self.mult(trans_mat, mat);
}
/// Get translation Vec3 from current matrix.
pub fn extractTranslation(self: Self) Vector3(T) {
return Vector3(T).new(self.data[3][0], self.data[3][1], self.data[3][2]);
}
/// Construct a 4x4 matrix from given axis and angle (in degrees).
pub fn fromRotation(angle_in_degrees: T, axis: Vector3(T)) Self {
var mat = Self.identity();
const norm_axis = axis.norm();
const sin_theta = math.sin(root.toRadians(angle_in_degrees));
const cos_theta = math.cos(root.toRadians(angle_in_degrees));
const cos_value = 1.0 - cos_theta;
mat.data[0][0] = (norm_axis.x * norm_axis.x * cos_value) + cos_theta;
mat.data[0][1] = (norm_axis.x * norm_axis.y * cos_value) + (norm_axis.z * sin_theta);
mat.data[0][2] = (norm_axis.x * norm_axis.z * cos_value) - (norm_axis.y * sin_theta);
mat.data[1][0] = (norm_axis.y * norm_axis.x * cos_value) - (norm_axis.z * sin_theta);
mat.data[1][1] = (norm_axis.y * norm_axis.y * cos_value) + cos_theta;
mat.data[1][2] = (norm_axis.y * norm_axis.z * cos_value) + (norm_axis.x * sin_theta);
mat.data[2][0] = (norm_axis.z * norm_axis.x * cos_value) + (norm_axis.y * sin_theta);
mat.data[2][1] = (norm_axis.z * norm_axis.y * cos_value) - (norm_axis.x * sin_theta);
mat.data[2][2] = (norm_axis.z * norm_axis.z * cos_value) + cos_theta;
return mat;
}
pub fn rotate(mat: Self, angle_in_degrees: T, axis: Vector3(T)) Self {
const rotation_mat = Self.fromRotation(angle_in_degrees, axis);
return Self.mult(mat, rotation_mat);
}
/// Construct a rotation matrix from euler angles (X * Y * Z).
/// Order matters because matrix multiplication are NOT commutative.
pub fn fromEulerAngle(euler_angle: Vector3(T)) Self {
const x = Self.fromRotation(euler_angle.x, Vec3.new(1, 0, 0));
const y = Self.fromRotation(euler_angle.y, Vec3.new(0, 1, 0));
const z = Self.fromRotation(euler_angle.z, Vec3.new(0, 0, 1));
return z.mult(y.mult(x));
}
/// Ortho normalize given matrix.
pub fn orthoNormalize(mat: Self) Self {
const column_1 = Vec3.new(mat.data[0][0], mat.data[0][1], mat.data[0][2]).norm();
const column_2 = Vec3.new(mat.data[1][0], mat.data[1][1], mat.data[1][2]).norm();
const column_3 = Vec3.new(mat.data[2][0], mat.data[2][1], mat.data[2][2]).norm();
var result = mat;
result.data[0][0] = column_1.x;
result.data[0][1] = column_1.y;
result.data[0][2] = column_1.z;
result.data[1][0] = column_2.x;
result.data[1][1] = column_2.y;
result.data[1][2] = column_2.z;
result.data[2][0] = column_3.x;
result.data[2][1] = column_3.y;
result.data[2][2] = column_3.z;
return result;
}
/// Return the rotation as Euler angles in degrees.
/// Taken from Mike Day at Insomniac Games (and `glm` as the same function).
/// For more details: https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
pub fn extractRotation(self: Self) Vector3(T) {
const m = self.orthoNormalize();
const theta_x = math.atan2(T, m.data[1][2], m.data[2][2]);
const c2 = math.sqrt(math.pow(f32, m.data[0][0], 2) + math.pow(f32, m.data[0][1], 2));
const theta_y = math.atan2(T, -m.data[0][2], math.sqrt(c2));
const s1 = math.sin(theta_x);
const c1 = math.cos(theta_x);
const theta_z = math.atan2(T, s1 * m.data[2][0] - c1 * m.data[1][0], c1 * m.data[1][1] - s1 * m.data[2][1]);
return Vec3.new(root.toDegrees(theta_x), root.toDegrees(theta_y), root.toDegrees(theta_z));
}
pub fn fromScale(axis: Vector3(T)) Self {
var mat = Self.identity();
mat.data[0][0] = axis.x;
mat.data[1][1] = axis.y;
mat.data[2][2] = axis.z;
return mat;
}
pub fn scale(mat: Self, axis: Vector3(T)) Self {
const scale_mat = Self.fromScale(axis);
return Self.mult(scale_mat, mat);
}
pub fn extractScale(mat: Self) Vector3(T) {
const scale_x = Vec3.new(mat.data[0][0], mat.data[0][1], mat.data[0][2]).length();
const scale_y = Vec3.new(mat.data[1][0], mat.data[1][1], mat.data[1][2]).length();
const scale_z = Vec3.new(mat.data[2][0], mat.data[2][1], mat.data[2][2]).length();
return Vector3(T).new(scale_x, scale_y, scale_z);
}
/// Construct a perspective 4x4 matrix.
/// Note: Field of view is given in degrees.
/// Also for more details https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/gluPerspective.xml.
pub fn perspective(fovy_in_degrees: T, aspect_ratio: T, z_near: T, z_far: T) Self {
var mat: Self = Self.identity();
const f = 1.0 / math.tan(root.toRadians(fovy_in_degrees) * 0.5);
mat.data[0][0] = f / aspect_ratio;
mat.data[1][1] = f;
mat.data[2][2] = (z_near + z_far) / (z_near - z_far);
mat.data[2][3] = -1;
mat.data[3][2] = 2 * z_far * z_near / (z_near - z_far);
mat.data[3][3] = 0;
return mat;
}
/// Construct an orthographic 4x4 matrix.
pub fn orthographic(left: T, right: T, bottom: T, top: T, z_near: T, z_far: T) Self {
var mat: Self = undefined;
mat.data[0][0] = 2.0 / (right - left);
mat.data[1][1] = 2.0 / (top - bottom);
mat.data[2][2] = 2.0 / (z_near - z_far);
mat.data[3][3] = 1.0;
mat.data[3][0] = (left + right) / (left - right);
mat.data[3][1] = (bottom + top) / (bottom - top);
mat.data[3][2] = (z_far + z_near) / (z_near - z_far);
return mat;
}
/// Right-handed lookAt function.
pub fn lookAt(eye: Vector3(T), target: Vector3(T), up: Vector3(T)) Self {
const f = Vector3(T).norm(Vector3(T).sub(target, eye));
const s = Vector3(T).norm(Vector3(T).cross(f, up));
const u = Vector3(T).cross(s, f);
var mat: Self = undefined;
mat.data[0][0] = s.x;
mat.data[0][1] = u.x;
mat.data[0][2] = -f.x;
mat.data[0][3] = 0.0;
mat.data[1][0] = s.y;
mat.data[1][1] = u.y;
mat.data[1][2] = -f.y;
mat.data[1][3] = 0.0;
mat.data[2][0] = s.z;
mat.data[2][1] = u.z;
mat.data[2][2] = -f.z;
mat.data[2][3] = 0.0;
mat.data[3][0] = -Vector3(T).dot(s, eye);
mat.data[3][1] = -Vector3(T).dot(u, eye);
mat.data[3][2] = Vector3(T).dot(f, eye);
mat.data[3][3] = 1.0;
return mat;
}
/// Matrices multiplication.
/// Produce a new matrix from given two matrices.
pub fn mult(left: Self, right: Self) Self {
var mat = Self.identity();
var columns: usize = 0;
while (columns < 4) : (columns += 1) {
var rows: usize = 0;
while (rows < 4) : (rows += 1) {
var sum: T = 0.0;
var current_mat: usize = 0;
while (current_mat < 4) : (current_mat += 1) {
sum += left.data[current_mat][rows] * right.data[columns][current_mat];
}
mat.data[columns][rows] = sum;
}
}
return mat;
}
/// Construct inverse 4x4 from given matrix.
/// Note: This is not the most efficient way to do this.
/// TODO: Make it more efficient.
pub fn inv(mat: Self) Self {
var inv_mat: Self = undefined;
var s: [6]T = undefined;
var c: [6]T = undefined;
s[0] = mat.data[0][0] * mat.data[1][1] - mat.data[1][0] * mat.data[0][1];
s[1] = mat.data[0][0] * mat.data[1][2] - mat.data[1][0] * mat.data[0][2];
s[2] = mat.data[0][0] * mat.data[1][3] - mat.data[1][0] * mat.data[0][3];
s[3] = mat.data[0][1] * mat.data[1][2] - mat.data[1][1] * mat.data[0][2];
s[4] = mat.data[0][1] * mat.data[1][3] - mat.data[1][1] * mat.data[0][3];
s[5] = mat.data[0][2] * mat.data[1][3] - mat.data[1][2] * mat.data[0][3];
c[0] = mat.data[2][0] * mat.data[3][1] - mat.data[3][0] * mat.data[2][1];
c[1] = mat.data[2][0] * mat.data[3][2] - mat.data[3][0] * mat.data[2][2];
c[2] = mat.data[2][0] * mat.data[3][3] - mat.data[3][0] * mat.data[2][3];
c[3] = mat.data[2][1] * mat.data[3][2] - mat.data[3][1] * mat.data[2][2];
c[4] = mat.data[2][1] * mat.data[3][3] - mat.data[3][1] * mat.data[2][3];
c[5] = mat.data[2][2] * mat.data[3][3] - mat.data[3][2] * mat.data[2][3];
const determ = 1.0 / (s[0] * c[5] - s[1] * c[4] + s[2] * c[3] + s[3] * c[2] - s[4] * c[1] + s[5] * c[0]);
inv_mat.data[0][0] =
(mat.data[1][1] * c[5] - mat.data[1][2] * c[4] + mat.data[1][3] * c[3]) * determ;
inv_mat.data[0][1] =
(-mat.data[0][1] * c[5] + mat.data[0][2] * c[4] - mat.data[0][3] * c[3]) * determ;
inv_mat.data[0][2] =
(mat.data[3][1] * s[5] - mat.data[3][2] * s[4] + mat.data[3][3] * s[3]) * determ;
inv_mat.data[0][3] =
(-mat.data[2][1] * s[5] + mat.data[2][2] * s[4] - mat.data[2][3] * s[3]) * determ;
inv_mat.data[1][0] =
(-mat.data[1][0] * c[5] + mat.data[1][2] * c[2] - mat.data[1][3] * c[1]) * determ;
inv_mat.data[1][1] =
(mat.data[0][0] * c[5] - mat.data[0][2] * c[2] + mat.data[0][3] * c[1]) * determ;
inv_mat.data[1][2] =
(-mat.data[3][0] * s[5] + mat.data[3][2] * s[2] - mat.data[3][3] * s[1]) * determ;
inv_mat.data[1][3] =
(mat.data[2][0] * s[5] - mat.data[2][2] * s[2] + mat.data[2][3] * s[1]) * determ;
inv_mat.data[2][0] =
(mat.data[1][0] * c[4] - mat.data[1][1] * c[2] + mat.data[1][3] * c[0]) * determ;
inv_mat.data[2][1] =
(-mat.data[0][0] * c[4] + mat.data[0][1] * c[2] - mat.data[0][3] * c[0]) * determ;
inv_mat.data[2][2] =
(mat.data[3][0] * s[4] - mat.data[3][1] * s[2] + mat.data[3][3] * s[0]) * determ;
inv_mat.data[2][3] =
(-mat.data[2][0] * s[4] + mat.data[2][1] * s[2] - mat.data[2][3] * s[0]) * determ;
inv_mat.data[3][0] =
(-mat.data[1][0] * c[3] + mat.data[1][1] * c[1] - mat.data[1][2] * c[0]) * determ;
inv_mat.data[3][1] =
(mat.data[0][0] * c[3] - mat.data[0][1] * c[1] + mat.data[0][2] * c[0]) * determ;
inv_mat.data[3][2] =
(-mat.data[3][0] * s[3] + mat.data[3][1] * s[1] - mat.data[3][2] * s[0]) * determ;
inv_mat.data[3][3] =
(mat.data[2][0] * s[3] - mat.data[2][1] * s[1] + mat.data[2][2] * s[0]) * determ;
return inv_mat;
}
/// Return 4x4 matrix from given all transform components; `translation`, `rotation` and `sclale`.
/// The final order is T * R * S.
/// Note: `rotation` could be `Vec3` (Euler angles) or a `quat`.
pub fn recompose(translation: Vector3(T), rotation: anytype, scaler: Vector3(T)) Self {
const t = Self.fromTranslate(translation);
const s = Self.fromScale(scaler);
const r = switch (@TypeOf(rotation)) {
Quaternion(T) => Quaternion(T).toMat4(rotation),
Vector3(T) => Self.fromEulerAngle(rotation),
else => @compileError("Recompose not implemented for " ++ @typeName(@TypeOf(rotation))),
};
return t.mult(r.mult(s));
}
/// Return `translation`, `rotation` and `scale` components from given matrix.
/// For now, the rotation returned is a quaternion. If you want to get Euler angles
/// from it, just do: `returned_quat.extractRotation()`.
/// Note: We ortho nornalize the given matrix before extracting the rotation.
pub fn decompose(mat: Self) struct { t: Vector3(T), r: Quaternion(T), s: Vector3(T) } {
const t = mat.extractTranslation();
const s = mat.extractScale();
const r = Quat.fromMat4(mat.orthoNormalize());
return .{
.t = t,
.r = r,
.s = s,
};
}
/// Print the 4x4 to stderr.
pub fn debugPrint(self: Self) void {
const string =
\\ ({d}, {d}, {d}, {d})
\\ ({d}, {d}, {d}, {d})
\\ ({d}, {d}, {d}, {d})
\\ ({d}, {d}, {d}, {d})
\\
;
print(string, .{
self.data[0][0],
self.data[1][0],
self.data[2][0],
self.data[3][0],
self.data[0][1],
self.data[1][1],
self.data[2][1],
self.data[3][1],
self.data[0][2],
self.data[1][2],
self.data[2][2],
self.data[3][2],
self.data[0][3],
self.data[1][3],
self.data[2][3],
self.data[3][3],
});
}
};
}
test "zalgebra.Mat4.eql" {
const a = Mat4.identity();
const b = Mat4.identity();
const c = Mat4{
.data = .{
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
},
};
try testing.expectEqual(Mat4.eql(a, b), true);
try testing.expectEqual(Mat4.eql(a, c), false);
}
test "zalgebra.Mat4.negate" {
const a = Mat4{
.data = .{
.{ 1, 2, 3, 4 },
.{ 5, -6, 7, 8 },
.{ 9, 10, 11, -12 },
.{ 13, 14, 15, 16 },
},
};
const b = Mat4{
.data = .{
.{ -1, -2, -3, -4 },
.{ -5, 6, -7, -8 },
.{ -9, -10, -11, 12 },
.{ -13, -14, -15, -16 },
},
};
try testing.expectEqual(Mat4.eql(a.negate(), b), true);
}
test "zalgebra.Mat4.transpose" {
const a = Mat4{
.data = .{
.{ 1, 2, 3, 4 },
.{ 5, 6, 7, 8 },
.{ 9, 10, 11, 12 },
.{ 13, 14, 15, 16 },
},
};
const b = Mat4{
.data = .{
.{ 1, 5, 9, 13 },
.{ 2, 6, 10, 14 },
.{ 3, 7, 11, 15 },
.{ 4, 8, 12, 16 },
},
};
try testing.expectEqual(Mat4.eql(a.transpose(), b), true);
}
test "zalgebra.Mat4.fromSlice" {
const data = [_]f32{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
const result = Mat4.fromSlice(&data);
try testing.expectEqual(Mat4.eql(result, Mat4.identity()), true);
}
test "zalgebra.Mat4.fromTranslate" {
const a = Mat4.fromTranslate(Vec3.new(2, 3, 4));
try testing.expectEqual(Mat4.eql(a, Mat4{
.data = .{
.{ 1, 0, 0, 0 },
.{ 0, 1, 0, 0 },
.{ 0, 0, 1, 0 },
.{ 2, 3, 4, 1 },
},
}), true);
}
test "zalgebra.Mat4.translate" {
const a = Mat4.fromTranslate(Vec3.new(2, 3, 2));
const result = Mat4.translate(a, Vec3.new(2, 3, 4));
try testing.expectEqual(Mat4.eql(result, Mat4{
.data = .{
.{ 1, 0, 0, 0 },
.{ 0, 1, 0, 0 },
.{ 0, 0, 1, 0 },
.{ 4, 6, 6, 1 },
},
}), true);
}
test "zalgebra.Mat4.fromScale" {
const a = Mat4.fromScale(Vec3.new(2, 3, 4));
try testing.expectEqual(Mat4.eql(a, Mat4{
.data = .{
.{ 2, 0, 0, 0 },
.{ 0, 3, 0, 0 },
.{ 0, 0, 4, 0 },
.{ 0, 0, 0, 1 },
},
}), true);
}
test "zalgebra.Mat4.scale" {
const a = Mat4.fromScale(Vec3.new(2, 3, 4));
const result = Mat4.scale(a, Vec3.new(2, 2, 2));
try testing.expectEqual(Mat4.eql(result, Mat4{
.data = .{
.{ 4, 0, 0, 0 },
.{ 0, 6, 0, 0 },
.{ 0, 0, 8, 0 },
.{ 0, 0, 0, 1 },
},
}), true);
}
test "zalgebra.Mat4.inv" {
const a: Mat4 = .{
.data = .{
.{ 2, 0, 0, 4 },
.{ 0, 2, 0, 0 },
.{ 0, 0, 2, 0 },
.{ 4, 0, 0, 2 },
},
};
try testing.expectEqual(Mat4.eql(a.inv(), Mat4{
.data = .{
.{ -0.1666666716337204, 0, 0, 0.3333333432674408 },
.{ 0, 0.5, 0, 0 },
.{ 0, 0, 0.5, 0 },
.{ 0.3333333432674408, 0, 0, -0.1666666716337204 },
},
}), true);
}
test "zalgebra.Mat4.extractTranslation" {
var a = Mat4.fromTranslate(Vec3.new(2, 3, 2));
a = a.translate(Vec3.new(2, 3, 2));
try testing.expectEqual(Vec3.eql(a.extractTranslation(), Vec3.new(4, 6, 4)), true);
}
test "zalgebra.Mat4.extractRotation" {
const a = Mat4.fromEulerAngle(Vec3.new(45, -5, 20));
try testing.expectEqual(Vec3.eql(
a.extractRotation(),
Vec3.new(45.000003814697266, -4.99052524, 19.999998092651367),
), true);
}
test "zalgebra.Mat4.extractScale" {
var a = Mat4.fromScale(Vec3.new(2, 4, 8));
a = a.scale(Vec3.new(2, 4, 8));
try testing.expectEqual(Vec3.eql(a.extractScale(), Vec3.new(4, 16, 64)), true);
}
test "zalgebra.Mat4.recompose" {
const result = Mat4.recompose(
Vec3.new(2, 2, 2),
Vec3.new(45, 5, 0),
Vec3.new(1, 1, 1),
);
try testing.expectEqual(Mat4.eql(result, Mat4{
.data = .{
.{ 0.9961947202682495, 0, -0.08715573698282242, 0 },
.{ 0.06162841618061066, 0.7071067690849304, 0.7044160962104797, 0 },
.{ 0.06162841245532036, -0.7071068286895752, 0.704416036605835, 0 },
.{ 2, 2, 2, 1 },
},
}), true);
}
test "zalgebra.Mat4.decompose" {
const a = Mat4.recompose(
Vec3.new(10, 5, 5),
Vec3.new(45, 5, 0),
Vec3.new(1, 1, 1),
);
const result = a.decompose();
try testing.expectEqual(result.t.eql(Vec3.new(10, 5, 5)), true);
try testing.expectEqual(result.s.eql(Vec3.new(1, 1, 1)), true);
try testing.expectEqual(result.r.extractRotation().eql(Vec3.new(45, 5, -0.00000010712935250012379)), true);
}

View File

@ -0,0 +1,411 @@
const std = @import("std");
const math = std.math;
const testing = std.testing;
const assert = std.debug.assert;
const root = @import("main.zig");
const vec4 = @import("vec4.zig");
const vec3 = @import("vec3.zig");
const mat4 = @import("mat4.zig");
const Vec3 = vec3.Vec3;
const Vector3 = vec3.Vector3;
const Vec4 = vec4.Vec4;
const Vector4 = vec4.Vector4;
const Mat4x4 = mat4.Mat4x4;
pub const Quat = Quaternion(f32);
pub const Quat_f64 = Quaternion(f64);
/// A Quaternion for 3D rotations.
pub fn Quaternion(comptime T: type) type {
if (@typeInfo(T) != .Float) {
@compileError("Quaternion not implemented for " ++ @typeName(T));
}
return extern struct {
w: T,
x: T,
y: T,
z: T,
const Self = @This();
/// Construct new quaternion from floats.
pub fn new(w: T, x: T, y: T, z: T) Self {
return .{
.w = w,
.x = x,
.y = y,
.z = z,
};
}
/// Construct most basic quaternion.
pub fn zero() Self {
return Self.new(1, 0, 0, 0);
}
/// Construct new quaternion from slice.
/// Note: Careful, the latest component `slice[3]` is the `W` component.
pub fn fromSlice(slice: []const T) Self {
return Self.new(slice[3], slice[0], slice[1], slice[2]);
}
pub fn fromVec3(w: T, axis: Vector3(T)) Self {
return .{
.w = w,
.x = axis.x,
.y = axis.y,
.z = axis.z,
};
}
pub fn eql(left: Self, right: Self) bool {
return (left.w == right.w and
left.x == right.x and
left.y == right.y and
left.z == right.z);
}
pub fn norm(self: Self) Self {
const l = length(self);
assert(l != 0);
return Self.new(self.w / l, self.x / l, self.y / l, self.z / l);
}
pub fn length(self: Self) T {
return math.sqrt((self.w * self.w) +
(self.x * self.x) +
(self.y * self.y) +
(self.z * self.z));
}
pub fn sub(left: Self, right: Self) Self {
return Self.new(
left.w - right.w,
left.x - right.x,
left.y - right.y,
left.z - right.z,
);
}
pub fn add(left: Self, right: Self) Self {
return Self.new(
left.w + right.w,
left.x + right.x,
left.y + right.y,
left.z + right.z,
);
}
pub fn mult(left: Self, right: Self) Self {
var q: Self = undefined;
q.x = (left.x * right.w) + (left.y * right.z) - (left.z * right.y) + (left.w * right.x);
q.y = (-left.x * right.z) + (left.y * right.w) + (left.z * right.x) + (left.w * right.y);
q.z = (left.x * right.y) - (left.y * right.x) + (left.z * right.w) + (left.w * right.z);
q.w = (-left.x * right.x) - (left.y * right.y) - (left.z * right.z) + (left.w * right.w);
return q;
}
pub fn scale(mat: Self, scalar: T) Self {
var result: Self = undefined;
result.w = mat.w * scalar;
result.x = mat.x * scalar;
result.y = mat.y * scalar;
result.z = mat.z * scalar;
return result;
}
/// Return the dot product between two quaternion.
pub fn dot(left: Self, right: Self) T {
return (left.x * right.x) + (left.y * right.y) + (left.z * right.z) + (left.w * right.w);
}
/// Convert given quaternion to rotation 4x4 matrix.
/// Mostly taken from https://github.com/HandmadeMath/Handmade-Math.
pub fn toMat4(self: Self) Mat4x4(T) {
var result: Mat4x4(T) = undefined;
const normalized = self.norm();
const xx = normalized.x * normalized.x;
const yy = normalized.y * normalized.y;
const zz = normalized.z * normalized.z;
const xy = normalized.x * normalized.y;
const xz = normalized.x * normalized.z;
const yz = normalized.y * normalized.z;
const wx = normalized.w * normalized.x;
const wy = normalized.w * normalized.y;
const wz = normalized.w * normalized.z;
result.data[0][0] = 1.0 - 2.0 * (yy + zz);
result.data[0][1] = 2.0 * (xy + wz);
result.data[0][2] = 2.0 * (xz - wy);
result.data[0][3] = 0.0;
result.data[1][0] = 2.0 * (xy - wz);
result.data[1][1] = 1.0 - 2.0 * (xx + zz);
result.data[1][2] = 2.0 * (yz + wx);
result.data[1][3] = 0.0;
result.data[2][0] = 2.0 * (xz + wy);
result.data[2][1] = 2.0 * (yz - wx);
result.data[2][2] = 1.0 - 2.0 * (xx + yy);
result.data[2][3] = 0.0;
result.data[3][0] = 0.0;
result.data[3][1] = 0.0;
result.data[3][2] = 0.0;
result.data[3][3] = 1.0;
return result;
}
/// From Mike Day at Insomniac Games.
/// For more details: https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2015/01/matrix-to-quat.pdf
pub fn fromMat4(m: Mat4x4(T)) Self {
var t: f32 = 0;
var result: Self = undefined;
if (m.data[2][2] < 0.0) {
if (m.data[0][0] > m.data[1][1]) {
t = 1 + m.data[0][0] - m.data[1][1] - m.data[2][2];
result = Self.new(
m.data[1][2] - m.data[2][1],
t,
m.data[0][1] + m.data[1][0],
m.data[2][0] + m.data[0][2],
);
} else {
t = 1 - m.data[0][0] + m.data[1][1] - m.data[2][2];
result = Self.new(
m.data[2][0] - m.data[0][2],
m.data[0][1] + m.data[1][0],
t,
m.data[1][2] + m.data[2][1],
);
}
} else {
if (m.data[0][0] < -m.data[1][1]) {
t = 1 - m.data[0][0] - m.data[1][1] + m.data[2][2];
result = Self.new(
m.data[0][1] - m.data[1][0],
m.data[2][0] + m.data[0][2],
m.data[1][2] + m.data[2][1],
t,
);
} else {
t = 1 + m.data[0][0] + m.data[1][1] + m.data[2][2];
result = Self.new(
t,
m.data[1][2] - m.data[2][1],
m.data[2][0] - m.data[0][2],
m.data[0][1] - m.data[1][0],
);
}
}
return Self.scale(result, 0.5 / math.sqrt(t));
}
/// Convert all Euler angles to quaternion.
pub fn fromEulerAngle(axis: Vector3(T)) Self {
const x = Self.fromAxis(axis.x, Vec3.new(1, 0, 0));
const y = Self.fromAxis(axis.y, Vec3.new(0, 1, 0));
const z = Self.fromAxis(axis.z, Vec3.new(0, 0, 1));
return z.mult(y.mult(x));
}
/// Convert Euler angle around specified axis to quaternion.
pub fn fromAxis(degrees: T, axis: Vector3(T)) Self {
const radians = root.toRadians(degrees);
const rot_sin = math.sin(radians / 2.0);
const quat_axis = axis.norm().scale(rot_sin);
const w = math.cos(radians / 2.0);
return Self.fromVec3(w, quat_axis);
}
/// Extract euler angles from quaternion.
pub fn extractRotation(self: Self) Vector3(T) {
const yaw = math.atan2(
T,
2.0 * (self.y * self.z + self.w * self.x),
self.w * self.w - self.x * self.x - self.y * self.y + self.z * self.z,
);
const pitch = math.asin(
-2.0 * (self.x * self.z - self.w * self.y),
);
const roll = math.atan2(
T,
2.0 * (self.x * self.y + self.w * self.z),
self.w * self.w + self.x * self.x - self.y * self.y - self.z * self.z,
);
return Vector3(T).new(root.toDegrees(yaw), root.toDegrees(pitch), root.toDegrees(roll));
}
/// Lerp between two quaternions.
pub fn lerp(left: Self, right: Self, t: f32) Self {
const w = root.lerp(T, left.w, right.w, t);
const x = root.lerp(T, left.x, right.x, t);
const y = root.lerp(T, left.y, right.y, t);
const z = root.lerp(T, left.z, right.z, t);
return Self.new(w, x, y, z);
}
// Shortest path slerp between two quaternions.
// Taken from "Physically Based Rendering, 3rd Edition, Chapter 2.9.2"
// https://pbr-book.org/3ed-2018/Geometry_and_Transformations/Animating_Transformations#QuaternionInterpolation
pub fn slerp(left: Self, right: Self, t: f32) Self {
const ParallelThreshold: f32 = 0.9995;
var cos_theta = dot(left, right);
var right1 = right;
// We need the absolute value of the dot product to take the shortest path
if (cos_theta < 0.0) {
cos_theta *= -1;
right1 = right.scale(-1);
}
if (cos_theta > ParallelThreshold) {
// Use regular old lerp to avoid numerical instability
return lerp(left, right1, t);
} else {
var theta = math.acos(math.clamp(cos_theta, -1, 1));
var thetap = theta * t;
var qperp = right1.sub(left.scale(cos_theta)).norm();
return left.scale(math.cos(thetap)).add(qperp.scale(math.sin(thetap)));
}
}
/// Rotate the vector v using the sandwich product.
/// Taken from "Foundations of Game Engine Development Vol. 1 Mathematics".
pub fn rotateVec(self: Self, v: Vector3(T)) Vector3(T) {
const q = self.norm();
const b = Vector3(T).new(q.x, q.y, q.z);
const b2 = b.x * b.x + b.y * b.y + b.z * b.z;
return v.scale(q.w * q.w - b2).add(b.scale(v.dot(b) * 2.0)).add(b.cross(v).scale(q.w * 2.0));
}
};
}
test "zalgebra.Quaternion.new" {
const q = Quat.new(1.5, 2.6, 3.7, 4.7);
try testing.expectEqual(q.w, 1.5);
try testing.expectEqual(q.x, 2.6);
try testing.expectEqual(q.y, 3.7);
try testing.expectEqual(q.z, 4.7);
}
test "zalgebra.Quaternion.fromSlice" {
const array = [4]f32{ 2, 3, 4, 1 };
try testing.expectEqual(Quat.eql(Quat.fromSlice(&array), Quat.new(1, 2, 3, 4)), true);
}
test "zalgebra.Quaternion.fromVec3" {
const q = Quat.fromVec3(1.5, Vec3.new(2.6, 3.7, 4.7));
try testing.expectEqual(q.w, 1.5);
try testing.expectEqual(q.x, 2.6);
try testing.expectEqual(q.y, 3.7);
try testing.expectEqual(q.z, 4.7);
}
test "zalgebra.Quaternion.fromVec3" {
const q1 = Quat.fromVec3(1.5, Vec3.new(2.6, 3.7, 4.7));
const q2 = Quat.fromVec3(1.5, Vec3.new(2.6, 3.7, 4.7));
const q3 = Quat.fromVec3(1, Vec3.new(2.6, 3.7, 4.7));
try testing.expectEqual(q1.eql(q2), true);
try testing.expectEqual(q1.eql(q3), false);
}
test "zalgebra.Quaternion.norm" {
const q1 = Quat.fromVec3(1, Vec3.new(2, 2.0, 2.0));
const q2 = Quat.fromVec3(0.2773500978946686, Vec3.new(0.5547001957893372, 0.5547001957893372, 0.5547001957893372));
try testing.expectEqual(q1.norm().eql(q2), true);
}
test "zalgebra.Quaternion.fromEulerAngle" {
const q1 = Quat.fromEulerAngle(Vec3.new(10, 5, 45));
const res_q1 = q1.extractRotation();
const q2 = Quat.fromEulerAngle(Vec3.new(0, 55, 22));
const res_q2 = q2.toMat4().extractRotation();
try testing.expectEqual(Vec3.eql(res_q1, Vec3.new(9.999999046325684, 5.000000476837158, 45)), true);
try testing.expectEqual(Vec3.eql(res_q2, Vec3.new(0, 47.245025634765625, 22)), true);
}
test "zalgebra.Quaternion.fromAxis" {
const q1 = Quat.fromAxis(45, Vec3.new(0, 1, 0));
const res_q1 = q1.extractRotation();
try testing.expectEqual(Vec3.eql(res_q1, Vec3.new(0, 45.0000076, 0)), true);
}
test "zalgebra.Quaternion.extractRotation" {
const q1 = Quat.fromVec3(0.5, Vec3.new(0.5, 1, 0.3));
const res_q1 = q1.extractRotation();
try testing.expectEqual(Vec3.eql(res_q1, Vec3.new(129.6000213623047, 44.427005767822266, 114.41073608398438)), true);
}
test "zalgebra.Quaternion.rotateVec" {
const eps_value = comptime std.math.epsilon(f32);
const q = Quat.fromEulerAngle(Vec3.new(45, 45, 45));
const m = q.toMat4();
const v = Vec3.new(0, 1, 0);
const v1 = q.rotateVec(v);
const v2 = m.multByVec4(Vec4.new(v.x, v.y, v.z, 1.0));
try testing.expect(std.math.approxEqAbs(f32, v1.x, -1.46446585e-01, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v1.y, 8.53553473e-01, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v1.z, 0.5, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v1.x, v2.x, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v1.y, v2.y, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v1.z, v2.z, eps_value));
}
test "zalgebra.Quaternion.lerp" {
const eps_value = comptime std.math.epsilon(f32);
var v1 = Quat.zero();
var v2 = Quat.fromAxis(180, Vec3.up());
try testing.expectEqual(Quat.eql(
Quat.lerp(v1, v2, 1.0),
v2,
), true);
var v3 = Quat.lerp(v1, v2, 0.5);
var v4 = Quat.new(4.99999970e-01, 0, 4.99999970e-01, 0);
try testing.expect(std.math.approxEqAbs(f32, v3.w, v4.w, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v3.x, v4.x, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v3.y, v4.y, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v3.z, v4.z, eps_value));
}
test "zalgebra.Quaternion.slerp" {
const eps_value = comptime std.math.epsilon(f32);
var v1 = Quat.zero();
var v2 = Quat.fromAxis(180, Vec3.up());
try testing.expectEqual(Quat.eql(
Quat.slerp(v1, v2, 1.0),
Quat.new(7.54979012e-08, 0, -1, 0),
), true);
var v3 = Quat.slerp(v1, v2, 0.5);
var v4 = Quat.new(7.071067e-01, 0, -7.071067e-01, 0);
try testing.expect(std.math.approxEqAbs(f32, v3.w, v4.w, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v3.x, v4.x, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v3.y, v4.y, eps_value));
try testing.expect(std.math.approxEqAbs(f32, v3.z, v4.z, eps_value));
}

View File

@ -0,0 +1,315 @@
const std = @import("std");
const root = @import("main.zig");
const math = std.math;
const testing = std.testing;
const panic = std.debug.panic;
pub const Vec2 = Vector2(f32);
pub const Vec2_f64 = Vector2(f64);
pub const Vec2_i32 = Vector2(i32);
/// A 2 dimensional vector.
pub fn Vector2(comptime T: type) type {
if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) {
@compileError("Vector2 not implemented for " ++ @typeName(T));
}
return extern struct {
x: T,
y: T,
const Self = @This();
/// Construct vector from given 2 components.
pub fn new(x: T, y: T) Self {
return .{ .x = x, .y = y };
}
/// Set all components to the same given value.
pub fn set(val: T) Self {
return Self.new(val, val);
}
pub fn zero() Self {
return Self.new(0, 0);
}
pub fn one() Self {
return Self.new(1, 1);
}
pub fn up() Self {
return Self.new(0, 1);
}
/// Negate the given vector.
pub fn negate(self: Self) Self {
return self.scale(-1);
}
/// Cast a type to another type. Only for integers and floats.
/// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt.
pub fn cast(self: Self, dest: anytype) Vector2(dest) {
const source_info = @typeInfo(T);
const dest_info = @typeInfo(dest);
if (source_info == .Float and dest_info == .Int) {
const x = @floatToInt(dest, self.x);
const y = @floatToInt(dest, self.y);
return Vector2(dest).new(x, y);
}
if (source_info == .Int and dest_info == .Float) {
const x = @intToFloat(dest, self.x);
const y = @intToFloat(dest, self.y);
return Vector2(dest).new(x, y);
}
return switch (dest_info) {
.Float => {
const x = @floatCast(dest, self.x);
const y = @floatCast(dest, self.y);
return Vector2(dest).new(x, y);
},
.Int => {
const x = @intCast(dest, self.x);
const y = @intCast(dest, self.y);
return Vector2(dest).new(x, y);
},
else => panic(
"Error, given type should be integer or float.\n",
.{},
),
};
}
/// Construct new vector from slice.
pub fn fromSlice(slice: []const T) Self {
return Self.new(slice[0], slice[1]);
}
/// Transform vector to array.
pub fn toArray(self: Self) [2]T {
return .{ self.x, self.y };
}
/// Return the angle in degrees between two vectors.
pub fn getAngle(left: Self, right: Self) T {
const dot_product = Self.dot(left.norm(), right.norm());
return root.toDegrees(math.acos(dot_product));
}
/// Compute the length (magnitude) of given vector |a|.
pub fn length(self: Self) T {
return math.sqrt((self.x * self.x) + (self.y * self.y));
}
/// Compute the distance between two points.
pub fn distance(a: Self, b: Self) T {
return math.sqrt(
math.pow(T, b.x - a.x, 2) + math.pow(T, b.y - a.y, 2),
);
}
/// Construct new normalized vector from a given vector.
pub fn norm(self: Self) Self {
var l = length(self);
return Self.new(self.x / l, self.y / l);
}
pub fn eql(left: Self, right: Self) bool {
return left.x == right.x and left.y == right.y;
}
/// Substraction between two given vector.
pub fn sub(left: Self, right: Self) Self {
return Self.new(left.x - right.x, left.y - right.y);
}
/// Addition betwen two given vector.
pub fn add(left: Self, right: Self) Self {
return Self.new(left.x + right.x, left.y + right.y);
}
/// Multiply each components by the given scalar.
pub fn scale(v: Self, scalar: T) Self {
return Self.new(v.x * scalar, v.y * scalar);
}
/// Return the dot product between two given vector.
pub fn dot(left: Self, right: Self) T {
return (left.x * right.x) + (left.y * right.y);
}
/// Lerp between two vectors.
pub fn lerp(left: Self, right: Self, t: T) Self {
const x = root.lerp(T, left.x, right.x, t);
const y = root.lerp(T, left.y, right.y, t);
return Self.new(x, y);
}
/// Construct a new vector from the min components between two vectors.
pub fn min(left: Self, right: Self) Self {
return Self.new(
math.min(left.x, right.x),
math.min(left.y, right.y),
);
}
/// Construct a new vector from the max components between two vectors.
pub fn max(left: Self, right: Self) Self {
return Self.new(
math.max(left.x, right.x),
math.max(left.y, right.y),
);
}
};
}
test "zalgebra.Vec2.init" {
var a = Vec2.new(1.5, 2.6);
try testing.expectEqual(a.x, 1.5);
try testing.expectEqual(a.y, 2.6);
}
test "zalgebra.Vec2.set" {
var a = Vec2.new(2.5, 2.5);
var b = Vec2.set(2.5);
try testing.expectEqual(Vec2.eql(a, b), true);
}
test "zalgebra.Vec2.negate" {
var a = Vec2.set(5);
var b = Vec2.set(-5);
try testing.expectEqual(Vec2.eql(a.negate(), b), true);
}
test "zalgebra.Vec2.getAngle" {
var a = Vec2.new(1, 0);
var b = Vec2.up();
var c = Vec2.new(-1, 0);
var d = Vec2.new(1, 1);
try testing.expectEqual(Vec2.getAngle(a, b), 90);
try testing.expectEqual(Vec2.getAngle(a, c), 180);
try testing.expectEqual(Vec2.getAngle(a, d), 45);
}
test "zalgebra.Vec2.toArray" {
const a = Vec2.up().toArray();
const b = [_]f32{ 0, 1 };
try testing.expectEqual(std.mem.eql(f32, &a, &b), true);
}
test "zalgebra.Vec2.eql" {
var a = Vec2.new(1, 2);
var b = Vec2.new(1, 2);
var c = Vec2.new(1.5, 2);
try testing.expectEqual(Vec2.eql(a, b), true);
try testing.expectEqual(Vec2.eql(a, c), false);
}
test "zalgebra.Vec2.length" {
var a = Vec2.new(1.5, 2.6);
try testing.expectEqual(a.length(), 3.00166606);
}
test "zalgebra.Vec2.distance" {
var a = Vec2.new(0, 0);
var b = Vec2.new(-1, 0);
var c = Vec2.new(0, 5);
try testing.expectEqual(Vec2.distance(a, b), 1);
try testing.expectEqual(Vec2.distance(a, c), 5);
}
test "zalgebra.Vec2.normalize" {
var a = Vec2.new(1.5, 2.6);
try testing.expectEqual(Vec2.eql(a.norm(), Vec2.new(0.499722480, 0.866185605)), true);
}
test "zalgebra.Vec2.sub" {
var a = Vec2.new(1, 2);
var b = Vec2.new(2, 2);
try testing.expectEqual(Vec2.eql(Vec2.sub(a, b), Vec2.new(-1, 0)), true);
}
test "zalgebra.Vec2.add" {
var a = Vec2.new(1, 2);
var b = Vec2.new(2, 2);
try testing.expectEqual(Vec2.eql(Vec2.add(a, b), Vec2.new(3, 4)), true);
}
test "zalgebra.Vec2.scale" {
var a = Vec2.new(1, 2);
try testing.expectEqual(Vec2.eql(Vec2.scale(a, 5), Vec2.new(5, 10)), true);
}
test "zalgebra.Vec2.dot" {
var a = Vec2.new(1.5, 2.6);
var b = Vec2.new(2.5, 3.45);
try testing.expectEqual(Vec2.dot(a, b), 12.7200002);
}
test "zalgebra.Vec2.lerp" {
var a = Vec2.new(-10.0, 0.0);
var b = Vec2.new(10.0, 10.0);
try testing.expectEqual(Vec2.eql(Vec2.lerp(a, b, 0.5), Vec2.new(0.0, 5.0)), true);
}
test "zalgebra.Vec2.min" {
var a = Vec2.new(10.0, -2.0);
var b = Vec2.new(-10.0, 5.0);
try testing.expectEqual(Vec2.eql(Vec2.min(a, b), Vec2.new(-10.0, -2.0)), true);
}
test "zalgebra.Vec2.max" {
var a = Vec2.new(10.0, -2.0);
var b = Vec2.new(-10.0, 5.0);
try testing.expectEqual(Vec2.eql(Vec2.max(a, b), Vec2.new(10.0, 5.0)), true);
}
test "zalgebra.Vec2.fromSlice" {
const array = [2]f32{ 2, 4 };
try testing.expectEqual(Vec2.eql(Vec2.fromSlice(&array), Vec2.new(2, 4)), true);
}
test "zalgebra.Vec2.cast" {
const a = Vec2_i32.new(3, 6);
const b = Vector2(usize).new(3, 6);
try testing.expectEqual(
Vector2(usize).eql(a.cast(usize), b),
true,
);
const c = Vec2.new(3.5, 6.5);
const d = Vec2_f64.new(3.5, 6.5);
try testing.expectEqual(
Vec2_f64.eql(c.cast(f64), d),
true,
);
const e = Vec2_i32.new(3, 6);
const f = Vec2.new(3.0, 6.0);
try testing.expectEqual(
Vec2.eql(e.cast(f32), f),
true,
);
const g = Vec2.new(3.0, 6.0);
const h = Vec2_i32.new(3, 6);
try testing.expectEqual(
Vec2_i32.eql(g.cast(i32), h),
true,
);
}

View File

@ -0,0 +1,414 @@
const std = @import("std");
const root = @import("main.zig");
const math = std.math;
const assert = std.debug.assert;
const panic = std.debug.panic;
const testing = std.testing;
pub const Vec3 = Vector3(f32);
pub const Vec3_f64 = Vector3(f64);
pub const Vec3_i32 = Vector3(i32);
/// A 3 dimensional vector.
pub fn Vector3(comptime T: type) type {
if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) {
@compileError("Vector3 not implemented for " ++ @typeName(T));
}
return extern struct {
x: T,
y: T,
z: T,
const Self = @This();
/// Construct a vector from given 3 components.
pub fn new(x: T, y: T, z: T) Self {
return Self{ .x = x, .y = y, .z = z };
}
/// Return component from given index.
pub fn at(self: *const Self, index: i32) T {
assert(index <= 2);
if (index == 0) {
return self.x;
} else if (index == 1) {
return self.y;
} else {
return self.z;
}
}
/// Set all components to the same given value.
pub fn set(val: T) Self {
return Self.new(val, val, val);
}
/// Shorthand for writing vec3.new(0, 0, 0).
pub fn zero() Self {
return Self.new(0, 0, 0);
}
/// Shorthand for writing vec3.new(1, 1, 1).
pub fn one() Self {
return Self.new(1, 1, 1);
}
/// Shorthand for writing vec3.new(0, 1, 0).
pub fn up() Self {
return Self.new(0, 1, 0);
}
/// Shorthand for writing vec3.new(0, -1, 0).
pub fn down() Self {
return Self.new(0, -1, 0);
}
/// Shorthand for writing vec3.new(1, 0, 0).
pub fn right() Self {
return Self.new(1, 0, 0);
}
/// Shorthand for writing vec3.new(-1, 0, 0).
pub fn left() Self {
return Self.new(-1, 0, 0);
}
/// Shorthand for writing vec3.new(0, 0, -1).
pub fn back() Self {
return Self.new(0, 0, -1);
}
/// Shorthand for writing vec3.new(0, 0, 1).
pub fn forward() Self {
return Self.new(0, 0, 1);
}
/// Negate the given vector.
pub fn negate(self: Self) Self {
return self.scale(-1);
}
/// Cast a type to another type. Only for integers and floats.
/// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt
pub fn cast(self: Self, dest: anytype) Vector3(dest) {
const source_info = @typeInfo(T);
const dest_info = @typeInfo(dest);
if (source_info == .Float and dest_info == .Int) {
const x = @floatToInt(dest, self.x);
const y = @floatToInt(dest, self.y);
const z = @floatToInt(dest, self.z);
return Vector3(dest).new(x, y, z);
}
if (source_info == .Int and dest_info == .Float) {
const x = @intToFloat(dest, self.x);
const y = @intToFloat(dest, self.y);
const z = @intToFloat(dest, self.z);
return Vector3(dest).new(x, y, z);
}
return switch (dest_info) {
.Float => {
const x = @floatCast(dest, self.x);
const y = @floatCast(dest, self.y);
const z = @floatCast(dest, self.z);
return Vector3(dest).new(x, y, z);
},
.Int => {
const x = @intCast(dest, self.x);
const y = @intCast(dest, self.y);
const z = @intCast(dest, self.z);
return Vector3(dest).new(x, y, z);
},
else => panic(
"Error, given type should be integers or float.\n",
.{},
),
};
}
/// Construct new vector from slice.
pub fn fromSlice(slice: []const T) Self {
return Self.new(slice[0], slice[1], slice[2]);
}
/// Transform vector to array.
pub fn toArray(self: Self) [3]T {
return .{ self.x, self.y, self.z };
}
/// Return the angle in degrees between two vectors.
pub fn getAngle(lhs: Self, rhs: Self) T {
const dot_product = Self.dot(lhs.norm(), rhs.norm());
return root.toDegrees(math.acos(dot_product));
}
/// Compute the length (magnitude) of given vector |a|.
pub fn length(self: Self) T {
return math.sqrt(
(self.x * self.x) + (self.y * self.y) + (self.z * self.z),
);
}
/// Compute the distance between two points.
pub fn distance(a: Self, b: Self) T {
return math.sqrt(
math.pow(T, b.x - a.x, 2) +
math.pow(T, b.y - a.y, 2) +
math.pow(T, b.z - a.z, 2),
);
}
/// Construct new normalized vector from a given vector.
pub fn norm(self: Self) Self {
var l = length(self);
return Self.new(self.x / l, self.y / l, self.z / l);
}
pub fn eql(lhs: Self, rhs: Self) bool {
return lhs.x == rhs.x and lhs.y == rhs.y and lhs.z == rhs.z;
}
/// Substraction between two given vector.
pub fn sub(lhs: Self, rhs: Self) Self {
return Self.new(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z);
}
/// Addition betwen two given vector.
pub fn add(lhs: Self, rhs: Self) Self {
return Self.new(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z);
}
/// Multiply each components by the given scalar.
pub fn scale(v: Self, scalar: T) Self {
return Self.new(v.x * scalar, v.y * scalar, v.z * scalar);
}
/// Compute the cross product from two vector.
pub fn cross(lhs: Self, rhs: Self) Self {
return Self.new(
(lhs.y * rhs.z) - (lhs.z * rhs.y),
(lhs.z * rhs.x) - (lhs.x * rhs.z),
(lhs.x * rhs.y) - (lhs.y * rhs.x),
);
}
/// Return the dot product between two given vector.
pub fn dot(lhs: Self, rhs: Self) T {
return (lhs.x * rhs.x) + (lhs.y * rhs.y) + (lhs.z * rhs.z);
}
/// Lerp between two vectors.
pub fn lerp(lhs: Self, rhs: Self, t: T) Self {
const x = root.lerp(T, lhs.x, rhs.x, t);
const y = root.lerp(T, lhs.y, rhs.y, t);
const z = root.lerp(T, lhs.z, rhs.z, t);
return Self.new(x, y, z);
}
/// Construct a new vector from the min components between two vectors.
pub fn min(lhs: Self, rhs: Self) Self {
return Self.new(
math.min(lhs.x, rhs.x),
math.min(lhs.y, rhs.y),
math.min(lhs.z, rhs.z),
);
}
/// Construct a new vector from the max components between two vectors.
pub fn max(lhs: Self, rhs: Self) Self {
return Self.new(
math.max(lhs.x, rhs.x),
math.max(lhs.y, rhs.y),
math.max(lhs.z, rhs.z),
);
}
};
}
test "zalgebra.Vec3.init" {
var a = Vec3.new(1.5, 2.6, 3.7);
try testing.expectEqual(a.x, 1.5);
try testing.expectEqual(a.y, 2.6);
try testing.expectEqual(a.z, 3.7);
}
test "zalgebra.Vec3.set" {
var a = Vec3.new(2.5, 2.5, 2.5);
var b = Vec3.set(2.5);
try testing.expectEqual(Vec3.eql(a, b), true);
}
test "zalgebra.Vec3.negate" {
var a = Vec3.set(10);
var b = Vec3.set(-10);
try testing.expectEqual(Vec3.eql(a.negate(), b), true);
}
test "zalgebra.Vec3.getAngle" {
var a = Vec3.new(1, 0, 0);
var b = Vec3.up();
var c = Vec3.new(-1, 0, 0);
var d = Vec3.new(1, 1, 0);
try testing.expectEqual(Vec3.getAngle(a, b), 90);
try testing.expectEqual(Vec3.getAngle(a, c), 180);
try testing.expectEqual(Vec3.getAngle(a, d), 45);
}
test "zalgebra.Vec3.toArray" {
const a = Vec3.up().toArray();
const b = [_]f32{ 0, 1, 0 };
try testing.expectEqual(std.mem.eql(f32, &a, &b), true);
}
test "zalgebra.Vec3.eql" {
var a = Vec3.new(1, 2, 3);
var b = Vec3.new(1, 2, 3);
var c = Vec3.new(1.5, 2, 3);
try testing.expectEqual(Vec3.eql(a, b), true);
try testing.expectEqual(Vec3.eql(a, c), false);
}
test "zalgebra.Vec3.length" {
var a = Vec3.new(1.5, 2.6, 3.7);
try testing.expectEqual(a.length(), 4.7644519);
}
test "zalgebra.Vec3.distance" {
var a = Vec3.new(0, 0, 0);
var b = Vec3.new(-1, 0, 0);
var c = Vec3.new(0, 5, 0);
try testing.expectEqual(Vec3.distance(a, b), 1);
try testing.expectEqual(Vec3.distance(a, c), 5);
}
test "zalgebra.Vec3.normalize" {
var a = Vec3.new(1.5, 2.6, 3.7);
try testing.expectEqual(Vec3.eql(a.norm(), Vec3.new(0.314831584, 0.545708060, 0.776584625)), true);
}
test "zalgebra.Vec3.sub" {
var a = Vec3.new(1, 2, 3);
var b = Vec3.new(2, 2, 3);
try testing.expectEqual(Vec3.eql(Vec3.sub(a, b), Vec3.new(-1, 0, 0)), true);
}
test "zalgebra.Vec3.add" {
var a = Vec3.new(1, 2, 3);
var b = Vec3.new(2, 2, 3);
try testing.expectEqual(Vec3.eql(Vec3.add(a, b), Vec3.new(3, 4, 6)), true);
}
test "zalgebra.Vec3.scale" {
var a = Vec3.new(1, 2, 3);
try testing.expectEqual(Vec3.eql(Vec3.scale(a, 5), Vec3.new(5, 10, 15)), true);
}
test "zalgebra.Vec3.cross" {
var a = Vec3.new(1.5, 2.6, 3.7);
var b = Vec3.new(2.5, 3.45, 1.0);
var c = Vec3.new(1.5, 2.6, 3.7);
var result_1 = Vec3.cross(a, c);
var result_2 = Vec3.cross(a, b);
try testing.expectEqual(Vec3.eql(result_1, Vec3.new(0, 0, 0)), true);
try testing.expectEqual(Vec3.eql(
result_2,
Vec3.new(-10.1650009, 7.75, -1.32499980),
), true);
}
test "zalgebra.Vec3.dot" {
var a = Vec3.new(1.5, 2.6, 3.7);
var b = Vec3.new(2.5, 3.45, 1.0);
try testing.expectEqual(Vec3.dot(a, b), 16.42);
}
test "zalgebra.Vec3.lerp" {
var a = Vec3.new(-10.0, 0.0, -10.0);
var b = Vec3.new(10.0, 10.0, 10.0);
try testing.expectEqual(Vec3.eql(
Vec3.lerp(a, b, 0.5),
Vec3.new(0.0, 5.0, 0.0),
), true);
}
test "zalgebra.Vec3.min" {
var a = Vec3.new(10.0, -2.0, 0.0);
var b = Vec3.new(-10.0, 5.0, 0.0);
try testing.expectEqual(Vec3.eql(
Vec3.min(a, b),
Vec3.new(-10.0, -2.0, 0.0),
), true);
}
test "zalgebra.Vec3.max" {
var a = Vec3.new(10.0, -2.0, 0.0);
var b = Vec3.new(-10.0, 5.0, 0.0);
try testing.expectEqual(Vec3.eql(
Vec3.max(a, b),
Vec3.new(10.0, 5.0, 0.0),
), true);
}
test "zalgebra.Vec3.at" {
const t = Vec3.new(10.0, -2.0, 0.0);
try testing.expectEqual(t.at(0), 10.0);
try testing.expectEqual(t.at(1), -2.0);
try testing.expectEqual(t.at(2), 0.0);
}
test "zalgebra.Vec3.fromSlice" {
const array = [3]f32{ 2, 1, 4 };
try testing.expectEqual(Vec3.eql(
Vec3.fromSlice(&array),
Vec3.new(2, 1, 4),
), true);
}
test "zalgebra.Vec3.cast" {
const a = Vec3_i32.new(3, 6, 2);
const b = Vector3(usize).new(3, 6, 2);
try testing.expectEqual(
Vector3(usize).eql(a.cast(usize), b),
true,
);
const c = Vec3.new(3.5, 6.5, 2.0);
const d = Vec3_f64.new(3.5, 6.5, 2);
try testing.expectEqual(
Vec3_f64.eql(c.cast(f64), d),
true,
);
const e = Vec3_i32.new(3, 6, 2);
const f = Vec3.new(3.0, 6.0, 2.0);
try testing.expectEqual(
Vec3.eql(e.cast(f32), f),
true,
);
const g = Vec3.new(3.0, 6.0, 2.0);
const h = Vec3_i32.new(3, 6, 2);
try testing.expectEqual(
Vec3_i32.eql(g.cast(i32), h),
true,
);
}

View File

@ -0,0 +1,367 @@
const std = @import("std");
const root = @import("main.zig");
const math = std.math;
const panic = std.debug.panic;
const testing = std.testing;
pub const Vec4 = Vector4(f32);
pub const Vec4_f64 = Vector4(f64);
pub const Vec4_i32 = Vector4(i32);
/// A 4 dimensional vector.
pub fn Vector4(comptime T: type) type {
if (@typeInfo(T) != .Float and @typeInfo(T) != .Int) {
@compileError("Vector4 not implemented for " ++ @typeName(T));
}
return extern struct {
x: T,
y: T,
z: T,
w: T,
const Self = @This();
/// Constract vector from given 3 components.
pub fn new(x: T, y: T, z: T, w: T) Self {
return Self{
.x = x,
.y = y,
.z = z,
.w = w,
};
}
/// Set all components to the same given value.
pub fn set(val: T) Self {
return Self.new(val, val, val, val);
}
pub fn zero() Self {
return Self.new(0, 0, 0, 0);
}
pub fn one() Self {
return Self.new(1, 1, 1, 1);
}
/// Negate the given vector.
pub fn negate(self: Self) Self {
return self.scale(-1);
}
/// Cast a type to another type. Only for integers and floats.
/// It's like builtins: @intCast, @floatCast, @intToFloat, @floatToInt
pub fn cast(self: Self, dest: anytype) Vector4(dest) {
const source_info = @typeInfo(T);
const dest_info = @typeInfo(dest);
if (source_info == .Float and dest_info == .Int) {
const x = @floatToInt(dest, self.x);
const y = @floatToInt(dest, self.y);
const z = @floatToInt(dest, self.z);
const w = @floatToInt(dest, self.w);
return Vector4(dest).new(x, y, z, w);
}
if (source_info == .Int and dest_info == .Float) {
const x = @intToFloat(dest, self.x);
const y = @intToFloat(dest, self.y);
const z = @intToFloat(dest, self.z);
const w = @intToFloat(dest, self.w);
return Vector4(dest).new(x, y, z, w);
}
return switch (dest_info) {
.Float => {
const x = @floatCast(dest, self.x);
const y = @floatCast(dest, self.y);
const z = @floatCast(dest, self.z);
const w = @floatCast(dest, self.w);
return Vector4(dest).new(x, y, z, w);
},
.Int => {
const x = @intCast(dest, self.x);
const y = @intCast(dest, self.y);
const z = @intCast(dest, self.z);
const w = @intCast(dest, self.w);
return Vector4(dest).new(x, y, z, w);
},
else => panic(
"Error, given type should be integers or float.\n",
.{},
),
};
}
/// Construct new vector from slice.
pub fn fromSlice(slice: []const T) Self {
return Self.new(slice[0], slice[1], slice[2], slice[3]);
}
/// Transform vector to array.
pub fn toArray(self: Self) [4]T {
return .{ self.x, self.y, self.z, self.w };
}
/// Compute the length (magnitude) of given vector |a|.
pub fn length(self: Self) T {
return math.sqrt(
(self.x * self.x) +
(self.y * self.y) +
(self.z * self.z) +
(self.w * self.w),
);
}
/// Compute the distance between two points.
pub fn distance(a: Self, b: Self) T {
return math.sqrt(
math.pow(T, b.x - a.x, 2) +
math.pow(T, b.y - a.y, 2) +
math.pow(T, b.z - a.z, 2) +
math.pow(T, b.w - a.w, 2),
);
}
/// Construct new normalized vector from a given vector.
pub fn norm(self: Self) Self {
var l = length(self);
return Self.new(self.x / l, self.y / l, self.z / l, self.w / l);
}
pub fn eql(left: Self, right: Self) bool {
return left.x == right.x and
left.y == right.y and
left.z == right.z and
left.w == right.w;
}
/// Substraction between two given vector.
pub fn sub(left: Self, right: Self) Self {
return Self.new(
left.x - right.x,
left.y - right.y,
left.z - right.z,
left.w - right.w,
);
}
/// Addition betwen two given vector.
pub fn add(left: Self, right: Self) Self {
return Self.new(
left.x + right.x,
left.y + right.y,
left.z + right.z,
left.w + right.w,
);
}
/// Multiply each components by the given scalar.
pub fn scale(v: Self, scalar: T) Self {
return Self.new(
v.x * scalar,
v.y * scalar,
v.z * scalar,
v.w * scalar,
);
}
/// Return the dot product between two given vector.
pub fn dot(left: Self, right: Self) T {
return (left.x * right.x) + (left.y * right.y) + (left.z * right.z) + (left.w * right.w);
}
/// Lerp between two vectors.
pub fn lerp(left: Self, right: Self, t: T) Self {
const x = root.lerp(T, left.x, right.x, t);
const y = root.lerp(T, left.y, right.y, t);
const z = root.lerp(T, left.z, right.z, t);
const w = root.lerp(T, left.w, right.w, t);
return Self.new(x, y, z, w);
}
/// Construct a new vector from the min components between two vectors.
pub fn min(left: Self, right: Self) Self {
return Self.new(
math.min(left.x, right.x),
math.min(left.y, right.y),
math.min(left.z, right.z),
math.min(left.w, right.w),
);
}
/// Construct a new vector from the max components between two vectors.
pub fn max(left: Self, right: Self) Self {
return Self.new(
math.max(left.x, right.x),
math.max(left.y, right.y),
math.max(left.z, right.z),
math.max(left.w, right.w),
);
}
};
}
test "zalgebra.Vec4.init" {
var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 4.7);
try testing.expectEqual(_vec_0.x, 1.5);
try testing.expectEqual(_vec_0.y, 2.6);
try testing.expectEqual(_vec_0.z, 3.7);
try testing.expectEqual(_vec_0.w, 4.7);
}
test "zalgebra.Vec4.eql" {
var _vec_0 = Vec4.new(1, 2, 3, 4);
var _vec_1 = Vec4.new(1, 2, 3, 4);
var _vec_2 = Vec4.new(1, 2, 3, 5);
try testing.expectEqual(Vec4.eql(_vec_0, _vec_1), true);
try testing.expectEqual(Vec4.eql(_vec_0, _vec_2), false);
}
test "zalgebra.Vec4.set" {
var _vec_0 = Vec4.new(2.5, 2.5, 2.5, 2.5);
var _vec_1 = Vec4.set(2.5);
try testing.expectEqual(Vec4.eql(_vec_0, _vec_1), true);
}
test "zalgebra.Vec4.negate" {
var a = Vec4.set(5);
var b = Vec4.set(-5);
try testing.expectEqual(Vec4.eql(a.negate(), b), true);
}
test "zalgebra.Vec2.toArray" {
const _vec_0 = Vec4.new(0, 1, 0, 1).toArray();
const _vec_1 = [_]f32{ 0, 1, 0, 1 };
try testing.expectEqual(std.mem.eql(f32, &_vec_0, &_vec_1), true);
}
test "zalgebra.Vec4.length" {
var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 4.7);
try testing.expectEqual(_vec_0.length(), 6.69253301);
}
test "zalgebra.Vec4.distance" {
var a = Vec4.new(0, 0, 0, 0);
var b = Vec4.new(-1, 0, 0, 0);
var c = Vec4.new(0, 5, 0, 0);
try testing.expectEqual(Vec4.distance(a, b), 1);
try testing.expectEqual(Vec4.distance(a, c), 5);
}
test "zalgebra.Vec4.normalize" {
var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 4.0);
try testing.expectEqual(Vec4.eql(
_vec_0.norm(),
Vec4.new(0.241121411, 0.417943745, 0.594766139, 0.642990410),
), true);
}
test "zalgebra.Vec4.sub" {
var _vec_0 = Vec4.new(1, 2, 3, 6);
var _vec_1 = Vec4.new(2, 2, 3, 5);
try testing.expectEqual(Vec4.eql(
Vec4.sub(_vec_0, _vec_1),
Vec4.new(-1, 0, 0, 1),
), true);
}
test "zalgebra.Vec4.add" {
var _vec_0 = Vec4.new(1, 2, 3, 5);
var _vec_1 = Vec4.new(2, 2, 3, 6);
try testing.expectEqual(Vec4.eql(
Vec4.add(_vec_0, _vec_1),
Vec4.new(3, 4, 6, 11),
), true);
}
test "zalgebra.Vec4.scale" {
var _vec_0 = Vec4.new(1, 2, 3, 4);
try testing.expectEqual(Vec4.eql(
Vec4.scale(_vec_0, 5),
Vec4.new(5, 10, 15, 20),
), true);
}
test "zalgebra.Vec4.dot" {
var _vec_0 = Vec4.new(1.5, 2.6, 3.7, 5);
var _vec_1 = Vec4.new(2.5, 3.45, 1.0, 1);
try testing.expectEqual(Vec4.dot(_vec_0, _vec_1), 21.4200000);
}
test "zalgebra.Vec4.lerp" {
var _vec_0 = Vec4.new(-10.0, 0.0, -10.0, -10.0);
var _vec_1 = Vec4.new(10.0, 10.0, 10.0, 10.0);
try testing.expectEqual(Vec4.eql(
Vec4.lerp(_vec_0, _vec_1, 0.5),
Vec4.new(0.0, 5.0, 0.0, 0.0),
), true);
}
test "zalgebra.Vec4.min" {
var _vec_0 = Vec4.new(10.0, -2.0, 0.0, 1.0);
var _vec_1 = Vec4.new(-10.0, 5.0, 0.0, 1.01);
try testing.expectEqual(Vec4.eql(
Vec4.min(_vec_0, _vec_1),
Vec4.new(-10.0, -2.0, 0.0, 1.0),
), true);
}
test "zalgebra.Vec4.max" {
var _vec_0 = Vec4.new(10.0, -2.0, 0.0, 1.0);
var _vec_1 = Vec4.new(-10.0, 5.0, 0.0, 1.01);
try testing.expectEqual(Vec4.eql(
Vec4.max(_vec_0, _vec_1),
Vec4.new(10.0, 5.0, 0.0, 1.01),
), true);
}
test "zalgebra.Vec2.fromSlice" {
const array = [4]f32{ 2, 4, 3, 6 };
try testing.expectEqual(Vec4.eql(
Vec4.fromSlice(&array),
Vec4.new(2, 4, 3, 6),
), true);
}
test "zalgebra.Vec4.cast" {
const a = Vec4_i32.new(3, 6, 2, 0);
const b = Vector4(usize).new(3, 6, 2, 0);
try testing.expectEqual(
Vector4(usize).eql(a.cast(usize), b),
true,
);
const c = Vec4.new(3.5, 6.5, 2.0, 0);
const d = Vec4_f64.new(3.5, 6.5, 2, 0.0);
try testing.expectEqual(
Vec4_f64.eql(c.cast(f64), d),
true,
);
const e = Vec4_i32.new(3, 6, 2, 0);
const f = Vec4.new(3.0, 6.0, 2.0, 0.0);
try testing.expectEqual(
Vec4.eql(e.cast(f32), f),
true,
);
const g = Vec4.new(3.0, 6.0, 2.0, 0.0);
const h = Vec4_i32.new(3, 6, 2, 0);
try testing.expectEqual(
Vec4_i32.eql(g.cast(i32), h),
true,
);
}