[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Reports\ndescription: Open a new issue\nbody:\n\n- type: markdown\n  attributes:\n    value: |\n      Before opening a new issue:\n      - Read the [readme](https://github.com/bnpr/Malt#malt), check that you meet the minimum requirements and have installed Malt properly.\n      - Make sure you are running the latest Malt version.\n      - Search [existing issues](https://github.com/bnpr/Malt/issues) to ensure it has not already been reported.\n\n- type: input\n  attributes:\n    label: Malt version\n    placeholder: Release | Development\n  validations:\n    required: true\n\n- type: input\n  attributes:\n    label: Blender version\n    placeholder: Blender 3.0.0\n  validations:\n    required: true\n\n- type: input\n  attributes:\n    label: OS\n    placeholder: Windows 10 64 bits\n  validations:\n    required: true\n\n- type: input\n  attributes:\n    label: Hardware info\n    placeholder: Intel i7-4790K | 16GB RAM | Nvidia GTX 1060 6GB\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Issue description and reproduction steps\n  validations:\n    required: true\n\n- type: textarea\n  attributes:\n    label: Attachments\n    description: |\n      - The **Log file** from the Blender session where the bug happened.\n        You can find it in *Preferences > Addons > BlenderMalt > Open Session Log* or search for it in your system temporary folder.\n      - A zipped **.blend file** with a minimal example to reproduce the error. *(Unless the error is not file dependent)*\n      > Don't copy/paste the *Log* text. Drag and drop the files to upload them.\n  validations:\n    required: true\n\n\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n\ncontact_links:\n  - name: Q&A\n    url: https://github.com/bnpr/Malt/discussions/categories/q-a\n    about: Ask your *How to* questions.\n\n  - name: Feedback & Feature Requests\n    url: https://github.com/bnpr/Malt/discussions/categories/feedback-feature-requests\n    about: Post your feedback and feature requests.\n\n"
  },
  {
    "path": ".github/workflows/BlenderMalt.yml",
    "content": "name: BLENDERMALT_PACKAGE_AND_RELEASE\n\non:\n  push:\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    \n    - uses: nelonoel/branch-name@v1.0.1\n\n    - name: Rollback Release\n      uses: author/action-rollback@stable\n      continue-on-error: true\n      with:\n        tag: ${{ env.BRANCH_NAME }}-latest\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      if: ${{ github.ref_type == 'branch' }}\n\n    - uses: ncipollo/release-action@v1\n      id: create_release\n      if: ${{ github.ref_type == 'branch' }}\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        allowUpdates: true\n        tag: ${{ env.BRANCH_NAME }}-latest\n        commit: ${{ github.sha }}\n        prerelease: ${{ env.BRANCH_NAME != 'Release' }}\n        body: |\n          [**BlenderMalt-Windows.zip**](https://github.com/${{github.repository}}/releases/download/${{env.BRANCH_NAME}}-latest/BlenderMalt-Windows.zip)\n          [**BlenderMalt-Linux.zip**](https://github.com/${{github.repository}}/releases/download/${{env.BRANCH_NAME}}-latest/BlenderMalt-Linux.zip) \n           \n    \n    - name: Rollback Tagged Release\n      uses: author/action-rollback@stable\n      continue-on-error: true\n      with:\n        tag: ${{ github.action_ref }}\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      if: ${{ github.ref_type == 'tag' }}\n    \n    - uses: ncipollo/release-action@v1\n      id: create_tagged_release\n      if: ${{ github.ref_type == 'tag' }}\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        commit: ${{ github.sha }}\n        allowUpdates: true\n        prerelease: ${{ env.BRANCH_NAME != 'Release' }}\n        body: |\n          [**BlenderMalt-Windows.zip**](https://github.com/${{github.repository}}/releases/download/${{github.ref_name}}/BlenderMalt-Windows.zip)\n          [**BlenderMalt-Linux.zip**](https://github.com/${{github.repository}}/releases/download/${{github.ref_name}}/BlenderMalt-Linux.zip)\n          \n            \n    outputs:\n      upload_url: ${{ steps.create_release.outputs.upload_url }}  \n      release_id: ${{ steps.create_release.outputs.id }}\n      tagged_upload_url: ${{ steps.create_tagged_release.outputs.upload_url }}  \n      tagged_release_id: ${{ steps.create_tagged_release.outputs.id }}\n  \n  package-blender:\n    needs: [release]\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [windows-latest, ubuntu-latest]\n\n    steps:\n    - uses: actions/checkout@v2\n        \n    - uses: actions/setup-python@v6\n      with:\n        python-version: '3.13'\n    \n    - name: setup_blender_addon.py\n      run: python setup_blender_addon.py --copy-modules\n      working-directory: ${{ github.workspace }}/scripts\n    \n    - name: Zip Addon\n      shell: python\n      run: |\n        import shutil\n        shutil.make_archive('BlenderMalt', 'zip', '.', 'BlenderMalt')\n    \n    - name: Upload Artifact\n      uses: actions/upload-artifact@v4\n      with:\n        name: BlenderMalt-${{ runner.os }}\n        path: ${{ github.workspace }}/BlenderMalt.zip\n    \n    - name: Remove Old Files\n      uses: flcdrg/remove-release-asset-action@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        release_id: ${{ needs.release.outputs.release_id }}\n        asset_name: BlenderMalt-${{ runner.os }}.zip\n      continue-on-error: true\n      if: ${{ github.ref_type == 'branch' }}\n    \n    - name: Release Addon\n      uses: actions/upload-release-asset@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        upload_url: ${{ needs.release.outputs.upload_url }}\n        asset_path: ${{ github.workspace }}/BlenderMalt.zip\n        asset_name: BlenderMalt-${{ runner.os }}.zip\n        asset_content_type: application/zip\n      if: ${{ github.ref_type == 'branch' }}\n    \n    - name: Remove Old Tagged Files\n      uses: flcdrg/remove-release-asset-action@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        release_id: ${{ needs.release.outputs.tagged_release_id }}\n        asset_name: BlenderMalt-${{ runner.os }}.zip\n      continue-on-error: true\n      if: ${{ github.ref_type == 'tag' }}\n\n    - name: Release Tagged Addon\n      uses: actions/upload-release-asset@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        upload_url: ${{ needs.release.outputs.tagged_upload_url }}\n        asset_path: ${{ github.workspace }}/BlenderMalt.zip\n        asset_name: BlenderMalt-${{ runner.os }}.zip\n        asset_content_type: application/zip\n      if: ${{ github.ref_type == 'tag' }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".*\n!.gitignore\n!.gitattributes\n!.gitmodules\n\n!.github\n\n__pycache__\n\n*.lib\n*.dll\n*.so\n\n*.log\n\n"
  },
  {
    "path": "BlenderMalt/CBlenderMalt/CBlenderMalt.cpp",
    "content": "#include \"stdio.h\"\n#include \"mikktspace.h\"\n\n#ifdef _WIN32\n#define EXPORT extern \"C\" __declspec( dllexport )\n#else\n#define EXPORT extern \"C\" __attribute__ ((visibility (\"default\")))\n#endif\n\nEXPORT void retrieve_mesh_data(\n  float* in_positions,\n  int* in_loop_verts, int loop_count,\n  int* in_loop_tris,\n  int* in_loop_tri_polys, int loop_tri_count,\n  int* in_mat_indices,\n  float* out_positions, unsigned int** out_indices, unsigned int* out_index_lengths)\n{\n  for(int i = 0; i < loop_count; i++)\n  {\n    out_positions[i*3+0] = in_positions[in_loop_verts[i]*3+0];\n    out_positions[i*3+1] = in_positions[in_loop_verts[i]*3+1];\n    out_positions[i*3+2] = in_positions[in_loop_verts[i]*3+2];\n  }\n\n  unsigned int* mat_i = out_index_lengths;\n\n  for(int i = 0; i < loop_tri_count; i++)\n  {\n    int mat = in_mat_indices ? in_mat_indices[in_loop_tri_polys[i]] : 0;\n    out_indices[mat][mat_i[mat]++] = in_loop_tris[i*3+0];\n    out_indices[mat][mat_i[mat]++] = in_loop_tris[i*3+1];\n    out_indices[mat][mat_i[mat]++] = in_loop_tris[i*3+2];\n  }\n}\n\nEXPORT bool mesh_tangents(\n  int* in_indices, int index_len,\n  float* in_positions, float* in_normals, float* in_uvs,\n  float* out_tangents)\n{\n  struct MData\n  {\n    int* indices;\n    int index_len;\n    float* positions;\n    float* normals;\n    float* uvs;\n    float* tangents;\n  };\n\n  MData data = {\n    in_indices,\n    index_len,\n    in_positions,\n    in_normals,\n    in_uvs,\n    out_tangents,\n  };\n\n  SMikkTSpaceInterface mti = {0};\n  mti.m_getNumFaces = [](const SMikkTSpaceContext * pContext){\n    return ((MData*)pContext->m_pUserData)->index_len / 3;\n  };\n\n\tmti.m_getNumVerticesOfFace = [](const SMikkTSpaceContext * pContext, const int iFace){\n    return 3;\n  };\n\n\tmti.m_getPosition = [](const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert){\n    MData* m = (MData*)pContext->m_pUserData;\n    int i = iFace * 3 + iVert;\n    fvPosOut[0] = m->positions[m->indices[i] * 3 + 0];\n    fvPosOut[1] = m->positions[m->indices[i] * 3 + 1];\n    fvPosOut[2] = m->positions[m->indices[i] * 3 + 2];\n  };\n\n\tmti.m_getNormal = [](const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert){\n    MData* m = (MData*)pContext->m_pUserData;\n    int i = iFace * 3 + iVert;\n    fvNormOut[0] = m->normals[m->indices[i] * 3 + 0];\n    fvNormOut[1] = m->normals[m->indices[i] * 3 + 1];\n    fvNormOut[2] = m->normals[m->indices[i] * 3 + 2];\n  };\n\n\tmti.m_getTexCoord = [](const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert){\n    MData* m = (MData*)pContext->m_pUserData;\n    int i = iFace * 3 + iVert;\n    fvTexcOut[0] = m->uvs[m->indices[i] * 2 + 0];\n    fvTexcOut[1] = m->uvs[m->indices[i] * 2 + 1];\n  };\n\n\tmti.m_setTSpaceBasic = [](const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert){\n    MData* m = (MData*)pContext->m_pUserData;\n    int i = iFace * 3 + iVert;\n    m->tangents[m->indices[i] * 4 + 0] = fvTangent[0];\n    m->tangents[m->indices[i] * 4 + 1] = fvTangent[1];\n    m->tangents[m->indices[i] * 4 + 2] = fvTangent[2];\n    m->tangents[m->indices[i] * 4 + 3] = fSign;\n  };\n  \n  SMikkTSpaceContext mtc;\n  mtc.m_pInterface = &mti;\n  mtc.m_pUserData = &data;\n\n  return genTangSpaceDefault(&mtc);\n}\n"
  },
  {
    "path": "BlenderMalt/CBlenderMalt/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.10)\n\nSET(CMAKE_CXX_STANDARD 17)\n# set(CMAKE_GENERATOR_PLATFORM x64)\n\nproject(CBlenderMalt)\n\nSET(CMAKE_BUILD_TYPE Release)\nSET(BUILD_SHARED_LIBS ON)\n\nadd_library(CBlenderMalt CBlenderMalt.cpp mikktspace.c)\ntarget_include_directories(CBlenderMalt PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/blender_dna)\n\ninstall(TARGETS CBlenderMalt CONFIGURATIONS Release DESTINATION ${PROJECT_SOURCE_DIR})\n"
  },
  {
    "path": "BlenderMalt/CBlenderMalt/__init__.py",
    "content": "import subprocess\nimport os\nimport ctypes\n\nimport platform\n\nsrc_dir = os.path.abspath(os.path.dirname(__file__))\n\nlibrary = 'libCBlenderMalt.so'\nif platform.system() == 'Windows': library = 'CBlenderMalt.dll'\nif platform.system() == 'Darwin': library = 'libCBlenderMalt.dylib'\n\nCBlenderMalt = ctypes.CDLL(os.path.join(src_dir, library))\n\nretrieve_mesh_data = CBlenderMalt['retrieve_mesh_data']\nretrieve_mesh_data.argtypes = [\n    ctypes.POINTER(ctypes.c_float),\n    ctypes.POINTER(ctypes.c_int), ctypes.c_int,\n    ctypes.POINTER(ctypes.c_int),\n    ctypes.POINTER(ctypes.c_int), ctypes.c_int,\n    ctypes.POINTER(ctypes.c_int),\n    ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_uint32)\n]\nretrieve_mesh_data.restype = None\n\nmesh_tangents = CBlenderMalt['mesh_tangents']\nmesh_tangents.argtypes = [\n    ctypes.POINTER(ctypes.c_int), ctypes.c_int,\n    ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float),\n    ctypes.POINTER(ctypes.c_float)\n]\nmesh_tangents.restype = ctypes.c_bool\n"
  },
  {
    "path": "BlenderMalt/CBlenderMalt/build.py",
    "content": "import subprocess\nimport os\nimport platform\n\nsrc_dir = os.path.abspath(os.path.dirname(__file__))\nbuild_dir = os.path.join(src_dir, '.build') \n\ntry: os.mkdir(build_dir)\nexcept: pass\n\nif platform.system() == 'Windows': #Multi-config generators, like Visual Studio\n    subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir)\n    subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir)\nelse: #Single-config generators\n    subprocess.check_call(['cmake', '..'], cwd=build_dir)\n    subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir)\n\nsubprocess.check_call(['cmake', '--install', '.'], cwd=build_dir)\n"
  },
  {
    "path": "BlenderMalt/CBlenderMalt/mikktspace.c",
    "content": "/** \\file mikktspace/mikktspace.c\n *  \\ingroup mikktspace\n */\n/**\n *  Copyright (C) 2011 by Morten S. Mikkelsen\n *\n *  This software is provided 'as-is', without any express or implied\n *  warranty.  In no event will the authors be held liable for any damages\n *  arising from the use of this software.\n *\n *  Permission is granted to anyone to use this software for any purpose,\n *  including commercial applications, and to alter it and redistribute it\n *  freely, subject to the following restrictions:\n *\n *  1. The origin of this software must not be misrepresented; you must not\n *     claim that you wrote the original software. If you use this software\n *     in a product, an acknowledgment in the product documentation would be\n *     appreciated but is not required.\n *  2. Altered source versions must be plainly marked as such, and must not be\n *     misrepresented as being the original software.\n *  3. This notice may not be removed or altered from any source distribution.\n */\n\n#include <assert.h>\n#include <stdio.h>\n#include <math.h>\n#include <string.h>\n#include <float.h>\n#include <stdlib.h>\n\n#include \"mikktspace.h\"\n\n#define TFALSE\t\t0\n#define TTRUE\t\t1\n\n#ifndef M_PI\n#define M_PI\t3.1415926535897932384626433832795\n#endif\n\n#define INTERNAL_RND_SORT_SEED\t\t39871946\n\n// internal structure\ntypedef struct {\n\tfloat x, y, z;\n} SVec3;\n\nstatic tbool\t\t\tveq( const SVec3 v1, const SVec3 v2 )\n{\n\treturn (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z);\n}\n\nstatic SVec3\t\tvadd( const SVec3 v1, const SVec3 v2 )\n{\n\tSVec3 vRes;\n\n\tvRes.x = v1.x + v2.x;\n\tvRes.y = v1.y + v2.y;\n\tvRes.z = v1.z + v2.z;\n\n\treturn vRes;\n}\n\n\nstatic SVec3\t\tvsub( const SVec3 v1, const SVec3 v2 )\n{\n\tSVec3 vRes;\n\n\tvRes.x = v1.x - v2.x;\n\tvRes.y = v1.y - v2.y;\n\tvRes.z = v1.z - v2.z;\n\n\treturn vRes;\n}\n\nstatic SVec3\t\tvscale(const float fS, const SVec3 v)\n{\n\tSVec3 vRes;\n\n\tvRes.x = fS * v.x;\n\tvRes.y = fS * v.y;\n\tvRes.z = fS * v.z;\n\n\treturn vRes;\n}\n\nstatic float\t\t\tLengthSquared( const SVec3 v )\n{\n\treturn v.x*v.x + v.y*v.y + v.z*v.z;\n}\n\nstatic float\t\t\tLength( const SVec3 v )\n{\n\treturn sqrtf(LengthSquared(v));\n}\n\nstatic SVec3\t\tNormalize( const SVec3 v )\n{\n\treturn vscale(1 / Length(v), v);\n}\n\nstatic float\t\tvdot( const SVec3 v1, const SVec3 v2)\n{\n\treturn v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;\n}\n\n\nstatic tbool NotZero(const float fX)\n{\n\t// could possibly use FLT_EPSILON instead\n\treturn fabsf(fX) > FLT_MIN;\n}\n\nstatic tbool VNotZero(const SVec3 v)\n{\n\t// might change this to an epsilon based test\n\treturn NotZero(v.x) || NotZero(v.y) || NotZero(v.z);\n}\n\n\n\ntypedef struct {\n\tint iNrFaces;\n\tint * pTriMembers;\n} SSubGroup;\n\ntypedef struct {\n\tint iNrFaces;\n\tint * pFaceIndices;\n\tint iVertexRepresentitive;\n\ttbool bOrientPreservering;\n} SGroup;\n\n// \n#define MARK_DEGENERATE\t\t\t\t1\n#define QUAD_ONE_DEGEN_TRI\t\t\t2\n#define GROUP_WITH_ANY\t\t\t\t4\n#define ORIENT_PRESERVING\t\t\t8\n\n\n\ntypedef struct {\n\tint FaceNeighbors[3];\n\tSGroup * AssignedGroup[3];\n\t\n\t// normalized first order face derivatives\n\tSVec3 vOs, vOt;\n\tfloat fMagS, fMagT;\t// original magnitudes\n\n\t// determines if the current and the next triangle are a quad.\n\tint iOrgFaceNumber;\n\tint iFlag, iTSpacesOffs;\n\tunsigned char vert_num[4];\n} STriInfo;\n\ntypedef struct {\n\tSVec3 vOs;\n\tfloat fMagS;\n\tSVec3 vOt;\n\tfloat fMagT;\n\tint iCounter;\t// this is to average back into quads.\n\ttbool bOrient;\n} STSpace;\n\nstatic int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn);\nstatic void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn);\nstatic void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn);\nstatic int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn);\nstatic tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[],\n                             const int iNrActiveGroups, const int piTriListIn[], const float fThresCos,\n                             const SMikkTSpaceContext * pContext);\n\nstatic int MakeIndex(const int iFace, const int iVert)\n{\n\tassert(iVert>=0 && iVert<4 && iFace>=0);\n\treturn (iFace<<2) | (iVert&0x3);\n}\n\nstatic void IndexToData(int * piFace, int * piVert, const int iIndexIn)\n{\n\tpiVert[0] = iIndexIn&0x3;\n\tpiFace[0] = iIndexIn>>2;\n}\n\nstatic STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1)\n{\n\tSTSpace ts_res;\n\n\t// this if is important. Due to floating point precision\n\t// averaging when ts0==ts1 will cause a slight difference\n\t// which results in tangent space splits later on\n\tif (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT &&\n\t   veq(pTS0->vOs,pTS1->vOs)\t&& veq(pTS0->vOt, pTS1->vOt))\n\t{\n\t\tts_res.fMagS = pTS0->fMagS;\n\t\tts_res.fMagT = pTS0->fMagT;\n\t\tts_res.vOs = pTS0->vOs;\n\t\tts_res.vOt = pTS0->vOt;\n\t}\n\telse\n\t{\n\t\tts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS);\n\t\tts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT);\n\t\tts_res.vOs = vadd(pTS0->vOs,pTS1->vOs);\n\t\tts_res.vOt = vadd(pTS0->vOt,pTS1->vOt);\n\t\tif ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs);\n\t\tif ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt);\n\t}\n\n\treturn ts_res;\n}\n\n\n\nstatic SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index);\nstatic SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index);\nstatic SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index);\n\n\n// degen triangles\nstatic void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris);\nstatic void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris);\n\n\ntbool genTangSpaceDefault(const SMikkTSpaceContext * pContext)\n{\n\treturn genTangSpace(pContext, 180.0f);\n}\n\ntbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold)\n{\n\t// count nr_triangles\n\tint * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL;\n\tSTriInfo * pTriInfos = NULL;\n\tSGroup * pGroups = NULL;\n\tSTSpace * psTspace = NULL;\n\tint iNrTrianglesIn = 0, f=0, t=0, i=0;\n\tint iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0;\n\tint iNrActiveGroups = 0, index = 0;\n\tconst int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext);\n\ttbool bRes = TFALSE;\n\tconst float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f);\n\n\t// verify all call-backs have been set\n\tif ( pContext->m_pInterface->m_getNumFaces==NULL ||\n\t\tpContext->m_pInterface->m_getNumVerticesOfFace==NULL ||\n\t\tpContext->m_pInterface->m_getPosition==NULL ||\n\t\tpContext->m_pInterface->m_getNormal==NULL ||\n\t\tpContext->m_pInterface->m_getTexCoord==NULL )\n\t\treturn TFALSE;\n\n\t// count triangles on supported faces\n\tfor (f=0; f<iNrFaces; f++)\n\t{\n\t\tconst int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f);\n\t\tif (verts==3) ++iNrTrianglesIn;\n\t\telse if (verts==4) iNrTrianglesIn += 2;\n\t}\n\tif (iNrTrianglesIn<=0) return TFALSE;\n\n\t// allocate memory for an index list\n\tpiTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn);\n\tpTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn);\n\tif (piTriListIn==NULL || pTriInfos==NULL)\n\t{\n\t\tif (piTriListIn!=NULL) free(piTriListIn);\n\t\tif (pTriInfos!=NULL) free(pTriInfos);\n\t\treturn TFALSE;\n\t}\n\n\t// make an initial triangle --> face index list\n\tiNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);\n\n\t// make a welded index list of identical positions and attributes (pos, norm, texc)\n\t//printf(\"gen welded index list begin\\n\");\n\tGenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn);\n\t//printf(\"gen welded index list end\\n\");\n\n\t// Mark all degenerate triangles\n\tiTotTris = iNrTrianglesIn;\n\tiDegenTriangles = 0;\n\tfor (t=0; t<iTotTris; t++)\n\t{\n\t\tconst int i0 = piTriListIn[t*3+0];\n\t\tconst int i1 = piTriListIn[t*3+1];\n\t\tconst int i2 = piTriListIn[t*3+2];\n\t\tconst SVec3 p0 = GetPosition(pContext, i0);\n\t\tconst SVec3 p1 = GetPosition(pContext, i1);\n\t\tconst SVec3 p2 = GetPosition(pContext, i2);\n\t\tif (veq(p0,p1) || veq(p0,p2) || veq(p1,p2))\t// degenerate\n\t\t{\n\t\t\tpTriInfos[t].iFlag |= MARK_DEGENERATE;\n\t\t\t++iDegenTriangles;\n\t\t}\n\t}\n\tiNrTrianglesIn = iTotTris - iDegenTriangles;\n\n\t// mark all triangle pairs that belong to a quad with only one\n\t// good triangle. These need special treatment in DegenEpilogue().\n\t// Additionally, move all good triangles to the start of\n\t// pTriInfos[] and piTriListIn[] without changing order and\n\t// put the degenerate triangles last.\n\tDegenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris);\n\n\t\n\t// evaluate triangle level attributes and neighbor list\n\t//printf(\"gen neighbors list begin\\n\");\n\tInitTriInfo(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);\n\t//printf(\"gen neighbors list end\\n\");\n\n\t\n\t// based on the 4 rules, identify groups based on connectivity\n\tiNrMaxGroups = iNrTrianglesIn*3;\n\tpGroups = (SGroup *) malloc(sizeof(SGroup)*iNrMaxGroups);\n\tpiGroupTrianglesBuffer = (int *) malloc(sizeof(int)*iNrTrianglesIn*3);\n\tif (pGroups==NULL || piGroupTrianglesBuffer==NULL)\n\t{\n\t\tif (pGroups!=NULL) free(pGroups);\n\t\tif (piGroupTrianglesBuffer!=NULL) free(piGroupTrianglesBuffer);\n\t\tfree(piTriListIn);\n\t\tfree(pTriInfos);\n\t\treturn TFALSE;\n\t}\n\t//printf(\"gen 4rule groups begin\\n\");\n\tiNrActiveGroups =\n\t\tBuild4RuleGroups(pTriInfos, pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn);\n\t//printf(\"gen 4rule groups end\\n\");\n\n\t//\n\n\tpsTspace = (STSpace *) malloc(sizeof(STSpace)*iNrTSPaces);\n\tif (psTspace==NULL)\n\t{\n\t\tfree(piTriListIn);\n\t\tfree(pTriInfos);\n\t\tfree(pGroups);\n\t\tfree(piGroupTrianglesBuffer);\n\t\treturn TFALSE;\n\t}\n\tmemset(psTspace, 0, sizeof(STSpace)*iNrTSPaces);\n\tfor (t=0; t<iNrTSPaces; t++)\n\t{\n\t\tpsTspace[t].vOs.x=1.0f; psTspace[t].vOs.y=0.0f; psTspace[t].vOs.z=0.0f; psTspace[t].fMagS = 1.0f;\n\t\tpsTspace[t].vOt.x=0.0f; psTspace[t].vOt.y=1.0f; psTspace[t].vOt.z=0.0f; psTspace[t].fMagT = 1.0f;\n\t}\n\n\t// make tspaces, each group is split up into subgroups if necessary\n\t// based on fAngularThreshold. Finally a tangent space is made for\n\t// every resulting subgroup\n\t//printf(\"gen tspaces begin\\n\");\n\tbRes = GenerateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, pContext);\n\t//printf(\"gen tspaces end\\n\");\n\t\n\t// clean up\n\tfree(pGroups);\n\tfree(piGroupTrianglesBuffer);\n\n\tif (!bRes)\t// if an allocation in GenerateTSpaces() failed\n\t{\n\t\t// clean up and return false\n\t\tfree(pTriInfos); free(piTriListIn); free(psTspace);\n\t\treturn TFALSE;\n\t}\n\n\n\t// degenerate quads with one good triangle will be fixed by copying a space from\n\t// the good triangle to the coinciding vertex.\n\t// all other degenerate triangles will just copy a space from any good triangle\n\t// with the same welded index in piTriListIn[].\n\tDegenEpilogue(psTspace, pTriInfos, piTriListIn, pContext, iNrTrianglesIn, iTotTris);\n\n\tfree(pTriInfos); free(piTriListIn);\n\n\tindex = 0;\n\tfor (f=0; f<iNrFaces; f++)\n\t{\n\t\tconst int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f);\n\t\tif (verts!=3 && verts!=4) continue;\n\t\t\n\n\t\t// I've decided to let degenerate triangles and group-with-anythings\n\t\t// vary between left/right hand coordinate systems at the vertices.\n\t\t// All healthy triangles on the other hand are built to always be either or.\n\n\t\t/*// force the coordinate system orientation to be uniform for every face.\n\t\t// (this is already the case for good triangles but not for\n\t\t// degenerate ones and those with bGroupWithAnything==true)\n\t\tbool bOrient = psTspace[index].bOrient;\n\t\tif (psTspace[index].iCounter == 0)\t// tspace was not derived from a group\n\t\t{\n\t\t\t// look for a space created in GenerateTSpaces() by iCounter>0\n\t\t\tbool bNotFound = true;\n\t\t\tint i=1;\n\t\t\twhile (i<verts && bNotFound)\n\t\t\t{\n\t\t\t\tif (psTspace[index+i].iCounter > 0) bNotFound=false;\n\t\t\t\telse ++i;\n\t\t\t}\n\t\t\tif (!bNotFound) bOrient = psTspace[index+i].bOrient;\n\t\t}*/\n\n\t\t// set data\n\t\tfor (i=0; i<verts; i++)\n\t\t{\n\t\t\tconst STSpace * pTSpace = &psTspace[index];\n\t\t\tfloat tang[] = {pTSpace->vOs.x, pTSpace->vOs.y, pTSpace->vOs.z};\n\t\t\tfloat bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z};\n\t\t\tif (pContext->m_pInterface->m_setTSpace!=NULL)\n\t\t\t\tpContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i);\n\t\t\tif (pContext->m_pInterface->m_setTSpaceBasic!=NULL)\n\t\t\t\tpContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i);\n\n\t\t\t++index;\n\t\t}\n\t}\n\n\tfree(psTspace);\n\n\t\n\treturn TTRUE;\n}\n\n///////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\ntypedef struct {\n\tfloat vert[3];\n\tint index;\n} STmpVert;\n\nstatic const int g_iCells = 2048;\n\n#ifdef _MSC_VER\n#  define NOINLINE __declspec(noinline)\n#else\n#  define NOINLINE __attribute__ ((noinline))\n#endif\n\n// it is IMPORTANT that this function is called to evaluate the hash since\n// inlining could potentially reorder instructions and generate different\n// results for the same effective input value fVal.\nstatic NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal)\n{\n\tconst float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin));\n\tconst int iIndex = (int)fIndex;\n\treturn iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1);\n}\n\nstatic void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in);\nstatic void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries);\nstatic void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn);\n\nstatic void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn)\n{\n\n\t// Generate bounding box\n\tint * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL;\n\tSTmpVert * pTmpVert = NULL;\n\tint i=0, iChannel=0, k=0, e=0;\n\tint iMaxCount=0;\n\tSVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim;\n\tfloat fMin, fMax;\n\tfor (i=1; i<(iNrTrianglesIn*3); i++)\n\t{\n\t\tconst int index = piTriList_in_and_out[i];\n\n\t\tconst SVec3 vP = GetPosition(pContext, index);\n\t\tif (vMin.x > vP.x) vMin.x = vP.x;\n\t\telse if (vMax.x < vP.x) vMax.x = vP.x;\n\t\tif (vMin.y > vP.y) vMin.y = vP.y;\n\t\telse if (vMax.y < vP.y) vMax.y = vP.y;\n\t\tif (vMin.z > vP.z) vMin.z = vP.z;\n\t\telse if (vMax.z < vP.z) vMax.z = vP.z;\n\t}\n\n\tvDim = vsub(vMax,vMin);\n\tiChannel = 0;\n\tfMin = vMin.x; fMax=vMax.x;\n\tif (vDim.y>vDim.x && vDim.y>vDim.z)\n\t{\n\t\tiChannel=1;\n\t\tfMin = vMin.y;\n\t\tfMax = vMax.y;\n\t}\n\telse if (vDim.z>vDim.x)\n\t{\n\t\tiChannel=2;\n\t\tfMin = vMin.z;\n\t\tfMax = vMax.z;\n\t}\n\n\t// make allocations\n\tpiHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3);\n\tpiHashCount = (int *) malloc(sizeof(int)*g_iCells);\n\tpiHashOffsets = (int *) malloc(sizeof(int)*g_iCells);\n\tpiHashCount2 = (int *) malloc(sizeof(int)*g_iCells);\n\n\tif (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL)\n\t{\n\t\tif (piHashTable!=NULL) free(piHashTable);\n\t\tif (piHashCount!=NULL) free(piHashCount);\n\t\tif (piHashOffsets!=NULL) free(piHashOffsets);\n\t\tif (piHashCount2!=NULL) free(piHashCount2);\n\t\tGenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn);\n\t\treturn;\n\t}\n\tmemset(piHashCount, 0, sizeof(int)*g_iCells);\n\tmemset(piHashCount2, 0, sizeof(int)*g_iCells);\n\n\t// count amount of elements in each cell unit\n\tfor (i=0; i<(iNrTrianglesIn*3); i++)\n\t{\n\t\tconst int index = piTriList_in_and_out[i];\n\t\tconst SVec3 vP = GetPosition(pContext, index);\n\t\tconst float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z);\n\t\tconst int iCell = FindGridCell(fMin, fMax, fVal);\n\t\t++piHashCount[iCell];\n\t}\n\n\t// evaluate start index of each cell.\n\tpiHashOffsets[0]=0;\n\tfor (k=1; k<g_iCells; k++)\n\t\tpiHashOffsets[k]=piHashOffsets[k-1]+piHashCount[k-1];\n\n\t// insert vertices\n\tfor (i=0; i<(iNrTrianglesIn*3); i++)\n\t{\n\t\tconst int index = piTriList_in_and_out[i];\n\t\tconst SVec3 vP = GetPosition(pContext, index);\n\t\tconst float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z);\n\t\tconst int iCell = FindGridCell(fMin, fMax, fVal);\n\t\tint * pTable = NULL;\n\n\t\tassert(piHashCount2[iCell]<piHashCount[iCell]);\n\t\tpTable = &piHashTable[piHashOffsets[iCell]];\n\t\tpTable[piHashCount2[iCell]] = i;\t// vertex i has been inserted.\n\t\t++piHashCount2[iCell];\n\t}\n\tfor (k=0; k<g_iCells; k++)\n\t\tassert(piHashCount2[k] == piHashCount[k]);\t// verify the count\n\tfree(piHashCount2);\n\n\t// find maximum amount of entries in any hash entry\n\tiMaxCount = piHashCount[0];\n\tfor (k=1; k<g_iCells; k++)\n\t\tif (iMaxCount<piHashCount[k])\n\t\t\tiMaxCount=piHashCount[k];\n\tpTmpVert = (STmpVert *) malloc(sizeof(STmpVert)*iMaxCount);\n\t\n\n\t// complete the merge\n\tfor (k=0; k<g_iCells; k++)\n\t{\n\t\t// extract table of cell k and amount of entries in it\n\t\tint * pTable = &piHashTable[piHashOffsets[k]];\n\t\tconst int iEntries = piHashCount[k];\n\t\tif (iEntries < 2) continue;\n\n\t\tif (pTmpVert!=NULL)\n\t\t{\n\t\t\tfor (e=0; e<iEntries; e++)\n\t\t\t{\n\t\t\t\tint i = pTable[e];\n\t\t\t\tconst SVec3 vP = GetPosition(pContext, piTriList_in_and_out[i]);\n\t\t\t\tpTmpVert[e].vert[0] = vP.x; pTmpVert[e].vert[1] = vP.y;\n\t\t\t\tpTmpVert[e].vert[2] = vP.z; pTmpVert[e].index = i;\n\t\t\t}\n\t\t\tMergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, 0, iEntries-1);\n\t\t}\n\t\telse\n\t\t\tMergeVertsSlow(piTriList_in_and_out, pContext, pTable, iEntries);\n\t}\n\n\tif (pTmpVert!=NULL) { free(pTmpVert); }\n\tfree(piHashTable);\n\tfree(piHashCount);\n\tfree(piHashOffsets);\n}\n\nstatic void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in)\n{\n\t// make bbox\n\tint c=0, l=0, channel=0;\n\tfloat fvMin[3], fvMax[3];\n\tfloat dx=0, dy=0, dz=0, fSep=0;\n\tfor (c=0; c<3; c++)\n\t{\tfvMin[c]=pTmpVert[iL_in].vert[c]; fvMax[c]=fvMin[c];\t}\n\tfor (l=(iL_in+1); l<=iR_in; l++) {\n\t\tfor (c=0; c<3; c++) {\n\t\t\tif (fvMin[c]>pTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c];\n\t\t\tif (fvMax[c]<pTmpVert[l].vert[c]) fvMax[c]=pTmpVert[l].vert[c];\n\t\t}\n\t}\n\n\tdx = fvMax[0]-fvMin[0];\n\tdy = fvMax[1]-fvMin[1];\n\tdz = fvMax[2]-fvMin[2];\n\n\tchannel = 0;\n\tif (dy>dx && dy>dz) channel=1;\n\telse if (dz>dx) channel=2;\n\n\tfSep = 0.5f*(fvMax[channel]+fvMin[channel]);\n\n\t// stop if all vertices are NaNs\n\tif (!isfinite(fSep))\n\t\treturn;\n\n\t// terminate recursion when the separation/average value\n\t// is no longer strictly between fMin and fMax values.\n\tif (fSep>=fvMax[channel] || fSep<=fvMin[channel])\n\t{\n\t\t// complete the weld\n\t\tfor (l=iL_in; l<=iR_in; l++)\n\t\t{\n\t\t\tint i = pTmpVert[l].index;\n\t\t\tconst int index = piTriList_in_and_out[i];\n\t\t\tconst SVec3 vP = GetPosition(pContext, index);\n\t\t\tconst SVec3 vN = GetNormal(pContext, index);\n\t\t\tconst SVec3 vT = GetTexCoord(pContext, index);\n\n\t\t\ttbool bNotFound = TTRUE;\n\t\t\tint l2=iL_in, i2rec=-1;\n\t\t\twhile (l2<l && bNotFound)\n\t\t\t{\n\t\t\t\tconst int i2 = pTmpVert[l2].index;\n\t\t\t\tconst int index2 = piTriList_in_and_out[i2];\n\t\t\t\tconst SVec3 vP2 = GetPosition(pContext, index2);\n\t\t\t\tconst SVec3 vN2 = GetNormal(pContext, index2);\n\t\t\t\tconst SVec3 vT2 = GetTexCoord(pContext, index2);\n\t\t\t\ti2rec=i2;\n\n\t\t\t\t//if (vP==vP2 && vN==vN2 && vT==vT2)\n\t\t\t\tif (vP.x==vP2.x && vP.y==vP2.y && vP.z==vP2.z &&\n\t\t\t\t\tvN.x==vN2.x && vN.y==vN2.y && vN.z==vN2.z &&\n\t\t\t\t\tvT.x==vT2.x && vT.y==vT2.y && vT.z==vT2.z)\n\t\t\t\t\tbNotFound = TFALSE;\n\t\t\t\telse\n\t\t\t\t\t++l2;\n\t\t\t}\n\t\t\t\n\t\t\t// merge if previously found\n\t\t\tif (!bNotFound)\n\t\t\t\tpiTriList_in_and_out[i] = piTriList_in_and_out[i2rec];\n\t\t}\n\t}\n\telse\n\t{\n\t\tint iL=iL_in, iR=iR_in;\n\t\tassert((iR_in-iL_in)>0);\t// at least 2 entries\n\n\t\t// separate (by fSep) all points between iL_in and iR_in in pTmpVert[]\n\t\twhile (iL < iR)\n\t\t{\n\t\t\ttbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE;\n\t\t\twhile ((!bReadyLeftSwap) && iL<iR)\n\t\t\t{\n\t\t\t\tassert(iL>=iL_in && iL<=iR_in);\n\t\t\t\tbReadyLeftSwap = !(pTmpVert[iL].vert[channel]<fSep);\n\t\t\t\tif (!bReadyLeftSwap) ++iL;\n\t\t\t}\n\t\t\twhile ((!bReadyRightSwap) && iL<iR)\n\t\t\t{\n\t\t\t\tassert(iR>=iL_in && iR<=iR_in);\n\t\t\t\tbReadyRightSwap = pTmpVert[iR].vert[channel]<fSep;\n\t\t\t\tif (!bReadyRightSwap) --iR;\n\t\t\t}\n\t\t\tassert( (iL<iR) || !(bReadyLeftSwap && bReadyRightSwap) );\n\n\t\t\tif (bReadyLeftSwap && bReadyRightSwap)\n\t\t\t{\n\t\t\t\tconst STmpVert sTmp = pTmpVert[iL];\n\t\t\t\tassert(iL<iR);\n\t\t\t\tpTmpVert[iL] = pTmpVert[iR];\n\t\t\t\tpTmpVert[iR] = sTmp;\n\t\t\t\t++iL; --iR;\n\t\t\t}\n\t\t}\n\n\t\tassert(iL==(iR+1) || (iL==iR));\n\t\tif (iL==iR)\n\t\t{\n\t\t\tconst tbool bReadyRightSwap = pTmpVert[iR].vert[channel]<fSep;\n\t\t\tif (bReadyRightSwap) ++iL;\n\t\t\telse --iR;\n\t\t}\n\n\t\t// only need to weld when there is more than 1 instance of the (x,y,z)\n\t\tif (iL_in < iR)\n\t\t\tMergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, iL_in, iR);\t// weld all left of fSep\n\t\tif (iL < iR_in)\n\t\t\tMergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, iL, iR_in);\t// weld all right of (or equal to) fSep\n\t}\n}\n\nstatic void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries)\n{\n\t// this can be optimized further using a tree structure or more hashing.\n\tint e=0;\n\tfor (e=0; e<iEntries; e++)\n\t{\n\t\tint i = pTable[e];\n\t\tconst int index = piTriList_in_and_out[i];\n\t\tconst SVec3 vP = GetPosition(pContext, index);\n\t\tconst SVec3 vN = GetNormal(pContext, index);\n\t\tconst SVec3 vT = GetTexCoord(pContext, index);\n\n\t\ttbool bNotFound = TTRUE;\n\t\tint e2=0, i2rec=-1;\n\t\twhile (e2<e && bNotFound)\n\t\t{\n\t\t\tconst int i2 = pTable[e2];\n\t\t\tconst int index2 = piTriList_in_and_out[i2];\n\t\t\tconst SVec3 vP2 = GetPosition(pContext, index2);\n\t\t\tconst SVec3 vN2 = GetNormal(pContext, index2);\n\t\t\tconst SVec3 vT2 = GetTexCoord(pContext, index2);\n\t\t\ti2rec = i2;\n\n\t\t\tif (veq(vP,vP2) && veq(vN,vN2) && veq(vT,vT2))\n\t\t\t\tbNotFound = TFALSE;\n\t\t\telse\n\t\t\t\t++e2;\n\t\t}\n\t\t\n\t\t// merge if previously found\n\t\tif (!bNotFound)\n\t\t\tpiTriList_in_and_out[i] = piTriList_in_and_out[i2rec];\n\t}\n}\n\nstatic void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn)\n{\n\tint iNumUniqueVerts = 0, t=0, i=0;\n\tfor (t=0; t<iNrTrianglesIn; t++)\n\t{\n\t\tfor (i=0; i<3; i++)\n\t\t{\n\t\t\tconst int offs = t*3 + i;\n\t\t\tconst int index = piTriList_in_and_out[offs];\n\n\t\t\tconst SVec3 vP = GetPosition(pContext, index);\n\t\t\tconst SVec3 vN = GetNormal(pContext, index);\n\t\t\tconst SVec3 vT = GetTexCoord(pContext, index);\n\n\t\t\ttbool bFound = TFALSE;\n\t\t\tint t2=0, index2rec=-1;\n\t\t\twhile (!bFound && t2<=t)\n\t\t\t{\n\t\t\t\tint j=0;\n\t\t\t\twhile (!bFound && j<3)\n\t\t\t\t{\n\t\t\t\t\tconst int index2 = piTriList_in_and_out[t2*3 + j];\n\t\t\t\t\tconst SVec3 vP2 = GetPosition(pContext, index2);\n\t\t\t\t\tconst SVec3 vN2 = GetNormal(pContext, index2);\n\t\t\t\t\tconst SVec3 vT2 = GetTexCoord(pContext, index2);\n\t\t\t\t\t\n\t\t\t\t\tif (veq(vP,vP2) && veq(vN,vN2) && veq(vT,vT2))\n\t\t\t\t\t\tbFound = TTRUE;\n\t\t\t\t\telse\n\t\t\t\t\t\t++j;\n\t\t\t\t}\n\t\t\t\tif (!bFound) ++t2;\n\t\t\t}\n\n\t\t\tassert(bFound);\n\t\t\t// if we found our own\n\t\t\tif (index2rec == index) { ++iNumUniqueVerts; }\n\n\t\t\tpiTriList_in_and_out[offs] = index2rec;\n\t\t}\n\t}\n}\n\nstatic int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn)\n{\n\tint iTSpacesOffs = 0, f=0, t=0;\n\tint iDstTriIndex = 0;\n\tfor (f=0; f<pContext->m_pInterface->m_getNumFaces(pContext); f++)\n\t{\n\t\tconst int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f);\n\t\tif (verts!=3 && verts!=4) continue;\n\n\t\tpTriInfos[iDstTriIndex].iOrgFaceNumber = f;\n\t\tpTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs;\n\n\t\tif (verts==3)\n\t\t{\n\t\t\tunsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num;\n\t\t\tpVerts[0]=0; pVerts[1]=1; pVerts[2]=2;\n\t\t\tpiTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0);\n\t\t\tpiTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1);\n\t\t\tpiTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2);\n\t\t\t++iDstTriIndex;\t// next\n\t\t}\n\t\telse\n\t\t{\n\t\t\t{\n\t\t\t\tpTriInfos[iDstTriIndex+1].iOrgFaceNumber = f;\n\t\t\t\tpTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs;\n\t\t\t}\n\n\t\t\t{\n\t\t\t\t// need an order independent way to evaluate\n\t\t\t\t// tspace on quads. This is done by splitting\n\t\t\t\t// along the shortest diagonal.\n\t\t\t\tconst int i0 = MakeIndex(f, 0);\n\t\t\t\tconst int i1 = MakeIndex(f, 1);\n\t\t\t\tconst int i2 = MakeIndex(f, 2);\n\t\t\t\tconst int i3 = MakeIndex(f, 3);\n\t\t\t\tconst SVec3 T0 = GetTexCoord(pContext, i0);\n\t\t\t\tconst SVec3 T1 = GetTexCoord(pContext, i1);\n\t\t\t\tconst SVec3 T2 = GetTexCoord(pContext, i2);\n\t\t\t\tconst SVec3 T3 = GetTexCoord(pContext, i3);\n\t\t\t\tconst float distSQ_02 = LengthSquared(vsub(T2,T0));\n\t\t\t\tconst float distSQ_13 = LengthSquared(vsub(T3,T1));\n\t\t\t\ttbool bQuadDiagIs_02;\n\t\t\t\tif (distSQ_02<distSQ_13)\n\t\t\t\t\tbQuadDiagIs_02 = TTRUE;\n\t\t\t\telse if (distSQ_13<distSQ_02)\n\t\t\t\t\tbQuadDiagIs_02 = TFALSE;\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tconst SVec3 P0 = GetPosition(pContext, i0);\n\t\t\t\t\tconst SVec3 P1 = GetPosition(pContext, i1);\n\t\t\t\t\tconst SVec3 P2 = GetPosition(pContext, i2);\n\t\t\t\t\tconst SVec3 P3 = GetPosition(pContext, i3);\n\t\t\t\t\tconst float distSQ_02 = LengthSquared(vsub(P2,P0));\n\t\t\t\t\tconst float distSQ_13 = LengthSquared(vsub(P3,P1));\n\n\t\t\t\t\tbQuadDiagIs_02 = distSQ_13<distSQ_02 ? TFALSE : TTRUE;\n\t\t\t\t}\n\n\t\t\t\tif (bQuadDiagIs_02)\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\tunsigned char * pVerts_A = pTriInfos[iDstTriIndex].vert_num;\n\t\t\t\t\t\tpVerts_A[0]=0; pVerts_A[1]=1; pVerts_A[2]=2;\n\t\t\t\t\t}\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+0] = i0;\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+1] = i1;\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+2] = i2;\n\t\t\t\t\t++iDstTriIndex;\t// next\n\t\t\t\t\t{\n\t\t\t\t\t\tunsigned char * pVerts_B = pTriInfos[iDstTriIndex].vert_num;\n\t\t\t\t\t\tpVerts_B[0]=0; pVerts_B[1]=2; pVerts_B[2]=3;\n\t\t\t\t\t}\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+0] = i0;\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+1] = i2;\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+2] = i3;\n\t\t\t\t\t++iDstTriIndex;\t// next\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\tunsigned char * pVerts_A = pTriInfos[iDstTriIndex].vert_num;\n\t\t\t\t\t\tpVerts_A[0]=0; pVerts_A[1]=1; pVerts_A[2]=3;\n\t\t\t\t\t}\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+0] = i0;\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+1] = i1;\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+2] = i3;\n\t\t\t\t\t++iDstTriIndex;\t// next\n\t\t\t\t\t{\n\t\t\t\t\t\tunsigned char * pVerts_B = pTriInfos[iDstTriIndex].vert_num;\n\t\t\t\t\t\tpVerts_B[0]=1; pVerts_B[1]=2; pVerts_B[2]=3;\n\t\t\t\t\t}\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+0] = i1;\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+1] = i2;\n\t\t\t\t\tpiTriList_out[iDstTriIndex*3+2] = i3;\n\t\t\t\t\t++iDstTriIndex;\t// next\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tiTSpacesOffs += verts;\n\t\tassert(iDstTriIndex<=iNrTrianglesIn);\n\t}\n\n\tfor (t=0; t<iNrTrianglesIn; t++)\n\t\tpTriInfos[t].iFlag = 0;\n\n\t// return total amount of tspaces\n\treturn iTSpacesOffs;\n}\n\nstatic SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index)\n{\n\tint iF, iI;\n\tSVec3 res; float pos[3];\n\tIndexToData(&iF, &iI, index);\n\tpContext->m_pInterface->m_getPosition(pContext, pos, iF, iI);\n\tres.x=pos[0]; res.y=pos[1]; res.z=pos[2];\n\treturn res;\n}\n\nstatic SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index)\n{\n\tint iF, iI;\n\tSVec3 res; float norm[3];\n\tIndexToData(&iF, &iI, index);\n\tpContext->m_pInterface->m_getNormal(pContext, norm, iF, iI);\n\tres.x=norm[0]; res.y=norm[1]; res.z=norm[2];\n\treturn res;\n}\n\nstatic SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index)\n{\n\tint iF, iI;\n\tSVec3 res; float texc[2];\n\tIndexToData(&iF, &iI, index);\n\tpContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI);\n\tres.x=texc[0]; res.y=texc[1]; res.z=1.0f;\n\treturn res;\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////////////\n/////////////////////////////////////////////////////////////////////////////////////////////////////\n\ntypedef union {\n\tstruct\n\t{\n\t\tint i0, i1, f;\n\t};\n\tint array[3];\n} SEdge;\n\nstatic void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn);\nstatic void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn);\n\n// returns the texture area times 2\nstatic float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[])\n{\n\tconst SVec3 t1 = GetTexCoord(pContext, indices[0]);\n\tconst SVec3 t2 = GetTexCoord(pContext, indices[1]);\n\tconst SVec3 t3 = GetTexCoord(pContext, indices[2]);\n\n\tconst float t21x = t2.x-t1.x;\n\tconst float t21y = t2.y-t1.y;\n\tconst float t31x = t3.x-t1.x;\n\tconst float t31y = t3.y-t1.y;\n\n\tconst float fSignedAreaSTx2 = t21x*t31y - t21y*t31x;\n\n\treturn fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2;\n}\n\nstatic void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn)\n{\n\tint f=0, i=0, t=0;\n\t// pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function.\n\n\t// generate neighbor info list\n\tfor (f=0; f<iNrTrianglesIn; f++)\n\t\tfor (i=0; i<3; i++)\n\t\t{\n\t\t\tpTriInfos[f].FaceNeighbors[i] = -1;\n\t\t\tpTriInfos[f].AssignedGroup[i] = NULL;\n\n\t\t\tpTriInfos[f].vOs.x=0.0f; pTriInfos[f].vOs.y=0.0f; pTriInfos[f].vOs.z=0.0f;\n\t\t\tpTriInfos[f].vOt.x=0.0f; pTriInfos[f].vOt.y=0.0f; pTriInfos[f].vOt.z=0.0f;\n\t\t\tpTriInfos[f].fMagS = 0;\n\t\t\tpTriInfos[f].fMagT = 0;\n\n\t\t\t// assumed bad\n\t\t\tpTriInfos[f].iFlag |= GROUP_WITH_ANY;\n\t\t}\n\n\t// evaluate first order derivatives\n\tfor (f=0; f<iNrTrianglesIn; f++)\n\t{\n\t\t// initial values\n\t\tconst SVec3 v1 = GetPosition(pContext, piTriListIn[f*3+0]);\n\t\tconst SVec3 v2 = GetPosition(pContext, piTriListIn[f*3+1]);\n\t\tconst SVec3 v3 = GetPosition(pContext, piTriListIn[f*3+2]);\n\t\tconst SVec3 t1 = GetTexCoord(pContext, piTriListIn[f*3+0]);\n\t\tconst SVec3 t2 = GetTexCoord(pContext, piTriListIn[f*3+1]);\n\t\tconst SVec3 t3 = GetTexCoord(pContext, piTriListIn[f*3+2]);\n\n\t\tconst float t21x = t2.x-t1.x;\n\t\tconst float t21y = t2.y-t1.y;\n\t\tconst float t31x = t3.x-t1.x;\n\t\tconst float t31y = t3.y-t1.y;\n\t\tconst SVec3 d1 = vsub(v2,v1);\n\t\tconst SVec3 d2 = vsub(v3,v1);\n\n\t\tconst float fSignedAreaSTx2 = t21x*t31y - t21y*t31x;\n\t\t//assert(fSignedAreaSTx2!=0);\n\t\tSVec3 vOs = vsub(vscale(t31y,d1), vscale(t21y,d2));\t// eq 18\n\t\tSVec3 vOt = vadd(vscale(-t31x,d1), vscale(t21x,d2)); // eq 19\n\n\t\tpTriInfos[f].iFlag |= (fSignedAreaSTx2>0 ? ORIENT_PRESERVING : 0);\n\n\t\tif ( NotZero(fSignedAreaSTx2) )\n\t\t{\n\t\t\tconst float fAbsArea = fabsf(fSignedAreaSTx2);\n\t\t\tconst float fLenOs = Length(vOs);\n\t\t\tconst float fLenOt = Length(vOt);\n\t\t\tconst float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f;\n\t\t\tif ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs);\n\t\t\tif ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt);\n\n\t\t\t// evaluate magnitudes prior to normalization of vOs and vOt\n\t\t\tpTriInfos[f].fMagS = fLenOs / fAbsArea;\n\t\t\tpTriInfos[f].fMagT = fLenOt / fAbsArea;\n\n\t\t\t// if this is a good triangle\n\t\t\tif ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT))\n\t\t\t\tpTriInfos[f].iFlag &= (~GROUP_WITH_ANY);\n\t\t}\n\t}\n\n\t// force otherwise healthy quads to a fixed orientation\n\twhile (t<(iNrTrianglesIn-1))\n\t{\n\t\tconst int iFO_a = pTriInfos[t].iOrgFaceNumber;\n\t\tconst int iFO_b = pTriInfos[t+1].iOrgFaceNumber;\n\t\tif (iFO_a==iFO_b)\t// this is a quad\n\t\t{\n\t\t\tconst tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE;\n\t\t\tconst tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE;\n\t\t\t\n\t\t\t// bad triangles should already have been removed by\n\t\t\t// DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false\n\t\t\tif ((bIsDeg_a||bIsDeg_b)==TFALSE)\n\t\t\t{\n\t\t\t\tconst tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;\n\t\t\t\tconst tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;\n\t\t\t\t// if this happens the quad has extremely bad mapping!!\n\t\t\t\tif (bOrientA!=bOrientB)\n\t\t\t\t{\n\t\t\t\t\t//printf(\"found quad with bad mapping\\n\");\n\t\t\t\t\ttbool bChooseOrientFirstTri = TFALSE;\n\t\t\t\t\tif ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE;\n\t\t\t\t\telse if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) )\n\t\t\t\t\t\tbChooseOrientFirstTri = TTRUE;\n\n\t\t\t\t\t// force match\n\t\t\t\t\t{\n\t\t\t\t\t\tconst int t0 = bChooseOrientFirstTri ? t : (t+1);\n\t\t\t\t\t\tconst int t1 = bChooseOrientFirstTri ? (t+1) : t;\n\t\t\t\t\t\tpTriInfos[t1].iFlag &= (~ORIENT_PRESERVING);\t// clear first\n\t\t\t\t\t\tpTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING);\t// copy bit\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tt += 2;\n\t\t}\n\t\telse\n\t\t\t++t;\n\t}\n\t\n\t// match up edge pairs\n\t{\n\t\tSEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3);\n\t\tif (pEdges==NULL)\n\t\t\tBuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn);\n\t\telse\n\t\t{\n\t\t\tBuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn);\n\t\n\t\t\tfree(pEdges);\n\t\t}\n\t}\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////////////\n/////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstatic tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup);\nstatic void AddTriToGroup(SGroup * pGroup, const int iTriIndex);\n\nstatic int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn)\n{\n\tconst int iNrMaxGroups = iNrTrianglesIn*3;\n\tint iNrActiveGroups = 0;\n\tint iOffset = 0, f=0, i=0;\n\t(void)iNrMaxGroups;  /* quiet warnings in non debug mode */\n\tfor (f=0; f<iNrTrianglesIn; f++)\n\t{\n\t\tfor (i=0; i<3; i++)\n\t\t{\n\t\t\t// if not assigned to a group\n\t\t\tif ((pTriInfos[f].iFlag&GROUP_WITH_ANY)==0 && pTriInfos[f].AssignedGroup[i]==NULL)\n\t\t\t{\n\t\t\t\ttbool bOrPre;\n\t\t\t\tint neigh_indexL, neigh_indexR;\n\t\t\t\tconst int vert_index = piTriListIn[f*3+i];\n\t\t\t\tassert(iNrActiveGroups<iNrMaxGroups);\n\t\t\t\tpTriInfos[f].AssignedGroup[i] = &pGroups[iNrActiveGroups];\n\t\t\t\tpTriInfos[f].AssignedGroup[i]->iVertexRepresentitive = vert_index;\n\t\t\t\tpTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0;\n\t\t\t\tpTriInfos[f].AssignedGroup[i]->iNrFaces = 0;\n\t\t\t\tpTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset];\n\t\t\t\t++iNrActiveGroups;\n\n\t\t\t\tAddTriToGroup(pTriInfos[f].AssignedGroup[i], f);\n\t\t\t\tbOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;\n\t\t\t\tneigh_indexL = pTriInfos[f].FaceNeighbors[i];\n\t\t\t\tneigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2];\n\t\t\t\tif (neigh_indexL>=0) // neighbor\n\t\t\t\t{\n\t\t\t\t\tconst tbool bAnswer =\n\t\t\t\t\t\tAssignRecur(piTriListIn, pTriInfos, neigh_indexL,\n\t\t\t\t\t\t\t\t\tpTriInfos[f].AssignedGroup[i] );\n\t\t\t\t\t\n\t\t\t\t\tconst tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;\n\t\t\t\t\tconst tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE;\n\t\t\t\t\tassert(bAnswer || bDiff);\n\t\t\t\t\t(void)bAnswer, (void)bDiff;  /* quiet warnings in non debug mode */\n\t\t\t\t}\n\t\t\t\tif (neigh_indexR>=0) // neighbor\n\t\t\t\t{\n\t\t\t\t\tconst tbool bAnswer =\n\t\t\t\t\t\tAssignRecur(piTriListIn, pTriInfos, neigh_indexR,\n\t\t\t\t\t\t\t\t\tpTriInfos[f].AssignedGroup[i] );\n\n\t\t\t\t\tconst tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;\n\t\t\t\t\tconst tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE;\n\t\t\t\t\tassert(bAnswer || bDiff);\n\t\t\t\t\t(void)bAnswer, (void)bDiff;  /* quiet warnings in non debug mode */\n\t\t\t\t}\n\n\t\t\t\t// update offset\n\t\t\t\tiOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces;\n\t\t\t\t// since the groups are disjoint a triangle can never\n\t\t\t\t// belong to more than 3 groups. Subsequently something\n\t\t\t\t// is completely screwed if this assertion ever hits.\n\t\t\t\tassert(iOffset <= iNrMaxGroups);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn iNrActiveGroups;\n}\n\nstatic void AddTriToGroup(SGroup * pGroup, const int iTriIndex)\n{\n\tpGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex;\n\t++pGroup->iNrFaces;\n}\n\nstatic tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[],\n\t\t\t\t const int iMyTriIndex, SGroup * pGroup)\n{\n\tSTriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex];\n\n\t// track down vertex\n\tconst int iVertRep = pGroup->iVertexRepresentitive;\n\tconst int * pVerts = &piTriListIn[3*iMyTriIndex+0];\n\tint i=-1;\n\tif (pVerts[0]==iVertRep) i=0;\n\telse if (pVerts[1]==iVertRep) i=1;\n\telse if (pVerts[2]==iVertRep) i=2;\n\tassert(i>=0 && i<3);\n\n\t// early out\n\tif (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE;\n\telse if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE;\n\tif ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0)\n\t{\n\t\t// first to group with a group-with-anything triangle\n\t\t// determines it's orientation.\n\t\t// This is the only existing order dependency in the code!!\n\t\tif ( pMyTriInfo->AssignedGroup[0] == NULL &&\n\t\t\tpMyTriInfo->AssignedGroup[1] == NULL &&\n\t\t\tpMyTriInfo->AssignedGroup[2] == NULL )\n\t\t{\n\t\t\tpMyTriInfo->iFlag &= (~ORIENT_PRESERVING);\n\t\t\tpMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0);\n\t\t}\n\t}\n\t{\n\t\tconst tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;\n\t\tif (bOrient != pGroup->bOrientPreservering) return TFALSE;\n\t}\n\n\tAddTriToGroup(pGroup, iMyTriIndex);\n\tpMyTriInfo->AssignedGroup[i] = pGroup;\n\n\t{\n\t\tconst int neigh_indexL = pMyTriInfo->FaceNeighbors[i];\n\t\tconst int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2];\n\t\tif (neigh_indexL>=0)\n\t\t\tAssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup);\n\t\tif (neigh_indexR>=0)\n\t\t\tAssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup);\n\t}\n\n\n\n\treturn TTRUE;\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////////////\n/////////////////////////////////////////////////////////////////////////////////////////////////////\n\nstatic tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2);\nstatic void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed);\nstatic STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive);\n\nstatic tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[],\n                             const int iNrActiveGroups, const int piTriListIn[], const float fThresCos,\n                             const SMikkTSpaceContext * pContext)\n{\n\tSTSpace * pSubGroupTspace = NULL;\n\tSSubGroup * pUniSubGroups = NULL;\n\tint * pTmpMembers = NULL;\n\tint iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0;\n\tfor (g=0; g<iNrActiveGroups; g++)\n\t\tif (iMaxNrFaces < pGroups[g].iNrFaces)\n\t\t\tiMaxNrFaces = pGroups[g].iNrFaces;\n\n\tif (iMaxNrFaces == 0) return TTRUE;\n\n\t// make initial allocations\n\tpSubGroupTspace = (STSpace *) malloc(sizeof(STSpace)*iMaxNrFaces);\n\tpUniSubGroups = (SSubGroup *) malloc(sizeof(SSubGroup)*iMaxNrFaces);\n\tpTmpMembers = (int *) malloc(sizeof(int)*iMaxNrFaces);\n\tif (pSubGroupTspace==NULL || pUniSubGroups==NULL || pTmpMembers==NULL)\n\t{\n\t\tif (pSubGroupTspace!=NULL) free(pSubGroupTspace);\n\t\tif (pUniSubGroups!=NULL) free(pUniSubGroups);\n\t\tif (pTmpMembers!=NULL) free(pTmpMembers);\n\t\treturn TFALSE;\n\t}\n\n\n\tiUniqueTspaces = 0;\n\tfor (g=0; g<iNrActiveGroups; g++)\n\t{\n\t\tconst SGroup * pGroup = &pGroups[g];\n\t\tint iUniqueSubGroups = 0, s=0;\n\n\t\tfor (i=0; i<pGroup->iNrFaces; i++)\t// triangles\n\t\t{\n\t\t\tconst int f = pGroup->pFaceIndices[i];\t// triangle number\n\t\t\tint index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0;\n\t\t\tSSubGroup tmp_group;\n\t\t\ttbool bFound;\n\t\t\tSVec3 n, vOs, vOt;\n\t\t\tif (pTriInfos[f].AssignedGroup[0]==pGroup) index=0;\n\t\t\telse if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1;\n\t\t\telse if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2;\n\t\t\tassert(index>=0 && index<3);\n\n\t\t\tiVertIndex = piTriListIn[f*3+index];\n\t\t\tassert(iVertIndex==pGroup->iVertexRepresentitive);\n\n\t\t\t// is normalized already\n\t\t\tn = GetNormal(pContext, iVertIndex);\n\t\t\t\n\t\t\t// project\n\t\t\tvOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n));\n\t\t\tvOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n));\n\t\t\tif ( VNotZero(vOs) ) vOs = Normalize(vOs);\n\t\t\tif ( VNotZero(vOt) ) vOt = Normalize(vOt);\n\n\t\t\t// original face number\n\t\t\tiOF_1 = pTriInfos[f].iOrgFaceNumber;\n\t\t\t\n\t\t\tiMembers = 0;\n\t\t\tfor (j=0; j<pGroup->iNrFaces; j++)\n\t\t\t{\n\t\t\t\tconst int t = pGroup->pFaceIndices[j];\t// triangle number\n\t\t\t\tconst int iOF_2 = pTriInfos[t].iOrgFaceNumber;\n\n\t\t\t\t// project\n\t\t\t\tSVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n));\n\t\t\t\tSVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n));\n\t\t\t\tif ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2);\n\t\t\t\tif ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2);\n\n\t\t\t\t{\n\t\t\t\t\tconst tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE;\n\t\t\t\t\t// make sure triangles which belong to the same quad are joined.\n\t\t\t\t\tconst tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE;\n\n\t\t\t\t\tconst float fCosS = vdot(vOs,vOs2);\n\t\t\t\t\tconst float fCosT = vdot(vOt,vOt2);\n\n\t\t\t\t\tassert(f!=t || bSameOrgFace);\t// sanity check\n\t\t\t\t\tif (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos))\n\t\t\t\t\t\tpTmpMembers[iMembers++] = t;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// sort pTmpMembers\n\t\t\ttmp_group.iNrFaces = iMembers;\n\t\t\ttmp_group.pTriMembers = pTmpMembers;\n\t\t\tif (iMembers>1)\n\t\t\t{\n\t\t\t\tunsigned int uSeed = INTERNAL_RND_SORT_SEED;\t// could replace with a random seed?\n\t\t\t\tQuickSort(pTmpMembers, 0, iMembers-1, uSeed);\n\t\t\t}\n\n\t\t\t// look for an existing match\n\t\t\tbFound = TFALSE;\n\t\t\tl=0;\n\t\t\twhile (l<iUniqueSubGroups && !bFound)\n\t\t\t{\n\t\t\t\tbFound = CompareSubGroups(&tmp_group, &pUniSubGroups[l]);\n\t\t\t\tif (!bFound) ++l;\n\t\t\t}\n\t\t\t\n\t\t\t// assign tangent space index\n\t\t\tassert(bFound || l==iUniqueSubGroups);\n\t\t\t//piTempTangIndices[f*3+index] = iUniqueTspaces+l;\n\n\t\t\t// if no match was found we allocate a new subgroup\n\t\t\tif (!bFound)\n\t\t\t{\n\t\t\t\t// insert new subgroup\n\t\t\t\tint * pIndices = (int *) malloc(sizeof(int)*iMembers);\n\t\t\t\tif (pIndices==NULL)\n\t\t\t\t{\n\t\t\t\t\t// clean up and return false\n\t\t\t\t\tint s=0;\n\t\t\t\t\tfor (s=0; s<iUniqueSubGroups; s++)\n\t\t\t\t\t\tfree(pUniSubGroups[s].pTriMembers);\n\t\t\t\t\tfree(pUniSubGroups);\n\t\t\t\t\tfree(pTmpMembers);\n\t\t\t\t\tfree(pSubGroupTspace);\n\t\t\t\t\treturn TFALSE;\n\t\t\t\t}\n\t\t\t\tpUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers;\n\t\t\t\tpUniSubGroups[iUniqueSubGroups].pTriMembers = pIndices;\n\t\t\t\tmemcpy(pIndices, tmp_group.pTriMembers, iMembers*sizeof(int));\n\t\t\t\tpSubGroupTspace[iUniqueSubGroups] =\n\t\t\t\t\tEvalTspace(tmp_group.pTriMembers, iMembers, piTriListIn, pTriInfos, pContext, pGroup->iVertexRepresentitive);\n\t\t\t\t++iUniqueSubGroups;\n\t\t\t}\n\n\t\t\t// output tspace\n\t\t\t{\n\t\t\t\tconst int iOffs = pTriInfos[f].iTSpacesOffs;\n\t\t\t\tconst int iVert = pTriInfos[f].vert_num[index];\n\t\t\t\tSTSpace * pTS_out = &psTspace[iOffs+iVert];\n\t\t\t\tassert(pTS_out->iCounter<2);\n\t\t\t\tassert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering);\n\t\t\t\tif (pTS_out->iCounter==1)\n\t\t\t\t{\n\t\t\t\t\t*pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]);\n\t\t\t\t\tpTS_out->iCounter = 2;\t// update counter\n\t\t\t\t\tpTS_out->bOrient = pGroup->bOrientPreservering;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tassert(pTS_out->iCounter==0);\n\t\t\t\t\t*pTS_out = pSubGroupTspace[l];\n\t\t\t\t\tpTS_out->iCounter = 1;\t// update counter\n\t\t\t\t\tpTS_out->bOrient = pGroup->bOrientPreservering;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// clean up and offset iUniqueTspaces\n\t\tfor (s=0; s<iUniqueSubGroups; s++)\n\t\t\tfree(pUniSubGroups[s].pTriMembers);\n\t\tiUniqueTspaces += iUniqueSubGroups;\n\t}\n\n\t// clean up\n\tfree(pUniSubGroups);\n\tfree(pTmpMembers);\n\tfree(pSubGroupTspace);\n\n\treturn TTRUE;\n}\n\nstatic STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[],\n                          const SMikkTSpaceContext * pContext, const int iVertexRepresentitive)\n{\n\tSTSpace res;\n\tfloat fAngleSum = 0;\n\tint face=0;\n\tres.vOs.x=0.0f; res.vOs.y=0.0f; res.vOs.z=0.0f;\n\tres.vOt.x=0.0f; res.vOt.y=0.0f; res.vOt.z=0.0f;\n\tres.fMagS = 0; res.fMagT = 0;\n\n\tfor (face=0; face<iFaces; face++)\n\t{\n\t\tconst int f = face_indices[face];\n\n\t\t// only valid triangles get to add their contribution\n\t\tif ( (pTriInfos[f].iFlag&GROUP_WITH_ANY)==0 )\n\t\t{\n\t\t\tSVec3 n, vOs, vOt, p0, p1, p2, v1, v2;\n\t\t\tfloat fCos, fAngle, fMagS, fMagT;\n\t\t\tint i=-1, index=-1, i0=-1, i1=-1, i2=-1;\n\t\t\tif (piTriListIn[3*f+0]==iVertexRepresentitive) i=0;\n\t\t\telse if (piTriListIn[3*f+1]==iVertexRepresentitive) i=1;\n\t\t\telse if (piTriListIn[3*f+2]==iVertexRepresentitive) i=2;\n\t\t\tassert(i>=0 && i<3);\n\n\t\t\t// project\n\t\t\tindex = piTriListIn[3*f+i];\n\t\t\tn = GetNormal(pContext, index);\n\t\t\tvOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n));\n\t\t\tvOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n));\n\t\t\tif ( VNotZero(vOs) ) vOs = Normalize(vOs);\n\t\t\tif ( VNotZero(vOt) ) vOt = Normalize(vOt);\n\n\t\t\ti2 = piTriListIn[3*f + (i<2?(i+1):0)];\n\t\t\ti1 = piTriListIn[3*f + i];\n\t\t\ti0 = piTriListIn[3*f + (i>0?(i-1):2)];\n\n\t\t\tp0 = GetPosition(pContext, i0);\n\t\t\tp1 = GetPosition(pContext, i1);\n\t\t\tp2 = GetPosition(pContext, i2);\n\t\t\tv1 = vsub(p0,p1);\n\t\t\tv2 = vsub(p2,p1);\n\n\t\t\t// project\n\t\t\tv1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1);\n\t\t\tv2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2);\n\n\t\t\t// weight contribution by the angle\n\t\t\t// between the two edge vectors\n\t\t\tfCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos);\n\t\t\tfAngle = (float) acos(fCos);\n\t\t\tfMagS = pTriInfos[f].fMagS;\n\t\t\tfMagT = pTriInfos[f].fMagT;\n\n\t\t\tres.vOs=vadd(res.vOs, vscale(fAngle,vOs));\n\t\t\tres.vOt=vadd(res.vOt,vscale(fAngle,vOt));\n\t\t\tres.fMagS+=(fAngle*fMagS);\n\t\t\tres.fMagT+=(fAngle*fMagT);\n\t\t\tfAngleSum += fAngle;\n\t\t}\n\t}\n\n\t// normalize\n\tif ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs);\n\tif ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt);\n\tif (fAngleSum>0)\n\t{\n\t\tres.fMagS /= fAngleSum;\n\t\tres.fMagT /= fAngleSum;\n\t}\n\n\treturn res;\n}\n\nstatic tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2)\n{\n\ttbool bStillSame=TTRUE;\n\tint i=0;\n\tif (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE;\n\twhile (i<pg1->iNrFaces && bStillSame)\n\t{\n\t\tbStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE;\n\t\tif (bStillSame) ++i;\n\t}\n\treturn bStillSame;\n}\n\nstatic void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed)\n{\n\tint iL, iR, n, index, iMid, iTmp;\n\n\t// Random\n\tunsigned int t=uSeed&31;\n\tt=(uSeed<<t)|(uSeed>>(32-t));\n\tuSeed=uSeed+t+3;\n\t// Random end\n\n\tiL=iLeft; iR=iRight;\n\tn = (iR-iL)+1;\n\tassert(n>=0);\n\tindex = (int) (uSeed%n);\n\n\tiMid=pSortBuffer[index + iL];\n\n\n\tdo\n\t{\n\t\twhile (pSortBuffer[iL] < iMid)\n\t\t\t++iL;\n\t\twhile (pSortBuffer[iR] > iMid)\n\t\t\t--iR;\n\n\t\tif (iL <= iR)\n\t\t{\n\t\t\tiTmp = pSortBuffer[iL];\n\t\t\tpSortBuffer[iL] = pSortBuffer[iR];\n\t\t\tpSortBuffer[iR] = iTmp;\n\t\t\t++iL; --iR;\n\t\t}\n\t}\n\twhile (iL <= iR);\n\n\tif (iLeft < iR)\n\t\tQuickSort(pSortBuffer, iLeft, iR, uSeed);\n\tif (iL < iRight)\n\t\tQuickSort(pSortBuffer, iL, iRight, uSeed);\n}\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n/////////////////////////////////////////////////////////////////////////////////////////////\n\nstatic void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed);\nstatic void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in);\n\nstatic void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn)\n{\n\t// build array of edges\n\tunsigned int uSeed = INTERNAL_RND_SORT_SEED;\t\t\t\t// could replace with a random seed?\n\tint iEntries=0, iCurStartIndex=-1, f=0, i=0;\n\tfor (f=0; f<iNrTrianglesIn; f++)\n\t\tfor (i=0; i<3; i++)\n\t\t{\n\t\t\tconst int i0 = piTriListIn[f*3+i];\n\t\t\tconst int i1 = piTriListIn[f*3+(i<2?(i+1):0)];\n\t\t\tpEdges[f*3+i].i0 = i0 < i1 ? i0 : i1;\t\t\t// put minimum index in i0\n\t\t\tpEdges[f*3+i].i1 = !(i0 < i1) ? i0 : i1;\t\t// put maximum index in i1\n\t\t\tpEdges[f*3+i].f = f;\t\t\t\t\t\t\t// record face number\n\t\t}\n\n\t// sort over all edges by i0, this is the pricy one.\n\tQuickSortEdges(pEdges, 0, iNrTrianglesIn*3-1, 0, uSeed);\t// sort channel 0 which is i0\n\n\t// sub sort over i1, should be fast.\n\t// could replace this with a 64 bit int sort over (i0,i1)\n\t// with i0 as msb in the quicksort call above.\n\tiEntries = iNrTrianglesIn*3;\n\tiCurStartIndex = 0;\n\tfor (i=1; i<iEntries; i++)\n\t{\n\t\tif (pEdges[iCurStartIndex].i0 != pEdges[i].i0)\n\t\t{\n\t\t\tconst int iL = iCurStartIndex;\n\t\t\tconst int iR = i-1;\n\t\t\t//const int iElems = i-iL;\n\t\t\tiCurStartIndex = i;\n\t\t\tQuickSortEdges(pEdges, iL, iR, 1, uSeed);\t// sort channel 1 which is i1\n\t\t}\n\t}\n\n\t// sub sort over f, which should be fast.\n\t// this step is to remain compliant with BuildNeighborsSlow() when\n\t// more than 2 triangles use the same edge (such as a butterfly topology).\n\tiCurStartIndex = 0;\n\tfor (i=1; i<iEntries; i++)\n\t{\n\t\tif (pEdges[iCurStartIndex].i0 != pEdges[i].i0 || pEdges[iCurStartIndex].i1 != pEdges[i].i1)\n\t\t{\n\t\t\tconst int iL = iCurStartIndex;\n\t\t\tconst int iR = i-1;\n\t\t\t//const int iElems = i-iL;\n\t\t\tiCurStartIndex = i;\n\t\t\tQuickSortEdges(pEdges, iL, iR, 2, uSeed);\t// sort channel 2 which is f\n\t\t}\n\t}\n\n\t// pair up, adjacent triangles\n\tfor (i=0; i<iEntries; i++)\n\t{\n\t\tconst int i0=pEdges[i].i0;\n\t\tconst int i1=pEdges[i].i1;\n\t\tconst int f = pEdges[i].f;\n\t\ttbool bUnassigned_A;\n\n\t\tint i0_A, i1_A;\n\t\tint edgenum_A, edgenum_B=0;\t// 0,1 or 2\n\t\tGetEdge(&i0_A, &i1_A, &edgenum_A, &piTriListIn[f*3], i0, i1);\t// resolve index ordering and edge_num\n\t\tbUnassigned_A = pTriInfos[f].FaceNeighbors[edgenum_A] == -1 ? TTRUE : TFALSE;\n\n\t\tif (bUnassigned_A)\n\t\t{\n\t\t\t// get true index ordering\n\t\t\tint j=i+1, t;\n\t\t\ttbool bNotFound = TTRUE;\n\t\t\twhile (j<iEntries && i0==pEdges[j].i0 && i1==pEdges[j].i1 && bNotFound)\n\t\t\t{\n\t\t\t\ttbool bUnassigned_B;\n\t\t\t\tint i0_B, i1_B;\n\t\t\t\tt = pEdges[j].f;\n\t\t\t\t// flip i0_B and i1_B\n\t\t\t\tGetEdge(&i1_B, &i0_B, &edgenum_B, &piTriListIn[t*3], pEdges[j].i0, pEdges[j].i1);\t// resolve index ordering and edge_num\n\t\t\t\t//assert(!(i0_A==i1_B && i1_A==i0_B));\n\t\t\t\tbUnassigned_B =  pTriInfos[t].FaceNeighbors[edgenum_B]==-1 ? TTRUE : TFALSE;\n\t\t\t\tif (i0_A==i0_B && i1_A==i1_B && bUnassigned_B)\n\t\t\t\t\tbNotFound = TFALSE;\n\t\t\t\telse\n\t\t\t\t\t++j;\n\t\t\t}\n\n\t\t\tif (!bNotFound)\n\t\t\t{\n\t\t\t\tint t = pEdges[j].f;\n\t\t\t\tpTriInfos[f].FaceNeighbors[edgenum_A] = t;\n\t\t\t\t//assert(pTriInfos[t].FaceNeighbors[edgenum_B]==-1);\n\t\t\t\tpTriInfos[t].FaceNeighbors[edgenum_B] = f;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn)\n{\n\tint f=0, i=0;\n\tfor (f=0; f<iNrTrianglesIn; f++)\n\t{\n\t\tfor (i=0; i<3; i++)\n\t\t{\n\t\t\t// if unassigned\n\t\t\tif (pTriInfos[f].FaceNeighbors[i] == -1)\n\t\t\t{\n\t\t\t\tconst int i0_A = piTriListIn[f*3+i];\n\t\t\t\tconst int i1_A = piTriListIn[f*3+(i<2?(i+1):0)];\n\n\t\t\t\t// search for a neighbor\n\t\t\t\ttbool bFound = TFALSE;\n\t\t\t\tint t=0, j=0;\n\t\t\t\twhile (!bFound && t<iNrTrianglesIn)\n\t\t\t\t{\n\t\t\t\t\tif (t!=f)\n\t\t\t\t\t{\n\t\t\t\t\t\tj=0;\n\t\t\t\t\t\twhile (!bFound && j<3)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// in rev order\n\t\t\t\t\t\t\tconst int i1_B = piTriListIn[t*3+j];\n\t\t\t\t\t\t\tconst int i0_B = piTriListIn[t*3+(j<2?(j+1):0)];\n\t\t\t\t\t\t\t//assert(!(i0_A==i1_B && i1_A==i0_B));\n\t\t\t\t\t\t\tif (i0_A==i0_B && i1_A==i1_B)\n\t\t\t\t\t\t\t\tbFound = TTRUE;\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t++j;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif (!bFound) ++t;\n\t\t\t\t}\n\n\t\t\t\t// assign neighbors\n\t\t\t\tif (bFound)\n\t\t\t\t{\n\t\t\t\t\tpTriInfos[f].FaceNeighbors[i] = t;\n\t\t\t\t\t//assert(pTriInfos[t].FaceNeighbors[j]==-1);\n\t\t\t\t\tpTriInfos[t].FaceNeighbors[j] = f;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed)\n{\n\tunsigned int t;\n\tint iL, iR, n, index, iMid;\n\n\t// early out\n\tSEdge sTmp;\n\tconst int iElems = iRight-iLeft+1;\n\tif (iElems<2) return;\n\telse if (iElems==2)\n\t{\n\t\tif (pSortBuffer[iLeft].array[channel] > pSortBuffer[iRight].array[channel])\n\t\t{\n\t\t\tsTmp = pSortBuffer[iLeft];\n\t\t\tpSortBuffer[iLeft] = pSortBuffer[iRight];\n\t\t\tpSortBuffer[iRight] = sTmp;\n\t\t}\n\t\treturn;\n\t}\n\n\t// Random\n\tt=uSeed&31;\n\tt=(uSeed<<t)|(uSeed>>(32-t));\n\tuSeed=uSeed+t+3;\n\t// Random end\n\n\tiL = iLeft;\n\tiR = iRight;\n\tn = (iR-iL)+1;\n\tassert(n>=0);\n\tindex = (int) (uSeed%n);\n\n\tiMid=pSortBuffer[index + iL].array[channel];\n\n\tdo\n\t{\n\t\twhile (pSortBuffer[iL].array[channel] < iMid)\n\t\t\t++iL;\n\t\twhile (pSortBuffer[iR].array[channel] > iMid)\n\t\t\t--iR;\n\n\t\tif (iL <= iR)\n\t\t{\n\t\t\tsTmp = pSortBuffer[iL];\n\t\t\tpSortBuffer[iL] = pSortBuffer[iR];\n\t\t\tpSortBuffer[iR] = sTmp;\n\t\t\t++iL; --iR;\n\t\t}\n\t}\n\twhile (iL <= iR);\n\n\tif (iLeft < iR)\n\t\tQuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed);\n\tif (iL < iRight)\n\t\tQuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed);\n}\n\n// resolve ordering and edge number\nstatic void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in)\n{\n\t*edgenum_out = -1;\n\t\n\t// test if first index is on the edge\n\tif (indices[0]==i0_in || indices[0]==i1_in)\n\t{\n\t\t// test if second index is on the edge\n\t\tif (indices[1]==i0_in || indices[1]==i1_in)\n\t\t{\n\t\t\tedgenum_out[0]=0;\t// first edge\n\t\t\ti0_out[0]=indices[0];\n\t\t\ti1_out[0]=indices[1];\n\t\t}\n\t\telse\n\t\t{\n\t\t\tedgenum_out[0]=2;\t// third edge\n\t\t\ti0_out[0]=indices[2];\n\t\t\ti1_out[0]=indices[0];\n\t\t}\n\t}\n\telse\n\t{\n\t\t// only second and third index is on the edge\n\t\tedgenum_out[0]=1;\t// second edge\n\t\ti0_out[0]=indices[1];\n\t\ti1_out[0]=indices[2];\n\t}\n}\n\n\n/////////////////////////////////////////////////////////////////////////////////////////////\n/////////////////////////////////// Degenerate triangles ////////////////////////////////////\n\nstatic void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris)\n{\n\tint iNextGoodTriangleSearchIndex=-1;\n\ttbool bStillFindingGoodOnes;\n\n\t// locate quads with only one good triangle\n\tint t=0;\n\twhile (t<(iTotTris-1))\n\t{\n\t\tconst int iFO_a = pTriInfos[t].iOrgFaceNumber;\n\t\tconst int iFO_b = pTriInfos[t+1].iOrgFaceNumber;\n\t\tif (iFO_a==iFO_b)\t// this is a quad\n\t\t{\n\t\t\tconst tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE;\n\t\t\tconst tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE;\n\t\t\tif ((bIsDeg_a^bIsDeg_b)!=0)\n\t\t\t{\n\t\t\t\tpTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI;\n\t\t\t\tpTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI;\n\t\t\t}\n\t\t\tt += 2;\n\t\t}\n\t\telse\n\t\t\t++t;\n\t}\n\n\t// reorder list so all degen triangles are moved to the back\n\t// without reordering the good triangles\n\tiNextGoodTriangleSearchIndex = 1;\n\tt=0;\n\tbStillFindingGoodOnes = TTRUE;\n\twhile (t<iNrTrianglesIn && bStillFindingGoodOnes)\n\t{\n\t\tconst tbool bIsGood = (pTriInfos[t].iFlag&MARK_DEGENERATE)==0 ? TTRUE : TFALSE;\n\t\tif (bIsGood)\n\t\t{\n\t\t\tif (iNextGoodTriangleSearchIndex < (t+2))\n\t\t\t\tiNextGoodTriangleSearchIndex = t+2;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tint t0, t1;\n\t\t\t// search for the first good triangle.\n\t\t\ttbool bJustADegenerate = TTRUE;\n\t\t\twhile (bJustADegenerate && iNextGoodTriangleSearchIndex<iTotTris)\n\t\t\t{\n\t\t\t\tconst tbool bIsGood = (pTriInfos[iNextGoodTriangleSearchIndex].iFlag&MARK_DEGENERATE)==0 ? TTRUE : TFALSE;\n\t\t\t\tif (bIsGood) bJustADegenerate=TFALSE;\n\t\t\t\telse ++iNextGoodTriangleSearchIndex;\n\t\t\t}\n\n\t\t\tt0 = t;\n\t\t\tt1 = iNextGoodTriangleSearchIndex;\n\t\t\t++iNextGoodTriangleSearchIndex;\n\t\t\tassert(iNextGoodTriangleSearchIndex > (t+1));\n\n\t\t\t// swap triangle t0 and t1\n\t\t\tif (!bJustADegenerate)\n\t\t\t{\n\t\t\t\tint i=0;\n\t\t\t\tfor (i=0; i<3; i++)\n\t\t\t\t{\n\t\t\t\t\tconst int index = piTriList_out[t0*3+i];\n\t\t\t\t\tpiTriList_out[t0*3+i] = piTriList_out[t1*3+i];\n\t\t\t\t\tpiTriList_out[t1*3+i] = index;\n\t\t\t\t}\n\t\t\t\t{\n\t\t\t\t\tconst STriInfo tri_info = pTriInfos[t0];\n\t\t\t\t\tpTriInfos[t0] = pTriInfos[t1];\n\t\t\t\t\tpTriInfos[t1] = tri_info;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t\tbStillFindingGoodOnes = TFALSE;\t// this is not supposed to happen\n\t\t}\n\n\t\tif (bStillFindingGoodOnes) ++t;\n\t}\n\n\tassert(bStillFindingGoodOnes);\t// code will still work.\n\tassert(iNrTrianglesIn == t);\n}\n\nstatic void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris)\n{\n\tint t=0, i=0;\n\t// deal with degenerate triangles\n\t// punishment for degenerate triangles is O(N^2)\n\tfor (t=iNrTrianglesIn; t<iTotTris; t++)\n\t{\n\t\t// degenerate triangles on a quad with one good triangle are skipped\n\t\t// here but processed in the next loop\n\t\tconst tbool bSkip = (pTriInfos[t].iFlag&QUAD_ONE_DEGEN_TRI)!=0 ? TTRUE : TFALSE;\n\n\t\tif (!bSkip)\n\t\t{\n\t\t\tfor (i=0; i<3; i++)\n\t\t\t{\n\t\t\t\tconst int index1 = piTriListIn[t*3+i];\n\t\t\t\t// search through the good triangles\n\t\t\t\ttbool bNotFound = TTRUE;\n\t\t\t\tint j=0;\n\t\t\t\twhile (bNotFound && j<(3*iNrTrianglesIn))\n\t\t\t\t{\n\t\t\t\t\tconst int index2 = piTriListIn[j];\n\t\t\t\t\tif (index1==index2) bNotFound=TFALSE;\n\t\t\t\t\telse ++j;\n\t\t\t\t}\n\n\t\t\t\tif (!bNotFound)\n\t\t\t\t{\n\t\t\t\t\tconst int iTri = j/3;\n\t\t\t\t\tconst int iVert = j%3;\n\t\t\t\t\tconst int iSrcVert=pTriInfos[iTri].vert_num[iVert];\n\t\t\t\t\tconst int iSrcOffs=pTriInfos[iTri].iTSpacesOffs;\n\t\t\t\t\tconst int iDstVert=pTriInfos[t].vert_num[i];\n\t\t\t\t\tconst int iDstOffs=pTriInfos[t].iTSpacesOffs;\n\t\t\t\t\t\n\t\t\t\t\t// copy tspace\n\t\t\t\t\tpsTspace[iDstOffs+iDstVert] = psTspace[iSrcOffs+iSrcVert];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// deal with degenerate quads with one good triangle\n\tfor (t=0; t<iNrTrianglesIn; t++)\n\t{\n\t\t// this triangle belongs to a quad where the\n\t\t// other triangle is degenerate\n\t\tif ( (pTriInfos[t].iFlag&QUAD_ONE_DEGEN_TRI)!=0 )\n\t\t{\n\t\t\tSVec3 vDstP;\n\t\t\tint iOrgF=-1, i=0;\n\t\t\ttbool bNotFound;\n\t\t\tunsigned char * pV = pTriInfos[t].vert_num;\n\t\t\tint iFlag = (1<<pV[0]) | (1<<pV[1]) | (1<<pV[2]);\n\t\t\tint iMissingIndex = 0;\n\t\t\tif ((iFlag&2)==0) iMissingIndex=1;\n\t\t\telse if ((iFlag&4)==0) iMissingIndex=2;\n\t\t\telse if ((iFlag&8)==0) iMissingIndex=3;\n\n\t\t\tiOrgF = pTriInfos[t].iOrgFaceNumber;\n\t\t\tvDstP = GetPosition(pContext, MakeIndex(iOrgF, iMissingIndex));\n\t\t\tbNotFound = TTRUE;\n\t\t\ti=0;\n\t\t\twhile (bNotFound && i<3)\n\t\t\t{\n\t\t\t\tconst int iVert = pV[i];\n\t\t\t\tconst SVec3 vSrcP = GetPosition(pContext, MakeIndex(iOrgF, iVert));\n\t\t\t\tif (veq(vSrcP, vDstP)==TTRUE)\n\t\t\t\t{\n\t\t\t\t\tconst int iOffs = pTriInfos[t].iTSpacesOffs;\n\t\t\t\t\tpsTspace[iOffs+iMissingIndex] = psTspace[iOffs+iVert];\n\t\t\t\t\tbNotFound=TFALSE;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\t++i;\n\t\t\t}\n\t\t\tassert(!bNotFound);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "BlenderMalt/CBlenderMalt/mikktspace.h",
    "content": "/** \\file mikktspace/mikktspace.h\n *  \\ingroup mikktspace\n */\n/**\n *  Copyright (C) 2011 by Morten S. Mikkelsen\n *\n *  This software is provided 'as-is', without any express or implied\n *  warranty.  In no event will the authors be held liable for any damages\n *  arising from the use of this software.\n *\n *  Permission is granted to anyone to use this software for any purpose,\n *  including commercial applications, and to alter it and redistribute it\n *  freely, subject to the following restrictions:\n *\n *  1. The origin of this software must not be misrepresented; you must not\n *     claim that you wrote the original software. If you use this software\n *     in a product, an acknowledgment in the product documentation would be\n *     appreciated but is not required.\n *  2. Altered source versions must be plainly marked as such, and must not be\n *     misrepresented as being the original software.\n *  3. This notice may not be removed or altered from any source distribution.\n */\n\n#ifndef __MIKKTSPACE_H__\n#define __MIKKTSPACE_H__\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* Author: Morten S. Mikkelsen\n * Version: 1.0\n *\n * The files mikktspace.h and mikktspace.c are designed to be\n * stand-alone files and it is important that they are kept this way.\n * Not having dependencies on structures/classes/libraries specific\n * to the program, in which they are used, allows them to be copied\n * and used as is into any tool, program or plugin.\n * The code is designed to consistently generate the same\n * tangent spaces, for a given mesh, in any tool in which it is used.\n * This is done by performing an internal welding step and subsequently an order-independent evaluation\n * of tangent space for meshes consisting of triangles and quads.\n * This means faces can be received in any order and the same is true for\n * the order of vertices of each face. The generated result will not be affected\n * by such reordering. Additionally, whether degenerate (vertices or texture coordinates)\n * primitives are present or not will not affect the generated results either.\n * Once tangent space calculation is done the vertices of degenerate primitives will simply\n * inherit tangent space from neighboring non degenerate primitives.\n * The analysis behind this implementation can be found in my master's thesis\n * which is available for download --> http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf\n * Note that though the tangent spaces at the vertices are generated in an order-independent way,\n * by this implementation, the interpolated tangent space is still affected by which diagonal is\n * chosen to split each quad. A sensible solution is to have your tools pipeline always\n * split quads by the shortest diagonal. This choice is order-independent and works with mirroring.\n * If these have the same length then compare the diagonals defined by the texture coordinates.\n * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin\n * and also quad triangulator plugin.\n */\n\n\ntypedef int tbool;\ntypedef struct SMikkTSpaceContext SMikkTSpaceContext;\n\ntypedef struct {\n\t// Returns the number of faces (triangles/quads) on the mesh to be processed.\n\tint (*m_getNumFaces)(const SMikkTSpaceContext * pContext);\n\n\t// Returns the number of vertices on face number iFace\n\t// iFace is a number in the range {0, 1, ..., getNumFaces()-1}\n\tint (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace);\n\n\t// returns the position/normal/texcoord of the referenced face of vertex number iVert.\n\t// iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads.\n\tvoid (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert);\n\tvoid (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert);\n\tvoid (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert);\n\n\t// either (or both) of the two setTSpace callbacks can be set.\n\t// The call-back m_setTSpaceBasic() is sufficient for basic normal mapping.\n\n\t// This function is used to return the tangent and fSign to the application.\n\t// fvTangent is a unit length vector.\n\t// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.\n\t// bitangent = fSign * cross(vN, tangent);\n\t// Note that the results are returned unindexed. It is possible to generate a new index list\n\t// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.\n\t// DO NOT! use an already existing index list.\n\tvoid (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert);\n\n\t// This function is used to return tangent space results to the application.\n\t// fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their\n\t// true magnitudes which can be used for relief mapping effects.\n\t// fvBiTangent is the \"real\" bitangent and thus may not be perpendicular to fvTangent.\n\t// However, both are perpendicular to the vertex normal.\n\t// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.\n\t// fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);\n\t// bitangent = fSign * cross(vN, tangent);\n\t// Note that the results are returned unindexed. It is possible to generate a new index list\n\t// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.\n\t// DO NOT! use an already existing index list.\n\tvoid (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT,\n\t\t\t\t\t\tconst tbool bIsOrientationPreserving, const int iFace, const int iVert);\n} SMikkTSpaceInterface;\n\nstruct SMikkTSpaceContext\n{\n\tSMikkTSpaceInterface * m_pInterface;\t// initialized with callback functions\n\tvoid * m_pUserData;\t\t\t\t\t\t// pointer to client side mesh data etc. (passed as the first parameter with every interface call)\n};\n\n// these are both thread safe!\ntbool genTangSpaceDefault(const SMikkTSpaceContext * pContext);\t// Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled)\ntbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold);\n\n\n// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the\n// normal map sampler must use the exact inverse of the pixel shader transformation.\n// The most efficient transformation we can possibly do in the pixel shader is\n// achieved by using, directly, the \"unnormalized\" interpolated tangent, bitangent and vertex normal: vT, vB and vN.\n// pixel shader (fast transform out)\n// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );\n// where vNt is the tangent space normal. The normal map sampler must likewise use the\n// interpolated and \"unnormalized\" tangent, bitangent and vertex normal to be compliant with the pixel shader.\n// sampler does (exact inverse of pixel shader):\n// float3 row0 = cross(vB, vN);\n// float3 row1 = cross(vN, vT);\n// float3 row2 = cross(vT, vB);\n// float fSign = dot(vT, row0)<0 ? -1 : 1;\n// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) );\n// where vNout is the sampled normal in some chosen 3D space.\n//\n// Should you choose to reconstruct the bitangent in the pixel shader instead\n// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also.\n// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of\n// quads as your renderer then problems will occur since the interpolated tangent spaces will differ\n// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before\n// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier.\n// However, this must be used both by the sampler and your tools/rendering pipeline.\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "BlenderMalt/MaltLights.py",
    "content": "import math\nimport bpy\n\nclass MaltLight(bpy.types.PropertyGroup):\n\n    def sync_data(self, context):\n        light = self.id_data\n        #light.shadow_soft_size = self.radius\n        if light.type == 'SPOT':\n            light.cutoff_distance = self.radius\n            light.spot_size = self.spot_angle\n            light.spot_blend = self.spot_blend_angle / self.spot_angle\n\n    strength : bpy.props.FloatProperty(name='Strength', default=1,\n        options={'LIBRARY_EDITABLE', 'ANIMATABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    override_global_settings : bpy.props.BoolProperty(name='Override Global Settings', default=False,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    max_distance : bpy.props.FloatProperty(name='Max Distance', default=100,\n        options={'LIBRARY_EDITABLE', 'ANIMATABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    radius : bpy.props.FloatProperty(\n        name='Radius',\n        default=5,\n        update=sync_data,\n        options={'LIBRARY_EDITABLE', 'ANIMATABLE'},\n        override={'LIBRARY_OVERRIDABLE'}\n    )\n    spot_angle : bpy.props.FloatProperty(\n        name='Angle',\n        default=1,\n        subtype='ANGLE',\n        min=0,\n        max=math.pi,\n        update=sync_data,\n        options={'LIBRARY_EDITABLE', 'ANIMATABLE'},\n        override={'LIBRARY_OVERRIDABLE'}\n    )\n    spot_blend_angle : bpy.props.FloatProperty(\n        name='Blend',\n        default=0.1,\n        subtype='ANGLE',\n        min=0,\n        max=math.pi,\n        update=sync_data,\n        options={'LIBRARY_EDITABLE', 'ANIMATABLE'},\n        override={'LIBRARY_OVERRIDABLE'}\n    )\n\n    def draw_ui(self, layout):\n        layout.prop(self.id_data, 'color')\n        layout.prop(self, 'strength')\n        if self.id_data.type == 'SUN':\n            layout.prop(self, 'override_global_settings')\n            if self.override_global_settings:\n                layout.prop(self, 'max_distance')\n        if self.id_data.type != 'SUN':\n            layout.prop(self, 'radius')\n        if self.id_data.type == 'SPOT':\n            layout.prop(self, 'spot_angle')\n            layout.prop(self, 'spot_blend_angle')\n\nclasses = [\n    MaltLight\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    bpy.types.Light.malt = bpy.props.PointerProperty(type=MaltLight,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n    del bpy.types.Light.malt\n"
  },
  {
    "path": "BlenderMalt/MaltMaterial.py",
    "content": "import os\nimport bpy\nfrom BlenderMalt.MaltUtils import malt_path_set_transform, malt_path_get_transform\nfrom . MaltProperties import MaltPropertyGroup\n\n_MATERIALS = {}\n\nclass MaltMaterial(bpy.types.PropertyGroup):\n\n    def update_source(self, context):\n        path = self.get_source_path()\n        if path in _MATERIALS.keys() and _MATERIALS[path]:\n            self.compiler_error = _MATERIALS[path].compiler_error\n            self.parameters.setup(_MATERIALS[path].parameters)\n        else:\n            self.compiler_error = ''\n            self.parameters.setup({})\n            track_shader_changes()\n    \n    def update_nodes(self, context):\n        self.parameters.parent = self.shader_nodes\n        self.update_source(context)\n    \n    def poll_tree(self, object):\n        return object.bl_idname == 'MaltTree' and (object.graph_type == self.material_type or self.material_type == '')\n    \n    material_type : bpy.props.StringProperty(name='Type',\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    shader_source : bpy.props.StringProperty(name=\"Shader Source\", subtype='FILE_PATH', update=update_source,\n        set_transform=malt_path_set_transform, get_transform=malt_path_get_transform,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n        \n    shader_nodes : bpy.props.PointerProperty(name=\"Node Tree\", type=bpy.types.NodeTree, update=update_nodes, poll=poll_tree,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    compiler_error : bpy.props.StringProperty(name=\"Compiler Error\",\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    parameters : bpy.props.PointerProperty(type=MaltPropertyGroup, name=\"Shader Parameters\",\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def get_source_path(self):\n        path = None\n        if self.shader_nodes:\n            if self.shader_nodes.is_active():\n                path = self.shader_nodes.get_generated_source_path()\n        else:\n            path = bpy.path.abspath(self.shader_source, library=self.id_data.library)\n        if path:\n            return path\n        else:\n            return ''\n    \n    def draw_ui(self, layout, extension, material_parameters):\n        layout.active = self.id_data.library is None #only local data can be edited\n        layout.prop_search(self, 'material_type', bpy.context.scene.world.malt, 'material_types')\n        row = layout.row()\n        row.active = self.shader_nodes is None\n        row.prop(self, 'shader_source')\n\n        def nodes_add_or_duplicate():\n            if self.shader_nodes:\n                self.shader_nodes = self.shader_nodes.get_copy()\n            else:\n                self.shader_nodes = bpy.data.node_groups.new(f'{self.id_data.name} Node Tree', 'MaltTree')\n                self.shader_nodes.graph_type = self.material_type\n            self.id_data.update_tag()\n            self.shader_nodes.update_tag()\n        row = layout.row(align=True)\n        row.template_ID(self, \"shader_nodes\")\n        if self.shader_nodes:\n            row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set(nodes_add_or_duplicate, 'Duplicate')\n            from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree\n            row.operator('wm.malt_callback', text = '', icon = 'GREASEPENCIL').callback.set(\n                lambda: set_node_tree(bpy.context, self.shader_nodes)\n            )\n        else:\n            row.operator('wm.malt_callback', text='New', icon='ADD').callback.set(nodes_add_or_duplicate, 'New')\n\n        source_path = self.get_source_path()\n\n        if source_path != '' and source_path.endswith(extension) == False:\n            box = layout.box()\n            box.label(text='Wrong shader extension, should be '+extension+'.', icon='ERROR')\n            return\n            \n        if self.compiler_error != '':\n            layout.operator(\"wm.malt_print_error\", icon='ERROR').message = self.compiler_error\n            box = layout.box()\n            lines = self.compiler_error.splitlines()\n            for line in lines:\n                box.label(text=line)\n        \n        material_parameters.draw_ui(layout, filter=extension)\n        self.parameters.draw_ui(layout)\n\n\nclass MALT_PT_MaterialSettings(bpy.types.Panel):\n    bl_space_type = 'PROPERTIES'\n    bl_region_type = 'WINDOW'\n\n    bl_context = \"material\"\n    bl_label = \"Malt Settings\"\n    COMPAT_ENGINES = {'MALT'}\n\n    @classmethod\n    def poll(cls, context):\n        return context.scene.render.engine == 'MALT' and context.object is not None\n\n    def draw(self, context):\n        layout = self.layout\n        ob = context.object\n        slot = context.material_slot\n\n        if ob:\n            is_sortable = len(ob.material_slots) > 1\n            rows = 3\n            if is_sortable:\n                rows = 5\n\n            row = layout.row()\n\n            row.template_list(\"MATERIAL_UL_matslots\", \"\", ob, \"material_slots\", ob, \"active_material_index\", rows=rows)\n\n            col = row.column(align=True)\n            col.operator(\"object.material_slot_add\", icon='ADD', text=\"\")\n            col.operator(\"object.material_slot_remove\", icon='REMOVE', text=\"\")\n\n            col.separator()\n\n            col.menu(\"MATERIAL_MT_context_menu\", icon='DOWNARROW_HLT', text=\"\")\n\n            if is_sortable:\n                col.separator()\n\n                col.operator(\"object.material_slot_move\", icon='TRIA_UP', text=\"\").direction = 'UP'\n                col.operator(\"object.material_slot_move\", icon='TRIA_DOWN', text=\"\").direction = 'DOWN'\n\n        row = layout.row(align=True)\n\n        if ob:\n            row.template_ID(ob, \"active_material\")\n            def add_or_duplicate():\n                nonlocal ob, slot\n                name = f'{ob.data.name} Material'\n                material = None\n                if slot and slot.material:\n                    material = slot.material.copy()\n                else:\n                    material = bpy.data.materials.new(name)\n                material.malt.material_type = 'Mesh'\n                if slot:\n                    slot.material = material\n                else:\n                    ob.data.materials.append(material)\n                ob.data.update_tag()\n                material.update_tag()\n            \n            if slot and slot.material:\n                row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set(\n                    add_or_duplicate, 'Duplicate')\n            else:\n                row.operator('wm.malt_callback', text='New', icon='ADD').callback.set(\n                    add_or_duplicate, 'New')\n            if slot:\n                icon_link = 'MESH_DATA' if slot.link == 'DATA' else 'OBJECT_DATA'\n                row.prop(slot, \"link\", icon=icon_link, icon_only=True)\n\n            if ob.mode == 'EDIT':\n                row = layout.row(align=True)\n                row.operator(\"object.material_slot_assign\", text=\"Assign\")\n                row.operator(\"object.material_slot_select\", text=\"Select\")\n                row.operator(\"object.material_slot_deselect\", text=\"Deselect\")\n        \n        if context.material:\n            context.material.malt.draw_ui(layout, '.mesh.glsl', context.material.malt_parameters)\n\n\nclasses = (\n    MaltMaterial,\n    MALT_PT_MaterialSettings\n)\n\ndef reset_materials():\n    global _MATERIALS\n    _MATERIALS = {}\n\nimport time\n__TIMESTAMP = time.time()\n\nINITIALIZED = False\ndef track_shader_changes(force_update=False, async_compilation=True):\n    from BlenderMalt import MaltPipeline\n    if MaltPipeline.is_malt_active() == False and force_update == False:\n        return 1\n        \n    global INITIALIZED\n    global __TIMESTAMP\n    global _MATERIALS\n    try:\n        start_time = time.time()\n\n        needs_update = []\n\n        for material in bpy.data.materials:\n            path = material.malt.get_source_path()\n            if path not in needs_update:\n                if os.path.exists(path):\n                    stats = os.stat(path)\n                    if path not in _MATERIALS.keys() or stats.st_mtime > __TIMESTAMP:\n                        if path not in _MATERIALS:\n                            _MATERIALS[path] = None\n                        needs_update.append(path)\n\n        compiled_materials = {}\n\n        from . import MaltPipeline\n        if len(needs_update) > 0:\n            compiled_materials = MaltPipeline.get_bridge().compile_materials(needs_update, async_compilation=async_compilation)\n        else:\n            compiled_materials = MaltPipeline.get_bridge().receive_async_compilation_materials()\n        \n        if len(compiled_materials) > 0:\n            for key, value in compiled_materials.items():\n                _MATERIALS[key] = value\n            for material in bpy.data.materials:\n                path = material.malt.get_source_path()\n                if path in compiled_materials.keys():\n                    material.malt.compiler_error = compiled_materials[path].compiler_error\n                    #TODO: Use parent parameters as defaults for materials with nodes\n                    material.malt.parameters.setup(compiled_materials[path].parameters)\n            for screen in bpy.data.screens:\n                for area in screen.areas:\n                    area.tag_redraw()\n        \n        __TIMESTAMP = start_time\n        INITIALIZED = True\n    except:\n        import traceback\n        traceback.print_exc()\n    return 0.1 #Track again in 0.1 second\n    \n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    bpy.types.Material.malt = bpy.props.PointerProperty(type=MaltMaterial,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    bpy.app.timers.register(track_shader_changes, persistent=True)\n\ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n    del bpy.types.Material.malt\n    \n    bpy.app.timers.unregister(track_shader_changes)\n"
  },
  {
    "path": "BlenderMalt/MaltMeshes.py",
    "content": "import ctypes\nimport bpy\n\nMESHES = {}\n\ndef get_mesh_name(object):\n    name = object.name_full\n    if len(object.modifiers) == 0 and object.data:\n        name = object.type + '_' + object.data.name_full\n    return name\n\ndef get_mesh(object):\n    key = get_mesh_name(object)\n    if key not in MESHES.keys() or MESHES[key] is None:\n        MESHES[key] = load_mesh(object, key)\n    return MESHES[key]\n\ndef load_mesh(object, name):\n    from . import CBlenderMalt\n\n    m = object.data\n    if object.type != 'MESH' or object.mode == 'EDIT':\n        m = object.to_mesh()\n    \n    if m is None or len(m.polygons) == 0:\n        return None\n    \n    m.calc_loop_triangles()\n\n    def to_ptr(ptr, ctype):\n        return (ctype * 1).from_address(ptr)\n\n    def attribute_ptr(name, ctype):\n        ptr = 0\n        if name in m.attributes:\n            ptr = m.attributes[name].data[0].as_pointer()\n        return to_ptr(ptr, ctype)\n    \n    mesh_ptr = ctypes.c_void_p(m.as_pointer())\n\n    loop_count = len(m.loops)\n    loop_tri_count = len(m.loop_triangles)\n    material_count = max(1, len(m.materials))\n\n    positions = get_load_buffer('positions', ctypes.c_float, (loop_count * 3))\n    indices = []\n    indices_ptrs = (ctypes.c_void_p * material_count)()\n    for i in range(material_count):\n        indices.append(get_load_buffer('indices'+str(i), ctypes.c_uint32, (loop_tri_count * 3)))\n        indices_ptrs[i] = ctypes.cast(indices[i].buffer(), ctypes.c_void_p)\n    \n    indices_lengths = (ctypes.c_uint32 * material_count)()\n\n    CBlenderMalt.retrieve_mesh_data(\n        attribute_ptr(\"position\", ctypes.c_float),\n        attribute_ptr(\".corner_vert\", ctypes.c_int), loop_count,\n        to_ptr(m.loop_triangles[0].as_pointer(), ctypes.c_int),\n        to_ptr(m.loop_triangle_polygons[0].as_pointer(), ctypes.c_int), loop_tri_count,\n        attribute_ptr(\"material_index\", ctypes.c_int),\n        positions.buffer(), indices_ptrs, indices_lengths)\n    \n    for i in range(material_count):\n        indices[i]._size = indices_lengths[i]\n\n    normals = get_load_buffer('normals', ctypes.c_float, (loop_count * 3))\n    ctypes.memmove(normals.buffer(), m.corner_normals[0].as_pointer(), normals.size_in_bytes())\n\n    uvs_list = []\n    tangents_buffer = None\n    for i, uv_layer in enumerate(m.uv_layers):\n        if i >= 4: break\n        uvs = (ctypes.c_float * (loop_count * 2)).from_address(uv_layer.data[0].as_pointer())\n        uv_buffer = get_load_buffer('uv'+str(i), ctypes.c_float, loop_count * 2)\n        ctypes.memmove(uv_buffer.buffer(), uvs, uv_buffer.size_in_bytes())\n        uvs_list.append(uv_buffer)\n        if i == 0 and object.original.data.malt_parameters.bools['precomputed_tangents'].boolean:\n            tangents_buffer = get_load_buffer('tangents'+str(i), ctypes.c_float, (loop_count * 4))\n            CBlenderMalt.mesh_tangents(\n                to_ptr(m.loop_triangles[0].as_pointer(), ctypes.c_int),\n                loop_tri_count * 3,\n                positions.buffer(),\n                normals.buffer(),\n                uv_buffer.buffer(),\n                tangents_buffer.buffer())\n    \n    colors_list = [None]*4\n    if object.type == 'MESH':\n        for i in range(4):\n            override = getattr(object.original.data, f'malt_vertex_color_override_{i}')\n            attribute = m.color_attributes.get(override)\n            #if attribute is None and i < len(m.color_attributes):\n            #    attribute = m.color_attributes[i]\n            if attribute and attribute.domain == 'CORNER':\n                type = None\n                if attribute.data_type == 'BYTE_COLOR':\n                    type = ctypes.c_uint8\n                elif attribute.data_type == 'FLOAT_COLOR':\n                    type = ctypes.c_float\n                else:\n                    continue\n                color = (type * (loop_count * 4)).from_address(attribute.data[0].as_pointer())\n                color_buffer = get_load_buffer('colors'+str(i), type, loop_count*4)\n                ctypes.memmove(color_buffer.buffer(), color, color_buffer.size_in_bytes())\n                colors_list[i] = color_buffer\n\n    mesh_data = {\n        'positions': positions,\n        'indices': indices,\n        'normals': normals,\n        'uvs': uvs_list,\n        'tangents': tangents_buffer,\n        'colors': colors_list,\n    }\n\n    from . import MaltPipeline\n    MaltPipeline.get_bridge().load_mesh(name, mesh_data)\n\n    from Bridge.Proxys import MeshProxy\n    return [MeshProxy(name, i) for i in range(material_count)]\n\ndef get_load_buffer(name, ctype, size):\n    from . import MaltPipeline\n    return MaltPipeline.get_bridge().get_shared_buffer(ctype, size)\n\ndef unload_mesh(object):\n    MESHES[get_mesh_name(object)] = None\n\ndef reset_meshes():\n    global MESHES\n    MESHES = {}\n\ndef draw_vertex_color_overrides(self, context):\n    if context.scene.render.engine != 'MALT':\n        return\n    mesh = context.object.data\n    self.layout.use_property_split = True\n    self.layout.label(text='Malt Vertex Colors')\n    def draw_color_override(key):\n        value = getattr(mesh, key)\n        layout = self.layout\n        if value in mesh.color_attributes and mesh.color_attributes[value].domain != 'CORNER':\n            layout = self.layout.box()\n            layout.label(text='Only Face Corner attributes are supported', icon='ERROR')\n        layout.prop_search(mesh, key, mesh, 'color_attributes')    \n\n    draw_color_override('malt_vertex_color_override_0')\n    draw_color_override('malt_vertex_color_override_1')\n    draw_color_override('malt_vertex_color_override_2')\n    draw_color_override('malt_vertex_color_override_3')\n\n\ndef register():\n    bpy.types.Mesh.malt_vertex_color_override_0 = bpy.props.StringProperty(name='0',\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.Mesh.malt_vertex_color_override_1 = bpy.props.StringProperty(name='1',\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.Mesh.malt_vertex_color_override_2 = bpy.props.StringProperty(name='2',\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.Mesh.malt_vertex_color_override_3 = bpy.props.StringProperty(name='3',\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.DATA_PT_vertex_colors.append(draw_vertex_color_overrides)\n\n\ndef unregister():\n    bpy.types.DATA_PT_vertex_colors.remove(draw_vertex_color_overrides)\n    del bpy.types.Mesh.malt_vertex_color_override_0\n    del bpy.types.Mesh.malt_vertex_color_override_1\n    del bpy.types.Mesh.malt_vertex_color_override_2\n    del bpy.types.Mesh.malt_vertex_color_override_3\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/MaltCustomPasses.py",
    "content": "import bpy\nfrom BlenderMalt import MaltPipeline\n\nclass MaltIOParameter(bpy.types.PropertyGroup):\n\n    def get_parameter_enums(self, context=None):\n        types = ['None']\n        bridge = MaltPipeline.get_bridge()\n        if bridge and self.graph_type in bridge.graphs:\n            graph = bridge.graphs[self.graph_type]\n            if self.io_type in graph.graph_io.keys():\n                if self.is_output:\n                    types = graph.graph_io[self.io_type].dynamic_output_types\n                else:\n                    types = graph.graph_io[self.io_type].dynamic_input_types\n        return [(type, type, type) for type in types]\n    \n    def get_parameter(self):\n        try:\n            return self.get_parameter_enums().index(tuple(self['PARAMETER']))\n        except:\n            return 0\n\n    def set_parameter(self, value):\n        self['PARAMETER'] = self.get_parameter_enums()[value]\n\n    graph_type : bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    io_type : bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    is_output : bpy.props.BoolProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    parameter : bpy.props.EnumProperty(items=get_parameter_enums, get=get_parameter, set=set_parameter,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def draw(self, context, layout, owner):\n        layout.label(text='', icon='DOT')\n        layout.prop(self, 'name', text='')\n        layout.prop(self, 'parameter', text='')\n\nclass MaltCustomIO(bpy.types.PropertyGroup):\n\n    inputs : bpy.props.CollectionProperty(type=MaltIOParameter,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    inputs_index : bpy.props.IntProperty()\n    outputs : bpy.props.CollectionProperty(type=MaltIOParameter,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    outputs_index : bpy.props.IntProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\nclass MaltCustomPasses(bpy.types.PropertyGroup):\n\n    io : bpy.props.CollectionProperty(type=MaltCustomIO,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n\nclass MaltGraphType(bpy.types.PropertyGroup):\n\n    custom_passes : bpy.props.CollectionProperty(type=MaltCustomPasses,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n\ndef setup_default_passes(graphs, world=None):\n    if world is None:\n        world = bpy.context.scene.world\n    for name, graph in graphs.items():\n        if graph.graph_type == graph.SCENE_GRAPH:\n            if name not in world.malt_graph_types:\n                world.malt_graph_types.add().name = name\n            graph_type = world.malt_graph_types[name]\n            if 'Default' not in graph_type.custom_passes:\n                graph_type.custom_passes.add().name = 'Default'\n            for custom_pass in graph_type.custom_passes:\n                for name, io in graph.graph_io.items():\n                    is_new = False\n                    if name not in custom_pass.io:\n                        custom_pass.io.add().name = name\n                        is_new = True\n                    custom_io = custom_pass.io[name]\n                    if is_new:\n                        def add_io_parameter(name, type, is_output):\n                            parameters = custom_io.outputs if is_output else custom_io.inputs\n                            if name not in parameters:\n                                parameters.add().name = name\n                                parameters[name].graph_type = graph_type.name\n                                parameters[name].io_type = io.name\n                                parameters[name].is_output = is_output\n                                parameters[name].parameter = type\n                        for key, type in io.default_dynamic_inputs.items():\n                            add_io_parameter(key, type, False)\n                        for key, type in io.default_dynamic_outputs.items():\n                            add_io_parameter(key, type, True)\n\nclass OT_MaltAddCustomPass(bpy.types.Operator):\n    bl_idname = \"wm.malt_add_custom_pass\"\n    bl_label = \"Malt Add Cusstom Pass\"\n    bl_options = {'INTERNAL'}\n\n    graph_type : bpy.props.StringProperty()\n    name : bpy.props.StringProperty(default='Custom Pass')\n\n    def draw(self, context):\n        self.layout.prop(self,'name')\n\n    def execute(self, context):\n        new_pass = context.scene.world.malt_graph_types[self.graph_type].custom_passes.add()\n        new_pass.name = self.name\n        for name in MaltPipeline.get_bridge().graphs[self.graph_type].graph_io.keys():\n            new_pass.io.add().name = name\n        return {'FINISHED'}\n    \n    def invoke(self, context, event):\n        return context.window_manager.invoke_props_dialog(self)\n\n\nclasses = [\n    MaltIOParameter,\n    MaltCustomIO,\n    MaltCustomPasses,\n    MaltGraphType,\n    OT_MaltAddCustomPass,\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    bpy.types.World.malt_graph_types = bpy.props.CollectionProperty(type=MaltGraphType,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    \ndef unregister():\n    del bpy.types.World.malt_graph_types\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/MaltNode.py",
    "content": "from Malt.PipelineParameters import Parameter, Type\nimport bpy    \nfrom BlenderMalt.MaltProperties import MaltPropertyGroup\n\nCOLUMN_TYPES = ['Texture','sampler','Material']\n\nclass MaltNode():\n\n    malt_parameters : bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    disable_updates : bpy.props.BoolProperty(name=\"Disable Updates\", default=False,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    first_setup : bpy.props.BoolProperty(default=True,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    subscribed : bpy.props.BoolProperty(name=\"Subscribed\", default=False,\n        options={'SKIP_SAVE','LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    malt_label : bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    #Used on copy() to find the correct node instance\n    temp_id : bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    internal_name : bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def get_parameters(self, overrides, resources):\n        parameters = self.id_data.malt_parameters.get_parameters(overrides, resources)\n        result = {}\n        for name, input in self.inputs.items():\n            if '@' in name or input.active == False:\n                continue\n            key = input.get_source_global_reference()\n            key = key.replace('\"','')\n            if key in parameters:\n                result[name] = parameters[key]\n        return result\n\n    # Blender will trigger update callbacks even before init and update has finished\n    # So we use some wrappers to get a more sane behaviour\n    def _disable_updates_wrapper(self, function):\n        tree = self.id_data\n        initial_tree_updates = tree.disable_updates\n        tree.disable_updates = True\n        self.disable_updates = True\n        try:\n            function()\n        except:\n            import traceback\n            traceback.print_exc()\n        tree.disable_updates = initial_tree_updates\n        self.disable_updates = False\n\n    def init(self, context):\n        self._disable_updates_wrapper(self.malt_init)\n        \n    def setup(self, context=None):\n        self.setup_implementation()\n    \n    def setup_implementation(self, copy=None):\n        self.temp_id = str(hash(self))\n        if self.internal_name == '':\n            label = self.malt_label if self.malt_label != '' else self.name\n            self.internal_name = self.id_data.get_unique_node_id(label)\n        self._disable_updates_wrapper(lambda: self.malt_setup(copy=copy))\n        self.first_setup = False\n        if self.subscribed == False:\n            def callback(dummy=None):\n                self.setup_implementation()\n            bpy.msgbus.subscribe_rna(key=self.path_resolve('name', False), owner=self, args=(None,), notify=callback)\n            self.subscribed = True\n\n    def update(self):\n        if self.disable_updates:\n            return\n        self._disable_updates_wrapper(self.malt_update)\n    \n    def copy(self, node):\n        if self.id_data.on_copy:\n            return\n        #Find the node from its node tree so we have access to the node id_data\n        for tree in bpy.data.node_groups:\n            if node.name in tree.nodes:\n                if node.temp_id == tree.nodes[node.name].temp_id:\n                    node = tree.nodes[node.name]\n                    break\n        #We can't copy a node without ID data\n        if node.id_data is None:\n            node = None\n        self.subscribed = False #TODO: Is this needed???\n        self.internal_name = ''\n        self.setup_implementation(copy=node)\n    \n    def free(self):\n        for input in self.inputs:\n            key = self.get_input_parameter_name(input.name)\n            self.id_data.malt_parameters.remove_property(key)\n        \n    def malt_init(self):\n        pass\n\n    def malt_setup(self, copy=None):\n        pass\n    \n    def malt_update(self):\n        pass\n\n    def on_socket_update(self, socket):\n        pass\n\n    def setup_sockets(self, inputs, outputs, expand_structs=True, show_in_material_panel=False, copy=None):\n        def _expand_structs(sockets):\n            result = {}\n            for name, dic in sockets.items():\n                result[name] = dic\n                struct_type = self.id_data.get_struct_type(dic['type'])\n                if struct_type:\n                    for member in struct_type['members']:\n                        result[f\"{name}.{member['name']}\"] = member\n            return result\n        if expand_structs:\n            inputs = _expand_structs(inputs)\n            outputs = _expand_structs(outputs)\n        def setup(current, new):\n            remove = []\n            for e in current.keys():\n                if e not in new:\n                    remove.append(current[e])\n            for e in remove:\n                if e.is_linked == False or self.should_delete_outdated_links():\n                    current.remove(e)\n                else:\n                    e.active = False\n            socket_index = 0\n            for i, (name, dic) in enumerate(new.items()):\n                if '@' in name:\n                    continue #Skip overrides\n                type = dic['type']\n                size = dic['size'] if 'size' in dic else 0\n                is_new_socket = name not in current\n                if is_new_socket:\n                    current.new('MaltSocket', name)\n                    current[name].show_in_material_panel = show_in_material_panel\n                if isinstance(type, Parameter):\n                    current[name].data_type = type.type_string()\n                    current[name].array_size = 0 #TODO\n                else:\n                    current[name].data_type = type\n                    current[name].array_size = size\n                current[name].active = True\n                current[name].default_initialization = ''\n                try:\n                    current[name].ui_label = dic['meta']['label']\n                except:\n                    current[name].ui_label = name\n                try:\n                    meta = dic['meta']\n                    if 'default_initialization' in meta:\n                        current[name].default_initialization = meta['default_initialization']\n                    else:\n                        default = meta['default']\n                        if isinstance(default, str):\n                            current[name].default_initialization = default\n                except:\n                    pass\n                if is_new_socket:\n                    current[name].setup_shape()\n                if current.find(name) != socket_index:\n                    current.move(current.find(name), socket_index)\n                socket_index += 1\n\n        setup(self.inputs, inputs)\n        setup(self.outputs, outputs)\n        parameters = {}\n        for name, input in inputs.items():\n            parameter = None\n            type = input['type']\n            size = input['size'] if 'size' in input else 0\n            try:\n                subtype = input['meta']['subtype']\n            except:\n                subtype = None\n            if isinstance(type, Parameter):\n                parameter = type\n            else:\n                default_value = input.get('meta', {}).get('default', None)\n                if size == 0:\n                    try:\n                        parameter = Parameter.from_glsl_type(type, subtype, default_value)\n                    except:\n                        parameter = Parameter(type, Type.OTHER)\n                else:\n                    parameter = Parameter(type, Type.OTHER)\n            if parameter:\n                for k,v in input.get('meta', {}).items():\n                    if k not in parameter.__dict__.keys():\n                        parameter.__dict__[k] = v\n                label = input.get('meta', {}).get('label', name)\n                node_label = self.draw_label().replace('.', ' ')\n                parameter.label = f'{node_label}.{label}'\n                parameters[self.get_input_parameter_name(name)] = parameter\n        \n        copy_map = None\n        copy_map = {}\n        if copy is None:\n            #Copy from the node parameters (backward compatibility)\n            materials = [m for m in bpy.data.materials if m.malt.shader_nodes is self.id_data]\n            transpiler = self.id_data.get_transpiler()\n            #Rename old material parameters (backward compatibility) \n            for key in self.malt_parameters.get_rna().keys():\n                new_name = self.get_input_parameter_name(key)\n                if new_name in self.id_data.malt_parameters.get_rna().keys():\n                    continue\n                copy_map[new_name] = key\n                old_name = transpiler.global_reference(transpiler.get_source_name(self.name), key)\n                for material in materials:\n                    if old_name in material.malt.parameters.get_rna().keys():\n                        material.malt.parameters.rename_property(old_name, new_name)\n            copy = self.malt_parameters\n        else:\n            for key in inputs.keys():\n                from_name = copy.get_input_parameter_name(key)\n                to_name = self.get_input_parameter_name(key)\n                copy_map[to_name] = from_name\n            copy = copy.id_data.malt_parameters\n\n        self.id_data.malt_parameters.setup(parameters, skip_private=False, replace_parameters=False,\n            copy_from=copy, copy_map=copy_map)\n        for input in self.inputs:\n            #Sync old nodes with the new system\n            input.show_in_material_panel_update()\n        if self.first_setup:\n            self.setup_width()\n    \n    def get_input_parameter_name(self, key):\n        name = key\n        postfix = ''\n        if ' @ ' in name:\n            name_postfix = name.split(' @ ')\n            name = name_postfix[0]\n            postfix = ' @ ' + name_postfix[1]\n        if name in self.inputs.keys() and self.inputs[name].active:\n            name = self.inputs[name].get_source_global_reference()\n        name += postfix \n        name = name.replace('\"','')\n        return name\n    \n    def should_delete_outdated_links(self):\n        return False\n    \n    def calc_node_width(self, point_size) -> float:\n        import blf \n        blf.size(0, point_size)\n        header_padding = 36 # account for a little space for the arrow icon + extra padding on the side of a label\n        socket_padding = 31 # account for little offset of the text from the node border\n\n        def adjust_width(width, text, scale=1, padding=0):\n            new_width = blf.dimensions(0, text)[0] * scale + padding\n            result = max(width, new_width)\n            return result\n\n        max_width = adjust_width(0, self.draw_label(), padding=header_padding)\n        for input in self.inputs.values():\n            scale = 2 \n            if input.default_initialization or '.' in input.name:\n                scale = 1\n            max_width = adjust_width(max_width, input.get_ui_label(), scale=scale, padding=socket_padding*scale)\n        for output in self.outputs.values():\n            max_width = adjust_width(max_width, output.get_ui_label(), padding=socket_padding)\n        return max_width\n\n    def setup_width(self):\n        point_size = bpy.context.preferences.ui_styles[0].widget.points\n        self.width = self.calc_node_width(point_size)\n\n    def get_source_name(self):\n        return self.id_data.get_transpiler().get_source_name(self.internal_name)\n\n    def get_source_code(self, transpiler):\n        if self.id_data.get_source_language() == 'GLSL':\n            return '/*{} not implemented*/'.format(self)\n        elif self.id_data.get_source_language() == 'Python':\n            return '# {} not implemented'.format(self)\n\n    def get_source_socket_reference(self, socket):\n        if self.id_data.get_source_language() == 'GLSL':\n            return '/*{} not implemented*/'.format(socket.name)\n        elif self.id_data.get_source_language() == 'Python':\n            return '# {} not implemented'.format(socket.name)\n    \n    def sockets_to_global_parameters(self, sockets, transpiler):\n        code = ''\n        for socket in sockets:\n            if socket.active == False:\n                continue\n            if socket.data_type != '' and socket.get_linked() is None and socket.is_struct_member() == False:\n                code += transpiler.global_declaration(socket.data_type, socket.array_size, socket.get_source_global_reference())\n        return code\n    \n    def get_source_global_parameters(self, transpiler):\n        return self.sockets_to_global_parameters(self.inputs, transpiler)\n    \n    def setup_socket_shapes(self):\n        from itertools import chain\n        for socket in chain(self.inputs.values(), self.outputs.values()):\n            socket.setup_shape()\n    \n    def is_column_type(self, data_type):\n        global COLUMN_TYPES\n        for type in COLUMN_TYPES:\n            if type in data_type:\n                return True\n        return False\n    \n    def draw_socket(self, context, layout, socket, text):\n        draw_parameter = socket.is_output == False and socket.get_linked() is None and socket.default_initialization == ''\n        if socket.is_struct_member() and (socket.get_struct_socket().get_linked() or socket.get_struct_socket().default_initialization != ''):\n            draw_parameter = False\n        if draw_parameter:\n            column = layout.column()\n            def get_layout():\n                if draw_parameter and self.is_column_type(socket.data_type):\n                    return column.column()\n                else:\n                    return column.row()\n            parameter_key = socket.get_source_global_reference()\n            parameter_key = parameter_key.replace('\"','')\n            self.id_data.malt_parameters.draw_parameter(get_layout(), parameter_key, text, is_node_socket=True)\n            rna = self.id_data.malt_parameters.get_rna()\n            rna_keys = rna.keys()\n            for override in ('Preview', 'Final Render'):\n                key = f'{parameter_key} @ {override}'\n                if key in rna_keys and rna[key].get('active'):\n                    self.id_data.malt_parameters.draw_parameter(get_layout(), key, None, is_node_socket=True)\n        else:\n            layout.label(text=text)\n\n    @classmethod\n    def poll(cls, ntree):\n        return ntree.bl_idname == 'MaltTree'\n    \n    def draw_label(self):\n        print_label = self.malt_label != ''\n        if print_label:\n            for input in self.inputs:\n                if input.show_in_material_panel:\n                    print_label = False\n                    break\n        if print_label:\n            return self.malt_label\n        else:\n            return self.name.replace('_', ' ')\n\n    \nclasses = []\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    \ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/MaltNodeTree.py",
    "content": "import os, time\nfrom Malt.SourceTranspiler import GLSLTranspiler, PythonTranspiler\nimport bpy\nfrom BlenderMalt.MaltProperties import MaltPropertyGroup\nfrom BlenderMalt import MaltPipeline\nfrom BlenderMalt.MaltUtils import malt_path_set_transform, malt_path_get_transform\n\nfrom BlenderMalt.MaltNodes.MaltNode import MaltNode\n\ndef get_pipeline_graph(context):\n    if context is None or context.space_data is None or context.space_data.edit_tree is None:\n        return None\n    return context.space_data.edit_tree.get_pipeline_graph()\n\nclass MaltTree(bpy.types.NodeTree):\n\n    bl_label = \"Malt Node Tree\"\n    bl_icon = 'NODETREE'\n\n    on_copy : bpy.props.BoolProperty(name=\"On Copy\", default=False,\n        options={'SKIP_SAVE','LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def get_copy(self):\n        self.on_copy = True\n        copy = self.copy()\n        self.on_copy = False\n        copy.on_copy = False\n        copy.subscribed = False\n        copy.malt_parameters.handle_duplication()\n        copy.reload_nodes()\n        copy.update_ext(force_update=True)\n        return copy\n    \n    type : bpy.props.EnumProperty(name = 'Type', items = [(\"MALT\", \"Malt\", \"Malt\")],\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    @classmethod\n    def poll(cls, context):\n        return context.scene.render.engine == 'MALT'\n    \n    def poll_material(self, material):\n        return material.malt.shader_nodes is self\n    \n    def update_graph_type(self, context):\n        graph = self.get_pipeline_graph()\n        if graph and graph.default_graph_path and len(self.nodes) == 0:\n            blend_path, tree_name = graph.default_graph_path\n            blend_path += '.blend'\n            if tree_name not in bpy.data.node_groups:\n                internal_dir = 'NodeTree'\n                bpy.ops.wm.append(\n                    filepath=os.path.join(blend_path, internal_dir, tree_name),\n                    directory=os.path.join(blend_path, internal_dir),\n                    filename=tree_name\n                )\n                bpy.data.node_groups[tree_name].reload_nodes()\n            name = self.name\n            copy = bpy.data.node_groups[tree_name]#.get_copy() Workaround crash in 3.4\n            self.user_remap(copy)\n            bpy.data.node_groups.remove(self)\n            copy.name = name\n    \n    graph_type: bpy.props.StringProperty(name='Type', update=update_graph_type,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    #deprecated\n    library_source : bpy.props.StringProperty(name=\"Local Library\", subtype='FILE_PATH',\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE'},\n        set_transform=malt_path_set_transform, get_transform=malt_path_get_transform)\n\n    disable_updates : bpy.props.BoolProperty(name=\"Disable Updates\", default=False,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    malt_parameters : bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    subscribed : bpy.props.BoolProperty(name=\"Subscribed\", default=False,\n        options={'SKIP_SAVE','LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    links_hash : bpy.props.StringProperty(options={'SKIP_SAVE','LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE'})\n\n    def is_active(self):\n        return self.get_pipeline_graph() is not None\n\n    def get_source_language(self):\n        return self.get_pipeline_graph().language\n\n    def get_transpiler(self):\n        if self.get_source_language() == 'GLSL':\n            return GLSLTranspiler\n        elif self.get_source_language() == 'Python':\n            return PythonTranspiler\n\n    def get_library_path(self):\n        if self.library_source != '':\n            src_path = bpy.path.abspath(self.library_source, library=self.library)\n            if os.path.exists(src_path):\n                return src_path\n        return None\n    \n    #deprecated\n    def get_library(self):\n        library_path = self.get_library_path()\n        if library_path:\n            return get_libraries()[library_path]\n        else:\n            return get_empty_library()\n    \n    def get_full_library(self):\n        #TODO: Cache\n        graph = self.get_pipeline_graph()\n        library = self.get_library()\n        if library:\n            result = get_empty_library()\n            result['functions'].update(graph.functions)\n            result['structs'].update(graph.structs)\n            result['subcategories'].update(graph.subcategories)\n            result['functions'].update(library['functions'])\n            result['structs'].update(library['structs'])\n            return result\n        else:\n            return {\n                'functions' : graph.functions,\n                'structs' : graph.structs,\n                'subcategories' : graph.subcategories,\n            }\n    \n    def get_pipeline_graph(self, graph_type=None):\n        if graph_type is None: \n            graph_type = self.graph_type\n        bridge = MaltPipeline.get_bridge()\n        if bridge and graph_type in bridge.graphs:\n            return bridge.graphs[graph_type]\n        return None\n    \n    def get_unique_node_id(self, base_name):\n        base_name = base_name.lower() # Avoid ALL CAPS uniforms, since they are considered private\n        if 'NODE_NAMES' not in self.keys():\n            self['NODE_NAMES'] = {}\n        if base_name not in self['NODE_NAMES'].keys():\n            self['NODE_NAMES'][base_name] = 1\n        else:\n            self['NODE_NAMES'][base_name] += 1\n        return base_name + str(self['NODE_NAMES'][base_name])\n    \n    def get_custom_io(self, io_type):\n        params = []\n        for node in self.nodes:\n            if node.bl_idname == 'MaltIONode' and node.io_type == io_type:\n                io = 'out' if node.is_output else 'in'\n                for parameter in node.get_custom_parameters():\n                    params.append({\n                        'name': parameter.name,\n                        'type': 'Texture', #TODO\n                        'size': 0,\n                        'io': io,\n                    })\n        return params\n    \n    def cast(self, from_type, to_type):\n        cast_function = f'{to_type}_from_{from_type}'\n        lib = self.get_full_library()\n        for function in lib['functions'].values():\n            if function['name'] == cast_function and len(function['parameters']) == 1:\n                #TODO: If more than 1 parameter, check if they have default values?\n                return function['name']\n        return None\n    \n    def get_struct_type(self, struct_type):\n        lib = self.get_full_library()\n        if struct_type in lib['structs']:\n            return lib['structs'][struct_type]\n        return None\n    \n    def get_generated_source_dir(self):\n        import os, tempfile\n        base_path = tempfile.gettempdir()\n        if bpy.context.blend_data.is_saved:\n            base_path = bpy.path.abspath('//')\n        return os.path.join(base_path,'.malt-autogenerated')\n\n    def get_generated_source_path(self):\n        import os\n        file_prefix = 'temp'\n        if self.library:\n            file_prefix = bpy.path.basename(self.library.filepath).split('.')[0]\n        elif bpy.context.blend_data.is_saved:  \n            file_prefix = bpy.path.basename(bpy.context.blend_data.filepath).split('.')[0]\n        pipeline_graph = self.get_pipeline_graph()\n        if pipeline_graph:\n            return os.path.join(self.get_generated_source_dir(),'{}-{}{}'.format(file_prefix, self.name, pipeline_graph.file_extension))\n        return None\n    \n    def get_generated_source(self, force_update=False):\n        if force_update == False and self.get('source'):\n            return self['source']\n\n        output_nodes = []\n        linked_nodes = []\n        \n        pipeline_graph = self.get_pipeline_graph()\n        if pipeline_graph:\n            for node in self.nodes:\n                #TODO: MaltNode.is_result()\n                if node.bl_idname == 'MaltIONode' and node.is_output:\n                    output_nodes.append(node)\n                    linked_nodes.append(node)\n        \n        def add_node_inputs(node, list, io_type):\n            for input in node.inputs:\n                if input.get_linked():\n                    new_node = input.get_linked().node\n                    if new_node.bl_idname == 'MaltIONode' and new_node.io_type != io_type:\n                        input.links[0].is_muted = True\n                        continue\n                    if new_node not in list:\n                        add_node_inputs(new_node, list, io_type)\n                        list.append(new_node)\n                    if new_node not in linked_nodes:\n                        linked_nodes.append(new_node)\n        \n        transpiler = self.get_transpiler()\n        def get_source(output):\n            nodes = []\n            add_node_inputs(output, nodes, output.io_type)\n            code = ''\n            for node in nodes:\n                if hasattr(node, 'get_source_code'):\n                    code += node.get_source_code(transpiler) + '\\n'\n            code += output.get_source_code(transpiler)\n            return code\n\n        shader ={}\n        for output in output_nodes:\n            shader[output.io_type] = get_source(output)\n        shader['GLOBAL'] = ''\n        library_path = self.get_library_path()\n        if library_path:\n            shader['GLOBAL'] += '#include \"{}\"\\n'.format(library_path)\n        for node in linked_nodes:\n            if hasattr(node, 'get_source_global_parameters'):\n                shader['GLOBAL'] += node.get_source_global_parameters(transpiler)\n        self['source'] = pipeline_graph.generate_source(shader)\n        return self['source']\n    \n    def reload_nodes(self):\n        self.disable_updates = True\n        try:\n            for node in self.nodes:\n                if hasattr(node, 'setup'):\n                    node.setup()\n            for node in self.nodes:\n                if hasattr(node, 'update'):\n                    node.update()\n        except:\n            import traceback\n            traceback.print_exc()\n        self.disable_updates = False\n\n    def update(self):\n        if self.is_active():\n            self.update_ext()\n    \n    def update_ext(self, force_track_shader_changes=True, force_update=False):\n        if self.disable_updates:\n            return\n\n        if self.get_pipeline_graph() is None:\n            return\n        \n        if self.subscribed == False:\n            bpy.msgbus.subscribe_rna(key=self.path_resolve('name', False),\n                owner=self, args=(None,), notify=lambda _ : self.update_ext(force_update=True))\n            self.subscribed = True\n        \n        links_str = ''\n        for link in self.links:\n            try:\n                b = link.to_socket\n                a = b.get_linked(ignore_muted=False)\n                links_str += str(a) + str(b)\n            except:\n                pass #Reroute Node\n        links_hash = str(hash(links_str))\n        if force_update == False and links_hash == self.links_hash:\n            return\n        self.links_hash = links_hash\n\n        self.disable_updates = True\n        try:\n            for link in self.links:\n                try:\n                    b = link.to_socket\n                    a = b.get_linked(ignore_muted=False)\n                    if (a.array_size != b.array_size or \n                        (a.data_type != b.data_type and\n                        self.cast(a.data_type, b.data_type) is None)):\n                        link.is_muted = True\n                    else:\n                        link.is_muted = False\n                except:\n                    pass\n            \n            source = self.get_generated_source(force_update=True)\n            source_dir = self.get_generated_source_dir()\n            source_path = self.get_generated_source_path()\n            import pathlib\n            pathlib.Path(source_dir).mkdir(parents=True, exist_ok=True)\n            with open(source_path,'w') as f:\n                f.write(source)\n            if force_track_shader_changes:\n                from BlenderMalt import MaltMaterial\n                MaltMaterial.track_shader_changes()\n        except:\n            import traceback\n            traceback.print_exc()\n        self.disable_updates = False\n        \n        # Force a depsgraph update. \n        # Otherwise these will be outddated in scene_eval\n        self.update_tag()\n\n\ndef setup_node_trees():\n    graphs = MaltPipeline.get_bridge().graphs\n\n    for name, graph in graphs.items():\n        preload_menus(graph.structs, graph.functions, graph)\n    \n    track_library_changes(force_update=True, is_initial_setup=True)\n    \n    for tree in bpy.data.node_groups:\n        if tree.bl_idname == 'MaltTree' and tree.is_active():\n            tree.reload_nodes()\n            tree.update_ext(force_track_shader_changes=False, force_update=True)\n    from BlenderMalt import MaltMaterial\n    MaltMaterial.track_shader_changes()\n\n#SKIP_SAVE doesn't work\ndef manual_skip_save():\n    for tree in bpy.data.node_groups:\n        if tree.bl_idname == 'MaltTree':\n            tree.subscribed = False\n            tree.links_hash = ''\n            for node in tree.nodes:\n                if hasattr(node, 'subscribed'):\n                    node.subscribed = False\n\n__LIBRARIES = {}    \ndef get_libraries():\n    return __LIBRARIES\ndef get_empty_library():\n    return {\n        'structs':{},\n        'functions':{},\n        'subcategories':{},\n        'paths':[],\n    }\n__TIMESTAMP = time.time()\n\ndef track_library_changes(force_update=False, is_initial_setup=False):\n    from BlenderMalt import MaltPipeline\n    if MaltPipeline.is_malt_active() == False and force_update == False:\n        return 1\n    \n    bridge = MaltPipeline.get_bridge()\n    graphs = MaltPipeline.get_bridge().graphs\n    updated_graphs = []\n    if is_initial_setup == False:\n        for name, graph in graphs.items():\n            if graph.needs_reload():\n                updated_graphs.append(name)\n        if len(updated_graphs) > 0:        \n            bridge.reload_graphs(updated_graphs)\n            for graph_name in updated_graphs:\n                graph = graphs[graph_name]\n                preload_menus(graph.structs, graph.functions, graphs[graph_name])\n\n    global __LIBRARIES\n    global __TIMESTAMP\n    start_time = time.time()\n\n    #purge unused libraries\n    new_dic = {}\n    for tree in bpy.data.node_groups:\n        if tree.bl_idname == 'MaltTree' and tree.is_active():\n            src_path = tree.get_library_path()\n            if src_path:\n                if src_path in __LIBRARIES:\n                    new_dic[src_path] = __LIBRARIES[src_path]\n                else:\n                    new_dic[src_path] = None\n    __LIBRARIES = new_dic\n\n    needs_update = set()\n    for path, library in __LIBRARIES.items():\n        root_dir = os.path.dirname(path)\n        if os.path.exists(path):\n            if library is None:\n                needs_update.add(path)\n            else:\n                for sub_path in library['paths']:\n                    sub_path = os.path.join(root_dir, sub_path)\n                    if os.path.exists(sub_path):\n                        # Don't track individual files granularly since macros can completely change them\n                        if os.stat(sub_path).st_mtime > __TIMESTAMP:\n                            needs_update.add(path)\n                            break\n    \n    if len(needs_update) > 0:\n        results = MaltPipeline.get_bridge().reflect_source_libraries(needs_update)\n        for path, reflection in results.items():\n            __LIBRARIES[path] = reflection\n            preload_menus(reflection['structs'], reflection['functions'])\n        \n    if is_initial_setup == False and max(len(needs_update), len(updated_graphs)) > 0:\n        for tree in bpy.data.node_groups:\n            if tree.bl_idname == 'MaltTree' and tree.is_active():\n                src_path = tree.get_library_path()\n                if tree.graph_type in updated_graphs or (src_path and src_path in needs_update):\n                    tree.reload_nodes()\n                    tree.update_ext(force_track_shader_changes=False, force_update=True)\n        from BlenderMalt import MaltMaterial\n        MaltMaterial.track_shader_changes()\n    \n    __TIMESTAMP = start_time\n    return 0.1\n\n\nclass NODE_PT_MaltNodeTree(bpy.types.Panel):\n\n    bl_space_type = 'NODE_EDITOR'\n    bl_region_type = 'UI'\n    bl_category = \"Malt Nodes\"\n    bl_label = \"Malt Node Tree UI\"\n\n    @classmethod\n    def poll(cls, context):\n        return context.space_data.tree_type == 'MaltTree'\n    \n    def draw(self, context):\n        layout = self.layout\n        #layout.prop(context.space_data.node_tree, 'generated_source')\n\n\ndef preload_menus(structs, functions, graph=None):\n    if graph is None:\n        return\n\n    from nodeitems_utils import NodeCategory, NodeItem, register_node_categories, unregister_node_categories\n    from collections import OrderedDict\n\n    # Uses copied code from the <nodeitems_utils> module. Manual check for updates required.\n    class MaltNodeItem(NodeItem):\n\n        def __init__(self, nodetype, category, *, label=None, settings=None, poll=None, draw=None, item_params=None):\n            if settings is None:\n                settings = {}\n\n            self.nodetype = nodetype\n            self._label = f'{category} - {label}'\n            self.button_label = label\n            self.settings = settings\n            self.poll = poll\n            self.item_params = item_params\n            \n            def draw_default(self, layout, _context):\n                props = layout.operator(\"node.add_node\", text=self.button_label, text_ctxt=self.translation_context)\n                props.type = self.nodetype\n                props.use_transform = True\n\n                for setting in self.settings.items():\n                    ops = props.settings.add()\n                    ops.name = setting[0]\n                    ops.value = setting[1]\n\n            self.draw = staticmethod(draw) if draw else staticmethod(draw_default)\n    \n    class MaltSearchMenuItem(MaltNodeItem):\n\n        def __init__(self, nodetype, category, *, label=None, settings=None, poll=None):\n            def draw_nothing(self, layout, _context):\n                return\n            super().__init__(nodetype, category, label=label, settings=settings, poll=poll, draw=draw_nothing)\n\n\n    category_id = f'BLENDERMALT_{graph.name.upper()}'\n\n    try:\n        unregister_node_categories(category_id) # you could also check the hidden <nodeitems_utils._node_categories>\n    except:\n        pass #First run\n\n    categories = {\n        'Input' : [],\n        'Parameters' : [],\n        'Math' : [],\n        'Vector' : [],\n        'Color' : [],\n        'Texturing' : [],\n        'Shading' : [],\n        'Filter' : [],\n        'Other' : [],\n        'Node Tree' : [],\n        'Internal' : [],\n    }\n\n    for name in graph.graph_io:\n        label = name.replace('_',' ')\n        categories['Node Tree'].append(NodeItem('MaltIONode', label=f'{label} Input', settings=OrderedDict({\n            'name' : repr(f'{label} Input'),\n            'is_output' : repr(False),\n            'io_type' : repr(name),\n        })))\n        categories['Node Tree'].append(NodeItem('MaltIONode', label=f'{label} Output', settings=OrderedDict({\n            'name' : repr(f'{label} Output'),\n            'is_output' : repr(True),\n            'io_type' : repr(name),\n        })))\n    \n    if graph.language == 'GLSL':\n        categories['Other'].append(NodeItem('MaltInlineNode', label='Inline Code', settings={\n            'name' : repr('Inline Code')\n        }))\n        categories['Other'].append(NodeItem('MaltArrayIndexNode', label='Array Element', settings={\n            'name' : repr('Array Element')\n        }))\n\n    subcategories = set()\n    \n    def add_to_category(dic, node_type):\n        for k,v in dic.items():\n            if (is_internal := v['meta'].get('internal')):\n                category = 'Internal'\n            else:\n                category = v['meta'].get('category')\n            if category is None:\n                category = v['file'].replace('\\\\', '/').replace('/', ' - ').replace('.glsl', '').replace('_',' ')\n            if category not in categories:\n                categories[category] = []\n\n            _node_type = node_type\n            label = v['meta'].get('label', v['name'])\n            subcategory = v['meta'].get('subcategory')\n            \n            settings = OrderedDict({\n                'name': repr(label),\n                'malt_label': repr(label)\n            })\n            \n            if subcategory and not is_internal:\n                _node_type = 'MaltFunctionSubCategoryNode'\n                label = subcategory\n                settings.update({\n                    'name' : repr(label),\n                    'malt_label': repr(label),\n                    'subcategory': repr(subcategory),\n                    'function_enum': repr(k),\n                })\n                func_label = v['meta']['label']\n\n                def draw_subcategory_item(self: MaltNodeItem, layout, _context):\n                    props = layout.operator('node.add_subcategory_node', text=self.button_label)\n                    props.settings = repr(self.settings)\n                \n                #add subcategory functions to the search menu but not to the regular menus\n                categories['Node Tree'].append(MaltSearchMenuItem(_node_type, category, label=f'{subcategory}: {func_label}', settings=settings))\n                if subcategory in subcategories:\n                    continue\n                subcategories.add(subcategory)\n                node_item = MaltNodeItem(_node_type, category, label=label, settings=settings, draw=draw_subcategory_item)\n            else:\n                if node_type == 'MaltFunctionNode':\n                    settings['function_type'] = repr(k)\n                elif node_type == 'MaltStructNode':\n                    settings['struct_type'] = repr(k)\n                node_item = MaltNodeItem(_node_type, category, label=label, settings=settings)\n            \n            categories[category].append(node_item)\n\n    add_to_category(functions, 'MaltFunctionNode')\n    add_to_category(structs, 'MaltStructNode')\n\n    def poll(cls, context):\n        tree = context.space_data.edit_tree\n        return tree and tree.bl_idname == 'MaltTree' and tree.graph_type == graph.name\n\n    def poll_internal(cls, context):\n        preferences = bpy.context.preferences.addons['BlenderMalt'].preferences\n        return poll(cls, context) and preferences.show_internal_nodes\n\n    category_type = type(category_id, (NodeCategory,), \n    {\n        'poll': classmethod(poll),\n    })\n    category_internal_type = type(f'{category_id}_INTERNAL', (category_type,),\n    {\n        'poll': classmethod(poll_internal),\n    })\n\n    from BlenderMalt import _PLUGINS\n    for plugin in _PLUGINS:\n        try:\n            for category, nodeitems in plugin.blendermalt_register_nodeitems(MaltNodeItem).items():\n                if category not in categories.keys():\n                    categories[category] = []\n                categories[category].extend(nodeitems)\n        except:\n            import traceback\n            traceback.print_exc()\n            \n    category_list = []\n    for category_name, node_items in categories.items():\n        if not len(node_items):\n            continue\n        bl_id = f'{category_id}_{category_name}'\n        bl_id = ''.join(c for c in bl_id if c.isalnum())\n        if len(bl_id) > 64:\n            bl_id = bl_id[:64]\n        if category_name == 'Internal':\n            category_list.append(category_internal_type(bl_id, category_name, items=node_items))\n        else:\n            category_list.append(category_type(bl_id, category_name, items=node_items))\n\n    register_node_categories(category_id, category_list)\n\n\ndef node_header_ui(self, context):\n    node_tree = context.space_data.edit_tree\n    if context.space_data.tree_type != 'MaltTree' or node_tree is None:\n        return\n    def duplicate():\n        context.space_data.node_tree = node_tree.get_copy()\n    self.layout.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set(duplicate, 'Duplicate')\n    def recompile():\n        node_tree.update_ext(force_update=True)\n    self.layout.operator(\"wm.malt_callback\", text='', icon='FILE_REFRESH').callback.set(recompile, 'Recompile')\n    self.layout.prop_search(node_tree, 'graph_type', context.scene.world.malt, 'graph_types',text='')\n    \n\ndef get_node_spaces(context):\n    spaces = []\n    locked_spaces = []\n    for window in context.window_manager.windows:\n        for area in window.screen.areas:\n            if area.type == 'NODE_EDITOR':\n                for space in area.spaces:\n                    if space.type == 'NODE_EDITOR' and space.tree_type == 'MaltTree':\n                        if space.pin == False or space.node_tree is None:\n                            spaces.append(space)\n                        else:\n                            locked_spaces.append(space)\n    return spaces, locked_spaces\n\n\ndef set_node_tree(context, node_tree, node = None):\n    if context.space_data.type == 'NODE_EDITOR' and context.area.ui_type == 'MaltTree':\n        context.space_data.path.append(node_tree, node = node)\n    else:\n        spaces, locked_spaces = get_node_spaces(context)\n        if len(spaces) > 0:\n            spaces[0].node_tree = node_tree\n        elif len(locked_spaces) > 0:\n            locked_spaces[0].node_tree = node_tree\n\n\ndef active_material_update(dummy=None):\n    try:\n        material = bpy.context.object.active_material\n        node_tree = material.malt.shader_nodes\n    except:\n        node_tree = None\n    if node_tree:\n        spaces, locked_spaces = get_node_spaces(bpy.context)\n        for space in spaces:\n            if space.node_tree is None or space.node_tree.graph_type == 'Mesh':\n                space.node_tree = node_tree\n                return\n\n\n@bpy.app.handlers.persistent\ndef depsgraph_update(scene, depsgraph):\n    # Show the active material node tree in the Node Editor\n    from BlenderMalt import MaltPipeline\n    if MaltPipeline.is_malt_active() == False:\n        return\n    scene_updated = False\n    for deps_update in depsgraph.updates:\n        if isinstance(deps_update.id, bpy.types.Scene):\n            scene_updated = True\n    if scene_updated == False:\n        return\n    active_material_update()\n\n@bpy.app.handlers.persistent\ndef load_post(dummy=None):\n    #msgbus subscriptions can't be persistent across file loads :(\n    bpy.msgbus.subscribe_rna(\n        key=(bpy.types.Object, \"active_material_index\"),\n        owner=__msgbus_owner,\n        args=(None,),\n        notify=active_material_update\n    )\n\nclasses = [\n    MaltTree,\n    NODE_PT_MaltNodeTree,\n]\n__msgbus_owner = object()\n\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n\n    bpy.types.NODE_HT_header.append(node_header_ui)\n\n    bpy.app.timers.register(track_library_changes, persistent=True)\n    bpy.app.handlers.depsgraph_update_post.append(depsgraph_update)\n    bpy.app.handlers.load_post.append(load_post)\n    \n\ndef unregister():\n    bpy.msgbus.clear_by_owner(__msgbus_owner)\n\n    bpy.app.handlers.load_post.remove(load_post)\n    bpy.app.handlers.depsgraph_update_post.remove(depsgraph_update)\n    bpy.app.timers.unregister(track_library_changes)\n    \n    bpy.types.NODE_HT_header.remove(node_header_ui)\n\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/MaltNodeUITools.py",
    "content": "import bpy\r\nimport string\r\nfrom typing import Union\r\nfrom bpy.types import NodeTree\r\nfrom bpy.props import PointerProperty\r\nfrom . MaltNodeTree import MaltTree\r\nimport blf, gpu\r\nfrom gpu_extras.batch import batch_for_shader\r\nfrom mathutils import Vector\r\n\r\nclass NodeTreePreview(bpy.types.PropertyGroup):\r\n\r\n    # Name of the node used as a preview\r\n    node_name: bpy.props.StringProperty(\r\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\r\n    # Socket name and index are stored to provide fallbacks\r\n    socket_name: bpy.props.StringProperty(\r\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\r\n    socket_index: bpy.props.IntProperty(\r\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\r\n\r\n    @property\r\n    def node_tree(self) -> NodeTree:\r\n        return self.id_data\r\n\r\n    def is_socket_valid(self, socket: bpy.types.NodeSocket) -> bool:\r\n        return (\r\n            socket.node in self.node_tree.nodes.values()\r\n            and not socket.is_output\r\n        )\r\n    \r\n    def get_node(self) -> Union[bpy.types.Node, None]:\r\n        return self.node_tree.nodes.get(self.node_name, None)\r\n    \r\n    def get_socket_ex(self) -> tuple[Union[bpy.types.NodeSocket, None], Union[str, int]]:\r\n        '''Gets the stored socket. Because all references have to be strings and ints, the validity of the references have to be checked first.'''\r\n        if (node := self.get_node()) == None:\r\n            return None, ''\r\n        if self.socket_name in node.inputs.keys():\r\n            return node.inputs[self.socket_name], self.socket_name\r\n        elif len(node.inputs) > self.socket_index:\r\n            return node.inputs[self.socket_index], self.socket_index\r\n        else:\r\n            return None, ''\r\n    \r\n    def get_socket(self) -> Union[bpy.types.NodeSocket, None]:\r\n        return self.get_socket_ex()[0]\r\n\r\n    @staticmethod\r\n    def _get_visible_node_sockets(node: bpy.types.Node, get_outputs: bool = False) -> list[bpy.types.NodeSocket]:\r\n        front = node.outputs if get_outputs else node.inputs\r\n        return [s for s in front.values() if s.enabled and (not s.hide or len(s.links))]\r\n\r\n    def set_socket(self, socket: bpy.types.NodeSocket) -> bool:\r\n        '''Store the given socket as the preview.'''\r\n        if not self.is_socket_valid(socket):\r\n            return False\r\n\r\n        self.node_name = socket.node.name\r\n        self.socket_name = socket.name\r\n        self.socket_index = next(i for i, s in enumerate(self._get_visible_node_sockets(socket.node, get_outputs=False)) if s == socket)\r\n        return True\r\n    \r\n    def reset_socket_from_node(self, node: bpy.types.Node) -> bool:\r\n        '''Store a socket as the preview of the given node. If a socket of that node is already the preview, cycle through the sockets, otherwise use the first socket.'''\r\n        node_inputs = self._get_visible_node_sockets(node, get_outputs=False)\r\n        if (input_count := len(node_inputs)) < 1:\r\n            return False\r\n        old_socket = self.get_socket()\r\n        if not self.get_node() == node or old_socket == None:\r\n            return self.set_socket(node_inputs[0])\r\n        else:\r\n            old_index = next(i for i, s in enumerate(node_inputs) if s == old_socket)\r\n            return self.set_socket(node_inputs[(old_index + 1) % input_count])\r\n    \r\n    def connect_socket(self, socket: bpy.types.NodeSocket) -> bool:\r\n        preview_socket = self.get_socket()\r\n        if not preview_socket or preview_socket.node == socket.node or not socket.is_output:\r\n            return False\r\n        self.node_tree.links.new(socket, preview_socket)\r\n    \r\n    def reconnect_node(self, node: bpy.types.Node) -> bool:\r\n        '''Connect a node to the preview socket by cycling through its sockets.'''\r\n        preview_socket = self.get_socket()\r\n        node_outputs = self._get_visible_node_sockets(node, get_outputs=True)\r\n        if not preview_socket or preview_socket.node == node or len(node_outputs) < 1:\r\n            return False\r\n        try: # Node is already connected to preview socket. Connect next socket\r\n            previous_socket = next(\r\n                link.from_socket \r\n                for link in preview_socket.links \r\n                if link.from_socket.node == node and link.from_socket in node_outputs)\r\n            previous_index = next(i for i, s in enumerate(node_outputs) if s == previous_socket)\r\n            new_socket = node_outputs[(previous_index + 1) % len(node_outputs)]\r\n            self.connect_socket(new_socket)\r\n        except StopIteration: # When the node is not already connected, use the first socket\r\n            self.connect_socket(node_outputs[0])\r\n        return True\r\n\r\ndef is_malt_tree_context(context: bpy.types.Context) -> bool:\r\n    return (context.area.ui_type == 'MaltTree' and context.space_data.type == 'NODE_EDITOR' and\r\n        context.space_data.edit_tree is not None)\r\n\r\ndef is_malt_node_context(context: bpy.types.Context) -> bool:\r\n    return is_malt_tree_context(context) and context.active_node is not None\r\n\r\nclass OT_MaltEditNodeTree(bpy.types.Operator):\r\n    bl_idname = 'wm.malt_edit_node_tree'\r\n    bl_label = 'Edit Node Tree'\r\n    bl_description = 'Edit the graph of the active group node'\r\n\r\n    @classmethod\r\n    def poll( cls, context ):\r\n        return is_malt_tree_context(context)\r\n            \r\n    def execute( self, context ):\r\n        node = context.active_node\r\n        space_path = context.space_data.path\r\n        node_tree = None\r\n        if node and hasattr(node, 'get_pass_node_tree'):\r\n            node_tree = node.get_pass_node_tree()\r\n        if node_tree:\r\n            space_path.append(node_tree, node = node)\r\n        else:\r\n            space_path.pop()\r\n        return {'FINISHED'}\r\n\r\nclass OT_MaltSetTreePreview(bpy.types.Operator):\r\n    bl_idname = 'wm.malt_set_tree_preview'\r\n    bl_label = 'Set Tree Preview'\r\n    bl_description = 'Set an input socket of the active node as the tree preview socket'\r\n    bl_options = {'UNDO'}\r\n\r\n    @classmethod\r\n    def poll(cls, context):\r\n        return is_malt_node_context(context)\r\n    \r\n    def execute(self, context):\r\n        node_tree: NodeTree = context.space_data.edit_tree\r\n        node = context.active_node\r\n        if not node:\r\n            return {'CANCELLED'}\r\n        node_tree.tree_preview.reset_socket_from_node(node)\r\n        context.area.tag_redraw()\r\n        return {'FINISHED'}\r\n\r\nclass OT_MaltConnectTreePreview(bpy.types.Operator):\r\n    bl_idname = 'wm.malt_connect_tree_preview'\r\n    bl_label = 'Connect To Tree Preview'\r\n    bl_description = 'Connect an output of the active node to the node tree preview socket'\r\n    bl_options = {'UNDO'}\r\n\r\n    @classmethod\r\n    def poll(cls, context):\r\n        return is_malt_node_context(context)\r\n    \r\n    def execute(self, context):\r\n        node_tree: NodeTree = context.space_data.edit_tree\r\n        node = context.active_node\r\n        if not node:\r\n            return {'CANCELLED'}\r\n        tp: NodeTreePreview = node_tree.tree_preview\r\n        tp.reconnect_node(node)\r\n        context.area.tag_redraw()\r\n        return {'FINISHED'}\r\n\r\nclass OT_MaltCycleSubCategories(bpy.types.Operator):\r\n    bl_idname = 'wm.malt_cycle_sub_categories'\r\n    bl_label = 'Cycle Subcategories'\r\n    bl_description = 'Cycle the subcategories of the active subcategory node'\r\n    bl_options = {'UNDO'}\r\n\r\n    @classmethod\r\n    def poll(cls, context):\r\n        return is_malt_node_context(context) and context.active_node.bl_idname == 'MaltFunctionSubCategoryNode'\r\n\r\n    def invoke(self, context: bpy.types.Context, event: bpy.types.Event):\r\n        self.node_tree: MaltTree = context.space_data.edit_tree\r\n        self.node_tree.disable_updates = True\r\n        self.node = context.active_node\r\n            \r\n        self.function_enums:list[tuple[str,str,str]] = self.node.get_function_enums(context)\r\n        self.init_function_enum = self.node.function_enum\r\n\r\n        self.register_interface(True)\r\n        wm = context.window_manager\r\n        wm.modal_handler_add(self)\r\n        return{'RUNNING_MODAL'}\r\n    \r\n    def register_interface(self, register: bool) -> None:\r\n        space = bpy.types.SpaceNodeEditor\r\n        if register:\r\n            self.reset_ui_lists()\r\n            self.draw_handler = space.draw_handler_add(self.draw_modal_interface, (self,), 'WINDOW', 'POST_PIXEL')\r\n        elif self.draw_handler:\r\n            space.draw_handler_remove(self.draw_handler, 'WINDOW')\r\n            del self.draw_handler\r\n    \r\n    def reset_ui_lists(self):\r\n        '''These lists are being read by the UI callback.'''\r\n        self.prev_enums: list[tuple[str, str, str]] = []\r\n        self.next_enums: list[tuple[str, str, str]] = []\r\n\r\n    def cycle_function_enums(self, letter: str, cycle_forward: bool) -> None:\r\n        letter = letter.lower()\r\n        enum_subset = [enum for enum in self.function_enums if enum[1].lower().startswith(letter)]\r\n        if not len(enum_subset):\r\n            return #do nothing if there are no possible function_enums with the given letter\r\n\r\n        self.reset_ui_lists()\r\n        new_index = 0 if cycle_forward else len(enum_subset) - 1\r\n        new_function_enum = enum_subset[new_index][0]\r\n\r\n        if self.node.function_enum in (enum[0] for enum in enum_subset):\r\n            old_index = next(i for i, enum in enumerate(enum_subset) if enum[0] == self.node.function_enum)\r\n            offset = 1 if cycle_forward else -1\r\n            new_index = (old_index + offset) % len(enum_subset)\r\n            new_function_enum = enum_subset[new_index][0]\r\n        \r\n        self.prev_enums = enum_subset[:new_index]\r\n        self.next_enums = enum_subset[new_index + 1:]\r\n        \r\n        self.node.function_enum = new_function_enum\r\n        self.node.setup_width()\r\n\r\n    def modal(self, context: bpy.types.Context, event: bpy.types.Event):\r\n        context.area.tag_redraw()\r\n        if event.type in ['LEFTMOUSE', 'RET', 'SPACE']:\r\n            return self.execute(context)\r\n        if event.type in ['ESC', 'RIGHTMOUSE'] and event.value == 'PRESS':\r\n            return self.cancel(context)\r\n        if event.type in string.ascii_uppercase and event.value == 'PRESS':\r\n            self.cycle_function_enums(event.type, not event.shift)\r\n            return{'RUNNING_MODAL'}\r\n        \r\n        return{'PASS_THROUGH'}\r\n\r\n    def execute(self, context: bpy.types.Context):\r\n        self.node_tree.disable_updates = False\r\n        if self.node.function_enum != self.init_function_enum:\r\n            self.node_tree.update_ext(force_update=True)\r\n        self.register_interface(False)\r\n        context.area.tag_redraw()\r\n        return{'FINISHED'}\r\n    \r\n    def cancel(self, context):\r\n        self.node.function_enum = self.init_function_enum\r\n        self.node_tree.disable_updates = False\r\n        self.register_interface(False)\r\n        context.area.tag_redraw()\r\n        return{'CANCELLED'}\r\n    \r\n    @staticmethod\r\n    def draw_modal_interface(operator: 'OT_MaltCycleSubCategories') -> None:\r\n        context = bpy.context\r\n        node: bpy.types.Node = operator.node\r\n        font_id = 0\r\n\r\n        if context.space_data.path[-1].node_tree != node.id_data:\r\n            return\r\n\r\n        prefs = context.preferences\r\n        label_style = prefs.ui_styles[0].widget\r\n        zoom = MaltNodeDrawCallbacks.get_view_zoom(context)\r\n        dpifac = MaltNodeDrawCallbacks.get_dpifac(context)\r\n        to_region_loc = MaltNodeDrawCallbacks.real_region_loc\r\n\r\n        prev_enums = operator.prev_enums\r\n        next_enums = operator.next_enums\r\n\r\n        text_spacing = 15.0 * zoom\r\n        has_display_items = any(len(x) for x in (prev_enums, next_enums))\r\n        rect_size = 70 if has_display_items else 40\r\n        rect_size *= zoom\r\n\r\n        top_left = to_region_loc(Vector(node.location), context)\r\n        bottom_right = to_region_loc(Vector(node.location) + Vector(node.dimensions) * Vector((1/dpifac, - 1/dpifac)), context)\r\n        m_color = Vector((0.0, 0.0, 0.0, 0.8))\r\n        t_color = Vector((0.0, 0.0, 0.0, 0.0))\r\n\r\n        vertices = (\r\n            top_left + Vector((0, rect_size)),         (bottom_right.x, top_left.y + rect_size),\r\n            top_left,                                   (bottom_right.x, top_left.y),\r\n\r\n            (top_left.x, bottom_right.y),               bottom_right,\r\n            (top_left.x, bottom_right.y - rect_size),   bottom_right + Vector((0, - rect_size))\r\n        )\r\n        vertex_colors = (\r\n            t_color, t_color,\r\n            m_color, m_color,\r\n            m_color, m_color, \r\n            t_color, t_color\r\n        )\r\n        indices = (\r\n            (0,1,2), (2,1,3),\r\n            (4,5,6), (6,5,7),\r\n        )\r\n\r\n        shader = gpu.shader.from_builtin('SMOOTH_COLOR')\r\n        batch = batch_for_shader(shader, 'TRIS', {'pos': vertices, 'color': vertex_colors}, indices=indices)\r\n        gpu.state.blend_set('ALPHA')\r\n        batch.draw(shader)\r\n        gpu.state.blend_set('NONE')\r\n\r\n        blf.size(font_id, label_style.points * zoom)\r\n        blf.color(font_id, 1,1,1,1)\r\n\r\n        for i, e in enumerate(reversed(prev_enums)):\r\n            loc = top_left + Vector((text_spacing * 0.5, i * text_spacing + text_spacing * 0.5))\r\n            blf.position(font_id, *loc, 0)\r\n            blf.draw(font_id, e[1])\r\n\r\n        for i, e in enumerate(next_enums):\r\n            loc = Vector((top_left.x, bottom_right.y)) + Vector((text_spacing * 0.5, -((i + 1) * text_spacing)))\r\n            blf.position(font_id, *loc, 0)\r\n            blf.draw(font_id, e[1])\r\n\r\nclass NODE_OT_add_malt_subcategory_node(bpy.types.Operator):\r\n    bl_idname = 'node.add_subcategory_node'\r\n    bl_label = 'Add Malt Subcategory Node'\r\n    bl_description = 'Add a new Malt Subcategory node. Automatically invoke subcategory cycling'\r\n    bl_options = {'UNDO'}\r\n\r\n    nodetype: bpy.props.StringProperty(default='MaltFunctionSubCategoryNode')\r\n    settings: bpy.props.StringProperty(default='dict()')\r\n\r\n    @classmethod\r\n    def poll(cls, context: bpy.types.Context):\r\n        return is_malt_tree_context(context)\r\n\r\n    def execute(self, context: bpy.types.Context):\r\n        for n in context.space_data.edit_tree.nodes:\r\n            n.select = False\r\n        bpy.ops.node.add_node('INVOKE_DEFAULT', type=self.nodetype, use_transform=True)\r\n        node: bpy.types.Node = context.active_node\r\n        from collections import OrderedDict #maybe there is a better solution for this but without an exception will be thrown because the class is not imported\r\n        for k, v in eval(self.settings).items():\r\n            try:\r\n                setattr(node, k, eval(v))\r\n            except:\r\n                print(f'Attribute {repr(k)} could not be set on {repr(node)}')\r\n        if context.preferences.addons['BlenderMalt'].preferences.use_subfunction_cycling:\r\n            bpy.ops.wm.malt_cycle_sub_categories('INVOKE_DEFAULT')\r\n        return{'FINISHED'}\r\n\r\n\r\nclasses = [\r\n    NodeTreePreview,\r\n    OT_MaltEditNodeTree,\r\n    OT_MaltSetTreePreview,\r\n    OT_MaltConnectTreePreview,\r\n    OT_MaltCycleSubCategories,\r\n    NODE_OT_add_malt_subcategory_node,\r\n]\r\n\r\nclass MaltNodeDrawCallbacks:\r\n\r\n    @staticmethod\r\n    def get_dpifac(context: bpy.types.Context):\r\n        p = context.preferences\r\n        return p.system.dpi * p.system.pixel_size / 72\r\n    \r\n    @staticmethod\r\n    def real_region_loc(view_location: Vector|tuple|list, context: bpy.types.Context) -> Vector:\r\n        return Vector(\r\n            context.region.view2d.view_to_region(\r\n                *[x * MaltNodeDrawCallbacks.get_dpifac(context) for x in view_location], \r\n                clip=False\r\n                )\r\n            )\r\n    \r\n    @staticmethod\r\n    def get_view_zoom(context: bpy.types.Context) -> float:\r\n        get_loc = MaltNodeDrawCallbacks.real_region_loc\r\n        return (get_loc((100, 0), context).x - get_loc((0, 0), context).x) / 100.0\r\n    \r\n    @staticmethod\r\n    def context_path_ui_callback():\r\n        import blf\r\n        font_id = 0\r\n        context = bpy.context\r\n        space = context.space_data\r\n        if not is_malt_tree_context(context):\r\n            return\r\n        if not space.overlay.show_context_path:\r\n            return\r\n        path = space.path\r\n        text = ' > '.join(x.node_tree.name for x in path)\r\n        preferences = context.preferences\r\n        ui_scale = preferences.view.ui_scale\r\n        size = preferences.ui_styles[0].widget.points * ui_scale\r\n        color = preferences.themes[0].node_editor.space.text\r\n        dpi = preferences.system.dpi\r\n        blf.size(font_id, size * (dpi / 72.0))\r\n        blf.position(font_id, 10, 10, 0)\r\n        blf.color(font_id, *color, 1)\r\n        blf.draw(font_id, text)\r\n    \r\n    @staticmethod\r\n    def tree_preview_ui_callback():\r\n        font_id = 0\r\n        context = bpy.context\r\n        space:bpy.types.SpaceNodeEditor = context.space_data\r\n        if not is_malt_tree_context(context):\r\n            return\r\n        node_tree: MaltTree = space.edit_tree\r\n        tp:NodeTreePreview = node_tree.tree_preview\r\n        socket, identifier = tp.get_socket_ex()\r\n        if socket == None:\r\n            return\r\n        \r\n        preferences = context.preferences\r\n        label_style = preferences.ui_styles[0].widget\r\n        size = label_style.points\r\n        #calculate the zoom of the view by taking the difference of transformed points in the x-axis\r\n        zoom = MaltNodeDrawCallbacks.get_view_zoom(context)\r\n\r\n        node = socket.node\r\n        view_loc = Vector(node.location) + Vector((5,5))\r\n        region_loc = MaltNodeDrawCallbacks.real_region_loc(view_loc, context)\r\n        text = f'Preview: {repr(identifier)}'\r\n        #mix the socket color with white to get a result thats not too dark\r\n        color = (Vector(socket.draw_color(context, node)) + Vector((1,1,1,1))) * 0.5\r\n\r\n        def draw_text(text: str, size: float, loc: tuple[float, float], color: tuple[float, float, float, float]):\r\n            blf.size(font_id, size)\r\n            blf.position(font_id, *loc, 0)\r\n            blf.color(font_id, *color)\r\n            blf.draw(font_id, text)\r\n\r\n        draw_text(text, size * zoom, tuple(region_loc + Vector((0,-1))), (0,0,0,1))\r\n        draw_text(text, size * zoom, region_loc, color)\r\n\r\nkeymaps = []\r\ndef register_node_tree_shortcuts():\r\n    wm = bpy.context.window_manager\r\n    kc = wm.keyconfigs.addon\r\n\r\n    def add_shortcut(keymaps: list, operator: bpy.types.Operator, *, type: str, value: str, **modifiers):\r\n        km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')\r\n        kmi = km.keymap_items.new(operator.bl_idname, type=type, value=value, **modifiers)\r\n        keymaps.append((km, kmi))\r\n\r\n    if kc:\r\n        add_shortcut(keymaps, OT_MaltEditNodeTree, type='TAB', value='PRESS')\r\n        add_shortcut(keymaps, OT_MaltSetTreePreview, type='LEFTMOUSE', value='PRESS', shift=True, alt=True)\r\n        add_shortcut(keymaps, OT_MaltConnectTreePreview, type='LEFTMOUSE', value='PRESS', shift=True, ctrl=True)\r\n\r\ndef register():\r\n    for _class in classes: bpy.utils.register_class(_class)\r\n\r\n    global CONTEXT_PATH_DRAW_HANDLER, TREE_PREVIEW_DRAW_HANDLER\r\n    CONTEXT_PATH_DRAW_HANDLER = bpy.types.SpaceNodeEditor.draw_handler_add(MaltNodeDrawCallbacks.context_path_ui_callback, (), 'WINDOW', 'POST_PIXEL')\r\n    TREE_PREVIEW_DRAW_HANDLER = bpy.types.SpaceNodeEditor.draw_handler_add(MaltNodeDrawCallbacks.tree_preview_ui_callback, (), 'WINDOW', 'POST_PIXEL')\r\n\r\n    register_node_tree_shortcuts()\r\n\r\n    NodeTree.tree_preview = PointerProperty(type=NodeTreePreview, name='Node Tree Preview',\r\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\r\n    \r\n\r\ndef unregister():\r\n    del NodeTree.tree_preview\r\n\r\n    for km, kmi in keymaps:\r\n        km.keymap_items.remove(kmi)\r\n        keymaps.clear()\r\n\r\n    global CONTEXT_PATH_DRAW_HANDLER, TREE_PREVIEW_DRAW_HANDLER\r\n    bpy.types.SpaceNodeEditor.draw_handler_remove(CONTEXT_PATH_DRAW_HANDLER, 'WINDOW')\r\n    bpy.types.SpaceNodeEditor.draw_handler_remove(TREE_PREVIEW_DRAW_HANDLER, 'WINDOW')\r\n\r\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\r\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/MaltSocket.py",
    "content": "import bpy\n\n__TYPE_COLORS = {\n    'bool': (0.8, 0.65, 0.84, 1.0),\n    'Bool': (0.8, 0.65, 0.84, 1.0),\n    'float': (0.63, 0.63, 0.63, 1.0),\n    'Float': (0.63, 0.63, 0.63, 1.0),\n    'int': (0.33, 0.55, 0.36, 1.0),\n    'Int': (0.33, 0.55, 0.36, 1.0),\n\n    'vec2': (0.39, 0.74, 0.78, 1.0),\n    'vec3': (0.39, 0.39, 0.78, 1.0),\n    'vec4': (0.60, 0.39, 0.78, 1.0),\n    'mat4': (0.78, 0.39, 0.58, 1.0),\n\n    'sampler1D': (0.78, 0.78, 0.46, 1.0),\n    'sampler2D': (0.78, 0.64, 0.16, 1.0),\n    'Texture': (0.78, 0.64, 0.16, 1.0),\n\n    'Scene': (1.0, 1.0, 1.0, 1.0),\n}\ndef get_type_color(type):\n    if type not in __TYPE_COLORS:\n        import random, hashlib\n        seed = hashlib.sha1(type.encode('ascii')).digest()\n        rand = random.Random(seed)\n        __TYPE_COLORS[type] = (rand.random(),rand.random(),rand.random(),1.0)\n    return __TYPE_COLORS[type]\n        \n\nclass MaltSocket(bpy.types.NodeSocket):\n    \n    bl_label = \"Malt Node Socket\"\n\n    def on_type_update(self, context):\n        self.node.on_socket_update(self)\n\n    data_type: bpy.props.StringProperty(update=on_type_update,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    array_size: bpy.props.IntProperty(default=0, update=on_type_update,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    default_initialization: bpy.props.StringProperty(default='',\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    def show_in_material_panel_update(self, context=None):\n        key = self.node.get_input_parameter_name(self.name)\n        show_in_children = self.node.id_data.malt_parameters.show_in_children\n        if key in show_in_children.keys():\n            show_in_children[key].boolean = self.show_in_material_panel\n\n    show_in_material_panel: bpy.props.BoolProperty(default=True, update=show_in_material_panel_update,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    active: bpy.props.BoolProperty(default=True,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    ui_label: bpy.props.StringProperty()\n\n    def is_instantiable_type(self):\n        return self.data_type.startswith('sampler') == False\n\n    def get_source_reference(self, target_type=None):\n        assert(self.active)\n        if not self.is_instantiable_type() and not self.is_output and self.get_linked() is not None:\n            self.get_linked().get_source_reference()\n        else:\n            reference = self.node.get_source_socket_reference(self)\n            if target_type and target_type != self.data_type:\n                cast_function = self.node.id_data.cast(self.data_type, target_type)\n                return f'{cast_function}({reference})'\n            return reference\n    \n    def get_source_global_reference(self):\n        assert(self.active)\n        transpiler = self.id_data.get_transpiler()\n        result = transpiler.global_reference(self.node.get_source_name(), self.name)\n        if len(result) > 63:\n            #Blender dictionary keys are limited to 63 characters\n            import xxhash\n            result = result[:59] + xxhash.xxh32_hexdigest(result)[:4]\n        return result\n    \n    def is_struct_member(self):\n        return '.' in self.name\n    \n    def get_struct_socket(self):\n        if self.is_struct_member():\n            struct_socket_name = self.name.split('.')[0]\n            if self.is_output:\n                return self.node.outputs[struct_socket_name]\n            else:\n                return self.node.inputs[struct_socket_name]\n        return None\n    \n    def get_source_initialization(self):\n        assert(self.active)\n        if self.get_linked():\n            return self.get_linked().get_source_reference(self.data_type)\n        elif self.default_initialization != '':\n            return self.default_initialization\n        elif self.is_struct_member() and (self.get_struct_socket().get_linked() or self.get_struct_socket().default_initialization != ''):\n            return None\n        else:\n            return self.get_source_global_reference()\n\n    def get_linked(self, ignore_muted=True):\n        def get_linked_internal(socket):\n            if socket.is_linked == False and ignore_muted:\n                return None\n            else:\n                try: link = socket.links[0]\n                except: return None #socket.links can be empty even if is_linked is true!?!?!\n                if ignore_muted and link.is_muted:\n                    return None\n                linked = link.to_socket if socket.is_output else link.from_socket\n                if isinstance(linked.node, bpy.types.NodeReroute):\n                    sockets = linked.node.inputs if linked.is_output else linked.node.outputs\n                    if len(sockets) == 0:\n                        return None\n                    return get_linked_internal(sockets[0])\n                else:\n                    return linked if linked.active else None\n        return get_linked_internal(self)\n    \n    def get_ui_label(self, print_type=True):\n        name = self.ui_label\n        if print_type == False:\n            return name\n\n        type = self.data_type\n        if self.array_size > 0:\n            type += f'[{self.array_size}]'\n        \n        if self.is_output:\n            return f'({type}) : {name}'\n        else:\n            return f'{name} : ({type})'\n    \n    def draw(self, context, layout, node, text):\n        if self.active == False:\n            layout.active = False\n            layout.label(text=text)\n        elif context.region.type != 'UI' or self.get_source_global_reference() == self.get_source_initialization():\n            preferences = bpy.context.preferences.addons['BlenderMalt'].preferences\n            text = self.get_ui_label(preferences.show_socket_types)\n            node.draw_socket(context, layout, self, text)\n            if context.region.type == 'UI':\n                icon = 'HIDE_OFF' if self.show_in_material_panel else 'HIDE_ON'\n                layout.prop(self, 'show_in_material_panel', text='', icon=icon)\n    \n    def setup_shape(self):\n        from Malt.PipelineParameters import Parameter\n        base_type = True\n        try:\n            Parameter.from_glsl_type(self.data_type)\n        except:\n            base_type = False\n        array_type = self.array_size > 0\n        if base_type:\n            if array_type:\n                self.display_shape = 'CIRCLE_DOT'\n            else:\n                self.display_shape = 'CIRCLE'\n        else:\n            if array_type:\n                self.display_shape = 'SQUARE_DOT'\n            else:\n                self.display_shape = 'SQUARE'\n\n    def draw_color(self, context, node):\n        color = get_type_color(self.data_type)\n        if self.active == False:\n            color = list(color)\n            color[3] = 0.25\n        return color\n\n\nclasses = [\n    MaltSocket\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n\ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/Nodes/MaltArrayIndexNode.py",
    "content": "from Malt.PipelineParameters import Parameter, Type\nimport bpy    \nfrom BlenderMalt.MaltNodes.MaltNode import MaltNode\n\n\nclass MaltArrayIndexNode(bpy.types.Node, MaltNode):\n    \n    bl_label = \"Array Index Node\"\n\n    def malt_init(self):\n        self.setup()\n        \n    def malt_setup(self, copy=None):\n        self.setup_sockets({ 'array' : {'type': '', 'size': 1}, 'index' : {'type': Parameter(0, Type.INT) }},\n            {'element' : {'type': ''} }, copy=copy)\n        \n    def malt_update(self):\n        inputs = { \n            'array' : {'type': '', 'size': 1},\n            'index' : {'type': 'int', 'meta':{'value':'0'} }\n        }\n        outputs = { 'element' : {'type': ''} }\n        \n        linked = self.inputs['array'].get_linked()\n        if linked and linked.array_size > 0:\n            inputs['array']['type'] = linked.data_type\n            inputs['array']['size'] = linked.array_size\n            outputs['element']['type'] = linked.data_type\n\n        self.setup_sockets(inputs, outputs)\n\n    def get_source_socket_reference(self, socket):\n        return '{}_0_{}'.format(self.get_source_name(), socket.name)\n    \n    def get_source_code(self, transpiler):\n        array = self.inputs['array']\n        index = self.inputs['index']\n        element = self.outputs['element']\n        element_reference = index.get_source_global_reference()\n        if index.get_linked():\n            element_reference = index.get_linked().get_source_reference()\n        initialization = '{}[{}]'.format(array.get_linked().get_source_reference(), element_reference)\n        return transpiler.declaration(element.data_type, element.array_size, element.get_source_reference(), initialization)\n\n    \nclasses = [\n    MaltArrayIndexNode,\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    \ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py",
    "content": "from Malt.PipelineParameters import Type, Parameter, MaterialParameter, GraphParameter\nimport bpy    \nfrom BlenderMalt.MaltNodes.MaltNode import MaltNode\n\n\nclass MaltFunctionNodeBase(MaltNode):\n        \n    def malt_setup(self, copy=None):\n        pass_type = self.get_pass_type()\n        if pass_type != '':\n            self.pass_graph_type, self.pass_graph_io_type = pass_type.split('.')\n\n        function = self.get_function(skip_overrides=False, find_replacement=True)\n\n        inputs = {}\n        outputs = {}\n\n        if function['type'] != 'void':\n            outputs['result'] = {'type': function['type']} #TODO: Array return type\n        for parameter in function['parameters']:\n            if parameter['io'] in ['out','inout']:\n                outputs[parameter['name']] = parameter\n            if parameter['io'] in ['','in','inout']:\n                inputs[parameter['name']] = parameter\n        \n        show_in_material_panel = function['meta'].get('category', '') == 'Parameters'\n        \n        self.setup_sockets(inputs, outputs, show_in_material_panel=show_in_material_panel, copy=copy)\n        \n        if self.pass_graph_type != '':\n            graph = self.id_data.get_pipeline_graph(self.pass_graph_type)\n            if graph.graph_type == graph.GLOBAL_GRAPH:\n                if graph.language == 'Python':\n                    self.malt_parameters.setup(\n                        {'PASS_GRAPH': GraphParameter(None, graph.name)},\n                        replace_parameters=False,\n                        skip_private=False\n                    )\n                else:\n                    self.malt_parameters.setup(\n                        {'PASS_MATERIAL': MaterialParameter(None, graph.file_extension, self.pass_graph_type)},\n                        replace_parameters=False,\n                        skip_private=False\n                    )\n\n    function_type : bpy.props.StringProperty(update=MaltNode.setup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    pass_graph_type: bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    pass_graph_io_type: bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def get_parameters(self, overrides, resources):\n        parameters = super().get_parameters(overrides, resources)\n        parameters['CUSTOM_IO'] = self.get_custom_io()\n        if 'PASS_GRAPH' in self.malt_parameters.graphs.keys():\n            try: parameters['PASS_GRAPH'] = self.malt_parameters.get_parameter('PASS_GRAPH', overrides, resources)\n            except: pass\n        if 'PASS_MATERIAL' in self.malt_parameters.materials.keys():\n            try: parameters['PASS_MATERIAL'] = self.malt_parameters.get_parameter('PASS_MATERIAL', overrides, resources)\n            except: pass\n        return parameters\n    \n    def find_replacement_function(self):\n        print(f'Try to find replacement function for \"{self.name}\"')\n        print(f'Current function is \"{self.function_type}\"')\n        library = self.id_data.get_full_library()['functions']\n        for key, function in library.items():\n            try:\n                for replace in eval(function['meta']['replace']).split(','):\n                    if replace in self.function_type:\n                        self.function_type = key\n                        return function\n            except:\n                pass\n        parameters = set()\n        from itertools import chain\n        for socket in chain(self.inputs.keys(), self.outputs.keys()):\n            parameters.add(socket)\n        key, function = None, None\n        matching_parameters = 0\n        total_parameters = 0\n        for _key, _function in library.items():\n            if _function['name'] in self.function_type:\n                matching_count = 0\n                for parameter in _function['parameters']:\n                    if parameter['name'] in parameters:\n                        matching_count += 1\n                if (key is None or matching_count > matching_parameters or \n                (matching_count == matching_parameters and len(_function['parameters']) < total_parameters)):\n                    total_parameters = len(_function['parameters'])\n                    key = _key\n                    function = _function\n        if key:\n            print(f'Found replacement function: \"{key}\"')\n            self.function_type = key\n            return function\n        else:\n            self.select = True\n\n    def get_function(self, skip_overrides=True, find_replacement=False):\n        graph = self.id_data.get_pipeline_graph()\n        function = None\n        if self.function_type in graph.functions:\n            function = graph.functions[self.function_type]\n        elif self.function_type in self.id_data.get_library()['functions']:\n            function = self.id_data.get_library()['functions'][self.function_type]\n        elif find_replacement:\n            function = self.find_replacement_function()\n        if function:\n            from copy import deepcopy\n            function = deepcopy(function)\n            function['parameters'] += self.get_custom_io()\n            if skip_overrides:\n                function['parameters'] = [p for p in function['parameters'] if '@' not in p['name']]\n            return function\n    \n    def get_pass_type(self):\n        graph = self.id_data.get_pipeline_graph()\n        if graph.language == 'Python':\n            pass_type = graph.functions[self.function_type]['pass_type']\n            if pass_type:\n                return pass_type\n        return ''\n    \n    def get_pass_node_tree(self):\n        if self.pass_graph_type != '':\n            graph = self.id_data.get_pipeline_graph(self.pass_graph_type)\n            if graph.graph_type == graph.GLOBAL_GRAPH:\n                if graph.language == 'Python':\n                    return self.malt_parameters.graphs['PASS_GRAPH'].graph\n                else:\n                    material = self.malt_parameters.materials['PASS_MATERIAL'].material\n                    if material:\n                        return material.malt.shader_nodes\n    \n    def get_custom_io(self):\n        if self.pass_graph_type != '':\n            graph = self.id_data.get_pipeline_graph(self.pass_graph_type)\n            if graph.graph_type == graph.GLOBAL_GRAPH:\n                tree = None\n                if graph.language == 'Python':\n                    if 'PASS_GRAPH' in self.malt_parameters.graphs.keys():\n                        tree = self.malt_parameters.graphs['PASS_GRAPH'].graph\n                else:\n                    if 'PASS_MATERIAL' in self.malt_parameters.materials.keys():\n                        material = self.malt_parameters.materials['PASS_MATERIAL'].material\n                        if material:\n                            tree = material.malt.shader_nodes\n                if tree:\n                    return tree.get_custom_io(self.pass_graph_io_type)\n            else:\n                world = bpy.context.scene.world\n                if world:\n                    custom_io = world.malt_graph_types[self.pass_graph_type].custom_passes['Default'].io[self.pass_graph_io_type]\n                    result = []\n                    for parameter in custom_io.inputs:\n                        result.append({\n                            'name': parameter.name,\n                            'type': 'Texture', #TODO\n                            'subtype': parameter.parameter,\n                            'size': 0,\n                            'io': 'in',\n                        })\n                    for parameter in custom_io.outputs:\n                        result.append({\n                            'name': parameter.name,\n                            'type': 'Texture', #TODO\n                            'subtype': parameter.parameter,\n                            'size': 0,\n                            'io': 'out',\n                        })\n                    return result\n        return []\n\n    def get_source_socket_reference(self, socket):\n        transpiler = self.id_data.get_transpiler()\n        if transpiler.is_instantiable_type(socket.data_type):\n            return transpiler.parameter_reference(self.get_source_name(), socket.name, 'out' if socket.is_output else 'in')\n        else:\n            source = self.get_source_code(transpiler)\n            return source.splitlines()[-1].split('=')[-1].split(';')[0]\n\n    def get_source_code(self, transpiler):\n        function = self.get_function()\n        source_name = self.get_source_name()\n\n        parameters = []\n        post_parameter_initialization = ''\n        for input in self.inputs:\n            if input.active and input.is_struct_member():\n                initialization = input.get_source_initialization()\n                if initialization:\n                    post_parameter_initialization += transpiler.asignment(input.get_source_reference(), initialization)\n\n        for parameter in function['parameters']:\n            initialization = None\n            if parameter['io'] in ['','in','inout']:\n                socket = self.inputs[parameter['name']]\n                initialization = socket.get_source_initialization()\n            parameters.append(initialization)\n        \n        if self.pass_graph_type != '':\n            def add_implicit_parameter(name):\n                parameter = transpiler.parameter_reference(self.get_source_name(), name, None)\n                initialization = transpiler.global_reference(self.get_source_name(), name)\n                nonlocal post_parameter_initialization\n                post_parameter_initialization += transpiler.asignment(parameter, initialization)\n            graph = self.id_data.get_pipeline_graph(self.pass_graph_type)\n            if graph.graph_type == graph.GLOBAL_GRAPH:\n                if graph.language == 'Python':\n                    add_implicit_parameter('PASS_GRAPH')\n                else:\n                    add_implicit_parameter('PASS_MATERIAL')\n            add_implicit_parameter('CUSTOM_IO')\n\n        return transpiler.call(function, source_name, parameters, post_parameter_initialization)\n    \n    def draw_buttons(self, context, layout):\n        if self.pass_graph_type != '':\n            layout.operator('wm.malt_callback', text='Reload Sockets', icon='FILE_REFRESH').callback.set(self.setup, 'Reload Sockets')\n            graph = self.id_data.get_pipeline_graph(self.pass_graph_type)\n            if graph.graph_type == graph.GLOBAL_GRAPH:\n                if graph.language == 'Python':\n                    self.malt_parameters.draw_parameter(layout, 'PASS_GRAPH', None, is_node_socket=True)\n                else:\n                    self.malt_parameters.draw_parameter(layout, 'PASS_MATERIAL', None, is_node_socket=True)\n\n\nclass MaltFunctionNode(bpy.types.Node, MaltFunctionNodeBase):\n    bl_label = \"Function Node\"\n\nclasses = [\n    MaltFunctionNode,\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    \ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/Nodes/MaltFunctionSubCategory.py",
    "content": "import bpy\nfrom BlenderMalt.MaltNodes.Nodes.MaltFunctionNode import MaltFunctionNodeBase\n\nclass MaltFunctionSubCategoryNode(bpy.types.Node, MaltFunctionNodeBase):\n\n    bl_label = \"Function SubCategory Node\"\n\n    subcategory : bpy.props.StringProperty(options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def get_function_enums(self, context=None):\n        library = self.id_data.get_full_library()\n        items = []\n        for key in library['subcategories'][self.subcategory]:\n            function = library['functions'].get(key)\n            if function is None or function['meta'].get('internal'):\n                continue\n            label = function['meta'].get('label')\n            if label is None:\n                label = function['name'].replace('_',' ').title()\n            items.append((key, label, label))\n        return items\n    \n    def update_function_enum(self, context=None):\n        self.function_type = self.function_enum\n        if self.disable_updates == False:\n            self.id_data.update_ext(force_update=True)\n\n    function_enum : bpy.props.EnumProperty(name='', items=get_function_enums, update=update_function_enum,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    def malt_setup(self, copy=None):\n        #Keep the correct function when the subcategory list changes\n        if self.function_type != '' and self.function_type != self.function_enum:\n            try: self.function_enum = self.function_type\n            except:\n                try:\n                    self.function_type = self.function_enum\n                except:\n                    self.function_type = self.get_function_enums()[0][0]\n        return super().malt_setup(copy=copy)\n    \n    def should_delete_outdated_links(self):\n        return True\n\n    def draw_buttons(self, context, layout):\n        r = layout.row(align=True)\n        r.context_pointer_set('active_node', self)\n        r.prop(self, 'function_enum')\n        if context.preferences.addons['BlenderMalt'].preferences.use_subfunction_cycling:\n            r.operator('wm.malt_cycle_sub_categories', text='', icon='COLLAPSEMENU')\n        return super().draw_buttons(context, layout)\n    \n    def calc_node_width(self, point_size) -> float:\n        import blf\n        blf.size(0, point_size)\n        max_width = super().calc_node_width(point_size)\n        layout_padding = 70 # account for the spaces on both sides of the enum dropdown\n        label = next(enum[1] for enum in self.get_function_enums() if enum[0]==self.function_enum)\n        return max(max_width, blf.dimensions(0, label)[0] + layout_padding)\n\n    def draw_label(self):\n        label = super().draw_label()\n        if self.hide:\n            label += ' - ' + self.get_function()['meta'].get('label', None)\n        return label\n\n\ndef register():\n    bpy.utils.register_class(MaltFunctionSubCategoryNode)\n\ndef unregister():\n    bpy.utils.unregister_class(MaltFunctionSubCategoryNode)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/Nodes/MaltIONode.py",
    "content": "import bpy    \nfrom BlenderMalt.MaltNodes.MaltNode import MaltNode\nfrom BlenderMalt.MaltProperties import MaltPropertyGroup\nfrom BlenderMalt.MaltNodes.MaltCustomPasses import *\n\n\nclass MaltIONode(bpy.types.Node, MaltNode):\n    \n    bl_label = \"IO Node\"\n\n    properties: bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    is_output: bpy.props.BoolProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def get_custom_pass_enums(self, context):\n        custom_passes = ['Default']\n        if self.allow_custom_pass:\n            custom_passes = context.scene.world.malt_graph_types[self.id_data.graph_type].custom_passes.keys()\n        return [(p,p,p) for p in custom_passes]\n        \n    custom_pass: bpy.props.EnumProperty(items=get_custom_pass_enums,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    allow_custom_pass : bpy.props.BoolProperty(default=False,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    allow_custom_parameters : bpy.props.BoolProperty(default=False,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def malt_setup(self, copy=None):\n        function = self.get_function()\n        \n        self.graph_type = self.id_data.graph_type\n        self.pass_type = self.io_type\n        \n        graph = self.id_data.get_pipeline_graph()\n        self.allow_custom_pass = graph.graph_type == graph.SCENE_GRAPH\n        self.allow_custom_parameters = len(self.get_dynamic_parameter_types()) > 0\n\n        inputs = {}\n        outputs = {}\n        \n        if function['type'] != 'void' and self.is_output:\n            inputs['result'] = {'type': function['type']}\n        for parameter in function['parameters']:\n            if parameter['io'] in ['out','inout'] and self.is_output:\n                if parameter['io'] == 'inout':\n                    if 'meta' not in parameter: parameter['meta'] = {}\n                    parameter['meta']['default_initialization'] = parameter['name']\n                inputs[parameter['name']] = parameter\n            if parameter['io'] in ['','in','inout'] and self.is_output == False:\n                outputs[parameter['name']] = parameter\n        \n        for parameter in self.get_custom_parameters():\n            list = inputs if self.is_output else outputs\n            if parameter.name not in list.keys(): #Don't override properties\n                list[parameter.name] = {\n                    'type': parameter.parameter\n                }\n        \n        self.setup_sockets(inputs, outputs, copy=copy)\n\n    io_type : bpy.props.StringProperty(update=MaltNode.setup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    custom_parameters : bpy.props.CollectionProperty(type=MaltIOParameter,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    custom_parameters_index : bpy.props.IntProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def get_function(self):\n        graph = self.id_data.get_pipeline_graph()\n        return graph.graph_io[self.io_type].function\n    \n    def get_custom_pass_io(self):\n        if self.allow_custom_pass and self.allow_custom_parameters:\n            world = bpy.context.scene.world\n            return world.malt_graph_types[self.id_data.graph_type].custom_passes[self.custom_pass].io[self.io_type]\n    \n    def get_custom_parameters(self):\n        if self.allow_custom_parameters:\n            if self.allow_custom_pass:\n                io = self.get_custom_pass_io()\n                return io.outputs if self.is_output else io.inputs\n            else:\n                return self.custom_parameters\n        else:\n            return {}\n    \n    def get_dynamic_parameter_types(self):\n        graph = self.id_data.get_pipeline_graph()\n        if self.is_output:\n            return graph.graph_io[self.io_type].dynamic_output_types\n        else: \n            return graph.graph_io[self.io_type].dynamic_input_types\n    \n    def is_custom_socket(self, socket):\n        parameters = [parameter['name'] for parameter in self.get_function()['parameters']]\n        if (socket.name == 'result' and self.is_output) or socket.name in parameters:\n            return False\n        elif socket.name in self.get_custom_parameters().keys():\n            return True\n        else:\n            return False\n\n    def get_source_socket_reference(self, socket):\n        transpiler = self.id_data.get_transpiler()\n        io = 'out' if self.is_output else 'in'\n        if self.is_custom_socket(socket):\n            return transpiler.custom_io_reference(io, self.io_type, socket.name)\n        else:\n            return transpiler.io_parameter_reference(socket.name, io)\n    \n    def get_source_code(self, transpiler):\n        code = ''\n        if self.is_output:\n            function = self.get_function()\n            custom_outputs = ''\n            for socket in self.inputs:\n                if socket.active == False:\n                    continue\n                if self.is_custom_socket(socket):\n                    custom_outputs += transpiler.asignment(self.get_source_socket_reference(socket), socket.get_source_initialization())\n                else:\n                    if socket.name == 'result':\n                        code += transpiler.declaration(socket.data_type, socket.array_size, socket.name)\n                    initialization = socket.get_source_initialization()\n                    if initialization:\n                        code += transpiler.asignment(socket.get_source_reference(), initialization)\n            if custom_outputs != '':\n                graph_io = self.id_data.get_pipeline_graph().graph_io[self.io_type]\n                try: io_wrap = graph_io.io_wrap\n                except: io_wrap = ''\n                code += transpiler.preprocessor_wrap(io_wrap, custom_outputs)\n            if function['type'] != 'void':\n                code += transpiler.result(self.inputs['result'].get_source_reference())\n\n        return code\n    \n    def get_source_global_parameters(self, transpiler):\n        src = MaltNode.get_source_global_parameters(self, transpiler)\n        custom_outputs = ''\n        graph_io = self.id_data.get_pipeline_graph().graph_io[self.io_type]\n        index = graph_io.custom_output_start_index\n        for key, parameter in self.get_custom_parameters().items():\n            if parameter.is_output:\n                socket = self.inputs[key]\n                custom_outputs += transpiler.custom_output_declaration(socket.data_type, key, index, self.io_type)\n                index += 1\n            else:\n                socket = self.outputs[key]\n                src += transpiler.global_declaration(parameter.parameter, 0, self.get_source_socket_reference(socket))\n        if custom_outputs != '':\n            try: io_wrap = graph_io.io_wrap\n            except: io_wrap = ''\n            src += transpiler.preprocessor_wrap(io_wrap, custom_outputs)\n        return src\n    \n    def draw_buttons(self, context, layout):\n        return #TODO: only 1 custom pass signature for now\n        if self.allow_custom_pass and (self.is_output or self.allow_custom_parameters):\n            row = layout.row(align=True)\n            row.prop(self, 'custom_pass', text='Custom Pass')\n            row.operator('wm.malt_add_custom_pass', text='', icon='ADD').graph_type = self.id_data.graph_type\n            if self.custom_pass != 'Default':\n                def remove():\n                    custom_passes = context.scene.world.malt_graph_types[self.id_data.graph_type].custom_passes\n                    custom_passes.remove(custom_passes.find(self.custom_pass))\n                    #self.custom_pass = 'Default'\n                row.operator('wm.malt_callback', text='', icon='REMOVE').callback.set(remove)\n    \n    def draw_buttons_ext(self, context, layout):\n        if self.allow_custom_parameters:\n            def refresh():\n                #TODO: Overkill\n                for tree in bpy.data.node_groups:\n                    if tree.bl_idname == 'MaltTree':\n                        tree.reload_nodes()\n                        tree.update_ext(force_update=True)\n            layout.operator(\"wm.malt_callback\", text='Reload', icon='FILE_REFRESH').callback.set(refresh, 'Reload')\n            def draw_parameters_list(owner, parameters_key):\n                row = layout.row()\n                index_key = f'{parameters_key}_index'\n                row.template_list('COMMON_UL_UI_List', '', owner, parameters_key, owner, index_key)\n                col = row.column()\n                parameters = getattr(owner, parameters_key)\n                index = getattr(owner, index_key)\n                def add_custom_socket():\n                    new_param = parameters.add()\n                    new_param.graph_type = self.id_data.graph_type\n                    new_param.io_type = self.io_type\n                    new_param.is_output = self.is_output\n                    name = f\"Custom {'Output' if new_param.is_output else 'Input'}\"\n                    i = 1\n                    #TODO: Check against default parameters\n                    while f'{name} {i}' in parameters.keys():\n                        i += 1\n                    new_param.name = f'{name} {i}'\n                add_row = col.row()\n                graph = self.id_data.get_pipeline_graph()\n                if (self.is_output and graph.language == 'GLSL' and\n                len(parameters) >= (8 - graph.graph_io[self.io_type].custom_output_start_index) and\n                self.id_data.graph_type.endswith(\"(Group)\") == False): \n                    add_row.enabled = False\n                add_row.operator(\"wm.malt_callback\", text='', icon='ADD').callback.set(add_custom_socket, 'Add')\n                def remove_custom_socket():\n                    parameters.remove(index)\n                col.operator(\"wm.malt_callback\", text='', icon='REMOVE').callback.set(remove_custom_socket, 'Remove')\n            if self.allow_custom_pass:\n                draw_parameters_list(self.get_custom_pass_io(), 'outputs' if self.is_output else 'inputs')\n            else:\n                draw_parameters_list(self, 'custom_parameters')\n    \n    \nclasses = [\n    MaltIONode,\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    \ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/Nodes/MaltInlineNode.py",
    "content": "import bpy    \nfrom BlenderMalt.MaltNodes.MaltNode import MaltNode\n\n\nclass MaltInlineNode(bpy.types.Node, MaltNode):\n    \n    bl_label = \"Inline Code Node\"\n\n    def code_update(self, context):\n        #update the node tree\n        self.id_data.update_ext(force_update=True)\n\n    code : bpy.props.StringProperty(update=code_update,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def on_socket_update(self, socket):\n        self.update()\n        self.id_data.update_ext(force_update=True)\n\n    def malt_init(self):\n        self.setup()\n    \n    def malt_update(self):\n        last = 0\n        for i, input in enumerate(self.inputs):\n            if input.data_type != '' or input.get_linked():\n                last = i + 1\n        variables = 'abcdefgh'[:min(last+1,8)]\n        \n        inputs = {}\n        for var in variables:\n            inputs[var] = {'type': ''}\n            if var in self.inputs:\n                input = self.inputs[var]\n                linked = self.inputs[var].get_linked()\n                if linked and linked.data_type != '':\n                    inputs[var] = {'type': linked.data_type, 'size': linked.array_size}\n                else:\n                    inputs[var] = {'type': input.data_type, 'size': input.array_size}\n        \n        outputs = { 'result' : {'type': ''} }\n        if 'result' in self.outputs:\n            out = self.outputs['result'].get_linked()\n            if out:\n                outputs['result'] = {'type': out.data_type, 'size': out.array_size}\n        \n        self.setup_sockets(inputs, outputs)\n\n    def draw_buttons(self, context, layout):\n        layout.prop(self, 'code', text='')\n    \n    def draw_socket(self, context, layout, socket, text):\n        if socket.is_output == False:\n            MaltNode.draw_socket(self, context, layout, socket, socket.name)\n            layout.prop(socket, 'data_type', text='')\n        else:\n            MaltNode.draw_socket(self, context, layout, socket, socket.name)\n\n    def get_source_socket_reference(self, socket):\n        return '{}_0_{}'.format(self.get_source_name(), socket.name)\n    \n    def get_source_code(self, transpiler):\n        code = ''\n        result_socket = self.outputs['result']\n        code += transpiler.declaration(result_socket.data_type, result_socket.array_size, result_socket.get_source_reference())\n\n        scoped_code = ''\n        for input in self.inputs:\n            if input.data_type != '':\n                initialization = input.get_source_initialization()\n                scoped_code += transpiler.declaration(input.data_type, input.array_size, input.name, initialization)\n        if self.code != '':\n            scoped_code += transpiler.asignment(self.outputs['result'].get_source_reference(), self.code)\n\n        return code + transpiler.scoped(scoped_code)\n\n    \nclasses = [\n    MaltInlineNode,\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    \ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/Nodes/MaltStructNode.py",
    "content": "import bpy    \nfrom BlenderMalt.MaltNodes.MaltNode import MaltNode\n\n\nclass MaltStructNode(bpy.types.Node, MaltNode):\n    \n    bl_label = \"Struct Node\"\n\n    def malt_setup(self, copy=None):\n        inputs = {}\n        inputs[self.struct_type] = {'type' : self.struct_type}\n        outputs = {}\n        outputs[self.struct_type] = {'type' : self.struct_type}\n\n        self.setup_sockets(inputs, outputs, copy=copy)\n\n    struct_type : bpy.props.StringProperty(update=MaltNode.setup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def get_struct(self):\n        graph = self.id_data.get_pipeline_graph()\n        if self.struct_type in graph.structs:\n            return graph.structs[self.struct_type]\n        else:\n            return self.id_data.get_library()['structs'][self.struct_type]\n\n    def get_source_socket_reference(self, socket):\n        if socket.name == self.struct_type:\n            return self.get_source_name()\n        else:\n            return socket.name.replace(self.struct_type, self.get_source_name())\n    \n    def struct_input_is_linked(self):\n        return self.inputs[self.struct_type].get_linked() is not None\n\n    def get_source_code(self, transpiler):\n        code = ''\n        \n        for input in self.inputs:\n            initialization = input.get_source_initialization()\n            if input.is_struct_member():\n                if initialization:\n                    code += transpiler.asignment(input.get_source_reference(), initialization)\n            else:\n                code += transpiler.declaration(input.data_type, 0, self.get_source_name(), initialization)\n        \n        return code\n\n        \nclasses = [\n    MaltStructNode,\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    \ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/MaltNodes/_init_.py",
    "content": "def get_modules():\n    from . import MaltNode, MaltNodeUITools, MaltNodeTree, MaltSocket, MaltCustomPasses\n    modules = [MaltNode, MaltNodeUITools,MaltNodeTree, MaltSocket, MaltCustomPasses]\n    \n    import importlib, os\n    nodes_dir = os.path.join(os.path.dirname(__file__), 'Nodes')\n    for name in os.listdir(nodes_dir):\n        try:\n            if name == '__pycache__':\n                continue\n            if name.endswith('.py'):\n                name = name[:-3]\n            module = importlib.import_module(f'BlenderMalt.MaltNodes.Nodes.{name}')\n            modules.append(module)\n        except ModuleNotFoundError:\n            # Ignore it. The file or dir is not a python module\n            pass\n        except Exception:\n            import traceback\n            traceback.print_exc()\n    \n    return modules\n    \n\ndef register():\n    import importlib\n    for module in get_modules():\n        importlib.reload(module)\n\n    for module in get_modules():\n        module.register()\n\ndef unregister():\n    for module in reversed(get_modules()):\n        module.unregister()\n"
  },
  {
    "path": "BlenderMalt/MaltPipeline.py",
    "content": "import os, platform, time\nimport bpy\nfrom BlenderMalt.MaltUtils import malt_path_set_transform, malt_path_get_transform\nfrom . import MaltMaterial, MaltMeshes, MaltTextures\n\n_BRIDGE = None\n_PIPELINE_PARAMETERS = None\n_TIMESTAMP = time.time()\n\ndef is_malt_active():\n    if bpy.context.scene.render.engine == 'MALT':\n        return True\n    for scene in bpy.data.scenes:\n        if scene.render.engine == 'MALT':\n            return True\n    return False\n\ndef get_bridge(world=None, force_creation=False):\n    global _BRIDGE\n    bridge = _BRIDGE\n    if (bridge and bridge.lost_connection) or (bridge is None and force_creation):\n        _BRIDGE = None\n        if is_malt_active() == False:\n            return None\n        if world is None:\n            world = bpy.context.scene.world\n        world.malt.update_pipeline(bpy.context)\n    return _BRIDGE\n\ndef sync_pipeline_settings(default_world=None):\n    for scene in bpy.data.scenes:\n        if scene.render.engine == 'MALT' and scene.world is None:\n            scene.world = bpy.data.worlds.new(f'{scene.name} World')\n            setup_parameters([scene.world])\n    if default_world is None:\n        default_world = bpy.data.worlds[0]\n    for world in bpy.data.worlds:\n        if world.malt.pipeline != default_world.malt.pipeline:\n            world.malt.pipeline = default_world.malt.pipeline\n        if world.malt.plugins_dir != default_world.malt.plugins_dir:\n            world.malt.plugins_dir = default_world.malt.plugins_dir\n        if world.malt.viewport_bit_depth != default_world.malt.viewport_bit_depth:\n            world.malt.viewport_bit_depth = default_world.malt.viewport_bit_depth\n\n_ON_PIPELINE_SETTINGS_UPDATE = False\n\nclass MaltPipeline(bpy.types.PropertyGroup):\n\n    def update_pipeline(self, context):\n        global _TIMESTAMP\n        _TIMESTAMP = time.time()\n        \n        #TODO: Sync all scenes. Only one active pipeline per Blender instance is supported atm.\n        pipeline = self.pipeline\n        if pipeline == '':\n            current_dir = os.path.dirname(os.path.abspath(__file__))\n            default_pipeline = os.path.join(current_dir,'.MaltPath','Malt','Pipelines','NPR_Pipeline','NPR_Pipeline.py')\n            if platform.system() == 'Darwin':\n                # The NPR Pipeline doesn't work on OpenGL implementations limited to 16 sampler uniforms\n                default_pipeline = os.path.join(current_dir,'.MaltPath','Malt','Pipelines','MiniPipeline','MiniPipeline.py')\n            pipeline = default_pipeline\n        \n        preferences = bpy.context.preferences.addons['BlenderMalt'].preferences\n\n        debug_mode = bool(preferences.debug_mode)\n        renderdoc_path = preferences.renderdoc_path\n        plugin_dirs = []\n        if os.path.exists(preferences.plugins_dir):\n            plugin_dirs.append(preferences.plugins_dir)\n        plugin_dir = bpy.path.abspath(self.plugins_dir, library=self.id_data.library)\n        if os.path.exists(plugin_dir):\n            plugin_dirs.append(plugin_dir)\n        \n        docs_path = preferences.docs_path\n        docs_path = docs_path if os.path.exists(docs_path) else None\n        \n        path = bpy.path.abspath(pipeline, library=self.id_data.library)\n        import Bridge\n        bridge = Bridge.Client_API.Bridge(path, int(self.viewport_bit_depth), debug_mode, renderdoc_path, plugin_dirs, docs_path)\n        from Malt.Utils import LOG\n        LOG.info('Blender {} {} {}'.format(bpy.app.version_string, bpy.app.build_branch, bpy.app.build_hash))\n        params = bridge.get_parameters()\n\n        global _BRIDGE, _PIPELINE_PARAMETERS\n        _BRIDGE = bridge\n        _PIPELINE_PARAMETERS = params\n        \n        MaltMaterial.reset_materials()\n        MaltMeshes.reset_meshes()\n        MaltTextures.reset_textures()\n        \n        #TODO: This can fail depending on the current context, ID classes might not be writeable\n        setup_all_ids()\n\n    def update_pipeline_settings(self, context):\n        global _ON_PIPELINE_SETTINGS_UPDATE\n        if _ON_PIPELINE_SETTINGS_UPDATE:\n            return\n        _ON_PIPELINE_SETTINGS_UPDATE = True\n\n        sync_pipeline_settings(self.id_data)\n        \n        _ON_PIPELINE_SETTINGS_UPDATE = False\n        \n        self.update_pipeline(context)\n\n    pipeline : bpy.props.StringProperty(name=\"Malt Pipeline\", subtype='FILE_PATH', update=update_pipeline_settings,\n        set_transform=malt_path_set_transform, get_transform=malt_path_get_transform,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    plugins_dir : bpy.props.StringProperty(name=\"Local Plugins\", subtype='DIR_PATH', update=update_pipeline_settings,\n        set_transform=malt_path_set_transform, get_transform=malt_path_get_transform,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    viewport_bit_depth : bpy.props.EnumProperty(items=[('8', '8', ''),('16', '16', ''),('32', '32', '')], \n        name=\"Bit Depth (Viewport)\", update=update_pipeline_settings,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    graph_types : bpy.props.CollectionProperty(type=bpy.types.PropertyGroup,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    material_types : bpy.props.CollectionProperty(type=bpy.types.PropertyGroup,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    overrides : bpy.props.StringProperty(name='Pipeline Overrides', default='Preview,Final Render',\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def draw_ui(self, layout):\n        layout.use_property_split = True\n        layout.use_property_decorate = False\n        row = layout.row(align=True)\n        row.prop(self, 'pipeline')\n        row.operator('wm.malt_reload_pipeline', text='', icon='FILE_REFRESH')\n        layout.prop(self, 'plugins_dir')\n        layout.prop(self, 'viewport_bit_depth')\n\n\nclass OT_MaltReloadPipeline(bpy.types.Operator):\n    bl_idname = \"wm.malt_reload_pipeline\"\n    bl_label = \"Malt Reload Pipeline\"\n\n    @classmethod\n    def poll(cls, context):\n        return context.scene.render.engine == 'MALT' and context.scene.world is not None\n\n    def execute(self, context):\n        import Bridge\n        Bridge.reload()\n        context.scene.world.malt.update_pipeline(context)\n        return {'FINISHED'}\n\n\nclass MALT_PT_Pipeline(bpy.types.Panel):\n    bl_space_type = 'PROPERTIES'\n    bl_region_type = 'WINDOW'\n\n    bl_context = \"world\"\n    bl_label = \"Pipeline Settings\"\n    COMPAT_ENGINES = {'MALT'}\n\n    @classmethod\n    def poll(cls, context):\n        return context.scene.render.engine == 'MALT' and context.scene.world is not None\n\n    def draw(self, context):\n        context.scene.world.malt.draw_ui(self.layout)\n\nclasses = (\n    MaltPipeline,\n    OT_MaltReloadPipeline,\n    MALT_PT_Pipeline,\n)\n\ndef setup_all_ids():\n    setup_parameters(bpy.data.scenes)\n    setup_parameters(bpy.data.worlds)\n    setup_parameters(bpy.data.cameras)\n    setup_parameters(bpy.data.objects)\n    setup_parameters(bpy.data.materials)\n    setup_parameters(bpy.data.meshes)\n    setup_parameters(bpy.data.curves)\n    setup_parameters(bpy.data.metaballs)\n    setup_parameters(bpy.data.lights)\n    from . MaltNodes.MaltNodeTree import setup_node_trees\n    setup_node_trees()\n    MaltMaterial.track_shader_changes(force_update=True)\n\ndef setup_parameters(ids):\n    global _PIPELINE_PARAMETERS\n    pipeline_parameters = _PIPELINE_PARAMETERS\n\n    class_parameters_map = {\n        bpy.types.Scene : pipeline_parameters.scene,\n        bpy.types.World : pipeline_parameters.world,\n        bpy.types.Camera : pipeline_parameters.camera,\n        bpy.types.Object : pipeline_parameters.object,\n        bpy.types.Material : pipeline_parameters.material,\n        bpy.types.Mesh : pipeline_parameters.mesh,\n        bpy.types.Curve : pipeline_parameters.mesh,\n        bpy.types.MetaBall : pipeline_parameters.mesh,\n        bpy.types.Light : pipeline_parameters.light,\n    }\n\n    for bid in ids:\n        if isinstance(bid, bpy.types.World):\n            bid.malt.graph_types.clear()\n            bid.malt.material_types.clear()\n            for graph in get_bridge().graphs.values():\n                bid.malt.graph_types.add().name = graph.name\n                if graph.language == 'GLSL':\n                    bid.malt.material_types.add().name = graph.name\n            from BlenderMalt.MaltNodes import MaltCustomPasses\n            MaltCustomPasses.setup_default_passes(get_bridge().graphs, bid)\n        if isinstance(bid, bpy.types.Material):\n            #Patch material types\n            if bid.malt.material_type == '':\n                if bid.malt.shader_nodes:\n                    bid.malt.material_type = bid.malt.shader_nodes.graph_type\n                else:\n                    bid.malt.material_type = 'Mesh'\n        for cls, parameters in class_parameters_map.items():\n            if isinstance(bid, cls):\n                bid.malt_parameters.setup(parameters)\n\n_ON_DEPSGRAPH_UPDATE = False\n\n@bpy.app.handlers.persistent\ndef depsgraph_update(scene, depsgraph):\n    global _BRIDGE, _ON_DEPSGRAPH_UPDATE\n\n    if _ON_DEPSGRAPH_UPDATE:\n        return\n    _ON_DEPSGRAPH_UPDATE = True\n    try:\n        if is_malt_active() == False:\n            # Don't do anything if Malt is not the active renderer,\n            # but make sure we setup all IDs the next time Malt is enabled\n            _BRIDGE = None\n            return\n        \n        sync_pipeline_settings()\n\n        if _BRIDGE is None:\n            scene.world.malt.update_pipeline(bpy.context)\n            return\n\n        ids = []\n        class_data_map = {\n            bpy.types.Scene : bpy.data.scenes,\n            bpy.types.World : bpy.data.worlds,\n            bpy.types.Camera : bpy.data.cameras,\n            bpy.types.Object : bpy.data.objects,\n            bpy.types.Material : bpy.data.materials,\n            bpy.types.Mesh : bpy.data.meshes,\n            bpy.types.Curve : bpy.data.curves,\n            bpy.types.MetaBall : bpy.data.metaballs,\n            bpy.types.Light : bpy.data.lights,\n        }\n        for update in depsgraph.updates:\n            # Try to avoid as much re-setups as possible. \n            # Ideally we would do it only on ID creation.\n            if update.is_updated_geometry == True or update.is_updated_transform == False:\n                for cls, data in class_data_map.items():\n                    if isinstance(update.id, cls):\n                        ids.append(data[update.id.name])\n        setup_parameters(ids)\n\n        from . MaltNodes.MaltNodeTree import MaltTree\n\n        redraw = False\n        for update in depsgraph.updates:\n            if update.is_updated_geometry:\n                if isinstance(update.id, bpy.types.Object):\n                    MaltMeshes.unload_mesh(update.id)\n            if isinstance(update.id, bpy.types.Image):\n                MaltTextures.unload_texture(update.id)\n                redraw = True\n            elif isinstance(update.id, bpy.types.Texture):\n                MaltTextures.unload_gradients(update.id)\n                redraw = True\n            elif update.id.__class__.__name__ == 'MaltTree':\n                redraw = True\n        if redraw:\n            for screen in bpy.data.screens:\n                for area in screen.areas:\n                    area.tag_redraw()\n    except:\n        import traceback\n        traceback.print_exc()\n    finally:\n        _ON_DEPSGRAPH_UPDATE = False\n\n@bpy.app.handlers.persistent\ndef load_scene(dummy1=None,dummy2=None):\n    global _BRIDGE\n    _BRIDGE = None\n\n@bpy.app.handlers.persistent\ndef load_scene_post(dummy1=None,dummy2=None):\n    from BlenderMalt.MaltNodes.MaltNodeTree import manual_skip_save\n    manual_skip_save()\n    if is_malt_active():\n        bpy.context.scene.world.malt.update_pipeline(bpy.context)\n\n__SAVE_PATH = None\n@bpy.app.handlers.persistent\ndef save_pre(dummy1=None,dummy2=None):\n    global __SAVE_PATH\n    __SAVE_PATH = bpy.data.filepath\n\n@bpy.app.handlers.persistent\ndef save_post(dummy1=None,dummy2=None):\n    if __SAVE_PATH != bpy.data.filepath:\n        load_scene_post()\n\ndef track_pipeline_changes():\n    if is_malt_active() == False:\n        return 1\n    try:\n        scene = bpy.context.scene\n        malt = scene.world.malt\n        path = bpy.path.abspath(malt.pipeline, library=malt.id_data.library)\n        if os.path.exists(path):\n            stats = os.stat(path)\n            if stats.st_mtime > _TIMESTAMP:\n                malt.update_pipeline(bpy.context)\n    except:\n        import traceback\n        print(traceback.format_exc())\n\n    return 1\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n    bpy.types.World.malt = bpy.props.PointerProperty(type=MaltPipeline,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.app.handlers.depsgraph_update_post.append(depsgraph_update)\n    bpy.app.handlers.load_pre.append(load_scene)\n    bpy.app.handlers.load_post.append(load_scene_post)\n    bpy.app.handlers.save_pre.append(save_pre)\n    bpy.app.handlers.save_post.append(save_post)\n    bpy.app.timers.register(track_pipeline_changes, persistent=True)\n    \ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n    del bpy.types.World.malt\n    bpy.app.handlers.depsgraph_update_post.remove(depsgraph_update)\n    bpy.app.handlers.load_pre.remove(load_scene)\n    bpy.app.handlers.load_post.remove(load_scene_post)\n    bpy.app.handlers.save_pre.remove(save_pre)\n    bpy.app.handlers.save_post.remove(save_post)\n    bpy.app.timers.unregister(track_pipeline_changes)\n"
  },
  {
    "path": "BlenderMalt/MaltProperties.py",
    "content": "import os\nimport bpy\nfrom Malt.PipelineParameters import Type, Parameter, MaterialParameter\nfrom Malt import Scene\nfrom . import MaltTextures\n\nclass MaltBoolPropertyWrapper(bpy.types.PropertyGroup):\n    boolean : bpy.props.BoolProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\nclass MaltEnumPropertyWrapper(bpy.types.PropertyGroup):\n    enum_options : bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    def get_items(self, context=None):\n        for option in self.enum_options.split(','):\n            yield (option, option, option)\n    \n    enum : bpy.props.EnumProperty(items=get_items, name='',\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n# WORKAROUND: We can't declare color ramps from python,\n# so we use the ones stored inside textures\nclass MaltGradientPropertyWrapper(bpy.types.PropertyGroup):\n    def poll(self, texture):\n        return texture.type == 'BLEND' and texture.use_color_ramp\n    texture : bpy.props.PointerProperty(type=bpy.types.Texture, poll=poll,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def add_or_duplicate(self, name=None):\n        if self.texture:\n            self.texture = self.texture.copy()\n        else:\n            if name is None:\n                name = f'{self.id_data.name} - {self.name}'\n            texture = bpy.data.textures.new(name, 'BLEND')\n            texture.use_color_ramp = True\n            texture.color_ramp.elements[0].alpha = 1.0\n            self.texture = texture\n        self.id_data.update_tag()\n        self.texture.update_tag()\n        from . MaltTextures import add_gradient_workaround\n        add_gradient_workaround(self.texture)\n\nclass MaltTexturePropertyWrapper(bpy.types.PropertyGroup):\n    texture : bpy.props.PointerProperty(type=bpy.types.Image,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\nclass MaltMaterialPropertyWrapper(bpy.types.PropertyGroup):\n    def poll(self, material):\n        return material.malt.material_type == self.type or self.type == ''\n    material : bpy.props.PointerProperty(type=bpy.types.Material, poll=poll,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    extension : bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    type : bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def add_or_duplicate(self, name=None):\n        if name is None:\n            name = f'{self.id_data.name} - {self.name}'\n        if self.material:\n            self.material = self.material.copy()\n        else:\n            self.material = bpy.data.materials.new(name)\n            self.material.malt.material_type = self.type\n        self.id_data.update_tag()\n        self.material.update_tag()\n\nclass MaltGraphPropertyWrapper(bpy.types.PropertyGroup):\n    def poll(self, tree):\n        return tree.bl_idname == 'MaltTree' and tree.graph_type == self.type\n    graph : bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    type : bpy.props.StringProperty(\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n    def add_or_duplicate(self, name=None):\n        if name is None:\n            name = f'{self.id_data.name} - {self.name} - {self.type}'\n        if self.graph:\n            self.graph = self.graph.get_copy()\n        else:\n            self.graph = bpy.data.node_groups.new(name, 'MaltTree')\n            self.graph.graph_type = self.type\n        self.id_data.update_tag()\n        self.graph.update_tag()\n\nclass MaltPropertyGroup(bpy.types.PropertyGroup):\n\n    bools : bpy.props.CollectionProperty(type=MaltBoolPropertyWrapper,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    enums : bpy.props.CollectionProperty(type=MaltEnumPropertyWrapper,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    gradients : bpy.props.CollectionProperty(type=MaltGradientPropertyWrapper,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})    \n    textures : bpy.props.CollectionProperty(type=MaltTexturePropertyWrapper,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})    \n    materials : bpy.props.CollectionProperty(type=MaltMaterialPropertyWrapper,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    graphs : bpy.props.CollectionProperty(type=MaltGraphPropertyWrapper,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n\n    parent : bpy.props.PointerProperty(type=bpy.types.ID, name=\"Override From\",\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    override_from_parents : bpy.props.CollectionProperty(type=MaltBoolPropertyWrapper,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n    show_in_children : bpy.props.CollectionProperty(type=MaltBoolPropertyWrapper,\n        options={'LIBRARY_EDITABLE'},\n        override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})\n\n    def get_rna(self):\n        try:\n            if '_MALT_' not in self.keys():\n                if '_RNA_UI' in self.keys():\n                    old_rna = self['_RNA_UI']\n                    self['_MALT_'] = {k : old_rna[k] for k in old_rna.keys()}\n                    self.pop('_RNA_UI')\n                else:\n                    self['_MALT_'] = {}\n            return self['_MALT_']\n        except:\n            return {}\n\n    def setup(self, parameters, replace_parameters=True, reset_to_defaults=False, skip_private=True,\n        copy_from=None, copy_map=None):\n        rna = self.get_rna()\n\n        copy_values = copy_from is not None\n        if copy_from is None and self.parent:\n            copy_from = self.parent.malt_parameters\n        \n        def setup_parameter(name, parameter):\n            if name not in rna.keys():\n                rna[name] = {}\n\n            type_changed = 'type' not in rna[name].keys() or rna[name]['type'] != parameter.type\n            size_changed = 'size' in rna[name].keys() and rna[name]['size'] != parameter.size\n\n            copy_name = name\n            if copy_map and name in copy_map:\n                copy_name = copy_map[name]\n\n            if copy_from and copy_name in copy_from.get_rna():\n                rna_prop = copy_from.get_rna()[copy_name]\n                parameter.default_value = rna_prop.get(\"default\")\n                parameter.type = rna_prop.get('type')\n                parameter.subtype = rna_prop.get('malt_subtype')\n                parameter.size = rna_prop.get('size')\n                parameter.filter = rna_prop.get('filter')\n                if self.parent:\n                    parameter.label = rna_prop.get('label')\n                parameter.enum_options = rna_prop.get('enum_options')\n                parameter.min = rna_prop.get('min')\n                parameter.max = rna_prop.get('max')\n            \n            if hasattr(parameter, 'label') and parameter.label:\n                rna[name]['label'] = parameter.label\n            else:\n                rna[name]['label'] = name.removeprefix('U_0_').replace('_0_','.').replace('_',' ').title()\n\n            if reset_to_defaults:\n                #TODO: Rename\n                type_changed = True\n\n            def to_basic_type(value):\n                try: return tuple(value)\n                except: return value\n            def equals(a, b):\n                return to_basic_type(a) == to_basic_type(b)\n\n            def resize():\n                if parameter.size == 1:\n                       self[name] = self[name][0]\n                else:\n                    if rna[name]['size'] > parameter.size:\n                        self[name] = self[name][:parameter.size]\n                    else:\n                        first = self[name]\n                        try: first = list(first)\n                        except: first = [first]\n                        second = list(parameter.default_value)\n                        self[name] = first + second[rna[name]['size']:]\n\n            if parameter.type in (Type.INT, Type.FLOAT):\n                if type_changed or equals(rna[name]['default'], self[name]):\n                    if copy_values and copy_name in copy_from.keys():\n                        self[name] = copy_from[copy_name]\n                    else:\n                        self[name] = parameter.default_value   \n                elif size_changed:\n                    resize()\n            \n            if parameter.type == Type.STRING:\n                if type_changed or equals(rna[name]['default'], self[name]):\n                    if copy_values and copy_name in copy_from.keys():\n                        self[name] = copy_from[copy_name]\n                    else:\n                        self[name] = parameter.default_value  \n\n            if parameter.type == Type.BOOL:\n                if name not in self.bools:\n                    self.bools.add().name = name\n                if type_changed or equals(rna[name]['default'], self.bools[name].boolean):\n                    if copy_values and copy_name in copy_from.bools.keys():\n                        self.bools[name].boolean = copy_from.bools[copy_name].boolean\n                    else:\n                        self.bools[name].boolean = parameter.default_value\n                elif size_changed:\n                    resize()\n            \n            if parameter.type == Type.ENUM:\n                if name not in self.enums:\n                    self.enums.add().name = name\n                rna[name]['enum_options'] = parameter.enum_options\n                self.enums[name].enum_options = ','.join(parameter.enum_options)\n                if type_changed or equals(rna[name]['default'], self.enums[name].enum):\n                    if copy_values and copy_name in copy_from.enums.keys():\n                        self.enums[name].enum = copy_from.enums[copy_name].enum\n                    else:\n                        self.enums[name].enum = parameter.default_value\n            \n            if parameter.type == Type.TEXTURE:\n                if name not in self.textures:\n                    self.textures.add().name = name\n                if type_changed or self.textures[name] == rna[name]['default']:\n                    if copy_values and copy_name in copy_from.textures.keys():\n                        self.textures[name].texture = copy_from.textures[copy_name].texture\n                    elif isinstance(parameter.default_value, bpy.types.Image):\n                        self.textures.texture = parameter.default_value\n\n            if parameter.type == Type.GRADIENT:\n                if name not in self.gradients:\n                    self.gradients.add().name = name\n                    _name = f\"{self.id_data.name} - {rna[name]['label'].replace('.',' - ')}\"\n                    self.gradients[name].add_or_duplicate(name=_name)\n                    # Load gradient from material nodes (backward compatibility)\n                    if isinstance(self.id_data, bpy.types.Material):\n                        material = self.id_data\n                        if material.use_nodes and name in material.node_tree.nodes:\n                            old = material.node_tree.nodes[name].color_ramp\n                            new = self.gradients[name].texture.color_ramp\n                            MaltTextures.copy_color_ramp(old, new)\n                    if copy_values and copy_name in copy_from.gradients.keys():\n                        parent = copy_from.gradients[copy_name].texture.color_ramp\n                        child = self.gradients[name].texture.color_ramp\n                        MaltTextures.copy_color_ramp(parent, child)\n                    elif isinstance(parameter.default_value, bpy.types.Texture):\n                        self.gradients.texture = parameter.default_value\n\n            if parameter.type == Type.MATERIAL:\n                if name not in self.materials:\n                    self.materials.add().name = name\n                \n                self.materials[name].extension = parameter.extension\n                self.materials[name].type = parameter.graph_type\n                shader_path = parameter.default_value\n                \n                if type_changed and copy_values and copy_name in copy_from.materials.keys():\n                    self.materials[name].material = copy_from.materials[copy_name].material\n                elif shader_path and shader_path != '':\n                    if isinstance(shader_path, str):\n                        material_name = name + ' : ' + os.path.basename(shader_path)\n                        if material_name not in bpy.data.materials:\n                            bpy.data.materials.new(material_name)\n                            material = bpy.data.materials[material_name]\n                            material.malt.shader_source = shader_path\n                        material = self.materials[name].material\n                        if type_changed or (material and rna[name]['default'] == material.malt.shader_source):\n                            self.materials[name].material = bpy.data.materials[material_name]\n                    \n                    if isinstance(shader_path, tuple):\n                        blend_path, material_name = parameter.default_value\n                        blend_path += '.blend'\n                        if material_name not in bpy.data.materials:\n                            internal_dir = 'Material'\n                            bpy.ops.wm.append(\n                                filepath=os.path.join(blend_path, internal_dir, material_name),\n                                directory=os.path.join(blend_path, internal_dir),\n                                filename=material_name\n                            )\n                            if bpy.data.materials[material_name].malt.shader_nodes:\n                                bpy.data.materials[material_name].malt.shader_nodes.reload_nodes()\n                        if type_changed:\n                            self.materials[name].material = bpy.data.materials[material_name]\n                    \n            if parameter.type == Type.GRAPH:\n                if name not in self.graphs:\n                    self.graphs.add().name = name\n                self.graphs[name].type = parameter.graph_type\n\n                if type_changed and copy_values and copy_name in copy_from.graphs.keys():\n                    self.graphs[name].graph = copy_from.graphs[copy_name].graph\n                elif parameter.default_value and isinstance(parameter.default_value, tuple):\n                    blend_path, tree_name = parameter.default_value\n                    blend_path += '.blend'\n                    if tree_name not in bpy.data.node_groups:\n                        internal_dir = 'NodeTree'\n                        bpy.ops.wm.append(\n                            filepath=os.path.join(blend_path, internal_dir, tree_name),\n                            directory=os.path.join(blend_path, internal_dir),\n                            filename=tree_name\n                        )\n                        bpy.data.node_groups[tree_name].reload_nodes()\n                    if type_changed:\n                        self.graphs[name].graph = bpy.data.node_groups[tree_name]\n                    if self.graphs[name].graph is not None:\n                        assert(parameter.graph_type == self.graphs[name].graph.graph_type)\n\n            if name not in self.override_from_parents:\n                self.override_from_parents.add().name = name\n            if name not in self.show_in_children:\n                prop = self.show_in_children.add()\n                prop.name = name\n                prop.boolean = True\n\n            rna[name]['active'] = True\n            rna[name][\"default\"] = parameter.default_value\n            rna[name]['type'] = parameter.type\n            rna[name]['malt_subtype'] = parameter.subtype\n            rna[name]['size'] = parameter.size\n            rna[name]['filter'] = parameter.filter\n\n            rna[name]['min'] = getattr(parameter, 'min', None)\n            rna[name]['max'] = getattr(parameter, 'max', None)\n            \n\n        #TODO: We should purge non active properties (specially textures)\n        # at some point, likely on file save or load \n        # so we don't lose them immediately when changing shaders/pipelines\n        if replace_parameters:\n            for key, value in rna.items():\n                if '@' not in key:\n                    rna[key]['active'] = False\n        \n        for name, parameter in parameters.items():\n            if skip_private and (name.isupper() or name.startswith('_')):\n                # We treat underscored and all caps uniforms as \"private\"\n                continue\n            setup_parameter(name, parameter)\n\n        for key, value in rna.items():\n            if '@' in key and key not in parameters.keys():\n                main_name = key.split(' @ ')[0]\n                rna[key]['active'] = main_name in rna.keys() and rna[main_name]['active'] and rna[key]['active']\n                if rna[key]['active']:\n                    if rna[key]['type'] != rna[main_name]['type'] or rna[key]['size'] != rna[main_name]['size']:\n                        parameter = Parameter(rna[main_name]['default'], rna[main_name]['type'],\n                            rna[main_name]['size'], rna[main_name]['filter'], rna[main_name]['malt_subtype'])\n                        setup_parameter(key, parameter)\n        \n        for key, value in rna.items():\n            rna_prop = rna[key]\n            if rna_prop['active'] == False:\n                continue\n            if rna_prop['type'] not in (Type.FLOAT, Type.INT) or isinstance(rna_prop['default'], str):\n                continue\n            #Default to color since it's the most common use case\n            malt_subtype = rna_prop.get('malt_subtype')\n            if rna_prop['type'] == Type.FLOAT and rna_prop['size'] >= 3 and (malt_subtype is None or malt_subtype == 'Color'):\n                rna_prop['subtype'] = 'COLOR'\n                rna_prop['soft_min'] = 0.0\n                rna_prop['soft_max'] = 1.0\n            else:\n                rna_prop['subtype'] = 'NONE'\n\n            ui_properties = {}\n            for ui_key in ('default', 'subtype', 'min', 'max', 'soft_min', 'soft_max'):\n                if ui_key in rna_prop and rna_prop[ui_key] is not None:\n                    ui_properties[ui_key] = rna_prop[ui_key]\n            \n            ui = self.id_properties_ui(key)\n            ui.clear()\n            ui.update(**ui_properties)\n\n        # Force a depsgraph update. \n        # Otherwise these won't be available inside scene_eval\n        self.id_data.update_tag()\n        for screen in bpy.data.screens:\n            for area in screen.areas:\n                area.tag_redraw()\n    \n    def rename_property(self, old_name, new_name):\n        rna = self.get_rna()\n        rna[new_name] = rna.pop(old_name)\n        type = rna[new_name]['type']\n        self.override_from_parents[old_name].name = new_name\n        if old_name in self.show_in_children.keys():\n            self.show_in_children[old_name].name = new_name\n        if type in (Type.FLOAT, Type.INT, Type.STRING):\n            self[new_name] = self.pop(old_name)\n        elif type == Type.BOOL:\n            self.bools[old_name].name = new_name\n        elif type == Type.ENUM:\n            self.enums[old_name].name = new_name\n        elif type == Type.TEXTURE:\n            self.textures[old_name].name = new_name\n        elif type == Type.GRADIENT:\n            self.gradients[old_name].name = new_name\n        elif type == Type.MATERIAL:\n            self.materials[old_name].name = new_name\n        elif type == Type.GRAPH:\n            self.graphs[old_name].name = new_name\n    \n    def remove_property(self, name):\n        rna = self.get_rna()\n        rna_prop = rna[name]\n        type = rna_prop['type']\n        rna.pop(name)\n        def remove(collection, key):\n            collection.remove(collection.find(key))\n        remove(self.override_from_parents, name)\n        remove(self.show_in_children, name)\n        if type in (Type.FLOAT, Type.INT, Type.STRING):\n            self.pop(name)\n        elif type == Type.BOOL:\n            remove(self.bools, name)\n        elif type == Type.ENUM:\n            remove(self.enums, name)\n        elif type == Type.TEXTURE:\n            remove(self.textures, name)\n        elif type == Type.GRADIENT:\n            remove(self.gradients, name)\n        elif type == Type.MATERIAL:\n            remove(self.materials, name)\n        elif type == Type.GRAPH:\n            remove(self.graphs, name)\n\n    def add_override(self, property_name, override_name):\n        main_prop = self.get_rna()[property_name]\n        new_name = property_name + ' @ ' + override_name\n        property = {}\n        parameter = None\n        if main_prop['type'] == Type.MATERIAL:\n            parameter =  MaterialParameter(main_prop['default'], \n            self.materials[property_name].extension, self.materials[property_name].type)\n        else:\n            parameter = Parameter(main_prop['default'], main_prop['type'], main_prop['size'],\n                main_prop['filter'], main_prop['malt_subtype'])\n            parameter.default_value = main_prop.get(\"default\")\n            parameter.type = main_prop.get('type')\n        parameter.subtype = main_prop.get('malt_subtype')\n        parameter.size = main_prop.get('size')\n        parameter.filter = main_prop.get('filter')\n        parameter.label = main_prop.get('label') + ' @ ' + override_name\n        parameter.enum_options = main_prop.get('enum_options')\n        parameter.min = main_prop.get('min')\n        parameter.max = main_prop.get('max')\n        property[new_name] = parameter\n        self.setup(property, replace_parameters= False)\n    \n    def remove_override(self, property):\n        rna = self.get_rna()\n        if property in rna:\n            rna[property]['active'] = False\n            self.id_data.update_tag()\n    \n    def handle_duplication(self):\n        for gradient in self.gradients.values():\n            gradient.texture = gradient.texture.copy()\n\n    def get_parameters(self, overrides, proxys):\n        if '_MALT_' not in self.keys():\n            return {}\n        rna = self.get_rna()\n        parameters = {}\n        for key in rna.keys():\n            if '@' in key:\n                continue\n            if rna[key]['active'] == False:\n                continue\n            parameters[key] = self.get_parameter(key, overrides, proxys)\n        return parameters\n    \n    def get_parameter(self, key, overrides, proxys, retrieve_blender_type=False, rna_copy={}):\n        if self.parent and self.override_from_parents[key].boolean == False:\n            try:\n                return self.parent.malt_parameters.get_parameter(key, overrides, proxys, retrieve_blender_type, rna_copy)\n            except:\n                pass\n\n        rna = self.get_rna()\n        rna_copy.update(rna[key])\n        for override in reversed(overrides):\n            override_key = key + ' @ ' +  override\n            if override_key in rna.keys():\n                if rna[override_key]['active']:\n                    key = override_key\n        if rna[key]['active'] == False:\n            raise Exception()\n\n        if rna[key]['type'] in (Type.INT, Type.FLOAT):\n            try:\n                return tuple(self[key])\n            except:\n                return self[key]\n        elif rna[key]['type'] == Type.STRING:\n            return self[key]\n        elif rna[key]['type'] == Type.BOOL:\n            return bool(self.bools[key].boolean)\n        elif rna[key]['type'] == Type.ENUM:\n            return self.enums[key].enum_options.split(',').index(self.enums[key].enum)\n        elif rna[key]['type'] == Type.TEXTURE:\n            texture = self.textures[key].texture\n            if retrieve_blender_type:\n                return texture\n            if texture:\n                texture_key = ('texture', texture.name_full)\n                if texture_key not in proxys.keys():\n                    if proxy := MaltTextures.get_texture(texture):\n                        proxys[texture_key] = proxy\n                    else:\n                        return None\n                return proxys[texture_key]\n            else:\n                return None\n        elif rna[key]['type'] == Type.GRADIENT:\n            texture = self.gradients[key].texture\n            if retrieve_blender_type:\n                return texture\n            if texture:\n                gradient_key = ('gradient', texture.name_full)\n                if gradient_key not in proxys.keys():\n                    proxys[gradient_key] = MaltTextures.get_gradient(texture)\n                return proxys[gradient_key]\n            else:\n                return None\n        elif rna[key]['type'] == Type.MATERIAL:\n            material = self.materials[key].material\n            extension = self.materials[key].extension\n            if retrieve_blender_type:\n                return material\n            if material:\n                material_key = ('material', material.name_full)\n                if material_key not in proxys.keys():\n                    path = material.malt.get_source_path()\n                    shader_parameters = material.malt.parameters.get_parameters(overrides, proxys)\n                    material_parameters = material.malt_parameters.get_parameters(overrides, proxys)\n                    from Bridge.Proxys import MaterialProxy\n                    proxys[material_key] = MaterialProxy(path, shader_parameters, material_parameters)\n                return proxys[material_key]\n            else:\n                return None\n        elif rna[key]['type'] == Type.GRAPH:\n            graph = self.graphs[key].graph\n            type = self.graphs[key].type\n            if retrieve_blender_type:\n                return graph\n            if graph and graph.is_active():\n                result = {}\n                result['source'] = graph.get_generated_source()\n                result['parameters'] = {}\n                for node in graph.nodes:\n                    if hasattr(node, 'get_source_name'):\n                        result['parameters'][node.get_source_name()] = node.get_parameters(overrides, proxys)\n                return result\n            else:\n                return None\n\n    \n    def draw_ui(self, layout, filter=None):\n        layout.use_property_decorate = False\n        #layout.prop(self, \"parent\")\n\n        if '_MALT_' not in self.keys():\n            return #Can't modify ID classes from here\n        rna = self.get_rna()\n\n        namespace_stack = [(None, layout)]\n\n        def get_label(key):\n            label = rna[key].get('label')\n            if self.parent:\n                parent_prop = self.parent.malt_parameters.get_rna().get(key)\n                if parent_prop:\n                    label = parent_prop.get('label', label)\n            if label is None:\n                label = key.replace('_0_','.').replace('_',' ')\n            return label\n        \n        # Most drivers sort the uniforms in alphabetical order anyway, \n        # so there's no point in tracking the actual index since it doesn't follow\n        # the declaration order\n        import re\n        def natural_sort_labels(k):\n            label = get_label(k)\n            n_split = re.split('([0-9]+)', label)\n            ns_split = []\n            for n in n_split:\n                ns_split.extend(n.split('.'))\n            result = []\n            for e in ns_split:\n                if e.isdigit():\n                    result.append('z')\n                    result.append(int(e))\n                else:\n                    result.append(e)\n                    result.append(0)\n            return result\n        keys = sorted(rna.keys(), key=natural_sort_labels)\n        # Put Settings first. Kind of hacky, but ¯\\_(ツ)_/¯\n        def settings_first(k):\n            return not rna[k].get('label', k).startswith('Settings.')\n        keys.sort(key=settings_first)\n        \n        for key in keys:\n            if rna[key]['active'] == False:\n                continue\n\n            if filter and rna[key]['filter'] and rna[key]['filter'] != filter:\n                continue\n\n            labels = get_label(key).split('.')\n            label = labels[-1]\n            \n            #defer layout (box) creation until a property is actually drawn\n            def get_layout():\n                nonlocal namespace_stack\n                layout = None\n                if len(labels) == 1:\n                    namespace_stack = namespace_stack[:1]\n                    layout = namespace_stack[0][1]\n                else:\n                    for i in range(0, len(labels) - 1):\n                        label = labels[i]\n                        stack_i = i+1\n                        if len(namespace_stack) > stack_i and namespace_stack[stack_i][0] != label:\n                            namespace_stack = namespace_stack[:stack_i]\n                        if len(namespace_stack) < stack_i+1:\n                            box = namespace_stack[stack_i - 1][1].box()\n                            box.label(text=label + \" :\")\n                            namespace_stack.append((label, box))\n                        layout = namespace_stack[stack_i][1]\n                return layout.column()\n\n            def draw_callback(layout, property_group):\n                is_self = self.as_pointer() == property_group.as_pointer()\n                if self.parent and (is_self == False or self.override_from_parents[key].boolean == True):\n                    layout.prop(self.override_from_parents[key], 'boolean', text='')\n                if is_self == False:\n                    layout.active = False\n            \n            self.draw_parameter(get_layout, key, label, draw_callback=draw_callback)\n\n\n    def draw_parameter(self, layout, key, label, draw_callback=None, is_node_socket=False, drawn_from_child=False):\n        if self.parent and self.override_from_parents[key].boolean == False:\n            if self.parent.malt_parameters.draw_parameter(layout, key, label, draw_callback, is_node_socket, True):\n                return True\n\n        rna = self.get_rna()\n        \n        if key not in rna.keys():\n            return False\n        \n        if drawn_from_child and key in self.show_in_children.keys() and self.show_in_children[key].boolean == False:\n            return True\n\n        if callable(layout):\n            layout = layout()\n\n        def make_row(label_only = False):\n            result = layout\n            nonlocal label\n            row = layout.row(align=True)\n            result = row.split()\n            is_override = '@' in key\n            \n            if is_override:\n                if label is None:\n                    label = key\n                label = '⇲ '+label.split(' @ ')[-1]\n            \n            if label is not None:\n                if label_only == False and is_node_socket == False:          \n                    result = result.split(factor=0.66)\n                    result.alignment = 'RIGHT'\n                result.label(text=label)\n            \n            if is_override:\n                row.operator('wm.malt_callback', text='', icon='X').callback.set(\n                    lambda : self.remove_override(key), 'Remove Override')\n            else:\n                row.operator('wm.malt_new_override', text='', icon='DECORATE_OVERRIDE').callback.set(\n                    lambda override_name: self.add_override(key, override_name))\n            \n            if draw_callback:\n                draw_callback(row, self)\n            \n            return result\n\n        if rna[key]['type'] in (Type.INT, Type.FLOAT, Type.STRING):\n            #TODO: add subtype toggle\n            slider = rna[key]['malt_subtype'] == 'Slider'\n            make_row().prop(self, '[\"{}\"]'.format(key), text='', slider=slider)\n        elif rna[key]['type'] == Type.BOOL:\n            make_row().prop(self.bools[key], 'boolean', text='')\n        elif rna[key]['type'] == Type.ENUM:\n            make_row().prop(self.enums[key], 'enum', text='')\n        elif rna[key]['type'] == Type.TEXTURE:\n            make_row(True)\n            row = layout.row(align=True)\n            if self.textures[key].texture:\n                row = row.split(factor=0.8, align=True)\n            row.template_ID(self.textures[key], \"texture\", new=\"image.new\", open=\"image.open\")\n            if self.textures[key].texture:\n                row.prop(self.textures[key].texture.colorspace_settings, 'name', text='')\n        elif rna[key]['type'] == Type.GRADIENT:\n            make_row(True)\n            column = layout.column()\n            row = column.row(align=True)\n            row.template_ID(self.gradients[key], \"texture\")\n            if self.gradients[key].texture:\n                row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set(\n                    self.gradients[key].add_or_duplicate, 'Duplicate')\n                column.template_color_ramp(self.gradients[key].texture, 'color_ramp')\n            else:\n                row.operator('wm.malt_callback', text='New', icon='ADD').callback.set(\n                    self.gradients[key].add_or_duplicate, 'New')\n        elif rna[key]['type'] == Type.MATERIAL:\n            make_row(True)\n            row = layout.row(align=True)\n            row.template_ID(self.materials[key], \"material\")\n            if self.materials[key].material:\n                extension = self.materials[key].extension\n                row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set(\n                    self.materials[key].add_or_duplicate, 'Duplicate')\n                material = self.materials[key].material\n                material.malt.draw_ui(layout.box(), extension, material.malt_parameters)\n            else:\n                row.operator('wm.malt_callback', text='New', icon='ADD').callback.set(\n                    self.materials[key].add_or_duplicate, 'New')\n        elif rna[key]['type'] == Type.GRAPH:\n            make_row(True)\n            row = layout.row(align=True)\n            row.template_ID(self.graphs[key], \"graph\")\n            if self.graphs[key].graph:\n                row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set(\n                    self.graphs[key].add_or_duplicate, 'Duplicate')\n                from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree\n                node = self.id_data if isinstance(self.id_data, bpy.types.Node) else None\n                row.operator('wm.malt_callback', text = '', icon = 'GREASEPENCIL').callback.set(\n                    lambda: set_node_tree(bpy.context, self.graphs[key].graph, node)\n                )\n            else:\n                row.operator('wm.malt_callback', text='New', icon='ADD').callback.set(\n                    self.graphs[key].add_or_duplicate, 'New')\n        else:\n            make_row(True)\n            \n        return True\n\n\nfrom . import MaltUtils\n\nclass OT_MaltNewOverride(bpy.types.Operator):\n    bl_idname = \"wm.malt_new_override\"\n    bl_label = \"Malt Add A Property Override\"\n    bl_options = {'INTERNAL'}\n\n    def get_override_enums(self, context):\n        overrides = context.scene.world.malt.overrides.split(',')\n        result = []\n        for i, override in enumerate(overrides):\n            result.append((override, override, '', i))\n        return result\n    override : bpy.props.EnumProperty(items=get_override_enums,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    callback : bpy.props.PointerProperty(type=MaltUtils.MaltCallback,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    \n    def invoke(self, context, event):\n        return context.window_manager.invoke_props_dialog(self)\n    \n    def draw(self, context):\n        layout = self.layout\n        layout.prop(self, \"override\")\n    \n    def execute(self, context):\n        self.callback.call(self.override)\n        return {'FINISHED'}\n\n\nclass MALT_PT_Base(bpy.types.Panel):\n    bl_space_type = 'PROPERTIES'\n    bl_region_type = 'WINDOW'\n    bl_context = \"disabled\"\n\n    bl_label = \"Malt Settings\"\n    COMPAT_ENGINES = {'MALT'}\n\n    @classmethod\n    def get_malt_property_owner(cls, context):\n        return None\n    \n    @classmethod\n    def get_parameter_type(cls):\n        return None\n\n    @classmethod\n    def poll(cls, context):\n        if context.scene.render.engine == 'MALT' and cls.get_malt_property_owner(context):\n            from BlenderMalt.MaltPipeline import get_bridge\n            bridge = get_bridge()\n            parameter_type = cls.get_parameter_type()\n            if bridge is None or parameter_type is None or len(getattr(bridge.parameters, parameter_type)) > 0:\n                return True\n        return False\n    \n    def draw(self, context):\n        owner = self.__class__.get_malt_property_owner(context)\n        if owner:\n            self.layout.active = owner.library is None #Only local data can be edited\n            owner.malt_parameters.draw_ui(self.layout)\n\n\nclass MALT_PT_Scene(MALT_PT_Base):\n    bl_context = \"scene\"\n    @classmethod\n    def get_parameter_type(cls):\n        return 'scene'\n    @classmethod\n    def get_malt_property_owner(cls, context):\n        return context.scene\n\nclass MALT_PT_World(MALT_PT_Base):\n    bl_context = \"world\"\n    @classmethod\n    def get_parameter_type(cls):\n        return 'world'\n    @classmethod\n    def get_malt_property_owner(cls, context):\n        return context.world\n\nclass MALT_PT_Camera(MALT_PT_Base):\n    bl_context = \"data\"\n    @classmethod\n    def get_parameter_type(cls):\n        return 'camera'\n    @classmethod\n    def get_malt_property_owner(cls, context):\n        return context.camera\n\nclass MALT_PT_Object(MALT_PT_Base):\n    bl_context = \"object\"\n    @classmethod\n    def get_parameter_type(cls):\n        return 'object'\n    @classmethod\n    def get_malt_property_owner(cls, context):\n        return context.object\n\n# In MaltMaterials\n'''\nclass MALT_PT_Material(MALT_PT_Base):\n    bl_context = \"material\"\n    @classmethod\n    def get_malt_property_owner(cls, context):\n        if context.material:\n            return context.material\n'''\n\nclass MALT_PT_Mesh(MALT_PT_Base):\n    bl_context = \"data\"\n    @classmethod\n    def get_parameter_type(cls):\n        return 'mesh'\n    @classmethod\n    def get_malt_property_owner(cls, context):\n        if context.mesh:\n            return context.mesh\n        if context.curve:\n            return context.curve\n        if context.meta_ball:\n            return context.meta_ball\n        if context.object and context.object.type in ('SURFACE', 'FONT'):\n            return context.object.data\n\nclass MALT_PT_Light(MALT_PT_Base):\n    bl_context = \"data\"\n    @classmethod\n    def get_malt_property_owner(cls, context):\n        return context.light\n\n    def draw(self, context):\n        layout = self.layout\n        owner = self.__class__.get_malt_property_owner(context)\n        if owner and owner.type != 'AREA':\n            '''\n            layout.prop(owner, 'color')\n            if owner.type == 'POINT':\n                layout.prop(owner, 'shadow_soft_size', text='Radius')\n            elif owner.type == 'SPOT':\n                layout.prop(owner, 'cutoff_distance', text='Distance')\n                layout.prop(owner, 'spot_size', text='Spot Angle')\n                layout.prop(owner, 'spot_blend', text='Spot Blend')\n            '''\n            owner.malt.draw_ui(layout)\n            owner.malt_parameters.draw_ui(layout)\n\nclasses = (\n    MaltBoolPropertyWrapper,\n    MaltEnumPropertyWrapper,\n    MaltGradientPropertyWrapper,\n    MaltTexturePropertyWrapper,\n    MaltMaterialPropertyWrapper,\n    MaltGraphPropertyWrapper,\n    MaltPropertyGroup,\n    OT_MaltNewOverride,\n    MALT_PT_Base,\n    MALT_PT_Scene,\n    MALT_PT_World,\n    MALT_PT_Camera,\n    MALT_PT_Object,\n    MALT_PT_Mesh,\n    MALT_PT_Light,\n)\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n\n    bpy.types.Scene.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.World.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.Camera.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.Object.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.Material.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.Mesh.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.Curve.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.MetaBall.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n    bpy.types.Light.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup,\n        options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})\n\n\ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n\n    del bpy.types.Scene.malt_parameters\n    del bpy.types.World.malt_parameters\n    del bpy.types.Camera.malt_parameters\n    del bpy.types.Object.malt_parameters\n    del bpy.types.Material.malt_parameters\n    del bpy.types.Mesh.malt_parameters\n    del bpy.types.Curve.malt_parameters\n    del bpy.types.Light.malt_parameters\n"
  },
  {
    "path": "BlenderMalt/MaltRenderEngine.py",
    "content": "import ctypes, time, platform\nimport xxhash\nimport bpy\nimport gpu\nfrom mathutils import Vector, Matrix, Quaternion\nfrom Malt import Scene\nfrom Malt.Pipeline import SHADER_DIR\nfrom Malt.GL import GL\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.Shader import Shader, shader_preprocessor\nfrom Malt.GL.Mesh import Mesh\nfrom . import MaltPipeline, MaltMeshes, MaltMaterial, CBlenderMalt\n\nCAPTURE = False\n\nWINM = None\nif platform.system() == 'Windows':\n    WINM = ctypes.WinDLL('winmm')\n\ndef high_res_sleep(seconds):\n    if WINM:\n        WINM.timeBeginPeriod(1)\n        time.sleep(seconds)\n        WINM.timeEndPeriod(1)\n    else:\n        time.sleep(seconds)\n\n\nclass MaltRenderEngine(bpy.types.RenderEngine):\n    bl_idname = \"MALT\"\n    bl_label = \"Malt\"\n    bl_use_preview = False\n    bl_use_postprocess = True\n    bl_use_shading_nodes_custom = False\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.display_draw = None\n        self.scene = Scene.Scene()\n        self.view_matrix = None\n        self.request_new_frame = True\n        self.request_scene_update = True\n        self.bridge = MaltPipeline.get_bridge()\n        self.bridge_id = self.bridge.get_viewport_id() if self.bridge else None\n        self.last_frame_time = 0\n        self.render_backend = gpu.platform.backend_type_get()\n\n    def __del__(self):\n        try:\n            self.bridge.free_viewport_id(self.bridge_id)\n            self.bridge = None\n        except:\n            # Sometimes Blender seems to call the destructor on un-initialized instances (???)\n            pass\n        try:\n            super().__del__()\n        except AttributeError:\n            # Quiet __del__ not being defined on bpy.types.RenderEngine\n            pass\n\n    def get_scene(self, context, depsgraph, request_scene_update, overrides):\n        if request_scene_update == True:\n            scene = Scene.Scene()\n            self.scene = scene\n        scene = self.scene\n        \n        if hasattr(scene, 'proxys') == False:\n            scene.proxys = {}\n\n        scene.parameters = depsgraph.scene_eval.malt_parameters.get_parameters(overrides, scene.proxys)\n        scene.world_parameters = depsgraph.scene_eval.world.malt_parameters.get_parameters(overrides, scene.proxys)\n\n        override_material = scene.world_parameters['Material.Override']\n        default_material = scene.world_parameters['Material.Default']\n\n        scene.frame = depsgraph.scene_eval.frame_current\n        r = depsgraph.scene_eval.render\n        fps = r.fps / r.fps_base\n        remap = r.frame_map_new / r.frame_map_old\n        scene.time = (scene.frame / fps) * remap\n        \n        def flatten_matrix(matrix):\n            return [e for v in matrix.transposed() for e in v]\n        \n        #Camera\n        if depsgraph.mode == 'VIEWPORT':\n            view_3d = context.region_data \n            camera_matrix = flatten_matrix(view_3d.view_matrix)\n            projection_matrix = flatten_matrix(view_3d.window_matrix)\n            if view_3d.perspective_matrix != self.view_matrix:\n                self.view_matrix = view_3d.perspective_matrix.copy()\n                self.request_new_frame = True\n            scene.camera = Scene.Camera(camera_matrix, projection_matrix)\n        else:\n            camera = depsgraph.scene_eval.camera\n            camera_matrix = flatten_matrix(camera.matrix_world.inverted())\n            projection_matrix = flatten_matrix(\n                camera.calc_matrix_camera( depsgraph, \n                    x=depsgraph.scene_eval.render.resolution_x, \n                    y=depsgraph.scene_eval.render.resolution_y\n            ))\n            scene.camera = Scene.Camera(camera_matrix, projection_matrix)\n        \n        if request_scene_update == False:\n            return scene\n        \n        meshes = {}\n\n        #Objects\n        def add_object(obj, matrix, id):\n            if obj.type in ('MESH','CURVE','SURFACE','META', 'FONT'):\n                name = MaltMeshes.get_mesh_name(obj)\n                if depsgraph.mode == 'RENDER':\n                    name = '___F12___' + name\n                \n                if name not in meshes:\n                    # (Uses obj.original) Malt Parameters are not present in the evaluated mesh\n                    parameters = obj.original.data.malt_parameters.get_parameters(overrides, scene.proxys)\n                    malt_mesh = None\n                    \n                    if depsgraph.mode == 'VIEWPORT':\n                        malt_mesh = MaltMeshes.get_mesh(obj)\n                    else: #always load the mesh for final renders\n                        malt_mesh = MaltMeshes.load_mesh(obj, name)\n                    \n                    if malt_mesh:\n                        meshes[name] = [Scene.Mesh(submesh, parameters) for submesh in malt_mesh]\n                        for i, mesh in enumerate(meshes[name]):\n                            scene.proxys[('mesh',name,i)] = mesh.mesh\n                    else:\n                        meshes[name] = None\n\n                mesh = meshes[name]\n                if mesh is None:\n                    return\n                \n                scale = matrix.to_scale()\n                mirror_scale = scale[0]*scale[1]*scale[2] < 0.0\n                matrix = flatten_matrix(matrix)\n\n                obj_parameters = obj.malt_parameters.get_parameters(overrides, scene.proxys)\n                obj_parameters['ID'] = id\n\n                tags = set(collection.name for collection in obj.original.users_collection)\n                \n                if len(obj.material_slots) > 0:\n                    for i, slot in enumerate(obj.material_slots):\n                        material = default_material\n                        if slot.material and slot.material.malt.get_source_path() != '':\n                            material_name = slot.material.name_full\n                            material_key = ('material',material_name)\n                            if material_key not in scene.proxys.keys():\n                                path = slot.material.malt.get_source_path()\n                                shader_parameters = slot.material.malt.parameters.get_parameters(overrides, scene.proxys)\n                                material_parameters = slot.material.malt_parameters.get_parameters(overrides, scene.proxys)\n                                from Bridge.Proxys import MaterialProxy\n                                scene.proxys[material_key]  = MaterialProxy(path, shader_parameters, material_parameters)\n                            material = scene.proxys[material_key]\n                        if override_material: material = override_material\n                        result = Scene.Object(matrix, mesh[i], material, obj_parameters, mirror_scale, tags)\n                        scene.objects.append(result)\n                else:\n                    material = default_material\n                    if override_material: material = override_material\n                    result = Scene.Object(matrix, mesh[0], material, obj_parameters, mirror_scale, tags)\n                    scene.objects.append(result)\n           \n            elif obj.type == 'LIGHT':\n                if obj.data.type == 'AREA':\n                    return #Not supported\n\n                malt_light = obj.data.malt\n\n                light = Scene.Light()\n                light.color = tuple(obj.data.color * malt_light.strength)\n                light.position = tuple(matrix.translation)\n                light.direction = tuple(matrix.to_quaternion() @ Vector((0.0,0.0,-1.0)))\n                if malt_light.override_global_settings:\n                    light.sun_max_distance = malt_light.max_distance\n                light.radius = malt_light.radius\n                light.spot_angle = malt_light.spot_angle\n                light.spot_blend = malt_light.spot_blend_angle\n                light.parameters = obj.data.malt_parameters.get_parameters(overrides, scene.proxys)\n\n                types = {\n                    'SUN' : 1,\n                    'POINT' : 2,\n                    'SPOT' : 3,\n                }\n                light.type = types[obj.data.type]\n\n                if light.type == types['SUN']:\n                    light.matrix = flatten_matrix(matrix.to_quaternion().to_matrix().to_4x4().inverted())\n                else:\n                    #Scaling too ????\n                    light.matrix = flatten_matrix(matrix.inverted())\n                \n                scene.lights.append(light)\n\n        is_f12 = depsgraph.mode == 'RENDER'\n\n        def visible_display(obj):\n            return obj.display_type in ('TEXTURED','SOLID') or obj.type == 'LIGHT'\n\n        for obj in depsgraph.objects:\n            if is_f12 or (visible_display(obj) and obj.visible_in_viewport_get(context.space_data)):\n                id = xxhash.xxh3_64_intdigest(obj.name_full.encode()) % (2**16)\n                add_object(obj, obj.matrix_world, id)\n\n        for instance in depsgraph.object_instances:\n            if instance.instance_object:\n                obj = instance.instance_object\n                parent = instance.parent\n                if is_f12 or (visible_display(obj) and visible_display(parent) and\n                parent.visible_in_viewport_get(context.space_data)):\n                    id = abs(instance.random_id) % (2**16)\n                    add_object(instance.instance_object, instance.matrix_world, id)\n        \n        return scene\n    \n    def get_AOVs(self, scene):\n        #TODO: Hardcoded for now\n        result = {}\n        try:\n            render_tree = scene.world.malt_parameters.graphs['Render'].graph\n            for io in render_tree.get_custom_io('Render'):\n                if io['io'] in ['out', 'inout'] and io['type'] == 'Texture':\n                    result[io['name']] = GL.GL_RGBA32F\n        except:\n            import traceback\n            traceback.print_exc()\n        return result        \n    \n    def update_render_passes(self, scene=None, renderlayer=None):\n        bridge = MaltPipeline.get_bridge(scene.world, True)\n        render_outputs = bridge.render_outputs\n        if 'COLOR' in render_outputs.keys():\n            self.register_pass(scene, renderlayer, \"Combined\", 4, \"RGBA\", 'COLOR')\n        if 'DEPTH' in render_outputs.keys():\n            self.register_pass(scene, renderlayer, \"Depth\", 1, \"R\", 'VALUE')\n        from itertools import chain\n        for output, format in chain(render_outputs.items(), self.get_AOVs(scene).items()):\n            if output not in ('COLOR', 'DEPTH'):\n                #TODO: 'COLOR' vs 'VECTOR' ???\n                self.register_pass(scene, renderlayer, output, 4, \"RGBA\", 'COLOR')\n\n    def render(self, depsgraph):\n        scene = depsgraph.scene_eval\n        scale = scene.render.resolution_percentage / 100.0\n\n        self.size_x = int(scene.render.resolution_x * scale)\n        self.size_y = int(scene.render.resolution_y * scale)\n        resolution = (self.size_x, self.size_y)\n\n        overrides = ['Final Render']\n\n        bridge = MaltPipeline.get_bridge(depsgraph.scene.world, True)\n        if self.bridge is not bridge:\n            self.bridge = bridge\n            self.bridge_id = self.bridge.get_viewport_id()\n        \n        MaltMaterial.track_shader_changes(force_update=True, async_compilation=False)\n\n        AOVs = self.get_AOVs(scene)\n        scene = self.get_scene(None, depsgraph, True, overrides)\n        self.bridge.render(0, resolution, scene, True, AOVs=AOVs)\n\n        buffers = None\n        finished = False\n\n        import time\n        while not finished:\n            buffers, finished, read_resolution = self.bridge.render_result(0)\n            time.sleep(0.1)\n            if finished: break\n        \n        size = self.size_x * self.size_y\n\n        from itertools import chain\n        for output in chain(self.bridge.render_outputs.keys(), AOVs.keys()):\n            if output not in ('COLOR', 'DEPTH'):\n                self.add_pass(output, 4, 'RGBA')\n        \n        result = self.begin_result(0, 0, self.size_x, self.size_y, layer=depsgraph.view_layer.name)\n        passes = result.layers[0].passes\n        \n        for key, value in passes.items():\n            buffer_name = key\n            if key == 'Combined': buffer_name = 'COLOR'\n            if key == 'Depth': buffer_name = 'DEPTH'\n            if buffer_name in buffers and hasattr(buffers[buffer_name], 'buffer'):\n                value.rect = buffers[buffer_name].as_np_array((size , value.channels))\n        \n        self.end_result(result)\n        # Delete the scene. Otherwise we get memory leaks.\n        # Blender never deletes RenderEngine instances ???\n        del self.scene\n\n    def view_update(self, context, depsgraph):\n        self.request_new_frame = True\n        self.request_scene_update = True\n\n        for update in depsgraph.updates:\n            if update.is_updated_geometry:\n                if isinstance(update.id, bpy.types.Object):\n                    MaltMeshes.unload_mesh(update.id)\n\n    def view_draw(self, context, depsgraph):\n        if self.bridge is not MaltPipeline.get_bridge():\n            #The Bridge has been reset\n            self.bridge = MaltPipeline.get_bridge()\n            self.bridge_id = self.bridge.get_viewport_id()\n            self.request_new_frame = True\n            self.request_scene_update = True\n        \n        global CAPTURE\n        if CAPTURE:\n            self.request_new_frame = True\n        \n        overrides = []\n        if context.space_data.shading.type == 'MATERIAL':\n            overrides.append('Preview')\n\n        scene = self.get_scene(context, depsgraph, self.request_scene_update, overrides)\n        viewport_resolution = context.region.width, context.region.height\n        resolution = viewport_resolution\n\n        resolution_scale = scene.world_parameters['Viewport.Resolution Scale']\n        mag_filter = GL.GL_LINEAR\n        if resolution_scale != 1.0:\n            w,h = resolution\n            resolution = round(w*resolution_scale), round(h*resolution_scale)\n            smooth_interpolation = scene.world_parameters['Viewport.Smooth Interpolation']\n            mag_filter = GL.GL_LINEAR if smooth_interpolation else GL.GL_NEAREST\n\n        if self.request_new_frame:\n            self.bridge.render(self.bridge_id, resolution, scene, self.request_scene_update, CAPTURE)\n            CAPTURE = False\n            self.request_new_frame = False\n            self.request_scene_update = False\n        \n        target_fps = context.preferences.addons['BlenderMalt'].preferences.render_fps_cap\n        if target_fps > 0:\n            delta_time = time.perf_counter() - self.last_frame_time\n            target_delta = 1.0 / target_fps\n            if delta_time < target_delta:\n                high_res_sleep(target_delta - delta_time)\n        \n        self.last_frame_time = time.perf_counter() \n\n        buffers, finished, read_resolution = self.bridge.render_result(self.bridge_id)\n        pixels = buffers['COLOR']\n\n        if not finished:\n            self.tag_redraw()\n        if pixels is None or resolution != read_resolution:\n            # Only render if resolution is the same as read_resolution.\n            # This avoids visual glitches when the viewport is resizing.\n            # The alternative would be locking when writing/reading the pixel buffer.\n            return\n        \n        for region in context.area.regions:\n            if region.type == 'UI':\n                region.tag_redraw()\n\n        global DISPLAY_DRAW\n        if self.render_backend == 'OPENGL':\n            fbo = GL.gl_buffer(GL.GL_INT, 1)\n            GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, fbo)\n\n            data_format = GL.GL_FLOAT\n            texture_format = GL.GL_RGBA32F\n            if self.bridge.viewport_bit_depth == 8:\n                data_format = GL.GL_UNSIGNED_BYTE\n                texture_format = GL.GL_RGBA8\n                if GL.glGetInternalformativ(GL.GL_TEXTURE_2D, texture_format, GL.GL_READ_PIXELS, 1) != GL.GL_ZERO:\n                    data_format = GL.glGetInternalformativ(GL.GL_TEXTURE_2D, texture_format, GL.GL_TEXTURE_IMAGE_TYPE, 1)\n            elif self.bridge.viewport_bit_depth == 16:\n                data_format = GL.GL_HALF_FLOAT\n                texture_format = GL.GL_RGBA16F\n            \n            try:\n                render_texture = Texture(resolution, texture_format, data_format, pixels.buffer(),\n                    mag_filter=mag_filter, pixel_format=GL.GL_RGBA)\n            except:\n                # Fallback to unsigned byte, just in case (matches Server behavior)\n                render_texture = Texture(resolution, GL.GL_RGBA8, GL.GL_UNSIGNED_BYTE, pixels.buffer(),\n                    mag_filter=mag_filter)\n            \n            if DISPLAY_DRAW is None:\n                DISPLAY_DRAW = DisplayDrawGL()\n            DISPLAY_DRAW.draw(fbo, render_texture)\n        else:\n            import gpu\n            data_size = len(pixels)\n            w,h = resolution\n            if self.bridge.viewport_bit_depth == 8:\n                data_size = data_size // 4\n                h = h // 4\n            elif self.bridge.viewport_bit_depth == 16:\n                data_size = data_size // 2\n                h = h // 2\n            data_format = 'FLOAT' #Pretend we are uploading float data, since it's the only supported format.\n            texture_format = 'RGBA32F'\n            data_as_float = (ctypes.c_float * data_size).from_address(pixels._buffer.data)\n            buffer = gpu.types.Buffer(data_format, data_size, data_as_float)\n            render_texture = gpu.types.GPUTexture((w, h), format=texture_format, data=buffer)\n\n            if DISPLAY_DRAW is None:\n                DISPLAY_DRAW = DisplayDrawGPU()\n            DISPLAY_DRAW.draw(self.bridge.viewport_bit_depth, resolution, render_texture)\n\n\nDISPLAY_DRAW = None\n\nclass DisplayDrawGL():\n    def __init__(self):\n        positions=[\n             1.0,  1.0, 1.0,\n             1.0, -1.0, 1.0,\n            -1.0, -1.0, 1.0,\n            -1.0,  1.0, 1.0,\n        ]\n        indices=[\n            0, 1, 3,\n            1, 2, 3,\n        ]\n        self.quad = Mesh(positions, indices)\n        source='#include \"Passes/sRGBConversion.glsl\"'\n        vertex_src = shader_preprocessor(source, [SHADER_DIR], ['VERTEX_SHADER'])\n        pixel_src = shader_preprocessor(source, [SHADER_DIR], ['PIXEL_SHADER'])\n        self.shader = Shader(vertex_src, pixel_src)\n\n    def draw(self, fbo, texture):\n        GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, fbo[0])\n        self.shader.uniforms[\"to_srgb\"].set_value(False)\n        self.shader.uniforms[\"convert\"].set_value(texture.internal_format == GL.GL_RGBA8)\n        self.shader.textures[\"input_texture\"] = texture\n        self.shader.bind()\n        self.quad.draw()\n\nclass DisplayDrawGPU():    \n    def __init__(self):\n        import gpu\n        from gpu_extras.batch import batch_for_shader\n\n        vertex_src = \"\"\"\n        void main()\n        {\n            IO_POSITION = IN_POSITION * vec3(1000, 1000, 0.5);\n            gl_Position = vec4(IO_POSITION, 1);\n        }\n        \"\"\"\n\n        pixel_src = \"\"\"\n        vec3 srgb_to_linear(vec3 srgb)\n        {\n            vec3 low = srgb / 12.92;\n            vec3 high = pow((srgb + 0.055)/1.055, vec3(2.4));\n            return mix(low, high, greaterThan(srgb, vec3(0.04045)));\n        }\n\n        void main()\n        {\n            vec2 uv =  IO_POSITION.xy * 0.5 + 0.5;\n\n            int divisor = 32 / bit_depth;\n\n            ivec2 output_texel = ivec2(vec2(output_res) * uv);\n            int output_texel_linear = output_texel.y * output_res.x + output_texel.x;\n            \n            int texel_linear_read = output_texel_linear / divisor;\n            ivec2 texel_read = ivec2(texel_linear_read % output_res.x, texel_linear_read / output_res.x);\n            int sub_texel_index = output_texel_linear % divisor;\n\n            vec4 texel_value = texelFetch(input_texture, texel_read, 0);\n\n            if(bit_depth == 32)\n            {\n                OUT_COLOR = texel_value;\n            }\n            else if(bit_depth == 16)\n            {\n                vec2 sub_texel_value = sub_texel_index == 0 ? texel_value.xy : texel_value.zw;\n\n                uint packed_xy = floatBitsToUint(sub_texel_value.x);\n                uint packed_yz = floatBitsToUint(sub_texel_value.y);\n\n                OUT_COLOR.rg = unpackHalf2x16(packed_xy);\n                OUT_COLOR.ba = unpackHalf2x16(packed_yz);\n            }\n            else if(bit_depth == 8)\n            {\n                float sub_texel_value = texel_value[sub_texel_index];\n                uint packed_value = floatBitsToUint(sub_texel_value);\n                OUT_COLOR = unpackUnorm4x8(packed_value);\n                OUT_COLOR.rgb = srgb_to_linear(OUT_COLOR.rgb);\n            }\n            else{\n                OUT_COLOR = vec4(1,1,0,1);\n            }\n        }\n        \"\"\"\n\n        self.iface = gpu.types.GPUStageInterfaceInfo(\"IFace\")\n        self.iface.smooth('VEC3', \"IO_POSITION\")\n        \n        self.sh_info = gpu.types.GPUShaderCreateInfo()\n        self.sh_info.push_constant('INT', \"bit_depth\")\n        self.sh_info.push_constant('IVEC2', \"output_res\")\n        self.sh_info.sampler(0, 'FLOAT_2D', \"input_texture\")\n        self.sh_info.vertex_source(vertex_src)\n        self.sh_info.vertex_in(0, 'VEC3', \"IN_POSITION\")\n        self.sh_info.vertex_out(self.iface)\n        self.sh_info.fragment_source(pixel_src)\n        self.sh_info.fragment_out(0, 'VEC4', \"OUT_COLOR\")\n\n        self.shader = gpu.shader.create_from_info(self.sh_info)\n\n        positions=[\n            ( 1.0,  1.0, 1.0),\n            ( 1.0, -1.0, 1.0),\n            (-1.0, -1.0, 1.0),\n            (-1.0,  1.0, 1.0),\n        ]\n        indices=[\n            (0, 1, 3),\n            (1, 2, 3),\n        ]\n        \n        self.quad = batch_for_shader(\n            self.shader, 'TRIS',\n            {\"IN_POSITION\": positions},\n            indices=indices\n        )\n\n    def draw(self, bit_depth, resolution, texture):\n        self.shader.bind()\n        self.shader.uniform_int(\"bit_depth\", bit_depth)\n        self.shader.uniform_int(\"output_res\", resolution)\n        self.shader.uniform_sampler(\"input_texture\", texture)\n        self.quad.draw(self.shader)\n\n\nclass OT_MaltRenderDocCapture(bpy.types.Operator):\n    bl_idname = \"wm.malt_renderdoc_capture\"\n    bl_label = \"RenderDoc Capture\"\n\n    def execute(self, context):\n        global CAPTURE\n        CAPTURE = True\n        context.area.tag_redraw()\n        return {'FINISHED'}\n    \nclass VIEW3D_PT_Malt_Stats(bpy.types.Panel):\n    bl_space_type = 'VIEW_3D'\n    bl_region_type = 'UI'\n    bl_category = \"View\"\n    bl_label = \"Malt Stats\"\n\n    @classmethod\n    def poll(cls, context):\n        return context.scene.render.engine == 'MALT'\n\n    def draw(self, context):\n        from . import MaltPipeline\n        self.layout.operator(\"wm.malt_renderdoc_capture\")\n        stats = MaltPipeline.get_bridge().get_stats()\n        for line in stats.splitlines():\n            self.layout.label(text=line)\n\nclasses = [\n    MaltRenderEngine,\n    OT_MaltRenderDocCapture,\n    VIEW3D_PT_Malt_Stats,\n]\n\ndef get_panels():\n    exclude_panels = {\n        'VIEWLAYER_PT_filter',\n        'VIEWLAYER_PT_layer_passes',\n        'DATA_PT_area',\n    }\n\n    panels = []\n    for panel in bpy.types.Panel.__subclasses__():\n        if hasattr(panel, 'COMPAT_ENGINES') and 'BLENDER_RENDER' in panel.COMPAT_ENGINES:\n            if panel.__name__ not in exclude_panels:\n                panels.append(panel)\n\n    return panels\n\ndef register():\n    for cls in classes:\n        bpy.utils.register_class(cls)\n\n    for panel in get_panels():\n        panel.COMPAT_ENGINES.add('MALT')\n\ndef unregister():\n    for cls in classes:\n        bpy.utils.unregister_class(cls)\n\n    for panel in get_panels():\n        if 'MALT' in panel.COMPAT_ENGINES:\n            panel.COMPAT_ENGINES.remove('MALT')\n"
  },
  {
    "path": "BlenderMalt/MaltTextures.py",
    "content": "import ctypes\nfrom . import MaltPipeline\n\n__TEXTURES = {}\n\ndef get_texture(texture):\n    name = texture.name_full\n    if name not in __TEXTURES or __TEXTURES[name] is None:\n        __TEXTURES[name] = __load_texture(texture)\n    return __TEXTURES[name]\n\n\ndef __load_texture(texture):\n    w,h = texture.size\n    channels = int(texture.channels)\n    size = w*h*channels\n    sRGB = texture.colorspace_settings.name == 'sRGB' and texture.is_float == False\n    if size == 0:\n        return False\n\n    buffer = MaltPipeline.get_bridge().get_shared_buffer(ctypes.c_float, size)\n    texture.pixels.foreach_get(buffer.as_np_array())\n    \n    MaltPipeline.get_bridge().load_texture(texture.name_full, buffer, (w,h), channels, sRGB)\n    \n    from Bridge.Proxys import TextureProxy\n    return TextureProxy(texture.name_full)\n\n__GRADIENTS = {}\n__GRADIENT_RESOLUTION = 256\n# Blender doesn't trigger depsgraph updates for newly created textures,\n# so we always reload gradients until it's been succesfully unloaded once (TODO)\n__GRADIENTS_WORKAROUND = []\ndef add_gradient_workaround(texture):\n    __GRADIENTS_WORKAROUND.append(texture.name_full)\n\ndef get_gradient(texture):\n    name = texture.name_full\n    if name not in __GRADIENTS or __GRADIENTS[name] is None or name in __GRADIENTS_WORKAROUND:\n        __GRADIENTS[name] = __load_gradient(texture)\n    return __GRADIENTS[name]\n\ndef __load_gradient(texture):\n    pixels = []\n    for i in range(0, __GRADIENT_RESOLUTION):\n        pixel = texture.color_ramp.evaluate( i*(1.0 / __GRADIENT_RESOLUTION))\n        pixels.extend(pixel)\n    nearest = texture.color_ramp.interpolation == 'CONSTANT'\n    MaltPipeline.get_bridge().load_gradient(texture.name_full, pixels, nearest)\n    from Bridge.Proxys import GradientProxy\n    return GradientProxy(texture.name_full)\n\ndef copy_color_ramp(old, new):\n    new.color_mode = old.color_mode\n    new.hue_interpolation = old.hue_interpolation\n    new.interpolation = old.interpolation\n    while len(new.elements) > len(old.elements):\n        new.elements.remove(new.elements[len(new.elements)-1])\n    for i, o in enumerate(old.elements):\n        n = new.elements[i] if i < len(new.elements) else new.elements.new(o.position) \n        n.position = o.position\n        n.color = o.color[:]\n        n.alpha = o.alpha\n    \ndef reset_textures():\n    global __TEXTURES\n    __TEXTURES = {}\n    global __GRADIENTS\n    __GRADIENTS = {}\n\ndef unload_texture(texture):\n    __TEXTURES[texture.name_full] = None\n\ndef unload_gradients(texture):\n    __GRADIENTS[texture.name_full] = None\n    if texture.name_full in __GRADIENTS_WORKAROUND:\n        __GRADIENTS_WORKAROUND.remove(texture.name_full)\n\ndef register():\n    pass\n\ndef unregister():\n    pass\n"
  },
  {
    "path": "BlenderMalt/MaltUtils.py",
    "content": "import bpy\n\nclass OT_MaltPrintError(bpy.types.Operator):\n    bl_idname = \"wm.malt_print_error\"\n    bl_label = \"Print Malt Error\"\n    bl_description = \"MALT ERROR\"\n    bl_options = {'INTERNAL'}\n\n    message : bpy.props.StringProperty(default=\"Malt Error\", description='Error Message')\n\n    @classmethod\n    def description(cls, context, properties):\n        return properties.message\n\n    def execute(self, context):\n        self.report({'ERROR'}, self.message)\n        return {'FINISHED'}\n    \n    def modal(self, context, event):\n        self.report({'ERROR'}, self.message)\n        return {'FINISHED'}\n\n    def invoke(self, context, event):\n        context.window_manager.modal_handler_add(self)\n        return {'RUNNING_MODAL'}\n\n# Always store paths in UNIX format so saved files work across OSs\ndef malt_path_set_transform(self, new_value, curr_value, is_set):\n    return new_value.replace('\\\\','/')\n\ndef malt_path_get_transform(self, curr_value, is_set):\n    return curr_value.replace('\\\\','/')\n\n# Operator buttons are generated every time the UI is redrawn.\n# The UI is redrawn for every frame the cursor hovers over it\n# The operator button called is not the last one created (???)\n# So _MAX_CALLBACKS should be at least (max drawn operator buttons) * (max ui redraws after the button was created)\n_MAX_CALLBACKS = 1000\n_CALLBACKS = [None] * _MAX_CALLBACKS\n_LAST_HANDLE = 0\n\nclass MaltCallback(bpy.types.PropertyGroup):\n\n    def set(self, callback, message=''):\n        global _CALLBACKS, _LAST_HANDLE, _MAX_CALLBACKS\n        _LAST_HANDLE += 1\n        _LAST_HANDLE = _LAST_HANDLE % _MAX_CALLBACKS\n        _CALLBACKS[_LAST_HANDLE] = callback\n        self['_MALT_CALLBACK_'] = _LAST_HANDLE\n        self['_MESSAGE_'] = message\n    \n    def call(self, *args, **kwargs):\n        global _CALLBACKS\n        _CALLBACKS[self['_MALT_CALLBACK_']](*args, **kwargs)\n\nclass OT_MaltCallback(bpy.types.Operator):\n    bl_idname = \"wm.malt_callback\"\n    bl_label = \"Malt Callback Operator\"\n    bl_options = {'INTERNAL'}\n\n    callback : bpy.props.PointerProperty(type=MaltCallback)\n\n    @classmethod\n    def description(self, context, properties):\n        return properties.callback['_MESSAGE_']\n\n    def execute(self, context):\n        self.callback.call()\n        return {'FINISHED'}\n\nclass COMMON_UL_UI_List(bpy.types.UIList):\n    \n    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):\n        item.draw(context, layout, data)\n\ndef malt_template_list(layout, owner, list_property, index_property, add_callback = None, remove_callback = None):\n    row = layout.row()\n    row.template_list('COMMON_UL_UI_List', '', owner, list_property, owner, index_property)\n    col = row.column()\n    list = getattr(owner, list_property)\n    index = getattr(owner, index_property)\n    col.operator('wm.malt_callback', text='', icon='ADD').callback.set(\n        add_callback if add_callback else list.add, 'Add')\n    col.operator('wm.malt_callback', text='', icon='REMOVE').callback.set(\n        remove_callback if remove_callback else lambda: list.remove(index), 'Remove')\n\n\nimport json\n\n# https://developer.blender.org/T51096\ndef to_json_rna_path_node_workaround(malt_property_group, path_from_group):\n    tree = malt_property_group.id_data\n    assert(isinstance(tree, bpy.types.NodeTree))\n    for node in tree.nodes:\n        if node.malt_parameters.as_pointer() == malt_property_group.as_pointer():\n            path = 'nodes[\"{}\"].{}'.format(node.name, path_from_group)\n            return json.dumps(('NodeTree', tree.name_full, path))\n\ndef to_json_rna_path(prop):\n    blend_id = prop.id_data\n    id_type = str(blend_id.__class__).split('.')[-1]\n    if isinstance(prop.id_data, bpy.types.NodeTree):\n        id_type = 'NodeTree'\n    id_name = blend_id.name_full\n    path = prop.path_from_id()\n    return json.dumps((id_type, id_name, path))\n\ndef from_json_rna_path(prop):\n    id_type, id_name, path = json.loads(prop)\n    data_map = {\n        'Object' : bpy.data.objects,\n        'Mesh' : bpy.data.meshes,\n        'Light' : bpy.data.lights,\n        'Camera' : bpy.data.cameras,\n        'Material' : bpy.data.materials,\n        'World': bpy.data.worlds,\n        'Scene': bpy.data.scenes,\n        'NodeTree' : bpy.data.node_groups\n    }\n    for class_name, data in data_map.items():\n        if class_name in id_type:\n            return data[id_name].path_resolve(path)\n    return None\n\nclasses=[\n    OT_MaltPrintError,\n    MaltCallback,\n    OT_MaltCallback,\n    COMMON_UL_UI_List,\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n\ndef unregister():\n    global _CALLBACKS\n    _CALLBACKS = [None] * _MAX_CALLBACKS\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n"
  },
  {
    "path": "BlenderMalt/__init__.py",
    "content": "bl_info = {\n    \"name\": \"BlenderMalt\",\n    \"description\" : \"Extensible Python Render Engine\",\n    \"author\" : \"Miguel Pozo\",\n    \"version\": (1,0,0,'Release'),\n    \"blender\" : (5, 1, 0),\n    \"doc_url\": \"https://malt3d.com\",\n    \"tracker_url\": \"https://github.com/bnpr/Malt/issues/new/choose\",\n    \"category\": \"Render\"\n}\n\nimport sys, os\nfrom os import path\nimport bpy\n\n#Add Malt and dependencies to the import path\n__CURRENT_DIR = path.dirname(path.realpath(__file__))\n__MALT_PATH = path.join(__CURRENT_DIR, '.MaltPath')\nif __MALT_PATH not in sys.path: sys.path.append(__MALT_PATH)\n_PY_VERSION = str(sys.version_info[0])+str(sys.version_info[1])\n__MALT_DEPENDENCIES_PATH = path.join(__MALT_PATH,'Malt','.Dependencies-{}'.format(_PY_VERSION))\nif __MALT_DEPENDENCIES_PATH not in sys.path: sys.path.append(__MALT_DEPENDENCIES_PATH)\n\nfrom BlenderMalt.MaltUtils import malt_path_set_transform, malt_path_get_transform\n\nclass Preferences(bpy.types.AddonPreferences):\n    # this must match the addon name\n    bl_idname = __package__\n\n    setup_vs_code : bpy.props.BoolProperty(name=\"Auto setup VSCode\", default=True,\n        description=\"Setups a VSCode project on your .blend file folder\")\n\n    renderdoc_path : bpy.props.StringProperty(name=\"RenderDoc Path\", subtype='FILE_PATH',\n        set_transform=malt_path_set_transform, get_transform=malt_path_get_transform)\n    \n    plugins_dir : bpy.props.StringProperty(name=\"Global Plugins\", subtype='DIR_PATH',\n        set_transform=malt_path_set_transform, get_transform=malt_path_get_transform)\n    \n    docs_path : bpy.props.StringProperty(name=\"Docs Path\", subtype='DIR_PATH',\n        set_transform=malt_path_set_transform, get_transform=malt_path_get_transform)\n    \n    render_fps_cap : bpy.props.IntProperty(name=\"Max Viewport Render Framerate\", default=30)\n    \n    def update_debug_mode(self, context):\n        if context.scene.render.engine == 'MALT':\n            context.scene.world.malt.update_pipeline(context)\n\n    debug_mode : bpy.props.BoolProperty(name=\"Debug Mode\", default=False, update=update_debug_mode,\n        description=\"Developers only. Do not touch !!!\")\n\n    #Drawn in NODE_PT_overlay\n    show_socket_types : bpy.props.BoolProperty(name='Show Socket Types', default=True)\n    show_internal_nodes : bpy.props.BoolProperty(name='Show Internal Nodes', default=False)\n    use_subfunction_cycling: bpy.props.BoolProperty(name='Use Subfunction Cycling', default=True)\n\n    def draw(self, context):\n        layout = self.layout\n\n        if context.scene.render.engine == 'MALT':\n            layout.operator('wm.path_open', text=\"Open Session Log\").filepath=sys.stdout.log_path\n        else:\n            row = layout.row()\n            row.enabled = False\n            row.operator('wm.path_open', text=\"Open Session Log\")\n\n        layout.prop(self, \"plugins_dir\")\n        layout.prop(self, \"render_fps_cap\")\n        layout.prop(self, \"setup_vs_code\")\n        layout.prop(self, \"renderdoc_path\")\n        layout.label(text='Developer Settings :')\n        layout.prop(self, \"debug_mode\")\n        layout.prop(self, \"docs_path\")\n    \ndef draw_node_tree_overlays(self:bpy.types.Menu, context: bpy.types.Context):\n    preferences = bpy.context.preferences.addons['BlenderMalt'].preferences\n    self.layout.label(text='Malt')\n    self.layout.prop(preferences, 'show_socket_types')\n    self.layout.prop(preferences, 'show_internal_nodes')\n    self.layout.prop(preferences, 'use_subfunction_cycling')\n\n_VS_CODE_SETTINGS = '''\n{{\n    \"files.associations\": {{\n        \"*.glsl\": \"cpp\"\n    }},\n    \"C_Cpp.default.includePath\": [\"{}\"],\n    \"C_Cpp.default.forcedInclude\": [\"{}\"],\n    \"C_Cpp.autoAddFileAssociations\": true,\n    \"C_Cpp.default.cppStandard\": \"c++03\",\n    \"C_Cpp.default.compilerPath\": \"\",\n    \"C_Cpp.default.browse.limitSymbolsToIncludedHeaders\": true,\n    \"C_Cpp.errorSquiggles\": \"Disabled\",\n    \"python.analysis.extraPaths\": [\"{}\",\"{}\"],\n}}\n'''\n\n@bpy.app.handlers.persistent\ndef setup_vs_code(dummy):\n    if bpy.context.scene.render.engine == 'MALT':\n        if bpy.context.preferences.addons['BlenderMalt'].preferences.setup_vs_code:\n            shaders_path = path.join(__MALT_PATH, 'Malt', 'Shaders')\n            intellisense_path = path.join(shaders_path, 'Intellisense', 'intellisense.glsl')\n\n            vscode_settings = _VS_CODE_SETTINGS.format(shaders_path, intellisense_path, __MALT_PATH, __MALT_DEPENDENCIES_PATH)\n            vscode_settings = vscode_settings.replace('\\\\','\\\\\\\\')\n\n            settings_dir = bpy.path.abspath('//.vscode')\n\n            if path.exists(settings_dir) == False:\n                os.makedirs(settings_dir)\n\n            with open(path.join(settings_dir, 'settings.json'), 'w') as f:\n                f.write(vscode_settings)\n\n_PLUGINS = []\n_PLUGIN_DIRS = []\n\ndef register_plugins():\n    global _PLUGINS, _PLUGIN_DIRS\n    preferences = bpy.context.preferences.addons['BlenderMalt'].preferences\n    plugins_dir = preferences.plugins_dir\n    if not os.path.exists(plugins_dir):\n        return\n    import importlib\n    if plugins_dir not in sys.path:\n        sys.path.append(plugins_dir)\n        _PLUGIN_DIRS.append(plugins_dir)\n    for e in os.scandir(plugins_dir):\n        if (e.path.startswith('.') or e.path.startswith('_') or \n            e.is_file() and e.path.endswith('.py') == False):\n            continue\n        try:\n            module = importlib.import_module(e.name)\n            importlib.reload(module)\n            module.PLUGIN.blendermalt_register()\n            _PLUGINS.append(module.PLUGIN)\n        except:\n            import traceback\n            traceback.print_exc()\n\ndef unregister_plugins():\n    global _PLUGINS, _PLUGIN_DIRS\n    for plugin in _PLUGINS:\n        try:\n            plugin.blendermalt_unregister()\n        except:\n            import traceback\n            traceback.print_exc()\n    _PLUGINS = []\n    for dir in _PLUGIN_DIRS:\n        sys.path.remove(dir)\n    _PLUGIN_DIRS = []\n\nclass OT_MaltReloadPlugins(bpy.types.Operator):\n    bl_idname = \"wm.malt_reload_plugins\"\n    bl_label = \"Malt Reload Plugins\"\n\n    def execute(self, context):\n        unregister_plugins()\n        bpy.ops.wm.malt_reload_pipeline()\n        register_plugins()\n        return{\"FINISHED\"}\n\ndef get_modules():\n    from . import MaltUtils, MaltTextures, MaltMeshes, MaltLights, MaltProperties, MaltPipeline, MaltMaterial, MaltRenderEngine\n    from . MaltNodes import _init_ as MaltNodes\n    return [ MaltUtils, MaltTextures, MaltMeshes, MaltLights, MaltProperties, MaltPipeline, MaltNodes, MaltMaterial, MaltRenderEngine ]\n\nclasses=[\n    Preferences,\n    OT_MaltReloadPlugins,\n]\n\ndef register():\n    for _class in classes: bpy.utils.register_class(_class)\n\n    import importlib\n    for module in get_modules():\n        importlib.reload(module)\n    \n    import Bridge\n    Bridge.reload()\n\n    for module in get_modules():\n        module.register()\n\n    register_plugins()\n\n    bpy.app.handlers.save_post.append(setup_vs_code)\n\n    bpy.types.NODE_PT_overlay.append(draw_node_tree_overlays)\n\ndef unregister():\n    for _class in reversed(classes): bpy.utils.unregister_class(_class)\n    \n    unregister_plugins()\n\n    for module in reversed(get_modules()):\n        module.unregister()\n    \n    bpy.app.handlers.save_post.remove(setup_vs_code)\n\n    bpy.types.NODE_PT_overlay.remove(draw_node_tree_overlays)\n"
  },
  {
    "path": "BlenderMalt/readme.md",
    "content": "# BlenderMalt\n\n## Introduction\n\n*BlenderMalt* is a [*Malt Host*](../Malt#malt-pipelines) for *Blender*.  \nIt handles all the *Scene* data loading and syncronization and exposes a minimal UI suitable for a code-centric workflow.  \n*BlenderMalt* communicates with *Malt* through a [*Bridge*](../Bridge) instance.  \n\n## RenderEngine\n\n[*MaltRenderEngine.py*](MaltRenderEngine.py) implements the *Blender* [*RenderEngine*](https://docs.blender.org/api/current/bpy.types.RenderEngine.html) interface.\n\nIt takes care of generating a *Malt Scene* from the *Blender* [*DepsGraph*](https://docs.blender.org/api/current/bpy.types.Depsgraph.html), send it to *Bridge* for rendering and pass the result back to *Blender*.\n\n## Pipeline\n\n[*MaltPipeline.py*](MaltPipeline.py) handles *Bridge* instances, makes sure all *Blender* objects have their respective *Pipeline Parameters* registered as *MaltPropertyGroups*, and handles *Meshes* and *Textures* updates.\n\n## Pipeline Properties\n\n[*MaltPropertyGroups*](MaltProperties.py) store *Malt* *Pipeline Parameters* as native *Blender* [*PropertyGroups*](https://docs.blender.org/api/current/bpy.types.PropertyGroup.html) and convert them to *Malt* *Scene* parameters on request.\nThey also can handle their own UI rendering automatically, all that is needed is to pass a *Blender* [*UI Layout*](https://docs.blender.org/api/current/bpy.types.UILayout.html) to their *draw_ui* method.\n\n## Materials\n\n[*MaltMaterial.py*](MaltMaterial.py) handles the compilation (including automatic hot-reloading), UI rendering and storage as native *Blender* materials of *Malt* *Pipeline* materials.\n\n## Meshes\n\n[*MaltMeshes.py*](MaltMeshes.py) retrieves any *Blender* geometry as vertex and index buffers and sends it to *Bridge*.  \nThis module is highly optimized to allow real-time mesh editing and (when possible) retrieves vertex data directly from *Blender* internal C data through the [*CBlenderMalt*](CBlenderMalt) library.\n\n## Textures\n\n[*MaltTextures.py*](MaltTextures.py) sends 1D and 2D textures pixel data to *Bridge*.  \n\n"
  },
  {
    "path": "Bridge/Client_API.py",
    "content": "import ctypes, logging as LOG, io, sys\nfrom Bridge.ipc import SharedBuffer\n\ndef bridge_method(function):\n    def result(*args, **kwargs):\n        self = args[0]\n        try:\n            if self.lost_connection == False:\n                return function(*args, **kwargs)\n        except:\n            import traceback\n            LOG.error(traceback.format_exc())\n            self.lost_connection = True\n        return None\n    return result\n\nclass IOCapture(io.StringIO):\n    def __init__(self, parent, log_path, log_level):\n        super().__init__()\n        self.parent = parent\n        self.log_path = log_path\n        self.log_level = log_level\n    \n    def write(self, s):\n        self.parent.write(s)\n        LOG.log(self.log_level, s)\n        return super().write(s)\n\nclass Bridge():\n\n    def __init__(self, pipeline_path, viewport_bit_depth=8, debug_mode=False, renderdoc_path=None, plugins_paths=[], docs_path=None):\n        super().__init__()\n\n        import sys\n        if not isinstance(sys.stdout, IOCapture):\n            import os, tempfile, time\n            date = time.strftime(\"%Y-%m-%d(%H-%M)\")\n            log_path = os.path.join(tempfile.gettempdir(),'malt ' + date + '.log')\n            LOG.basicConfig(filename=log_path, level=LOG.DEBUG, format='Blender > %(message)s')\n            sys.stdout = IOCapture(sys.stdout, log_path, LOG.INFO)\n            sys.stderr = IOCapture(sys.stderr, log_path, LOG.ERROR)\n            LOG.info('SETUP IOCapture')\n        \n        import multiprocessing, random, string\n        mp = multiprocessing.get_context('spawn')\n\n        self.viewport_bit_depth = viewport_bit_depth\n\n        self.manager = mp.Manager()\n        self.shared_dict = self.manager.dict()\n        self.lock = None\n        #SharedBuffer.setup_class(self.manager)\n        self.connections = {}\n        self.process = None\n        self.lost_connection = True\n        self.parameters = {}\n        self.graphs = {}\n        self.render_outputs = {}\n        self.render_buffers = {}\n        self.shared_buffers = []\n        self.id = ''.join(random.choices(string.ascii_letters + string.digits, k=8))\n\n        self.viewport_ids = []\n\n        listeners = {}\n        bridge_to_malt = {}\n        malt_to_bridge = {}\n        \n        import multiprocessing.connection as connection\n        def add_connection(name):\n            address = ('localhost', 0)\n            listener = connection.Listener(address)\n            address = listener.address\n            listeners[name] = listener\n            malt_to_bridge[name] = address\n\n        for name in ['MAIN','REFLECTION']: add_connection(name)\n\n        from . import start_server\n        self.process = mp.Process(target=start_server, kwargs={\n            'pipeline_path': pipeline_path, \n            'viewport_bit_depth': viewport_bit_depth, \n            'connection_addresses': malt_to_bridge, \n            'shared_dic': self.shared_dict,\n            'lock': self.lock,\n            'log_path': sys.stdout.log_path,\n            'debug_mode': debug_mode,\n            'renderdoc_path': renderdoc_path,\n            'plugins_paths': plugins_paths,\n            'docs_path': docs_path,\n        })\n        self.process.daemon = True\n        self.process.start()\n\n        for name, listener in listeners.items():\n            bridge_to_malt[name] = listener.accept()\n        \n        self.connections = bridge_to_malt\n\n        params = self.connections['MAIN'].recv()\n        assert(params['msg_type'] == 'PARAMS')\n        self.parameters = params['params']\n        self.graphs = params['graphs']\n        self.render_outputs = params['outputs']\n        self.lost_connection = False\n\n    \n    def __del__(self):\n        if self.process:\n            self.process.terminate()\n        \n    \n    def get_parameters(self):\n        return self.parameters\n    \n    @bridge_method\n    def get_stats(self):\n        if 'STATS' in self.shared_dict and self.shared_dict['STATS']:\n            return self.shared_dict['STATS']\n        else:\n            return ''\n\n    @bridge_method\n    def compile_material(self, path, search_paths=[], custom_passes=[]):\n        self.connections['MAIN'].send({\n            'msg_type': 'MATERIAL',\n            'path': path,\n            'search_paths': search_paths,\n            'custom_passes': custom_passes,\n        })\n        return self.connections['MAIN'].recv()\n\n    @bridge_method\n    def compile_materials(self, paths, search_paths=[], async_compilation=False):\n        for path in paths:\n            self.connections['MAIN'].send({\n                'msg_type': 'MATERIAL',\n                'path': path,\n                'search_paths': search_paths,\n                'custom_passes': [],\n            })\n        results = {}\n        received = []\n        if async_compilation == False:\n            while True:\n                completed = True\n                for path in paths:\n                    if path not in received:\n                        completed = False\n                        break\n                if completed:\n                    break\n                msg = self.connections['MAIN'].recv()\n                assert(msg['msg_type'] == 'MATERIAL')\n                material = msg['material']\n                results[material.path] = material\n                received.append(material.path)\n        return results\n    \n    @bridge_method\n    def receive_async_compilation_materials(self):\n        results = {}\n        while self.connections['MAIN'].poll():\n            msg = self.connections['MAIN'].recv()\n            assert(msg['msg_type'] == 'MATERIAL')\n            material = msg['material']\n            results[material.path] = material\n        return results\n    \n    @bridge_method\n    def reflect_source_libraries(self, paths):\n        self.connections['REFLECTION'].send({\n            'msg_type': 'SHADER REFLECTION',\n            'paths': paths\n        })\n        return self.connections['REFLECTION'].recv()\n    \n    @bridge_method\n    def reload_graphs(self, graph_types):\n        self.connections['REFLECTION'].send({\n            'msg_type': 'GRAPH RELOAD',\n            'graph_types': graph_types\n        })\n        self.graphs.update(self.connections['REFLECTION'].recv())\n    \n    @bridge_method\n    def get_shared_buffer(self, ctype, size):\n        from . import ipc\n        #return ipc.SharedBuffer(ctype, size)\n        requested_size = ctypes.sizeof(ctype) * size\n        reuse_buffer = None\n        for buffer in self.shared_buffers:\n            release_flag = ctypes.c_bool.from_address(buffer._release_flag.data)\n            min_ref_count = 3 # shared_buffers + local buffer var + getrefcount ref\n            if release_flag.value == True and sys.getrefcount(buffer) == min_ref_count:\n                if buffer._buffer.size >= requested_size:\n                    if reuse_buffer is None or buffer._buffer.size < reuse_buffer._buffer.size:\n                        reuse_buffer = buffer\n        \n        if reuse_buffer is None:\n            min_size = 1024*1024\n            new_size = max(requested_size * 2, min_size)\n            reuse_buffer = ipc.SharedBuffer(ctypes.c_byte, new_size)\n            self.shared_buffers.append(reuse_buffer)\n        \n        ctypes.c_bool.from_address(reuse_buffer._release_flag.data).value = True\n        reuse_buffer._ctype = ctype\n        reuse_buffer._size = size\n        return reuse_buffer\n\n    @bridge_method\n    def load_mesh(self, name, mesh_data):\n        self.connections['MAIN'].send({\n            'msg_type': 'MESH',\n            'name': name,\n            'data': mesh_data\n        })\n    \n    @bridge_method\n    def load_texture(self, name, buffer, resolution, channels, sRGB):\n        self.connections['MAIN'].send({\n            'msg_type': 'TEXTURE',\n            'buffer': buffer,\n            'name': name,\n            'resolution': resolution,\n            'channels': channels,\n            'sRGB' : sRGB,\n        })\n\n    @bridge_method\n    def load_gradient(self, name, pixels, nearest):\n        self.connections['MAIN'].send({\n            'msg_type': 'GRADIENT',\n            'name': name,\n            'pixels': pixels,\n            'nearest' : nearest,\n        })\n\n    @bridge_method\n    def get_viewport_id(self):\n        i = 1 #0 is reserved for F12\n        while True:\n            if i not in self.viewport_ids:\n                self.viewport_ids.append(i)\n                return i\n            i+=1\n\n    @bridge_method\n    def free_viewport_id(self, viewport_id):\n        self.viewport_ids.remove(viewport_id)\n\n    @bridge_method\n    def render(self, viewport_id, resolution, scene, scene_update, renderdoc_capture=False, AOVs={}):\n        assert(viewport_id in self.viewport_ids or viewport_id == 0)\n\n        new_buffers = None\n        buffers = self.render_buffers.get(viewport_id)\n        if buffers is None or buffers['__resolution'] != resolution or buffers['__AOVs'] != AOVs:\n            self.render_buffers[viewport_id] = {\n                '__resolution' : resolution,\n                '__AOVs' : AOVs\n            }\n            from itertools import chain\n            for key, texture_format in chain(self.render_outputs.items(), AOVs.items()):\n                buffer_type = ctypes.c_float\n                if viewport_id != 0:\n                    if self.viewport_bit_depth == 8:\n                        buffer_type = ctypes.c_byte\n                    elif self.viewport_bit_depth == 16:\n                        buffer_type = ctypes.c_ushort\n                w,h = resolution\n                self.render_buffers[viewport_id][key] = self.get_shared_buffer(buffer_type, w*h*4)\n                if viewport_id != 0: #viewport render\n                    #we only need the color buffer\n                    break\n            new_buffers = self.render_buffers[viewport_id]\n\n        if (viewport_id, 'SETUP') in self.shared_dict:\n            import time\n            start = time.perf_counter()\n            while self.shared_dict[(viewport_id, 'SETUP')] == False:\n                # Don't stack multiple render workloads for the same viewport\n                if time.perf_counter() - start > 1:\n                    #But don't stall Blender forever\n                    if new_buffers is None and scene_update == False:\n                        #Never skip new_buffers setup or scene update\n                        return\n                    else:\n                        break\n                \n        self.shared_dict[(viewport_id, 'FINISHED')] = None\n        self.connections['MAIN'].send({\n            'msg_type': 'RENDER',\n            'viewport_id': viewport_id,\n            'resolution': resolution,\n            'scene': scene,\n            'scene_update': scene_update,\n            'new_buffers': new_buffers,\n            'renderdoc_capture' : renderdoc_capture,\n        })\n        self.shared_dict[(viewport_id, 'SETUP')] = False\n\n    @bridge_method\n    def render_result(self, viewport_id):\n        finished = False\n        if (viewport_id, 'FINISHED') in self.shared_dict:\n            finished = self.shared_dict[(viewport_id, 'FINISHED')] == True\n        \n        read_resolution = None\n        if (viewport_id, 'READ_RESOLUTION') in self.shared_dict:\n            read_resolution = self.shared_dict[viewport_id, 'READ_RESOLUTION']\n        \n        if viewport_id in self.render_buffers.keys():\n            return self.render_buffers[viewport_id], finished, read_resolution\n        else:\n            return None, finished, read_resolution\n"
  },
  {
    "path": "Bridge/Docs.py",
    "content": "def build_docs(pipeline, docs_path):\n    import os\n    output_path = os.path.join(docs_path, 'reference')\n    parameters = pipeline.get_parameters()\n    graphs = pipeline.get_graphs()\n\n    def clean_str(str):\n        return ''.join(line.lstrip() for line in str.strip().splitlines(True))\n\n    parameters_result = \"# Malt Settings\\n\"\n\n    def parameters_string(name, parameters):\n        if len(parameters) == 0:\n            return\n        nonlocal parameters_result\n        parameters_result += f\"## {name}\\n\"\n        stack = []\n        for key, parameter in parameters.items():\n            if '@' in key:\n                continue\n            _stack = key.split('.')[:-1]\n            for i, e in enumerate(_stack):\n                if i+1 > len(stack) or e != stack[i]:\n                    parameters_result += '\\t'*i + f\"- **{e}**\\n\"\n            stack = _stack\n            key = key.split('.')[-1]\n\n            tabs = '\\t'*len(stack) if stack else ''\n\n            parameters_result += f\"{tabs}- **{key}** *: ( {parameter.type_string()} )*\"\n            default = parameter.default_value\n            if default is None or default == '':\n                default = 'None'\n            elif isinstance(default, tuple):\n                default = default[1]\n            parameters_result += f'* = {default}*  \\n'\n            if parameter.doc:\n                parameters_result += f'{tabs}>' + clean_str(parameter.doc) + \"\\n\"\n    \n    parameters_string('Scene', parameters.scene)\n    parameters_string('World', parameters.world)\n    parameters_string('Camera', parameters.camera)\n    parameters_string('Object', parameters.object)\n    parameters_string('Material', parameters.material)\n    parameters_string('Mesh', parameters.mesh)\n    parameters_string('Light', parameters.light)\n\n    open(os.path.join(output_path, 'settings.md'), 'w').write(parameters_result)\n    \n    from textwrap import indent\n\n    for graph in graphs.values():\n        result = f\"# {graph.name} Graph Reference\\n\"\n        if len(graph.functions) > 0:\n            categories = {\n                'Input' : {},\n                'Parameters' : {},\n                'Math' : {},\n                'Vector' : {},\n                'Color' : {},\n                'Texturing' : {},\n                'Shading' : {},\n                'Filter' : {},\n                'Other' : {},\n                'Node Tree' : {},\n            }\n            for key, function in graph.functions.items():\n                if function['meta'].get('internal'):\n                    continue\n\n                category = function['meta'].get('category')\n                if category is None:\n                    category = function['file'].replace('\\\\', '/').replace('/', ' - ').replace('.glsl', '').replace('_',' ')\n                if category not in categories:\n                    categories[category] = {}\n                subcategory = function['meta'].get('subcategory')\n                if subcategory:\n                    if subcategory not in categories[category]:\n                        categories[category][subcategory] = []\n                    categories[category][subcategory].append(function)\n                else:\n                    categories[category][key] = function\n\n            def draw_function(function, depth=3):\n                nonlocal result\n                result += '---\\n'\n                result += f\"{'#'*depth} **{function['meta'].get('label', function['name'])}**\\n\"\n                \n                if pass_type := function.get('pass_type'):\n                    result += f\">Graph Type / Pass : *{pass_type.replace('.', ' / ')}*\\n\\n\"\n\n                if doc := function['meta'].get('doc'):\n                    result += clean_str(doc) + \"\\n\\n\"\n                \n                inputs = {}\n                outputs = {}\n                if function['type'] != 'void':\n                    outputs['result'] = {'type': function['type'], 'meta':{}}\n\n                for parameter in function['parameters']:\n                    label = parameter['meta'].get('label', parameter['name'])\n                    if parameter['io'] in ('in', 'inout'):\n                        inputs[label] = parameter\n                    if parameter['io'] in ('out', 'inout'):\n                        outputs[label] = parameter\n                \n                def draw_params(type, dict):\n                    if len(dict) == 0:\n                        return\n                    nonlocal result\n                    result += f\"- **{type}**  \\n\"\n                    params = \"\"\n                    for key, parameter in dict.items():\n                        if '@' in key:\n                            continue\n                        try:\n                            type = parameter['type'].type_string()\n                        except:\n                            type = parameter['type']\n                        if subtype := parameter['meta'].get('subtype'):\n                            type += f' | {subtype}'\n                        \n                        type = f'( {type} )'\n\n                        if default := parameter['meta'].get('default'):\n                            type += f' - default = {default}'\n                        \n                        params += f\"- **{key}** *: {type}*  \\n\"\n\n                        if doc := parameter['meta'].get('doc'):\n                            params += '>' + clean_str(doc) + \"\\n\"\n                    \n                    result += indent(params, '\\t')\n                \n                draw_params('Inputs', inputs)\n                draw_params('Outputs', outputs)\n            \n            for category, items in categories.items():\n                if len(items) == 0:\n                    continue\n                result += '---\\n'\n                result += f\"## {category}\\n\"\n\n                for k, v in items.items():\n                    if isinstance(v, dict):\n                        draw_function(v)\n                    else:\n                        result += '---\\n'\n                        result += f\"### **{k}**\\n\"\n                        for subcategory_function in v:\n                            draw_function(subcategory_function, 4)\n        \n            open(os.path.join(output_path, f'{graph.name}-graph.md'), 'w').write(result)\n"
  },
  {
    "path": "Bridge/Material.py",
    "content": "from Malt.PipelineParameters import Parameter\n\nMATERIAL_SHADERS = {}\n\nclass Material():\n\n    def __init__(self, path, pipeline, search_paths=[], custom_passes={}):\n        self.path = path\n        self.parameters = {}\n        self.compiler_error = ''\n        \n        compiled_material = pipeline.compile_material(path, search_paths)#, custom_passes)\n        \n        if isinstance(compiled_material, str):\n            self.compiler_error = compiled_material\n        else:\n            for pass_name, shader in compiled_material.items():\n                for uniform_name, uniform in shader.uniforms.items():\n                    self.parameters[uniform_name] = Parameter.from_uniform(uniform)\n                if shader.error:\n                    self.compiler_error += pass_name + \" : \" + shader.error\n                if shader.validator:\n                    self.compiler_error += pass_name + \" : \" + shader.validator\n        \n        if self.compiler_error == '':\n            global MATERIAL_SHADERS\n            MATERIAL_SHADERS[self.path] = compiled_material\n        else:\n            MATERIAL_SHADERS[self.path] = {}\n\n\ndef get_shader(path, parameters):\n    if path not in MATERIAL_SHADERS.keys():\n        return {}\n    shaders = MATERIAL_SHADERS[path]\n    new_shader = {}\n\n    for pass_name, pass_shader in shaders.items():\n        if pass_shader.error:\n            new_shader = {}\n            break\n\n        pass_shader_copy = pass_shader.copy()\n        new_shader[pass_name] = pass_shader_copy\n\n        for name, parameter in parameters.items():\n            if name in pass_shader_copy.textures.keys():\n                pass_shader_copy.textures[name] = parameter\n            elif name in pass_shader_copy.uniforms.keys():\n                pass_shader_copy.uniforms[name].set_value(parameter)\n    \n    return new_shader\n"
  },
  {
    "path": "Bridge/Mesh.py",
    "content": "MESHES = {}\n\ndef load_mesh(pipeline, msg):\n    name = msg['name']\n    data = msg['data']\n\n    MESHES[name] = pipeline.load_mesh(\n        position = data['positions'],\n        indices = data['indices'],\n        normal = data['normals'],\n        tangent = data['tangents'],\n        uvs = data['uvs'],\n        colors = data['colors']\n    )\n"
  },
  {
    "path": "Bridge/Proxys.py",
    "content": "from Malt.GL.Mesh import Mesh\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.Texture import Gradient\nfrom Malt.Scene import Material\n\nclass MeshProxy(Mesh):\n\n    def  __init__(self, name, submesh_index):\n        self.name = name\n        self.mesh = None\n        self.submesh_index = submesh_index\n    \n    def resolve(self):\n        import Bridge.Mesh\n        self.mesh = Bridge.Mesh.MESHES[self.name][self.submesh_index]\n        self.__dict__.update(self.mesh.__dict__)\n    \n    def __del__(self):\n        pass\n\nclass TextureProxy(Texture):\n\n    def  __init__(self, name):\n        self.name = name\n        self.texture = None\n    \n    def resolve(self):\n        import Bridge.Texture\n        self.texture = Bridge.Texture.TEXTURES[self.name]\n        self.__dict__.update(self.texture.__dict__)\n    \n    def __del__(self):\n        pass\n\nclass GradientProxy(Gradient):\n\n    def  __init__(self, name):\n        self.name = name\n        self.gradient = None\n    \n    def resolve(self):\n        import Bridge.Texture\n        self.gradient = Bridge.Texture.GRADIENTS[self.name]\n        self.__dict__.update(self.gradient.__dict__)\n    \n    def __del__(self):\n        pass\n\nclass MaterialProxy(Material):\n\n    def __init__(self, path, shader_parameters, parameters):\n        self.path = path\n        self.shader_parameters = shader_parameters\n        super().__init__(None, parameters)\n    \n    def resolve(self):\n        import Bridge.Material\n        self.shader = Bridge.Material.get_shader(self.path, self.shader_parameters)\n"
  },
  {
    "path": "Bridge/Server.py",
    "content": "import importlib\nimport os, sys, time, ctypes, os, copy\nimport cProfile, pstats, io\nimport multiprocessing.connection as connection\n\nimport glfw\n\nfrom Malt.GL import GL\nfrom Malt.GL.GL import *\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.GL.Texture import Texture\nfrom Malt.PipelinePlugin import load_plugins_from_dir\n\nimport Bridge.Mesh, Bridge.Material, Bridge.Texture\nfrom . import ipc as ipc\n\nfrom Malt.Utils import LOG\n\ndef log_system_info():\n    import sys, platform\n    LOG.info('SYSTEM INFO')\n    LOG.info('-'*80)\n    LOG.info('PYTHON: {}'.format(sys.version))\n    LOG.info('OS: {}'.format(platform.platform()))\n    LOG.info('CPU: {}'.format(platform.processor()))\n\n    LOG.info('OPENGL CONTEXT:')\n    LOG.info(glGetString(GL_VENDOR).decode())\n    LOG.info(glGetString(GL_RENDERER).decode())\n    LOG.info(glGetString(GL_VERSION).decode())\n    LOG.info(glGetString(GL_SHADING_LANGUAGE_VERSION).decode())\n    LOG.info(f\"GL_ARB_bindless_texture support : {hasGLExtension('GL_ARB_bindless_texture')}\")\n    for key, value in GL_NAMES.items():\n        if key.startswith('GL_MAX'):\n            try:\n                LOG.info('{}: {}'.format(key, glGetInteger(value)))\n            except:\n                pass\n\n    def log_format_prop(format, prop):\n        read = glGetInternalformativ(GL_TEXTURE_2D, format, prop, 1)\n        try:\n            LOG.info('{} {}: {}'.format(GL_ENUMS[format], GL_ENUMS[prop], GL_ENUMS[read]))\n        except:\n            #Some returned formats are not present in GL_ENUMS? See #393\n            import traceback\n            traceback.print_exc()\n\n    def log_format_props(format):\n        log_format_prop(format, GL_READ_PIXELS)\n        log_format_prop(format, GL_READ_PIXELS_FORMAT)\n        log_format_prop(format, GL_READ_PIXELS_TYPE)\n        log_format_prop(format, GL_TEXTURE_IMAGE_FORMAT)\n        log_format_prop(format, GL_TEXTURE_IMAGE_TYPE)\n\n    log_format_props(GL_RGB8)\n    log_format_props(GL_RGBA8)\n    log_format_props(GL_RGBA)\n    log_format_props(GL_SRGB)\n    log_format_props(GL_SRGB_ALPHA)\n    log_format_props(GL_RGB16F)\n    log_format_props(GL_RGBA16F)\n    log_format_props(GL_RGB32F)\n    log_format_props(GL_RGBA32F)\n\n    LOG.info('-'*80)\n\nclass PBO():\n\n    def __init__(self):\n        self.handle = gl_buffer(GL_INT, 1)\n        self.size = None\n        self.sync = None\n        self.buffer = None\n    \n    def __del__(self):\n        glDeleteBuffers(1, self.handle)\n    \n    def setup(self, texture, buffer):\n        self.buffer = buffer\n        render_target = RenderTarget([texture])\n        w,h = texture.resolution\n        size = w * h * texture.channel_count * texture.channel_size\n        assert(buffer.size_in_bytes() >= size)\n        if self.size != size:\n            self.size = size\n            glDeleteBuffers(1, self.handle)\n            glGenBuffers(1, self.handle)\n            glBindBuffer(GL_PIXEL_PACK_BUFFER, self.handle[0])\n            glBufferData(GL_PIXEL_PACK_BUFFER, size, None, GL_STREAM_READ)\n            glBindBuffer(GL_PIXEL_PACK_BUFFER, 0)\n        \n        render_target.bind()\n        GL.glReadBuffer(GL.GL_COLOR_ATTACHMENT0)\n        \n        glBindBuffer(GL_PIXEL_PACK_BUFFER, self.handle[0])\n        GL.glReadPixels(0, 0, w, h, texture.format, texture.data_format, 0)\n        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0)\n\n        if self.sync:\n            glDeleteSync(self.sync)\n        self.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)\n    \n    def poll(self):\n        wait = glClientWaitSync(self.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 0)\n        return wait == GL_ALREADY_SIGNALED\n    \n    def load(self):\n        wait = glClientWaitSync(self.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 0)\n        if wait == GL_ALREADY_SIGNALED:\n            glBindBuffer(GL_PIXEL_PACK_BUFFER, self.handle[0])\n            result = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY)\n            if result:\n                ctypes.memmove(self.buffer.buffer(), result, self.size)\n                glUnmapBuffer(GL_PIXEL_PACK_BUFFER)\n            glBindBuffer(GL_PIXEL_PACK_BUFFER, 0)\n            return True\n        return False\n\n\nclass Viewport():\n\n    def __init__(self, pipeline, is_final_render, bit_depth):\n        self.pipeline = pipeline\n        self.buffers = None\n        self.resolution = None\n        self.read_resolution = None\n        self.scene = None\n        self.bit_depth = bit_depth\n        self.target_format = None\n        self.final_texture = None\n        self.final_target = None\n        self.pbos_active = []\n        self.pbos_inactive = []\n        self.is_new_frame = True\n        self.needs_more_samples = True\n        self.is_final_render = is_final_render\n        self.renderdoc_capture = False\n\n        self.stat_max_frame_latency = 0\n        self.stat_cpu_frame_time = 0\n        self.stat_time_start = 0\n        self.stat_render_time = 0\n    \n    def get_print_stats(self):\n        return '\\n'.join((\n            'Resolution : {}'.format(self.resolution),\n            'Sample : {} / {}'.format(self.pipeline.sample_count, len(self.pipeline.get_samples())),\n            'Sample Time : {:.3f} ms'.format((self.stat_render_time * 1000) / self.pipeline.sample_count),\n            'Total Time : {:.3f} s'.format(self.stat_render_time),\n            'Latency : {} frames'.format(len(self.pbos_active)),\n            'Max Latency : {} frames'.format(self.stat_max_frame_latency),\n        ))\n    \n    def setup(self, new_buffers, resolution, scene, scene_update, renderdoc_capture):\n        if self.resolution != resolution:\n            self.resolution = resolution\n            self.pbos_inactive.extend(self.pbos_active)\n            self.pbos_active = []\n            assert(new_buffers is not None)\n            if self.bit_depth == 8:\n                self.target_format = GL_UNSIGNED_BYTE\n                if glGetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_READ_PIXELS, 1) != GL_ZERO:\n                    self.target_format = glGetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_TEXTURE_IMAGE_TYPE, 1)\n                try:\n                    self.final_texture = Texture(resolution, GL_RGBA8, self.target_format, pixel_format=GL_RGBA)\n                except:\n                    # Fallback to unsigned byte, just in case\n                    self.target_format = GL_UNSIGNED_BYTE\n                    self.final_texture = Texture(resolution, GL_RGBA8, self.target_format)\n                self.final_texture.channel_size = 1\n                self.final_target = RenderTarget([self.final_texture])\n            else:\n                if self.bit_depth == 16:\n                    self.target_format = GL_RGBA16F\n                elif self.bit_depth == 32:\n                    self.target_format = GL_RGBA32F\n                self.final_texture = Texture(resolution, self.target_format)\n                self.final_target = RenderTarget([self.final_texture])\n        \n        if new_buffers:\n            self.buffers = new_buffers\n\n        self.sample_index = 0\n        self.is_new_frame = True\n        self.needs_more_samples = True\n\n        self.renderdoc_capture = renderdoc_capture\n\n        self.stat_time_start = time.perf_counter()\n        \n        if scene_update or self.scene is None:\n            for key, proxy in scene.proxys.items():\n                proxy.resolve()\n            \n            for obj in scene.objects:\n                obj.matrix = (ctypes.c_float * 16)(*obj.matrix)\n            \n            scene.batches = self.pipeline.build_scene_batches(scene.objects)\n            self.scene = scene\n        else:\n            self.scene.camera = scene.camera\n            self.scene.time = scene.time\n            self.scene.frame = scene.frame\n    \n    TO_SRGB_SHADER = None\n    def to_srgb(self, texture, target):\n        if Viewport.TO_SRGB_SHADER is None:\n            source='#include \"Passes/sRGBConversion.glsl\"'\n            Viewport.TO_SRGB_SHADER = self.pipeline.compile_shader_from_source(source)\n        Viewport.TO_SRGB_SHADER.uniforms[\"to_srgb\"].set_value(True)\n        Viewport.TO_SRGB_SHADER.textures[\"input_texture\"] = texture\n        self.pipeline.draw_screen_pass(Viewport.TO_SRGB_SHADER, target)   \n    def ensure_correct_format(self, key, texture):\n        format = GL_R32F if key == 'DEPTH' else self.target_format\n        if texture.format == format and texture.resolution == self.resolution:\n            return texture\n        target = self.final_target\n        if key != 'COLOR': #Create on the fly, since final render targets can be large\n            target = RenderTarget([Texture(self.resolution, format)])\n        if self.bit_depth == 8 and key != 'DEPTH':\n            self.to_srgb(texture, target)\n        else:\n            self.pipeline.copy_textures(target, [texture])\n        return target.targets[0]\n    \n    def render(self):\n        from . import renderdoc\n        if self.renderdoc_capture:\n            renderdoc.capture_start()\n\n        if self.needs_more_samples:\n            result = self.pipeline.render(self.resolution, self.scene, self.is_final_render, self.is_new_frame)\n            self.is_new_frame = False\n            self.needs_more_samples = self.pipeline.needs_more_samples()\n            if self.is_final_render == False or self.needs_more_samples == False:\n                pbos = None\n                if len(self.pbos_inactive) > 0:\n                    pbos = self.pbos_inactive.pop()\n                else:\n                    pbos = {}\n                for key, texture in result.items():\n                    if texture and key in self.buffers.keys():\n                        if key not in pbos.keys():\n                            pbos[key] = PBO()\n                        texture = self.ensure_correct_format(key, texture)\n                        pbos[key].setup(texture, self.buffers[key])\n                self.pbos_active.append(pbos)\n            \n        if len(self.pbos_active) > 0:\n            for i, pbos in reversed(list(enumerate(self.pbos_active))):\n                is_ready = True\n                for pbo in pbos.values():\n                    if pbo.poll() == False:\n                        is_ready = False\n                if is_ready:\n                    for pbo in pbos.values():\n                        pbo.load()\n                        self.pbos_inactive.extend(self.pbos_active[:i+1])\n                        self.pbos_active = self.pbos_active[i+1:]\n                        self.read_resolution = self.resolution\n                    break\n            \n            self.stat_render_time = time.perf_counter() - self.stat_time_start\n            self.stat_max_frame_latency = max(len(self.pbos_active), self.stat_max_frame_latency)\n        \n        if self.renderdoc_capture:\n            renderdoc.capture_end()\n            self.renderdoc_capture = False\n        \n        return self.needs_more_samples == False and len(self.pbos_active) == 0\n\n\nPROFILE = False\n\n@GLDEBUGPROC\ndef gl_debug_callback(source, type, id, severity, length, message, user_param):\n    def fmt(enum):\n        try:\n            return GL_ENUMS[enum].removeprefix(\"GL_DEBUG_\").removesuffix(\"_KHR\").removesuffix(\"_ARB\").replace(\"_\",\" \")\n        except:\n            return \"(format error)\"\n    msg = f\"OPENGL DEBUG CALLBACK ({id}):\\n{fmt(type)} > {fmt(severity)} > {fmt(source)}\\n{message.decode('utf-8')}\"\n    if type == GL_DEBUG_TYPE_ERROR:\n        LOG.error(msg)\n    elif severity == GL_DEBUG_SEVERITY_NOTIFICATION:\n        LOG.info(msg)\n    else:\n        LOG.warning(msg)\n\ndef main(pipeline_path, viewport_bit_depth, connection_addresses,\n    shared_dic, lock, log_path, debug_mode, plugins_paths, docs_path):\n    LOG.info('DEBUG MODE: {}'.format(debug_mode))\n\n    LOG.info('CONNECTIONS:')\n    connections = {}\n    for name, address in connection_addresses.items():\n        LOG.info('Name: {} Adress: {}'.format(name, address))\n        connections[name] = connection.Client(address)\n    \n    glfw.ERROR_REPORTING = True\n    glfw.init()\n\n    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4)\n    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 5)\n    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)\n    \n    if debug_mode:\n        glfw.window_hint(glfw.OPENGL_DEBUG_CONTEXT, True)\n    \n    window = glfw.create_window(256, 256, 'Malt', None, None)\n    glfw.make_context_current(window)\n    \n    if debug_mode:\n        glEnable(GL_DEBUG_OUTPUT)\n        glDebugMessageCallback(gl_debug_callback, None)\n\n    # Don't hide for better OS/Drivers schedule priority\n    #glfw.hide_window(window)\n    # Minimize instead:\n    glfw.iconify_window(window)\n\n    glfw.swap_interval(0)\n\n    log_system_info()\n    \n    LOG.info('INIT PIPELINE: ' + pipeline_path)\n\n    try:\n        pipeline_dir, pipeline_name = os.path.split(pipeline_path)\n        if pipeline_dir not in sys.path:\n            sys.path.append(pipeline_dir)\n        module_name = pipeline_name.split('.')[0]\n        module = __import__(module_name)\n\n        pipeline_class = module.PIPELINE\n        pipeline_class.SHADER_INCLUDE_PATHS.append(pipeline_dir)\n        \n        if docs_path: #build docs before loading plugins\n            from . import Docs\n            try:\n                Docs.build_docs(pipeline_class(), docs_path)\n            except:\n                import traceback\n                traceback.print_exc()\n        \n        plugins = []\n        for dir in plugins_paths:\n            plugins += load_plugins_from_dir(dir)\n        pipeline = pipeline_class(plugins)\n\n        params = pipeline.get_parameters()\n        graphs = pipeline.get_graphs()\n        outputs = pipeline.get_render_outputs()\n        connections['MAIN'].send({\n            'msg_type': 'PARAMS',\n            'params': params,\n            'graphs': graphs,\n            'outputs': outputs\n        })\n    except:\n        import traceback\n        exception = traceback.format_exc()\n        LOG.error(exception)\n\n        from Malt import PipelineParameters\n        \n        connections['MAIN'].send({\n            'msg_type': 'PARAMS',\n            'params': PipelineParameters.PipelineParameters(),\n            'graphs': {},\n            'outputs': {}\n        })\n\n    viewports = {}\n\n    while glfw.window_should_close(window) == False:\n        \n        try:\n            profiler = cProfile.Profile()\n            profiling_data = io.StringIO()\n            global PROFILE\n            if PROFILE:\n                profiler.enable()\n            \n            start_time = time.perf_counter()\n\n            glfw.poll_events()\n\n            while connections['REFLECTION'].poll():\n                msg = connections['REFLECTION'].recv()\n                if msg['msg_type'] == 'SHADER REFLECTION':\n                    LOG.debug('REFLECT SHADER : {}'.format(msg))\n                    paths = msg['paths']\n                    results = {}\n                    from Malt.GL.Shader import glsl_reflection, shader_preprocessor\n                    for path in paths:\n                        try:\n                            root_path = os.path.dirname(path)\n                            src = '#include \"{}\"\\n'.format(path)\n                            src = shader_preprocessor(src, [root_path])\n                            reflection = glsl_reflection(src, root_path)\n                            reflection['paths'] = set([path])\n                            for struct in reflection['structs'].values(): reflection['paths'].add(struct['file'])\n                            for function in reflection['functions'].values(): reflection['paths'].add(function['file'])\n                            results[path] = reflection\n                        except:\n                            results[path] = {\n                                'structs':{},\n                                'functions':{},\n                                'paths':[],\n                            }\n                            import traceback\n                            LOG.error(traceback.format_exc())\n                    connections['REFLECTION'].send(results)\n                if msg['msg_type'] == 'GRAPH RELOAD':\n                    graph_types = msg['graph_types']\n                    for type in graph_types:\n                        pipeline.graphs[type].setup_reflection()\n                    for viewport in viewports.values():\n                        viewport.pipeline.graphs = pipeline.graphs\n                    graphs = pipeline.get_graphs()\n                    connections['REFLECTION'].send(graphs)\n\n            while connections['MAIN'].poll():\n                msg = connections['MAIN'].recv()\n                \n                if msg['msg_type'] == 'MATERIAL':\n                    LOG.debug('COMPILE MATERIAL : {}'.format(msg))\n                    path = msg['path']\n                    search_paths = msg['search_paths']\n                    custom_passes = msg['custom_passes']\n                    material = Bridge.Material.Material(path, pipeline, search_paths, custom_passes)\n                    connections['MAIN'].send({\n                        'msg_type': 'MATERIAL',\n                        'material' : material\n                    })\n                \n                if msg['msg_type'] == 'MESH':\n                    msg_log = copy.copy(msg)\n                    msg_log['data'] = None\n                    LOG.debug('LOAD MESH : {}'.format(msg_log))\n                    Bridge.Mesh.load_mesh(pipeline, msg)\n                \n                if msg['msg_type'] == 'TEXTURE':\n                    LOG.debug('LOAD TEXTURE : {}'.format(msg))\n                    Bridge.Texture.load_texture(msg)\n                \n                if msg['msg_type'] == 'GRADIENT':\n                    msg_log = copy.copy(msg)\n                    msg_log['pixels'] = None\n                    LOG.debug('LOAD GRADIENT : {}'.format(msg_log))\n                    name = msg['name']\n                    pixels = msg['pixels']\n                    nearest = msg['nearest']\n                    Bridge.Texture.load_gradient(name, pixels, nearest)\n                \n                if msg['msg_type'] == 'RENDER':\n                    LOG.debug('SETUP RENDER : {}'.format(msg))\n                    viewport_id = msg['viewport_id']\n                    resolution = msg['resolution']\n                    scene = msg['scene']\n                    scene_update = msg['scene_update']\n                    new_buffers = msg['new_buffers']\n                    renderdoc_capture = msg['renderdoc_capture']\n\n                    if viewport_id not in viewports:\n                        bit_depth = viewport_bit_depth if viewport_id != 0 else 32\n                        viewports[viewport_id] = Viewport(pipeline_class(plugins), viewport_id == 0, bit_depth)\n\n                    viewports[viewport_id].setup(new_buffers, resolution, scene, scene_update, renderdoc_capture)\n                    shared_dic[(viewport_id, 'FINISHED')] = False\n                    shared_dic[(viewport_id, 'SETUP')] = True\n\n                    if viewport_id == 0: # Final Render\n                        # Render all samples at once to ensure render is done with the correct state\n                        while viewports[0].render() == False:\n                            continue\n            \n            active_viewports = {}\n            render_finished = True\n            for v_id, v in viewports.items():\n                if v.needs_more_samples:\n                    active_viewports[v_id] = v\n                has_finished = v.render()\n                if has_finished == False:\n                    render_finished = False\n                shared_dic[(v_id, 'READ_RESOLUTION')] = v.read_resolution\n                if has_finished and shared_dic[(v_id, 'FINISHED')] == False:\n                    shared_dic[(v_id, 'FINISHED')] = True\n            \n            if render_finished:\n                glfw.swap_interval(1)\n            else:\n                glfw.swap_interval(0)\n            glfw.swap_buffers(window)\n\n            if len(active_viewports) > 0:\n                stats = ''\n                for v_id, v in active_viewports.items():\n                    stats += \"Viewport ({}):\\n{}\\n\\n\".format(v_id, v.get_print_stats())\n                shared_dic['STATS'] = stats\n                LOG.debug('STATS: {} '.format(stats))\n            \n            if PROFILE:\n                profiler.disable()\n                stats = pstats.Stats(profiler, stream=profiling_data)\n                stats.strip_dirs()\n                stats.sort_stats(pstats.SortKey.CUMULATIVE)\n                stats.print_stats()\n                if active_viewports:\n                    LOG.debug(profiling_data.getvalue())\n        except (ConnectionResetError, EOFError):\n            #Connection Lost\n            break\n        except:\n            import traceback\n            LOG.error(traceback.format_exc())\n\n    glfw.terminate()\n"
  },
  {
    "path": "Bridge/Texture.py",
    "content": "from Malt.GL import Texture\nfrom Malt.GL.GL import *\n\nTEXTURES = {}\n\ndef load_texture(msg):\n    name = msg['name']\n    data = msg['buffer'].buffer()\n    resolution = msg['resolution']\n    channels = msg['channels']\n    sRGB = msg['sRGB']\n\n    internal_formats = [\n        GL_R32F,\n        GL_RG32F,\n        GL_RGB32F,\n        GL_RGBA32F,\n    ]\n    pixel_formats = [\n        GL_RED,\n        GL_RG,\n        GL_RGB,\n        GL_RGBA\n    ]\n    internal_format = internal_formats[channels-1]\n    pixel_format = pixel_formats[channels-1]\n    \n    if sRGB:\n        if channels == 4:\n            internal_format = GL_SRGB_ALPHA\n        else:\n            internal_format = GL_SRGB\n\n    #Nearest + Anisotropy seems to yield the best results with temporal super sampling\n    TEXTURES[name] = Texture.Texture(resolution, internal_format, GL_FLOAT, data, pixel_format=pixel_format, \n        wrap=GL_REPEAT, min_filter=GL_NEAREST_MIPMAP_NEAREST, build_mipmaps=True, anisotropy=True)\n\nGRADIENTS = {}\n\ndef load_gradient(name, pixels, nearest):\n    GRADIENTS[name] = Texture.Gradient(pixels, len(pixels)/4, nearest_interpolation=nearest)\n"
  },
  {
    "path": "Bridge/__init__.py",
    "content": "def reload():\n    import importlib\n    from . import Client_API, Server, Material, Mesh, Texture\n    for module in [ Client_API, Server, Material, Mesh, Texture ]:\n        importlib.reload(module)\n\ndef start_server(pipeline_path, viewport_bit_depth, connection_addresses, \n    shared_dic, lock, log_path, debug_mode, renderdoc_path, plugins_paths, docs_path):\n    import logging\n    log_level = logging.DEBUG if debug_mode else logging.INFO\n    logging.basicConfig(filename=log_path, level=log_level, format='Malt > %(message)s')\n    console_logger = logging.StreamHandler()\n    console_logger.setLevel(logging.WARNING)\n    logging.getLogger().addHandler(console_logger)\n    \n    import os, sys, ctypes\n    if sys.platform == 'win32':\n        win = ctypes.windll.kernel32\n        HIGH_PRIORITY_CLASS = 0x00000080\n        PROCESS_SET_INFORMATION = 0x0200\n        process = win.OpenProcess(PROCESS_SET_INFORMATION, 0, win.GetCurrentProcessId())\n        win.SetPriorityClass(process, HIGH_PRIORITY_CLASS)\n        win.CloseHandle(process)\n    if renderdoc_path and os.path.exists(renderdoc_path):\n        import subprocess\n        subprocess.call([renderdoc_path, 'inject', '--PID={}'.format(os.getpid())])\n\n    from . import Server\n    try:\n        Server.main(pipeline_path, viewport_bit_depth, connection_addresses,\n            shared_dic, lock, log_path, debug_mode, plugins_paths, docs_path)\n    except:\n        import traceback\n        logging.error(traceback.format_exc())\n"
  },
  {
    "path": "Bridge/ipc/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.10)\n\n# set(CMAKE_GENERATOR_PLATFORM x64)\n\nproject(Ipc)\n\nSET(CMAKE_BUILD_TYPE Release)\nSET(BUILD_SHARED_LIBS ON)\n\nadd_library(Ipc ipc.c)\n\nif(NOT WIN32)\n    target_link_libraries(Ipc pthread)\n    if(NOT APPLE)\n        target_link_libraries(Ipc rt)\n    endif()\nendif()\n\ninstall(TARGETS Ipc CONFIGURATIONS Release DESTINATION ${PROJECT_SOURCE_DIR})\n\n"
  },
  {
    "path": "Bridge/ipc/__init__.py",
    "content": "import os, ctypes, platform\n\nsrc_dir = os.path.abspath(os.path.dirname(__file__))\n\nlibrary = 'libIpc.so'\nif platform.system() == 'Windows': library = 'Ipc.dll'\nif platform.system() == 'Darwin': library = 'libIpc.dylib'\n\nIpc = ctypes.CDLL(os.path.join(src_dir, library))\n\nclass C_SharedMemory(ctypes.Structure):\n    _fields_ = [\n        ('name', ctypes.c_char_p),\n        ('data', ctypes.c_void_p),\n        ('size', ctypes.c_size_t),\n        ('handle', ctypes.c_void_p),\n        ('int', ctypes.c_int),\n    ]\n\ndef errcheck(ret, func, args):\n    if ret != 0:\n        import os\n        raise OSError(ret, os.strerror(ret))\n    return ret\n\ncreate_shared_memory = Ipc['create_shared_memory']\ncreate_shared_memory.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.POINTER(C_SharedMemory)]\ncreate_shared_memory.restype = ctypes.c_int\ncreate_shared_memory.errcheck = errcheck\n\nopen_shared_memory = Ipc['open_shared_memory']\nopen_shared_memory.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.POINTER(C_SharedMemory)]\nopen_shared_memory.restype = ctypes.c_int\nopen_shared_memory.errcheck = errcheck\n\nclose_shared_memory = Ipc['close_shared_memory']\nclose_shared_memory.argtypes = [C_SharedMemory, ctypes.c_bool]\nclose_shared_memory.restype = None\n\nfrom Malt.Utils import IBuffer\n\nclass SharedBuffer(IBuffer):\n\n    _GARBAGE = []\n    \n    @classmethod\n    def GC(cls):\n        from copy import copy\n        for buffer, release_flag in copy(cls._GARBAGE):\n            if ctypes.c_bool.from_address(release_flag.data).value == True:\n                close_shared_memory(buffer, True)\n                close_shared_memory(release_flag, True)\n                cls._GARBAGE.remove((buffer, release_flag))\n\n    def __init__(self, ctype, size):\n        import random, string\n        self._ctype = ctype\n        self._size = size\n        self.id = ''.join(random.choices(string.ascii_letters + string.digits, k=16))\n        self._buffer = C_SharedMemory()\n        create_shared_memory(('MALT_SHARED_'+self.id).encode('ascii'), self.size_in_bytes(), ctypes.byref(self._buffer))\n        self._release_flag = C_SharedMemory()\n        create_shared_memory(('MALT_FLAG_'+self.id).encode('ascii'), ctypes.sizeof(ctypes.c_bool), ctypes.byref(self._release_flag))\n        ctypes.c_bool.from_address(self._release_flag.data).value = True\n        self._is_owner = True\n    \n    def ctype(self):\n        return self._ctype\n    \n    def __len__(self):\n        return self._size\n    \n    def buffer(self):\n        return (self._ctype*self._size).from_address(self._buffer.data)\n    \n    def __getstate__(self):\n        assert(self._is_owner)\n        ctypes.c_bool.from_address(self._release_flag.data).value = False\n        state = self.__dict__.copy()\n        state['_buffer'] = None\n        state['_release_flag'] = None\n        return state\n\n    def __setstate__(self, state):\n        self.__dict__.update(state)\n        self._is_owner = False\n        self._buffer = C_SharedMemory()\n        open_shared_memory(('MALT_SHARED_'+self.id).encode('ascii'), self.size_in_bytes(), ctypes.byref(self._buffer))\n        self._release_flag = C_SharedMemory()\n        open_shared_memory(('MALT_FLAG_'+self.id).encode('ascii'), ctypes.sizeof(ctypes.c_bool), ctypes.byref(self._release_flag))\n\n    def __del__(self):\n        if self._is_owner == False or ctypes.c_bool.from_address(self._release_flag.data).value == True:\n            ctypes.c_bool.from_address(self._release_flag.data).value = True\n            close_shared_memory(self._buffer, self._is_owner)\n            close_shared_memory(self._release_flag, self._is_owner)\n        else:\n            buffer_copy = C_SharedMemory()\n            ctypes.memmove(ctypes.addressof(buffer_copy), ctypes.addressof(self._buffer), ctypes.sizeof(C_SharedMemory))\n            flag_copy = C_SharedMemory()\n            ctypes.memmove(ctypes.addressof(flag_copy), ctypes.addressof(self._release_flag), ctypes.sizeof(C_SharedMemory))\n            self._GARBAGE.append((buffer_copy, flag_copy))\n            self.GC()\n"
  },
  {
    "path": "Bridge/ipc/build.py",
    "content": "import subprocess\nimport os\nimport platform\n\nsrc_dir = os.path.abspath(os.path.dirname(__file__))\nbuild_dir = os.path.join(src_dir, '.build') \n\ntry: os.mkdir(build_dir)\nexcept: pass\n\nif platform.system() == 'Windows': #Multi-config generators, like Visual Studio\n    subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir)\n    subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir)\nelse: #Single-config generators\n    subprocess.check_call(['cmake', '..'], cwd=build_dir)\n    subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir)\n\nsubprocess.check_call(['cmake', '--install', '.'], cwd=build_dir)\n"
  },
  {
    "path": "Bridge/ipc/ipc.c",
    "content": "#ifdef _WIN32\n#define EXPORT __declspec( dllexport )\n#else\n#define EXPORT __attribute__ ((visibility (\"default\")))\n#endif\n\n#define IPC_IMPLEMENTATION\n#include \"ipc.h\"\n\nEXPORT int create_shared_memory(char* name, size_t size, ipc_sharedmemory* mem)\n{\n    ipc_mem_init(mem, name, size);\n    if (ipc_mem_create(mem) != 0) {\n        return errno;\n    }\n    return 0;\n}\n\nEXPORT int open_shared_memory(char* name, size_t size, ipc_sharedmemory* mem)\n{\n    ipc_mem_init(mem, name, size);\n    if (ipc_mem_open_existing(mem) != 0) {\n        return errno;\n    }\n    return 0;\n}\n\nEXPORT void close_shared_memory(ipc_sharedmemory  mem, bool release)\n{\n    ipc_mem_close(&mem, release);\n}\n"
  },
  {
    "path": "Bridge/ipc/ipc.h",
    "content": "/*  ipc.h - v0.2 - public domain cross platform inter process communication\n                   no warranty implied; use at your own risk\n\n    by Jari Komppa, http://iki.fi/sol/\n\nINCLUDE\n\n    Do this:\n       #define IPC_IMPLEMENTATION\n    before you include this file in *one* C or C++ file to create the implementation.\n\n    // i.e. it should look like this:\n    #include ...\n    #include ...\n    #include ...\n    #define IPC_IMPLEMENTATION\n    #include \"ipc.h\"\n\n    You can #define IPC_MALLOC, and IPC_FREE to avoid using malloc,free\n\nLICENSE\n\n    This is free and unencumbered software released into the public domain.\n\n    Anyone is free to copy, modify, publish, use, compile, sell, or\n    distribute this software, either in source code form or as a compiled\n    binary, for any purpose, commercial or non-commercial, and by any\n    means.\n\n    In jurisdictions that recognize copyright laws, the author or authors\n    of this software dedicate any and all copyright interest in the\n    software to the public domain. We make this dedication for the benefit\n    of the public at large and to the detriment of our heirs and\n    successors. We intend this dedication to be an overt act of\n    relinquishment in perpetuity of all present and future rights to this\n    software under copyright law.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n    IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n    OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n    OTHER DEALINGS IN THE SOFTWARE.\n\n    For more information, please refer to <http://unlicense.org/>\n\nUSAGE\n\n    Shared memory API:\n\n    void ipc_mem_init(ipc_sharedmemory *mem, unsigned char *name, size_t size);\n    - Initialize ipc_sharedmemory structure for use. Call this first.\n\n    int ipc_mem_open_existing(ipc_sharedmemory *mem);\n    - Try to open existing shared memory. Returns 0 for success.\n\n    int ipc_mem_create(ipc_sharedmemory *mem);\n    - Try to create shared memory. Returns 0 for success.\n\n    void ipc_mem_close(ipc_sharedmemory *mem);\n    - Close shared memory and free allocated stuff. Shared memory will only get\n      destroyed when nobody is accessing it. Call this last.\n\n    unsigned char *ipc_mem_access(ipc_sharedmemory *mem);\n    - Access the shared memory. Same as poking mem->data directly, but some people\n      like accessors.\n\n\n\n    Shared semaphore API:\n\n    void ipc_sem_init(ipc_sharedsemaphore *sem, unsigned char *name);\n    - Initialize ipc_sharedsemaphore for use. Call this first.\n\n    int ipc_sem_create(ipc_sharedsemaphore *sem, int initialvalue);\n    - Try to create semaphore. Returns 0 for success.\n\n    void ipc_sem_close(ipc_sharedsemaphore *sem);\n    - Close the semaphore and deallocate. Shared semaphore will only go away after\n      nobody is using it. Call this last.\n\n    void ipc_sem_increment(ipc_sharedsemaphore *sem);\n    - Increment the semaphore.\n\n    void ipc_sem_decrement(ipc_sharedsemaphore *sem);\n    - Decrement the semaphore, waiting forever if need be.\n\n    int ipc_sem_try_decrement(ipc_sharedsemaphore *sem);\n    - Try to decrement the semaphore, returns 1 for success, 0 for failure.\n\nTROUBLESHOOTING\n\n    - Thread programming issues apply; don't mess with the memory someone else might\n      be reading, make sure you release semaphore after use not to hang someone else,\n      etc.\n    - If process crashes while holding a semaphore, the others may end up waiting\n      forever for them to release.\n    - On Linux, named items will remain after your application closes unless you\n      close them. This is particularly fun if your application crashes. Having a\n      commandline mode that just tries to create and then close all your shared\n      resources may be convenient. Saves on rebooting, at least.. On windows, if\n      nobody is around to use the resource, they disappear.\n*/\n\n#ifndef IPC_H_INCLUDE_GUARD\n#define IPC_H_INCLUDE_GUARD\n\n#include <stdbool.h>\n\ntypedef void* HANDLE;\n#ifndef _WIN32\n#include <semaphore.h>\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct ipc_sharedmemory_\n{\n    char *name;\n    unsigned char *data;\n    size_t        size;\n    HANDLE        handle;\n    int           fd;\n} ipc_sharedmemory;\n\nextern void ipc_mem_init(ipc_sharedmemory *mem, char *name, size_t size);\nextern int ipc_mem_open_existing(ipc_sharedmemory *mem);\nextern int ipc_mem_create(ipc_sharedmemory *mem);\nextern void ipc_mem_close(ipc_sharedmemory *mem, bool unlink);\nextern unsigned char *ipc_mem_access(ipc_sharedmemory *mem);\n\ntypedef struct ipc_sharedsemaphore_\n{\n    char *name;\n#if defined(_WIN32)\n    HANDLE        handle;\n#else\n    sem_t         *semaphore;\n#endif\n} ipc_sharedsemaphore;\n\nextern void ipc_sem_init(ipc_sharedsemaphore *sem, char *name);\nextern int ipc_sem_create(ipc_sharedsemaphore *sem, int initialvalue);\nextern void ipc_sem_close(ipc_sharedsemaphore *sem);\nextern void ipc_sem_increment(ipc_sharedsemaphore *sem);\nextern void ipc_sem_decrement(ipc_sharedsemaphore *sem);\nextern int ipc_sem_try_decrement(ipc_sharedsemaphore *sem);\n\n#ifdef __cplusplus\n}\n#endif\n\n//////// end of header ////////\n\n#ifdef IPC_IMPLEMENTATION\n\n#if defined(_WIN32)\n#include <windows.h>\n#else // !_WIN32\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <errno.h>\n#include <stdlib.h>\n#endif // !_WIN32\n\n#ifndef IPC_ASSERT\n#define IPC_ASSERT(x) assert(x)\n#endif\n\n#ifndef IPC_MALLOC\n#define IPC_MALLOC(x) malloc(x)\n#endif\n\n#ifndef IPC_FREE\n#define IPC_FREE(x) free(x)\n#endif\n\nstatic char * ipc_strdup(char *src)\n{\n    int i, len;\n    char *dst = NULL;\n    len = 0;\n    while (src[len]) len++;\n#if !defined(_WIN32)\n    len++;\n#endif\n    dst = (char *)IPC_MALLOC(len+1);\n    if (!dst) return NULL;\n    dst[len] = 0;\n\n#if defined(_WIN32)\n    for (i = 0; i < len; i++)\n        dst[i] = src[i];\n#else\n    dst[0] = '/';\n    for (i = 0; i < len-1; i++)\n        dst[i+1] = src[i];\n#endif\n    return dst;\n}\n\nvoid ipc_mem_init(ipc_sharedmemory *mem, char *name, size_t size)\n{\n    mem->name = ipc_strdup(name);\n\n    mem->size = size;\n    mem->data = NULL;\n#if defined(_WIN32)\n    mem->handle = 0;\n#else\n    mem->fd = -1;\n#endif\n}\n\nunsigned char *ipc_mem_access(ipc_sharedmemory *mem)\n{\n    return mem->data;\n}\n\nvoid ipc_sem_init(ipc_sharedsemaphore *sem, char *name)\n{\n    sem->name = ipc_strdup(name);\n#if defined(_WIN32)\n    sem->handle = 0;\n#else\n    sem->semaphore = NULL;\n#endif\n\n}\n\n\n#if defined(_WIN32)\n\nint ipc_mem_open_existing(ipc_sharedmemory *mem)\n{\n    mem->handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE,  mem->name);\n\n    if (!mem->handle)\n        return -1;\n\n    mem->data = (unsigned char*)MapViewOfFile(mem->handle, FILE_MAP_ALL_ACCESS, 0, 0, mem->size);\n\n    if (!mem->data)\n        return -1;\n    return 0;\n}\n\nint ipc_mem_create(ipc_sharedmemory *mem)\n{\n    mem->handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, mem->size, mem->name);\n\n    if (!mem->handle)\n        return -1;\n\n    mem->data = (unsigned char*)MapViewOfFile(mem->handle, FILE_MAP_ALL_ACCESS, 0, 0, mem->size);\n\n    if (!mem->data)\n        return -1;\n\n    return 0;\n\n}\n\nvoid ipc_mem_close(ipc_sharedmemory *mem, bool unlink)\n{\n    if (mem->data != NULL)\n    {\n        UnmapViewOfFile(mem->data);\n        mem->data = NULL;\n    }\n    IPC_FREE(mem->name);\n    mem->name = NULL;\n    mem->size = 0;\n}\n\nint ipc_sem_create(ipc_sharedsemaphore *sem, int initialvalue)\n{\n    sem->handle = CreateSemaphoreA(NULL, initialvalue, 0x7fffffff, sem->name);\n    if (!sem->handle)\n        return -1;\n    return 0;\n}\n\nvoid ipc_sem_close(ipc_sharedsemaphore *sem)\n{\n    CloseHandle(sem->handle);\n    IPC_FREE(sem->name);\n    sem->handle = 0;\n}\n\nvoid ipc_sem_increment(ipc_sharedsemaphore *sem)\n{\n    ReleaseSemaphore(sem->handle, 1, NULL);\n}\n\nvoid ipc_sem_decrement(ipc_sharedsemaphore *sem)\n{\n    WaitForSingleObject(sem->handle, INFINITE);\n}\n\nint ipc_sem_try_decrement(ipc_sharedsemaphore *sem)\n{\n    DWORD ret = WaitForSingleObject(sem->handle, 0);\n    if (ret == WAIT_OBJECT_0)\n        return 1;\n    return 0;\n}\n\n#else // !defined(_WIN32)\n\nint ipc_mem_open_existing(ipc_sharedmemory *mem)\n{\n    mem->fd = shm_open(mem->name, O_RDWR, 0755);\n    if (mem->fd < 0)\n        return -1;\n\n    mem->data = (unsigned char *)mmap(NULL, mem->size, PROT_READ | PROT_WRITE, MAP_SHARED, mem->fd, 0);\n    if (!mem->data)\n        return -1;\n\n    // file descriptor can close after mmap\n    close(mem->fd);\n\n    return 0;\n}\n\nint ipc_mem_create(ipc_sharedmemory *mem)\n{\n    int ret;\n    ret = shm_unlink(mem->name);\n    if (ret < 0 && errno != ENOENT)\n        return -1;\n\n    mem->fd = shm_open(mem->name, O_CREAT | O_RDWR, 0755);\n    if (mem->fd < 0)\n        return -1;\n\n    ftruncate(mem->fd, mem->size);\n\n    mem->data = (unsigned char *)mmap(NULL, mem->size, PROT_READ | PROT_WRITE, MAP_SHARED, mem->fd, 0);\n    if (!mem->data)\n        return -1;\n\n    // file descriptor can close after mmap\n    close(mem->fd);\n\n    return 0;\n}\n\nvoid ipc_mem_close(ipc_sharedmemory *mem, bool unlink)\n{\n    if (mem->data != NULL)\n    {\n        munmap(mem->data, mem->size);\n        // close(mem->fd);\n        if (unlink) shm_unlink(mem->name);\n    }\n    IPC_FREE(mem->name);\n    mem->name = NULL;\n    mem->size = 0;\n}\n\nint ipc_sem_create(ipc_sharedsemaphore *sem, int initialvalue)\n{\n    sem->semaphore = sem_open(sem->name, O_CREAT, 0700, initialvalue);\n    if (sem->semaphore == SEM_FAILED)\n        return -1;\n    return 0;\n}\n\nvoid ipc_sem_close(ipc_sharedsemaphore *sem)\n{\n    sem_close(sem->semaphore);\n    sem_unlink(sem->name);\n    IPC_FREE(sem->name);\n}\n\nvoid ipc_sem_increment(ipc_sharedsemaphore *sem)\n{\n    sem_post(sem->semaphore);\n}\n\nvoid ipc_sem_decrement(ipc_sharedsemaphore *sem)\n{\n    sem_wait(sem->semaphore);\n}\n\nint ipc_sem_try_decrement(ipc_sharedsemaphore *sem)\n{\n    int res = sem_trywait(sem->semaphore);\n    if (res == 0)\n        return 1;\n    return 0;\n}\n\n#endif // !_WIN32\n\n#endif // IPC_IMPLEMENTATION\n\n#endif // IPC_H_INCLUDE_GUARD\n"
  },
  {
    "path": "Bridge/renderdoc/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.10)\n\n# set(CMAKE_GENERATOR_PLATFORM x64)\n\nproject(RenderDocWrapper)\n\nSET(CMAKE_BUILD_TYPE Release)\nSET(BUILD_SHARED_LIBS ON)\n\nadd_library(renderdoc_wrapper renderdoc_wrapper.c)\n\nif(NOT WIN32)\n    target_link_libraries(renderdoc_wrapper dl)\nendif()\n\ninstall(TARGETS renderdoc_wrapper CONFIGURATIONS Release DESTINATION ${PROJECT_SOURCE_DIR})\n\n"
  },
  {
    "path": "Bridge/renderdoc/__init__.py",
    "content": "import subprocess\nimport os\nimport ctypes\n\nimport platform\n\nsrc_dir = os.path.abspath(os.path.dirname(__file__))\n\nlibrary = 'librenderdoc_wrapper.so'\nif platform.system() == 'Windows': library = 'renderdoc_wrapper.dll'\nif platform.system() == 'Darwin': library = 'librenderdoc_wrapper.dylib'\n\nrenderdoc = ctypes.CDLL(os.path.join(src_dir, library))\n\ncapture_start = renderdoc['capture_start']\ncapture_start.restype = None\n\ncapture_end = renderdoc['capture_end']\ncapture_end.restype = None\n"
  },
  {
    "path": "Bridge/renderdoc/build.py",
    "content": "import subprocess\nimport os\nimport platform\n\nsrc_dir = os.path.abspath(os.path.dirname(__file__))\nbuild_dir = os.path.join(src_dir, '.build') \n\ntry: os.mkdir(build_dir)\nexcept: pass\n\nif platform.system() == 'Windows': #Multi-config generators, like Visual Studio\n    subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir)\n    subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir)\nelse: #Single-config generators\n    subprocess.check_call(['cmake', '..'], cwd=build_dir)\n    subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir)\n\nsubprocess.check_call(['cmake', '--install', '.'], cwd=build_dir)\n"
  },
  {
    "path": "Bridge/renderdoc/renderdoc_app.h",
    "content": "/******************************************************************************\n * The MIT License (MIT)\n *\n * Copyright (c) 2019-2021 Baldur Karlsson\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n ******************************************************************************/\n\n#pragma once\n\n//////////////////////////////////////////////////////////////////////////////////////////////////\n//\n// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html\n//\n\n#if !defined(RENDERDOC_NO_STDINT)\n#include <stdint.h>\n#endif\n\n#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)\n#define RENDERDOC_CC __cdecl\n#elif defined(__linux__)\n#define RENDERDOC_CC\n#elif defined(__APPLE__)\n#define RENDERDOC_CC\n#else\n#error \"Unknown platform\"\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n//////////////////////////////////////////////////////////////////////////////////////////////////\n// Constants not used directly in below API\n\n// This is a GUID/magic value used for when applications pass a path where shader debug\n// information can be found to match up with a stripped shader.\n// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =\n// RENDERDOC_ShaderDebugMagicValue_value\n#define RENDERDOC_ShaderDebugMagicValue_struct                                \\\n  {                                                                           \\\n    0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \\\n  }\n\n// as an alternative when you want a byte array (assuming x86 endianness):\n#define RENDERDOC_ShaderDebugMagicValue_bytearray                                                 \\\n  {                                                                                               \\\n    0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \\\n  }\n\n// truncated version when only a uint64_t is available (e.g. Vulkan tags):\n#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL\n\n//////////////////////////////////////////////////////////////////////////////////////////////////\n// RenderDoc capture options\n//\n\ntypedef enum RENDERDOC_CaptureOption {\n  // Allow the application to enable vsync\n  //\n  // Default - enabled\n  //\n  // 1 - The application can enable or disable vsync at will\n  // 0 - vsync is force disabled\n  eRENDERDOC_Option_AllowVSync = 0,\n\n  // Allow the application to enable fullscreen\n  //\n  // Default - enabled\n  //\n  // 1 - The application can enable or disable fullscreen at will\n  // 0 - fullscreen is force disabled\n  eRENDERDOC_Option_AllowFullscreen = 1,\n\n  // Record API debugging events and messages\n  //\n  // Default - disabled\n  //\n  // 1 - Enable built-in API debugging features and records the results into\n  //     the capture, which is matched up with events on replay\n  // 0 - no API debugging is forcibly enabled\n  eRENDERDOC_Option_APIValidation = 2,\n  eRENDERDOC_Option_DebugDeviceMode = 2,    // deprecated name of this enum\n\n  // Capture CPU callstacks for API events\n  //\n  // Default - disabled\n  //\n  // 1 - Enables capturing of callstacks\n  // 0 - no callstacks are captured\n  eRENDERDOC_Option_CaptureCallstacks = 3,\n\n  // When capturing CPU callstacks, only capture them from drawcalls.\n  // This option does nothing without the above option being enabled\n  //\n  // Default - disabled\n  //\n  // 1 - Only captures callstacks for drawcall type API events.\n  //     Ignored if CaptureCallstacks is disabled\n  // 0 - Callstacks, if enabled, are captured for every event.\n  eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,\n\n  // Specify a delay in seconds to wait for a debugger to attach, after\n  // creating or injecting into a process, before continuing to allow it to run.\n  //\n  // 0 indicates no delay, and the process will run immediately after injection\n  //\n  // Default - 0 seconds\n  //\n  eRENDERDOC_Option_DelayForDebugger = 5,\n\n  // Verify buffer access. This includes checking the memory returned by a Map() call to\n  // detect any out-of-bounds modification, as well as initialising buffers with undefined contents\n  // to a marker value to catch use of uninitialised memory.\n  //\n  // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do\n  // not do the same kind of interception & checking and undefined contents are really undefined.\n  //\n  // Default - disabled\n  //\n  // 1 - Verify buffer access\n  // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in\n  //     RenderDoc.\n  eRENDERDOC_Option_VerifyBufferAccess = 6,\n\n  // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.\n  // This option now controls the filling of uninitialised buffers with 0xdddddddd which was\n  // previously always enabled\n  eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,\n\n  // Hooks any system API calls that create child processes, and injects\n  // RenderDoc into them recursively with the same options.\n  //\n  // Default - disabled\n  //\n  // 1 - Hooks into spawned child processes\n  // 0 - Child processes are not hooked by RenderDoc\n  eRENDERDOC_Option_HookIntoChildren = 7,\n\n  // By default RenderDoc only includes resources in the final capture necessary\n  // for that frame, this allows you to override that behaviour.\n  //\n  // Default - disabled\n  //\n  // 1 - all live resources at the time of capture are included in the capture\n  //     and available for inspection\n  // 0 - only the resources referenced by the captured frame are included\n  eRENDERDOC_Option_RefAllResources = 8,\n\n  // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or\n  // getting it will be ignored, to allow compatibility with older versions.\n  // In v1.1 the option acts as if it's always enabled.\n  //\n  // By default RenderDoc skips saving initial states for resources where the\n  // previous contents don't appear to be used, assuming that writes before\n  // reads indicate previous contents aren't used.\n  //\n  // Default - disabled\n  //\n  // 1 - initial contents at the start of each captured frame are saved, even if\n  //     they are later overwritten or cleared before being used.\n  // 0 - unless a read is detected, initial contents will not be saved and will\n  //     appear as black or empty data.\n  eRENDERDOC_Option_SaveAllInitials = 9,\n\n  // In APIs that allow for the recording of command lists to be replayed later,\n  // RenderDoc may choose to not capture command lists before a frame capture is\n  // triggered, to reduce overheads. This means any command lists recorded once\n  // and replayed many times will not be available and may cause a failure to\n  // capture.\n  //\n  // NOTE: This is only true for APIs where multithreading is difficult or\n  // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option\n  // and always capture all command lists since the API is heavily oriented\n  // around it and the overheads have been reduced by API design.\n  //\n  // 1 - All command lists are captured from the start of the application\n  // 0 - Command lists are only captured if their recording begins during\n  //     the period when a frame capture is in progress.\n  eRENDERDOC_Option_CaptureAllCmdLists = 10,\n\n  // Mute API debugging output when the API validation mode option is enabled\n  //\n  // Default - enabled\n  //\n  // 1 - Mute any API debug messages from being displayed or passed through\n  // 0 - API debugging is displayed as normal\n  eRENDERDOC_Option_DebugOutputMute = 11,\n\n  // Option to allow vendor extensions to be used even when they may be\n  // incompatible with RenderDoc and cause corrupted replays or crashes.\n  //\n  // Default - inactive\n  //\n  // No values are documented, this option should only be used when absolutely\n  // necessary as directed by a RenderDoc developer.\n  eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,\n\n} RENDERDOC_CaptureOption;\n\n// Sets an option that controls how RenderDoc behaves on capture.\n//\n// Returns 1 if the option and value are valid\n// Returns 0 if either is invalid and the option is unchanged\ntypedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);\ntypedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);\n\n// Gets the current value of an option as a uint32_t\n//\n// If the option is invalid, 0xffffffff is returned\ntypedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);\n\n// Gets the current value of an option as a float\n//\n// If the option is invalid, -FLT_MAX is returned\ntypedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);\n\ntypedef enum RENDERDOC_InputButton {\n  // '0' - '9' matches ASCII values\n  eRENDERDOC_Key_0 = 0x30,\n  eRENDERDOC_Key_1 = 0x31,\n  eRENDERDOC_Key_2 = 0x32,\n  eRENDERDOC_Key_3 = 0x33,\n  eRENDERDOC_Key_4 = 0x34,\n  eRENDERDOC_Key_5 = 0x35,\n  eRENDERDOC_Key_6 = 0x36,\n  eRENDERDOC_Key_7 = 0x37,\n  eRENDERDOC_Key_8 = 0x38,\n  eRENDERDOC_Key_9 = 0x39,\n\n  // 'A' - 'Z' matches ASCII values\n  eRENDERDOC_Key_A = 0x41,\n  eRENDERDOC_Key_B = 0x42,\n  eRENDERDOC_Key_C = 0x43,\n  eRENDERDOC_Key_D = 0x44,\n  eRENDERDOC_Key_E = 0x45,\n  eRENDERDOC_Key_F = 0x46,\n  eRENDERDOC_Key_G = 0x47,\n  eRENDERDOC_Key_H = 0x48,\n  eRENDERDOC_Key_I = 0x49,\n  eRENDERDOC_Key_J = 0x4A,\n  eRENDERDOC_Key_K = 0x4B,\n  eRENDERDOC_Key_L = 0x4C,\n  eRENDERDOC_Key_M = 0x4D,\n  eRENDERDOC_Key_N = 0x4E,\n  eRENDERDOC_Key_O = 0x4F,\n  eRENDERDOC_Key_P = 0x50,\n  eRENDERDOC_Key_Q = 0x51,\n  eRENDERDOC_Key_R = 0x52,\n  eRENDERDOC_Key_S = 0x53,\n  eRENDERDOC_Key_T = 0x54,\n  eRENDERDOC_Key_U = 0x55,\n  eRENDERDOC_Key_V = 0x56,\n  eRENDERDOC_Key_W = 0x57,\n  eRENDERDOC_Key_X = 0x58,\n  eRENDERDOC_Key_Y = 0x59,\n  eRENDERDOC_Key_Z = 0x5A,\n\n  // leave the rest of the ASCII range free\n  // in case we want to use it later\n  eRENDERDOC_Key_NonPrintable = 0x100,\n\n  eRENDERDOC_Key_Divide,\n  eRENDERDOC_Key_Multiply,\n  eRENDERDOC_Key_Subtract,\n  eRENDERDOC_Key_Plus,\n\n  eRENDERDOC_Key_F1,\n  eRENDERDOC_Key_F2,\n  eRENDERDOC_Key_F3,\n  eRENDERDOC_Key_F4,\n  eRENDERDOC_Key_F5,\n  eRENDERDOC_Key_F6,\n  eRENDERDOC_Key_F7,\n  eRENDERDOC_Key_F8,\n  eRENDERDOC_Key_F9,\n  eRENDERDOC_Key_F10,\n  eRENDERDOC_Key_F11,\n  eRENDERDOC_Key_F12,\n\n  eRENDERDOC_Key_Home,\n  eRENDERDOC_Key_End,\n  eRENDERDOC_Key_Insert,\n  eRENDERDOC_Key_Delete,\n  eRENDERDOC_Key_PageUp,\n  eRENDERDOC_Key_PageDn,\n\n  eRENDERDOC_Key_Backspace,\n  eRENDERDOC_Key_Tab,\n  eRENDERDOC_Key_PrtScrn,\n  eRENDERDOC_Key_Pause,\n\n  eRENDERDOC_Key_Max,\n} RENDERDOC_InputButton;\n\n// Sets which key or keys can be used to toggle focus between multiple windows\n//\n// If keys is NULL or num is 0, toggle keys will be disabled\ntypedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);\n\n// Sets which key or keys can be used to capture the next frame\n//\n// If keys is NULL or num is 0, captures keys will be disabled\ntypedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);\n\ntypedef enum RENDERDOC_OverlayBits {\n  // This single bit controls whether the overlay is enabled or disabled globally\n  eRENDERDOC_Overlay_Enabled = 0x1,\n\n  // Show the average framerate over several seconds as well as min/max\n  eRENDERDOC_Overlay_FrameRate = 0x2,\n\n  // Show the current frame number\n  eRENDERDOC_Overlay_FrameNumber = 0x4,\n\n  // Show a list of recent captures, and how many captures have been made\n  eRENDERDOC_Overlay_CaptureList = 0x8,\n\n  // Default values for the overlay mask\n  eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |\n                                eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),\n\n  // Enable all bits\n  eRENDERDOC_Overlay_All = ~0U,\n\n  // Disable all bits\n  eRENDERDOC_Overlay_None = 0,\n} RENDERDOC_OverlayBits;\n\n// returns the overlay bits that have been set\ntypedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)();\n// sets the overlay bits with an and & or mask\ntypedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);\n\n// this function will attempt to remove RenderDoc's hooks in the application.\n//\n// Note: that this can only work correctly if done immediately after\n// the module is loaded, before any API work happens. RenderDoc will remove its\n// injected hooks and shut down. Behaviour is undefined if this is called\n// after any API functions have been called, and there is still no guarantee of\n// success.\ntypedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)();\n\n// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.\ntypedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;\n\n// This function will unload RenderDoc's crash handler.\n//\n// If you use your own crash handler and don't want RenderDoc's handler to\n// intercede, you can call this function to unload it and any unhandled\n// exceptions will pass to the next handler.\ntypedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)();\n\n// Sets the capture file path template\n//\n// pathtemplate is a UTF-8 string that gives a template for how captures will be named\n// and where they will be saved.\n//\n// Any extension is stripped off the path, and captures are saved in the directory\n// specified, and named with the filename and the frame number appended. If the\n// directory does not exist it will be created, including any parent directories.\n//\n// If pathtemplate is NULL, the template will remain unchanged\n//\n// Example:\n//\n// SetCaptureFilePathTemplate(\"my_captures/example\");\n//\n// Capture #1 -> my_captures/example_frame123.rdc\n// Capture #2 -> my_captures/example_frame456.rdc\ntypedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);\n\n// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string\ntypedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)();\n\n// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.\ntypedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;\ntypedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;\n\n// returns the number of captures that have been made\ntypedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)();\n\n// This function returns the details of a capture, by index. New captures are added\n// to the end of the list.\n//\n// filename will be filled with the absolute path to the capture file, as a UTF-8 string\n// pathlength will be written with the length in bytes of the filename string\n// timestamp will be written with the time of the capture, in seconds since the Unix epoch\n//\n// Any of the parameters can be NULL and they'll be skipped.\n//\n// The function will return 1 if the capture index is valid, or 0 if the index is invalid\n// If the index is invalid, the values will be unchanged\n//\n// Note: when captures are deleted in the UI they will remain in this list, so the\n// capture path may not exist anymore.\ntypedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,\n                                                      uint32_t *pathlength, uint64_t *timestamp);\n\n// Sets the comments associated with a capture file. These comments are displayed in the\n// UI program when opening.\n//\n// filePath should be a path to the capture file to add comments to. If set to NULL or \"\"\n// the most recent capture file created made will be used instead.\n// comments should be a NULL-terminated UTF-8 string to add as comments.\n//\n// Any existing comments will be overwritten.\ntypedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,\n                                                              const char *comments);\n\n// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise\ntypedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)();\n\n// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.\n// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for\n// backwards compatibility with old code, it is castable either way since it's ABI compatible\n// as the same function pointer type.\ntypedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;\n\n// This function will launch the Replay UI associated with the RenderDoc library injected\n// into the running application.\n//\n// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter\n// to connect to this application\n// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open\n// if cmdline is NULL, the command line will be empty.\n//\n// returns the PID of the replay UI if successful, 0 if not successful.\ntypedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,\n                                                          const char *cmdline);\n\n// RenderDoc can return a higher version than requested if it's backwards compatible,\n// this function returns the actual version returned. If a parameter is NULL, it will be\n// ignored and the others will be filled out.\ntypedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);\n\n//////////////////////////////////////////////////////////////////////////\n// Capturing functions\n//\n\n// A device pointer is a pointer to the API's root handle.\n//\n// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc\ntypedef void *RENDERDOC_DevicePointer;\n\n// A window handle is the OS's native window handle\n//\n// This would be an HWND, GLXDrawable, etc\ntypedef void *RENDERDOC_WindowHandle;\n\n// A helper macro for Vulkan, where the device handle cannot be used directly.\n//\n// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.\n//\n// Specifically, the value needed is the dispatch table pointer, which sits as the first\n// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and\n// indirect once.\n#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))\n\n// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will\n// respond to keypresses. Neither parameter can be NULL\ntypedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,\n                                                       RENDERDOC_WindowHandle wndHandle);\n\n// capture the next frame on whichever window and API is currently considered active\ntypedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)();\n\n// capture the next N frames on whichever window and API is currently considered active\ntypedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);\n\n// When choosing either a device pointer or a window handle to capture, you can pass NULL.\n// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify\n// any API rendering to a specific window, or a specific API instance rendering to any window,\n// or in the simplest case of one window and one API, you can just pass NULL for both.\n//\n// In either case, if there are two or more possible matching (device,window) pairs it\n// is undefined which one will be captured.\n//\n// Note: for headless rendering you can pass NULL for the window handle and either specify\n// a device pointer or leave it NULL as above.\n\n// Immediately starts capturing API calls on the specified device pointer and window handle.\n//\n// If there is no matching thing to capture (e.g. no supported API has been initialised),\n// this will do nothing.\n//\n// The results are undefined (including crashes) if two captures are started overlapping,\n// even on separate devices and/oror windows.\ntypedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,\n                                                         RENDERDOC_WindowHandle wndHandle);\n\n// Returns whether or not a frame capture is currently ongoing anywhere.\n//\n// This will return 1 if a capture is ongoing, and 0 if there is no capture running\ntypedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)();\n\n// Ends capturing immediately.\n//\n// This will return 1 if the capture succeeded, and 0 if there was an error capturing.\ntypedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,\n                                                           RENDERDOC_WindowHandle wndHandle);\n\n// Ends capturing immediately and discard any data stored without saving to disk.\n//\n// This will return 1 if the capture was discarded, and 0 if there was an error or no capture\n// was in progress\ntypedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,\n                                                               RENDERDOC_WindowHandle wndHandle);\n\n//////////////////////////////////////////////////////////////////////////////////////////////////\n// RenderDoc API versions\n//\n\n// RenderDoc uses semantic versioning (http://semver.org/).\n//\n// MAJOR version is incremented when incompatible API changes happen.\n// MINOR version is incremented when functionality is added in a backwards-compatible manner.\n// PATCH version is incremented when backwards-compatible bug fixes happen.\n//\n// Note that this means the API returned can be higher than the one you might have requested.\n// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned\n// instead of 1.0.0. You can check this with the GetAPIVersion entry point\ntypedef enum RENDERDOC_Version {\n  eRENDERDOC_API_Version_1_0_0 = 10000,    // RENDERDOC_API_1_0_0 = 1 00 00\n  eRENDERDOC_API_Version_1_0_1 = 10001,    // RENDERDOC_API_1_0_1 = 1 00 01\n  eRENDERDOC_API_Version_1_0_2 = 10002,    // RENDERDOC_API_1_0_2 = 1 00 02\n  eRENDERDOC_API_Version_1_1_0 = 10100,    // RENDERDOC_API_1_1_0 = 1 01 00\n  eRENDERDOC_API_Version_1_1_1 = 10101,    // RENDERDOC_API_1_1_1 = 1 01 01\n  eRENDERDOC_API_Version_1_1_2 = 10102,    // RENDERDOC_API_1_1_2 = 1 01 02\n  eRENDERDOC_API_Version_1_2_0 = 10200,    // RENDERDOC_API_1_2_0 = 1 02 00\n  eRENDERDOC_API_Version_1_3_0 = 10300,    // RENDERDOC_API_1_3_0 = 1 03 00\n  eRENDERDOC_API_Version_1_4_0 = 10400,    // RENDERDOC_API_1_4_0 = 1 04 00\n  eRENDERDOC_API_Version_1_4_1 = 10401,    // RENDERDOC_API_1_4_1 = 1 04 01\n} RENDERDOC_Version;\n\n// API version changelog:\n//\n// 1.0.0 - initial release\n// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered\n//         by keypress or TriggerCapture, instead of Start/EndFrameCapture.\n// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation\n// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new\n//         function pointer is added to the end of the struct, the original layout is identical\n// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote\n//         replay/remote server concept in replay UI)\n// 1.1.2 - Refactor: Renamed \"log file\" in function names to just capture, to clarify that these\n//         are captures and not debug logging files. This is the first API version in the v1.0\n//         branch.\n// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be\n//         displayed in the UI program on load.\n// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions\n//         which allows users to opt-in to allowing unsupported vendor extensions to function.\n//         Should be used at the user's own risk.\n//         Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to\n//         eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to\n//         0xdddddddd of uninitialised buffer contents.\n// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop\n//         capturing without saving anything to disk.\n// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening\n\ntypedef struct RENDERDOC_API_1_4_1\n{\n  pRENDERDOC_GetAPIVersion GetAPIVersion;\n\n  pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;\n  pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;\n\n  pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;\n  pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;\n\n  pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;\n  pRENDERDOC_SetCaptureKeys SetCaptureKeys;\n\n  pRENDERDOC_GetOverlayBits GetOverlayBits;\n  pRENDERDOC_MaskOverlayBits MaskOverlayBits;\n\n  // Shutdown was renamed to RemoveHooks in 1.4.1.\n  // These unions allow old code to continue compiling without changes\n  union\n  {\n    pRENDERDOC_Shutdown Shutdown;\n    pRENDERDOC_RemoveHooks RemoveHooks;\n  };\n  pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;\n\n  // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.\n  // These unions allow old code to continue compiling without changes\n  union\n  {\n    // deprecated name\n    pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;\n    // current name\n    pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;\n  };\n  union\n  {\n    // deprecated name\n    pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;\n    // current name\n    pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;\n  };\n\n  pRENDERDOC_GetNumCaptures GetNumCaptures;\n  pRENDERDOC_GetCapture GetCapture;\n\n  pRENDERDOC_TriggerCapture TriggerCapture;\n\n  // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.\n  // This union allows old code to continue compiling without changes\n  union\n  {\n    // deprecated name\n    pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;\n    // current name\n    pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;\n  };\n  pRENDERDOC_LaunchReplayUI LaunchReplayUI;\n\n  pRENDERDOC_SetActiveWindow SetActiveWindow;\n\n  pRENDERDOC_StartFrameCapture StartFrameCapture;\n  pRENDERDOC_IsFrameCapturing IsFrameCapturing;\n  pRENDERDOC_EndFrameCapture EndFrameCapture;\n\n  // new function in 1.1.0\n  pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;\n\n  // new function in 1.2.0\n  pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;\n\n  // new function in 1.4.0\n  pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;\n} RENDERDOC_API_1_4_1;\n\ntypedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_0;\ntypedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_1;\ntypedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_2;\ntypedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_0;\ntypedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_1;\ntypedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_2;\ntypedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_2_0;\ntypedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_3_0;\ntypedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_4_0;\n\n//////////////////////////////////////////////////////////////////////////////////////////////////\n// RenderDoc API entry point\n//\n// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.\n//\n// The name is the same as the typedef - \"RENDERDOC_GetAPI\"\n//\n// This function is not thread safe, and should not be called on multiple threads at once.\n// Ideally, call this once as early as possible in your application's startup, before doing\n// any API work, since some configuration functionality etc has to be done also before\n// initialising any APIs.\n//\n// Parameters:\n//   version is a single value from the RENDERDOC_Version above.\n//\n//   outAPIPointers will be filled out with a pointer to the corresponding struct of function\n//   pointers.\n//\n// Returns:\n//   1 - if the outAPIPointers has been filled with a pointer to the API struct requested\n//   0 - if the requested version is not supported or the arguments are invalid.\n//\ntypedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);\n\n#ifdef __cplusplus\n}    // extern \"C\"\n#endif\n"
  },
  {
    "path": "Bridge/renderdoc/renderdoc_wrapper.c",
    "content": "#include \"renderdoc_app.h\"\n#include <stddef.h>\n\n#ifdef _WIN32\n#define EXPORT __declspec( dllexport )\n#else\n#define EXPORT __attribute__ ((visibility (\"default\")))\n#endif\n\nRENDERDOC_API_1_4_1* API = NULL;\n\n#ifdef _WIN32\n#include <Windows.h>\nvoid init()\n{\n    if (API) return;\n    HMODULE module = GetModuleHandleA(\"renderdoc.dll\");\n    if (module)\n    {\n        pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(module, \"RENDERDOC_GetAPI\");\n        int result = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_4_1, (void **)&API);\n        if (result != 1) API = NULL;\n    }\n}\n#else\n#include <dlfcn.h>\nvoid init()\n{\n    if (API) return;\n    void* module = dlopen(\"librenderdoc.so\", RTLD_NOW | RTLD_NOLOAD);\n    if (module)\n    {\n        pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(module, \"RENDERDOC_GetAPI\");\n        int result = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_4_1, (void **)&API);\n        if (result != 1) API = NULL;\n    }\n}\n#endif\n\nEXPORT void capture_start()\n{\n    init();\n    if (API) API->StartFrameCapture(NULL, NULL);\n}\n\nEXPORT void capture_end()\n{\n    init();\n    if (API) API->EndFrameCapture(NULL, NULL);\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nMalt - Copyright (c) 2020-2022 BNPR, Miguel Pozo and contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "LICENSE - DEPENDENCIES",
    "content": "Malt:\n\nPyOpenGL\nBSD License\nhttp://pyopengl.sourceforge.net\nhttps://github.com/mcfletch/pyopengl/blob/master/license.txt\n\npyGLFW\nMIT License\nhttps://github.com/FlorianRhiem/pyGLFW\nhttps://github.com/FlorianRhiem/pyGLFW/blob/master/LICENSE.txt\n\nGLFW\nzlib License\nhttps://www.glfw.org/\nhttps://github.com/glfw/glfw/blob/master/LICENSE.md\n\nmcpp\nMIT License\nhttps://github.com/pragma37/mcpp\nhttps://github.com/pragma37/mcpp/blob/master/LICENSE\n\nmultipledispatch\nBSD License\nhttp://github.com/mrocklin/multipledispatch/\nhttps://github.com/mrocklin/multipledispatch/blob/master/LICENSE.txt\n\nnumpy\nBSD License\nhttps://www.numpy.org\nhttps://github.com/numpy/numpy/blob/main/LICENSE.txt\nhttps://github.com/numpy/numpy/blob/main/LICENSES_bundled.txt\n\npyrr\nBSD License\nhttps://github.com/adamlwgriffiths/Pyrr\nhttps://github.com/adamlwgriffiths/Pyrr/blob/master/LICENSE\n\nsix\nMIT License\nhttps://github.com/benjaminp/six\nhttps://github.com/benjaminp/six/blob/master/LICENSE\n\nGLSLParser:\n\nPEGTL\nBoost Software License\nhttps://github.com/taocpp/PEGTL\nhttps://github.com/taocpp/PEGTL/blob/main/LICENSE_1_0.txt\n\nrapidjson\nMIT License\nhttps://github.com/Tencent/rapidjson\nhttps://github.com/Tencent/rapidjson/blob/master/license.txt\n\nBridge:\n\nipc\nThe Unlicense\nhttps://github.com/jarikomppa/ipc\nhttps://github.com/jarikomppa/ipc/blob/master/LICENSE\n\nrenderdoc (renderdoc_app.h)\nMIT License\nhttps://renderdoc.org/\nhttps://github.com/baldurk/renderdoc/blob/v1.x/LICENSE.md\n\nBlenderMalt:\n\nblender\nGNU GPL\nhttps://blender.org\nhttps://github.com/blender/blender/blob/master/doc/license/GPL3-license.txt\n\nMikkTSpace\nzlib License\nhttp://mikktspace.com/\nhttps://github.com/mmikk/MikkTSpace/blob/master/mikktspace.h\n"
  },
  {
    "path": "LICENSE - DEPENDENCIES (FULL TEXT)",
    "content": "Malt:\n********************************************************************************\nPyOpenGL\nBSD License\nhttp://pyopengl.sourceforge.net\nNOTE:\n\n    THIS SOFTWARE IS NOT FAULT TOLERANT AND SHOULD NOT BE USED IN ANY\n    SITUATION ENDANGERING HUMAN LIFE OR PROPERTY.\n    \nOpenGL-ctypes License\n\n    Copyright (c) 2005-2014, Michael C. Fletcher and Contributors\n    All rights reserved.\n    \n    Redistribution and use in source and binary forms, with or without\n    modification, are permitted provided that the following conditions\n    are met:\n    \n        Redistributions of source code must retain the above copyright\n        notice, this list of conditions and the following disclaimer.\n    \n        Redistributions in binary form must reproduce the above\n        copyright notice, this list of conditions and the following\n        disclaimer in the documentation and/or other materials\n        provided with the distribution.\n    \n        The name of Michael C. Fletcher, or the name of any Contributor,\n        may not be used to endorse or promote products derived from this \n        software without specific prior written permission.\n    \n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n    COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n    OF THE POSSIBILITY OF SUCH DAMAGE. \n\nOpenGL-ctypes includes code from the PyOpenGL 2.x series licensed under\nversion 3 of the PyOpenGL License (BSD-style):\n\n    PyOpenGL License (v3)\n\n    PyOpenGL is based on PyOpenGL 1.5.5, Copyright &copy; 1997-1998 by\n    James Hugunin, Cambridge MA, USA, Thomas Schwaller, Munich, Germany\n    and David Ascher, San Francisco CA, USA.\n\n    Contributors to the PyOpenGL project in addition to those listed \n    above include:\n        * David Konerding\n        * Soren Renner\n        * Rene Liebscher\n        * Randall Hopper\n        * Michael Fletcher\n        * Thomas Malik\n        * Thomas Hamelryck\n        * Jack Jansen\n        * Michel Sanner\n        * Tarn Weisner Burton\n        * Andrew Cox\n        * Rene Dudfield\n\n    PyOpenGL is Copyright (c) 1997-1998, 2000-2006 by the contributors.\n\n    All rights reserved.\n\n    Redistribution and use in source and binary forms, with or without\n    modification, are permitted provided that the following conditions are\n    met:\n    \n    * Redistributions of source code must retain the above copyright\n        notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n        notice, this list of conditions and the following disclaimer in\n        the documentation and/or other materials provided with the\n        distribution.\n    * The names of the contributors may not be used to endorse or\n        promote products derived from this software without specific prior\n        written permission.\n    \n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n    \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n    HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\n    OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\n    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE\n    USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH\n    DAMAGE.\n\nOpenGL-ctypes includes code from the Pyglet project, licensed under the \nPyglet License (BSD Style):\n\n    Copyright (c) 2006-2008 Alex Holkner\n    All rights reserved.\n\n    Redistribution and use in source and binary forms, with or without\n    modification, are permitted provided that the following conditions \n    are met:\n    \n    * Redistributions of source code must retain the above copyright\n       notice, this list of conditions and the following disclaimer.\n     * Redistributions in binary form must reproduce the above copyright \n       notice, this list of conditions and the following disclaimer in\n       the documentation and/or other materials provided with the\n       distribution.\n     * Neither the name of pyglet nor the names of its\n       contributors may be used to endorse or promote products\n       derived from this software without specific prior written\n       permission.\n\n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n    \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n    POSSIBILITY OF SUCH DAMAGE.\n\nOpenGL-ctypes may include source code from the GLE (GL Tubing and Extrusion) library, which is \nlicensed under the license declared in OpenGL/DLLS/gle_COPYING.src if GLE is included in this \ndistribution.  Copyright notice follows:\n\n    This software is owned by International Business Machines Corporation\n    (\"IBM\"), or its subsidiaries or IBM's suppliers, and is copyrighted and\n    licensed, not sold.  IBM retains title to the software, and grants you a\n    nonexclusive license for the software.\n\nOpenGL-ctypes may include source code from FreeGLUT (GL Utility Toolkit) library,\nwhich is licensed under the MIT/X-Consortium License, available in \nOpenGL/DLLS/freeglut_COPYING.txt if FreeGLUT is included in this distribution:\n\n    Freeglut code without an explicit copyright is covered by the following \n    copyright:\n\n    Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.\n    Permission is hereby granted, free of charge,  to any person obtaining a copy \n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction,  including without limitation the rights \n    to use, copy,  modify, merge,  publish, distribute,  sublicense,  and/or sell \n    copies or substantial portions of the Software.\n\n    The above  copyright notice  and this permission notice  shall be included in \n    all copies or substantial portions of the Software.\n\n    THE SOFTWARE  IS PROVIDED \"AS IS\",  WITHOUT WARRANTY OF ANY KIND,  EXPRESS OR \n    IMPLIED,  INCLUDING  BUT  NOT LIMITED  TO THE WARRANTIES  OF MERCHANTABILITY, \n    FITNESS  FOR  A PARTICULAR PURPOSE  AND NONINFRINGEMENT.  IN  NO EVENT  SHALL \n    PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM,  DAMAGES OR OTHER LIABILITY, WHETHER \n    IN  AN ACTION  OF CONTRACT,  TORT OR OTHERWISE,  ARISING FROM,  OUT OF  OR IN \n    CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n    Except as contained in this notice,  the name of Pawel W. Olszta shall not be \n    used  in advertising  or otherwise to promote the sale, use or other dealings \n    in this Software without prior written authorization from Pawel W. Olszta.\n\nOpenGL-ctypes may include binary distributions of the Tk Togl widget, which is licensed under the \nfollowing license/copyright notice (available in OpenGL/Tk/*/LICENSE if Togl is included).\n\n    This software is copyrighted by Brian Paul (brian@mesa3d.org),\n    Benjamin Bederson (bederson@cs.umd.edu), and Greg Couch\n    (gregcouch@users.sourceforge.net).  The following terms apply to all\n    files associated with the software unless explicitly disclaimed in\n    individual files.\n\n    The authors hereby grant permission to use, copy, modify, distribute,\n    and license this software and its documentation for any purpose, provided\n    that existing copyright notices are retained in all copies and that this\n    notice is included verbatim in any distributions.  No written agreement,\n    license, or royalty fee is required for any of the authorized uses.\n    Modifications to this software may be copyrighted by their authors\n    and need not follow the licensing terms described here, provided that\n    the new terms are clearly indicated on the first page of each file where\n    they apply.\n\n    IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY\n    FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES\n    ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY\n    DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE\n    POSSIBILITY OF SUCH DAMAGE.\n\n    THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,\n    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE\n    IS PROVIDED ON AN \"AS IS\" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE\n    NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR\n    MODIFICATIONS.\n\nOpenGL-ctypes includes an OS-Mesa platform driver which is (MIT\nLicensed), Copyright (c) 2012 Xu, Yuan <xuyuan.cn@gmail.com>:\n\n    https://github.com/xuyuan/PyOSMesa\n    http://opensource.org/licenses/MIT\n\nOpenGL-ctypes uses a table from the Chromium Regal project to provide \nconstant:array-size mappings.  Regal is:\n\n    Copyright (c) 2011-2012 NVIDIA Corporation\n    Copyright (c) 2011-2012 Cass Everitt\n    Copyright (c) 2012 Scott Nations\n    Copyright (c) 2012 Mathias Schott\n    Copyright (c) 2012 Nigel Stewart\n    All rights reserved.\n\n    Redistribution and use in source and binary forms, with or without modification,\n    are permitted provided that the following conditions are met:\n\n    Redistributions of source code must retain the above copyright notice, this\n    list of conditions and the following disclaimer.\n\n    Redistributions in binary form must reproduce the above copyright notice,\n    this list of conditions and the following disclaimer in the documentation\n    and/or other materials provided with the distribution.\n\n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n    IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\n    OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n    OF THE POSSIBILITY OF SUCH DAMAGE.\n\n********************************************************************************\npyGLFW\nMIT License\nhttps://github.com/FlorianRhiem/pyGLFW\nThe MIT License (MIT)\n\nCopyright (c) 2013-2019 Florian Rhiem\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n********************************************************************************\nGLFW\nzlib License\nhttps://www.glfw.org/\nCopyright (c) 2002-2006 Marcus Geelnard\n\nCopyright (c) 2006-2019 Camilla Lwy\n\nThis software is provided 'as-is', without any express or implied\nwarranty. In no event will the authors be held liable for any damages\narising from the use of this software.\n\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must not\n   claim that you wrote the original software. If you use this software\n   in a product, an acknowledgment in the product documentation would\n   be appreciated but is not required.\n\n2. Altered source versions must be plainly marked as such, and must not\n   be misrepresented as being the original software.\n\n3. This notice may not be removed or altered from any source\n   distribution.\n\n\n********************************************************************************\nmcpp\nMIT License\nhttps://github.com/pragma37/mcpp\n/*-\n * Copyright (c) 1998, 2002-2008 Kiyoshi Matsui <kmatsui@t3.rim.or.jp>\n * All rights reserved.\n *\n * This software including the files in this directory is provided under\n * the following license.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n */\n\n\n********************************************************************************\nmultipledispatch\nBSD License\nhttp://github.com/mrocklin/multipledispatch/\nCopyright (c) 2014 Matthew Rocklin\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n  a. Redistributions of source code must retain the above copyright notice,\n     this list of conditions and the following disclaimer.\n  b. Redistributions in binary form must reproduce the above copyright\n     notice, this list of conditions and the following disclaimer in the\n     documentation and/or other materials provided with the distribution.\n  c. Neither the name of multipledispatch nor the names of its contributors\n     may be used to endorse or promote products derived from this software\n     without specific prior written permission.\n\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH\nDAMAGE.\n\n********************************************************************************\nnumpy\nBSD License\nhttps://www.numpy.org\nCopyright (c) 2005-2022, NumPy Developers.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n       notice, this list of conditions and the following disclaimer.\n\n    * Redistributions in binary form must reproduce the above\n       copyright notice, this list of conditions and the following\n       disclaimer in the documentation and/or other materials provided\n       with the distribution.\n\n    * Neither the name of the NumPy Developers nor the names of any\n       contributors may be used to endorse or promote products derived\n       from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThe NumPy repository and source distributions bundle several libraries that are\ncompatibly licensed.  We list these here.\n\nName: lapack-lite\nFiles: numpy/linalg/lapack_lite/*\nLicense: BSD-3-Clause\n  For details, see numpy/linalg/lapack_lite/LICENSE.txt\n\nName: tempita\nFiles: tools/npy_tempita/*\nLicense: MIT\n  For details, see tools/npy_tempita/license.txt\n\nName: dragon4\nFiles: numpy/core/src/multiarray/dragon4.c\nLicense: MIT\n  For license text, see numpy/core/src/multiarray/dragon4.c\n\nName: libdivide\nFiles: numpy/core/include/numpy/libdivide/*\nLicense: Zlib\n  For license text, see numpy/core/include/numpy/libdivide/LICENSE.txt\n\n********************************************************************************\npyrr\nBSD License\nhttps://github.com/adamlwgriffiths/Pyrr\nIn the original BSD license, both occurrences of the phrase \"COPYRIGHT HOLDERS AND CONTRIBUTORS\" in the disclaimer read \"REGENTS AND CONTRIBUTORS\".\n\nHere is the license template:\n\nCopyright (c) 2015, Adam Griffiths\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met: \n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. \n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. \n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThe views and conclusions contained in the software and documentation are those\nof the authors and should not be interpreted as representing official policies, \neither expressed or implied, of the FreeBSD Project.\n\n********************************************************************************\nsix\nMIT License\nhttps://github.com/benjaminp/six\nCopyright (c) 2010-2020 Benjamin Peterson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n********************************************************************************\nGLSLParser:\n********************************************************************************\nPEGTL\nBoost Software License\nhttps://github.com/taocpp/PEGTL\nBoost Software License - Version 1.0 - August 17th, 2003\n\nPermission is hereby granted, free of charge, to any person or organization\nobtaining a copy of the software and accompanying documentation covered by\nthis license (the \"Software\") to use, reproduce, display, distribute,\nexecute, and transmit the Software, and to prepare derivative works of the\nSoftware, and to permit third-parties to whom the Software is furnished to\ndo so, all subject to the following:\n\nThe copyright notices in the Software and this entire statement, including\nthe above license grant, this restriction and the following disclaimer,\nmust be included in all copies of the Software, in whole or in part, and\nall derivative works of the Software, unless such copies or derivative\nworks are solely in the form of machine-executable object code generated by\na source language processor.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\nSHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\nFOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n\n********************************************************************************\nrapidjson\nMIT License\nhttps://github.com/Tencent/rapidjson\nTencent is pleased to support the open source community by making RapidJSON available. \n \nCopyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.  All rights reserved.\n\nIf you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License.\nIf you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms.  Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license.\nA copy of the MIT License is included in this file.\n\nOther dependencies and licenses:\n\nOpen Source Software Licensed Under the BSD License:\n--------------------------------------------------------------------\n\nThe msinttypes r29 \nCopyright (c) 2006-2013 Alexander Chemeris \nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. \n* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n* Neither the name of  copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nOpen Source Software Licensed Under the JSON License:\n--------------------------------------------------------------------\n\njson.org \nCopyright (c) 2002 JSON.org\nAll Rights Reserved.\n\nJSON_checker\nCopyright (c) 2002 JSON.org\nAll Rights Reserved.\n\n\t\nTerms of the JSON License:\n---------------------------------------------------\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nThe Software shall be used for Good, not Evil.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\nTerms of the MIT License:\n--------------------------------------------------------------------\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n********************************************************************************\nBridge:\n********************************************************************************\nipc\nThe Unlicense\nhttps://github.com/jarikomppa/ipc\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org>\n\n********************************************************************************\nrenderdoc (renderdoc_app.h)\nMIT License\nhttps://renderdoc.org/\n# The MIT License (MIT)\n\nCopyright (c) 2015-2021 Baldur Karlsson\n\nCopyright (c) 2014 Crytek\n\nCopyright (c) 1998-2018 [Third party code and tools](docs/credits_acknowledgements.rst)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n********************************************************************************\nBlenderMalt:\n********************************************************************************\nblender\nGNU GPL\nhttps://blender.org\n                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n\n********************************************************************************\nMikkTSpace\nzlib License\nhttp://mikktspace.com/\n\nCopyright (C) 2011 by Morten S. Mikkelsen\n\nThis software is provided 'as-is', without any express or implied\nwarranty.  In no event will the authors be held liable for any damages\narising from the use of this software.\n\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must not\n   claim that you wrote the original software. If you use this software\n   in a product, an acknowledgment in the product documentation would be\n   appreciated but is not required.\n2. Altered source versions must be plainly marked as such, and must not be\n   misrepresented as being the original software.\n3. This notice may not be removed or altered from any source distribution.\n\n********************************************************************************\n********************************************************************************\n"
  },
  {
    "path": "Malt/GL/GL.py",
    "content": "import collections\n\nimport OpenGL\n#OpenGL.ERROR_LOGGING = False\n#OpenGL.FULL_LOGGING = False\n#OpenGL.ERROR_ON_COPY = False\nfrom OpenGL.GL import *\nfrom OpenGL.extensions import hasGLExtension\n\nif True: \n    #For some reason PyOpenGL doesnt support the most common depth/stencil buffer by default ???\n    #https://sourceforge.net/p/pyopengl/bugs/223/\n    from OpenGL import images\n    images.TYPE_TO_ARRAYTYPE[GL_UNSIGNED_INT_24_8] = GL_UNSIGNED_INT\n    images.TIGHT_PACK_FORMATS[GL_UNSIGNED_INT_24_8] = 4\n    images.TYPE_TO_ARRAYTYPE[GL_HALF_FLOAT] = GL_HALF_FLOAT\n    from OpenGL import arrays\n    if arrays.ADT:\n        arrays.GL_CONSTANT_TO_ARRAY_TYPE[GL_HALF_FLOAT] = arrays.ADT(GL_HALF_FLOAT, GLhalfARB)\n    else:\n        class GLhalfFloatArray(ArrayDatatype, ctypes.POINTER(GLhalfARB)):\n            baseType = GLhalfARB\n            typeConstant = GL_HALF_FLOAT\n        arrays.GL_CONSTANT_TO_ARRAY_TYPE[GL_HALF_FLOAT] = GLhalfFloatArray\n\nNULL = None\nGL_ENUMS = {}\nGL_NAMES = {}\n\nif True: #create new scope to import OpenGL\n    from OpenGL import GL\n    for e in dir(GL):\n        if e.startswith('GL_'):\n            GL_ENUMS[getattr(GL, e)] = e\n            GL_NAMES[e] = getattr(GL, e)\n\nclass DrawQuery():\n\n    def __init__(self, query_type=GL_ANY_SAMPLES_PASSED):\n        self.query = None\n        self.query_type = query_type\n    \n    def begin_query(self):\n        if self.query:\n            glDeleteQueries(1, self.query)\n        self.query = gl_buffer(GL_UNSIGNED_INT, 1)\n        glGenQueries(1, self.query)\n        glBeginQuery(self.query_type, self.query[0])\n\n    def end_query(self):        \n        glEndQuery(self.query_type)\n\n    def begin_conditional_draw(self, wait_mode=GL_QUERY_WAIT):\n        glBeginConditionalRender(self.query[0], wait_mode)\n    \n    def end_conditional_draw(self):\n        glEndConditionalRender()\n    \n\ndef gl_buffer(type, size, data=None):\n    types = {\n        GL_BYTE : GLbyte,\n        GL_UNSIGNED_BYTE : GLubyte,\n        GL_SHORT : GLshort,\n        GL_UNSIGNED_SHORT : GLushort,\n        GL_INT : GLint,\n        GL_UNSIGNED_INT : GLuint,\n        #GL_HALF_FLOAT : GLhalfARB,\n        GL_HALF_FLOAT : GLfloat,\n        GL_FLOAT : GLfloat,\n        GL_DOUBLE : GLdouble,\n        GL_BOOL : GLboolean,\n    }\n    gl_type = (types[type] * size)\n    if data:\n        try:\n            return gl_type(*data)\n        except:\n            return gl_type(data)\n    else:\n        return gl_type()\n\n\ndef buffer_to_string(buffer):\n    chars = []\n    for char in list(buffer):\n        if chr(char) == '\\0':\n            break\n        chars.append(chr(char))\n    return ''.join(chars)\n"
  },
  {
    "path": "Malt/GL/GLSLEval.py",
    "content": "def glsl_vector(convert, length, *args):\n    unpacked_args = []\n    for arg in args:\n        try:\n            unpacked_args.extend([*arg])\n        except:\n            unpacked_args.append(arg)\n    unpacked_args = [convert(arg) for arg in unpacked_args]\n    if len(unpacked_args) == 0:\n        return (0.0)*length\n    elif len(unpacked_args) == 1:\n        return (unpacked_args[0],) * length\n    else:\n        assert(len(unpacked_args) == length)\n        return tuple(unpacked_args)\n\ndef _vec2(convert, *args): return glsl_vector(convert, 2, *args)\ndef _vec3(convert, *args): return glsl_vector(convert, 3, *args)\ndef _vec4(convert, *args): return glsl_vector(convert, 4, *args)\n\ndef vec2(*args): return _vec2(float, *args)\ndef vec3(*args): return _vec3(float, *args)\ndef vec4(*args): return _vec4(float, *args)\n\ndef ivec2(*args): return _vec2(int, *args)\ndef ivec3(*args): return _vec3(int, *args)\ndef ivec4(*args): return _vec4(int, *args)\n\ndef uint(n): return max(int(n), 0)\n\ndef uvec2(*args): return _vec2(uint, *args)\ndef uvec3(*args): return _vec3(uint, *args)\ndef uvec4(*args): return _vec4(uint, *args)\n\ndef glsl_eval(str):\n    true = True\n    false = False\n    return eval(str)\n"
  },
  {
    "path": "Malt/GL/GLSLParser/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.15)\nproject(GLSLParser)\nset(CMAKE_CXX_STANDARD 17)\n\nadd_subdirectory(external)\n\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}/.bin\")\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})\n\nfile(GLOB_RECURSE src_glob \"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp\")\n\nadd_executable(${PROJECT_NAME} ${src_glob})\n\ntarget_include_directories(${PROJECT_NAME} PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}/src\")\n\n#target_precompile_headers(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/pch.h)\n\ntarget_link_libraries(${PROJECT_NAME} pegtl)\ntarget_include_directories(${PROJECT_NAME} PUBLIC ${RAPIDJSON_INCLUDE_DIR})\n\n"
  },
  {
    "path": "Malt/GL/GLSLParser/build.py",
    "content": "import subprocess\nimport os\nimport platform\n\ncurrent_dir = os.path.abspath(os.path.dirname(__file__))\nbuild_dir = os.path.join(current_dir, '.build') \n\ntry: os.mkdir(build_dir)\nexcept: pass\n\nif platform.system() == 'Windows': #Multi-config generators, like Visual Studio\n    subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir)\n    subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir)\nelse: #Single-config generators\n    subprocess.check_call(['cmake', '..'], cwd=build_dir)\n    subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir)\n\n#subprocess.check_call(['cmake', '--install', '.', '--prefix', '.'], cwd=build_dir)\n"
  },
  {
    "path": "Malt/GL/GLSLParser/external/CMakeLists.txt",
    "content": "include(FetchContent)\ninclude(ExternalProject)\n\nFetchContent_Declare(\n  pegtl\n  GIT_REPOSITORY https://github.com/taocpp/PEGTL\n  GIT_TAG        3.2.8\n)\nFetchContent_MakeAvailable(pegtl)\n\n\nFetchContent_Declare(\n  rapidjson\n  GIT_REPOSITORY https://github.com/Tencent/rapidjson\n  GIT_TAG        24b5e7a8b27f42fa16b96fc70aade9106cf7102f\n)\nset(RAPIDJSON_BUILD_DOC OFF CACHE INTERNAL \"\")\nset(RAPIDJSON_BUILD_EXAMPLES OFF CACHE INTERNAL \"\")\nset(RAPIDJSON_BUILD_TESTS OFF CACHE INTERNAL \"\")\nFetchContent_MakeAvailable(rapidjson)\nFetchContent_GetProperties(rapidjson)\nset(RAPIDJSON_INCLUDE_DIR ${rapidjson_SOURCE_DIR}/include PARENT_SCOPE)\n"
  },
  {
    "path": "Malt/GL/GLSLParser/external/PEGTL-LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2007-2021 Dr. Colin Hirsch and Daniel Frey\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "Malt/GL/GLSLParser/external/rapidjson-LICENSE",
    "content": "Tencent is pleased to support the open source community by making RapidJSON available. \n \nCopyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip.  All rights reserved.\n\nIf you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License.\nIf you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms.  Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license.\nA copy of the MIT License is included in this file.\n\nOther dependencies and licenses:\n\nOpen Source Software Licensed Under the BSD License:\n--------------------------------------------------------------------\n\nThe msinttypes r29 \nCopyright (c) 2006-2013 Alexander Chemeris \nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. \n* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n* Neither the name of  copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nOpen Source Software Licensed Under the JSON License:\n--------------------------------------------------------------------\n\njson.org \nCopyright (c) 2002 JSON.org\nAll Rights Reserved.\n\nJSON_checker\nCopyright (c) 2002 JSON.org\nAll Rights Reserved.\n\n\t\nTerms of the JSON License:\n---------------------------------------------------\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nThe Software shall be used for Good, not Evil.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\nTerms of the MIT License:\n--------------------------------------------------------------------\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "Malt/GL/GLSLParser/src/main.cpp",
    "content": "#include <string>\n#include <map>\n#include <iostream>\n\n#include <tao/pegtl.hpp>\n#include <tao/pegtl/contrib/analyze.hpp>\n#include <tao/pegtl/contrib/trace.hpp>\n#include <tao/pegtl/contrib/parse_tree.hpp>\n#include <tao/pegtl/contrib/parse_tree_to_dot.hpp>\n\n#include <rapidjson/document.h>\n#include \"rapidjson/prettywriter.h\"\n\nusing namespace tao::pegtl;\n\n#define STRING(string) TAO_PEGTL_STRING(string)\n#define ISTRING(string) TAO_PEGTL_ISTRING(string)\n#define KEYWORD(string) TAO_PEGTL_KEYWORD(string)\n\nstruct line_comment : seq<STRING(\"//\"), until<eol, any>> {};\nstruct multiline_comment : seq<STRING(\"/*\"), until<STRING(\"*/\"), any>> {};\nstruct preprocessor_directive : seq<STRING(\"#\"), until<eol, any>> {};\nstruct META;\nstruct META_GLOBAL;\nstruct _s_ : star<not_at<sor<META, META_GLOBAL>>, sor<line_comment, multiline_comment, preprocessor_directive, space>> {};\n\nstruct LPAREN : one<'('> {};\nstruct RPAREN : one<')'> {};\nstruct LBRACE : one<'{'> {};\nstruct RBRACE : one<'}'> {};\nstruct LBRACKET : one<'['> {};\nstruct RBRACKET : one<']'> {};\nstruct COMMA : one<','> {};\nstruct END : one<';'> {};\nstruct STRUCT : KEYWORD(\"struct\") {};\nstruct IDENTIFIER : identifier {};\nstruct TYPE : identifier {};\nstruct DIGITS : plus<digit> {};\nstruct PRECISION : sor<KEYWORD(\"lowp\"), KEYWORD(\"mediump\"), KEYWORD(\"highp\")> {};\nstruct IO : sor<KEYWORD(\"in\"), KEYWORD(\"out\"), KEYWORD(\"inout\")> {};\nstruct ARRAY_SIZE : seq<LBRACKET, _s_, DIGITS, _s_, RBRACKET> {};\n\nstruct FILE_PATH : plus<not_one<'\"'>> {};\nstruct QUOTED_FILE_PATH : seq<one<'\"'>, FILE_PATH, one<'\"'>> {};\nstruct LINE_DIRECTIVE : seq<STRING(\"#line\"), _s_, DIGITS, _s_, QUOTED_FILE_PATH> {};\n\nstruct _ms_ : star<sor<space, preprocessor_directive>> {};\nstruct META_PROP_VALUE : star<not_at<one<';'>>, any> {};\nstruct META_PROP : seq<not_at<one<'@'>>, IDENTIFIER, _ms_, one<'='>, _ms_, META_PROP_VALUE, _ms_, one<';'>> {};\nstruct META_PROPS : plus<seq<_ms_, META_PROP, _ms_>> {};\nstruct META_MEMBER : seq<one<'@'>, _ms_, IDENTIFIER, _ms_, one<':'>, META_PROPS> {};\nstruct META_MEMBERS : plus<seq<_ms_, META_MEMBER, _ms_>> {};\nstruct META : seq<STRING(\"/*\"), _ms_, STRING(\"META\"), _ms_, META_MEMBERS, _ms_, STRING(\"*/\")> {};\n\nstruct META_GLOBAL : seq<STRING(\"/*\"), _ms_, STRING(\"META GLOBAL\"), _ms_, META_MEMBERS, _ms_, STRING(\"*/\")> {};\n\nstruct MEMBER : seq<opt<PRECISION>, _s_, TYPE, _s_, IDENTIFIER, _s_, opt<ARRAY_SIZE>, _s_, END> {};\nstruct MEMBERS : plus<seq<_s_, MEMBER, _s_>> {};\nstruct STRUCT_DEF : seq<opt<META, _ms_>, STRUCT, _s_, IDENTIFIER, _s_, LBRACE, _s_, opt<MEMBERS>, _s_, RBRACE> {};\n\nstruct PARAMETER : seq<opt<IO>, _s_, opt<PRECISION>, _s_, TYPE, _s_, IDENTIFIER, _s_, opt<ARRAY_SIZE>> {};\nstruct PARAMETERS : list<seq<_s_, PARAMETER, _s_>, seq<_s_, COMMA, _s_>> {};\nstruct FUNCTION_SIG : seq<TYPE, _s_, IDENTIFIER, _s_, LPAREN, _s_, opt<PARAMETERS>, _s_, RPAREN> {}; \nstruct FUNCTION_DEC : seq<opt<META, _ms_>, FUNCTION_SIG, _s_, LBRACE> {}; \n\nstruct GLSL_GRAMMAR : star<sor<LINE_DIRECTIVE, META_GLOBAL, STRUCT_DEF, FUNCTION_DEC, any>> {};\n\ntemplate<typename Rule>\nusing selector = parse_tree::selector\n<\n    Rule,\n    parse_tree::store_content::on\n    <\n        IDENTIFIER,\n        TYPE,\n        DIGITS,\n        PRECISION,\n        IO,\n        ARRAY_SIZE,\n        FILE_PATH,\n        META_PROP_VALUE,\n        META_PROP,\n        META_MEMBER,\n        META,\n        META_GLOBAL,\n        MEMBER,\n        MEMBERS,\n        STRUCT_DEF,\n        PARAMETER,\n        PARAMETERS,\n        FUNCTION_SIG,\n        FUNCTION_DEC\n    >\n>;\n\nvoid print_nodes(parse_tree::node& node)\n{\n    if(node.has_content())\n    {\n        std::cout << node.type << \" : \" << node.string_view() << std::endl;\n    }\n    \n    for(auto& child : node.children)\n    {\n        if(child)\n        {\n            print_nodes(*child);\n        }\n    }\n}\n\ntemplate<typename T>\nparse_tree::node* get(parse_tree::node* parent)\n{\n    for(auto& child : parent->children)\n    {\n        if(child->is_type<T>())\n        {\n            return child.get();\n        }\n    }\n    return nullptr;\n}\n\nstd::map<std::string, rapidjson::Value> get_meta_dict(parse_tree::node* meta, rapidjson::MemoryPoolAllocator<>& allocator)\n{\n    using namespace rapidjson;\n    std::map<std::string, Value> meta_dict = {};\n    if(meta)\n    {\n        for(auto& meta_member : meta->children)\n        {\n            std::string member_name = std::string(get<IDENTIFIER>(meta_member.get())->string_view());\n            Value member_meta = Value(kObjectType);\n            \n            for(auto& meta_prop : meta_member->children)\n            {\n                if(!meta_prop->is_type<META_PROP>())\n                {\n                    continue; //Other type of child\n                }\n                std::string prop_name = std::string(get<IDENTIFIER>(meta_prop.get())->string_view());\n                std::string prop_value = std::string(get<META_PROP_VALUE>(meta_prop.get())->string_view());\n                member_meta.AddMember(\n                    Value(prop_name.c_str(), allocator), Value(prop_value.c_str(), allocator), allocator\n                );\n            }\n            \n            meta_dict[member_name] = member_meta;\n        }\n    }\n    return meta_dict;\n}\n\nstd::string remove_extra_whitespace(const std::string& str)\n{\n    int len = str.length();\n\tstd::string result;\n    result.reserve(len);\n    bool was_space = false;\n    for(int i=0; i < len; i++)\n    {\n        bool is_space = isspace(str[i]);\n        if(is_space)\n        {\n            if(!was_space) result += \" \";\n        }\n        else\n        {\n            result += str[i];\n        }\n        was_space = is_space;\n    }\n    return result;\n}\n\nint main(int argc, char* argv[])\n{\n    if(argc < 2) return 1;\n    const char* path = argv[1];\n\n    file_input input(path);\n    \n    #ifdef _DEBUG\n    {\n        const std::size_t issues = analyze<GLSL_GRAMMAR>();\n        if(issues) return issues;\n    }\n    #endif\n\n    //standard_trace<GLSL_GRAMMAR>(input);\n    //input.restart();\n    \n    auto root = parse_tree::parse<GLSL_GRAMMAR, selector>(input);\n    input.restart();\n    if(!root) return 1;\n\n    //print_nodes(*root);\n    //parse_tree::print_dot(std::cout, *root);\n\n    using namespace rapidjson;\n\n    Document json;\n    json.Parse(\"{}\");\n    json.AddMember(\"meta globals\", Value(kObjectType), json.GetAllocator());\n    Value& metal_globals = json[\"meta globals\"];\n    json.AddMember(\"structs\", Value(kObjectType), json.GetAllocator());\n    Value& structs = json[\"structs\"];\n    json.AddMember(\"functions\", Value(kObjectType), json.GetAllocator());\n    Value& functions = json[\"functions\"];\n\n    std::string current_file = \"\";\n\n    for(auto& child : root->children)\n    {\n        if(child->is_type<FILE_PATH>())\n        {\n            current_file = std::string(child->string_view());\n        }\n        else if(child->is_type<META_GLOBAL>())\n        {\n            auto meta_dict = get_meta_dict(child.get(), json.GetAllocator());\n            if(meta_dict.count(\"meta\"))\n            {\n                metal_globals.AddMember(Value(current_file.c_str(), json.GetAllocator()), meta_dict[\"meta\"], json.GetAllocator());\n            }\n        }\n        else if(child->is_type<STRUCT_DEF>())\n        {\n            std::string name = std::string(get<IDENTIFIER>(child.get())->string_view());\n            \n            structs.AddMember(Value(name.c_str(), json.GetAllocator()), Value(kObjectType), json.GetAllocator());\n            Value& struct_def = structs[name.c_str()];\n            struct_def.AddMember(\"name\", Value(name.c_str(), json.GetAllocator()), json.GetAllocator());\n            struct_def.AddMember(\"file\", Value(current_file.c_str(), json.GetAllocator()), json.GetAllocator());\n            struct_def.AddMember(\"members\", Value(kArrayType), json.GetAllocator());\n            \n            parse_tree::node* meta = get<META>(child.get());\n            auto meta_dict = get_meta_dict(meta, json.GetAllocator());\n\n            if(meta_dict.count(\"meta\"))\n            {\n                struct_def.AddMember(\"meta\", meta_dict[\"meta\"], json.GetAllocator());\n            }\n            else\n            {\n                struct_def.AddMember(\"meta\", Value(kObjectType), json.GetAllocator());\n            }\n            \n            Value& members_array = struct_def[\"members\"];\n\n            parse_tree::node* members = get<MEMBERS>(child.get());\n            if(!members) continue;\n\n            for(auto& member : members->children)\n            {\n                std::string name = std::string(get<IDENTIFIER>(member.get())->string_view());\n                std::string type = std::string(get<TYPE>(member.get())->string_view());\n                int array_size = 0;\n                \n                parse_tree::node* array_size_node = get<ARRAY_SIZE>(member.get());\n                if(array_size_node)\n                {\n                    std::string size = std::string(get<DIGITS>(array_size_node)->string_view());\n                    array_size = std::stoi(size);\n                }\n\n                Value member_def = Value(kObjectType);\n                member_def.AddMember(\"name\", Value(name.c_str(), json.GetAllocator()), json.GetAllocator());\n                member_def.AddMember(\"type\", Value(type.c_str(), json.GetAllocator()), json.GetAllocator());\n                member_def.AddMember(\"size\", Value(array_size), json.GetAllocator());\n                \n                if(meta_dict.count(name))\n                {\n                    member_def.AddMember(\"meta\", meta_dict[name], json.GetAllocator());\n                }\n                else\n                {\n                    member_def.AddMember(\"meta\", Value(kObjectType), json.GetAllocator());\n                }\n\n                members_array.PushBack(member_def, json.GetAllocator());\n            }\n        }\n        else if(child->is_type<FUNCTION_DEC>())\n        {\n            parse_tree::node* signature = get<FUNCTION_SIG>(child.get());\n            std::string name = std::string(get<IDENTIFIER>(signature)->string_view());\n            std::string signature_str = std::string(signature->string_view());\n            signature_str = remove_extra_whitespace(signature_str);\n            std::string key_name = name;\n            if(functions.HasMember(key_name.c_str()))\n            {\n                key_name += \" - \" + signature_str;\n            }\n            std::string type = std::string(get<TYPE>(signature)->string_view());\n            \n            functions.AddMember(Value(key_name.c_str(), json.GetAllocator()), Value(kObjectType), json.GetAllocator());\n            Value& function_dec = functions[key_name.c_str()];\n            function_dec.AddMember(\"name\", Value(name.c_str(), json.GetAllocator()), json.GetAllocator());\n            function_dec.AddMember(\"type\", Value(type.c_str(), json.GetAllocator()), json.GetAllocator());\n            function_dec.AddMember(\"file\", Value(current_file.c_str(), json.GetAllocator()), json.GetAllocator());\n            function_dec.AddMember(\"signature\", Value(signature_str.c_str(), json.GetAllocator()), json.GetAllocator());\n            function_dec.AddMember(\"parameters\", Value(kArrayType), json.GetAllocator());\n\n            parse_tree::node* meta = get<META>(child.get());\n            auto meta_dict = get_meta_dict(meta, json.GetAllocator());\n\n            if(meta_dict.count(\"meta\"))\n            {\n                function_dec.AddMember(\"meta\", meta_dict[\"meta\"], json.GetAllocator());\n            }\n            else\n            {\n                function_dec.AddMember(\"meta\", Value(kObjectType), json.GetAllocator());\n            }\n\n            Value& parameters_array = function_dec[\"parameters\"];\n\n            parse_tree::node* parameters = get<PARAMETERS>(signature);\n            if(!parameters) continue;\n\n            for(auto& parameter : parameters->children)\n            {\n                std::string name = std::string(get<IDENTIFIER>(parameter.get())->string_view());\n                std::string type = std::string(get<TYPE>(parameter.get())->string_view());\n                std::string io = \"in\";\n                int array_size = 0;\n                \n                parse_tree::node* array_size_node = get<ARRAY_SIZE>(parameter.get());\n                if(array_size_node)\n                {\n                    std::string size = std::string(get<DIGITS>(array_size_node)->string_view());\n                    array_size = std::stoi(size);\n                }\n                \n                parse_tree::node* io_node = get<IO>(parameter.get());\n                if(io_node)\n                {\n                    io = std::string(io_node->string_view());\n                }\n\n                Value parameter_dec = Value(kObjectType);\n                parameter_dec.AddMember(\"name\", Value(name.c_str(), json.GetAllocator()), json.GetAllocator());\n                parameter_dec.AddMember(\"type\", Value(type.c_str(), json.GetAllocator()), json.GetAllocator());\n                parameter_dec.AddMember(\"size\", Value(array_size), json.GetAllocator());\n                parameter_dec.AddMember(\"io\", Value(io.c_str(), json.GetAllocator()), json.GetAllocator());\n                \n                if(meta_dict.count(name))\n                {\n                    parameter_dec.AddMember(\"meta\", meta_dict[name], json.GetAllocator());\n                }\n                else\n                {\n                    parameter_dec.AddMember(\"meta\", Value(kObjectType), json.GetAllocator());\n                }\n\n                parameters_array.PushBack(parameter_dec, json.GetAllocator());\n            }\n        }\n    }\n\n    StringBuffer result;\n    PrettyWriter<StringBuffer> writer(result);\n    json.Accept(writer);\n\n    std::cout << result.GetString();\n\n    return 0;\n}\n"
  },
  {
    "path": "Malt/GL/Mesh.py",
    "content": "import ctypes\n\nfrom Malt.GL.GL import *\n\nclass Mesh():\n\n    def __init__(self, position, index, normal=None, tangent=None, uvs=[], colors=[]):\n        self.position = None\n        self.normal = None\n        self.tangent = None\n        self.uvs = []\n        self.colors = []\n        self.color_is_srgb = [False]*4\n\n        self.index_count = len(index)\n\n        self.VAO = None\n        self.EBO = gl_buffer(GL_INT, 1)\n        index_buffer = index\n        #make sure it's an uint32 c array\n        if isinstance(index_buffer, ctypes.Array) == False or index_buffer._type_ != ctypes.c_uint32:\n            index_buffer = gl_buffer(GL_UNSIGNED_INT, len(index), index)\n        glGenBuffers(1, self.EBO)\n        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.EBO[0])\n        glBufferData(GL_ELEMENT_ARRAY_BUFFER, len(index_buffer) * 4, index_buffer, GL_STATIC_DRAW)\n        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)\n        \n        def load_VBO(data):\n            #make sure it's a float c array\n            if isinstance(data, ctypes.Array) == False or data._type_ != ctypes.c_float:\n                data = gl_buffer(GL_FLOAT, len(data), data)\n            VBO = gl_buffer(GL_INT, 1)\n            glGenBuffers(1, VBO)\n            glBindBuffer(GL_ARRAY_BUFFER, VBO[0])\n            glBufferData(GL_ARRAY_BUFFER, len(data) * 4, data, GL_STATIC_DRAW)\n            glBindBuffer(GL_ARRAY_BUFFER, 0)\n            return VBO\n        \n        self.position = load_VBO(position)\n        if normal:\n            self.normal = load_VBO(normal)\n        if tangent:\n            self.tangent = load_VBO(tangent)\n        for uv in uvs:\n            self.uvs.append(load_VBO(uv))\n        for color in colors:\n            self.colors.append(load_VBO(color))\n    \n    #Blender uses different OGL contexts, this function should only be called from the draw callback\n    #https://developer.blender.org/T65208\n    def __load_VAO(self):\n        self.VAO = gl_buffer(GL_INT, 1)\n        glGenVertexArrays(1, self.VAO)\n        glBindVertexArray(self.VAO[0])\n\n        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.EBO[0])\n\n        def bind_VBO(VBO, index, element_size):\n            glBindBuffer(GL_ARRAY_BUFFER, VBO[0])\n            glEnableVertexAttribArray(index)\n            glVertexAttribPointer(index, element_size, GL_FLOAT, GL_FALSE, 0, None)\n        \n        bind_VBO(self.position, 0, 3)\n        if(self.normal):\n            bind_VBO(self.normal, 1, 3)\n        if(self.tangent):\n            bind_VBO(self.tangent, 2, 4)\n        \n        max_uv = 4\n        uv0_index = 3\n        color0_index = uv0_index + max_uv\n        for i, uv in enumerate(self.uvs):\n            assert(i < max_uv)\n            bind_VBO(uv, uv0_index + i, 2)\n        for i, color in enumerate(self.colors):\n            bind_VBO(color, color0_index + i, 4)\n\n        glBindVertexArray(0)\n\n    def bind(self):\n        if self.VAO is None:\n            self.__load_VAO()\n        glBindVertexArray(self.VAO[0])\n    \n    def draw(self, bind=True):\n        if bind:\n            self.bind()\n        glDrawElements(GL_TRIANGLES, self.index_count, GL_UNSIGNED_INT, NULL)\n        if bind:\n            glBindVertexArray(0)\n    \n    def __del__(self):\n        if self.VAO:\n            glDeleteVertexArrays(1, self.VAO)\n        \n        def delete_buffer(buffer):\n            if buffer:\n                glDeleteBuffers(1, buffer)\n\n        delete_buffer(self.EBO)\n        delete_buffer(self.position)\n        delete_buffer(self.normal)\n        delete_buffer(self.tangent)\n        for uv in self.uvs:\n            delete_buffer(uv)\n        for color in self.colors:\n            delete_buffer(color)\n            \n\n#Class for custom mesh loading\n#See Bridge/Mesh.py\nclass MeshCustomLoad(Mesh):\n\n    def __init__(self):\n        self.position = None\n        self.normal = None\n        self.tangent = None\n        self.uvs = []\n        self.colors = []\n        self.color_is_srgb = [False]*4\n\n        self.index_count = 0\n\n        self.VAO = None\n        self.EBO = None\n"
  },
  {
    "path": "Malt/GL/RenderTarget.py",
    "content": "import ctypes\n\nfrom Malt.GL.GL import *\n\n\nclass RenderTarget():\n\n    def __init__(self, targets=[], depth_stencil=None):\n        self.FBO = gl_buffer(GL_INT, 1)\n        glGenFramebuffers(1, self.FBO)\n\n        self.targets = targets\n        self.depth_stencil = depth_stencil\n\n        self.resolution = None\n\n        glBindFramebuffer(GL_FRAMEBUFFER, self.FBO[0])\n\n        attachments = gl_buffer(GL_INT, len(targets))\n        for i, target in enumerate(targets):\n            if target:\n                if self.resolution is None : self.resolution = target.resolution\n                assert(target.resolution == self.resolution)\n                if hasattr(target, 'attach'):\n                    target.attach(GL_COLOR_ATTACHMENT0+i)\n                else:\n                    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+i, GL_TEXTURE_2D, target.texture[0], 0)\n                attachments[i] = GL_COLOR_ATTACHMENT0+i\n            else:\n                attachments[i] = GL_NONE\n        \n        glDrawBuffers(len(attachments), attachments)\n        \n        if depth_stencil:\n            if self.resolution is None : self.resolution = depth_stencil.resolution\n            assert(depth_stencil.resolution == self.resolution)\n            attachment = {\n                GL_DEPTH_STENCIL : GL_DEPTH_STENCIL_ATTACHMENT,\n                GL_DEPTH_COMPONENT : GL_DEPTH_ATTACHMENT,\n                GL_STENCIL : GL_STENCIL_ATTACHMENT,\n            }\n            if hasattr(depth_stencil, 'attach'):\n                depth_stencil.attach(attachment[depth_stencil.format])\n            else:\n                glFramebufferTexture2D(GL_FRAMEBUFFER, attachment[depth_stencil.format], GL_TEXTURE_2D, depth_stencil.texture[0], 0)\n        \n        glBindFramebuffer(GL_FRAMEBUFFER, 0)\n    \n    def bind(self):\n        glBindFramebuffer(GL_FRAMEBUFFER, self.FBO[0])\n        glViewport(0, 0, self.resolution[0], self.resolution[1])\n        glDisable(GL_SCISSOR_TEST)\n    \n    def clear(self, colors=[], depth=None, stencil=None):\n        self.bind()\n        flags = 0\n        for i, color in enumerate(colors):\n            function_map = {\n                GL_INT : glClearBufferiv,\n                GL_UNSIGNED_INT : glClearBufferuiv,\n                GL_FLOAT : glClearBufferfv,\n                GL_HALF_FLOAT : glClearBufferfv,\n                GL_UNSIGNED_BYTE : glClearBufferfv,\n            }\n            target = self.targets[i]\n            if target is None:\n                continue\n            if isinstance(color, ctypes.Array) == False:\n                size = 1\n                try: size = len(color)\n                except: pass\n                color = gl_buffer(target.data_format, size, color)\n            function_map[target.data_format](GL_COLOR, i, color)\n        if depth:\n            glClearDepth(depth)\n            flags |= GL_DEPTH_BUFFER_BIT\n        if stencil:\n            glClearStencil(stencil)\n            flags |= GL_STENCIL_BUFFER_BIT\n        glClear(flags)\n    \n    def __del__(self):\n        glDeleteFramebuffers(1, self.FBO)\n\n\nclass TargetBase():\n    def attach(self, attachment):\n        pass\n\n\nclass ArrayLayerTarget(TargetBase):\n    def __init__(self, texture_array, layer):\n        self.texture_array = texture_array.texture[0]\n        self.layer = layer\n        self.resolution = texture_array.resolution\n        self.internal_format = texture_array.internal_format\n        self.format = texture_array.format\n        self.data_format = texture_array.data_format\n    \n    def attach(self, attachment):\n        glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, self.texture_array, 0, self.layer)\n"
  },
  {
    "path": "Malt/GL/Shader.py",
    "content": "import ctypes, os\n\nfrom Malt.GL.GL import *\nfrom Malt.Utils import LOG\n\n\nclass Shader():\n\n    def __init__(self, vertex_source, pixel_source):\n        if vertex_source and pixel_source:\n            self.vertex_source = vertex_source\n            self.pixel_source = pixel_source\n            self.program, self.error = compile_gl_program(vertex_source, pixel_source)\n            self.validator = glslang_validator(vertex_source,'vert')\n            self.validator += glslang_validator(pixel_source,'frag')\n            if self.validator == '':\n                self.validator = None\n        else:\n            self.vertex_source = vertex_source\n            self.pixel_source = pixel_source\n            self.program = None\n            self.error = 'NO SOURCE'\n            self.validator = None\n        self.uniforms = {}\n        self.textures = {}\n        self.uniform_blocks = {}\n        if self.error == '':\n            self.error = None\n            self.uniforms = reflect_program_uniforms(self.program)\n            texture_index = 0\n            for name, uniform in self.uniforms.items():\n                if uniform.is_sampler():\n                    uniform.set_value(texture_index)\n                    texture_index += 1 \n                    self.textures[name] = None\n            self.uniform_blocks = reflect_program_uniform_blocks(self.program)\n        elif self.error != 'NO SOURCE':\n            LOG.error(self.error)\n        \n    def bind(self):\n        glUseProgram(self.program)\n        for uniform in self.uniforms.values():\n            uniform.bind()\n        for name, texture in self.textures.items():\n            if name not in self.uniforms:\n                LOG.debug(\"Texture Uniform {} not found\".format(name))\n                continue\n            uniform = self.uniforms[name]\n            glActiveTexture(GL_TEXTURE0 + uniform.value[0])\n            if texture:\n                if hasattr(texture, 'bind'):\n                    texture.bind()\n                else: #Then it's just a externally generated bind code\n                    glBindTexture(uniform.texture_type(), texture)\n            else:\n                glBindTexture(uniform.texture_type(), 0)\n    \n    def copy(self):\n        new = Shader(None, None)\n        new.vertex_source = self.vertex_source\n        new.pixel_source = self.pixel_source\n        new.program = self.program\n        new.error = self.error\n        for name, uniform in self.uniforms.items():\n            new.uniforms[name] = uniform.copy()\n        for name, texture in self.textures.items():\n            new.textures[name] = texture\n        for name, block in self.uniform_blocks.items():\n            new.uniform_blocks[name] = block\n        \n        return new\n    \n    def __del__(self):\n        #TODO: Programs are shared between Shaders. Should refcount them\n        pass\n\n\nclass GLUniform():\n    def __init__(self, index, type, value, array_length=1):\n        self.index = index\n        self.type = type\n        self.base_type, self.base_size = uniform_type_to_base_type_and_size(self.type)\n        self.array_length = array_length\n        self.set_function = uniform_type_set_function(self.type)\n        self.value = None\n        self.set_value(value)\n    \n    def is_sampler(self):\n        return 'SAMPLER' in GL_ENUMS[self.type]\n    \n    def texture_type(self):\n        if self.is_sampler() == False:\n            return None\n        table = {\n            '1D_ARRAY': GL_TEXTURE_1D_ARRAY,\n            '_1D': GL_TEXTURE_1D,\n            '2D_ARRAY': GL_TEXTURE_2D_ARRAY,\n            '_2D': GL_TEXTURE_2D,\n            '_3D': GL_TEXTURE_3D,\n            'CUBE_MAP_ARRAY': GL_TEXTURE_CUBE_MAP_ARRAY,\n            '_CUBE_MAP': GL_TEXTURE_CUBE_MAP,\n        }\n        name = GL_ENUMS[self.type]\n        for key, value in table.items():\n            if key in name:\n                return value\n    \n    def set_value(self, value):\n        if self.base_type == GL_UNSIGNED_INT:\n            try: value = max(0, value)\n            except: value = [max(0, v) for v in value]\n        self.value = gl_buffer(self.base_type, self.base_size * self.array_length, value)\n    \n    def set_buffer(self, buffer):\n        self.value = buffer\n    \n    def bind(self, buffer=None):\n        if buffer is None:\n            buffer = self.value\n        self.set_function(self.index, self.array_length, buffer)\n    \n    def copy(self):\n        return GLUniform(\n            self.index,\n            self.type, \n            self.value, \n            self.array_length)\n\n\nclass UBO():\n\n    BINDS = {}\n\n    def __init__(self):\n        self.size = 0\n        self.buffer = gl_buffer(GL_INT, 1)\n        self.location = None\n        glGenBuffers(1, self.buffer)\n    \n    def load_data(self, structure):\n        self.size = ctypes.sizeof(structure)\n        glBindBuffer(GL_UNIFORM_BUFFER, self.buffer[0])\n        glBufferData(GL_UNIFORM_BUFFER, self.size, ctypes.pointer(structure), GL_STREAM_DRAW)\n        glBindBuffer(GL_UNIFORM_BUFFER, 0)\n\n    def bind(self, uniform_block):\n        location = uniform_block['bind']\n        if self.location != location or self.BINDS[location] != self:\n            glBindBufferRange(GL_UNIFORM_BUFFER, location, self.buffer[0], 0, min(self.size, uniform_block['size']))\n            self.location = location\n            self.BINDS[location] = self\n    \n    def __del__(self):\n        glDeleteBuffers(1, self.buffer[0])\n\n\ndef shader_preprocessor(shader_source, include_directories=[], definitions=[]):\n    import tempfile, subprocess, sys, platform\n\n    if hasGLExtension('GL_ARB_bindless_texture'):\n        definitions.append('GL_ARB_bindless_texture')\n    \n    shader_source = shader_source + '\\n'\n    tmp = tempfile.NamedTemporaryFile(delete=False)\n    tmp.write(shader_source.encode('utf-8'))\n    tmp.close()\n\n    py_version = str(sys.version_info[0])+str(sys.version_info[1])\n    dependencies_path = os.path.join(os.path.dirname(__file__), '..', f'.Dependencies-{py_version}')\n    mcpp = os.path.join(dependencies_path, f'mcpp-{platform.system()}')\n\n    if platform.system() == \"Linux\":\n        # NixOS and Guix System will cannot run the included mcpp binary\n        result = subprocess.run(f'\"{mcpp}\"', shell=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n        if result.returncode != 0:\n            # Search for system mcpp\n            for directory in os.get_exec_path():\n                path = os.path.join(directory, \"mcpp\")\n                if os.path.isfile(path) and os.access(path, os.X_OK):\n                    mcpp = path\n\n\n    command = f'\"{mcpp}\"'\n    command += ' -C' #keep comments\n    for directory in include_directories:\n        command += f' -I\"{directory}\"'\n    for definition in definitions:\n        command += f' -D\"{definition}\"'\n    command += f' \"{tmp.name}\"'\n    \n    if platform.system() == 'Windows':\n        #run with utf8 code page to support non-ascii paths\n        command = f'CHCP 65001 > nul && {command}'\n    \n    try:\n        result = subprocess.run(command, shell=True, stdout = subprocess.PIPE, stderr=subprocess.PIPE)\n        if result.returncode != 0:\n            raise Exception(result.stderr.decode('utf-8'))\n    except:\n        if platform.system() == 'Linux': #Linux seemingly removes exec permissions when unzipping (?)\n            import stat\n            os.chmod(mcpp, os.stat(mcpp).st_mode | stat.S_IEXEC)\n            result = subprocess.run(command, shell=True, stdout = subprocess.PIPE, stderr=subprocess.PIPE)\n    finally:\n        os.remove(tmp.name)\n        if result.returncode != 0:\n            raise Exception(result.stderr.decode('utf-8'))\n        else:\n            return result.stdout.decode('utf-8')\n\n\n__LINE_DIRECTIVE_SUPPORT = None\n\ndef directive_line_support():\n    global __LINE_DIRECTIVE_SUPPORT\n    if __LINE_DIRECTIVE_SUPPORT is not None:\n        return __LINE_DIRECTIVE_SUPPORT\n    def try_compilation (line_directive):\n        src = f'''\n        #version 410 core\n        #extension GL_ARB_shading_language_include : enable\n        {line_directive}\n        layout (location = 0) out vec4 OUT_COLOR;\n        void main() {{ OUT_COLOR = vec4(1); }}\n        '''\n        status = gl_buffer(GL_INT,1)\n        shader = glCreateShader(GL_FRAGMENT_SHADER)\n        glShaderSource(shader, src)\n        glCompileShader(shader)\n        glGetShaderiv(shader, GL_COMPILE_STATUS, status)\n        glDeleteShader(shader)\n        return status[0] != GL_FALSE\n    \n    if try_compilation('#line 1 \"/ .A1a1!·$%&/()=?¿|@#~€/test.glsl\"'):\n        __LINE_DIRECTIVE_SUPPORT = 'FULL'\n    elif try_compilation('#line 1 \"/Basic test/test_1.glsl\"'):\n        __LINE_DIRECTIVE_SUPPORT = 'BASIC_STRING'\n    elif try_compilation('#line 1 1'):\n        __LINE_DIRECTIVE_SUPPORT = 'FILE_NUMBER'\n    elif try_compilation('#line 1'):\n        __LINE_DIRECTIVE_SUPPORT = 'LINE_NUMBER'\n    else:\n        __LINE_DIRECTIVE_SUPPORT = 'NONE'\n    \n    return __LINE_DIRECTIVE_SUPPORT\n\n\ndef fix_line_directive_paths(source):\n    support = directive_line_support()\n    if support == 'FULL':\n        return source\n    include_paths = []\n    result = ''\n    for line in source.splitlines(keepends=True):\n        if line.startswith(\"#line\") and '\"' in line:\n            start = line.index('\"')\n            end = line.index('\"', start + 1)\n            include_path = line[start:end+1]\n            if support == 'BASIC_STRING':\n                basic_string = ''.join([c for c in include_path if c.isalnum() or c in '/._ '])\n                line = line.replace(include_path, f'\"{basic_string}\"')\n            elif support == 'FILE_NUMBER':\n                    if include_path not in include_paths:\n                        include_paths.append(include_path)\n                    line = line.replace(include_path, str(include_paths.index(include_path)))\n            elif support == 'LINE_NUMBER':\n                if '\"' in line:\n                    line = line.split('\"',1)[0]\n            else:\n                line = \"\\n\"\n        result += line\n    return result\n\n\ndef compile_gl_program(vertex, fragment):\n    def finalize_source(source):\n        bindless_setup = '''\n        #define OPTIONALLY_BINDLESS\n        '''\n        if hasGLExtension('GL_ARB_bindless_texture'):\n            bindless_setup = '''\n            #extension GL_ARB_bindless_texture : enable\n            #define OPTIONALLY_BINDLESS layout(bindless_sampler)\n            '''\n        import textwrap\n        source = textwrap.dedent(f'''\n        #version 450 core\n        #extension GL_ARB_shading_language_include : enable\n        {bindless_setup}\n        #line 1 \"src\"\n        ''') + source\n        return fix_line_directive_paths(source)\n    \n    vertex = finalize_source(vertex)\n    fragment = finalize_source(fragment)\n\n    status = gl_buffer(GL_INT,1)\n    info_log = gl_buffer(GL_BYTE, 1024)\n\n    hash_src = vertex + fragment\n    ''.splitlines()\n    hash_src = ''.join([line for line in hash_src.splitlines(True) if line.startswith('#line') == False])\n    import hashlib, tempfile\n    shader_hash = hashlib.sha1(hash_src.encode()).hexdigest()\n    cache_folder = os.path.join(tempfile.gettempdir(), 'MALT_SHADERS_CACHE')\n    os.makedirs(cache_folder, exist_ok=True)\n    cache_path = os.path.join(cache_folder, shader_hash+'.bin')\n    format_path = os.path.join(cache_folder, shader_hash+'.fmt')\n    cache, format = None, None\n    if os.path.exists(cache_path) and os.path.exists(format_path):\n        from pathlib import Path\n        Path(cache_path).touch()\n        with open(cache_path, 'rb') as f:\n            bin = f.read()\n            cache = (GLubyte*len(bin)).from_buffer_copy(bin)\n        Path(format_path).touch()\n        with open(format_path, 'rb') as f:\n            format = GLuint.from_buffer_copy(f.read())\n    \n    program = glCreateProgram()\n    error = \"\"\n\n    if cache:\n        try:\n            glProgramBinary(program, format, cache, len(cache))\n            glGetProgramiv(program, GL_LINK_STATUS, status)\n            if status[0] != GL_FALSE:\n                return (program, error)\n        except:\n            #Program binary format can change on driver updates\n            LOG.error(f\"Failed to load cached program binary: {shader_hash} ({format})\")\n\n    def compile_shader (source, shader_type):\n        shader = glCreateShader(shader_type)\n        glShaderSource(shader, source)\n        glCompileShader(shader)\n\n        glGetShaderiv(shader, GL_COMPILE_STATUS, status)\n        if status[0] == GL_FALSE:\n            info_log = glGetShaderInfoLog(shader)\n            nonlocal error\n            error += 'SHADER COMPILER ERROR :\\n' + buffer_to_string(info_log)\n        \n        return shader\n\n    vertex_shader = compile_shader(vertex, GL_VERTEX_SHADER)\n    fragment_shader = compile_shader(fragment, GL_FRAGMENT_SHADER)\n\n    glAttachShader(program, vertex_shader)\n    glAttachShader(program, fragment_shader)\n    glLinkProgram(program)\n\n    glDeleteShader(vertex_shader)\n    glDeleteShader(fragment_shader)\n    \n    glGetProgramiv(program, GL_LINK_STATUS, status)\n    if status[0] == GL_FALSE:\n        info_log = glGetProgramInfoLog(program)\n        error += 'SHADER LINKER ERROR :\\n' + buffer_to_string(info_log)\n    else:\n        length = gl_buffer(GL_INT, 1)\n        glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, length)\n        format = gl_buffer(GL_UNSIGNED_INT, 1)\n        buffer = gl_buffer(GL_UNSIGNED_BYTE, length[0])\n        glGetProgramBinary(program, length[0], NULL, format, buffer)\n        with open(cache_path, 'wb') as f:\n            f.write(buffer)\n        with open(format_path, 'wb') as f:\n            f.write(format)\n\n    return (program, error)\n\n\ndef reflect_program_uniforms(program):\n    max_string_length = 128\n    string_length = gl_buffer(GL_INT, 1)\n    uniform_type = gl_buffer(GL_INT, 1)\n    uniform_name = gl_buffer(GL_BYTE, max_string_length)\n    array_length = gl_buffer(GL_INT, 1)\n    uniform_count = gl_buffer(GL_INT,1)\n    \n    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, uniform_count)\n\n    uniforms = {}\n\n    query_indices = gl_buffer(GL_INT, uniform_count[0])\n    for i in range(len(query_indices)):\n        query_indices[i] = i\n    block_indices = gl_buffer(GL_INT, uniform_count[0])\n    glGetActiveUniformsiv(program, uniform_count[0], query_indices, GL_UNIFORM_BLOCK_INDEX, block_indices)\n\n    for i in range(0, uniform_count[0]):\n        if block_indices[i] != -1:\n            continue #A built-in uniform or an Uniform Block\n\n        glGetActiveUniform(program, i, max_string_length, string_length,\n        array_length, uniform_type, uniform_name)\n        name = buffer_to_string(uniform_name)\n\n        #Uniform location can be different from index\n        location = glGetUniformLocation(program, name)\n\n        if location == -1:\n            continue #A built-in uniform or an Uniform Block\n\n        base_type, size = uniform_type_to_base_type_and_size(uniform_type[0])\n\n        #TODO: Should use glGetnUniform to support arrays\n        gl_get = {\n            GL_FLOAT : glGetUniformfv,\n            GL_DOUBLE : glGetUniformdv,\n            GL_INT : glGetUniformiv,\n            GL_UNSIGNED_INT : glGetUniformuiv,\n            GL_BOOL : glGetUniformiv, #TODO: check,\n        }\n        value = gl_buffer(base_type, size * array_length[0])\n        gl_get[base_type](program, location, value)\n\n        if array_length[0] > 1:\n            for i in range(array_length[0]):\n                _name = '[{}]'.format(i).join(name.rsplit('[0]', 1))\n                _value = value[i*size:i*size+size]\n                _location = glGetUniformLocation(program, _name)\n                uniforms[_name] = GLUniform(_location, uniform_type[0], _value)\n        else:\n            uniforms[name] = GLUniform(location, uniform_type[0], value)\n    \n    return uniforms\n\n\ndef uniform_type_to_base_type_and_size(type):\n    base_types = {\n        'GL_FLOAT' : GL_FLOAT,\n        'GL_DOUBLE' : GL_DOUBLE,\n        'GL_INT' : GL_INT,\n        'GL_UNSIGNED_INT' : GL_UNSIGNED_INT,\n        'GL_BOOL' : GL_BOOL, #TODO: check docs,\n    }\n    gl_sizes = {\n        'VEC2' : 2,\n        'VEC3' : 3,\n        'VEC4' : 4,\n        'MAT2' : 4,\n        'MAT3' : 9,\n        'MAT4' : 16,\n    }\n    type_name = GL_ENUMS[type]\n    if 'SAMPLER' in type_name or 'IMAGE' in type_name:\n        return (GL_INT, 1)\n    for base_name, base_type in base_types.items():\n        if type_name.startswith(base_name):\n            for size_name, size in gl_sizes.items():\n                if size_name in type_name:\n                    return (base_type, size)\n            return (base_type, 1)\n    raise Exception(type_name, ' Uniform type not supported')\n\n\ndef uniform_type_set_function(uniform_type):\n    base_type, size = uniform_type_to_base_type_and_size(uniform_type)\n    gl_types = {\n        GL_FLOAT : 'fv',\n        GL_DOUBLE : 'dv',\n        GL_INT : 'iv',\n        GL_UNSIGNED_INT : 'uiv',\n        GL_BOOL : 'uiv',\n    }\n    gl_size = {\n        1 : '1',\n        2 : '2',\n        3 : '3',\n        4 : '4', #TODO: Matrix2\n        9 : 'Matrix3',\n        16: 'Matrix4'\n    }\n    function_name = 'glUniform' + gl_size[size] + gl_types[base_type]\n    function = globals()[function_name]\n    if size > 4: #is matrix\n        def set_matrix_wrapper(location, count, value):\n            function(location, count, GL_FALSE, value)\n        return set_matrix_wrapper\n    elif base_type == GL_BOOL and size > 1:\n        function = globals()[f'glUniform{gl_size[size]}ui'] #uiv doesn't work on AMD (See #439)\n        def bool_wrapper(location, count, value):\n            function(location, *value)\n        return bool_wrapper\n    else:\n        return function\n\n\ndef reflect_program_uniform_blocks(program):\n    block_count = gl_buffer(GL_INT,1)\n    max_string_length = 128\n    block_name = gl_buffer(GL_BYTE, max_string_length)\n    block_bind = gl_buffer(GL_INT, 1)\n    block_size = gl_buffer(GL_INT, 1)\n\n    glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, block_count)\n\n    blocks = {}\n    for i in range(0, block_count[0]):\n        glGetActiveUniformBlockName(program, i, max_string_length, NULL, block_name)\n        name = buffer_to_string(block_name)\n        glUniformBlockBinding(program, i, i) # All Uniform Blocks are binded at 0 by default. :/\n        glGetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_BINDING, block_bind)\n        glGetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_DATA_SIZE, block_size)\n        blocks[name] = {\n            'bind' : block_bind[0],\n            'size' : block_size[0],\n            'name' : name,\n        }\n    \n    return blocks\n\nUSE_GLSLANG_VALIDATOR = False\n\ndef glsl_reflection(code, root_paths=[]):\n    import tempfile, subprocess, json, platform\n    \n    GLSLParser = os.path.join(os.path.dirname(__file__), 'GLSLParser', '.bin', 'GLSLParser')\n    \n    tmp = tempfile.NamedTemporaryFile(delete=False)\n    tmp.write(code.encode('utf-8'))\n    tmp.close()\n\n    command = f'\"{GLSLParser}\" \"{tmp.name}\"'\n    if platform.system() == 'Windows':\n        #run with utf8 code page to support non-ascii paths\n        command = f'CHCP 65001 > nul && {command}'\n    \n    try:\n        json_string = subprocess.check_output(command, shell=True)\n    except:\n        import stat\n        os.chmod(GLSLParser, os.stat(GLSLParser).st_mode | stat.S_IEXEC)\n        json_string = subprocess.check_output(command, shell=True)\n    \n    os.remove(tmp.name)\n    \n    reflection = json.loads(json_string)\n\n    def patch_meta(dic):\n        for e in dic.values():\n            path = e['file']\n            if path in reflection['meta globals']:\n                for k, v in reflection['meta globals'][path].items():\n                    if k not in e['meta'].keys():\n                        e['meta'][k] = v\n    \n    patch_meta(reflection['functions'])\n    patch_meta(reflection['structs'])\n    \n    from Malt.GL.GLSLEval import glsl_eval\n\n    def eval_meta(dict):\n        meta_dict = dict['meta']\n        for k, v in meta_dict.items():\n            try:\n                meta_dict[k] = glsl_eval(v)\n            except:\n                pass\n    \n    def meta_label(dict):\n        label = dict['meta'].get('label')\n        if label is None:\n            label = dict['name'].replace('_',' ').title()\n        dict['meta']['label'] = label\n\n    for function in reflection['functions'].values():\n        eval_meta(function)\n        meta_label(function)\n        for parameter in function['parameters']:\n            eval_meta(parameter)\n            meta_label(parameter)\n            \n    for struct in reflection['structs'].values():\n        eval_meta(struct)\n        meta_label(struct)\n        for member in struct['members']:\n            eval_meta(member)\n            meta_label(member)\n\n    def handle_paths(dic):\n        for e in dic.values():\n            path = e['file']\n            if '.internal.' in path or '__internal__' in path:\n                if 'internal' not in e['meta'].keys():\n                    e['meta']['internal'] = True\n            path = os.path.normpath(path)\n            for root_path in root_paths:\n                try:\n                    _path = os.path.relpath(e['file'], root_path)\n                    if len(_path) < len(path):\n                        path = _path\n                except: pass\n            e['file'] = path.replace('\\\\','/')\n    \n    handle_paths(reflection['structs'])\n    handle_paths(reflection['functions'])\n\n    functions = {}\n    for key, function in reflection['functions'].items():\n        new_key = function['file'].replace('/',' - ').replace('.glsl',' - ') + function['name']\n        if function['name'].isupper() or function['name'].startswith('_'):\n            new_key = function['name']\n        if new_key not in functions.keys():\n            functions[new_key] = []\n        functions[new_key].append(function)\n    reflection['functions'] = {}\n    for key, functions in functions.items():\n        if len(functions) == 1:\n            reflection['functions'][key] = functions[0]\n        else:\n            for function in functions:\n                new_key = key + ' - ' + function['signature']\n                reflection['functions'][new_key] = function\n    \n    reflection['subcategories'] = {}\n    for key, function in reflection['functions'].items():\n        subcategory = function['meta'].get('subcategory')\n        if subcategory:\n            if subcategory not in reflection['subcategories'].keys():\n                reflection['subcategories'][subcategory] = []\n            reflection['subcategories'][subcategory].append(key)\n    \n    return reflection\n\n\ndef glslang_validator(source, stage):\n    if not USE_GLSLANG_VALIDATOR:\n        return ''\n        \n    import subprocess\n    import tempfile\n    import os\n    tmp = tempfile.NamedTemporaryFile(delete=False)\n    tmp.write(source.replace('GL_ARB_shading_language_include', 'GL_GOOGLE_include_directive').encode('utf-8'))\n    tmp.close()\n    out = None\n    try:\n        out = subprocess.check_output(['glslangValidator','-S',stage,tmp.name])\n        if out != b'':\n            out = out\n    except subprocess.CalledProcessError as error:\n        out = error.output\n    except:\n        pass\n    os.unlink(tmp.name)\n    if out:\n        out = out.decode('utf-8')\n        #Remove the first line since it's the path to a temp file\n        out = out.split('\\n')[1]\n        stages = {\n            'vert' : 'VERTEX',\n            'frag' : 'PIXEL',\n        }\n        return '{} SHADER VALIDATION :\\n{}'.format(stages[stage], out)\n    else:\n        return ''\n"
  },
  {
    "path": "Malt/GL/Texture.py",
    "content": "from Malt.GL.GL import *\nfrom Malt.GL import Mesh\n\n\nclass Texture():\n\n    def __init__(self, resolution, internal_format=GL_RGB32F, data_format = None, data = NULL, \n        wrap=GL_CLAMP_TO_EDGE, min_filter=GL_LINEAR, mag_filter=GL_LINEAR, pixel_format=None, \n        build_mipmaps = False, anisotropy = False):\n        \n        self.resolution = resolution\n        self.internal_format = internal_format\n        self.format = pixel_format or internal_format_to_format(internal_format)\n        self.data_format = data_format or internal_format_to_data_format(internal_format)\n        self.channel_count = format_channels(self.format)\n        self.channel_size = data_format_size(self.data_format)\n\n        self.texture = gl_buffer(GL_INT, 1)\n        glGenTextures(1, self.texture)\n\n        glBindTexture(GL_TEXTURE_2D, self.texture[0])\n        glTexImage2D(GL_TEXTURE_2D, 0, self.internal_format, resolution[0], resolution[1], \n            0, self.format, self.data_format, data)\n\n        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap)\n        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap)\n        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter)\n        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter)\n        \n        if build_mipmaps:\n            glGenerateMipmap(GL_TEXTURE_2D)\n        if anisotropy:\n            level = glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY)\n            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, level)\n\n        glBindTexture(GL_TEXTURE_2D, 0)\n    \n    def bind(self):\n        glBindTexture(GL_TEXTURE_2D, self.texture[0])\n    \n    def __del__(self):\n        glDeleteTextures(1, self.texture)\n\n\nclass TextureArray():\n\n    def __init__(self, resolution, length, internal_format=GL_RGB32F, data_format = GL_FLOAT, data = NULL, wrap=GL_CLAMP_TO_EDGE, min_filter=GL_LINEAR, mag_filter=GL_LINEAR):\n        self.resolution = resolution\n        self.internal_format = internal_format\n        self.format = internal_format_to_format(internal_format)\n        self.data_format = data_format\n        self.length = length\n\n        self.texture = gl_buffer(GL_INT, 1)\n        glGenTextures(1, self.texture)\n\n        glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture[0])\n        glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, self.internal_format, resolution[0], resolution[1], length);\n        '''\n        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, self.internal_format, resolution[0], resolution[1], length,\n            0, self.format, self.data_format, data)\n        '''\n        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, wrap)\n        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, wrap)\n        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, min_filter)\n        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, mag_filter)\n\n        glBindTexture(GL_TEXTURE_2D_ARRAY, 0)\n    \n    def bind(self):\n        glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture[0])\n    \n    def __del__(self):\n        glDeleteTextures(1, self.texture)\n\n\nclass CubeMap():\n\n    def __init__(self, resolution, internal_format=GL_RGB32F, data_format = GL_FLOAT, data = [NULL]*6, min_filter=GL_LINEAR, mag_filter=GL_LINEAR):\n        self.resolution = resolution\n        self.internal_format = internal_format\n        self.format = internal_format_to_format(internal_format)\n        self.data_format = data_format\n\n        self.texture = gl_buffer(GL_INT, 1)\n        glGenTextures(1, self.texture)\n\n        glBindTexture(GL_TEXTURE_CUBE_MAP, self.texture[0])\n        \n        for i in range(6):\n            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, self.internal_format, resolution[0], resolution[1], \n                0, self.format, self.data_format, NULL)\n            #glGenerateMipmap(GL_TEXTURE_2D)\n\n        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)\n        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)\n        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)\n        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, min_filter)\n        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, mag_filter)\n\n        glBindTexture(GL_TEXTURE_CUBE_MAP, 0)\n    \n    def bind(self):\n        glBindTexture(GL_TEXTURE_CUBE_MAP, self.texture[0])\n    \n    def __del__(self):\n        glDeleteTextures(1, self.texture)\n\n\nclass CubeMapArray():\n\n    def __init__(self, resolution, length, internal_format=GL_RGB32F, data_format = GL_FLOAT, data = NULL, min_filter=GL_LINEAR, mag_filter=GL_LINEAR):\n        self.resolution = resolution\n        self.internal_format = internal_format\n        self.format = internal_format_to_format(internal_format)\n        self.data_format = data_format\n        self.length = length\n\n        self.texture = gl_buffer(GL_INT, 1)\n        glGenTextures(1, self.texture)\n\n        glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, self.texture[0])\n        glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 1, self.internal_format, resolution[0], resolution[1], length*6);\n        '''\n        glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, self.internal_format, resolution[0], resolution[1], length,\n            0, self.format, self.data_format, data)\n        '''\n        glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)\n        glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)\n        glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)\n        glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, min_filter)\n        glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, mag_filter)\n\n        glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, 0)\n    \n    def bind(self):\n        glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, self.texture[0])\n    \n    def __del__(self):\n        glDeleteTextures(1, self.texture)\n\n\nclass Gradient():\n\n    def __init__(self, data, resolution, internal_format=GL_RGBA32F, data_format = GL_FLOAT, nearest_interpolation = False):\n        self.resolution = resolution\n        self.internal_format = internal_format\n        self.format = internal_format_to_format(internal_format)\n        self.data_format = data_format\n\n        self.texture = gl_buffer(GL_INT, 1)\n        glGenTextures(1, self.texture)\n\n        glBindTexture(GL_TEXTURE_1D, self.texture[0])\n        glTexImage1D(GL_TEXTURE_1D, 0, self.internal_format, resolution, 0, self.format, self.data_format, data)\n        #glGenerateMipmap(GL_TEXTURE_2D)\n\n        glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)\n        interpolation = GL_NEAREST if nearest_interpolation else GL_LINEAR\n        glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, interpolation)\n        glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, interpolation)\n\n        glBindTexture(GL_TEXTURE_1D, 0)\n    \n    def bind(self):\n        glBindTexture(GL_TEXTURE_1D, self.texture[0])\n    \n    def __del__(self):\n        glDeleteTextures(1, self.texture)\n\n\ndef internal_format_to_data_format(internal_format):\n    name = GL_ENUMS[internal_format]\n    table = {\n        '32F' : GL_FLOAT,\n        '16F' : GL_HALF_FLOAT,\n        'UI' : GL_UNSIGNED_INT,\n        'I' : GL_INT,\n    }\n    for key, value in table.items():\n        if name.endswith(key):\n            return value\n    return GL_UNSIGNED_BYTE\n\ndef data_format_size(data_format):\n    name = GL_ENUMS[data_format]\n    table = {\n        'BYTE' : 1,\n        'SHORT' : 2,\n        'HALF' : 2,\n    }\n    for key, value in table.items():\n        if key in name:\n            return value\n    return 4\n\ndef internal_format_to_sampler_type(internal_format):\n    table = {\n        GL_UNSIGNED_BYTE : 'sampler2D',\n        GL_FLOAT : 'sampler2D',\n        GL_HALF_FLOAT : 'sampler2D',\n        GL_INT : 'isampler2D',\n        GL_UNSIGNED_INT : 'usampler2D'\n    }\n    return table[internal_format_to_data_format(internal_format)]\n\ndef internal_format_to_vector_type(internal_format):\n    table = {\n        'sampler2D' : 'vec4',\n        'isampler2D' : 'ivec4',\n        'usampler2D' : 'uvec4',\n    }\n    return table[internal_format_to_sampler_type(internal_format)]\n\ndef internal_format_to_format(internal_format):\n    name = GL_ENUMS[internal_format]\n    table = {\n        'GL_RGBA' : GL_RGBA,\n        'GL_RGB' : GL_RGB,\n        'GL_RG' : GL_RG,\n        'GL_R' : GL_RED,\n        'GL_DEPTH_COMPONENT' : GL_DEPTH_COMPONENT,\n        'GL_DEPTH24_STENCIL8' : GL_DEPTH_STENCIL,\n        'GL_DEPTH32F_STENCIL8' : GL_DEPTH_STENCIL,\n        'GL_STENCIL' : GL_STENCIL,\n    }\n    for key, value in table.items():\n        if key in name:\n            if name.endswith('I'):\n                return GL_NAMES[GL_ENUMS[value] + '_INTEGER']\n            else:\n                return value\n    raise Exception(name, ' Texture format not supported')\n\ndef format_channels(format):\n    table = {\n        GL_RGBA : 4,\n        GL_RGB : 3,\n        GL_RG : 2,\n        GL_RED : 1,\n    }\n    if format in table.keys():\n        return table[format]\n    return 1\n"
  },
  {
    "path": "Malt/GL/readme.md",
    "content": "# GL\n\nThe GL folder contains a series of modules that abstracts commonly needed OpenGL functionality.  \nIt doesn't try to be a complete abstraction, so it's meant to be used alongside raw OpenGL calls.  \n\n## [GL.py](GL.py) \nLoads OpenGL functions via [PyOpenGL](https://pypi.org/project/PyOpenGL/), provides OpenGL enums reflection dictionaries and implements a Python to OpenGL types conversion function via the *gl_buffer function*.\n\n## [Mesh.py](Mesh.py)\n\nBuilds VAOs with the same layout used by [Common.glsl](Malt/Shaders/Common.glsl).  \nEach parameter expects a flat 1D iterable with all the per-vertex data.  \nTangents, UVs and Vertex Colors buffers must be passed inside a list, even if you pass only one. Four of each are allowed at most.  \n\nThe MeshCustomLoad class doesn't do any loading by itself. It's reserved for cases where special opimizations are needed, like [BlenderMalt/MaltMeshes.py](BlenderMalt/MaltMeshes.py).  \n\n* [OpenGL Wiki - Vertex Array Object](https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_Array_Object)\n* [LearnOpenGL - Hello Triangle](https://learnopengl.com/Getting-started/Hello-Triangle)\n\n## [Shader.py](Shader.py)\n\nBuilds OpenGL programs from GLSL vertex and fragment shader source code and provides an interface for reflection and configuration of shader parameters.  \n\nThe *shader_preprocessor* function parses source code with a C preprocessor to provide support for *#include directives* in glsl shaders.  \n\n* [OpenGL Wiki - GLSL Objects](https://www.khronos.org/opengl/wiki/GLSL_Object)\n* [Learn OpenGL - Shaders](https://learnopengl.com/Getting-started/Shaders)\n\n## [Texture.py](Texture.py)\n\nLoads 1D textures (*Gradient*), 2D textures (*Texture*), 2D texture arrays (*TextureArray*), cube maps (*CubeMap*) and cube map arrays (*CubeMapArrays*) under a simple and mostly shared interface.  \nThe *internal_format* parameter is the [OpenGL image format](https://www.khronos.org/opengl/wiki/Image_Format) the texture will be stored inside the GPU.\n\n* [OpenGL Wiki - Texture](https://www.khronos.org/opengl/wiki/Texture)\n* [Learn OpenGL - Textures](https://learnopengl.com/Getting-started/Textures)\n\n## [RenderTarget.py](RenderTarget.py)\n\nBuilds FBOs from Textures. It accepts an arbitrary number of color targets and a single depth/stencil target.  \nColor target Textures must be passed inside a list, even if you pass only one.  \n\nFor rendering to other types of targets (like Cube Maps and Texture Arrays) you can pass an object with a custom attach method (See the *ArrayLayerTarget* class for an example).  \n\n* [OpenGL Wiki - Framebuffer Objects](https://www.khronos.org/opengl/wiki/Framebuffer_Object)\n* [Learn OpenGL - Framebuffers](https://learnopengl.com/Advanced-OpenGL/Framebuffers)\n\n## Basic Example (draw a full screen quad)\n\n```python\npositions=[\n     1.0,  1.0, 0.0,\n     1.0, -1.0, 0.0,\n    -1.0, -1.0, 0.0,\n    -1.0,  1.0, 0.0,\n]\nindices=[\n    0, 1, 3,\n    1, 2, 3,\n]\n\nmesh = Mesh(positions, indices)\n\nvertex_source='''\nlayout (location = 0) in vec3 POSITION;\nvoid main()\n{\n    gl_Position = vec4(POSITION, 1);\n}\n'''\n\npixel_source='''\nlayout (location = 0) out vec4 RESULT;\nuniform vec4 color = vec4(1,0,0,1);\nvoid main()\n{\n    RESULT = color;\n}\n'''\n\nshader = Shader(vertex_source, pixel_source)\n\nshader.uniforms['color'].set_value((0,1,0,1))\n\nresult_texture = Texture((1024, 1024), GL_RGBA32F)\n\nresult_target = RenderTarget([result_texture])\n\nresult_target.bind()\nshader.bind()\nmesh.draw()\n```\n"
  },
  {
    "path": "Malt/Nodes/LineRender.py",
    "content": "from Malt.GL.GL import *\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\n\n_SHADER = None\n\nclass LineRender(PipelineNode):\n    \"\"\"\n    Expands the line up to the width especified in the *Line Width* texture\n    and composites it on top of the *Color* texture.\n    \"\"\"\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n        self.resolution = None\n    \n    @classmethod\n    def reflect_inputs(cls):\n        inputs = {}\n        inputs['Color'] = Parameter('', Type.TEXTURE)\n        inputs['Line Color'] = Parameter('', Type.TEXTURE)\n        inputs['Line Width'] = Parameter('', Type.TEXTURE)\n        inputs['Max Width'] = Parameter(10, Type.INT, doc=\"\"\"\n            The maximum width the shader can render.\n            Increasing the value lowers the render performance.\"\"\")\n        inputs['Line Scale'] = Parameter(1.0, Type.FLOAT, doc=\"\"\"\n            Scale all Line Width values with this one.  \n            *(Useful for rendering at different resolutions)*\"\"\")\n        inputs['Normal Depth'] = Parameter('', Type.TEXTURE)\n        inputs['ID'] = Parameter('', Type.TEXTURE)\n        return inputs\n    \n    @classmethod\n    def reflect_outputs(cls):\n        outputs = {}\n        outputs['Color'] = Parameter('', Type.TEXTURE)\n        return outputs\n    \n    def setup_render_targets(self, resolution):\n        self.t_color = Texture(resolution, GL_RGBA16F)\n        self.fbo_color = RenderTarget([self.t_color])\n\n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n\n        if self.pipeline.resolution != self.resolution:\n            self.setup_render_targets(self.pipeline.resolution)\n            self.resolution = self.pipeline.resolution\n        \n        global _SHADER\n        if _SHADER is None:\n            _SHADER = self.pipeline.compile_shader_from_source('#include \"Passes/LineComposite.glsl\"')\n        \n        _SHADER.textures['color_texture'] = inputs['Color']\n        _SHADER.textures['depth_texture'] = inputs['Normal Depth']\n        _SHADER.uniforms['depth_channel'].set_value(3)\n        _SHADER.textures['id_texture'] = inputs['ID']\n        _SHADER.textures['line_color_texture'] = inputs['Line Color']\n        _SHADER.textures['line_width_texture'] = inputs['Line Width']\n        _SHADER.uniforms['line_width_scale'].set_value(inputs['Line Scale'])\n        _SHADER.uniforms['brute_force_range'].set_value(inputs['Max Width'])\n        \n        self.pipeline.common_buffer.shader_callback(_SHADER)\n        self.pipeline.draw_screen_pass(_SHADER, self.fbo_color)\n\n        outputs['Color'] = self.t_color\n\nNODE = LineRender    \n"
  },
  {
    "path": "Malt/Nodes/SceneFilter.py",
    "content": "from Malt.GL.GL import *\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\nfrom copy import copy, deepcopy\n\nclass SceneFilter(PipelineNode):\n    \"\"\"\n    Filters a Scene based on object tags.\n    \"\"\"\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n        self.last_scene = None\n        self.matches = None\n        self.non_matches = None\n    \n    @classmethod\n    def reflect_inputs(cls):\n        return {\n            'Scene' : Parameter('Scene', Type.OTHER),\n            'Filter' : Parameter('', Type.STRING)\n        }\n    \n    @classmethod\n    def reflect_outputs(cls):\n        return {\n            'Matches' : Parameter('Scene', Type.OTHER),\n            'Non Matches' : Parameter('Scene', Type.OTHER)\n        }\n    \n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n        \n        scene = inputs['Scene']\n        tag = inputs['Filter']\n\n        if scene != self.last_scene:\n            self.last_scene = scene\n            self.matches = copy(scene)\n            self.matches.objects = []\n            self.matches.shader_resources = copy(self.matches.shader_resources)\n            self.non_matches = copy(scene)\n            self.non_matches.objects = []\n            self.non_matches.shader_resources = copy(self.non_matches.shader_resources)\n            for obj in scene.objects:\n                if tag in obj.tags:\n                    self.matches.objects.append(obj)\n                else:\n                    self.non_matches.objects.append(obj)\n            self.matches.batches = self.pipeline.build_scene_batches(self.matches.objects)\n            self.non_matches.batches = self.pipeline.build_scene_batches(self.non_matches.objects)\n            \n        outputs['Matches'] = self.matches\n        outputs['Non Matches'] = self.non_matches\n\n\nNODE = SceneFilter\n"
  },
  {
    "path": "Malt/Nodes/SuperSamplingAA.py",
    "content": "from Malt.GL.GL import *\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\n\nclass SuperSamplingAA(PipelineNode):\n\n    \"\"\"\n    Performs anti-aliasing by accumulating multiple render samples into a single texture.\n    \"\"\"\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n        self.resolution = None\n    \n    @classmethod\n    def reflect_inputs(cls):\n        inputs = {}\n        inputs['Color'] = Parameter('', Type.TEXTURE)\n        return inputs\n    \n    @classmethod\n    def reflect_outputs(cls):\n        outputs = {}\n        outputs['Color'] = Parameter('', Type.TEXTURE)\n        return outputs\n    \n    def setup_render_targets(self, resolution):\n        self.t_color = Texture(resolution, GL_RGBA16F)\n        self.fbo = RenderTarget([self.t_color])\n\n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n\n        if self.pipeline.resolution != self.resolution:\n            self.setup_render_targets(self.pipeline.resolution)\n            self.resolution = self.pipeline.resolution\n        \n        if self.pipeline.is_new_frame:\n            self.fbo.clear([(0,0,0,0)])\n        if inputs['Color']:\n            self.pipeline.blend_texture(inputs['Color'], self.fbo, 1.0 / (self.pipeline.sample_count + 1))\n            outputs['Color'] = self.t_color\n\nNODE = SuperSamplingAA\n"
  },
  {
    "path": "Malt/Pipeline.py",
    "content": "import math, os, ctypes\nfrom os import path\n\nfrom Malt.Utils import LOG\n\nfrom Malt.GL.GL import *\nfrom Malt.GL.Mesh import Mesh, MeshCustomLoad\nfrom Malt.GL.Shader import Shader, UBO, shader_preprocessor\n\nfrom Malt.Render import Common\nfrom Malt.PipelineParameters import *\n\nSHADER_DIR = path.join(path.dirname(__file__), 'Shaders')\n\nclass Pipeline():\n\n    SHADER_INCLUDE_PATHS = []\n\n    BLEND_SHADER = None\n    COPY_SHADER = None\n\n    def __init__(self, plugins=[]):\n        from multiprocessing.dummy import Pool\n        self.pool = Pool(16)\n\n        if SHADER_DIR not in Pipeline.SHADER_INCLUDE_PATHS:\n            Pipeline.SHADER_INCLUDE_PATHS.append(SHADER_DIR)\n\n        self.resolution = None\n        self.sample_count = 0\n        self.result = None\n        self.is_final_render = None\n        \n        plugins = [plugin for plugin in plugins if plugin.poll_pipeline(self)]\n        self.setup_parameters()\n        for plugin in plugins:\n            plugin.register_pipeline_parameters(self.parameters)\n        self.setup_graphs()\n        for plugin in plugins:\n            for graph in plugin.register_pipeline_graphs():\n                self.add_graph(graph)\n        for plugin in plugins:\n            plugin.register_graph_libraries(self.graphs)\n        for graph in self.graphs.values():\n            graph.setup_reflection()\n        self.setup_resources()\n    \n    def setup_parameters(self):\n        self.parameters = PipelineParameters()\n        \n        self.parameters.mesh['double_sided'] = Parameter(False, Type.BOOL, doc=\n            \"Disables backface culling, so geometry is rendered from both sides.\")\n        \n        self.parameters.mesh['precomputed_tangents'] = Parameter(False, Type.BOOL, doc=\"\"\"\n            Load precomputed mesh tangents *(needed for improving normal mapping quality on low poly meshes)*. \n            It's disabled by default since it slows down mesh loading in Blender.  \n            When disabled, the *tangents* are calculated on the fly from the *pixel shader*.\"\"\")\n        \n        self.parameters.world['Material.Default'] = MaterialParameter('', '.mesh.glsl', 'Mesh', doc=\n            \"The default material, used for objects with no material assigned.\")\n        \n        self.parameters.world['Material.Override'] = MaterialParameter('', '.mesh.glsl', 'Mesh', doc=\n            \"When set, overrides all scene materials with this one.\")\n        \n        self.parameters.world['Viewport.Resolution Scale'] = Parameter(1.0 , Type.FLOAT, doc=\"\"\"\n            A multiplier for the viewport resolution.\n            It can be lowered to improve viewport performance or for specific styles, like *pixel art*.\"\"\"\n        )\n        self.parameters.world['Viewport.Smooth Interpolation'] = Parameter(True , Type.BOOL, doc=\"\"\"\n            The interpolation mode used when *Resolution Scale* is not 1.\n            Toggles between *Nearest/Bilinear* interpolation.\"\"\")\n    \n    def get_parameters(self):\n        return self.parameters\n\n    def setup_graphs(self):\n        self.graphs = {}\n    \n    def add_graph(self, graph):\n        if graph.file_extension.endswith('glsl'):\n            graph.include_paths += self.SHADER_INCLUDE_PATHS\n        self.graphs[graph.name] = graph\n    \n    def get_graphs(self):\n        result = {}\n        for name, graph in self.graphs.items():\n            result[name] = graph.get_serializable_copy()\n        return result\n\n    def setup_resources(self):\n        self.common_buffer = Common.CommonBuffer()\n        positions=[\n             1.0,  1.0, 0.0,\n             1.0, -1.0, 0.0,\n            -1.0, -1.0, 0.0,\n            -1.0,  1.0, 0.0,\n        ]\n        indices=[\n            0, 1, 3,\n            1, 2, 3,\n        ]\n        self.quad = Mesh(positions, indices)\n        \n        if Pipeline.BLEND_SHADER is None:\n            source='''#include \"Passes/BlendTexture.glsl\"'''\n            Pipeline.BLEND_SHADER = self.compile_shader_from_source(source)\n        self.blend_shader = Pipeline.BLEND_SHADER\n\n        if Pipeline.COPY_SHADER is None:\n            source = '''#include \"Passes/CopyTextures.glsl\"'''\n            Pipeline.COPY_SHADER = self.compile_shader_from_source(source)\n        self.copy_shader = Pipeline.COPY_SHADER\n    \n    def get_render_outputs(self):\n        return {\n            'COLOR' : GL_RGBA32F,\n            'DEPTH' : GL_R32F,\n        }\n    \n    def get_samples(self):\n        return [(0,0)]\n    \n    def needs_more_samples(self):\n        return self.sample_count < len(self.get_samples())\n    \n    def setup_render_targets(self, resolution):\n        pass\n    \n    def find_shader_path(self, path, search_paths=[]):\n        if os.path.exists(path):\n            return path\n        else:\n            for shader_path in self.SHADER_INCLUDE_PATHS + search_paths:\n                full_path = os.path.join(shader_path, path)\n                if os.path.exists(full_path):\n                    return full_path\n        return None\n    \n    def compile_shader_from_source(self, source, include_paths=[], defines=[]):\n        vertex_src = shader_preprocessor(source, include_paths + self.SHADER_INCLUDE_PATHS, defines + ['VERTEX_SHADER'])\n        pixel_src = shader_preprocessor(source, include_paths + self.SHADER_INCLUDE_PATHS, defines + ['PIXEL_SHADER'])\n        return Shader(vertex_src, pixel_src)\n            \n    def compile_material_from_source(self, material_type, source, include_paths=[]):\n        return self.graphs[material_type].compile_material(source, include_paths)\n    \n    def compile_material(self, shader_path, search_paths=[]):\n        try:\n            file_dir = path.dirname(shader_path)\n            source = '#include \"{}\"'.format(path.basename(shader_path))\n            material_type = shader_path.split('.')[-2]\n            for graph in self.graphs.values():\n                if shader_path.endswith(graph.file_extension):\n                    material_type = graph.name\n            return self.compile_material_from_source(material_type, source, [file_dir] + search_paths)\n        except Exception as e:\n            import traceback\n            traceback.print_exc()\n            return str(e)\n    \n    def load_mesh(self, position, indices, normal, tangent=None, uvs=[], colors=[]):  \n        # Each parameter implements the Malt.Utils.IBuffer interface\n        # Indices is an array of index buffers corresponding to each of the materials a mesh has\n        # VBOs are shared for all the materials\n          \n        def load_VBO(data):\n            VBO = gl_buffer(GL_INT, 1)\n            glGenBuffers(1, VBO)\n            glBindBuffer(GL_ARRAY_BUFFER, VBO[0])\n            glBufferData(GL_ARRAY_BUFFER, data.size_in_bytes(), data.buffer(), GL_STATIC_DRAW)\n            glBindBuffer(GL_ARRAY_BUFFER, 0)\n            return VBO\n\n        position_vbo = load_VBO(position)\n        normal_vbo = load_VBO(normal)\n        tangent_vbo = load_VBO(tangent) if tangent else None\n        uv_vbos = [load_VBO(e) for e in uvs]\n        color_vbos = [load_VBO(e) if e else None for e in colors]\n\n        results = []\n\n        for i, index in enumerate(indices):\n            result = MeshCustomLoad()\n            \n            result.VAO = gl_buffer(GL_INT, 1)\n            glGenVertexArrays(1, result.VAO)\n            glBindVertexArray(result.VAO[0])\n            \n            result.EBO = gl_buffer(GL_INT, 1)\n            glGenBuffers(1, result.EBO)\n            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, result.EBO[0])\n            glBufferData(GL_ELEMENT_ARRAY_BUFFER, index.size_in_bytes(), index.buffer(), GL_STATIC_DRAW)\n            \n            result.index_count = len(index)\n\n            result.position = position_vbo\n            result.normal = normal_vbo\n            result.tangent = tangent_vbo\n            result.uvs = uv_vbos\n            result.colors = color_vbos\n\n            def bind_VBO(VBO, index, element_size, gl_type=GL_FLOAT, gl_normalize=GL_FALSE):\n                glBindBuffer(GL_ARRAY_BUFFER, VBO[0])\n                glEnableVertexAttribArray(index)\n                glVertexAttribPointer(index, element_size, gl_type, gl_normalize, 0, None)\n            \n            bind_VBO(result.position, 0, 3)\n            if position.size_in_bytes() == normal.size_in_bytes():\n                bind_VBO(result.normal, 1, 3)\n            else:\n                bind_VBO(result.normal, 1, 3, GL_SHORT, GL_TRUE)\n            \n            if tangent:\n                bind_VBO(result.tangent, 2, 4)\n            \n            max_uv = 4\n            max_vertex_colors = 4\n            uv0_index = 3\n            color0_index = uv0_index + max_uv\n            for i, uv in enumerate(result.uvs):\n                if i >= max_uv:\n                    LOG.warning('{} : UV count exceeds max supported UVs ({})'.format(name, max_uv))\n                    break\n                bind_VBO(uv, uv0_index + i, 2)\n            for i, color in enumerate(result.colors):\n                if i >= max_vertex_colors:\n                    LOG.warning('{} : Vertex Color Layer count exceeds max supported layers ({})'.format(name, max_uv))\n                    break\n                if color:\n                    if colors[i]._ctype == ctypes.c_uint8:\n                        bind_VBO(color, color0_index + i, 4, GL_UNSIGNED_BYTE, GL_TRUE)\n                        result.color_is_srgb[i] = True\n                    if colors[i]._ctype == ctypes.c_float:\n                        bind_VBO(color, color0_index + i, 4, GL_FLOAT)\n\n            glBindVertexArray(0)\n            results.append(result)\n\n        return results\n    \n    def draw_screen_pass(self, shader, target, blend = False):\n        #Allow screen passes draw to gl_FragDepth\n        glEnable(GL_DEPTH_TEST)\n        glDepthFunc(GL_ALWAYS)\n        glDisable(GL_CULL_FACE)\n        if blend:\n            glEnable(GL_BLEND)\n        else:\n            glDisable(GL_BLEND)\n        target.bind()\n        shader.bind()\n        self.quad.draw()\n    \n    def blend_texture(self, blend_texture, target, opacity):\n        self.blend_shader.textures['blend_texture'] = blend_texture\n        glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA)\n        glBlendEquation(GL_FUNC_ADD)\n        glBlendColor(0, 0, 0, opacity)\n        self.draw_screen_pass(self.blend_shader, target, True)\n    \n    def copy_textures(self, target, color_sources=[], depth_source=None):\n        for i, texture in enumerate(color_sources):\n            self.copy_shader.textures[f'IN[{str(i)}]'] = texture\n        self.copy_shader.textures['IN_DEPTH'] = depth_source\n        self.draw_screen_pass(self.copy_shader, target)\n    \n    def build_scene_batches(self, objects):\n        result = {}\n        for obj in objects:\n            if obj.material not in result:\n                result[obj.material] = {}\n            if obj.mesh not in result[obj.material]:\n                result[obj.material][obj.mesh] = {}\n            mesh_dict = result[obj.material][obj.mesh]\n            if obj.mirror_scale:\n                if 'mirror_scale' not in mesh_dict:\n                    mesh_dict['mirror_scale'] = []\n                mesh_dict['mirror_scale'].append(obj)\n            else:\n                if 'normal_scale' not in mesh_dict:\n                    mesh_dict['normal_scale'] = []\n                mesh_dict['normal_scale'].append(obj)\n        \n        # Assume at least 64kb of UBO storage (d3d11 requirement) and max element size of mat4\n        max_instances = 1000\n        models = (max_instances * (ctypes.c_float * 16))()\n        ids = (max_instances * ctypes.c_uint)()\n\n        for material, meshes in result.items():\n            for mesh, scale_groups in meshes.items():\n                for scale_group, objs in scale_groups.items():\n                    batches = []\n                    scale_groups[scale_group] = batches\n                    \n                    i = 0\n                    batch_length = len(objs)\n                    \n                    while i < batch_length:\n                        instance_i = i % max_instances\n                        models[instance_i] = objs[i].matrix\n                        ids[instance_i] = objs[i].parameters['ID']\n\n                        i+=1\n                        instances_count = instance_i + 1\n\n                        if i == batch_length or instances_count == max_instances:\n                            local_models = ((ctypes.c_float * 16) * instances_count).from_address(ctypes.addressof(models))\n                            # IDs are stored as uvec4, so we make sure the buffer count is a multiple of 4,\n                            # since some drivers will only bind a full uvec4 (see issue #319)\n                            id_buffer_count = math.ceil(instances_count/4)*4\n                            local_ids = (ctypes.c_uint * id_buffer_count).from_address(ctypes.addressof(ids))\n\n                            models_UBO = UBO()\n                            ids_UBO = UBO()\n\n                            models_UBO.load_data(local_models)\n                            ids_UBO.load_data(local_ids)\n\n                            batches.append({\n                                'instances_count': instances_count,\n                                'BATCH_MODELS':models_UBO,\n                                'BATCH_IDS':ids_UBO,\n                            })\n            \n        return result\n    \n    def draw_scene_pass(self, render_target, scene_batches, pass_name=None, default_shader=None, shader_resources={}, depth_test_function=GL_LEQUAL):\n        glDisable(GL_BLEND)\n        glEnable(GL_DEPTH_TEST)\n        glDepthFunc(depth_test_function)\n        glDepthMask(GL_TRUE)\n        glDepthRange(0,1)\n\n        render_target.bind()\n\n        _double_sided = None\n\n        for material in scene_batches.keys():\n            shader = default_shader\n            if material and pass_name in material.shader and material.shader[pass_name]:\n                shader = material.shader[pass_name]\n            \n            for resource in shader_resources.values():\n                resource.shader_callback(shader)\n            \n            shader.bind()\n            \n            precomputed_tangents_uniform = shader.uniforms.get('PRECOMPUTED_TANGENTS')\n            _precomputed_tangents = None\n            _scale_group = None\n            _color_is_srgb = None\n            \n            meshes = scene_batches[material]\n            for mesh in meshes.keys():\n                mesh.mesh.bind()\n                \n                double_sided = mesh.parameters['double_sided']\n                if double_sided != _double_sided:\n                    _double_sided = double_sided\n                    if _double_sided:\n                        glDisable(GL_CULL_FACE)\n                    else:\n                        glEnable(GL_CULL_FACE)\n                        glCullFace(GL_BACK)\n                \n                color_is_srgb = tuple(mesh.mesh.color_is_srgb)\n                if color_is_srgb != _color_is_srgb :\n                    if 'COLOR_IS_SRGB' in shader.uniforms:\n                        shader.uniforms['COLOR_IS_SRGB'].bind(color_is_srgb)\n                        _color_is_srgb = color_is_srgb\n\n                if precomputed_tangents_uniform:\n                    precomputed_tangents = mesh.parameters['precomputed_tangents']\n                    if _precomputed_tangents != precomputed_tangents:\n                        _precomputed_tangents = precomputed_tangents\n                        precomputed_tangents_uniform.bind(precomputed_tangents)\n\n                for scale_group, batches in meshes[mesh].items():\n                    if scale_group != _scale_group:\n                        _scale_group = scale_group\n                        if scale_group == 'normal_scale':\n                            glFrontFace(GL_CCW)\n                            if 'MIRROR_SCALE' in shader.uniforms:\n                                shader.uniforms['MIRROR_SCALE'].bind(False)\n                        else:\n                            glFrontFace(GL_CW)\n                            if 'MIRROR_SCALE' in shader.uniforms:\n                                shader.uniforms['MIRROR_SCALE'].bind(True)\n                \n                    for batch in batches:\n                        batch['BATCH_MODELS'].bind(shader.uniform_blocks['BATCH_MODELS'])\n                        batch['BATCH_IDS'].bind(shader.uniform_blocks['BATCH_IDS'])\n                        glDrawElementsInstanced(GL_TRIANGLES, mesh.mesh.index_count, GL_UNSIGNED_INT, NULL, batch['instances_count'])\n\n\n    def render(self, resolution, scene, is_final_render, is_new_frame):\n        self.is_final_render = is_final_render\n        if self.resolution != resolution:\n            self.resolution = resolution\n            self.setup_render_targets(resolution)\n            self.sample_count = 0\n        \n        if is_new_frame:\n            self.sample_count = 0\n        \n        if self.needs_more_samples() == False:\n            return self.result\n        \n        self.common_buffer.load(scene, resolution)\n        self.result = self.do_render(resolution, scene, is_final_render, is_new_frame)\n        \n        self.sample_count += 1\n\n        return self.result\n\n    def do_render(self, resolution, scene, is_final_render, is_new_frame):\n        return {}\n"
  },
  {
    "path": "Malt/PipelineGraph.py",
    "content": "from Malt.Utils import scan_dirs, LOG\n\n\nclass PipelineGraphIO():\n\n    def __init__(self, name, dynamic_input_types = [], dynamic_output_types = [], \n    default_dynamic_inputs = {}, default_dynamic_outputs = {}, function=None):\n        self.name = name\n        self.dynamic_input_types = dynamic_input_types\n        self.dynamic_output_types = dynamic_output_types\n        self.default_dynamic_inputs = default_dynamic_inputs\n        self.default_dynamic_outputs = default_dynamic_outputs\n        self.function = function\n        self.custom_output_start_index = 0\n\nclass PipelineGraph():\n\n    INTERNAL_GRAPH = 0\n    GLOBAL_GRAPH = 1\n    SCENE_GRAPH = 2\n    \n    def __init__(self, name, language, file_extension, graph_type, graph_io, default_graph_path):\n        self.name = name\n        self.language = language\n        self.file_extension = file_extension\n        self.graph_type = graph_type\n        self.libs = []\n        self.lib_files = []\n        self.include_paths = []\n        self.functions = {}\n        self.structs = {}\n        self.subcategories = {}\n        self.graph_io = { io.name : io for io in graph_io }\n        self.default_graph_path = default_graph_path\n        self.timestamp = 0\n    \n    def add_library(self, path):\n        import os\n        from Malt.Utils import scan_dirs\n        extension = self.file_extension.split('.')[-1]\n        if os.path.isdir(path):\n            self.include_paths.append(path)\n            def file_callback(file):\n                if file.path.endswith(extension):\n                    self.lib_files.append(file.path)\n            scan_dirs(path, file_callback)\n        else:\n            self.include_paths.append(os.path.dirname(path))\n            self.lib_files.append(path)\n        self.libs.append(path)\n    \n    def needs_reload(self):\n        extension = self.file_extension.split('.')[-1]\n        needs_reload = False\n        def file_callback(file):\n            if file.path.endswith(extension) and file.stat().st_mtime > self.timestamp:\n                nonlocal needs_reload\n                needs_reload = True\n        for path in self.include_paths:\n            scan_dirs(path, file_callback)\n        return needs_reload\n    \n    def setup_reflection(self):\n        import time\n        self.timestamp = time.time()\n    \n    def generate_source(self, parameters):\n        return ''\n    \n    def get_serializable_copy(self):\n        from copy import copy\n        return copy(self)\n\n\nclass GLSLGraphIO(PipelineGraphIO): \n\n    COMMON_INPUT_TYPES = ['sampler2D']\n    COMMON_OUTPUT_TYPES = ['float','vec2','vec3','vec4']\n    \n    def __init__(self, name, define = None, io_wrap=None, dynamic_input_types = [], dynamic_output_types = [], \n    default_dynamic_inputs = {}, default_dynamic_outputs = {}, shader_type=None, custom_output_start_index=0):\n        super().__init__(name, dynamic_input_types, dynamic_output_types, default_dynamic_inputs, default_dynamic_outputs)\n        self.define = define\n        self.io_wrap = io_wrap\n        self.shader_type = shader_type\n        self.signature = None\n        self.custom_output_start_index = custom_output_start_index\n\n\nclass GLSLPipelineGraph(PipelineGraph):\n\n    def __init__(self, name, graph_type, default_global_scope, default_shader_src, shaders=['SHADER'], graph_io=[],\n        default_graph_path=None):\n        file_extension = f'.{name.lower()}.glsl'\n        super().__init__(name, 'GLSL', file_extension, graph_type, graph_io, default_graph_path)\n        self.default_global_scope = default_global_scope\n        self.default_shader_src = default_shader_src\n        self.shaders = shaders\n        from multiprocessing.dummy import Pool\n        self.pool = Pool(16)\n    \n    def get_serializable_copy(self):\n        result = super().get_serializable_copy()\n        result.pool = None\n        return result\n    \n    def name_as_macro(self, name):\n        return ''.join(c for c in name.replace(' ','_').upper() if c.isalnum() or c == '_')\n    \n    def get_material_define(self):\n        return f'IS_{self.name_as_macro(self.name)}_SHADER'\n    \n    def preprocess_shader_from_source(self, source, include_paths=[], defines=[]):\n        from Malt.GL.Shader import shader_preprocessor\n        return shader_preprocessor(source, self.include_paths + include_paths, [self.get_material_define()] + defines)\n\n    def setup_reflection(self):\n        super().setup_reflection()\n        src = self.default_global_scope + self.default_shader_src\n        for file in self.lib_files:\n            src += f'\\n#include \"{file}\"\\n'\n        src = self.preprocess_shader_from_source(src, [], ['VERTEX_SHADER','PIXEL_SHADER','REFLECTION'])\n        from Malt.GL.Shader import glsl_reflection\n        reflection = glsl_reflection(src, self.include_paths)\n        functions = reflection[\"functions\"]\n        structs = reflection[\"structs\"]\n        subcategories = reflection[\"subcategories\"]\n        for io in self.graph_io.values():\n            io.function = functions[io.name]\n            io.signature = io.function['signature']\n        for key in [*functions.keys()]:\n            name = functions[key]['name']\n            if name.startswith('_') or name.isupper() or name == 'main':\n                functions.pop(key)\n        for name in [*structs.keys()]:\n            if name.startswith('_'): #TODO: Upper???\n                structs.pop(name)\n        for key, subcategory in subcategories.items():\n            subcategories[key] = [k for k in subcategory if k in functions.keys()]\n        self.functions = functions\n        self.structs = structs\n        self.subcategories = subcategories\n    \n    def generate_source(self, parameters):\n        import textwrap\n        from Malt.SourceTranspiler import GLSLTranspiler\n        code = ''\n        for graph_io in self.graph_io.values():\n            if graph_io.name in parameters.keys() and graph_io.define:\n                code += '#define {}\\n'.format(graph_io.define)\n        code += '\\n\\n' + self.default_global_scope + '\\n\\n'\n        for file in self.lib_files:\n            code += f'#include \"{file}\"\\n'\n        code += '\\n\\n' + parameters['GLOBAL'] + '\\n\\n'\n        for graph_io in self.graph_io.values():\n            if graph_io.name in parameters.keys():\n                code += GLSLTranspiler.preprocessor_wrap(graph_io.shader_type,\n                '{}\\n{{\\n{}\\n}}'.format(graph_io.signature, textwrap.indent(parameters[graph_io.name],'\\t')))\n        code += '\\n\\n'\n        return code\n    \n    def compile_material(self, source, include_paths=[]):\n        def preprocess(params):\n            return self.preprocess_shader_from_source(*params)\n        \n        params = []\n        for shader in self.shaders:\n            params.append((source, include_paths, [shader, 'VERTEX_SHADER']))\n            params.append((source, include_paths, [shader, 'PIXEL_SHADER']))\n        preprocessed = self.pool.map(preprocess, params)\n\n        from Malt.GL.Shader import Shader\n        shaders = {}\n        for shader in self.shaders:\n            shaders[shader] = Shader(preprocessed.pop(0), preprocessed.pop(0))\n        return shaders\n\nclass PythonGraphIO(PipelineGraphIO):\n\n    COMMON_IO_TYPES = ['Texture']\n\n    def __init__(self, name, dynamic_input_types = [], dynamic_output_types = [],\n    default_dynamic_inputs = {}, default_dynamic_outputs = {}, function=None):\n        super().__init__(name, dynamic_input_types, dynamic_output_types, \n            default_dynamic_inputs, default_dynamic_outputs, function)\n\nclass PythonPipelineGraph(PipelineGraph):\n    \n    def __init__(self, name, graph_io, default_graph_path=None):\n        extension = f'-{name}.py'\n        super().__init__(name, 'Python', extension, self.GLOBAL_GRAPH, graph_io, default_graph_path)\n        self.node_instances = {}\n        self.nodes = {}\n\n    def get_serializable_copy(self):\n        result = super().get_serializable_copy()\n        result.nodes = None\n        result.node_instances = None\n        return result\n    \n    def setup_reflection(self):\n        super().setup_reflection()\n        import importlib.util\n        nodes = []\n        self.node_instances = {}\n        for file in self.lib_files:\n            try:\n                spec = importlib.util.spec_from_file_location(\"_dynamic_node_module_\", file)\n                module = importlib.util.module_from_spec(spec)\n                spec.loader.exec_module(module)\n                nodes.append(module.NODE)\n            except:\n                import traceback\n                LOG.error('FILEPATH : ', file, '\\n', traceback.format_exc())\n        self.functions = {}\n        self.structs = {}\n        for node_class in nodes:\n            reflection = node_class.reflect()\n            self.functions[reflection['name']] = reflection\n            self.nodes[reflection['name']] = node_class\n    \n    def generate_source(self, parameters):\n        src = ''\n        for io in self.graph_io.keys():\n            if io in parameters.keys():\n                src += parameters[io]\n        return src\n    \n    def run_source(self, pipeline, source, PARAMETERS, IN, OUT):\n        try:\n            def run_node(node_name, node_type, parameters):\n                if node_name not in self.node_instances.keys():\n                    node_class = self.nodes[node_type]\n                    self.node_instances[node_name] = node_class(pipeline)\n                parameters['__GLOBALS__'] = PARAMETERS\n                self.node_instances[node_name].execute(parameters)\n            exec(source)\n        except:\n            raise MaltGraphExecutionException(source, PARAMETERS, IN, OUT)\n\nclass MaltGraphExecutionException(Exception):\n    def __init__(self, source, parameters, inputs, outputs):\n        import pprint\n\n        self.message = \"\\n\".join((\"\",\n        \"IN:\", pprint.pformat(inputs),\n        \"OUT:\", pprint.pformat(outputs),\n        \"SOURCE:\", source,\n        \"PARAMETERS:\", pprint.pformat(parameters)))\n\n        super().__init__(self.message)\n"
  },
  {
    "path": "Malt/PipelineNode.py",
    "content": "class PipelineNode():\n\n    def __init__(self, pipeline):\n        self.pipeline = pipeline\n    \n    @staticmethod\n    def get_pass_type():\n        return None\n    \n    @classmethod\n    def static_reflect(cls, name, inputs, outputs):\n        meta = {}\n        if cls.__doc__:\n            meta['doc'] = cls.__doc__\n        dictionary = {\n            'name' : name,\n            'type' : 'void',\n            'file' : 'Render',\n            'parameters' : [],\n            'pass_type' : cls.get_pass_type(),\n            'meta' : meta,\n        }\n        for name, input in inputs.items():\n            meta = {}\n            if input.doc:\n                meta['doc'] = input.doc\n            if input.subtype:\n                meta['subtype'] = input.subtype\n            if input.default_value is not None and input.default_value != input.type_string():\n                meta['default'] = input.default_value\n            dictionary['parameters'].append({\n                'name' : name,\n                'type' : input,\n                'size' : input.size,\n                'io' : 'in',\n                'meta' : meta,\n            })\n        for name, output in outputs.items():\n            meta = {}\n            if output.doc:\n                meta['doc'] = output.doc\n            if output.subtype:\n                meta['subtype'] = output.subtype\n            if output.default_value is not None and output.default_value != output.type_string():\n                meta['default'] = output.default_value\n            dictionary['parameters'].append({\n                'name' : name,\n                'type' : output,\n                'size' : output.size,\n                'io' : 'out',\n                'meta' : {},\n            })\n        return dictionary\n    \n    @classmethod\n    def reflect(cls):\n        return cls.static_reflect(cls.__name__, cls.reflect_inputs(), cls.reflect_outputs())\n\n    @classmethod\n    def reflect_inputs(cls):\n        return {}\n\n    @classmethod\n    def reflect_outputs(cls):\n        return {}\n    \n    def execute(self, parameters):\n        pass\n"
  },
  {
    "path": "Malt/PipelineParameters.py",
    "content": "class PipelineParameters():\n\n    def __init__(self, scene={}, world={}, camera={}, object={}, material={}, mesh={}, light={}):\n        self.scene = scene\n        self.world = world\n        self.camera = camera\n        self.object = object\n        self.material = material\n        self.mesh = mesh\n        self.light = light\n\nclass Type():\n    BOOL=0\n    INT=1\n    FLOAT=2\n    STRING=3\n    ENUM=4\n    OTHER=5\n    TEXTURE=6\n    GRADIENT=7\n    MATERIAL=8\n    #RENDER_TARGET=9 #TODO\n    GRAPH=10\n\n    @classmethod\n    def string_list(cls):\n        return ['Bool', 'Int', 'Float', 'String', 'Other', 'Enum', 'Texture', 'Gradient',\n            'Material', 'RenderTarget', 'Graph']\n    @classmethod\n    def to_string(cls, type):\n        return cls.string_list()[type]\n    @classmethod\n    def from_string(cls, type):\n        return cls.string_list().index(type)\n\nclass Parameter():\n    def __init__(self, default_value, type, size=1, filter=None, subtype=None, doc=None):\n        self.default_value = default_value\n        self.type = type\n        self.subtype = subtype\n        self.size = size\n        self.filter = filter\n        self.doc = doc\n    \n    def type_string(self):\n        if self.type == Type.OTHER:\n            return self.default_value\n        else:\n            return Type.to_string(self.type)\n\n    @classmethod\n    def from_uniform(cls, uniform):\n        type, size = gl_type_to_malt_type(uniform.type)\n        value = uniform.value\n        if size > 1:\n            value = tuple(value)\n        else:\n            value = value[0]\n        #TODO: uniform length ??? (Arrays)\n        return Parameter(value, type, size)\n    \n    @classmethod\n    def from_glsl_type(cls, glsl_type, subtype=None, default_value=None):\n        type, size = glsl_type_to_malt_type(glsl_type)\n        if subtype and subtype.startswith('ENUM'):\n            try:\n                enum_options = subtype.split('ENUM(')[1][:-1]\n                enum_options = enum_options.split(',')\n                if isinstance(default_value, int):\n                    default_value = enum_options[default_value]\n                if default_value is None:\n                    default_value = enum_options[0]\n                return EnumParameter(enum_options, default_value)\n            except:\n                pass\n        if default_value is None:\n            if type is Type.INT:\n                default_value = tuple([0] * size)\n            if type is Type.FLOAT:\n                default_value = tuple([0.0] * size)\n            if type is Type.BOOL:\n                default_value = tuple([False] * size)\n            if default_value and len(default_value) == 1:\n                default_value = default_value[0]\n            overrides = {\n                ('vec3',None):(0.5,0.5,0.5),\n                ('vec4',None):(0.5,0.5,0.5,1.0),\n                ('vec3','Color'):(0.5,0.5,0.5),\n                ('vec4','Color'):(0.5,0.5,0.5,1.0),\n                ('vec3','Normal'):(1.0,0.0,0.0),\n                ('vec4','Quaternion'):(0.0,0.0,0.0,1.0),\n                ('mat3',None):(\n                    1.0,0.0,0.0,\n                    0.0,1.0,0.0,\n                    0.0,0.0,1.0,\n                ),\n                ('mat4',None):(\n                    1.0,0.0,0.0,0.0,\n                    0.0,1.0,0.0,0.0,\n                    0.0,0.0,1.0,0.0,\n                    0.0,0.0,0.0,1.0,\n                ),\n            }\n            override = overrides.get((glsl_type, subtype))\n            if override is not None:\n                default_value = override\n        return Parameter(default_value, type, size, subtype=subtype)\n\nclass MaterialParameter(Parameter):\n    def __init__(self, default_path, extension, graph_type=None, filter=None, doc=None):\n        super().__init__(default_path, Type.MATERIAL, 1, filter, extension, doc)\n        self.extension = extension\n        self.graph_type = graph_type\n\nclass GraphParameter(Parameter):\n    def __init__(self, default_path, graph_type, filter=None, doc=None):\n        super().__init__(default_path, Type.GRAPH, 1, filter, graph_type, doc)\n        self.graph_type = graph_type\n\nclass EnumParameter(Parameter):\n    def __init__(self, options, default_option, filter=None, doc=None):\n        self.enum_options = options\n        super().__init__(default_option, Type.ENUM, 1, filter, None, doc)\n    \n    def from_index(self, index):\n        return self.enum_options[index]\n    \nclass FloatParameter(Parameter):\n    def __init__(self, default_value, min=None, max=None, size=1, filter=None, doc=None):\n        self.min = min\n        self.max = max\n        super().__init__(default_value, Type.FLOAT, size, filter, None, doc)\n\nclass IntParameter(Parameter):\n    def __init__(self, default_value, min=None, max=None, size=1, filter=None, doc=None):\n        self.min = min\n        self.max = max\n        super().__init__(default_value, Type.INT, size, filter, None, doc)\n\ndef gl_type_to_malt_type(gl_type):\n    from Malt.GL import GL\n    types = {\n        'FLOAT' : Type.FLOAT,\n        'DOUBLE' : Type.FLOAT,\n        'INT' : Type.INT,\n        'BOOL' : Type.BOOL,\n        'SAMPLER_1D' : Type.GRADIENT,\n        'SAMPLER' : Type.TEXTURE,\n    }\n    sizes = {\n        'VEC2' : 2,\n        'VEC3' : 3,\n        'VEC4' : 4,\n        'MAT2' : 4,\n        'MAT3' : 9,\n        'MAT4' : 16,\n    }\n    gl_name = GL.GL_ENUMS[gl_type]\n\n    for type_name, type in types.items():\n        if type_name in gl_name:\n            for size_name, size in sizes.items():\n                if size_name in gl_name:\n                    return (type, size)\n            return (type, 1)\n    \n    raise Exception(gl_name, ' Uniform type not supported')\n\ndef glsl_type_to_malt_type(glsl_type):\n    types = {\n        'float' : Type.FLOAT,\n        'vec' : Type.FLOAT,\n        'mat' : Type.FLOAT,\n        'double' : Type.FLOAT,\n        'd' : Type.FLOAT,\n        'int' : Type.INT,\n        'i' : Type.INT,\n        'uint' : Type.INT,\n        'u' : Type.INT,\n        'bool' : Type.BOOL,\n        'b' : Type.BOOL,\n        'sampler1D' : Type.GRADIENT,\n        'sampler2D' : Type.TEXTURE,\n    }\n    sizes = {\n        'vec2' : 2,\n        'vec3' : 3,\n        'vec4' : 4,\n        'mat2' : 4,\n        'mat3' : 9,\n        'mat4' : 16,\n    }\n    for type_name, type in types.items():\n        if glsl_type.startswith(type_name):\n            for size_name, size in sizes.items():\n                if size_name in glsl_type:\n                    return (type, size)\n            return (type, 1)\n    \n    return None\n"
  },
  {
    "path": "Malt/PipelinePlugin.py",
    "content": "from Malt.Utils import isinstance_str\n\nclass PipelinePlugin():\n\n    @classmethod\n    def poll_pipeline(self, pipeline):\n        return True\n\n    @classmethod\n    def register_pipeline_parameters(self, parameters):\n        pass\n\n    @classmethod\n    def register_pipeline_graphs(self):\n        return []\n\n    @classmethod\n    def register_graph_libraries(self, graphs):\n        pass\n\n    @classmethod\n    def blendermalt_register(self):\n        pass\n\n    @classmethod\n    def blendermalt_unregister(self):\n        pass\n\n    @classmethod\n    def blendermalt_register_nodeitems(self, MaltNodeItemClass):\n        # Should return a dictionary where keys are category names and values are arrays of MaltNodeItems\n        return {}\n\ndef load_plugins_from_dir(dir):\n    import sys, os, importlib\n    if dir not in sys.path:\n        sys.path.append(dir)\n    plugins=[]\n    for e in os.scandir(dir):\n        if (e.path.startswith('.') or e.path.startswith('_') or \n            e.is_file() and e.path.endswith('.py') == False):\n            continue\n        try:\n            '''\n            spec = importlib.util.spec_from_file_location(\"_dynamic_plugin_module_\", e.path)\n            module = importlib.util.module_from_spec(spec)\n            spec.loader.exec_module(module)\n            '''\n            module = importlib.import_module(e.name)\n            importlib.reload(module)\n            plugins.append(module.PLUGIN)\n        except:\n            import traceback\n            traceback.print_exc()\n            print('FILEPATH : ', e.path)\n    return plugins\n"
  },
  {
    "path": "Malt/Pipelines/MiniPipeline/MiniPipeline.py",
    "content": "from os import path\n\nfrom Malt.GL.GL import *\nfrom Malt.GL.Mesh import Mesh\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.GL.Shader import Shader, UBO\nfrom Malt.GL.Texture import Texture\n\nfrom Malt.Pipeline import *\n\nclass MiniPipeline(Pipeline):\n\n    DEFAULT_SHADER = None\n\n    def __init__(self, plugins=[]):\n        super().__init__(plugins)\n\n        self.parameters.world['Background Color'] = Parameter((0.5,0.5,0.5,1), Type.FLOAT, 4)\n        \n        if MiniPipeline.DEFAULT_SHADER is None: \n            source = '''\n            #include \"Common.glsl\"\n\n            #ifdef VERTEX_SHADER\n            void main()\n            {\n                DEFAULT_VERTEX_SHADER();\n            }\n            #endif\n\n            #ifdef PIXEL_SHADER\n            layout (location = 0) out vec4 RESULT;\n            void main()\n            {\n                PIXEL_SETUP_INPUT();\n                RESULT = vec4(1);\n            }\n            #endif\n            '''\n            MiniPipeline.DEFAULT_SHADER = self.compile_material_from_source('mesh', source)\n        \n        self.default_shader = MiniPipeline.DEFAULT_SHADER\n        \n    def compile_material_from_source(self, material_type, source, include_paths=[]):\n        return {\n            'MAIN_PASS' : self.compile_shader_from_source(\n                source, include_paths, ['MAIN_PASS']\n            )\n        }\n    \n    def setup_render_targets(self, resolution):\n        self.t_depth = Texture(resolution, GL_DEPTH_COMPONENT32F)\n        self.t_main_color = Texture(resolution, GL_RGBA32F)\n        self.fbo_main = RenderTarget([self.t_main_color], self.t_depth)\n        \n    def do_render(self, resolution, scene, is_final_render, is_new_frame):        \n        shader_resources = { 'COMMON_UNIFORMS' : self.common_buffer }\n\n        self.fbo_main.clear([scene.world_parameters['Background Color']], 1)\n\n        self.draw_scene_pass(self.fbo_main, scene.batches, 'MAIN_PASS', self.default_shader['MAIN_PASS'], shader_resources)\n\n        return { 'COLOR' : self.t_main_color }\n\n\nPIPELINE = MiniPipeline\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/NPR_LightShaders.py",
    "content": "\nfrom Malt.GL.GL import *\nfrom Malt.GL.Shader import UBO\nfrom Malt.GL.Texture import TextureArray\nfrom Malt.GL.RenderTarget import ArrayLayerTarget, RenderTarget\n\nfrom Malt.Render import Lighting\n\nfrom Malt.PipelineGraph import *\n\nimport ctypes\n\nclass NPR_LightShaders():\n\n    def __init__(self):\n        class C_NPR_LightShadersBuffer(ctypes.Structure):\n            _fields_ = [\n                ('custom_shading_index', ctypes.c_int*Lighting.MAX_LIGHTS),\n            ]\n        self.data = C_NPR_LightShadersBuffer()\n        self.custom_shading_count = 0\n        self.UBO = UBO()\n\n        self.texture = None\n        self.fbos = None\n    \n    def load(self, pipeline, depth_texture, scene):\n        self.custom_shading_count = 0\n        for i, light in enumerate(scene.lights):\n            custom_shading_index = -1\n            if light.parameters['Shader'] is not None:\n                custom_shading_index = self.custom_shading_count\n                self.custom_shading_count += 1\n            self.data.custom_shading_index[i] = custom_shading_index\n\n        self.UBO.load_data(self.data)\n\n        if self.custom_shading_count == 0:\n            return\n\n        lights = [l for l in scene.lights if l.parameters['Shader'] is not None]\n        tex = self.texture\n        if tex is None or tex.resolution != depth_texture.resolution or tex.length < len(lights):\n            self.texture = TextureArray(depth_texture.resolution, len(lights), GL_RGB32F)\n            self.fbos = []\n            for i in range(len(lights)):\n                self.fbos.append(RenderTarget([ArrayLayerTarget(self.texture, i)]))\n        \n        for i, light in enumerate(lights):\n            material = light.parameters['Shader']\n            if material.shader and 'SHADER' in material.shader.keys():\n                shader = material.shader['SHADER']\n                for resource in scene.shader_resources.values():\n                    resource.shader_callback(shader)\n                shader.textures['IN_DEPTH'] = depth_texture\n                if 'LIGHT_INDEX' in shader.uniforms:\n                    light_index = scene.lights.index(light)\n                    shader.uniforms['LIGHT_INDEX'].set_value(light_index)\n                pipeline.draw_screen_pass(shader, self.fbos[i])\n    \n    def shader_callback(self, shader):\n        if 'LIGHTS_CUSTOM_SHADING' in shader.uniform_blocks:\n            self.UBO.bind(shader.uniform_blocks['LIGHTS_CUSTOM_SHADING'])\n        shader.textures['IN_LIGHT_CUSTOM_SHADING'] = self.texture\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/NPR_Lighting.py",
    "content": "from Malt.GL.GL import *\nfrom Malt.GL.Shader import UBO\nfrom Malt.GL.Texture import TextureArray, CubeMapArray\nfrom Malt.GL.RenderTarget import ArrayLayerTarget, RenderTarget\n\nfrom Malt.Render import Lighting\n\nclass NPR_LightsGroupsBuffer():\n\n    def __init__(self):\n        class C_NPR_LightGroupsBuffer(ctypes.Structure):\n            _fields_ = [\n                ('light_group_index', ctypes.c_int*Lighting.MAX_LIGHTS),\n            ]\n        self.data = C_NPR_LightGroupsBuffer()\n        self.UBO = UBO()\n    \n    def load(self, scene):\n        for i, light in enumerate(scene.lights):\n            self.data.light_group_index[i] = light.parameters['Light Group']\n\n        self.UBO.load_data(self.data)\n\n        for material in scene.batches.keys():\n            for shader in material.shader.values():\n                if 'MATERIAL_LIGHT_GROUPS' in shader.uniforms.keys():\n                    shader.uniforms['MATERIAL_LIGHT_GROUPS'].set_value(material.parameters['Light Groups.Light'])\n\n    def shader_callback(self, shader):\n        if 'LIGHT_GROUPS' in shader.uniform_blocks:\n            self.UBO.bind(shader.uniform_blocks['LIGHT_GROUPS'])    \n\n\nclass NPR_ShadowMaps(Lighting.ShadowMaps):\n\n    def __init__(self):\n        super().__init__()\n        self.spot_id_t = None\n        self.sun_id_t = None\n        self.point_id_t = None\n    \n    def setup(self, create_fbos=True):\n        super().setup(False)\n        self.spot_id_t = TextureArray((self.spot_resolution, self.spot_resolution), self.max_spots, \n            GL_R16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST)\n        self.sun_id_t = TextureArray((self.sun_resolution, self.sun_resolution), self.max_suns, \n            GL_R16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST)\n        self.point_id_t = CubeMapArray((self.point_resolution, self.point_resolution), self.max_points, \n            GL_R16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST)\n        \n        if create_fbos:\n            self.spot_fbos = []\n            for i in range(self.spot_depth_t.length):\n                self.spot_fbos.append(RenderTarget([ArrayLayerTarget(self.spot_id_t, i)], ArrayLayerTarget(self.spot_depth_t, i)))\n\n            self.sun_fbos = []\n            for i in range(self.sun_depth_t.length):\n                self.sun_fbos.append(RenderTarget([ArrayLayerTarget(self.sun_id_t, i)], ArrayLayerTarget(self.sun_depth_t, i)))\n            \n            self.point_fbos = []\n            for i in range(self.point_depth_t.length*6):\n                self.point_fbos.append(RenderTarget([ArrayLayerTarget(self.point_id_t, i)], ArrayLayerTarget(self.point_depth_t, i)))\n    \n    def clear(self, spot_count, sun_count, point_count):\n        for i in range(spot_count):\n            self.spot_fbos[i].clear([0], depth=1)\n        for i in range(sun_count):\n            self.sun_fbos[i].clear([0], depth=1)\n        for i in range(point_count*6):\n            self.point_fbos[i].clear([0], depth=1)\n    \n    def shader_callback(self, shader):\n        super().shader_callback(shader)\n        shader.textures['SHADOWMAPS_ID_SPOT'] = self.spot_id_t\n        shader.textures['SHADOWMAPS_ID_SUN'] = self.sun_id_t\n        shader.textures['SHADOWMAPS_ID_POINT'] = self.point_id_t\n\nclass NPR_TransparentShadowMaps(NPR_ShadowMaps):\n\n    def __init__(self):\n        super().__init__()\n        self.spot_color_t = None\n        self.sun_color_t = None\n        self.point_color_t = None\n    \n    def setup(self, create_fbos=True):\n        super().setup(False)\n        self.spot_color_t = TextureArray((self.spot_resolution, self.spot_resolution), self.max_spots, GL_RGB8)\n        self.sun_color_t = TextureArray((self.sun_resolution, self.sun_resolution), self.max_suns, GL_RGB8)\n        self.point_color_t = CubeMapArray((self.point_resolution, self.point_resolution), self.max_points, GL_RGB8)\n        \n        if create_fbos:\n            self.spot_fbos = []\n            for i in range(self.spot_depth_t.length):\n                targets = [ArrayLayerTarget(self.spot_id_t, i), ArrayLayerTarget(self.spot_color_t, i)]\n                self.spot_fbos.append(RenderTarget(targets, ArrayLayerTarget(self.spot_depth_t, i)))\n\n            self.sun_fbos = []\n            for i in range(self.sun_depth_t.length):\n                targets = [ArrayLayerTarget(self.sun_id_t, i), ArrayLayerTarget(self.sun_color_t, i)]\n                self.sun_fbos.append(RenderTarget(targets, ArrayLayerTarget(self.sun_depth_t, i)))\n            \n            self.point_fbos = []\n            for i in range(self.point_depth_t.length*6):\n                targets = [ArrayLayerTarget(self.point_id_t, i), ArrayLayerTarget(self.point_color_t, i)]\n                self.point_fbos.append(RenderTarget(targets, ArrayLayerTarget(self.point_depth_t, i)))\n    \n    def clear(self, spot_count, sun_count, point_count):\n        for i in range(spot_count):\n            self.spot_fbos[i].clear([0, (0,0,0,0)], depth=1)\n        for i in range(sun_count):\n            self.sun_fbos[i].clear([0, (0,0,0,0)], depth=1)\n        for i in range(point_count*6):\n            self.point_fbos[i].clear([0, (0,0,0,0)], depth=1)\n\n    def shader_callback(self, shader):\n        shader.textures['TRANSPARENT_SHADOWMAPS_DEPTH_SPOT'] = self.spot_depth_t\n        shader.textures['TRANSPARENT_SHADOWMAPS_DEPTH_SUN'] = self.sun_depth_t\n        shader.textures['TRANSPARENT_SHADOWMAPS_DEPTH_POINT'] = self.point_depth_t\n        shader.textures['TRANSPARENT_SHADOWMAPS_ID_SPOT'] = self.spot_id_t\n        shader.textures['TRANSPARENT_SHADOWMAPS_ID_SUN'] = self.sun_id_t\n        shader.textures['TRANSPARENT_SHADOWMAPS_ID_POINT'] = self.point_id_t\n        shader.textures['TRANSPARENT_SHADOWMAPS_COLOR_SPOT'] = self.spot_color_t\n        shader.textures['TRANSPARENT_SHADOWMAPS_COLOR_SUN'] = self.sun_color_t\n        shader.textures['TRANSPARENT_SHADOWMAPS_COLOR_POINT'] = self.point_color_t\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/NPR_Pipeline.py",
    "content": "from os import path\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.GL.Texture import Texture\n\nfrom Malt.Pipeline import *\nfrom Malt.PipelineGraph import *\nfrom Malt.PipelineNode import PipelineNode\n\nfrom Malt.GL.GL import *\n\nfrom Malt.Render import DepthToCompositeDepth\nfrom Malt.Render import Sampling\n\n_SCREEN_SHADER_HEADER='''\n#include \"NPR_ScreenShader.glsl\"\n#include \"Node Utils 2/node_utils_2.glsl\"\n#include \"Node Utils/node_utils.glsl\"\n'''\n\n_MESH_SHADER_HEADER='''\n#include \"NPR_MeshShader.glsl\"\n#include \"Node Utils 2/node_utils_2.glsl\"\n#include \"Node Utils/node_utils.glsl\"\n'''\n\n_LIGHT_SHADER_HEADER='''\n#include \"NPR_LightShader.glsl\"\n#include \"Node Utils 2/node_utils_2.glsl\"\n#include \"Node Utils/node_utils.glsl\"\n'''\n\n_DEFAULT_SHADER = None\n\n_DEFAULT_SHADER_SRC='''\nvoid PRE_PASS_PIXEL_SHADER(inout PrePassOutput PPO){ }\nvoid DEPTH_OFFSET(inout float depth_offset, inout bool offset_position){ }\n\n#ifdef MAIN_PASS\nlayout (location = 0) out vec4 OUT_0;\nlayout (location = 1) out vec4 OUT_1;\nlayout (location = 2) out vec4 OUT_2;\nlayout (location = 3) out vec4 OUT_3;\nlayout (location = 4) out vec4 OUT_4;\nlayout (location = 5) out vec4 OUT_5;\nlayout (location = 6) out vec4 OUT_6;\nlayout (location = 7) out vec4 OUT_7;\n#endif //MAIN_PASS\n\nvoid MAIN_PASS_PIXEL_SHADER()\n{    \n    #ifdef MAIN_PASS\n    {\n        OUT_0 = vec4(1,1,0,1);\n        OUT_1 = vec4(1,1,0,1);\n        OUT_2 = vec4(1,1,0,1);\n        OUT_3 = vec4(1,1,0,1);\n        OUT_4 = vec4(1,1,0,1);\n        OUT_5 = vec4(1,1,0,1);\n        OUT_6 = vec4(1,1,0,1);\n        OUT_7 = vec4(1,1,0,1);\n    }\n    #endif //MAIN_PASS\n}\n'''\n\nDEFAULTS_PATH = os.path.join(os.path.dirname(__file__), 'Defaults', 'defaults')\n\nclass NPR_Pipeline(Pipeline):\n\n    def __init__(self, plugins=[]):\n        shader_dir = path.join(path.dirname(__file__), 'Shaders')\n        if shader_dir not in self.SHADER_INCLUDE_PATHS:\n            self.SHADER_INCLUDE_PATHS.append(shader_dir)\n        self.sampling_grid_size = 1\n        self.samples = None\n        super().__init__(plugins)\n    \n    def setup_parameters(self):\n        super().setup_parameters()\n        self.parameters.world['Samples.Grid Size'] = Parameter(8, Type.INT, doc=\"\"\"\n            The number of render samples per side in the sampling grid. \n            The total number of samples is the square of this value minus the samples that fall outside the sampling radius.  \n            Higher values will provide cleaner renders at the cost of increased render times.\"\"\")\n        \n        self.parameters.world['Samples.Grid Size @ Preview'] = Parameter(4, Type.INT)\n        \n        self.parameters.world['Samples.Width'] = Parameter(1.0, Type.FLOAT, doc=\"\"\"\n            The width (and height) of the sampling grid. \n            Larger values will result in smoother/blurrier images while lower values will result in sharper/more aliased ones. \n            Keep it withing the 1-2 range for best results.\"\"\")\n                \n        self.parameters.world['Material.Default'] = MaterialParameter((DEFAULTS_PATH, 'Malt - Default Mesh Material'),\n            '.mesh.glsl', 'Mesh',\n            doc = self.parameters.world['Material.Default'].doc)\n        \n        self.parameters.world['Render'] = GraphParameter((DEFAULTS_PATH, 'Default Render'), 'Render', doc=\"\"\"\n            The *Render Node Tree* used to render the scene. \n            See [Render & Render Layers](#Render & Render Layers) for more info.\"\"\")\n        \n        self.parameters.light['Light Group'] = Parameter(1, Type.INT, doc=\n            \"Lights only affect materials with a matching *Light Group* value.\")\n        \n        self.parameters.light['Shader'] = MaterialParameter('', '.light.glsl', 'Light', doc=\n            \"When set, the *Material* with a custom *Light Shader* or *Light Node Tree* that will be used to render this light.\")\n        \n        self.parameters.material['Light Groups.Light'] = Parameter([1,0,0,0], Type.INT, 4, '.mesh.glsl', doc=\n            \"The *Light Groups* (up to 4) that lit this material.\")\n        \n        self.parameters.material['Light Groups.Shadow'] = Parameter([1,0,0,0], Type.INT, 4, '.mesh.glsl', doc=\n            \"The *Light Groups* (up to 4) that this material casts shadows on.\")\n    \n    def setup_graphs(self):\n        super().setup_graphs()\n\n        mesh = GLSLPipelineGraph(\n            name='Mesh',\n            graph_type=GLSLPipelineGraph.SCENE_GRAPH,\n            default_global_scope=_MESH_SHADER_HEADER,\n            default_shader_src=_DEFAULT_SHADER_SRC,\n            shaders=['PRE_PASS', 'MAIN_PASS', 'SHADOW_PASS'],\n            default_graph_path=(DEFAULTS_PATH, 'Mesh Node Tree Base'),\n            graph_io=[\n                GLSLGraphIO(\n                    name='PRE_PASS_PIXEL_SHADER',\n                    define='CUSTOM_PRE_PASS',\n                    io_wrap='PRE_PASS',\n                    shader_type='PIXEL_SHADER',\n                    dynamic_output_types=GLSLGraphIO.COMMON_OUTPUT_TYPES,\n                    custom_output_start_index=2,\n                ),\n                GLSLGraphIO(\n                    name='DEPTH_OFFSET',\n                    define='CUSTOM_DEPTH_OFFSET',\n                    shader_type='PIXEL_SHADER',\n                ),\n                GLSLGraphIO(\n                    name='MAIN_PASS_PIXEL_SHADER',\n                    io_wrap='MAIN_PASS',\n                    shader_type='PIXEL_SHADER',\n                    dynamic_input_types=GLSLGraphIO.COMMON_INPUT_TYPES,\n                    dynamic_output_types=GLSLGraphIO.COMMON_OUTPUT_TYPES,\n                    default_dynamic_outputs={\n                        'Color': 'vec4',\n                        'Line Color': 'vec4',\n                        'Line Width': 'float',\n                    }\n                ),\n                GLSLGraphIO(\n                    name='VERTEX_DISPLACEMENT_SHADER',\n                    define='CUSTOM_VERTEX_DISPLACEMENT',\n                    shader_type='VERTEX_SHADER'\n                ),\n                GLSLGraphIO(\n                    name='COMMON_VERTEX_SHADER',\n                    define='CUSTOM_VERTEX_SHADER',\n                    shader_type='VERTEX_SHADER',\n                ),\n            ]\n        )\n        self.add_graph(mesh)\n\n        screen = GLSLPipelineGraph(\n            name='Screen',\n            graph_type=GLSLPipelineGraph.GLOBAL_GRAPH,\n            default_global_scope=_SCREEN_SHADER_HEADER,\n            default_shader_src=\"void SCREEN_SHADER(){ }\",\n            default_graph_path=(DEFAULTS_PATH, 'Screen Node Tree Base'),\n            graph_io=[ \n                GLSLGraphIO(\n                    name='SCREEN_SHADER',\n                    shader_type='PIXEL_SHADER',\n                    dynamic_input_types= GLSLGraphIO.COMMON_INPUT_TYPES,\n                    dynamic_output_types= GLSLGraphIO.COMMON_OUTPUT_TYPES,\n                    default_dynamic_outputs={\n                        'Color': 'vec4',\n                    }\n                )\n            ]\n        )\n        self.add_graph(screen)\n\n        light = GLSLPipelineGraph(\n            name='Light',\n            graph_type=GLSLPipelineGraph.INTERNAL_GRAPH,\n            default_global_scope=_LIGHT_SHADER_HEADER,\n            default_shader_src=\"void LIGHT_SHADER(vec3 relative_coordinates, vec3 uvw, inout vec3 color, inout float attenuation) { }\",\n            default_graph_path=(DEFAULTS_PATH, 'Light Node Tree Base'),\n            graph_io=[ \n                GLSLGraphIO(\n                    name='LIGHT_SHADER',\n                    shader_type='PIXEL_SHADER',\n                )\n            ]\n        )\n        self.add_graph(light)\n        \n        render_layer = PythonPipelineGraph(\n            name='Render Layer',\n            default_graph_path=(DEFAULTS_PATH, 'Default Render Layer'),\n            graph_io = [\n                PythonGraphIO(\n                    name = 'Render Layer',\n                    dynamic_input_types= PythonGraphIO.COMMON_IO_TYPES,\n                    dynamic_output_types= PythonGraphIO.COMMON_IO_TYPES,\n                    function = PipelineNode.static_reflect(\n                        name = 'Render Layer',\n                        inputs = {\n                            'Scene' : Parameter('Scene', Type.OTHER),\n                        },\n                        outputs = {\n                            'Color' : Parameter('', Type.TEXTURE),\n                        },\n                    )\n                )\n            ]\n        )\n        render_layer.add_library(os.path.join(os.path.dirname(__file__),'..','..','Nodes'))\n        render_layer.add_library(os.path.join(os.path.dirname(__file__), 'Nodes', 'RenderLayer'))\n        self.add_graph(render_layer)\n\n        render = PythonPipelineGraph(\n            name='Render',\n            default_graph_path=(DEFAULTS_PATH, 'Default Render'),\n            graph_io = [\n                PythonGraphIO(\n                    name = 'Render',\n                    dynamic_output_types= PythonGraphIO.COMMON_IO_TYPES,\n                    function = PipelineNode.static_reflect(\n                        name = 'Render',\n                        inputs = {\n                            'Scene' : Parameter('Scene', Type.OTHER),\n                        },\n                        outputs = {\n                            'Color' : Parameter('', Type.TEXTURE),\n                            'Depth' : Parameter('', Type.TEXTURE),\n                        },\n                    )\n                )\n            ]\n        )\n        render.add_library(os.path.join(os.path.dirname(__file__),'..','..','Nodes'))\n        render.add_library(os.path.join(os.path.dirname(__file__), 'Nodes', 'Render'))\n        self.add_graph(render)\n        \n    \n    def setup_resources(self):\n        super().setup_resources()\n        self.composite_depth = DepthToCompositeDepth.CompositeDepth()\n        global _DEFAULT_SHADER\n        if _DEFAULT_SHADER is None: _DEFAULT_SHADER = self.compile_material_from_source('Mesh', _MESH_SHADER_HEADER + _DEFAULT_SHADER_SRC)\n        self.default_shader = _DEFAULT_SHADER\n    \n    def get_samples(self):\n        if self.samples is None:\n            self.samples = Sampling.get_RGSS_samples(self.sampling_grid_size, 1.0)\n        return self.samples\n    \n    def get_sample(self, width):\n        w, h = self.get_samples()[self.sample_count]\n        w*=width\n        h*=width\n        return w, h\n    \n    def get_scene_batches(self, scene):\n        opaque_batches = {}\n        transparent_batches = {}\n        for material, meshes in scene.batches.items():\n            if material and material.shader:\n                if material.shader['PRE_PASS'].uniforms['Settings.Transparency'].value[0] == True:\n                    transparent_batches[material] = meshes\n                    continue\n            opaque_batches[material] = meshes\n        return opaque_batches, transparent_batches\n\n    def do_render(self, resolution, scene, is_final_render, is_new_frame):\n        #SETUP SAMPLING\n        if self.sampling_grid_size != scene.world_parameters['Samples.Grid Size']:\n            self.sampling_grid_size = scene.world_parameters['Samples.Grid Size']\n            self.samples = None\n        \n        self.is_new_frame = is_new_frame\n        \n        sample_offset = self.get_sample(scene.world_parameters['Samples.Width'])\n\n        opaque_batches, transparent_batches = self.get_scene_batches(scene)\n        \n        self.common_buffer.load(scene, resolution, sample_offset, self.sample_count)\n        scene.shader_resources = {\n            'COMMON_UNIFORMS' : self.common_buffer\n        }\n        \n        result = {\n            'COLOR': None,\n            'DEPTH': None,\n        }\n        graph = scene.world_parameters['Render']\n        if graph:\n            IN = {'Scene' : scene}\n            OUT = {'Color' : None}\n            self.graphs['Render'].run_source(self, graph['source'], graph['parameters'], IN, OUT)\n            result = OUT\n            result['COLOR'] = result['Color']\n            result['DEPTH'] = result['Depth']\n\n        #COMPOSITE DEPTH\n        if is_final_render and result['DEPTH'] is None:\n            if self.sample_count == len(self.samples) - 1:\n                normal_depth = Texture(resolution, GL_RGBA32F)\n                target = RenderTarget([normal_depth], Texture(resolution, GL_DEPTH_COMPONENT32F))\n                target.clear([(0,0,1,1)], 1)\n                self.common_buffer.load(scene, resolution)\n                self.draw_scene_pass(target, opaque_batches, 'PRE_PASS', self.default_shader, scene.shader_resources)\n                result['DEPTH'] = self.composite_depth.render(self, self.common_buffer, normal_depth, depth_channel=3)\n        \n        return result\n\n\nPIPELINE = NPR_Pipeline\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Nodes/Render/RenderLayers.py",
    "content": "from Malt.GL import GL\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\n\n_BLEND_TRANSPARENCY_SHADER = None\n\nclass RenderLayers(PipelineNode):\n    \"\"\"\n    Renders the scene geometry, using multiple *depth peeling* layers for transparent objects.  \n    The node sockets are dynamic, based on the graph selected.  \n    \"\"\"\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n        self.resolution = None\n        self.texture_targets = {}\n        self.render_target = None\n        self.custom_io = []\n    \n    @staticmethod\n    def get_pass_type():\n        return 'Render Layer.Render Layer'\n    \n    @classmethod\n    def reflect_inputs(cls):\n        inputs = {}\n        inputs['Scene'] = Parameter('Scene', Type.OTHER)\n        inputs['Transparent Layers'] = Parameter(4, Type.INT, doc=\"\"\"\n            The maximum number of overlapping transparency layers.  \n            Incresing this values lowers performance.\n        \"\"\")\n        inputs['Transparent Layers @ Preview'] = Parameter(2, Type.INT)\n        return inputs\n    \n    @classmethod\n    def reflect_outputs(cls):\n        outputs = {}\n        return outputs\n    \n    def setup_render_targets(self, resolution, custom_io):\n        self.opaque_targets = {}\n        self.transparent_targets = {}\n        self.color_targets = {}\n        \n        for io in custom_io:\n            if io['io'] == 'out' and io['type'] == 'Texture':#TODO\n                self.opaque_targets[io['name']] = Texture(resolution, GL.GL_RGBA16F)\n                self.transparent_targets[io['name']] = Texture(resolution, GL.GL_RGBA16F)\n                self.color_targets[io['name']] = Texture(resolution, GL.GL_RGBA16F)\n        \n        self.fbo_opaque = RenderTarget([*self.opaque_targets.values()])\n        self.fbo_transparent = RenderTarget([*self.transparent_targets.values()])\n        self.fbo_color = RenderTarget([*self.color_targets.values()])\n    \n    def blend_transparency(self, back_textures, front_textures, fbo):\n        global _BLEND_TRANSPARENCY_SHADER\n        if _BLEND_TRANSPARENCY_SHADER is None:\n            _BLEND_TRANSPARENCY_SHADER = self.pipeline.compile_shader_from_source('#include \"Passes/BlendTransparency.glsl\"')\n        for i in range(len(fbo.targets)):\n            _BLEND_TRANSPARENCY_SHADER.textures[f'IN_BACK[{str(i)}]'] = back_textures[i]\n            _BLEND_TRANSPARENCY_SHADER.textures[f'IN_FRONT[{str(i)}]'] = front_textures[i]\n        self.pipeline.draw_screen_pass(_BLEND_TRANSPARENCY_SHADER, fbo)\n    \n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n        custom_io = parameters['CUSTOM_IO']\n        graph = parameters['PASS_GRAPH']\n        scene = inputs['Scene']\n        if scene and graph:\n            if self.pipeline.resolution != self.resolution or self.custom_io != custom_io:\n                self.setup_render_targets(self.pipeline.resolution, custom_io)\n                self.resolution = self.pipeline.resolution\n                self.custom_io = custom_io\n\n            self.fbo_color.clear([(0,0,0,0)]*len(self.fbo_color.targets))\n            self.fbo_transparent.clear([(0,0,0,0)]*len(self.fbo_transparent.targets))\n            \n            self.layer_index = 0\n            self.layer_count = inputs['Transparent Layers'] + 1\n            graph['parameters']['__RENDER_LAYERS__'] = self\n            for i in range(self.layer_count):\n                graph['parameters']['__LAYER_INDEX__'] = self.layer_index\n                graph['parameters']['__LAYER_COUNT__'] = self.layer_count\n                self.pipeline.graphs['Render Layer'].run_source(self.pipeline, graph['source'], graph['parameters'], inputs, outputs)\n                results = []\n                for io in self.custom_io:\n                    if io['io'] == 'out' and io['type'] == 'Texture':\n                        results.append(outputs[io['name']])\n                if i == 0:\n                    self.pipeline.copy_textures(self.fbo_opaque, results)\n                else:\n                    self.blend_transparency(results, self.fbo_transparent.targets, self.fbo_color)\n                    self.pipeline.copy_textures(self.fbo_transparent, self.fbo_color.targets)\n                self.layer_index += 1     \n            \n            self.blend_transparency(self.fbo_opaque.targets, self.fbo_transparent.targets, self.fbo_color)\n            outputs.update(self.color_targets)\n\n\nNODE = RenderLayers  \n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Nodes/Render/SceneLighting.py",
    "content": "from Malt.GL.GL import *\nfrom Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\n\nfrom Malt.Render import Common\nfrom Malt.Render import Lighting\nfrom Malt.Pipelines.NPR_Pipeline import NPR_Lighting\n\nclass SceneLighting(PipelineNode):\n    \"\"\"\n    Renders the shadow maps and attaches them along the scene lights data to the *Scene* shader resources.\n    \"\"\"\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n        self.lights_buffer = Lighting.get_lights_buffer()\n        self.light_groups_buffer = NPR_Lighting.NPR_LightsGroupsBuffer()\n        self.shadowmaps_opaque = NPR_Lighting.NPR_ShadowMaps()\n        self.shadowmaps_transparent = NPR_Lighting.NPR_TransparentShadowMaps()\n        self.common_buffer = Common.CommonBuffer()\n\n    @classmethod\n    def reflect_inputs(cls):\n        inputs = {}\n        inputs['Scene'] = Parameter('Scene', Type.OTHER)\n        inputs['Point Resolution'] = Parameter(2048, Type.INT, doc=\n            \"Shadowmap resolution for point lights *(for each cubemap side)*.\")\n        inputs['Point Resolution @ Preview'] = Parameter(512, Type.INT)\n        \n        inputs['Spot Resolution'] = Parameter(2048, Type.INT, doc=\n            \"Shadowmap resolution for spot lights.\")\n        inputs['Spot Resolution @ Preview'] = Parameter(512, Type.INT)\n        \n        inputs['Sun Resolution'] = Parameter(2048, Type.INT, doc=\n            \"Shadowmap resolution for sun light lights *(for each cascade side)*.\")\n        \n        inputs['Sun Max Distance'] = Parameter(100, Type.FLOAT,doc=\"\"\"\n            The maximum distance from the view origin at which objects will still cast shadows.\n            The lower the value, the higher the perceived resolution.\"\"\")\n        inputs['Sun Max Distance @ Preview'] = Parameter(25, Type.FLOAT)\n        \n        inputs['Sun CSM Count'] = Parameter(4, Type.INT, doc=\n            \"The number of [Shadow Cascades](https://docs.microsoft.com/en-us/windows/win32/dxtecharts/cascaded-shadow-maps#cascaded-shadow-maps-and-perspective-aliasing) for sun lights.\")\n        inputs['Sun CSM Count @ Preview'] = Parameter(2, Type.INT)\n        \n        inputs['Sun CSM Distribution'] = Parameter(0.9, Type.FLOAT, doc=\"\"\"\n            Interpolates the cascades distribution along the view distance between linear distribution *(at 0)* and logarithmic distribution *(at 1)*.  \n            The appropriate value depends on camera FOV and scene characteristics.\"\"\")\n        return inputs\n    \n    @classmethod\n    def reflect_outputs(cls):\n        outputs = {}\n        outputs['Scene'] = Parameter('Scene', Type.OTHER, doc=\n            \"The scene with the light data already loaded in the shader resources.\")\n        return outputs\n\n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n\n        scene = inputs['Scene']\n        if scene is None:\n            return\n\n        sample_offset = self.pipeline.get_sample(scene.world_parameters['Samples.Width'])\n        opaque_batches, transparent_batches = self.pipeline.get_scene_batches(scene)\n\n        inputs['Spot Resolution'] = max(8, inputs['Spot Resolution'])\n        inputs['Sun Resolution'] = max(8, inputs['Sun Resolution'])\n        inputs['Point Resolution'] = max(8, inputs['Point Resolution'])\n        inputs['Sun CSM Count'] = max(1, inputs['Sun CSM Count'])\n\n        self.lights_buffer.load(scene, \n            inputs['Spot Resolution'],\n            inputs['Sun Resolution'],\n            inputs['Point Resolution'],\n            inputs['Sun CSM Count'], \n            inputs['Sun CSM Distribution'],\n            inputs['Sun Max Distance'],\n            sample_offset)\n        self.light_groups_buffer.load(scene)\n        self.shadowmaps_opaque.load(scene,\n            inputs['Spot Resolution'],\n            inputs['Sun Resolution'],\n            inputs['Point Resolution'],\n            inputs['Sun CSM Count'])\n        self.shadowmaps_transparent.load(scene,\n            inputs['Spot Resolution'],\n            inputs['Sun Resolution'],\n            inputs['Point Resolution'],\n            inputs['Sun CSM Count'])\n        \n        shader_resources = scene.shader_resources.copy()\n        shader_resources['COMMON_UNIFORMS'] = self.common_buffer\n        shader_resources['SCENE_LIGHTS'] = self.lights_buffer\n\n        def render_shadowmaps(lights, fbos_opaque, fbos_transparent):\n            for light_index, light_matrices_pair in enumerate(lights.items()):\n                light, matrices = light_matrices_pair\n                for matrix_index, camera_projection_pair in enumerate(matrices): \n                    camera, projection = camera_projection_pair\n                    i = light_index * len(matrices) + matrix_index\n                    self.common_buffer.load(scene, fbos_opaque[i].resolution, (0,0), self.pipeline.sample_count, camera, projection)\n                    def get_light_group_batches(batches):\n                        result = {}\n                        for material, meshes in batches.items():\n                            if material and light.parameters['Light Group'] in material.parameters['Light Groups.Shadow']:\n                                result[material] = meshes\n                        return result\n                    #TODO: Callback\n                    self.pipeline.draw_scene_pass(fbos_opaque[i], get_light_group_batches(opaque_batches), \n                        'SHADOW_PASS', self.pipeline.default_shader['SHADOW_PASS'], shader_resources)\n                    self.pipeline.draw_scene_pass(fbos_transparent[i], get_light_group_batches(transparent_batches), \n                        'SHADOW_PASS', self.pipeline.default_shader['SHADOW_PASS'], shader_resources)\n        \n        render_shadowmaps(self.lights_buffer.spots,\n            self.shadowmaps_opaque.spot_fbos, self.shadowmaps_transparent.spot_fbos)\n        \n        glEnable(GL_DEPTH_CLAMP)\n        render_shadowmaps(self.lights_buffer.suns,\n            self.shadowmaps_opaque.sun_fbos, self.shadowmaps_transparent.sun_fbos)\n        glDisable(GL_DEPTH_CLAMP)\n\n        render_shadowmaps(self.lights_buffer.points,\n            self.shadowmaps_opaque.point_fbos, self.shadowmaps_transparent.point_fbos)\n        \n        import copy\n        scene = copy.copy(scene)\n        scene.shader_resources = copy.copy(scene.shader_resources)\n\n        scene.shader_resources['SCENE_LIGHTS'] = self.lights_buffer\n        scene.shader_resources['LIGHT_GROUPS'] = self.light_groups_buffer\n        scene.shader_resources['SHADOWMAPS'] = self.shadowmaps_opaque\n        scene.shader_resources['TRANSPARENT_SHADOWMAPS'] = self.shadowmaps_transparent\n\n        outputs['Scene'] = scene\n\n\nNODE = SceneLighting\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Nodes/Render/ScreenPass.py",
    "content": "from Malt.GL import GL\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\nfrom Malt.Scene import TextureShaderResource\n\n\nclass ScreenPass(PipelineNode):\n    \"\"\"\n    Renders a full screen shader pass.  \n    The node sockets are dynamic, based on the shader selected.\n    \"\"\"\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n        self.resolution = None\n        self.texture_targets = {}\n        self.render_target = None\n        self.custom_io = []\n    \n    @staticmethod\n    def get_pass_type():\n        return 'Screen.SCREEN_SHADER'\n    \n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n        material = parameters['PASS_MATERIAL']\n        custom_io = parameters['CUSTOM_IO']\n\n        if self.pipeline.resolution != self.resolution or self.custom_io != custom_io:\n            self.texture_targets = {}\n            for io in custom_io:\n                if io['io'] == 'out':\n                    if io['type'] == 'Texture':#TODO\n                        self.texture_targets[io['name']] = Texture(self.pipeline.resolution, GL.GL_RGBA16F)\n            self.render_target = RenderTarget([*self.texture_targets.values()])\n            self.resolution = self.pipeline.resolution\n            self.custom_io = custom_io\n        \n        self.render_target.clear([(0,0,0,0)]*len(self.texture_targets))\n\n        if material and material.shader and 'SHADER' in material.shader:\n            shader = material.shader['SHADER']\n            for io in custom_io:\n                if io['io'] == 'in':\n                    if io['type'] == 'Texture':#TODO\n                        from Malt.SourceTranspiler import GLSLTranspiler\n                        glsl_name = GLSLTranspiler.custom_io_reference('IN', 'SCREEN_SHADER', io['name'])\n                        shader.textures[glsl_name] = inputs[io['name']]\n            self.pipeline.common_buffer.shader_callback(shader)\n            shader.uniforms['RENDER_LAYER_MODE'].set_value(False)\n            self.pipeline.draw_screen_pass(shader, self.render_target)\n        \n        for io in custom_io:\n            if io['io'] == 'out':\n                if io['type'] == 'Texture':#TODO\n                    outputs[io['name']] = self.texture_targets[io['name']]\n\n\nNODE = ScreenPass    \n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Nodes/RenderLayer/MainPass.py",
    "content": "from Malt.GL.GL import *\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\nfrom Malt.Scene import TextureShaderResource\n\nclass MainPass(PipelineNode):\n    \"\"\"\n    Renders the scene geometry using the *Mesh Main Pass*.  \n    The node sockets are dynamic, based on the *Main Pass Custom IO*.  \n    If *Normal Depth/ID* is empty, the *Pre Pass* *Normal Depth/ID* will be used.\n    \"\"\"\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n        self.resolution = None\n        self.t_depth = None\n    \n    @staticmethod\n    def get_pass_type():\n        return 'Mesh.MAIN_PASS_PIXEL_SHADER'\n    \n    @classmethod\n    def reflect_inputs(cls):\n        inputs = {}\n        inputs['Scene'] = Parameter('Scene', Type.OTHER)\n        inputs['Normal Depth'] = Parameter('', Type.TEXTURE)\n        inputs['ID'] = Parameter('', Type.TEXTURE)\n        return inputs\n    \n    @classmethod\n    def reflect_outputs(cls):\n        outputs = {}\n        return outputs\n    \n    def setup_render_targets(self, resolution, t_depth, custom_io):\n        self.custom_targets = {}\n        for io in custom_io:\n            if io['io'] == 'out' and io['type'] == 'Texture':#TODO\n                formats = {\n                    'float' : GL.GL_R16F,\n                    'vec2' : GL.GL_RG16F,\n                    'vec3' : GL.GL_RGB16F,\n                    'vec4' : GL.GL_RGBA16F,\n                }\n                self.custom_targets[io['name']] = Texture(resolution, formats[io['subtype']])\n        self.t_depth = t_depth\n        self.fbo = RenderTarget([*self.custom_targets.values()], self.t_depth)\n\n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n        custom_io = parameters['CUSTOM_IO']\n        \n        scene = inputs['Scene']\n        if scene is None:\n            return\n        t_normal_depth = inputs['Normal Depth']\n        t_id = inputs['ID']\n\n        shader_resources = scene.shader_resources.copy()\n        if t_normal_depth:\n            shader_resources['IN_NORMAL_DEPTH'] = TextureShaderResource('IN_NORMAL_DEPTH', t_normal_depth)\n        if t_id:\n            shader_resources['IN_ID'] = TextureShaderResource('IN_ID', t_id)\n        \n        t_depth = shader_resources['T_DEPTH'].texture\n        if self.pipeline.resolution != self.resolution or self.custom_io != custom_io or t_depth != self.t_depth:\n            self.setup_render_targets(self.pipeline.resolution, t_depth, custom_io)\n            self.resolution = self.pipeline.resolution\n            self.custom_io = custom_io\n        \n        for io in custom_io:\n            if io['io'] == 'in':\n                if io['type'] == 'Texture':#TODO\n                    from Malt.SourceTranspiler import GLSLTranspiler\n                    glsl_name = GLSLTranspiler.custom_io_reference('IN', 'MAIN_PASS_PIXEL_SHADER', io['name'])\n                    shader_resources['CUSTOM_IO'+glsl_name] = TextureShaderResource(glsl_name, inputs[io['name']])\n                    \n        self.fbo.clear([(0,0,0,0)] * len(self.fbo.targets))\n        self.pipeline.draw_scene_pass(self.fbo, scene.batches, 'MAIN_PASS', self.pipeline.default_shader['MAIN_PASS'], \n            shader_resources, GL_EQUAL)\n\n        outputs.update(self.custom_targets)\n\nNODE = MainPass\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Nodes/RenderLayer/PrePass.py",
    "content": "from Malt.GL.GL import *\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\nfrom Malt.Scene import TextureShaderResource\n\nfrom Malt.Pipelines.NPR_Pipeline.NPR_LightShaders import NPR_LightShaders\n\nclass PrePass(PipelineNode):\n    \"\"\"\n    Renders the scene geometry using the *Mesh Pre Pass*.  \n    The node sockets are dynamic, based on the *Pre Pass Custom IO*.  \n    If *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used.\n    \"\"\"\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n        self.resolution = None\n        self.custom_io = []\n        self.npr_light_shaders = NPR_LightShaders()\n    \n    @staticmethod\n    def get_pass_type():\n        return 'Mesh.PRE_PASS_PIXEL_SHADER'\n    \n    @classmethod\n    def reflect_inputs(cls):\n        inputs = {}\n        inputs['Scene'] = Parameter('Scene', Type.OTHER)\n        return inputs\n    \n    @classmethod\n    def reflect_outputs(cls):\n        outputs = {}\n        outputs['Scene'] = Parameter('Scene', Type.OTHER)\n        outputs['Normal Depth'] = Parameter('', Type.TEXTURE)\n        outputs['ID'] = Parameter('', Type.TEXTURE)\n        return outputs\n    \n    def setup_render_targets(self, resolution, custom_io):\n        self.t_depth = Texture(resolution, GL_DEPTH_COMPONENT32F)\n        \n        self.t_normal_depth = Texture(resolution, GL_RGBA32F)\n        self.t_id = Texture(resolution, GL_RGBA16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST)\n        self.custom_targets = {}\n        for io in custom_io:\n            if io['io'] == 'out' and io['type'] == 'Texture':#TODO\n                self.custom_targets[io['name']] = Texture(resolution, GL.GL_RGBA16F)\n        self.fbo = RenderTarget([self.t_normal_depth, self.t_id, *self.custom_targets.values()], self.t_depth)\n        \n        self.t_last_layer_id = Texture(resolution, GL_R16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST)\n        self.fbo_last_layer_id = RenderTarget([self.t_last_layer_id])\n\n        self.t_opaque_depth = Texture(resolution, GL_DEPTH_COMPONENT32F)\n        self.fbo_opaque_depth = RenderTarget([], self.t_opaque_depth)\n        self.t_transparent_depth = Texture(resolution, GL_DEPTH_COMPONENT32F)\n        self.fbo_transparent_depth = RenderTarget([], self.t_transparent_depth)\n\n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n        custom_io = parameters['CUSTOM_IO']\n        scene = inputs['Scene']\n\n        is_opaque_pass = parameters['__GLOBALS__']['__LAYER_INDEX__'] == 0\n\n        if self.pipeline.resolution != self.resolution or self.custom_io != custom_io:\n            self.setup_render_targets(self.pipeline.resolution, custom_io)\n            self.resolution = self.pipeline.resolution\n            self.custom_io = custom_io\n        \n        import copy\n        scene = copy.copy(scene)\n        opaque_batches, transparent_batches = self.pipeline.get_scene_batches(scene)\n        scene.batches = opaque_batches if is_opaque_pass else transparent_batches\n        \n        shader_resources = scene.shader_resources.copy()\n        shader_resources.update({\n            'IN_OPAQUE_DEPTH': TextureShaderResource('IN_OPAQUE_DEPTH', self.t_opaque_depth),\n            'IN_TRANSPARENT_DEPTH': TextureShaderResource('IN_TRANSPARENT_DEPTH', self.t_transparent_depth),\n            'IN_LAST_ID': TextureShaderResource('IN_LAST_ID', self.t_last_layer_id),\n        })\n        self.fbo.clear([(0,0,0,1), (0,0,0,0)] + [(0,0,0,0)]*len(self.custom_targets), 1)\n\n        self.pipeline.draw_scene_pass(self.fbo, scene.batches, 'PRE_PASS', self.pipeline.default_shader['PRE_PASS'], shader_resources)\n\n        if is_opaque_pass:\n            self.fbo_last_layer_id.clear([(0,0,0,0)])\n            self.fbo_transparent_depth.clear([], -1)\n            self.pipeline.copy_textures(self.fbo_opaque_depth, [], self.t_depth)\n        else:\n            self.pipeline.copy_textures(self.fbo_last_layer_id, [self.t_id])\n            self.pipeline.copy_textures(self.fbo_transparent_depth, [], self.t_depth)\n\n        #CUSTOM LIGHT SHADERS\n        self.npr_light_shaders.load(self.pipeline, self.t_depth, scene)\n\n        scene.shader_resources = scene.shader_resources.copy()\n        scene.shader_resources.update({\n            'LIGHTS_CUSTOM_SHADING': self.npr_light_shaders,\n            'IN_NORMAL_DEPTH': TextureShaderResource('IN_NORMAL_DEPTH', self.t_normal_depth),\n            'IN_ID': TextureShaderResource('IN_ID', self.t_id),\n            'T_DEPTH': TextureShaderResource('', self.t_depth), #just pass the reference\n        })\n\n        outputs['Scene'] = scene\n        outputs['Normal Depth'] = self.t_normal_depth\n        outputs['ID'] = self.t_id\n        outputs.update(self.custom_targets)\n\n\nNODE = PrePass\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Nodes/RenderLayer/ScreenPass.py",
    "content": "from Malt.GL import GL\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.RenderTarget import RenderTarget\nfrom Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\nfrom Malt.Scene import TextureShaderResource\n\n\nclass ScreenPass(PipelineNode):\n    \"\"\"\n    Renders a full screen shader pass.  \n    The node sockets are dynamic, based on the shader selected.  \n    If *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used.\n    \"\"\"\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n        self.resolution = None\n        self.texture_targets = {}\n        self.render_target = None\n        self.custom_io = []\n    \n    @staticmethod\n    def get_pass_type():\n        return 'Screen.SCREEN_SHADER'\n    \n    @classmethod\n    def reflect_inputs(cls):\n        inputs = {}\n        inputs['Layer Only'] = Parameter(True, Type.BOOL, doc=\"\"\"\n            Draw only on top of the current layer geometry,\n            to avoid accidentally covering previous layers.\n        \"\"\")\n        inputs['Scene'] = Parameter('Scene', Type.OTHER)\n        inputs['Normal Depth'] = Parameter('', Type.TEXTURE)\n        inputs['ID'] = Parameter('', Type.TEXTURE)\n        return inputs\n    \n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n        material = parameters['PASS_MATERIAL']\n        custom_io = parameters['CUSTOM_IO']\n\n        deferred_mode = inputs['Layer Only']\n        scene = inputs['Scene']\n        t_normal_depth = inputs['Normal Depth']\n        t_id = inputs['ID']\n\n        shader_resources = {}\n        if scene:\n            shader_resources = scene.shader_resources.copy()\n        if t_normal_depth:\n            shader_resources['IN_NORMAL_DEPTH'] = TextureShaderResource('IN_NORMAL_DEPTH', t_normal_depth)\n        if t_id:\n            shader_resources['IN_ID'] = TextureShaderResource('IN_ID', t_id)\n\n        if self.pipeline.resolution != self.resolution or self.custom_io != custom_io:\n            self.texture_targets = {}\n            for io in custom_io:\n                if io['io'] == 'out':\n                    if io['type'] == 'Texture':#TODO\n                        self.texture_targets[io['name']] = Texture(self.pipeline.resolution, GL.GL_RGBA16F)\n            self.render_target = RenderTarget([*self.texture_targets.values()])\n            self.resolution = self.pipeline.resolution\n            self.custom_io = custom_io\n        \n        self.render_target.clear([(0,0,0,0)]*len(self.texture_targets))\n\n        if material and material.shader and 'SHADER' in material.shader:\n            shader = material.shader['SHADER']\n            for io in custom_io:\n                if io['io'] == 'in':\n                    if io['type'] == 'Texture':#TODO\n                        from Malt.SourceTranspiler import GLSLTranspiler\n                        glsl_name = GLSLTranspiler.custom_io_reference('IN', 'SCREEN_SHADER', io['name'])\n                        shader.textures[glsl_name] = inputs[io['name']]\n            self.pipeline.common_buffer.bind(shader.uniform_blocks['COMMON_UNIFORMS'])\n            for resource in shader_resources.values():\n                resource.shader_callback(shader)\n            shader.uniforms['RENDER_LAYER_MODE'].set_value(True)\n            shader.uniforms['DEFERRED_MODE'].set_value(deferred_mode)\n            self.pipeline.draw_screen_pass(shader, self.render_target)\n        \n        for io in custom_io:\n            if io['io'] == 'out':\n                if io['type'] == 'Texture':#TODO\n                    outputs[io['name']] = self.texture_targets[io['name']]\n\n\nNODE = ScreenPass    \n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Intellisense.glsl",
    "content": "//This is just for getting text editor autocompletion on user shaders.\n//It doesn't have any effect at runtime\n#ifdef __INTELLISENSE__\n\n#define VERTEX_SHADER\n#define PIXEL_SHADER\n\n#define IS_MESH_SHADER\n\n#define CUSTOM_VERTEX_DISPLACEMENT\n#define SHADOW_PASS\n#define PRE_PASS\n#define MAIN_PASS\n\n#define IS_LIGHT_SHADER\n\n#define IS_SCREEN_SHADER\n\n#endif //__INTELLISENSE__\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Shaders/NPR_LightShader.glsl",
    "content": "#define NO_NORMAL_INPUT\n#define NO_UV_INPUT\n#define NO_VERTEX_COLOR_INPUT\n#define NO_MODEL_INPUT\n#define NO_ID_INPUT\n\n#ifdef PIXEL_SHADER\nfloat DEFERRED_PIXEL_DEPTH;\n#define CUSTOM_PIXEL_DEPTH DEFERRED_PIXEL_DEPTH\n#endif\n\n#include \"NPR_Intellisense.glsl\"\n#include \"Common.glsl\"\n#include \"Lighting/Lighting.glsl\"\n\nuniform int LIGHT_INDEX;\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\n\nuniform sampler2D IN_DEPTH;\n\nlayout (location = 0) out vec3 RESULT;\n\nvoid LIGHT_SHADER(vec3 relative_coordinates, vec3 uvw, inout vec3 color, inout float attenuation);\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    float depth = texelFetch(IN_DEPTH, screen_pixel(), 0).x;\n    DEFERRED_PIXEL_DEPTH = depth;\n    POSITION = screen_to_camera(screen_uv(), depth);\n    POSITION = transform_point(inverse(CAMERA), POSITION);\n\n    Light L = LIGHTS.lights[LIGHT_INDEX];\n    LitSurface LS = lit_surface(POSITION, vec3(0), L, false);\n    \n    vec3 light_space = vec3(0);\n    vec3 uvw = vec3(0);\n\n    if(L.type == LIGHT_SPOT)\n    {\n        light_space = project_point(LIGHTS.spot_matrices[L.type_index], POSITION);\n        uvw.xy = light_space.xy * 0.5 + 0.5;\n    }\n    if(L.type == LIGHT_SUN)\n    {\n        vec3 z = L.direction;\n        vec3 c = vec3(0,0,1);\n        if(abs(dot(z, c)) < 1.0)\n        {\n            vec3 x = normalize(cross(c, z));\n            vec3 y = normalize(cross(x, z));\n            mat3 rotation = mat3(x,y,z);\n            mat4 m = mat4_translation(L.position) * mat4(rotation);\n            m = inverse(m);\n\n            light_space = transform_point(m, POSITION);\n        }\n        else\n        {\n            light_space = POSITION;\n            light_space -= L.position;\n        }\n        \n        uvw.xy = light_space.xy;\n    }\n    if(L.type == LIGHT_POINT)\n    {\n        light_space = POSITION - L.position;\n        light_space /= L.radius;    \n        \n        uvw = normalize(light_space);\n    }\n\n    vec3 color = L.color;\n    float attenuation = LS.P;\n\n    LIGHT_SHADER(light_space, uvw, color, attenuation);\n    \n    RESULT = color * attenuation;\n}\n\n#endif //PIXEL_SHADER\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Shaders/NPR_MeshShader.glsl",
    "content": "#include \"NPR_Intellisense.glsl\"\n#include \"Common.glsl\"\n\n/* META GLOBAL\n    @meta: internal=true; \n*/\nstruct NPR_Settings\n{\n    // Global material settings. Can be modified in the material panel UI\n    bool Receive_Shadow;\n    bool Self_Shadow;\n    bool Transparency;\n    bool Transparency_Single_Layer;\n    float Vertex_Displacement_Offset;\n};\n\nuniform NPR_Settings Settings = NPR_Settings(true, true, false, false, 0.001);\n\nuniform ivec4 MATERIAL_LIGHT_GROUPS;\n\nstruct Vertex\n{\n    vec3 position;\n    vec3 normal;\n    uvec4 id;\n    vec3 tangent;\n    vec3 bitangent;\n    vec2 uv[4];\n    vec4 color[4];\n};\n\n/*  META\n    @id: label=ID;\n    @opacity: label=Opacity Mask;\n    @transparent_shadowmap_color: label=Transparent Shadow Color;\n*/\nstruct PrePassOutput\n{\n    vec3 normal;\n    uvec4 id;\n    float opacity;\n    vec3 transparent_shadowmap_color;\n};\n\n#ifdef VERTEX_SHADER\n\nvoid COMMON_VERTEX_SHADER(inout Vertex V);\n\n#ifndef CUSTOM_VERTEX_SHADER\nvoid COMMON_VERTEX_SHADER(inout Vertex V){}\n#endif\n\nvec3 VERTEX_DISPLACEMENT_SHADER();\n\nvec3 VERTEX_DISPLACEMENT_WRAPPER(Vertex V)\n{\n    Vertex real;\n    real.position = POSITION;\n    real.tangent = TANGENT;\n    real.bitangent = BITANGENT;\n\n    POSITION = V.position;\n    TANGENT = V.tangent;\n    BITANGENT = V.bitangent;\n\n    vec3 result = VERTEX_DISPLACEMENT_SHADER();\n\n    POSITION = real.position;\n    TANGENT = real.tangent;\n    BITANGENT = real.bitangent;\n\n    return result;\n}\n\n#ifndef CUSTOM_VERTEX_DISPLACEMENT\nvec3 VERTEX_DISPLACEMENT_SHADER(){ return vec3(0); }\n#endif\n\n#ifndef VERTEX_DISPLACEMENT_OFFSET\n    #define VERTEX_DISPLACEMENT_OFFSET 0.1\n#endif\n\n#ifndef CUSTOM_MAIN\nvoid main()\n{\n    DEFAULT_VERTEX_SHADER();\n\n    Vertex V;\n    V.position = POSITION;\n    V.normal = NORMAL;\n    V.tangent = TANGENT;\n    V.bitangent = BITANGENT;\n    V.uv = UV;\n    V.color = COLOR;\n    V.id = ID;\n    \n    COMMON_VERTEX_SHADER(V);\n\n    POSITION = V.position;\n    NORMAL = V.normal;\n    TANGENT = V.tangent;\n    BITANGENT = V.bitangent;\n    UV = V.uv;\n    COLOR = V.color;\n    ID = V.id;\n\n    #ifdef CUSTOM_VERTEX_DISPLACEMENT\n    {\n        vec3 displaced_position = POSITION + VERTEX_DISPLACEMENT_WRAPPER(V);\n        \n        if(!PRECOMPUTED_TANGENTS)\n        {\n            vec3 axis = vec3(0,0,1);\n            axis = abs(dot(axis, NORMAL)) < 0.99 ? axis : vec3(1,0,0);\n            vec3 tangent = normalize(cross(axis, NORMAL));\n            TANGENT = tangent;\n            BITANGENT = normalize(cross(NORMAL, tangent));\n        }\n        \n        Vertex v = V;\n\n        v.position = POSITION + TANGENT * Settings.Vertex_Displacement_Offset;\n        vec3 displaced_tangent = v.position + VERTEX_DISPLACEMENT_WRAPPER(v);\n        TANGENT = normalize(displaced_tangent - displaced_position);\n\n        v.position = POSITION + BITANGENT * Settings.Vertex_Displacement_Offset;\n        vec3 displaced_bitangent = v.position + VERTEX_DISPLACEMENT_WRAPPER(v);\n        BITANGENT = normalize(displaced_bitangent - displaced_position);\n        \n        POSITION = displaced_position;\n        NORMAL = normalize(cross(TANGENT, BITANGENT));\n        \n        if(!PRECOMPUTED_TANGENTS)\n        {\n            TANGENT = vec3(0);\n            BITANGENT = vec3(0);\n        }\n    }\n    #endif\n    \n    VERTEX_SETUP_OUTPUT();\n}\n#endif //NDEF CUSTOM_MAIN\n\n#endif //VERTEX_SHADER\n\n#ifdef PIXEL_SHADER\n\n#ifdef SHADOW_PASS\nlayout (location = 0) out uint OUT_ID;\nlayout (location = 1) out vec3 OUT_SHADOW_MULTIPLY_COLOR;\n#endif //PRE_PASS\n\n#ifdef PRE_PASS\nuniform sampler2D IN_OPAQUE_DEPTH;\nuniform sampler2D IN_TRANSPARENT_DEPTH;\nuniform usampler2D IN_LAST_ID;\n\nlayout (location = 0) out vec4 OUT_NORMAL_DEPTH;\nlayout (location = 1) out uvec4 OUT_ID;\n#endif //PRE_PASS\n\n#ifdef MAIN_PASS\nuniform sampler2D IN_NORMAL_DEPTH;\nuniform usampler2D IN_ID;\n#endif //MAIN_PASS\n\n#ifndef CUSTOM_MAIN\n\n#ifdef CUSTOM_PRE_PASS\nvoid PRE_PASS_PIXEL_SHADER(inout PrePassOutput PPO);\n#endif\n#ifdef CUSTOM_DEPTH_OFFSET\nvoid DEPTH_OFFSET(inout float depth_offset, inout bool offset_position);\n#endif\n#ifdef MAIN_PASS\nvoid MAIN_PASS_PIXEL_SHADER();\n#endif\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    PrePassOutput PPO;\n    PPO.normal = NORMAL;\n    PPO.id = ID;\n    PPO.opacity = 1;\n    PPO.transparent_shadowmap_color = vec3(0);\n\n    float depth = gl_FragCoord.z;\n    vec3 offset_position = POSITION;\n\n    // Discard pixel at the end of the shader, to avoid derivative glitches.\n    bool discard_pixel = false;\n    \n    #ifdef CUSTOM_PRE_PASS\n    {\n        PRE_PASS_PIXEL_SHADER(PPO);\n\n        if(PPO.opacity <= 0)\n        {\n            discard_pixel = true;\n        }\n        else if(!Settings.Transparency)\n        {\n            PPO.opacity = 1.0;\n        }\n    }\n    #endif\n\n    #ifdef CUSTOM_DEPTH_OFFSET\n    {\n        float depth_offset = 0;\n        bool offset_position = false;\n        DEPTH_OFFSET(depth_offset, offset_position);\n        \n        #ifdef SHADOW_PASS\n        {\n            if(!offset_position) depth_offset = 0;\n        }\n        #endif\n        \n        vec3 position = POSITION + view_direction() * depth_offset;\n\n        depth = project_point_to_screen_coordinates(PROJECTION * CAMERA, position).z;\n        gl_FragDepth = depth;\n\n        if(offset_position) POSITION = position;\n    }\n    #endif\n\n    #ifdef SHADOW_PASS\n    {\n        OUT_ID = PPO.id.r;\n\n        if(Settings.Transparency)\n        {\n            float pass_through = hash(vec2(ID.x, SAMPLE_COUNT)).x;\n            if(pass_through > PPO.opacity)\n            {\n                discard_pixel = true;\n            }\n            OUT_SHADOW_MULTIPLY_COLOR = PPO.transparent_shadowmap_color;\n        }\n    }\n    #endif\n    \n    #ifdef PRE_PASS\n    {\n        if(Settings.Transparency)\n        {\n            float opaque_depth = texelFetch(IN_OPAQUE_DEPTH, ivec2(gl_FragCoord.xy), 0).x;\n            float transparent_depth = texelFetch(IN_TRANSPARENT_DEPTH, ivec2(gl_FragCoord.xy), 0).x;\n            \n            if(depth >= opaque_depth || depth <= transparent_depth)\n            {\n                discard_pixel = true;\n            }\n\n            if(Settings.Transparency_Single_Layer)\n            {\n                if(PPO.id.r == texelFetch(IN_LAST_ID, ivec2(gl_FragCoord.xy), 0).x)\n                {\n                    discard_pixel = true;\n                }\n            }\n        }\n\n        OUT_NORMAL_DEPTH.xyz = PPO.normal;\n        OUT_NORMAL_DEPTH.w = depth;\n        OUT_ID = PPO.id;\n    }\n    #endif\n\n    #ifdef MAIN_PASS\n    {\n        NORMAL = texelFetch(IN_NORMAL_DEPTH, ivec2(gl_FragCoord.xy), 0).xyz;\n        ID = texelFetch(IN_ID, ivec2(gl_FragCoord.xy), 0);\n        MAIN_PASS_PIXEL_SHADER();\n    }\n    #endif\n\n    if(discard_pixel)\n    {\n        discard;\n    }\n}\n\n#endif //NDEF CUSTOM_MAIN\n\n#endif //PIXEL_SHADER\n\n#include \"NPR_Pipeline/NPR_Mesh.glsl\"\n#include \"NPR_Pipeline/NPR_Shading2.glsl\"\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Filters.glsl",
    "content": "#ifndef NPR_FILTERS_GLSL\n#define NPR_FILTERS_GLSL\n\n#include \"Filters/AO.glsl\"\n#include \"Filters/Bevel.glsl\"\n#include \"Filters/Curvature.glsl\"\n#include \"Filters/Line.glsl\"\n\n#if defined(PIXEL_SHADER) && (defined(MAIN_PASS) || defined(IS_SCREEN_SHADER))\n#define NPR_FILTERS_ACTIVE\n#endif\n\n/*  META GLOBAL\n    @meta: category=Shading;\n*/\n\n/*  META\n    @meta: label=Ambient Occlusion;\n    @samples: default=32; min=1;\n    @radius: default=1.0; min=0.0;\n    @distribution_exponent: default=5.0;\n    @contrast: subtype=Slider; default=0.1; min=0.0; max=1.0;\n    @bias: subtype=Slider; default=0.01; min=0.01; max=0.1;\n*/\nfloat ao(int samples, float radius, float distribution_exponent, float contrast, float bias)\n{\n    #ifdef NPR_FILTERS_ACTIVE\n    {\n        vec3 normal = NORMAL;\n        #ifdef IS_MESH_SHADER\n        {\n            normal = is_front_facing() ? IO_NORMAL : -IO_NORMAL;\n        }\n        #endif\n        float ao = ao(IN_NORMAL_DEPTH, 3, POSITION, normal, samples, radius, distribution_exponent, bias);\n        return pow(ao, map_range(contrast, 0.0, 1.0, 1.0, 10.0));\n    }\n    #else\n    {\n        return 1.0;\n    }\n    #endif\n}\n\n/*  META\n    @meta: subcategory=Curvature;\n*/\nfloat curvature()\n{\n    #ifdef NPR_FILTERS_ACTIVE\n    {\n        vec3 x = transform_normal(inverse(CAMERA), vec3(1,0,0));\n        vec3 y = transform_normal(inverse(CAMERA), vec3(0,1,0));\n        return curvature(IN_NORMAL_DEPTH, screen_uv(), 1.0, x, y);\n    }\n    #else\n    {\n        return 0.5;\n    }\n    #endif\n}\n\n/*  META\n    @meta: subcategory=Curvature;\n    @depth_range: default=0.1;\n*/\nfloat surface_curvature(float depth_range)\n{\n    #ifdef NPR_FILTERS_ACTIVE\n    {\n        vec3 x = transform_normal(inverse(CAMERA), vec3(1,0,0));\n        vec3 y = transform_normal(inverse(CAMERA), vec3(0,1,0));\n        return surface_curvature(IN_NORMAL_DEPTH, IN_NORMAL_DEPTH, 3, screen_uv(), 1.0, x, y, depth_range);\n    }\n    #else\n    {\n        return 0.5;\n    }\n    #endif\n}\n\n/*  META\n    @meta: category=Vector; subcategory=Bevel;\n    @samples: default=32; min=1;\n    @radius: default=0.02; min=0.0;\n    @distribution_exponent: default=2.0;\n*/\nvec3 soft_bevel(int samples, float radius, float distribution_exponent, bool only_self)\n{\n    #ifdef NPR_FILTERS_ACTIVE\n    {\n        uint id = texture(IN_ID, screen_uv())[0];\n        return bevel(\n            IN_NORMAL_DEPTH, IN_NORMAL_DEPTH, 3,\n            id, only_self, IN_ID, 0,\n            samples, radius, distribution_exponent,\n            false, 1);\n    }\n    #endif\n    return NORMAL;\n}\n\n/*  META\n    @meta: category=Vector; subcategory=Bevel;\n    @samples: default=32; min=1;\n    @radius: default=0.01; min=0.0;\n    @max_dot: default=0.75;\n*/\nvec3 hard_bevel(int samples, float radius, float max_dot, bool only_self)\n{\n    #ifdef NPR_FILTERS_ACTIVE\n    {\n        uint id = texture(IN_ID, screen_uv())[0];\n        return bevel(\n            IN_NORMAL_DEPTH, IN_NORMAL_DEPTH, 3,\n            id, only_self, IN_ID, 0,\n            samples, radius, 1.0,\n            true, max_dot);\n    }\n    #endif\n    return NORMAL;\n}\n\nvoid _fix_range(inout float value, inout float range)\n{\n    if(range < 0)\n    {\n        range = abs(range);\n        value -= range;\n    }\n}\n\n/*  META\n    @meta: label=Line Detection;\n    @is_id_boundary: label=Is ID Boundary;\n*/\nvoid line_detection_2(\n    out float delta_distance,\n    out float delta_angle,\n    out vec4 is_id_boundary\n)\n{\n    #ifdef NPR_FILTERS_ACTIVE\n    {\n        LineDetectionOutput result;\n\n        result = line_detection_2(\n            IN_NORMAL_DEPTH,\n            3,\n            IN_NORMAL_DEPTH,\n            IN_ID\n        );\n\n        delta_distance = result.delta_distance;\n        delta_angle = result.delta_angle;\n        is_id_boundary = vec4(result.id_boundary);\n    }\n    #endif\n}\n\n/*META @meta: internal=true;*/\nLineDetectionOutput line_detection()\n{\n    LineDetectionOutput result;\n\n    #ifdef NPR_FILTERS_ACTIVE\n    {\n        result = line_detection(\n            POSITION,\n            NORMAL, true_normal(),\n            1,\n            LINE_DEPTH_MODE_NEAR,\n            screen_uv(),\n            IN_NORMAL_DEPTH,\n            3,\n            IN_NORMAL_DEPTH,\n            IN_ID\n        );\n    }\n    #endif\n\n    return result;\n}\n\n\n/*  META\n    @meta: label=Line Width;\n    @width_scale: min=0.0; default=4.0;\n    @width_units: subtype=ENUM(Pixel,Screen,World);\n    @id_boundary_width: subtype=Slider; min=0.0; max=1.0; default=vec4(1.0);\n    @depth_width: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @depth_threshold: subtype=Slider; min=0.0; max=1.0; default=0.1;\n    @depth_threshold_range: subtype=Slider; min=0.0; max=1.0; default=0.0;\n    @normal_width: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @normal_threshold: subtype=Slider; min=0.0; max=1.0; default=0.5;\n    @normal_threshold_range: subtype=Slider; min=0.0; max=1.0; default=0.0;\n*/\nfloat line_width_2(\n    float width_scale, int width_units,\n    float depth_width, float depth_threshold, float depth_threshold_range,\n    float normal_width, float normal_threshold, float normal_threshold_range,\n    vec4 id_boundary_width\n)\n{\n    #ifdef NPR_FILTERS_ACTIVE\n    {\n        depth_threshold = pow(depth_threshold, 10) * 999 + 1;\n        if (depth_threshold_range > 0)\n            depth_threshold_range = pow(depth_threshold_range, 10) * 1000;\n\n        LineDetectionOutput lo = line_detection_2(\n            IN_NORMAL_DEPTH,\n            3,\n            IN_NORMAL_DEPTH,\n            IN_ID\n        );\n\n        float line = 0;\n\n        vec4 id = vec4(lo.id_boundary) * id_boundary_width;\n        \n        for(int i = 0; i < 4; i++)\n        {\n            line = max(line, id[i]);\n        }\n\n        _fix_range(depth_threshold, depth_threshold_range);\n\n        if(lo.delta_distance > depth_threshold)\n        {\n            float depth = depth_width;\n            if(depth_threshold_range != 0)\n            {\n                depth = map_range_clamped(\n                    lo.delta_distance, \n                    depth_threshold, depth_threshold + depth_threshold_range,\n                    0, depth_width\n                );\n            }\n\n            line = max(line, depth);\n        }\n\n        _fix_range(normal_threshold, normal_threshold_range);\n\n        normal_threshold = max(0.01, normal_threshold);\n\n        if(lo.delta_angle > normal_threshold)\n        {\n            float angle = normal_width;\n            if(normal_threshold_range != 0)\n            {\n                angle = map_range_clamped(\n                    lo.delta_angle, \n                    normal_threshold, normal_threshold + normal_threshold_range,\n                    0, normal_width\n                );\n            }\n\n            line = max(line, angle);\n        }\n\n        if(width_units == 1)//Screen %\n        {\n            width_scale *= length(vec2(RESOLUTION)) / 1000.0;\n        }\n        if(width_units == 2)//World\n        {\n            width_scale /= pixel_world_size() * 100.0;\n        }\n                \n        return line * width_scale;\n    }\n    #else\n    {\n        return 0.0;\n    }\n    #endif\n}\n\n/*  META\n    @meta: internal=true;\n    @line_width_scale: default=2.0;\n    @id_boundary_width: subtype=Data; default=vec4(1);\n    @depth_width: default=1.0;\n    @depth_threshold: default=0.5;\n    @normal_width: default=1.0;\n    @normal_threshold: default=0.5;\n*/\nfloat line_width(\n    float line_width_scale, vec4 id_boundary_width,\n    float depth_width, float depth_width_range, float depth_threshold, float depth_threshold_range,\n    float normal_width, float normal_width_range, float normal_threshold, float normal_threshold_range\n)\n{\n    #ifdef NPR_FILTERS_ACTIVE\n    {\n        LineDetectionOutput lo = line_detection();\n\n        float line = 0;\n\n        vec4 id = vec4(lo.id_boundary) * id_boundary_width;\n        \n        for(int i = 0; i < 4; i++)\n        {\n            line = max(line, id[i]);\n        }\n\n        _fix_range(depth_width, depth_width_range);\n        _fix_range(depth_threshold, depth_threshold_range);\n\n        if(lo.delta_distance > depth_threshold)\n        {\n            float depth = map_range_clamped(\n                lo.delta_distance, \n                depth_threshold, depth_threshold + depth_threshold_range,\n                depth_width, depth_width + depth_width_range\n            );\n\n            line = max(line, depth);\n        }\n\n        _fix_range(normal_width, normal_width_range);\n        _fix_range(normal_threshold, normal_threshold_range);\n\n        if(lo.delta_angle > normal_threshold)\n        {\n            float angle = map_range_clamped(\n                lo.delta_angle, \n                normal_threshold, normal_threshold + normal_threshold_range,\n                normal_width, normal_width + normal_width_range\n            );\n\n            line = max(line, angle);\n        }\n\n        return line * line_width_scale;\n    }\n    #else\n    {\n        return 0.0;\n    }\n    #endif\n}\n\n#endif //NPR_FILTERS_GLSL\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Lighting.glsl",
    "content": "#ifndef NPR_LIGHTING_GLSL\n#define NPR_LIGHTING_GLSL\n\n#include \"Lighting/Lighting.glsl\"\n\nuniform usampler2DArray SHADOWMAPS_ID_SPOT;\nuniform usampler2DArray SHADOWMAPS_ID_SUN;\nuniform usamplerCubeArray SHADOWMAPS_ID_POINT;\n\nuniform sampler2DArray TRANSPARENT_SHADOWMAPS_DEPTH_SPOT;\nuniform sampler2DArray TRANSPARENT_SHADOWMAPS_DEPTH_SUN;\nuniform samplerCubeArray TRANSPARENT_SHADOWMAPS_DEPTH_POINT;\n\nuniform usampler2DArray TRANSPARENT_SHADOWMAPS_ID_SPOT;\nuniform usampler2DArray TRANSPARENT_SHADOWMAPS_ID_SUN;\nuniform usamplerCubeArray TRANSPARENT_SHADOWMAPS_ID_POINT;\n\nuniform sampler2DArray TRANSPARENT_SHADOWMAPS_COLOR_SPOT;\nuniform sampler2DArray TRANSPARENT_SHADOWMAPS_COLOR_SUN;\nuniform samplerCubeArray TRANSPARENT_SHADOWMAPS_COLOR_POINT;\n\nlayout(std140) uniform LIGHT_GROUPS\n{\n    //Use ivec4 so the ints are tightly packed\n    ivec4 LIGHT_GROUP_INDEX[MAX_LIGHTS/4+1];\n};\n#define LIGHT_GROUP_INDEX(light_index) LIGHT_GROUP_INDEX[(light_index)/4][(light_index)%4]\n\nlayout(std140) uniform LIGHTS_CUSTOM_SHADING\n{\n    //Use ivec4 so the ints are tightly packed\n    ivec4 CUSTOM_SHADING_INDEX[MAX_LIGHTS/4+1];\n};\n#define CUSTOM_SHADING_INDEX(light_index) CUSTOM_SHADING_INDEX[(light_index)/4][(light_index)%4];\n\nuniform sampler2DArray IN_LIGHT_CUSTOM_SHADING;\n\n/*  META\n    @meta: internal=true;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @id: default=ID[0];\n*/\nLitSurface npr_lit_surface(vec3 position, vec3 normal, uint id, Light light, int light_index, bool shadows, bool self_shadows)\n{\n    LitSurface S = lit_surface(position, normal, light, false);\n\n    S.light_color = light.color * S.P;\n\n    int custom_shading_index = CUSTOM_SHADING_INDEX(light_index);\n    if(custom_shading_index >= 0)\n    {\n        S.light_color = texelFetch(IN_LIGHT_CUSTOM_SHADING, ivec3(screen_pixel(), custom_shading_index), 0).rgb;\n    }\n\n    if(!shadows)\n    {\n        return S;\n    }\n\n    if(light.type_index >= 0)\n    {\n        float bias = 1e-5;\n\n        //Opaque shadow\n        ShadowData shadow;\n        uint shadow_id;\n        //Transparent shadow\n        ShadowData t_shadow;\n        vec3 t_shadow_multiply;\n        uint t_shadow_id;\n        \n        if(light.type == LIGHT_SPOT)\n        {\n            shadow = spot_shadow(position, light, SHADOWMAPS_DEPTH_SPOT, bias);\n            vec2 uv = shadow.light_uv.xy;\n            int index = light.type_index;\n\n            shadow_id = texture(SHADOWMAPS_ID_SPOT, vec3(uv, index)).x;\n\n            t_shadow = spot_shadow(position, light, TRANSPARENT_SHADOWMAPS_DEPTH_SPOT, bias);\n\n            t_shadow_multiply = texture(TRANSPARENT_SHADOWMAPS_COLOR_SPOT, vec3(uv, index)).rgb;\n            t_shadow_id = texture(TRANSPARENT_SHADOWMAPS_ID_SPOT, vec3(uv, index)).x;\n        }\n        if(light.type == LIGHT_SUN)\n        {\n            bias = 1e-3;\n            \n            shadow = sun_shadow(position, light, SHADOWMAPS_DEPTH_SUN, bias, S.cascade);\n            vec2 uv = shadow.light_uv.xy;\n            int index = light.type_index * LIGHTS.cascades_count + S.cascade;\n\n            shadow_id = texture(SHADOWMAPS_ID_SUN, vec3(uv, index)).x;\n\n            t_shadow = sun_shadow(position, light, TRANSPARENT_SHADOWMAPS_DEPTH_SUN, bias, S.cascade);\n\n            t_shadow_multiply = texture(TRANSPARENT_SHADOWMAPS_COLOR_SUN, vec3(uv, index)).rgb;\n            t_shadow_id = texture(TRANSPARENT_SHADOWMAPS_ID_SUN, vec3(uv, index)).x;\n        }\n        if(light.type == LIGHT_POINT)\n        {\n            shadow = point_shadow(position, light, SHADOWMAPS_DEPTH_POINT, bias);\n            vec3 uv = shadow.light_uv;\n            int index = light.type_index;\n\n            shadow_id = texture(SHADOWMAPS_ID_POINT, vec4(uv, index)).x;\n\n            t_shadow = point_shadow(position, light, TRANSPARENT_SHADOWMAPS_DEPTH_POINT, bias);\n\n            t_shadow_multiply = texture(TRANSPARENT_SHADOWMAPS_COLOR_POINT, vec4(uv, index)).rgb;\n            t_shadow_id = texture(TRANSPARENT_SHADOWMAPS_ID_POINT, vec4(uv, index)).x;\n        }\n\n        S.shadow = shadow.shadow;\n\n        if(self_shadows == false && id == shadow_id)\n        {\n            S.shadow = false;\n        }\n        \n        S.shadow_multiply = S.shadow ? vec3(0) : vec3(1);\n\n        if(!S.shadow && t_shadow.shadow)\n        {\n            if(self_shadows == false && id == t_shadow_id)\n            {\n                t_shadow.shadow = false;\n            }\n\n            if(t_shadow.shadow)\n            {\n                S.shadow = true;\n                S.shadow_multiply = t_shadow_multiply;\n            }\n        }\n    }\n\n    return S;\n}\n\n#endif //NPR_LIGHTING_GLSL\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Mesh.glsl",
    "content": "#ifndef NPR_MESH_GLSL\n#define NPR_MESH_GLSL\n\n#include \"NPR_Pipeline/NPR_Filters.glsl\"\n#include \"NPR_Pipeline/NPR_Shading.glsl\"\n\n/*  META GLOBAL\n    @meta: internal=true;\n*/\n\n/* META @meta: subcategory=NPR Diffuse;*/\nvec3 diffuse_shading()\n{\n    vec3 result = vec3(0);\n    for(int i = 0; i < 4; i++)\n    {\n        result += diffuse_shading(POSITION, NORMAL, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow);\n    }\n    return result;\n}\n\n/* META @meta: subcategory=NPR Diffuse;*/\nvec3 diffuse_half_shading()\n{\n    vec3 result = vec3(0);\n    for(int i = 0; i < 4; i++)\n    {\n        result += diffuse_half_shading(POSITION, NORMAL, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow);\n    }\n    return result;\n}\n\n/* META @meta: subcategory=NPR Diffuse;*/\nvec3 diffuse_gradient_shading(sampler1D gradient_texture)\n{\n    vec3 result = vec3(0);\n    for(int i = 0; i < 4; i++)\n    {\n        result += diffuse_gradient_shading(POSITION, NORMAL, gradient_texture, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow);\n    }\n    return result;\n}\n\n/*  META\n    @meta: subcategory=NPR Specular;\n    @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1));\n    @anisotropy: default=0.5;\n    @roughness: default=0.5;\n*/\nvec3 specular_shading(float roughness)\n{\n    vec3 result = vec3(0);\n    for(int i = 0; i < 4; i++)\n    {\n        result += specular_shading(POSITION, NORMAL, roughness, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow);\n    }\n    return result;\n}\n\n/*  META\n    @meta: subcategory=NPR Specular;\n    @roughness: default=0.5;\n*/\nvec3 specular_gradient_shading(sampler1D gradient_texture, float roughness)\n{\n    vec3 result = vec3(0);\n    for(int i = 0; i < 4; i++)\n    {\n        result += specular_gradient_shading(POSITION, NORMAL, roughness, gradient_texture, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow);\n    }\n    return result;\n}\n\n/*  META\n    @meta: subcategory=NPR Specular;\n    @roughness: default=0.5;\n    @anisotropy: default=0.5;\n    @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1));\n*/\nvec3 specular_anisotropic_shading(float roughness, float anisotropy, vec3 tangent)\n{\n    vec3 result = vec3(0);\n    for(int i = 0; i < 4; i++)\n    {\n        result += specular_anisotropic_shading(POSITION, NORMAL, tangent, anisotropy, roughness, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow);\n    }\n    return result;\n}\n\n/*  META\n    @meta: subcategory=NPR Specular;\n    @roughness: default=0.5;\n    @anisotropy: default=0.5;\n    @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1));\n*/\nvec3 specular_anisotropic_gradient_shading(sampler1D gradient_texture, float roughness, float anisotropy, vec3 tangent)\n{\n    vec3 result = vec3(0);\n    for(int i = 0; i < 4; i++)\n    {\n        result += specular_anisotropic_gradient_shading(POSITION, NORMAL, tangent, anisotropy, roughness, gradient_texture, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow);\n    }\n    return result;\n}\n\n/* META @meta: label=NPR Toon Shading; */\nvec3 toon_shading(float size, float gradient_size, float specularity, float offset)\n{\n    vec3 result = vec3(0);\n    for(int i = 0; i < 4; i++)\n    {\n        result += toon_shading(POSITION, NORMAL, size, gradient_size, specularity, offset, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow);\n    }\n    return result;\n}\n\n\nbool is_shadow_pass()\n{\n    #ifdef SHADOW_PASS\n    {\n        return true;\n    }\n    #else\n    {\n        return false;\n    }\n    #endif\n}\n\nbool is_pre_pass()\n{\n    #ifdef PRE_PASS\n    {\n        return true;\n    }\n    #else\n    {\n        return false;\n    }\n    #endif\n}\n\nbool is_main_pass()\n{\n    #ifdef MAIN_PASS\n    {\n        return true;\n    }\n    #else\n    {\n        return false;\n    }\n    #endif\n}\n\n/*META @meta: category=Input; internal=false;*/\nvoid pass_info(\n    out bool Is_Main_Pass,\n    out bool Is_Pre_Pass,\n    out bool Is_Shadow_Pass\n)\n{\n    Is_Main_Pass = is_main_pass();\n    Is_Pre_Pass = is_pre_pass();\n    Is_Shadow_Pass = is_shadow_pass();\n}\n\nuint _pack_id_channel(vec4 color)\n{\n    uint max_uint16 = 65535;\n    uint p = packUnorm4x8(color);\n    if(p <= max_uint16)\n    {\n        //Since it's already in the 16 bit range, assume the custom ID has not been overriden\n        //For actual colors, they will only fit if alpha < 0.002\n        return p;\n    }\n    //Otherwise, generate a pcg4d hash and scale it to the 16 bit range\n    uvec4 h = _pcg4d(floatBitsToUint(color));\n    return h.x / max_uint16;\n}\n\n/*  META\n    @meta: label=Pack ID; category=Node Tree; internal=false;\n    @object_id: label=Object ID; default=unpackUnorm4x8(ID.x);\n    @custom_id_a: label=Custom ID A; default=unpackUnorm4x8(ID.y);\n    @custom_id_b: label=Custom ID B; default=unpackUnorm4x8(ID.z);\n    @custom_id_c: label=Custom ID C; default=unpackUnorm4x8(ID.w);\n    @id: label=ID;\n*/\nvoid pack_id(\n    vec4 object_id,\n    vec4 custom_id_a,\n    vec4 custom_id_b,\n    vec4 custom_id_c,\n    out uvec4 id\n)\n{\n    id.x = _pack_id_channel(object_id);\n    id.y = _pack_id_channel(custom_id_a);\n    id.z = _pack_id_channel(custom_id_b);\n    id.w = _pack_id_channel(custom_id_c);\n}\n\n#endif //NPR_MESH_GLSL\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Shading.glsl",
    "content": "#ifndef NPR_SHADING_GLSL\n#define NPR_SHADING_GLSL\n\n#include \"NPR_Lighting.glsl\"\n#include \"Shading/ShadingModels.glsl\"\n\n/*  META GLOBAL\n    @meta: internal=true;\n*/\n\n#define _LIT_SCENE_MACRO(callback, light_group, shadows, self_shadows)\\\n    vec3 result = vec3(0,0,0);\\\n    for (int i = 0; i < LIGHTS.lights_count; i++)\\\n    {\\\n        if(LIGHT_GROUP_INDEX(i) != light_group) continue;\\\n        Light L = LIGHTS.lights[i];\\\n        LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadows, self_shadows);\\\n        result += (callback);\\\n    }\\\n    return result;\\\n\n/*  META\n    @meta: subcategory=Diffuse;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @light_group: default=1;\n    @shadows: default=true;\n    @self_shadows: default=true;\n*/\nvec3 diffuse_shading(vec3 position, vec3 normal, int light_group, bool shadows, bool self_shadows) \n{ \n    _LIT_SCENE_MACRO(diffuse_lit_surface(LS), light_group, shadows, self_shadows);\n}\n/*  META\n    @meta: subcategory=Diffuse;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @light_group: default=1;\n    @shadows: default=true;\n    @self_shadows: default=true;\n*/\nvec3 diffuse_half_shading(vec3 position, vec3 normal, int light_group, bool shadows, bool self_shadows) \n{ \n    _LIT_SCENE_MACRO(diffuse_half_lit_surface(LS), light_group, shadows, self_shadows);\n}\n/*  META\n    @meta: subcategory=Diffuse;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @light_group: default=1;\n    @shadows: default=true;\n    @self_shadows: default=true;\n*/\nvec3 diffuse_gradient_shading(vec3 position, vec3 normal, sampler1D gradient, int light_group, bool shadows, bool self_shadows)\n{\n    _LIT_SCENE_MACRO(diffuse_gradient_lit_surface(LS, gradient), light_group, shadows, self_shadows);\n}\n/*  META\n    @meta: subcategory=Specular;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @roughness: default=0.5;\n    @light_group: default=1;\n    @shadows: default=true;\n    @self_shadows: default=true;\n*/\nvec3 specular_shading(vec3 position, vec3 normal, float roughness, int light_group, bool shadows, bool self_shadows) \n{\n    _LIT_SCENE_MACRO(specular_lit_surface(LS, roughness), light_group, shadows, self_shadows);\n}\n/*  META\n    @meta: subcategory=Specular;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @roughness: default=0.5;\n    @light_group: default=1;\n    @shadows: default=true;\n    @self_shadows: default=true;\n*/\nvec3 specular_gradient_shading(vec3 position, vec3 normal, float roughness, sampler1D gradient, int light_group, bool shadows, bool self_shadows)\n{\n    _LIT_SCENE_MACRO(specular_gradient_lit_surface(LS, roughness, gradient), light_group, shadows, self_shadows);\n}\n/*  META\n    @meta: subcategory=Specular;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1));\n    @anisotropy: default=0.5;\n    @roughness: default=0.5;\n    @light_group: default=1;\n    @shadows: default=true;\n    @self_shadows: default=true;\n*/\nvec3 specular_anisotropic_shading(vec3 position, vec3 normal, vec3 tangent, float anisotropy, float roughness, int light_group, bool shadows, bool self_shadows)\n{\n    _LIT_SCENE_MACRO(specular_anisotropic_lit_surface(LS, tangent, anisotropy, roughness), light_group, shadows, self_shadows);\n}\n/*  META\n    @meta: subcategory=Specular;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1));\n    @anisotropy: default=0.5;\n    @roughness: default=0.5;\n    @light_group: default=1;\n    @shadows: default=true;\n    @self_shadows: default=true;\n*/\nvec3 specular_anisotropic_gradient_shading(vec3 position, vec3 normal, vec3 tangent, float anisotropy, float roughness, sampler1D gradient, int light_group, bool shadows, bool self_shadows)\n{\n    _LIT_SCENE_MACRO(specular_anisotropic_gradient_lit_surface(LS, tangent, anisotropy, roughness, gradient), light_group, shadows, self_shadows);\n}\n/*  META\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @size: default=0.49;\n    @light_group: default=1;\n    @shadows: default=true;\n    @self_shadows: default=true;\n*/\nvec3 toon_shading(vec3 position, vec3 normal, float size, float gradient_size, float specularity, float offset, int light_group, bool shadows, bool self_shadows)\n{\n    _LIT_SCENE_MACRO(toon_lit_surface(LS, size, gradient_size, specularity, offset), light_group, shadows, self_shadows);\n}\n\n#endif //NPR_SHADING_GLSL\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Shading2.glsl",
    "content": "#ifndef NPR_SHADING2_GLSL\n#define NPR_SHADING2_GLSL\n\n#include \"NPR_Lighting.glsl\"\n#include \"Shading/ShadingModels.glsl\"\n\n/*  META GLOBAL\n    @meta: category=Shading;\n*/\n\nvoid _shadow_params(int enum_value, out bool shadows, out bool self_shadows)\n{\n    #ifdef IS_MESH_SHADER\n    {\n        shadows = enum_value == 0 ? Settings.Receive_Shadow : enum_value < 3;\n        self_shadows = enum_value == 0 ? Settings.Self_Shadow : enum_value == 1;\n    }\n    #else\n    {\n        shadows = enum_value < 2;\n        self_shadows = enum_value == 0;\n    }\n    #endif\n}\n\n#ifdef IS_MESH_SHADER\n/*  META\n    @meta: label=Color Ramp; subcategory=NPR Diffuse;\n    @base_color: default=vec3(0);\n    @color: default=vec3(1);\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0;\n    @light_groups: default=MATERIAL_LIGHT_GROUPS;\n    @shadow_mode: subtype=ENUM(Multiply,Remap); default=0;\n    @shadows: subtype=ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0;\n*/\n#else\n/*  META\n    @meta: label=Color Ramp; subcategory=NPR Diffuse;\n    @base_color: default=vec3(0);\n    @color: default=vec3(1);\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0;\n    @light_groups: default=ivec4(1,0,0,0);\n    @shadow_mode: subtype=ENUM(Multiply,Remap); default=0;\n    @shadows: subtype=ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0;\n*/\n#endif\nvec3 NPR_diffuse_shading(\n    vec3 base_color,\n    vec3 color,\n    sampler1D gradient,\n    float offset,\n    bool full_range,\n    bool max_contribution,\n    //int shadow_mode,\n    int shadows,\n    ivec4 light_groups,\n    vec3 position,\n    vec3 normal\n)\n{\n    bool shadow;\n    bool self_shadow;\n    _shadow_params(shadows, shadow, self_shadow);\n\n    if(full_range)\n    {\n        self_shadow = false;\n    }\n    \n    vec3 result = vec3(0,0,0);\n    for (int i = 0; i < LIGHTS.lights_count; i++)\n    {\n        for(int group_index = 0; group_index < 4; group_index++)\n        {\n            if(LIGHT_GROUP_INDEX(i) != light_groups[group_index])\n            {\n                continue;\n            }\n            Light L = LIGHTS.lights[i];\n            LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadow, self_shadow);\n\n            if(!full_range && LS.NoL < 0)\n            {\n                continue;\n            }\n            \n            float lambert = LS.NoL;\n            if(full_range)\n            {\n                lambert = map_range(LS.NoL, -1, 1, 0, 1);\n            }\n            lambert = saturate(lambert + offset);\n\n            vec4 gradient_sample = texture(gradient, lambert);\n            gradient_sample *= gradient_sample.a;\n            vec3 diffuse = gradient_sample.rgb * LS.light_color * LS.shadow_multiply;\n            /*\n            vec3 diffuse;\n            vec3 shadow_multiply = LS.shadow_multiply;\n            if(shadow_mode == 0)//Multiply\n            {\n                diffuse = texture(gradient, lambert).rgb * shadow_multiply * LS.light_color;\n            }\n            else if(shadow_mode == 1)//Remap\n            {                \n                if(full_range)\n                {\n                    shadow_multiply = map_range_clamped(LS.shadow_multiply, vec3(0), vec3(1), vec3(0.5), vec3(1));\n                }\n                diffuse = rgb_gradient(gradient, min(vec3(lambert), shadow_multiply)) * LS.light_color;\n            }\n            */\n            if(max_contribution)\n            {\n                result = max(result, diffuse);\n            }\n            else\n            {\n                result += diffuse;\n            }\n        }\n    }\n    \n    return mix(base_color, color, result);\n}\n\nvec3 _map_gradient(float size, float gradient_size, vec3 uvw)\n{\n    if(gradient_size > 0.0)\n    {\n        return map_range_clamped(uvw, vec3(1.0 - size), vec3(1.0 - size + gradient_size), vec3(0.0), vec3(1.0));\n    }\n    else\n    {\n        return vec3(greaterThan(uvw, vec3(1.0 - size)));\n    }\n}\n\n#ifdef IS_MESH_SHADER\n/*  META\n    @meta: label=Color Layer; subcategory=NPR Diffuse;\n    @base_color: default=vec3(0);\n    @color: default=vec3(1);\n    @size: subtype=Slider; min=-0.0; max=1.0; default=1.0;\n    @gradient_size: subtype=Slider; min=0.0; max=1.0; default=0.1;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0;\n    @light_groups: default=MATERIAL_LIGHT_GROUPS;\n    @shadow_mode: subtype=ENUM(Multiply,Remap); default=0;\n    @shadows: subtype=ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0;\n*/\n#else\n/*  META\n    @meta: label=Color Layer; subcategory=NPR Diffuse;\n    @base_color: default=vec3(0);\n    @color: default=vec3(1);\n    @size: subtype=Slider; min=-0.0; max=1.0; default=1.0;\n    @gradient_size: subtype=Slider; min=0.0; max=1.0; default=0.1;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0;\n    @light_groups: default=ivec4(1,0,0,0);\n    @shadow_mode: subtype=ENUM(Multiply,Remap); default=0;\n    @shadows: subtype=ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0;\n*/\n#endif\nvec3 NPR_diffuse_layer(\n    vec3 base_color,\n    vec3 color,\n    float size,\n    float gradient_size,\n    float offset,\n    bool full_range,\n    //int shadow_mode,\n    bool max_contribution,\n    int shadows,\n    ivec4 light_groups,\n    vec3 position,\n    vec3 normal\n)\n{\n    bool shadow;\n    bool self_shadow;\n    _shadow_params(shadows, shadow, self_shadow);\n    if(full_range)\n    {\n        self_shadow = false;\n    }\n\n    size = saturate(size + offset);\n    gradient_size = min(size, gradient_size);\n    \n    vec3 result = vec3(0,0,0);\n    for (int i = 0; i < LIGHTS.lights_count; i++)\n    {\n        for(int group_index = 0; group_index < 4; group_index++)\n        {\n            if(LIGHT_GROUP_INDEX(i) != light_groups[group_index])\n            {\n                continue;\n            }\n            Light L = LIGHTS.lights[i];\n            LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadow, self_shadow);\n\n            if(!full_range && LS.NoL < 0)\n            {\n                continue;\n            }\n            \n            float lambert = LS.NoL;\n            if(full_range)\n            {\n                lambert = map_range(LS.NoL, -1.0, 1.0, 0.0, 1.0);\n            }\n\n            vec3 diffuse = _map_gradient(size, gradient_size, vec3(lambert)) * LS.light_color * LS.shadow_multiply;\n            /*\n            vec3 diffuse;\n            vec3 shadow_multiply = LS.shadow_multiply;\n            if(shadow_mode == 0)//Multiply\n            {\n                diffuse = _map_gradient(size, gradient_size, vec3(lambert)) * shadow_multiply * LS.light_color;\n            }\n            else if(shadow_mode == 1)//Remap\n            {                \n                if(full_range)\n                {\n                    shadow_multiply = map_range_clamped(LS.shadow_multiply, vec3(0), vec3(1), vec3(0.5), vec3(1));\n                }\n                diffuse = _map_gradient(size, gradient_size, min(vec3(lambert), shadow_multiply)) * LS.light_color;\n            }\n            */\n            if(max_contribution)\n            {\n                result = max(result, diffuse);\n            }\n            else\n            {\n                result += diffuse;\n            }\n        }\n    }\n    \n    return mix(base_color, color, result);\n}\n\n#ifdef IS_MESH_SHADER\n/*  META\n    @meta: label=Color Ramp; subcategory=NPR Specular;\n    @base_color: default=vec3(0);\n    @color: default=vec3(1);\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1));\n    @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0;\n    @anisotropy: subtype=Slider; min=0.0; max=1.0; default=0.5;\n    @roughness: subtype=Slider; min=0.0; max=1.0; default=0.5;\n    @light_groups: default=MATERIAL_LIGHT_GROUPS;\n    @shadows: subtype=ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0;\n*/\n#else\n/*  META\n    @meta: label=Color Ramp; subcategory=NPR Specular;\n    @base_color: default=vec3(0);\n    @color: default=vec3(1);\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1));\n    @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0;\n    @anisotropy: subtype=Slider; min=0.0; max=1.0; default=0.5;\n    @roughness: subtype=Slider; min=0.0; max=1.0; default=0.5;\n    @light_groups: default=ivec4(1,0,0,0);\n    @shadows: subtype=ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0;\n*/\n#endif\nvec3 NPR_specular_shading(\n    vec3 base_color,\n    vec3 color,\n    sampler1D gradient,\n    float offset,\n    float roughness,\n    float anisotropy,\n    bool max_contribution,\n    int shadows,\n    ivec4 light_groups,\n    vec3 position,\n    vec3 normal,\n    vec3 tangent\n)\n{\n    bool shadow;\n    bool self_shadow;\n    _shadow_params(shadows, shadow, self_shadow);\n    \n    vec3 result = vec3(0,0,0);\n    for (int i = 0; i < LIGHTS.lights_count; i++)\n    {\n        for(int group_index = 0; group_index < 4; group_index++)\n        {\n            if(LIGHT_GROUP_INDEX(i) != light_groups[group_index])\n            {\n                continue;\n            }\n            Light L = LIGHTS.lights[i];\n            LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadow, self_shadow);\n\n            if(LS.NoL < 0)\n            {\n                continue;\n            }\n\n            float NoH = dot(normal, LS.H);\n            \n            vec3 bitangent = normalize(cross(normal, tangent));\n            float XoH = dot(LS.H, tangent);\n            float YoH = dot(LS.H, bitangent);\n\n            vec2 a = vec2(anisotropy, 1.0 - anisotropy) * roughness;\n\n            float custom_ggx = (1.0 / (pow((XoH*XoH) / (a.x*a.x) + (YoH*YoH) / (a.y*a.y) + NoH*NoH, 3.0)));\n            \n            //minimal geometric shadowing\n            custom_ggx *= 1.0 - pow(1.0 - LS.NoL, 5);\n            \n            custom_ggx = saturate(custom_ggx + offset);\n\n            vec4 gradient_sample = texture(gradient, custom_ggx);\n            gradient_sample *= gradient_sample.a;\n            vec3 specular = gradient_sample.rgb * LS.light_color * LS.shadow_multiply;\n            \n            if(max_contribution)\n            {\n                result = max(result, specular);\n            }\n            else\n            {\n                result += specular;\n            }\n        }\n    }\n    \n    return mix(base_color, color, result);\n}\n\n#ifdef IS_MESH_SHADER\n/*  META\n    @meta: label=Color Layer; subcategory=NPR Specular;\n    @base_color: default=vec3(0);\n    @color: default=vec3(1);\n    @size: subtype=Slider; min=-0.0; max=1.0; default=1.0;\n    @gradient_size: subtype=Slider; min=0.0; max=1.0; default=0.1;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1));\n    @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0;\n    @anisotropy: subtype=Slider; min=0.0; max=1.0; default=0.5;\n    @roughness: subtype=Slider; min=0.0; max=1.0; default=0.5;\n    @light_groups: default=MATERIAL_LIGHT_GROUPS;\n    @shadows: subtype=ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0;\n*/\n#else\n/*  META\n    @meta: label=Color Layer; subcategory=NPR Specular;\n    @base_color: default=vec3(0);\n    @color: default=vec3(1);\n    @size: subtype=Slider; min=-0.0; max=1.0; default=1.0;\n    @gradient_size: subtype=Slider; min=0.0; max=1.0; default=0.1;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1));\n    @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0;\n    @anisotropy: subtype=Slider; min=0.0; max=1.0; default=0.5;\n    @roughness: subtype=Slider; min=0.0; max=1.0; default=0.5;\n    @light_groups: default=ivec4(1,0,0,0);\n    @shadows: subtype=ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0;\n*/\n#endif\nvec3 NPR_specular_layer(\n    vec3 base_color,\n    vec3 color,\n    float size,\n    float gradient_size,\n    float offset,\n    float roughness,\n    float anisotropy,\n    bool max_contribution,\n    int shadows,\n    ivec4 light_groups,\n    vec3 position,\n    vec3 normal,\n    vec3 tangent\n)\n{\n    bool shadow;\n    bool self_shadow;\n    _shadow_params(shadows, shadow, self_shadow);\n\n    size = saturate(size + offset) * 0.99;\n    gradient_size = min(size, gradient_size);\n    \n    vec3 result = vec3(0,0,0);\n    for (int i = 0; i < LIGHTS.lights_count; i++)\n    {\n        for(int group_index = 0; group_index < 4; group_index++)\n        {\n            if(LIGHT_GROUP_INDEX(i) != light_groups[group_index])\n            {\n                continue;\n            }\n            Light L = LIGHTS.lights[i];\n            LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadow, self_shadow);\n\n            if(LS.NoL < 0)\n            {\n                continue;\n            }\n\n            float NoH = dot(normal, LS.H);\n            \n            vec3 bitangent = normalize(cross(normal, tangent));\n            float XoH = dot(LS.H, tangent);\n            float YoH = dot(LS.H, bitangent);\n\n            vec2 a = vec2(anisotropy, 1.0 - anisotropy) * roughness;\n\n            float custom_ggx = (1.0 / (pow((XoH*XoH) / (a.x*a.x) + (YoH*YoH) / (a.y*a.y) + NoH*NoH, 3.0)));\n            \n            //minimal geometric shadowing\n            custom_ggx *= 1.0 - pow(1.0 - LS.NoL, 5);\n\n            vec3 specular = _map_gradient(size, gradient_size, vec3(custom_ggx)) * LS.light_color * LS.shadow_multiply;\n            \n            if(max_contribution)\n            {\n                result = max(result, specular);\n            }\n            else\n            {\n                result += specular;\n            }\n        }\n    }\n    \n    return mix(base_color, color, result);\n}\n\n#endif //NPR_SHADING2_GLSL\n"
  },
  {
    "path": "Malt/Pipelines/NPR_Pipeline/Shaders/NPR_ScreenShader.glsl",
    "content": "#define NO_UV_INPUT\n#define NO_VERTEX_COLOR_INPUT\n#define NO_MODEL_INPUT\n\n#include \"NPR_Intellisense.glsl\"\n\n#ifdef PIXEL_SHADER\nvec3 DEFERRED_TRUE_NORMAL;\n#define CUSTOM_TRUE_NORMAL DEFERRED_TRUE_NORMAL\n\nfloat DEFERRED_PIXEL_DEPTH;\n#define CUSTOM_PIXEL_DEPTH DEFERRED_PIXEL_DEPTH\n#endif\n\n#include \"Common.glsl\"\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\nuniform sampler2D IN_NORMAL_DEPTH;\nuniform usampler2D IN_ID;\n\nuniform bool RENDER_LAYER_MODE = false;\nuniform bool DEFERRED_MODE = false;\n\nvoid SCREEN_SHADER();\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n    \n    DEFERRED_TRUE_NORMAL = reconstruct_normal(IN_NORMAL_DEPTH, 3, screen_pixel());\n    DEFERRED_PIXEL_DEPTH = texelFetch(IN_NORMAL_DEPTH, screen_pixel(), 0).w;\n\n    if(RENDER_LAYER_MODE)\n    {\n        vec4 normal_depth =  texture(IN_NORMAL_DEPTH, UV[0]);\n        if(DEFERRED_MODE && normal_depth.w == 1.0)\n        {\n            discard;\n        }\n        POSITION = screen_to_camera(UV[0], normal_depth.w);\n        POSITION = transform_point(inverse(CAMERA), POSITION);\n        NORMAL = normal_depth.xyz;\n        ID = texture(IN_ID, UV[0]);\n    }\n\n    SCREEN_SHADER();\n}\n#endif\n\n#include \"NPR_Pipeline/NPR_Filters.glsl\"\n#include \"NPR_Pipeline/NPR_Shading.glsl\"\n#include \"NPR_Pipeline/NPR_Shading2.glsl\"\n"
  },
  {
    "path": "Malt/Render/Common.py",
    "content": "import ctypes\n\nfrom Malt.GL.Shader import UBO\n\nclass C_CommonBuffer(ctypes.Structure):\n    _fields_ = [\n        ('CAMERA', ctypes.c_float*16),\n        ('PROJECTION', ctypes.c_float*16),\n        ('RESOLUTION', ctypes.c_int*2),\n        ('SAMPLE_OFFSET', ctypes.c_float*2),\n        ('SAMPLE_COUNT', ctypes.c_int),\n        ('FRAME', ctypes.c_int),\n        ('TIME', ctypes.c_float),\n        ('__padding', ctypes.c_int),\n    ]\n\ndef bake_sample_offset(projection_matrix, sample_offset, resolution):\n    offset_x = sample_offset[0] / resolution[0]\n    offset_y = sample_offset[1] / resolution[1]\n    if projection_matrix[-1] == 1.0:\n        #Orthographic camera\n        projection_matrix[12] += offset_x\n        projection_matrix[13] += offset_y\n    else:\n        #Perspective camera\n        projection_matrix[8] += offset_x\n        projection_matrix[9] += offset_y\n\nclass CommonBuffer():\n    \n    def __init__(self):\n        self.data = C_CommonBuffer()\n        self.UBO = UBO()\n    \n    def load(self, scene, resolution, sample_offset=(0,0), sample_count=0, camera=None, projection = None):\n        self.data.CAMERA = tuple(camera if camera else scene.camera.camera_matrix)\n        self.data.PROJECTION = tuple(projection if projection else scene.camera.projection_matrix)\n        self.data.RESOLUTION = resolution\n        self.data.SAMPLE_OFFSET = sample_offset\n        self.data.SAMPLE_COUNT = sample_count\n        self.data.FRAME = scene.frame\n        self.data.TIME = scene.time\n        \n        bake_sample_offset(self.data.PROJECTION, sample_offset, resolution)\n\n        self.UBO.load_data(self.data)\n    \n    def bind(self, block):\n        self.UBO.bind(block)\n    \n    def shader_callback(self, shader):\n        if 'COMMON_UNIFORMS' in shader.uniform_blocks:\n            self.bind(shader.uniform_blocks['COMMON_UNIFORMS'])\n"
  },
  {
    "path": "Malt/Render/DepthToCompositeDepth.py",
    "content": "from Malt.GL.GL import *\nfrom Malt.GL.Texture import Texture\nfrom Malt.GL.RenderTarget import RenderTarget\n\n_shader_src='''\n#include \"Passes/DepthToBlenderDepth.glsl\"\n'''\n\n_SHADER = None\n\nclass CompositeDepth():\n    \n    def __init__(self):\n        self.t = None\n        self.fbo = None\n\n        self.shader = None\n    \n    def render(self, pipeline, common_buffer, depth_texture, depth_channel=0):\n        if self.t is None or self.t.resolution != depth_texture.resolution:\n            self.t = Texture(depth_texture.resolution, GL_R32F)\n            self.fbo = RenderTarget([self.t])\n        \n        if self.shader == None:\n            global _SHADER\n            if _SHADER is None: _SHADER = pipeline.compile_shader_from_source(_shader_src)\n            self.shader = _SHADER\n        \n        self.shader.textures['DEPTH_TEXTURE'] = depth_texture\n        self.shader.uniforms['DEPTH_CHANNEL'].set_value(depth_channel)\n        common_buffer.shader_callback(self.shader)\n        pipeline.draw_screen_pass(self.shader, self.fbo)\n        return self.t\n"
  },
  {
    "path": "Malt/Render/Lighting.py",
    "content": "import math\nimport ctypes\n\nimport pyrr\n\nfrom Malt.GL.GL import *\nfrom Malt.GL.Shader import UBO\nfrom Malt.GL.Texture import TextureArray, CubeMapArray\nfrom Malt.GL.RenderTarget import ArrayLayerTarget, RenderTarget\n\nfrom Malt import Pipeline\n\nfrom Malt.Render.Common import bake_sample_offset\n\n_LIGHTS_BUFFER = None\n\ndef get_lights_buffer():\n    global _LIGHTS_BUFFER\n    if _LIGHTS_BUFFER is None: _LIGHTS_BUFFER = LightsBuffer()\n    return _LIGHTS_BUFFER\n\n_SHADOWMAPS = None\n\ndef get_shadow_maps():\n    global _SHADOWMAPS\n    if _SHADOWMAPS is None: _SHADOWMAPS = ShadowMaps()\n    return _SHADOWMAPS\n\nLIGHT_SUN = 1\nLIGHT_POINT = 2\nLIGHT_SPOT = 3\n\nclass C_Light(ctypes.Structure):\n    _fields_ = [\n        ('color', ctypes.c_float*3),\n        ('type', ctypes.c_int32),\n        ('position', ctypes.c_float*3),\n        ('radius', ctypes.c_float),\n        ('direction', ctypes.c_float*3),\n        ('spot_angle', ctypes.c_float),\n        ('spot_blend', ctypes.c_float),\n        ('type_index', ctypes.c_int32),\n        ('__padding', ctypes.c_int32*2),\n    ]\n\nMAX_SPOTS = 64\nMAX_SUNS = 64\nMAX_POINTS = 64\n\nMAX_LIGHTS = 128\n\nclass C_LightsBuffer(ctypes.Structure):\n    \n    _fields_ = [\n        ('lights', C_Light*MAX_LIGHTS),\n        ('lights_count', ctypes.c_int),\n        ('cascades_count', ctypes.c_int),\n        ('__padding', ctypes.c_int32*2),\n        ('spot_matrices', ctypes.c_float*16*MAX_SPOTS),\n        ('sun_matrices', ctypes.c_float*16*MAX_SUNS),\n        ('point_matrices', ctypes.c_float*16*MAX_POINTS),\n    ]\n\nclass ShadowMaps():\n\n    def __init__(self):\n        self.max_spots = 1\n        self.spot_resolution = 2048\n\n        self.spot_depth_t = None\n        self.spot_fbos = []\n\n        self.max_suns = 1\n        self.sun_resolution = 2048\n        \n        self.sun_depth_t = None\n        self.sun_fbos = []\n\n        self.max_points = 1\n        self.point_resolution = 512\n\n        self.point_depth_t = None\n        self.point_fbos = []\n\n        self.initialized = False\n\n    def load(self, scene, spot_resolution, sun_resolution, point_resolution, sun_cascades):\n        needs_setup = self.initialized is False\n        self.initialized = True\n        \n        new_settings = (spot_resolution, sun_resolution, point_resolution)\n        current_settings = (self.spot_resolution, self.sun_resolution, self.point_resolution)\n        if new_settings != current_settings:\n            self.spot_resolution = spot_resolution\n            self.sun_resolution = sun_resolution\n            self.point_resolution = point_resolution\n            needs_setup = True\n        \n        spot_count = len([l for l in scene.lights if l.type == LIGHT_SPOT])\n        if spot_count > self.max_spots:\n            self.max_spots = spot_count\n            needs_setup = True\n        \n        sun_count = len([l for l in scene.lights if l.type == LIGHT_SUN])\n        sun_count  = sun_count * sun_cascades\n        if sun_count > self.max_suns:\n            self.max_suns = sun_count\n            needs_setup = True \n\n        point_count = len([l for l in scene.lights if l.type == LIGHT_POINT])\n        if point_count > self.max_points:\n            self.max_points = point_count\n            needs_setup = True\n        \n        if needs_setup:\n            self.setup()\n        \n        self.clear(spot_count, sun_count, point_count)\n    \n    def setup(self, create_fbos=True):\n        self.spot_depth_t = TextureArray((self.spot_resolution, self.spot_resolution), self.max_spots, GL_DEPTH_COMPONENT32F)\n        self.sun_depth_t = TextureArray((self.sun_resolution, self.sun_resolution), self.max_suns, GL_DEPTH_COMPONENT32F)\n        self.point_depth_t = CubeMapArray((self.point_resolution, self.point_resolution), self.max_points, GL_DEPTH_COMPONENT32F)\n\n        if create_fbos:\n            self.spot_fbos = []\n            for i in range(self.spot_depth_t.length):\n                self.spot_fbos.append(RenderTarget([], ArrayLayerTarget(self.spot_depth_t, i)))\n            \n            self.sun_fbos = []\n            for i in range(self.sun_depth_t.length):\n                self.sun_fbos.append(RenderTarget([], ArrayLayerTarget(self.sun_depth_t, i)))\n                    \n            self.point_fbos = []\n            for i in range(self.point_depth_t.length*6):\n                self.point_fbos.append(RenderTarget([], ArrayLayerTarget(self.point_depth_t, i)))\n        \n    def clear(self, spot_count, sun_count, point_count):\n        for i in range(spot_count):\n            self.spot_fbos[i].clear(depth=1)\n        for i in range(sun_count):\n            self.sun_fbos[i].clear(depth=1)\n        for i in range(point_count*6):\n            self.point_fbos[i].clear(depth=1)\n    \n    def shader_callback(self, shader):\n        shader.textures['SHADOWMAPS_DEPTH_SPOT'] = self.spot_depth_t\n        shader.textures['SHADOWMAPS_DEPTH_SUN'] = self.sun_depth_t\n        shader.textures['SHADOWMAPS_DEPTH_POINT'] = self.point_depth_t\n\n\nclass LightsBuffer():\n    \n    def __init__(self):\n        self.data = C_LightsBuffer()\n        self.UBO = UBO()\n        self.spots = None\n        self.suns = None\n        self.points = None\n    \n    def load(\n        self, scene, spot_resolution, sun_resolution, point_resolution,\n        cascades_count, cascades_distribution_scalar, cascades_max_distance=1.0, sample_offset=(0,0)\n    ):\n        #TODO: Automatic distribution exponent basedd on FOV\n\n        spot_count=0\n        sun_count=0\n        point_count=0\n\n        from collections import OrderedDict\n\n        self.spots = OrderedDict()\n        self.suns = OrderedDict()\n        self.points = OrderedDict()\n\n        for i, light in enumerate(scene.lights):\n            self.data.lights[i].color = light.color\n            self.data.lights[i].type = light.type\n            self.data.lights[i].position = light.position\n            self.data.lights[i].radius = light.radius\n            self.data.lights[i].direction = light.direction\n            self.data.lights[i].spot_angle = light.spot_angle\n            self.data.lights[i].spot_blend = light.spot_blend\n\n            if light.type == LIGHT_SPOT:\n                self.data.lights[i].type_index = spot_count\n\n                projection_matrix = make_projection_matrix(light.spot_angle,1,0.01,light.radius,\n                    sample_offset,(spot_resolution, spot_resolution))\n                spot_matrix = projection_matrix * pyrr.Matrix44(light.matrix)\n                \n                self.data.spot_matrices[spot_count] = flatten_matrix(spot_matrix)\n\n                self.spots[light] = [(light.matrix, flatten_matrix(projection_matrix))]\n\n                spot_count+=1\n            \n            if light.type == LIGHT_SUN:\n                self.data.lights[i].type_index = sun_count\n\n                sun_matrix = pyrr.Matrix44(light.matrix)\n                projection_matrix = [*scene.camera.projection_matrix]\n                projection_matrix = pyrr.Matrix44(projection_matrix)\n                view_matrix = projection_matrix * pyrr.Matrix44(scene.camera.camera_matrix)\n\n                max_distance = cascades_max_distance\n                if light.sun_max_distance != 0:\n                    max_distance = light.sun_max_distance\n                \n                cascades_matrices = get_sun_cascades(sun_matrix, projection_matrix, view_matrix, cascades_count, cascades_distribution_scalar, max_distance, sample_offset, sun_resolution)\n\n                self.suns[light] = []\n                for i, cascade in enumerate(cascades_matrices):\n                    matrix = pyrr.Matrix44(cascade[1])*pyrr.Matrix44(cascade[0])\n                    self.data.sun_matrices[sun_count * cascades_count + i] = flatten_matrix(matrix)\n                    self.suns[light].append(cascade)\n\n                sun_count+=1\n            \n            if light.type == LIGHT_POINT:\n                self.data.lights[i].type_index = point_count\n\n                cube_map_axes = [\n                    (( 1, 0, 0),( 0,-1, 0)),\n                    ((-1, 0, 0),( 0,-1, 0)),\n                    (( 0, 1, 0),( 0, 0, 1)),\n                    (( 0,-1, 0),( 0, 0,-1)),\n                    (( 0, 0, 1),( 0,-1, 0)),\n                    (( 0, 0,-1),( 0,-1, 0))\n                ]\n                matrices = []\n                position = pyrr.Vector3(light.position)\n                offset_matrix = pyrr.Matrix44.from_translation(position)\n                rotation_matrix = pyrr.Matrix44.from_eulers((sample_offset[0], sample_offset[1], 0.0))\n                for axes in cube_map_axes:\n                    front = pyrr.Matrix33.from_matrix44(rotation_matrix) * pyrr.Vector3(axes[0])\n                    up = pyrr.Matrix33.from_matrix44(rotation_matrix) * pyrr.Vector3(axes[1])\n                    matrices.append(pyrr.Matrix44.look_at(position, position + front, up))\n                self.data.point_matrices[point_count] = flatten_matrix((offset_matrix * rotation_matrix).inverse)\n\n                projection_matrix = make_projection_matrix(math.pi / 2.0, 1.0, 0.01, light.radius, \n                    (0,0), (point_resolution, point_resolution))\n\n                self.points[light] = []\n                for i in range(6):\n                    self.points[light].append((flatten_matrix(matrices[i]), flatten_matrix(projection_matrix)))\n                \n                point_count+=1\n            \n        self.data.lights_count = len(scene.lights)\n        self.data.cascades_count = cascades_count\n        \n        self.UBO.load_data(self.data)\n    \n    def bind(self, block):\n        self.UBO.bind(block)\n    \n    def shader_callback(self, shader):\n        if 'SCENE_LIGHTS' in shader.uniform_blocks:\n            self.bind(shader.uniform_blocks['SCENE_LIGHTS'])\n\n\ndef flatten_matrix(matrix):\n    return (ctypes.c_float * 16)(*[e for v in matrix for e in v])\n\n\n#TODO: Hard-coded for Blender conventions for now\ndef make_projection_matrix(fov, aspect_ratio, near, far, sample_offset, resolution):\n    x_scale = 1.0 / math.tan(fov / 2.0)\n    y_scale = x_scale * aspect_ratio\n    matrix = [\n        x_scale, 0, 0, 0,\n        0, y_scale, 0, 0,\n        0, 0, (-(far + near)) / (far - near), -1,\n        0, 0, (-2.0 * far * near) / (far - near), 0\n    ]\n    bake_sample_offset(matrix, sample_offset, resolution)\n    return pyrr.Matrix44(matrix)\n\n\ndef get_sun_cascades(sun_from_world_matrix, projection_matrix, view_from_world_matrix, cascades_count, cascades_distribution_scalar, cascades_max_distance, sample_offset, resolution):\n    cascades = []\n    splits = []\n\n    n,f = 0,0    \n    if projection_matrix[3][3] == 1.0:\n        # ortho\n        n = cascades_max_distance / 2\n        f = -cascades_max_distance / 2\n    else:\n        # perspective\n        clip_start = projection_matrix.inverse * pyrr.Vector4([0,0,-1,1])\n        clip_start /= clip_start.w\n        n = clip_start.z\n        f = -cascades_max_distance\n\n    def lerp(a,b,f):\n        f = max(0,min(f,1))\n        return a * (1.0 - f) + b * f\n\n    for i in range(cascades_count+1):\n        split_log = n * pow(f/n, i/cascades_count)\n        split_uniform = n + (f-n) * (i/cascades_count)\n        split = lerp(split_uniform, split_log, cascades_distribution_scalar)\n\n        projected = projection_matrix * pyrr.Vector4([0,0,split,1])\n        projected = (projected / projected.w) * (1.0 if projected.w >= 0 else -1.0)\n        splits.append(projected.z)\n        \n    for i in range(1, len(splits)):\n        near = splits[i-1]\n        far = splits[i]\n        # Make the cascades overlap a bit\n        if i > 1 :\n            near = lerp(near, splits[i-1], 0.01)\n        if i+1 < len(splits):\n            far = lerp(far, splits[i+1], 0.01)\n        cascades.append(sun_shadowmap_matrix(sun_from_world_matrix, view_from_world_matrix, near, far, sample_offset, resolution))\n    \n    return cascades\n\n\ndef frustum_corners(view_from_world_matrix, near, far):\n    m = view_from_world_matrix.inverse\n    corners = []\n\n    for x in (-1, 1):\n        for y in (-1, 1):\n            for z in (near, far):\n                v = pyrr.Vector4([x, y, z, 1])\n                v = m * v\n                v /= v.w\n                corners.append(v)\n    \n    return corners\n\n\ndef sun_shadowmap_matrix(sun_from_world_matrix, view_from_world_matrix, near, far, sample_offset, resolution):\n    INFINITY = float('inf')\n    aabb = {\n        'min': pyrr.Vector3([ INFINITY,  INFINITY,  INFINITY]),\n        'max': pyrr.Vector3([-INFINITY, -INFINITY, -INFINITY])\n    }\n    \n    for corner in frustum_corners(view_from_world_matrix, near, far):\n        corner = sun_from_world_matrix * corner\n        aabb['min'].x = min(aabb['min'].x, corner.x)\n        aabb['min'].y = min(aabb['min'].y, corner.y)\n        aabb['min'].z = min(aabb['min'].z, corner.z)\n        aabb['max'].x = max(aabb['max'].x, corner.x)\n        aabb['max'].y = max(aabb['max'].y, corner.y)\n        aabb['max'].z = max(aabb['max'].z, corner.z)\n\n    world_from_light_space = sun_from_world_matrix.inverse\n\n    size = aabb['max'] - aabb['min']\n    aabb['min'] = world_from_light_space * pyrr.Vector4([*aabb['min'].tolist(), 1.0])\n    aabb['max'] = world_from_light_space * pyrr.Vector4([*aabb['max'].tolist(), 1.0])\n    center = (aabb['min'] + aabb['max']) / 2.0\n    center = pyrr.Vector3(center.tolist()[:3])\n\n    translate = pyrr.Matrix44.from_translation(center)\n    matrix = translate * world_from_light_space\n\n    o_x = sample_offset[0]/resolution\n    o_y = sample_offset[1]/resolution\n\n    scale = 1.0 / (size / 2.0)\n    screen = [\n        scale[0], 0, 0, 0,\n        0, scale[1], 0, 0,\n        0, 0,-scale[2], 0,\n        o_x, o_y, 0, 1\n    ]\n\n    return flatten_matrix(matrix.inverse), screen\n"
  },
  {
    "path": "Malt/Render/Sampling.py",
    "content": "import math\n\nimport random\n#Don't share state\nrandom = random.Random()\n\n#Rotated Grid Super Sampling pattern\n#https://en.wikipedia.org/wiki/Supersampling#Rotated_grid\ndef get_RGSS_samples(grid_size, width=1.0):\n    samples = []\n    for x in range(0, grid_size):\n        for y in range(0, grid_size):\n            _x = (x / grid_size) * 2.0 - 1.0 #(-1 ... +1 range)\n            _y = (y / grid_size) * 2.0 - 1.0 #(-1 ... +1 range)\n\n            angle = math.atan(1/2)\n            sin = math.sin(angle)\n            cos = math.cos(angle)\n            r_x = _x * cos - _y * sin\n            r_y = _x * sin + _y * cos\n            \n            scale = math.sqrt(5)/2\n            r_x *= scale\n            \n            #discard samples where radius > 1\n            if r_x * r_x + r_y * r_y <= 1:\n                r_x *= width\n                r_y *= width\n                samples.append((r_x,r_y))\n\n    random.seed(0)\n    #randomize the sampling order to get better early results\n    samples = sorted(samples, key=lambda k: random.random())\n\n    #Make sure there's at least one sample\n    if len(samples) == 0:\n        samples = [(0,0)]\n\n    return samples\n    \n\n#Random sampling. Best results at high sample counts\ndef get_random_samples(grid_size, width=1.0):\n    random.seed(0)\n    samples = []\n    for i in range(0, grid_size * grid_size):\n        x = 2\n        y = 2\n\n        #discard samples where radius > 1\n        while x*x + y*y > 1.0:\n            x = random.random() * 2.0 - 1.0 #(-1 ... +1 range)\n            y = random.random() * 2.0 - 1.0 #(-1 ... +1 range)\n\n        x *= width\n        y *= width\n\n        samples.append((x,y))\n    \n    #Make sure there's at least one sample\n    if len(samples) == 0:\n        samples = [(0,0)]\n    \n    return samples\n"
  },
  {
    "path": "Malt/Render/readme.md",
    "content": "# Render Library\n\nAny *Pipeline* agnostic render utility that can't be implemented only as *GLSL* code should go here.  \nFrom simple *Uniform Buffer Objects* ([Common.py](Common.py)) and pure *Python* utilities ([Sampling.py](Sampling.py)) to more complex features like Lighting and Shadowmaps ([Lighting.py](Lighting.py)).\n\n*Render* modules don't follow any specific API and while similar features share a similar interface, they are free to implement the most appropriate for their needs.\n\nAs more features are implemented and common patterns arise, a *Pipeline Plugins* API can be designed so using this utilities requires less manual communication from the *Pipeline* side.\n\n"
  },
  {
    "path": "Malt/Scene.py",
    "content": "class Camera():\n\n    def __init__(self, camera_matrix, projection_matrix, parameters={}):\n        self.camera_matrix = camera_matrix\n        self.projection_matrix = projection_matrix\n        self.parameters = parameters\n\nclass Material():\n\n    def __init__(self, shader, parameters={}):\n        self.shader = shader\n        self.parameters = parameters\n\nclass Mesh():\n\n    def __init__(self, mesh, parameters={}):\n        self.mesh = mesh\n        self.parameters = parameters\n\nclass Object():\n\n    def __init__(self, matrix, mesh, material, parameters={}, mirror_scale=False, tags=[]):\n        self.matrix = matrix\n        self.mesh = mesh\n        self.material = material\n        self.parameters = parameters\n        self.mirror_scale = mirror_scale\n        self.tags = tags\n\nclass Light():\n\n    def __init__(self):\n        self.type = 0\n        self.color = (0,0,0)\n        self.parameters = {}\n        self.position = (0,0,0)\n        self.direction = (0,0,0)\n        self.sun_max_distance = 0\n        self.spot_angle = 0\n        self.spot_blend = 0\n        self.radius = 0\n        self.matrix = None\n\nclass Scene():\n\n    def __init__(self):\n        self.camera = None\n        self.objects = []\n        self.lights = []\n        self.parameters = {}\n        self.world_parameters = {}\n        self.frame = 0\n        self.time = 0\n\n        self.batches = None\n        self.shader_resources = {}\n\nclass ShaderResource():\n    \n    def shader_callback(self, shader):\n        pass\n\nclass TextureShaderResource():\n\n    def __init__(self, name, texture):\n        self.name = name\n        self.texture = texture\n    \n    def shader_callback(self, shader):\n        if self.name in shader.textures.keys():\n            shader.textures[self.name] = self.texture\n"
  },
  {
    "path": "Malt/Shaders/Common/Color.glsl",
    "content": "#ifndef COMMON_COLOR_GLSL\n#define COMMON_COLOR_GLSL\n\n/*  META GLOBAL\n    @meta: category=Color;\n*/\n\n/*  META\n    @meta:doc=\n    Blends the blend color as a layer over the base color.;\n    @blend:default=vec4(0); doc=\n    The blend color.;\n*/\nvec4 alpha_blend(vec4 base, vec4 blend)\n{\n    if(blend.a <= 0)\n    {\n        return base;\n    }\n    else if(blend.a >= 1)\n    {\n        return blend;\n    }\n    \n    float alpha = blend.a + base.a * (1.0 - blend.a);\n    vec4 result = (blend * blend.a + base * base.a * (1.0 - blend.a)) / alpha;\n    result.a = alpha;\n\n    return result;\n}\n\n/* META @meta: label=Grayscale; */\nfloat relative_luminance(vec3 color)\n{\n    return dot(color, vec3(0.2126,0.7152,0.0722));\n}\n\n/* META @meta: internal=true; */\nfloat luma(vec3 color)\n{\n    return dot(color, vec3(0.299,0.587,0.114));\n}\n\n/* META @meta: label=Linear To sRGB; */\nvec3 linear_to_srgb(vec3 linear)\n{\n    vec3 low = linear * 12.92;\n    vec3 high = 1.055 * pow(linear, vec3(1.0/2.4)) - 0.055;\n    return mix(low, high, greaterThan(linear, vec3(0.0031308)));\n}\n\n/* META @meta: label=sRGB To Linear; */\nvec3 srgb_to_linear(vec3 srgb)\n{\n    vec3 low = srgb / 12.92;\n    vec3 high = pow((srgb + 0.055)/1.055, vec3(2.4));\n    return mix(low, high, greaterThan(srgb, vec3(0.04045)));\n}\n\n/* META @meta: label=RGB To HSV; */\nvec3 rgb_to_hsv(vec3 rgb)\n{\n    rgb = linear_to_srgb(rgb);\n    //http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl\n    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n    vec4 p = rgb.g < rgb.b ? vec4(rgb.bg, K.wz) : vec4(rgb.gb, K.xy);\n    vec4 q = rgb.r < p.x ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx);\n\n    float d = q.x - min(q.w, q.y);\n    float e = 1.0e-10;\n    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n}\n\n/*  META\n    @meta: label=HSV To RGB;\n    @hsv:subtype=HSV; min=0.0; max=1.0;\n*/\nvec3 hsv_to_rgb(vec3 hsv)\n{\n    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n    vec3 p = abs(fract(hsv.xxx + K.xyz) * 6.0 - K.www);\n    return srgb_to_linear(max(hsv.z,0) * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), max(hsv.y,0)));\n}\n\n/* META @meta: label=HSV Edit; */\nvec4 hsv_edit(vec4 color, float hue, float saturation, float value)\n{\n    vec3 hsv = rgb_to_hsv(color.rgb);\n    hsv += vec3(hue, saturation, value);\n    return vec4(hsv_to_rgb(hsv), color.a);\n}\n\n/* META @meta: label=Bright/Contrast; */\nvec4 bright_contrast(vec4 color, float brightness, float contrast)\n{\n    float a = 1.0 + contrast;\n    float b = brightness - contrast * 0.5;\n    vec3 result = max(a * color.rgb + b, vec3(0.0));\n    return vec4(result, color.a);\n}\n/* META \n    @meta: label=Gamma; \n    @gamma: default=1.0; min=0.0;\n*/\nvec4 gamma_correction(vec4 color, float gamma)\n{\n    vec3 result = max(pow(color.rgb, vec3(gamma)), vec3(0));\n    return vec4(result, color.a);\n}\n\n/* META \n    @meta: label=Invert; \n    @fac: subtype=Slider; min=0.0; max=1.0;\n*/\nvec4 color_invert(vec4 color, float fac)\n{\n    vec4 inverted = vec4(1.0 - color.rgb, color.a);\n    return mix(color, inverted, fac);\n}\n\n/* META @meta: internal=true; */\nvec3 rgb_gradient(sampler1D gradient, vec3 uvw)\n{\n    return vec3\n    (\n        texture(gradient, uvw.r).r,\n        texture(gradient, uvw.g).g,\n        texture(gradient, uvw.b).b\n    );\n}\n\n/* META @meta: internal=true; */\nvec4 rgba_gradient(sampler1D gradient, vec4 uvw)\n{\n    return vec4\n    (\n        texture(gradient, uvw.r).r,\n        texture(gradient, uvw.g).g,\n        texture(gradient, uvw.b).b,\n        texture(gradient, uvw.a).a\n    );\n}\n\n#endif // COMMON_COLOR_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Common/Hash.glsl",
    "content": "#ifndef COMMON_HASH_GLSL\n#define COMMON_HASH_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; internal=True;\n*/\n\nuvec4 _pcg4d(uvec4 v)\n{\n    //http://www.jcgt.org/published/0009/03/02/\n    v = v * 1664525u + 1013904223u;\n    v.x += v.y*v.w;\n    v.y += v.z*v.x;\n    v.z += v.x*v.y;\n    v.w += v.y*v.z;\n    v ^= v >> 16u;\n    v.x += v.y*v.w;\n    v.y += v.z*v.x;\n    v.z += v.x*v.y;\n    v.w += v.y*v.z;\n    return v;\n}\n\nvec4 _pcg4d(vec4 v)\n{\n    uvec4 u = _pcg4d(floatBitsToUint(v));\n    return vec4(u) / float(0xffffffffU);\n}\n\nvec4 hash(float v){ return _pcg4d(vec4(v,0,0,0)); }\nvec4 hash(vec2  v){ return _pcg4d(vec4(v,0,0)); }\nvec4 hash(vec3  v){ return _pcg4d(vec4(v,0)); }\n/* META @meta: internal=false; @v:subtype=Data; */\nvec4 hash(vec4  v){ return _pcg4d(v); }\n\n#endif // COMMON_HASH_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Common/Mapping.glsl",
    "content": "#ifndef COMMON_MAPPING_GLSL\n#define COMMON_MAPPING_GLSL\n\n#include \"Common.glsl\"\n\n/* META GLOBAL\n    @meta: category=Vector; internal=True;\n*/\n\nvec3 view_direction();\nvec3 transform_normal(mat4 matrix, vec3 normal);\n\n/*  META\n\t@meta: label=Matcap UV;\n    @normal: subtype=Normal; default=NORMAL;\n*/\nvec2 matcap_uv(vec3 normal)\n{\n\tvec3 N = transform_normal(CAMERA, normal);\n\tvec3 I = transform_normal(CAMERA, -view_direction());\n\n\tvec3 x = vec3(1,0,0);\n\tvec3 tangent = normalize(x - I * dot(x, I));\n\tvec3 y = vec3(0,1,0);\n\tvec3 bitangent = normalize(y - I * dot(y, I));\n\t\n\tvec3 screen_normal = vec3\n\t(\n\t\tdot(N, tangent),\n\t\tdot(N, bitangent),\n\t\tdot(N, I)\n\t);\n\n\tscreen_normal = normalize(screen_normal);\n\n\treturn screen_normal.xy * 0.499 + 0.5;\n}\n\n/*  META\n\t@meta: category=Texturing;\n    @normal: subtype=Normal; default=NORMAL;\n*/\nvec4 sample_matcap(sampler2D matcap, vec3 normal)\n{\n\treturn textureLod(matcap, matcap_uv(normal), 0);\n}\n\n/*  META\n\t@meta: label=HDRI UV;\n    @normal: subtype=Normal; default=NORMAL;\n*/\nvec2 hdri_uv(vec3 normal)\n{\n    vec2 uv = vec2(atan(normal.y, normal.x), asin(normal.z));\n    vec2 inverse_atan = vec2(0.1591, 0.3183);\n    return uv * inverse_atan + 0.5;\n}\n\n/*  META\n\t@meta: category=Texturing; label=Sample HDRI;\n    @normal: subtype=Normal; default=NORMAL;\n*/\nvec4 sample_hdri(sampler2D hdri, vec3 normal)\n{\n\treturn textureLod(hdri, hdri_uv(normal), 0);\n}\n\nvec2 curve_view_mapping(vec2 uv, vec3 normal, vec3 tangent, vec3 incoming)\n{\n\tvec3 screen_bitangent = transform_normal(CAMERA, cross(incoming, tangent));\n\tvec3 screen_normal = transform_normal(CAMERA, normal);\n\tfloat y_grad = dot(screen_bitangent, screen_normal);\n\treturn vec2(uv.x, (y_grad + 1) * 0.5);\n}\n\nvec4 sample_flipbook(sampler2D tex, vec2 uv, ivec2 dimensions, int page, bool top_left)\n{\n    int page_count = dimensions.x * dimensions.y;\n    page = int(mod(page, page_count));\n    vec2 offset = vec2(\n        mod(page, dimensions.x),\n        floor(top_left ? dimensions.y - 1 - (page / dimensions.y) : (page / dimensions.y))\n    );\n    uv = (uv + offset) / vec2(dimensions);\n    return texture(tex, uv);\n}\n\nfloat pingpong(float a, float b);\n\nvec4 flowmap(sampler2D tex, vec2 uv, vec2 flow, float progression, int samples)\n{\n    vec4 result;\n    float fraction = 1.0 / float(samples);\n    for(int i = 0; i < samples; i++)\n    {\n        float flow_scale = fract(progression - i * fraction);\n        vec4 color = texture(tex, uv - flow * flow_scale);\n\n        float range = fract(progression - (float(i) / float(samples))) * samples;\n        float p = pingpong(range, 1.0);\n        float bounds = (range > 0.0 && range < 2.0)? 1.0 : 0.0;\n        result += color * p * bounds;\n    }\n    return result;\n}\n\n#endif //COMMON_MAPPING_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Common/Math.glsl",
    "content": "#ifndef COMMON_MATH_GLSL\n#define COMMON_MATH_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; internal=true;\n*/\n\n//C Standard constants\n\n#define M_E             2.71828182845904523536028747135266250   /* e */\n#define M_LOG2E         1.44269504088896340735992468100189214   /* log2(e) */\n#define M_LOG10E        0.434294481903251827651128918916605082  /* log10(e) */\n#define M_LN2           0.693147180559945309417232121458176568  /* ln(2) */\n#define M_LN10          2.30258509299404568401799145468436421   /* ln(10) */\n#define M_PI            3.14159265358979323846264338327950288   /* pi */\n#define M_PI_2          1.57079632679489661923132169163975144   /* pi/2 */\n#define M_PI_4          0.785398163397448309615660845819875721  /* pi/4 */\n#define M_1_PI          0.318309886183790671537767526745028724  /* 1/pi */\n#define M_2_PI          0.636619772367581343075535053490057448  /* 2/pi */\n#define M_2_SQRTPI      1.12837916709551257389615890312154517   /* 2/sqrt(pi) */\n#define M_SQRT2         1.41421356237309504880168872420969808   /* sqrt(2) */\n#define M_SQRT1_2       0.707106781186547524400844362104849039  /* 1/sqrt(2) */\n\n#define GOLDEN_RATIO    1.618033988749894848204586834365638117  /* phi */\n#define GOLDEN_ANGLE    2.399963229728652887367000547681316645  /* pi*(3-sqrt(5)) */\n#define PI M_PI\n\n//These are defined as macros to make them work across different types. (Poor Man's Generics)\n\n#define saturate(value) clamp((value), 0, 1)\n\nfloat safe_mix(float a, float b, float f) {return f == 0.0 ? a : f == 1.0 ? b : mix(a, b, f);}\nvec2 safe_mix(vec2 a, vec2 b, vec2 f) {return f == vec2(0) ? a : f == vec2(1) ? b : mix(a, b, f);}\nvec2 safe_mix(vec2 a, vec2 b, float f) {return f == 0.0 ? a : f == 1.0 ? b : mix(a, b, f);}\nvec3 safe_mix(vec3 a, vec3 b, vec3 f) {return f == vec3(0) ? a : f == vec3(1) ? b : mix(a, b, f);}\nvec3 safe_mix(vec3 a, vec3 b, float f) {return f == 0.0 ? a : f == 1.0 ? b : mix(a, b, f);}\nvec4 safe_mix(vec4 a, vec4 b, vec4 f) {return f == vec4(0) ? a : f == vec4(1) ? b : mix(a, b, f);}\nvec4 safe_mix(vec4 a, vec4 b, float f) {return f == 0.0 ? a : f == 1.0 ? b : mix(a, b, f);}\n\n#define map_range(value, from_min, from_max, to_min, to_max) (safe_mix((to_min), (to_max), ((value) - (from_min)) / ((from_max) - (from_min))))\n#define map_range_clamped(value, from_min, from_max, to_min, to_max) clamp(map_range(value, from_min, from_max, to_min, to_max), min(to_min, to_max), max(to_max, to_min))\n\n#define snap(value, range) (round((value) / (range)) * (range))\n#define distort(base, distortion, fac) (base + (distortion - 0.5) * 2 * fac)\n\n#define vector_angle(a,b) (acos((dot(a,b))/(length(a) * length(b))))\n\n#include \"Common.glsl\"\n\nfloat pingpong(float a, float b)\n{\n    return (b != 0.0)? abs(fract((a - b) / (b * 2.0)) * b * 2.0 - b) : 0.0;\n}\n\n/* META @meta: subcategory=Random; */\nvec4 random_per_object(float seed)\n{\n    return hash(vec2(IO_ID.x, seed));\n}\n\n/* META @meta: subcategory=Random; */\nvec4 random_per_sample(float seed)\n{\n    return hash(vec2(float(SAMPLE_COUNT), seed));\n}\n\nvec2 screen_uv(); //FORWARD DECLARATION\n\n/* META @meta: subcategory=Random; */\nvec4 random_per_pixel(float seed) \n{\n    return hash(vec4(screen_uv(), float(SAMPLE_COUNT), seed));\n}\n\nvec2 phyllotaxis_disk(float p, int total)\n{\n    // https://en.wikipedia.org/wiki/Phyllotaxis\n    // https://www.mathrecreation.com/2008/09/phyllotaxis-spirals.html\n\n    // returns a point on a disk where all points are evenly spaced. The 'total' argument makes sure that your point ends up in a disk of radius=1\n    // this is a good approach of getting a spherical kernel with arbitrary sample count\n    float r = p * GOLDEN_ANGLE;\n    return vec2(cos(r), sin(r)) * vec2(pow(p / float(total), 0.5));\n}\n\n#endif // COMMON_MATH_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Common/Matrix.glsl",
    "content": "#ifndef COMMON_MATRIX_GLSL\n#define COMMON_MATRIX_GLSL\n\n/* META GLOBAL\n    @meta: category=Math; subcategory=Matrix;\n*/\n\n#include \"Common/Quaternion.glsl\"\n\n/*  META\n    @meta: label=From Translation;\n    @t: subtype=Vector;\n*/\nmat4 mat4_translation(vec3 t)\n{\n    mat4 m = mat4(1.0);\n    m[3] = vec4(t, 1);\n    return m;\n}\n\n/*  META\n    @meta: label=From Quaternion;\n    @q: subtype=Quaternion; default=vec4(0,0,0,1);\n*/\nmat4 mat4_rotation_from_quaternion(vec4 q)\n{\n    return mat4(mat3(\n        quaternion_transform(q, vec3(1,0,0)),\n        quaternion_transform(q, vec3(0,1,0)),\n        quaternion_transform(q, vec3(0,0,1))\n    ));\n}\n\n/*  META\n    @meta: label=From Euler;\n    @e: subtype=Euler;\n*/\nmat4 mat4_rotation_from_euler(vec3 e)\n{\n    mat4 x = mat4_rotation_from_quaternion(quaternion_from_axis_angle(vec3(1,0,0), e.x));\n    mat4 y = mat4_rotation_from_quaternion(quaternion_from_axis_angle(vec3(0,1,0), e.y));\n    mat4 z = mat4_rotation_from_quaternion(quaternion_from_axis_angle(vec3(0,0,1), e.z));\n\n    return z * y * x;\n}\n\n/*  META\n    @meta: label=From Scale;\n    @s: subtype=Vector; default=vec3(1);\n*/\nmat4 mat4_scale(vec3 s)\n{\n    return mat4(mat3(s.x,0,0, 0,s.y,0, 0,0,s.z));\n}\n\n/*  META\n    @meta: label=Is Orthographic;\n    @matrix: default=mat4(1);\n*/\nbool is_ortho(mat4 matrix)\n{\n    return matrix[3][3] == 1.0;\n}\n\n#endif // COMMON_MATRIX_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Common/Normal.glsl",
    "content": "#ifndef COMMON_NORMAL_GLSL\n#define COMMON_NORMAL_GLSL\n\n#include \"Common.glsl\"\n\n/*  META GLOBAL\n    @meta: category=Vector; internal=true;\n*/\n\n/* META\n    @meta: category=Input;\n*/\nvec3 true_normal()\n{\n    #ifndef CUSTOM_TRUE_NORMAL\n    {\n        #ifdef PIXEL_SHADER\n        {\n            return normalize(cross(dFdx(POSITION), dFdy(POSITION)));\n        }\n        #endif //PIXEL_SHADER\n        \n        return NORMAL;\n    }\n    #else\n    {\n        return CUSTOM_TRUE_NORMAL;\n    }\n    #endif //CUSTOM_TRUE_NORMAL\n}\n\n/* META\n    @meta: category=Input;\n*/\nfloat facing()\n{\n    return dot(NORMAL, -view_direction());\n}\n\n/* META\n    @meta: category=Input;\n*/\nbool is_front_facing()\n{\n    #ifdef PIXEL_SHADER\n    {\n        return gl_FrontFacing;\n    }\n    #endif\n    return facing() > 0;\n}\n\n/* META @meta: subcategory=Tangent; */\nvec4 compute_tangent(vec2 uv)\n{\n    // Copyright (c) 2020 mmikk. MIT License\n    // http://jcgt.org/published/0009/03/04/\n    #ifdef PIXEL_SHADER\n    {\n        vec3 normal = normalize(NORMAL);\n        \n        vec3 position_dx = dFdx(POSITION);\n        vec3 position_dy = dFdy(POSITION);\n\n        vec3 sigma_x = position_dx - dot(position_dx, normal) * normal;\n        vec3 sigma_y = position_dy - dot(position_dy, normal) * normal;\n\n        float flip_sign = dot(position_dy, cross(normal, position_dx)) < 0 ? -1 : 1;\n        \n        vec2 uv_dx = dFdx(uv);\n        vec2 uv_dy = dFdy(uv);\n\n        float determinant = dot(uv_dx, vec2(uv_dy.y, -uv_dy.x));\n        float determinant_sign = determinant < 0.0 ? -1.0 : 1.0;\n    \n        // inverse represents (dXds, dYds), but we don't divide\n        // by the determinant. Instead, we scale by the sign.\n        vec2 inverse = determinant_sign * vec2(uv_dy.y, -uv_dx.y); \n        \n        vec3 tangent = sigma_x * inverse.x + sigma_y * inverse.y;\n        if (abs(determinant) > 0.0)\n        {\n            tangent = normalize(tangent);\n        }\n\n        return vec4(tangent, (determinant_sign * flip_sign));\n    }\n    #endif\n\n    return vec4(0);\n}\n\n/* META @meta: subcategory=Tangent; */\nvec3 get_tangent(int uv_index)\n{\n    if(PRECOMPUTED_TANGENTS && uv_index == 0) return TANGENT;\n    return compute_tangent(UV[uv_index]).xyz;\n}\n\n/* META @meta: subcategory=Tangent; */\nvec3 get_bitangent(int uv_index)\n{\n    if(PRECOMPUTED_TANGENTS && uv_index == 0) return BITANGENT;\n    vec4 T = compute_tangent(UV[uv_index]);\n    return normalize(cross(NORMAL, T.xyz) * T.w);\n}\n\n/* META @meta: subcategory=Tangent; */\nmat3 get_TBN(int uv_index)\n{\n    return mat3(get_tangent(uv_index), get_bitangent(uv_index), NORMAL);\n}\n\n/* META \n    @tangent_normal: subtype=Vector; default='vec3(0.5, 0.5, 1.0)'; \n    @TBN: label=TBN; default=get_TBN(0);\n*/\nvec3 tangent_to_world_normal(vec3 tangent_normal, mat3 TBN)\n{\n    return normalize(TBN * (tangent_normal * 2 - 1));\n}\n\n/* META \n    @meta: category=Texturing;\n    @TBN: default=get_TBN(0);\n    @uv: default=UV[0]; label=UV;\n*/\nvec3 sample_normal_map_ex(sampler2D normal_texture, mat3 TBN, vec2 uv)\n{\n    return tangent_to_world_normal(texture(normal_texture, uv).rgb, TBN);\n}\n\n/* META \n    @meta: category=Texturing; label=Normal Map; \n    @uv_index: min=0; max=3;\n    @uv: default=UV[0];\n*/\nvec3 sample_normal_map(sampler2D normal_texture, int uv_index, vec2 uv)\n{\n    return sample_normal_map_ex(normal_texture, get_TBN(uv_index), uv);\n}\n\n/*  META\n    @meta: category=Vector; subcategory=Tangent;\n    @normal: subtype=Normal; default=NORMAL;\n    @axis: subtype=Normal; default=(0.0,0.0,1.0);\n*/\nvec3 radial_tangent(vec3 normal, vec3 axis)\n{\n    float d = abs(dot(normal, axis));\n    float epsilon = 1e-5;\n    if(d > 1.0 - epsilon)\n    {\n        // Avoid singularities when normal == axis.\n        vec3 alternative_axis = normalize(axis + vec3(0.1, 0.2, 0.3));\n        axis = mix(axis, alternative_axis, map_range_clamped(d, 1.0 - epsilon, 1.0, 0.0, epsilon));\n    }\n    return normalize(cross(axis, normal));\n}\n\n/*  META\n    @meta: internal=false;\n    @base_normal: subtype=Normal; default=NORMAL;\n    @custom_normal: subtype=Normal; default=NORMAL;\n*/\nvec3 surface_gradient_from_normal(vec3 base_normal, vec3 custom_normal)\n{\n    // Copyright (c) 2020 mmikk. MIT License\n    // http://jcgt.org/published/0009/03/04/\n    const float epsilon = 1.192092896e-07f;\n    float NoC = dot(base_normal, custom_normal);\n    return (NoC * base_normal - custom_normal) / max(epsilon, abs(NoC));\n}\n\nvec3 surface_gradient_from_normal(vec3 custom_normal)\n{\n    return surface_gradient_from_normal(NORMAL, custom_normal);\n}\n\n/*  META\n    @meta: internal=false;\n    @base_normal: subtype=Normal; default=NORMAL;\n    @surface_gradient: subtype=Vector; default=vec3(0);\n*/\nvec3 normal_from_surface_gradient(vec3 base_normal, vec3 surface_gradient)\n{\n    // Copyright (c) 2020 mmikk. MIT License\n    // http://jcgt.org/published/0009/03/04/\n    return normalize(base_normal - surface_gradient);\n}\n\nvec3 normal_from_surface_gradient(vec3 surface_gradient)\n{\n    return normal_from_surface_gradient(NORMAL, surface_gradient);\n}\n\n#endif //COMMON_NORMAL_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Common/Quaternion.glsl",
    "content": "#ifndef COMMON_QUATERNION_GLSL\n#define COMMON_QUATERNION_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; subcategory=Quaternion;\n*/\n\n\n/*  META\n    @meta: label=From Axis Angle;\n    @axis: subtype=Normal;\n    @angle: subtype=Angle; default=0;\n*/\nvec4 quaternion_from_axis_angle(vec3 axis, float angle)\n{\n    return vec4(axis * sin(0.5 * angle), cos(0.5 * angle));\n}\n\n/*  META\n    @meta: label=From Vector Delta;\n    @from: subtype=Normal;\n    @to: subtype=Normal;\n*/\nvec4 quaternion_from_vector_delta(vec3 from, vec3 to)\n{\n    return normalize(vec4(cross(from, to), 1.0 + dot(from, to)));\n}\n\n/*  META\n    @meta: label=Inverted;\n    @a: label=Q; subtype=Quaternion; default=vec4(0,0,0,1);\n*/\nvec4 quaternion_inverted(vec4 a)\n{ \n    return vec4(-a.xyz, a.w);\n}\n\n/*  META\n    @meta: label=Multiply;\n    @a: subtype=Quaternion; default=vec4(0,0,0,1);\n    @b: subtype=Quaternion; default=vec4(0,0,0,1);\n*/\nvec4 quaternion_multiply(vec4 a, vec4 b) \n{\n    return vec4\n    (\n        a.xyz * b.w + b.xyz * a.w + cross(a.xyz, b.xyz),\n        a.w * b.w - dot(a.xyz, b.xyz)\n    );\n}\n\n/*  META\n    @meta: label=Transform;\n    @a: label=Q; subtype=Quaternion; default=vec4(0,0,0,1);\n    @vector: subtype=Vector; default=vec3(0);\n*/\nvec3 quaternion_transform(vec4 a, vec3 vector)\n{\n    vec3 t = cross(a.xyz, vector) * 2.0;\n    return vector + t * a.w + cross(a.xyz, t);\n}\n\n/*  META\n    @meta: label=Mix;\n    @a: subtype=Quaternion; default=vec4(0,0,0,1);\n    @b: subtype=Quaternion; default=vec4(0,0,0,1);\n    @factor: subtype=Slider; default=0.5; min=0; max=1;\n*/\nvec4 quaternion_mix(vec4 a, vec4 b, float factor)\n{\n    return normalize(mix(a, b, factor));\n}\n\n#endif //COMMON_QUATERION_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Common/Transform.glsl",
    "content": "#ifndef COMMON_TRANSFORM_GLSL\n#define COMMON_TRANSFORM_GLSL\n\n/*  META GLOBAL\n    @meta: category=Vector; internal=true;\n*/\n\n#include \"Common.glsl\"\n\n/*  META\n    @meta: subcategory=Matrix; internal=false;\n    @matrix: default=mat4(1);\n    @point: subtype=Vector;\n*/\nvec3 transform_point(mat4 matrix, vec3 point)\n{\n    return (matrix * vec4(point, 1.0)).xyz;\n}\n\n/*  META\n    @meta: subcategory=Matrix; internal=false;\n    @matrix: default=mat4(1);\n    @point: subtype=Vector;\n*/\nvec3 project_point(mat4 matrix, vec3 point)\n{\n    vec4 result = matrix * vec4(point, 1.0);\n    return (result.xyz / result.w) * sign(result.w);\n}\n\n/*  META\n    @meta: subcategory=Matrix; internal=false;\n    @matrix: default=mat4(1);\n    @point: subtype=Vector;\n*/\nvec3 project_point_to_screen_coordinates(mat4 matrix, vec3 point)\n{\n    //Assumes gl_DepthRange is 0...1\n    return map_range(project_point(matrix, point), vec3(-1), vec3(1), vec3(0), vec3(1));\n}\n\n/*  META\n    @meta: subcategory=Matrix; internal=false;\n    @matrix: default=mat4(1);\n    @direction: subtype=Vector;\n*/\nvec3 transform_direction(mat4 matrix, vec3 direction)\n{\n    return mat3(matrix) * direction;\n}\n\n/*  META\n    @meta: subcategory=Matrix; internal=false;\n    @matrix: default=mat4(1);\n    @normal: subtype=Normal;\n*/\nvec3 transform_normal(mat4 matrix, vec3 normal)\n{\n    mat3 m = transpose(inverse(mat3(matrix)));\n    return normalize(m * normal);\n}\n\nvec3 screen_to_camera(vec2 uv, float depth);\n\nvec3 camera_direction_to_screen_space(vec3 vector)\n{\n    vec3 N = normalize(vector);\n    vec3 I = -normalize(screen_to_camera(screen_uv(), 1));\n    vec3 x = vec3(1,0,0);\n\tvec3 tangent = normalize(x - I * dot(x, I));\n\tvec3 y = vec3(0,1,0);\n\tvec3 bitangent = normalize(y - I * dot(y, I));\n\t\n\tvec3 screen_normal = vec3\n\t(\n\t\tdot(N, tangent),\n\t\tdot(N, bitangent),\n\t\tdot(N, I)\n\t);\n\n\treturn normalize(screen_normal) * length(vector);\n}\n\n/* META @meta: category=Input; */\nvec3 camera_position()\n{\n    return transform_point(inverse(CAMERA), vec3(0,0,0));\n}\n\n/* META @meta: category=Input; */\nvec3 model_position()\n{\n    return transform_point(MODEL, vec3(0,0,0));\n}\n\n/* META @meta: category=Input; */\nvec2 screen_uv()\n{\n    #ifdef PIXEL_SHADER\n    {\n        return vec2(gl_FragCoord) / vec2(RESOLUTION);\n    }\n    #else\n    {\n        return project_point(PROJECTION * CAMERA, POSITION).xy * 0.5 + 0.5;\n    }\n    #endif //PIXEL_SHADER\n}\n\nivec2 screen_pixel()\n{\n    #ifdef PIXEL_SHADER\n    {\n        return ivec2(floor(gl_FragCoord.xy));\n    }\n    #else\n    {\n        return ivec2(floor(screen_uv() * RESOLUTION));\n    }\n    #endif\n}\n/* META\n    @uv: default=UV[0];\n*/\nvec3 screen_to_camera(vec2 uv, float depth)\n{\n    vec3 clip_position = vec3(uv, depth) * 2.0 - 1.0;\n    vec4 camera_position = inverse(PROJECTION) * vec4(clip_position, 1.0);\n    camera_position /= camera_position.w;\n\n    return camera_position.xyz;\n}\n\n/* META @meta: category=Input; */\nvec3 view_direction()\n{\n    return transform_normal(inverse(CAMERA), screen_to_camera(screen_uv(), 1));\n}\n\nfloat pixel_depth()\n{\n    #ifdef PIXEL_SHADER\n    {\n        #ifdef CUSTOM_PIXEL_DEPTH\n        {\n            return CUSTOM_PIXEL_DEPTH;\n        }\n        #else\n        {\n            return gl_FragCoord.z;\n        }\n        #endif\n    }\n    #endif\n\n    return 0.0;\n}\n\nfloat depth_to_z(float depth)\n{\n    return screen_to_camera(vec2(0,0), depth).z;\n}\n\n/*  META\n    @meta: label=Pixel Size in World Space; internal=false;\n    @depth: default=pixel_depth();\n*/\nfloat pixel_world_size_at(float depth)\n{\n    vec2 uv = screen_uv();\n    vec2 offset = vec2(1.0 / RESOLUTION.x, 0);\n    return distance(screen_to_camera(uv, depth), screen_to_camera(uv + offset, depth));\n}\n\nfloat pixel_world_size()\n{\n    #ifdef PIXEL_SHADER\n    {\n        return pixel_world_size_at(pixel_depth());\n    }\n    #else\n    {\n        return pixel_world_size_at(project_point(PROJECTION * CAMERA, POSITION).z * 0.5 + 0.5);\n    }\n    #endif\n}\n\nvec3 _reconstruct_cs_position(sampler2D depth_texture, int depth_channel, ivec2 texel)\n{\n    float depth = texelFetch(depth_texture, texel, 0)[depth_channel];\n    ivec2 size = textureSize(depth_texture, 0);\n    vec2 uv = (vec2(texel) + vec2(0.5)) / vec2(size);\n\n    return screen_to_camera(uv, depth);\n}\n\nvec3 reconstruct_normal(sampler2D depth_texture, int depth_channel, ivec2 texel)\n{\n    vec3 t0 = _reconstruct_cs_position(depth_texture, depth_channel, texel);\n    vec3 x1 = _reconstruct_cs_position(depth_texture, depth_channel, texel + ivec2(-1, 0));\n    vec3 x2 = _reconstruct_cs_position(depth_texture, depth_channel, texel + ivec2( 1, 0));\n    vec3 y1 = _reconstruct_cs_position(depth_texture, depth_channel, texel + ivec2( 0,-1));\n    vec3 y2 = _reconstruct_cs_position(depth_texture, depth_channel, texel + ivec2( 0, 1));\n\n    vec3 x = distance(x1.z, t0.z) < distance(x2.z, t0.z) ? x1 : x2;\n    vec3 y = distance(y1.z, t0.z) < distance(y2.z, t0.z) ? y1 : y2;\n\n    vec3 n = normalize(cross(x - t0, y - t0));\n    \n    vec3 view_direction = screen_to_camera(screen_uv(), 1);\n    n = dot(n, view_direction) < 0 ? n : -n;\n\n    return transform_normal(inverse(CAMERA), n);\n}\n\n/*  META\n    @ray_origin: subtype=Vector;\n    @ray_direction: subtype=Vector;\n    @plane_position: subtype=Vector;\n    @plane_normal: subtype=Normal;\n*/\nfloat ray_plane_intersection(vec3 ray_origin, vec3 ray_direction, vec3 plane_position, vec3 plane_normal)\n{\n    float r_direction = dot(ray_direction, plane_normal);\n    float r_origin = dot(ray_origin, plane_normal);\n    float p_position = dot(plane_position, plane_normal);\n\n    return (p_position - r_origin) / r_direction;\n}\n\nvec2 rotate_2d(vec2 p, float angle)\n{\n    mat2 rot = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));\n    return rot * p;\n}\n\n#endif //COMMON_TRANSFORM_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Common.glsl",
    "content": "#ifndef COMMON_GLSL\n#define COMMON_GLSL\n\n#ifdef VERTEX_SHADER\n#define vertex_out out\n#else\n#define vertex_out in\n#endif\n\nvec3 POSITION;\nvec3 NORMAL;\nvec3 TANGENT;\nvec3 BITANGENT;\nvec2 UV[4];\nvec4 COLOR[4];\nuvec4 ID;\n\nvertex_out mat4 MODEL;\n\nlayout(std140) uniform COMMON_UNIFORMS\n{\n    uniform mat4 CAMERA;\n    uniform mat4 PROJECTION;\n    uniform ivec2 RESOLUTION;\n    uniform vec2 SAMPLE_OFFSET; //Sample offset is already baked into PROJECTION.\n    uniform int SAMPLE_COUNT;\n    uniform int FRAME;\n    uniform float TIME;\n};\n\nuniform bool MIRROR_SCALE = false;\nuniform bool PRECOMPUTED_TANGENTS = false;\n\nuniform bvec4 COLOR_IS_SRGB = bvec4(false);\n\n#ifndef MAX_BATCH_SIZE\n    // Assume at least 64kb of UBO storage (d3d11 requirement) and max element size of mat4\n    #define MAX_BATCH_SIZE 1000\n#endif\n\nlayout(std140) uniform BATCH_MODELS\n{\n    uniform mat4 BATCH_MODEL[MAX_BATCH_SIZE];\n};\nlayout(std140) uniform BATCH_IDS\n{\n    //Use uvec4 so the uints are tightly packed\n    uniform uvec4 BATCH_ID[MAX_BATCH_SIZE/4+1];\n};\n#define BATCH_ID(index) BATCH_ID[(index)/4][(index)%4]\n\nvertex_out vec3 IO_POSITION;\nvertex_out vec3 IO_NORMAL;\nvertex_out vec3 IO_TANGENT;\nvertex_out vec3 IO_BITANGENT;\nvertex_out vec2 IO_UV[4];\nvertex_out vec4 IO_COLOR[4];\nflat vertex_out uvec4 IO_ID;\n\n#include \"Common/Color.glsl\"\n#include \"Common/Hash.glsl\"\n#include \"Common/Mapping.glsl\"\n#include \"Common/Math.glsl\"\n#include \"Common/Matrix.glsl\"\n#include \"Common/Normal.glsl\"\n#include \"Common/Quaternion.glsl\"\n#include \"Common/Transform.glsl\"\n\n#ifdef VERTEX_SHADER\n\nlayout (location = 0) in vec3 in_position;\nlayout (location = 1) in vec3 in_normal;\nlayout (location = 2) in vec4 in_tangent;\nlayout (location = 3) in vec2 in_uv0;\nlayout (location = 4) in vec2 in_uv1;\nlayout (location = 5) in vec2 in_uv2;\nlayout (location = 6) in vec2 in_uv3;\nlayout (location = 7) in vec4 in_color0;\nlayout (location = 8) in vec4 in_color1;\nlayout (location = 9) in vec4 in_color2;\nlayout (location = 10) in vec4 in_color3;\n\nvoid VERTEX_SETUP_OUTPUT()\n{\n    gl_Position = PROJECTION * CAMERA * vec4(POSITION, 1);\n\n    IO_POSITION = POSITION;\n    IO_NORMAL = NORMAL;\n    IO_TANGENT = TANGENT;\n    IO_BITANGENT = BITANGENT;\n    IO_UV = UV;\n    IO_COLOR = COLOR;\n    IO_ID = ID;\n}\n\nvoid DEFAULT_VERTEX_SHADER()\n{\n    MODEL = BATCH_MODEL[gl_InstanceID];\n    ID = uvec4(BATCH_ID(gl_InstanceID),0,0,0);\n\n    POSITION = transform_point(MODEL, in_position);\n    NORMAL = transform_normal(MODEL, in_normal);\n\n    if(PRECOMPUTED_TANGENTS)\n    {\n        TANGENT = transform_normal(MODEL, in_tangent.xyz);\n        float mirror_scale = MIRROR_SCALE ? -1 : 1;\n        BITANGENT = normalize(cross(NORMAL, TANGENT) * in_tangent.w) * mirror_scale;\n    }\n\n    UV[0]=in_uv0;\n    UV[1]=in_uv1;\n    UV[2]=in_uv2;\n    UV[3]=in_uv3;\n\n    COLOR[0]=in_color0;\n    COLOR[1]=in_color1;\n    COLOR[2]=in_color2;\n    COLOR[3]=in_color3;\n\n    for(int i = 0; i < 4; i++)\n    {\n        if(COLOR_IS_SRGB[i])\n        {\n            COLOR[i].rgb = srgb_to_linear(COLOR[i].rgb);\n        }\n    }\n\n    VERTEX_SETUP_OUTPUT();\n}\n\nvoid DEFAULT_SCREEN_VERTEX_SHADER()\n{\n    IO_POSITION = in_position;\n    IO_UV[0] = in_position.xy * 0.5 + 0.5;\n    gl_Position = vec4(in_position, 1);\n}\n\n#endif //VERTEX_SHADER\n\n#ifdef PIXEL_SHADER\n\nvoid PIXEL_SETUP_INPUT()\n{\n    POSITION = IO_POSITION;\n    NORMAL = normalize(IO_NORMAL) * (gl_FrontFacing ? 1.0 : -1.0);\n    TANGENT = IO_TANGENT;\n    BITANGENT = IO_BITANGENT;\n    UV = IO_UV;\n    COLOR = IO_COLOR;\n    ID = IO_ID;\n}\n\n#endif //PIXEL_SHADER\n\n#endif //COMMON_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Filters/AO.glsl",
    "content": "#ifndef AO_GLSL\n#define AO_GLSL\n\n#include \"Common/Math.glsl\"\n#include \"Common/Transform.glsl\"\n\n/*  META\n    @meta: internal=true;\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @samples: default=8;\n    @radius: default=1.0;\n    @distribution_exponent: default=5.0;\n    @bias: default=0.01;\n*/\nfloat ao(sampler2D depth_texture, int depth_channel, vec3 position, vec3 normal, int samples, float radius, float distribution_exponent, float bias)\n{\n    //Loosely based on https://learnopengl.com/Advanced-Lighting/SSAO\n    float occlusion = 0;\n\n    if(samples <= 0 || radius <= 0.0)\n    {\n        return 1.0;\n    }\n    \n    for(int i = 0; i < samples; i++)\n    {\n        // Generate a random TBN matrix\n        vec3 random_vec = random_per_pixel(i).xyz;\n        random_vec.xyz = random_vec.xyz * 2.0 - 1.0;\n\n        vec3 normal = transform_normal(CAMERA, normal);\n        vec3 tangent = normalize(random_vec - normal * dot(random_vec, normal));\n        vec3 bitangent = cross(normal, tangent);\n        mat3 TBN = mat3(tangent, bitangent, normal);\n\n        vec3 random_offset = random_per_pixel(samples+i).xyz;\n        // Make samples close to the center more likely, based on distribution exponent\n        random_offset = normalize(random_offset) * pow(length(random_offset), distribution_exponent);\n        random_offset *= radius;\n\n        vec3 sample_offset = TBN * random_offset;\n        vec3 sample_position = transform_point(CAMERA, position) + sample_offset;\n\n        vec3 sample_uv = project_point_to_screen_coordinates(PROJECTION, sample_position);\n\n        float sampled_depth = texture(depth_texture, sample_uv.xy)[depth_channel];\n        sampled_depth = screen_to_camera(sample_uv.xy, sampled_depth).z;\n\n        float range_check = smoothstep(0.0, 1.0, radius / abs(sample_position.z - sampled_depth));\n        \n        occlusion += (sampled_depth >= sample_position.z + bias ? 1.0 : 0.0) * range_check;\n    }\n\n    return 1.0 - (occlusion / samples);\n}\n\n#endif //AO_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Filters/Bevel.glsl",
    "content": "#ifndef FILTERS_BEVEL_GLSL\n#define FILTERS_BEVEL_GLSL\n\n#include \"Common/Math.glsl\"\n#include \"Common/Transform.glsl\"\n\n/*  META\n    @meta: internal=true;\n    @id: default=ID[0];\n    @samples: default=8;\n    @radius: default=1.0;\n    @distribution_exponent: default=5.0;\n    @hard_bevel_max_dot: default=0.5;\n*/\nvec3 bevel\n(\n    sampler2D normal_texture, sampler2D depth_texture, int depth_channel,\n    uint id, bool filter_by_id, usampler2D id_texture, int id_channel,\n    int samples, float radius, float distribution_exponent,\n    bool hard_bevel, float hard_bevel_max_dot\n)\n{\n    vec2 uv = screen_uv();\n\n    vec3 pixel_normal = texture(normal_texture, uv).xyz;\n    float pixel_depth = texture(depth_texture, uv)[depth_channel];\n    vec3 pixel_position = screen_to_camera(uv, pixel_depth);\n\n    float closest_distance = radius;\n    \n    vec3 normal = pixel_normal;\n\n    float screen_radius = radius / pixel_world_size_at(pixel_depth);\n\n    for(int i = 0; i < samples; i++)\n    {\n        vec2 offset = random_per_pixel(i).xy;\n        if(length(offset) > 1)\n        {\n            offset = normalize(offset);\n        }\n        offset = offset * 2.0 - 1.0;\n        if(distribution_exponent > 1)\n        {\n            offset = pow(abs(offset), vec2(distribution_exponent)) * sign(offset);\n        }\n        offset *= screen_radius;\n\n        vec2 offset_uv = uv + (offset / vec2(RESOLUTION));\n        ivec2 offset_texel = ivec2(RESOLUTION * offset_uv);\n\n        if(filter_by_id)\n        {\n            uint offset_id = texelFetch(id_texture, offset_texel, 0)[id_channel];\n            if (offset_id != id)\n            {\n                continue;\n            }\n        }\n\n        vec3 offset_normal = texelFetch(normal_texture, offset_texel, 0).xyz;\n        float offset_depth = texelFetch(depth_texture, offset_texel, 0)[depth_channel];\n\n        vec3 offset_position = screen_to_camera(offset_uv, offset_depth);\n        float offset_distance = distance(pixel_position, offset_position);\n\n        if(offset_distance < radius)\n        {\n            if(hard_bevel)\n            {\n                float offset_dot = dot(pixel_normal, offset_normal);\n                if(offset_dot <= hard_bevel_max_dot)\n                {\n                    if(offset_distance < closest_distance)\n                    {\n                        closest_distance = offset_distance;\n                        normal = offset_normal;\n                    }\n                }\n            }\n            else\n            {\n                normal += offset_normal;\n            }\n        }\n    }\n\n    if(hard_bevel)\n    {\n        float mix_factor = 1.0 - (closest_distance / screen_radius);\n        mix_factor = saturate(pow(mix_factor, distribution_exponent) * distribution_exponent);\n        return normalize(mix(NORMAL, normalize(NORMAL + normalize(normal)), mix_factor));\n    }\n    else\n    {\n        return normalize(normal);\n    }\n}\n\n#endif //FILTERS_BEVEL_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Filters/Blur.glsl",
    "content": "#ifndef BLUR_GLSL\n#define BLUR_GLSL\n\n/*  META GLOBAL\n    @meta: category=Filter; subcategory=Blur;\n*/\n\n/*  META\n    @input_texture: label=Texture;\n    @uv: label=UV; default=UV[0];\n    @radius: default=5.0; min=0.0;\n*/\nvec4 box_blur(sampler2D input_texture, vec2 uv, float radius, bool circular)\n{\n    if(radius <= 1.0)\n    {\n        return texture(input_texture, uv);\n    }\n    vec2 resolution = textureSize(input_texture, 0);\n\n    vec4 result = vec4(0);\n    float total_weight = 0.0;\n\n    for(float x = -radius; x <= radius; x++)\n    {\n        for(float y = -radius; y <= radius; y++)\n        {\n            vec2 offset = vec2(x,y);\n            if(!circular || length(offset) <= radius)\n            {\n                result += texture(input_texture, uv + offset / resolution);\n                total_weight += 1.0;\n            }\n        }   \n    }\n\n    return result / total_weight;\n}\n\nfloat _gaussian_weight(float x, float sigma)\n{\n    float sigma2 = sigma * sigma;\n\n    return (1.0 / sqrt(2*PI*sigma2)) * exp(-(x*x / 2.0*sigma2));\n}\n\nfloat _gaussian_weight_2d(vec2 v, float sigma)\n{\n    float sigma2 = sigma * sigma;\n\n    return (1.0 / (2*PI*sigma2)) * exp(-(dot(v,v) / (2.0*sigma2)));\n}\n\nfloat _gaussian_weight_3d(vec3 v, float sigma)\n{\n\treturn 0.39894*exp(-0.5*dot(v,v)/(sigma*sigma))/sigma;\n}\n\n/*  META\n    @input_texture: label=Texture;\n    @uv: label=UV; default=UV[0];\n    @radius: default=5.0; min=0.0;\n    @sigma: default=1.0;\n*/\nvec4 gaussian_blur(sampler2D input_texture, vec2 uv, float radius, float sigma)\n{\n    if(radius <= 1.0 || sigma <= 0.0)\n    {\n        return texture(input_texture, uv);\n    }\n    vec2 resolution = textureSize(input_texture, 0);\n\n    vec4 result = vec4(0);\n    float total_weight = 0.0;\n\n    for(float x = -radius; x <= radius; x++)\n    {\n        for(float y = -radius; y <= radius; y++)\n        {\n            vec2 offset = vec2(x,y);\n            float weight = _gaussian_weight_2d(offset, sigma);\n            result += texture(input_texture, uv + offset / resolution) * weight;\n            total_weight += weight;\n        }   \n    }\n\n    return result / total_weight;\n}\n\n#include \"Common/Math.glsl\"\n\n/*  META\n    @input_texture: label=Texture;\n    @uv: label=UV; default=UV[0];\n    @radius: default=5.0;\n    @distribution_exponent: default=5.0;\n    @samples: default=8; min=1;\n*/\nvec4 jitter_blur(sampler2D input_texture, vec2 uv, float radius, float distribution_exponent, int samples)\n{\n    if(samples <= 0 || radius <= 0.0)\n    {\n        return texture(input_texture, uv);\n    }\n    vec2 resolution = textureSize(input_texture, 0);\n    vec4 result = vec4(0);\n\n    for(int i = 0; i < samples;  i++)\n    {\n        vec4 random = random_per_pixel(i);\n        float angle = random.x * PI * 2;\n        float length = random.y;\n        length = pow(length, distribution_exponent) * radius;\n        float x = cos(angle) * length;\n        float y = sin(angle) * length;\n        vec2 offset = vec2(x,y) / resolution;\n        result += texture(input_texture, uv + offset) / samples;\n    }\n\n    return result;\n}\n\n/* META\n    @meta: internal=true;\n    @uv: default=UV[0];\n*/\nvec4 tent_blur(sampler2D tex, vec2 uv)\n{\n    // Half pixel offset takes advantage of hardware texture interpolation to achieve a 3x3 gaussian blur\n    // with just 4 samples instead of 9.\n    // The resulting kernel looks like this:\n    //  1  2  1\n    //  2  4  2\n    //  1  2  1\n    vec2 texel = 1.0 / textureSize(tex, 0);\n    return\n        (texture(tex, uv + texel * vec2(-0.5, -0.5)) +\n         texture(tex, uv + texel * vec2(-0.5, +0.5)) +\n         texture(tex, uv + texel * vec2(+0.5, -0.5)) +\n         texture(tex, uv + texel * vec2(+0.5, +0.5))) / 4.0;\n}\n\n/* META\n    @meta: label=Bilateral;\n    @tex: label=Texture;\n    @uv: label=UV; default=UV[0];\n    @radius: default=5; min=0;\n    @sigma: default=10.0; min=0.0;\n    @bsigma: label=BSigma; default=0.1; min=0.0;\n\n*/\nvec4 bilateral_blur(sampler2D input_texture, vec2 uv, float radius, float sigma, float bsigma)\n{\n\n    // Similar to a gaussian blur but in addition to weighting the pixel distance it also weights the color difference.\n    // Is good at preserving edges\n    // Sigma -> controls pixel distance weight\n    // BSigma -> controls color distance weight\n\n    // https://people.csail.mit.edu/sparis/bf_course/course_notes.pdf\n\n    vec2 texel = 1.0 / textureSize(input_texture, 0);\n\n    float total_weight = 0.0;\n    vec3 total_color = vec3(0.0);\n    vec4 main_color = texture(input_texture, uv);\n\n    if(radius <= 0.0 || sigma <= 0.0 || bsigma <= 0.0)\n    {\n        return main_color;\n    }\n\n    for(float u = -radius; u <= radius; u ++)\n    {\n        for(float v = -radius; v <= radius; v++)\n        {\n            vec2 o = vec2(u,v);\n            vec3 color = texture(input_texture, uv + o * texel).rgb;\n\n            float dist_weight = _gaussian_weight_2d(o, sigma);\n            float diff_weight = _gaussian_weight_3d(color - main_color.rgb, bsigma);\n            float weight = dist_weight * diff_weight;\n\n            total_weight += weight;\n            total_color += weight * color;\n        }\n    }\n    return vec4(total_color / total_weight, main_color.a);\n\n}\n\n/* META\n    @meta: label=Orientation-Aligned Bilateral;\n    @tex: label=Texture;\n    @uv: label=UV; default=UV[0];\n    @flow: default='vec2(0)';\n    @radius: default=6.0; min=0.0;\n    @smoothness: default=0.55; min=0.0;\n*/\nvec4 OAB_blur(sampler2D input_texture, vec2 uv, vec2 flow, float radius, float smoothness)\n{\n    // Orientation-aligned Bilateral Blur\n    // Smoothes image while preserving edges by using the local structure of the texture\n\n    if(radius <= 0.0 || smoothness <= 0.0)\n    {\n        return texture(input_texture, uv);\n    }\n    float D = 2 * radius * radius;\n    float R = 2 * smoothness * smoothness;\n\n    vec2 dir = flow;\n    vec2 dir_abs = abs(dir);\n    float d_max = 1.0 / max(dir_abs.x, dir_abs.y);\n    dir *= 1.0 / textureSize(input_texture, 0);\n\n    vec4 main_color = texture(input_texture, uv);\n    vec3 total_color = main_color.rgb;\n    float total_weight = 1.0;\n    float half_width = 2.0 * radius;\n    for(float d = -half_width; d <= half_width; d += d_max)\n    {\n        vec3 color = texture(input_texture, uv + d * dir).rgb;\n        float difference = length(color - main_color.rgb);\n        float dist_influence = exp(-d * d / D);\n        float diff_influence = exp(-difference * difference / R);\n        float local_weight = dist_influence * diff_influence;\n        total_weight += local_weight;\n        total_color += local_weight * color;\n    }\n    return vec4(total_color / total_weight, main_color.a);\n}\n\n#endif //BLUR_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Filters/Curvature.glsl",
    "content": "#ifndef CURVATURE_GLSL\n#define CURVATURE_GLSL\n\n#include \"Common/Math.glsl\"\n\n/*  META GLOBAL\n    @meta: category=Filter;\n*/\n\n/*  META\n    @uv: label=UV; default=UV[0];\n    @width: default=1.0;\n    @x: subtype=Normal; default=vec3(1,0,0);\n    @y: subtype=Normal; default=vec3(0,1,0);\n*/\nfloat curvature(sampler2D normal_texture, vec2 uv, float width, vec3 x, vec3 y)\n{\n    // x and y must be the screen x and y axis in the same coordinate space as the texture normals\n    vec2 offset = vec2(width) / vec2(textureSize(normal_texture, 0));\n\n    vec3 l = texture(normal_texture, uv + vec2(-offset.x,0)).xyz;\n    vec3 r = texture(normal_texture, uv + vec2( offset.x,0)).xyz;\n    vec3 d = texture(normal_texture, uv + vec2(0,-offset.y)).xyz;\n    vec3 u = texture(normal_texture, uv + vec2(0, offset.y)).xyz;\n\n    if(width != 1.0)\n    {\n        l = normalize(l);\n        r = normalize(r);\n        d = normalize(d);\n        u = normalize(u);\n    }\n\n    float curvature = (dot(u,y) - dot(d,y)) + (dot(r,x) - dot(l,x));\n\n    return map_range_clamped(curvature, -1, 1, 0, 1);\n}\n\n#include \"Filters/Line.glsl\"\n\n// Like curvature, but discard depth discontinuities\n/*  META\n    @meta: internal=true;\n    @uv: default=UV[0];\n    @width: default=1.0;\n    @x: subtype=Normal; default=vec3(1,0,0);\n    @y: subtype=Normal; default=vec3(0,1,0);\n    @depth_range: default=0.1;\n*/\nfloat surface_curvature(sampler2D normal_texture, sampler2D depth_texture, int depth_channel, \n    vec2 uv, float width, vec3 x, vec3 y, float depth_range)\n{\n    float curvature = curvature(normal_texture, uv, width, x, y);\n\n    float delta_depth = _line_detection_depth(depth_texture, depth_channel, uv, width, LINE_DEPTH_MODE_ANY);\n\n    delta_depth /= depth_range;\n    delta_depth = clamp(delta_depth, 0, 1);\n\n    return mix(curvature, 0.5, delta_depth);\n}\n\n#endif //CURVATURE_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Filters/JumpFlood.glsl",
    "content": "#ifndef JUMP_FLOOD_GLSL\n#define JUMP_FLOOD_GLSL\n\n/*  META\n    @width: default=1.0;\n*/\n//input should be a texture where x and y are screen space uvs.\n//samples with values == vec(-1,-1) are ignored\n//More info : https://medium.com/@bgolus/the-quest-for-very-wide-outlines-ba82ed442cd9#c5bb\nvec4 jump_flood(sampler2D input_texture, vec2 uv, float width, bool third_channel_scale)\n{\n    vec4 nearest = vec4(-1,-1,-1,-1);\n    vec2 resolution = vec2(textureSize(input_texture, 0));\n    vec2 offset = vec2(width) / resolution;\n\n    for(int x = -1; x <= 1; x++)\n    {\n        for(int y = -1; y <= 1; y++)\n        {\n            vec2 sample_uv = uv + vec2(x,y) * offset;\n            vec4 sampled = texture(input_texture, sample_uv);\n            if(sampled.xy != vec2(-1,-1)) //Check if it's valid\n            {\n                float stored_distance = distance(uv * resolution, nearest.xy * resolution);\n                float sampled_distance = distance(uv * resolution, sampled.xy * resolution);\n                \n                if(third_channel_scale)\n                {\n                    if(nearest.z > 0)\n                    {\n                        stored_distance /= nearest.z;\n                    }\n                    if(sampled.z > 0)\n                    {\n                        sampled_distance /= sampled.z;\n                    }\n                }\n\n                if(nearest.xy == vec2(-1,-1) || sampled_distance < stored_distance)\n                {\n                    nearest = sampled;\n                }\n            }\n        }\n    }\n\n    return nearest;\n}\n\n\n#endif //JUMP_FLOOD_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Filters/Kuwahara.glsl",
    "content": "#ifndef KUWAHARA_GLSL\r\n#define KUWAHARA_GLSL\r\n\r\n/* META GLOBAL\r\n    @meta: category=Filter; subcategory=Kuwahara;\r\n*/\r\n\r\n/*  META\r\n    @meta: label=Isotropic;\r\n    @tex: label=Texture;\r\n    @uv: default = UV[0]; label=UV;\r\n    @size: default=5; min=0;\r\n*/\r\nvec4 kuwahara(sampler2D tex, vec2 uv, int size)\r\n{\r\n    if(size <= 0)\r\n    {\r\n        return texture(tex, uv);\r\n    }\r\n    vec3 mean[4] = vec3[](vec3(0), vec3(0), vec3(0), vec3(0));\r\n    vec3 sigma[4] = vec3[](vec3(0), vec3(0), vec3(0), vec3(0));\r\n\r\n    vec2 offsets[4] = vec2[]\r\n        (\r\n            vec2(-size, -size), \r\n            vec2(-size, 0),\r\n            vec2(0, -size),\r\n            vec2(0, 0)\r\n        );\r\n    \r\n    vec3 color;\r\n    vec2 texel = 1.0 / textureSize(tex, 0);\r\n\r\n    float sigma_f;\r\n    float minimum = 99.0;\r\n\r\n    for(int i = 0; i < 4; i++)\r\n    {\r\n        float total_weight = 0.0;\r\n        for(int u = 0; u <= ceil(size); u++)\r\n        {\r\n            for(int v = 0; v <= ceil(size); v++)\r\n            {\r\n                vec2 offset = (vec2(u,v) + offsets[i]) * texel;\r\n                float weight = max(0, 1.0 - length(offset / texel) / float(size));\r\n                \r\n                color = texture(tex, uv + offset).rgb;\r\n                mean[i] += color * weight;\r\n                sigma[i] += (color * color) * weight;\r\n                total_weight += weight;\r\n            }\r\n        }\r\n        mean[i] /= total_weight;\r\n        sigma[i] = abs(sigma[i] / total_weight - mean[i] * mean[i]);\r\n    }\r\n\r\n    \r\n    for(int i = 0; i < 4; i++)\r\n    {\r\n        sigma_f = sigma[i].r + sigma[i].g + sigma[i].b;\r\n        if(sigma_f < minimum)\r\n        {\r\n            minimum = sigma_f;\r\n            color = mean[i];\r\n        }\r\n    }\r\n    return vec4(color, texture(tex, uv).a);\r\n}\r\n\r\n/* META\r\n    @meta: label=Anisotropic;\r\n    @tex: label=Texture;\r\n    @uv: label=UV; default=UV[0];\r\n    @direction: default='vec2(0.0, 0.0)';\r\n    @size: default=2.0; min=0.0;\r\n    @samples: default=50; min=0;\r\n*/\r\nvec4 anisotropic_kuwahara(sampler2D tex, vec2 uv, vec2 direction, float size, int samples)\r\n{\r\n    if(size <= 0.0 || samples <= 0)\r\n    {\r\n        return texture(tex, uv);\r\n    }\r\n\r\n    vec2 texel = 1.0 / textureSize(tex, 0);\r\n\r\n    float a = atan(direction.y, direction.x);\r\n    mat2 rot_mat = mat2(cos(a), -sin(a), sin(a), cos(a));\r\n\r\n    int segments = 8;\r\n    vec3 mean[8] = vec3[](vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0));\r\n    vec3 sigma[8] = vec3[](vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0));\r\n    float total_weights[8] = float[](0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);\r\n\r\n    for(int i = 0; i < samples; i++)\r\n    {\r\n        vec2 offset = phyllotaxis_disk(float(i), samples);\r\n        float l = length(offset);\r\n        float rad = atan(offset.y, offset.x) / (2.0 * PI) + 0.5;\r\n        int segment_index = int(floor(rad * segments));\r\n        float weight = exp(-(l * l) * 2.0);\r\n        offset *= size * texel;\r\n        offset.x *= length(direction) + 1.0;\r\n        offset = rot_mat * offset;\r\n\r\n        vec3 color = texture(tex, uv + offset).rgb;\r\n        mean[segment_index] += color * weight;\r\n        sigma[segment_index] += (color * color) * weight;\r\n        total_weights[segment_index] += weight;\r\n    }\r\n\r\n    float sigma_f;\r\n    float minimum = 99.0;\r\n    vec3 result;\r\n\r\n    for(int i = 0; i < segments; i++)\r\n    {\r\n        mean[i] /= total_weights[i];\r\n        sigma[i] = abs(sigma[i] / total_weights[i] - mean[i] * mean[i]);\r\n\r\n        sigma_f = sigma[i].r + sigma[i].g + sigma[i].b;\r\n        if(sigma_f < minimum)\r\n        {\r\n            minimum = sigma_f;\r\n            result = mean[i];\r\n        }\r\n    }\r\n    return vec4(result, texture(tex, uv).a);\r\n}\r\n\r\n#endif //KUWAHARA_GLSL\r\n"
  },
  {
    "path": "Malt/Shaders/Filters/Line.glsl",
    "content": "#ifndef LINE_GLSL\n#define LINE_GLSL\n\n#define LINE_DEPTH_MODE_NEAR 0\n#define LINE_DEPTH_MODE_FAR  1\n#define LINE_DEPTH_MODE_ANY  2\n\n/* META GLOBAL\n    @meta: internal=true;\n*/\n\nvoid _sampling_pattern(out vec2 samples[4])\n{\n    samples = vec2[4](\n        vec2(-1,-1),\n        vec2(-1,1),\n        vec2(1,-1),\n        vec2(1,1)\n    );\n\n    samples = vec2[4](\n        vec2(-1, 0),\n        vec2( 1, 0),\n        vec2( 0,-1),\n        vec2( 0, 1)\n    );\n}\n\n//TODO: Remove. Used by surface_curvature\nfloat _line_detection_depth(sampler2D depth_texture, int channel, vec2 uv, float pixel_width, int LINE_DEPTH_MODE)\n{\n    vec2 offsets[4];\n    _sampling_pattern(offsets);\n\n    vec2 offset = vec2(pixel_width) / vec2(textureSize(depth_texture, 0));\n    float depth = texture(depth_texture, uv)[channel];\n    depth = -depth_to_z(depth);\n    float delta = 0.0;\n\n    for(int i = 0; i < offsets.length(); i++)\n    {   \n        float sampled_depth = texture(depth_texture, uv + offsets[i]*offset)[channel];\n        sampled_depth = -depth_to_z(sampled_depth);\n\n        if\n        (\n            LINE_DEPTH_MODE == LINE_DEPTH_MODE_ANY ||\n            LINE_DEPTH_MODE == LINE_DEPTH_MODE_NEAR && depth < sampled_depth ||\n            LINE_DEPTH_MODE == LINE_DEPTH_MODE_FAR && depth > sampled_depth\n        )\n        {\n            delta = max(delta, abs(depth - sampled_depth));\n        }\n    }\n\n    return delta;\n}\n\nstruct LineDetectionOutput\n{\n    float delta_distance;\n    float delta_angle;\n    bvec4 id_boundary;\n};\n\n/*  META\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n    @true_normal: subtype=Normal; default=true_normal();\n    @width: default=1.0;\n    @LINE_DEPTH_MODE: subtype=ENUM(Near, Far, Any);\n    @uv: default=UV[0];\n*/\nLineDetectionOutput line_detection(\n    vec3 position,\n    vec3 normal,\n    vec3 true_normal,\n    float width,\n    int LINE_DEPTH_MODE,\n    vec2 uv,\n    sampler2D depth_texture,\n    int depth_channel,\n    sampler2D normal_texture,\n    usampler2D id_texture\n)\n{\n    LineDetectionOutput result;\n    result.delta_distance = 0.0;\n    result.delta_angle = 1.0;\n    result.id_boundary = bvec4(false);\n\n    vec2 offsets[4];\n    _sampling_pattern(offsets);\n    vec2 offset = vec2(width) / RESOLUTION;\n\n    vec3 true_normal_camera = transform_normal(CAMERA, true_normal);\n    float depth = texture(depth_texture, uv)[depth_channel];\n    position = transform_point(CAMERA, position);\n    uvec4 id = texture(id_texture, uv);\n\n    for(int i = 0; i < offsets.length(); i++)\n    {   \n        vec2 sample_uv = uv + offsets[i]*offset;\n\n        vec3 sampled_normal = texture(normal_texture, sample_uv).xyz;\n        float sampled_depth = texture(depth_texture, sample_uv)[depth_channel];\n        vec3 sampled_position = screen_to_camera(sample_uv, sampled_depth);\n        uvec4 sampled_id = texture(id_texture, sample_uv);\n\n        float delta_distance = 0;\n\n        if(is_ortho(PROJECTION))\n        {\n            //TODO: Use ray-plane intersection here too.\n            delta_distance = abs(sampled_position.z - position.z);\n            delta_distance *= dot(true_normal, -view_direction());\n        }\n        else\n        {\n            vec3 ray_origin = vec3(0);\n            vec3 ray_direction = normalize(sampled_position);\n\n            float expected_distance = ray_plane_intersection\n            (\n                ray_origin, ray_direction,\n                position, true_normal_camera\n            );\n\n            delta_distance = abs(distance(sampled_position, ray_origin) - expected_distance);\n        }\n\n        if\n        (\n            LINE_DEPTH_MODE == LINE_DEPTH_MODE_ANY ||\n            LINE_DEPTH_MODE == LINE_DEPTH_MODE_NEAR && depth <= sampled_depth ||\n            LINE_DEPTH_MODE == LINE_DEPTH_MODE_FAR && depth >= sampled_depth\n        )\n        {\n            result.delta_distance = max(result.delta_distance, delta_distance);\n            result.delta_angle = min(result.delta_angle, dot(normal, sampled_normal));\n            for(int i = 0; i < 4; i++)\n            {\n                result.id_boundary[i] = result.id_boundary[i] || sampled_id[i] != id[i];\n            }\n        }\n    }\n\n    result.delta_angle = acos(result.delta_angle);\n\n    return result;\n}\n\n\nLineDetectionOutput line_detection_2(\n    sampler2D depth_texture,\n    int depth_channel,\n    sampler2D normal_texture,\n    usampler2D id_texture\n)\n{\n    ivec2 uv = screen_pixel();\n\n    LineDetectionOutput result;\n    result.delta_distance = 0.0;\n    result.delta_angle = 0.0;\n    result.id_boundary = bvec4(false);\n\n    float depth = texelFetch(depth_texture, uv, 0)[depth_channel];\n    vec3 normal = texelFetch(normal_texture, uv, 0).xyz;;\n    \n    vec3 position = screen_to_camera(screen_uv(), depth);\n\n    vec3 true_normal = true_normal();\n    //Reconstructing the normal from depth provides more stable results across samples than the \"real\" true_normal\n    //true_normal = reconstruct_normal(depth_texture, depth_channel, uv);\n\n    vec3 average_normal = vec3(0);\n    int radius = 1;\n    for(int x = -radius; x <= radius; x++)\n    {\n        for(int y = -radius; y <= radius; y++)\n        {\n            ivec2 uv = uv + ivec2(x,y);\n            if(uv != clamp(uv, ivec2(0), RESOLUTION))\n            {\n                continue;\n            }\n            vec3 n = reconstruct_normal(depth_texture, depth_channel, uv);\n            average_normal += n;\n        }   \n    }\n    true_normal = normalize(average_normal);\n\n    uvec4 id = texelFetch(id_texture, uv, 0);\n    \n    vec3 true_normal_camera = transform_normal(CAMERA, true_normal);\n\n    vec2 offsets[4]; _sampling_pattern(offsets);\n    vec2 offset = vec2(1.0) / RESOLUTION;\n\n    for(int i = 0; i < offsets.length(); i++)\n    {   \n        ivec2 sample_uv = uv + ivec2(offsets[i]);\n        if(sample_uv != clamp(sample_uv, ivec2(0), RESOLUTION))\n        {\n            continue;\n        }\n        vec2 f_sample_uv = screen_uv() + offsets[i] * offset;\n\n        vec3 sampled_normal = texelFetch(normal_texture, sample_uv, 0).xyz;\n        float sampled_depth = texelFetch(depth_texture, sample_uv, 0)[depth_channel];\n        vec3 sampled_position = screen_to_camera(f_sample_uv , sampled_depth);\n        uvec4 sampled_id = texelFetch(id_texture, sample_uv, 0);\n\n        float delta_normal = dot(normal, sampled_normal);\n        \n        float plane_distance = dot(true_normal_camera, position);\n        float offset_plane_distance = dot(true_normal_camera, sampled_position);\n\n        // Scale by pixel world size so results are more stable at different distances and resolutions\n        float delta_distance = abs(plane_distance - offset_plane_distance) / pixel_world_size_at(sampled_depth);\n\n        /* Alternative depth computation\n        {\n            vec3 ray_origin = vec3(0);\n            vec3 ray_direction = normalize(sampled_position);\n\n            float expected_distance = ray_plane_intersection\n            (\n                ray_origin, ray_direction,\n                position, true_normal_camera\n            );\n            \n            delta_distance = distance(position, sampled_position) / distance(position, ray_origin + ray_direction * expected_distance);\n        }\n        //*/\n\n        if(depth <= sampled_depth)\n        {\n            result.delta_distance = max(result.delta_distance, delta_distance);\n            result.delta_angle = max(result.delta_angle, 1.0 - delta_normal);\n            \n            for(int i = 0; i < 4; i++)\n            {\n                result.id_boundary[i] = result.id_boundary[i] || sampled_id[i] != id[i];\n            }\n        }\n    }\n\n    return result;\n}\n\n\nstruct LineExpandOutput\n{\n    vec4 color;\n    float depth;\n};\n\n/*  META\n    @uv: default=UV[0];\n    @max_width: default=10;\n*/\nLineExpandOutput line_expand(vec2 uv, int max_width,\n                             sampler2D line_color_texture, sampler2D line_width_texture, int line_width_channel, float line_width_scale,\n                             sampler2D depth_texture, int depth_channel, usampler2D id_texture, int id_channel)\n{\n    vec2 resolution = vec2(textureSize(line_color_texture,0));\n\n    int max_half_width = int(ceil(max_width / 2.0));\n\n    float depth = texture(depth_texture, uv)[depth_channel];\n    uint id = texture(id_texture, uv)[id_channel];\n\n    vec4 line_color = vec4(0);\n    float line_depth = 1.0;\n    float line_ldepth = -depth_to_z(line_depth);\n\n    for(int x = -max_half_width; x <= max_half_width; x++)\n    {\n        for(int y = -max_half_width; y <= max_half_width; y++)\n        {\n            vec2 offset = vec2(x,y);\n            float offset_length = length(offset) - 0.5;\n            vec2 offset_uv = uv + offset / resolution;\n\n            float offset_width = texture(line_width_texture, offset_uv)[line_width_channel] * line_width_scale;\n            offset_width = min(offset_width, max_width);\n\n            if(offset_width > 0 && offset_length <= offset_width / 2.0)\n            {\n                vec4 offset_line_color = texture(line_color_texture, offset_uv);\n                float offset_line_depth = texture(depth_texture, offset_uv)[depth_channel];\n                float offset_line_ldepth = -depth_to_z(offset_line_depth);\n                uint offset_line_id = texture(id_texture, offset_uv)[id_channel];\n\n                if(offset_length <= 0 && offset_width <= 1.0)\n                {\n                    offset_line_color.a *= offset_width;\n                }\n                else\n                {\n                    offset_line_color.a *= clamp(offset_width / 2.0 - offset_length, 0.0, 1.0);\n                }\n                \n                float alpha = offset_line_color.a;\n                float random_ldepth_offset = hash(vec4(offset, float(SAMPLE_COUNT), hash(uv).x)).x;\n\n                offset_line_ldepth += random_ldepth_offset * offset_width * 0.5 * pixel_world_size_at(offset_line_depth);\n\n                bool override = false;\n                \n                if(alpha == line_color.a && offset_line_ldepth < line_ldepth)\n                {\n                    override = true;\n                }\n                \n                if(alpha > line_color.a)\n                {\n                    override = true;\n                }\n\n                if(offset_line_id != id && depth < offset_line_depth)\n                {\n                    override = false;\n                }\n\n                if(override)\n                {\n                    line_color = offset_line_color;\n                    line_ldepth = offset_line_ldepth;\n                    line_depth = offset_line_depth;\n                }\n            }\n        }\n    }\n    \n    return LineExpandOutput(line_color, line_depth);\n}\n\n#endif //LINE_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Filters/Sharpen.glsl",
    "content": "#ifndef SHARPEN_GLSL\r\n#define SHARPEN_GLSL\r\n\r\n#include \"Filters/Blur.glsl\"\r\n\r\n/*  META GLOBAL\r\n    @meta: category=Filter; subcategory=Sharpen;\r\n*/\r\n\r\nvec4 _sharpen_common(sampler2D tex, vec2 uv, vec4 blurred, float sharpness)\r\n{\r\n    vec4 base = texture(tex, uv);\r\n    float scaler = 10.0; // Use scalar to put the range of the sharpness value mostly between 0-1 for convenience\r\n    return base + (base - blurred) * sharpness * scaler;\r\n}\r\n\r\n/* META\r\n    @meta: label=Box;\r\n    @tex: label=Texture;\r\n    @uv: label=UV; default = UV[0];\r\n    @radius: default=1.0; min=0.0;\r\n    @sharpness: default = 0.3; min=0.0;\r\n*/\r\nvec4 box_sharpen(sampler2D tex, vec2 uv, float radius, bool circular, float sharpness)\r\n{\r\n    vec4 blurred = box_blur(tex, uv, radius, circular);\r\n    return _sharpen_common(tex, uv, blurred, sharpness);\r\n}\r\n\r\n/* META\r\n    @meta: label=Gaussian;\r\n    @tex: label=Texture;\r\n    @uv: label=UV; default = UV[0];\r\n    @radius: default=1.0; min=0.0;\r\n    @sigma: default=1.0;\r\n    @sharpness: default = 0.3; min=0.0;\r\n*/\r\nvec4 gaussian_sharpen(sampler2D tex, vec2 uv, float radius, float sigma, float sharpness)\r\n{\r\n    vec4 blurred = gaussian_blur(tex, uv, radius, sigma);\r\n    return _sharpen_common(tex, uv, blurred, sharpness);\r\n}\r\n\r\n/* META\r\n    @meta: label=Jitter;\r\n    @tex: label=Texture;\r\n    @uv: label=UV; default = UV[0];\r\n    @radius: default=1.0; min=0.0;\r\n    @distribution_exponent: default=5.0;\r\n    @samples: default=8; min=1;\r\n    @sharpness: default = 0.3; min=0.0;\r\n*/\r\nvec4 jitter_sharpen(sampler2D tex, vec2 uv, float radius, float distribution_exponent, int samples, float sharpness)\r\n{\r\n    vec4 blurred = jitter_blur(tex, uv, radius, distribution_exponent, samples);\r\n    return _sharpen_common(tex, uv, blurred, sharpness);\r\n}\r\n\r\n\r\n#endif //SHARPEN_GLSL\r\n"
  },
  {
    "path": "Malt/Shaders/Filters/StructureTensor.glsl",
    "content": "#ifndef STRUCTURE_TENSOR_GLSL\r\n#define STRUCTURE_TENSOR_GLSL\r\n\r\n// Described in: https://en.wikipedia.org/wiki/Structure_tensor\r\n\r\n/* META\r\n    @meta: category=Filter; internal=true;\r\n    @uv: default=UV[0];\r\n*/\r\nvec3 structure_tensor(sampler2D tex, vec2 uv)\r\n{\r\n    vec2 texel = 1.0 / textureSize(tex, 0);\r\n\r\n    // Stores the texel offset in x,y and the weight in z. the cells with weight 0 are ignored. \r\n    // Notice that for v, the axes of the kernel are flipped to effectively rotate the kernel.\r\n    //   -1  0  1\r\n    //   -2  0  2\r\n    //   -1  0  1\r\n    vec3 sobel_kernel[6] = vec3[](\r\n        vec3(-1, -1, -1), vec3(+1, -1, +1),\r\n        vec3(-1, +0, -2), vec3(+1, +0, +2),\r\n        vec3(-1, +1, -1), vec3(+1, -1, +1)\r\n    );\r\n\r\n    vec3 u = vec3(0.0);\r\n    vec3 v = vec3(0.0);\r\n    for(int i = 0; i < 6; i++)\r\n    {\r\n        vec3 k = sobel_kernel[i];\r\n        u += texture(tex, uv + texel * k.xy).xyz * k.z;\r\n        v += texture(tex, uv + texel * k.yx).xyz * k.z;\r\n    }\r\n    u /= 4.0;\r\n    v /= 4.0;\r\n    \r\n    return vec3(\r\n        dot(u, u),\r\n        dot(v, v),\r\n        dot(u, v)\r\n    );\r\n}\r\n\r\n/* META @meta: internal=true; */\r\nvec3 flow_from_structure(vec3 s)\r\n{\r\n    float l = 0.5 * (s.y + s.x +sqrt(s.y*s.y - 2.0*s.x*s.y + s.x*s.x + 4.0*s.z*s.z));\r\n    vec2 d = vec2(s.x - l, s.z);\r\n    return (length(d) > 0.0)? vec3(normalize(d), sqrt(l)) : vec3(0,1,0);\r\n}\r\n\r\n#endif //STRUCTURE_TENSOR_GLSL\r\n"
  },
  {
    "path": "Malt/Shaders/Intellisense/intellisense.glsl",
    "content": "\n//This file contains a series of C++ macros, structs and function declarations\n//to make GLSL autocompletion work with C++ autocompletion implementations\n\n#ifdef __INTELLISENSE__\n\n//Define GLSL keywords\n\n#define in\n#define out\n#define inout\n#define uniform\n#define varying\n#define layout(index)\n#define discard\n#define uint unsigned int\n#define atomic_uint uint\n\n\n//Declare GLSL built-in types\n\nstruct vec2 {};\nstruct vec3 {};\nstruct vec4 {};\nstruct dvec2 {};\nstruct dvec3 {};\nstruct dvec4 {};\nstruct ivec2 {};\nstruct ivec3 {};\nstruct ivec4 {};\nstruct uvec2 {};\nstruct uvec3 {};\nstruct uvec4 {};\nstruct bvec2 {};\nstruct bvec3 {};\nstruct bvec4 {};\nstruct mat2 {};\nstruct dmat2 {};\nstruct mat3 {};\nstruct dmat3 {};\nstruct mat4 {};\nstruct dmat4 {};\nstruct mat2x2 {};\nstruct dmat2x2 {};\nstruct mat2x3 {};\nstruct dmat2x3 {};\nstruct mat2x4 {};\nstruct dmat2x4 {};\nstruct mat3x2 {};\nstruct dmat3x2 {};\nstruct mat3x3 {};\nstruct dmat3x3 {};\nstruct mat3x4 {};\nstruct dmat3x4 {};\nstruct mat4x2 {};\nstruct dmat4x2 {};\nstruct mat4x3 {};\nstruct dmat4x3 {};\nstruct mat4x4 {};\nstruct dmat4x4 {};\nstruct sampler1D {};\nstruct isampler1D {};\nstruct usampler1D {};\nstruct sampler2D {};\nstruct isampler2D {};\nstruct usampler2D {};\nstruct sampler3D {};\nstruct isampler3D {};\nstruct usampler3D {};\nstruct sampler2DRect {};\nstruct isampler2DRect {};\nstruct usampler2DRect {};\nstruct sampler1DArray {};\nstruct isampler1DArray {};\nstruct usampler1DArray {};\nstruct sampler2DArray {};\nstruct isampler2DArray {};\nstruct usampler2DArray {};\nstruct samplerBuffer {};\nstruct isamplerBuffer {};\nstruct usamplerBuffer {};\nstruct sampler2DMS {};\nstruct isampler2DMS {};\nstruct usampler2DMS {};\nstruct sampler2DMSArray {};\nstruct isampler2DMSArray {};\nstruct usampler2DMSArray {};\nstruct samplerCube {};\nstruct isamplerCube {};\nstruct usamplerCube {};\nstruct samplerCubeArray {};\nstruct isamplerCubeArray {};\nstruct usamplerCubeArray {};\nstruct sampler2DDArray {};\nstruct isampler2DDArray {};\nstruct usampler2DDArray {};\nstruct samplerRect {};\nstruct isamplerRect {};\nstruct usamplerRect {};\n\n\n//Declare GLSL standard library functions\n\n//return the absolute value of the parameter\ntemplate<typename T> T abs(T x);\n//return the absolute value of the parameter\ntemplate<typename I> I abs(I x);\n//return the absolute value of the parameter\ntemplate<typename D> D abs(D x);\n//return the arccosine of the parameter\ntemplate<typename T> T acos(T x);\n//return the arc hyperbolic cosine of the parameter\ntemplate<typename T> T acosh(T x);\n//check whether all elements of a boolean vector are true\ntemplate<typename bvec> bool all(bvec x);\n//check whether any element of a boolean vector is true\ntemplate<typename bvec> bool any(bvec x);\n//return the arcsine of the parameter\ntemplate<typename T> T asin(T x);\n//return the arc hyperbolic sine of the parameter\ntemplate<typename T> T asinh(T x);\n//return the arc-tangent of the parameters\ntemplate<typename T> T atan(T y, T x);\n//return the arc-tangent of the parameters\ntemplate<typename T> T atan(T y_over_x);\n//return the arc hyperbolic tangent of the parameter\ntemplate<typename T> T atanh(T x);\n//counts the number of 1 bits in an integer\ntemplate<typename I> I bitCount(I value);\n//counts the number of 1 bits in an integer\ntemplate<typename I, typename U> I bitCount(U value);\n//extract a range of bits from an integer\ntemplate<typename I> I bitfieldExtract(I value, int offset, int bits);\n//extract a range of bits from an integer\ntemplate<typename U> U bitfieldExtract(U value, int offset, int bits);\n//insert a range of bits into an integer\ntemplate<typename I> I bitfieldInsert(I base, I insert, int offset, int bits);\n//insert a range of bits into an integer\ntemplate<typename U> U bitfieldInsert(U base, U insert, int offset, int bits);\n//reverse the order of bits in an integer\ntemplate<typename I> I bitfieldReverse(I value);\n//reverse the order of bits in an integer\ntemplate<typename U> U bitfieldReverse(U value);\n//find the nearest integer that is greater than or equal to the parameter\ntemplate<typename T> T ceil(T x);\n//find the nearest integer that is greater than or equal to the parameter\ntemplate<typename D> D ceil(D x);\n//constrain a value to lie between two further values\ntemplate<typename T> T clamp(T x, T minVal, T maxVal);\n//constrain a value to lie between two further values\ntemplate<typename T> T clamp(T x, float minVal, float maxVal);\n//constrain a value to lie between two further values\ntemplate<typename D> D clamp(D x, D minVal, D maxVal);\n//constrain a value to lie between two further values\ntemplate<typename D> D clamp(D x, double minVal, double maxVal);\n//constrain a value to lie between two further values\ntemplate<typename I> I clamp(I x, I minVal, I maxVal);\n//constrain a value to lie between two further values\ntemplate<typename I> I clamp(I x, int minVal, int maxVal);\n//constrain a value to lie between two further values\ntemplate<typename U> U clamp(U x, U minVal, U maxVal);\n//constrain a value to lie between two further values\ntemplate<typename U> U clamp(U x, uint minVal, uint maxVal);\n//return the cosine of the parameter\ntemplate<typename T> T cos(T angle);\n//return the hyperbolic cosine of the parameter\ntemplate<typename T> T cosh(T x);\n//calculate the cross product of two vectors\nvec3 cross(vec3 x, vec3 y);\n//calculate the cross product of two vectors\ndvec3 cross(dvec3 x, dvec3 y);\n//convert a quantity in radians to degrees\ntemplate<typename T> T degrees(T radians);\n//calculate the determinant of a matrix\nfloat determinant(mat2 m);\n//calculate the determinant of a matrix\nfloat determinant(mat3 m);\n//calculate the determinant of a matrix\nfloat determinant(mat4 m);\n//calculate the determinant of a matrix\ndouble determinant(dmat2 m);\n//calculate the determinant of a matrix\ndouble determinant(dmat3 m);\n//calculate the determinant of a matrix\ndouble determinant(dmat4 m);\n//return the partial derivative of an argument with respect to x or y\ntemplate<typename T> T dFdx(T p);\n//return the partial derivative of an argument with respect to x or y\ntemplate<typename T> T dFdy(T p);\n//return the partial derivative of an argument with respect to x or y\ntemplate<typename T> T dFdxCoarse(T p);\n//return the partial derivative of an argument with respect to x or y\ntemplate<typename T> T dFdyCoarse(T p);\n//return the partial derivative of an argument with respect to x or y\ntemplate<typename T> T dFdxFine(T p);\n//return the partial derivative of an argument with respect to x or y\ntemplate<typename T> T dFdyFine(T p);\n//calculate the distance between two points\ntemplate<typename T> float distance(T p0, T p1);\n//calculate the distance between two points\ntemplate<typename D> double distance(D p0, D p1);\n//calculate the dot product of two vectors\ntemplate<typename T> float dot(T x, T y);\n//calculate the dot product of two vectors\ntemplate<typename D> double dot(D x, D y);\n//emit a vertex to a specified stream\nvoid EmitStreamVertex(int stream);\n//emit a vertex to the first vertex stream\nvoid EmitVertex();\n//complete the current output primitive on the first vertex stream\nvoid EndPrimitive();\n//complete the current output primitive on a specified stream\nvoid EndStreamPrimitive(int stream);\n//perform a component-wise equal-to comparison of two vectors\ntemplate<typename bvec, typename vec> bvec equal(vec x, vec y);\n//perform a component-wise equal-to comparison of two vectors\ntemplate<typename bvec, typename ivec> bvec equal(ivec x, ivec y);\n//perform a component-wise equal-to comparison of two vectors\ntemplate<typename bvec, typename uvec> bvec equal(uvec x, uvec y);\n//return the natural exponentiation of the parameter\ntemplate<typename T> T exp(T x);\n//return 2 raised to the power of the parameter\ntemplate<typename T> T exp2(T x);\n//return a vector pointing in the same direction as another\ntemplate<typename T> T faceforward(T N, T I, T Nref);\n//return a vector pointing in the same direction as another\ntemplate<typename D> D faceforward(D N, D I, D Nref);\n//find the index of the least significant bit set to 1 in an integer\ntemplate<typename I> I findLSB(I value);\n//find the index of the least significant bit set to 1 in an integer\ntemplate<typename I, typename U> I findLSB(U value);\n//find the index of the most significant bit set to 1 in an integer\ntemplate<typename I> I findMSB(I value);\n//find the index of the most significant bit set to 1 in an integer\ntemplate<typename I, typename U> I findMSB(U value);\n//produce the encoding of a floating point value as an integer\ntemplate<typename I, typename T> I floatBitsToInt(T x);\n//produce the encoding of a floating point value as an integer\ntemplate<typename U, typename T> U floatBitsToUint(T x);\n//find the nearest integer less than or equal to the parameter\ntemplate<typename T> T floor(T x);\n//find the nearest integer less than or equal to the parameter\ntemplate<typename D> D floor(D x);\n//perform a fused multiply-add operation\ntemplate<typename T> T fma(T a, T b, T c);\n//perform a fused multiply-add operation\ntemplate<typename D> D fma(D a, D b, D c);\n//compute the fractional part of the argument\ntemplate<typename T> T fract(T x);\n//compute the fractional part of the argument\ntemplate<typename D> D fract(D x);\n//split a floating point number\ntemplate<typename T, typename I> T frexp(T x, out I exp);\n//split a floating point number\ntemplate<typename D, typename I> D frexp(D x, out I exp);\n//return the sum of the absolute value of derivatives in x and y\ntemplate<typename T> T fwidth(T p);\n//return the sum of the absolute value of derivatives in x and y\ntemplate<typename T> T fwidthCoarse(T p);\n//return the sum of the absolute value of derivatives in x and y\ntemplate<typename T> T fwidthFine(T p);\n//perform a component-wise greater-than comparison of two vectors\ntemplate<typename bvec, typename vec> bvec greaterThan(vec x, vec y);\n//perform a component-wise greater-than comparison of two vectors\ntemplate<typename bvec, typename ivec> bvec greaterThan(ivec x, ivec y);\n//perform a component-wise greater-than comparison of two vectors\ntemplate<typename bvec, typename uvec> bvec greaterThan(uvec x, uvec y);\n//perform a component-wise greater-than-or-equal comparison of two vectors\ntemplate<typename bvec, typename vec> bvec greaterThanEqual(vec x, vec y);\n//perform a component-wise greater-than-or-equal comparison of two vectors\ntemplate<typename bvec, typename ivec> bvec greaterThanEqual(ivec x, ivec y);\n//perform a component-wise greater-than-or-equal comparison of two vectors\ntemplate<typename bvec, typename uvec> bvec greaterThanEqual(uvec x, uvec y);\n//produce a floating point using an encoding supplied as an integer\ntemplate<typename T, typename I> T intBitsToFloat(I x);\n//produce a floating point using an encoding supplied as an integer\ntemplate<typename T, typename U> T uintBitsToFloat(U x);\n//sample a varying at the centroid of a pixel\nfloat interpolateAtCentroid(float interpolant);\n//sample a varying at the centroid of a pixel\nvec2 interpolateAtCentroid(vec2 interpolant);\n//sample a varying at the centroid of a pixel\nvec3 interpolateAtCentroid(vec3 interpolant);\n//sample a varying at the centroid of a pixel\nvec4 interpolateAtCentroid(vec4 interpolant);\n//sample a varying at specified offset from the center of a pixel\nfloat interpolateAtOffset(float interpolant, vec2 offset);\n//sample a varying at specified offset from the center of a pixel\nvec2 interpolateAtOffset(vec2 interpolant, vec2 offset);\n//sample a varying at specified offset from the center of a pixel\nvec3 interpolateAtOffset(vec3 interpolant, vec2 offset);\n//sample a varying at specified offset from the center of a pixel\nvec4 interpolateAtOffset(vec4 interpolant, vec2 offset);\n//sample a varying at the location of a specified sample\nfloat interpolateAtSample(float interpolant, int sample);\n//sample a varying at the location of a specified sample\nvec2 interpolateAtSample(vec2 interpolant, int sample);\n//sample a varying at the location of a specified sample\nvec3 interpolateAtSample(vec3 interpolant, int sample);\n//sample a varying at the location of a specified sample\nvec4 interpolateAtSample(vec4 interpolant, int sample);\n//calculate the inverse of a matrix\nmat2 inverse(mat2 m);\n//calculate the inverse of a matrix\nmat3 inverse(mat3 m);\n//calculate the inverse of a matrix\nmat4 inverse(mat4 m);\n//calculate the inverse of a matrix\ndmat2 inverse(dmat2 m);\n//calculate the inverse of a matrix\ndmat3 inverse(dmat3 m);\n//calculate the inverse of a matrix\ndmat4 inverse(dmat4 m);\n//return the inverse of the square root of the parameter\ntemplate<typename T> T inversesqrt(T x);\n//return the inverse of the square root of the parameter\ntemplate<typename D> D inversesqrt(D x);\n//determine whether the parameter is positive or negative infinity\ntemplate<typename B, typename T> B isinf(T x);\n//determine whether the parameter is positive or negative infinity\ntemplate<typename B, typename D> B isinf(D x);\n//determine whether the parameter is a number\ntemplate<typename B, typename T> B isnan(T x);\n//determine whether the parameter is a number\ntemplate<typename B, typename D> B isnan(D x);\n//assemble a floating point number from a value and exponent\ntemplate<typename T, typename I> T ldexp(T x, I exp);\n//assemble a floating point number from a value and exponent\ntemplate<typename D, typename I> D ldexp(D x, I exp);\n//calculate the length of a vector\ntemplate<typename T> float length(T x);\n//calculate the length of a vector\ntemplate<typename D> double length(D x);\n//perform a component-wise less-than comparison of two vectors\ntemplate<typename bvec, typename vec> bvec lessThan(vec x, vec y);\n//perform a component-wise less-than comparison of two vectors\ntemplate<typename bvec, typename ivec> bvec lessThan(ivec x, ivec y);\n//perform a component-wise less-than comparison of two vectors\ntemplate<typename bvec, typename uvec> bvec lessThan(uvec x, uvec y);\n//perform a component-wise less-than-or-equal comparison of two vectors\ntemplate<typename bvec, typename vec> bvec lessThanEqual(vec x, vec y);\n//perform a component-wise less-than-or-equal comparison of two vectors\ntemplate<typename bvec, typename ivec> bvec lessThanEqual(ivec x, ivec y);\n//perform a component-wise less-than-or-equal comparison of two vectors\ntemplate<typename bvec, typename uvec> bvec lessThanEqual(uvec x, uvec y);\n//return the natural logarithm of the parameter\ntemplate<typename T> T log(T x);\n//return the base 2 logarithm of the parameter\ntemplate<typename T> T log2(T x);\n//perform a component-wise multiplication of two matrices\ntemplate<typename mat> mat matrixCompMult(mat x, mat y);\n//perform a component-wise multiplication of two matrices\ntemplate<typename dmat> dmat matrixCompMult(dmat x, dmat y);\n//return the greater of two values\ntemplate<typename T> T max(T x, T y);\n//return the greater of two values\ntemplate<typename T> T max(T x, float y);\n//return the greater of two values\ntemplate<typename D> D max(D x, D y);\n//return the greater of two values\ntemplate<typename D> D max(D x, double y);\n//return the greater of two values\ntemplate<typename I> I max(I x, I y);\n//return the greater of two values\ntemplate<typename I> I max(I x, int y);\n//return the greater of two values\ntemplate<typename U> U max(U x, U y);\n//return the greater of two values\ntemplate<typename U> U max(U x, uint y);\n//return the lesser of two values\ntemplate<typename T> T min(T x, T y);\n//return the lesser of two values\ntemplate<typename T> T min(T x, float y);\n//return the lesser of two values\ntemplate<typename D> D min(D x, D y);\n//return the lesser of two values\ntemplate<typename D> D min(D x, double y);\n//return the lesser of two values\ntemplate<typename I> I min(I x, I y);\n//return the lesser of two values\ntemplate<typename I> I min(I x, int y);\n//return the lesser of two values\ntemplate<typename U> U min(U x, U y);\n//return the lesser of two values\ntemplate<typename U> U min(U x, uint y);\n//linearly interpolate between two values\ntemplate<typename T> T mix(T x, T y, T a);\n//linearly interpolate between two values\ntemplate<typename T> T mix(T x, T y, float a);\n//linearly interpolate between two values\ntemplate<typename D> D mix(D x, D y, D a);\n//linearly interpolate between two values\ntemplate<typename D> D mix(D x, D y, double a);\n//linearly interpolate between two values\ntemplate<typename T, typename B> T mix(T x, T y, B a);\n//linearly interpolate between two values\ntemplate<typename D, typename B> D mix(D x, D y, B a);\n//linearly interpolate between two values\ntemplate<typename I, typename B> I mix(I x, I y, B a);\n//linearly interpolate between two values\ntemplate<typename U, typename B> U mix(U x, U y, B a);\n//linearly interpolate between two values\ntemplate<typename B> B mix(B x, B y, B a);\n//compute value of one parameter modulo another\ntemplate<typename T> T mod(T x, float y);\n//compute value of one parameter modulo another\ntemplate<typename T> T mod(T x, T y);\n//compute value of one parameter modulo another\ntemplate<typename D> D mod(D x, double y);\n//compute value of one parameter modulo another\ntemplate<typename D> D mod(D x, D y);\n//separate a value into its integer and fractional components\ntemplate<typename T> T modf(T x, out T i);\n//separate a value into its integer and fractional components\ntemplate<typename D> D modf(D x, out D i);\n//calculates the unit vector in the same direction as the original vector\ntemplate<typename T> T normalize(T v);\n//calculates the unit vector in the same direction as the original vector\ntemplate<typename D> D normalize(D v);\n//logically invert a boolean vector\ntemplate<typename bvec> bvec not(bvec x);\n//perform a component-wise not-equal-to comparison of two vectors\ntemplate<typename bvec, typename vec> bvec notEqual(vec x, vec y);\n//perform a component-wise not-equal-to comparison of two vectors\ntemplate<typename bvec, typename ivec> bvec notEqual(ivec x, ivec y);\n//perform a component-wise not-equal-to comparison of two vectors\ntemplate<typename bvec, typename uvec> bvec notEqual(uvec x, uvec y);\n//calculate the outer product of a pair of vectors\nmat2 outerProduct(vec2 c, vec2 r);\n//calculate the outer product of a pair of vectors\nmat3 outerProduct(vec3 c, vec3 r);\n//calculate the outer product of a pair of vectors\nmat4 outerProduct(vec4 c, vec4 r);\n//calculate the outer product of a pair of vectors\nmat2x3 outerProduct(vec3 c, vec2 r);\n//calculate the outer product of a pair of vectors\nmat3x2 outerProduct(vec2 c, vec3 r);\n//calculate the outer product of a pair of vectors\nmat2x4 outerProduct(vec4 c, vec2 r);\n//calculate the outer product of a pair of vectors\nmat4x2 outerProduct(vec2 c, vec4 r);\n//calculate the outer product of a pair of vectors\nmat3x4 outerProduct(vec4 c, vec3 r);\n//calculate the outer product of a pair of vectors\nmat4x3 outerProduct(vec3 c, vec4 r);\n//calculate the outer product of a pair of vectors\ndmat2 outerProduct(dvec2 c, dvec2 r);\n//calculate the outer product of a pair of vectors\ndmat3 outerProduct(dvec3 c, dvec3 r);\n//calculate the outer product of a pair of vectors\ndmat4 outerProduct(dvec4 c, dvec4 r);\n//calculate the outer product of a pair of vectors\ndmat2x3 outerProduct(dvec3 c, dvec2 r);\n//calculate the outer product of a pair of vectors\ndmat3x2 outerProduct(dvec2 c, dvec3 r);\n//calculate the outer product of a pair of vectors\ndmat2x4 outerProduct(dvec4 c, dvec2 r);\n//calculate the outer product of a pair of vectors\ndmat4x2 outerProduct(dvec2 c, dvec4 r);\n//calculate the outer product of a pair of vectors\ndmat3x4 outerProduct(dvec4 c, dvec3 r);\n//calculate the outer product of a pair of vectors\ndmat4x3 outerProduct(dvec3 c, dvec4 r);\n//create a double-precision value from a pair of unsigned integers\ndouble packDouble2x32(uvec2 v);\n//convert two 32-bit floating-point quantities to 16-bit quantities and pack them into a single 32-bit integer\nuint packHalf2x16(vec2 v);\n//pack floating-point values into an unsigned integer\nuint packUnorm2x16(vec2 v);\n//pack floating-point values into an unsigned integer\nuint packSnorm2x16(vec2 v);\n//pack floating-point values into an unsigned integer\nuint packUnorm4x8(vec4 v);\n//pack floating-point values into an unsigned integer\nuint packSnorm4x8(vec4 v);\n//return the value of the first parameter raised to the power of the second\ntemplate<typename T> T pow(T x, T y);\n//convert a quantity in degrees to radians\ntemplate<typename T> T radians(T degrees);\n//calculate the reflection direction for an incident vector\ntemplate<typename T> T reflect(T I, T N);\n//calculate the reflection direction for an incident vector\ntemplate<typename D> D reflect(D I, D N);\n//calculate the refraction direction for an incident vector\ntemplate<typename T> T refract(T I, T N, float eta);\n//calculate the refraction direction for an incident vector\ntemplate<typename D> D refract(D I, D N, float eta);\n//find the nearest integer to the parameter\ntemplate<typename T> T round(T x);\n//find the nearest integer to the parameter\ntemplate<typename D> D round(D x);\n//find the nearest even integer to the parameter\ntemplate<typename T> T roundEven(T x);\n//find the nearest even integer to the parameter\ntemplate<typename D> D roundEven(D x);\n//extract the sign of the parameter\ntemplate<typename T> T sign(T x);\n//extract the sign of the parameter\ntemplate<typename I> I sign(I x);\n//extract the sign of the parameter\ntemplate<typename D> D sign(D x);\n//return the sine of the parameter\ntemplate<typename T> T sin(T angle);\n//return the hyperbolic sine of the parameter\ntemplate<typename T> T sinh(T x);\n//perform Hermite interpolation between two values\ntemplate<typename T> T smoothstep(T edge0, T edge1, T x);\n//perform Hermite interpolation between two values\ntemplate<typename T> T smoothstep(float edge0, float edge1, T x);\n//perform Hermite interpolation between two values\ntemplate<typename D> D smoothstep(D edge0, D edge1, D x);\n//perform Hermite interpolation between two values\ntemplate<typename D> D smoothstep(double edge0, double edge1, D x);\n//return the square root of the parameter\ntemplate<typename T> T sqrt(T x);\n//return the square root of the parameter\ntemplate<typename D> D sqrt(D x);\n//generate a step function by comparing two values\ntemplate<typename T> T step(T edge, T x);\n//generate a step function by comparing two values\ntemplate<typename T> T step(float edge, T x);\n//generate a step function by comparing two values\ntemplate<typename D> D step(D edge, D x);\n//generate a step function by comparing two values\ntemplate<typename D> D step(double edge, D x);\n//return the tangent of the parameter\ntemplate<typename T> T tan(T angle);\n//return the hyperbolic tangent of the parameter\ntemplate<typename T> T tanh(T x);\n//perform a lookup of a single texel within a texture\ntemplate<typename gvec4, typename gsampler1D> gvec4 texelFetch(gsampler1D sampler, int P, int lod);\n//perform a lookup of a single texel within a texture\ntemplate<typename gvec4, typename gsampler2D> gvec4 texelFetch(gsampler2D sampler, ivec2 P, int lod);\n//perform a lookup of a single texel within a texture\ntemplate<typename gvec4, typename gsampler3D> gvec4 texelFetch(gsampler3D sampler, ivec3 P, int lod);\n//perform a lookup of a single texel within a texture\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 texelFetch(gsampler2DRect sampler, ivec2 P);\n//perform a lookup of a single texel within a texture\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 texelFetch(gsampler1DArray sampler, ivec2 P, int lod);\n//perform a lookup of a single texel within a texture\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 texelFetch(gsampler2DArray sampler, ivec3 P, int lod);\n//perform a lookup of a single texel within a texture\ntemplate<typename gvec4, typename gsamplerBuffer> gvec4 texelFetch(gsamplerBuffer sampler, int P);\n//perform a lookup of a single texel within a texture\ntemplate<typename gvec4, typename gsampler2DMS> gvec4 texelFetch(gsampler2DMS sampler, ivec2 P, int sample);\n//perform a lookup of a single texel within a texture\ntemplate<typename gvec4, typename gsampler2DMSArray> gvec4 texelFetch(gsampler2DMSArray sampler, ivec3 P, int sample);\n//perform a lookup of a single texel within a texture with an offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 texelFetchOffset(gsampler1D sampler, int P, int lod, int offset);\n//perform a lookup of a single texel within a texture with an offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 texelFetchOffset(gsampler2D sampler, ivec2 P, int lod, ivec2 offset);\n//perform a lookup of a single texel within a texture with an offset\ntemplate<typename gvec4, typename gsampler3D> gvec4 texelFetchOffset(gsampler3D sampler, ivec3 P, int lod, ivec3 offset);\n//perform a lookup of a single texel within a texture with an offset\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 texelFetchOffset(gsampler2DRect sampler, ivec2 P, ivec2 offset);\n//perform a lookup of a single texel within a texture with an offset\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 texelFetchOffset(gsampler1DArray sampler, ivec2 P, int lod, ivec2 offset);\n//perform a lookup of a single texel within a texture with an offset\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 texelFetchOffset(gsampler2DArray sampler, ivec3 P, int lod, ivec3 offset);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler1D> gvec4 texture(gsampler1D sampler, float P);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler1D> gvec4 texture(gsampler1D sampler, float P, float  bias);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler2D> gvec4 texture(gsampler2D sampler, vec2 P);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler2D> gvec4 texture(gsampler2D sampler, vec2 P, float  bias);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler3D> gvec4 texture(gsampler3D sampler, vec3 P);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler3D> gvec4 texture(gsampler3D sampler, vec3 P, float  bias);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsamplerCube> gvec4 texture(gsamplerCube sampler, vec3 P);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsamplerCube> gvec4 texture(gsamplerCube sampler, vec3 P, float  bias);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 texture(gsampler1DArray sampler, vec2 P);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 texture(gsampler1DArray sampler, vec2 P, float  bias);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 texture(gsampler2DArray sampler, vec3 P);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 texture(gsampler2DArray sampler, vec3 P, float  bias);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsamplerCubeArray> gvec4 texture(gsamplerCubeArray sampler, vec4 P);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsamplerCubeArray> gvec4 texture(gsamplerCubeArray sampler, vec4 P, float  bias);\n//retrieves texels from a texture\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 texture(gsampler2DRect sampler, vec2 P);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureGather(gsampler2D sampler, vec2 P);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureGather(gsampler2D sampler, vec2 P, int comp);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureGather(gsampler2DArray sampler, vec3 P);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureGather(gsampler2DArray sampler, vec3 P, int comp);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsamplerCube> gvec4 textureGather(gsamplerCube sampler, vec3 P);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsamplerCube> gvec4 textureGather(gsamplerCube sampler, vec3 P, int comp);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsamplerCubeArray> gvec4 textureGather(gsamplerCubeArray sampler, vec4 P);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsamplerCubeArray> gvec4 textureGather(gsamplerCubeArray sampler, vec4 P, int comp);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureGather(gsampler2DRect sampler, vec3 P);\n//gathers four texels from a texture\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureGather(gsampler2DRect sampler, vec3 P, int comp);\n//gathers four texels from a texture with offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureGatherOffset(gsampler2D sampler, vec2 P, ivec2 offset);\n//gathers four texels from a texture with offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureGatherOffset(gsampler2D sampler, vec2 P, ivec2 offset, int comp);\n//gathers four texels from a texture with offset\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureGatherOffset(gsampler2DArray sampler, vec3 P, ivec2 offset);\n//gathers four texels from a texture with offset\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureGatherOffset(gsampler2DArray sampler, vec3 P, ivec2 offset, int comp);\n//gathers four texels from a texture with offset\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureGatherOffset(gsampler2DRect sampler, vec3 P, ivec2 offset);\n//gathers four texels from a texture with offset\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureGatherOffset(gsampler2DRect sampler, vec3 P, ivec2 offset, int comp);\n//gathers four texels from a texture with an array of offsets\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureGatherOffsets(gsampler2D sampler, vec2 P, ivec2 offsets[4]);\n//gathers four texels from a texture with an array of offsets\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureGatherOffsets(gsampler2D sampler, vec2 P, ivec2 offsets[4], int comp);\n//gathers four texels from a texture with an array of offsets\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureGatherOffsets(gsampler2DArray sampler, vec3 P, ivec2 offsets[4]);\n//gathers four texels from a texture with an array of offsets\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureGatherOffsets(gsampler2DArray sampler, vec3 P, ivec2 offsets[4], int comp);\n//gathers four texels from a texture with an array of offsets\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureGatherOffsets(gsampler2DRect sampler, vec3 P, ivec2 offsets[4]);\n//gathers four texels from a texture with an array of offsets\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureGatherOffsets(gsampler2DRect sampler, vec3 P, ivec2 offsets[4], int comp);\n//perform a texture lookup with explicit gradients\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureGrad(gsampler1D sampler, float P, float dPdx, float dPdy);\n//perform a texture lookup with explicit gradients\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureGrad(gsampler2D sampler, vec2 P, vec2 dPdx, vec2 dPdy);\n//perform a texture lookup with explicit gradients\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureGrad(gsampler3D sampler, vec3 P, vec3 dPdx, vec3 dPdy);\n//perform a texture lookup with explicit gradients\ntemplate<typename gvec4, typename gsamplerCube> gvec4 textureGrad(gsamplerCube sampler, vec3 P, vec3 dPdx, vec3 dPdy);\n//perform a texture lookup with explicit gradients\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureGrad(gsampler2DRect sampler, vec2 P, vec2 dPdx, vec2 dPdy);\n//perform a texture lookup with explicit gradients\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 textureGrad(gsampler1DArray sampler, vec2 P, float dPdx, float dPdy);\n//perform a texture lookup with explicit gradients\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureGrad(gsampler2DArray sampler, vec3 P, vec2 dPdx, vec2 dPdy);\n//perform a texture lookup with explicit gradients\ntemplate<typename gvec4, typename gsamplerCubeArray> gvec4 textureGrad(gsamplerCubeArray sampler, vec4 P, vec3 dPdx, vec3 dPdy);\n//perform a texture lookup with explicit gradients and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureGradOffset(gsampler1D sampler, float P, float dPdx, float dPdy, int offset);\n//perform a texture lookup with explicit gradients and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureGradOffset(gsampler2D sampler, vec2 P, vec2 dPdx, vec2 dPdy, ivec2 offset);\n//perform a texture lookup with explicit gradients and offset\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureGradOffset(gsampler3D sampler, vec3 P, vec3 dPdx, vec3 dPdy, ivec3 offset);\n//perform a texture lookup with explicit gradients and offset\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureGradOffset(gsampler2DRect sampler, vec2 P, vec2 dPdx, vec2 dPdy, ivec2 offset);\n//perform a texture lookup with explicit gradients and offset\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 textureGradOffset(gsampler1DArray sampler, vec2 P, float dPdx, float dPdy, int offset);\n//perform a texture lookup with explicit gradients and offset\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureGradOffset(gsampler2DArray sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset);\n//perform a texture lookup with explicit level-of-detail\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureLod(gsampler1D sampler, float P, float lod);\n//perform a texture lookup with explicit level-of-detail\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureLod(gsampler2D sampler, vec2 P, float lod);\n//perform a texture lookup with explicit level-of-detail\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureLod(gsampler3D sampler, vec3 P, float lod);\n//perform a texture lookup with explicit level-of-detail\ntemplate<typename gvec4, typename gsamplerCube> gvec4 textureLod(gsamplerCube sampler, vec3 P, float lod);\n//perform a texture lookup with explicit level-of-detail\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 textureLod(gsampler1DArray sampler, vec2 P, float lod);\n//perform a texture lookup with explicit level-of-detail\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureLod(gsampler2DArray sampler, vec3 P, float lod);\n//perform a texture lookup with explicit level-of-detail\ntemplate<typename gvec4, typename gsamplerCubeArray> gvec4 textureLod(gsamplerCubeArray sampler, vec4 P, float lod);\n//perform a texture lookup with explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureLodOffset(gsampler1D sampler, float P, float lod, int offset);\n//perform a texture lookup with explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureLodOffset(gsampler2D sampler, vec2 P, float lod, ivec2 offset);\n//perform a texture lookup with explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureLodOffset(gsampler3D sampler, vec3 P, float lod, ivec3 offset);\n//perform a texture lookup with explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 textureLodOffset(gsampler1DArray sampler, vec2 P, float lod, int offset);\n//perform a texture lookup with explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureLodOffset(gsampler2DArray sampler, vec3 P, float lod, ivec2 offset);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureOffset(gsampler1D sampler, float P, int offset);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureOffset(gsampler1D sampler, float P, int offset, float  bias);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureOffset(gsampler2D sampler, vec2 P, ivec2 offset);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureOffset(gsampler2D sampler, vec2 P, ivec2 offset, float  bias);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureOffset(gsampler3D sampler, vec3 P, ivec3 offset);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureOffset(gsampler3D sampler, vec3 P, ivec3 offset, float  bias);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureOffset(gsampler2DRect sampler, vec2 P, ivec2 offset);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 textureOffset(gsampler1DArray sampler, vec2 P, int offset);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler1DArray> gvec4 textureOffset(gsampler1DArray sampler, vec2 P, int offset, float  bias);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureOffset(gsampler2DArray sampler, vec3 P, ivec2 offset);\n//perform a texture lookup with offset\ntemplate<typename gvec4, typename gsampler2DArray> gvec4 textureOffset(gsampler2DArray sampler, vec3 P, ivec2 offset, float  bias);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProj(gsampler1D sampler, vec2 P);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProj(gsampler1D sampler, vec2 P, float  bias);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProj(gsampler1D sampler, vec4 P);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProj(gsampler1D sampler, vec4 P, float  bias);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProj(gsampler2D sampler, vec3 P);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProj(gsampler2D sampler, vec3 P, float  bias);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProj(gsampler2D sampler, vec4 P);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProj(gsampler2D sampler, vec4 P, float  bias);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureProj(gsampler3D sampler, vec4 P);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureProj(gsampler3D sampler, vec4 P, float  bias);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureProj(gsampler2DRect sampler, vec3 P);\n//perform a texture lookup with projection\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureProj(gsampler2DRect sampler, vec4 P);\n//perform a texture lookup with projection and explicit gradients\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjGrad(gsampler1D sampler, vec2 P, float pDx, float pDy);\n//perform a texture lookup with projection and explicit gradients\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjGrad(gsampler1D sampler, vec4 P, float pDx, float pDy);\n//perform a texture lookup with projection and explicit gradients\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjGrad(gsampler2D sampler, vec3 P, vec2 pDx, vec2 pDy);\n//perform a texture lookup with projection and explicit gradients\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjGrad(gsampler2D sampler, vec4 P, vec2 pDx, vec2 pDy);\n//perform a texture lookup with projection and explicit gradients\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureProjGrad(gsampler3D sampler, vec4 P, vec3 pDx, vec3 pDy);\n//perform a texture lookup with projection and explicit gradients\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureProjGrad(gsampler2DRect sampler, vec3 P, vec2 pDx, vec2 pDy);\n//perform a texture lookup with projection and explicit gradients\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureProjGrad(gsampler2DRect sampler, vec4 P, vec2 pDx, vec2 pDy);\n//perform a texture lookup with projection, explicit gradients and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjGradOffset(gsampler1D sampler, vec2 P, float dPdx, float dPdy, int offset);\n//perform a texture lookup with projection, explicit gradients and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjGradOffset(gsampler1D sampler, vec4 P, float dPdx, float dPdy, int offset);\n//perform a texture lookup with projection, explicit gradients and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjGradOffset(gsampler2D sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset);\n//perform a texture lookup with projection, explicit gradients and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjGradOffset(gsampler2D sampler, vec4 P, vec2 dPdx, vec2 dPdy, ivec2 offset);\n//perform a texture lookup with projection, explicit gradients and offset\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureProjGradOffset(gsampler3D sampler, vec4 P, vec3 dPdx, vec3 dPdy, ivec3 offset);\n//perform a texture lookup with projection, explicit gradients and offset\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureProjGradOffset(gsampler2DRect sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset);\n//perform a texture lookup with projection, explicit gradients and offset\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureProjGradOffset(gsampler2DRect sampler, vec4 P, vec2 dPdx, vec2 dPdy, ivec2 offset);\n//perform a texture lookup with projection and explicit level-of-detail\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjLod(gsampler1D sampler, vec2 P, float lod);\n//perform a texture lookup with projection and explicit level-of-detail\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjLod(gsampler1D sampler, vec4 P, float lod);\n//perform a texture lookup with projection and explicit level-of-detail\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjLod(gsampler2D sampler, vec3 P, float lod);\n//perform a texture lookup with projection and explicit level-of-detail\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjLod(gsampler2D sampler, vec4 P, float lod);\n//perform a texture lookup with projection and explicit level-of-detail\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureProjLod(gsampler3D sampler, vec4 P, float lod);\n//perform a texture lookup with projection and explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjLodOffset(gsampler1D sampler, vec2 P, float lod, int offset);\n//perform a texture lookup with projection and explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjLodOffset(gsampler1D sampler, vec4 P, float lod, int offset);\n//perform a texture lookup with projection and explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjLodOffset(gsampler2D sampler, vec3 P, float lod, ivec2 offset);\n//perform a texture lookup with projection and explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjLodOffset(gsampler2D sampler, vec4 P, float lod, ivec2 offset);\n//perform a texture lookup with projection and explicit level-of-detail and offset\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureProjLodOffset(gsampler3D sampler, vec4 P, float lod, ivec3 offset);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjOffset(gsampler1D sampler, vec2 P, int offset);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjOffset(gsampler1D sampler, vec2 P, int offset, float  bias);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjOffset(gsampler1D sampler, vec4 P, int offset);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler1D> gvec4 textureProjOffset(gsampler1D sampler, vec4 P, int offset, float  bias);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjOffset(gsampler2D sampler, vec3 P, ivec2 offset);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjOffset(gsampler2D sampler, vec3 P, ivec2 offset, float  bias);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjOffset(gsampler2D sampler, vec4 P, ivec2 offset);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler2D> gvec4 textureProjOffset(gsampler2D sampler, vec4 P, ivec2 offset, float  bias);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureProjOffset(gsampler3D sampler, vec4 P, ivec3 offset);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler3D> gvec4 textureProjOffset(gsampler3D sampler, vec4 P, ivec3 offset, float  bias);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureProjOffset(gsampler2DRect sampler, vec3 P, ivec2 offset);\n//perform a texture lookup with projection and offset\ntemplate<typename gvec4, typename gsampler2DRect> gvec4 textureProjOffset(gsampler2DRect sampler, vec4 P, ivec2 offset);\n//compute the number of accessible mipmap levels of a texture\ntemplate<typename gsampler1D> int textureQueryLevels(gsampler1D sampler);\n//compute the number of accessible mipmap levels of a texture\ntemplate<typename gsampler2D> int textureQueryLevels(gsampler2D sampler);\n//compute the number of accessible mipmap levels of a texture\ntemplate<typename gsampler3D> int textureQueryLevels(gsampler3D sampler);\n//compute the number of accessible mipmap levels of a texture\ntemplate<typename gsamplerCube> int textureQueryLevels(gsamplerCube sampler);\n//compute the number of accessible mipmap levels of a texture\ntemplate<typename gsampler1DArray> int textureQueryLevels(gsampler1DArray sampler);\n//compute the number of accessible mipmap levels of a texture\ntemplate<typename gsampler2DDArray> int textureQueryLevels(gsampler2DDArray sampler);\n//compute the number of accessible mipmap levels of a texture\ntemplate<typename gsamplerCubeArray> int textureQueryLevels(gsamplerCubeArray sampler);\n//compute the level-of-detail that would be used to sample from a texture\ntemplate<typename gsampler1D> vec2 textureQueryLod(gsampler1D sampler, float P);\n//compute the level-of-detail that would be used to sample from a texture\ntemplate<typename gsampler2D> vec2 textureQueryLod(gsampler2D sampler, vec2 P);\n//compute the level-of-detail that would be used to sample from a texture\ntemplate<typename gsampler3D> vec2 textureQueryLod(gsampler3D sampler, vec3 P);\n//compute the level-of-detail that would be used to sample from a texture\ntemplate<typename gsamplerCube> vec2 textureQueryLod(gsamplerCube sampler, vec3 P);\n//compute the level-of-detail that would be used to sample from a texture\ntemplate<typename gsampler1DArray> vec2 textureQueryLod(gsampler1DArray sampler, float P);\n//compute the level-of-detail that would be used to sample from a texture\ntemplate<typename gsampler2DDArray> vec2 textureQueryLod(gsampler2DDArray sampler, vec2 P);\n//compute the level-of-detail that would be used to sample from a texture\ntemplate<typename gsamplerCubeArray> vec2 textureQueryLod(gsamplerCubeArray sampler, vec3 P);\n//return the number of samples of a texture\ntemplate<typename gsampler2DMS> int textureSamples(gsampler2DMS sampler);\n//return the number of samples of a texture\ntemplate<typename gsampler2DMSArray> int textureSamples(gsampler2DMSArray sampler);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsampler1D> int textureSize(gsampler1D sampler, int lod);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsampler2D> ivec2 textureSize(gsampler2D sampler, int lod);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsampler3D> ivec3 textureSize(gsampler3D sampler, int lod);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsamplerCube> ivec2 textureSize(gsamplerCube sampler, int lod);\n//retrieve the dimensions of a level of a texture\nivec3 textureSize(samplerCubeArray sampler, int lod);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsamplerRect> ivec2 textureSize(gsamplerRect sampler);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsampler1DArray> ivec2 textureSize(gsampler1DArray sampler, int lod);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsampler2DArray> ivec3 textureSize(gsampler2DArray sampler, int lod);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsamplerBuffer> int textureSize(gsamplerBuffer sampler);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsampler2DMS> ivec2 textureSize(gsampler2DMS sampler);\n//retrieve the dimensions of a level of a texture\ntemplate<typename gsampler2DMSArray> ivec3 textureSize(gsampler2DMSArray sampler);\n//calculate the transpose of a matrix\nmat2 transpose(mat2 m);\n//calculate the transpose of a matrix\nmat3 transpose(mat3 m);\n//calculate the transpose of a matrix\nmat4 transpose(mat4 m);\n//calculate the transpose of a matrix\nmat2x3 transpose(mat3x2 m);\n//calculate the transpose of a matrix\nmat2x4 transpose(mat4x2 m);\n//calculate the transpose of a matrix\nmat3x2 transpose(mat2x3 m);\n//calculate the transpose of a matrix\nmat3x4 transpose(mat4x3 m);\n//calculate the transpose of a matrix\nmat4x2 transpose(mat2x4 m);\n//calculate the transpose of a matrix\nmat4x3 transpose(mat3x4 m);\n//calculate the transpose of a matrix\ndmat2 transpose(dmat2 m);\n//calculate the transpose of a matrix\ndmat3 transpose(dmat3 m);\n//calculate the transpose of a matrix\ndmat4 transpose(dmat4 m);\n//calculate the transpose of a matrix\ndmat2x3 transpose(dmat3x2 m);\n//calculate the transpose of a matrix\ndmat2x4 transpose(dmat4x2 m);\n//calculate the transpose of a matrix\ndmat3x2 transpose(dmat2x3 m);\n//calculate the transpose of a matrix\ndmat3x4 transpose(dmat4x3 m);\n//calculate the transpose of a matrix\ndmat4x2 transpose(dmat2x4 m);\n//calculate the transpose of a matrix\ndmat4x3 transpose(dmat3x4 m);\n//find the truncated value of the parameter\ntemplate<typename T> T trunc(T x);\n//find the truncated value of the parameter\ntemplate<typename D> D trunc(D x);\n//add unsigned integers and generate carry\ntemplate<typename U> U uaddCarry(U x, U y, out U carry);\n//perform a 32- by 32-bit multiply to produce a 64-bit result\ntemplate<typename U> void umulExtended(U x, U y, out U msb, out U lsb);\n//perform a 32- by 32-bit multiply to produce a 64-bit result\ntemplate<typename I> void imulExtended(I x, I y, out I msb, out I lsb);\n//produce two unsigned integers containing the bit encoding of a double precision floating point value\nuvec2 unpackDouble2x32(double d);\n//convert two 16-bit floating-point values packed into a single 32-bit integer into a vector of two 32-bit floating-point quantities\nvec2 unpackHalf2x16(uint v);\n//unpack floating-point values from an unsigned integer\nvec2 unpackUnorm2x16(uint p);\n//unpack floating-point values from an unsigned integer\nvec2 unpackSnorm2x16(uint p);\n//unpack floating-point values from an unsigned integer\nvec4 unpackUnorm4x8(uint p);\n//unpack floating-point values from an unsigned integer\nvec4 unpackSnorm4x8(uint p);\n//subtract unsigned integers and generate borrow\ntemplate<typename U> U usubBorrow(U x, U y, out U borrow);\n\n\n#endif\n"
  },
  {
    "path": "Malt/Shaders/Lighting/Lighting.glsl",
    "content": "#ifndef COMMON_LIGHTING_GLSL\n#define COMMON_LIGHTING_GLSL\n\n#include \"Common.glsl\"\n\n#ifndef MAX_LIGHTS\n    #define MAX_LIGHTS 128\n#endif\n\n#define LIGHT_SUN 1\n#define LIGHT_POINT 2\n#define LIGHT_SPOT 3\n\n/* META GLOBAL\n    @meta: internal=true;\n*/\n\nstruct Light\n{\n    vec3 color;\n    int type;\n    vec3 position;\n    float radius;\n    vec3 direction;\n    float spot_angle;\n    float spot_blend;\n    int type_index;\n};\n\n#define MAX_SPOTS 64\n#define MAX_SUNS 64\n#define MAX_POINTS 64\n\nstruct SceneLights\n{\n    Light lights[MAX_LIGHTS];\n    int lights_count;\n    int cascades_count;\n    mat4 spot_matrices[MAX_SPOTS];\n    mat4 sun_matrices[MAX_SUNS];\n    mat4 point_matrices[MAX_POINTS];\n};\n\nlayout(std140) uniform SCENE_LIGHTS\n{\n    SceneLights LIGHTS;\n};\n\nuniform sampler2DArray SHADOWMAPS_DEPTH_SPOT;\nuniform sampler2DArray SHADOWMAPS_DEPTH_SUN;\nuniform samplerCubeArray SHADOWMAPS_DEPTH_POINT;\n\nstruct LitSurface\n{\n    vec3 N;// Surface normal\n    vec3 L;// Surface to light direction (normalized)\n    vec3 V;// Surface to camera (view) direction (normalized)\n    vec3 R;// -L reflected on N\n    vec3 H;// Halfway vector\n    float NoL;// Dot product between N and L\n    float P;// Power Scalar\n    bool shadow;\n    int cascade;\n    vec3 shadow_multiply;\n    vec3 light_color;\n};\n\nstruct ShadowData\n{\n    vec3 light_uv;\n    bool shadow;\n    vec3 light_space;\n    float depth;\n};\n\nShadowData spot_shadow(vec3 position, Light light, sampler2DArray shadowmap, float bias);\nShadowData sun_shadow(vec3 position, Light light, sampler2DArray shadowmap, float bias, out int cascade);\nShadowData point_shadow(vec3 position, Light light, samplerCubeArray shadowmap, float bias);\n\n/*  META\n    @position: subtype=Vector; default=POSITION;\n    @normal: subtype=Normal; default=NORMAL;\n*/\nLitSurface lit_surface(vec3 position, vec3 normal, Light light, bool shadows)\n{\n    LitSurface S;\n\n    S.N = normal;\n    \n    if (light.type == LIGHT_SUN)\n    {\n        S.L = -light.direction;\n    }\n    else\n    {\n        S.L = normalize(light.position - position);\n    }\n\n    S.V = -view_direction();\n    S.R = reflect(-S.L, S.N);\n    S.H = normalize(S.L + S.V);\n    S.NoL = dot(S.N,S.L);\n\n    S.P = 1.0;\n\n    if (light.type != LIGHT_SUN) //Point or Spot\n    {\n        float normalized_distance = distance(position, light.position) / light.radius;\n        normalized_distance = clamp(normalized_distance, 0, 1);\n\n        S.P = 1.0 - normalized_distance;\n    }\n    \n    if (light.type == LIGHT_SPOT)\n    {\n        float spot_angle = dot(light.direction, normalize(position - light.position));\n        spot_angle = acos(spot_angle);\n\n        float end_angle = light.spot_angle / 2.0;\n        float start_angle = end_angle - light.spot_blend;\n        float delta_angle = end_angle - start_angle;\n        \n        float spot_scalar = clamp((spot_angle - start_angle) / delta_angle, 0, 1);\n\n        S.P *= 1.0 - spot_scalar;\n    }\n\n    S.shadow = false;\n    S.cascade = -1;\n    \n    if(shadows)\n    {\n\n        if(light.type_index >= 0)\n        {\n            float bias = 1e-5;\n            if(light.type == LIGHT_SPOT)\n            {\n                S.shadow = spot_shadow(position, light, SHADOWMAPS_DEPTH_SPOT, bias).shadow;\n            }\n            if(light.type == LIGHT_SUN)\n            {\n                float bias = 1e-3;\n                //bias *= 1.0 - abs(S.NoL);\n                S.shadow = sun_shadow(position, light, SHADOWMAPS_DEPTH_SUN, bias, S.cascade).shadow;\n            }\n            if(light.type == LIGHT_POINT)\n            {\n                S.shadow = point_shadow(position, light, SHADOWMAPS_DEPTH_POINT, bias).shadow;\n            }\n        }\n    }\n\n    S.shadow_multiply = S.shadow ? vec3(0) : vec3(1);\n    S.light_color = light.color * S.P * S.shadow_multiply;\n\n    return S;\n}\n\nShadowData spot_shadow(vec3 position, Light light, sampler2DArray shadowmap, float bias)\n{\n    vec2 shadowmap_size = vec2(textureSize(shadowmap, 0));\n    \n    ShadowData S;\n    S.light_space = project_point(LIGHTS.spot_matrices[light.type_index], position);\n    \n    S.light_uv = S.light_space * 0.5 + 0.5;\n    S.depth = texture(shadowmap, vec3(S.light_uv.xy, light.type_index)).x;\n\n    S.shadow = S.depth < S.light_uv.z - bias && S.light_uv == clamp(S.light_uv, vec3(0), vec3(1));\n    //if(!S.shadow) S.depth = 0;\n\n    return S;\n}\n\nShadowData sun_shadow(vec3 position, Light light, sampler2DArray shadowmap, float bias, out int cascade)\n{\n    vec2 shadowmap_size = vec2(textureSize(shadowmap, 0));\n    \n    ShadowData S;\n    S.shadow = false;\n\n    for(int c = 0; c < LIGHTS.cascades_count; c++)\n    {\n        int index = light.type_index * LIGHTS.cascades_count + c;\n        \n        S.light_space = project_point(LIGHTS.sun_matrices[index], position);\n        \n        S.light_uv = S.light_space * 0.5 + 0.5;\n\n        if(S.light_space == clamp(S.light_space, vec3(-0.99), vec3(0.99)))\n        {\n            S.depth = texture(shadowmap, vec3(S.light_uv.xy, index)).x;\n            S.shadow = S.depth < S.light_uv.z - bias;\n\n            cascade = c;\n            break;\n        }\n    }\n\n    return S;\n}\n\nShadowData point_shadow(vec3 position, Light light, samplerCubeArray shadowmap, float bias)\n{\n    vec2 shadowmap_size = vec2(textureSize(shadowmap, 0));\n    \n    ShadowData S;\n    S.light_space = transform_point(LIGHTS.point_matrices[light.type_index], position);\n    S.light_uv = normalize(S.light_space);\n    \n    float cubemap_side_depth = max(abs(S.light_space.x), max(abs(S.light_space.y), abs(S.light_space.z)));\n    float n = 0.01; //Near is hard-coded for point lights\n    float f = light.radius;\n    float buffer_depth = (f+n) / (f-n) - (2*f*n)/(f-n) / cubemap_side_depth;\n    buffer_depth = (buffer_depth + 1.0) * 0.5;\n\n    S.depth = texture(shadowmap, vec4(S.light_uv, light.type_index)).x;\n    \n    S.shadow = S.depth < buffer_depth - bias;\n    if(!S.shadow) S.depth = 0;\n\n    return S;\n}\n\n#endif //COMMON_LIGHTING_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/bool.glsl",
    "content": "#ifndef BOOL_GLSL\n#define BOOL_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\nbool bool_and(bool a, bool b) { return a && b; }\nbool bool_or(bool a, bool b) { return a || b; }\nbool bool_not(bool b) { return !b; }\n\nbool bool_equal(bool a, bool b){ return a == b; }\nbool bool_not_equal(bool a, bool b){ return a != b; }\n\nbool if_else(bool condition, bool if_true, bool if_false){ return condition ? if_true : if_false; }\n\n#endif //BOOL_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/common.glsl",
    "content": "#ifndef NODE_UTILS_COMMON_GLSL\n#define NODE_UTILS_COMMON_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\nvec3 surface_position() { return POSITION; }\nvec3 surface_normal() { return NORMAL; }\nvec3 surface_tangent(int index) { return get_tangent(index); }\nvec3 surface_bitangent(int index) { return get_bitangent(index); }\nvec2 surface_uv(int index) { return UV[index]; }\nvec4 surface_vertex_color(int index) { return COLOR[index]; }\n\nvec3 surface_original_position() { return IO_POSITION; }\nvec3 surface_original_normal() { return IO_NORMAL; }\n\nuvec4 object_id() { return ID; }\n\nuvec4 object_original_id() { return IO_ID; }\n\nmat4 model_matrix() { return MODEL; }\nmat4 camera_matrix() { return CAMERA; }\nmat4 projection_matrix() { return PROJECTION; }\n\nvec2 render_resolution() { return vec2(RESOLUTION); }\nvec2 sample_offset() { return SAMPLE_OFFSET; }\nint sample_count() { return SAMPLE_COUNT; }\n\nint current_frame() { return FRAME; }\nfloat current_time() { return TIME; }\n\n#endif // NODE_UTILS_COMMON_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/float.glsl",
    "content": "#ifndef FLOAT_GLSL\n#define FLOAT_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\nfloat float_add(float a, float b){ return a+b; }\nfloat float_subtract(float a, float b){ return a-b; }\nfloat float_multiply(float a, float b){ return a*b; }\nfloat float_divide(float a, float b){ return a/b; }\nfloat float_modulo(float a, float b){ return mod(a,b); }\nfloat float_pow(float f, float e){ return pow(f, e); }\nfloat float_sqrt(float f){ return sqrt(f); }\n\nfloat float_round(float f){ return round(f); }\nfloat float_fract(float f){ return fract(f); }\nfloat float_floor(float f){ return floor(f); }\nfloat float_ceil(float f){ return ceil(f); }\n\nfloat float_clamp(float f, float min, float max){ return clamp(f, min, max); }\n\nfloat float_sign(float f){ return sign(f); }\nfloat float_abs(float f){ return abs(f); }\nfloat float_min(float a, float b){ return min(a,b); }\nfloat float_max(float a, float b){ return max(a,b); }\n\nfloat float_mix(float a, float b, float factor){ return mix(a,b,factor); }\n\nfloat float_sin(float f) { return sin(f); }\nfloat float_cos(float f) { return cos(f); }\nfloat float_tan(float f) { return tan(f); }\nfloat float_asin(float f) { return asin(f); }\nfloat float_acos(float f) { return acos(f); }\nfloat float_atan(float f) { return atan(f); }\n\nfloat float_degrees(float r) { return degrees(r); }\nfloat float_radians(float d) { return radians(d); }\n\nbool float_equal(float a, float b){ return a == b; }\nbool float_not_equal(float a, float b){ return a != b; }\nbool float_greater(float a, float b){ return a > b; }\nbool float_greater_or_equal(float a, float b){ return a >= b; }\nbool float_less(float a, float b){ return a < b; }\nbool float_less_or_equal(float a, float b){ return a <= b; }\n\nfloat float_if_else(bool condition, float if_true, float if_false){ return condition ? if_true : if_false; }\n\n#endif //FLOAT_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/int.glsl",
    "content": "#ifndef INT_GLSL\n#define INT_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\nint int_add(int a, int b){ return a+b; }\nint int_subtract(int a, int b){ return a-b; }\nint int_multiply(int a, int b){ return a*b; }\nint int_divide(int a, int b){ return a/b; }\nint int_modulo(int a, int b){ return a%b; }\n\nint int_clamp(int i, int min, int max){ return clamp(i, min, max); }\n\nint int_sign(int i){ return sign(i); }\nint int_abs(int i){ return abs(i); }\nint int_min(int a, int b){ return min(a,b); }\nint int_max(int a, int b){ return max(a,b); }\n\nbool int_equal(int a, int b){ return a == b; }\nbool int_not_equal(int a, int b){ return a != b; }\nbool int_greater(int a, int b){ return a > b; }\nbool int_greater_or_equal(int a, int b){ return a >= b; }\nbool int_less(int a, int b){ return a < b; }\nbool int_less_or_equal(int a, int b){ return a <= b; }\n\nint int_if_else(bool condition, int if_true, int if_false){ return condition ? if_true : if_false; }\n\n#endif //INT_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/node_utils.glsl",
    "content": "#ifndef NODE_UTILS_GLSL\n#define NODE_UTILS_GLSL\n\n#include \"Node Utils/common.glsl\"\n#include \"Node Utils/bool.glsl\"\n#include \"Node Utils/float.glsl\"\n#include \"Node Utils/int.glsl\"\n#include \"Node Utils/packing.glsl\"\n#include \"Node Utils/properties.glsl\"\n#include \"Node Utils/sampler.glsl\"\n#include \"Node Utils/vec2.glsl\"\n#include \"Node Utils/vec3.glsl\"\n#include \"Node Utils/vec4.glsl\"\n\n// Basic common API\n#include \"Common.glsl\"\n#include \"Filters/AO.glsl\"\n#include \"Filters/Bevel.glsl\"\n#include \"Filters/Blur.glsl\"\n#include \"Filters/Curvature.glsl\"\n#include \"Filters/Line.glsl\"\n#include \"Procedural/Noise.glsl\"\n#include \"Procedural/Cell_Noise.glsl\"\n#include \"Procedural/Fractal_Noise.glsl\"\n#include \"Shading/ShadingModels.glsl\"\n\n#endif //NODE_UTILS_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/packing.glsl",
    "content": "#ifndef PACKING_GLSL\n#define PACKING_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\nuvec4 pack_8bit(vec4 a, vec4 b, vec4 c, vec4 d)\n{\n    return uvec4(packUnorm4x8(a), packUnorm4x8(b), packUnorm4x8(c), packUnorm4x8(d));\n}\n\nvoid unpack_8bit(uvec4 packed_vector, out vec4 a, out vec4 b, out vec4 c, out vec4 d)\n{\n    a = unpackUnorm4x8(packed_vector.x);\n    b = unpackUnorm4x8(packed_vector.y);\n    c = unpackUnorm4x8(packed_vector.z);\n    d = unpackUnorm4x8(packed_vector.w);\n}\n\n#endif //PACKING_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/properties.glsl",
    "content": "#ifndef PROPERTIES_GLSL\n#define PROPERTIES_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\nbool bool_property(bool b) { return b; }\n\nfloat float_property(float f) { return f; }\n\nint int_property(int i) { return i; }\n\nvec2 vec2_property(vec2 v) { return v; }\n\n/*  META @v: subtype=Vector;*/\nvec3 vec3_property(vec3 v) { return v; }\n/*  META @v: subtype=Vector;*/\nvec4 vec4_property(vec4 v) { return v; }\n\nvec3 vec3_color_property(vec3 v) { return v; }\nvec4 vec4_color_property(vec4 v) { return v; }\n\n#endif //PROPERTIES_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/sampler.glsl",
    "content": "#ifndef SAMPLER_GLSL\n#define SAMPLER_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\nvec4 sampler1D_sample(sampler1D t, float u) { return texture(t, u); }\nint sampler1D_size(sampler1D t) { return textureSize(t, 0); }\nvec4 sampler1D_textel_fetch(sampler1D t, int u) { return texelFetch(t, u, 0); }\n\n/* META @uv: default=UV[0];*/\nvec4 sampler2D_sample(sampler2D t, vec2 uv) { return texture(t, uv); }\n/* META @uv: default=UV[0];*/\nvec4 sampler2D_sample_nearest(sampler2D t, vec2 uv){ return texelFetch(t, ivec2(textureSize(t, 0) * uv), 0); }\nivec2 sampler2D_size(sampler2D t) { return textureSize(t, 0); }\nvec4 sampler2D_textel_fetch(sampler2D t, ivec2 uv) { return texelFetch(t, uv, 0); }\n\n#endif //SAMPLER_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/vec2.glsl",
    "content": "#ifndef VEC2_GLSL\n#define VEC2_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\nvec2 vec2_add(vec2 a, vec2 b){ return a+b; }\nvec2 vec2_subtract(vec2 a, vec2 b){ return a-b; }\nvec2 vec2_multiply(vec2 a, vec2 b){ return a*b; }\nvec2 vec2_divide(vec2 a, vec2 b){ return a/b; }\nvec2 vec2_scale(vec2 v, float s){ return v*s; }\nvec2 vec2_modulo(vec2 a, vec2 b){ return mod(a,b); }\nvec2 vec2_pow(vec2 v, vec2 e){ return pow(v, e); }\nvec2 vec2_sqrt(vec2 v){ return sqrt(v); }\n\nvec2 vec2_round(vec2 v){ return round(v); }\nvec2 vec2_fract(vec2 v){ return fract(v); }\nvec2 vec2_floor(vec2 v){ return floor(v); }\nvec2 vec2_ceil(vec2 v){ return ceil(v); }\n\nvec2 vec2_clamp(vec2 v, vec2 min, vec2 max){ return clamp(v, min, max); }\n\nvec2 vec2_sign(vec2 v){ return sign(v); }\nvec2 vec2_abs(vec2 v){ return abs(v); }\nvec2 vec2_min(vec2 a, vec2 b){ return min(a,b); }\nvec2 vec2_max(vec2 a, vec2 b){ return max(a,b); }\n\nvec2 vec2_mix(vec2 a, vec2 b, vec2 factor){ return mix(a,b,factor); }\nvec2 vec2_mix_float(vec2 a, vec2 b, float factor){ return mix(a,b,factor); }\n\nvec2 vec2_normalize(vec2 v){ return normalize(v); }\n\nfloat vec2_length(vec2 v){ return length(v); }\nfloat vec2_distance(vec2 a, vec2 b){ return distance(a,b); }\nfloat vec2_dot_product(vec2 a, vec2 b){ return dot(a,b); }\n\nbool vec2_equal(vec2 a, vec2 b){ return a == b; }\nbool vec2_not_equal(vec2 a, vec2 b){ return a != b; }\n\nvec2 vec2_if_else(bool condition, vec2 if_true, vec2 if_false){ return condition ? if_true : if_false; }\n\nvec2 vec2_join(float x, float y) { return vec2(x,y);}\nvoid vec2_split(vec2 v, out float x, out float y){ x=v.x; y=v.y; }\n\n#endif //VEC2_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/vec3.glsl",
    "content": "#ifndef VEC3_GLSL\n#define VEC3_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 vec3_add(vec3 a, vec3 b){ return a+b; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 vec3_subtract(vec3 a, vec3 b){ return a-b; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 vec3_multiply(vec3 a, vec3 b){ return a*b; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 vec3_divide(vec3 a, vec3 b){ return a/b; }\n/*META @v: subtype=Vector;*/\nvec3 vec3_scale(vec3 v, float s){ return v*s; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 vec3_modulo(vec3 a, vec3 b){ return mod(a,b); }\n/*META @v: subtype=Vector; @e: subtype=Vector;*/\nvec3 vec3_pow(vec3 v, vec3 e){ return pow(v, e); }\n/*META @v: subtype=Vector;*/\nvec3 vec3_sqrt(vec3 v){ return sqrt(v); }\n\n/*META @v: subtype=Vector;*/\nvec3 vec3_round(vec3 v){ return round(v); }\n/*META @v: subtype=Vector;*/\nvec3 vec3_fract(vec3 v){ return fract(v); }\n/*META @v: subtype=Vector;*/\nvec3 vec3_floor(vec3 v){ return floor(v); }\n/*META @v: subtype=Vector;*/\nvec3 vec3_ceil(vec3 v){ return ceil(v); }\n\n/*META @v: subtype=Vector; @min: subtype=Vector; @max: subtype=Vector;*/\nvec3 vec3_clamp(vec3 v, vec3 min, vec3 max){ return clamp(v, min, max); }\n\n/*META @v: subtype=Vector;*/\nvec3 vec3_sign(vec3 v){ return sign(v); }\n/*META @v: subtype=Vector;*/\nvec3 vec3_abs(vec3 v){ return abs(v); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 vec3_min(vec3 a, vec3 b){ return min(a,b); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 vec3_max(vec3 a, vec3 b){ return max(a,b); }\n\n/*META @a: subtype=Vector; @b: subtype=Vector; @factor: subtype=Vector;*/\nvec3 vec3_mix(vec3 a, vec3 b, vec3 factor){ return mix(a,b,factor); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 vec3_mix_float(vec3 a, vec3 b, float factor){ return mix(a,b,factor); }\n\n/*META @v: subtype=Vector;*/\nvec3 vec3_normalize(vec3 v){ return normalize(v); }\n\n/*META @v: subtype=Vector;*/\nfloat vec3_length(vec3 v){ return length(v); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nfloat vec3_distance(vec3 a, vec3 b){ return distance(a,b); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nfloat vec3_dot_product(vec3 a, vec3 b){ return dot(a,b); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 vec3_cross_product(vec3 a, vec3 b){ return cross(a,b); }\n\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nbool vec3_equal(vec3 a, vec3 b){ return a == b; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nbool vec3_not_equal(vec3 a, vec3 b){ return a != b; }\n\n/*META @if_true: subtype=Vector; @if_false: subtype=Vector;*/\nvec3 vec3_if_else(bool condition, vec3 if_true, vec3 if_false){ return condition ? if_true : if_false; }\n\nvec3 vec3_join(float x, float y, float z) { return vec3(x,y,z);}\n/*META @v: subtype=Vector;*/\nvoid vec3_split(vec3 v, out float x, out float y, out float z){ x=v.x; y=v.y; z=v.z; }\n\n#endif //VEC3_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils/vec4.glsl",
    "content": "#ifndef VEC4_GLSL\n#define VEC4_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 vec4_add(vec4 a, vec4 b){ return a+b; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 vec4_subtract(vec4 a, vec4 b){ return a-b; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 vec4_multiply(vec4 a, vec4 b){ return a*b; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 vec4_divide(vec4 a, vec4 b){ return a/b; }\n/*META @v: subtype=Vector;*/\nvec4 vec4_scale(vec4 v, float s){ return v*s; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 vec4_modulo(vec4 a, vec4 b){ return mod(a,b); }\n/*META @v: subtype=Vector; @e: subtype=Vector;*/\nvec4 vec4_pow(vec4 v, vec4 e){ return pow(v, e); }\n/*META @v: subtype=Vector;*/\nvec4 vec4_sqrt(vec4 v){ return sqrt(v); }\n\n/*META @v: subtype=Vector;*/\nvec4 vec4_round(vec4 v){ return round(v); }\n/*META @v: subtype=Vector;*/\nvec4 vec4_fract(vec4 v){ return fract(v); }\n/*META @v: subtype=Vector;*/\nvec4 vec4_floor(vec4 v){ return floor(v); }\n/*META @v: subtype=Vector;*/\nvec4 vec4_ceil(vec4 v){ return ceil(v); }\n\n/*META @v: subtype=Vector; @min: subtype=Vector; @max: subtype=Vector;*/\nvec4 vec4_clamp(vec4 v, vec4 min, vec4 max){ return clamp(v, min, max); }\n\n/*META @v: subtype=Vector;*/\nvec4 vec4_sign(vec4 v){ return sign(v); }\n/*META @v: subtype=Vector;*/\nvec4 vec4_abs(vec4 v){ return abs(v); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 vec4_min(vec4 a, vec4 b){ return min(a,b); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 vec4_max(vec4 a, vec4 b){ return max(a,b); }\n\n/*META @a: subtype=Vector; @b: subtype=Vector; @factor: subtype=Vector;*/\nvec4 vec4_mix(vec4 a, vec4 b, vec4 factor){ return mix(a,b,factor); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 vec4_mix_float(vec4 a, vec4 b, float factor){ return mix(a,b,factor); }\n\n/*META @v: subtype=Vector;*/\nvec4 vec4_normalize(vec4 v){ return normalize(v); }\n\n/*META @v: subtype=Vector;*/\nfloat vec4_length(vec4 v){ return length(v); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nfloat vec4_distance(vec4 a, vec4 b){ return distance(a,b); }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nfloat vec4_dot_product(vec4 a, vec4 b){ return dot(a,b); }\n\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nbool vec4_equal(vec4 a, vec4 b){ return a == b; }\n/*META @a: subtype=Vector; @b: subtype=Vector;*/\nbool vec4_not_equal(vec4 a, vec4 b){ return a != b; }\n\n/*META @if_true: subtype=Vector; @if_false: subtype=Vector;*/\nvec4 vec4_if_else(bool condition, vec4 if_true, vec4 if_false){ return condition ? if_true : if_false; }\n\nvec4 vec4_join(float r, float g, float b, float a) { return vec4(r,g,b,a);}\n/*META @v: subtype=Vector;*/\nvoid vec4_split(vec4 v, out float r, out float g, out float b, out float a){ r=v.r; g=v.g; b=v.b; a=v.a; }\n\n#endif //VEC4_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Bool.glsl",
    "content": "#ifndef NODE_UTILS_2_BOOL_GLSL\n#define NODE_UTILS_2_BOOL_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; subcategory=Boolean Logic;\n*/\n\n/*META @meta: label=And;*/\nbool Bool_and(bool a, bool b) { return a && b; }\n/*META @meta: label=Or;*/\nbool Bool_or(bool a, bool b) { return a || b; }\n/*META @meta: label=Not;*/\nbool Bool_not(bool b) { return !b; }\n/*META @meta: label=Equal;*/\nbool Bool_equal(bool a, bool b){ return a == b; }\n/*META @meta: label=Not Equal;*/\nbool Bool_not_equal(bool a, bool b){ return a != b; }\n/*META @meta: label=If Else; @a: label=If True; @b: label=If False; */\nbool Bool_if_else(bool condition, bool a, bool b){ return condition ? a : b; }\n\n#endif //NODE_UTILS_2_BOOL_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Color.glsl",
    "content": "#ifndef NODE_UTILS_2_COLOR_GLSL\n#define NODE_UTILS_2_COLOR_GLSL\n\n/*  META GLOBAL\n    @meta: category=Color;\n*/\n\n/*  META \n    @meta: label=Gradient; subcategory=Color Gradient;\n    @Coord: label=U; subtype=Slider; min=0.0; max=1.0;\n*/\nvec4 Color_Gradient_1d(sampler1D Color_Ramp, float Coord) { return texture(Color_Ramp, Coord); }\n\n/*  META \n    @meta: label=RGB Gradient; subcategory=Color Gradient;\n    @Coord: label=UVW; subtype=Slider; min=0.0; max=1.0;\n*/\nvec3 Color_Gradient_3d(sampler1D Color_Ramp, vec3 Coord) { return rgb_gradient(Color_Ramp, Coord); }\n\n/*  META \n    @meta: label=RGBA Gradient; subcategory=Color Gradient;\n    @Coord: label=UVWX; subtype=Slider; min=0.0; max=1.0;\n*/\nvec4 Color_Gradient_4d(sampler1D Color_Ramp, vec4 Coord) { return rgba_gradient(Color_Ramp, Coord); }\n\n#include \"Node Utils 2/ColorBlend.glsl\"\n\n#endif // NODE_UTILS_2_COLOR_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/ColorBlend.glsl",
    "content": "#ifndef COMMON_BLEND_MODES_GLSL\n#define COMMON_BLEND_MODES_GLSL\n\n#include \"Common/Color.glsl\"\n\n/*  META GLOBAL\n    @meta: category=Color; subcategory=Layer Blend;\n*/\n\n//Based on https://www.w3.org/TR/compositing-1\n\nvoid _blend_prepare_inputs(inout vec4 base, inout vec4 blend, int mode, float opacity)\n{\n    blend.a *= opacity;\n\n    if(mode == 1 || mode == 3)//Clamp\n    {\n        base = saturate(base);\n        blend = saturate(blend);\n    }\n    if(mode == 2 || mode == 3)//sRGB\n    {\n        base.rgb = linear_to_srgb(base.rgb);\n        blend.rgb = linear_to_srgb(blend.rgb);\n    }\n}\n\n//https://www.w3.org/TR/compositing-1/#generalformula\nvec4 _blend_common(vec4 base, vec4 blend, vec3 mixed, int mode)\n{\n    if(mode == 1 || mode == 3)//Clamp\n    {\n        mixed = saturate(mixed);\n    }\n    vec4 result;\n    vec3 color = (1.0 - base.a) * blend.rgb + base.a * mixed;\n    result.rgb = color * blend.a + base.rgb * base.a * (1.0 - blend.a);\n    result.a = blend.a + base.a * (1.0 - blend.a);\n    if(mode == 2 || mode == 3)//sRGB\n    {\n        result.rgb = srgb_to_linear(result.rgb);\n    }\n    return result;\n}\n\n/*  META\n    @meta: label=Normal;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_normal(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    return _blend_common(base, blend, blend.rgb, mode);\n}\n\n/*  META\n    @meta: label=Add;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_add(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = (base + blend).rgb;\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Multiply;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_multiply(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = (base * blend).rgb;\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Overlay;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_overlay(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed;\n    for(int i=0; i<3; i++)\n    {\n        if(base[i] <= 0.5)\n            mixed[i] = blend[i] * 2.0 * base[i];\n        else\n            mixed[i] = blend[i] + (2.0*base[i]-1.0) - (blend[i] * (2.0*base[i]-1.0));\n    }\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Screen;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_screen(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = (base + blend - (base * blend)).rgb;\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Darken;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_darken(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = min(base, blend).rgb;\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Lighten;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_lighten(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = max(base, blend).rgb;\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Soft Light;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_soft_light(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed;\n    for(int i=0; i<3; i++)\n    {\n        float d;\n        if(base[i] <= 0.25)\n            d = ((16.0*base[i] - 12.0) * base[i] + 4.0) * base[i];\n        else\n            d = sqrt(base[i]);\n\n        if(blend[i] <= 0.5)\n            mixed[i] = base[i] - (1.0 - 2.0*blend[i]) * base[i] * (1.0 - base[i]);\n        else\n            mixed[i] = base[i] + (2.0*blend[i] - 1.0) * (d - base[i]);\n    }\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Hard Light;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_hard_light(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed;\n    for(int i=0; i<3; i++)\n    {\n        if(blend[i] <= 0.5)\n            mixed[i] = base[i] * 2.0*blend[i];\n        else\n            mixed[i] = base[i] + (2.0*blend[i]-1.0) - (base[i] * (2.0*blend[i]-1.0));\n    }\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Linear Light;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_linear_light(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = (base + (2.0 * blend) - 1.0).rgb;\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Dodge;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_dodge(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed;\n    for(int i=0; i<3; i++)\n    {\n        if(base[i] == 0)\n            mixed[i] = 0;\n        else if(blend[i] == 1)\n            mixed[i] = 1;\n        else\n            mixed[i] = min(1.0, base[i] / (1.0 - blend[i]));\n    }\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Burn;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_burn(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed;\n    for(int i=0; i<3; i++)\n    {\n        if(base[i] == 1)\n            mixed[i] = 1;\n        else if(blend[i] == 0)\n            mixed[i] = 0;\n        else\n            mixed[i] = 1 - min(1, (1.0 - base[i]) / blend[i]);\n    }\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Subtract;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_subtract(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = (base - blend).rgb;\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Difference;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_difference(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = abs(base - blend).rgb;\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Divide;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_divide(float opacity, vec4 base, vec4 blend, int mode)\n{\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = (base / blend).rgb;\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Hue;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_hue(float opacity, vec4 base, vec4 blend, int mode)\n{\n    vec3 base_hsv = rgb_to_hsv(base.rgb);\n    vec3 blend_hsv = rgb_to_hsv(blend.rgb);\n    vec3 mixed_hsv = base_hsv;\n    mixed_hsv.x = blend_hsv.x;\n    if(blend_hsv.y == 0) mixed_hsv.y = 0;\n\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = hsv_to_rgb(mixed_hsv);\n    if(mode == 2 || mode == 3)//sRGB\n    {\n        mixed = linear_to_srgb(mixed);\n    }\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Saturation;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_saturation(float opacity, vec4 base, vec4 blend, int mode)\n{\n    vec3 base_hsv = rgb_to_hsv(base.rgb);\n    vec3 blend_hsv = rgb_to_hsv(blend.rgb);\n    vec3 mixed_hsv = base_hsv;\n    if(base_hsv.y != 0) mixed_hsv.y = blend_hsv.y;\n\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = hsv_to_rgb(mixed_hsv);\n    if(mode == 2 || mode == 3)//sRGB\n    {\n        mixed = linear_to_srgb(mixed);\n    }\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Value;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_value(float opacity, vec4 base, vec4 blend, int mode)\n{\n    vec3 base_hsv = rgb_to_hsv(base.rgb);\n    vec3 blend_hsv = rgb_to_hsv(blend.rgb);\n    vec3 mixed_hsv = base_hsv;\n    mixed_hsv.z = blend_hsv.z;\n\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = hsv_to_rgb(mixed_hsv);\n    if(mode == 2 || mode == 3)//sRGB\n    {\n        mixed = linear_to_srgb(mixed);\n    }\n    return _blend_common(base, blend, mixed, mode);\n}\n\n/*  META\n    @meta: label=Color;\n    @blend: default=vec4(0);\n    @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0;\n    @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp);\n*/\nvec4 blend_color(float opacity, vec4 base, vec4 blend, int mode)\n{\n    vec3 base_hsv = rgb_to_hsv(base.rgb);\n    vec3 blend_hsv = rgb_to_hsv(blend.rgb);\n    vec3 mixed_hsv = base_hsv;\n    mixed_hsv.xy = blend_hsv.xy;\n\n    _blend_prepare_inputs(base, blend, mode, opacity);\n    vec3 mixed = hsv_to_rgb(mixed_hsv);\n    if(mode == 2 || mode == 3)//sRGB\n    {\n        mixed = linear_to_srgb(mixed);\n    }\n    return _blend_common(base, blend, mixed, mode);\n}\n\n#endif //COMMON_BLEND_MODES_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Filter.glsl",
    "content": "#ifndef NODE_UTILS_2_FILTER_GLSL\n#define NODE_UTILS_2_FILTER_GLSL\n\n/* META GLOBAL\n    @meta: category=Filter;\n*/\n\n#endif //NODE_UTILS_2_FILTER_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Float.glsl",
    "content": "#ifndef NODE_UTILS_2_FLOAT_GLSL\n#define NODE_UTILS_2_FLOAT_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; subcategory=Float;\n*/\n\n/*META @meta: label=Add;*/\nfloat Float_add(float a, float b){ return a+b; }\n/*META @meta: label=Subtract;*/\nfloat Float_subtract(float a, float b){ return a-b; }\n/*META @meta: label=Multiply;*/\nfloat Float_multiply(float a, float b){ return a*b; }\n/*META @meta: label=Divide;*/\nfloat Float_divide(float a, float b){ return a/b; }\n/*META \n    @meta: label=Map Range; \n    @clamped: default=true;\n    @value: default = 0.5;\n    @from_min: default = 0.0;\n    @from_max: default = 1.0;\n    @to_min: default = 0.0;\n    @to_max: default = 1.0;\n*/\nfloat Float_map_range(bool clamped, float value, float from_min, float from_max, float to_min, float to_max)\n{\n    if(clamped)\n    {\n        return map_range_clamped(value, from_min, from_max, to_min, to_max);\n    }\n    else\n    {\n        return map_range(value, from_min, from_max, to_min, to_max);\n    }\n}\n/*META @meta: label=Modulo;*/\nfloat Float_modulo(float a, float b){ return mod(a,b); }\n/*META @meta: label=Power;*/\nfloat Float_pow(float a, float b){ return pow(a, b); }\n/*META @meta: label=Square Root;*/\nfloat Float_sqrt(float a){ return sqrt(a); }\n\n/*META @meta: label=Round;*/\nfloat Float_round(float a){ return round(a); }\n/*META @meta: label=Fractional Part;*/\nfloat Float_fract(float a){ return fract(a); }\n/*META @meta: label=Floor;*/\nfloat Float_floor(float a){ return floor(a); }\n/*META @meta: label=Ceil;*/\nfloat Float_ceil(float a){ return ceil(a); }\n\n/*META @meta: label=Clamp; @b: label=Min; @c: label=Max; */\nfloat Float_clamp(float a, float b, float c){ return clamp(a, b, c); }\n\n/*META @meta: label=Sign;*/\nfloat Float_sign(float a){ return sign(a); }\n/*META @meta: label=Absolute;*/\nfloat Float_abs(float a){ return abs(a); }\n/*META @meta: label=Minimum;*/\nfloat Float_min(float a, float b){ return min(a,b); }\n/*META @meta: label=Maximum;*/\nfloat Float_max(float a, float b){ return max(a,b); }\n/*META @meta: label=Smooth Minimum; @s: min=0.0; */\n\n/*META @meta: label=Mix;*/\nfloat Float_mix(float a, float b, float fac){ return safe_mix(a,b,fac); }\n\n/*META @meta: label=Sine;*/\nfloat Float_sin(float a) { return sin(a); }\n/*META @meta: label=Cosine;*/\nfloat Float_cos(float a) { return cos(a); }\n/*META @meta: label=Tangent;*/\nfloat Float_tan(float a) { return tan(a); }\n/*META @meta: label=Arcsine;*/\nfloat Float_asin(float a) { return asin(a); }\n/*META @meta: label=Arcosine;*/\nfloat Float_acos(float a) { return acos(a); }\n/*META @meta: label=Arctangent;*/\nfloat Float_atan(float a) { return atan(a); }\n/*META @meta: label=Arctangent 2;*/\nfloat Float_atan2(float a, float b) { return atan(a, b); }\n\n/*META @meta: label=Radians to Degrees;*/\nfloat Float_degrees(float a) { return degrees(a); }\n/*META @meta: label=Degrees to Radians;*/\nfloat Float_radians(float a) { return radians(a); }\n\n/*META @meta: label=Equal; @e: default=0.1; min=0.0; */\nbool Float_equal(float a, float b, float e){ return abs(a - b) < abs(e); }\n/*META @meta: label=Not Equal; @e: default=0.1; min=0.0; */\nbool Float_not_equal(float a, float b, float e){ return !Float_equal(a, b, e); }\n/*META @meta: label=Greater;*/\nbool Float_greater(float a, float b){ return a > b; }\n/*META @meta: label=Greater or Equal;*/\nbool Float_greater_or_equal(float a, float b){ return a >= b; }\n/*META @meta: label=Less;*/\nbool Float_less(float a, float b){ return a < b; }\n/*META @meta: label=Less or Equal;*/\nbool Float_less_or_equal(float a, float b){ return a <= b; }\n\n/*META @meta: label=If Else; @a: label=If True; @b: label=If False; */\nfloat Float_if_else(bool condition, float a, float b){ return condition ? a : b; }\n\n#endif //NODE_UTILS_2_FLOAT_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Input.glsl",
    "content": "#ifndef NODE_UTILS_2_INPUT_GLSL\n#define NODE_UTILS_2_INPUT_GLSL\n\n#include \"Shading/BRDF.glsl\"\n\n/*  META GLOBAL\n    @meta: category=Input;\n*/\n\n#if !(defined(NO_POSITION_INPUT) && defined(NO_NORMAL_INPUT))\n\n/*  META\n    @Coordinate_Space: label=Space; subtype=ENUM(Object,World,Camera,Screen); default=1;\n    @Normal: default=NORMAL;\n    @IOR: label=IOR; min=1.0; max=3.0; default=1.45;\n*/\nvoid Geometry(\n    int Coordinate_Space,\n    out vec3 Position,\n    out vec3 Incoming,\n    out vec3 Normal,\n    out vec3 True_Normal,\n    out bool Is_Backfacing,\n    out float Facing,\n    out float Fresnel,\n    out vec3 Reflection,\n    out vec3 Refraction,\n    float IOR\n)\n{\n    Position = POSITION;\n    Incoming = -view_direction();\n    Normal = NORMAL;\n    True_Normal = true_normal();\n    Facing = dot(Normal, Incoming);\n    Is_Backfacing = !is_front_facing();\n    float F0 = pow((1-IOR) / (1+IOR), 2);\n    Fresnel = F_cook_torrance(Facing, F0);\n    Reflection = reflect(view_direction(), Normal);\n    Refraction = refract(Incoming, Normal, 1.0 / IOR);\n\n    if(Coordinate_Space == 0) //Object\n    {\n        mat4 m = inverse(MODEL);\n        Position = transform_point(m, Position);\n        Incoming = transform_normal(m, Incoming);\n        Normal = transform_normal(m, Normal);\n        True_Normal = transform_normal(m, True_Normal);\n        Reflection = transform_normal(m, Reflection);\n        Refraction = transform_normal(m, Refraction);\n    }\n    if(Coordinate_Space == 2) //Camera\n    {\n        mat4 m = CAMERA;\n        Position = transform_point(m, Position);\n        Incoming = transform_normal(m, Incoming);\n        Normal = transform_normal(m, Normal);\n        True_Normal = transform_normal(m, True_Normal);\n        Reflection = transform_normal(m, Reflection);\n        Refraction = transform_normal(m, Refraction);\n    }\n    if(Coordinate_Space == 3) //Screen\n    {\n        Position = project_point_to_screen_coordinates(PROJECTION * CAMERA, Position);\n        mat4 m = CAMERA;\n        Incoming = camera_direction_to_screen_space(transform_normal(m, Incoming));\n        Normal = camera_direction_to_screen_space(transform_normal(m, Normal));\n        True_Normal = camera_direction_to_screen_space(transform_normal(m, True_Normal));\n        Reflection = camera_direction_to_screen_space(transform_normal(m, Reflection));\n        Refraction = camera_direction_to_screen_space(transform_normal(m, Refraction));\n    }\n}\n\n#endif //NO_POSITION_INPUT && NO_NORMAL_INPUT\n\n#ifndef NO_UV_INPUT\n\n/*  META\n    @meta: label=UV Map;\n    @Index: min=0; max=3;\n    @uv: label=UV;\n*/\nvoid UV_Map(\n    int Index,\n    out vec2 uv\n)\n{\n    uv = UV[Index];\n}\n\n/*  META\n    @meta: subcategory=Tangent; label=UV Map;\n    @UV_Index: min=0; max=3;\n*/\nvoid Tangent_UV_Map(\n    int UV_Index,\n    out vec3 Tangent,\n    out vec3 Bitangent\n)\n{\n    Tangent = get_tangent(UV_Index);\n    Bitangent = get_bitangent(UV_Index);\n}\n\n#endif //NO_GEOMETRY_UV\n\n#ifndef NO_NORMAL_INPUT\n\n/*  META\n    @meta: subcategory=Tangent; label=Radial;\n    @Axis: subtype=ENUM(X,Y,Z); default=2;\n    @Object_Space: default=true;\n*/\nvoid Tangent_Radial(\n    int Axis,\n    bool Object_Space,\n    out vec3 Tangent,\n    out vec3 Bitangent\n)\n{\n    vec3 normal = NORMAL;\n    if(Object_Space)\n    {\n        normal = transform_normal(inverse(MODEL), normal);\n    }\n    vec3 _axis = vec3[3](vec3(1,0,0), vec3(0,1,0), vec3(0,0,1))[Axis];\n    Tangent = radial_tangent(NORMAL, _axis);\n    Bitangent = normalize(cross(NORMAL, Tangent)) * (is_front_facing() ? 1 : -1);\n}\n/*  META\n    @meta: subcategory=Tangent; label=Procedural UV;\n*/\nvoid Tangent_Procedural_UV(\n    vec2 UV,\n    out vec3 Tangent,\n    out vec3 Bitangent\n)\n{\n    vec4 t = compute_tangent(UV);\n    Tangent = t.xyz;\n    Bitangent = normalize(cross(NORMAL, Tangent)) * t.w;\n}\n\n#endif //NO_NORMAL_INPUT\n\n#ifndef NO_VERTEX_COLOR_INPUT\n\n/*  META\n    @Index: min=0; max=3;\n    @uv: label=UV;\n*/\nvoid Vertex_Color(\n    int Index,\n    out vec4 Vertex_Color\n)\n{\n    Vertex_Color = COLOR[Index];\n}\n\n#endif //NO_VERTEX_COLOR_INPUT\n\n#ifndef NO_ID_INPUT\n\n/*  META\n    @meta: label=Id;\n*/\nvoid ID_Node(\n    out vec4 Object_Id,\n    out vec4 Custom_Id_A,\n    out vec4 Custom_Id_B,\n    out vec4 Custom_Id_C\n)\n{\n    Object_Id = unpackUnorm4x8(ID.x);\n    Custom_Id_A = unpackUnorm4x8(ID.y);\n    Custom_Id_B = unpackUnorm4x8(ID.z);\n    Custom_Id_C = unpackUnorm4x8(ID.w);\n}\n\n#endif //NO_ID_INPUT\n\n#ifndef NO_MODEL_INPUT\n\nvoid Object_Info(\n    out vec3 Position,\n    out mat4 Rotation,\n    out vec3 Scale,\n    out vec4 Id,\n    out mat4 Matrix\n)\n{\n    Position = MODEL[3].xyz;\n    vec3 i = MODEL[0].xyz;\n    vec3 j = MODEL[1].xyz;\n    vec3 k = MODEL[2].xyz;\n    //TODO: Signed Scale?\n    Scale = vec3(length(i), length(j), length(k));\n    i /= Scale.x;\n    j /= Scale.y;\n    k /= Scale.z;\n    Rotation = mat4(mat3(i,j,k));\n\n    Id = unpackUnorm4x8(IO_ID.x);\n    Matrix = MODEL;\n}\n\n#endif //NO_MODEL_INPUT\n\n#ifndef NO_CAMERA_INPUT\n\n/*  META\n    @Screen_UV: label=Screen UV;\n*/\nvoid Camera_Data(\n    out vec3 View_Direction,\n    out vec2 Screen_UV,\n    out float Z_Depth,\n    out float View_Distance,\n    out vec3 Camera_Position,\n    out mat4 Camera_Matrix,\n    out mat4 Projection_Matrix,\n    out bool Is_Orthographic\n)\n{\n    Screen_UV = screen_uv();\n    View_Direction = view_direction();\n    Z_Depth = -transform_point(CAMERA, POSITION).z;\n    Camera_Position = camera_position();\n    View_Distance = distance(Camera_Position, POSITION);\n    Camera_Matrix = CAMERA;\n    Projection_Matrix = PROJECTION;\n    Is_Orthographic = is_ortho(PROJECTION);\n}\n\n#endif //NO_CAMERA_INPUT\n\nvoid Render_Info(\n    out vec2 Resolution,\n    out int Current_Sample,\n    out vec2 Sample_Offset\n)\n{\n    Resolution = RESOLUTION;\n    Current_Sample = SAMPLE_COUNT;\n    Sample_Offset = SAMPLE_OFFSET;\n}\n\nvoid Time_Info(\n    out float Time,\n    out int Frame\n)\n{\n    Time = TIME;\n    Frame = FRAME;\n}\n\nvoid Random(\n    float seed,\n    out vec4 per_object,\n    out vec4 per_sample,\n    out vec4 per_pixel\n)\n{\n    per_object = random_per_object(seed);\n    per_sample = random_per_sample(seed);\n    per_pixel = random_per_pixel(seed);\n}\n\n#if !(defined(NO_NORMAL_INPUT) || defined(NO_UV_INPUT))\n\n/* META\n    @scale: default=vec2(1.0);\n    @Uv: label=UV;\n*/\nvoid Curve_View_Mapping(\n    vec2 scale,\n    out vec2 Uv,\n    out float Facing\n)\n{\n    Uv = curve_view_mapping(UV[0], NORMAL, get_tangent(0), -view_direction()) * scale;\n    Facing = 1 - (abs(Uv.y - 0.5) * 2);\n}\n\n#endif //NO_NORMAL_INPUT || NO_UV_INPUT\n\n#endif //NODE_UTILS_2_INPUT_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Int.glsl",
    "content": "#ifndef NODE_UTILS_2_INT_GLSL\n#define NODE_UTILS_2_INT_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; subcategory=Integer;\n*/\n\n/*META @meta: label=Add;*/\nint Int_add(int a, int b){ return a+b; }\n/*META @meta: label=Subtract;*/\nint Int_subtract(int a, int b){ return a-b; }\n/*META @meta: label=Multiply;*/\nint Int_multiply(int a, int b){ return a*b; }\n/*META @meta: label=Divide;*/\nint Int_divide(int a, int b){ return a/b; }\n/*META @meta: label=Modulo;*/\nint Int_modulo(int a, int b){ return a%b; }\n\n/*META @meta: label=Clamp; @b: label=Min; @c: label=Max; */\nint Int_clamp(int a, int b, int c){ return clamp(a, b, c); }\n\n/*META @meta: label=Sign;*/\nint Int_sign(int a){ return sign(a); }\n/*META @meta: label=Absolute;*/\nint Int_abs(int a){ return abs(a); }\n/*META @meta: label=Minimum;*/\nint Int_min(int a, int b){ return min(a,b); }\n/*META @meta: label=Maximum;*/\nint Int_max(int a, int b){ return max(a,b); }\n\n/*META @meta: label=Equal;*/\nbool Int_equal(int a, int b){ return a == b; }\n/*META @meta: label=Not Equal;*/\nbool Int_not_equal(int a, int b){ return a != b; }\n/*META @meta: label=Greater;*/\nbool Int_greater(int a, int b){ return a > b; }\n/*META @meta: label=Greater or Equal;*/\nbool Int_greater_or_equal(int a, int b){ return a >= b; }\n/*META @meta: label=Less;*/\nbool Int_less(int a, int b){ return a < b; }\n/*META @meta: label=Less or Equal;*/\nbool Int_less_or_equal(int a, int b){ return a <= b; }\n\n/*META @meta: label=If Else; @a: label=If True; @b: label=If False; */\nint Int_if_else(bool condition, int a, int b){ return condition ? a : b; }\n\n#endif //NODE_UTILS_2_INT_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Mat4.glsl",
    "content": "#ifndef NODE_UTILS_2_MAT4_GLSL\n#define NODE_UTILS_2_MAT4_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; subcategory=Matrix;\n*/\n\n/*  META\n    @meta: label=Inverse;\n    @a: label=Matrix; default=mat4(1);\n*/\nmat4 Mat4_inverse(mat4 a)\n{\n    return inverse(a);\n}\n\n/*  META\n    @meta: label=Multiply;\n    @a: default=mat4(1);\n    @b: default=mat4(1);\n*/\nmat4 Mat4_multiply(mat4 a, mat4 b)\n{\n    return a * b;\n}\n\n#endif //NODE_UTILS_2_MAT4_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Parameters.glsl",
    "content": "#ifndef NODE_UTILS_2_PARAMETERS_GLSL\n#define NODE_UTILS_2_PARAMETERS_GLSL\n\n/*META GLOBAL\n    @meta: category=Parameters;\n*/\n\n/*  META\n    @meta: label=Boolean;\n*/\nbool Bool_property(bool b) { return b; }\n/*  META\n    @meta: label=Float;\n*/\nfloat Float_property(float f) { return f; }\n/*  META\n    @meta: label=Integer;\n*/\nint Int_property(int i) { return i; }\n/*  META\n    @meta: label=Vector 2D;\n*/\nvec2 Vec2_property(vec2 v) { return v; }\n\n/*  META\n    @meta: label=Vector 3D;\n    @v: subtype=Vector;\n*/\nvec3 Vec3_property(vec3 v) { return v; }\n/*  META\n    @meta: label=Vector 4D;\n    @v: subtype=Vector;\n*/\nvec4 Vec4_property(vec4 v) { return v; }\n\n/*  META\n    @meta: label=RGB Color;\n    @v: subtype=Color;\n*/\nvec3 Vec3_color_property(vec3 v) { return v; }\n/*  META\n    @meta: label=RGBA Color;\n    @v: subtype=Color;\n*/\nvec4 Vec4_color_property(vec4 v) { return v; }\n\n#ifdef GL_ARB_bindless_texture\n/*  META\n    @meta: label=Color Ramp;\n*/\nsampler1D Sampler1D_property(sampler1D color_ramp) { return color_ramp; }\n/*  META\n    @meta: label=Image;\n*/\nsampler2D Sampler2D_property(sampler2D image) { return image; }\n#endif\n\n#endif //NODE_UTILS_2_PARAMETERS_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Texturing.glsl",
    "content": "#ifndef NODE_UTILS_2_TEXTURING_GLSL\n#define NODE_UTILS_2_TEXTURING_GLSL\n\n#include \"Procedural/Fractal_Noise.glsl\"\n#include \"Procedural/Cell_Noise.glsl\"\n\n/*  META GLOBAL\n    @meta: category=Texturing;\n*/\n\n/*  META\n    @UV: label=UV; default=UV[0];\n    @Smooth_Interpolation: default=true;\n*/\nvoid Image(sampler2D Image, vec2 UV, bool Smooth_Interpolation, out vec4 Color, out vec2 Resolution)\n{\n    Resolution = vec2(textureSize(Image, 0));\n    if(Smooth_Interpolation)\n    {\n        Color = texture(Image, UV);\n    }\n    else\n    {\n        ivec2 texel = ivec2(mod(UV * Resolution, Resolution));\n        Color = texelFetch(Image, texel, 0);\n    }\n}\n/* META\n    @UV: label=UV; default=UV[0];\n    @UV_Index: label=UV Index;\n*/\nvoid Normal_Map(sampler2D Texture, vec2 UV, int UV_Index, out vec3 Normal)\n{   \n    Normal = sample_normal_map(Texture, UV_Index, UV);\n}\n\n/* META\n    @tex: label=Texture;\n    @uv: label=UV; default=UV[0];\n    @dimensions: default=(1,1);\n    @page: min=0.0;\n    @origin: subtype=ENUM(Top Left,Bottom Left);\n*/\nvec4 Flipbook(sampler2D tex, vec2 uv, ivec2 dimensions, float page, int origin)\n{   \n    int f = int(floor(page));\n    float frac = fract(page);\n    vec4 c = sample_flipbook(tex, uv, dimensions, f, origin == 0 ? true : false);\n    if(frac == 0.0)\n    {\n        return c;\n    }\n    return mix(\n        c,\n        sample_flipbook(tex, uv, dimensions, f + 1, origin == 0 ? true : false),\n        fract(page)\n    );\n}\n\n/* META\n    @tex: label=Texture;\n    @uv: label=UV; default=UV[0];\n    @flow: default=\"vec2(0.0)\";\n    @samples: default=2; min=1 ;\n*/\nvec4 Flowmap(sampler2D tex, vec2 uv, vec2 flow, float progression, int samples)\n{\n    return flowmap(tex, uv, flow, progression, samples);\n}\n\n/*  META\n    @Normal: subtype=Normal; default=NORMAL;\n*/\nvoid Matcap(sampler2D Matcap, vec3 Normal, out vec4 Color, out vec2 UV)\n{\n    UV = matcap_uv(Normal);\n    Color = sample_matcap(Matcap, Normal);\n}\n\n/*  META\n    @meta: label=HDRI;\n    @Normal: subtype=Normal; default=NORMAL;\n*/\nvoid Hdri(sampler2D Hdri, vec3 Normal, out vec4 Color, out vec2 UV)\n{\n    UV = hdri_uv(Normal);\n    Color = sample_hdri(Hdri, Normal);\n}\n\n/*  META\n    @meta: subcategory=Noise; label=Infinite;\n    @coord: subtype=Vector; default=POSITION;\n    @scale: default=5.0;\n    @detail: default=3.0; min=1.0; max=16.0;\n    @balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n*/\nvec4 Noise(vec3 coord, float seed, float scale, float detail, float balance)\n{\n    detail = min(detail, 16);\n    return fractal_noise(vec4(coord * scale, seed), detail, balance);\n}\n\n/*  META\n    @meta: subcategory=Noise; label=Tiled;\n    @coord: subtype=Vector; default=POSITION;\n    @scale: default=5.0;\n    @detail: default=3.0; min=1.0; max=16.0;\n    @balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n    @tile_size: subtype=Vector; default=ivec4(5); min=2;\n*/\nvec4 Noise_Tiled(vec3 coord, float seed, float scale, float detail, float balance, ivec4 tile_size)\n{\n    detail = min(detail, 16);\n    tile_size = max(ivec4(2), tile_size);\n    return fractal_noise(vec4(coord * scale, seed), detail, balance, tile_size);\n}\n\n/* META\n    @meta: subcategory=Voronoi; label=Infinite;\n    @coord: subtype=Vector; default=POSITION;\n    @scale: default=5.0;\n*/\nvoid Voronoi( \n    vec3 coord, float seed,\n    float scale,\n    out vec4 cell_color,\n    out vec4 cell_position,\n    out float cell_distance\n)\n{\n    CellNoiseResult result = cell_noise(vec4(coord * scale, seed));\n    cell_color = result.cell_color;\n    cell_position = result.cell_position;\n    cell_distance = result.cell_distance;\n}\n\n/* META\n    @meta: subcategory=Voronoi; label=Tiled;\n    @coord: subtype=Vector; default=POSITION;\n    @scale: default=5.0;\n    @tile_size: subtype=Vector; default=ivec4(5); min=2;\n*/\nvoid Voronoi_Tiled(\n    vec3 coord, float seed,\n    float scale,\n    ivec4 tile_size,\n    out vec4 cell_color,\n    out vec4 cell_position,\n    out float cell_distance\n)\n{\n    tile_size = max(ivec4(2), tile_size);\n    CellNoiseResult result = cell_noise(vec4(coord * scale, seed), tile_size);\n    cell_color = result.cell_color;\n    cell_position = result.cell_position;\n    cell_distance = result.cell_distance;\n}\n\n#include \"Procedural/Bayer.glsl\"\n\n/* META\n    @size: subtype=ENUM(2x2,3x3,4x4,8x8); default=2;\n    @texel: default=vec2(screen_pixel());\n*/\nfloat bayer_pattern(int size, vec2 texel)\n{\n    switch(size)\n    {\n        case 0: return bayer_2x2(ivec2(texel));\n        case 1: return bayer_3x3(ivec2(texel));\n        case 2: return bayer_4x4(ivec2(texel));\n        case 3: return bayer_8x8(ivec2(texel));\n    }\n\n    return 0.0;\n}\n/* META @meta: subcategory=Gradient; label=Linear; @value: default=UV[0].x; */\nfloat Linear_Gradient(float value)\n{\n    return clamp(value, 0.0, 1.0);\n}\n/* META @meta: subcategory=Gradient; label=Quadratic; @value: default=UV[0].x; */\nfloat Quadratic_Gradient(float value)\n{\n    return clamp(value*value, 0.0, 1.0);\n}\n/* META @meta: subcategory=Gradient; label=Easing; @value: default=UV[0].x; */\nfloat Easing_Gradient(float value)\n{\n    float r = clamp(value, 0.0, 1.0);\n    float t = r*r;\n    return (3.0 * t - 2.0 * t * r);\n}\n/* META @meta: subcategory=Gradient; label=Diagonal; @value: label=UV; default=UV[0]; */\nfloat Diagonal_Gradient(vec2 value)\n{\n    return (value.x + value.y) * 0.5;\n}\n/* META @meta: subcategory=Gradient; label=Spherical; @value: label=Vector; subtype=Vector; default=POSITION; */\nfloat Spherical_Gradient(vec3 value)\n{\n    return max(0.999999 - length(value), 0.0);\n}\n/* META @meta: subcategory=Gradient; label=Quadratic Sphere; @value: label=Vector; subtype=Vector; default=POSITION; */\nfloat Quadratic_Sphere_Gradient(vec3 value)\n{\n    return pow(Spherical_Gradient(value), 2.0);\n}\n/* META @meta: subcategory=Gradient; label=Radial; @value: label=UV; default=UV[0]; */\nfloat Radial_Gradient(vec2 value)\n{\n    return atan(value.x, value.y) / (M_PI * 2) + 0.5;\n}\n\n/* META @meta: label=Wave; @mode: subtype=ENUM(Sine,Saw,Triangle); @value: label=Coord; default=UV[0].x; @scale: default=5.0;*/\nfloat Wave_Texture(int mode, float value, float scale, float phase)\n{\n    switch(mode)\n    {\n        case 0:\n            value = value * scale * M_PI * 2.0 + phase;\n            return sin(value - PI/2.0) * 0.5 + 0.5;\n        case 1:\n            value = value * scale + phase;\n            return fract(value);\n        case 2:\n            value = value * scale + phase;\n            return 1.0 - (abs(fract(value) - 0.5) * 2.0);\n    }\n}\n\n#endif //NODE_UTILS_2_TEXTURING_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Vec2.glsl",
    "content": "#ifndef NODE_UTILS_2_VEC2_GLSL\n#define NODE_UTILS_2_VEC2_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; subcategory=Vector 2D;\n*/\n\n/* META @meta: label=Add; */\nvec2 Vec2_add(vec2 a, vec2 b){ return a+b; }\n/* META @meta: label=Subtract; */\nvec2 Vec2_subtract(vec2 a, vec2 b){ return a-b; }\n/* META @meta: label=Multiply; */\nvec2 Vec2_multiply(vec2 a, vec2 b){ return a*b; }\n/* META @meta: label=Divide; */\nvec2 Vec2_divide(vec2 a, vec2 b){ return a/b; }\n/* META @meta: label=Scale; */\nvec2 Vec2_scale(vec2 a, float fac){ return a*fac; }\n/*META \n    @meta: label=Map Range; \n    @clamped: default=true;\n    @a: label=UV; default = 'vec2(0.5)';\n    @from_min: default = vec2(0.0);\n    @from_max: default = vec2(1.0);\n    @to_min: default = vec2(0.0);\n    @to_max: default = vec2(1.0);\n*/\nvec2 Vec2_map_range(bool clamped, vec2 a, vec2 from_min, vec2 from_max, vec2 to_min, vec2 to_max)\n{\n    if(clamped)\n    {\n        return map_range_clamped(a, from_min, from_max, to_min, to_max);\n    }\n    else\n    {\n        return map_range(a, from_min, from_max, to_min, to_max);\n    }\n}\n/* META @meta: label=Modulo; */\nvec2 Vec2_modulo(vec2 a, vec2 b){ return mod(a,b); }\n/* META @meta: label=Power; */\nvec2 Vec2_pow(vec2 a, vec2 b){ return pow(a, b); }\n/* META @meta: label=Square Root; */\nvec2 Vec2_sqrt(vec2 a){ return sqrt(a); }\n/* META @meta: label=Distort; */\nvec2 Vec2_distort(vec2 a, vec2 b, float fac) { return distort(a,b,fac); }\n\n/* META @meta: label=Round; */\nvec2 Vec2_round(vec2 a){ return round(a); }\n/* META @meta: label=Fraction; */\nvec2 Vec2_fract(vec2 a){ return fract(a); }\n/* META @meta: label=Floor; */\nvec2 Vec2_floor(vec2 a){ return floor(a); }\n/* META @meta: label=Ceil; */\nvec2 Vec2_ceil(vec2 a){ return ceil(a); }\n/* META @meta: label=Snap; */\nvec2 Vec2_snap(vec2 a, vec2 b){ return snap(a,b);}\n\n/* META @meta: label=Clamp; @b: label=Min; @c: label=Max; */\nvec2 Vec2_clamp(vec2 a, vec2 b, vec2 c){ return clamp(a, b, c); }\n\n/* META @meta: label=Sign; */\nvec2 Vec2_sign(vec2 a){ return sign(a); }\n/* META @meta: label=Absolute; */\nvec2 Vec2_abs(vec2 a){ return abs(a); }\n/* META @meta: label=Min; */\nvec2 Vec2_min(vec2 a, vec2 b){ return min(a,b); }\n/* META @meta: label=Max; */\nvec2 Vec2_max(vec2 a, vec2 b){ return max(a,b); }\n\n/* META @meta: label=Mix 2D; @c: label=Factor; */\nvec2 Vec2_mix(vec2 a, vec2 b, vec2 c){ return safe_mix(a,b,c); }\n/* META @meta: label=Mix; */\nvec2 Vec2_mix_float(vec2 a, vec2 b, float fac){ return safe_mix(a,b,fac); }\n\n/* META @meta: label=Normalize; */\nvec2 Vec2_normalize(vec2 a){ return a != vec2(0) ? normalize(a) : vec2(0); }\n\n/* META @meta: label=Length; */\nfloat Vec2_length(vec2 a){ return a != vec2(0) ? length(a) : 0; }\n/* META @meta: label=Distance; */\nfloat Vec2_distance(vec2 a, vec2 b){ return a != b ? distance(a,b) : 0; }\n/* META @meta: label=Dot Product; */\nfloat Vec2_dot_product(vec2 a, vec2 b){ return dot(a,b); }\n\n/* META @meta: label=Sine; */\nvec2 Vec2_sin(vec2 a) { return sin(a); }\n/* META @meta: label=Cosine; */\nvec2 Vec2_cos(vec2 a) { return cos(a); }\n/* META @meta: label=Tangent; */\nvec2 Vec2_tan(vec2 a) { return tan(a); }\n/* META @meta: label=Rotate; */\nvec2 Vec2_rotate(vec2 a, float angle) { return rotate_2d(a, angle); }\n/* META @meta: label=Angle; */\nfloat Vec2_angle(vec2 a, vec2 b) { return vector_angle(a, b); }\n\n/* META @meta: label=Equal; */\nbool Vec2_equal(vec2 a, vec2 b){ return a == b; }\n/* META @meta: label=Not Equal; */\nbool Vec2_not_equal(vec2 a, vec2 b){ return a != b; }\n\n/* META @meta: label=If Else; @a: label=If True; @b: label=If False; */\nvec2 Vec2_if_else(bool condition, vec2 a, vec2 b){ return condition ? a : b; }\n\n/* META @meta: label=Combine; */\nvec2 Vec2_combine(float x, float y) { return vec2(x,y);}\n/* META @meta: label=Separate; */\nvoid Vec2_separate(vec2 a, out float x, out float y){ x=a.x; y=a.y; }\n\n#endif //NODE_UTILS_2_VEC2_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Vec3.glsl",
    "content": "#ifndef NODE_UTILS_2_VEC3_GLSL\n#define NODE_UTILS_2_VEC3_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; subcategory=Vector 3D;\n*/\n\n/*META @meta: label=Add; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_add(vec3 a, vec3 b){ return a+b; }\n/*META @meta: label=Subtract; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_subtract(vec3 a, vec3 b){ return a-b; }\n/*META @meta: label=Multiply; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_multiply(vec3 a, vec3 b){ return a*b; }\n/*META @meta: label=Divide; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_divide(vec3 a, vec3 b){ return a/b; }\n/*META @meta: label=Scale; @a: subtype=Vector;*/\nvec3 Vec3_scale(vec3 a, float fac){ return a*fac; }\n/*META \n    @meta: label=Map Range;\n    @clamped: default=true;\n    @a: label=Vector; default = 'vec3(0.5)';\n    @from_min: subtype=Vector; default = vec3(0.0);\n    @from_max: subtype=Vector; default = vec3(1.0);\n    @to_min: subtype=Vector; default = vec3(0.0);\n    @to_max: subtype=Vector; default = vec3(1.0);\n*/\nvec3 Vec3_map_range(bool clamped, vec3 a, vec3 from_min, vec3 from_max, vec3 to_min, vec3 to_max)\n{\n    if(clamped)\n    {\n        return map_range_clamped(a, from_min, from_max, to_min, to_max);\n    }\n    else\n    {\n        return map_range(a, from_min, from_max, to_min, to_max);\n    }\n}\n/*META @meta: label=Modulo; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_modulo(vec3 a, vec3 b){ return mod(a,b); }\n/*META @meta: label=Power; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_pow(vec3 a, vec3 b){ return pow(a, b); }\n/*META @meta: label=Square Root; @a: subtype=Vector;*/\nvec3 Vec3_sqrt(vec3 a){ return sqrt(a); }\n/*META @meta: label=Distort; @a: subtype=Vector; @b: subtype=Vector; */\nvec3 Vec3_distort(vec3 a, vec3 b, float fac) { return distort(a,b,fac); }\n\n/*META @meta: label=Round; @a: subtype=Vector;*/\nvec3 Vec3_round(vec3 a){ return round(a); }\n/*META @meta: label=Fraction; @a: subtype=Vector;*/\nvec3 Vec3_fract(vec3 a){ return fract(a); }\n/*META @meta: label=Floor; @a: subtype=Vector;*/\nvec3 Vec3_floor(vec3 a){ return floor(a); }\n/*META @meta: label=Ceil; @a: subtype=Vector;*/\nvec3 Vec3_ceil(vec3 a){ return ceil(a); }\n/*META @meta: label=Snap; @a: subtype=Vector; @b: subtype=Vector; */\nvec3 Vec3_snap(vec3 a, vec3 b){ return snap(a,b); }\n\n/*META @meta: label=Clamp; @a: subtype=Vector; @b: label=Min; subtype=Vector; @c: label=Max; subtype=Vector;*/\nvec3 Vec3_clamp(vec3 a, vec3 b, vec3 c){ return clamp(a, b, c); }\n\n/*META @meta: label=Sign; @a: subtype=Vector;*/\nvec3 Vec3_sign(vec3 a){ return sign(a); }\n/*META @meta: label=Absolute; @a: subtype=Vector;*/\nvec3 Vec3_abs(vec3 a){ return abs(a); }\n/*META @meta: label=Min; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_min(vec3 a, vec3 b){ return min(a,b); }\n/*META @meta: label=Max; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_max(vec3 a, vec3 b){ return max(a,b); }\n\n/*META @meta: label=Mix 3D; @a: subtype=Vector; @b: subtype=Vector; @c: label=Factor; subtype=Vector;*/\nvec3 Vec3_mix(vec3 a, vec3 b, vec3 c){ return safe_mix(a,b,c); }\n/*META @meta: label=Mix; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_mix_float(vec3 a, vec3 b, float fac){ return safe_mix(a,b,fac); }\n\n/*META @meta: label=Normalize; @a: subtype=Vector;*/\nvec3 Vec3_normalize(vec3 a){ return a != vec3(0) ? normalize(a) : vec3(0); }\n\n/*META @meta: label=Length; @a: subtype=Vector;*/\nfloat Vec3_length(vec3 a){ return a != vec3(0) ? length(a) : 0; }\n/*META @meta: label=Distance; @a: subtype=Vector; @b: subtype=Vector;*/\nfloat Vec3_distance(vec3 a, vec3 b){ return a != b ? distance(a,b) : 0; }\n/*META @meta: label=Dot Product; @a: subtype=Vector; @b: subtype=Vector;*/\nfloat Vec3_dot_product(vec3 a, vec3 b){ return dot(a,b); }\n/*META @meta: label=Cross Product; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_cross_product(vec3 a, vec3 b){ return cross(a,b); }\n/*META @meta: label=Reflect; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_reflect(vec3 a, vec3 b){ return reflect(a,b); }\n/*META @meta: label=Refract; @a: subtype=Vector; @b: subtype=Vector;*/\nvec3 Vec3_refract(vec3 a, vec3 b, float ior){ return refract(a,normalize(b),ior); }\n/*META @meta: label=Faceforward; @a: subtype=Vector; @b: subtype=Vector; @c: subtype=Vector; */\nvec3 Vec3_faceforward(vec3 a, vec3 b, vec3 c){ return faceforward(a,b,c); }\n\n/* META @meta: label=Sine; @a: subtype=Vector; */\nvec3 Vec3_sin(vec3 a) { return sin(a); }\n/* META @meta: label=Cosine; @a: subtype=Vector; */\nvec3 Vec3_cos(vec3 a) { return cos(a); }\n/* META @meta: label=Tangent; @a: subtype=Vector; */\nvec3 Vec3_tan(vec3 a) { return tan(a); }\n/* META @meta: label=Rotate Euler; @a: subtype=Vector; @euler: subtype=Euler; */\nvec3 Vec3_rotate_euler(vec3 a, vec3 euler, bool invert)\n{\n    mat4 m = mat4_rotation_from_euler(euler);\n    if(invert)\n    {\n        m = inverse(m);\n    }\n    return transform_point(m, a);\n}\n/* META @meta: label=Rotate Axis Angle; @a: subtype=Vector; @b: label=Axis; subtype=Vector; default=vec3(0.0, 0.0, 1.0); */\nvec3 Vec3_rotate_axis_angle(vec3 a, vec3 b, float angle) \n{ \n    mat4 m = mat4_rotation_from_quaternion(quaternion_from_axis_angle(b, angle));\n    return transform_point(m, a);\n}\n/* META @meta: label=Angle; @a: subtype=Vector; @b: subtype=Vector; */\nfloat Vec3_angle(vec3 a, vec3 b) { return vector_angle(a, b); }\n\n/*META @meta: label=Equal; @a: subtype=Vector; @b: subtype=Vector;*/\nbool Vec3_equal(vec3 a, vec3 b){ return a == b; }\n/*META @meta: label=Not Equal; @a: subtype=Vector; @b: subtype=Vector;*/\nbool Vec3_not_equal(vec3 a, vec3 b){ return a != b; }\n\n/*META @meta: label=If Else; @a: label=If True; subtype=Vector; @b: label=If False; subtype=Vector;*/\nvec3 Vec3_if_else(bool condition, vec3 a, vec3 b){ return condition ? a : b; }\n\n/* META @meta: label=Combine; */\nvec3 Vec3_combine(float x, float y, float z) { return vec3(x,y,z);}\n/*META @meta: label=Separate; @a: subtype=Vector;*/\nvoid Vec3_separate(vec3 a, out float x, out float y, out float z){ x=a.x; y=a.y; z=a.z; }\n\n#endif //NODE_UTILS_2_VEC3_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Vec4.glsl",
    "content": "#ifndef NODE_UTILS_2_VEC4_GLSL\n#define NODE_UTILS_2_VEC4_GLSL\n\n/*  META GLOBAL\n    @meta: category=Math; subcategory=Vector 4D;\n*/\n\n/*META @meta: label=Add; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_add(vec4 a, vec4 b){ return a+b; }\n/*META @meta: label=Subtract; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_subtract(vec4 a, vec4 b){ return a-b; }\n/*META @meta: label=Multiply; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_multiply(vec4 a, vec4 b){ return a*b; }\n/*META @meta: label=Divide; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_divide(vec4 a, vec4 b){ return a/b; }\n/*META @meta: label=Scale; @a: subtype=Vector;*/\nvec4 Vec4_scale(vec4 a, float fac){ return a*fac; }\n/*META \n    @meta: label=Map Range; \n    @clamped: default=true;\n    @a: label=Vector; default = 'vec4(0.5)';\n    @from_min: subtype=Vector; default = vec4(0.0);\n    @from_max: subtype=Vector; default = vec4(1.0);\n    @to_min: subtype=Vector; default = vec4(0.0);\n    @to_max: subtype=Vector; default = vec4(1.0);\n*/\nvec4 Vec4_map_range(bool clamped, vec4 a, vec4 from_min, vec4 from_max, vec4 to_min, vec4 to_max)\n{\n    if(clamped)\n    {\n        return map_range_clamped(a, from_min, from_max, to_min, to_max);\n    }\n    else\n    {\n        return map_range(a, from_min, from_max, to_min, to_max);\n    }\n}\n/*META @meta: label=Modulo; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_modulo(vec4 a, vec4 b){ return mod(a,b); }\n/*META @meta: label=Power; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_pow(vec4 a, vec4 b){ return pow(a, b); }\n/*META @meta: label=Square Root; @a: subtype=Vector;*/\nvec4 Vec4_sqrt(vec4 a){ return sqrt(a); }\n/*META @meta: label=Distort; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_distort(vec4 a, vec4 b, float fac) { return distort(a,b,fac); }\n\n/*META @meta: label=Round; @a: subtype=Vector;*/\nvec4 Vec4_round(vec4 a){ return round(a); }\n/*META @meta: label=Fraction; @a: subtype=Vector;*/\nvec4 Vec4_fract(vec4 a){ return fract(a); }\n/*META @meta: label=Floor; @a: subtype=Vector;*/\nvec4 Vec4_floor(vec4 a){ return floor(a); }\n/*META @meta: label=Ceil; @a: subtype=Vector;*/\nvec4 Vec4_ceil(vec4 a){ return ceil(a); }\n\n/*META @meta: label=Clamp; @a: subtype=Vector; @b: label=Min; subtype=Vector; @c: label=Max; subtype=Vector;*/\nvec4 Vec4_clamp(vec4 a, vec4 b, vec4 c){ return clamp(a, b, c); }\n\n/*META @meta: label=Sign; @a: subtype=Vector;*/\nvec4 Vec4_sign(vec4 a){ return sign(a); }\n/*META @meta: label=Absolute; @a: subtype=Vector;*/\nvec4 Vec4_abs(vec4 a){ return abs(a); }\n/*META @meta: label=Min; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_min(vec4 a, vec4 b){ return min(a,b); }\n/*META @meta: label=Max; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_max(vec4 a, vec4 b){ return max(a,b); }\n\n/*META @meta: label=Mix 4D; @a: subtype=Vector; @b: subtype=Vector; @c: label=Factor; subtype=Vector;*/\nvec4 Vec4_mix(vec4 a, vec4 b, vec4 c){ return safe_mix(a,b,c); }\n/*META @meta: label=Mix; @a: subtype=Vector; @b: subtype=Vector;*/\nvec4 Vec4_mix_float(vec4 a, vec4 b, float fac){ return safe_mix(a,b,fac); }\n\n/*META @meta: label=Normalize; @a: subtype=Vector;*/\nvec4 Vec4_normalize(vec4 a){ return a != vec4(0) ? normalize(a) : vec4(0); }\n\n/*META @meta: label=Length; @a: subtype=Vector;*/\nfloat Vec4_length(vec4 a){ return a != vec4(0) ? length(a) : 0; }\n/*META @meta: label=Distance; @a: subtype=Vector; @b: subtype=Vector;*/\nfloat Vec4_distance(vec4 a, vec4 b){ return a != b ? distance(a,b) : 0; }\n/*META @meta: label=Dot Product; @a: subtype=Vector; @b: subtype=Vector;*/\nfloat Vec4_dot_product(vec4 a, vec4 b){ return dot(a,b); }\n\n/* META @meta: label=Sine; @a: subtype=Vector; */\nvec4 Vec4_sin(vec4 a) { return sin(a); }\n/* META @meta: label=Cosine; @a: subtype=Vector; */\nvec4 Vec4_cos(vec4 a) { return cos(a); }\n/* META @meta: label=Tangent; @a: subtype=Vector; */\nvec4 Vec4_tan(vec4 a) { return tan(a); }\n/* META @meta: label=Angle; @a: subtype=Vector; @b: subtype=Vector; */\nfloat Vec4_angle(vec4 a, vec4 b) { return vector_angle(a, b); }\n\n/*META @meta: label=Equal; @a: subtype=Vector; @b: subtype=Vector;*/\nbool Vec4_equal(vec4 a, vec4 b){ return a == b; }\n/*META @meta: label=Not Equal; @a: subtype=Vector; @b: subtype=Vector;*/\nbool Vec4_not_equal(vec4 a, vec4 b){ return a != b; }\n\n/*META @a: label=If True; subtype=Vector; @b: label=If False; subtype=Vector;*/\nvec4 Vec4_if_else(bool condition, vec4 a, vec4 b){ return condition ? a : b; }\n\n/*META @meta: label=Combine; */\nvec4 Vec4_combine(float r, float g, float b, float a) { return vec4(r,g,b,a);}\n/* META @meta: label=Combine Color; @c: subtype=Color; @a: subtype=Slider; min=0.0; max=1.0; default=1.0;*/\nvec4 Vec4_combine_color(vec3 c, float a){ return vec4(c, a); }\n/*META @meta: label=Separate; @a: subtype=Vector; @w: label=A;*/\nvoid Vec4_separate(vec4 a, out float r, out float g, out float b, out float w){ r=a.r; g=a.g; b=a.b; w=a.a; }\n/*META @meta: label=Separate Color; @a: subtype=Color; @w: label=A; */\nvoid Vec4_separate_color(vec4 a, out vec3 c, out float w){ c=a.xyz; w=a.a; }\n\n#endif //NODE_UTILS_2_VEC4_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/Vector.glsl",
    "content": "#ifndef NODE_UTILS_2_VECTOR_GLSL\n#define NODE_UTILS_2_VECTOR_GLSL\n\n/*  META GLOBAL\n    @meta: category=Vector;\n*/\n\n/*  META\n    @Type: subtype=ENUM(Point,Vector,Normal);\n    @From: subtype=ENUM(Object,World,Camera);\n    @To: subtype=ENUM(Object,World,Camera,Screen);\n    @Vector: subtype=Vector;\n*/\nvoid Transform(\n    int Type,\n    int From,\n    int To,\n    inout vec3 Vector\n)\n{\n    mat4 TRANSFORM_CONVERSION_TABLE[3*3] = mat4[3*3](\n        mat4(1), MODEL, CAMERA*MODEL,\n        inverse(MODEL), mat4(1), CAMERA,\n        inverse(CAMERA*MODEL), inverse(CAMERA), mat4(1)\n    );\n\n    mat4 m = TRANSFORM_CONVERSION_TABLE[clamp(From,0,2)*3 + clamp(To,0,2)];\n    bool project = To == 3;\n    if(Type==0)//Point\n    {\n        if(project)\n        {\n            m = PROJECTION * m;\n            Vector = project_point_to_screen_coordinates(m, Vector);\n        }\n        else\n        {\n            Vector = transform_point(m, Vector);\n        }\n    }\n    else\n    {\n        if(Type==1)//Vector\n        {\n            Vector = transform_direction(m, Vector);\n        }\n        if(Type==2)//Normal\n        {\n            Vector = transform_normal(m, Vector);\n        }\n        if (project)\n        {\n            Vector = camera_direction_to_screen_space(Vector);\n        }\n    }\n}\n\n/* META\n    @meta: subcategory=Mapping; label=Point;\n    @vector: subtype=Vector; default='vec3(0.0)';\n    @location: subtype=Vector;\n    @rotation: subtype=Euler;\n    @scale: subtype=Vector; default=vec3(1.0);\n*/\nvec3 mapping_point(vec3 vector, vec3 location, vec3 rotation, vec3 scale)\n{\n    vec3 result = vector * scale;\n    result = transform_point(mat4_rotation_from_euler(rotation), result);\n    return result + location;\n}\n\n/* META\n    @meta: subcategory=Mapping; label=Texture;\n    @vector: subtype=Vector; default='vec3(0.0)';\n    @location: subtype=Vector;\n    @rotation: subtype=Euler;\n    @scale: subtype=Vector; default=vec3(1.0);\n*/\nvec3 mapping_texture(vec3 vector, vec3 location, vec3 rotation, vec3 scale)\n{\n    vec3 result = vector - location;\n    result = transform_point(inverse(mat4_rotation_from_euler(rotation)), result);\n    return result / scale;\n}\n\n/* META\n    @meta: subcategory=Mapping; label=Vector;\n    @vector: subtype=Vector; default='vec3(0.0)';\n    @rotation: subtype=Euler;\n    @scale: subtype=Vector; default=vec3(1.0);\n*/\nvec3 mapping_vector(vec3 vector, vec3 rotation, vec3 scale)\n{\n    return transform_direction(mat4_rotation_from_euler(rotation), vector * scale);\n}\n\n/* META\n    @meta: subcategory=Mapping; label=Normal;\n    @vector: subtype=Vector; default='vec3(0.0)';\n    @rotation: subtype=Euler;\n    @scale: subtype=Vector; default=vec3(1.0);\n*/\n\nvec3 mapping_normal(vec3 vector, vec3 rotation, vec3 scale)\n{\n    return normalize(mapping_vector(vector, rotation, scale));\n}\n\n#endif // COMMON_VECTOR_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/conversion.glsl",
    "content": "#ifndef NODE_UTILS_2_CONVERSION_GLSL\n#define NODE_UTILS_2_CONVERSION_GLSL\n\n/*  META GLOBAL\n    @meta: internal = true;\n*/\n\n//float\nfloat float_from_int(int i) { return float(i); }\nfloat float_from_uint(uint u) { return float(u); }\nfloat float_from_bool(bool b) { return float(b); }\nfloat float_from_vec2(vec2 v) { return (v.x + v.y)/2.0; }\nfloat float_from_vec3(vec3 v) { return (v.x + v.y + v.z)/3.0; }\nfloat float_from_vec4(vec4 c) { return (c.x + c.y + c.z)/3.0 * c.a; }\n\n//int\nint int_from_float(float f) { return int(f); }\nint int_from_uint(uint u) { return int(u); }\nint int_from_bool(bool b) { return int(b); }\nint int_from_vec2(vec2 v) { return int(float_from_vec2(v)); }\nint int_from_vec3(vec3 v) { return int(float_from_vec3(v)); }\nint int_from_vec4(vec4 c) { return int(float_from_vec4(c)); }\n\n//uint\nuint uint_from_float(float f) { return uint(f); }\nuint uint_from_int(int i) { return uint(i); }\nuint uint_from_bool(bool b) { return uint(b); }\nuint uint_from_vec2(vec2 v) { return uint(float_from_vec2(v)); }\nuint uint_from_vec3(vec3 v) { return uint(float_from_vec3(v)); }\nuint uint_from_vec4(vec4 c) { return uint(float_from_vec4(c)); }\n\n//bool\nbool bool_from_float(float f) { return bool(f); }\nbool bool_from_int(int i) { return bool(i); }\nbool bool_from_uint(uint u) { return bool(u); }\nbool bool_from_vec2(vec2 v) { return bool(float_from_vec2(v)); }\nbool bool_from_vec3(vec3 v) { return bool(float_from_vec3(v)); }\nbool bool_from_vec4(vec4 c) { return bool(float_from_vec4(c)); }\n\n//vec2\nvec2 vec2_from_float(float f) { return vec2(f); }\nvec2 vec2_from_int(int i) { return vec2(i); }\nvec2 vec2_from_uint(uint u) { return vec2(u); }\nvec2 vec2_from_bool(bool b) { return vec2(b); }\nvec2 vec2_from_vec3(vec3 v) { return vec2(v.xy); }\nvec2 vec2_from_vec4(vec4 v) { return vec2(v.xy); }\nvec2 vec2_from_ivec2(ivec2 v) { return vec2(v); }\nvec2 vec2_from_ivec3(ivec3 v) { return vec2(v.xy); }\nvec2 vec2_from_ivec4(ivec4 v) { return vec2(v.xy); }\nvec2 vec2_from_uvec2(uvec2 v) { return vec2(v); }\nvec2 vec2_from_uvec3(uvec3 v) { return vec2(v.xy); }\nvec2 vec2_from_uvec4(uvec4 v) { return vec2(v.xy); }\nvec2 vec2_from_bvec2(bvec2 v) { return vec2(v); }\nvec2 vec2_from_bvec3(bvec3 v) { return vec2(v.xy); }\nvec2 vec2_from_bvec4(bvec4 v) { return vec2(v.xy); }\n\n//vec3\nvec3 vec3_from_float(float f) { return vec3(f); }\nvec3 vec3_from_int(int i) { return vec3(i); }\nvec3 vec3_from_uint(uint u) { return vec3(u); }\nvec3 vec3_from_bool(bool b) { return vec3(b); }\nvec3 vec3_from_vec2(vec2 v) { return vec3(v, 0); }\nvec3 vec3_from_vec4(vec4 v) { return vec3(v.xyz); }\nvec3 vec3_from_ivec2(ivec2 v) { return vec3(v, 0); }\nvec3 vec3_from_ivec3(ivec3 v) { return vec3(v); }\nvec3 vec3_from_ivec4(ivec4 v) { return vec3(v.xyz); }\nvec3 vec3_from_uvec2(uvec2 v) { return vec3(v, 0); }\nvec3 vec3_from_uvec3(uvec3 v) { return vec3(v); }\nvec3 vec3_from_uvec4(uvec4 v) { return vec3(v.xyz); }\nvec3 vec3_from_bvec2(bvec2 v) { return vec3(v, 0); }\nvec3 vec3_from_bvec3(bvec3 v) { return vec3(v); }\nvec3 vec3_from_bvec4(bvec4 v) { return vec3(v.xyz); }\n\n//vec4\nvec4 vec4_from_float(float f) { return vec4(f, f, f, 1); }\nvec4 vec4_from_int(int i) { return vec4(i, i, i, 1); }\nvec4 vec4_from_uint(uint u) { return vec4(u, u, u, 1); }\nvec4 vec4_from_bool(bool b) { return vec4(b, b, b, 1); }\nvec4 vec4_from_vec2(vec2 v) { return vec4(v, 0, 1); }\nvec4 vec4_from_vec3(vec3 v) { return vec4(v, 1); }\nvec4 vec4_from_ivec2(ivec2 v) { return vec4(v, 0, 1); }\nvec4 vec4_from_ivec3(ivec3 v) { return vec4(v, 1); }\nvec4 vec4_from_ivec4(ivec4 v) { return vec4(v); }\nvec4 vec4_from_uvec2(uvec2 v) { return vec4(v, 0, 1); }\nvec4 vec4_from_uvec3(uvec3 v) { return vec4(v, 1); }\nvec4 vec4_from_uvec4(uvec4 v) { return vec4(v); }\nvec4 vec4_from_bvec2(bvec2 v) { return vec4(v, 0, 1); }\nvec4 vec4_from_bvec3(bvec3 v) { return vec4(v, 1); }\nvec4 vec4_from_bvec4(bvec4 v) { return vec4(v); }\n\n//ivec2\nivec2 ivec2_from_float(float f) { return ivec2(f); }\nivec2 ivec2_from_int(int i) { return ivec2(i); }\nivec2 ivec2_from_uint(uint u) { return ivec2(u); }\nivec2 ivec2_from_bool(bool b) { return ivec2(b); }\nivec2 ivec2_from_vec2(vec2 v) { return ivec2(v); }\nivec2 ivec2_from_vec3(vec3 v) { return ivec2(v.xy); }\nivec2 ivec2_from_vec4(vec4 v) { return ivec2(v.xy); }\nivec2 ivec2_from_ivec3(ivec3 v) { return ivec2(v.xy); }\nivec2 ivec2_from_ivec4(ivec4 v) { return ivec2(v.xy); }\nivec2 ivec2_from_uvec2(uvec2 v) { return ivec2(v); }\nivec2 ivec2_from_uvec3(uvec3 v) { return ivec2(v.xy); }\nivec2 ivec2_from_uvec4(uvec4 v) { return ivec2(v.xy); }\nivec2 ivec2_from_bvec2(bvec2 v) { return ivec2(v); }\nivec2 ivec2_from_bvec3(bvec3 v) { return ivec2(v.xy); }\nivec2 ivec2_from_bvec4(bvec4 v) { return ivec2(v.xy); }\n\n//ivec3\nivec3 ivec3_from_float(float f) { return ivec3(f); }\nivec3 ivec3_from_int(int i) { return ivec3(i); }\nivec3 ivec3_from_uint(uint u) { return ivec3(u); }\nivec3 ivec3_from_bool(bool b) { return ivec3(b); }\nivec3 ivec3_from_vec2(vec2 v) { return ivec3(v, 0); }\nivec3 ivec3_from_vec3(vec3 v) { return ivec3(v); }\nivec3 ivec3_from_vec4(vec4 v) { return ivec3(v.xyz); }\nivec3 ivec3_from_ivec2(ivec2 v) { return ivec3(v, 0); }\nivec3 ivec3_from_ivec4(ivec4 v) { return ivec3(v.xyz); }\nivec3 ivec3_from_uvec2(uvec2 v) { return ivec3(v, 0); }\nivec3 ivec3_from_uvec3(uvec3 v) { return ivec3(v); }\nivec3 ivec3_from_uvec4(uvec4 v) { return ivec3(v.xyz); }\nivec3 ivec3_from_bvec2(bvec2 v) { return ivec3(v, 0); }\nivec3 ivec3_from_bvec3(bvec3 v) { return ivec3(v); }\nivec3 ivec3_from_bvec4(bvec4 v) { return ivec3(v.xyz); }\n\n//ivec4\nivec4 ivec4_from_float(float f) { return ivec4(f, f, f, 1); }\nivec4 ivec4_from_int(int i) { return ivec4(i, i, i, 1); }\nivec4 ivec4_from_uint(uint u) { return ivec4(u, u, u, 1); }\nivec4 ivec4_from_bool(bool b) { return ivec4(b, b, b, 1); }\nivec4 ivec4_from_vec2(vec2 v) { return ivec4(v, 0, 1); }\nivec4 ivec4_from_vec3(vec3 v) { return ivec4(v, 1); }\nivec4 ivec4_from_vec4(vec4 v) { return ivec4(v); }\nivec4 ivec4_from_ivec2(ivec2 v) { return ivec4(v, 0, 1); }\nivec4 ivec4_from_ivec3(ivec3 v) { return ivec4(v, 1); }\nivec4 ivec4_from_uvec2(uvec2 v) { return ivec4(v, 0, 1); }\nivec4 ivec4_from_uvec3(uvec3 v) { return ivec4(v, 1); }\nivec4 ivec4_from_uvec4(uvec4 v) { return ivec4(v); }\nivec4 ivec4_from_bvec2(bvec2 v) { return ivec4(v, 0, 1); }\nivec4 ivec4_from_bvec3(bvec3 v) { return ivec4(v, 1); }\nivec4 ivec4_from_bvec4(bvec4 v) { return ivec4(v); }\n\n//uvec2\nuvec2 uvec2_from_float(float f) { return uvec2(f); }\nuvec2 uvec2_from_int(int i) { return uvec2(i); }\nuvec2 uvec2_from_uint(uint u) { return uvec2(u); }\nuvec2 uvec2_from_bool(bool b) { return uvec2(b); }\nuvec2 uvec2_from_vec2(vec2 v) { return uvec2(v); }\nuvec2 uvec2_from_vec3(vec3 v) { return uvec2(v.xy); }\nuvec2 uvec2_from_vec4(vec4 v) { return uvec2(v.xy); }\nuvec2 uvec2_from_ivec2(ivec2 v) { return uvec2(v); }\nuvec2 uvec2_from_ivec3(ivec3 v) { return uvec2(v.xy); }\nuvec2 uvec2_from_ivec4(ivec4 v) { return uvec2(v.xy); }\nuvec2 uvec2_from_uvec3(uvec3 v) { return uvec2(v.xy); }\nuvec2 uvec2_from_uvec4(uvec4 v) { return uvec2(v.xy); }\nuvec2 uvec2_from_bvec2(bvec2 v) { return uvec2(v); }\nuvec2 uvec2_from_bvec3(bvec3 v) { return uvec2(v.xy); }\nuvec2 uvec2_from_bvec4(bvec4 v) { return uvec2(v.xy); }\n\n//uvec3\nuvec3 uvec3_from_float(float f) { return uvec3(f); }\nuvec3 uvec3_from_int(int i) { return uvec3(i); }\nuvec3 uvec3_from_uint(uint u) { return uvec3(u); }\nuvec3 uvec3_from_bool(bool b) { return uvec3(b); }\nuvec3 uvec3_from_vec2(vec2 v) { return uvec3(v, 0); }\nuvec3 uvec3_from_vec3(vec3 v) { return uvec3(v); }\nuvec3 uvec3_from_vec4(vec4 v) { return uvec3(v.xyz); }\nuvec3 uvec3_from_ivec2(ivec2 v) { return uvec3(v, 0); }\nuvec3 uvec3_from_ivec3(ivec3 v) { return uvec3(v); }\nuvec3 uvec3_from_ivec4(ivec4 v) { return uvec3(v.xyz); }\nuvec3 uvec3_from_uvec2(uvec2 v) { return uvec3(v, 0); }\nuvec3 uvec3_from_uvec4(uvec4 v) { return uvec3(v.xyz); }\nuvec3 uvec3_from_bvec2(bvec2 v) { return uvec3(v, 0); }\nuvec3 uvec3_from_bvec3(bvec3 v) { return uvec3(v); }\nuvec3 uvec3_from_bvec4(bvec4 v) { return uvec3(v.xyz); }\n\n//uvec4\nuvec4 uvec4_from_float(float f) { return uvec4(f, f, f, 1); }\nuvec4 uvec4_from_int(int i) { return uvec4(i, i, i, 1); }\nuvec4 uvec4_from_uint(uint u) { return uvec4(u, u, u, 1); }\nuvec4 uvec4_from_bool(bool b) { return uvec4(b, b, b, 1); }\nuvec4 uvec4_from_vec2(vec2 v) { return uvec4(v, 0, 1); }\nuvec4 uvec4_from_vec3(vec3 v) { return uvec4(v, 1); }\nuvec4 uvec4_from_vec4(vec4 v) { return uvec4(v); }\nuvec4 uvec4_from_ivec2(ivec2 v) { return uvec4(v, 0, 1); }\nuvec4 uvec4_from_ivec3(ivec3 v) { return uvec4(v, 1); }\nuvec4 uvec4_from_ivec4(ivec4 v) { return uvec4(v); }\nuvec4 uvec4_from_uvec2(uvec2 v) { return uvec4(v, 0, 1); }\nuvec4 uvec4_from_uvec3(uvec3 v) { return uvec4(v, 1); }\nuvec4 uvec4_from_bvec2(bvec2 v) { return uvec4(v, 0, 1); }\nuvec4 uvec4_from_bvec3(bvec3 v) { return uvec4(v, 1); }\nuvec4 uvec4_from_bvec4(bvec4 v) { return uvec4(v); }\n\n//bvec2\nbvec2 bvec2_from_float(float f) { return bvec2(f); }\nbvec2 bvec2_from_int(int i) { return bvec2(i); }\nbvec2 bvec2_from_uint(uint u) { return bvec2(u); }\nbvec2 bvec2_from_bool(bool b) { return bvec2(b); }\nbvec2 bvec2_from_vec2(vec2 v) { return bvec2(v); }\nbvec2 bvec2_from_vec3(vec3 v) { return bvec2(v.xy); }\nbvec2 bvec2_from_vec4(vec4 v) { return bvec2(v.xy); }\nbvec2 bvec2_from_ivec2(ivec2 v) { return bvec2(v); }\nbvec2 bvec2_from_ivec3(ivec3 v) { return bvec2(v.xy); }\nbvec2 bvec2_from_ivec4(ivec4 v) { return bvec2(v.xy); }\nbvec2 bvec2_from_uvec2(uvec2 v) { return bvec2(v); }\nbvec2 bvec2_from_uvec3(uvec3 v) { return bvec2(v.xy); }\nbvec2 bvec2_from_uvec4(uvec4 v) { return bvec2(v.xy); }\nbvec2 bvec2_from_bvec3(bvec3 v) { return bvec2(v.xy); }\nbvec2 bvec2_from_bvec4(bvec4 v) { return bvec2(v.xy); }\n\n//bvec3\nbvec3 bvec3_from_float(float f) { return bvec3(f); }\nbvec3 bvec3_from_int(int i) { return bvec3(i); }\nbvec3 bvec3_from_uint(uint u) { return bvec3(u); }\nbvec3 bvec3_from_bool(bool b) { return bvec3(b); }\nbvec3 bvec3_from_vec2(vec2 v) { return bvec3(v, 0); }\nbvec3 bvec3_from_vec3(vec3 v) { return bvec3(v); }\nbvec3 bvec3_from_vec4(vec4 v) { return bvec3(v.xyz); }\nbvec3 bvec3_from_ivec2(ivec2 v) { return bvec3(v, 0); }\nbvec3 bvec3_from_ivec3(ivec3 v) { return bvec3(v); }\nbvec3 bvec3_from_ivec4(ivec4 v) { return bvec3(v.xyz); }\nbvec3 bvec3_from_uvec2(uvec2 v) { return bvec3(v, 0); }\nbvec3 bvec3_from_uvec3(uvec3 v) { return bvec3(v); }\nbvec3 bvec3_from_uvec4(uvec4 v) { return bvec3(v.xyz); }\nbvec3 bvec3_from_bvec2(bvec2 v) { return bvec3(v, 0); }\nbvec3 bvec3_from_bvec4(bvec4 v) { return bvec3(v.xyz); }\n\n//bvec4\nbvec4 bvec4_from_float(float f) { return bvec4(f); }\nbvec4 bvec4_from_int(int i) { return bvec4(i); }\nbvec4 bvec4_from_uint(uint u) { return bvec4(u); }\nbvec4 bvec4_from_bool(bool b) { return bvec4(b); }\nbvec4 bvec4_from_vec2(vec2 v) { return bvec4(v, 0, 0); }\nbvec4 bvec4_from_vec3(vec3 v) { return bvec4(v, 0); }\nbvec4 bvec4_from_vec4(vec4 v) { return bvec4(v); }\nbvec4 bvec4_from_ivec2(ivec2 v) { return bvec4(v, 0, 0); }\nbvec4 bvec4_from_ivec3(ivec3 v) { return bvec4(v, 0); }\nbvec4 bvec4_from_ivec4(ivec4 v) { return bvec4(v); }\nbvec4 bvec4_from_uvec2(uvec2 v) { return bvec4(v, 0, 0); }\nbvec4 bvec4_from_uvec3(uvec3 v) { return bvec4(v, 0); }\nbvec4 bvec4_from_uvec4(uvec4 v) { return bvec4(v); }\nbvec4 bvec4_from_bvec2(bvec2 v) { return bvec4(v, 0, 0); }\nbvec4 bvec4_from_bvec3(bvec3 v) { return bvec4(v, 0); }\n\n#endif //NODE_UTILS_2_CONVERSION_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Node Utils 2/node_utils_2.glsl",
    "content": "#ifndef NODE_UTILS_2_GLSL\n#define NODE_UTILS_2_GLSL\n\n#include \"Node Utils 2/conversion.glsl\"\n#include \"Node Utils 2/Bool.glsl\"\n#include \"Node Utils 2/Float.glsl\"\n#include \"Node Utils 2/Int.glsl\"\n#include \"Node Utils 2/Mat4.glsl\"\n#include \"Node Utils 2/Vec2.glsl\"\n#include \"Node Utils 2/Vec3.glsl\"\n#include \"Node Utils 2/Vec4.glsl\"\n\n#include \"Node Utils 2/Input.glsl\"\n#include \"Node Utils 2/Color.glsl\"\n#include \"Node Utils 2/Vector.glsl\"\n#include \"Node Utils 2/Parameters.glsl\"\n#include \"Node Utils 2/Texturing.glsl\"\n#include \"Node Utils 2/Filter.glsl\"\n\n// Basic common API\n#include \"Common.glsl\"\n#include \"Filters/AO.glsl\"\n#include \"Filters/Bevel.glsl\"\n#include \"Filters/Blur.glsl\"\n#include \"Filters/Sharpen.glsl\"\n#include \"Filters/Curvature.glsl\"\n#include \"Filters/Kuwahara.glsl\"\n#include \"Filters/Line.glsl\"\n#include \"Filters/StructureTensor.glsl\"\n#include \"Procedural/Noise.glsl\"\n#include \"Procedural/Cell_Noise.glsl\"\n#include \"Procedural/Fractal_Noise.glsl\"\n#include \"Shading/ShadingModels.glsl\"\n\n#endif //NODE_UTILS_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Passes/BlendTexture.glsl",
    "content": "#include \"Common.glsl\"\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\n\nuniform sampler2D blend_texture;\n\nlayout (location = 0) out vec4 OUT_COLOR;\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    vec4 color = texture(blend_texture, UV[0]);\n    color.rgb *= color.a;\n    OUT_COLOR = color;\n}\n\n#endif //PIXEL_SHADER\n"
  },
  {
    "path": "Malt/Shaders/Passes/BlendTransparency.glsl",
    "content": "#include \"Common.glsl\"\n#include \"Common/Color.glsl\"\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\n\nuniform sampler2D IN_BACK[8];\nuniform sampler2D IN_FRONT[8];\n\nlayout (location = 0) out vec4 OUT_RESULT_0;\nlayout (location = 1) out vec4 OUT_RESULT_1;\nlayout (location = 2) out vec4 OUT_RESULT_2;\nlayout (location = 3) out vec4 OUT_RESULT_3;\nlayout (location = 4) out vec4 OUT_RESULT_4;\nlayout (location = 5) out vec4 OUT_RESULT_5;\nlayout (location = 6) out vec4 OUT_RESULT_6;\nlayout (location = 7) out vec4 OUT_RESULT_7;\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    ivec2 uv = ivec2(gl_FragCoord.xy);\n\n    OUT_RESULT_0 = alpha_blend(texelFetch(IN_BACK[0], uv, 0), texelFetch(IN_FRONT[0], uv, 0));\n    OUT_RESULT_1 = alpha_blend(texelFetch(IN_BACK[1], uv, 0), texelFetch(IN_FRONT[1], uv, 0));\n    OUT_RESULT_2 = alpha_blend(texelFetch(IN_BACK[2], uv, 0), texelFetch(IN_FRONT[2], uv, 0));\n    OUT_RESULT_3 = alpha_blend(texelFetch(IN_BACK[3], uv, 0), texelFetch(IN_FRONT[3], uv, 0));\n    OUT_RESULT_4 = alpha_blend(texelFetch(IN_BACK[4], uv, 0), texelFetch(IN_FRONT[4], uv, 0));\n    OUT_RESULT_5 = alpha_blend(texelFetch(IN_BACK[5], uv, 0), texelFetch(IN_FRONT[5], uv, 0));\n    OUT_RESULT_6 = alpha_blend(texelFetch(IN_BACK[6], uv, 0), texelFetch(IN_FRONT[6], uv, 0));\n    OUT_RESULT_7 = alpha_blend(texelFetch(IN_BACK[7], uv, 0), texelFetch(IN_FRONT[7], uv, 0));\n}\n\n#endif //PIXEL_SHADER\n"
  },
  {
    "path": "Malt/Shaders/Passes/CopyTextures.glsl",
    "content": "#include \"Common.glsl\"\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\n\nuniform sampler2D IN[8];\n\nuniform sampler2D IN_DEPTH;\n\nlayout (location = 0) out vec4 OUT_0;\nlayout (location = 1) out vec4 OUT_1;\nlayout (location = 2) out vec4 OUT_2;\nlayout (location = 3) out vec4 OUT_3;\nlayout (location = 4) out vec4 OUT_4;\nlayout (location = 5) out vec4 OUT_5;\nlayout (location = 6) out vec4 OUT_6;\nlayout (location = 7) out vec4 OUT_7;\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    ivec2 uv = ivec2(gl_FragCoord.xy);\n    \n    OUT_0 = texelFetch(IN[0], uv, 0);\n    OUT_1 = texelFetch(IN[1], uv, 0);\n    OUT_2 = texelFetch(IN[2], uv, 0);\n    OUT_3 = texelFetch(IN[3], uv, 0);\n    OUT_4 = texelFetch(IN[4], uv, 0);\n    OUT_5 = texelFetch(IN[5], uv, 0);\n    OUT_6 = texelFetch(IN[6], uv, 0);\n    OUT_7 = texelFetch(IN[7], uv, 0);\n\n    gl_FragDepth = texelFetch(IN_DEPTH, uv, 0).x;\n}\n\n#endif //PIXEL_SHADER\n"
  },
  {
    "path": "Malt/Shaders/Passes/DepthToBlenderDepth.glsl",
    "content": "#include \"Common.glsl\"\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\n\nuniform sampler2D DEPTH_TEXTURE;\nuniform int DEPTH_CHANNEL;\n\nlayout (location = 0) out vec4 OUT_RESULT;\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    float depth = texture(DEPTH_TEXTURE, UV[0])[DEPTH_CHANNEL];\n    vec3 camera = screen_to_camera(UV[0], depth);\n    OUT_RESULT.r = -camera.z;\n}\n\n#endif //PIXEL_SHADER\n"
  },
  {
    "path": "Malt/Shaders/Passes/JumpFlood.glsl",
    "content": "#include \"Common.glsl\"\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\n\n#include \"Filters/JumpFlood.glsl\"\n\nuniform sampler2D input_texture;\nuniform float width;\n\nlayout (location = 0) out vec4 OUT_RESULT;\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    vec2 uv = screen_uv();\n    OUT_RESULT = jump_flood(input_texture, uv, width, true);\n}\n\n#endif //PIXEL_SHADER\n"
  },
  {
    "path": "Malt/Shaders/Passes/LineComposite.glsl",
    "content": "#include \"Common.glsl\"\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\n\n#include \"Filters/Line.glsl\"\n\nlayout (location = 0) out vec4 OUT_RESULT;\n\nuniform sampler2D color_texture;\n\nuniform sampler2D depth_texture;\nuniform int depth_channel;\n\nuniform usampler2D id_texture;\nuniform int id_channel;\n\nuniform sampler2D line_color_texture;\n\nuniform sampler2D line_width_texture;\nuniform int line_width_channel;\nuniform float line_width_scale = 1.0;\n\nuniform int brute_force_range = 10;\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    vec2 uv = UV[0];\n    vec4 line_color = line_expand(\n        uv, brute_force_range,\n        line_color_texture, line_width_texture, line_width_channel, line_width_scale,\n        depth_texture, depth_channel, id_texture, id_channel\n    ).color;\n\n    vec4 color = texture(color_texture, uv);\n    \n    OUT_RESULT = alpha_blend(color, line_color);\n}\n\n#endif //PIXEL_SHADER\n"
  },
  {
    "path": "Malt/Shaders/Passes/Unpack8bitTextures.glsl",
    "content": "#include \"Common.glsl\"\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\n\nuniform usampler2D IN_PACKED;\n\nlayout (location = 0) out vec4 OUT_A;\nlayout (location = 1) out vec4 OUT_B;\nlayout (location = 2) out vec4 OUT_C;\nlayout (location = 3) out vec4 OUT_D;\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    uvec4 packed_pixel = texture(IN_PACKED, UV[0]);    \n    OUT_A = unpackUnorm4x8(packed_pixel.r);\n    OUT_B = unpackUnorm4x8(packed_pixel.g);\n    OUT_C = unpackUnorm4x8(packed_pixel.b);\n    OUT_D = unpackUnorm4x8(packed_pixel.a);\n}\n\n#endif //PIXEL_SHADER\n"
  },
  {
    "path": "Malt/Shaders/Passes/sRGBConversion.glsl",
    "content": "#include \"Common.glsl\"\n\n#ifdef VERTEX_SHADER\nvoid main()\n{\n    DEFAULT_SCREEN_VERTEX_SHADER();\n}\n#endif\n\n#ifdef PIXEL_SHADER\n\nuniform sampler2D input_texture;\nuniform bool to_srgb;\nuniform bool convert = true;\n\nlayout (location = 0) out vec4 OUT_COLOR;\n\nvoid main()\n{\n    PIXEL_SETUP_INPUT();\n\n    vec4 color = texture(input_texture, UV[0]);\n    if(convert)\n    {\n        if(to_srgb)\n        {\n            color.rgb = linear_to_srgb(color.rgb);\n        }\n        else\n        {\n            color.rgb = srgb_to_linear(color.rgb);\n        }\n    }\n    OUT_COLOR = color;\n}\n\n#endif //PIXEL_SHADER\n"
  },
  {
    "path": "Malt/Shaders/Procedural/Bayer.glsl",
    "content": "#ifndef BAYER_GLSL\n#define BAYER_GLSL\n\n// Based on: https://en.wikipedia.org/wiki/Ordered_dithering\n\n/*  META GLOBAL\n    @meta: internal=true;\n*/\n\nint _bayer_index(ivec2 texel, int size)\n{\n    texel = texel % ivec2(size);\n    return texel.x + size*texel.y;\n}\n\nfloat bayer_2x2(ivec2 texel)\n{\n    int matrix[4] = int[4](\n        0,2,\n        3,1\n    );\n    return float(matrix[_bayer_index(texel, 2)]) / 4.0;\n}\n\nfloat bayer_3x3(ivec2 texel)\n{\n    int matrix[9] = int[9](\n        0,7,3,\n        6,5,2,\n        4,1,8\n    );\n    return float(matrix[_bayer_index(texel, 3)]) / 9.0;\n}\n\nfloat bayer_4x4(ivec2 texel)\n{\n    int matrix[16] = int[16](\n        0,8,2,10,\n        12,4,14,6,\n        3,11,1,9,\n        15,7,13,5\n    );\n    return float(matrix[_bayer_index(texel, 4)]) / 16.0;\n}\n\nfloat bayer_8x8(ivec2 texel)\n{\n    int matrix[64] = int[64](\n        0,32,8,40,2,34,10,42,\n        48,16,56,24,50,18,58,26,\n        12,44,4,36,14,46,6,38,\n        60,28,53,20,62,30,54,22,\n        3,35,11,43,1,33,9,41,\n        51,19,59,27,49,17,57,25,\n        15,47,7,39,13,45,5,37,\n        63,31,55,23,61,29,53,21\n    );\n    return float(matrix[_bayer_index(texel, 8)]) / 64.0;\n}\n\n#endif //BAYER_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Procedural/Cell_Noise.glsl",
    "content": "#ifndef PROCEDURAL_CELL_NOISE_GLSL\n#define PROCEDURAL_CELL_NOISE_GLSL\n\n#include \"Common/Hash.glsl\"\n\n/*  META GLOBAL\n    @meta: category=Texturing; internal=true;\n*/\n\nstruct CellNoiseResult\n{\n    vec4 cell_color;\n    vec4 cell_position;\n    float cell_distance;\n};\n\n/*  META\n    @coord: subtype=Vector; default=vec4(POSITION,0);\n    @tile_size: subtype=Vector; default=vec4(1);\n*/\nCellNoiseResult cell_noise_ex(vec4 coord, bool tile, vec4 tile_size)\n{\n    //Kept for backward compatibility\n    CellNoiseResult result;\n    #define DIMENSIONS 4\n    #define T vec4\n    if(tile)\n    {\n        #define TILE 1\n        #include \"Cell_Noise.inl\"\n        result.cell_color = r_cell_color;\n        result.cell_position = r_cell_position;\n        result.cell_distance = r_cell_distance;\n    }\n    else\n    {\n        #define TILE 0\n        #include \"Cell_Noise.inl\"\n        result.cell_color = r_cell_color;\n        result.cell_position = r_cell_position;\n        result.cell_distance = r_cell_distance;\n    }\n    return result;\n}\n\n/*  META\n    @coord: subtype=Vector; default=vec4(POSITION,0);\n*/\nCellNoiseResult cell_noise(vec4 coord)\n{\n    CellNoiseResult result;\n    #define DIMENSIONS 4\n    #define T vec4\n    #define TILE 0\n    #include \"Cell_Noise.inl\"\n    result.cell_color = r_cell_color;\n    result.cell_position = r_cell_position;\n    result.cell_distance = r_cell_distance;\n    return result;\n}\n\n/*  META\n    @coord: subtype=Vector; default=vec4(POSITION,0);\n    @tile_size: default=ivec4(5);\n*/\nCellNoiseResult cell_noise(vec4 coord, ivec4 tile_size)\n{\n    CellNoiseResult result;\n    #define DIMENSIONS 4\n    #define T vec4\n    #define TILE 1\n    #include \"Cell_Noise.inl\"\n    result.cell_color = r_cell_color;\n    result.cell_position = r_cell_position;\n    result.cell_distance = r_cell_distance;\n    return result;\n}\n\n/*  META\n    @coord: subtype=Vector; default=vec3(POSITION);\n*/\nCellNoiseResult cell_noise(vec3 coord)\n{\n    CellNoiseResult result;\n    #define DIMENSIONS 3\n    #define T vec3\n    #define TILE 0\n    #include \"Cell_Noise.inl\"\n    result.cell_color = r_cell_color;\n    result.cell_position.xyz = r_cell_position;\n    result.cell_distance = r_cell_distance;\n    return result;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION;\n    @tile_size: default=ivec3(5);\n*/\nCellNoiseResult cell_noise(vec3 coord, ivec3 tile_size)\n{\n    CellNoiseResult result;\n    #define DIMENSIONS 3\n    #define T vec3\n    #define TILE 1\n    #include \"Cell_Noise.inl\"\n    result.cell_color = r_cell_color;\n    result.cell_position.xyz = r_cell_position;\n    result.cell_distance = r_cell_distance;\n    return result;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.xy;\n*/\nCellNoiseResult cell_noise(vec2 coord)\n{\n    CellNoiseResult result;\n    #define DIMENSIONS 2\n    #define T vec2\n    #define TILE 0\n    #include \"Cell_Noise.inl\"\n    result.cell_color = r_cell_color;\n    result.cell_position.xy = r_cell_position;\n    result.cell_distance = r_cell_distance;\n    return result;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.xy;\n    @tile_size: default=ivec2(5);\n*/\nCellNoiseResult cell_noise(vec2 coord, ivec2 tile_size)\n{\n    CellNoiseResult result;\n    #define DIMENSIONS 2\n    #define T vec2\n    #define TILE 1\n    #include \"Cell_Noise.inl\"\n    result.cell_color = r_cell_color;\n    result.cell_position.xy = r_cell_position;\n    result.cell_distance = r_cell_distance;\n    return result;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.x;\n*/\nCellNoiseResult cell_noise(float coord)\n{\n    CellNoiseResult result;\n    #define DIMENSIONS 1\n    #define T float\n    #define TILE 0\n    #include \"Cell_Noise.inl\"\n    result.cell_color = r_cell_color;\n    result.cell_position.x = r_cell_position;\n    result.cell_distance = r_cell_distance;\n    return result;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.x;\n    @tile_size: default=5;\n*/\nCellNoiseResult cell_noise(float coord, int tile_size)\n{\n    CellNoiseResult result;\n    #define DIMENSIONS 1\n    #define T float\n    #define TILE 1\n    #include \"Cell_Noise.inl\"\n    result.cell_color = r_cell_color;\n    result.cell_position.x = r_cell_position;\n    result.cell_distance = r_cell_distance;\n    return result;\n}\n\n#undef DIMENSIONS\n#undef T\n#undef TILE\n\n#endif // PROCEDURAL_CELL_NOISE_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Procedural/Cell_Noise.inl",
    "content": "//Should define T, DIMENSIONS and TILE\n//Should declare coord and tile_size (if TILE)\nT real_coord = coord;\n#if TILE != 0\n    T _tile_size = round(T(tile_size));\n    coord = mod(coord, _tile_size);\n#endif\nT real_origin = floor(real_coord) + 0.5;\nT origin = floor(coord) + 0.5;\nT delta = fract(coord);\n\nvec4 r_cell_color;\nT r_cell_position;\nfloat r_cell_distance = 10;\n\n#if DIMENSIONS == 4\n    const ivec4 D = ivec4(1);\n#elif DIMENSIONS == 3\n    const ivec4 D = ivec4(1,1,1,0);\n#elif DIMENSIONS == 2\n    const ivec4 D = ivec4(1,1,0,0);\n#elif DIMENSIONS == 1\n    const ivec4 D = ivec4(1,0,0,0);\n#endif\n\nfor(int w = -D.w; w <= D.w; w++)\n{\n    for(int z = -D.z; z <= D.z; z++)\n    {\n        for(int y = -D.y; y <= D.y; y++)\n        {\n            for(int x = -D.x; x <= D.x; x++)\n            {\n                T offset = T(vec4(x,y,z,w));\n                T real_corner = real_origin + offset;\n                T corner = origin + offset;\n                #if TILE != 0\n                    corner = mod(corner, _tile_size);\n                #endif\n                vec4 corner_hash = hash(corner);\n                T real_cell_position = real_corner + (T(corner_hash) - 0.5);\n                T cell_position = corner + (T(corner_hash) - 0.5);\n                float cell_distance = distance(real_coord, real_cell_position);\n                if(cell_distance < r_cell_distance)\n                {\n                    r_cell_color = corner_hash;\n                    r_cell_position = real_cell_position;\n                    r_cell_distance = cell_distance;\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Malt/Shaders/Procedural/Fractal_Noise.glsl",
    "content": "#ifndef PROCEDURAL_FRACTAL_NOISE_GLSL\n#define PROCEDURAL_FRACTAL_NOISE_GLSL\n\n#include \"Noise.glsl\"\n\n/*  META GLOBAL\n    @meta: category=Texturing; @meta: internal=true;\n*/\n\n/*  META\n    @coord: subtype=Vector; default=vec4(POSITION,0);\n    @detail: default=3.0; min=1.0;\n    @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n    @tile_size: default=ivec4(5);\n*/\nvec4 fractal_noise(vec4 coord, float detail, float detail_balance, ivec4 tile_size)\n{\n    #define TILE 1\n    #include \"Fractal_Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=vec4(POSITION,0);\n    @detail: default=3.0; min=1.0;\n    @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n*/\nvec4 fractal_noise(vec4 coord, float detail, float detail_balance)\n{\n    #define TILE 0\n    #include \"Fractal_Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION;\n    @detail: default=3.0; min=1.0;\n    @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n    @tile_size: default=ivec3(5);\n*/\nvec4 fractal_noise(vec3 coord, float detail, float detail_balance, ivec3 tile_size)\n{\n    #define TILE 1\n    #include \"Fractal_Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION;\n    @detail: default=3.0; min=1.0;\n    @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n*/\nvec4 fractal_noise(vec3 coord, float detail, float detail_balance)\n{\n    #define TILE 0\n    #include \"Fractal_Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.xy;\n    @detail: default=3.0; min=1.0;\n    @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n    @tile_size: default=ivec2(5);\n*/\nvec4 fractal_noise(vec2 coord, float detail, float detail_balance, ivec2 tile_size)\n{\n    #define TILE 1\n    #include \"Fractal_Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.xy;\n    @detail: default=3.0; min=1.0;\n    @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n*/\nvec4 fractal_noise(vec2 coord, float detail, float detail_balance)\n{\n    #define TILE 0\n    #include \"Fractal_Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.x;\n    @detail: default=3.0; min=1.0;\n    @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n    @tile_size: default=5;\n*/\nvec4 fractal_noise(float coord, float detail, float detail_balance, int tile_size)\n{\n    #define TILE 1\n    #include \"Fractal_Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.x;\n    @detail: default=3.0; min=1.0;\n    @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;\n*/\nvec4 fractal_noise(float coord, float detail, float detail_balance)\n{\n    #define TILE 0\n    #include \"Fractal_Noise.inl\"\n    return color;\n}\n\n#undef TILE\n\n// Keep for backward compatibility\nvec4 fractal_noise_ex(vec4 coord, int octaves, bool tile, vec4 tile_size)\n{\n    if(tile)\n    {\n        return fractal_noise(coord, float(octaves), 0.5, ivec4(tile_size));\n    }\n    else\n    {\n        return fractal_noise(coord, float(octaves), 0.5);\n    }\n}\nvec4 fractal_noise(vec4 coord, int octaves)\n{\n    return fractal_noise(coord, float(octaves), 0.5);\n}\n\n\n#endif // PROCEDURAL_FRACTAL_NOISE_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Procedural/Fractal_Noise.inl",
    "content": "//Should define TILE\n//Should declare coord and tile_size (if TILE)\n\nvec4 color = vec4(0);\nfloat weight = 1.0;\nfloat total_weight = 0.0;\ndetail_balance = clamp(detail_balance, 0.00001, 1.0);\ndetail = max(1.0, detail);\n\nfor (int i = 0; i < ceil(detail); i++) \n{\n    #if TILE != 0\n        vec4 noise = noise(coord, tile_size);\n    #else\n        vec4 noise = noise(coord);\n    #endif\n    float octave_weight = (i + 1 > floor(detail))? mod(detail, 1.0) : 1.0;\n    weight *= detail_balance * 2 * octave_weight;\n    color += weight * noise;\n    total_weight += weight;\n    coord *= 2.0;\n    #if TILE != 0\n        tile_size *= 2;\n    #endif\n}\n\ncolor /= total_weight;"
  },
  {
    "path": "Malt/Shaders/Procedural/Noise.glsl",
    "content": "#ifndef PROCEDURAL_NOISE_GLSL\n#define PROCEDURAL_NOISE_GLSL\n\n#include \"Common/Hash.glsl\"\n\n/*  META GLOBAL\n    @meta: internal=true;\n*/\n\n/*  META\n    @coord: subtype=Vector; default=vec4(POSITION,0);\n    @tile_size: subtype=Vector; default=vec4(1);\n*/\nvec4 noise_ex(vec4 coord, bool tile, vec4 tile_size)\n{\n    //Kept for backward compatibility\n    #define DIMENSIONS 4\n    #define T vec4\n    if(tile)\n    {\n        #define TILE 1\n        #include \"Noise.inl\"\n        return color;\n    }\n    else\n    {\n        #define TILE 0\n        #include \"Noise.inl\"\n        return color;\n    }\n}\n\n/*  META\n    @coord: subtype=Vector; default=vec4(POSITION,0);\n    @tile_size: subtype=Vector; default=ivec4(5);\n*/\nvec4 noise(vec4 coord, ivec4 tile_size)\n{\n    #define DIMENSIONS 4\n    #define T vec4\n    #define TILE 1\n    #include \"Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=vec4(POSITION,0);\n*/\nvec4 noise(vec4 coord)\n{\n    #define DIMENSIONS 4\n    #define T vec4\n    #define TILE 0\n    #include \"Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION;\n    @tile_size: subtype=Vector; default=ivec3(5);\n*/\nvec4 noise(vec3 coord, ivec3 tile_size)\n{\n    #define DIMENSIONS 3\n    #define T vec3\n    #define TILE 1\n    #include \"Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION;\n*/\nvec4 noise(vec3 coord)\n{\n    #define DIMENSIONS 3\n    #define T vec3\n    #define TILE 0\n    #include \"Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.xy;\n    @tile_size: subtype=Vector; default=ivec2(5);\n*/\nvec4 noise(vec2 coord, ivec2 tile_size)\n{\n    #define DIMENSIONS 2\n    #define T vec2\n    #define TILE 1\n    #include \"Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.xy;\n*/\nvec4 noise(vec2 coord)\n{\n    #define DIMENSIONS 2\n    #define T vec2\n    #define TILE 0\n    #include \"Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.x;\n    @tile_size: subtype=Vector; default=5;\n*/\nvec4 noise(float coord, int tile_size)\n{\n    #define DIMENSIONS 1\n    #define T float\n    #define TILE 1\n    #include \"Noise.inl\"\n    return color;\n}\n\n/*  META\n    @coord: subtype=Vector; default=POSITION.x;\n*/\nvec4 noise(float coord)\n{\n    #define DIMENSIONS 1\n    #define T float\n    #define TILE 0\n    #include \"Noise.inl\"\n    return color;\n}\n\n#undef DIMENSIONS\n#undef T\n#undef TILE\n\n#endif // PROCEDURAL_NOISE_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Procedural/Noise.inl",
    "content": "//Should define T, DIMENSIONS and TILE\n//Should declare coord and tile_size (if TILE)\n#if TILE != 0\n    T _tile_size = round(tile_size);\n    coord = mod(coord, _tile_size);\n#endif\nT origin = floor(coord);\nT delta = fract(coord);\n//quintic interpolation factor\nT factor = delta*delta*delta*(delta*(delta*6.0-15.0)+10.0);\n\n#if DIMENSIONS == 4\n    const ivec4 D = ivec4(1);\n#elif DIMENSIONS == 3\n    const ivec4 D = ivec4(1,1,1,0);\n#elif DIMENSIONS == 2\n    const ivec4 D = ivec4(1,1,0,0);\n#elif DIMENSIONS == 1\n    const ivec4 D = ivec4(1,0,0,0);\n#endif\n\nvec4 w_results[2];\nvec4 z_results[2];\nvec4 y_results[2];\nvec4 x_results[2];\n\nfor(int w = 0; w <= D.w; w++)\n{\n    for(int z = 0; z <= D.z; z++)\n    {\n        for(int y = 0; y <= D.y; y++)\n        {\n            for(int x = 0; x <= D.x; x++)\n            {\n                T offset = T(vec4(x,y,z,w));\n                T corner = origin + offset;\n                #if TILE != 0\n                    corner = mod(corner, _tile_size);\n                #endif\n                T corner_hash_r = T(hash(corner)) * 2.0 - 1.0; //(-1|+1) range\n                T corner_hash_g = T(hash(corner_hash_r)) * 2.0 - 1.0;\n                T corner_hash_b = T(hash(corner_hash_g)) * 2.0 - 1.0;\n                T corner_hash_a = T(hash(corner_hash_b)) * 2.0 - 1.0;\n                x_results[x].r = dot(corner_hash_r, delta-offset);\n                x_results[x].g = dot(corner_hash_g, delta-offset);\n                x_results[x].b = dot(corner_hash_b, delta-offset);\n                x_results[x].a = dot(corner_hash_a, delta-offset);\n            }\n            #if DIMENSIONS >= 2\n                y_results[y] = mix(x_results[0], x_results[1], factor.x);\n            #endif\n        }\n        #if DIMENSIONS >= 3\n            z_results[z] = mix(y_results[0], y_results[1], factor.y);\n        #endif\n    }\n    #if DIMENSIONS >= 4\n        w_results[w] = mix(z_results[0], z_results[1], factor.z);\n    #endif\n}\n\nvec4 color;\n#if DIMENSIONS == 4\n    color = mix(w_results[0], w_results[1], factor.w) * 0.5 + 0.5;\n#elif DIMENSIONS == 3\n    color = mix(z_results[0], z_results[1], factor.z) * 0.5 + 0.5;\n#elif DIMENSIONS == 2\n    color = mix(y_results[0], y_results[1], factor.y) * 0.5 + 0.5;\n#elif DIMENSIONS == 1\n    color = mix(x_results[0], x_results[1], factor) * 0.5 + 0.5;\n#endif\n\n"
  },
  {
    "path": "Malt/Shaders/SDF/SDF.glsl",
    "content": "#ifndef SDF_SDF_GLSL\n#define SDF_SDF_GLSL\n\n#include \"Common/Transform.glsl\"\n\n// SDF functions adapted from https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm\n\n/* META GLOBAL\n    @meta: internal=true;\n*/\n\nfloat sdf_box(vec3 p, vec3 size)\n{\n    vec3 q = abs(p) - (size / 2.0);\n    return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);\n}\n\nfloat sdf_sphere(vec3 p, float radius)\n{\n    return length(p) - radius;\n}\n\nfloat sdf_ellipsoid(vec3 p, vec3 radius)\n{\n    float k0 = length(p / radius);\n    float k1 = length(p / (radius * radius));\n    return k0 * (k0 - 1.0) / k1;\n}\n\nfloat sdf_cone(vec3 p, float radius, float height)\n{\n    vec2 q = vec2(radius, -height);\n    vec2 w = vec2(length(p.xy), p.z - height);\n    vec2 a = w - (q * clamp(dot(w, q) / dot(q, q), 0.0, 1.0));\n    vec2 b = w - (q * vec2(clamp(w.x / q.x, 0.0, 1.0), 1.0));\n    float k = sign(q.y);\n    float d = min(dot(a, a),dot(b, b));\n    float s = max(k * ((w.x * q.y) - (w.y * q.x)), k * (w.y - q.y));\n    return sqrt(d) * sign(s);\n}\n\nfloat sdf_capsule(vec3 p, vec3 a, vec3 b, float radius)\n{\n    vec3 pa = p - a;\n    vec3 ba = b - a;\n    float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);\n    return length(pa - (ba * h)) - radius;\n}\n\nfloat sdf_smooth(float a, float radius) { return a - radius; }\n\nfloat sdf_union(float a, float b) { return min(a, b); }\n\nfloat sdf_difference(float a, float b) { return max(a, -b); }\n\nfloat sdf_intersection(float a, float b) { return max(a, b); }\n\nfloat sdf_union_smooth(float a, float b, float smooth_size)\n{\n    float h = clamp(0.5 + 0.5 * (b - a) / smooth_size, 0.0, 1.0);\n    return mix(b, a, h) - smooth_size * h * (1.0 - h);\n}\n\nfloat sdf_difference_smooth(float a, float b, float smooth_size)\n{\n    float h = clamp(0.5 - 0.5 * (b + a) / smooth_size, 0.0, 1.0);\n    return mix(a, -b, h) + smooth_size * h * (1.0 - h);\n}\n\nfloat sdf_intersection_smooth(float a, float b, float smooth_size)\n{\n    float h = clamp(0.5 - 0.5 * (b - a) / smooth_size, 0.0, 1.0);\n    return mix(b, a, h) + smooth_size * h * (1.0 - h);\n}\n\nstruct RayMarchResult\n{\n    bool hit;\n    int step;\n    float depth;\n    vec3 position;\n    vec3 normal;\n};\n\nfloat scene_sdf(vec3 p);\n\n// To include this file and call this function you must define your own scene_sdf function\nRayMarchResult raymarch_scene(vec3 ray_start, vec3 ray_end, int max_steps, float min_precision)\n{\n    RayMarchResult r = RayMarchResult(false, 0, 0, vec3(0), vec3(0));\n    vec3 ray = normalize(ray_end - ray_start);\n    float ray_length = distance(ray_start, ray_end);\n    \n    for (r.step = 0; r.step < max_steps; r.step++)\n    {\n        float signed_distance = scene_sdf(ray_start + min(r.depth, ray_length) * ray);\n        if (signed_distance < min_precision)\n        {\n            break;\n        }\n        if (r.depth > ray_length)\n        {\n            return r;\n        }\n        r.depth += signed_distance;\n    }\n    \n    r.hit = true;\n    r.position = ray_start + r.depth * ray;\n    float projected_depth = project_point(PROJECTION * CAMERA, r.position).z;\n    float offset_scale = pixel_world_size_at(projected_depth);\n    // https://www.iquilezles.org/www/articles/normalsSDF/normalsSDF.htm\n    vec2 k = vec2(1,-1);\n    r.normal = normalize\n    (\n        k.xyy * scene_sdf(r.position + k.xyy * offset_scale) + \n        k.yyx * scene_sdf(r.position + k.yyx * offset_scale) + \n        k.yxy * scene_sdf(r.position + k.yxy * offset_scale) + \n        k.xxx * scene_sdf(r.position + k.xxx * offset_scale)\n    );\n    \n    return r;\n}\n\n#endif //SDF_SDF_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Shading/BRDF.glsl",
    "content": "#ifndef BRDF_GLSL\n#define BRDF_GLSL\n\n// The following formulas follow the naming conventions explained in the LitSurface struct declaration (Lighing.glsl)\n// X is for tangent and Y for bitangent. (ie. XoH means dot(tangent, halfway_vector))\n// (a) parameter stands for roughness factor (0..1)\n// Dot products should be clamped to (MIN_DOT..1)\n\n//Division by PI has been factored out for a more intuitive artistic workflow\n//https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/\n\n//UTILS\n\n#define MIN_DOT 1e-10\n\n/* META @meta: internal=true; */\nfloat safe_dot(vec3 a, vec3 b)\n{\n    return clamp(dot(a,b), MIN_DOT, 1.0);\n}\n\n/* META @meta: internal=true; */\nfloat roughness_to_shininess(float roughness)\n{\n    return 2.0 / pow(max(0.1, roughness), 3);\n}\n\n// DIFFUSE BRDFs\n\n/* META @meta: internal=true; */\nfloat BRDF_lambert(float NoL)\n{\n    return NoL;\n}\n\nfloat F_schlick(float VoH, float F0, float F90); //Forward declaration, definition in Fresnel section\n\n/* META @meta: internal=true; */\nfloat BRDF_burley(float NoL, float NoV, float VoH, float a)\n{\n    //https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf\n    float f90 = 0.5 + 2.0 * a * VoH*VoH;\n\n    return F_schlick(NoL, 1.0, f90) * F_schlick(NoV, 1.0, f90) * NoL;\n}\n\n/* META @meta: internal=true; */\nfloat BRDF_oren_nayar(float NoL, float NoV, float LoV, float a)\n{\n    //https://mimosa-pudica.net/improved-oren-nayar.html\n    float s = LoV - NoL * NoV;\n    float t = s <= 0 ? 1.0 : max(NoL, NoV);\n    float A = 1.0 - 0.5 * (a*a / (a*a + 0.33) + 0.17 * (a*a / (a*a + 0.13)));\n    float B = 0.45 * (a*a / (a*a + 0.09));\n\n    return NoL * (A + B * (s / t));\n}\n\n// SPECULAR BRDFs\n//http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html\n\n/* META @meta: internal=true; */\nfloat BRDF_specular_cook_torrance(float D, float F, float G, float NoL, float NoV)\n{\n    return (D * F * G) / (4.0 * NoL * NoV) * NoL * PI;\n}\n\n// Specular Normal Distribution Functions\n\n/* META @meta: internal=true; */\nfloat D_phong(float VoR, float a)\n{\n    return pow(VoR, roughness_to_shininess(a));\n}\n\n/* META @meta: internal=true; */\nfloat D_blinn_phong(float NoH, float a)\n{\n    return pow(NoH, roughness_to_shininess(a));\n}\n\n/* META @meta: internal=true; */\nfloat D_ward(float NoL, float NoV, float NoH, float XoH, float YoH, float aX, float aY)\n{\n    float e = -2.0 * ((pow(XoH / aX, 2) + pow(YoH / aY, 2)) / (1.0 + NoH));\n    return (1.0 / sqrt(NoL * NoV)) * (NoL / (4.0 * PI * aX * aY)) * exp(e);\n}\n\n/* META @meta: internal=true; */\nfloat D_beckmann(float NoH, float a)\n{\n    return (1.0 / (PI * a*a * pow(NoH, 4.0))) * exp((NoH*NoH - 1.0) / (a*a * NoH*NoH));\n}\n\n/* META @meta: internal=true; */\nfloat D_GGX(float NoH, float a)\n{\n    return (a*a) / (PI * pow(NoH*NoH * (a*a - 1.0) + 1.0, 2.0));\n}\n\n/* META @meta: internal=true; */\nfloat D_GGX_anisotropic(float NoH, float XoH, float YoH, float ax, float ay)\n{\n    return (1.0 / (PI * ax*ay)) * (1.0 / (pow((XoH*XoH) / (ax*ax) + (YoH*YoH) / (ay*ay) + NoH*NoH, 2.0)));\n}\n\n// Specular Geometric Shadowing Functions\n\n/* META @meta: internal=true; */\nfloat G_implicit(float NoL, float NoV)\n{\n    return NoL*NoV;\n}\n\n/* META @meta: internal=true; */\nfloat G_neumann(float NoL, float NoV)\n{\n    return (NoL*NoV) / max(NoL, NoV);\n}\n\n/* META @meta: internal=true; */\nfloat G_cook_torrance(float NoH, float NoV, float NoL, float VoH)\n{\n    return min(1.0, min((2.0 * NoH * NoV) / VoH, (2.0 * NoH * NoL) / VoH));\n}\n\n/* META @meta: internal=true; */\nfloat G_kelemen(float NoL, float NoV, float VoH)\n{\n    return (NoL*NoV) / VoH*VoH;\n}\n\nfloat _G1_beckmann(float NoLV, float a)\n{\n    float c = NoLV / (a * sqrt(1.0 - NoLV*NoLV));\n\n    if(c >= 1.6) return 1.0;\n\n    return (3.535*c + 2.181*c*c) / (1.0 + 2.276*c + 2.577*c*c);\n}\n\n/* META @meta: internal=true; */\nfloat G_beckmann(float NoL, float NoV, float a)\n{\n    return _G1_beckmann(NoL, a) * _G1_beckmann(NoV, a);\n}\n\nfloat _G1_GGX(float NoLV, float a)\n{\n    return (2 * NoLV) / (NoLV + (sqrt(a*a + (1 - a*a) * NoLV*NoLV)));\n}\n\n/* META @meta: internal=true; */\nfloat G_GGX(float NoL, float NoV, float a)\n{\n    return _G1_GGX(NoL, a) * _G1_GGX(NoV, a);\n}\n\n// Specular Fresnel Functions\n\n/* META @meta: internal=true; */\nfloat F_schlick(float VoNH, float F0, float F90)\n{\n    // https://en.wikipedia.org/wiki/Schlick%27s_approximation\n    return F0 + (F90 - F0) * pow(1.0 - VoNH, 5.0);\n}\n\n/* META @meta: internal=true; */\nfloat F_cook_torrance(float VoNH, float F0)\n{\n    float n = (1.0 + sqrt(F0)) / (1.0 - sqrt(F0));\n    float c = VoNH;\n    float g = sqrt(n*n + c*c - 1.0);\n\n    float A = (g - c) / (g + c);\n    float B = ((g + c) * c - 1.0) / ((g - c) * c + 1.0);\n\n    return 0.5 * A*A * (1.0 + B*B);\n}\n\n#endif //BRDF_GLSL\n"
  },
  {
    "path": "Malt/Shaders/Shading/ShadingModels.glsl",
    "content": "#ifndef SHADING_MODELS_GLSL\n#define SHADING_MODELS_GLSL\n\n#include \"Shading/BRDF.glsl\"\n\n/*  META GLOBAL\n    @meta: category=Shading; internal=true;\n*/\n\nvec3 diffuse_lit_surface(LitSurface LS)\n{\n    return clamp(LS.NoL, 0, 1) * LS.light_color * LS.shadow_multiply;\n}\n\nvec3 _diffuse_half_lit_surface_common(LitSurface LS)\n{\n    vec3 diffuse = vec3(map_range_clamped(LS.NoL, -1, 1, 0, 1));\n    vec3 shadow = map_range_clamped(LS.shadow_multiply, vec3(0),vec3(1),vec3(0.5),vec3(1));\n    return min(diffuse, shadow);\n}\n\nvec3 diffuse_half_lit_surface(LitSurface LS)\n{\n    return _diffuse_half_lit_surface_common(LS) * LS.light_color;\n}\n\nvec3 diffuse_gradient_lit_surface(LitSurface LS, sampler1D gradient)\n{\n    return rgb_gradient(gradient, _diffuse_half_lit_surface_common(LS)) * LS.light_color;\n}\n\nfloat _specular_shadowing(float NoL, float specular)\n{\n    return clamp(specular * (1.0 - pow(1.0 - max(NoL, 0), 20.0)), 0, 1);\n}\n\nfloat _specular_common_lit_surface(LitSurface LS, float roughness)\n{\n    float VoR = dot(LS.V, LS.R);\n    VoR = _specular_shadowing(LS.NoL, VoR);\n    return pow(VoR, roughness_to_shininess(roughness));\n}\n\nvec3 specular_lit_surface(LitSurface LS, float roughness)\n{\n    return _specular_common_lit_surface(LS, roughness) * LS.light_color * LS.shadow_multiply;\n}\n\nvec3 specular_gradient_lit_surface(LitSurface LS, float roughness, sampler1D gradient)\n{\n    return texture(gradient, _specular_common_lit_surface(LS, roughness)).rgb * LS.light_color * LS.shadow_multiply;\n}\n\nfloat _specular_anisotropic_lit_surface_common(LitSurface LS, vec3 tangent, float anisotropy, float roughness)\n{\n    vec2 a = vec2(anisotropy, 1.0 - anisotropy);\n    a *= roughness;\n    vec3 bitangent = normalize(cross(LS.N, tangent));\n\n    float NoL = max(dot(LS.N, LS.L), MIN_DOT);\n    float NoV = max(dot(LS.N, LS.V), MIN_DOT);\n    float NoH = max(dot(LS.H, LS.N), MIN_DOT);\n    float XoH = dot(LS.H, tangent);\n    float YoH = dot(LS.H, bitangent);\n\n    float specular = D_ward(NoL, NoV, XoH, XoH, YoH, a.x, a.y);\n\n    return _specular_shadowing(NoL, specular);\n}\n\nvec3 specular_anisotropic_lit_surface(LitSurface LS, vec3 tangent, float anisotropy, float roughness)\n{\n    return _specular_anisotropic_lit_surface_common(LS, tangent, anisotropy, roughness) * LS.light_color * LS.shadow_multiply;\n}\n\nvec3 specular_anisotropic_gradient_lit_surface(LitSurface LS, vec3 tangent, float anisotropy, float roughness, sampler1D gradient)\n{\n    return texture(gradient, _specular_anisotropic_lit_surface_common(LS, tangent, anisotropy, roughness)).rgb * LS.light_color * LS.shadow_multiply;\n}\n\nvec3 toon_lit_surface(LitSurface LS, float size, float gradient_size, float specularity, float offset)\n{\n    float D = mix(LS.NoL, dot(LS.V, LS.R), specularity);\n\n    float angle = acos(D);\n    float delta = angle / PI;\n    delta -= offset;\n\n    gradient_size = min(size, gradient_size);\n\n    float value = 1.0 - map_range_clamped(delta, size - gradient_size, size, 0.0, 1.0);\n\n    float color_at_05 =  1.0 - map_range_clamped(0.5, size - gradient_size, size, 0.0, 1.0);\n\n    return min(LS.shadow ? color_at_05 : 1.0, value) * LS.light_color;\n}\n\n/*  META\n    @meta: internal=false;\n    @normal: subtype=Normal; default=NORMAL;\n    @angle: default=0.0;\n    @rim_length: default=2.0;\n    @length_fallof: default=0.1;\n    @thickness: default=0.1;\n    @thickness_fallof: default=0.0;\n*/\nfloat rim_light(vec3 normal, float angle, float rim_length, float length_falloff, float thickness, float thickness_falloff)\n{\n    vec2 angle_vec = vec2(cos(angle), sin(angle));\n\n    vec3 r = cross(transform_normal(CAMERA, -view_direction()), transform_normal(CAMERA, normal));\n    vec2 r2d = normalize(r.xy);\n\n    float angle_dot = dot(r2d, angle_vec);\n    angle_dot = angle_dot * 0.5 + 0.5;\n    float facing_dot = dot(-view_direction(), normal);\n    facing_dot = 1.0 - facing_dot;\n\n    length_falloff = max(1e-6, length_falloff);\n    thickness_falloff = max(1e-6, thickness_falloff);\n\n    float angle_result = map_range_clamped(angle_dot, 1.0 - rim_length, 1.0 - (rim_length - length_falloff), 0.0, 1.0);\n    float facing_result = map_range_clamped(facing_dot * angle_result, 1.0 - thickness, 1.0 - (thickness - thickness_falloff), 0.0, 1.0);\n\n    return angle_result * facing_result;\n}\n\n#endif //SHADING_MODELS_GLSL\n"
  },
  {
    "path": "Malt/Shaders/readme.md",
    "content": "# Shader Library\n\nAll *GLSL* code should go here.  \nAll shader files have their own [*include guard*](https://en.wikipedia.org/wiki/Include_guard) macro.  \n*Include guards* are used instead of the *#pragma once* preprocessor directive to allow user code to override the Malt built-in code when needed.\n\nWhenever it makes sense, functions should be implemented in a pipeline/renderer agnostic way.  \nIdeally it should be possible to copy-paste *Malt* functions into any other render engine with *GLSL* support.\n\n[Common.glsl](Common.glsl) contains all the code assumed to be shared by all pipelines, including the vertex attributes layout, the *COMMON_UNIFORMS* block and the uniform blocks needed for batch rendering.\n\nThe [Common](Common) folder contains 3d math and rendering utility functions.\n\n[Intellisense](Intellisense) is autogenerated from a script ([build_intellisense_glsl.py](../../scripts/build_intellisense_glsl.py)) and enables *GLSL* autocompletion to work through *C++* autocompletion providers. It's just for code editors and has no use at runtime.\n\n[Lighting](Lighting) contains a basic lighting and shadow mapping implementation and it's intended to be extended by custom pipelines.\n\n[Shading](Shading) has an extensive collection of basic building blocks for implementing *BRDFs*.\n\n[Filters](Filters) is for texture processing functions, from *Blur* to *AO*.  \nStandalone texture processing and other types of shaders that wouldn't benefit from a library-like interface are located in [Passes](Passes)\n\nAny pipeline specific code should go inside their own folder in [Pipelines](Pipelines).\n"
  },
  {
    "path": "Malt/SourceTranspiler.py",
    "content": "import textwrap\n\n#TODO: Send transpiler along graph types\nclass SourceTranspiler():\n    \n    @classmethod\n    def get_source_name(self, name):\n        name = name.replace('.','_').replace(' ', '_')\n        name = '_' + ''.join(char for char in name if char.isalnum() or char == '_')\n        while '__' in name:\n            name = name.replace('__','_')\n        return name\n\n    @classmethod\n    def asignment(self, name, asignment):\n        pass\n\n    @classmethod\n    def declaration(self, type, size, name, initialization=None):\n        pass\n\n    @classmethod\n    def global_reference(self, node_name, parameter_name):\n        pass\n    \n    @classmethod\n    def global_declaration(self, type, size, name, initialization=None):\n        pass\n\n    @classmethod\n    def custom_io_reference(self, io, graph_io_type, name):\n        pass\n\n    @classmethod\n    def preprocessor_wrap(self, define, content):\n        return content\n\n    @classmethod\n    def custom_output_declaration(self, type, name, index, graph_io_type):\n        pass\n\n    @classmethod\n    def parameter_reference(self, node_name, parameter_name, io_type):\n        pass\n\n    @classmethod\n    def io_parameter_reference(self, parameter_name, io_type):\n        return parameter_name\n\n    @classmethod\n    def is_instantiable_type(self, type):\n        return True\n\n    @classmethod\n    def call(self, name, parameters=[], full_statement=False):\n        pass\n\n    @classmethod\n    def result(self, result):\n        pass\n\n    @classmethod\n    def scoped(self, code):\n        pass\n\nclass GLSLTranspiler(SourceTranspiler):\n\n    @classmethod\n    def asignment(self, name, asignment):\n        return f'{name} = {asignment};\\n'\n\n    @classmethod\n    def declaration(self, type, size, name, initialization=None):\n        array = '' if size == 0 else f'[{size}]'\n        asignment = f' = {initialization}' if initialization else ''\n        return f'{type} {name}{array}{asignment};\\n'\n\n    @classmethod    \n    def global_reference(self, node_name, parameter_name):\n        return f\"U_0{node_name}_0_{self.get_source_name(parameter_name)}\".replace('__','_')\n\n    @classmethod\n    def global_declaration(self, type, size, name, initialization=None):\n        uniform_declaration = 'uniform '\n        if 'sampler' in type:\n            uniform_declaration = 'OPTIONALLY_BINDLESS uniform '\n        return uniform_declaration + self.declaration(type, size, name, initialization)\n    \n    @classmethod\n    def custom_io_reference(self, io, graph_io_type, name):\n        return f\"{io.upper()}_{graph_io_type.upper()}_{''.join(char.upper() for char in name if char.isalnum())}\"\n    \n    @classmethod\n    def preprocessor_wrap(self, define, content):\n        if define is None:\n            return content\n        return textwrap.dedent('''\\\n        #ifdef {}\n        {}\n        #endif //{}\n        ''').format(define, content.strip(), define)\n\n    @classmethod\n    def custom_output_declaration(self, type, name, index, graph_io_type):\n        return f\"layout (location = {index}) out {type} {self.custom_io_reference('OUT', graph_io_type, name)};\\n\"\n\n    @classmethod\n    def parameter_reference(self, node_name, parameter_name, io_type):\n        return f'{node_name}_0_{parameter_name}'\n\n    @classmethod    \n    def is_instantiable_type(self, type):\n        return type.startswith('sampler') == False\n\n    @classmethod\n    def call(self, function, name, parameters=[], post_parameter_initialization = ''):\n        src = ''\n        for i, parameter in enumerate(function['parameters']):\n            if parameter['io'] in ['out','inout']:\n                initialization = parameters[i]\n                src_reference = self.parameter_reference(name, parameter['name'], parameter['io'])\n                src += self.declaration(parameter['type'], parameter['size'], src_reference, initialization)\n                parameters[i] = src_reference\n        src += post_parameter_initialization\n\n        initialization = f'{function[\"name\"]}({\",\".join(parameters)})'\n        \n        if function['type'] != 'void' and self.is_instantiable_type(function['type']):\n            src += self.declaration(function['type'], 0, self.parameter_reference(name, 'result', 'out'), initialization)\n        else:\n            src += initialization + ';\\n'\n        \n        return src\n\n    @classmethod\n    def result(self, result):\n        return f'return {result};\\n'\n\n    @classmethod    \n    def scoped(self, code):\n        import textwrap\n        code = textwrap.indent(code, '\\t')\n        return f'{{\\n{code}}}\\n'\n\nclass PythonTranspiler(SourceTranspiler):\n\n    @classmethod\n    def asignment(self, name, asignment):\n        return f'{name} = {asignment}\\n'\n\n    @classmethod\n    def declaration(self, type, size, name, initialization=None):\n        if initialization is None: initialization = 'None'\n        return self.asignment(name, initialization)\n\n    @classmethod    \n    def global_reference(self, node_name, parameter_name):\n        return f'PARAMETERS[\"{node_name}\"][\"{parameter_name}\"]'\n\n    @classmethod    \n    def global_declaration(self, type, size, name, initialization=None):\n        return ''\n        return self.declaration(type, size, name, initialization)\n    \n    @classmethod\n    def custom_io_reference(self, io, graph_io_type, name):\n        return self.io_parameter_reference(name, io)\n\n    @classmethod\n    def custom_output_declaration(self, type, name, index, graph_io_type):\n        return self.declaration(type, 0, self.io_parameter_reference(name, 'out'))\n\n    @classmethod    \n    def parameter_reference(self, node_name, parameter_name, io_type):\n        if io_type:\n            return f'{node_name}_parameters[\"{io_type.upper()}\"][\"{parameter_name}\"]'\n        else:\n            return f'{node_name}_parameters[\"{parameter_name}\"]'\n\n    @classmethod    \n    def io_parameter_reference(self, parameter_name, io_type):\n        return f'{io_type.upper()}[\"{parameter_name}\"]'\n\n    @classmethod\n    def call(self, function, name, parameters=[], post_parameter_initialization = ''):\n        import textwrap\n        src = ''\n        src += textwrap.dedent(f'''\n        {name}_parameters = {{\n            'IN' : {{}},\n            'OUT' : {{}},\n        }}\n        ''')\n        for i, parameter in enumerate(function['parameters']):\n            initialization = parameters[i]\n            if initialization is None:\n                initialization = 'None'\n            parameter_reference = self.parameter_reference(name, parameter['name'], parameter['io'])\n            src += f'{parameter_reference} = {initialization}\\n'\n        src += post_parameter_initialization\n        src += f'run_node(\"{name}\", \"{function[\"name\"]}\", {name}_parameters)\\n'\n        return src\n\n    @classmethod\n    def result(self, result):\n        return f'return {result}\\n'\n\n    @classmethod    \n    def scoped(self, code):\n        import textwrap\n        code = textwrap.indent(code, '\\t')\n        return f'if True:\\n{code}'\n"
  },
  {
    "path": "Malt/Utils.py",
    "content": "import logging\n\nclass MaltLogger():\n\n    def __init__(self):\n        self.last_msg = None\n        self.repeated_msg = 0\n    \n    def log(self, level, *args):\n        if level < logging.root.level:\n            return\n        args = [str(arg) for arg in args]\n        msg = ' '.join(args)\n        if msg != self.last_msg:\n            self.last_msg = msg\n            self.repeated_msg = 0\n            logging.log(level, msg)\n        else:\n            self.repeated_msg += 1\n            if self.repeated_msg in (1, 10, 100, 1000):\n                logging.log(level, '(Repeated {}+ times)'.format(self.repeated_msg))\n\n    def debug(self, *args):\n        self.log(logging.DEBUG, *args)\n\n    def info(self, *args):\n        self.log(logging.INFO, *args)\n\n    def warning(self, *args):\n        self.log(logging.WARNING, *args)\n\n    def error(self, *args):\n        self.log(logging.ERROR, *args)\n\n    def critical(self, *args):\n        self.log(logging.CRITICAL, *args)\n\nLOG = MaltLogger()\n        \n\ndef dump_function(function):\n    import textwrap, inspect\n    name = function.__name__\n    function = textwrap.dedent(inspect.getsource(function))\n    return (name, function)\n\ndef load_function(function):\n    name, function = function\n    f = {}\n    exec(function, f)\n    return f[name]\n\ndef scan_dirs(path, file_callback):\n    import os\n    for e in os.scandir(path):\n        if e.is_file():\n            file_callback(e)\n        if e.is_dir():\n            scan_dirs(e, file_callback)\n\ndef isinstance_str(object, class_name):\n    classes = [object.__class__, *object.__class__.__bases__]\n    for cls in classes:\n        if cls.__name__ == class_name:\n            return True\n    return False\n\nimport cProfile, io, pstats\n\ndef profile_function(function):\n    def profiled_function(*args, **kwargs):\n        profiler = cProfile.Profile()\n        profiling_data = io.StringIO()\n        profiler.enable()\n\n        result = function(*args, **kwargs)\n\n        profiler.disable()\n        stats = pstats.Stats(profiler, stream=profiling_data)\n        stats.strip_dirs()\n        stats.sort_stats(pstats.SortKey.CUMULATIVE)\n        stats.print_stats()\n        print('PROFILE FUNCTION: ', function.__name__)\n        print(profiling_data.getvalue())\n\n        return result\n    \n    return profiled_function\n\n# https://numpy.org/doc/stable/reference/arrays.interface.html\nclass Array_Interface():\n    def __init__(self, pointer, typestr, shape, read_only=False):\n        self.__array_interface__ = {\n            'data': (pointer, read_only),\n            'typestr': typestr,\n            'shape': shape\n        }\n\nclass IBuffer():\n\n    def ctype(self):\n        raise Exception('ctype() method not implemented')\n    \n    def __len__(self):\n        raise Exception('__len__() method not implemented')\n    \n    def buffer(self):\n        raise Exception('buffer() method not implemented')\n    \n    def size_in_bytes(self):\n        import ctypes\n        return ctypes.sizeof(self.ctype()) * len(self)\n    \n    def as_array_interface(self, shape=None):\n        import ctypes\n        type_map = {\n            ctypes.c_float : 'f',\n            ctypes.c_int : 'i',\n            ctypes.c_uint : 'u',\n            ctypes.c_bool : 'b',\n        }\n        \n        if shape is None:\n            shape = (len(self),)\n\n        return Array_Interface(\n            ctypes.addressof(self.buffer()),\n            type_map[self.ctype()],\n            shape\n        )\n    \n    def as_np_array(self, shape=None):\n        import numpy as np\n        return np.array(self.as_array_interface(shape), copy=False)\n"
  },
  {
    "path": "Malt/__init__.py",
    "content": "\n"
  },
  {
    "path": "Malt/readme.md",
    "content": "# Malt\n\n## Introduction\n\n*Malt* is a fully customizable rendering framework written in **Python** and **OpenGL**.\n\nIts main goal is to support offline rendering for **animation** and **illustration**, with special care put into supporting the needs of **stylized, non photo-realistic rendering** and being **accesible** to technical artists and users without previous graphics programming experience.\n\nTherefore, while it's a **real-time renderer**, it **prioritizes image quality, flexibility and simplicity over rendering performance**.\n\n## Malt Pipelines\n\nThe core class in *Malt* is the [*Pipeline*](Pipeline.py).\n*Malt* allows to write completely custom render *Pipelines* while providing ready to use render utilities in the [*Shaders*](Shaders) and [*Render*](Render) libraries.\n\n*Malt* is meant to be used by a *Host* application, like [*BlenderMalt*](../BlenderMalt).  \nThe *Host* is responsible for preparing and sending the *Scene* data to *Malt*, including already loaded and ready to use [*Meshes*, *Shaders* and *Textures*](GL). \n\n### Render\n\n*Pipelines* main task is to implement a *render* function.\nThe *render* function takes a *Scene* and must return the rendered result as a *Texture*.\n\n### Scene\n\nThe [*Scene*](Scene.py) class makes as little assumptions as posible about the data needed by the *Pipeline*.  \nInstead, the *Pipeline* can declare custom [*Parameters*](PipelineParameters.py) for each *Scene* object type.  \nThe host is responsible for exposing those parameters so users can edit them.\n\n### Materials\n\nAdditionaly, Pipelines are responsible for compiling *Materials*.  \nA *Material* is a *Python Dictionary* of [Shaders](GL/Shader.py) with an arbitrary number of entries.  \nThe host is responsible for sending back scene objects with their respective materials and the shader parameters already setup.  \n\n*Materials* should share the same source code for all the *Shaders* they generate by using conditional compilation through the *Preprocesor*.  \nBy default *Pipelines* declares the *VERTEX_SHADER* define when generating the *Vertex Shader* source code and *PIXEL_SHADER* for *Pixel/Fragment Shaders*.\n\n### Implementing a Pipeline\n\nCustom *Pipelines* can be implemented by creating a new *class* that inherits the *Pipeline* *class*. The pipeline can then be loaded from the *Host* settings.\n\nThe main functions to override are:\n\n* *\\_\\_init__*: *Pipeline Parameters* *(self.parameters)* should be registered here, in addition to any *Render Resource* like *Shaders* and *UBOs* needed by the *Pipeline*.\n\n> 💡 Since there can be multiple instances of the same *Pipeline*, it's a good practice to share resources between them when possible, to shorten *Pipeline* creation times and lower memory consumption.\n\n* *setup_render_targets*: Any resolution dependent resource, like *RenderTargets* should be created here. This function is called whenever the *Pipeline* resolution changes, including the first time *render* is called.  \n\n* *compile_material_from_source*: This should return a dictionary of all the *Shaders* generated from a *Pipeline* *Material*.\n\n* *do_render*: This one is called by the *render* function after setup and as its name implies should render a whole frame and return it as a *Texture*.\n\n#### Minimal example\n\n```Python\nclass MiniPipeline(Pipeline):\n\n    DEFAULT_SHADER = None\n\n    def __init__(self):\n        super().__init__()\n\n        self.parameters.world['Background Color'] = Parameter((0.5,0.5,0.5,1), Type.FLOAT, 4)\n\n        self.common_buffer = Common.CommonBuffer()\n        \n        if MiniPipeline.DEFAULT_SHADER is None: \n            source = '''\n            #include \"Common.glsl\"\n\n            #ifdef VERTEX_SHADER\n            void main()\n            {\n                DEFAULT_VERTEX_SHADER();\n            }\n            #endif\n\n            #ifdef PIXEL_SHADER\n            layout (location = 0) out vec4 RESULT;\n            void main()\n            {\n                RESULT = vec4(1);\n            }\n            #endif\n            '''\n            MiniPipeline.DEFAULT_SHADER = self.compile_material_from_source('mesh', source)\n        \n        self.default_shader = MiniPipeline.DEFAULT_SHADER\n        \n    def compile_material_from_source(self, material_type, source, include_paths=[]):\n        return {\n            'MAIN_PASS' : self.compile_shader_from_source(\n                source, include_paths, ['MAIN_PASS']\n            )\n        }\n    \n    def setup_render_targets(self, resolution):\n        self.t_depth = Texture(resolution, GL_DEPTH_COMPONENT32F)\n        self.t_main_color = Texture(resolution, GL_RGBA32F)\n        self.fbo_main = RenderTarget([self.t_main_color], self.t_depth)\n        \n    def do_render(self, resolution, scene, is_final_render, is_new_frame):\n        self.common_buffer.load(scene, resolution)\n        \n        UBOS = { 'COMMON_UNIFORMS' : self.common_buffer }\n\n        self.fbo_main.clear([scene.world_parameters['Background Color']], 1)\n\n        self.draw_scene_pass(self.fbo_main, scene.batches, 'MAIN_PASS', self.default_shader['MAIN_PASS'], UBOS)\n\n        return { 'COLOR' : self.t_main_color }\n```\n\nFor a complete example see the [NPR_Pipeline](Pipelines/NPR_Pipeline)\n\n"
  },
  {
    "path": "README.md",
    "content": "# Malt\n\nMalt is a fully customizable real-time rendering framework for animation and illustration.  \nIt's aimed at advanced users and technical artists who want more control over their workflow and/or their art style, with special care put into the needs of stylized non photorealistic rendering.\n\n[Download](#install) | [Docs](https://malt3d.com) | [Forums & Support](https://github.com/bnpr/Malt/discussions) | [Bug Reports](https://github.com/bnpr/Malt/issues) | [Twitter](https://twitter.com/pragma37) | [Patreon](https://patreon.com/pragma37)\n\n## Features\n\n- **Free and Open Source**. MIT License.\n- **Real Time Rendering**.\n- **Complete *Blender* integration**.\n- **Built-in Pipeline for Stylized Non Photorealistic Rendering**.\n- **Code as a First Class Citizen**\n    - Automatic reloading.\n    - *VSCode* integration, including *GLSL* autocompletion.\n    - Automatic generation of nodes from *GLSL* functions.\n    - Automatic UI for *Shader* and *Pipeline* parameters.\n    - 100% customizable *Python* Render Pipelines.\n\n## Requirements\n\n- OpenGL 4.5\n- Latest Blender stable release.\n- Windows or Linux\n\n> A dedicated Nvidia or AMD graphics card is highly recomended.  \n\n## Install\n \n- Go to [the latest Release page](https://github.com/bnpr/Malt/releases/tag/Release-latest).\n- Download the *BlenderMalt* version that matches your OS.\n- Open Blender. Go to *Preferences > Addons*, click on the *Install...* button and select *BlenderMalt.zip* from your downloads. *(It will take a few seconds)*\n- Tick the box in the *BlenderMalt* panel to enable it.\n\n> Altenatively, you can download the [Development version](https://github.com/bnpr/Malt/releases/tag/Development-latest) to test the latest features.       \n\n## Uninstall\n\n- Untick the box in *Preferences > Addons > BlenderMalt* to disable the addon.\n- Restart *Blender*.\n- Go back to *Preferences > Addons > BlenderMalt*, expand the panel and click the *Remove* button.\n\n## First steps\n\nTo learn how to use *Malt*, check the [Docs](https://malt3d.com/Documentation/Getting%20Started/), this [playlist](https://www.youtube.com/playlist?list=PLiN2BGdwwlLqbks8h5MohvH0Xd0Zql_Sg) and the [Sample Files](https://github.com/bnpr/Malt/discussions/94).  \nThe [Q&A section](https://github.com/bnpr/Malt/discussions/categories/q-a) is full of info as well.\n\n## Developer Documentation\n\n[How to setup BlenderMalt for Development.](docs/Setup-BlenderMalt-for-Development.md)\n\nDeveloper documentation is best viewed directly in [Github](https://github.com/bnpr/Malt/tree/Development#developer-documentation), most folders in the source code have relevant documentation.  \nThe [Malt folder documentation](Malt#malt) is a good starting point.\n"
  },
  {
    "path": "__init__.py",
    "content": "# Some people are used to download the zipped repo from Github to install Blender addons.\n# This file is just to redirect those users to the right path. It's not distributed in the actual addon.\n\nbl_info = {\n    \"name\": \"Oops! You downloaded the wrong BlenderMalt file.\",\n    \"description\" : \"Please, read the install intructions on Github or malt3d.com\",\n    \"author\" : \"Miguel Pozo\",\n    \"version\": (1,0,0),\n    \"blender\" : (3, 0, 0),\n    \"doc_url\": \"https://malt3d.com/documentation/getting started/#install\",\n    \"tracker_url\": \"https://github.com/bnpr/Malt#install\",\n    \"category\": \"Render\"\n}\n\ndef register():\n    pass\n\ndef unregister():\n    pass\n"
  },
  {
    "path": "docs/Documentation/Getting Started.md",
    "content": "# Gettting Started\n\n## Requirements\n\n- OpenGL 4.5 support.\n- Latest Blender stable release.\n\n> A dedicated Nvidia or AMD graphics card is highly recomended.  \n\n## Install\n \n- Go to [the latest Release page](https://github.com/bnpr/Malt/releases/tag/Release-latest).\n- Download the *BlenderMalt* version that matches your OS.\n- Open Blender. Go to *Preferences > Addons*, click on the *Install...* button and select *BlenderMalt.zip* from your downloads. *(This will take a few seconds)*\n- Tick the box in the *BlenderMalt* panel to enable it.\n\n> Altenatively, you can download the [Development version](https://github.com/bnpr/Malt/releases/tag/Development-latest) to test the latest features.       \n\n## Uninstall\n\n- Untick the box in *Preferences > Addons > BlenderMalt* to disable the addon.\n- Restart *Blender*.\n- Go back to *Preferences > Addons > BlenderMalt*, expand the panel and click the *Remove* button.\n\n## Enable Malt\n\n![img](2022-02-16-16-45-47.png)\n\n*Malt* is a separate render engine, just like *Cycles* and *EEVEE*.  \nTo enable it, select *Malt* in *Properties Panel > Render Properties > Render Engine*.\n\n> When *Malt* is enabled, a tiny black window will pop up. This is the process where the renderer runs.  \n![img2](2022-02-16-17-20-18.png)  \nFeel free to ignore it, it's only there because hiding it can lower the process priority and impact the render performance.\n\n## Sample Files\n\nSample files can be found at [Github](https://github.com/bnpr/Malt/discussions/94).  \n\n![](2022-03-21-15-58-04.png)\n\n## Pipelines\n\n*Malt* allows implementing custom *render pipelines* for advanced use cases.\n\nHowever, the built-in *NPR Pipeline* is a fully featured and highly customizable pipeline designed to cover most use cases.  \n\nMost parts of this documentation apply only to the *NPR Pipeline*.\n\n> For building custom *render pipelines*, see the [Developer Documentation](https://github.com/bnpr/Malt/tree/Development/Malt#malt).\n"
  },
  {
    "path": "docs/Documentation/Graphs.md",
    "content": "# Graphs & Nodes\n\n*Pipelines* and *Plugins* declare their own *Node Tree* subtypes, a.k.a *Graph* types.\n\nNodes have their own *Malt Node Tree* editor.\n![](2022-02-23-17-03-18.png)\n\n*Pipelines* and *Plugins* can declare their own *Graph* types:\n\n![](2022-02-23-17-11-03.png)\n\n> *Pipelines* and *Plugins* can also declare custom *Node Libraries*.\n\nThere are 2 main groups of *Graph* types: *Shader Graphs* and *Render Graphs*.\n\n*Shader Graphs* compile to *GLSL* (*OpenGL Shading Language*), while *Render Graphs* compile to *Python* scripts.  \n\n> These source files can be found in the *.malt-autogenerated* folder alongside their *.blend* file.\n\n*Shader Graphs* are always used from a *Material*. Multiple *Materials* can use the same *Node Tree* while overriding some parameters.\n\n> It's best to reuse *node trees* when possible for improved render performance and shader compilation times.\n\n## Socket Types\n \n*Node socket* types keep the *GLSL* naming:\n\n- **bool**  \nA value that can be either *true* or *false*.  \n- **float**  \nA positive or negative number that can have decimals. *(ie: -123.456, -1.0, 0.0, 1.0, 123.456)*  \n- **int**  \nA positive or negative number that can't have decimals. *(ie: -123, -1, 0, 1, 123)*  \n- **uint**  \nA positive number that can't have decimals.  *(ie: 0, 1, 123)*  \n\n- **vec2**  \nA vector of 2 floats. Used for 2d coordinates, like *UVs*.  \n- **vec3**  \nA vector of 3 floats. Used for 3d coordinates, like *positions* and *normals*, and *RGB* colors.  \n- **vec4**  \nA vector of 4 floats. Used for *RGBA* colors.\n\n- **bvec(n)**  \nA vector of (n) bools.  \n- **ivec(n)**  \nA vector of (n) ints.  \n- **uvec(n)**  \nA vector of (n) uints.  \n\n- **mat4**  \nA 4x4 *Matrix*.\n\n- **sampler1D**  \nA 1D image (like a color gradient) that holds float values.  \n- **sampler2D**  \nA 2D image (like a texture) that holds float values.  \n\n- **isampler(n)D**  \nAn (n) dimesional image that holds int values.  \n- **usampler(n)D**  \nAn (n) dimesional image that holds uint values.  \n\nYou can find more details in the [OpenGL wiki](https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)).\n\n### Structs\n\nIn addition to basic types, you will also find custom types, a.k.a. *structs*.  \n*Structs* are just groups of basic types and/or other *structs*.\n\nFor example, a typical *PBR* material setup is composed of albedo *(vec4)*, normal *(vec3)*, roughness *(float)* and metalness *(float)*.\n\nSo nodes that operate with these values could pack all of them in a single *PBR_Properties* socket, instead of having them as 4 individual sockets.\n\n*Struct sockets* always expose their subproperties so you can still override them individually if needed.  \nSo in the *PBR_Properties* example you would see these sockets:  \n\n![](2022-04-02-17-36-17.png)\n\n### Arrays\n\nArrays are lists of multiple values of the same type.  \nThey are expressed as `type[n]` where `n` is the number of values it stores.  \nFor example, a socket expressed as `random_colors: (vec4[8])` is a list of 8 values of type *vec4*.\n\n### Opaque Types\n\nIn the case of *Python Render Graphs* there are some types, like *Scene*, that can't be edited directly in the node editor.  \nHowever, they can be edited from code in *custom nodes*.\n\n### Automatic type conversion\n\nConversion between types is handled automatically whenever there's an unambiguous mapping between the 2.  \n\n![](2022-04-02-17-35-20.png)\n\nYou can add extra automatic conversions by adding functions with the signature:\n```glsl\ntypeB typeB_from_typeA(typeA value);\n```\nThe full list of built-in conversion functions can be found at [Malt/Shaders/Node Utils/conversion.glsl](https://github.com/bnpr/Malt/blob/Development/Malt/Shaders/Node%20Utils/conversion.glsl)\n\n## Node Types\nThe main categories of nodes are *Functions*, *Structs*, *Inputs/Outputs* and *Other*.\n\n![](2022-03-21-18-17-30.png)\n\n### Functions\nThis is the most common node type, they behave like a typical node where their inputs are used to compute their outputs.  \nIn the case of Shader nodes, they're auto-generated from GLSL functions. \n\n### Structs\nUsed to pack/unpack and modify struct values.    \nIn the case of Shader nodes, they're auto-generated from GLSL structs.\n\n> Struct nodes can be used to create a new struct from scratch, override the properties of an existing node or retrieving the value of individual properties:  \n![](2022-02-25-20-18-44.png)\n\n### Other\n\n#### Array\nUsed to retrieve an individual value from an array.\n\n![](2022-04-02-17-34-35.png)\n\n#### Inline Code\nAllows you to write value assignment directly in code form. Useful to write math formulas and to workaround the node system limitations.\n\n![](2022-03-21-18-05-53.png)\n\n### Inputs/Outputs\nEvery node system produces a result from some given intputs. Ie, a material computes the color of a surface given some geometry, and render nodes generate an image from the scene data.  \n\nEach graph type has at least one set of input/output nodes, but some of them (like Mesh) have several. \n\n#### Custom IO\nSome IO nodes allow adding extra properties. These are usually known as AOVs (Arbitrary Output Values) in other render engines, but Malt supports custom inputs too.  \n\n![](2022-03-21-18-11-27.png)\n\n> Custom IO can be set up from the Input/Output node properties panel.  \n>  \n> Modifying the Custom IO will requires the recompilation of all graphs affected by it, so changes aren't applied automatically.  \nYou must click the *Reload* button to apply your changes.\n\nThese *custom IO* properties are exposed as sockets in nodes that execute these *graphs*.  \nFor example, the *Screen Pass* node in *Render Graphs* will expose as sockets the *custom IO* from the *Screen Shader* it's using.\n\n![](2022-03-21-18-12-34.png)\n\nSome graph types (like Mesh) share their custom IO properties across all the materials in a scene, while others (like Screen) can be diferent for each graph.\n\n<!--TODO\nlibraries\nreload\nnode trees vs materials\n-->\n\n## Graph Types\n\n### Mesh\n\n> [Mesh Graph Reference](../../reference/Mesh-graph)\n\nMesh Graphs define the surface properties of an object. These are the equivalent to *Blender Material Nodes*. \nMesh Graphs are always used as part of a Material attached to an Object.\n\n#### Passes & Outputs\n\nMultiple shader programs are generated from a single Mesh Graph: the Shadow Pass, the Pre Pass and the Main Pass. Each of them consisting of a vertex and a pixel shader.\n\nThe vertex shaders are generated from the CUSTOM_VERTEX_SHADER Output and the DISPLACEMENT_SHADER Output.  \nThe Shadow Pass and the Pre Pass pixel shaders are generated from the PRE_PASS_PIXEL_SHADER Output and the DEPTH_OFFSET output.  \nThe Main Pass pixel shader is generated from the MAIN_PASS_PIXEL_SHADER Output and the DEPTH_OFFSET output.  \n\n```mermaid\ngraph LR\n    MG{Mesh Graph}\n\n    classDef Graph fill:#4602bd, color:#fff, stroke-width:5px;\n    class MG Graph;\n    \n    CVO[[COMMON VERTEX SHADER Output]]\n    VDO[[VERTEX DISPLACEMENT SHADER Output]]\n    PPO[[PRE PASS PIXEL SHADER Output]]\n    MPO[[MAIN PASS PIXEL SHADER Output]]\n    DOO[[DEPTH OFFSET Output]]\n\n    classDef Output fill:#222, color:#fff, stroke-width:2px;\n    class CVO,VDO,PPO,MPO,DOO Output;\n\n    SP((Shadow Pass))\n    PP((Pre Pass))\n    MP((Main Pass))\n\n    classDef Pass fill:#d43c00, color:#fff, stroke-width:4px, stroke:#fff;\n    class SP,PP,MP Pass;\n\n    SMT(Shadow Maps)\n    NDT(Normal & Depth)\n    IDT(IDs)\n\n    classDef Texture fill:#7f00d4, color:#fff, stroke-width:2px, stroke:#000;\n    class SMT,NDT,IDT Texture;\n    \n    MG ===> CVO & VDO & PPO & MPO & DOO \n    CVO & VDO & PPO ---> SP\n    CVO & VDO & PPO & DOO --> PP\n    CVO & VDO & MPO & DOO --> MP\n\n    SP -.-> SMT -.-> PP & MP\n    PP -.-> NDT & IDT -.-> MP\n```\n\n### Light\n\n> [Light Graph Reference](../../reference/Light-graph)\n\nLight Graphs define the color projected by a light at any given point. While most lights don't need a custom shader, this can be useful for certain effects, like the light emited from a projector or a flashlight.  \nLights with custom shaders are more expensive to render.\nLight shaders don't take shadowmaps into account, shadows are handled by Mesh shaders.\n\nLight Graphs are always used as part of a Material attached to a Light. \n\n### Screen\n\n> [Screen Graph Reference](../../reference/Screen-graph)\n\nScreen Graphs define screen-space shaders. The most common use case for screen-space shaders are post-processing effects, like color correction, or bloom.\nHowever they can also fit other use cases, like deferred shading or precalculating textures, like AO, before the Main Pass.\n\nScreen Graphs are always used as part of a Material, usually asigned to a Screen Pass node in a Render or Render Layer graph.\n\n### Render & Render Layers\n\n> [Render Graph Reference](../../reference/Render-graph)\n\n> [Render Layer Graph Reference](../../reference/Render Layer-graph)\n\nRender nodes define the steps for rendering a frame, from preparing the shadowmaps to performing the image antialiasing.\n\nMost render engines only allow users to edit the render pipeline after the scene has been drawn, in the form of post-processing/compositing, but Malt exposes the full process for customization. \n\n*Render graphs* have the *RenderLayers* node, that executes a *Render Layer* graph.\nThe scene geometry is drawn inside *Render Layer*, using *Depth Peeling*.\n\n#### Depth Peeling\nReal-time rendering often relies heavily on screen-space techniques, however this usually doesn't work with transparent objects.  \nMalt renders opaque objects first, and then transparent objects in multiple overlapping layers (a.k.a. *Depth Peeling*), so every type of object can follow the same render path.  \nHowever, it's important for screen-space shaders to avoid accidentaly covering back layers from the front ones.  \nFor this purpuse, the *Screen Node* has the *Layer Only* setting.\n"
  },
  {
    "path": "docs/Documentation/Plugins.md",
    "content": "# Plugins\n\nRender pipelines can be customized through plugins.\nPlugins can:\n\n- Add new node libraries to *Pipeline Graphs*.\n- Add new *Pipeline Parameters*.\n- Add new *PipelineGraph types*.\n\nPlugins can be installed globally in the *Addon Settings* or per world in the *World Panel*.\n\nA basic example can be found in [Development/plugins](https://github.com/bnpr/Malt/tree/Development/plugins)\n\n> The Plugins path should point to the plugins parent folder, then any plugin that you drop into that folder should be automatically loaded.  \n\n> For example, the Plugins path shouldn't point to `C:/Malt-plugins/My-plugin-example/`, it should point to `C:/Malt-plugins/`\n"
  },
  {
    "path": "docs/Documentation/Settings.md",
    "content": "# Setup & Settings\n\n## Addon Settings\n\n![](2022-03-21-19-27-33.png)\n\n- **Open Session Log**  \n>Opens the current session log in a text editor.\n- **Global Plugins**  \n>The path to the *plugins* folder. See [Plugins](../Plugins) for more info.\n- **Show sockets in Material Panel**\n>Show node socket properties in the Material Panel by default.\n- **Max Viewport Render Framerate**  \n>Framerate cap for the viewport. Limiting *Blender* framerate can improve *Malt* performance and animation playback stability. Set it to 0 to disable it.\n- **Auto setup VSCode**  \n>On file save, setups a VSCode project on your *.blend* file folder.\n- **RenderDoc Path**  \n>Path to the **renderdoccmd** executable, for [RenderDoc](https://renderdoc.org/) debugging.\n- **Debug Mode**  \n>Include debug info in the *Session Logs*. Enabling it increases the log sizes and can negatively affect performance, don't enable it unless a developer asks you for it in a bug report.\n\n## Pipeline Configuration Settings\n\nThe *Pipeline Configuration Settings* contain the settings to select and setup the *pipeline* itself.  \n\nIn *Malt*, the *pipeline* configuration and render settings are part of the *World* properties. This allows sharing the same setup across *Scenes* and *.blend* files.\n\n![](2022-02-16-17-08-23.png)  \n\n- **Malt Pipeline**  \n>The path to a custom *render pipeline*. If it's empty (the default), the *NPR Pipeline* will be used.  \n>The button at the right is the *Reload Pipeline* operator, which fully restarts the renderer.  \n- **Local Plugins**  \n>The path to the *World* specific *plugins* folder. See [Plugins](../Plugins) for more info.  \n- **Bit Depth (Viewport)**  \n>The viewport image *bit depth*. Higher *bit depths* can yield better image quality (avoiding *banding* and *clamping*), but can bottleneck your *GPU<->CPU* bandwith at high resolutions.  \n>Final renders are always sent to *Blender* as 32bit images for best quality, regardless of this setting.\n\n### Material Settings\n- **Shader Source**  \n>GLSL source file for the material.  *(Ignored when a Node Tree is selected)*\n- **Node Tree**  \n>*Node Tree* used for this material.\n\nThe material panel will also show the parameters declared in its *Shader/Node Tree*.  \n\nIn code based materials, \"ALL CAPS\" uniforms and uniforms starting with an underscore \"_\" are treated as \"private\" and won't be shown in the material panel.  \n\nFor node based materials, visibility can be toggled from the node *UI*.  \n\n![](2022-03-21-19-36-40.png)\n\n### Light Settings\n\n- **Color**  \n>The light color if no custom shader is in use.\n- **Radius**  \n>The area of effect radius for *Point* and *Spot Lights.*\n- **Angle**  \n>*Spot light* cone angle.\n- **Blend**  \n>*Spot light* cone gradient angle.\n\n## Pipeline Settings\n\nThe settings created by the active *pipeline* and *plugins* can be found in the *Properties Panel* inside a *Malt Settings* menu.  \n\n[Pipeline Settings Reference](/reference/settings).\n \n![](2022-02-16-17-05-31.png)\n\n\n### Parameter Overrides\n\n*Malt* uses different performance profiles, allowing you to choose the appropiate performance/quality tradeoff for different tasks.\n\n> For example, you may want to use lower quality settings while modeling or animating, higher quality settings for material lookdev, and even higher for the final render.\n\nEach setting can have a different value for each profile.\nThere are 3 performance profiles:  \n\n- **Default**  \nUsed when the *Blender Viewport Shading Mode* is set to *Render* or when there's no active override.  \nIt's best to set Default settings at a quality level that provides a close result to the final render, while keeping a reasonable performance for viewport navigation.\n- **Preview**  \nUsed when the *Blender Viewport Shading Mode* is set to *Preview*.  \nIt's best to set Preview settings at a quality level that allows you to edit assets and play animations in real time.\n- **Final Render**  \nUsed for the final render (*F12*). For setting that are too heavy for the viewport, but needed for the final render quality.  \n\nAny setting can be overridden for a specific profile by clicking on the *Override* button at its right. Otherwise the *Default* profile value will be used.  \n![](parameter-override.gif)\n\n\n"
  },
  {
    "path": "docs/Documentation/Tooling.md",
    "content": "# External Tools\n\n## VSCode\n\n*Malt* has built-in integration with [VSCode](https://code.visualstudio.com), including a workaround to provide GLSL intellisense via C++ language servers.  \nWhen *Malt* is the active render engine, it will auto-setup a *VSCode* workspace on the same folder you save your *.blend* file.  \n>*(This feature can be disabled in the Addon Preferences)*\n\n\n<div style=\"position: relative; width: 100%; padding-top: 56.25%; /* 16:9 Aspect Ratio */\">\n<iframe style=\"position: absolute; top: 0; left: 0; bottom: 0;right: 0; width:100%; height:100%; margin:0; border:0;\" \nsrc=\"https://www.youtube-nocookie.com/embed/UjsW7Ce0BKo\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n</div>\n\n## RenderDoc\n\n*Malt* has built-in integration with [RenderDoc](https://renderdoc.org).  \nIt can be enabled by setting path to the **renderdoccmd** executable in the addon settings.  \nOnnce Malt has been initialized, RenderDoc can be attached to Malt through the [attach to running instance](https://renderdoc.org/docs/window/capture_connection.html) option.  \n\n![](2022-04-02-18-02-24.png)\n\nCaptures can be triggered directly through the Blender UI:\n![](2022-03-21-15-36-42.png)\n"
  },
  {
    "path": "docs/FAQ.md",
    "content": "# FAQ\n\n# What's the difference between Malt and BEER?\n\n|   | Malt | BEER |\n|---|------|------|\n| **Target Audience** | Advanced users | Everyone |\n| **Worflow** | Code & Nodes | Layers/Stacks |\n| **Backend** | OpenGL | Malt |\n| **Project Lead** | [Miguel Pozo](https://twitter.com/pragma37) | [LightBWK](https://twitter.com/Lightbwk) |\n| **Main Developer** | [Miguel Pozo](https://twitter.com/pragma37) | *TBD* |\n| **Funding** | [Patreon](https://patreon.com/pragma37) | [BEER Dev Fund](https://blendernpr.org/beer/) |\n\n# Will Malt be ported to Vulkan?\n\nThe most likely replacement for *OpenGL* in *Malt* is WebGPU Native, since it's easier to use than *Vulkan* and has better *MacOS* support.\n\n# Can Malt be used for games?\n\nNot at the moment.    \nMalt main focus is animation and illustration and it prioritizes flexibility and image quality, which usually comes at a performance cost.\n\n# How can I help?\n\n- Share your *Malt* artworks! [*#malt3d*](https://twitter.com/hashtag/malt3d)\n- Make tutorials.\n- Implement new features.\n- Join the [Patreon](https://www.patreon.com/pragma37).\n\n"
  },
  {
    "path": "docs/From-Nodes-To-Code/From-Nodes-To-Code.md",
    "content": "# From Nodes To Code\n\nThis tutorial will teach you how to make your own *Malt* shaders.\n\n*Malt* shaders are written in *GLSL* (OpenGL Shading Language), the shader programming language used by *OpenGL* and *Vulkan*.\n\nThis tutorial assumes you are already familiar with *EEVEE* nodes.  \nYou may be surprised by how much of your knowledge about node based programming can easily be applied to code and how much more powerfull and convenient can be for advanced tasks.\n\n## Before we start\n\nShader files are just plain text, so any text editor works for writing them.  \nBut we can make our lifes much easier by using a code editor.\n\n*Malt* has built-in integration with [VSCode](https://code.visualstudio.com/download), it's free, cross-platform and open source so go ahead, download and install it!\n\n<div style=\"position: relative; width: 100%; padding-top: 56.25%; /* 16:9 Aspect Ratio */\">\n<iframe style=\"position: absolute; top: 0; left: 0; bottom: 0;right: 0; width:100%; height:100%; margin:0; border:0;\" \nsrc=\"https://www.youtube-nocookie.com/embed/UjsW7Ce0BKo\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>\n</div>\n\nAfter installation:\n\n1. Open Blender, change the render engine to *Malt* and save the *.blend* file in a new folder.  \n(When *Malt* is active it will auto-setup a *VSCode* workspace on the same folder you save your *.blend* file)\n2. Open the folder in the OS file explorer and *Right Click > Open with Code*.\n3. Create a new file and save it as ```malt-tutorial.mesh.glsl```. (or any othe name, but make sure it ends in ```.mesh.glsl```)\n4. VSCode will tell you it has extensions for that file format and will ask you if you want to install them, say yes!\n5. Select the shader file as the *Shader Source* for a new material (*Material Properties* Panel).\n\nNow we can make changes in our shader file and as soon as we save it (*Ctrl+S*) Malt will reload it.\n\n## First Contact\n\nLet's start by looking at some basic *Malt* examples and what would be their *EEVEE* counterparts, so you can see how they compare to each other.  \nRead the code, but don't worry if you don't really understand it, we will take a more detailed look at it later.  \n\nTo get a better feel of it, let's test this examples as we go.  \nYou could just copy-paste them, but you will get a better grasp by typing them yourself.  \nIt will also serve to get familiar with the extra goodies VSCode provides, like code auto-completion.\n\nIf you miss-type something, you will see an error pop up in the Material panel inside *Blender* that will tell you what and where the error is.\n\nTry to play with the examples, modify them, break them on purpose and take a look at the errors.\n\n### Starting Point (an empty shader)\n\n<div style=\"width: 50%; float:left\">\n\n```glsl\n#include \"NPR_Pipeline.glsl\"\n\nvoid COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO)\n{\n\n}\n```\n\n</div>\n\n<div style=\"width: 50%; float:right\">\n\n![](starting-point.png)\n\n</div>\n\n<div style=\"clear: both\"></div>\n\n*Just an empty black shader.*  \n*Even the most advanced procedural shader you can imagine needs this.*\n\n### Flat Color\n\n<div style=\"width: 50%; float:left\">\n\n```glsl\n#include \"NPR_Pipeline.glsl\"\n\nuniform vec3 color = vec3(0,1,0);\n\nvoid COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO)\n{\n    PO.color.rgb = color;\n}\n```\n\n</div>\n\n<div style=\"width: 50%; float:right\">\n\n![](flat-color.png)\n\n</div>\n\n<div style=\"clear: both\"></div>\n\n*See how a new color property has appeared in your Material panel?*  \n*Could you change the default green value ```vec3(0,1,0)``` in the code to red?*  \n*What happens if you set ```vec3(0.5)``` as the default value?*  \n\n> If you have edited a property in the UI, you can always *Right Click > Reset to Defaul Value*\n\n### Textures\n\n<div style=\"width: 50%; float:left\">\n\n```glsl\n#include \"NPR_Pipeline.glsl\"\n\nuniform int uv_channel = 0;\nuniform sampler2D color_texture;\n\nvoid COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO)\n{\n    vec2 texture_coordinates = S.uv[uv_channel];\n    \n    vec4 sampled_color = texture(color_texture, texture_coordinates);\n    \n    PO.color = sampled_color;\n}\n```\n\n</div>\n\n<div style=\"width: 50%; float:right\">\n\n![](textures.png)\n\n</div>\n\n<div style=\"clear: both\"></div>\n\n*In programming, the first element from a list is the number 0, so the first UV from your mesh is the number 0.*  \n*It's not that weird if you think of it as the offset from the start of the list.*\n\n### Lighting\n\n<div style=\"width: 50%; float:left\">\n\n```glsl\n#include \"NPR_Pipeline.glsl\"\n\nuniform vec3 color = vec3(0,1,0);\n\nvoid COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO)\n{\n    PO.color.rgb = color * get_diffuse();\n}\n```\n\n</div>\n\n<div style=\"width: 50%; float:right\">\n\n![](lighting.png)\n\n</div>\n\n<div style=\"clear: both\"></div>\n\n*See how we can multiply colors just by using the multiply sign ```color * get_diffuse()``` ?*  \n*The same goes for addition (+), subtraction (-) and division (/).*  \n*Isn't that cool?*  \n*You can even combine them in the same line and use parenthesis, like ```vec3 result = vec3(1) - (A+B+C) / 3.0;```.`*   \n*Now compare it with the same formula in node form!*\n\n\n### Recap\n\nIt's time for something that looks a bit more like an actual material.\nLet's combine the previous examples!\n\n<div style=\"width: 50%; float:left\">\n\n```glsl\n#include \"NPR_Pipeline.glsl\"\n\nuniform vec3 ambient_color = vec3(0.5,0.5,0.5);\n\nuniform int uv_channel;\nuniform sampler2D color_texture;\n\nvoid COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO)\n{\n    vec3 light_color = get_diffuse() + ambient_color;\n    \n    vec2 texture_coordinates = S.uv[uv_channel];\n    \n    vec4 surface_color = texture(color_texture, texture_coordinates);\n\n    vec3 result = surface_color.rgb * light_color;\n    \n    PO.color.rgb = result;\n}\n```\n\n</div>\n\n<div style=\"width: 50%; float:right\">\n\n![](all-together.png)\n\n</div>\n\n<div style=\"clear: both\"></div>\n\nWoah! That was a lot of information, right?  \nIt's ok if you don't feel like you actually understand it as long as you have a rough intuition of what's going on.\n\nNow would be a good time to look at the [Shader Examples](https://github.com/bnpr/Malt/tree/master/Shader%20Examples).  \nEach of them implements a single feature, why don't you try to mix some of them in the same shader?  \nFor example, a gradient material with outlines, rim lights and ambient occlusion.\n\n## GLSL\n\nOn the last chapter we have relied on examples and intuition.  \nYou could continue just by copy-pasting and combining examples, but you would always feel out of control.\n\nLuckily GLSL is a very small language, so there's not really a lot of details to learn.\n\nThere are 2 main concepts you should understand, *Functions* and *Variables* .  \n*Functions* are basically the same as nodes, they take some parameters and gives you a result.  \nInstead of connecting *Functions* with wires, we give names to their results, so we can refer to them later.  \nThose names where we store results are *Variables*.\n\n### Comments\n\nBefore we go further, let's start with an easy one. Comments!\n\n```glsl\n// This is a comment!\n// Comments are ignored by the computer, they are for the people reading the code.\n// Any line that starts with 2 dashes \"//\" is a comment.\n```\n\n### Variables\n\n*Variables* are like node sockets, they have a type, a name and a value.\n\n```glsl\n// We create variables by writing their type, followed by their name and a semicolor:\n// type name;\n// The name can be whatever you want as long as it goes all together;\n// type ThisReallyLong_and_w3iRd0_name_is_OK_1234;\n// We can optionally assign them a value when we create it:\n// type name = value;\n// Once a variable has been created, you can change its value as many times as you want.\n// name = other_value;\n\n// The types we use the most are floats and vectors\n\n// Floats are just computer lingo for numbers.\nfloat some_number = 1;\n// They can be negative too\nsome_number = -1;\n// Have decimals\nsome_number = -1.5;\n// And can be as big as you want\nsome_number = 176189672672868126.71671861876871678;\n\n// Floats can be added, subtracted, multiplied and divided\nfloat a = 1;\nfloat b = 2;\nsome_number = a + b; \n// Now some_number is  3\nsome_number = a - b;\n// Now some_number is -1\nsome_number = a * b;\n// Now some_number is  2\nsome_number = a / b;\n// Now some_number is  0.5\n\n// Vectors are a group of 2, 3 or 4 floats\n// We use them for positions and normals\nvec2 position_2d = vec2(1,2);\nvec3 position_3d = vec3(1,2,3);\nvec3 up_normal = vec3(0,0,1);\n// And colors too\nvec3 green = vec3(0,1,0);\nvec4 semi_transparent_red = vec4(1,0,0,0.5);\n\n// We can read their individual properties by typing the vector name followed by a dot (.) and the name of the property\nfloat x_position = position_3d.x; //1\nfloat y_position = position_3d.y; //2\nfloat z_position = position_3d.z; //3\n// It also works for writing\nposition_3d.x = 0; // Now position_3d is (0,2,3)\n\n// Since vectors are used for colors too, this is also valid:\nfloat red_channel = green.r;\nfloat green_channel = green.g;\nfloat blue_channel = green.b;\nfloat alpha_channel = semi_transparent_red.a;\n\n// Using rgba or xyzw as properties name is just a matter of preference, they are 100% equivalent\nvec3 some_color = vec3(1,2,3);\nsome_color.x = 0; //Now some_color.r is 0\n\n// Additionaly this kind of combinations are also valid\nposition_2d = position_3d.xy; // (0, 2)\nsome_color = semi_transparent_red.rgb; //(0,1,0)\n```\n\n### Functions\n\n*Functions* are equivalent to nodes, they take some parameters and gives you a result.\n\n```glsl\n// We declare functions by typing the type of value they return,\n// followed by its name and a pair of parenthesis.\nvec3 give_me_a_green_color()\n{\n    vec3 green_color = vec3(0,1,0);\n    \n    // We return a value by typing the keyword \"return\" followed by the return value and a semicolor.\n    return green_color;\n}\n\n// If a function doesn't return any value, its type is \"void\".\n// We will see later why functions that don't return a value can also be useful.\nvoid example_function()\n{\n    //To use a function (a.k.a. call a function) we type its name followed by a pair of parenthesis.\n    vec3 color_result = give_me_a_green_color();\n    // color_result is now (0,1,0)\n}\n\n// If a funtion takes input parameters, we write their type and name inside the parenthesis.\n// Each parameter must be separated by commas.\nfloat add_two_numbers(float first_number, float second_number)\n{\n    float result = first_number + second_number;\n\n    return result;\n}\n\nvoid another_example_function()\n{\n    float A = 1;\n    float B = 2;\n\n    // For calling a function that takes parameters, we write their values separated by commas.\n    float C = add_two_numbers(A, B);\n    // C is now 3.\n}\n\n// Functions can also have output parameters\nvoid give_me_two_colors(inout vec3 first_result, inout vec3 second_result)\n{\n    first_result = vec3(1,0,0);\n    second_result = vec3(0,1,0);\n}\n\nvoid third_example()\n{\n    vec3 A;\n    vec3 B;\n\n    give_me_two_colors(A, B);\n    // Now A is (1,0,0) and B is (0,1,0).\n}\n\n```\n\n\n>If you reached here, congrats\\!  \n>You can share your questions and feedback in this [thread](https://github.com/bnpr/Malt/discussions/46)\\.  \n>I'm working on new chapters, meanwhile, [The Book of Shaders](https://thebookofshaders.com/) is a great resource\\!  \n\n\n"
  },
  {
    "path": "docs/Setup-BlenderMalt-for-Development.md",
    "content": "# How to Setup BlenderMalt for Development\n\n- Clone the repository.  \n`git clone https://github.com/bnpr/Malt.git`\n- Set a user scripts folder for Blender if you don't have one.  \n[*Blender > Preferences > File Paths > Scripts*](https://docs.blender.org/manual/en/latest/editors/preferences/file_paths.html)\n- Locate the *Python* executable from your *Blender* installation.  \nFor example: ```\"C:\\Program Files\\Blender Foundation\\Blender 2.93\\2.93\\python\\bin\\python.exe\"```\n- Run: ```<Blender Python Path> <Cloned Repo Path>/scripts/setup_blender_addon.py --scripts-folder <User Scripts Path>```\n\n> The setup script will try to compile the CBlenderMalt and the Bridge ipc libraries using CMake,  \n> if you don't have the required toolchain you can just copy them from the Github release.\n\nNow if you restart *Blender* and go to *Preferences > Add-ons*, you should be able to enable *BlenderMalt*.\n\n"
  },
  {
    "path": "docs/extra/extra.css",
    "content": "/* https://github.com/squidfunk/mkdocs-material/issues/1635#issuecomment-811058692 */\n\n[data-md-color-scheme=\"slate\"] {\n    --md-default-bg-color: #24272e;\n}\n\n/*Disable the edit button*/\n.md-content__button {\n    display: none;\n}\n\n.zoom {\n    /*transition: transform ease-in-out 0.5s;*/\n    cursor: zoom-in;\n}\n\n.image-zoom-large {\n    width: auto;\n    max-width: 100vw;\n    max-height: 100vh;\n    /*transform: scale(1.5);*/\n    cursor: zoom-out;\n    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);\n    z-index: 100;\n    position: absolute;\n    left: 50%;\n    transform: translate(-50%, 0);\n}"
  },
  {
    "path": "docs/extra/extra.js",
    "content": "/* https://github.com/squidfunk/mkdocs-material/issues/1635#issuecomment-811058692 */\n\ndocument.querySelectorAll('.zoom').forEach(item => {\n    item.addEventListener('click', function () {\n        this.classList.toggle('image-zoom-large');\n    })\n});"
  },
  {
    "path": "docs/index.md",
    "content": "---\nhide:\n  #- toc\n  #- navigation\n---\n\n# Malt\n\n\n***Malt*** is a fully ***customizable real-time rendering*** framework for animation and illustration.  \nIt's aimed at artists who want more control over their workflow and/or their art style, with special care put into the needs of ***stylized non photorealistic rendering***.  \n\nDesigned as a community effort to expand the possibility space of 3d rendering, it provides graphics programmers and technical artist an enjoyable *“shadertoy-like”* workflow inside ***Blender***, while still allowing to effortlessly share their work with non technical artists through ***Python*** and ***GLSL*** plugins.\n\n---\n\n<figure class=\"video_container\">\n  <video autoplay muted loop style=\"max-height:50vh; max-width: 100%; border-radius: 5rem;\">\n    <source src=\"https://www.pragma37.com/docs/rtc2021/explorer.mp4\" type=\"video/mp4\">\n  </video>\n  <figcaption style=\"max-width: 100%; text-align: center; margin: 0; font-size: small\">\n    <a href=\"https://twitter.com/pragma37\" target=\"_blank\" style=\"color: grey;\">@pragma37</a>\n  </figcaption>\n</figure>\n\n---\n\n- Free and **Open Source**. *MIT License*\n- **Real Time Rendering**\n- Full **Blender** integration\n- Built-in Pipeline for **Stylized Non Photorealistic Rendering**  \n    - Stylized shading models\n    - Light Groups\n    - Line Rendering\n    - Fully customizable through nodes *(Materials, Light shaders, Screen shaders and even the render pipeline itself)*\n- Code as a *First Class Citizen*  \n    - *Auto-reloading* for everything\n    - **VSCode** (including **GLSL** *intellisense*) and **Renderdoc** integration\n    - Automatic generation of nodes from plain GLSL functions\n    - Automatic UI for Shader and Pipeline parameters\n    - 100% customizable Python Render Pipelines\n\n---\n\n<figure class=\"video_container\">\n  <video controls autoplay muted loop style=\"max-height:75vh; max-width:100%; margin: auto; border-radius: 1rem;\">\n    <source src=\"https://www.pragma37.com/docs/rtc2021/pixel-art.mp4\" type=\"video/mp4\">\n  </video>\n  <figcaption style=\"max-width: 100%; text-align: center; margin: 0; font-size: small\">\n    <a href=\"https://twitter.com/pragma37\" target=\"_blank\" style=\"color: grey;\">@pragma37</a>\n  </figcaption>\n</figure>\n\n---\n\n<figure class=\"video_container\">\n  <video controls autoplay muted loop style=\"max-height:75vh; max-width:100%; margin: auto; border-radius: 1rem;\">\n    <source src=\"https://www.pragma37.com/docs/rtc2021/renato3xl.mp4\" type=\"video/mp4\">\n  </video>\n  <figcaption style=\"max-width: 100%; text-align: center; margin: 0; font-size: small\">\n    <a href=\"https://linktr.ee/Renato3xl\" target=\"_blank\" style=\"color: grey;\">@Renato3xl</a>\n  </figcaption>\n</figure>\n"
  },
  {
    "path": "docs/overrides/partials/_footer.html",
    "content": ""
  },
  {
    "path": "docs/reference/Light-graph.md",
    "content": "# Light Graph Reference\n---\n## Input\n---\n### **Geometry**\n- **Inputs**  \n\t- **Space** *: ( int | ENUM(Object,World,Camera,Screen) ) - default = 1*  \n\t- **IOR** *: ( float ) - default = 1.45*  \n- **Outputs**  \n\t- **Position** *: ( vec3 )*  \n\t- **Incoming** *: ( vec3 )*  \n\t- **Normal** *: ( vec3 ) - default = NORMAL*  \n\t- **True Normal** *: ( vec3 )*  \n\t- **Is Backfacing** *: ( bool )*  \n\t- **Facing** *: ( float )*  \n\t- **Fresnel** *: ( float )*  \n\t- **Reflection** *: ( vec3 )*  \n\t- **Refraction** *: ( vec3 )*  \n---\n### **Camera Data**\n- **Outputs**  \n\t- **View Direction** *: ( vec3 )*  \n\t- **Screen UV** *: ( vec2 )*  \n\t- **Z Depth** *: ( float )*  \n\t- **View Distance** *: ( float )*  \n\t- **Camera Position** *: ( vec3 )*  \n\t- **Camera Matrix** *: ( mat4 )*  \n\t- **Projection Matrix** *: ( mat4 )*  \n\t- **Is Orthographic** *: ( bool )*  \n---\n### **Render Info**\n- **Outputs**  \n\t- **Resolution** *: ( vec2 )*  \n\t- **Current Sample** *: ( int )*  \n\t- **Sample Offset** *: ( vec2 )*  \n---\n### **Time Info**\n- **Outputs**  \n\t- **Time** *: ( float )*  \n\t- **Frame** *: ( int )*  \n---\n### **Random**\n- **Inputs**  \n\t- **Seed** *: ( float )*  \n- **Outputs**  \n\t- **Per Object** *: ( vec4 )*  \n\t- **Per Sample** *: ( vec4 )*  \n\t- **Per Pixel** *: ( vec4 )*  \n---\n## Parameters\n---\n### **Boolean**\n- **Inputs**  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n### **Float**\n- **Inputs**  \n\t- **F** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Integer**\n- **Inputs**  \n\t- **I** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n### **Vector 2D**\n- **Inputs**  \n\t- **V** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n### **Vector 3D**\n- **Inputs**  \n\t- **V** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Vector 4D**\n- **Inputs**  \n\t- **V** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **RGB Color**\n- **Inputs**  \n\t- **V** *: ( vec3 | Color )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **RGBA Color**\n- **Inputs**  \n\t- **V** *: ( vec4 | Color )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Color Ramp**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n- **Outputs**  \n\t- **result** *: ( sampler1D )*  \n---\n### **Image**\n- **Inputs**  \n\t- **Image** *: ( sampler2D )*  \n- **Outputs**  \n\t- **result** *: ( sampler2D )*  \n---\n## Math\n---\n### **Hash**\n- **Inputs**  \n\t- **V** *: ( vec4 | Data )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Quaternion**\n---\n#### **From Axis Angle**\n- **Inputs**  \n\t- **Axis** *: ( vec3 | Normal )*  \n\t- **Angle** *: ( float | Angle )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **From Vector Delta**\n- **Inputs**  \n\t- **From** *: ( vec3 | Normal )*  \n\t- **To** *: ( vec3 | Normal )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Inverted**\n- **Inputs**  \n\t- **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Transform**\n- **Inputs**  \n\t- **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **Vector** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **Factor** *: ( float | Slider ) - default = 0.5*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Matrix**\n---\n#### **From Translation**\n- **Inputs**  \n\t- **T** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **From Quaternion**\n- **Inputs**  \n\t- **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **From Euler**\n- **Inputs**  \n\t- **E** *: ( vec3 | Euler )*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **From Scale**\n- **Inputs**  \n\t- **S** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **Is Orthographic**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Inverse**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( mat4 ) - default = mat4(1)*  \n\t- **B** *: ( mat4 ) - default = mat4(1)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n### **Boolean Logic**\n---\n#### **And**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Or**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not**\n- **Inputs**  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( bool )*  \n\t- **If False** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n### **Float**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **Value** *: ( float ) - default = 0.5*  \n\t- **From Min** *: ( float )*  \n\t- **From Max** *: ( float ) - default = 1.0*  \n\t- **To Min** *: ( float )*  \n\t- **To Max** *: ( float ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Fractional Part**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **Min** *: ( float )*  \n\t- **Max** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Minimum**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Maximum**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Arcsine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Arcosine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Arctangent**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Radians to Degrees**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Degrees to Radians**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **E** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **E** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater or Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less or Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( float )*  \n\t- **If False** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Integer**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **Min** *: ( int )*  \n\t- **Max** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Minimum**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Maximum**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater or Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less or Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( int )*  \n\t- **If False** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n### **Vector 2D**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Scale**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **UV** *: ( vec2 ) - default = vec2(0.5)*  \n\t- **From Min** *: ( vec2 ) - default = (0.0, 0.0)*  \n\t- **From Max** *: ( vec2 ) - default = (1.0, 1.0)*  \n\t- **To Min** *: ( vec2 ) - default = (0.0, 0.0)*  \n\t- **To Max** *: ( vec2 ) - default = (1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Distort**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Fraction**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Snap**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **Min** *: ( vec2 )*  \n\t- **Max** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Min**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Max**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Mix 2D**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n\t- **Factor** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Normalize**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Length**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Distance**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Dot Product**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Rotate**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **Angle** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Angle**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( vec2 )*  \n\t- **If False** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Combine**\n- **Inputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Separate**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n---\n### **Vector 3D**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Scale**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **Vector** *: ( vec3 ) - default = vec3(0.5)*  \n\t- **From Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n\t- **From Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n\t- **To Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n\t- **To Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Distort**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Fraction**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Snap**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Min** *: ( vec3 | Vector )*  \n\t- **Max** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Min**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Max**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Mix 3D**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Factor** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Normalize**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Length**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Distance**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Dot Product**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Cross Product**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Reflect**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Refract**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Ior** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Faceforward**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **C** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Rotate Euler**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Euler** *: ( vec3 | Euler )*  \n\t- **Invert** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Rotate Axis Angle**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Axis** *: ( vec3 | Vector ) - default = (0.0, 0.0, 1.0)*  \n\t- **Angle** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Angle**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( vec3 | Vector )*  \n\t- **If False** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Combine**\n- **Inputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n\t- **Z** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Separate**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n\t- **Z** *: ( float )*  \n---\n### **Vector 4D**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Scale**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **Vector** *: ( vec4 ) - default = vec4(0.5)*  \n\t- **From Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **From Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)*  \n\t- **To Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **To Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Distort**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Fraction**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **Min** *: ( vec4 | Vector )*  \n\t- **Max** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Min**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Max**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Mix 4D**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n\t- **Factor** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Normalize**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Length**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Distance**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Dot Product**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Angle**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Vec4 If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( vec4 | Vector )*  \n\t- **If False** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Combine**\n- **Inputs**  \n\t- **R** *: ( float )*  \n\t- **G** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Combine Color**\n- **Inputs**  \n\t- **C** *: ( vec3 | Color )*  \n\t- **A** *: ( float | Slider ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Separate**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **R** *: ( float )*  \n\t- **G** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **A** *: ( float )*  \n---\n#### **Separate Color**\n- **Inputs**  \n\t- **A** *: ( vec4 | Color )*  \n- **Outputs**  \n\t- **C** *: ( vec3 )*  \n\t- **A** *: ( float )*  \n---\n## Vector\n---\n### **Surface Gradient From Normal**\n- **Inputs**  \n\t- **Base Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Custom Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Normal From Surface Gradient**\n- **Inputs**  \n\t- **Base Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Surface Gradient** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Matrix**\n---\n#### **Transform Point**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Point** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Project Point**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Point** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Project Point To Screen Coordinates**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Point** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Transform Direction**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Direction** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Transform Normal**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Normal** *: ( vec3 | Normal )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Pixel Size in World Space**\n- **Inputs**  \n\t- **Depth** *: ( float ) - default = pixel_depth()*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Transform**\n- **Inputs**  \n\t- **Type** *: ( int | ENUM(Point,Vector,Normal) )*  \n\t- **From** *: ( int | ENUM(Object,World,Camera) )*  \n\t- **To** *: ( int | ENUM(Object,World,Camera,Screen) )*  \n\t- **Vector** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **Vector** *: ( vec3 | Vector )*  \n---\n### **Mapping**\n---\n#### **Point**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Location** *: ( vec3 | Vector )*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Texture**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Location** *: ( vec3 | Vector )*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Vector**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Normal**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n## Color\n---\n### **Alpha Blend**\nBlends the blend color as a layer over the base color.\n\n- **Inputs**  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t>The blend color.\n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Grayscale**\n- **Inputs**  \n\t- **Color** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Linear To sRGB**\n- **Inputs**  \n\t- **Linear** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **sRGB To Linear**\n- **Inputs**  \n\t- **Srgb** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **RGB To HSV**\n- **Inputs**  \n\t- **Rgb** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **HSV To RGB**\n- **Inputs**  \n\t- **Hsv** *: ( vec3 | HSV )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **HSV Edit**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Hue** *: ( float )*  \n\t- **Saturation** *: ( float )*  \n\t- **Value** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Bright/Contrast**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Brightness** *: ( float )*  \n\t- **Contrast** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Gamma**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Gamma** *: ( float ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Invert**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Fac** *: ( float | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Color Gradient**\n---\n#### **Gradient**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n\t- **U** *: ( float | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **RGB Gradient**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n\t- **UVW** *: ( vec3 | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **RGBA Gradient**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n\t- **UVWX** *: ( vec4 | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Layer Blend**\n---\n#### **Normal**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Add**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Overlay**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Screen**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Darken**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Lighten**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Soft Light**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Hard Light**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Linear Light**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Dodge**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Burn**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Difference**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Hue**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Saturation**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Value**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Color**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n## Texturing\n---\n### **Image**\n- **Inputs**  \n\t- **Image** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Smooth Interpolation** *: ( bool ) - default = True*  \n- **Outputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Resolution** *: ( vec2 )*  \n---\n### **Normal Map**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **UV Index** *: ( int )*  \n- **Outputs**  \n\t- **Normal** *: ( vec3 )*  \n---\n### **Flipbook**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Dimensions** *: ( ivec2 )*  \n\t- **Page** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Flowmap**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Flow** *: ( vec2 ) - default = vec2(0.0)*  \n\t- **Progression** *: ( float )*  \n\t- **Samples** *: ( int ) - default = 2*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Matcap**\n- **Inputs**  \n\t- **Matcap** *: ( sampler2D )*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Uv** *: ( vec2 )*  \n---\n### **HDRI**\n- **Inputs**  \n\t- **Hdri** *: ( sampler2D )*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Uv** *: ( vec2 )*  \n---\n### **Noise**\n---\n#### **Infinite**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Detail** *: ( float ) - default = 3.0*  \n\t- **Balance** *: ( float | Slider ) - default = 0.5*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Tiled**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Detail** *: ( float ) - default = 3.0*  \n\t- **Balance** *: ( float | Slider ) - default = 0.5*  \n\t- **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Voronoi**\n---\n#### **Infinite**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n- **Outputs**  \n\t- **Cell Color** *: ( vec4 )*  \n\t- **Cell Position** *: ( vec4 )*  \n\t- **Cell Distance** *: ( float )*  \n---\n#### **Tiled**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)*  \n- **Outputs**  \n\t- **Cell Color** *: ( vec4 )*  \n\t- **Cell Position** *: ( vec4 )*  \n\t- **Cell Distance** *: ( float )*  \n---\n### **Bayer Pattern**\n- **Inputs**  \n\t- **Size** *: ( int | ENUM(2x2,3x3,4x4,8x8) ) - default = 2*  \n\t- **Texel** *: ( vec2 ) - default = vec2(screen_pixel())*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Gradient**\n---\n#### **Linear**\n- **Inputs**  \n\t- **Value** *: ( float ) - default = UV[0].x*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Quadratic**\n- **Inputs**  \n\t- **Value** *: ( float ) - default = UV[0].x*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Easing**\n- **Inputs**  \n\t- **Value** *: ( float ) - default = UV[0].x*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Diagonal**\n- **Inputs**  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Spherical**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = POSITION*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Quadratic Sphere**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = POSITION*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Radial**\n- **Inputs**  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Wave**\n- **Inputs**  \n\t- **Mode** *: ( int | ENUM(Sine,Saw,Triangle) )*  \n\t- **Coord** *: ( float ) - default = UV[0].x*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Phase** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n## Shading\n---\n### **Rim Light**\n- **Inputs**  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Angle** *: ( float )*  \n\t- **Rim Length** *: ( float ) - default = 2.0*  \n\t- **Length Falloff** *: ( float )*  \n\t- **Thickness** *: ( float ) - default = 0.1*  \n\t- **Thickness Falloff** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n## Filter\n---\n### **Blur**\n---\n#### **Box Blur**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5.0*  \n\t- **Circular** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Gaussian Blur**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5.0*  \n\t- **Sigma** *: ( float ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Jitter Blur**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5.0*  \n\t- **Distribution Exponent** *: ( float ) - default = 5.0*  \n\t- **Samples** *: ( int ) - default = 8*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Bilateral**\n- **Inputs**  \n\t- **Input Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5*  \n\t- **Sigma** *: ( float ) - default = 10.0*  \n\t- **BSigma** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Orientation-Aligned Bilateral**\n- **Inputs**  \n\t- **Input Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Flow** *: ( vec2 ) - default = vec2(0)*  \n\t- **Radius** *: ( float ) - default = 6.0*  \n\t- **Smoothness** *: ( float ) - default = 0.55*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Sharpen**\n---\n#### **Box**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Circular** *: ( bool )*  \n\t- **Sharpness** *: ( float ) - default = 0.3*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Gaussian**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Sigma** *: ( float ) - default = 1.0*  \n\t- **Sharpness** *: ( float ) - default = 0.3*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Jitter**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Distribution Exponent** *: ( float ) - default = 5.0*  \n\t- **Samples** *: ( int ) - default = 8*  \n\t- **Sharpness** *: ( float ) - default = 0.3*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Curvature**\n- **Inputs**  \n\t- **Normal Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Width** *: ( float ) - default = 1.0*  \n\t- **X** *: ( vec3 | Normal ) - default = (1.0, 0.0, 0.0)*  \n\t- **Y** *: ( vec3 | Normal ) - default = (0.0, 1.0, 0.0)*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Kuwahara**\n---\n#### **Isotropic**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Size** *: ( int ) - default = 5*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Anisotropic**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Direction** *: ( vec2 ) - default = vec2(0.0, 0.0)*  \n\t- **Size** *: ( float ) - default = 2.0*  \n\t- **Samples** *: ( int ) - default = 50*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n"
  },
  {
    "path": "docs/reference/Mesh-graph.md",
    "content": "# Mesh Graph Reference\n---\n## Input\n---\n### **Pass Info**\n- **Outputs**  \n\t- **Is Main Pass** *: ( bool )*  \n\t- **Is Pre Pass** *: ( bool )*  \n\t- **Is Shadow Pass** *: ( bool )*  \n---\n### **Geometry**\n- **Inputs**  \n\t- **Space** *: ( int | ENUM(Object,World,Camera,Screen) ) - default = 1*  \n\t- **IOR** *: ( float ) - default = 1.45*  \n- **Outputs**  \n\t- **Position** *: ( vec3 )*  \n\t- **Incoming** *: ( vec3 )*  \n\t- **Normal** *: ( vec3 ) - default = NORMAL*  \n\t- **True Normal** *: ( vec3 )*  \n\t- **Is Backfacing** *: ( bool )*  \n\t- **Facing** *: ( float )*  \n\t- **Fresnel** *: ( float )*  \n\t- **Reflection** *: ( vec3 )*  \n\t- **Refraction** *: ( vec3 )*  \n---\n### **UV Map**\n- **Inputs**  \n\t- **Index** *: ( int )*  \n- **Outputs**  \n\t- **UV** *: ( vec2 )*  \n---\n### **Tangent**\n---\n#### **UV Map**\n- **Inputs**  \n\t- **Uv Index** *: ( int )*  \n- **Outputs**  \n\t- **Tangent** *: ( vec3 )*  \n\t- **Bitangent** *: ( vec3 )*  \n---\n#### **Radial**\n- **Inputs**  \n\t- **Axis** *: ( int | ENUM(X,Y,Z) ) - default = 2*  \n\t- **Object Space** *: ( bool ) - default = True*  \n- **Outputs**  \n\t- **Tangent** *: ( vec3 )*  \n\t- **Bitangent** *: ( vec3 )*  \n---\n#### **Procedural UV**\n- **Inputs**  \n\t- **Uv** *: ( vec2 )*  \n- **Outputs**  \n\t- **Tangent** *: ( vec3 )*  \n\t- **Bitangent** *: ( vec3 )*  \n---\n### **Vertex Color**\n- **Inputs**  \n\t- **Index** *: ( int )*  \n- **Outputs**  \n\t- **Vertex Color** *: ( vec4 )*  \n---\n### **Id**\n- **Outputs**  \n\t- **Object Id** *: ( vec4 )*  \n\t- **Custom Id A** *: ( vec4 )*  \n\t- **Custom Id B** *: ( vec4 )*  \n\t- **Custom Id C** *: ( vec4 )*  \n---\n### **Object Info**\n- **Outputs**  \n\t- **Position** *: ( vec3 )*  \n\t- **Rotation** *: ( mat4 )*  \n\t- **Scale** *: ( vec3 )*  \n\t- **Id** *: ( vec4 )*  \n\t- **Matrix** *: ( mat4 )*  \n---\n### **Camera Data**\n- **Outputs**  \n\t- **View Direction** *: ( vec3 )*  \n\t- **Screen UV** *: ( vec2 )*  \n\t- **Z Depth** *: ( float )*  \n\t- **View Distance** *: ( float )*  \n\t- **Camera Position** *: ( vec3 )*  \n\t- **Camera Matrix** *: ( mat4 )*  \n\t- **Projection Matrix** *: ( mat4 )*  \n\t- **Is Orthographic** *: ( bool )*  \n---\n### **Render Info**\n- **Outputs**  \n\t- **Resolution** *: ( vec2 )*  \n\t- **Current Sample** *: ( int )*  \n\t- **Sample Offset** *: ( vec2 )*  \n---\n### **Time Info**\n- **Outputs**  \n\t- **Time** *: ( float )*  \n\t- **Frame** *: ( int )*  \n---\n### **Random**\n- **Inputs**  \n\t- **Seed** *: ( float )*  \n- **Outputs**  \n\t- **Per Object** *: ( vec4 )*  \n\t- **Per Sample** *: ( vec4 )*  \n\t- **Per Pixel** *: ( vec4 )*  \n---\n### **Curve View Mapping**\n- **Inputs**  \n\t- **Scale** *: ( vec2 ) - default = (1.0, 1.0)*  \n- **Outputs**  \n\t- **UV** *: ( vec2 )*  \n\t- **Facing** *: ( float )*  \n---\n## Parameters\n---\n### **Boolean**\n- **Inputs**  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n### **Float**\n- **Inputs**  \n\t- **F** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Integer**\n- **Inputs**  \n\t- **I** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n### **Vector 2D**\n- **Inputs**  \n\t- **V** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n### **Vector 3D**\n- **Inputs**  \n\t- **V** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Vector 4D**\n- **Inputs**  \n\t- **V** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **RGB Color**\n- **Inputs**  \n\t- **V** *: ( vec3 | Color )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **RGBA Color**\n- **Inputs**  \n\t- **V** *: ( vec4 | Color )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Color Ramp**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n- **Outputs**  \n\t- **result** *: ( sampler1D )*  \n---\n### **Image**\n- **Inputs**  \n\t- **Image** *: ( sampler2D )*  \n- **Outputs**  \n\t- **result** *: ( sampler2D )*  \n---\n## Math\n---\n### **Hash**\n- **Inputs**  \n\t- **V** *: ( vec4 | Data )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Quaternion**\n---\n#### **From Axis Angle**\n- **Inputs**  \n\t- **Axis** *: ( vec3 | Normal )*  \n\t- **Angle** *: ( float | Angle )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **From Vector Delta**\n- **Inputs**  \n\t- **From** *: ( vec3 | Normal )*  \n\t- **To** *: ( vec3 | Normal )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Inverted**\n- **Inputs**  \n\t- **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Transform**\n- **Inputs**  \n\t- **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **Vector** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **Factor** *: ( float | Slider ) - default = 0.5*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Matrix**\n---\n#### **From Translation**\n- **Inputs**  \n\t- **T** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **From Quaternion**\n- **Inputs**  \n\t- **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **From Euler**\n- **Inputs**  \n\t- **E** *: ( vec3 | Euler )*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **From Scale**\n- **Inputs**  \n\t- **S** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **Is Orthographic**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Inverse**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( mat4 ) - default = mat4(1)*  \n\t- **B** *: ( mat4 ) - default = mat4(1)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n### **Boolean Logic**\n---\n#### **And**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Or**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not**\n- **Inputs**  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( bool )*  \n\t- **If False** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n### **Float**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **Value** *: ( float ) - default = 0.5*  \n\t- **From Min** *: ( float )*  \n\t- **From Max** *: ( float ) - default = 1.0*  \n\t- **To Min** *: ( float )*  \n\t- **To Max** *: ( float ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Fractional Part**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **Min** *: ( float )*  \n\t- **Max** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Minimum**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Maximum**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Arcsine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Arcosine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Arctangent**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Radians to Degrees**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Degrees to Radians**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **E** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **E** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater or Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less or Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( float )*  \n\t- **If False** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Integer**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **Min** *: ( int )*  \n\t- **Max** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Minimum**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Maximum**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater or Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less or Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( int )*  \n\t- **If False** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n### **Vector 2D**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Scale**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **UV** *: ( vec2 ) - default = vec2(0.5)*  \n\t- **From Min** *: ( vec2 ) - default = (0.0, 0.0)*  \n\t- **From Max** *: ( vec2 ) - default = (1.0, 1.0)*  \n\t- **To Min** *: ( vec2 ) - default = (0.0, 0.0)*  \n\t- **To Max** *: ( vec2 ) - default = (1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Distort**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Fraction**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Snap**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **Min** *: ( vec2 )*  \n\t- **Max** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Min**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Max**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Mix 2D**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n\t- **Factor** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Normalize**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Length**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Distance**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Dot Product**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Rotate**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **Angle** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Angle**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( vec2 )*  \n\t- **If False** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Combine**\n- **Inputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Separate**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n---\n### **Vector 3D**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Scale**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **Vector** *: ( vec3 ) - default = vec3(0.5)*  \n\t- **From Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n\t- **From Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n\t- **To Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n\t- **To Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Distort**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Fraction**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Snap**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Min** *: ( vec3 | Vector )*  \n\t- **Max** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Min**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Max**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Mix 3D**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Factor** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Normalize**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Length**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Distance**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Dot Product**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Cross Product**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Reflect**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Refract**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Ior** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Faceforward**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **C** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Rotate Euler**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Euler** *: ( vec3 | Euler )*  \n\t- **Invert** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Rotate Axis Angle**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Axis** *: ( vec3 | Vector ) - default = (0.0, 0.0, 1.0)*  \n\t- **Angle** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Angle**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( vec3 | Vector )*  \n\t- **If False** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Combine**\n- **Inputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n\t- **Z** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Separate**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n\t- **Z** *: ( float )*  \n---\n### **Vector 4D**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Scale**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **Vector** *: ( vec4 ) - default = vec4(0.5)*  \n\t- **From Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **From Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)*  \n\t- **To Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **To Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Distort**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Fraction**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **Min** *: ( vec4 | Vector )*  \n\t- **Max** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Min**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Max**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Mix 4D**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n\t- **Factor** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Normalize**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Length**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Distance**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Dot Product**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Angle**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Vec4 If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( vec4 | Vector )*  \n\t- **If False** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Combine**\n- **Inputs**  \n\t- **R** *: ( float )*  \n\t- **G** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Combine Color**\n- **Inputs**  \n\t- **C** *: ( vec3 | Color )*  \n\t- **A** *: ( float | Slider ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Separate**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **R** *: ( float )*  \n\t- **G** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **A** *: ( float )*  \n---\n#### **Separate Color**\n- **Inputs**  \n\t- **A** *: ( vec4 | Color )*  \n- **Outputs**  \n\t- **C** *: ( vec3 )*  \n\t- **A** *: ( float )*  \n---\n## Vector\n---\n### **Surface Gradient From Normal**\n- **Inputs**  \n\t- **Base Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Custom Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Normal From Surface Gradient**\n- **Inputs**  \n\t- **Base Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Surface Gradient** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Matrix**\n---\n#### **Transform Point**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Point** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Project Point**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Point** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Project Point To Screen Coordinates**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Point** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Transform Direction**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Direction** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Transform Normal**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Normal** *: ( vec3 | Normal )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Pixel Size in World Space**\n- **Inputs**  \n\t- **Depth** *: ( float ) - default = pixel_depth()*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Bevel**\n---\n#### **Soft Bevel**\n- **Inputs**  \n\t- **Samples** *: ( int ) - default = 32*  \n\t- **Radius** *: ( float ) - default = 0.02*  \n\t- **Distribution Exponent** *: ( float ) - default = 2.0*  \n\t- **Only Self** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Hard Bevel**\n- **Inputs**  \n\t- **Samples** *: ( int ) - default = 32*  \n\t- **Radius** *: ( float ) - default = 0.01*  \n\t- **Max Dot** *: ( float ) - default = 0.75*  \n\t- **Only Self** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Transform**\n- **Inputs**  \n\t- **Type** *: ( int | ENUM(Point,Vector,Normal) )*  \n\t- **From** *: ( int | ENUM(Object,World,Camera) )*  \n\t- **To** *: ( int | ENUM(Object,World,Camera,Screen) )*  \n\t- **Vector** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **Vector** *: ( vec3 | Vector )*  \n---\n### **Mapping**\n---\n#### **Point**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Location** *: ( vec3 | Vector )*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Texture**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Location** *: ( vec3 | Vector )*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Vector**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Normal**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n## Color\n---\n### **Alpha Blend**\nBlends the blend color as a layer over the base color.\n\n- **Inputs**  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t>The blend color.\n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Grayscale**\n- **Inputs**  \n\t- **Color** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Linear To sRGB**\n- **Inputs**  \n\t- **Linear** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **sRGB To Linear**\n- **Inputs**  \n\t- **Srgb** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **RGB To HSV**\n- **Inputs**  \n\t- **Rgb** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **HSV To RGB**\n- **Inputs**  \n\t- **Hsv** *: ( vec3 | HSV )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **HSV Edit**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Hue** *: ( float )*  \n\t- **Saturation** *: ( float )*  \n\t- **Value** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Bright/Contrast**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Brightness** *: ( float )*  \n\t- **Contrast** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Gamma**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Gamma** *: ( float ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Invert**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Fac** *: ( float | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Color Gradient**\n---\n#### **Gradient**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n\t- **U** *: ( float | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **RGB Gradient**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n\t- **UVW** *: ( vec3 | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **RGBA Gradient**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n\t- **UVWX** *: ( vec4 | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Layer Blend**\n---\n#### **Normal**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Add**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Overlay**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Screen**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Darken**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Lighten**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Soft Light**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Hard Light**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Linear Light**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Dodge**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Burn**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Difference**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Hue**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Saturation**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Value**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Color**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n## Texturing\n---\n### **Image**\n- **Inputs**  \n\t- **Image** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Smooth Interpolation** *: ( bool ) - default = True*  \n- **Outputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Resolution** *: ( vec2 )*  \n---\n### **Normal Map**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **UV Index** *: ( int )*  \n- **Outputs**  \n\t- **Normal** *: ( vec3 )*  \n---\n### **Flipbook**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Dimensions** *: ( ivec2 )*  \n\t- **Page** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Flowmap**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Flow** *: ( vec2 ) - default = vec2(0.0)*  \n\t- **Progression** *: ( float )*  \n\t- **Samples** *: ( int ) - default = 2*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Matcap**\n- **Inputs**  \n\t- **Matcap** *: ( sampler2D )*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Uv** *: ( vec2 )*  \n---\n### **HDRI**\n- **Inputs**  \n\t- **Hdri** *: ( sampler2D )*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Uv** *: ( vec2 )*  \n---\n### **Noise**\n---\n#### **Infinite**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Detail** *: ( float ) - default = 3.0*  \n\t- **Balance** *: ( float | Slider ) - default = 0.5*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Tiled**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Detail** *: ( float ) - default = 3.0*  \n\t- **Balance** *: ( float | Slider ) - default = 0.5*  \n\t- **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Voronoi**\n---\n#### **Infinite**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n- **Outputs**  \n\t- **Cell Color** *: ( vec4 )*  \n\t- **Cell Position** *: ( vec4 )*  \n\t- **Cell Distance** *: ( float )*  \n---\n#### **Tiled**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)*  \n- **Outputs**  \n\t- **Cell Color** *: ( vec4 )*  \n\t- **Cell Position** *: ( vec4 )*  \n\t- **Cell Distance** *: ( float )*  \n---\n### **Bayer Pattern**\n- **Inputs**  \n\t- **Size** *: ( int | ENUM(2x2,3x3,4x4,8x8) ) - default = 2*  \n\t- **Texel** *: ( vec2 ) - default = vec2(screen_pixel())*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Gradient**\n---\n#### **Linear**\n- **Inputs**  \n\t- **Value** *: ( float ) - default = UV[0].x*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Quadratic**\n- **Inputs**  \n\t- **Value** *: ( float ) - default = UV[0].x*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Easing**\n- **Inputs**  \n\t- **Value** *: ( float ) - default = UV[0].x*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Diagonal**\n- **Inputs**  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Spherical**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = POSITION*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Quadratic Sphere**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = POSITION*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Radial**\n- **Inputs**  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Wave**\n- **Inputs**  \n\t- **Mode** *: ( int | ENUM(Sine,Saw,Triangle) )*  \n\t- **Coord** *: ( float ) - default = UV[0].x*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Phase** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n## Shading\n---\n### **Ambient Occlusion**\n- **Inputs**  \n\t- **Samples** *: ( int ) - default = 32*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Distribution Exponent** *: ( float ) - default = 5.0*  \n\t- **Contrast** *: ( float | Slider ) - default = 0.1*  \n\t- **Bias** *: ( float | Slider ) - default = 0.01*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Curvature**\n---\n#### **Curvature**\n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Surface Curvature**\n- **Inputs**  \n\t- **Depth Range** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Line Detection**\n- **Outputs**  \n\t- **Delta Distance** *: ( float )*  \n\t- **Delta Angle** *: ( float )*  \n\t- **Is ID Boundary** *: ( vec4 )*  \n---\n### **Line Width**\n- **Inputs**  \n\t- **Width Scale** *: ( float ) - default = 4.0*  \n\t- **Width Units** *: ( int | ENUM(Pixel,Screen,World) )*  \n\t- **Depth Width** *: ( float | Slider ) - default = 1.0*  \n\t- **Depth Threshold** *: ( float | Slider ) - default = 0.1*  \n\t- **Depth Threshold Range** *: ( float | Slider )*  \n\t- **Normal Width** *: ( float | Slider ) - default = 1.0*  \n\t- **Normal Threshold** *: ( float | Slider ) - default = 0.5*  \n\t- **Normal Threshold Range** *: ( float | Slider )*  \n\t- **Id Boundary Width** *: ( vec4 | Slider ) - default = (1.0, 1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Rim Light**\n- **Inputs**  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Angle** *: ( float )*  \n\t- **Rim Length** *: ( float ) - default = 2.0*  \n\t- **Length Falloff** *: ( float )*  \n\t- **Thickness** *: ( float ) - default = 0.1*  \n\t- **Thickness Falloff** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **NPR Diffuse**\n---\n#### **Color Ramp**\n- **Inputs**  \n\t- **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)*  \n\t- **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)*  \n\t- **Gradient** *: ( sampler1D )*  \n\t- **Offset** *: ( float | Slider )*  \n\t- **Full Range** *: ( bool )*  \n\t- **Max Contribution** *: ( bool )*  \n\t- **Shadows** *: ( int | ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows) )*  \n\t- **Light Groups** *: ( ivec4 ) - default = MATERIAL_LIGHT_GROUPS*  \n\t- **Position** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Color Layer**\n- **Inputs**  \n\t- **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)*  \n\t- **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)*  \n\t- **Size** *: ( float | Slider ) - default = 1.0*  \n\t- **Gradient Size** *: ( float | Slider ) - default = 0.1*  \n\t- **Offset** *: ( float | Slider )*  \n\t- **Full Range** *: ( bool )*  \n\t- **Max Contribution** *: ( bool )*  \n\t- **Shadows** *: ( int | ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows) )*  \n\t- **Light Groups** *: ( ivec4 ) - default = MATERIAL_LIGHT_GROUPS*  \n\t- **Position** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **NPR Specular**\n---\n#### **Color Ramp**\n- **Inputs**  \n\t- **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)*  \n\t- **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)*  \n\t- **Gradient** *: ( sampler1D )*  \n\t- **Offset** *: ( float | Slider )*  \n\t- **Roughness** *: ( float | Slider ) - default = 0.5*  \n\t- **Anisotropy** *: ( float | Slider ) - default = 0.5*  \n\t- **Max Contribution** *: ( bool )*  \n\t- **Shadows** *: ( int | ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows) )*  \n\t- **Light Groups** *: ( ivec4 ) - default = MATERIAL_LIGHT_GROUPS*  \n\t- **Position** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Tangent** *: ( vec3 | Normal ) - default = radial_tangent(NORMAL, vec3(0,0,1))*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Color Layer**\n- **Inputs**  \n\t- **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)*  \n\t- **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)*  \n\t- **Size** *: ( float | Slider ) - default = 1.0*  \n\t- **Gradient Size** *: ( float | Slider ) - default = 0.1*  \n\t- **Offset** *: ( float | Slider )*  \n\t- **Roughness** *: ( float | Slider ) - default = 0.5*  \n\t- **Anisotropy** *: ( float | Slider ) - default = 0.5*  \n\t- **Max Contribution** *: ( bool )*  \n\t- **Shadows** *: ( int | ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows) )*  \n\t- **Light Groups** *: ( ivec4 ) - default = MATERIAL_LIGHT_GROUPS*  \n\t- **Position** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Tangent** *: ( vec3 | Normal ) - default = radial_tangent(NORMAL, vec3(0,0,1))*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n## Filter\n---\n### **Curvature**\n- **Inputs**  \n\t- **Normal Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Width** *: ( float ) - default = 1.0*  \n\t- **X** *: ( vec3 | Normal ) - default = (1.0, 0.0, 0.0)*  \n\t- **Y** *: ( vec3 | Normal ) - default = (0.0, 1.0, 0.0)*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Blur**\n---\n#### **Box Blur**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5.0*  \n\t- **Circular** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Gaussian Blur**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5.0*  \n\t- **Sigma** *: ( float ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Jitter Blur**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5.0*  \n\t- **Distribution Exponent** *: ( float ) - default = 5.0*  \n\t- **Samples** *: ( int ) - default = 8*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Bilateral**\n- **Inputs**  \n\t- **Input Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5*  \n\t- **Sigma** *: ( float ) - default = 10.0*  \n\t- **BSigma** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Orientation-Aligned Bilateral**\n- **Inputs**  \n\t- **Input Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Flow** *: ( vec2 ) - default = vec2(0)*  \n\t- **Radius** *: ( float ) - default = 6.0*  \n\t- **Smoothness** *: ( float ) - default = 0.55*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Sharpen**\n---\n#### **Box**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Circular** *: ( bool )*  \n\t- **Sharpness** *: ( float ) - default = 0.3*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Gaussian**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Sigma** *: ( float ) - default = 1.0*  \n\t- **Sharpness** *: ( float ) - default = 0.3*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Jitter**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Distribution Exponent** *: ( float ) - default = 5.0*  \n\t- **Samples** *: ( int ) - default = 8*  \n\t- **Sharpness** *: ( float ) - default = 0.3*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Kuwahara**\n---\n#### **Isotropic**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Size** *: ( int ) - default = 5*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Anisotropic**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Direction** *: ( vec2 ) - default = vec2(0.0, 0.0)*  \n\t- **Size** *: ( float ) - default = 2.0*  \n\t- **Samples** *: ( int ) - default = 50*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n## Node Tree\n---\n### **Pack ID**\n- **Inputs**  \n\t- **Object ID** *: ( vec4 ) - default = unpackUnorm4x8(ID.x)*  \n\t- **Custom ID A** *: ( vec4 ) - default = unpackUnorm4x8(ID.y)*  \n\t- **Custom ID B** *: ( vec4 ) - default = unpackUnorm4x8(ID.z)*  \n\t- **Custom ID C** *: ( vec4 ) - default = unpackUnorm4x8(ID.w)*  \n- **Outputs**  \n\t- **ID** *: ( uvec4 )*  \n"
  },
  {
    "path": "docs/reference/Render Layer-graph.md",
    "content": "# Render Layer Graph Reference\n---\n## Render\n---\n### **LineRender**\nExpands the line up to the width especified in the *Line Width* texture\nand composites it on top of the *Color* texture.\n\n- **Inputs**  \n\t- **Color** *: ( Texture )*  \n\t- **Line Color** *: ( Texture )*  \n\t- **Line Width** *: ( Texture )*  \n\t- **Max Width** *: ( Int ) - default = 10*  \n\t>The maximum width the shader can render.\n\tIncreasing the value lowers the render performance.\n\t- **Line Scale** *: ( Float ) - default = 1.0*  \n\t>Scale all Line Width values with this one.  \n\t*(Useful for rendering at different resolutions)*\n\t- **Normal Depth** *: ( Texture )*  \n\t- **ID** *: ( Texture )*  \n- **Outputs**  \n\t- **Color** *: ( Texture )*  \n---\n### **SuperSamplingAA**\nPerforms anti-aliasing by accumulating multiple render samples into a single texture.\n\n- **Inputs**  \n\t- **Color** *: ( Texture )*  \n- **Outputs**  \n\t- **Color** *: ( Texture )*  \n---\n### **MainPass**\n>Graph Type / Pass : *Mesh / MAIN_PASS_PIXEL_SHADER*\n\nRenders the scene geometry using the *Mesh Main Pass*.  \nThe node sockets are dynamic, based on the *Main Pass Custom IO*.  \nIf *Normal Depth/ID* is empty, the *Pre Pass* *Normal Depth/ID* will be used.\n\n- **Inputs**  \n\t- **Scene** *: ( Scene )*  \n\t- **Normal Depth** *: ( Texture )*  \n\t- **ID** *: ( Texture )*  \n---\n### **PrePass**\n>Graph Type / Pass : *Mesh / PRE_PASS_PIXEL_SHADER*\n\nRenders the scene geometry using the *Mesh Pre Pass*.  \nThe node sockets are dynamic, based on the *Pre Pass Custom IO*.  \nIf *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used.\n\n- **Inputs**  \n\t- **Scene** *: ( Scene )*  \n- **Outputs**  \n\t- **Scene** *: ( Scene )*  \n\t- **Normal Depth** *: ( Texture )*  \n\t- **ID** *: ( Texture )*  \n---\n### **ScreenPass**\n>Graph Type / Pass : *Screen / SCREEN_SHADER*\n\nRenders a full screen shader pass.  \nThe node sockets are dynamic, based on the shader selected.  \nIf *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used.\n\n- **Inputs**  \n\t- **Layer Only** *: ( Bool ) - default = True*  \n\t>Draw only on top of the current layer geometry,\n\tto avoid accidentally covering previous layers.\n\t- **Scene** *: ( Scene )*  \n\t- **Normal Depth** *: ( Texture )*  \n\t- **ID** *: ( Texture )*  \n"
  },
  {
    "path": "docs/reference/Render-graph.md",
    "content": "# Render Graph Reference\n---\n## Render\n---\n### **LineRender**\nExpands the line up to the width especified in the *Line Width* texture\nand composites it on top of the *Color* texture.\n\n- **Inputs**  \n\t- **Color** *: ( Texture )*  \n\t- **Line Color** *: ( Texture )*  \n\t- **Line Width** *: ( Texture )*  \n\t- **Max Width** *: ( Int ) - default = 10*  \n\t>The maximum width the shader can render.\n\tIncreasing the value lowers the render performance.\n\t- **Line Scale** *: ( Float ) - default = 1.0*  \n\t>Scale all Line Width values with this one.  \n\t*(Useful for rendering at different resolutions)*\n\t- **Normal Depth** *: ( Texture )*  \n\t- **ID** *: ( Texture )*  \n- **Outputs**  \n\t- **Color** *: ( Texture )*  \n---\n### **SuperSamplingAA**\nPerforms anti-aliasing by accumulating multiple render samples into a single texture.\n\n- **Inputs**  \n\t- **Color** *: ( Texture )*  \n- **Outputs**  \n\t- **Color** *: ( Texture )*  \n---\n### **RenderLayers**\n>Graph Type / Pass : *Render Layer / Render Layer*\n\nRenders the scene geometry, using multiple *depth peeling* layers for transparent objects.  \nThe node sockets are dynamic, based on the graph selected.\n\n- **Inputs**  \n\t- **Scene** *: ( Scene )*  \n\t- **Transparent Layers** *: ( Int ) - default = 4*  \n\t>The maximum number of overlapping transparency layers.  \n\tIncresing this values lowers performance.\n---\n### **SceneLighting**\nRenders the shadow maps and attaches them along the scene lights data to the *Scene* shader resources.\n\n- **Inputs**  \n\t- **Scene** *: ( Scene )*  \n\t- **Point Resolution** *: ( Int ) - default = 2048*  \n\t>Shadowmap resolution for point lights *(for each cubemap side)*.\n\t- **Spot Resolution** *: ( Int ) - default = 2048*  \n\t>Shadowmap resolution for spot lights.\n\t- **Sun Resolution** *: ( Int ) - default = 2048*  \n\t>Shadowmap resolution for sun light lights *(for each cascade side)*.\n\t- **Sun Max Distance** *: ( Float ) - default = 100*  \n\t>The maximum distance from the view origin at which objects will still cast shadows.\n\tThe lower the value, the higher the perceived resolution.\n\t- **Sun CSM Count** *: ( Int ) - default = 4*  \n\t>The number of [Shadow Cascades](https://docs.microsoft.com/en-us/windows/win32/dxtecharts/cascaded-shadow-maps#cascaded-shadow-maps-and-perspective-aliasing) for sun lights.\n\t- **Sun CSM Distribution** *: ( Float ) - default = 0.9*  \n\t>Interpolates the cascades distribution along the view distance between linear distribution *(at 0)* and logarithmic distribution *(at 1)*.  \n\tThe appropriate value depends on camera FOV and scene characteristics.\n- **Outputs**  \n\t- **Scene** *: ( Scene )*  \n---\n### **ScreenPass**\n>Graph Type / Pass : *Screen / SCREEN_SHADER*\n\nRenders a full screen shader pass.  \nThe node sockets are dynamic, based on the shader selected.\n\n"
  },
  {
    "path": "docs/reference/Screen-graph.md",
    "content": "# Screen Graph Reference\n---\n## Input\n---\n### **Geometry**\n- **Inputs**  \n\t- **Space** *: ( int | ENUM(Object,World,Camera,Screen) ) - default = 1*  \n\t- **IOR** *: ( float ) - default = 1.45*  \n- **Outputs**  \n\t- **Position** *: ( vec3 )*  \n\t- **Incoming** *: ( vec3 )*  \n\t- **Normal** *: ( vec3 ) - default = NORMAL*  \n\t- **True Normal** *: ( vec3 )*  \n\t- **Is Backfacing** *: ( bool )*  \n\t- **Facing** *: ( float )*  \n\t- **Fresnel** *: ( float )*  \n\t- **Reflection** *: ( vec3 )*  \n\t- **Refraction** *: ( vec3 )*  \n---\n### **Tangent**\n---\n#### **Radial**\n- **Inputs**  \n\t- **Axis** *: ( int | ENUM(X,Y,Z) ) - default = 2*  \n\t- **Object Space** *: ( bool ) - default = True*  \n- **Outputs**  \n\t- **Tangent** *: ( vec3 )*  \n\t- **Bitangent** *: ( vec3 )*  \n---\n#### **Procedural UV**\n- **Inputs**  \n\t- **Uv** *: ( vec2 )*  \n- **Outputs**  \n\t- **Tangent** *: ( vec3 )*  \n\t- **Bitangent** *: ( vec3 )*  \n---\n### **Id**\n- **Outputs**  \n\t- **Object Id** *: ( vec4 )*  \n\t- **Custom Id A** *: ( vec4 )*  \n\t- **Custom Id B** *: ( vec4 )*  \n\t- **Custom Id C** *: ( vec4 )*  \n---\n### **Camera Data**\n- **Outputs**  \n\t- **View Direction** *: ( vec3 )*  \n\t- **Screen UV** *: ( vec2 )*  \n\t- **Z Depth** *: ( float )*  \n\t- **View Distance** *: ( float )*  \n\t- **Camera Position** *: ( vec3 )*  \n\t- **Camera Matrix** *: ( mat4 )*  \n\t- **Projection Matrix** *: ( mat4 )*  \n\t- **Is Orthographic** *: ( bool )*  \n---\n### **Render Info**\n- **Outputs**  \n\t- **Resolution** *: ( vec2 )*  \n\t- **Current Sample** *: ( int )*  \n\t- **Sample Offset** *: ( vec2 )*  \n---\n### **Time Info**\n- **Outputs**  \n\t- **Time** *: ( float )*  \n\t- **Frame** *: ( int )*  \n---\n### **Random**\n- **Inputs**  \n\t- **Seed** *: ( float )*  \n- **Outputs**  \n\t- **Per Object** *: ( vec4 )*  \n\t- **Per Sample** *: ( vec4 )*  \n\t- **Per Pixel** *: ( vec4 )*  \n---\n## Parameters\n---\n### **Boolean**\n- **Inputs**  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n### **Float**\n- **Inputs**  \n\t- **F** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Integer**\n- **Inputs**  \n\t- **I** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n### **Vector 2D**\n- **Inputs**  \n\t- **V** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n### **Vector 3D**\n- **Inputs**  \n\t- **V** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Vector 4D**\n- **Inputs**  \n\t- **V** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **RGB Color**\n- **Inputs**  \n\t- **V** *: ( vec3 | Color )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **RGBA Color**\n- **Inputs**  \n\t- **V** *: ( vec4 | Color )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Color Ramp**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n- **Outputs**  \n\t- **result** *: ( sampler1D )*  \n---\n### **Image**\n- **Inputs**  \n\t- **Image** *: ( sampler2D )*  \n- **Outputs**  \n\t- **result** *: ( sampler2D )*  \n---\n## Math\n---\n### **Hash**\n- **Inputs**  \n\t- **V** *: ( vec4 | Data )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Quaternion**\n---\n#### **From Axis Angle**\n- **Inputs**  \n\t- **Axis** *: ( vec3 | Normal )*  \n\t- **Angle** *: ( float | Angle )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **From Vector Delta**\n- **Inputs**  \n\t- **From** *: ( vec3 | Normal )*  \n\t- **To** *: ( vec3 | Normal )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Inverted**\n- **Inputs**  \n\t- **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Transform**\n- **Inputs**  \n\t- **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **Vector** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n\t- **Factor** *: ( float | Slider ) - default = 0.5*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Matrix**\n---\n#### **From Translation**\n- **Inputs**  \n\t- **T** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **From Quaternion**\n- **Inputs**  \n\t- **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **From Euler**\n- **Inputs**  \n\t- **E** *: ( vec3 | Euler )*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **From Scale**\n- **Inputs**  \n\t- **S** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **Is Orthographic**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Inverse**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( mat4 ) - default = mat4(1)*  \n\t- **B** *: ( mat4 ) - default = mat4(1)*  \n- **Outputs**  \n\t- **result** *: ( mat4 )*  \n---\n### **Boolean Logic**\n---\n#### **And**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Or**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not**\n- **Inputs**  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( bool )*  \n\t- **B** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( bool )*  \n\t- **If False** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n### **Float**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **Value** *: ( float ) - default = 0.5*  \n\t- **From Min** *: ( float )*  \n\t- **From Max** *: ( float ) - default = 1.0*  \n\t- **To Min** *: ( float )*  \n\t- **To Max** *: ( float ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Fractional Part**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **Min** *: ( float )*  \n\t- **Max** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Minimum**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Maximum**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Arcsine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Arcosine**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Arctangent**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Radians to Degrees**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Degrees to Radians**\n- **Inputs**  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **E** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **E** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater or Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less or Equal**\n- **Inputs**  \n\t- **A** *: ( float )*  \n\t- **B** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( float )*  \n\t- **If False** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Integer**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **Min** *: ( int )*  \n\t- **Max** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Minimum**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Maximum**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Greater or Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Less or Equal**\n- **Inputs**  \n\t- **A** *: ( int )*  \n\t- **B** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( int )*  \n\t- **If False** *: ( int )*  \n- **Outputs**  \n\t- **result** *: ( int )*  \n---\n### **Vector 2D**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Scale**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **UV** *: ( vec2 ) - default = vec2(0.5)*  \n\t- **From Min** *: ( vec2 ) - default = (0.0, 0.0)*  \n\t- **From Max** *: ( vec2 ) - default = (1.0, 1.0)*  \n\t- **To Min** *: ( vec2 ) - default = (0.0, 0.0)*  \n\t- **To Max** *: ( vec2 ) - default = (1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Distort**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Fraction**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Snap**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **Min** *: ( vec2 )*  \n\t- **Max** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Min**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Max**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Mix 2D**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n\t- **Factor** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Normalize**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Length**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Distance**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Dot Product**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Rotate**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **Angle** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Angle**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n\t- **B** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( vec2 )*  \n\t- **If False** *: ( vec2 )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Combine**\n- **Inputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec2 )*  \n---\n#### **Separate**\n- **Inputs**  \n\t- **A** *: ( vec2 )*  \n- **Outputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n---\n### **Vector 3D**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Scale**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **Vector** *: ( vec3 ) - default = vec3(0.5)*  \n\t- **From Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n\t- **From Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n\t- **To Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n\t- **To Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Distort**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Fraction**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Snap**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Min** *: ( vec3 | Vector )*  \n\t- **Max** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Min**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Max**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Mix 3D**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Factor** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Normalize**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Length**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Distance**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Dot Product**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Cross Product**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Reflect**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Refract**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **Ior** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Faceforward**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n\t- **C** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Rotate Euler**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Euler** *: ( vec3 | Euler )*  \n\t- **Invert** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Rotate Axis Angle**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **Axis** *: ( vec3 | Vector ) - default = (0.0, 0.0, 1.0)*  \n\t- **Angle** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Angle**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n\t- **B** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( vec3 | Vector )*  \n\t- **If False** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Combine**\n- **Inputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n\t- **Z** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Separate**\n- **Inputs**  \n\t- **A** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **X** *: ( float )*  \n\t- **Y** *: ( float )*  \n\t- **Z** *: ( float )*  \n---\n### **Vector 4D**\n---\n#### **Add**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Scale**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Map Range**\n- **Inputs**  \n\t- **Clamped** *: ( bool ) - default = True*  \n\t- **Vector** *: ( vec4 ) - default = vec4(0.5)*  \n\t- **From Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **From Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)*  \n\t- **To Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **To Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Modulo**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Power**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Square Root**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Distort**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Round**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Fraction**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Floor**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Ceil**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Clamp**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **Min** *: ( vec4 | Vector )*  \n\t- **Max** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Sign**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Absolute**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Min**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Max**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Mix 4D**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n\t- **Factor** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Mix**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n\t- **Fac** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Normalize**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Length**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Distance**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Dot Product**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Sine**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Cosine**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Tangent**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Angle**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Equal**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Not Equal**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n\t- **B** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( bool )*  \n---\n#### **Vec4 If Else**\n- **Inputs**  \n\t- **Condition** *: ( bool )*  \n\t- **If True** *: ( vec4 | Vector )*  \n\t- **If False** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Combine**\n- **Inputs**  \n\t- **R** *: ( float )*  \n\t- **G** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **A** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Combine Color**\n- **Inputs**  \n\t- **C** *: ( vec3 | Color )*  \n\t- **A** *: ( float | Slider ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Separate**\n- **Inputs**  \n\t- **A** *: ( vec4 | Vector )*  \n- **Outputs**  \n\t- **R** *: ( float )*  \n\t- **G** *: ( float )*  \n\t- **B** *: ( float )*  \n\t- **A** *: ( float )*  \n---\n#### **Separate Color**\n- **Inputs**  \n\t- **A** *: ( vec4 | Color )*  \n- **Outputs**  \n\t- **C** *: ( vec3 )*  \n\t- **A** *: ( float )*  \n---\n## Vector\n---\n### **Surface Gradient From Normal**\n- **Inputs**  \n\t- **Base Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Custom Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Normal From Surface Gradient**\n- **Inputs**  \n\t- **Base Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Surface Gradient** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Matrix**\n---\n#### **Transform Point**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Point** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Project Point**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Point** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Project Point To Screen Coordinates**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Point** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Transform Direction**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Direction** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Transform Normal**\n- **Inputs**  \n\t- **Matrix** *: ( mat4 ) - default = mat4(1)*  \n\t- **Normal** *: ( vec3 | Normal )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Pixel Size in World Space**\n- **Inputs**  \n\t- **Depth** *: ( float ) - default = pixel_depth()*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Bevel**\n---\n#### **Soft Bevel**\n- **Inputs**  \n\t- **Samples** *: ( int ) - default = 32*  \n\t- **Radius** *: ( float ) - default = 0.02*  \n\t- **Distribution Exponent** *: ( float ) - default = 2.0*  \n\t- **Only Self** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Hard Bevel**\n- **Inputs**  \n\t- **Samples** *: ( int ) - default = 32*  \n\t- **Radius** *: ( float ) - default = 0.01*  \n\t- **Max Dot** *: ( float ) - default = 0.75*  \n\t- **Only Self** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **Transform**\n- **Inputs**  \n\t- **Type** *: ( int | ENUM(Point,Vector,Normal) )*  \n\t- **From** *: ( int | ENUM(Object,World,Camera) )*  \n\t- **To** *: ( int | ENUM(Object,World,Camera,Screen) )*  \n\t- **Vector** *: ( vec3 | Vector )*  \n- **Outputs**  \n\t- **Vector** *: ( vec3 | Vector )*  \n---\n### **Mapping**\n---\n#### **Point**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Location** *: ( vec3 | Vector )*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Texture**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Location** *: ( vec3 | Vector )*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Vector**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Normal**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)*  \n\t- **Rotation** *: ( vec3 | Euler )*  \n\t- **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n## Color\n---\n### **Alpha Blend**\nBlends the blend color as a layer over the base color.\n\n- **Inputs**  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t>The blend color.\n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Grayscale**\n- **Inputs**  \n\t- **Color** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Linear To sRGB**\n- **Inputs**  \n\t- **Linear** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **sRGB To Linear**\n- **Inputs**  \n\t- **Srgb** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **RGB To HSV**\n- **Inputs**  \n\t- **Rgb** *: ( vec3 )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **HSV To RGB**\n- **Inputs**  \n\t- **Hsv** *: ( vec3 | HSV )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **HSV Edit**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Hue** *: ( float )*  \n\t- **Saturation** *: ( float )*  \n\t- **Value** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Bright/Contrast**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Brightness** *: ( float )*  \n\t- **Contrast** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Gamma**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Gamma** *: ( float ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Invert**\n- **Inputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Fac** *: ( float | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Color Gradient**\n---\n#### **Gradient**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n\t- **U** *: ( float | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **RGB Gradient**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n\t- **UVW** *: ( vec3 | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **RGBA Gradient**\n- **Inputs**  \n\t- **Color Ramp** *: ( sampler1D )*  \n\t- **UVWX** *: ( vec4 | Slider )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Layer Blend**\n---\n#### **Normal**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Add**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Multiply**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Overlay**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Screen**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Darken**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Lighten**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Soft Light**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Hard Light**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Linear Light**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Dodge**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Burn**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Subtract**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Difference**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Divide**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Hue**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Saturation**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Value**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Color**\n- **Inputs**  \n\t- **Opacity** *: ( float | Slider ) - default = 1.0*  \n\t- **Base** *: ( vec4 )*  \n\t- **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)*  \n\t- **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n## Texturing\n---\n### **Image**\n- **Inputs**  \n\t- **Image** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Smooth Interpolation** *: ( bool ) - default = True*  \n- **Outputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Resolution** *: ( vec2 )*  \n---\n### **Normal Map**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **UV Index** *: ( int )*  \n- **Outputs**  \n\t- **Normal** *: ( vec3 )*  \n---\n### **Flipbook**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Dimensions** *: ( ivec2 )*  \n\t- **Page** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Flowmap**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Flow** *: ( vec2 ) - default = vec2(0.0)*  \n\t- **Progression** *: ( float )*  \n\t- **Samples** *: ( int ) - default = 2*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Matcap**\n- **Inputs**  \n\t- **Matcap** *: ( sampler2D )*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Uv** *: ( vec2 )*  \n---\n### **HDRI**\n- **Inputs**  \n\t- **Hdri** *: ( sampler2D )*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **Color** *: ( vec4 )*  \n\t- **Uv** *: ( vec2 )*  \n---\n### **Noise**\n---\n#### **Infinite**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Detail** *: ( float ) - default = 3.0*  \n\t- **Balance** *: ( float | Slider ) - default = 0.5*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Tiled**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Detail** *: ( float ) - default = 3.0*  \n\t- **Balance** *: ( float | Slider ) - default = 0.5*  \n\t- **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Voronoi**\n---\n#### **Infinite**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n- **Outputs**  \n\t- **Cell Color** *: ( vec4 )*  \n\t- **Cell Position** *: ( vec4 )*  \n\t- **Cell Distance** *: ( float )*  \n---\n#### **Tiled**\n- **Inputs**  \n\t- **Coord** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Seed** *: ( float )*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)*  \n- **Outputs**  \n\t- **Cell Color** *: ( vec4 )*  \n\t- **Cell Position** *: ( vec4 )*  \n\t- **Cell Distance** *: ( float )*  \n---\n### **Bayer Pattern**\n- **Inputs**  \n\t- **Size** *: ( int | ENUM(2x2,3x3,4x4,8x8) ) - default = 2*  \n\t- **Texel** *: ( vec2 ) - default = vec2(screen_pixel())*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Gradient**\n---\n#### **Linear**\n- **Inputs**  \n\t- **Value** *: ( float ) - default = UV[0].x*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Quadratic**\n- **Inputs**  \n\t- **Value** *: ( float ) - default = UV[0].x*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Easing**\n- **Inputs**  \n\t- **Value** *: ( float ) - default = UV[0].x*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Diagonal**\n- **Inputs**  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Spherical**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = POSITION*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Quadratic Sphere**\n- **Inputs**  \n\t- **Vector** *: ( vec3 | Vector ) - default = POSITION*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Radial**\n- **Inputs**  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Wave**\n- **Inputs**  \n\t- **Mode** *: ( int | ENUM(Sine,Saw,Triangle) )*  \n\t- **Coord** *: ( float ) - default = UV[0].x*  \n\t- **Scale** *: ( float ) - default = 5.0*  \n\t- **Phase** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n## Shading\n---\n### **Ambient Occlusion**\n- **Inputs**  \n\t- **Samples** *: ( int ) - default = 32*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Distribution Exponent** *: ( float ) - default = 5.0*  \n\t- **Contrast** *: ( float | Slider ) - default = 0.1*  \n\t- **Bias** *: ( float | Slider ) - default = 0.01*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Curvature**\n---\n#### **Curvature**\n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n#### **Surface Curvature**\n- **Inputs**  \n\t- **Depth Range** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Line Detection**\n- **Outputs**  \n\t- **Delta Distance** *: ( float )*  \n\t- **Delta Angle** *: ( float )*  \n\t- **Is ID Boundary** *: ( vec4 )*  \n---\n### **Line Width**\n- **Inputs**  \n\t- **Width Scale** *: ( float ) - default = 4.0*  \n\t- **Width Units** *: ( int | ENUM(Pixel,Screen,World) )*  \n\t- **Depth Width** *: ( float | Slider ) - default = 1.0*  \n\t- **Depth Threshold** *: ( float | Slider ) - default = 0.1*  \n\t- **Depth Threshold Range** *: ( float | Slider )*  \n\t- **Normal Width** *: ( float | Slider ) - default = 1.0*  \n\t- **Normal Threshold** *: ( float | Slider ) - default = 0.5*  \n\t- **Normal Threshold Range** *: ( float | Slider )*  \n\t- **Id Boundary Width** *: ( vec4 | Slider ) - default = (1.0, 1.0, 1.0, 1.0)*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Rim Light**\n- **Inputs**  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Angle** *: ( float )*  \n\t- **Rim Length** *: ( float ) - default = 2.0*  \n\t- **Length Falloff** *: ( float )*  \n\t- **Thickness** *: ( float ) - default = 0.1*  \n\t- **Thickness Falloff** *: ( float )*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **NPR Diffuse**\n---\n#### **Color Ramp**\n- **Inputs**  \n\t- **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)*  \n\t- **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)*  \n\t- **Gradient** *: ( sampler1D )*  \n\t- **Offset** *: ( float | Slider )*  \n\t- **Full Range** *: ( bool )*  \n\t- **Max Contribution** *: ( bool )*  \n\t- **Shadows** *: ( int | ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows) )*  \n\t- **Light Groups** *: ( ivec4 ) - default = (1, 0, 0, 0)*  \n\t- **Position** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Color Layer**\n- **Inputs**  \n\t- **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)*  \n\t- **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)*  \n\t- **Size** *: ( float | Slider ) - default = 1.0*  \n\t- **Gradient Size** *: ( float | Slider ) - default = 0.1*  \n\t- **Offset** *: ( float | Slider )*  \n\t- **Full Range** *: ( bool )*  \n\t- **Max Contribution** *: ( bool )*  \n\t- **Shadows** *: ( int | ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows) )*  \n\t- **Light Groups** *: ( ivec4 ) - default = (1, 0, 0, 0)*  \n\t- **Position** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n### **NPR Specular**\n---\n#### **Color Ramp**\n- **Inputs**  \n\t- **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)*  \n\t- **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)*  \n\t- **Gradient** *: ( sampler1D )*  \n\t- **Offset** *: ( float | Slider )*  \n\t- **Roughness** *: ( float | Slider ) - default = 0.5*  \n\t- **Anisotropy** *: ( float | Slider ) - default = 0.5*  \n\t- **Max Contribution** *: ( bool )*  \n\t- **Shadows** *: ( int | ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows) )*  \n\t- **Light Groups** *: ( ivec4 ) - default = (1, 0, 0, 0)*  \n\t- **Position** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Tangent** *: ( vec3 | Normal ) - default = radial_tangent(NORMAL, vec3(0,0,1))*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n#### **Color Layer**\n- **Inputs**  \n\t- **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)*  \n\t- **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)*  \n\t- **Size** *: ( float | Slider ) - default = 1.0*  \n\t- **Gradient Size** *: ( float | Slider ) - default = 0.1*  \n\t- **Offset** *: ( float | Slider )*  \n\t- **Roughness** *: ( float | Slider ) - default = 0.5*  \n\t- **Anisotropy** *: ( float | Slider ) - default = 0.5*  \n\t- **Max Contribution** *: ( bool )*  \n\t- **Shadows** *: ( int | ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows) )*  \n\t- **Light Groups** *: ( ivec4 ) - default = (1, 0, 0, 0)*  \n\t- **Position** *: ( vec3 | Vector ) - default = POSITION*  \n\t- **Normal** *: ( vec3 | Normal ) - default = NORMAL*  \n\t- **Tangent** *: ( vec3 | Normal ) - default = radial_tangent(NORMAL, vec3(0,0,1))*  \n- **Outputs**  \n\t- **result** *: ( vec3 )*  \n---\n## Filter\n---\n### **Curvature**\n- **Inputs**  \n\t- **Normal Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Width** *: ( float ) - default = 1.0*  \n\t- **X** *: ( vec3 | Normal ) - default = (1.0, 0.0, 0.0)*  \n\t- **Y** *: ( vec3 | Normal ) - default = (0.0, 1.0, 0.0)*  \n- **Outputs**  \n\t- **result** *: ( float )*  \n---\n### **Blur**\n---\n#### **Box Blur**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5.0*  \n\t- **Circular** *: ( bool )*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Gaussian Blur**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5.0*  \n\t- **Sigma** *: ( float ) - default = 1.0*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Jitter Blur**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5.0*  \n\t- **Distribution Exponent** *: ( float ) - default = 5.0*  \n\t- **Samples** *: ( int ) - default = 8*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Bilateral**\n- **Inputs**  \n\t- **Input Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 5*  \n\t- **Sigma** *: ( float ) - default = 10.0*  \n\t- **BSigma** *: ( float ) - default = 0.1*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Orientation-Aligned Bilateral**\n- **Inputs**  \n\t- **Input Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Flow** *: ( vec2 ) - default = vec2(0)*  \n\t- **Radius** *: ( float ) - default = 6.0*  \n\t- **Smoothness** *: ( float ) - default = 0.55*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Sharpen**\n---\n#### **Box**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Circular** *: ( bool )*  \n\t- **Sharpness** *: ( float ) - default = 0.3*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Gaussian**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Sigma** *: ( float ) - default = 1.0*  \n\t- **Sharpness** *: ( float ) - default = 0.3*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Jitter**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Radius** *: ( float ) - default = 1.0*  \n\t- **Distribution Exponent** *: ( float ) - default = 5.0*  \n\t- **Samples** *: ( int ) - default = 8*  \n\t- **Sharpness** *: ( float ) - default = 0.3*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n### **Kuwahara**\n---\n#### **Isotropic**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Size** *: ( int ) - default = 5*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n---\n#### **Anisotropic**\n- **Inputs**  \n\t- **Texture** *: ( sampler2D )*  \n\t- **UV** *: ( vec2 ) - default = UV[0]*  \n\t- **Direction** *: ( vec2 ) - default = vec2(0.0, 0.0)*  \n\t- **Size** *: ( float ) - default = 2.0*  \n\t- **Samples** *: ( int ) - default = 50*  \n- **Outputs**  \n\t- **result** *: ( vec4 )*  \n"
  },
  {
    "path": "docs/reference/settings.md",
    "content": "# Malt Settings\n## World\n- **Material**\n\t- **Default** *: ( Material )** = Malt - Default Mesh Material*  \n\t>The default material, used for objects with no material assigned.\n\t- **Override** *: ( Material )** = None*  \n\t>When set, overrides all scene materials with this one.\n- **Viewport**\n\t- **Resolution Scale** *: ( Float )** = 1.0*  \n\t>A multiplier for the viewport resolution.\nIt can be lowered to improve viewport performance or for specific styles, like *pixel art*.\n\t- **Smooth Interpolation** *: ( Bool )** = True*  \n\t>The interpolation mode used when *Resolution Scale* is not 1.\nToggles between *Nearest/Bilinear* interpolation.\n- **Samples**\n\t- **Grid Size** *: ( Int )** = 8*  \n\t>The number of render samples per side in the sampling grid. \nThe total number of samples is the square of this value minus the samples that fall outside the sampling radius.  \nHigher values will provide cleaner renders at the cost of increased render times.\n\t- **Width** *: ( Float )** = 1.0*  \n\t>The width (and height) of the sampling grid. \nLarger values will result in smoother/blurrier images while lower values will result in sharper/more aliased ones. \nKeep it withing the 1-2 range for best results.\n- **Render** *: ( Graph )** = Default Render*  \n>The *Render Node Tree* used to render the scene. \nSee [Render & Render Layers](#Render & Render Layers) for more info.\n## Material\n- **Light Groups**\n\t- **Light** *: ( Int )** = [1, 0, 0, 0]*  \n\t>The *Light Groups* (up to 4) that lit this material.\n\t- **Shadow** *: ( Int )** = [1, 0, 0, 0]*  \n\t>The *Light Groups* (up to 4) that this material casts shadows on.\n## Mesh\n- **double_sided** *: ( Bool )** = False*  \n>Disables backface culling, so geometry is rendered from both sides.\n- **precomputed_tangents** *: ( Bool )** = False*  \n>Load precomputed mesh tangents *(needed for improving normal mapping quality on low poly meshes)*. \nIt's disabled by default since it slows down mesh loading in Blender.  \nWhen disabled, the *tangents* are calculated on the fly from the *pixel shader*.\n## Light\n- **Light Group** *: ( Int )** = 1*  \n>Lights only affect materials with a matching *Light Group* value.\n- **Shader** *: ( Material )** = None*  \n>When set, the *Material* with a custom *Light Shader* or *Light Node Tree* that will be used to render this light.\n"
  },
  {
    "path": "mkdocs/mkdocs.yml",
    "content": "site_name: Malt\n\nsite_url: https://malt3d.com\nsite_author: Miguel Pozo\nsite_description: Malt Render Engine\nrepo_name: bnpr/Malt\nrepo_url: https://github.com/bnpr/Malt\ndocs_dir: ../docs/\n#edit_uri: \"\"\n\nnav:\n  - 'index.md'\n  - 'Documentation':\n    - 'Documentation/Getting Started.md'\n    - 'Documentation/Settings.md'\n    - 'Documentation/Graphs.md'\n    - 'Reference':\n      - 'reference/settings.md'\n      - 'reference/Mesh-graph.md'\n      - 'reference/Screen-graph.md'\n      - 'reference/Light-graph.md'\n      - 'reference/Render-graph.md'\n      - 'reference/Render Layer-graph.md'\n    - 'Documentation/Plugins.md'\n    - 'Documentation/Tooling.md'\n  #- 'Developer Documentation' : 'https://github.com/bnpr/Malt/tree/Development/Malt#malt'\n  - 'Discussions - Q&A' : 'https://github.com/bnpr/Malt/discussions'\n  - 'Patreon' : 'https://patreon.com/pragma37'\n  - 'Download' : 'https://github.com/bnpr/Malt/releases/tag/Release-latest'\n\ntheme:\n  name: material\n  custom_dir: ../docs/overrides\n  logo: images/cube-solid.svg\n  favicon: images/cube-solid.svg\n  features:\n    - navigation.instant\n    #- navigation.tracking\n    - navigation.tabs\n    #- toc.integrate\n  palette:\n    - scheme: slate\n      primary: teal\n      toggle:\n        icon: material/toggle-switch-off-outline\n        name: Switch to light mode\n    - scheme: default\n      primary: teal\n      toggle:\n        icon: material/toggle-switch\n        name: Switch to dark mode\n\nextra_css:\n  - extra/extra.css\n\nextra_javascript:\n  - extra/extra.js\n\nmarkdown_extensions:\n  - attr_list\n  - pymdownx.highlight:\n      use_pygments: true\n      anchor_linenums: true\n  - pymdownx.inlinehilite\n  - pymdownx.snippets\n  - pymdownx.superfences\n  - pymdownx.superfences:\n      custom_fences:\n        - name: mermaid\n          class: mermaid\n          format: !!python/name:pymdownx.superfences.fence_code_format\n\nextra:\n  generator: false\n  social:\n    - icon: fontawesome/brands/github\n      link: https://github.com/bnpr/Malt\n    - icon: fontawesome/brands/youtube\n      link: https://www.youtube.com/channel/UCNjoz44PbgbQPqFrqy7Y4mg\n    - icon: fontawesome/brands/twitter\n      link: https://twitter.com/pragma37\n    - icon: fontawesome/brands/patreon\n      link: https://www.patreon.com/pragma37\n\n"
  },
  {
    "path": "mkdocs/netlify.toml",
    "content": "[build]\n  publish = \"site/\"\n  command = \"mkdocs build\"\n  ignore = \"git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../docs/\"\n"
  },
  {
    "path": "mkdocs/requirements.txt",
    "content": "mkdocs-material==8.2.8\n"
  },
  {
    "path": "plugins/Experimental/RenderLayer/OpaqueLayer.py",
    "content": "from Malt.PipelineNode import PipelineNode\nfrom Malt.PipelineParameters import Parameter, Type\n\nclass OpaqueLayer(PipelineNode):\n\n    def __init__(self, pipeline):\n        PipelineNode.__init__(self, pipeline)\n    \n    @classmethod\n    def reflect_inputs(cls):\n        return {\n            'Output Name' : Parameter('', Type.STRING)\n        }\n    \n    @classmethod\n    def reflect_outputs(cls):\n        return {\n            'Opaque Layer' : Parameter('', Type.TEXTURE)\n        }\n    \n    def execute(self, parameters):\n        inputs = parameters['IN']\n        outputs = parameters['OUT']\n\n        output_name = inputs['Output Name']\n        render_layers_node = parameters['__GLOBALS__']['__RENDER_LAYERS__']\n        outputs['Opaque Layer'] = render_layers_node.opaque_targets.get(output_name)\n\nNODE = OpaqueLayer    \n"
  },
  {
    "path": "plugins/Experimental/__init__.py",
    "content": "import os\nfrom Malt.PipelinePlugin import PipelinePlugin, isinstance_str\n\nclass ExperimentalNodes(PipelinePlugin):\n\n    @classmethod\n    def poll_pipeline(self, pipeline):\n        return isinstance_str(pipeline, 'NPR_Pipeline')\n    \n    @classmethod\n    def register_graph_libraries(self, graphs):\n        root = os.path.dirname(__file__)\n        graphs['Render Layer'].add_library(os.path.join(root, 'RenderLayer'))\n\nPLUGIN = ExperimentalNodes\n"
  },
  {
    "path": "plugins/PluginExample/Shaders/PluginExample.glsl",
    "content": "#include \"Common.glsl\"\n#include \"Common/Hash.glsl\"\n\nvec4 instance_random_color()\n{\n    return hash(ID[0]);\n}\n"
  },
  {
    "path": "plugins/PluginExample/__init__.py",
    "content": "import os\nfrom Malt.PipelinePlugin import PipelinePlugin, isinstance_str\n\nclass PluginExample(PipelinePlugin):\n\n    @classmethod\n    def poll_pipeline(self, pipeline):\n        return isinstance_str(pipeline, 'NPR_Pipeline')\n    \n    @classmethod\n    def register_graph_libraries(self, graphs):\n        library_path = os.path.join(os.path.dirname(__file__), 'Shaders')\n        for graph in graphs.values():\n            if graph.language == 'GLSL':\n                graph.add_library(library_path)\n\nPLUGIN = PluginExample\n"
  },
  {
    "path": "scripts/build_intellisense_glsl.py",
    "content": "import os, json, xmltodict, traceback, copy\n\nSKIP_TERMS = ['image','atomic','barrier','memory','shadow','noise']\n\nDEFINES = ['in','out','inout','uniform','varying','layout(index)','discard','uint unsigned int','atomic_uint uint']\n\nDEFINES = ''.join(['#define ' + define + '\\n' for define in DEFINES])\n\nGENERICS = {\n    'genType': ['float',  'vec2',  'vec3',  'vec4'],\n    'genDType':['double', 'dvec2', 'dvec3', 'dvec4'],\n    'genIType':['int',    'ivec2', 'ivec3', 'ivec4'],\n    'genUType':['uint',   'uvec2', 'uvec3', 'uvec4'],\n    'genBType':['bool',   'bvec2', 'bvec3', 'bvec4'],\n}\n\n#Start with types we don't want to define\nBUILTINS = [*GENERICS.keys(),'gvec4']\n\nTYPES = ''\n\nFUNCTIONS = ''\n\ndef add_type(type_name):\n    global TYPES\n    TYPES += 'struct ' + type_name + ' {};\\n'\n\ndef add_generic_type(type_name):\n    if type_name.startswith('g') and type_name not in BUILTINS:\n        BUILTINS.append(type_name)\n        for replace in ['','i','u']:\n            add_type(replace + type_name[1:])\n\nfor generic in GENERICS.values():\n    for _type in generic[1:]:\n        add_type(_type)\n\nfor mat_type in ['mat2','mat3','mat4','mat2x2','mat2x3','mat2x4','mat3x2','mat3x3','mat3x4','mat4x2','mat4x3','mat4x4']:\n    add_type(mat_type)\n    add_type('d'+mat_type)\n\ndef output_function(signatures, docstring):\n    generic_types = []\n    has_optional = False\n    for _type, name in signatures:\n        for term in SKIP_TERMS:\n            if term in _type.lower() or term in name.lower():\n                return\n        if _type.startswith('['):\n            has_optional = True\n        _type = _type.split(' ')[-1]\n        if _type.startswith('g') or _type.endswith('vec') or _type.endswith('mat'):\n            if _type not in generic_types:\n                generic_types.append(_type)\n    resolve_generics(signatures, generic_types, has_optional, docstring)\n\ndef resolve_generics(signatures, generic_types, has_optional, docstring):\n    if has_optional:\n        for remove in [True, False]:\n            _signatures = copy.deepcopy(signatures)\n            if remove:\n                _signatures.pop()\n            else:\n                _signatures[-1][0]= _signatures[-1][0].replace('[','').replace(']','')\n                _signatures[-1][1]= _signatures[-1][1].replace('[','').replace(']','')\n            resolve_generics(_signatures, copy.deepcopy(generic_types), False, docstring)\n    else:\n        string = '//' + docstring + '\\n'\n        if len(generic_types) > 0:\n            string += 'template<'\n            while len(generic_types) > 0:\n                _type = generic_types.pop(0)\n                add_generic_type(_type)\n                string += 'typename '+_type\n                if len(generic_types) > 0:\n                    string += ', '\n            string += '> '\n\n        func = signatures.pop(0)\n        string += func[0] + ' ' + func[1] + '('\n        while len(signatures) > 0:\n            param = signatures.pop(0)\n            string += param[0] + ' ' + param[1]\n            if len(signatures) > 0:\n                string += ', '\n        string += ');'\n        global FUNCTIONS\n        FUNCTIONS += string + '\\n'\n        #print(string)\n\nfor entry in os.listdir('gl4'):\n    if entry.endswith('.xml'):\n        with open(os.path.join('gl4', entry), 'r') as xml:\n            xml = xml.read()\n            try:\n                d = xmltodict.parse(xml)\n                if d['refentry']['refsynopsisdiv']['title'] == 'Declaration':\n                    try:\n                        print(entry,'---------------------------------------------------------')\n                        docstring = d['refentry']['refnamediv']['refpurpose']\n                        synops = d['refentry']['refsynopsisdiv']['funcsynopsis']\n                        if isinstance(synops, list) == False:\n                            synops = [synops]\n                        for synop in synops:\n                            functions = synop['funcprototype']\n                            if isinstance(functions, list) == False:\n                                functions = [functions]\n                            for function in functions:\n                                name = function['funcdef']['function']\n                                return_type = function['funcdef']['#text']\n                                signatures = [[return_type, name]]\n                                declaration = return_type + ' ' + name + '('\n                                parameters = function['paramdef']\n                                if isinstance(parameters, str) == False: #not void\n                                    if isinstance(parameters, list) == False:\n                                        parameters = [parameters]\n                                    for parameter in parameters:\n                                        param_type = parameter['#text']\n                                        param_name = parameter['parameter']\n                                        signatures.append([param_type, param_name])\n                                        declaration += param_type + ' ' + param_name + ', '\n                                if declaration.endswith(', '):\n                                    declaration = declaration[:-2]\n                                declaration += ');'\n                                print(signatures)\n                                output_function(signatures, docstring)\n                    except Exception as e:\n                        print('exception')\n                        #print(json.dumps(d['refentry']['refsynopsisdiv'], indent=4))\n                        print(traceback.format_exc())\n            except Exception as e:\n                #print('FAILED', entry)\n                pass\n\nSHORTEN = True\nif SHORTEN:\n    FUNCTIONS = FUNCTIONS.replace('genType', 'T')\n    FUNCTIONS = FUNCTIONS.replace('genDType', 'D')\n    FUNCTIONS = FUNCTIONS.replace('genIType', 'I')\n    FUNCTIONS = FUNCTIONS.replace('genUType', 'U')\n    FUNCTIONS = FUNCTIONS.replace('genBType', 'B')\n\nFINAL_FILE = f'''\n//This file contains a series of C++ macros, structs and function declarations\n//to make GLSL autocompletion work with C++ autocompletion implementations\n\n#ifdef __INTELLISENSE__\n\n//Define GLSL keywords\n\n{DEFINES}\n\n//Declare GLSL built-in types\n\n{TYPES}\n\n//Declare GLSL standard library functions\n\n{FUNCTIONS}\n\n#endif\n'''\n\nwith open('intellisense.glsl', 'w') as f:\n    f.write(FINAL_FILE)\n"
  },
  {
    "path": "scripts/format.py",
    "content": "def scan_dirs(path, file_callback):\n    import os\n    for e in os.scandir(path):\n        if e.is_file():\n            extension = e.name.split('.')[-1]\n            if extension in ('py','glsl','h','c','cpp'):\n                file_callback(e)\n        if e.is_dir():\n            if e.name.startswith('.') or e.name.startswith('__'):\n                continue\n            scan_dirs(e, file_callback)\n\n\ndef fix_whitespace(path):\n    with open(path, 'r+') as f:\n        result = ''\n        lines = f.readlines()\n        for i, line in enumerate(lines):\n            new_line = ''\n            if line.isspace():\n                while i < len(lines) - 1:\n                    i += 1\n                    found_next_line = False\n                    if lines[i].isspace() == False:\n                        for c in lines[i]:\n                            if c.isspace():\n                                new_line += c\n                            else:\n                                break\n                        found_next_line = True\n                    else:\n                        continue\n                    if found_next_line:\n                        break\n            else:\n                new_line = line.rstrip()\n            result += new_line\n            result += '\\n'\n        result = result.splitlines(keepends=True)\n        while len(result) > 1 and result[-1].isspace():\n            while result[-1].isspace():\n                result.pop()\n        result = ''.join(result)\n        f.seek(0)\n        f.truncate()\n        f.write(result)\n\n\nimport sys\n\nscan_dirs(sys.argv[1], fix_whitespace)\n"
  },
  {
    "path": "scripts/generate_conversion_nodes.py",
    "content": "result = ''\n\nbase_types = ('float','int','uint','bool')\nvector_types = ('vec','ivec','uvec','bvec')\n\nfor to_base_type in base_types:\n    result += f'//{to_base_type}\\n'\n    for from_base_type in base_types:\n        if from_base_type == to_base_type:\n            continue\n        result += '/* META @meta: internal=true; */\\n'\n        param = from_base_type[:1]\n        result += f'{to_base_type} {to_base_type}_from_{from_base_type}({from_base_type} {param}) {{ return {to_base_type}({param}); }}\\n'\n    \n    result += '\\n'\n\nfor to_vector_type in vector_types:\n    for to_vector_len in range(2,5):\n        to_vector = f'{to_vector_type}{to_vector_len}'\n        \n        result += f'//{to_vector}\\n'\n        \n        for base_type in base_types:\n            result += '/* META @meta: internal=true; */\\n'\n            param = base_type[:1]\n            conversion = f'{to_vector}({param})'\n            if to_vector_len == 4 and to_vector_type != 'bvec':\n                conversion = f'{to_vector}({param}, {param}, {param}, 1)'\n            result += f'{to_vector} {to_vector}_from_{base_type}({base_type} {param}) {{ return {conversion}; }}\\n'\n\n        for from_vector_type in vector_types:\n            for from_vector_len in range(2,5):\n                if from_vector_type == to_vector_type and from_vector_len == to_vector_len:\n                    continue\n                from_vector = f'{from_vector_type}{from_vector_len}'\n\n                param = 'v'\n                if to_vector_len < from_vector_len:\n                    param += '.xyzw'[:to_vector_len+1]\n                if to_vector_len > from_vector_len:\n                    for i in range(from_vector_len, to_vector_len):\n                        if to_vector_type != 'bvec' and to_vector_len == 4 and i == to_vector_len - 1:\n                            param += ', 1'\n                        else:\n                            param += ', 0'\n                conversion = f'{to_vector}({param})'\n\n                result += '/* META @meta: internal=true; */\\n'\n                result += f'{to_vector} {to_vector}_from_{from_vector}({from_vector} v) {{ return {conversion}; }}\\n'\n        \n        result += '\\n'\n\n\nprint(result)\n"
  },
  {
    "path": "scripts/generate_license_dependencies_full.py",
    "content": "import os\n\nTOP_DIR = os.path.join(os.path.dirname(__file__), os.pardir)\n\ndependencies = open(os.path.join(TOP_DIR, 'LICENSE - DEPENDENCIES')).read()\n\nresult = ''\n\nfor dependency in dependencies.split('\\n\\n'):\n    lines = dependency.splitlines()\n    for i, line in enumerate(lines):\n        if i < 3:\n            result += line\n        else:\n            url = line.replace('github.com', 'raw.githubusercontent.com').replace('blob/','')\n            from urllib.request import urlopen\n            content = urlopen(url).read().decode('utf-8')\n            result+=content\n        result+='\\n'\n    result += '*'*80\n    result+='\\n'\n\nopen(os.path.join(TOP_DIR, 'LICENSE - DEPENDENCIES (FULL TEXT)'), 'w').write(result)\n"
  },
  {
    "path": "scripts/get_glslang.py",
    "content": "import os, stat, platform, shutil, zipfile\n\ncurrent_dir = os.path.dirname(os.path.realpath(__file__))\ngl_folder = os.path.join(current_dir, '..', 'Malt', 'GL')\n\nzip_file = os.path.join(current_dir, 'glslang.zip')\n\nglslang_url = {\n'Windows' : \"https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-windows-x64-Release.zip\",\n'Linux' : \"https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-linux-Release.zip\",\n'Darwin' : \"https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-osx-Release.zip\",\n}\nglslang_url = glslang_url[platform.system()]\n\nzipped_path = 'bin/glslangValidator'\ntarget_path = os.path.join(gl_folder, '.glslang')\nif platform.system() == 'Windows':\n    zipped_path += '.exe'\n    target_path += '.exe'\n\nimport urllib.request\nurllib.request.urlretrieve(glslang_url, zip_file)\n\nwith zipfile.ZipFile(zip_file) as z:\n    with z.open(zipped_path) as zip, open(target_path, 'wb') as unzip:\n        shutil.copyfileobj(zip, unzip)\n\n# Set as executable (Needed on Linux)\nos.chmod(target_path, os.stat(target_path).st_mode | stat.S_IEXEC)\n\nos.remove(zip_file)\n"
  },
  {
    "path": "scripts/install_dependencies.py",
    "content": "import os, subprocess, sys, shutil, stat\n\ncurrent_dir = os.path.dirname(os.path.realpath(__file__))\n\nmalt_folder = os.path.join(current_dir, '..', 'Malt')\n\ntry:\n    subprocess.check_call([sys.executable, '-m', 'pip', '--version'])\nexcept:\n    subprocess.check_call([sys.executable, '-m', 'ensurepip'])\n    os.environ.pop(\"PIP_REQ_TRACKER\", None)\n\npy_version = str(sys.version_info[0])+str(sys.version_info[1])\nmalt_dependencies_path = os.path.join(malt_folder, '.Dependencies-{}'.format(py_version))\ndependencies = ['glfw', 'PyOpenGL', 'PyOpenGL_accelerate', 'Pyrr', 'xxhash']\nfor dependency in dependencies:\n    try:\n        subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', dependency, '--target', malt_dependencies_path])\n    except:\n        print('ERROR: pip install {} failed.'.format(dependency))\n        import traceback\n        traceback.print_exc()\n\n\nfrom shutil import copytree\ncopytree(os.path.join(current_dir, \"PatchDependencies\"), malt_dependencies_path, dirs_exist_ok=True)\n\n#make sure mcpp has executable permissions\nfor str in ['Linux', 'Darwin']:\n    mcpp = os.path.join(malt_dependencies_path, f'mcpp-{str}')\n    os.chmod(mcpp, os.stat(mcpp).st_mode | stat.S_IEXEC)\n\n#Remove numpy since Blender already ships with it\n#Remove bin to avoid AVs false positives\nfor e in os.listdir(malt_dependencies_path):\n    if e.startswith('numpy') or e == 'bin':\n        path = os.path.join(malt_dependencies_path, e)\n        if os.path.isdir(path):\n            shutil.rmtree(path)\n        elif os.path.isfile(path):\n            os.remove(path)\n\n\n#subprocess.check_call([sys.executable, os.path.join(current_dir, 'get_glslang.py')])\n"
  },
  {
    "path": "scripts/print_pixel_formats.py",
    "content": "GL_ENUMS = {}\nGL_NAMES = {}\n\nif True: #create new scope to import OpenGL\n    from OpenGL import GL\n    for e in dir(GL):\n        if e.startswith('GL_'):\n            GL_ENUMS[getattr(GL, e)] = e\n            GL_NAMES[e] = getattr(GL, e)\n\nfrom OpenGL.GL import *\n\ndef print_format_prop(format, prop):\n    read = glGetInternalformativ(GL_TEXTURE_2D, format, prop, 1)\n    print(GL_ENUMS[format], GL_ENUMS[prop], GL_ENUMS[read])\n\ndef print_format_props(format):\n    print_format_prop(format, GL_READ_PIXELS)\n    print_format_prop(format, GL_READ_PIXELS_FORMAT)\n    print_format_prop(format, GL_READ_PIXELS_TYPE)\n    print_format_prop(format, GL_TEXTURE_IMAGE_FORMAT)\n    print_format_prop(format, GL_TEXTURE_IMAGE_TYPE)\n\nprint_format_props(GL_RGB8)\nprint_format_props(GL_RGBA8)\nprint_format_props(GL_RGB16F)\nprint_format_props(GL_RGBA16F)\nprint_format_props(GL_RGB32F)\nprint_format_props(GL_RGBA32F)\n"
  },
  {
    "path": "scripts/settings.json",
    "content": "{\n    \"files.associations\": {\n        \"*.pyx\": \"python\",\n        \"*.glsl\": \"cpp\"\n    },\n    \"cmake.configureOnOpen\": false,\n    \"C_Cpp.default.includePath\": [\"Malt\\\\Shaders\"],\n    \"C_Cpp.autoAddFileAssociations\": true,\n    \"C_Cpp.default.cppStandard\": \"c++03\",\n    \"C_Cpp.default.compilerPath\": \"\",\n    \"C_Cpp.default.browse.limitSymbolsToIncludedHeaders\": true,\n    \"C_Cpp.errorSquiggles\": \"Disabled\",\n}"
  },
  {
    "path": "scripts/setup_blender_addon.py",
    "content": "import os, sys, platform, subprocess, shutil, pathlib\n\nimport argparse\nparser = argparse.ArgumentParser()\nparser.add_argument('--scripts-folder', type=pathlib.Path, help='Create a symlink pointing to the addon.')\nparser.add_argument('--copy-modules', action='store_true', help='Copy Malt and Bridge instead of making a symlink. (Needed for zipping in Linux)')\nARGS = parser.parse_args()\n\ncurrent_dir = os.path.dirname(os.path.realpath(__file__))\nmain_dir = os.path.realpath(os.path.join(current_dir, '..'))\n\nblender_malt_folder = os.path.join(main_dir, 'BlenderMalt')\nbridge_folder = os.path.join(main_dir, 'Bridge')\nmalt_folder = os.path.join(main_dir, 'Malt')\n\ndef build_lib(path):\n    subprocess.check_call([sys.executable, 'build.py'], cwd=path)\n    import stat\n    def delete_read_only(func, path, exc_info):\n        os.chmod(path, stat.S_IWRITE)\n        os.remove(path)\n    shutil.rmtree(os.path.join(path, '.build'), onerror=delete_read_only)\n\nbuild_lib(os.path.join(blender_malt_folder, 'CBlenderMalt'))\nbuild_lib(os.path.join(malt_folder, 'GL', 'GLSLParser'))\nbuild_lib(os.path.join(bridge_folder, 'ipc'))\nbuild_lib(os.path.join(bridge_folder, 'renderdoc'))\n\nsubprocess.check_call([sys.executable, os.path.join(current_dir, 'install_dependencies.py')])\n\ndef ensure_dir(path):\n    if os.path.exists(path) == False:\n        os.mkdir(path)\n\ndef make_link(point_from, point_to):\n    if os.path.exists(point_from):\n        print('Already linked:', point_from, '<--->', point_to)\n        return\n    if platform.system() == 'Windows':\n        import _winapi\n        _winapi.CreateJunction(point_to, point_from)\n    else:\n        os.symlink(point_to, point_from, True)\n\ndef make_copy(copy_to, copy_from):\n    from shutil import copytree\n    copytree(copy_from, copy_to)\n\nimport_path = os.path.join(blender_malt_folder, '.MaltPath')\nensure_dir(import_path)\n\nsetup_modules = make_link\nif ARGS.copy_modules:\n    setup_modules = make_copy\n\nsetup_modules(os.path.join(import_path, 'Malt'), os.path.join(main_dir, 'Malt'))\nsetup_modules(os.path.join(import_path, 'Bridge'), os.path.join(main_dir, 'Bridge'))\n\nif ARGS.scripts_folder:\n    addons_folder = os.path.join(ARGS.scripts_folder, 'addons')\n    ensure_dir(addons_folder)\n    make_link(os.path.join(addons_folder, 'BlenderMalt'), blender_malt_folder)\n"
  }
]