Compare commits
1 commit
main
...
feat/test_
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ce413a1d7 |
248 changed files with 10109 additions and 14215 deletions
|
|
@ -1,17 +0,0 @@
|
|||
# How wide to allow formatted cmake files
|
||||
line_width: 80
|
||||
|
||||
# How many spaces to tab for indent
|
||||
tab_size: 4
|
||||
|
||||
dangle_parens: true
|
||||
|
||||
# Additional FLAGS and KWARGS for custom commands
|
||||
additional_commands:
|
||||
foo:
|
||||
flags: [BAR, BAZ]
|
||||
kwargs:
|
||||
HEADERS : '*'
|
||||
SOURCES : '*'
|
||||
DEPENDS : '*'
|
||||
|
||||
288
.drone.yml
288
.drone.yml
|
|
@ -1,44 +1,42 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: exec
|
||||
name: amd64 — msvc
|
||||
node:
|
||||
environment: lina
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
platform:
|
||||
os: windows
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: unit tests
|
||||
shell: powershell
|
||||
commands:
|
||||
- ./tools/ci/amd64/msvc/unit_tests.ps1
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: amd64 — gcc
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
||||
steps:
|
||||
- name: unit tests
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/amd64/gcc/unit_tests.sh
|
||||
|
||||
- name: valgrind
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/amd64/gcc/valgrind.sh
|
||||
|
||||
---
|
||||
# ---
|
||||
# kind: pipeline
|
||||
# type: exec
|
||||
# name: amd64 — msvc
|
||||
# trigger:
|
||||
# branch:
|
||||
# - main
|
||||
# platform:
|
||||
# os: windows
|
||||
# arch: amd64
|
||||
#
|
||||
# steps:
|
||||
# - name: unit tests
|
||||
# shell: powershell
|
||||
# commands:
|
||||
# - ./tools/ci/amd64/msvc/unit_tests.ps1
|
||||
#
|
||||
# ---
|
||||
# kind: pipeline
|
||||
# type: docker
|
||||
# name: amd64 — gcc
|
||||
# trigger:
|
||||
# branch:
|
||||
# - main
|
||||
#
|
||||
# steps:
|
||||
# - name: unit tests
|
||||
# image: ci:latest
|
||||
# pull: if-not-exists
|
||||
# commands:
|
||||
# - ./tools/ci/amd64/gcc/unit_tests.sh
|
||||
#
|
||||
# - name: valgrind
|
||||
# image: ci:latest
|
||||
# pull: if-not-exists
|
||||
# commands:
|
||||
# - ./tools/ci/amd64/gcc/valgrind.sh
|
||||
#
|
||||
# ---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: amd64 — clang
|
||||
|
|
@ -47,110 +45,114 @@ trigger:
|
|||
- main
|
||||
|
||||
steps:
|
||||
- name: code coverage
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
environment:
|
||||
CODECOV_TOKEN:
|
||||
from_secret: CODECOV_TOKEN
|
||||
commands:
|
||||
- ./tools/ci/amd64/clang/coverage.sh
|
||||
|
||||
- name: leak sanitizer
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/amd64/clang/lsan.sh
|
||||
|
||||
# - name: code coverage
|
||||
# image: ci:latest
|
||||
# pull: if-not-exists
|
||||
# environment:
|
||||
# CODECOV_TOKEN:
|
||||
# from_secret: CODECOV_TOKEN
|
||||
# commands:
|
||||
# - ./tools/ci/amd64/clang/coverage.sh
|
||||
#
|
||||
# - name: leak sanitizer
|
||||
# image: ci:latest
|
||||
# pull: if-not-exists
|
||||
# commands:
|
||||
# - ./tools/ci/amd64/clang/lsan.sh
|
||||
#
|
||||
- name: memory sanitizer
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/amd64/clang/msan.sh
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: static analysis
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
||||
steps:
|
||||
- name: clang tidy
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
privileged: true
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/clang_tidy.sh
|
||||
|
||||
- name: shell check
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/shell_check.sh
|
||||
|
||||
- name: clang format
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/clang_format.sh
|
||||
|
||||
- name: cmake format
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/cmake_format.sh
|
||||
|
||||
- name: shell format
|
||||
image: ci:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- ./tools/ci/static_analysis/shell_format.sh
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: documentation — development
|
||||
node:
|
||||
environment: ryali
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
||||
steps:
|
||||
- name: build and deploy
|
||||
image: documentation:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- cd docs
|
||||
- sphinx-build -M html . .
|
||||
|
||||
- rm -rf /light_docs_dev/*
|
||||
- mv ./html/* /light_docs_dev/
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: documentation — production
|
||||
node:
|
||||
environment: ryali
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
steps:
|
||||
- name: build and deploy
|
||||
image: documentation:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
- cd docs
|
||||
- mkdir generated
|
||||
- touch generated/changelogs.rst
|
||||
- touch generated/api.rst
|
||||
- sphinx-build -M html . .
|
||||
|
||||
- rm -rf /light_docs/*
|
||||
- mv ./html/* /light_docs/
|
||||
|
||||
#
|
||||
# ---
|
||||
# kind: pipeline
|
||||
# type: docker
|
||||
# name: static analysis
|
||||
# trigger:
|
||||
# branch:
|
||||
# - main
|
||||
#
|
||||
# steps:
|
||||
# - name: clang tidy
|
||||
# image: ci:latest
|
||||
# pull: if-not-exists
|
||||
# privileged: true
|
||||
# commands:
|
||||
# - ./tools/ci/static_analysis/clang_tidy.sh
|
||||
#
|
||||
# - name: shell check
|
||||
# image: ci:latest
|
||||
# pull: if-not-exists
|
||||
# commands:
|
||||
# - ./tools/ci/static_analysis/shell_check.sh
|
||||
#
|
||||
# - name: clang format
|
||||
# image: ci:latest
|
||||
# pull: if-not-exists
|
||||
# commands:
|
||||
# - ./tools/ci/static_analysis/clang_format.sh
|
||||
#
|
||||
# - name: cmake format
|
||||
# image: ci:latest
|
||||
# pull: if-not-exists
|
||||
# commands:
|
||||
# - ./tools/ci/static_analysis/cmake_format.sh
|
||||
#
|
||||
# - name: shell format
|
||||
# image: ci:latest
|
||||
# pull: if-not-exists
|
||||
# commands:
|
||||
# - ./tools/ci/static_analysis/shell_format.sh
|
||||
#
|
||||
# ---
|
||||
# kind: pipeline
|
||||
# type: docker
|
||||
# name: documentation — development
|
||||
# node:
|
||||
# environment: ryali
|
||||
# trigger:
|
||||
# branch:
|
||||
# - main
|
||||
#
|
||||
# steps:
|
||||
# - name: build and deploy
|
||||
# image: documentation:latest
|
||||
# pull: if-not-exists
|
||||
# commands:
|
||||
# - pwd
|
||||
# - cd docs
|
||||
# - mkdir generated
|
||||
# - touch generated/changelogs.rst
|
||||
# - touch generated/api.rst
|
||||
# - sphinx-build -M html . .
|
||||
#
|
||||
# - rm -rf /light_docs_dev/*
|
||||
# - mv ./html/* /light_docs_dev/
|
||||
#
|
||||
# ---
|
||||
#
|
||||
# kind: pipeline
|
||||
# type: docker
|
||||
# name: documentation — production
|
||||
# node:
|
||||
# environment: ryali
|
||||
# trigger:
|
||||
# event:
|
||||
# - tag
|
||||
#
|
||||
# steps:
|
||||
# - name: build and deploy
|
||||
# image: documentation:latest
|
||||
# pull: if-not-exists
|
||||
# commands:
|
||||
# - cd docs
|
||||
# - mkdir generated
|
||||
# - touch generated/changelogs.rst
|
||||
# - touch generated/api.rst
|
||||
# - sphinx-build -M html . .
|
||||
#
|
||||
# - rm -rf /light_docs/*
|
||||
# - mv ./html/* /light_docs/
|
||||
#
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_EPXORT_COMPILE_COMMANDS TRUE)
|
||||
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
set(CMAKE_CXX_MODULE_STD 1)
|
||||
|
||||
cmake_minimum_required(VERSION 4.1)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(Light)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake/functions.cmake)
|
||||
|
|
|
|||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
Discord: Light7734#4652.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
14
CONTRIBUTING.md
Normal file
14
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# How to contribute to Light Engine
|
||||
|
||||
Thanks for putting in the time to contribute to this project <3
|
||||
|
||||
## Coding conventions
|
||||
|
||||
For the time being, don't worry too much about the conventions...
|
||||
|
||||
Try to read other parts of the code and you'll get the hang of it
|
||||
|
||||
I have to learn clang-format, then everyone contributing can use it to format their code
|
||||
|
||||
|
||||
###### happy coding-
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
#version 450 core
|
||||
|
||||
layout(push_constant) uniform pc
|
||||
{
|
||||
mat4 view_projection;
|
||||
};
|
||||
|
||||
|
||||
struct VertexData
|
||||
{
|
||||
vec3 position;
|
||||
vec3 color;
|
||||
};
|
||||
|
||||
// readonly SSBO containing the vertex data
|
||||
layout(set = 0, binding = 0, std430) readonly buffer vertex_data {
|
||||
VertexData data[];
|
||||
};
|
||||
|
||||
vec3 position(int idx)
|
||||
{
|
||||
return data[idx].position;
|
||||
}
|
||||
|
||||
vec3 color(int idx)
|
||||
{
|
||||
return data[idx].color;
|
||||
}
|
||||
|
||||
layout(location = 0) out vec3 out_frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = view_projection * vec4(position(gl_VertexIndex), 1.0);
|
||||
out_frag_color = color(gl_VertexIndex);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,26 +1,21 @@
|
|||
#version 450 core
|
||||
|
||||
layout(push_constant) uniform pc {
|
||||
mat4 view_projection;
|
||||
};
|
||||
vec2 positions[3] = vec2[](
|
||||
vec2(0.0, -0.5),
|
||||
vec2(0.5, 0.5),
|
||||
vec2(-0.5, 0.5)
|
||||
);
|
||||
|
||||
struct VertexData
|
||||
{
|
||||
vec3 position;
|
||||
vec3 color;
|
||||
};
|
||||
|
||||
layout(std140, set = 0, binding = 0) readonly buffer Vertices {
|
||||
|
||||
VertexData vertices[];
|
||||
} ssbo_vertices;
|
||||
vec3 colors[3] = vec3[](
|
||||
vec3(1.0, 0.0, 0.0),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
vec3(0.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
layout(location = 0) out vec3 out_frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
VertexData vertex = ssbo_vertices.vertices[gl_VertexIndex];
|
||||
|
||||
gl_Position = view_projection * vec4(vertex.position, 1.0);
|
||||
out_frag_color = vertex.color;
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
out_frag_color = colors[gl_VertexIndex];
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
3
docs/.gitignore
vendored
3
docs/.gitignore
vendored
|
|
@ -1,4 +1,3 @@
|
|||
_build/
|
||||
generated/
|
||||
html/
|
||||
xml/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
TARGET = ./
|
||||
INPUT = "../modules"
|
||||
RECURSIVE = YES
|
||||
|
||||
PROJECT_NAME = "Light"
|
||||
JAVADOC_AUTOBRIEF = YES
|
||||
JAVADOC_BANNER = YES
|
||||
GENERATE_XML = YES
|
||||
|
||||
EXTRACT_PRIVATE = NO
|
||||
EXTRACT_STATIC = NO
|
||||
EXTRACT_LOCAL_CLASSES = NO
|
||||
HIDE_UNDOC_RELATIONS = YES
|
||||
HAVE_DOT = NO
|
||||
|
||||
GENERATE_TODOLIST = NO
|
||||
GENERATE_HTML = NO
|
||||
GENERATE_DOCSET = NO
|
||||
GENERATE_HTMLHELP = NO
|
||||
GENERATE_CHI = NO
|
||||
GENERATE_QHP = NO
|
||||
GENERATE_ECLIPSEHELP = NO
|
||||
GENERATE_TREEVIEW = NO
|
||||
GENERATE_LATEX = NO
|
||||
GENERATE_RTF = NO
|
||||
GENERATE_MAN = NO
|
||||
GENERATE_DOCBOOK = NO
|
||||
GENERATE_AUTOGEN_DEF = NO
|
||||
GENERATE_SQLITE3 = NO
|
||||
GENERATE_PERLMOD = NO
|
||||
GENERATE_TAGFILE = NO
|
||||
GENERATE_LEGEND = NO
|
||||
GENERATE_TESTLIST = NO
|
||||
GENERATE_BUGLIST = NO
|
||||
GENERATE_DEPRECATEDLIST= NO
|
||||
|
||||
FILE_PATTERNS = *.c \
|
||||
*.cc \
|
||||
*.cxx \
|
||||
*.cxxm \
|
||||
*.cpp \
|
||||
*.cppm \
|
||||
*.ccm \
|
||||
*.c++ \
|
||||
*.c++m \
|
||||
*.java \
|
||||
*.ii \
|
||||
*.ixx \
|
||||
*.ipp \
|
||||
*.i++ \
|
||||
*.inl \
|
||||
*.idl \
|
||||
*.ddl \
|
||||
*.odl \
|
||||
*.h \
|
||||
*.hh \
|
||||
*.hxx \
|
||||
*.hpp \
|
||||
*.h++ \
|
||||
*.l \
|
||||
*.cs \
|
||||
*.d \
|
||||
*.php \
|
||||
*.php4 \
|
||||
*.php5 \
|
||||
*.phtml \
|
||||
*.inc \
|
||||
*.m \
|
||||
*.markdown \
|
||||
*.md \
|
||||
*.mm \
|
||||
*.dox \
|
||||
*.py \
|
||||
*.pyw \
|
||||
*.f90 \
|
||||
*.f95 \
|
||||
*.f03 \
|
||||
*.f08 \
|
||||
*.f18 \
|
||||
*.f \
|
||||
*.for \
|
||||
*.vhd \
|
||||
*.vhdl \
|
||||
*.ucf \
|
||||
*.qsf \
|
||||
*.ice
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
Application
|
||||
===================================================================================================
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:caption: App
|
||||
|
||||
Functions
|
||||
---------------------------------------------------------------------------------------------------
|
||||
.. doxygenfunction:: main
|
||||
|
||||
Classes
|
||||
---------------------------------------------------------------------------------------------------
|
||||
.. doxygenclass:: lt::app::ISystem
|
||||
|
||||
.. doxygenstruct:: lt::app::TickInfo
|
||||
|
||||
.. doxygenstruct:: lt::app::TickResult
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
Renderer
|
||||
===================================================================================================
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:caption: App
|
||||
|
||||
Classes
|
||||
---------------------------------------------------------------------------------------------------
|
||||
.. doxygenenum:: lt::renderer::Api
|
||||
|
||||
.. doxygenclass:: lt::renderer::System
|
||||
|
||||
.. doxygenstruct:: lt::renderer::components::Sprite
|
||||
14
docs/conf.py
14
docs/conf.py
|
|
@ -13,21 +13,13 @@ author = 'light7734'
|
|||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = ['breathe']
|
||||
|
||||
breathe_projects = {"Light": "./xml"}
|
||||
breathe_default_project = "Light"
|
||||
breathe_default_members = ()
|
||||
|
||||
# Tell sphinx what the primary language being documented is.
|
||||
primary_domain = 'cpp'
|
||||
|
||||
# Tell sphinx what the pygments highlight language should be.
|
||||
highlight_language = 'cpp'
|
||||
extensions = []
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
|
|
|
|||
68
docs/generate_changelog.py
Normal file
68
docs/generate_changelog.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
from git import Repo
|
||||
import re
|
||||
|
||||
repo = Repo(search_parent_directories=True)
|
||||
assert not repo.bare
|
||||
|
||||
file_path = "generated/changelog.rst"
|
||||
|
||||
messages = []
|
||||
short_shas = []
|
||||
hex_shas = []
|
||||
logs = []
|
||||
|
||||
remote_url = "https://git.light7734.com/light7734/light/commit"
|
||||
def format_log(commit_type, message, major, minor, patch, short_sha, hex_sha):
|
||||
href = f"{remote_url}/{hex_sha}"
|
||||
version = f"{major}.{minor}.{patch}-kitten+{short_sha}";
|
||||
link = f"`{version} <{remote_url}/{hex_sha}>`__"
|
||||
return f"| **{message}** ({link})"
|
||||
|
||||
for commit in repo.iter_commits():
|
||||
messages.append(commit.summary)
|
||||
short_shas.append(repo.git.rev_parse(commit.hexsha, short=5))
|
||||
hex_shas.append(commit.hexsha)
|
||||
|
||||
ver_major = 0
|
||||
ver_minor = 0
|
||||
ver_patch = 0
|
||||
|
||||
idx = len(messages)
|
||||
for message in reversed(messages):
|
||||
idx = idx - 1;
|
||||
|
||||
commit_type = re.match("^(feat|fix|refactor|perf|build|asset|test|chore|ci|docs)", message)
|
||||
if not commit_type:
|
||||
continue
|
||||
|
||||
match commit_type.group(0):
|
||||
case "feat":
|
||||
ver_minor = ver_minor + 1
|
||||
ver_patch = 0
|
||||
|
||||
case "fix":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
case "refactor":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
case "perf":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
case "build":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
case "asset":
|
||||
ver_patch = ver_patch + 1
|
||||
|
||||
logs.append(format_log(commit_type, message, ver_major, ver_minor, ver_patch, short_shas[idx], hex_shas[idx]))
|
||||
|
||||
with open(file_path, "w") as f:
|
||||
f.write(".. changelogs\n\n\n")
|
||||
f.write("Changelogs\n")
|
||||
f.write("==================================================\n\n")
|
||||
|
||||
f.write("KITTEN\n")
|
||||
f.write("--------------------------------------------------\n\n")
|
||||
for log in reversed(logs):
|
||||
f.write(log + '\n')
|
||||
|
|
@ -1,46 +1,49 @@
|
|||
.. guidelines/development
|
||||
|
||||
Development Strategy
|
||||
Development
|
||||
===================================================================================================
|
||||
Defines:
|
||||
As a solo-project, I am not only the **developer**, but also the **manager**.
|
||||
Therefore there is a need, if this project is to succeed, to have a development plan.
|
||||
|
||||
- A **unit of work**.
|
||||
- A **pipeline** for making changes ---should **minimize ambiguity**.
|
||||
- A way for **distributing work** and **tracking productivity**.
|
||||
Such a plan should:
|
||||
|
||||
An **unpragmatic strategy** is utterly useless. It should pull our minds out from the **engineering dreamland**, and make us focus on the **product delivery**.
|
||||
- Define a way to **distribute work** (across time, since there's only 1 developer).
|
||||
- Define what is a **unit of work** (cycles).
|
||||
- Provide a way to **track productivity**, which helps projecting the future and **detecting patterns** early on.
|
||||
- Provide a **pipeline** for the work to go through and **minimize ambiguity**.
|
||||
|
||||
.. note::
|
||||
**Forgejo issues** is used as the project-management tool.
|
||||
No need for fancy boards or 3rd party apps. Simple tags, titles, milestones, etc... should suffice.
|
||||
These are the **management** aspects of the project, which help the development goals to be more **pragmatic**
|
||||
---by pulling my mind out of its **engineering dreamland**, and make it focus on the **broader picture**.
|
||||
|
||||
Cycle
|
||||
---------------------------------------------------------------------------------------------------
|
||||
A cycle is one **step** in development, 1 cycle == 1 issue, and it consists of 4 stages:
|
||||
A cycle is one **step** in development, one cycle = one ticket, and it consists of 4 stages:
|
||||
|
||||
1 - Make it known
|
||||
- Write the commit title.
|
||||
- Write the commit message.
|
||||
- This limits the **scope of changes** and gives you a very specific **goal** to work towards.
|
||||
- If something outside of this scope really bothers you, fix and stash for a future cycle.
|
||||
- Make a ticket if stash-fix is implausible ---**DO NOT** write **todo** comments.
|
||||
- The message should follow the project's **commit message specifications**.
|
||||
|
||||
- Make an issue.
|
||||
- Git is a **version-control** tool, not a **project-management** tool.
|
||||
- Preferably, provide a very brief description ---This may be used in the commit message's body.
|
||||
- Make a ticket.
|
||||
- Version control (git) is a **development-tool**, not a **management-tool**.
|
||||
- Provide a very brief description ---This may be used in the commit message's body.
|
||||
|
||||
2 - Make it work
|
||||
- Write high-level tests that confirms the cycle's requirements are met.
|
||||
- That is, specify requirements in a programming language instead of English.
|
||||
- You're done when all the tests pass.
|
||||
- Preferably write the tests first, but it's okay to start with the interface.
|
||||
- Tests **may** not be necessary depending on the requirements and commit type.
|
||||
- Tests may not be necessary depending on the requirements and commit type.
|
||||
|
||||
- **Make it work** doesn't mean liberally producing substandard code, you should:
|
||||
- "Make it work" doesn't mean liberally producing shit code, you should:
|
||||
- Follow project's **conventions**.
|
||||
- Follow **best practices** and **proven swe principles**.
|
||||
- Enable **warnings as errors**.
|
||||
- Enable **static analysis**.
|
||||
- Don't break existing tests.
|
||||
- Have the overall picture in mind.
|
||||
- Don't break any pre-existing-tests.
|
||||
- Have the over-all picture in mind.
|
||||
|
||||
3 - Make it right
|
||||
- Test driven refactoring
|
||||
|
|
@ -52,14 +55,12 @@ A cycle is one **step** in development, 1 cycle == 1 issue, and it consists of 4
|
|||
- Get a performance and/or memory profile and try to alleviate the bottlenecks.
|
||||
- Avoid premature optimizations, be certain what you change has performance benefits.
|
||||
|
||||
|
||||
Sprint
|
||||
---------------------------------------------------------------------------------------------------
|
||||
A sprint is the collection of all the finished cycles in **one week**.
|
||||
They start from **Monday mornings**, and end on **Sunday nights**,
|
||||
where we'll do a **12hr coding marathon** (streamed on Discord) to wrap everything up.
|
||||
A sprint is the collection of all the finished cycles in one week.
|
||||
It's meant to provide insight on development speed and help projecting the future.
|
||||
|
||||
Sprints begin by **defining** what cycles/issues are expected to be done.
|
||||
And end by **reflecting** on the results, which may **affect** our future approach.
|
||||
|
||||
Commit Message Specification
|
||||
---------------------------------------------------------------------------------------------------
|
||||
|
|
@ -115,10 +116,6 @@ With the following commit types:
|
|||
- For changes to the documentations.
|
||||
- Does not affect the version.
|
||||
|
||||
.. note::
|
||||
|
||||
Since we're in beta, any commit might change the api, no need for ! (breaking) tags.
|
||||
|
||||
Semantic Versioning
|
||||
---------------------------------------------------------------------------------------------------
|
||||
Coupled with conventional commit style messages, we can automajically version the project following
|
||||
|
|
@ -142,3 +139,9 @@ Using the following format:
|
|||
|
||||
The shortened hexsha of a commit is obtained by:
|
||||
``git rev-parse --short=5 <commit_hexsha>``
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@
|
|||
guidelines/conventions.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:caption: API
|
||||
:maxdepth: 2
|
||||
:caption: Generated Docs
|
||||
|
||||
api/app.rst
|
||||
api/renderer.rst
|
||||
generated/api.rst
|
||||
generated/changelog.rst
|
||||
|
||||
|
||||
|
|
|
|||
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
|
|
@ -1,262 +1,26 @@
|
|||
add_module(NAME preliminary INTERFACES module.cppm fundumental_types.cppm assertions.cppm build_constants.cppm)
|
||||
add_module(NAME logger INTERFACES logger.cppm TESTS logger.test.cpp DEPENDENCIES preliminary)
|
||||
add_module(NAME tracer INTERFACES tracer.cppm DEPENDENCIES preliminary logger)
|
||||
# engine
|
||||
add_subdirectory(./std)
|
||||
add_subdirectory(./bitwise)
|
||||
add_subdirectory(./env)
|
||||
add_subdirectory(./memory)
|
||||
add_subdirectory(./time)
|
||||
add_subdirectory(./logger)
|
||||
add_subdirectory(./debug)
|
||||
add_subdirectory(./math)
|
||||
#
|
||||
add_subdirectory(./asset_baker)
|
||||
add_subdirectory(./assets)
|
||||
#
|
||||
add_subdirectory(./camera)
|
||||
add_subdirectory(./input)
|
||||
# add_subdirectory(./ui)
|
||||
#
|
||||
add_subdirectory(./surface)
|
||||
add_subdirectory(./renderer)
|
||||
add_subdirectory(./ecs)
|
||||
#
|
||||
add_subdirectory(./app)
|
||||
|
||||
add_module(NAME bitwise INTERFACES operations.cppm DEPENDENCIES preliminary)
|
||||
add_module(NAME memory INTERFACES null_on_move.cppm reference.cppm scope.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
logger
|
||||
)
|
||||
add_module(NAME time INTERFACES timer.cppm TESTS timer.test.cpp DEPENDENCIES preliminary)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
test
|
||||
INTERFACES
|
||||
module.cppm
|
||||
test.cppm
|
||||
expects.cppm
|
||||
registry.cppm
|
||||
SOURCES
|
||||
entrypoint.cpp
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
logger
|
||||
TESTS
|
||||
test.test.cpp
|
||||
)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
math
|
||||
INTERFACES
|
||||
algebra.cppm
|
||||
trig.cppm
|
||||
vec2.cppm
|
||||
vec3.cppm
|
||||
vec4.cppm
|
||||
mat4.cppm
|
||||
components.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
TESTS
|
||||
trig.test.cpp
|
||||
vec2.test.cpp
|
||||
vec3.test.cpp
|
||||
vec4.test.cpp
|
||||
mat4.test.cpp
|
||||
)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
assets
|
||||
INTERFACES
|
||||
shader.cppm
|
||||
metadata.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
logger
|
||||
TESTS
|
||||
shader.test.cpp
|
||||
)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
asset_baker
|
||||
ROOT_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/asset_baker
|
||||
INTERFACES
|
||||
bakers.cppm
|
||||
ENTRYPOINT
|
||||
entrypoint.cpp
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
assets
|
||||
logger
|
||||
)
|
||||
|
||||
# add_executable(asset_baker entrypoint.cpp) target_link_libraries(asset_baker
|
||||
# PRIVATE libasset_baker)
|
||||
|
||||
add_module(NAME camera INTERFACES components.cppm DEPENDENCIES preliminary math)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
app
|
||||
INTERFACES
|
||||
application.cppm
|
||||
system.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
memory
|
||||
PRIVATE_DEPENDENCIES
|
||||
)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
ecs
|
||||
INTERFACES
|
||||
sparse_set.cppm
|
||||
registry.cppm
|
||||
entity.cppm
|
||||
DEPENDENCIES
|
||||
logger
|
||||
memory
|
||||
TESTS
|
||||
registry.test.cpp
|
||||
sparse_set.test.cpp
|
||||
)
|
||||
|
||||
add_module(NAME input_codes INTERFACES input_codes.cppm DEPENDENCIES preliminary)
|
||||
|
||||
if(WIN32)
|
||||
add_module(
|
||||
NAME
|
||||
surface
|
||||
INTERFACES
|
||||
constants.cppm
|
||||
system.cppm
|
||||
requests.cppm
|
||||
events.cppm
|
||||
components.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
ecs
|
||||
app
|
||||
math
|
||||
memory
|
||||
input_codes
|
||||
PRIVATE_DEPENDENCIES
|
||||
logger
|
||||
time
|
||||
TESTS
|
||||
system.test.cpp
|
||||
)
|
||||
|
||||
elseif(UNIX)
|
||||
add_module(
|
||||
NAME
|
||||
surface
|
||||
INTERFACES
|
||||
constants.cppm
|
||||
system.cppm
|
||||
requests.cppm
|
||||
events.cppm
|
||||
components.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
ecs
|
||||
app
|
||||
math
|
||||
memory
|
||||
input_codes
|
||||
wayland-client
|
||||
PRIVATE_DEPENDENCIES
|
||||
X11
|
||||
logger
|
||||
time
|
||||
TESTS
|
||||
system.test.cpp
|
||||
)
|
||||
|
||||
function(add_wayland_protocol_target TARGET_NAME SPEC NAME)
|
||||
add_custom_target(wayland_${TARGET_NAME}_header COMMAND wayland-scanner client-header /usr/share/wayland-protocols${SPEC} ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.h)
|
||||
add_dependencies(surface wayland_${TARGET_NAME}_header)
|
||||
add_custom_target(wayland_${TARGET_NAME}_source COMMAND wayland-scanner private-code /usr/share/wayland-protocols${SPEC} ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.c)
|
||||
add_dependencies(surface wayland_${TARGET_NAME}_source)
|
||||
|
||||
target_sources(surface PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.c)
|
||||
endfunction()
|
||||
|
||||
target_include_directories(surface PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/)
|
||||
add_wayland_protocol_target(xdg_shell "/stable/xdg-shell/xdg-shell.xml" xdg-shell)
|
||||
|
||||
else()
|
||||
message(FATAL "Failed to generate cmake: unsupported platform")
|
||||
|
||||
endif()
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
input
|
||||
INTERFACES
|
||||
system.cppm
|
||||
components.cppm
|
||||
events.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
input_codes
|
||||
surface
|
||||
math
|
||||
logger
|
||||
TESTS
|
||||
system.test.cpp
|
||||
)
|
||||
|
||||
find_package(Vulkan REQUIRED)
|
||||
message("Vulkan Libraries are: ${Vulkan_LIBRARIES}")
|
||||
add_module(
|
||||
NAME
|
||||
renderer
|
||||
INTERFACES
|
||||
data.cppm
|
||||
system.cppm
|
||||
frontends.cppm
|
||||
components.cppm
|
||||
factory.cppm
|
||||
vk/api_wrapper.cppm
|
||||
vk/device.cppm
|
||||
vk/gpu.cppm
|
||||
vk/instance.cppm
|
||||
vk/surface.cppm
|
||||
vk/swapchain.cppm
|
||||
vk/buffer.cppm
|
||||
vk/pass.cppm
|
||||
vk/renderer.cppm
|
||||
vk/debugger.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
app
|
||||
ecs
|
||||
memory
|
||||
assets
|
||||
time
|
||||
bitwise
|
||||
camera
|
||||
${Vulkan_LIBRARIES}
|
||||
Vulkan::Vulkan
|
||||
PRIVATE_DEPENDENCIES
|
||||
surface
|
||||
TESTS
|
||||
_tests/buffer.cpp
|
||||
_tests/debugger.cpp
|
||||
_tests/device.cpp
|
||||
_tests/pass.cpp
|
||||
_tests/renderer.cpp
|
||||
_tests/surface.cpp
|
||||
_tests/system.cpp
|
||||
TEST_INTERFACES
|
||||
_tests/utils.cppm
|
||||
)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
mirror
|
||||
ROOT_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mirror
|
||||
INTERFACES
|
||||
system.cppm
|
||||
DEPENDENCIES
|
||||
memory
|
||||
app
|
||||
time
|
||||
input
|
||||
surface
|
||||
renderer
|
||||
camera
|
||||
)
|
||||
|
||||
if(ENABLE_SANDBOX)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sandbox/)
|
||||
endif()
|
||||
# apps
|
||||
add_subdirectory(./mirror)
|
||||
add_subdirectory(test)
|
||||
|
|
|
|||
5
modules/app/CMakeLists.txt
Normal file
5
modules/app/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
add_library_module(app application.cpp)
|
||||
target_link_libraries(
|
||||
app
|
||||
PUBLIC memory
|
||||
PRIVATE lt_debug)
|
||||
|
|
@ -1,49 +1,7 @@
|
|||
export module app;
|
||||
#include <app/application.hpp>
|
||||
#include <app/system.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
|
||||
import preliminary;
|
||||
import app.system;
|
||||
import memory.reference;
|
||||
import memory.scope;
|
||||
|
||||
export namespace lt::app {
|
||||
|
||||
/** The main application class.
|
||||
* Think of this like an aggregate of systems, you register systems through this interface.
|
||||
* Then they'll tick every "application frame".
|
||||
*/
|
||||
class Application
|
||||
{
|
||||
public:
|
||||
Application(const Application &) = delete;
|
||||
|
||||
Application(Application &&) = delete;
|
||||
|
||||
auto operator=(const Application &) -> Application & = delete;
|
||||
|
||||
auto operator=(Application &&) -> Application & = delete;
|
||||
|
||||
virtual ~Application() = default;
|
||||
|
||||
void game_loop();
|
||||
|
||||
void register_system(memory::Ref<app::ISystem> system);
|
||||
|
||||
void unregister_system(memory::Ref<app::ISystem> system);
|
||||
|
||||
protected:
|
||||
Application() = default;
|
||||
|
||||
private:
|
||||
std::vector<memory::Ref<app::ISystem>> m_systems;
|
||||
|
||||
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_unregistered;
|
||||
|
||||
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_registered;
|
||||
};
|
||||
|
||||
} // namespace lt::app
|
||||
|
||||
module :private;
|
||||
namespace lt::app {
|
||||
|
||||
void Application::game_loop()
|
||||
47
modules/app/public/application.hpp
Normal file
47
modules/app/public/application.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/reference.hpp>
|
||||
#include <memory/scope.hpp>
|
||||
|
||||
namespace lt::app {
|
||||
|
||||
class ISystem;
|
||||
|
||||
extern memory::Scope<class Application> create_application();
|
||||
|
||||
/** The main application class.
|
||||
* Think of this like an aggregate of systems, you register systems through this interface.
|
||||
* Then they'll tick every "application frame".
|
||||
*/
|
||||
class Application
|
||||
{
|
||||
public:
|
||||
Application(const Application &) = delete;
|
||||
|
||||
Application(Application &&) = delete;
|
||||
|
||||
auto operator=(const Application &) -> Application & = delete;
|
||||
|
||||
auto operator=(Application &&) -> Application & = delete;
|
||||
|
||||
virtual ~Application() = default;
|
||||
|
||||
void game_loop();
|
||||
|
||||
void register_system(memory::Ref<app::ISystem> system);
|
||||
|
||||
void unregister_system(memory::Ref<app::ISystem> system);
|
||||
|
||||
protected:
|
||||
Application() = default;
|
||||
|
||||
private:
|
||||
std::vector<memory::Ref<app::ISystem>> m_systems;
|
||||
|
||||
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_unregistered;
|
||||
|
||||
std::vector<memory::Ref<app::ISystem>> m_systems_to_be_registered;
|
||||
};
|
||||
|
||||
|
||||
} // namespace lt::app
|
||||
27
modules/app/public/entrypoint.hpp
Normal file
27
modules/app/public/entrypoint.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <app/application.hpp>
|
||||
#include <memory/scope.hpp>
|
||||
|
||||
auto main(int argc, char *argv[]) -> int32_t
|
||||
try
|
||||
{
|
||||
std::ignore = argc;
|
||||
std::ignore = argv;
|
||||
|
||||
auto application = lt::memory::Scope<lt::app::Application> {};
|
||||
application = lt::app::create_application();
|
||||
if (!application)
|
||||
{
|
||||
throw std::runtime_error { "Failed to create application\n" };
|
||||
}
|
||||
|
||||
application->game_loop();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_crt("Terminating due to uncaught exception:");
|
||||
log_crt("\texception.what(): {}", exp.what());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
export module app.system;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
import logger;
|
||||
#include <chrono>
|
||||
|
||||
export namespace lt::app {
|
||||
namespace lt::app {
|
||||
|
||||
/** Information required to tick a system.
|
||||
* @note May be used across an entire application-frame (consisting of multiple systems ticking)
|
||||
|
|
@ -12,7 +11,7 @@ struct TickInfo
|
|||
{
|
||||
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
using Duration_T = std::chrono::duration<f64>;
|
||||
using Duration_T = std::chrono::duration<double>;
|
||||
|
||||
/** Duration since previous tick's end_time to current tick's start_time. */
|
||||
Duration_T delta_time {};
|
||||
|
|
@ -35,7 +34,7 @@ struct TickResult
|
|||
{
|
||||
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
using Duration_T = std::chrono::duration<f64>;
|
||||
using Duration_T = std::chrono::duration<double>;
|
||||
|
||||
/** The info supplied to the system for ticking. */
|
||||
TickInfo info;
|
||||
|
|
@ -47,9 +46,10 @@ struct TickResult
|
|||
Timepoint_T end_time;
|
||||
};
|
||||
|
||||
|
||||
struct SystemDiagnosis
|
||||
{
|
||||
enum class Severity : u8
|
||||
enum class Severity : uint8_t
|
||||
{
|
||||
verbose,
|
||||
info,
|
||||
|
|
@ -70,9 +70,9 @@ class SystemStats
|
|||
public:
|
||||
void push_diagnosis(SystemDiagnosis &&diagnosis)
|
||||
{
|
||||
auto &diag = m_diagnosis.emplace_back(std::move(diagnosis));
|
||||
auto diag = m_diagnosis.emplace_back(std::move(diagnosis));
|
||||
|
||||
log::info("message: {}", std::string { diag.message });
|
||||
log_dbg("message: {}", diag.message);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto empty_diagnosis() const -> bool
|
||||
6
modules/asset_baker/CMakeLists.txt
Normal file
6
modules/asset_baker/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
add_library_module(libasset_baker bakers.cpp)
|
||||
target_link_libraries(libasset_baker PUBLIC assets logger lt_debug tbb)
|
||||
add_test_module(libasset_baker bakers.test.cpp)
|
||||
|
||||
add_executable_module(asset_baker entrypoint/baker.cpp)
|
||||
target_link_libraries(asset_baker PRIVATE libasset_baker)
|
||||
2
modules/asset_baker/private/bakers.test.cpp
Normal file
2
modules/asset_baker/private/bakers.test.cpp
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
#include <asset_baker/bakers.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
import preliminary;
|
||||
import assets.shader;
|
||||
import logger;
|
||||
import bakers;
|
||||
#include <asset_baker/bakers.hpp>
|
||||
#include <assets/shader.hpp>
|
||||
|
||||
auto main(i32 argc, char *argv[]) -> i32
|
||||
auto main(int argc, char *argv[]) -> int32_t
|
||||
try
|
||||
{
|
||||
if (argc != 2)
|
||||
|
|
@ -20,8 +18,7 @@ try
|
|||
}
|
||||
|
||||
const auto &in_path = directory_iterator.path();
|
||||
const std::string in_path_str = in_path.generic_string();
|
||||
const auto out_path = std::format("{}.asset", in_path_str);
|
||||
const auto out_path = std::format("{}.asset", in_path.c_str());
|
||||
|
||||
if (in_path.extension() == ".vert")
|
||||
{
|
||||
|
|
@ -33,12 +30,12 @@ try
|
|||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
lt::log::critical("Terminating due to uncaught exception:");
|
||||
lt::log::critical("\texception.what: {}:", exp.what());
|
||||
log_crt("Terminating due to uncaught exception:");
|
||||
log_crt("\texception.what: {}:", exp.what());
|
||||
|
||||
return 1;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
export module bakers;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
import assets.metadata;
|
||||
import assets.shader;
|
||||
import logger;
|
||||
#include <assets/shader.hpp>
|
||||
|
||||
export void bake_shader(
|
||||
inline void bake_shader(
|
||||
const std::filesystem::path &in_path,
|
||||
const std::filesystem::path &out_path,
|
||||
lt::assets::ShaderAsset::Type type
|
||||
|
|
@ -14,18 +11,18 @@ export void bake_shader(
|
|||
using lt::assets::ShaderAsset;
|
||||
using enum lt::assets::ShaderAsset::Type;
|
||||
|
||||
auto glsl_path = std::string { in_path.string() };
|
||||
auto glsl_path = in_path.string();
|
||||
auto spv_path = std::format("{}.spv", glsl_path);
|
||||
lt::log::trace(
|
||||
log_trc(
|
||||
"Compiling {} shader {} -> {}",
|
||||
type == vertex ? "vertex" : "fragment",
|
||||
std::string { glsl_path },
|
||||
std::string { spv_path }
|
||||
glsl_path,
|
||||
spv_path
|
||||
);
|
||||
|
||||
// Don't bother linking to shaderc, just invoke the command with a system call.
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
std::system(
|
||||
system(
|
||||
std::format(
|
||||
"glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}",
|
||||
type == vertex ? "vert" : "frag",
|
||||
|
|
@ -36,7 +33,7 @@ export void bake_shader(
|
|||
);
|
||||
|
||||
auto stream = std::ifstream(spv_path, std::ios::binary);
|
||||
ensure(
|
||||
lt::ensure(
|
||||
stream.is_open(),
|
||||
"Failed to open compiled {} shader at: {}",
|
||||
type == vertex ? "vert" : "frag",
|
||||
|
|
@ -46,9 +43,10 @@ export void bake_shader(
|
|||
stream.seekg(0, std::ios::end);
|
||||
const auto size = stream.tellg();
|
||||
|
||||
auto bytes = std::vector<byte>(size);
|
||||
auto bytes = std::vector<std::byte>(size);
|
||||
stream.seekg(0, std::ios::beg);
|
||||
stream.read((char *)bytes.data(), size); // NOLINT
|
||||
log_dbg("BYTES: {}", bytes.size());
|
||||
stream.close();
|
||||
std::filesystem::remove(spv_path);
|
||||
|
||||
162
modules/asset_parser/private/assets/text.cpp
Normal file
162
modules/asset_parser/private/assets/text.cpp
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#include <asset_parser/assets/text.hpp>
|
||||
|
||||
namespace Assets {
|
||||
|
||||
/* static */ void TextAsset::pack(const PackageData &data, const std::filesystem::path &out_path)
|
||||
{
|
||||
const auto &[metadata, text_metadata, text] = data;
|
||||
|
||||
auto stream = std::ofstream { out_path, std::ios::binary | std::ios::trunc };
|
||||
if (!stream.is_open())
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Failed to open ofstream for packing Text at: {}", out_path.string())
|
||||
};
|
||||
}
|
||||
stream.seekp(0);
|
||||
|
||||
// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
stream.write((char *)¤t_version, sizeof(current_version));
|
||||
stream.write((char *)&metadata, sizeof(metadata));
|
||||
stream.write((char *)&text_metadata, sizeof(text_metadata));
|
||||
|
||||
constexpr auto number_of_blobs = uint32_t { 1 };
|
||||
stream.write((char *)&number_of_blobs, sizeof(number_of_blobs));
|
||||
|
||||
auto textblob_metadata = BlobMetadata {
|
||||
.tag = BlobMetadata::Tag::text,
|
||||
.offset = static_cast<size_t>(stream.tellp()) + sizeof(BlobMetadata),
|
||||
.compression_type = CompressionType::None,
|
||||
.compressed_size = text.size(),
|
||||
.uncompressed_size = text.size(),
|
||||
};
|
||||
|
||||
stream.write((char *)&textblob_metadata, sizeof(textblob_metadata));
|
||||
stream.write((char *)text.data(), static_cast<long>(text.size()));
|
||||
// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
}
|
||||
|
||||
TextAsset::TextAsset(const std::filesystem::path &path)
|
||||
{
|
||||
m_stream = std::ifstream { path, std::ios::binary };
|
||||
if (!m_stream.is_open())
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Failed to open ifstream for loading Text asset at: {}", path.string())
|
||||
};
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
m_stream.read((char *)&version, sizeof(version));
|
||||
m_stream.read((char *)&m_asset_metadata, sizeof(m_asset_metadata));
|
||||
m_stream.read((char *)&m_metadata, sizeof(m_metadata));
|
||||
|
||||
auto num_blobs = uint32_t {};
|
||||
m_stream.read((char *)&num_blobs, sizeof(num_blobs));
|
||||
if (num_blobs != 1)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Failed to load Text asset: invalid number of blobs: {}", num_blobs)
|
||||
};
|
||||
}
|
||||
|
||||
m_stream.read((char *)&m_text_blob_metadata, sizeof(m_text_blob_metadata));
|
||||
if (m_text_blob_metadata.tag != BlobMetadata::Tag::text)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"Failed to load Text asset: invalid blob tag, expected {}, got {}",
|
||||
std::to_underlying(BlobMetadata::Tag::text),
|
||||
std::to_underlying(m_text_blob_metadata.tag)
|
||||
),
|
||||
};
|
||||
}
|
||||
// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
}
|
||||
|
||||
void TextAsset::unpack_blob(
|
||||
BlobMetadata::Tag tag,
|
||||
std::byte *destination,
|
||||
size_t destination_capacity
|
||||
) const
|
||||
{
|
||||
if (tag != BlobMetadata::Tag::text)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Invalid tag for unpack_blob of TextAsset: {}", std::to_underlying(tag))
|
||||
};
|
||||
}
|
||||
|
||||
m_stream.seekg(static_cast<long>(m_text_blob_metadata.offset));
|
||||
switch (m_text_blob_metadata.compression_type)
|
||||
{
|
||||
case Assets::CompressionType::None:
|
||||
if (m_text_blob_metadata.uncompressed_size != m_text_blob_metadata.compressed_size)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Failed to unpack blob from TextAsset: "
|
||||
"compressed/uncompressed size mismatch for no compression "
|
||||
"type"
|
||||
);
|
||||
}
|
||||
|
||||
if (m_text_blob_metadata.uncompressed_size > destination_capacity)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Failed to unpack blob from TextAsset: "
|
||||
"uncompressed_size > destination_capacity, unpacking "
|
||||
"would result in segfault"
|
||||
);
|
||||
}
|
||||
|
||||
if (!m_stream.is_open())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Failed to unpack blob from TextAsset: ifstream is "
|
||||
"closed"
|
||||
);
|
||||
}
|
||||
|
||||
m_stream.read(
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
(char *)destination,
|
||||
static_cast<long>(m_text_blob_metadata.uncompressed_size)
|
||||
);
|
||||
|
||||
return;
|
||||
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
std::format(
|
||||
"Failed to unpack blob from TextAsset: unsupported "
|
||||
"compression type: {}",
|
||||
std::to_underlying(m_text_blob_metadata.compression_type)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto TextAsset::get_asset_metadata() const -> const Asset::Metadata &
|
||||
{
|
||||
return m_asset_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto TextAsset::get_metadata() const -> const Metadata &
|
||||
{
|
||||
return m_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto TextAsset::get_blob_metadata(BlobMetadata::Tag tag) const -> const BlobMetadata &
|
||||
{
|
||||
if (tag != BlobMetadata::Tag::text)
|
||||
{
|
||||
throw std::runtime_error { std::format(
|
||||
"Invalid tag for get_blob_metadata of TextAsset: {}",
|
||||
std::to_underlying(tag)
|
||||
) };
|
||||
}
|
||||
|
||||
return m_text_blob_metadata;
|
||||
}
|
||||
|
||||
} // namespace Assets
|
||||
5
modules/assets/CMakeLists.txt
Normal file
5
modules/assets/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
add_library_module(assets shader.cpp)
|
||||
|
||||
target_link_libraries(assets PUBLIC logger lt_debug)
|
||||
|
||||
add_test_module(assets shader.test.cpp)
|
||||
|
|
@ -1,80 +1,4 @@
|
|||
export module assets.shader;
|
||||
|
||||
import preliminary;
|
||||
import assets.metadata;
|
||||
import logger;
|
||||
|
||||
export namespace lt::assets {
|
||||
|
||||
class ShaderAsset
|
||||
{
|
||||
public:
|
||||
static constexpr auto asset_type_identifier = Type_T { "SHADER_________" };
|
||||
|
||||
enum class BlobTag : Tag_T
|
||||
{
|
||||
code,
|
||||
};
|
||||
|
||||
enum class Type : u8
|
||||
{
|
||||
vertex,
|
||||
fragment,
|
||||
geometry,
|
||||
compute,
|
||||
};
|
||||
|
||||
struct Metadata
|
||||
{
|
||||
Type type;
|
||||
};
|
||||
|
||||
static void pack(
|
||||
const std::filesystem::path &destination,
|
||||
AssetMetadata asset_metadata,
|
||||
Metadata metadata,
|
||||
Blob code_blob
|
||||
);
|
||||
|
||||
ShaderAsset(const std::filesystem::path &path);
|
||||
|
||||
void unpack_to(BlobTag tag, std::span<byte> destination) const;
|
||||
|
||||
[[nodiscard]] auto unpack(BlobTag tag) const -> Blob;
|
||||
|
||||
[[nodiscard]] auto get_asset_metadata() const -> const AssetMetadata &
|
||||
{
|
||||
return m_asset_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_metadata() const -> const Metadata &
|
||||
{
|
||||
return m_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_blob_metadata(BlobTag tag) const -> const BlobMetadata &
|
||||
{
|
||||
ensure(
|
||||
tag == BlobTag::code,
|
||||
"Invalid blob tag for shader asset: {}",
|
||||
std::to_underlying(tag)
|
||||
);
|
||||
|
||||
return m_code_blob_metadata;
|
||||
}
|
||||
|
||||
private:
|
||||
AssetMetadata m_asset_metadata {};
|
||||
|
||||
Metadata m_metadata {};
|
||||
|
||||
BlobMetadata m_code_blob_metadata {};
|
||||
|
||||
mutable std::ifstream m_stream;
|
||||
};
|
||||
|
||||
} // namespace lt::assets
|
||||
|
||||
#include <assets/shader.hpp>
|
||||
|
||||
namespace lt::assets {
|
||||
|
||||
|
|
@ -88,8 +12,7 @@ constexpr auto total_metadata_size = //
|
|||
+ sizeof(BlobMetadata::compressed_size) //
|
||||
+ sizeof(BlobMetadata::uncompressed_size);
|
||||
|
||||
ShaderAsset::ShaderAsset(const std::filesystem::path &path)
|
||||
: m_stream(path, std::ios::beg | std::ios::binary)
|
||||
ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
|
||||
{
|
||||
ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string());
|
||||
const auto read = [this](auto &field) {
|
||||
|
|
@ -110,7 +33,6 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path)
|
|||
read(m_asset_metadata.type);
|
||||
read(m_asset_metadata.version);
|
||||
read(m_metadata.type);
|
||||
|
||||
read(m_code_blob_metadata.tag);
|
||||
read(m_code_blob_metadata.offset);
|
||||
read(m_code_blob_metadata.compression_type);
|
||||
|
|
@ -192,7 +114,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path)
|
|||
stream.write(std::bit_cast<char *>(code_blob.data()), static_cast<long long>(code_blob.size()));
|
||||
}
|
||||
|
||||
void ShaderAsset::unpack_to(BlobTag tag, std::span<byte> destination) const
|
||||
void ShaderAsset::unpack_to(BlobTag tag, std::span<std::byte> destination) const
|
||||
{
|
||||
ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
|
||||
|
||||
|
|
@ -206,10 +128,10 @@ void ShaderAsset::unpack_to(BlobTag tag, std::span<byte> destination) const
|
|||
m_code_blob_metadata.uncompressed_size
|
||||
);
|
||||
|
||||
m_stream.seekg(static_cast<long long>(m_code_blob_metadata.offset), std::ifstream::beg);
|
||||
m_stream.seekg(static_cast<long long>(m_code_blob_metadata.offset));
|
||||
m_stream.read(
|
||||
std::bit_cast<char *>(destination.data()),
|
||||
m_code_blob_metadata.uncompressed_size
|
||||
static_cast<long long>(m_code_blob_metadata.uncompressed_size)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1,65 +1,44 @@
|
|||
import test;
|
||||
import assets.metadata;
|
||||
import assets.shader;
|
||||
#include <assets/shader.hpp>
|
||||
#include <ranges>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using ::lt::assets::AssetMetadata;
|
||||
using ::lt::assets::Blob;
|
||||
using ::lt::assets::BlobMetadata;
|
||||
using ::lt::assets::ShaderAsset;
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::expect_eq;
|
||||
using ::lt::test::expect_throw;
|
||||
using ::lt::test::expect_true;
|
||||
using ::lt::test::Suite;
|
||||
|
||||
const auto test_data_path = std::filesystem::path { "./data/test_assets" };
|
||||
const auto tmp_path = std::filesystem::path { "/tmp/lt_assets_tests/" };
|
||||
|
||||
[[nodiscard]] auto generate_blob(size_t size) -> Blob
|
||||
{
|
||||
auto blob = Blob {};
|
||||
for (auto idx : std::views::iota(0u, size))
|
||||
{
|
||||
blob.emplace_back(static_cast<byte>(idx));
|
||||
}
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
Suite raii = "shader_raii"_suite = [] {
|
||||
std::filesystem::current_path(test_data_path);
|
||||
std::filesystem::create_directories(tmp_path);
|
||||
|
||||
Case { "happy paths" } = [] {
|
||||
auto shader_asset = ShaderAsset { "triangle.frag.asset" };
|
||||
Case { "happy path won't throw" } = [] {
|
||||
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
// non-existent file
|
||||
expect_throw([] { ShaderAsset { "path" }; });
|
||||
|
||||
// incompatible type
|
||||
expect_throw([] { ShaderAsset { "dummytext" }; });
|
||||
|
||||
// some random stressing
|
||||
expect_throw([] {
|
||||
for (auto idx : std::views::iota(0u, 1'000u))
|
||||
{
|
||||
auto shader_asset = ShaderAsset { std::to_string(idx) };
|
||||
}
|
||||
});
|
||||
Case { "many won't freeze/throw" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0u, 1'000u))
|
||||
{
|
||||
ignore = idx;
|
||||
auto shader_asset = ShaderAsset { "triangle.frag.asset" };
|
||||
}
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { ShaderAsset { "random_path" }; });
|
||||
};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-interfaces-global-init)
|
||||
Suite packing = "shader_pack"_suite = [] {
|
||||
Case { "Unpacking packed data returns the same data" } = [] {
|
||||
Case { "" } = [] {
|
||||
const auto out_path = tmp_path / "shader_packing";
|
||||
constexpr auto blob_size = size_t { 255u };
|
||||
|
||||
auto blob = generate_blob(blob_size);
|
||||
auto dummy_blob = lt::assets::Blob {};
|
||||
for (auto idx : std::views::iota(0, 255))
|
||||
{
|
||||
dummy_blob.emplace_back(static_cast<std::byte>(idx));
|
||||
}
|
||||
|
||||
const auto expected_size = //
|
||||
sizeof(AssetMetadata::type) //
|
||||
|
|
@ -70,7 +49,7 @@ Suite packing = "shader_pack"_suite = [] {
|
|||
+ sizeof(BlobMetadata::compression_type) //
|
||||
+ sizeof(BlobMetadata::compressed_size) //
|
||||
+ sizeof(BlobMetadata::uncompressed_size) //
|
||||
+ blob.size();
|
||||
+ dummy_blob.size();
|
||||
|
||||
ShaderAsset::pack(
|
||||
out_path,
|
||||
|
|
@ -81,7 +60,7 @@ Suite packing = "shader_pack"_suite = [] {
|
|||
ShaderAsset::Metadata {
|
||||
.type = ShaderAsset::Type::vertex,
|
||||
},
|
||||
std::move(blob)
|
||||
std::move(dummy_blob)
|
||||
);
|
||||
|
||||
auto stream = std::ifstream {
|
||||
|
|
@ -90,7 +69,7 @@ Suite packing = "shader_pack"_suite = [] {
|
|||
};
|
||||
expect_true(stream.is_open());
|
||||
|
||||
stream.seekg(0u, std::ios::end);
|
||||
stream.seekg(0, std::ios::end);
|
||||
const auto file_size = static_cast<size_t>(stream.tellg());
|
||||
expect_eq(file_size, expected_size);
|
||||
stream.close();
|
||||
|
|
@ -104,12 +83,12 @@ Suite packing = "shader_pack"_suite = [] {
|
|||
const auto &metadata = shader_asset.get_metadata();
|
||||
expect_eq(metadata.type, ShaderAsset::Type::vertex);
|
||||
|
||||
auto unpakced_blob = shader_asset.unpack(ShaderAsset::BlobTag::code);
|
||||
expect_eq(unpakced_blob.size(), blob_size);
|
||||
auto blob = shader_asset.unpack(ShaderAsset::BlobTag::code);
|
||||
expect_eq(blob.size(), 255);
|
||||
|
||||
for (auto idx : std::views::iota(0u, blob_size))
|
||||
for (auto idx : std::views::iota(0, 255))
|
||||
{
|
||||
expect_eq(unpakced_blob[idx], static_cast<byte>(idx));
|
||||
expect_eq(blob[idx], static_cast<std::byte>(idx));
|
||||
}
|
||||
};
|
||||
};
|
||||
3
modules/assets/public/compressors/lz4.hpp
Normal file
3
modules/assets/public/compressors/lz4.hpp
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
// TO BE DOOO
|
||||
|
|
@ -1,20 +1,18 @@
|
|||
export module assets.metadata;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
|
||||
export namespace lt::assets {
|
||||
namespace lt::assets {
|
||||
|
||||
using Type_T = std::array<const char, 16>;
|
||||
|
||||
using Tag_T = u8;
|
||||
using Tag_T = uint8_t;
|
||||
|
||||
using Version = u8;
|
||||
using Version = uint8_t;
|
||||
|
||||
using Blob = std::vector<byte>;
|
||||
using Blob = std::vector<std::byte>;
|
||||
|
||||
constexpr auto current_version = Version { 1u };
|
||||
|
||||
enum class CompressionType : u8
|
||||
enum class CompressionType : uint8_t
|
||||
{
|
||||
none,
|
||||
lz4,
|
||||
74
modules/assets/public/shader.hpp
Normal file
74
modules/assets/public/shader.hpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/metadata.hpp>
|
||||
|
||||
namespace lt::assets {
|
||||
|
||||
class ShaderAsset
|
||||
{
|
||||
public:
|
||||
static constexpr auto asset_type_identifier = Type_T { "SHADER_________" };
|
||||
|
||||
enum class BlobTag : Tag_T
|
||||
{
|
||||
code,
|
||||
};
|
||||
|
||||
enum class Type : uint8_t
|
||||
{
|
||||
vertex,
|
||||
fragment,
|
||||
geometry,
|
||||
compute,
|
||||
};
|
||||
|
||||
struct Metadata
|
||||
{
|
||||
Type type;
|
||||
};
|
||||
|
||||
static void pack(
|
||||
const std::filesystem::path &destination,
|
||||
AssetMetadata asset_metadata,
|
||||
Metadata metadata,
|
||||
Blob code_blob
|
||||
);
|
||||
|
||||
ShaderAsset(const std::filesystem::path &path);
|
||||
|
||||
void unpack_to(BlobTag tag, std::span<std::byte> destination) const;
|
||||
|
||||
[[nodiscard]] auto unpack(BlobTag tag) const -> Blob;
|
||||
|
||||
[[nodiscard]] auto get_asset_metadata() const -> const AssetMetadata &
|
||||
{
|
||||
return m_asset_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_metadata() const -> const Metadata &
|
||||
{
|
||||
return m_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_blob_metadata(BlobTag tag) const -> const BlobMetadata &
|
||||
{
|
||||
ensure(
|
||||
tag == BlobTag::code,
|
||||
"Invalid blob tag for shader asset: {}",
|
||||
std::to_underlying(tag)
|
||||
);
|
||||
|
||||
return m_code_blob_metadata;
|
||||
}
|
||||
|
||||
private:
|
||||
AssetMetadata m_asset_metadata {};
|
||||
|
||||
Metadata m_metadata {};
|
||||
|
||||
BlobMetadata m_code_blob_metadata {};
|
||||
|
||||
mutable std::ifstream m_stream;
|
||||
};
|
||||
|
||||
} // namespace lt::assets
|
||||
1
modules/bitwise/CMakeLists.txt
Normal file
1
modules/bitwise/CMakeLists.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
add_library_module(bitwise)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
export module bitwise;
|
||||
import preliminary;
|
||||
|
||||
namespace lt::bitwise {
|
||||
|
||||
/* bit-wise */
|
||||
export constexpr auto bit(u32 x) -> u32
|
||||
{
|
||||
return u32 { 1u } << x;
|
||||
}
|
||||
|
||||
} // namespace lt::bitwise
|
||||
13
modules/bitwise/public/operations.hpp
Normal file
13
modules/bitwise/public/operations.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace lt::bitwise {
|
||||
|
||||
/* bit-wise */
|
||||
constexpr auto bit(uint32_t x) -> uint32_t
|
||||
{
|
||||
return 1u << x;
|
||||
}
|
||||
|
||||
} // namespace lt::bitwise
|
||||
3
modules/camera/CMakeLists.txt
Normal file
3
modules/camera/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
add_library_module(camera camera.cpp scene.cpp)
|
||||
|
||||
target_link_libraries(camera PUBLIC math)
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
export module camera.components;
|
||||
|
||||
import preliminary;
|
||||
import math.vec4;
|
||||
|
||||
export namespace lt::camera::components {
|
||||
|
||||
struct PerspectiveCamera
|
||||
{
|
||||
f32 vertical_fov {};
|
||||
|
||||
f32 near_plane {};
|
||||
|
||||
f32 far_plane {};
|
||||
|
||||
f32 aspect_ratio {};
|
||||
|
||||
math::vec4 background_color;
|
||||
|
||||
bool is_primary {};
|
||||
};
|
||||
|
||||
} // namespace lt::camera::components
|
||||
6
modules/camera/private/camera.cpp
Normal file
6
modules/camera/private/camera.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#include <camera/camera.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
|
||||
}
|
||||
84
modules/camera/private/scene.cpp
Normal file
84
modules/camera/private/scene.cpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#include <camera/camera.hpp>
|
||||
#include <camera/component.hpp>
|
||||
#include <math/algebra.hpp>
|
||||
#include <math/trig.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
SceneCamera::SceneCamera()
|
||||
: m_orthographic_specification { .size = 1000.0f, .near_plane = -1.0f, .far_plane = 10000.0f }
|
||||
, m_perspective_specification { .vertical_fov = math::radians(45.0f),
|
||||
.near_plane = 0.01f,
|
||||
.far_plane = 10000.0f }
|
||||
, m_aspect_ratio(16.0f / 9.0f)
|
||||
|
||||
{
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_viewport_size(unsigned int width, unsigned int height)
|
||||
{
|
||||
m_aspect_ratio = static_cast<float>(width) / static_cast<float>(height);
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_projection_type(ProjectionType projection_type)
|
||||
{
|
||||
m_projection_type = projection_type;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_orthographic_size(float size)
|
||||
{
|
||||
m_orthographic_specification.size = size;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_orthographic_far_plane(float far_plane)
|
||||
{
|
||||
m_orthographic_specification.far_plane = far_plane;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_orthographic_near_plane(float near_plane)
|
||||
{
|
||||
m_orthographic_specification.near_plane = near_plane;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_perspective_vertical_fov(float vertical_fov)
|
||||
{
|
||||
m_perspective_specification.vertical_fov = vertical_fov;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_perspective_far_plane(float far_plane)
|
||||
{
|
||||
m_perspective_specification.far_plane = far_plane;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::set_perspective_near_plane(float near_plane)
|
||||
{
|
||||
m_perspective_specification.near_plane = near_plane;
|
||||
calculate_projection();
|
||||
}
|
||||
|
||||
void SceneCamera::calculate_projection()
|
||||
{
|
||||
// TODO(Light): implement ortho perspective
|
||||
if (m_projection_type == ProjectionType::Orthographic)
|
||||
{
|
||||
// throw std::runtime_error { "ortho perspective not supported yet" };
|
||||
}
|
||||
|
||||
// defaults to perspective for now...
|
||||
m_projection = math::perspective(
|
||||
m_perspective_specification.vertical_fov,
|
||||
m_aspect_ratio,
|
||||
m_perspective_specification.near_plane,
|
||||
m_perspective_specification.far_plane
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
35
modules/camera/public/camera.hpp
Normal file
35
modules/camera/public/camera.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <math/mat4.hpp>
|
||||
#include <math/vec4.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class Camera
|
||||
{
|
||||
public:
|
||||
Camera() = default;
|
||||
|
||||
[[nodiscard]] auto get_projection() const -> const math::mat4 &
|
||||
{
|
||||
return m_projection;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_background_color() const -> const math::vec4 &
|
||||
{
|
||||
return m_background_color;
|
||||
}
|
||||
|
||||
void set_background_color(const math::vec4 &color)
|
||||
{
|
||||
m_background_color = color;
|
||||
}
|
||||
|
||||
protected:
|
||||
math::mat4 m_projection;
|
||||
|
||||
private:
|
||||
math::vec4 m_background_color = math::vec4(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
29
modules/camera/public/component.hpp
Normal file
29
modules/camera/public/component.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <camera/scene.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
struct CameraComponent
|
||||
{
|
||||
CameraComponent() = default;
|
||||
|
||||
CameraComponent(const CameraComponent &) = default;
|
||||
|
||||
CameraComponent(SceneCamera _camera, bool _isPrimary = false)
|
||||
: camera(_camera)
|
||||
, isPrimary(_isPrimary)
|
||||
{
|
||||
}
|
||||
|
||||
operator SceneCamera() const
|
||||
{
|
||||
return camera;
|
||||
}
|
||||
|
||||
SceneCamera camera;
|
||||
|
||||
bool isPrimary {};
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
100
modules/camera/public/scene.hpp
Normal file
100
modules/camera/public/scene.hpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#pragma once
|
||||
|
||||
#include <camera/camera.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class SceneCamera: public Camera
|
||||
{
|
||||
public:
|
||||
enum class ProjectionType
|
||||
{
|
||||
Orthographic = 0,
|
||||
Perspetcive = 1
|
||||
};
|
||||
|
||||
struct OrthographicSpecification
|
||||
{
|
||||
float size;
|
||||
|
||||
float near_plane;
|
||||
|
||||
float far_plane;
|
||||
};
|
||||
|
||||
struct PerspectiveSpecification
|
||||
{
|
||||
float vertical_fov;
|
||||
|
||||
float near_plane;
|
||||
|
||||
float far_plane;
|
||||
};
|
||||
|
||||
SceneCamera();
|
||||
|
||||
void set_viewport_size(unsigned int width, unsigned int height);
|
||||
|
||||
void set_projection_type(ProjectionType projection_type);
|
||||
|
||||
void set_orthographic_size(float size);
|
||||
|
||||
void set_orthographic_far_plane(float far_plane);
|
||||
|
||||
void set_orthographic_near_plane(float near_plane);
|
||||
|
||||
void set_perspective_vertical_fov(float vertical_fov);
|
||||
|
||||
void set_perspective_far_plane(float far_plane);
|
||||
|
||||
void set_perspective_near_plane(float near_plane);
|
||||
|
||||
[[nodiscard]] auto get_orthographic_size() const -> float
|
||||
{
|
||||
return m_orthographic_specification.size;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_orthographic_far_plane() const -> float
|
||||
{
|
||||
return m_orthographic_specification.far_plane;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_orthographic_near_plane() const -> float
|
||||
{
|
||||
return m_orthographic_specification.near_plane;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_perspective_vertical_fov() const -> float
|
||||
{
|
||||
return m_perspective_specification.vertical_fov;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_perspective_far_plane() const -> float
|
||||
{
|
||||
return m_perspective_specification.far_plane;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_perspective_near_plane() const -> float
|
||||
{
|
||||
return m_perspective_specification.near_plane;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_projection_type() const -> ProjectionType
|
||||
{
|
||||
return m_projection_type;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
OrthographicSpecification m_orthographic_specification;
|
||||
|
||||
PerspectiveSpecification m_perspective_specification;
|
||||
|
||||
float m_aspect_ratio;
|
||||
|
||||
ProjectionType m_projection_type { ProjectionType::Orthographic };
|
||||
|
||||
void calculate_projection();
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
4
modules/debug/CMakeLists.txt
Normal file
4
modules/debug/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
add_library_module(lt_debug instrumentor.cpp)
|
||||
target_link_libraries(lt_debug PUBLIC logger)
|
||||
target_precompile_headers(lt_debug PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/private/pch.hpp)
|
||||
71
modules/debug/private/instrumentor.cpp
Normal file
71
modules/debug/private/instrumentor.cpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#include <logger/logger.hpp>
|
||||
#include <lt_debug/instrumentor.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
void Instrumentor::begin_session_impl(const std::string &outputPath)
|
||||
{
|
||||
std::filesystem::create_directory(outputPath.substr(0, outputPath.find_last_of('/') + 1));
|
||||
|
||||
m_output_file_stream.open(outputPath);
|
||||
m_output_file_stream << "{\"traceEvents\":[";
|
||||
}
|
||||
|
||||
void Instrumentor::end_session_impl()
|
||||
{
|
||||
if (m_current_session_count == 0u)
|
||||
{
|
||||
log_wrn("0 profiling for the ended session");
|
||||
}
|
||||
|
||||
m_current_session_count = 0u;
|
||||
|
||||
m_output_file_stream << "]}";
|
||||
m_output_file_stream.flush();
|
||||
m_output_file_stream.close();
|
||||
}
|
||||
|
||||
void Instrumentor::submit_scope_profile_impl(const ScopeProfileResult &profileResult)
|
||||
{
|
||||
if (m_current_session_count++ == 0u)
|
||||
{
|
||||
m_output_file_stream << "{";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_output_file_stream << ",{";
|
||||
}
|
||||
|
||||
m_output_file_stream << R"("name":")" << profileResult.name << "\",";
|
||||
m_output_file_stream << R"("cat": "scope",)";
|
||||
m_output_file_stream << R"("ph": "X",)";
|
||||
m_output_file_stream << "\"ts\":" << profileResult.start << ",";
|
||||
m_output_file_stream << "\"dur\":" << profileResult.duration << ",";
|
||||
m_output_file_stream << "\"pid\":0,";
|
||||
m_output_file_stream << "\"tid\":" << profileResult.threadID << "";
|
||||
m_output_file_stream << "}";
|
||||
}
|
||||
|
||||
InstrumentorTimer::InstrumentorTimer(const std::string &scopeName)
|
||||
: m_result({ .name = scopeName, .start = 0, .duration = 0, .threadID = 0 })
|
||||
, m_start(std::chrono::steady_clock::now())
|
||||
{
|
||||
}
|
||||
|
||||
InstrumentorTimer::~InstrumentorTimer()
|
||||
{
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
m_result.start = std::chrono::time_point_cast<std::chrono::microseconds>(m_start)
|
||||
.time_since_epoch()
|
||||
.count();
|
||||
|
||||
m_result.duration = std::chrono::time_point_cast<std::chrono::microseconds>(end)
|
||||
.time_since_epoch()
|
||||
.count()
|
||||
- m_result.start;
|
||||
|
||||
Instrumentor::submit_scope_profile(m_result);
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
3
modules/debug/private/pch.hpp
Normal file
3
modules/debug/private/pch.hpp
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
#include <lt_debug/assertions.hpp>
|
||||
36
modules/debug/public/assertions.hpp
Normal file
36
modules/debug/public/assertions.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <logger/logger.hpp>
|
||||
#include <source_location>
|
||||
|
||||
namespace lt {
|
||||
|
||||
template<typename Expression_T, typename... Args_T>
|
||||
struct ensure
|
||||
{
|
||||
ensure(
|
||||
const Expression_T &expression,
|
||||
std::format_string<Args_T...> fmt,
|
||||
Args_T &&...args,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
)
|
||||
{
|
||||
if (!static_cast<bool>(expression))
|
||||
{
|
||||
throw std::runtime_error { std::format(
|
||||
"exception: {}\nlocation: {}:{}",
|
||||
std::format(fmt, std::forward<Args_T>(args)...),
|
||||
location.file_name(),
|
||||
location.line()
|
||||
) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Expression_T, typename... Args_T>
|
||||
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
|
||||
-> ensure<Expression_T, Args_T...>;
|
||||
|
||||
} // namespace lt
|
||||
77
modules/debug/public/instrumentor.hpp
Normal file
77
modules/debug/public/instrumentor.hpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
|
||||
namespace lt {
|
||||
|
||||
struct ScopeProfileResult
|
||||
{
|
||||
std::string name;
|
||||
long long start, duration;
|
||||
uint32_t threadID;
|
||||
};
|
||||
|
||||
class Instrumentor
|
||||
{
|
||||
public:
|
||||
static auto instance() -> Instrumentor &
|
||||
{
|
||||
static auto instance = Instrumentor {};
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void begin_session(const std::string &outputPath)
|
||||
{
|
||||
instance().begin_session_impl(outputPath);
|
||||
}
|
||||
static void end_session()
|
||||
{
|
||||
instance().end_session_impl();
|
||||
}
|
||||
|
||||
static void submit_scope_profile(const ScopeProfileResult &profileResult)
|
||||
{
|
||||
instance().submit_scope_profile_impl(profileResult);
|
||||
}
|
||||
|
||||
private:
|
||||
std::ofstream m_output_file_stream;
|
||||
|
||||
unsigned int m_current_session_count { 0u };
|
||||
|
||||
Instrumentor() = default;
|
||||
|
||||
void begin_session_impl(const std::string &outputPath);
|
||||
|
||||
void end_session_impl();
|
||||
|
||||
void submit_scope_profile_impl(const ScopeProfileResult &profileResult);
|
||||
};
|
||||
|
||||
class InstrumentorTimer
|
||||
{
|
||||
public:
|
||||
InstrumentorTimer(const std::string &scopeName);
|
||||
|
||||
~InstrumentorTimer();
|
||||
|
||||
private:
|
||||
ScopeProfileResult m_result;
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_start;
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
|
||||
/* scope */
|
||||
#define lt_profile_scope(name) lt_profile_scope_no_redifinition(name, __LINE__)
|
||||
#define lt_profile_scope_no_redifinition(name, line) lt_profile_scope_no_redifinition2(name, line)
|
||||
#define lt_profile_scope_no_redifinition2(name, line) InstrumentorTimer timer##line(name)
|
||||
|
||||
/* function */
|
||||
#define LT_PROFILE_FUNCTION lt_profile_scope(__FUNCSIG__)
|
||||
|
||||
/* session */
|
||||
#define lt_profile_begin_session(outputPath) ::lt::Instrumentor::begin_session(outputPath)
|
||||
#define lt_profile_end_session() ::lt::Instrumentor::end_session()
|
||||
4
modules/ecs/CMakeLists.txt
Normal file
4
modules/ecs/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
add_library_module(ecs sparse_set.cpp)
|
||||
target_link_libraries(ecs PUBLIC logger lt_debug memory)
|
||||
|
||||
add_test_module(ecs sparse_set.test.cpp registry.test.cpp)
|
||||
|
|
@ -1,12 +1,23 @@
|
|||
import test;
|
||||
import ecs.registry;
|
||||
#include <ecs/registry.hpp>
|
||||
#include <ranges>
|
||||
#include <test/expects.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using ::lt::ecs::EntityId;
|
||||
using ::lt::ecs::Registry;
|
||||
using lt::test::Case;
|
||||
using lt::test::expect_unreachable;
|
||||
using lt::test::Suite;
|
||||
|
||||
using lt::test::expect_eq;
|
||||
|
||||
using lt::test::expect_false;
|
||||
using lt::test::expect_true;
|
||||
|
||||
using lt::ecs::EntityId;
|
||||
using lt::ecs::Registry;
|
||||
|
||||
struct Component
|
||||
{
|
||||
i32 m_int {};
|
||||
int m_int {};
|
||||
std::string m_string;
|
||||
|
||||
[[nodiscard]] friend auto operator==(const Component &lhs, const Component &rhs) -> bool
|
||||
|
|
@ -30,7 +41,7 @@ struct std::formatter<Component>
|
|||
|
||||
struct Component_B
|
||||
{
|
||||
f32 m_float {};
|
||||
float m_float {};
|
||||
|
||||
[[nodiscard]] friend auto operator==(const Component_B lhs, const Component_B &rhs) -> bool
|
||||
{
|
||||
|
|
@ -52,21 +63,20 @@ struct std::formatter<Component_B>
|
|||
};
|
||||
|
||||
Suite raii = "raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = Registry {};
|
||||
Case { "happy path won't throw" } = [] {
|
||||
std::ignore = Registry {};
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
Case { "many won't freeze/throw" } = [] {
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = Registry {};
|
||||
std::ignore = Registry {};
|
||||
}
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
};
|
||||
|
||||
Case { "post construct has correct state" } = [] {
|
||||
auto registry = Registry {};
|
||||
expect_eq(registry.get_entity_count(), 0);
|
||||
|
|
@ -150,7 +160,6 @@ Suite callbacks = "callbacks"_suite = [] {
|
|||
|
||||
Case { "on_construct/on_destruct won't get called on unrelated component" } = [] {
|
||||
auto registry = Registry {};
|
||||
|
||||
registry.connect_on_construct<Component>([&](Registry &, EntityId) {
|
||||
expect_unreachable();
|
||||
});
|
||||
|
|
@ -160,7 +169,6 @@ Suite callbacks = "callbacks"_suite = [] {
|
|||
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
ignore = idx;
|
||||
registry.add<Component_B>(registry.create_entity(), {});
|
||||
}
|
||||
};
|
||||
|
|
@ -182,8 +190,6 @@ Suite callbacks = "callbacks"_suite = [] {
|
|||
expect_true(on_destruct_called.empty());
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
ignore = idx;
|
||||
|
||||
auto entity = all_entities.emplace_back(registry.create_entity());
|
||||
registry.add<Component>(entity, {});
|
||||
}
|
||||
|
|
@ -218,7 +224,7 @@ Suite each = "each"_suite = [] {
|
|||
component_map_a[entity] = component;
|
||||
}
|
||||
|
||||
auto component_map_b = std::unordered_map<EntityId, Component_B> {};
|
||||
auto component_map_b = std::unordered_map<lt::ecs::EntityId, Component_B> {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
auto entity = EntityId {};
|
||||
|
|
@ -233,7 +239,7 @@ Suite each = "each"_suite = [] {
|
|||
}
|
||||
auto &component = registry.add<Component_B>(
|
||||
entity,
|
||||
{ .m_float = static_cast<f32>(idx) / 2.0f }
|
||||
{ .m_float = static_cast<float>(idx) / 2.0f }
|
||||
);
|
||||
|
||||
component_map_b[entity] = component;
|
||||
|
|
@ -304,7 +310,7 @@ Suite views = "views"_suite = [] {
|
|||
}
|
||||
auto &component = registry.add<Component_B>(
|
||||
entity,
|
||||
{ .m_float = static_cast<f32>(idx) / 2.0f }
|
||||
{ .m_float = static_cast<float>(idx) / 2.0f }
|
||||
);
|
||||
|
||||
component_map_b[entity] = component;
|
||||
0
modules/ecs/private/sparse_set.cpp
Normal file
0
modules/ecs/private/sparse_set.cpp
Normal file
|
|
@ -1,26 +1,28 @@
|
|||
import test;
|
||||
import ecs.sparse_set;
|
||||
#include <ecs/sparse_set.hpp>
|
||||
#include <ranges>
|
||||
#include <test/expects.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using Value_T = i32;
|
||||
using Set = lt::ecs::SparseSet<Value_T>;
|
||||
using lt::test::Case;
|
||||
using lt::test::Suite;
|
||||
|
||||
using lt::test::expect_eq;
|
||||
using lt::test::expect_false;
|
||||
using lt::test::expect_ne;
|
||||
using lt::test::expect_throw;
|
||||
using lt::test::expect_true;
|
||||
|
||||
using Set = lt::ecs::SparseSet<int>;
|
||||
constexpr auto capacity = 100;
|
||||
|
||||
Suite raii = "raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = Set {};
|
||||
ignore = Set { Set::max_capacity };
|
||||
Case { "happy path won't throw" } = [] {
|
||||
std::ignore = Set {};
|
||||
std::ignore = Set { Set::max_capacity };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
expect_throw([] { ignore = Set { Set::max_capacity + 1 }; });
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000))
|
||||
{
|
||||
ignore = Set { static_cast<size_t>(idx) };
|
||||
}
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { std::ignore = Set { Set::max_capacity + 1 }; });
|
||||
};
|
||||
|
||||
Case { "post construct has correct state" } = [&] {
|
||||
|
|
@ -31,29 +33,7 @@ Suite raii = "raii"_suite = [] {
|
|||
};
|
||||
|
||||
Suite element_raii = "element_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
auto set = Set { capacity };
|
||||
set.insert(0, {});
|
||||
set.remove(0);
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
expect_throw([] {
|
||||
auto set = Set { capacity };
|
||||
set.insert(Set::max_capacity + 1, {});
|
||||
});
|
||||
|
||||
expect_throw([] {
|
||||
auto set = Set { capacity };
|
||||
set.insert(0, {});
|
||||
set.insert(1, {});
|
||||
set.insert(2, {});
|
||||
|
||||
set.remove(3);
|
||||
});
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
Case { "many inserts/removes won't freeze/throw" } = [] {
|
||||
auto set = Set {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
|
|
@ -97,7 +77,7 @@ Suite element_raii = "element_raii"_suite = [] {
|
|||
|
||||
expect_eq(set.get_size(), 10'000 - (idx + 1));
|
||||
expect_false(set.contains(idx));
|
||||
expect_throw([&] { ignore = set.at(idx); });
|
||||
expect_throw([&] { std::ignore = set.at(idx); });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -117,7 +97,7 @@ Suite element_raii = "element_raii"_suite = [] {
|
|||
|
||||
for (auto &[identifier, value] : set)
|
||||
{
|
||||
expect_eq(static_cast<Value_T>(identifier), value);
|
||||
expect_eq(identifier, value);
|
||||
expect_ne(value, 0);
|
||||
expect_ne(value, 32);
|
||||
expect_ne(value, 69);
|
||||
|
|
@ -149,7 +129,7 @@ Suite getters = "getters"_suite = [] {
|
|||
|
||||
expect_eq(set.get_capacity(), 10'000);
|
||||
|
||||
set.insert(static_cast<Value_T>(set.get_size()), {});
|
||||
set.insert(set.get_size(), {});
|
||||
expect_ne(set.get_capacity(), 10'000);
|
||||
};
|
||||
|
||||
|
|
@ -160,12 +140,12 @@ Suite getters = "getters"_suite = [] {
|
|||
{
|
||||
expect_throw([&] {
|
||||
set.insert(idx, {});
|
||||
ignore = set.at(50);
|
||||
std::ignore = set.at(50);
|
||||
});
|
||||
}
|
||||
|
||||
set.insert(50, {});
|
||||
ignore = set.at(50); // should not throw
|
||||
std::ignore = set.at(50); // should not throw
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -179,10 +159,5 @@ Suite clear = "clear"_suite = [] {
|
|||
|
||||
set.clear();
|
||||
expect_eq(set.get_size(), 0);
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
expect_throw([&] { ignore = set.at(idx); });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
export module ecs.entity;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
import memory.reference;
|
||||
import ecs.registry;
|
||||
#include <ecs/registry.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
|
||||
export namespace lt::ecs {
|
||||
namespace lt::ecs {
|
||||
|
||||
/** High-level entity convenience wrapper */
|
||||
class Entity
|
||||
|
|
@ -51,4 +50,5 @@ private:
|
|||
EntityId m_identifier;
|
||||
};
|
||||
|
||||
|
||||
} // namespace lt::ecs
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
export module ecs.registry;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
import ecs.sparse_set;
|
||||
import memory.scope;
|
||||
#include <ecs/sparse_set.hpp>
|
||||
#include <memory/scope.hpp>
|
||||
|
||||
export namespace lt::ecs {
|
||||
namespace lt::ecs {
|
||||
|
||||
using EntityId = u32;
|
||||
using EntityId = uint32_t;
|
||||
|
||||
constexpr auto null_entity = std::numeric_limits<EntityId>::max();
|
||||
|
||||
|
|
@ -208,7 +207,7 @@ private:
|
|||
for (const auto &ch : std::string_view { str })
|
||||
{
|
||||
hash *= fnv_prime;
|
||||
hash ^= static_cast<u8>(ch);
|
||||
hash ^= static_cast<uint8_t>(ch);
|
||||
}
|
||||
|
||||
return hash;
|
||||
|
|
@ -251,16 +250,11 @@ private:
|
|||
|
||||
TypeId m_entity_count;
|
||||
|
||||
/** MSVC DOES NOT SUPPORT FLAT MAP!!
|
||||
* IT"S YEAR ~2026, great...
|
||||
* using ::std::map for the time being.
|
||||
*/
|
||||
std::flat_map<TypeId, memory::Scope<UnderlyingSparseSet_T>> m_sparsed_sets;
|
||||
|
||||
std::map<TypeId, memory::Scope<UnderlyingSparseSet_T>> m_sparsed_sets;
|
||||
std::flat_map<TypeId, Callback_T> m_on_construct_hooks;
|
||||
|
||||
std::map<TypeId, Callback_T> m_on_construct_hooks;
|
||||
|
||||
std::map<TypeId, Callback_T> m_on_destruct_hooks;
|
||||
std::flat_map<TypeId, Callback_T> m_on_destruct_hooks;
|
||||
};
|
||||
|
||||
} // namespace lt::ecs
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
export module ecs.sparse_set;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
|
||||
export namespace lt::ecs {
|
||||
namespace lt::ecs {
|
||||
|
||||
/**
|
||||
*
|
||||
* @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
|
||||
*/
|
||||
template<typename Identifier_T = u32>
|
||||
template<typename Identifier_T = uint32_t>
|
||||
class TypeErasedSparseSet
|
||||
{
|
||||
public:
|
||||
|
|
@ -26,7 +25,7 @@ public:
|
|||
virtual void remove(Identifier_T identifier) = 0;
|
||||
};
|
||||
|
||||
template<typename Value_T, typename Identifier_T = u32>
|
||||
template<typename Value_T, typename Identifier_T = uint32_t>
|
||||
class SparseSet: public TypeErasedSparseSet<Identifier_T>
|
||||
{
|
||||
public:
|
||||
|
|
@ -51,18 +50,21 @@ public:
|
|||
|
||||
auto insert(Identifier_T identifier, Value_T value) -> Dense_T &
|
||||
{
|
||||
ensure(identifier < max_capacity, "SparseSet::insert: identifier < max_capacity");
|
||||
|
||||
if (m_sparse.size() < identifier + 1)
|
||||
{
|
||||
auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
|
||||
new_capacity = std::min(new_capacity, max_capacity);
|
||||
|
||||
// log_dbg("Increasing sparse vector size:", m_dead_count);
|
||||
// log_dbg("\tdead_count: {}", m_dead_count);
|
||||
// log_dbg("\talive_count: {}", m_alive_count);
|
||||
// log_dbg("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
|
||||
|
||||
m_sparse.resize(new_capacity, null_identifier);
|
||||
}
|
||||
|
||||
++m_alive_count;
|
||||
m_sparse[identifier] = static_cast<Identifier_T>(m_dense.size());
|
||||
m_sparse[identifier] = m_dense.size();
|
||||
return m_dense.emplace_back(identifier, std::move(value));
|
||||
}
|
||||
|
||||
|
|
@ -72,27 +74,7 @@ public:
|
|||
*/
|
||||
void remove(Identifier_T identifier) override
|
||||
{
|
||||
ensure(
|
||||
identifier < m_sparse.size(),
|
||||
"Failed to ensure: identifier < m_sparse.size() [{} < {}]",
|
||||
identifier,
|
||||
m_sparse.size()
|
||||
);
|
||||
|
||||
auto &idx = m_sparse[identifier];
|
||||
ensure(
|
||||
idx != null_identifier,
|
||||
"Failed to ensure: idx != null_identifier [{} != {}]",
|
||||
idx,
|
||||
null_identifier
|
||||
);
|
||||
ensure(
|
||||
idx < m_dense.size(),
|
||||
"Failed to ensure: idx < m_dense.size() [{} < {}]",
|
||||
idx,
|
||||
m_dense.size()
|
||||
);
|
||||
|
||||
auto &[entity, component] = m_dense[idx];
|
||||
|
||||
auto &[last_entity, last_component] = m_dense.back();
|
||||
1
modules/env/CMakeLists.txt
vendored
Normal file
1
modules/env/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
add_library_module(env)
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
export module preliminary.build_constants;
|
||||
#pragma once
|
||||
|
||||
import preliminary.fundumental_types;
|
||||
import std;
|
||||
namespace lt {
|
||||
|
||||
export namespace build_constants {
|
||||
|
||||
enum class Platform : u8
|
||||
enum class Platform : uint8_t
|
||||
{
|
||||
/** The GNU/Linux platform.
|
||||
* Tested on the following distros: arch-x86_64
|
||||
|
|
@ -27,7 +24,7 @@ enum class Platform : u8
|
|||
};
|
||||
|
||||
/** The compiler that was used for compiling the project. */
|
||||
enum class Compiler : u8
|
||||
enum class Compiler : uint8_t
|
||||
{
|
||||
clang,
|
||||
gcc,
|
||||
|
|
@ -35,51 +32,37 @@ enum class Compiler : u8
|
|||
apple_clang,
|
||||
};
|
||||
|
||||
enum class BuildType
|
||||
{
|
||||
debug,
|
||||
release,
|
||||
distribution
|
||||
};
|
||||
namespace constants {
|
||||
|
||||
#if defined(LIGHT_PLATFORM_WINDOWS)
|
||||
#define lt_win(x)
|
||||
constexpr auto platform = Platform::windows;
|
||||
constexpr auto platform_name = "windows";
|
||||
constexpr auto platform_identifier = platform_name; // TODO(Light)
|
||||
|
||||
#undef LIGHT_PLATFORM_WINDOWS
|
||||
|
||||
#elif defined(LIGHT_PLATFORM_LINUX)
|
||||
constexpr auto platform = Platform::gnu_linux;
|
||||
constexpr auto platform_name = "gnu_linux";
|
||||
constexpr auto platform_identifier = platform_name; // TODO(Light)
|
||||
|
||||
#elif defined(LIGHT_PLATFORM_MAC)
|
||||
#define lt_mac(x) x
|
||||
constexpr auto platform = Platform::mac;
|
||||
constexpr auto platform_name = "mac";
|
||||
constexpr auto platform_identifier = platform_name; // TODO(Light)
|
||||
|
||||
#else
|
||||
#error "Unsupported platform: Unknown"
|
||||
|
||||
#endif
|
||||
|
||||
/** @TODO(Light): Handle other compilers... */
|
||||
#ifdef __clang__
|
||||
constexpr auto compiler = Compiler::clang;
|
||||
constexpr auto compiler_name = "clang";
|
||||
|
||||
/** @TODO(Light): insert the full identifier, including version information and such */
|
||||
constexpr auto compiler_identifier = "clang";
|
||||
/** @todo(Light): insert the full identifier, including version information and such */
|
||||
constexpr auto full_compiler_identifier = "clang";
|
||||
#endif
|
||||
|
||||
// @TODO(Light): inject build info through CMake using LIGHT_... constant macros
|
||||
#if defined(_DEBUG)
|
||||
constexpr auto build_type = BuildType::debug;
|
||||
#else
|
||||
constexpr auto build_type = BuildType::release;
|
||||
#endif
|
||||
} // namespace constants
|
||||
|
||||
} // namespace build_constants
|
||||
} // namespace lt
|
||||
4
modules/input/CMakeLists.txt
Normal file
4
modules/input/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
add_library_module(input system.cpp)
|
||||
target_link_libraries(input PUBLIC surface math logger tbb)
|
||||
|
||||
add_test_module(input system.test.cpp)
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
export module input.events;
|
||||
|
||||
import input.codes;
|
||||
import math.vec2;
|
||||
|
||||
import std;
|
||||
|
||||
namespace lt::input {
|
||||
|
||||
export class AnalogEvent
|
||||
{
|
||||
public:
|
||||
AnalogEvent(Key key, math::vec2_u32 pointer_position)
|
||||
: m_key(key)
|
||||
, m_pointer_position(pointer_position)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_key() const -> Key
|
||||
{
|
||||
return m_key;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto get_pointer_position() const -> math::vec2_u32
|
||||
{
|
||||
return m_pointer_position;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto to_string() const -> std::string
|
||||
{
|
||||
const auto &[x, y] = m_pointer_position;
|
||||
return std::format("input::AnalogEvent: {} @ {}, {}", std::to_underlying(m_key), x, y);
|
||||
}
|
||||
|
||||
private:
|
||||
Key m_key;
|
||||
|
||||
math::vec2_u32 m_pointer_position;
|
||||
};
|
||||
|
||||
} // namespace lt::input
|
||||
|
|
@ -1,65 +1,9 @@
|
|||
export module input.system;
|
||||
export import :components;
|
||||
import logger;
|
||||
import app.system;
|
||||
import ecs.registry;
|
||||
import memory.reference;
|
||||
import surface.system;
|
||||
import surface.events;
|
||||
import math.vec2;
|
||||
import std;
|
||||
#include <input/components.hpp>
|
||||
#include <input/system.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
|
||||
namespace lt::input {
|
||||
|
||||
export class System: public app::ISystem
|
||||
{
|
||||
public:
|
||||
System(memory::Ref<ecs::Registry> registry);
|
||||
|
||||
void tick(app::TickInfo tick) override;
|
||||
|
||||
void on_register() override;
|
||||
|
||||
void on_unregister() override;
|
||||
|
||||
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
|
||||
{
|
||||
return m_last_tick_result;
|
||||
}
|
||||
|
||||
private:
|
||||
void handle_event(const surface::SurfaceComponent::Event &event);
|
||||
|
||||
void on_surface_lost_focus();
|
||||
|
||||
void on_key_press(const lt::surface::KeyPressedEvent &event);
|
||||
|
||||
void on_key_release(const lt::surface::KeyReleasedEvent &event);
|
||||
|
||||
void on_pointer_move(const lt::surface::MouseMovedEvent &event);
|
||||
|
||||
void on_button_press(const lt::surface::ButtonPressedEvent &event);
|
||||
|
||||
void on_button_release(const lt::surface::ButtonReleasedEvent &event);
|
||||
|
||||
memory::Ref<ecs::Registry> m_registry;
|
||||
|
||||
std::array<bool, 512> m_keys {};
|
||||
|
||||
std::array<bool, 512> m_buttons {};
|
||||
|
||||
math::vec2 m_pointer_position;
|
||||
|
||||
app::TickResult m_last_tick_result {};
|
||||
};
|
||||
|
||||
|
||||
} // namespace lt::input
|
||||
|
||||
|
||||
module :private;
|
||||
namespace lt::input {
|
||||
|
||||
template<class... Ts>
|
||||
struct overloads: Ts...
|
||||
{
|
||||
|
|
@ -89,7 +33,7 @@ void System::tick(app::TickInfo tick)
|
|||
// instead of brute-force checking all of them.
|
||||
for (auto &action : input.m_actions)
|
||||
{
|
||||
auto code = std::to_underlying(action.trigger.mapped_keycode);
|
||||
auto code = action.trigger.mapped_keycode;
|
||||
if (code < m_keys.size() && m_keys[code])
|
||||
{
|
||||
if (action.state == InputAction::State::triggered)
|
||||
|
|
@ -155,9 +99,9 @@ void System::on_surface_lost_focus()
|
|||
|
||||
void System::on_key_press(const lt::surface::KeyPressedEvent &event)
|
||||
{
|
||||
if (std::to_underlying(event.get_key()) > m_keys.size())
|
||||
if (event.get_key() > m_keys.size())
|
||||
{
|
||||
log::warn(
|
||||
log_dbg(
|
||||
"Key code larger than key container size, implement platform-dependant "
|
||||
"key-code-mapping!"
|
||||
);
|
||||
|
|
@ -165,14 +109,14 @@ void System::on_key_press(const lt::surface::KeyPressedEvent &event)
|
|||
return;
|
||||
}
|
||||
|
||||
m_keys[std::to_underlying(event.get_key())] = true;
|
||||
m_keys[event.get_key()] = true;
|
||||
}
|
||||
|
||||
void System::on_key_release(const lt::surface::KeyReleasedEvent &event)
|
||||
{
|
||||
if (std::to_underlying(event.get_key()) > m_keys.size())
|
||||
if (event.get_key() > m_keys.size())
|
||||
{
|
||||
log::warn(
|
||||
log_dbg(
|
||||
"Key code larger than key container size, implement platform-dependant "
|
||||
"key-code-mapping!"
|
||||
);
|
||||
|
|
@ -180,7 +124,7 @@ void System::on_key_release(const lt::surface::KeyReleasedEvent &event)
|
|||
return;
|
||||
}
|
||||
|
||||
m_keys[std::to_underlying(event.get_key())] = false;
|
||||
m_keys[event.get_key()] = false;
|
||||
}
|
||||
|
||||
void System::on_pointer_move(const lt::surface::MouseMovedEvent &event)
|
||||
|
|
@ -190,12 +134,12 @@ void System::on_pointer_move(const lt::surface::MouseMovedEvent &event)
|
|||
|
||||
void System::on_button_press(const lt::surface::ButtonPressedEvent &event)
|
||||
{
|
||||
m_buttons[std::to_underlying(event.get_button())] = true;
|
||||
m_buttons[event.get_button()] = true;
|
||||
}
|
||||
|
||||
void System::on_button_release(const lt::surface::ButtonReleasedEvent &event)
|
||||
{
|
||||
m_buttons[std::to_underlying(event.get_button())] = false;
|
||||
m_buttons[event.get_button()] = false;
|
||||
}
|
||||
|
||||
} // namespace lt::input
|
||||
|
|
@ -1,18 +1,27 @@
|
|||
import test;
|
||||
import input.system;
|
||||
import input.codes;
|
||||
import surface.events;
|
||||
import memory.scope;
|
||||
import memory.reference;
|
||||
import app.system;
|
||||
import ecs.entity;
|
||||
import ecs.registry;
|
||||
import surface.system;
|
||||
#include <ecs/entity.hpp>
|
||||
#include <input/components.hpp>
|
||||
#include <input/system.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
#include <memory/scope.hpp>
|
||||
#include <ranges>
|
||||
#include <surface/system.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using ::lt::input::InputComponent;
|
||||
using ::lt::input::System;
|
||||
// NOLINTBEGIN
|
||||
using namespace lt;
|
||||
using input::InputComponent;
|
||||
using input::System;
|
||||
using std::ignore;
|
||||
using test::Case;
|
||||
using test::expect_eq;
|
||||
using test::expect_false;
|
||||
using test::expect_ne;
|
||||
using test::expect_not_nullptr;
|
||||
using test::expect_throw;
|
||||
using test::Suite;
|
||||
// NOLINTEND
|
||||
|
||||
[[nodiscard]] auto tick_info() -> lt::app::TickInfo
|
||||
[[nodiscard]] auto tick_info() -> app::TickInfo
|
||||
{
|
||||
return {
|
||||
.delta_time = std::chrono::milliseconds { 16 },
|
||||
|
|
@ -24,12 +33,12 @@ using ::lt::input::System;
|
|||
class Fixture
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] auto registry() -> lt::memory::Ref<lt::ecs::Registry>
|
||||
[[nodiscard]] auto registry() -> memory::Ref<ecs::Registry>
|
||||
{
|
||||
return m_registry;
|
||||
}
|
||||
|
||||
auto add_input_component() -> lt::ecs::EntityId
|
||||
auto add_input_component() -> ecs::EntityId
|
||||
{
|
||||
auto entity = m_registry->create_entity();
|
||||
m_registry->add<InputComponent>(entity, {});
|
||||
|
|
@ -37,7 +46,7 @@ public:
|
|||
return entity;
|
||||
}
|
||||
|
||||
auto add_surface_component() -> lt::ecs::EntityId
|
||||
auto add_surface_component() -> ecs::EntityId
|
||||
{
|
||||
auto entity = m_registry->create_entity();
|
||||
m_surface_system.create_surface_component(
|
||||
|
|
@ -49,28 +58,27 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
lt::memory::Ref<lt::ecs::Registry> m_registry = lt::memory::create_ref<lt::ecs::Registry>();
|
||||
memory::Ref<ecs::Registry> m_registry = memory::create_ref<ecs::Registry>();
|
||||
|
||||
lt::surface::System m_surface_system = lt::surface::System { m_registry };
|
||||
surface::System m_surface_system = surface::System { m_registry };
|
||||
};
|
||||
|
||||
Suite raii = "raii"_suite = "raii"_suite = [] {
|
||||
Case { "happy paths" } = [&] {
|
||||
Case { "happy path won't throw" } = [&] {
|
||||
System { Fixture {}.registry() };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
expect_throw([] { ignore = System { {} }; });
|
||||
};
|
||||
|
||||
Case { "many" } = [&] {
|
||||
Case { "many won't freeze/throw" } = [&] {
|
||||
auto fixture = Fixture {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = System { fixture.registry() };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { ignore = System { {} }; });
|
||||
};
|
||||
};
|
||||
|
||||
Suite system_events = "system_events"_suite = [] {
|
||||
|
|
@ -100,14 +108,14 @@ Suite registry_events = "registry_events"_suite = [] {
|
|||
auto registry = fixture.registry();
|
||||
auto system = System { registry };
|
||||
|
||||
fixture.add_input_component();
|
||||
const auto &entity = fixture.add_input_component();
|
||||
expect_eq(registry->view<InputComponent>().get_size(), 1);
|
||||
};
|
||||
|
||||
Case { "on_destrroy<InputComponent>" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto registry = fixture.registry();
|
||||
auto system = lt::memory::create_scope<System>(registry);
|
||||
auto system = memory::create_scope<System>(registry);
|
||||
|
||||
auto entity_a = fixture.add_input_component();
|
||||
auto entity_b = fixture.add_input_component();
|
||||
|
|
@ -139,7 +147,7 @@ Suite tick = "tick"_suite = [] {
|
|||
auto system = System { fixture.registry() };
|
||||
|
||||
auto surface_entity = fixture.add_surface_component();
|
||||
auto &surface = registry->get<lt::surface::SurfaceComponent>(surface_entity);
|
||||
auto &surface = registry->get<surface::SurfaceComponent>(surface_entity);
|
||||
|
||||
auto input_entity = fixture.add_input_component();
|
||||
auto &input = registry->get<InputComponent>(input_entity);
|
||||
|
|
@ -147,29 +155,48 @@ Suite tick = "tick"_suite = [] {
|
|||
auto action_key = input.add_action(
|
||||
{
|
||||
.name { "test" },
|
||||
.trigger = { .mapped_keycode = Key::a },
|
||||
.trigger = { .mapped_keycode = 69 },
|
||||
}
|
||||
);
|
||||
|
||||
using enum ::lt::input::InputAction::State;
|
||||
expect_eq(input.get_action(action_key).state, inactive);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, inactive);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||
|
||||
surface.push_event(lt::surface::KeyPressedEvent(Key::a));
|
||||
surface.push_event(surface::KeyPressedEvent(69));
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, triggered);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
|
||||
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, active);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
||||
|
||||
system.tick(tick_info());
|
||||
system.tick(tick_info());
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, active);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
||||
|
||||
surface.push_event(lt::surface::KeyReleasedEvent(Key::a));
|
||||
surface.push_event(surface::KeyReleasedEvent(69));
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, inactive);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||
};
|
||||
|
||||
Case { "Tick triggers" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto registry = fixture.registry();
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
auto surface_entity = fixture.add_surface_component();
|
||||
auto &surface = registry->get<surface::SurfaceComponent>(surface_entity);
|
||||
|
||||
auto input_entity = fixture.add_input_component();
|
||||
auto &input = registry->get<InputComponent>(input_entity);
|
||||
|
||||
|
||||
auto action_key = input.add_action(
|
||||
{
|
||||
.name { "test" },
|
||||
.trigger = { .mapped_keycode = 69 },
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
export module input.system:components;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
import input.codes;
|
||||
#include <vector>
|
||||
|
||||
export namespace lt::input {
|
||||
namespace lt::input {
|
||||
|
||||
struct Trigger
|
||||
{
|
||||
Key mapped_keycode;
|
||||
uint32_t mapped_keycode;
|
||||
};
|
||||
|
||||
struct InputAction
|
||||
{
|
||||
enum class State : u8
|
||||
using Key = size_t;
|
||||
|
||||
enum class State : uint8_t
|
||||
{
|
||||
inactive,
|
||||
active,
|
||||
|
|
@ -38,7 +39,7 @@ public:
|
|||
return m_actions.size() - 1;
|
||||
}
|
||||
|
||||
auto get_action(size_t idx) -> const InputAction &
|
||||
auto get_action(auto idx) -> const InputAction &
|
||||
{
|
||||
return m_actions[idx];
|
||||
}
|
||||
44
modules/input/public/events.hpp
Normal file
44
modules/input/public/events.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <math/vec2.hpp>
|
||||
|
||||
namespace lt::input {
|
||||
|
||||
class AnalogEvent
|
||||
{
|
||||
public:
|
||||
AnalogEvent(uint32_t input_code, math::uvec2 pointer_position)
|
||||
: m_input_code(input_code)
|
||||
, m_pointer_position(pointer_position)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_code() const -> uint32_t
|
||||
{
|
||||
return m_input_code;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto get_pointer_position() const -> math::uvec2
|
||||
{
|
||||
return m_pointer_position;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto to_string() const -> std::string
|
||||
{
|
||||
auto stream = std::stringstream {};
|
||||
const auto &[x, y] = m_pointer_position;
|
||||
stream << "input::AnalogEvent: " << m_input_code << " @ " << x << ", " << y;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t m_input_code;
|
||||
|
||||
math::uvec2 m_pointer_position;
|
||||
};
|
||||
|
||||
class AxisEvent
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace lt::input
|
||||
180
modules/input/public/key_codes.hpp
Normal file
180
modules/input/public/key_codes.hpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace lt::Key {
|
||||
|
||||
enum : uint16_t
|
||||
{
|
||||
/* digits */
|
||||
D0 = 48,
|
||||
D1 = 49,
|
||||
D2 = 50,
|
||||
D3 = 51,
|
||||
D4 = 52,
|
||||
D5 = 53,
|
||||
D6 = 54,
|
||||
D7 = 55,
|
||||
D8 = 56,
|
||||
D9 = 57,
|
||||
Semicolon = 59, // ;
|
||||
Equal = 61, // =
|
||||
|
||||
/* letters */
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
t = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
|
||||
/* brackets */
|
||||
LeftBracket = 91, // [
|
||||
LBracket = LeftBracket, // [
|
||||
RightBracket = 93, // ]
|
||||
RBracket = RightBracket, // ]
|
||||
|
||||
/* arrow */
|
||||
Right = 262,
|
||||
RightArrow = Right,
|
||||
RArrow = Right,
|
||||
Left = 263,
|
||||
LeftArrow = Left,
|
||||
LArrow = Left,
|
||||
Down = 264,
|
||||
DownArrow = Down,
|
||||
DArrow = Down,
|
||||
Up = 265,
|
||||
UpArrow = Up,
|
||||
UArrow = Up,
|
||||
|
||||
/* page */
|
||||
PageUp = 266,
|
||||
PageDown = 267,
|
||||
|
||||
/* home/end */
|
||||
Home = 268,
|
||||
end = 269,
|
||||
|
||||
/* toggles */
|
||||
CapsLock = 280,
|
||||
ScrollLock = 281,
|
||||
NumLock = 282,
|
||||
NumberLock = NumLock,
|
||||
|
||||
/* function */
|
||||
F1 = 290,
|
||||
F2 = 291,
|
||||
F3 = 292,
|
||||
F4 = 293,
|
||||
F5 = 294,
|
||||
F6 = 295,
|
||||
F7 = 296,
|
||||
F8 = 297,
|
||||
F9 = 298,
|
||||
F10 = 299,
|
||||
F11 = 300,
|
||||
F12 = 301,
|
||||
F13 = 302,
|
||||
F14 = 303,
|
||||
F15 = 304,
|
||||
F16 = 305,
|
||||
F17 = 306,
|
||||
F18 = 307,
|
||||
F19 = 308,
|
||||
F20 = 309,
|
||||
F21 = 310,
|
||||
F22 = 311,
|
||||
F23 = 312,
|
||||
F24 = 313,
|
||||
F25 = 314,
|
||||
|
||||
/* keypad */
|
||||
Kp0 = 320,
|
||||
Kp1 = 321,
|
||||
Kp2 = 322,
|
||||
Kp3 = 323,
|
||||
Kp4 = 324,
|
||||
Kp5 = 325,
|
||||
Kp6 = 326,
|
||||
Kp7 = 327,
|
||||
Kp8 = 328,
|
||||
Kp9 = 329,
|
||||
KpDecimal = 330,
|
||||
KpDivide = 331,
|
||||
KpMultiply = 332,
|
||||
KpSubstract = 333,
|
||||
KpAdd = 334,
|
||||
KpEnter = 335,
|
||||
KpEqual = 336,
|
||||
|
||||
/* modifiers */
|
||||
LeftShift = 340,
|
||||
LShift = LeftShift,
|
||||
LeftControl = 341,
|
||||
LControl = LeftControl,
|
||||
LeftAlt = 342,
|
||||
LAlt = LeftAlt,
|
||||
LeftSuper = 343,
|
||||
LSuper = LeftSuper,
|
||||
RightShift = 344,
|
||||
RShift = 344,
|
||||
RightControl = 345,
|
||||
RControl = 345,
|
||||
RightAlt = 346,
|
||||
RAlt = 346,
|
||||
RightSuper = 347,
|
||||
RSuper = 347,
|
||||
|
||||
/* misc */
|
||||
Space = 32,
|
||||
Apostrophe = 39, // '
|
||||
Quote = Apostrophe,
|
||||
|
||||
Comma = 44, // ,
|
||||
Minus = 45, // -
|
||||
Period = 46, // .
|
||||
Slash = 47, // /
|
||||
ForwardSlash = Slash, // /
|
||||
BackSlash = 92, // \
|
||||
|
||||
GraveAccent = 96, // `
|
||||
Console = GraveAccent,
|
||||
World1 = 161, // non-US #1
|
||||
World2 = 162, // non-US #2
|
||||
Escape = 256,
|
||||
Esc = Escape,
|
||||
Enter = 257,
|
||||
Tab = 258,
|
||||
BackSpace = 259,
|
||||
Insert = 260,
|
||||
Delete = 261,
|
||||
|
||||
PrintScreen = 283,
|
||||
Pause = 284,
|
||||
|
||||
Menu = 348,
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
23
modules/input/public/mouse_codes.hpp
Normal file
23
modules/input/public/mouse_codes.hpp
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace lt::Mouse {
|
||||
|
||||
enum : uint8_t
|
||||
{
|
||||
Button1 = 0,
|
||||
Button2 = 1,
|
||||
Button3 = 2,
|
||||
Button4 = 3,
|
||||
Button5 = 4,
|
||||
Button6 = 5,
|
||||
Button7 = 6,
|
||||
Button8 = 7,
|
||||
|
||||
LButton = Button1,
|
||||
RButton = Button2,
|
||||
MButton = Button3,
|
||||
};
|
||||
|
||||
}
|
||||
55
modules/input/public/system.hpp
Normal file
55
modules/input/public/system.hpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include <app/system.hpp>
|
||||
#include <ecs/registry.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
#include <surface/components.hpp>
|
||||
#include <surface/events/keyboard.hpp>
|
||||
#include <surface/events/mouse.hpp>
|
||||
|
||||
namespace lt::input {
|
||||
|
||||
class System: public app::ISystem
|
||||
{
|
||||
public:
|
||||
System(memory::Ref<ecs::Registry> registry);
|
||||
|
||||
void tick(app::TickInfo tick) override;
|
||||
|
||||
void on_register() override;
|
||||
|
||||
void on_unregister() override;
|
||||
|
||||
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
|
||||
{
|
||||
return m_last_tick_result;
|
||||
}
|
||||
|
||||
private:
|
||||
void handle_event(const surface::SurfaceComponent::Event &event);
|
||||
|
||||
void on_surface_lost_focus();
|
||||
|
||||
void on_key_press(const lt::surface::KeyPressedEvent &event);
|
||||
|
||||
void on_key_release(const lt::surface::KeyReleasedEvent &event);
|
||||
|
||||
void on_pointer_move(const lt::surface::MouseMovedEvent &event);
|
||||
|
||||
void on_button_press(const lt::surface::ButtonPressedEvent &event);
|
||||
|
||||
void on_button_release(const lt::surface::ButtonReleasedEvent &event);
|
||||
|
||||
memory::Ref<ecs::Registry> m_registry;
|
||||
|
||||
std::array<bool, 512> m_keys {};
|
||||
|
||||
std::array<bool, 512> m_buttons {};
|
||||
|
||||
math::vec2 m_pointer_position;
|
||||
|
||||
app::TickResult m_last_tick_result {};
|
||||
};
|
||||
|
||||
|
||||
} // namespace lt::input
|
||||
|
|
@ -1,292 +0,0 @@
|
|||
/**
|
||||
* @note: The reason this is a separate module, rather than being in the `Input` module is that
|
||||
* the input is received from the hardware through the `Surface` module, and it is further parsed
|
||||
* inside the `Input` module, USING the `Surface` module's events.
|
||||
*
|
||||
* Hence, both `Surface` and `Input` needs to agree to the same input codes, while `Input` depends
|
||||
* on `Surface`. The simplest solution is to keep the codes in a 3rd module and make both depend on
|
||||
* it. (I did not want to give `Surface` the responsibility of defining input codes...)
|
||||
*/
|
||||
export module input.codes;
|
||||
|
||||
import preliminary;
|
||||
|
||||
export enum class Key: u16 {
|
||||
none = 0,
|
||||
|
||||
left_button,
|
||||
l_button = left_button,
|
||||
|
||||
right_button,
|
||||
r_button = right_button,
|
||||
|
||||
middle_button,
|
||||
m_button = middle_button,
|
||||
|
||||
// the buttons on the sidse of some mouses
|
||||
x_button_1,
|
||||
x_button_2,
|
||||
|
||||
backspace,
|
||||
tab,
|
||||
capslock,
|
||||
enter,
|
||||
space,
|
||||
delete_,
|
||||
|
||||
shift,
|
||||
left_shit = shift,
|
||||
l_shift = shift,
|
||||
|
||||
right_shift,
|
||||
r_shift = right_shift,
|
||||
|
||||
control,
|
||||
left_control = control,
|
||||
l_control = control,
|
||||
ctrl = control,
|
||||
left_ctrl = control,
|
||||
l_ctrl = control,
|
||||
|
||||
right_control,
|
||||
r_control = right_control,
|
||||
right_ctrl = right_control,
|
||||
r_ctrl = right_control,
|
||||
|
||||
alt,
|
||||
left_alt = alt,
|
||||
l_alt = alt,
|
||||
|
||||
right_alt,
|
||||
r_alt = right_alt,
|
||||
|
||||
pageup,
|
||||
pagedown,
|
||||
home,
|
||||
end,
|
||||
|
||||
left_arrow,
|
||||
l_arrow = left_arrow,
|
||||
|
||||
up_arrow,
|
||||
u_arrow = up_arrow,
|
||||
|
||||
right_arrow,
|
||||
r_arrow = right_arrow,
|
||||
|
||||
down_arrow,
|
||||
d_arrow = down_arrow,
|
||||
|
||||
cancel,
|
||||
pause,
|
||||
select,
|
||||
print,
|
||||
snapshot, // aka. print-screen
|
||||
insert,
|
||||
help,
|
||||
sleep,
|
||||
eep = sleep,
|
||||
|
||||
digit_0,
|
||||
digit_1,
|
||||
digit_2,
|
||||
digit_3,
|
||||
digit_4,
|
||||
digit_5,
|
||||
digit_6,
|
||||
digit_7,
|
||||
digit_8,
|
||||
digit_9,
|
||||
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
g,
|
||||
h,
|
||||
i,
|
||||
j,
|
||||
k,
|
||||
l,
|
||||
m,
|
||||
n,
|
||||
o,
|
||||
p,
|
||||
q,
|
||||
r,
|
||||
s,
|
||||
t,
|
||||
u,
|
||||
v,
|
||||
w,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
|
||||
super,
|
||||
left_super = super,
|
||||
l_super = super,
|
||||
|
||||
right_super,
|
||||
r_super = right_super,
|
||||
|
||||
kp_0,
|
||||
kp_1,
|
||||
kp_2,
|
||||
kp_3,
|
||||
kp_4,
|
||||
kp_5,
|
||||
kp_6,
|
||||
kp_7,
|
||||
kp_8,
|
||||
kp_9,
|
||||
kp_decimal,
|
||||
kp_divide,
|
||||
kp_multiply,
|
||||
kp_subtract,
|
||||
kp_add,
|
||||
kp_enter,
|
||||
kp_equal,
|
||||
|
||||
f1,
|
||||
f2,
|
||||
f3,
|
||||
f4,
|
||||
f5,
|
||||
f6,
|
||||
f7,
|
||||
f8,
|
||||
f9,
|
||||
f10,
|
||||
f11,
|
||||
f12,
|
||||
|
||||
/** Input was received but was none of the above. */
|
||||
unknown,
|
||||
};
|
||||
|
||||
export [[nodiscard]] constexpr auto to_string(Key key) -> std::string
|
||||
{
|
||||
using enum Key;
|
||||
switch (key)
|
||||
{
|
||||
case none: return "<none>";
|
||||
|
||||
case left_button: return "left_button";
|
||||
case right_button: return "right_button";
|
||||
case middle_button: return "middle_button";
|
||||
|
||||
case x_button_1: return "x_button_1";
|
||||
case x_button_2: return "x_button_2";
|
||||
|
||||
case backspace: return "backspace";
|
||||
case tab: return "tab";
|
||||
case capslock: return "capslock";
|
||||
case enter: return "enter";
|
||||
case space: return "space";
|
||||
case delete_: return "delete";
|
||||
|
||||
case shift: return "shift";
|
||||
case control: return "control";
|
||||
case right_control: return "right_control";
|
||||
case alt: return "alt";
|
||||
case right_alt: return "right_alt";
|
||||
|
||||
case pageup: return "pageup";
|
||||
case pagedown: return "pagedown";
|
||||
case home: return "home";
|
||||
case end: return "end";
|
||||
|
||||
case left_arrow: return "left_arrow";
|
||||
case up_arrow: return "up_arrow";
|
||||
case right_arrow: return "right_arrow";
|
||||
case down_arrow: return "down_arrow";
|
||||
|
||||
case cancel: return "cancel";
|
||||
case pause: return "pause";
|
||||
case select: return "select";
|
||||
case print: return "print";
|
||||
case snapshot: return "snapshot";
|
||||
case insert: return "insert";
|
||||
case help: return "help";
|
||||
case sleep: return "sleep";
|
||||
|
||||
case digit_0: return "0";
|
||||
case digit_1: return "1";
|
||||
case digit_2: return "2";
|
||||
case digit_3: return "3";
|
||||
case digit_4: return "4";
|
||||
case digit_5: return "5";
|
||||
case digit_6: return "6";
|
||||
case digit_7: return "7";
|
||||
case digit_8: return "8";
|
||||
case digit_9: return "9";
|
||||
|
||||
case a: return "a";
|
||||
case b: return "b";
|
||||
case c: return "c";
|
||||
case d: return "d";
|
||||
case e: return "e";
|
||||
case f: return "f";
|
||||
case g: return "g";
|
||||
case h: return "h";
|
||||
case i: return "i";
|
||||
case j: return "j";
|
||||
case k: return "k";
|
||||
case l: return "l";
|
||||
case m: return "m";
|
||||
case n: return "n";
|
||||
case o: return "o";
|
||||
case p: return "p";
|
||||
case q: return "q";
|
||||
case r: return "r";
|
||||
case s: return "s";
|
||||
case t: return "t";
|
||||
case u: return "u";
|
||||
case v: return "v";
|
||||
case w: return "w";
|
||||
case x: return "x";
|
||||
case y: return "y";
|
||||
case z: return "z";
|
||||
|
||||
case super: return "super";
|
||||
case right_super: return "right_super";
|
||||
|
||||
case kp_0: return "kp_0";
|
||||
case kp_1: return "kp_1";
|
||||
case kp_2: return "kp_2";
|
||||
case kp_3: return "kp_3";
|
||||
case kp_4: return "kp_4";
|
||||
case kp_5: return "kp_5";
|
||||
case kp_6: return "kp_6";
|
||||
case kp_7: return "kp_7";
|
||||
case kp_8: return "kp_8";
|
||||
case kp_9: return "kp_9";
|
||||
case kp_decimal: return "kp_decimal";
|
||||
case kp_divide: return "kp_divide";
|
||||
case kp_multiply: return "kp_multiply";
|
||||
case kp_subtract: return "kp_subtract";
|
||||
case kp_add: return "kp_add";
|
||||
case kp_enter: return "kp_enter";
|
||||
case kp_equal: return "kp_equal";
|
||||
|
||||
case f1: return "f1";
|
||||
case f2: return "f2";
|
||||
case f3: return "f3";
|
||||
case f4: return "f4";
|
||||
case f5: return "f5";
|
||||
case f6: return "f6";
|
||||
case f7: return "f7";
|
||||
case f8: return "f8";
|
||||
case f9: return "f9";
|
||||
case f10: return "f10";
|
||||
case f11: return "f11";
|
||||
case f12: return "f12";
|
||||
|
||||
case unknown: return "<unknown>";
|
||||
}
|
||||
|
||||
return "<invalid>";
|
||||
}
|
||||
1
modules/logger/CMakeLists.txt
Normal file
1
modules/logger/CMakeLists.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
add_library_module(logger logger.cpp)
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
export module logger;
|
||||
|
||||
import preliminary;
|
||||
|
||||
export namespace lt::log {
|
||||
|
||||
/** Severity of a log message. */
|
||||
enum class Level : u8
|
||||
{
|
||||
/** Lowest and most vebose log level, for tracing execution paths and events */
|
||||
trace = 0,
|
||||
|
||||
/** Vebose log level, for enabling temporarily to debug */
|
||||
debug = 1,
|
||||
|
||||
/** General information */
|
||||
info = 2,
|
||||
|
||||
/** Things we should to be aware of and edge cases */
|
||||
warn = 3,
|
||||
|
||||
/** Defects, bugs and undesired behaviour */
|
||||
error = 4,
|
||||
|
||||
/** Unrecoverable errors */
|
||||
critical = 5,
|
||||
|
||||
/**
|
||||
* Logs from the testing-framework.
|
||||
* Highest so we still get them while turning off all logs from the code under test.
|
||||
*
|
||||
* @note: log::test does NOT include source_location
|
||||
*/
|
||||
test = 6,
|
||||
|
||||
/** No logging */
|
||||
off = 7,
|
||||
};
|
||||
|
||||
auto min_severity = Level::trace;
|
||||
|
||||
auto set_min_severity(Level severity)
|
||||
{
|
||||
min_severity = severity;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
struct [[maybe_unused]] print
|
||||
{
|
||||
[[maybe_unused]] print(
|
||||
Level level,
|
||||
const std::source_location &location,
|
||||
std::format_string<Args...> format,
|
||||
Args &&...arguments
|
||||
) noexcept
|
||||
{
|
||||
if (std::to_underlying(level) < std::to_underlying(min_severity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr auto to_string = [](Level level) {
|
||||
// clang-format off
|
||||
switch (level)
|
||||
{
|
||||
using enum ::lt::log::Level;
|
||||
case trace : return "\033[1;37m| trc |\033[0m";
|
||||
case debug : return "\033[1;36m| dbg |\033[0m";
|
||||
case info : return "\033[1;32m| inf |\033[0m";
|
||||
case warn : return "\033[1;33m| wrn |\033[0m";
|
||||
case error : return "\033[1;31m| err |\033[0m";
|
||||
case critical: return "\033[1;41m| crt |\033[0m";
|
||||
case off: return "off";
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
std::unreachable();
|
||||
};
|
||||
|
||||
const auto path = std::filesystem::path { location.file_name() };
|
||||
|
||||
std::println(
|
||||
"{} {} ==> {}",
|
||||
to_string(level),
|
||||
std::format("{}:{}", path.filename().string(), location.line()),
|
||||
std::format(format, std::forward<Args>(arguments)...)
|
||||
);
|
||||
}
|
||||
|
||||
[[maybe_unused]] print(
|
||||
Level level,
|
||||
std::format_string<Args...> format,
|
||||
Args &&...arguments
|
||||
) noexcept
|
||||
{
|
||||
constexpr auto to_string = [](Level level) {
|
||||
// clang-format off
|
||||
switch (level)
|
||||
{
|
||||
using enum ::lt::log::Level;
|
||||
case trace : return "\033[1;37m| trc |\033[0m";
|
||||
case debug : return "\033[1;36m| dbg |\033[0m";
|
||||
case info : return "\033[1;32m| inf |\033[0m";
|
||||
case warn : return "\033[1;33m| wrn |\033[0m";
|
||||
case error : return "\033[1;31m| err |\033[0m";
|
||||
case critical: return "\033[1;41m| crt |\033[0m";
|
||||
case test : return "\033[1;33m| test |\033[0m";
|
||||
case off : return "";
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
std::unreachable();
|
||||
};
|
||||
|
||||
std::println(
|
||||
"{} {}",
|
||||
to_string(level),
|
||||
std::format(format, std::forward<Args>(arguments)...)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
print(Level, const std::source_location &, std::format_string<Args...>, Args &&...) noexcept
|
||||
-> print<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
struct [[maybe_unused]] trace
|
||||
{
|
||||
[[maybe_unused]] trace(
|
||||
std::format_string<Args...> format,
|
||||
Args &&...arguments,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
) noexcept
|
||||
{
|
||||
print(Level::trace, location, format, std::forward<Args>(arguments)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
trace(std::format_string<Args...>, Args &&...) noexcept -> trace<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
struct [[maybe_unused]] debug
|
||||
{
|
||||
[[maybe_unused]] debug(
|
||||
std::format_string<Args...> format,
|
||||
Args &&...arguments,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
) noexcept
|
||||
{
|
||||
print(Level::debug, location, format, std::forward<Args>(arguments)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
debug(std::format_string<Args...>, Args &&...) noexcept -> debug<Args...>;
|
||||
|
||||
|
||||
template<typename... Args>
|
||||
struct [[maybe_unused]] info
|
||||
{
|
||||
[[maybe_unused]] info(
|
||||
std::format_string<Args...> format,
|
||||
Args &&...arguments,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
) noexcept
|
||||
{
|
||||
print(Level::info, location, format, std::forward<Args>(arguments)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
info(std::format_string<Args...>, Args &&...) noexcept -> info<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
struct [[maybe_unused]] warn
|
||||
{
|
||||
[[maybe_unused]] warn(
|
||||
std::format_string<Args...> format,
|
||||
Args &&...arguments,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
) noexcept
|
||||
{
|
||||
print(Level::warn, location, format, std::forward<Args>(arguments)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
warn(std::format_string<Args...>, Args &&...) noexcept -> warn<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
struct [[maybe_unused]] error
|
||||
{
|
||||
[[maybe_unused]] error(
|
||||
std::format_string<Args...> format,
|
||||
Args &&...arguments,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
) noexcept
|
||||
{
|
||||
print(Level::error, location, format, std::forward<Args>(arguments)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
error(std::format_string<Args...>, Args &&...) noexcept -> error<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
struct [[maybe_unused]] critical
|
||||
{
|
||||
[[maybe_unused]] critical(
|
||||
std::format_string<Args...> format,
|
||||
Args &&...arguments,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
) noexcept
|
||||
{
|
||||
print(Level::critical, location, format, std::forward<Args>(arguments)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
critical(std::format_string<Args...>, Args &&...) noexcept -> critical<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
void test(std::format_string<Args...> format, Args &&...arguments) noexcept
|
||||
{
|
||||
print(Level::test, format, std::forward<Args>(arguments)...);
|
||||
}
|
||||
|
||||
} // namespace lt::log
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import test;
|
||||
|
||||
Suite suite = [] {
|
||||
Case { "formatless" } = [] {
|
||||
lt::log::trace("trace");
|
||||
lt::log::debug("debug");
|
||||
lt::log::info("info");
|
||||
lt::log::warn("warn");
|
||||
lt::log::error("error");
|
||||
lt::log::critical("critical");
|
||||
};
|
||||
|
||||
Case { "formatted" } = [] {
|
||||
lt::log::trace("trace {}", 69);
|
||||
lt::log::debug("debug {}", 69);
|
||||
lt::log::info("info {}", 69);
|
||||
lt::log::warn("warn {}", 69);
|
||||
lt::log::error("error {}", 69);
|
||||
lt::log::critical("critical {}", 69);
|
||||
};
|
||||
};
|
||||
1
modules/logger/private/logger.cpp
Normal file
1
modules/logger/private/logger.cpp
Normal file
|
|
@ -0,0 +1 @@
|
|||
#include <logger/logger.hpp>
|
||||
89
modules/logger/public/logger.hpp
Normal file
89
modules/logger/public/logger.hpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <print>
|
||||
|
||||
/** Severity of a log message. */
|
||||
enum class LogLvl : uint8_t
|
||||
{
|
||||
/** Lowest and most vebose log level, for tracing execution paths and events */
|
||||
trace = 0,
|
||||
|
||||
/** Vebose log level, for enabling temporarily to debug */
|
||||
debug = 1,
|
||||
|
||||
/** General information */
|
||||
info = 2,
|
||||
|
||||
/** Things we should to be aware of and edge cases */
|
||||
warn = 3,
|
||||
|
||||
/** Defects, bugs and undesired behaviour */
|
||||
error = 4,
|
||||
|
||||
/** Unrecoverable errors */
|
||||
critical = 5,
|
||||
|
||||
/** No logging */
|
||||
off = 6,
|
||||
};
|
||||
|
||||
/** Simple console logger */
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
void static show_imgui_window();
|
||||
|
||||
template<typename... Args>
|
||||
void static log(LogLvl lvl, std::format_string<Args...> fmt, Args &&...args) noexcept
|
||||
{
|
||||
std::ignore = lvl;
|
||||
std::println(fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void static log(LogLvl lvl, const char *message) noexcept
|
||||
{
|
||||
std::ignore = lvl;
|
||||
std::println("{}", message);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
Logger() = default;
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
void log_trc(std::format_string<Args...> fmt, Args &&...args) noexcept
|
||||
{
|
||||
Logger::log(LogLvl::trace, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void log_dbg(std::format_string<Args...> fmt, Args &&...args) noexcept
|
||||
{
|
||||
Logger::log(LogLvl::debug, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void log_inf(std::format_string<Args...> fmt, Args &&...args) noexcept
|
||||
{
|
||||
Logger::log(LogLvl::info, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void log_wrn(std::format_string<Args...> fmt, Args &&...args) noexcept
|
||||
{
|
||||
Logger::log(LogLvl::warn, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void log_err(std::format_string<Args...> fmt, Args &&...args) noexcept
|
||||
{
|
||||
Logger::log(LogLvl::error, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void log_crt(std::format_string<Args...> fmt, Args &&...args) noexcept
|
||||
{
|
||||
Logger::log(LogLvl::critical, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
1
modules/math/CMakeLists.txt
Normal file
1
modules/math/CMakeLists.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
add_library_module(math)
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
export module math.components;
|
||||
|
||||
import preliminary;
|
||||
import math.vec3;
|
||||
|
||||
namespace lt::math::components {
|
||||
|
||||
export struct Transform
|
||||
{
|
||||
math::vec3 translation;
|
||||
|
||||
math::vec3 scale;
|
||||
|
||||
math::vec3 rotation;
|
||||
};
|
||||
|
||||
} // namespace lt::math::components
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
export module math.mat4;
|
||||
|
||||
import preliminary;
|
||||
import math.vec2;
|
||||
import math.vec3;
|
||||
import math.vec4;
|
||||
|
||||
export namespace lt::math {
|
||||
|
||||
/** A 4 by 4 matrix, column major order
|
||||
*
|
||||
* @todo(Light): Use std::simd when it's implemented. */
|
||||
template<typename T = f32>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
struct mat4_impl
|
||||
{
|
||||
using Column_T = vec4_impl<T>;
|
||||
|
||||
using Underlying_T = Column_T::Underlying_T;
|
||||
|
||||
static constexpr auto num_elements = 4u * 4u;
|
||||
|
||||
constexpr explicit mat4_impl(T scalar = T {})
|
||||
: values(
|
||||
{
|
||||
Column_T { scalar },
|
||||
Column_T { scalar },
|
||||
Column_T { scalar },
|
||||
Column_T { scalar },
|
||||
}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr mat4_impl(
|
||||
// clang-format off
|
||||
const T& x0, const T& x1, const T& x2, const T& x3,
|
||||
const T& y0, const T& y1, const T& y2, const T& y3,
|
||||
const T& z0, const T& z1, const T& z2, const T& z3,
|
||||
const T& w0, const T& w1, const T& w2, const T& w3)
|
||||
// clang-format on
|
||||
: values({ { x0, x1, x2, x3 }, { y0, y1, y2, y3 }, { z0, z1, z2, z3 }, { w0, w1, w2, w3 } })
|
||||
{
|
||||
}
|
||||
|
||||
constexpr mat4_impl(
|
||||
const Column_T &column_x,
|
||||
const Column_T &column_y,
|
||||
const Column_T &column_z,
|
||||
const Column_T &column_w
|
||||
)
|
||||
: values({ column_x, column_y, column_z, column_w })
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr auto identity() -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {
|
||||
{ 1 }, {}, {}, {}, //
|
||||
{}, { 1 }, {}, {}, //
|
||||
{}, {}, { 1 }, {}, //
|
||||
{}, {}, {}, { 1 }, //
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const mat4_impl<T> &other) const -> mat4_impl<T>
|
||||
{
|
||||
const auto &[a_x, a_y, a_z, a_w] = values;
|
||||
const auto &[b_x, b_y, b_z, b_w] = other.values;
|
||||
|
||||
return mat4_impl<T>(
|
||||
// X column
|
||||
a_x.x * b_x.x + a_y.x * b_x.y + a_z.x * b_x.z + a_w.x * b_x.w,
|
||||
a_x.y * b_x.x + a_y.y * b_x.y + a_z.y * b_x.z + a_w.y * b_x.w,
|
||||
a_x.z * b_x.x + a_y.z * b_x.y + a_z.z * b_x.z + a_w.z * b_x.w,
|
||||
a_x.w * b_x.x + a_y.w * b_x.y + a_z.w * b_x.z + a_w.w * b_x.w,
|
||||
|
||||
// Y column
|
||||
a_x.x * b_y.x + a_y.x * b_y.y + a_z.x * b_y.z + a_w.x * b_y.w,
|
||||
a_x.y * b_y.x + a_y.y * b_y.y + a_z.y * b_y.z + a_w.y * b_y.w,
|
||||
a_x.z * b_y.x + a_y.z * b_y.y + a_z.z * b_y.z + a_w.z * b_y.w,
|
||||
a_x.w * b_y.x + a_y.w * b_y.y + a_z.w * b_y.z + a_w.w * b_y.w,
|
||||
|
||||
// Z column
|
||||
a_x.x * b_z.x + a_y.x * b_z.y + a_z.x * b_z.z + a_w.x * b_z.w,
|
||||
a_x.y * b_z.x + a_y.y * b_z.y + a_z.y * b_z.z + a_w.y * b_z.w,
|
||||
a_x.z * b_z.x + a_y.z * b_z.y + a_z.z * b_z.z + a_w.z * b_z.w,
|
||||
a_x.w * b_z.x + a_y.w * b_z.y + a_z.w * b_z.z + a_w.w * b_z.w,
|
||||
|
||||
// W column
|
||||
a_x.x * b_w.x + a_y.x * b_w.y + a_z.x * b_w.z + a_w.x * b_w.w,
|
||||
a_x.y * b_w.x + a_y.y * b_w.y + a_z.y * b_w.z + a_w.y * b_w.w,
|
||||
a_x.z * b_w.x + a_y.z * b_w.y + a_z.z * b_w.z + a_w.z * b_w.w,
|
||||
a_x.w * b_w.x + a_y.w * b_w.y + a_z.w * b_w.z + a_w.w * b_w.w
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](size_t idx) -> Column_T &
|
||||
{
|
||||
debug_check(idx < num_elements, "mat4 out of bound access: {}", idx);
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const Column_T &
|
||||
{
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr auto transpose(const mat4_impl<T> &mat) -> mat4_impl<T>
|
||||
{
|
||||
const auto &[x, y, z, w] = mat.values;
|
||||
return mat4_impl<T> {
|
||||
x.x, y.x, z.x, w.x, x.y, y.y, z.y, w.y, x.z, y.z, z.z, w.z, x.w, y.w, z.w, w.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr auto translate(const vec3_impl<T> &vec) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T>(
|
||||
T { 1 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
T { 1 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 1 },
|
||||
T { 0 },
|
||||
|
||||
vec.x,
|
||||
vec.y,
|
||||
vec.z,
|
||||
T { 1 }
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr auto scale(const vec3_impl<T> &vec) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T>(
|
||||
vec.x,
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
vec.y,
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
vec.z,
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 1 }
|
||||
);
|
||||
}
|
||||
|
||||
std::array<Column_T, 4u> values;
|
||||
};
|
||||
|
||||
using mat4 = mat4_impl<f32>;
|
||||
|
||||
using mat4_f32 = mat4;
|
||||
using mat4_f64 = mat4_impl<f64>;
|
||||
|
||||
using mat4_i8 = mat4_impl<i8>;
|
||||
using mat4_i16 = mat4_impl<i16>;
|
||||
using mat4_i32 = mat4_impl<i32>;
|
||||
using mat4_i64 = mat4_impl<i64>;
|
||||
|
||||
using mat4_u8 = mat4_impl<u8>;
|
||||
using mat4_u16 = mat4_impl<u16>;
|
||||
using mat4_u32 = mat4_impl<u32>;
|
||||
using mat4_u64 = mat4_impl<u64>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
|
@ -1,413 +0,0 @@
|
|||
import test;
|
||||
import math.vec3;
|
||||
import math.mat4;
|
||||
|
||||
using vec3 = ::lt::math::vec3;
|
||||
using mat4 = ::lt::math::mat4;
|
||||
|
||||
Suite static_tests = "mat4_static_checks"_suite = [] {
|
||||
constexpr auto num_elements = lt::math::mat4::num_elements;
|
||||
|
||||
static_assert(num_elements == 4u * 4u);
|
||||
static_assert(std::is_same_v<lt::math::mat4, lt::math::mat4_f32>);
|
||||
|
||||
static_assert(sizeof(lt::math::mat4_f32) == sizeof(f32) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_f64) == sizeof(f64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::mat4_i8) == sizeof(i8) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_i16) == sizeof(i16) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_i32) == sizeof(i32) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_i64) == sizeof(i64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::mat4_u8) == sizeof(u8) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_u16) == sizeof(u16) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_u32) == sizeof(u32) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_u64) == sizeof(u64) * num_elements);
|
||||
};
|
||||
|
||||
Suite raii = "mat4_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = mat4 {};
|
||||
ignore = mat4 { 1.0 };
|
||||
ignore = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
};
|
||||
ignore = mat4 {
|
||||
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
|
||||
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
|
||||
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
|
||||
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
|
||||
};
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = mat4 {};
|
||||
ignore = mat4 { 1.0 };
|
||||
ignore = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
|
||||
9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
};
|
||||
ignore = mat4 {
|
||||
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
|
||||
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
|
||||
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
|
||||
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post default construct has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4 {}.values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T {});
|
||||
expect_eq(x[1], mat4::Underlying_T {});
|
||||
expect_eq(x[2], mat4::Underlying_T {});
|
||||
expect_eq(x[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T {});
|
||||
expect_eq(y[1], mat4::Underlying_T {});
|
||||
expect_eq(y[2], mat4::Underlying_T {});
|
||||
expect_eq(y[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T {});
|
||||
expect_eq(z[1], mat4::Underlying_T {});
|
||||
expect_eq(z[2], mat4::Underlying_T {});
|
||||
expect_eq(z[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T {});
|
||||
expect_eq(w[1], mat4::Underlying_T {});
|
||||
expect_eq(w[2], mat4::Underlying_T {});
|
||||
expect_eq(w[3], mat4::Underlying_T {});
|
||||
};
|
||||
|
||||
Case { "post scalar construct has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4 { 69.0 }.values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(x[1], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(x[2], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(x[3], mat4::Underlying_T { 69.0 });
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(y[1], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(y[2], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(y[3], mat4::Underlying_T { 69.0 });
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(z[1], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(z[2], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(z[3], mat4::Underlying_T { 69.0 });
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(w[1], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(w[2], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(w[3], mat4::Underlying_T { 69.0 });
|
||||
};
|
||||
|
||||
Case { "post construct with all values has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
}.values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T { 1.0 });
|
||||
expect_eq(x[1], mat4::Underlying_T { 2.0 });
|
||||
expect_eq(x[2], mat4::Underlying_T { 3.0 });
|
||||
expect_eq(x[3], mat4::Underlying_T { 4.0 });
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T { 5.0 });
|
||||
expect_eq(y[1], mat4::Underlying_T { 6.0 });
|
||||
expect_eq(y[2], mat4::Underlying_T { 7.0 });
|
||||
expect_eq(y[3], mat4::Underlying_T { 8.0 });
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T { 9.0 });
|
||||
expect_eq(z[1], mat4::Underlying_T { 10.0 });
|
||||
expect_eq(z[2], mat4::Underlying_T { 11.0 });
|
||||
expect_eq(z[3], mat4::Underlying_T { 12.0 });
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T { 13.0 });
|
||||
expect_eq(w[1], mat4::Underlying_T { 14.0 });
|
||||
expect_eq(w[2], mat4::Underlying_T { 15.0 });
|
||||
expect_eq(w[3], mat4::Underlying_T { 16.0 });
|
||||
};
|
||||
|
||||
Case { "post construct with columns has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4 {
|
||||
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
|
||||
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
|
||||
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
|
||||
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
|
||||
}.values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T { 1.0 });
|
||||
expect_eq(x[1], mat4::Underlying_T { 2.0 });
|
||||
expect_eq(x[2], mat4::Underlying_T { 3.0 });
|
||||
expect_eq(x[3], mat4::Underlying_T { 4.0 });
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T { 5.0 });
|
||||
expect_eq(y[1], mat4::Underlying_T { 6.0 });
|
||||
expect_eq(y[2], mat4::Underlying_T { 7.0 });
|
||||
expect_eq(y[3], mat4::Underlying_T { 8.0 });
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T { 9.0 });
|
||||
expect_eq(z[1], mat4::Underlying_T { 10.0 });
|
||||
expect_eq(z[2], mat4::Underlying_T { 11.0 });
|
||||
expect_eq(z[3], mat4::Underlying_T { 12.0 });
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T { 13.0 });
|
||||
expect_eq(w[1], mat4::Underlying_T { 14.0 });
|
||||
expect_eq(w[2], mat4::Underlying_T { 15.0 });
|
||||
expect_eq(w[3], mat4::Underlying_T { 16.0 });
|
||||
};
|
||||
|
||||
Case { "post construct identity matrix has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4::identity().values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T { 1 });
|
||||
expect_eq(x[1], mat4::Underlying_T {});
|
||||
expect_eq(x[2], mat4::Underlying_T {});
|
||||
expect_eq(x[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T {});
|
||||
expect_eq(y[1], mat4::Underlying_T { 1 });
|
||||
expect_eq(y[2], mat4::Underlying_T {});
|
||||
expect_eq(y[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T {});
|
||||
expect_eq(z[1], mat4::Underlying_T {});
|
||||
expect_eq(z[2], mat4::Underlying_T { 1 });
|
||||
expect_eq(z[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T {});
|
||||
expect_eq(w[1], mat4::Underlying_T {});
|
||||
expect_eq(w[2], mat4::Underlying_T {});
|
||||
expect_eq(w[3], mat4::Underlying_T { 1 });
|
||||
};
|
||||
};
|
||||
|
||||
Suite arithmetic_operators = "mat4_arithmetic_operators"_suite = [] {
|
||||
Case { "operator *" } = [] {
|
||||
const auto lhs = mat4 {
|
||||
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
|
||||
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
|
||||
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
|
||||
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
|
||||
};
|
||||
|
||||
const auto rhs = mat4 {
|
||||
mat4::Column_T { 17.0, 18.0, 19.0, 20.0 },
|
||||
mat4::Column_T { 21.0, 22.0, 23.0, 24.0 },
|
||||
mat4::Column_T { 25.0, 26.0, 27.0, 28.0 },
|
||||
mat4::Column_T { 29.0, 30.0, 31.0, 32.0 },
|
||||
};
|
||||
|
||||
const auto [x, y, z, w] = (lhs * rhs).values;
|
||||
|
||||
expect_eq(x[0], 538.0);
|
||||
expect_eq(x[1], 612.0);
|
||||
expect_eq(x[2], 686.0);
|
||||
expect_eq(x[3], 760.0);
|
||||
|
||||
expect_eq(y[0], 650.0);
|
||||
expect_eq(y[1], 740.0);
|
||||
expect_eq(y[2], 830.0);
|
||||
expect_eq(y[3], 920.0);
|
||||
|
||||
expect_eq(z[0], 762.0);
|
||||
expect_eq(z[1], 868.0);
|
||||
expect_eq(z[2], 974.0);
|
||||
expect_eq(z[3], 1080.0);
|
||||
|
||||
expect_eq(w[0], 874.0);
|
||||
expect_eq(w[1], 996.0);
|
||||
expect_eq(w[2], 1118.0);
|
||||
expect_eq(w[3], 1240.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite access_operators = "mat4_access_operators"_suite = [] {
|
||||
Case { "operator []" } = [] {
|
||||
auto mat = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
};
|
||||
|
||||
expect_eq(mat[0][0], 1.0);
|
||||
expect_eq(mat[0][1], 2.0);
|
||||
expect_eq(mat[0][2], 3.0);
|
||||
expect_eq(mat[0][3], 4.0);
|
||||
|
||||
expect_eq(mat[1][0], 5.0);
|
||||
expect_eq(mat[1][1], 6.0);
|
||||
expect_eq(mat[1][2], 7.0);
|
||||
expect_eq(mat[1][3], 8.0);
|
||||
|
||||
expect_eq(mat[2][0], 9.0);
|
||||
expect_eq(mat[2][1], 10.0);
|
||||
expect_eq(mat[2][2], 11.0);
|
||||
expect_eq(mat[2][3], 12.0);
|
||||
|
||||
expect_eq(mat[3][0], 13.0);
|
||||
expect_eq(mat[3][1], 14.0);
|
||||
expect_eq(mat[3][2], 15.0);
|
||||
expect_eq(mat[3][3], 16.0);
|
||||
};
|
||||
|
||||
Case { "operator [] const" } = [] {
|
||||
const auto mat = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
};
|
||||
|
||||
expect_eq(mat[0][0], 1.0);
|
||||
expect_eq(mat[0][1], 2.0);
|
||||
expect_eq(mat[0][2], 3.0);
|
||||
expect_eq(mat[0][3], 4.0);
|
||||
|
||||
expect_eq(mat[1][0], 5.0);
|
||||
expect_eq(mat[1][1], 6.0);
|
||||
expect_eq(mat[1][2], 7.0);
|
||||
expect_eq(mat[1][3], 8.0);
|
||||
|
||||
expect_eq(mat[2][0], 9.0);
|
||||
expect_eq(mat[2][1], 10.0);
|
||||
expect_eq(mat[2][2], 11.0);
|
||||
expect_eq(mat[2][3], 12.0);
|
||||
|
||||
expect_eq(mat[3][0], 13.0);
|
||||
expect_eq(mat[3][1], 14.0);
|
||||
expect_eq(mat[3][2], 15.0);
|
||||
expect_eq(mat[3][3], 16.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite transformations = "mat4_transformations"_suite = [] {
|
||||
Case { "translate" } = [] {
|
||||
const auto &[x, y, z, w] = mat4::translate(vec3 { 1, 2, 3 }).values;
|
||||
|
||||
// identity basis
|
||||
expect_eq(x[0], 1);
|
||||
expect_eq(x[1], 0);
|
||||
expect_eq(x[2], 0);
|
||||
expect_eq(x[3], 0);
|
||||
expect_eq(y[0], 0);
|
||||
expect_eq(y[1], 1);
|
||||
expect_eq(y[2], 0);
|
||||
expect_eq(y[3], 0);
|
||||
expect_eq(z[0], 0);
|
||||
expect_eq(z[1], 0);
|
||||
expect_eq(z[2], 1);
|
||||
expect_eq(z[3], 0);
|
||||
|
||||
// translation column
|
||||
expect_eq(w[0], 1);
|
||||
expect_eq(w[1], 2);
|
||||
expect_eq(w[2], 3);
|
||||
expect_eq(w[3], 1);
|
||||
};
|
||||
|
||||
Case { "scale" } = [] {
|
||||
const auto [x, y, z, w] = mat4::scale(vec3 { 2, 3, 4 }).values;
|
||||
|
||||
expect_eq(x[0], 2);
|
||||
expect_eq(x[1], 0);
|
||||
expect_eq(x[2], 0);
|
||||
expect_eq(x[3], 0);
|
||||
expect_eq(y[0], 0);
|
||||
expect_eq(y[1], 3);
|
||||
expect_eq(y[2], 0);
|
||||
expect_eq(y[3], 0);
|
||||
expect_eq(z[0], 0);
|
||||
expect_eq(z[1], 0);
|
||||
expect_eq(z[2], 4);
|
||||
expect_eq(z[3], 0);
|
||||
expect_eq(w[0], 0);
|
||||
expect_eq(w[1], 0);
|
||||
expect_eq(w[2], 0);
|
||||
expect_eq(w[3], 1);
|
||||
};
|
||||
|
||||
Case { "scale -> translate" } = [] {
|
||||
const auto scale = mat4::scale(vec3 { 2, 2, 2 });
|
||||
const auto translate = mat4::translate(vec3 { 1, 2, 3 });
|
||||
const auto [x, y, z, w] = (scale * translate).values;
|
||||
|
||||
// scaled basis
|
||||
expect_eq(x[0], 2);
|
||||
expect_eq(x[1], 0);
|
||||
expect_eq(x[2], 0);
|
||||
expect_eq(x[3], 0);
|
||||
expect_eq(y[0], 0);
|
||||
expect_eq(y[1], 2);
|
||||
expect_eq(y[2], 0);
|
||||
expect_eq(y[3], 0);
|
||||
expect_eq(z[0], 0);
|
||||
expect_eq(z[1], 0);
|
||||
expect_eq(z[2], 2);
|
||||
expect_eq(z[3], 0);
|
||||
|
||||
// translation is scaled (local-space translation)
|
||||
expect_eq(w[0], 2); // 1 * 2
|
||||
expect_eq(w[1], 4); // 2 * 2
|
||||
expect_eq(w[2], 6); // 3 * 2
|
||||
expect_eq(w[3], 1);
|
||||
};
|
||||
|
||||
Case { "transpose" } = [] {
|
||||
const auto mat = mat4 {
|
||||
mat4::Column_T { 1, 2, 3, 4 },
|
||||
mat4::Column_T { 5, 6, 7, 8 },
|
||||
mat4::Column_T { 9, 10, 11, 12 },
|
||||
mat4::Column_T { 13, 14, 15, 16 },
|
||||
};
|
||||
const auto [x, y, z, w] = mat4::transpose(mat).values;
|
||||
|
||||
// rows become columns
|
||||
expect_eq(x[0], 1);
|
||||
expect_eq(x[1], 5);
|
||||
expect_eq(x[2], 9);
|
||||
expect_eq(x[3], 13);
|
||||
expect_eq(y[0], 2);
|
||||
expect_eq(y[1], 6);
|
||||
expect_eq(y[2], 10);
|
||||
expect_eq(y[3], 14);
|
||||
expect_eq(z[0], 3);
|
||||
expect_eq(z[1], 7);
|
||||
expect_eq(z[2], 11);
|
||||
expect_eq(z[3], 15);
|
||||
expect_eq(w[0], 4);
|
||||
expect_eq(w[1], 8);
|
||||
expect_eq(w[2], 12);
|
||||
expect_eq(w[3], 16);
|
||||
};
|
||||
|
||||
Case { "transpose twice" } = [] {
|
||||
const auto mat = mat4 {
|
||||
mat4::Column_T { 1, 2, 3, 4 },
|
||||
mat4::Column_T { 5, 6, 7, 8 },
|
||||
mat4::Column_T { 9, 10, 11, 12 },
|
||||
mat4::Column_T { 13, 14, 15, 16 },
|
||||
};
|
||||
const auto [x, y, z, w] = mat4::transpose(mat4::transpose(mat)).values;
|
||||
|
||||
expect_eq(x[0], 1);
|
||||
expect_eq(x[1], 2);
|
||||
expect_eq(x[2], 3);
|
||||
expect_eq(x[3], 4);
|
||||
expect_eq(y[0], 5);
|
||||
expect_eq(y[1], 6);
|
||||
expect_eq(y[2], 7);
|
||||
expect_eq(y[3], 8);
|
||||
expect_eq(z[0], 9);
|
||||
expect_eq(z[1], 10);
|
||||
expect_eq(z[2], 11);
|
||||
expect_eq(z[3], 12);
|
||||
expect_eq(w[0], 13);
|
||||
expect_eq(w[1], 14);
|
||||
expect_eq(w[2], 15);
|
||||
expect_eq(w[3], 16);
|
||||
};
|
||||
};
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
export module math.algebra;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
import math.mat4;
|
||||
#include <math/mat4.hpp>
|
||||
|
||||
export namespace lt::math {
|
||||
namespace lt::math {
|
||||
|
||||
/**
|
||||
* let...
|
||||
|
|
@ -32,30 +31,25 @@ export namespace lt::math {
|
|||
*
|
||||
* the 1 at [z][3] is to save the Z axis into the resulting W for perspective division.
|
||||
*
|
||||
* @ref Thanks to @pikuma for explaining the math behind this:
|
||||
* https://www.youtube.com/watch?v=EqNcqBdrNyI
|
||||
* thanks to pikuma: https://www.youtube.com/watch?v=EqNcqBdrNyI
|
||||
*/
|
||||
template<typename T>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far) -> mat4_impl<T>
|
||||
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far)
|
||||
{
|
||||
const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2));
|
||||
|
||||
auto result = mat4_impl<T>::identity();
|
||||
auto result = mat4_impl<T> { T { 0 } };
|
||||
|
||||
result[0][0] = T { 1 } / (aspect_ratio * half_fov_tan);
|
||||
//
|
||||
|
||||
result[1][1] = T { 1 } / (half_fov_tan);
|
||||
//
|
||||
// result[2][2] = -(z_far + z_near) / (z_far - z_near);
|
||||
//
|
||||
result[2][2] = z_far / (z_far - z_near);
|
||||
//
|
||||
|
||||
result[2][2] = -(z_far + z_near) / (z_far - z_near);
|
||||
|
||||
result[2][3] = -T { 1 };
|
||||
//
|
||||
// result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
|
||||
result[3][2] = -(z_far * z_near) / (z_far - z_near);
|
||||
//
|
||||
|
||||
result[3][2] = -(T { 2 } * z_far * z_near) / (z_far - z_near);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
109
modules/math/public/mat4.hpp
Normal file
109
modules/math/public/mat4.hpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#pragma once
|
||||
|
||||
#include <math/vec3.hpp>
|
||||
#include <math/vec4.hpp>
|
||||
|
||||
namespace lt::math {
|
||||
|
||||
template<typename T = float>
|
||||
struct mat4_impl
|
||||
{
|
||||
using Column_T = vec4_impl<T>;
|
||||
constexpr explicit mat4_impl(T scalar = 0)
|
||||
: values(
|
||||
{
|
||||
Column_T { scalar },
|
||||
Column_T { scalar },
|
||||
Column_T { scalar },
|
||||
Column_T { scalar },
|
||||
}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
constexpr mat4_impl(
|
||||
const T& x0, const T& y0, const T& z0, const T& w0,
|
||||
const T& x1, const T& y1, const T& z1, const T& w1,
|
||||
const T& x2, const T& y2, const T& z2, const T& w2,
|
||||
const T& x3, const T& y3, const T& z3, const T& w3
|
||||
)
|
||||
// clang-format on
|
||||
: values({ { x0, x1, x2, x3 }, { y0, y1, y2, y3 }, { z0, z1, z2, z3 }, { w0, w1, w2, w3 } })
|
||||
{
|
||||
}
|
||||
|
||||
constexpr mat4_impl(
|
||||
const Column_T &column_x,
|
||||
const Column_T &column_y,
|
||||
const Column_T &column_z,
|
||||
const Column_T &column_w
|
||||
)
|
||||
: values({ column_x, column_y, column_z, column_w })
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto identity() -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {
|
||||
{ 1 }, {}, {}, {}, //
|
||||
{}, { 1 }, {}, {}, //
|
||||
{}, {}, { 1 }, {}, //
|
||||
{}, {}, {}, { 1 }, //
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](size_t idx) -> Column_T &
|
||||
{
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const Column_T &
|
||||
{
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const mat4_impl<T> &other) const -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const vec4_impl<T> &other) const -> vec4_impl<T>
|
||||
{
|
||||
return vec4_impl<T> {};
|
||||
}
|
||||
|
||||
std::array<Column_T, 4> values; // NOLINT
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] inline auto translate(const vec3_impl<T> &value) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] inline auto rotate(float value, const vec3_impl<T> &xyz) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] inline auto scale(const vec3_impl<T> &value) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] inline auto inverse(const mat4_impl<T> &value) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
using mat4 = mat4_impl<float>;
|
||||
|
||||
using imat4 = mat4_impl<int32_t>;
|
||||
|
||||
using umat4 = mat4_impl<uint32_t>;
|
||||
|
||||
} // namespace lt::math
|
||||
26
modules/math/public/trig.hpp
Normal file
26
modules/math/public/trig.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::math {
|
||||
|
||||
[[nodiscard]] constexpr auto radians(float degrees) -> float
|
||||
{
|
||||
return degrees * 0.01745329251994329576923690768489f;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto radians(double degrees) -> double
|
||||
{
|
||||
return degrees * 0.01745329251994329576923690768489;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto degrees(float radians) -> float
|
||||
{
|
||||
return radians * 57.295779513082320876798154814105f;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto degrees(double radians) -> double
|
||||
{
|
||||
return radians * 57.295779513082320876798154814105;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lt::math
|
||||
80
modules/math/public/vec2.hpp
Normal file
80
modules/math/public/vec2.hpp
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::math {
|
||||
|
||||
template<typename T = float>
|
||||
struct vec2_impl
|
||||
{
|
||||
constexpr vec2_impl(): x(), y()
|
||||
{
|
||||
}
|
||||
|
||||
constexpr explicit vec2_impl(T scalar): x(scalar), y(scalar)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec2_impl(T x, T y): x(x), y(y)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const vec2_impl<T> &other) const -> bool
|
||||
{
|
||||
return x == other.x && y == other.y;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator!=(const vec2_impl<T> &other) const -> bool
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator*(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x * other.x,
|
||||
y * other.y,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator-(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x - other.x,
|
||||
y - other.y,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator*(float scalar) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x * scalar,
|
||||
y * scalar,
|
||||
};
|
||||
}
|
||||
|
||||
T x; // NOLINT
|
||||
|
||||
T y; // NOLINT
|
||||
};
|
||||
|
||||
|
||||
using vec2 = vec2_impl<float>;
|
||||
|
||||
using ivec2 = vec2_impl<int32_t>;
|
||||
|
||||
using uvec2 = vec2_impl<uint32_t>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
||||
template<typename T>
|
||||
struct std::formatter<lt::math::vec2_impl<T>>
|
||||
{
|
||||
constexpr auto parse(std::format_parse_context &context)
|
||||
{
|
||||
return context.begin();
|
||||
}
|
||||
|
||||
auto format(const lt::math::vec2_impl<T> &val, std::format_context &context) const
|
||||
{
|
||||
return std::format_to(context.out(), "{}, {}", val.x, val.y);
|
||||
}
|
||||
};
|
||||
84
modules/math/public/vec3.hpp
Normal file
84
modules/math/public/vec3.hpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <math/vec2.hpp>
|
||||
|
||||
namespace lt::math {
|
||||
|
||||
template<typename T = float>
|
||||
struct vec3_impl
|
||||
{
|
||||
constexpr vec3_impl(): x(), y(), z()
|
||||
{
|
||||
}
|
||||
|
||||
constexpr explicit vec3_impl(T scalar): x(scalar), y(scalar), z(scalar)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec3_impl(T x, T y, T z): x(x), y(y), z(z)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const vec3_impl<T> &other) const -> bool
|
||||
{
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator!=(const vec3_impl<T> &other) const -> bool
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator-(const vec3_impl<T> &other) const -> vec3_impl
|
||||
{
|
||||
return {
|
||||
x - other.x,
|
||||
y - other.y,
|
||||
z - other.z,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const vec3_impl<T> &other) const -> vec3_impl
|
||||
{
|
||||
return {
|
||||
x * other.x,
|
||||
y * other.y,
|
||||
z * other.z,
|
||||
};
|
||||
}
|
||||
|
||||
friend auto operator<<(std::ostream &stream, vec3_impl<T> value) -> std::ostream &
|
||||
{
|
||||
stream << value.x << ", " << value.y << ", " << value.z;
|
||||
return stream;
|
||||
}
|
||||
|
||||
T x; // NOLINT
|
||||
|
||||
T y; // NOLINT
|
||||
|
||||
T z; // NOLINT
|
||||
};
|
||||
|
||||
using vec3 = vec3_impl<float>;
|
||||
|
||||
using ivec3 = vec3_impl<int32_t>;
|
||||
|
||||
using uvec3 = vec3_impl<uint32_t>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
||||
template<typename T>
|
||||
struct std::formatter<lt::math::vec3_impl<T>>
|
||||
{
|
||||
constexpr auto parse(std::format_parse_context &context)
|
||||
{
|
||||
return context.begin();
|
||||
}
|
||||
|
||||
auto format(const lt::math::vec3_impl<T> &val, std::format_context &context) const
|
||||
{
|
||||
return std::format_to(context.out(), "{}, {}, {}", val.x, val.y, val.z);
|
||||
}
|
||||
};
|
||||
109
modules/math/public/vec4.hpp
Normal file
109
modules/math/public/vec4.hpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace lt::math {
|
||||
|
||||
template<typename T = float>
|
||||
struct vec4_impl
|
||||
{
|
||||
constexpr vec4_impl(): x(), y(), z(), w()
|
||||
{
|
||||
}
|
||||
|
||||
constexpr explicit vec4_impl(T scalar): x(scalar), y(scalar), z(scalar), w(scalar)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(T x, T y, T z, T w): x(x), y(y), z(z), w(w)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const vec4_impl<T> &other) const -> bool
|
||||
{
|
||||
return x == other.x && y == other.y && z == other.z && w == other.w;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator!=(const vec4_impl<T> &other) const -> bool
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator-(const vec4_impl<T> &other) const -> vec4_impl
|
||||
{
|
||||
return {
|
||||
x - other.x,
|
||||
y - other.y,
|
||||
z - other.z,
|
||||
w - other.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](size_t idx) -> T &
|
||||
{
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const T &
|
||||
{
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
friend auto operator<<(std::ostream &stream, vec4_impl<T> value) -> std::ostream &
|
||||
{
|
||||
stream << value.x << ", " << value.y << ", " << value.z << ", " << value.w;
|
||||
return stream;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
T x;
|
||||
|
||||
T y;
|
||||
|
||||
T z;
|
||||
|
||||
T w;
|
||||
};
|
||||
struct
|
||||
{
|
||||
T r;
|
||||
|
||||
T g;
|
||||
|
||||
T b;
|
||||
|
||||
T a;
|
||||
};
|
||||
struct
|
||||
{
|
||||
std::array<T, 4> values;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
using vec4 = vec4_impl<float>;
|
||||
|
||||
using ivec4 = vec4_impl<int32_t>;
|
||||
|
||||
using uvec4 = vec4_impl<uint32_t>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
||||
template<typename T>
|
||||
struct std::formatter<lt::math::vec4_impl<T>>
|
||||
{
|
||||
constexpr auto parse(std::format_parse_context &context)
|
||||
{
|
||||
return context.begin();
|
||||
}
|
||||
|
||||
auto format(const lt::math::vec4_impl<T> &val, std::format_context &context) const
|
||||
{
|
||||
return std::format_to(context.out(), "{}, {}, {}, {}", val.x, val.y, val.z, val.w);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
export module math.trig;
|
||||
|
||||
import preliminary;
|
||||
|
||||
export namespace lt::math {
|
||||
|
||||
[[nodiscard]] constexpr auto to_radians(f32 degrees) -> f32
|
||||
{
|
||||
return degrees * 0.01745329251994329576923690768489f;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto to_radians(f64 degrees) -> f64
|
||||
{
|
||||
return degrees * 0.01745329251994329576923690768489;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto to_degrees(f32 radians) -> f32
|
||||
{
|
||||
return radians * 57.295779513082320876798154814105f;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto to_degrees(f64 radians) -> f64
|
||||
{
|
||||
return radians * 57.295779513082320876798154814105;
|
||||
}
|
||||
|
||||
} // namespace lt::math
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import test;
|
||||
import math.trig;
|
||||
|
||||
Suite conversions = "trig_conversions"_suite = [] {
|
||||
using ::lt::math::to_degrees;
|
||||
using ::lt::math::to_radians;
|
||||
|
||||
Case { "to_radians <f32>" } = [] {
|
||||
expect_eq(to_radians(f32 { 0.0f }), f32 { 0.0f });
|
||||
expect_eq(to_radians(f32 { 90.0f }), f32 { 1.5707963267948966f });
|
||||
expect_eq(to_radians(f32 { 180.0f }), f32 { 3.1415926535897932f });
|
||||
expect_eq(to_radians(f32 { 360.0f }), f32 { 6.2831853071795864f });
|
||||
};
|
||||
|
||||
Case { "to_radians <f64>" } = [] {
|
||||
expect_eq(to_radians(f64 { 0.0 }), f64 { 0.0 });
|
||||
expect_eq(to_radians(f64 { 90.0 }), f64 { 1.5707963267948966 });
|
||||
expect_eq(to_radians(f64 { 180.0 }), f64 { 3.1415926535897932 });
|
||||
expect_eq(to_radians(f64 { 360.0 }), f64 { 6.2831853071795864 });
|
||||
};
|
||||
|
||||
Case { "to_degrees <f32>" } = [] {
|
||||
expect_eq(to_degrees(f32 { 0.0f }), f32 { 0.0f });
|
||||
expect_eq(to_degrees(f32 { 1.5707963267948966f }), f32 { 90.0f });
|
||||
expect_eq(to_degrees(f32 { 3.1415926535897932f }), f32 { 180.0f });
|
||||
expect_eq(to_degrees(f32 { 6.2831853071795864f }), f32 { 360.0f });
|
||||
};
|
||||
|
||||
Case { "to_degrees <f64>" } = [] {
|
||||
expect_eq(to_degrees(f64 { 0.0 }), f64 { 0.0 });
|
||||
expect_eq(to_degrees(f64 { 1.5707963267948966 }), f64 { 90.0 });
|
||||
expect_eq(to_degrees(f64 { 3.1415926535897932 }), f64 { 180.0 });
|
||||
expect_eq(to_degrees(f64 { 6.2831853071795864 }), f64 { 360.0 });
|
||||
};
|
||||
|
||||
Case { "to_degrees -> to_radians -> to_degrees <f32>" } = [] {
|
||||
expect_eq(to_degrees(to_radians(f32 { 45.0f })), f32 { 45.0f });
|
||||
};
|
||||
|
||||
Case { "to_degrees -> to_radians -> to_degrees <f64>" } = [] {
|
||||
expect_eq(to_degrees(to_radians(f64 { 45.0 })), f64 { 45.0 });
|
||||
};
|
||||
};
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
export module math.vec2;
|
||||
|
||||
import preliminary;
|
||||
|
||||
export namespace lt::math {
|
||||
|
||||
template<typename T = f32>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
struct vec2_impl
|
||||
{
|
||||
using Underlying_T = T;
|
||||
|
||||
static constexpr auto num_elements = 2u;
|
||||
|
||||
constexpr vec2_impl(): x(), y()
|
||||
{
|
||||
}
|
||||
|
||||
constexpr explicit vec2_impl(T scalar): x(scalar), y(scalar)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec2_impl(T x, T y): x(x), y(y)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const vec2_impl<T> &other) const -> bool
|
||||
{
|
||||
return x == other.x && y == other.y;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator!=(const vec2_impl<T> &other) const -> bool
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator+(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x + other.x,
|
||||
y + other.y,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator-(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x - other.x,
|
||||
y - other.y,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x * other.x,
|
||||
y * other.y,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator/(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x / other.x,
|
||||
y / other.y,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) -> T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec2 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) const -> const T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec2 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
|
||||
T x;
|
||||
|
||||
T y;
|
||||
};
|
||||
|
||||
using vec2 = vec2_impl<f32>;
|
||||
|
||||
using vec2_f32 = vec2;
|
||||
using vec2_f64 = vec2_impl<f64>;
|
||||
|
||||
using vec2_i8 = vec2_impl<i8>;
|
||||
using vec2_i16 = vec2_impl<i16>;
|
||||
using vec2_i32 = vec2_impl<i32>;
|
||||
using vec2_i64 = vec2_impl<i64>;
|
||||
|
||||
using vec2_u8 = vec2_impl<u8>;
|
||||
using vec2_u16 = vec2_impl<u16>;
|
||||
using vec2_u32 = vec2_impl<u32>;
|
||||
using vec2_u64 = vec2_impl<u64>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
||||
export template<typename T>
|
||||
struct std::formatter<lt::math::vec2_impl<T>>
|
||||
{
|
||||
constexpr auto parse(std::format_parse_context &context)
|
||||
{
|
||||
return context.begin();
|
||||
}
|
||||
|
||||
auto format(const lt::math::vec2_impl<T> &val, std::format_context &context) const
|
||||
{
|
||||
return std::format_to(context.out(), "{}, {}", val.x, val.y);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
import test;
|
||||
import math.vec2;
|
||||
|
||||
using vec2 = ::lt::math::vec2;
|
||||
using ivec2 = ::lt::math::vec2_i32;
|
||||
|
||||
Suite static_tests = "vec3_static_checks"_suite = [] {
|
||||
constexpr auto num_elements = lt::math::vec2::num_elements;
|
||||
|
||||
static_assert(num_elements == 2u);
|
||||
static_assert(std::is_same_v<lt::math::vec2, lt::math::vec2_f32>);
|
||||
|
||||
static_assert(sizeof(lt::math::vec2_f32) == sizeof(f32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_f64) == sizeof(f64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec2_i8) == sizeof(i8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_i16) == sizeof(i16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_i32) == sizeof(i32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_i64) == sizeof(i64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec2_u8) == sizeof(u8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_u16) == sizeof(u16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_u32) == sizeof(u32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_u64) == sizeof(u64) * num_elements);
|
||||
};
|
||||
|
||||
Suite raii = "vec2_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = vec2 {};
|
||||
ignore = vec2 { 2.0 };
|
||||
ignore = vec2 { 2.0, 4.0 };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = vec2 {};
|
||||
ignore = vec2 { 2.0 };
|
||||
ignore = vec2 { 2.0, 4.0 };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post default construct has correct state" } = [] {
|
||||
const auto vec = vec2 {};
|
||||
expect_eq(vec.x, 0.0);
|
||||
expect_eq(vec.y, 0.0);
|
||||
};
|
||||
|
||||
Case { "post scalar construct has correct state" } = [] {
|
||||
const auto vec = vec2 { 2.0 };
|
||||
expect_eq(vec.x, 2.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y has correct state" } = [] {
|
||||
const auto vec = vec2 { 2.0, 3.0 };
|
||||
expect_eq(vec.x, 2.0);
|
||||
expect_eq(vec.y, 3.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite arithmetic_operators = "vec2_operators"_suite = [] {
|
||||
Case { "operator ==" } = [] {
|
||||
const auto lhs = vec2 { 1.0, 2.0 };
|
||||
|
||||
expect_false(lhs == vec2 { {}, 2.0 });
|
||||
expect_false(lhs == vec2 { 1.0, {} });
|
||||
expect_true(lhs == vec2 { 1.0, 2.0 });
|
||||
};
|
||||
|
||||
Case { "operator !=" } = [] {
|
||||
const auto lhs = vec2 { 1.0, 2.0 };
|
||||
|
||||
expect_true(lhs != vec2 { {}, 2.0 });
|
||||
expect_true(lhs != vec2 { 1.0, {} });
|
||||
expect_false(lhs != vec2 { 1.0, 2.0 });
|
||||
};
|
||||
|
||||
Case { "operator +" } = [] {
|
||||
const auto lhs = vec2 { 2.0, 3.0 };
|
||||
const auto rhs = vec2 { 4.0, 5.0 };
|
||||
expect_eq(lhs + rhs, vec2 { 6.0, 8.0 });
|
||||
};
|
||||
|
||||
Case { "operator -" } = [] {
|
||||
const auto lhs = vec2 { 2.0, 3.0 };
|
||||
const auto rhs = vec2 { 4.0, 6.0 };
|
||||
expect_eq(lhs - rhs, vec2 { -2.0, -3.0 });
|
||||
};
|
||||
|
||||
Case { "operator *" } = [] {
|
||||
const auto lhs = vec2 { 2.0, 3.0 };
|
||||
const auto rhs = vec2 { 10.0, 20.0 };
|
||||
expect_eq(lhs * rhs, vec2 { 20.0, 60.0 });
|
||||
};
|
||||
|
||||
Case { "operator /" } = [] {
|
||||
const auto lhs = vec2 { 10.0, 20.0 };
|
||||
const auto rhs = vec2 { 2.0, 20.0 };
|
||||
expect_eq(lhs / rhs, vec2 { 5.0, 1.0 });
|
||||
};
|
||||
|
||||
Case { "operator []" } = [] {
|
||||
auto vec = vec2 { 0.0, 1.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
};
|
||||
|
||||
Case { "operator [] const" } = [] {
|
||||
const auto vec = vec2 { 0.0, 1.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite utilities = "vec2_utilities"_suite = [] {
|
||||
Case { "std::format float" } = [] {
|
||||
auto str = std::format("{}", vec2 { 10.0000f, 30.0005f });
|
||||
expect_eq(str, "10, 30.0005");
|
||||
};
|
||||
|
||||
Case { "std::format int" } = [] {
|
||||
auto str = std::format("{}", ivec2 { 10, 30 });
|
||||
expect_eq(str, "10, 30");
|
||||
};
|
||||
};
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
export module math.vec3;
|
||||
|
||||
import preliminary;
|
||||
import math.vec2;
|
||||
|
||||
export namespace lt::math {
|
||||
|
||||
template<typename T = f32>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
struct vec3_impl
|
||||
{
|
||||
using Underlying_T = T;
|
||||
|
||||
static constexpr auto num_elements = 3u;
|
||||
|
||||
constexpr vec3_impl(): x(), y(), z()
|
||||
{
|
||||
}
|
||||
|
||||
constexpr explicit vec3_impl(T scalar): x(scalar), y(scalar), z(scalar)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec3_impl(T x, T y, T z): x(x), y(y), z(z)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec3_impl(vec2_impl<T> xy, T z): x(xy.x), y(xy.y), z(z)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec3_impl(T x, vec2_impl<T> yz): x(x), y(yz.x), z(yz.y)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const vec3_impl<T> &other) const -> bool
|
||||
{
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator!=(const vec3_impl<T> &other) const -> bool
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator+(const vec3_impl<T> &other) const -> vec3_impl
|
||||
{
|
||||
return {
|
||||
x + other.x,
|
||||
y + other.y,
|
||||
z + other.z,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator-(const vec3_impl<T> &other) const -> vec3_impl
|
||||
{
|
||||
return {
|
||||
x - other.x,
|
||||
y - other.y,
|
||||
z - other.z,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const vec3_impl<T> &other) const -> vec3_impl
|
||||
{
|
||||
return {
|
||||
x * other.x,
|
||||
y * other.y,
|
||||
z * other.z,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator/(const vec3_impl<T> &other) const -> vec3_impl
|
||||
{
|
||||
return {
|
||||
x / other.x,
|
||||
y / other.y,
|
||||
z / other.z,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) -> T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec3 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) const -> const T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec3 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
|
||||
friend auto operator<<(std::ostream &stream, vec3_impl<T> value) -> std::ostream &
|
||||
{
|
||||
stream << value.x << ", " << value.y << ", " << value.z;
|
||||
return stream;
|
||||
}
|
||||
|
||||
T x;
|
||||
|
||||
T y;
|
||||
|
||||
T z;
|
||||
};
|
||||
|
||||
using vec3 = vec3_impl<f32>;
|
||||
|
||||
using vec3_f32 = vec3;
|
||||
using vec3_f64 = vec3_impl<f64>;
|
||||
|
||||
using vec3_i8 = vec3_impl<i8>;
|
||||
using vec3_i16 = vec3_impl<i16>;
|
||||
using vec3_i32 = vec3_impl<i32>;
|
||||
using vec3_i64 = vec3_impl<i64>;
|
||||
|
||||
using vec3_u8 = vec3_impl<u8>;
|
||||
using vec3_u16 = vec3_impl<u16>;
|
||||
using vec3_u32 = vec3_impl<u32>;
|
||||
using vec3_u64 = vec3_impl<u64>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
||||
export template<typename T>
|
||||
struct std::formatter<lt::math::vec3_impl<T>>
|
||||
{
|
||||
constexpr auto parse(std::format_parse_context &context)
|
||||
{
|
||||
return context.begin();
|
||||
}
|
||||
|
||||
auto format(const lt::math::vec3_impl<T> &val, std::format_context &context) const
|
||||
{
|
||||
return std::format_to(context.out(), "{}, {}, {}", val.x, val.y, val.z);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
import test;
|
||||
import math.vec2;
|
||||
import math.vec3;
|
||||
|
||||
using vec2 = ::lt::math::vec2;
|
||||
using vec3 = ::lt::math::vec3;
|
||||
using ivec3 = ::lt::math::vec3_i32;
|
||||
|
||||
Suite static_tests = "vec3_static_checks"_suite = [] {
|
||||
constexpr auto num_elements = lt::math::vec3::num_elements;
|
||||
|
||||
static_assert(num_elements == 3u);
|
||||
static_assert(std::is_same_v<lt::math::vec3, lt::math::vec3_f32>);
|
||||
|
||||
static_assert(sizeof(lt::math::vec3_f32) == sizeof(f32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_f64) == sizeof(f64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec3_i8) == sizeof(i8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_i16) == sizeof(i16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_i32) == sizeof(i32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_i64) == sizeof(i64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec3_u8) == sizeof(u8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_u16) == sizeof(u16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_u32) == sizeof(u32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_u64) == sizeof(u64) * num_elements);
|
||||
};
|
||||
|
||||
Suite raii = "vec3_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = vec3 {};
|
||||
ignore = vec3 { 2.0 };
|
||||
ignore = vec3 { 2.0, 4.0, 6.0 };
|
||||
ignore = vec3 { vec2 { 2.0, 4.0 }, 6.0 };
|
||||
ignore = vec3 { 2.0, vec2 { 4.0, 6.0 } };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = vec3 {};
|
||||
ignore = vec3 { 2.0 };
|
||||
ignore = vec3 { 2.0, 4.0, 6.0 };
|
||||
ignore = vec3 { vec2 { 2.0, 4.0 }, 6.0 };
|
||||
ignore = vec3 { 2.0, vec2 { 4.0, 6.0 } };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post default construct has correct state" } = [] {
|
||||
const auto vec = vec3 {};
|
||||
expect_eq(vec.x, 0.0);
|
||||
expect_eq(vec.y, 0.0);
|
||||
expect_eq(vec.z, 0.0);
|
||||
};
|
||||
|
||||
Case { "post scalar construct has correct state" } = [] {
|
||||
const auto vec = vec3 { 2.0 };
|
||||
expect_eq(vec.x, 2.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 2.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y,z has correct state" } = [] {
|
||||
const auto vec = vec3 { 1.0, 2.0, 3.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.y, 3.0);
|
||||
};
|
||||
|
||||
Case { "post construct with xy,z has correct state" } = [] {
|
||||
const auto vec = vec3 { vec2 { 1.0, 2.0 }, 3.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,yz has correct state" } = [] {
|
||||
const auto vec = vec3 { 1.0, vec2 { 2.0, 3.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite arithmetic_operators = "vec3_operators"_suite = [] {
|
||||
Case { "operator ==" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
|
||||
expect_false(lhs == vec3 { {}, 2.0, 3.0 });
|
||||
expect_false(lhs == vec3 { 1.0, {}, 3.0 });
|
||||
expect_false(lhs == vec3 { 1.0, 2.0, {} });
|
||||
expect_true(lhs == vec3 { 1.0, 2.0, 3.0 });
|
||||
};
|
||||
|
||||
Case { "operator !=" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
|
||||
expect_true(lhs != vec3 { {}, 2.0, 3.0 });
|
||||
expect_true(lhs != vec3 { 1.0, {}, 3.0 });
|
||||
expect_true(lhs != vec3 { 1.0, 2.0, {} });
|
||||
expect_false(lhs != vec3 { 1.0, 2.0, 3.0 });
|
||||
};
|
||||
|
||||
Case { "operator +" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
const auto rhs = vec3 { 4.0, 5.0, 6.0 };
|
||||
expect_eq(lhs + rhs, vec3 { 5.0, 7.0, 9.0 });
|
||||
};
|
||||
|
||||
Case { "operator -" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
const auto rhs = vec3 { 4.0, 5.0, 7.0 };
|
||||
expect_eq(lhs - rhs, vec3 { -2.0, -3.0, -4.0 });
|
||||
};
|
||||
|
||||
Case { "operator *" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
const auto rhs = vec3 { 4.0, 5.0, 6.0 };
|
||||
expect_eq(lhs * rhs, vec3 { 4.0, 10.0, 18.0 });
|
||||
};
|
||||
|
||||
Case { "operator /" } = [] {
|
||||
const auto lhs = vec3 { 4.0, 10.0, 30.0 };
|
||||
const auto rhs = vec3 { 1.0, 2.0, 5.0 };
|
||||
expect_eq(lhs / rhs, vec3 { 4.0, 5.0, 6.0 });
|
||||
};
|
||||
|
||||
Case { "operator []" } = [] {
|
||||
auto vec = vec3 { 0.0, 1.0, 2.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
expect_eq(vec[2], 2.0);
|
||||
};
|
||||
|
||||
Case { "operator [] const" } = [] {
|
||||
const auto vec = vec3 { 0.0, 1.0, 2.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
expect_eq(vec[2], 2.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite utilities = "vec3_utilities"_suite = [] {
|
||||
Case { "std::format float" } = [] {
|
||||
auto str = std::format("{}", vec3 { 10.0000f, 30.0005f, 40.00005f });
|
||||
expect_eq(str, "10, 30.0005, 40.00005");
|
||||
};
|
||||
|
||||
Case { "std::format int" } = [] {
|
||||
auto str = std::format("{}", ivec3 { 10, 30, 3'000'000 });
|
||||
expect_eq(str, "10, 30, 3000000");
|
||||
};
|
||||
};
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
export module math.vec4;
|
||||
|
||||
import preliminary;
|
||||
import math.vec2;
|
||||
import math.vec3;
|
||||
|
||||
export namespace lt::math {
|
||||
|
||||
template<typename T = f32>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
struct vec4_impl
|
||||
{
|
||||
using Underlying_T = T;
|
||||
|
||||
static constexpr auto num_elements = 4u;
|
||||
|
||||
constexpr vec4_impl(): x(), y(), z(), w()
|
||||
{
|
||||
}
|
||||
|
||||
constexpr explicit vec4_impl(T scalar): x(scalar), y(scalar), z(scalar), w(scalar)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(T x, T y, T z, T w): x(x), y(y), z(z), w(w)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(vec2_impl<T> xy, T z, T w): x(xy.x), y(xy.y), z(z), w(w)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(T x, vec2_impl<T> yz, T w): x(x), y(yz.x), z(yz.y), w(w)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(T x, T y, vec2_impl<T> zw): x(x), y(y), z(zw.x), w(zw.y)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(vec2_impl<T> xy, vec2_impl<T> zw): x(xy.x), y(xy.y), z(zw.x), w(zw.y)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(vec3_impl<T> xyz, T w): x(xyz.x), y(xyz.y), z(xyz.z), w(w)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(T x, vec3_impl<T> yzw): x(x), y(yzw.x), z(yzw.y), w(yzw.z)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const vec4_impl<T> &other) const -> bool
|
||||
{
|
||||
return x == other.x && y == other.y && z == other.z && w == other.w;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator!=(const vec4_impl<T> &other) const -> bool
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator+(const vec4_impl<T> &other) const -> vec4_impl
|
||||
{
|
||||
return {
|
||||
x + other.x,
|
||||
y + other.y,
|
||||
z + other.z,
|
||||
w + other.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator-(const vec4_impl<T> &other) const -> vec4_impl
|
||||
{
|
||||
return {
|
||||
x - other.x,
|
||||
y - other.y,
|
||||
z - other.z,
|
||||
w - other.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const vec4_impl<T> &other) const -> vec4_impl
|
||||
{
|
||||
return {
|
||||
x * other.x,
|
||||
y * other.y,
|
||||
z * other.z,
|
||||
w * other.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator/(const vec4_impl<T> &other) const -> vec4_impl
|
||||
{
|
||||
return {
|
||||
x / other.x,
|
||||
y / other.y,
|
||||
z / other.z,
|
||||
w / other.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) -> T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec4 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) const -> const T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec4 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
|
||||
friend auto operator<<(std::ostream &stream, vec4_impl<T> value) -> std::ostream &
|
||||
{
|
||||
stream << value.x << ", " << value.y << ", " << value.z << ", " << value.w;
|
||||
return stream;
|
||||
}
|
||||
|
||||
T x;
|
||||
|
||||
T y;
|
||||
|
||||
T z;
|
||||
|
||||
T w;
|
||||
};
|
||||
|
||||
using vec4 = vec4_impl<f32>;
|
||||
|
||||
using vec4_f32 = vec4;
|
||||
using vec4_f64 = vec4_impl<f64>;
|
||||
|
||||
using vec4_i8 = vec4_impl<i8>;
|
||||
using vec4_i16 = vec4_impl<i16>;
|
||||
using vec4_i32 = vec4_impl<i32>;
|
||||
using vec4_i64 = vec4_impl<i64>;
|
||||
|
||||
using vec4_u8 = vec4_impl<u8>;
|
||||
using vec4_u16 = vec4_impl<u16>;
|
||||
using vec4_u32 = vec4_impl<u32>;
|
||||
using vec4_u64 = vec4_impl<u64>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
||||
export template<typename T>
|
||||
struct std::formatter<lt::math::vec4_impl<T>>
|
||||
{
|
||||
constexpr auto parse(std::format_parse_context &context)
|
||||
{
|
||||
return context.begin();
|
||||
}
|
||||
|
||||
auto format(const lt::math::vec4_impl<T> &val, std::format_context &context) const
|
||||
{
|
||||
return std::format_to(context.out(), "{}, {}, {}, {}", val.x, val.y, val.z, val.w);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
import test;
|
||||
import math.vec2;
|
||||
import math.vec3;
|
||||
import math.vec4;
|
||||
import logger;
|
||||
|
||||
using vec2 = ::lt::math::vec2;
|
||||
using vec3 = ::lt::math::vec3;
|
||||
using vec4 = ::lt::math::vec4;
|
||||
using ivec4 = ::lt::math::vec4_i32;
|
||||
|
||||
Suite static_tests = "vec4_static_checks"_suite = [] {
|
||||
constexpr auto num_elements = lt::math::vec4::num_elements;
|
||||
|
||||
static_assert(num_elements == 4u);
|
||||
static_assert(std::is_same_v<lt::math::vec4, lt::math::vec4_f32>);
|
||||
|
||||
static_assert(sizeof(lt::math::vec4_f32) == sizeof(f32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_f64) == sizeof(f64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec4_i8) == sizeof(i8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_i16) == sizeof(i16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_i32) == sizeof(i32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_i64) == sizeof(i64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec4_u8) == sizeof(u8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_u16) == sizeof(u16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_u32) == sizeof(u32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_u64) == sizeof(u64) * num_elements);
|
||||
};
|
||||
|
||||
Suite raii = "vec4_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = vec4 {};
|
||||
ignore = vec4 { 2.0 };
|
||||
ignore = vec4 { 2.0, 4.0, 6.0, 8.0 };
|
||||
ignore = vec4 { vec2 { 2.0, 4.0 }, 6.0, 8.0 };
|
||||
ignore = vec4 { 2.0, 4.0, vec2 { 6.0, 8.0 } };
|
||||
ignore = vec4 { vec2 { 2.0, 4.0 }, vec2 { 6.0, 8.0 } };
|
||||
ignore = vec4 { vec3 { 2.0, 4.0, 6.0 }, 8.0 };
|
||||
ignore = vec4 { 2.0, vec3 { 4.0, 6.0, 8.0 } };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = vec4 {};
|
||||
ignore = vec4 { 2.0 };
|
||||
ignore = vec4 { 2.0, 4.0, 6.0, 8.0 };
|
||||
ignore = vec4 { vec2 { 2.0, 4.0 }, 6.0, 8.0 };
|
||||
ignore = vec4 { 2.0, 4.0, vec2 { 6.0, 8.0 } };
|
||||
ignore = vec4 { vec2 { 2.0, 4.0 }, vec2 { 6.0, 8.0 } };
|
||||
ignore = vec4 { vec3 { 2.0, 4.0, 6.0 }, 8.0 };
|
||||
ignore = vec4 { 2.0, vec3 { 4.0, 6.0, 8.0 } };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post default construct has correct state" } = [] {
|
||||
const auto vec = vec4 {};
|
||||
expect_eq(vec.x, 0.0);
|
||||
expect_eq(vec.y, 0.0);
|
||||
expect_eq(vec.z, 0.0);
|
||||
expect_eq(vec.w, 0.0);
|
||||
};
|
||||
|
||||
Case { "post scalar construct has correct state" } = [] {
|
||||
const auto vec = vec4 { 2.0 };
|
||||
expect_eq(vec.x, 2.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 2.0);
|
||||
expect_eq(vec.w, 2.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y,z,w has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.y, 3.0);
|
||||
expect_eq(vec.z, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with xy,z,w has correct state" } = [] {
|
||||
const auto vec = vec4 { vec2 { 1.0, 2.0 }, 3.0, 4.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y,zw has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, 2.0, vec2 { 3.0, 4.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,yz,w has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, vec2 { 2.0, 3.0 }, 4.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y,zw has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, 2.0, vec2 { 3.0, 4.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with xy,zw has correct state" } = [] {
|
||||
const auto vec = vec4 { vec2 { 1.0, 2.0 }, vec2 { 3.0, 4.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with xyz,w has correct state" } = [] {
|
||||
const auto vec = vec4 { vec3 { 1.0, 2.0, 3.0 }, 4.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,yzw has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, vec3 { 2.0, 3.0, 4.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite arithmetic_operators = "vec4_operators"_suite = [] {
|
||||
Case { "operator ==" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
|
||||
expect_false(lhs == vec4 { {}, 2.0, 3.0, 4.0 });
|
||||
expect_false(lhs == vec4 { 1.0, {}, 3.0, 4.0 });
|
||||
expect_false(lhs == vec4 { 1.0, 2.0, {}, 4.0 });
|
||||
expect_false(lhs == vec4 { 1.0, 2.0, 3.0, {} });
|
||||
expect_true(lhs == vec4 { 1.0, 2.0, 3.0, 4.0 });
|
||||
};
|
||||
|
||||
Case { "operator !=" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
|
||||
expect_true(lhs != vec4 { {}, 2.0, 3.0, 4.0 });
|
||||
expect_true(lhs != vec4 { 1.0, {}, 3.0, 4.0 });
|
||||
expect_true(lhs != vec4 { 1.0, 2.0, {}, 4.0 });
|
||||
expect_true(lhs != vec4 { 1.0, 2.0, 3.0, {} });
|
||||
expect_false(lhs != vec4 { 1.0, 2.0, 3.0, 4.0 });
|
||||
};
|
||||
|
||||
Case { "operator +" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
const auto rhs = vec4 { 5.0, 6.0, 7.0, 8.0 };
|
||||
expect_eq(lhs + rhs, vec4 { 6.0, 8.0, 10.0, 12.0 });
|
||||
};
|
||||
|
||||
Case { "operator -" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
const auto rhs = vec4 { 5.0, 10.0, 15.0, 20.0 };
|
||||
expect_eq(lhs - rhs, vec4 { -4.0, -8.0, -12.0, -16.0 });
|
||||
};
|
||||
|
||||
Case { "operator *" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
const auto rhs = vec4 { 5.0, 6.0, 7.0, 8.0 };
|
||||
expect_eq(lhs * rhs, vec4 { 5.0, 12.0, 21.0, 32.0 });
|
||||
};
|
||||
|
||||
Case { "operator /" } = [] {
|
||||
const auto lhs = vec4 { 5.0, 6.0, 30.0, 8.0 };
|
||||
const auto rhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
expect_eq(lhs / rhs, vec4 { 5.0, 3.0, 10.0, 2.0 });
|
||||
};
|
||||
|
||||
Case { "operator []" } = [] {
|
||||
auto vec = vec4 { 0.0, 1.0, 2.0, 3.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
expect_eq(vec[2], 2.0);
|
||||
expect_eq(vec[3], 3.0);
|
||||
};
|
||||
|
||||
Case { "operator [] const" } = [] {
|
||||
const auto vec = vec4 { 0.0, 1.0, 2.0, 3.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
expect_eq(vec[2], 2.0);
|
||||
expect_eq(vec[3], 3.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite utilities = "vec4_utilities"_suite = [] {
|
||||
Case { "std::format float" } = [] {
|
||||
auto str = std::format("{}", vec4 { 10.0000f, 30.0005f, 40.00005f, 0.0 });
|
||||
expect_eq(str, "10, 30.0005, 40.00005, 0");
|
||||
};
|
||||
|
||||
Case { "std::format int" } = [] {
|
||||
auto str = std::format("{}", ivec4 { 10, 30, 3'000'000, 13 });
|
||||
expect_eq(str, "10, 30, 3000000, 13");
|
||||
};
|
||||
};
|
||||
1
modules/memory/CMakeLists.txt
Normal file
1
modules/memory/CMakeLists.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
add_library_module(memory)
|
||||
|
|
@ -1,17 +1,13 @@
|
|||
export module memory.null_on_move;
|
||||
|
||||
import logger;
|
||||
|
||||
import preliminary;
|
||||
#pragma once
|
||||
|
||||
namespace lt::memory {
|
||||
|
||||
/** Holds an `Underlying_T`, assigns it to `null_value` when this object is moved.
|
||||
*
|
||||
* @note For avoiding the need to explicitly implement the move constructor for objects that hold
|
||||
* non-raii-handles (eg. Vulkan, Wayland).
|
||||
* Vulkan objects. But may server other purposes, hence why I kept the implementation generic.
|
||||
*/
|
||||
export template<typename Underlying_T, Underlying_T null_value = nullptr>
|
||||
template<typename Underlying_T, Underlying_T null_value = nullptr>
|
||||
class NullOnMove
|
||||
{
|
||||
public:
|
||||
|
|
@ -81,12 +77,12 @@ public:
|
|||
return m_value;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get() -> Underlying_T &
|
||||
operator uint64_t() const
|
||||
{
|
||||
return m_value;
|
||||
return (uint64_t)m_value;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get() const -> const Underlying_T &
|
||||
[[nodiscard]] auto get() -> Underlying_T
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
export module memory.reference;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
#include <memory/reference.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace lt::memory {
|
||||
|
||||
|
|
@ -9,21 +10,21 @@ namespace lt::memory {
|
|||
* @note Currently just an alias, might turn into an implementation later.
|
||||
* @ref https://en.cppreference.com/w/cpp/memory/shared_ptr.html
|
||||
*/
|
||||
export template<typename T>
|
||||
using Ref = std::shared_ptr<T>;
|
||||
template<typename t>
|
||||
using Ref = std::shared_ptr<t>;
|
||||
|
||||
/** Allocates memory for an `Underlying_T` and directly constructs it there.
|
||||
*
|
||||
* @return A Ref<Underlying_T> to the constructed object.
|
||||
*/
|
||||
export template<typename Underlying_T, typename... Args>
|
||||
template<typename Underlying_T, typename... Args>
|
||||
constexpr Ref<Underlying_T> create_ref(Args &&...args)
|
||||
{
|
||||
return std::make_shared<Underlying_T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/** Converts c-style pointer of type `Underlying_T` to a `Ref<Underlying_T>`. */
|
||||
export template<typename Underlying_T>
|
||||
template<typename Underlying_T>
|
||||
constexpr Ref<Underlying_T> make_ref(Underlying_T *raw_pointer)
|
||||
{
|
||||
return Ref<Underlying_T>(raw_pointer);
|
||||
|
|
@ -1,29 +1,30 @@
|
|||
export module memory.scope;
|
||||
#pragma once
|
||||
|
||||
import preliminary;
|
||||
#include <memory/scope.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace lt::memory {
|
||||
|
||||
/** @brief Wrapper around std::unique_ptr.
|
||||
/** Wrapper around std::unique_ptr.
|
||||
*
|
||||
* @note Currently just an alias, might turn into an implementation later.
|
||||
* @ref https://en.cppreference.com/w/cpp/memory/unique_ptr.html
|
||||
*/
|
||||
export template<typename t>
|
||||
template<typename t>
|
||||
using Scope = std::unique_ptr<t>;
|
||||
|
||||
/** Allocates memory for an `Underlying_T` and directly constructs it there.
|
||||
*
|
||||
* @return A Scope<Underlying_T> to the constructed object.
|
||||
*/
|
||||
export template<typename Underlying_T, typename... Args>
|
||||
template<typename Underlying_T, typename... Args>
|
||||
constexpr Scope<Underlying_T> create_scope(Args &&...args)
|
||||
{
|
||||
return std::make_unique<Underlying_T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/** Converts c-style pointer of type `Underlying_T` to a `Scope<Underlying_T>`. */
|
||||
export template<typename Underlying_T>
|
||||
template<typename Underlying_T>
|
||||
constexpr Scope<Underlying_T> make_scope(Underlying_T *raw_pointer)
|
||||
{
|
||||
return Scope<Underlying_T>(raw_pointer);
|
||||
9
modules/mirror/CMakeLists.txt
Normal file
9
modules/mirror/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
add_library_module(libmirror)
|
||||
target_link_libraries(libmirror INTERFACE app time input surface renderer)
|
||||
|
||||
add_test_module(
|
||||
libmirror layers/editor_layer.test.cpp panels/asset_browser.test.cpp
|
||||
panels/properties.test.cpp panels/scene_hierarchy.test.cpp)
|
||||
|
||||
add_executable_module(mirror entrypoint/mirror.cpp)
|
||||
target_link_libraries(mirror PRIVATE libmirror input)
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import app;
|
||||
import app.system;
|
||||
import std;
|
||||
import logger;
|
||||
import memory.scope;
|
||||
import mirror.system;
|
||||
import renderer.factory;
|
||||
|
||||
/** The ultimate entrypoint. */
|
||||
auto main(i32 argc, char *argv[]) -> i32
|
||||
{
|
||||
try
|
||||
{
|
||||
ignore = argc;
|
||||
ignore = argv;
|
||||
|
||||
auto application = lt::memory::create_scope<lt::Mirror>();
|
||||
if (!application)
|
||||
{
|
||||
throw std::runtime_error { "Failed to create application\n" };
|
||||
}
|
||||
|
||||
application->game_loop();
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
lt::log::critical("Terminating due to uncaught exception:");
|
||||
lt::log::critical("\texception.what(): {}", exp.what());
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue