refactor: refactor VxVector and its generator.

- refactor VxVector-like struct. split their declaration and implementatio because their implementation is too long. and occupy too much space in header.
- refactor VxVector struct generator. use jinja2 template engine, rather ran hand-written format string to make it is easy to read (although it still tough when first reading).
- add unary operator overloading for VxVector-like struct.
- add some VxMatrix functions which are essential to CKCamera.
- rename VxMatrix::ResetToIdentity to VxMatrix::SetIdentity to make it same as original Virtools SDK.
- the spaceship overloading with auto return value still may have bugs. please watch it carefully.
This commit is contained in:
2024-12-24 16:49:34 +08:00
parent 3eeb1f6cb6
commit 4bfc4782b5
15 changed files with 1199 additions and 644 deletions

View File

@ -1,2 +1,3 @@
# Result
*.hpp
VxTypes.hpp
VxTypes.cpp

View File

@ -0,0 +1,178 @@
{% import 'VxTypes.shared.jinja' as shared %}
{#
For friend operator overload, we do not need add CLASSNAME:: prefix for it.
Because they are not a part of that class.
#}
#pragma region {{ sname }}
{# Ctor type 1 - Default ctor -#}
{{ sname }}::{{ sname }}() : {{- shared.initialize_list_builder(svars, False) -}} {} {% if not is_vector %}// SET YOUR CUSTOM INIT{% endif %}
{#- Ctor type 2 - User specified ctor #}
{{ sname }}::{{ sname }}({{- shared.argument_list_builder(svars) -}}) : {{- shared.initialize_list_builder(svars, True) -}} {}
{#- Offset operator #}
CKFLOAT& {{ sname }}::operator[](size_t i) {
switch (i) {
{%- for item in svars %}
case {{ loop.index0 }}: return {{ item }};
{%- endfor %}
default: throw LogicException("Invalid index for {{ sname }}::operator[].");
}
}
const CKFLOAT& {{ sname }}::operator[](size_t i) const {
switch (i) {
{%- for item in svars %}
case {{ loop.index0 }}: return {{ item }};
{%- endfor %}
default: throw LogicException("Invalid index for {{ sname }}::operator[].");
}
}
{#- Equal operator #}
bool {{ sname }}::operator==(const {{ sname }}& rhs) const {
return (
{%- for item in svars -%}
{{ item }} == rhs.{{ item }} {%- if not loop.last %} && {% endif %}
{%- endfor -%}
);
}
{#- Spaceship operator #}
auto {{ sname }}::operator<=>(const {{ sname }}& rhs) const {
{%- for item in svars[:-1] %}
if (auto cmp = {{ item }} <=> rhs.{{ item }}; cmp != 0) return cmp;
{%- endfor %}
return {{ svars[-1] }} <=> rhs.{{ svars[-1] }};
}
{#- BEGIN VECTOR SPECIFIC #}
{%- if is_vector %}
{#- Add, minus operators #}
{#- Unary operators #}
{{ sname }} {{ sname }}::operator+() const {
return *this;
}
{{ sname }} {{ sname }}::operator-() const {
return {{ sname }}(
{%- for item in svars -%}
-{{ item }} {%- if not loop.last %}, {% endif %}
{%- endfor -%}
);
}
{#- Additive operators #}
{{ sname }}& {{ sname }}::operator+=(const {{ sname }}& rhs) {
{%- for item in svars %}
{{ item }} += rhs.{{ item }};
{%- endfor %}
return *this;
}
{{ sname }} operator+(const {{ sname }}& lhs, const {{ sname }}& rhs) {
return {{ sname }}(
{%- for item in svars -%}
lhs.{{ item }} + rhs.{{ item }} {%- if not loop.last %}, {% endif %}
{%- endfor -%}
);
}
{{ sname }}& {{ sname }}::operator-=(const {{ sname }}& rhs) {
{%- for item in svars %}
{{ item }} -= rhs.{{ item }};
{%- endfor %}
return *this;
}
{{ sname }} operator-(const {{ sname }}& lhs, const {{ sname }}& rhs) {
return {{ sname }}(
{%- for item in svars -%}
lhs.{{ item }} - rhs.{{ item }} {%- if not loop.last %}, {% endif %}
{%- endfor -%}
);
}
{#- Mul operator #}
{{ sname }}& {{ sname }}::operator*=(CKFLOAT rhs) {
{%- for item in svars %}
{{ item }} *= rhs;
{%- endfor %}
return *this;
}
{{ sname }} operator*(const {{ sname }}& lhs, CKFLOAT rhs) {
return {{ sname }}(
{%- for item in svars -%}
lhs.{{ item }} * rhs {%- if not loop.last %}, {% endif %}
{%- endfor -%}
);
}
{{ sname }} operator*(CKFLOAT lhs, const {{ sname }}& rhs) {
return {{ sname }}(
{%- for item in svars -%}
lhs * rhs.{{ item }} {%- if not loop.last %}, {% endif %}
{%- endfor -%}
);
}
CKFLOAT operator*(const {{ sname }}& lhs, const {{ sname }}& rhs) {
return (
{%- for item in svars -%}
lhs.{{ item }} * rhs.{{ item }} {%- if not loop.last %} + {% endif %}
{%- endfor -%}
);
}
{#- Div operator #}
{{ sname }}& {{ sname }}::operator/=(CKFLOAT rhs) {
if (rhs == 0.0f) return *this;
{%- for item in svars %}
{{ item }} /= rhs;
{%- endfor %}
return *this;
}
{{ sname }} operator/(const {{ sname }}& lhs, CKFLOAT rhs) {
if (rhs == 0.0f) return {{ sname }}();
else return {{ sname }}(
{%- for item in svars -%}
lhs.{{ item }} / rhs {%- if not loop.last %}, {% endif %}
{%- endfor -%}
);
}
{#- Length functions #}
CKFLOAT {{ sname }}::SquaredLength() const {
return (
{%- for item in svars -%}
{{ item }} * {{ item }} {%- if not loop.last %} + {% endif %}
{%- endfor -%}
);
}
CKFLOAT {{ sname }}::Length() const {
return std::sqrt(SquaredLength());
}
{#- Normalize functions #}
void {{ sname }}::Normalized() {
CKFLOAT len = Length();
if (len == 0.0f) return;
{%- for item in svars %}
{{ item }} /= len;
{%- endfor %}
}
{{ sname }} {{ sname }}::Normalize() const {
CKFLOAT len = Length();
if (len == 0.0f) return {{ sname }}();
else return {{ sname }}(
{%- for item in svars -%}
{{ item }} / len {%- if not loop.last %}, {% endif %}
{%- endfor -%}
);
}
{%- endif %}
{#- END VECTOR SPECIFIC #}
{#- User custom region #}
/* ===== BEGIN USER CUSTOM ===== */
/* ===== END USER CUSTOM ===== */
#pragma endregion

View File

@ -0,0 +1,65 @@
{% import 'VxTypes.shared.jinja' as shared %}
struct {{ sname }} {
{#- Variable declaration #}
CKFLOAT {{ ", ".join(svars) }};
{#- Ctor type 1 - Default ctor #}
{{ sname }}();
{#- Ctor type 2 - User specified ctor #}
{{ sname }}({{- shared.argument_list_builder(svars) -}});
{#- Default copy ctor, move ctor, copy assigner, move assigner #}
YYCC_DEF_CLS_COPY_MOVE({{ sname }});
{#- Offset operator #}
CKFLOAT& operator[](size_t i);
const CKFLOAT& operator[](size_t i) const;
{#- Equal operator #}
bool operator==(const {{ sname }}& rhs) const;
{#- Spaceship operator #}
auto operator<=>(const {{ sname }}& rhs) const;
{#- BEGIN VECTOR SPECIFIC #}
{%- if is_vector %}
{#- Add, minus operators #}
{#- Unary operators #}
{{ sname }} operator+() const;
{{ sname }} operator-() const;
{#- Additive operators #}
{{ sname }}& operator+=(const {{ sname }}& rhs);
friend {{ sname }} operator+(const {{ sname }}& lhs, const {{ sname }}& rhs);
{{ sname }}& operator-=(const {{ sname }}& rhs);
friend {{ sname }} operator-(const {{ sname }}& lhs, const {{ sname }}& rhs);
{#- Mul operator #}
{{ sname }}& operator*=(CKFLOAT rhs);
friend {{ sname }} operator*(const {{ sname }}& lhs, CKFLOAT rhs);
friend {{ sname }} operator*(CKFLOAT lhs, const {{ sname }}& rhs);
friend CKFLOAT operator*(const {{ sname }}& lhs, const {{ sname }}& rhs);
{#- Div operator #}
{{ sname }}& operator/=(CKFLOAT rhs);
friend {{ sname }} operator/(const {{ sname }}& lhs, CKFLOAT rhs);
{#- Length functions #}
CKFLOAT SquaredLength() const;
CKFLOAT Length() const;
{#- Normalize functions #}
void Normalized();
{{ sname }} Normalize() const;
{%- endif %}
{#- END VECTOR SPECIFIC #}
{#- User custom region #}
/* ===== BEGIN USER CUSTOM ===== */
/* ===== END USER CUSTOM ===== */
};

View File

@ -0,0 +1,21 @@
{#
The macro to generate C++ ctor argument list
It produce like this: `CKFLOAT _x, CKFLOAT _y, CKFLOAT _z, CKFLOAT _w`
#}
{% macro argument_list_builder(svars) %}
{%- for item in svars -%}
CKFLOAT _{{- item -}}{%- if not loop.last %}, {% endif -%}
{%- endfor -%}
{% endmacro %}
{#
The macro to generate C++ ctor initialize list
It produce like this: `x(0.0f), y(0.0f), z(0.0f), w(0.0f)`
or this: `x(_x), y(_y), z(_z), w(_w)`
according to user request.
#}
{% macro initialize_list_builder(svars, is_user) %}
{%- for item in svars -%}
{{- item -}}({%- if is_user -%}_{{- item -}}{%- else -%}0.0f{%- endif -%}){%- if not loop.last %}, {% endif -%}
{%- endfor -%}
{% endmacro %}

View File

@ -0,0 +1,63 @@
import os
import io
import typing
import jinja2
g_HppTemplateFile: str = 'VxTypes.hpp.jinja'
g_CppTemplateFile: str = 'VxTypes.cpp.jinja'
g_ResultHppFile: str = 'VxTypes.hpp'
g_ResultCppFile: str = 'VxTypes.cpp'
def get_root_directory() -> str:
return os.path.dirname(__file__)
class TemplateRender:
m_Loader: jinja2.BaseLoader
m_Environment: jinja2.Environment
m_HppTemplate: jinja2.Template
m_CppTemplate: jinja2.Template
m_OutputHpp: io.TextIOWrapper
m_OutputCpp: io.TextIOWrapper
def __init__(self, output_hpp_path: str, output_cpp_path: str) -> None:
self.m_Loader = jinja2.FileSystemLoader(get_root_directory())
self.m_Environment = jinja2.Environment(loader=self.m_Loader)
self.m_HppTemplate = self.m_Environment.get_template(g_HppTemplateFile)
self.m_CppTemplate = self.m_Environment.get_template(g_CppTemplateFile)
self.m_OutputHpp = open(os.path.join(get_root_directory(), output_hpp_path), 'w', encoding='utf-8')
self.m_OutputCpp = open(os.path.join(get_root_directory(), output_cpp_path), 'w', encoding='utf-8')
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.m_OutputHpp.close()
self.m_OutputCpp.close()
def __render(self, sname: str, is_vector: bool, svars: tuple[str]) -> None:
template_argument: dict[str, typing.Any] = {
'sname': sname,
'is_vector': is_vector,
'svars': svars
}
self.m_OutputHpp.write(self.m_HppTemplate.render(**template_argument))
self.m_OutputCpp.write(self.m_CppTemplate.render(**template_argument))
def render_vector(self, sname: str, svars: tuple[str]) -> None:
self.__render(sname, True, svars)
def render_others(self, sname: str, svars: tuple[str]) -> None:
self.__render(sname, False, svars)
if __name__ == '__main__':
with TemplateRender(g_ResultHppFile, g_ResultCppFile) as render:
render.render_vector('VxVector2', ('x', 'y', ))
render.render_vector('VxVector3', ('x', 'y', 'z', ))
render.render_vector('VxVector4', ('x', 'y', 'z', 'w', ))
render.render_others('VxQuaternion', ('x', 'y', 'z', 'w', ))
render.render_others('VxColor', ('r', 'g', 'b', 'a', ))

View File

@ -1,166 +0,0 @@
import os
def GetTmplDecl(svars: tuple[str]) -> str:
return f'CKFLOAT {", ".join(svars)};'
def GetTmplCtor1(sname: str, svars: tuple[str]) -> str:
return f'{sname}() : {", ".join(map(lambda x: f"{x}(0.0f)", svars))} {{}}'
def GetTmplCtor2(sname: str, svars: tuple[str]) -> str:
return f'{sname}({", ".join(map(lambda x: f"CKFLOAT _{x}", svars))}) : {", ".join(map(lambda x: f"{x}(_{x})", svars))} {{}}'
def GetTmplCopyCtor(sname: str, svars: tuple[str]) -> str:
return f'{sname}(const {sname}& rhs) : {", ".join(map(lambda x: f"{x}(rhs.{x})", svars))} {{}}'
def GetTmplMoveCtor(sname: str, svars: tuple[str]) -> str:
return f'{sname}({sname}&& rhs) : {", ".join(map(lambda x: f"{x}(rhs.{x})", svars))} {{}}'
def GetTmplOperAssignCopy(sname: str, svars: tuple[str]) -> str:
sp: str = '\n\t\t'
return f"""\t{sname}& operator=(const {sname}& rhs) {{
\t\t{sp.join(map(lambda x: f'{x} = rhs.{x};', svars))}
\t\treturn *this;
\t}}"""
def GetTmplOperAssignMove(sname: str, svars: tuple[str]) -> str:
sp: str = '\n\t\t'
return f"""\t{sname}& operator=({sname}&& rhs) {{
\t\t{sp.join(map(lambda x: f'{x} = rhs.{x};', svars))}
\t\treturn *this;
\t}}"""
def GetTmplOperOffset(sname: str, svars: tuple[str]) -> str:
sp: str = '\n\t\t\t'
return f"""\tCKFLOAT& operator[](size_t i) {{
\t\tswitch (i) {{
\t\t\t{sp.join(map(lambda x: f'case {x}: return {svars[x]};', range(len(svars))))}
\t\t\tdefault: throw LogicException("Invalid index for {sname}::operator[].");
\t\t}}
\t}}
\tconst CKFLOAT& operator[](size_t i) const {{
\t\tswitch (i) {{
\t\t\t{sp.join(map(lambda x: f'case {x}: return {svars[x]};', range(len(svars))))}
\t\t\tdefault: throw LogicException("Invalid index for {sname}::operator[].");
\t\t}}
\t}}"""
def GetTmplOperAddMinus(sname: str, svars: tuple[str], oper: str) -> str:
sp: str = '\n\t\t'
return f"""\t{sname}& operator{oper}=(const {sname}& rhs) {{
\t\t{sp.join(map(lambda x: f'{x} {oper}= rhs.{x};', svars))}
\t\treturn *this;
\t}}
\tfriend {sname} operator{oper}(const {sname}& lhs, const {sname}& rhs) {{
\t\treturn {sname}({', '.join(map(lambda x: f'lhs.{x} {oper} rhs.{x}', svars))});
\t}}"""
def GetTmplOperMul(sname: str, svars: tuple[str]) -> str:
sp: str = '\n\t\t'
return f"""\t{sname}& operator*=(CKFLOAT rhs) {{
\t\t{sp.join(map(lambda x: f'{x} *= rhs;', svars))}
\t\treturn *this;
\t}}
\tfriend {sname} operator*(const {sname}& lhs, CKFLOAT rhs) {{
\t\treturn {sname}({', '.join(map(lambda x: f'lhs.{x} * rhs', svars))});
\t}}
\tfriend {sname} operator*(CKFLOAT lhs, const {sname}& rhs) {{
\t\treturn {sname}({', '.join(map(lambda x: f'lhs * rhs.{x}', svars))});
\t}}
\tfriend CKFLOAT operator*(const {sname}& lhs, const {sname}& rhs) {{
\t\treturn ({' + '.join(map(lambda x: f'lhs.{x} * rhs.{x}', svars))});
\t}}"""
def GetTmplOperDiv(sname: str, svars: tuple[str]) -> str:
sp: str = '\n\t\t'
return f"""\t{sname}& operator/=(CKFLOAT rhs) {{
\t\tif (rhs == 0.0f) return *this;
\t\t{sp.join(map(lambda x: f'{x} /= rhs;', svars))}
\t\treturn *this;
\t}}
\tfriend {sname} operator/(const {sname}& lhs, CKFLOAT rhs) {{
\t\tif (rhs == 0.0f) return {sname}({', '.join(map(lambda x: '0.0f', range(len(svars))))});
\t\treturn {sname}({', '.join(map(lambda x: f'lhs.{x} / rhs', svars))});
\t}}"""
def GetTmplOperEqual(sname: str, svars: tuple[str]) -> str:
return f"""\tbool operator==(const {sname}& rhs) const {{
\t\treturn ({' && '.join(map(lambda x: f'{x} == rhs.{x}', svars))});
\t}}"""
def GetTmplOperSpaceship(sname: str, svars: tuple[str]) -> str:
sp: str = '\n\t\t'
return f"""\tauto operator<=>(const {sname}& rhs) const {{
\t\t{sp.join(map(lambda x: f'if (auto cmp = {x} <=> rhs.{x}; cmp != 0) return cmp;', svars[:-1]))}
\t\treturn {svars[-1]} <=> rhs.{svars[-1]};
\t}}"""
def GetTmplLength(sname: str, svars: tuple[str]) -> str:
return f"""\tCKFLOAT SquaredLength() const {{
\t\treturn ({' + '.join(map(lambda x: f'{x} * {x}', svars))});
\t}}
\tCKFLOAT Length() const {{
\t\treturn std::sqrt(SquaredLength());
\t}}"""
def GetTmplNormalize(sname: str, svars: tuple[str]) -> str:
sp: str = '\n\t\t'
return f"""\tvoid Normalized() {{
\t\tCKFLOAT len = Length();
\t\tif (len == 0.0f) return;
\t\t{sp.join(map(lambda x: f'{x} /= len;', svars))}
\t}}
\t{sname} Normalize() const {{
\t\tCKFLOAT len = Length();
\t\tif (len == 0.0f) return {sname}();
\t\treturn {sname}({', '.join(map(lambda x: f'{x} / len', svars))});
\t}}"""
def GetTmplVector(sname: str, svars: tuple[str]) -> str:
return f"""
struct {sname} {{
\t{GetTmplDecl(svars)}
\t{GetTmplCtor1(sname, svars)}
\t{GetTmplCtor2(sname, svars)}
\tYYCC_DEF_CLS_COPY_MOVE({sname});
{GetTmplOperOffset(sname, svars)}
{GetTmplOperAddMinus(sname, svars, '+')}
{GetTmplOperAddMinus(sname, svars, '-')}
{GetTmplOperMul(sname, svars)}
{GetTmplOperDiv(sname, svars)}
{GetTmplOperEqual(sname, svars)}
{GetTmplOperSpaceship(sname, svars)}
{GetTmplLength(sname, svars)}
{GetTmplNormalize(sname, svars)}
}};
"""
def GetTmplOthers(sname: str, svars: tuple[str]) -> str:
return f"""
struct {sname} {{
\t{GetTmplDecl(svars)}
\t{GetTmplCtor1(sname, svars)} // set your custom init.
\t{GetTmplCtor2(sname, svars)}
\tYYCC_DEF_CLS_COPY_MOVE({sname});
{GetTmplOperOffset(sname, svars)}
{GetTmplOperEqual(sname, svars)}
{GetTmplOperSpaceship(sname, svars)}
}};
"""
# use YYCC_DEF_CLS_COPY_MOVE instead of these outputs.
#\t{GetTmplCopyCtor(sname, svars)}
#\t{GetTmplMoveCtor(sname, svars)}
#{GetTmplOperAssignCopy(sname, svars)}
#{GetTmplOperAssignMove(sname, svars)}
if __name__ == '__main__':
# get file path
self_path: str = os.path.dirname(__file__)
cpp_file: str = os.path.join(self_path, 'VxTypes.hpp')
# generate files
with open(cpp_file, 'w', encoding='utf-8') as fs:
fs.write(GetTmplVector('VxVector2', ('x', 'y', )))
fs.write(GetTmplVector('VxVector3', ('x', 'y', 'z', )))
fs.write(GetTmplVector('VxVector4', ('x', 'y', 'z', 'w', )))
fs.write(GetTmplOthers('VxQuaternion', ('x', 'y', 'z', 'w', )))
fs.write(GetTmplOthers('VxColor', ('r', 'g', 'b', 'a', )))