├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── ask_question.md │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── android_opengl.yml │ ├── ios_metal.yml │ ├── linux_opengl.yml │ ├── linux_vulkan.yml │ ├── macos_metal.yml │ ├── windows_direct3d11.yml │ └── windows_direct3d12.yml ├── .gitignore ├── .gitmodules ├── Assets ├── Scene.arm ├── default_brush.arm ├── export_presets │ ├── base_color.json │ ├── generic.json │ ├── minecraft_mer.json │ ├── unigine.json │ ├── unity.json │ ├── unreal.json │ └── xplane.json ├── icons.png ├── icons2x.png ├── keymap_presets │ └── default.json ├── licenses │ ├── license.md │ ├── license_esrgan.md │ ├── license_onnx.md │ ├── license_photo_to_pbr.md │ ├── license_sd.md │ └── license_texsynth.md ├── locale │ ├── de.json │ ├── ja.json │ └── tools │ │ └── extract_locales.py ├── meshes │ └── keepme.txt ├── models │ └── README.md ├── placeholder.png ├── plugins │ ├── autosave.js │ ├── dev │ │ └── texture_breakdown.js │ ├── embed │ │ └── import_svg.js │ ├── hello_node_brush.js │ ├── hello_world.js │ ├── import_tiff.js │ ├── import_txt.js │ └── wasm │ │ ├── import_svg.js │ │ └── import_svg.wasm ├── raw │ └── icon │ │ ├── icon.ico │ │ ├── icon.png │ │ └── icon_raw.png └── readme │ ├── readme.txt │ ├── readme_android.txt │ ├── readme_dxr.txt │ ├── readme_ios.txt │ └── readme_vkrt.txt ├── LICENSE.md ├── README.md ├── Shaders ├── Material_mesh.frag.glsl ├── Material_mesh.vert.glsl ├── layer_copy.frag.glsl ├── layer_copy_bgra.frag.glsl ├── layer_copy_rrrr.frag.glsl ├── layer_copy_rrrr.vert.glsl ├── layer_view.vert.glsl └── mask_apply.frag.glsl ├── Sources ├── Main.hx ├── arm │ ├── App.hx │ ├── Args.hx │ ├── ConfigFormat.hx │ ├── Context.hx │ ├── Enums.hx │ ├── History.hx │ ├── KeymapFormat.hx │ ├── Layers.hx │ ├── PluginAPI.hx │ ├── Project.hx │ ├── ProjectFormat.hx │ ├── Strings.hx │ ├── io │ │ ├── ExportArm.hx │ │ ├── ExportGpl.hx │ │ ├── ExportMesh.hx │ │ ├── ExportObj.hx │ │ ├── ExportTexture.hx │ │ ├── ImportArm.hx │ │ ├── ImportAsset.hx │ │ ├── ImportBlend.hx │ │ ├── ImportEnvmap.hx │ │ ├── ImportFbx.hx │ │ ├── ImportGpl.hx │ │ ├── ImportKeymap.hx │ │ ├── ImportMesh.hx │ │ ├── ImportObj.hx │ │ ├── ImportPlugin.hx │ │ ├── ImportTexture.hx │ │ └── ImportTheme.hx │ ├── node │ │ ├── Brush.hx │ │ ├── LogicNode.hx │ │ ├── LogicTree.hx │ │ ├── MakeMaterial.hx │ │ ├── MakeMesh.hx │ │ ├── MakePaint.hx │ │ ├── NodesBrush.hx │ │ └── brush │ │ │ ├── BooleanNode.hx │ │ │ ├── BrushOutputNode.hx │ │ │ ├── ColorNode.hx │ │ │ ├── FloatNode.hx │ │ │ ├── ImageTextureNode.hx │ │ │ ├── InpaintNode.hx │ │ │ ├── IntegerNode.hx │ │ │ ├── MathNode.hx │ │ │ ├── NullNode.hx │ │ │ ├── PhotoToPBRNode.hx │ │ │ ├── RandomNode.hx │ │ │ ├── SeparateVectorNode.hx │ │ │ ├── StringNode.hx │ │ │ ├── TextToPhotoNode.hx │ │ │ ├── TilingNode.hx │ │ │ ├── UpscaleNode.hx │ │ │ ├── VarianceNode.hx │ │ │ ├── VectorMathNode.hx │ │ │ └── VectorNode.hx │ ├── render │ │ ├── Inc.hx │ │ ├── RenderPathDeferred.hx │ │ ├── RenderPathForward.hx │ │ ├── RenderPathPaint.hx │ │ └── Uniforms.hx │ ├── ui │ │ ├── BoxExport.hx │ │ ├── BoxPreferences.hx │ │ ├── BoxProjects.hx │ │ ├── TabBrowser.hx │ │ ├── TabConsole.hx │ │ ├── TabMeshes.hx │ │ ├── TabPlugins.hx │ │ ├── TabScript.hx │ │ ├── TabSwatches.hx │ │ ├── TabTextures.hx │ │ ├── UIBox.hx │ │ ├── UIFiles.hx │ │ ├── UIHeader.hx │ │ ├── UIMenu.hx │ │ ├── UIMenubar.hx │ │ ├── UINodes.hx │ │ ├── UISidebar.hx │ │ └── UIStatus.hx │ └── util │ │ ├── MeshUtil.hx │ │ └── UVUtil.hx └── import.hx ├── checkstyle.json ├── icon.png ├── khafile.js └── kincflags.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.hdr binary 2 | *.bin binary 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [luboslenco] 4 | custom: ['https://armorlab.org/download'] 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a question 3 | about: Post a question about ArmorLab 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | --- 8 | 9 | 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | 14 | **ArmorLab version:** 15 | 16 | 17 | 18 | **OS/device including version:** 19 | 20 | 21 | 22 | **Issue description:** 23 | 24 | 25 | 26 | **Steps to reproduce:** 27 | 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Disallow blank issues to make sure people follow one of the templates. 2 | blank_issues_enabled: false 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Create a feature request 4 | title: '' 5 | labels: feature request 6 | assignees: '' 7 | --- 8 | 9 | 12 | -------------------------------------------------------------------------------- /.github/workflows/android_opengl.yml: -------------------------------------------------------------------------------- 1 | name: Android (OpenGL) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Get Submodules 19 | run: git submodule update --init --recursive 20 | - name: Compile 21 | run: armorcore/Kinc/make --from armorcore android -g opengl 22 | -------------------------------------------------------------------------------- /.github/workflows/ios_metal.yml: -------------------------------------------------------------------------------- 1 | name: iOS (Metal) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: macOS-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Get Submodules 19 | run: git submodule update --init --recursive 20 | - name: Compile 21 | run: armorcore/Kinc/make --from armorcore ios -g metal 22 | -------------------------------------------------------------------------------- /.github/workflows/linux_opengl.yml: -------------------------------------------------------------------------------- 1 | name: Linux (OpenGL) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Apt Update 19 | run: sudo apt-get update 20 | - name: Apt Install 21 | run: sudo apt-get install libasound2-dev libxrandr-dev libxinerama-dev libgl1-mesa-dev libxi-dev libxcursor-dev libudev-dev libgtk-3-dev --yes --quiet 22 | - name: Get Submodules 23 | run: git submodule update --init --recursive 24 | - name: Compile 25 | run: armorcore/Kinc/make --from armorcore -g opengl --compiler clang 26 | -------------------------------------------------------------------------------- /.github/workflows/linux_vulkan.yml: -------------------------------------------------------------------------------- 1 | name: Linux (Vulkan) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Apt Update 19 | run: sudo apt-get update 20 | - name: Apt Install 21 | run: sudo apt-get install libasound2-dev libxrandr-dev libxinerama-dev libgl1-mesa-dev libxi-dev libxcursor-dev libudev-dev libgtk-3-dev --yes --quiet 22 | - name: Get Submodules 23 | run: git submodule update --init --recursive 24 | - name: Clone krafix 25 | run: git clone --recursive https://github.com/armory3d/glsl_to_spirv armorcore/Libraries/glsl_to_spirv 26 | - name: Compile 27 | run: armorcore/Kinc/make --from armorcore -g vulkan --compiler clang 28 | -------------------------------------------------------------------------------- /.github/workflows/macos_metal.yml: -------------------------------------------------------------------------------- 1 | name: macOS (Metal) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: macOS-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Get Submodules 19 | run: git submodule update --init --recursive 20 | - name: Compile 21 | run: armorcore/Kinc/make --from armorcore -g metal 22 | -------------------------------------------------------------------------------- /.github/workflows/windows_direct3d11.yml: -------------------------------------------------------------------------------- 1 | name: Windows (Direct3D11) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: windows-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Get Submodules 19 | run: git submodule update --init --recursive 20 | - name: Compile 21 | run: armorcore/Kinc/make --from armorcore -g direct3d11 22 | -------------------------------------------------------------------------------- /.github/workflows/windows_direct3d12.yml: -------------------------------------------------------------------------------- 1 | name: Windows (Direct3D12) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: windows-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Get Submodules 19 | run: git submodule update --init --recursive 20 | - name: Compile 21 | run: armorcore/Kinc/make --from armorcore -g direct3d12 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | .vscode 3 | *.DS_Store 4 | *.blend1 5 | Assets/models/*.onnx 6 | Assets/models/*.txt 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "armorcore"] 2 | path = armorcore 3 | url = https://github.com/armory3d/armorcore 4 | shallow = true 5 | [submodule "Libraries/armorbase"] 6 | path = Libraries/armorbase 7 | url = https://github.com/armory3d/armorbase 8 | [submodule "Libraries/zui"] 9 | path = Libraries/zui 10 | url = https://github.com/armory3d/zui 11 | [submodule "Libraries/iron"] 12 | path = Libraries/iron 13 | url = https://github.com/armory3d/iron 14 | [submodule "Libraries/plugins"] 15 | path = Libraries/plugins 16 | url = https://github.com/armory3d/armorlab_plugins 17 | -------------------------------------------------------------------------------- /Assets/Scene.arm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/armorlab/91f3f3c3c5bc14a70d4129f6c997b6a2b3c53d58/Assets/Scene.arm -------------------------------------------------------------------------------- /Assets/default_brush.arm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/armorlab/91f3f3c3c5bc14a70d4129f6c997b6a2b3c53d58/Assets/default_brush.arm -------------------------------------------------------------------------------- /Assets/export_presets/base_color.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": [ 3 | { "name": "base", "channels": ["base_r", "base_g", "base_b", "1.0"], "color_space": "linear" } 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /Assets/export_presets/generic.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": [ 3 | { "name": "base", "channels": ["base_r", "base_g", "base_b", "1.0"], "color_space": "linear" }, 4 | { "name": "nor", "channels": ["nor_r", "nor_g", "nor_b", "1.0"], "color_space": "linear" }, 5 | { "name": "occ", "channels": ["occ", "occ", "occ", "1.0"], "color_space": "linear" }, 6 | { "name": "rough", "channels": ["rough", "rough", "rough", "1.0"], "color_space": "linear" }, 7 | { "name": "height", "channels": ["height", "height", "height", "1.0"], "color_space": "linear" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /Assets/export_presets/minecraft_mer.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": [ 3 | { "name": "", "channels": ["base_r", "base_g", "base_b", "1.0"], "color_space": "linear" }, 4 | { "name": "normal", "channels": ["nor_r", "nor_g", "nor_b", "1.0"], "color_space": "linear" }, 5 | { "name": "mer", "channels": ["0.0", "emis", "rough", "1.0"], "color_space": "linear" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /Assets/export_presets/unigine.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": [ 3 | { "name": "base", "channels": ["base_r", "base_g", "base_b", "1.0"], "color_space": "linear" }, 4 | { "name": "nor", "channels": ["nor_r", "nor_g", "0.0", "1.0"], "color_space": "linear" }, 5 | { "name": "sh", "channels": ["0.0", "rough", "0.0", "1.0"], "color_space": "linear" }, 6 | { "name": "occ", "channels": ["occ", "0.0", "0.0", "1.0"], "color_space": "linear" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /Assets/export_presets/unity.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": [ 3 | { "name": "base", "channels": ["base_r", "base_g", "base_b", "1.0"], "color_space": "linear" }, 4 | { "name": "nor", "channels": ["nor_r", "nor_g", "nor_b", "1.0"], "color_space": "linear" }, 5 | { "name": "mos", "channels": ["0.0", "occ", "1.0", "smooth"], "color_space": "linear" }, 6 | { "name": "height", "channels": ["0.0", "height", "0.0", "1.0"], "color_space": "linear" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /Assets/export_presets/unreal.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": [ 3 | { "name": "base", "channels": ["base_r", "base_g", "base_b", "1.0"], "color_space": "linear" }, 4 | { "name": "nor", "channels": ["nor_r", "nor_g_directx", "nor_b", "1.0"], "color_space": "linear" }, 5 | { "name": "orm", "channels": ["occ", "rough", "0.0", "1.0"], "color_space": "linear" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /Assets/export_presets/xplane.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": [ 3 | { "name": "base", "channels": ["base_r", "base_g", "base_b", "opac"], "color_space": "linear" }, 4 | { "name": "nor", "channels": ["nor_r", "nor_g", "metal", "smooth"], "color_space": "linear" } 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /Assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/armorlab/91f3f3c3c5bc14a70d4129f6c997b6a2b3c53d58/Assets/icons.png -------------------------------------------------------------------------------- /Assets/icons2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/armorlab/91f3f3c3c5bc14a70d4129f6c997b6a2b3c53d58/Assets/icons2x.png -------------------------------------------------------------------------------- /Assets/keymap_presets/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_paint": "left", 3 | "action_rotate": "alt+left", 4 | "action_pan": "alt+middle", 5 | "action_zoom": "alt+right", 6 | "rotate_light": "shift+middle", 7 | "rotate_envmap": "ctrl+middle", 8 | "set_clone_source": "alt", 9 | "brush_radius": "f", 10 | "brush_radius_decrease": "[", 11 | "brush_radius_increase": "]", 12 | "brush_ruler": "shift", 13 | "file_new": "ctrl+n", 14 | "file_open": "ctrl+o", 15 | "file_open_recent": "ctrl+shift+o", 16 | "file_save": "ctrl+s", 17 | "file_save_as": "ctrl+shift+s", 18 | "file_reimport_textures": "ctrl+shift+r", 19 | "file_import_assets": "ctrl+i", 20 | "file_export_textures": "ctrl+e", 21 | "file_export_textures_as": "ctrl+shift+e", 22 | "edit_undo": "ctrl+z", 23 | "edit_redo": "ctrl+shift+z", 24 | "edit_prefs": "ctrl+k", 25 | "view_reset": "0", 26 | "view_front": "1", 27 | "view_back": "ctrl+1", 28 | "view_right": "3", 29 | "view_left": "ctrl+3", 30 | "view_top": "7", 31 | "view_bottom": "ctrl+7", 32 | "view_camera_type": "5", 33 | "view_orbit_left": "4", 34 | "view_orbit_right": "6", 35 | "view_orbit_up": "8", 36 | "view_orbit_down": "2", 37 | "view_orbit_opposite": "9", 38 | "view_zoom_in": "", 39 | "view_zoom_out": "", 40 | "view_distract_free": "f11", 41 | "viewport_mode": "ctrl+m", 42 | "toggle_node_editor": "tab", 43 | "toggle_browser": "`", 44 | "node_search": "space" 45 | } 46 | -------------------------------------------------------------------------------- /Assets/licenses/license.md: -------------------------------------------------------------------------------- 1 | # The zlib/libpng License 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 4 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 5 | The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 6 | Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 7 | This notice may not be removed or altered from any source distribution. 8 | 9 | https://github.com/armory3d/armorlab 10 | -------------------------------------------------------------------------------- /Assets/licenses/license_esrgan.md: -------------------------------------------------------------------------------- 1 | real-esrgan-license: 2 | https://github.com/xinntao/Real-ESRGAN 3 | 4 | BSD 3-Clause License 5 | 6 | Copyright (c) 2021, Xintao Wang 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | 3. Neither the name of the copyright holder nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /Assets/licenses/license_onnx.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | https://github.com/microsoft/onnxruntime 24 | -------------------------------------------------------------------------------- /Assets/licenses/license_photo_to_pbr.md: -------------------------------------------------------------------------------- 1 | 'Photo to PBR' node is based on work by: 2 | 3 | https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix 4 | 5 | Copyright (c) 2017, Jun-Yan Zhu and Taesung Park 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | --------------------------- LICENSE FOR pix2pix -------------------------------- 30 | BSD License 31 | 32 | For pix2pix software 33 | Copyright (c) 2016, Phillip Isola and Jun-Yan Zhu 34 | All rights reserved. 35 | 36 | Redistribution and use in source and binary forms, with or without 37 | modification, are permitted provided that the following conditions are met: 38 | 39 | * Redistributions of source code must retain the above copyright notice, this 40 | list of conditions and the following disclaimer. 41 | 42 | * Redistributions in binary form must reproduce the above copyright notice, 43 | this list of conditions and the following disclaimer in the documentation 44 | and/or other materials provided with the distribution. 45 | -------------------------------------------------------------------------------- /Assets/licenses/license_texsynth.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Embark Studios 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | 27 | https://github.com/EmbarkStudios/texture-synthesis 28 | -------------------------------------------------------------------------------- /Assets/locale/tools/extract_locales.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Extracts localizable strings from a set of source files and writes them to JSON files. 3 | # This script can create new translations or update existing ones. 4 | # Usage: ./extract_locales.py 5 | 6 | import fnmatch 7 | import json 8 | import os 9 | import sys 10 | from typing import Any, Dict, IO, List 11 | from typing_extensions import Final 12 | 13 | unique_str: List[str] = [] 14 | 15 | 16 | def process_file(f: IO[Any], fname: str, template_data: Dict[str, str]) -> None: 17 | line = f.readline() 18 | while line: 19 | patterns = ['tr("'] 20 | idx = 0 21 | pos = 0 22 | while pos >= 0: 23 | pos = line.find(patterns[idx], pos) 24 | if pos == -1: 25 | if idx < len(patterns) - 1: 26 | idx += 1 27 | pos = 0 28 | continue 29 | pos += len(patterns[idx]) 30 | 31 | msg = "" 32 | while pos < len(line) and (line[pos] != '"' or line[pos - 1] == "\\"): 33 | msg += line[pos] 34 | pos += 1 35 | 36 | # Only add each unique string once. 37 | if msg not in unique_str: 38 | # Empty keys are considered untranslated by the i18n library. 39 | # Fix newlines so they're not escaped anymore. Otherwise, 40 | # they won't match the source strings. 41 | template_data[msg.replace("\\n", "\n")] = "" 42 | unique_str.append(msg) 43 | 44 | line = f.readline() 45 | 46 | 47 | def main() -> None: 48 | if len(sys.argv) != 2: 49 | sys.exit(f"Usage: {sys.argv[0]} ") 50 | 51 | # Change to the directory where the script is located, 52 | # so that the script can be run from any location. 53 | os.chdir(os.path.dirname(os.path.realpath(__file__)) + "/../../..") 54 | 55 | output_path: Final = f"Assets/locale/{sys.argv[1]}.json" 56 | 57 | if not os.path.exists("Sources"): 58 | sys.exit( 59 | "ERROR: Couldn't find the Sources folder in the folder where this script is located." 60 | ) 61 | 62 | matches: List[str] = [] 63 | for folder in ["Sources", "Libraries"]: 64 | for root, dirnames, filenames in os.walk(folder): 65 | dirnames[:] = [d for d in dirnames] 66 | for filename in fnmatch.filter(filenames, "*.hx"): 67 | matches.append(os.path.join(root, filename)) 68 | matches.sort() 69 | 70 | template_data: Dict[str, str] = {} 71 | for filename in matches: 72 | with open(filename, "r", encoding="utf8") as f: 73 | # Read source files for localizable strings. 74 | process_file(f, filename, template_data) 75 | 76 | if os.path.exists(output_path): 77 | print(f'Updating the translation at "{output_path}"...') 78 | with open(output_path, "r", encoding="utf8") as f: 79 | existing_data = json.loads(f.read()) 80 | # Remove obsolete translations (i.e. translations that are no longer 81 | # present in the generated data). 82 | existing_data_no_obsolete = { 83 | key: value 84 | for key, value in existing_data.items() 85 | if key in template_data 86 | } 87 | # Merge existing data with the generated template data (so we keep 88 | # existing translations). 89 | template_data = {**template_data, **existing_data_no_obsolete} 90 | 91 | with open(output_path, "w", encoding="utf8") as f: 92 | json.dump(template_data, f, ensure_ascii=False, indent=4) 93 | else: 94 | print(f'Creating new translation template at "{output_path}"...') 95 | with open(output_path, "w", encoding="utf8") as f: 96 | json.dump(template_data, f, ensure_ascii=False, indent=4) 97 | 98 | 99 | if __name__ == "__main__": 100 | main() 101 | -------------------------------------------------------------------------------- /Assets/meshes/keepme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/armorlab/91f3f3c3c5bc14a70d4129f6c997b6a2b3c53d58/Assets/meshes/keepme.txt -------------------------------------------------------------------------------- /Assets/models/README.md: -------------------------------------------------------------------------------- 1 | 2 | Unpack `models.zip` from https://github.com/armory3d/armorai/releases using 7-Zip - Extract Here. 3 | -------------------------------------------------------------------------------- /Assets/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/armorlab/91f3f3c3c5bc14a70d4129f6c997b6a2b3c53d58/Assets/placeholder.png -------------------------------------------------------------------------------- /Assets/plugins/autosave.js: -------------------------------------------------------------------------------- 1 | 2 | let plugin = new arm.Plugin(); 3 | 4 | let h1 = new zui.Handle(); 5 | let h2 = new zui.Handle({value: 5}); 6 | let timer = 0.0; 7 | 8 | plugin.drawUI = function(ui) { 9 | if (ui.panel(h1, "Auto Save")) { 10 | ui.slider(h2, "min", 1, 15, false, 1); 11 | } 12 | } 13 | 14 | plugin.update = function() { 15 | if (arm.Project.filepath == "") return; 16 | timer += 1 / 60; 17 | if (timer >= h2.value * 60) { 18 | timer = 0.0; 19 | arm.Project.projectSave(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Assets/plugins/dev/texture_breakdown.js: -------------------------------------------------------------------------------- 1 | 2 | let plugin = new arm.Plugin(); 3 | let h1 = new zui.Handle(); 4 | let h2 = new zui.Handle(); 5 | 6 | let slots = ["base", "occ", "rough", "nor"]; 7 | let breakdown = null; 8 | 9 | plugin.drawUI = function(ui) { 10 | if (ui.panel(h1, "Texture Breakdown")) { 11 | 12 | ui.g.end(); 13 | drawBreakdown(); 14 | ui.g.begin(false); 15 | 16 | // ui.row([1 / 4]); 17 | // ui.combo(h2, ["Material", "Viewport"], "Type"); 18 | 19 | ui.image(breakdown); 20 | if (ui.isHovered && ui.inputReleasedR) { 21 | let x = ui.inputX - ui._windowX; 22 | let w = ui._windowW / slots.length; 23 | let i = (x / w) | 0; 24 | arm.UIMenu.draw(function(ui) { 25 | ui.text(slots[i], 2, ui.t.HIGHLIGHT_COL); 26 | if (ui.button("Delete", 0)) { 27 | slots.splice(i, 1); 28 | } 29 | }, 2); 30 | } 31 | 32 | ui.row([1 / 4, 1 / 4]); 33 | 34 | if (ui.button("Add")) { 35 | arm.UIMenu.draw(function(ui) { 36 | ui.text("Channel", 2, ui.t.HIGHLIGHT_COL); 37 | if (ui.button("Base Color", 0)) { slots.push("base"); } 38 | if (ui.button("Occlusion", 0)) { slots.push("occ"); } 39 | if (ui.button("Roughness", 0)) { slots.push("rough"); } 40 | if (ui.button("Metallic", 0)) { slots.push("metal"); } 41 | if (ui.button("Normal Map", 0)) { slots.push("nor"); } 42 | }, 6); 43 | } 44 | 45 | if (ui.button("Export")) { 46 | arm.UIFiles.show("png", true, false, function(path) { 47 | arm.App.notifyOnNextFrame(function() { 48 | var f = arm.UIFiles.filename; 49 | if (f === "") f = "untitled"; 50 | if (!f.endsWith(".png")) f += ".png"; 51 | Krom.writePng(path + arm.Path.sep + f, breakdown.getPixels().b.buffer, breakdown.get_width(), breakdown.get_height(), 2); 52 | }); 53 | }); 54 | } 55 | } 56 | } 57 | 58 | function drawBreakdown(type) { 59 | if (breakdown === null) { 60 | breakdown = core.Image.createRenderTarget(4096, 4096); 61 | } 62 | let g2 = breakdown.get_g2(); 63 | g2.begin(true, 0xff000000); 64 | g2.disableScissor(); 65 | 66 | if (h2.position === 0) { // Material 67 | var lay = arm.BrushOutputNode.inst; 68 | for (let i = 0; i < slots.length; ++i) { 69 | g2.set_pipeline(arm.UIView2D.pipe); 70 | let image = lay.texpaint; 71 | let channel = 0; 72 | if (slots[i] === "occ") { 73 | image = lay.texpaint_pack; 74 | channel = 1; 75 | } 76 | else if (slots[i] === "rough") { 77 | image = lay.texpaint_pack; 78 | channel = 2; 79 | } 80 | else if (slots[i] === "metal") { 81 | image = lay.texpaint_pack; 82 | channel = 3; 83 | } 84 | else if (slots[i] === "nor") { 85 | image = lay.texpaint_nor; 86 | channel = 5; 87 | } 88 | breakdown.get_g4().setInt(arm.UIView2D.channelLocation, channel); 89 | var step_source = image.get_width() / slots.length; 90 | var step_dest = breakdown.get_width() / slots.length; 91 | g2.drawScaledSubImage(image, step_source * i, 0, step_source, image.get_height(), step_dest * i, 0, step_dest, breakdown.get_height()); 92 | g2.flush(); 93 | } 94 | } 95 | else { // Viewport 96 | } 97 | 98 | g2.end(); 99 | } 100 | -------------------------------------------------------------------------------- /Assets/plugins/embed/import_svg.js: -------------------------------------------------------------------------------- 1 | 2 | let a = Krom_import_svg; 3 | class R { 4 | get buffer() { return Krom_import_svg._buffer(); } 5 | } 6 | let r = new R(); 7 | 8 | // import_svg.js 9 | let import_svg = function(path, done) { 10 | iron.Data.getBlob(path, function(b) { 11 | let buf = new Uint8Array(r.buffer, a._init(b.bytes.length + 1), b.bytes.length + 1); 12 | for (let i = 0; i < b.bytes.length; ++i) buf[i] = b.readU8(i); 13 | buf[b.bytes.length] = 0; 14 | 15 | a._parse(); 16 | let w = a._get_pixels_w(); 17 | let h = a._get_pixels_h(); 18 | let pixels = r.buffer.slice(a._get_pixels(), a._get_pixels() + w * h * 4); 19 | let image = core.Image.fromBytes(core.Bytes.ofData(pixels), w, h); 20 | done(image); 21 | 22 | a._destroy(); 23 | iron.Data.deleteBlob(path); 24 | }); 25 | } 26 | 27 | let plugin = new arm.Plugin(); 28 | let formats = arm.Path.textureFormats; 29 | let importers = arm.Path.textureImporters; 30 | formats.push("svg"); 31 | importers.h["svg"] = import_svg; 32 | 33 | plugin.delete = function() { 34 | formats.splice(formats.indexOf("svg"), 1); 35 | importers.h["svg"] = null; 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /Assets/plugins/hello_node_brush.js: -------------------------------------------------------------------------------- 1 | 2 | let plugin = new arm.Plugin(); 3 | 4 | let categoryName = "My Nodes"; 5 | let nodeName = "Hello World"; 6 | let nodeType = "HELLO_WORLD"; 7 | 8 | // Create new node category 9 | let categories = arm.NodesBrush.categories; 10 | categories.push(categoryName); 11 | 12 | // Create new node 13 | let nodes = [ 14 | { 15 | id: 0, 16 | name: nodeName, 17 | type: nodeType, 18 | x: 0, 19 | y: 0, 20 | color: 0xffb34f5a, 21 | inputs: [ 22 | { 23 | id: 0, 24 | node_id: 0, 25 | name: "Scale", 26 | type: "VALUE", 27 | color: 0xffa1a1a1, 28 | default_value: 1.0, 29 | min: 0.0, 30 | max: 5.0 31 | } 32 | ], 33 | outputs: [ 34 | { 35 | id: 0, 36 | node_id: 0, 37 | name: "Value", 38 | type: "VALUE", 39 | color: 0xffc7c729, 40 | default_value: 1.0 41 | } 42 | ], 43 | buttons: [] 44 | } 45 | ]; 46 | arm.NodesBrush.list.push(nodes); 47 | 48 | // Brush node 49 | arm.Brush.customNodes.set(nodeType, function(node, from) { 50 | return Math.sin(core.Scheduler.time() * node.inputs[0].get(0)); 51 | }); 52 | 53 | // Cleanup 54 | plugin.delete = function() { 55 | arm.Brush.customNodes.delete(nodeType); 56 | arm.NodesBrush.list.splice(arm.NodesBrush.list.indexOf(nodes), 1); 57 | categories.splice(categories.indexOf(categoryName), 1); 58 | }; 59 | -------------------------------------------------------------------------------- /Assets/plugins/hello_world.js: -------------------------------------------------------------------------------- 1 | 2 | let plugin = new arm.Plugin(); 3 | 4 | let h1 = new zui.Handle(); 5 | let h2 = new zui.Handle(); 6 | let h3 = new zui.Handle(); 7 | let h4 = new zui.Handle(); 8 | let h5 = new zui.Handle(); 9 | let h6 = new zui.Handle(); 10 | let h7 = new zui.Handle(); 11 | 12 | plugin.drawUI = function(ui) { 13 | if (ui.panel(h1, "My Plugin")) { 14 | ui.text("Label"); 15 | ui.textInput(h7, "Text Input"); 16 | if (ui.button("Button")) { 17 | console.error("Hello"); 18 | } 19 | ui.row([1/2, 1/2]); 20 | ui.button("Button A"); 21 | ui.button("Button B"); 22 | ui.combo(h5, ["Item 1", "Item 2"], "Combo"); 23 | ui.row([1/2, 1/2]); 24 | ui.slider(h2, "Slider", 0, 1, true); 25 | ui.slider(h3, "Slider", 0, 1, true); 26 | ui.check(h4, "Check"); 27 | ui.radio(h6, 0, "Radio 1"); 28 | ui.radio(h6, 1, "Radio 2"); 29 | ui.radio(h6, 2, "Radio 3"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Assets/plugins/import_txt.js: -------------------------------------------------------------------------------- 1 | 2 | let import_txt = function(path, done) { 3 | iron.Data.getBlob(path, function(b) { 4 | var filename = path.split('\\').pop().split('/').pop(); 5 | arm.UIBox.showMessage(filename, b.toString(), true); 6 | arm.UIBox.clickToHide = false; 7 | iron.Data.deleteBlob(path); 8 | }); 9 | } 10 | 11 | let plugin = new arm.Plugin(); 12 | let formats = arm.Path.textureFormats; 13 | let importers = arm.Path.textureImporters; 14 | formats.push("txt"); 15 | importers.h["txt"] = import_txt; 16 | 17 | plugin.delete = function() { 18 | formats.splice(formats.indexOf("txt"), 1); 19 | importers.h["txt"] = null; 20 | }; 21 | -------------------------------------------------------------------------------- /Assets/plugins/wasm/import_svg.js: -------------------------------------------------------------------------------- 1 | 2 | let Module = {}; 3 | Module["instantiateWasm"] = function(imports, successCallback) { 4 | let wasmbin = Krom.loadBlob("data/plugins/import_svg.wasm"); 5 | let module = new WebAssembly.Module(wasmbin); 6 | let inst = new WebAssembly.Instance(module, imports); 7 | successCallback(inst); 8 | return inst.exports; 9 | }; 10 | Module.print = console.log; 11 | Module.printErr = console.log; 12 | 13 | // emscripten-generated glue 14 | var a;a||(a=typeof Module !== 'undefined' ? Module : {});var g={},h;for(h in a)a.hasOwnProperty(h)&&(g[h]=a[h]);var k;k=function(b){if("function"===typeof readbuffer)return new Uint8Array(readbuffer(b));b=read(b,"binary");"object"===typeof b||m("Assertion failed: undefined");return b};"undefined"!==typeof print&&("undefined"===typeof console&&(console={}),console.log=print,console.warn=console.error="undefined"!==typeof printErr?printErr:print); 15 | var n=a.print||console.log.bind(console),p=a.printErr||console.warn.bind(console);for(h in g)g.hasOwnProperty(h)&&(a[h]=g[h]);g=null;var q;a.wasmBinary&&(q=a.wasmBinary);var noExitRuntime;a.noExitRuntime&&(noExitRuntime=a.noExitRuntime);"object"!==typeof WebAssembly&&p("no native wasm support detected");var r,t=new WebAssembly.Table({initial:2,maximum:2,element:"anyfunc"}),u=!1,v,w,x; 16 | function y(b){v=b;a.HEAP8=new Int8Array(b);a.HEAP16=new Int16Array(b);a.HEAP32=x=new Int32Array(b);a.HEAPU8=w=new Uint8Array(b);a.HEAPU16=new Uint16Array(b);a.HEAPU32=new Uint32Array(b);a.HEAPF32=new Float32Array(b);a.HEAPF64=new Float64Array(b)}var z=a.INITIAL_MEMORY||16777216;a.wasmMemory?r=a.wasmMemory:r=new WebAssembly.Memory({initial:z/65536,maximum:32768});r&&(v=r.buffer);z=v.byteLength;y(v);x[1492]=5249008; 17 | function A(b){for(;0>>=0;var c=w.length;if(2147483648=d;d*=2){var f=c*(1+.2/d);f=Math.min(f,b+100663296);f=Math.max(16777216,b,f);0>>16);y(r.buffer);var e=1;break a}catch(l){}e=void 0}if(e)return!0}return!1},memory:r,table:t},P=function(){function b(e){a.asm=e.exports;G--;a.monitorRunDependencies&&a.monitorRunDependencies(G);0==G&&(null!==H&&(clearInterval(H), 20 | H=null),I&&(e=I,I=null,e()))}function c(e){b(e.instance)}function d(e){return M().then(function(l){return WebAssembly.instantiate(l,f)}).then(e,function(l){p("failed to asynchronously prepare wasm: "+l);m(l)})}var f={a:O};G++;a.monitorRunDependencies&&a.monitorRunDependencies(G);if(a.instantiateWasm)try{return a.instantiateWasm(f,b)}catch(e){return p("Module.instantiateWasm callback failed with error: "+e),!1}(function(){if(q||"function"!==typeof WebAssembly.instantiateStreaming||J()||"function"!== 21 | typeof fetch)return d(c);fetch(K,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,f).then(c,function(l){p("wasm streaming compile failed: "+l);p("falling back to ArrayBuffer instantiation");d(c)})})})();return{}}();a.asm=P;var N=a.___wasm_call_ctors=function(){return(N=a.___wasm_call_ctors=a.asm.c).apply(null,arguments)};a._init=function(){return(a._init=a.asm.d).apply(null,arguments)};a._parse=function(){return(a._parse=a.asm.e).apply(null,arguments)}; 22 | a._get_pixels=function(){return(a._get_pixels=a.asm.f).apply(null,arguments)};a._get_pixels_w=function(){return(a._get_pixels_w=a.asm.g).apply(null,arguments)};a._get_pixels_h=function(){return(a._get_pixels_h=a.asm.h).apply(null,arguments)};a._destroy=function(){return(a._destroy=a.asm.i).apply(null,arguments)};a.asm=P;var Q;I=function R(){Q||S();Q||(I=R)}; 23 | function S(){function b(){if(!Q&&(Q=!0,a.calledRun=!0,!u)){A(C);A(D);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;){var c=a.postRun.shift();E.unshift(c)}A(E)}}if(!(0 76 | # Generates an `Assets/locale/.json` file 77 | ``` 78 | 79 | **Release builds** *Optional, used for best performance* 80 | ```bash 81 | # Compile krom.js using the closure compiler 82 | https://developers.google.com/closure/compiler 83 | # Generate a v8 snapshot file 84 | export ARM_SNAPSHOT=1 85 | armorcore/Kinc/make --from armorcore -g api 86 | ./ArmorLab . --snapshot 87 | # Generates a `krom.bin` file from `krom.js` file 88 | ``` 89 | -------------------------------------------------------------------------------- /Shaders/Material_mesh.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #include "../Libraries/armorbase/Shaders/std/gbuffer.glsl" 3 | in vec2 texCoord; 4 | in vec3 wnormal; 5 | in vec4 wvpposition; 6 | in vec4 prevwvpposition; 7 | out vec4 fragColor[3]; 8 | void main() { 9 | vec3 n = normalize(wnormal); 10 | vec3 basecol; 11 | float roughness; 12 | float metallic; 13 | float occlusion; 14 | float emission; 15 | float Mix_fac = 0.0; 16 | vec3 RGB_Color_res = vec3(0.2176000028848648, 0.2176000028848648, 0.2176000028848648); 17 | vec3 Mix_Color_res = RGB_Color_res + texCoord.x * 0.0000001; // keep texCoord 18 | basecol = Mix_Color_res; 19 | roughness = 0.4000000059604645; 20 | metallic = 0.0; 21 | occlusion = 1.0; 22 | emission = 0.0; 23 | n /= (abs(n.x) + abs(n.y) + abs(n.z)); 24 | n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy); 25 | uint matid = 0; 26 | if (emission > 0) { basecol *= emission; matid = 1; } 27 | fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, matid)); 28 | fragColor[1] = vec4(basecol, occlusion); 29 | vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5; 30 | vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5; 31 | fragColor[2].rg = vec2(posa - posb); 32 | } 33 | -------------------------------------------------------------------------------- /Shaders/Material_mesh.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | in vec4 pos; 3 | in vec2 nor; 4 | in vec2 tex; 5 | out vec2 texCoord; 6 | out vec3 wnormal; 7 | out vec4 wvpposition; 8 | out vec4 prevwvpposition; 9 | uniform mat3 N; 10 | uniform mat4 WVP; 11 | uniform float texUnpack; 12 | uniform mat4 prevWVP; 13 | void main() { 14 | vec4 spos = vec4(pos.xyz, 1.0); 15 | texCoord = tex * texUnpack; 16 | wnormal = normalize(N * vec3(nor.xy, pos.w)); 17 | gl_Position = WVP * spos; 18 | wvpposition = gl_Position; 19 | prevwvpposition = prevWVP * spos; 20 | } 21 | -------------------------------------------------------------------------------- /Shaders/layer_copy.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform sampler2D tex; 3 | in vec2 texCoord; 4 | in vec4 color; 5 | out vec4 FragColor; 6 | void main() { 7 | FragColor = textureLod(tex, texCoord, 0).rgba * color; 8 | } 9 | -------------------------------------------------------------------------------- /Shaders/layer_copy_bgra.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform sampler2D tex; 3 | in vec2 texCoord; 4 | in vec4 color; 5 | out vec4 FragColor; 6 | void main() { 7 | FragColor = textureLod(tex, texCoord, 0).bgra * color; 8 | } 9 | -------------------------------------------------------------------------------- /Shaders/layer_copy_rrrr.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform sampler2D tex; 3 | in vec2 texCoord; 4 | out vec4 FragColor; 5 | void main() { 6 | FragColor = textureLod(tex, texCoord, 0).rrrr; 7 | } 8 | -------------------------------------------------------------------------------- /Shaders/layer_copy_rrrr.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | in vec2 pos; 3 | out vec2 texCoord; 4 | void main() { 5 | gl_Position = vec4(pos.xy, 0.0, 1.0); 6 | const vec2 madd = vec2(0.5, 0.5); 7 | texCoord = pos.xy * madd + madd; 8 | #if defined(HLSL) || defined(METAL) || defined(SPIRV) 9 | texCoord.y = 1.0 - texCoord.y; 10 | #endif 11 | } 12 | -------------------------------------------------------------------------------- /Shaders/layer_view.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | in vec3 pos; 3 | in vec2 tex; 4 | in vec4 col; 5 | uniform mat4 projectionMatrix; 6 | out vec2 texCoord; 7 | out vec4 color; 8 | void main() { 9 | gl_Position = projectionMatrix * vec4(pos, 1.0); 10 | texCoord = tex; 11 | color = col; 12 | } 13 | -------------------------------------------------------------------------------- /Shaders/mask_apply.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform sampler2D tex0; 3 | uniform sampler2D texa; 4 | in vec2 texCoord; 5 | out vec4 FragColor; 6 | void main() { 7 | vec4 col0 = textureLod(tex0, texCoord, 0); 8 | float mask = textureLod(texa, texCoord, 0).r; 9 | FragColor = vec4(col0.rgb, col0.a * mask); 10 | } 11 | -------------------------------------------------------------------------------- /Sources/Main.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import kha.Window; 4 | import kha.System; 5 | import iron.object.Object; 6 | import iron.Scene; 7 | import iron.RenderPath; 8 | import arm.render.Inc; 9 | import arm.render.RenderPathForward; 10 | import arm.render.RenderPathDeferred; 11 | import arm.render.Uniforms; 12 | import arm.sys.BuildMacros; 13 | import arm.Config; 14 | import arm.Context; 15 | import arm.Res; 16 | 17 | class Main { 18 | 19 | public static inline var title = "ArmorLab"; 20 | public static var version = "0.1"; 21 | public static var sha = BuildMacros.sha().substr(1, 7); 22 | public static var date = BuildMacros.date().split(" ")[0]; 23 | static var tasks: Int; 24 | 25 | public static function main() { 26 | #if arm_snapshot 27 | 28 | var global = js.Syntax.code("globalThis"); 29 | global.kickstart = kickstart; 30 | 31 | Res.embedRaw("Scene", "Scene.arm", untyped global['data/Scene.arm']); 32 | untyped global['data/Scene.arm'] = null; 33 | 34 | Res.embedRaw("shader_datas", "shader_datas.arm", untyped global['data/shader_datas.arm']); 35 | untyped global['data/shader_datas.arm'] = null; 36 | 37 | Res.embedFont("font.ttf", untyped global['data/font.ttf']); 38 | untyped global['data/font.ttf'] = null; 39 | 40 | Res.embedFont("font_mono.ttf", untyped global['data/font_mono.ttf']); 41 | untyped global['data/font_mono.ttf'] = null; 42 | 43 | var files = [ 44 | "font13.bin", 45 | "ltc_mag.arm", 46 | "ltc_mat.arm", 47 | "default_brush.arm", 48 | "World_irradiance.arm", 49 | "World_radiance.k", 50 | "World_radiance_0.k", 51 | "World_radiance_1.k", 52 | "World_radiance_2.k", 53 | "World_radiance_3.k", 54 | "World_radiance_4.k", 55 | "World_radiance_5.k", 56 | "World_radiance_6.k", 57 | "World_radiance_7.k", 58 | "World_radiance_8.k", 59 | "brdf.k", 60 | "color_wheel.k", 61 | "black_white_gradient.k", 62 | "cursor.k", 63 | "icons.k", 64 | "icons2x.k", 65 | "placeholder.k", 66 | "noise256.k", 67 | "smaa_search.k", 68 | "smaa_area.k" 69 | ]; 70 | 71 | for (file in files) { 72 | Res.embedBlob(file, untyped global['data/' + file]); 73 | untyped global['data/' + file] = null; 74 | } 75 | 76 | #if (kha_direct3d12 || kha_vulkan) 77 | 78 | var ext = #if kha_direct3d12 ".cso" #else ".spirv" #end ; 79 | 80 | var files_renderlib = [ 81 | "bnoise_rank.k", 82 | "bnoise_scramble.k", 83 | "bnoise_sobol.k", 84 | "raytrace_brute_core" + ext, 85 | "raytrace_brute_full" + ext 86 | ]; 87 | 88 | for (file in files_renderlib) { 89 | Res.embedBlob(file, untyped global['data/' + file]); 90 | untyped global['data/' + file] = null; 91 | } 92 | 93 | #end 94 | 95 | #end // arm_snapshot 96 | 97 | #if (!arm_snapshot) 98 | iron.data.ShaderData.shaderPath = ""; // Use arm_data_dir 99 | kickstart(); 100 | #end 101 | } 102 | 103 | @:keep 104 | public static function kickstart() { 105 | // Used to locate external application data folder 106 | Krom.setApplicationName(Main.title); 107 | 108 | tasks = 1; 109 | tasks++; Config.load(function() { tasks--; start(); }); 110 | tasks--; start(); 111 | } 112 | 113 | static function start() { 114 | if (tasks > 0) return; 115 | 116 | Config.init(); 117 | System.start(Config.getOptions(), function(window: Window) { 118 | if (Config.raw.layout == null) arm.App.initLayout(); 119 | Krom.setApplicationName(Main.title); 120 | iron.App.init(function() { 121 | Scene.setActive("Scene", function(o: Object) { 122 | Uniforms.init(); 123 | var path = new RenderPath(); 124 | Inc.init(path); 125 | 126 | if (Context.renderMode == RenderForward) { 127 | RenderPathDeferred.init(path); // Allocate gbuffer 128 | RenderPathForward.init(path); 129 | path.commands = RenderPathForward.commands; 130 | } 131 | else { 132 | RenderPathDeferred.init(path); 133 | path.commands = RenderPathDeferred.commands; 134 | } 135 | 136 | RenderPath.setActive(path); 137 | new arm.App(); 138 | }); 139 | }); 140 | }); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Sources/arm/Args.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | import arm.sys.Path; 4 | import arm.sys.File; 5 | import arm.io.ExportTexture; 6 | import arm.io.ImportAsset; 7 | import arm.io.ImportArm; 8 | import arm.ui.BoxExport; 9 | import arm.Enums; 10 | 11 | class Args { 12 | 13 | static var useArgs = false; 14 | static var assetPath = ""; 15 | static var exportTextures = false; 16 | static var exportTexturesType = ""; 17 | static var exportTexturesPreset = ""; 18 | static var exportTexturesPath = ""; 19 | static var background = false; 20 | 21 | public static function parse() { 22 | if (Krom.getArgCount() > 1) { 23 | useArgs = true; 24 | 25 | var i = 0; 26 | while (i < Krom.getArgCount()) { 27 | // Process each arg 28 | var currentArg = Krom.getArg(i); 29 | if (Path.isProject(currentArg)) { 30 | Project.filepath = currentArg; 31 | } 32 | else if (Path.isTexture(currentArg)) { 33 | assetPath = currentArg; 34 | } 35 | else if (currentArg == "--export-textures" && (i + 3) <= Krom.getArgCount()) { 36 | exportTextures = true; 37 | ++i; 38 | exportTexturesType = Krom.getArg(i); 39 | ++i; 40 | exportTexturesPreset = Krom.getArg(i); 41 | ++i; 42 | exportTexturesPath = Krom.getArg(i); 43 | } 44 | else if (currentArg == "--b" || currentArg == "--background") { 45 | background = true; 46 | } 47 | ++i; 48 | } 49 | } 50 | } 51 | 52 | public static function run() { 53 | if (useArgs) { 54 | iron.App.notifyOnInit(function() { 55 | if (Project.filepath != "") { 56 | ImportArm.runProject(Project.filepath); 57 | } 58 | else if (assetPath != "") { 59 | ImportAsset.run(assetPath, -1, -1, false); 60 | } 61 | else if (exportTextures) { 62 | if (exportTexturesType == "png" || 63 | exportTexturesType == "jpg") { 64 | if (Path.isFolder(exportTexturesPath)) { 65 | // Applying the correct format type from args 66 | if (exportTexturesType == "png") { 67 | Context.formatType = FormatPng; 68 | } 69 | else if (exportTexturesType == "jpg") { 70 | Context.formatType = FormatJpg; 71 | } 72 | 73 | // Get export preset and apply the correct one from args 74 | BoxExport.files = File.readDirectory(Path.data() + Path.sep + "export_presets"); 75 | for (i in 0...BoxExport.files.length) { 76 | BoxExport.files[i] = BoxExport.files[i].substr(0, BoxExport.files[i].length - 5); // Strip .json 77 | } 78 | 79 | var file = "export_presets/" + BoxExport.files[0] + ".json"; 80 | for (f in BoxExport.files) if (f == exportTexturesPreset) { 81 | file = "export_presets/" + BoxExport.files[BoxExport.files.indexOf(f)] + ".json"; 82 | } 83 | 84 | iron.data.Data.getBlob(file, function(blob: kha.Blob) { 85 | BoxExport.preset = haxe.Json.parse(blob.toString()); 86 | iron.data.Data.deleteBlob("export_presets/" + file); 87 | }); 88 | 89 | // Export queue 90 | function _init() { 91 | ExportTexture.run(exportTexturesPath); 92 | } 93 | iron.App.notifyOnInit(_init); 94 | } 95 | else { 96 | trace("Invalid export directory"); 97 | } 98 | } 99 | else { 100 | trace("Invalid texture type"); 101 | } 102 | } 103 | if (background) kha.System.stop(); 104 | }); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/arm/ConfigFormat.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | typedef TConfig = { 4 | // The locale should be specified in ISO 639-1 format: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes 5 | // "system" is a special case that will use the system locale 6 | @:optional var locale: String; 7 | // Window 8 | @:optional var window_mode: Null; // window, fullscreen 9 | @:optional var window_w: Null; 10 | @:optional var window_h: Null; 11 | @:optional var window_x: Null; 12 | @:optional var window_y: Null; 13 | @:optional var window_resizable: Null; 14 | @:optional var window_maximizable: Null; 15 | @:optional var window_minimizable: Null; 16 | @:optional var window_vsync: Null; 17 | @:optional var window_frequency: Null; 18 | @:optional var window_scale: Null; 19 | // Render path 20 | @:optional var rp_supersample: Null; 21 | @:optional var rp_ssgi: Null; 22 | @:optional var rp_ssr: Null; 23 | @:optional var rp_bloom: Null; 24 | @:optional var rp_motionblur: Null; 25 | @:optional var rp_gi: Null; 26 | @:optional var rp_vignette: Null; 27 | // Application 28 | @:optional var version: String; // ArmorLab version 29 | @:optional var sha: String; // Commit id 30 | @:optional var recent_projects: Array; // Recently opened projects 31 | @:optional var bookmarks: Array; // Bookmarked folders in browser 32 | @:optional var plugins: Array; // List of enabled plugins 33 | @:optional var keymap: String; // Link to keymap file 34 | @:optional var theme: String; // Link to theme file 35 | @:optional var undo_steps: Null; // Number of undo steps to preserve 36 | @:optional var pressure_radius: Null; // Pen pressure controls 37 | @:optional var pressure_sensitivity: Null; 38 | @:optional var wrap_mouse: Null; 39 | @:optional var camera_pan_speed: Null; 40 | @:optional var camera_zoom_speed: Null; 41 | @:optional var camera_rotation_speed: Null; 42 | @:optional var zoom_direction: Null; 43 | @:optional var displace_strength: Null; 44 | @:optional var touch_ui: Null; 45 | @:optional var layout: Array; 46 | @:optional var workspace: Null; 47 | @:optional var layer_res: Null; 48 | @:optional var server: String; 49 | @:optional var gpu_inference: Null; 50 | 51 | @:optional var brush_live: Null; //// 52 | @:optional var brush_3d: Null; //// 53 | } 54 | -------------------------------------------------------------------------------- /Sources/arm/Enums.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | @:enum abstract WorkspaceTool(Int) from Int to Int { 4 | var ToolEraser = 0; 5 | var ToolClone = 1; 6 | var ToolBlur = 2; 7 | var ToolPicker = 3; 8 | } 9 | 10 | @:enum abstract SpaceType(Int) from Int to Int { 11 | var Space3D = 0; 12 | var Space2D = 1; 13 | } 14 | 15 | @:enum abstract SplitType(Int) from Int to Int { 16 | var SplitObject = 0; 17 | var SplitGroup = 1; 18 | var SplitMaterial = 2; 19 | var SplitUdim = 3; 20 | } 21 | 22 | @:enum abstract ViewportMode(Int) from Int to Int { 23 | var ViewLit = 0; 24 | var ViewBaseColor = 1; 25 | var ViewNormalMap = 2; 26 | var ViewOcclusion = 3; 27 | var ViewRoughness = 4; 28 | var ViewMetallic = 5; 29 | var ViewOpacity = 6; 30 | var ViewHeight = 7; 31 | var ViewPathTrace = 8; 32 | } 33 | 34 | @:enum abstract ChannelType(Int) from Int to Int { 35 | var ChannelBaseColor = 0; 36 | var ChannelOcclusion = 1; 37 | var ChannelRoughness = 2; 38 | var ChannelMetallic = 3; 39 | var ChannelNormalMap = 4; 40 | var ChannelHeight = 5; 41 | } 42 | 43 | @:enum abstract RenderMode(Int) from Int to Int { 44 | var RenderDeferred = 0; 45 | var RenderForward = 1; 46 | var RenderPathTrace = 2; 47 | } 48 | 49 | @:enum abstract ExportDestination(Int) from Int to Int { 50 | var DestinationDisk = 0; 51 | var DestinationPacked = 1; 52 | } 53 | 54 | #if (kha_direct3d12 || kha_vulkan) 55 | @:enum abstract PathTraceMode(Int) from Int to Int { 56 | var TraceCore = 0; 57 | var TraceFull = 1; 58 | } 59 | #end 60 | 61 | @:enum abstract CameraControls(Int) from Int to Int { 62 | var ControlsOrbit = 0; 63 | var ControlsRotate = 1; 64 | var ControlsFly = 2; 65 | } 66 | 67 | @:enum abstract CameraType(Int) from Int to Int { 68 | var CameraPerspective = 0; 69 | var CameraOrthographic = 1; 70 | } 71 | 72 | @:enum abstract TextureRes(Int) from Int to Int { 73 | var Res2048 = 0; 74 | var Res4096 = 1; 75 | var Res8192 = 2; 76 | var Res16384 = 3; 77 | var Res128 = 4; // Unused 78 | var Res256 = 5; 79 | var Res512 = 6; 80 | var Res1024 = 7; 81 | } 82 | 83 | @:enum abstract TextureFormatLdr(Int) from Int to Int { 84 | var FormatPng = 0; 85 | var FormatJpg = 1; 86 | } 87 | 88 | @:enum abstract MeshFormat(Int) from Int to Int { 89 | var FormatObj = 0; 90 | var FormatArm = 1; 91 | } 92 | 93 | @:enum abstract MenuCategory(Int) from Int to Int { 94 | var MenuFile = 0; 95 | var MenuEdit = 1; 96 | var MenuViewport = 2; 97 | var MenuMode = 3; 98 | var MenuCamera = 4; 99 | var MenuHelp = 5; 100 | } 101 | 102 | @:enum abstract BorderSide(Int) from Int to Int { 103 | var SideLeft = 0; 104 | var SideRight = 1; 105 | var SideTop = 2; 106 | var SideBottom = 3; 107 | } 108 | 109 | @:enum abstract ProjectModel(Int) from Int to Int { 110 | var ModelRoundedCube = 0; 111 | var ModelSphere = 1; 112 | var ModelTessellatedPlane = 2; 113 | var ModelCustom = 3; 114 | } 115 | 116 | @:enum abstract LayoutSize(Int) from Int to Int { 117 | var LayoutNodesW = 0; 118 | var LayoutStatusH = 1; 119 | } 120 | 121 | @:enum abstract ZoomDirection(Int) from Int to Int { 122 | var ZoomVertical = 0; 123 | var ZoomVerticalInverted = 1; 124 | var ZoomHorizontal = 2; 125 | var ZoomHorizontalInverted = 3; 126 | var ZoomVerticalAndHorizontal = 4; 127 | var ZoomVerticalAndHorizontalInverted = 5; 128 | } 129 | -------------------------------------------------------------------------------- /Sources/arm/History.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | import zui.Nodes; 4 | import arm.sys.Path; 5 | import arm.ui.UIFiles; 6 | import arm.ui.UINodes; 7 | import arm.Enums; 8 | 9 | class History { 10 | 11 | public static var steps: Array; 12 | public static var undoI = 0; // Undo layer 13 | public static var undos = 0; // Undos available 14 | public static var redos = 0; // Redos available 15 | 16 | public static function undo() { 17 | if (undos > 0) { 18 | var active = steps.length - 1 - redos; 19 | var step = steps[active]; 20 | 21 | if (step.name == tr("Edit Nodes")) { 22 | swapCanvas(step); 23 | } 24 | undos--; 25 | redos++; 26 | Context.ddirty = 2; 27 | } 28 | } 29 | 30 | public static function redo() { 31 | if (redos > 0) { 32 | var active = steps.length - redos; 33 | var step = steps[active]; 34 | 35 | if (step.name == tr("Edit Nodes")) { 36 | swapCanvas(step); 37 | } 38 | undos++; 39 | redos--; 40 | Context.ddirty = 2; 41 | } 42 | } 43 | 44 | public static function reset() { 45 | steps = [{name: tr("New")}]; 46 | undos = 0; 47 | redos = 0; 48 | undoI = 0; 49 | } 50 | 51 | public static function editNodes(canvas: TNodeCanvas, canvas_group: Null = null) { 52 | var step = push(tr("Edit Nodes")); 53 | step.canvas_group = canvas_group; 54 | step.canvas = haxe.Json.parse(haxe.Json.stringify(canvas)); 55 | } 56 | 57 | static function push(name: String): TStep { 58 | #if (krom_windows || krom_linux || krom_darwin) 59 | var filename = Project.filepath == "" ? UIFiles.filename : Project.filepath.substring(Project.filepath.lastIndexOf(Path.sep) + 1, Project.filepath.length - 4); 60 | kha.Window.get(0).title = filename + "* - " + Main.title; 61 | #end 62 | 63 | if (undos < Config.raw.undo_steps) undos++; 64 | if (redos > 0) { 65 | for (i in 0...redos) steps.pop(); 66 | redos = 0; 67 | } 68 | 69 | steps.push({ 70 | name: name 71 | }); 72 | 73 | while (steps.length > Config.raw.undo_steps + 1) steps.shift(); 74 | return steps[steps.length - 1]; 75 | } 76 | 77 | static function getCanvasOwner(step: TStep): Dynamic { 78 | return null; 79 | } 80 | 81 | static function swapCanvas(step: TStep) { 82 | var _canvas = getCanvasOwner(step).canvas; 83 | getCanvasOwner(step).canvas = step.canvas; 84 | step.canvas = _canvas; 85 | 86 | UINodes.inst.canvasChanged(); 87 | @:privateAccess UINodes.inst.getNodes().handle = new zui.Zui.Handle(); 88 | UINodes.inst.hwnd.redraws = 2; 89 | } 90 | } 91 | 92 | typedef TStep = { 93 | public var name: String; 94 | @:optional public var canvas: TNodeCanvas; // Node history 95 | @:optional public var canvas_group: Int; 96 | } 97 | -------------------------------------------------------------------------------- /Sources/arm/KeymapFormat.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | typedef TKeymap = { 4 | public var action_paint: String; 5 | public var action_rotate: String; 6 | public var action_pan: String; 7 | public var action_zoom: String; 8 | public var rotate_light: String; 9 | public var rotate_envmap: String; 10 | public var set_clone_source: String; 11 | public var brush_radius: String; 12 | public var brush_radius_decrease: String; 13 | public var brush_radius_increase: String; 14 | public var brush_ruler: String; 15 | public var file_new: String; 16 | public var file_open: String; 17 | public var file_open_recent: String; 18 | public var file_save: String; 19 | public var file_save_as: String; 20 | public var file_reimport_mesh: String; 21 | public var file_reimport_textures: String; 22 | public var file_import_assets: String; 23 | public var file_export_textures: String; 24 | public var file_export_textures_as: String; 25 | public var edit_undo: String; 26 | public var edit_redo: String; 27 | public var edit_prefs: String; 28 | public var view_reset: String; 29 | public var view_front: String; 30 | public var view_back: String; 31 | public var view_right: String; 32 | public var view_left: String; 33 | public var view_top: String; 34 | public var view_bottom: String; 35 | public var view_camera_type: String; 36 | public var view_orbit_left: String; 37 | public var view_orbit_right: String; 38 | public var view_orbit_up: String; 39 | public var view_orbit_down: String; 40 | public var view_orbit_opposite: String; 41 | public var view_zoom_in: String; 42 | public var view_zoom_out: String; 43 | public var view_distract_free: String; 44 | public var viewport_mode: String; 45 | public var toggle_node_editor: String; 46 | public var toggle_browser: String; 47 | public var node_search: String; 48 | } 49 | -------------------------------------------------------------------------------- /Sources/arm/PluginAPI.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | @:expose("arm") 4 | class ArmBridge { 5 | public static var App = arm.App; 6 | public static var Config = arm.Config; 7 | public static var Context = arm.Context; 8 | public static var History = arm.History; 9 | public static var Layers = arm.Layers; 10 | public static var Operator = arm.Operator; 11 | public static var Plugin = arm.Plugin; 12 | public static var Project = arm.Project; 13 | public static var Res = arm.Res; 14 | public static var Path = arm.sys.Path; 15 | public static var File = arm.sys.File; 16 | public static var NodesBrush = arm.node.NodesBrush; 17 | public static var Brush = arm.node.Brush; 18 | public static var BrushOutputNode = arm.node.brush.BrushOutputNode; 19 | public static var UISidebar = arm.ui.UISidebar; 20 | public static var UINodes = arm.ui.UINodes; 21 | public static var UIFiles = arm.ui.UIFiles; 22 | public static var UIMenu = arm.ui.UIMenu; 23 | public static var UIBox = arm.ui.UIBox; 24 | public static var MeshUtil = arm.util.MeshUtil; 25 | public static var UVUtil = arm.util.UVUtil; 26 | public static var Viewport = arm.Viewport; 27 | } 28 | 29 | @:keep 30 | class KeepLab { 31 | public static function keep() { 32 | var a = App.uiBox.panel; 33 | return [a]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/arm/ProjectFormat.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | import zui.Nodes; 4 | import iron.data.SceneFormat; 5 | 6 | typedef TProjectFormat = { 7 | @:optional public var version: String; 8 | @:optional public var material: TNodeCanvas; 9 | @:optional public var material_groups: Array; 10 | @:optional public var assets: Array; // texture_assets 11 | @:optional public var mesh_data: TMeshData; 12 | @:optional public var mesh_icon: haxe.io.Bytes; 13 | @:optional public var swatches: Array; 14 | @:optional public var is_bgra: Null; // Swapped red and blue channels for layer textures 15 | @:optional public var packed_assets: Array; 16 | @:optional public var envmap: String; // Asset name 17 | @:optional public var envmap_strength: Null; 18 | @:optional public var camera_world: kha.arrays.Float32Array; 19 | @:optional public var camera_origin: kha.arrays.Float32Array; 20 | @:optional public var camera_fov: Null; 21 | } 22 | 23 | typedef TAsset = { 24 | public var id: Int; 25 | public var name: String; 26 | public var file: String; 27 | } 28 | 29 | typedef TPackedAsset = { 30 | public var name: String; 31 | public var bytes: haxe.io.Bytes; 32 | } 33 | 34 | typedef TSwatchColor = { 35 | public var base: kha.Color; 36 | public var opacity: Float; 37 | public var occlusion: Float; 38 | public var roughness: Float; 39 | public var metallic: Float; 40 | public var normal: kha.Color; 41 | public var emission: Float; 42 | public var height: Float; 43 | public var subsurface: Float; 44 | } 45 | -------------------------------------------------------------------------------- /Sources/arm/Strings.hx: -------------------------------------------------------------------------------- 1 | package arm; 2 | 3 | class Strings { 4 | public static function error0(): String { return tr("Error: .arm file expected"); } 5 | public static function error1(): String { return tr("Error: Unknown asset format"); } 6 | public static function error2(): String { return tr("Error: Could not locate texture"); } 7 | public static function error3(): String { return tr("Error: Failed to read mesh data"); } 8 | public static function error5(): String { return tr("Error: Check internet connection to access the cloud"); } 9 | public static function info0(): String { return tr("Info: Asset already imported"); } 10 | } 11 | -------------------------------------------------------------------------------- /Sources/arm/io/ExportGpl.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import haxe.io.BytesOutput; 4 | import arm.ProjectFormat; 5 | 6 | class ExportGpl { 7 | 8 | public static function run(path: String, name: String, swatches: Array) { 9 | var o = new BytesOutput(); 10 | o.bigEndian = false; 11 | o.writeString("GIMP Palette\n"); 12 | o.writeString("Name: " + name + "\n"); 13 | o.writeString("# armorpaint.org\n"); 14 | o.writeString("#\n"); 15 | 16 | for (swatch in swatches) { 17 | o.writeString(Std.string(swatch.base.Rb) + " " + Std.string(swatch.base.Gb) + " " + Std.string(swatch.base.Bb) + "\n"); 18 | } 19 | 20 | Krom.fileSaveBytes(path, o.getBytes().getData(), o.getBytes().length); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/arm/io/ExportMesh.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import iron.object.MeshObject; 4 | import arm.ui.UISidebar; 5 | 6 | class ExportMesh { 7 | 8 | public static function run(path: String, paintObjects: Array = null, applyDisplacement = false) { 9 | if (paintObjects == null) paintObjects = Project.paintObjects; 10 | if (Context.exportMeshFormat == FormatObj) ExportObj.run(path, paintObjects, applyDisplacement); 11 | else ExportArm.runMesh(path, paintObjects); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/arm/io/ExportObj.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import haxe.io.Bytes; 4 | import haxe.io.BytesOutput; 5 | import kha.arrays.Int16Array; 6 | import iron.object.MeshObject; 7 | 8 | class ExportObj { 9 | 10 | public static function run(path: String, paintObjects: Array, applyDisplacement = false) { 11 | var o = new BytesOutput(); 12 | o.bigEndian = false; 13 | o.writeString("# armorpaint.org\n"); 14 | 15 | var poff = 0; 16 | var noff = 0; 17 | var toff = 0; 18 | for (p in paintObjects) { 19 | var mesh = p.data.raw; 20 | var inv = 1 / 32767; 21 | var sc = p.data.scalePos * inv; 22 | var posa = mesh.vertex_arrays[0].values; 23 | var nora = mesh.vertex_arrays[1].values; 24 | var texa = mesh.vertex_arrays[2].values; 25 | var len = Std.int(posa.length / 4); 26 | 27 | // Merge shared vertices and remap indices 28 | var posa2 = new Int16Array(len * 3); 29 | var nora2 = new Int16Array(len * 3); 30 | var texa2 = new Int16Array(len * 2); 31 | var posmap = new Map(); 32 | var normap = new Map(); 33 | var texmap = new Map(); 34 | 35 | var pi = 0; 36 | var ni = 0; 37 | var ti = 0; 38 | for (i in 0...len) { 39 | var found = false; 40 | for (j in 0...pi) { 41 | if (posa2[j * 3 ] == posa[i * 4 ] && 42 | posa2[j * 3 + 1] == posa[i * 4 + 1] && 43 | posa2[j * 3 + 2] == posa[i * 4 + 2]) { 44 | posmap.set(i, j); 45 | found = true; 46 | break; 47 | } 48 | } 49 | if (!found) { 50 | posmap.set(i, pi); 51 | posa2[pi * 3 ] = posa[i * 4 ]; 52 | posa2[pi * 3 + 1] = posa[i * 4 + 1]; 53 | posa2[pi * 3 + 2] = posa[i * 4 + 2]; 54 | pi++; 55 | } 56 | 57 | found = false; 58 | for (j in 0...ni) { 59 | if (nora2[j * 3 ] == nora[i * 2 ] && 60 | nora2[j * 3 + 1] == nora[i * 2 + 1] && 61 | nora2[j * 3 + 2] == posa[i * 4 + 3]) { 62 | normap.set(i, j); 63 | found = true; 64 | break; 65 | } 66 | } 67 | if (!found) { 68 | normap.set(i, ni); 69 | nora2[ni * 3 ] = nora[i * 2 ]; 70 | nora2[ni * 3 + 1] = nora[i * 2 + 1]; 71 | nora2[ni * 3 + 2] = posa[i * 4 + 3]; 72 | ni++; 73 | } 74 | 75 | found = false; 76 | for (j in 0...ti) { 77 | if (texa2[j * 2 ] == texa[i * 2 ] && 78 | texa2[j * 2 + 1] == texa[i * 2 + 1]) { 79 | texmap.set(i, j); 80 | found = true; 81 | break; 82 | } 83 | } 84 | if (!found) { 85 | texmap.set(i, ti); 86 | texa2[ti * 2 ] = texa[i * 2 ]; 87 | texa2[ti * 2 + 1] = texa[i * 2 + 1]; 88 | ti++; 89 | } 90 | } 91 | 92 | if (applyDisplacement) { 93 | // var height = Project.layers[0].texpaint_pack.getPixels(); 94 | // var res = Project.layers[0].texpaint_pack.width; 95 | // var strength = 0.1; 96 | // for (i in 0...len) { 97 | // var x = Std.int(texa2[i * 2 ] / 32767 * res); 98 | // var y = Std.int((1.0 - texa2[i * 2 + 1] / 32767) * res); 99 | // var h = (1.0 - height.get((y * res + x) * 4 + 3) / 255) * strength; 100 | // posa2[i * 3 ] -= Std.int(nora2[i * 3 ] * inv * h / sc); 101 | // posa2[i * 3 + 1] -= Std.int(nora2[i * 3 + 1] * inv * h / sc); 102 | // posa2[i * 3 + 2] -= Std.int(nora2[i * 3 + 2] * inv * h / sc); 103 | // } 104 | } 105 | 106 | o.writeString("o " + p.name + "\n"); 107 | for (i in 0...pi) { 108 | o.writeString("v "); 109 | o.writeString(posa2[i * 3] * sc + ""); 110 | o.writeString(" "); 111 | o.writeString(posa2[i * 3 + 2] * sc + ""); 112 | o.writeString(" "); 113 | o.writeString(-posa2[i * 3 + 1] * sc + ""); 114 | o.writeString("\n"); 115 | } 116 | for (i in 0...ni) { 117 | o.writeString("vn "); 118 | o.writeString(nora2[i * 3] * inv + ""); 119 | o.writeString(" "); 120 | o.writeString(nora2[i * 3 + 2] * inv + ""); 121 | o.writeString(" "); 122 | o.writeString(-nora2[i * 3 + 1] * inv + ""); 123 | o.writeString("\n"); 124 | } 125 | for (i in 0...ti) { 126 | o.writeString("vt "); 127 | o.writeString(texa2[i * 2] * inv + ""); 128 | o.writeString(" "); 129 | o.writeString(1.0 - texa2[i * 2 + 1] * inv + ""); 130 | o.writeString("\n"); 131 | } 132 | 133 | var inda = mesh.index_arrays[0].values; 134 | for (i in 0...Std.int(inda.length / 3)) { 135 | var pi1 = posmap.get(inda[i * 3 ]) + 1 + poff; 136 | var pi2 = posmap.get(inda[i * 3 + 1]) + 1 + poff; 137 | var pi3 = posmap.get(inda[i * 3 + 2]) + 1 + poff; 138 | var ni1 = normap.get(inda[i * 3 ]) + 1 + noff; 139 | var ni2 = normap.get(inda[i * 3 + 1]) + 1 + noff; 140 | var ni3 = normap.get(inda[i * 3 + 2]) + 1 + noff; 141 | var ti1 = texmap.get(inda[i * 3 ]) + 1 + toff; 142 | var ti2 = texmap.get(inda[i * 3 + 1]) + 1 + toff; 143 | var ti3 = texmap.get(inda[i * 3 + 2]) + 1 + toff; 144 | o.writeString("f "); 145 | o.writeString(pi1 + ""); 146 | o.writeString("/"); 147 | o.writeString(ti1 + ""); 148 | o.writeString("/"); 149 | o.writeString(ni1 + ""); 150 | o.writeString(" "); 151 | o.writeString(pi2 + ""); 152 | o.writeString("/"); 153 | o.writeString(ti2 + ""); 154 | o.writeString("/"); 155 | o.writeString(ni2 + ""); 156 | o.writeString(" "); 157 | o.writeString(pi3 + ""); 158 | o.writeString("/"); 159 | o.writeString(ti3 + ""); 160 | o.writeString("/"); 161 | o.writeString(ni3 + ""); 162 | o.writeString("\n"); 163 | } 164 | poff += pi; 165 | noff += ni; 166 | toff += ti; 167 | } 168 | 169 | if (!path.endsWith(".obj")) path += ".obj"; 170 | 171 | Krom.fileSaveBytes(path, o.getBytes().getData(), o.getBytes().length); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportArm.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import haxe.io.Bytes; 4 | import kha.Window; 5 | import kha.Blob; 6 | import kha.Image; 7 | import zui.Nodes; 8 | import iron.data.MeshData; 9 | import iron.data.Data; 10 | import iron.data.SceneFormat; 11 | import iron.math.Mat4; 12 | import iron.system.ArmPack; 13 | import iron.system.Lz4; 14 | import iron.object.Object; 15 | import iron.Scene; 16 | import iron.RenderPath; 17 | import arm.ProjectFormat; 18 | import arm.ui.UIFiles; 19 | import arm.ui.UIStatus; 20 | import arm.sys.Path; 21 | import arm.sys.File; 22 | import arm.Viewport; 23 | import arm.Enums; 24 | 25 | class ImportArm { 26 | 27 | public static function runProject(path: String) { 28 | Data.getBlob(path, function(b: Blob) { 29 | var project: TProjectFormat = ArmPack.decode(b.toBytes()); 30 | 31 | // if (project.version != null && project.layer_datas == null) { 32 | // // Import as swatches 33 | // else if (project.swatches != null) { 34 | // runSwatchesFromProject(project, path); 35 | // } 36 | // return; 37 | // } 38 | 39 | Project.projectNew(true); 40 | Project.filepath = path; 41 | UIFiles.filename = path.substring(path.lastIndexOf(Path.sep) + 1, path.lastIndexOf(".")); 42 | #if (krom_android || krom_ios) 43 | Window.get(0).title = UIFiles.filename; 44 | #else 45 | Window.get(0).title = UIFiles.filename + " - " + Main.title; 46 | #end 47 | 48 | // Save to recent 49 | #if krom_ios 50 | var recent_path = path.substr(path.lastIndexOf("/") + 1); 51 | #else 52 | var recent_path = path; 53 | #end 54 | var recent = Config.raw.recent_projects; 55 | recent.remove(recent_path); 56 | recent.unshift(recent_path); 57 | Config.save(); 58 | 59 | Project.raw = project; 60 | 61 | var base = Path.baseDir(path); 62 | if (Project.raw.envmap != null) { 63 | Project.raw.envmap = Data.isAbsolute(Project.raw.envmap) ? Project.raw.envmap : base + Project.raw.envmap; 64 | } 65 | if (Project.raw.envmap_strength != null) { 66 | iron.Scene.active.world.probe.raw.strength = Project.raw.envmap_strength; 67 | } 68 | if (Project.raw.camera_world != null) { 69 | iron.Scene.active.camera.transform.local = Mat4.fromFloat32Array(Project.raw.camera_world); 70 | iron.Scene.active.camera.transform.decompose(); 71 | iron.Scene.active.camera.data.raw.fov = Project.raw.camera_fov; 72 | iron.Scene.active.camera.buildProjection(); 73 | var origin = Project.raw.camera_origin; 74 | arm.Camera.inst.origins[0].x = origin[0]; 75 | arm.Camera.inst.origins[0].y = origin[1]; 76 | arm.Camera.inst.origins[0].z = origin[2]; 77 | } 78 | 79 | for (file in project.assets) { 80 | #if krom_windows 81 | file = file.replace("/", "\\"); 82 | #else 83 | file = file.replace("\\", "/"); 84 | #end 85 | // Convert image path from relative to absolute 86 | var abs = Data.isAbsolute(file) ? file : base + file; 87 | if (project.packed_assets != null) { 88 | abs = Path.normalize(abs); 89 | unpackAsset(project, abs, file); 90 | } 91 | if (Data.cachedImages.get(abs) == null && !File.exists(abs)) { 92 | makePink(abs); 93 | } 94 | var hdrAsEnvmap = abs.endsWith(".hdr") && Project.raw.envmap == abs; 95 | ImportTexture.run(abs, hdrAsEnvmap); 96 | } 97 | 98 | // Synchronous for now 99 | new MeshData(project.mesh_data, function(md: MeshData) { 100 | Context.paintObject.setData(md); 101 | Context.paintObject.transform.scale.set(1, 1, 1); 102 | Context.paintObject.transform.buildMatrix(); 103 | Context.paintObject.name = md.name; 104 | Project.paintObjects = [Context.paintObject]; 105 | }); 106 | 107 | Context.selectPaintObject(Context.mainObject()); 108 | Viewport.scaleToBounds(); 109 | Context.paintObject.skip_context = "paint"; 110 | Context.mergedObject.visible = true; 111 | 112 | arm.ui.UINodes.inst.hwnd.redraws = 2; 113 | arm.ui.UINodes.inst.groupStack = []; 114 | Project.materialGroups = []; 115 | if (project.material_groups != null) { 116 | for (g in project.material_groups) Project.materialGroups.push({ canvas: g, nodes: new Nodes() }); 117 | } 118 | 119 | initNodes(project.material.nodes); 120 | Project.canvas = project.material; 121 | arm.node.Brush.parse(Project.canvas, false); 122 | 123 | Context.ddirty = 4; 124 | 125 | Data.deleteBlob(path); 126 | }); 127 | } 128 | 129 | public static function runSwatches(path: String, replaceExisting = false) { 130 | Data.getBlob(path, function(b: Blob) { 131 | var project: TProjectFormat = ArmPack.decode(b.toBytes()); 132 | if (project.version == null) { Data.deleteBlob(path); return; } 133 | runSwatchesFromProject(project, path, replaceExisting); 134 | }); 135 | } 136 | 137 | public static function runSwatchesFromProject(project: TProjectFormat, path: String, replaceExisting = false) { 138 | if (replaceExisting) { 139 | Project.raw.swatches = []; 140 | 141 | if (project.swatches == null) { // No swatches contained 142 | Project.raw.swatches.push(Project.makeSwatch()); 143 | } 144 | } 145 | 146 | if (project.swatches != null) { 147 | for (s in project.swatches) { 148 | Project.raw.swatches.push(s); 149 | } 150 | } 151 | UIStatus.inst.statusHandle.redraws = 2; 152 | Data.deleteBlob(path); 153 | } 154 | 155 | static function makePink(abs: String) { 156 | Console.error(Strings.error2() + " " + abs); 157 | var b = Bytes.alloc(4); 158 | b.set(0, 255); 159 | b.set(1, 0); 160 | b.set(2, 255); 161 | b.set(3, 255); 162 | var pink = Image.fromBytes(b, 1, 1); 163 | Data.cachedImages.set(abs, pink); 164 | } 165 | 166 | static function initNodes(nodes: Array) { 167 | for (node in nodes) { 168 | if (node.type == "ImageTextureNode") { 169 | node.buttons[0].default_value = App.getAssetIndex(node.buttons[0].data); 170 | } 171 | } 172 | } 173 | 174 | static function unpackAsset(project: TProjectFormat, abs: String, file: String) { 175 | if (Project.raw.packed_assets == null) { 176 | Project.raw.packed_assets = []; 177 | } 178 | for (pa in project.packed_assets) { 179 | #if krom_windows 180 | pa.name = pa.name.replace("/", "\\"); 181 | #else 182 | pa.name = pa.name.replace("\\", "/"); 183 | #end 184 | pa.name = Path.normalize(pa.name); 185 | if (pa.name == file) pa.name = abs; // From relative to absolute 186 | if (pa.name == abs) { 187 | if (!Project.packedAssetExists(Project.raw.packed_assets, pa.name)) { 188 | Project.raw.packed_assets.push(pa); 189 | } 190 | kha.Image.fromEncodedBytes(pa.bytes, pa.name.endsWith(".jpg") ? ".jpg" : ".png", function(image: kha.Image) { 191 | Data.cachedImages.set(abs, image); 192 | }, null, false); 193 | break; 194 | } 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportAsset.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import arm.sys.Path; 4 | import arm.sys.File; 5 | import arm.ui.UINodes; 6 | import arm.ui.UIBox; 7 | import arm.Project; 8 | 9 | class ImportAsset { 10 | 11 | public static function run(path: String, dropX = -1.0, dropY = -1.0, showBox = true, hdrAsEnvmap = true, done: Void->Void = null) { 12 | 13 | if (path.startsWith("cloud")) { 14 | function doCacheCloud() { 15 | File.cacheCloud(path, function(abs: String) { 16 | if (abs == null) return; 17 | run(abs, dropX, dropY, showBox, hdrAsEnvmap, done); 18 | }); 19 | } 20 | 21 | #if (krom_android || krom_ios) 22 | arm.App.notifyOnNextFrame(function() { 23 | Console.toast(tr("Downloading")); 24 | arm.App.notifyOnNextFrame(doCacheCloud); 25 | }); 26 | #else 27 | doCacheCloud(); 28 | #end 29 | 30 | return; 31 | } 32 | 33 | if (Path.isMesh(path)) { 34 | showBox ? Project.importMeshBox(path) : ImportMesh.run(path); 35 | if (dropX > 0) UIBox.clickToHide = false; // Prevent closing when going back to window after drag and drop 36 | } 37 | else if (Path.isTexture(path)) { 38 | ImportTexture.run(path, hdrAsEnvmap); 39 | // Place image node 40 | var x0 = UINodes.inst.wx; 41 | var x1 = UINodes.inst.wx + UINodes.inst.ww; 42 | if (UINodes.inst.show && dropX > x0 && dropX < x1) { 43 | var assetIndex = 0; 44 | for (i in 0...Project.assets.length) { 45 | if (Project.assets[i].file == path) { 46 | assetIndex = i; 47 | break; 48 | } 49 | } 50 | UINodes.inst.acceptAssetDrag(assetIndex); 51 | UINodes.inst.getNodes().nodesDrag = false; 52 | UINodes.inst.hwnd.redraws = 2; 53 | } 54 | } 55 | else if (Path.isProject(path)) { 56 | ImportArm.runProject(path); 57 | } 58 | else if (Path.isPlugin(path)) { 59 | ImportPlugin.run(path); 60 | } 61 | else if (Path.isGimpColorPalette(path)) { 62 | ImportGpl.run(path, false); 63 | } 64 | else { 65 | if (Context.enableImportPlugin(path)) { 66 | run(path, dropX, dropY, showBox); 67 | } 68 | else { 69 | Console.error(Strings.error1()); 70 | } 71 | } 72 | 73 | if (done != null) done(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportEnvmap.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import kha.Image; 4 | import kha.Blob; 5 | import kha.graphics4.TextureFormat; 6 | import kha.graphics4.PipelineState; 7 | import kha.graphics4.VertexStructure; 8 | import kha.graphics4.VertexData; 9 | import kha.graphics4.TextureUnit; 10 | import kha.graphics4.ConstantLocation; 11 | import kha.graphics4.TextureAddressing; 12 | import kha.graphics4.TextureFilter; 13 | import kha.graphics4.MipMapFilter; 14 | import kha.arrays.Float32Array; 15 | import iron.data.Data; 16 | import iron.data.ConstData; 17 | import iron.math.Vec4; 18 | import iron.Scene; 19 | import arm.sys.Path; 20 | 21 | class ImportEnvmap { 22 | 23 | static var pipeline: PipelineState = null; 24 | static var paramsLocation: ConstantLocation; 25 | static var params = new Vec4(); 26 | static var n = new Vec4(); 27 | static var radianceLocation: TextureUnit; 28 | static var radiance: Image = null; 29 | static var radianceCpu: Image = null; 30 | static var mips: Array = null; 31 | static var mipsCpu: Array = null; 32 | 33 | public static function run(path: String, image: Image) { 34 | 35 | // Init 36 | if (pipeline == null) { 37 | pipeline = new PipelineState(); 38 | pipeline.vertexShader = kha.Shaders.getVertex("pass.vert"); 39 | pipeline.fragmentShader = kha.Shaders.getFragment("prefilter_envmap.frag"); 40 | var vs = new VertexStructure(); 41 | vs.add("pos", VertexData.Float2); 42 | pipeline.inputLayout = [vs]; 43 | pipeline.colorAttachmentCount = 1; 44 | pipeline.colorAttachments[0] = TextureFormat.RGBA128; 45 | pipeline.compile(); 46 | paramsLocation = pipeline.getConstantLocation("params"); 47 | radianceLocation = pipeline.getTextureUnit("radiance"); 48 | 49 | radiance = Image.createRenderTarget(1024, 512, TextureFormat.RGBA128); 50 | 51 | mips = []; 52 | var w = 512; 53 | for (i in 0...10) { 54 | mips.push(Image.createRenderTarget(w, w > 1 ? Std.int(w / 2) : 1, TextureFormat.RGBA128)); 55 | w = Std.int(w / 2); 56 | } 57 | 58 | if (ConstData.screenAlignedVB == null) ConstData.createScreenAlignedData(); 59 | } 60 | 61 | // Down-scale to 1024x512 62 | radiance.g2.begin(false); 63 | radiance.g2.pipeline = Layers.pipeCopy128; 64 | radiance.g2.drawScaledImage(image, 0, 0, 1024, 512); 65 | radiance.g2.pipeline = null; 66 | radiance.g2.end(); 67 | 68 | var radiancePixels = radiance.getPixels(); 69 | if (radianceCpu != null) radianceCpu.unload(); 70 | radianceCpu = Image.fromBytes(radiancePixels, radiance.width, radiance.height, TextureFormat.RGBA128, kha.graphics4.Usage.DynamicUsage); 71 | 72 | // Radiance 73 | if (mipsCpu != null) for (mip in mipsCpu) mip.unload(); 74 | mipsCpu = []; 75 | for (i in 0...mips.length) { 76 | getRadianceMip(mips[i], i, radiance); 77 | mipsCpu.push(Image.fromBytes(mips[i].getPixels(), mips[i].width, mips[i].height, TextureFormat.RGBA128, kha.graphics4.Usage.DynamicUsage)); 78 | } 79 | radianceCpu.setMipmaps(mipsCpu); 80 | 81 | // Irradiance 82 | Scene.active.world.probe.irradiance = getSphericalHarmonics(radiancePixels, radiance.width, radiance.height); 83 | 84 | // World 85 | Scene.active.world.probe.raw.strength = 1.0; 86 | Scene.active.world.probe.raw.radiance_mipmaps = mipsCpu.length - 2; 87 | Scene.active.world.envmap = image; 88 | Scene.active.world.raw.envmap = path; 89 | Scene.active.world.probe.radiance = radianceCpu; 90 | Scene.active.world.probe.radianceMipmaps = mipsCpu; 91 | Context.savedEnvmap = image; 92 | Context.showEnvmapHandle.selected = Context.showEnvmap = true; 93 | if (Context.showEnvmapBlur) { 94 | Scene.active.world.envmap = Scene.active.world.probe.radianceMipmaps[0]; 95 | } 96 | Context.ddirty = 2; 97 | Project.raw.envmap = path; 98 | } 99 | 100 | static function getRadianceMip(mip: kha.Image, level: Int, radiance: kha.Image) { 101 | mip.g4.begin(); 102 | mip.g4.setVertexBuffer(ConstData.screenAlignedVB); 103 | mip.g4.setIndexBuffer(ConstData.screenAlignedIB); 104 | mip.g4.setPipeline(pipeline); 105 | params.x = 0.1 + level / 8; 106 | mip.g4.setFloat4(paramsLocation, params.x, params.y, params.z, params.w); 107 | mip.g4.setTexture(radianceLocation, radiance); 108 | mip.g4.drawIndexedVertices(); 109 | mip.g4.end(); 110 | } 111 | 112 | static function reverseEquirect(x: Float, y: Float): Vec4 { 113 | var theta = x * Math.PI * 2 - Math.PI; 114 | var phi = y * Math.PI; 115 | // return n.set(Math.sin(phi) * Math.cos(theta), -(Math.sin(phi) * Math.sin(theta)), Math.cos(phi)); 116 | return n.set(-Math.cos(phi), Math.sin(phi) * Math.cos(theta), -(Math.sin(phi) * Math.sin(theta))); 117 | } 118 | 119 | // https://ndotl.wordpress.com/2015/03/07/pbr-cubemap-filtering 120 | // https://seblagarde.wordpress.com/2012/06/10/amd-cubemapgen-for-physically-based-rendering 121 | static function getSphericalHarmonics(source: haxe.io.Bytes, sourceWidth: Int, sourceHeight: Int): Float32Array { 122 | var sh = new Float32Array(9 * 3 + 1); // Align to mult of 4 - 27->28 123 | var accum = 0.0; 124 | var weight = 1.0; 125 | var weight1 = weight * 4 / 17; 126 | var weight2 = weight * 8 / 17; 127 | var weight3 = weight * 15 / 17; 128 | var weight4 = weight * 5 / 68; 129 | var weight5 = weight * 15 / 68; 130 | 131 | for (x in 0...sourceWidth) { 132 | for (y in 0...sourceHeight) { 133 | n = reverseEquirect(x / sourceWidth, y / sourceHeight); 134 | 135 | for (i in 0...3) { 136 | var value = source.getFloat(((x + y * sourceWidth) * 16 + i * 4)); 137 | value = Math.pow(value, 1.0 / 2.2); 138 | 139 | sh[0 + i] += value * weight1; 140 | sh[3 + i] += value * weight2 * n.x; 141 | sh[6 + i] += value * weight2 * n.y; 142 | sh[9 + i] += value * weight2 * n.z; 143 | 144 | sh[12 + i] += value * weight3 * n.x * n.z; 145 | sh[15 + i] += value * weight3 * n.z * n.y; 146 | sh[18 + i] += value * weight3 * n.y * n.x; 147 | 148 | sh[21 + i] += value * weight4 * (3.0 * n.z * n.z - 1.0); 149 | sh[24 + i] += value * weight5 * (n.x * n.x - n.y * n.y); 150 | 151 | accum += weight; 152 | } 153 | } 154 | } 155 | 156 | for (i in 0...sh.length) { 157 | sh[i] /= accum / 16; 158 | } 159 | 160 | return sh; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportFbx.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import kha.Blob; 4 | import iron.data.Data; 5 | import arm.format.FbxParser; 6 | import arm.ui.UISidebar; 7 | 8 | class ImportFbx { 9 | 10 | public static function run(path: String, replaceExisting = true) { 11 | Data.getBlob(path, function(b: Blob) { 12 | FbxParser.parseTransform = Context.parseTransform; 13 | FbxParser.parseVCols = Context.parseVCols; 14 | var obj = new FbxParser(b); 15 | replaceExisting ? ImportMesh.makeMesh(obj, path) : ImportMesh.addMesh(obj); 16 | while (obj.next()) { 17 | ImportMesh.addMesh(obj); 18 | } 19 | Data.deleteBlob(path); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportGpl.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import haxe.io.BytesInput; 4 | import kha.Color; 5 | import kha.Blob; 6 | import iron.data.Data; 7 | 8 | class ImportGpl { 9 | 10 | public static function run(path: String, replaceExisting: Bool) { 11 | Data.getBlob(path, function(b: Blob) { 12 | var swatches = []; 13 | try { 14 | var input = new BytesInput(b.bytes); 15 | // GIMP's color palette importer: https://gitlab.gnome.org/GNOME/gimp/-/blob/gimp-2-10/app/core/gimppalette-load.c#L39 16 | if (!input.readLine().startsWith("GIMP Palette")) { 17 | Console.error(tr("Not a valid GIMP color palette")); 18 | return; 19 | } 20 | 21 | var delimiter = ~/\s+/ig; 22 | while (true) { 23 | var line = input.readLine(); 24 | if (line.startsWith("Name:")) continue; 25 | else if (line.startsWith("Columns:")) continue; 26 | else if (line.startsWith("#")) continue; 27 | else { 28 | var tokens = delimiter.split(line); 29 | if (tokens.length < 3) continue; 30 | var swatch = Project.makeSwatch(Color.fromBytes(Std.parseInt(tokens[0]), Std.parseInt(tokens[1]), Std.parseInt(tokens[2]))); 31 | swatches.push(swatch); 32 | } 33 | } 34 | } 35 | catch (e: haxe.io.Eof) { 36 | // Is thrown if end of file is reached 37 | } 38 | if (replaceExisting) { 39 | Project.raw.swatches = []; 40 | 41 | if (swatches.length == 0) { // No swatches contained 42 | Project.raw.swatches.push(Project.makeSwatch()); 43 | } 44 | } 45 | 46 | if (swatches.length > 0) { 47 | for (s in swatches) { 48 | Project.raw.swatches.push(s); 49 | } 50 | } 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportKeymap.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import arm.sys.Path; 4 | import arm.sys.File; 5 | 6 | class ImportKeymap { 7 | 8 | public static function run(path: String) { 9 | if (!Path.isJson(path)) { 10 | Console.error(Strings.error1()); 11 | return; 12 | } 13 | 14 | var filename = path.substr(path.lastIndexOf(Path.sep) + 1); 15 | var dstPath = Path.data() + Path.sep + "keymap_presets" + Path.sep + filename; 16 | File.copy(path, dstPath); // Copy to preset folder 17 | arm.ui.BoxPreferences.fetchKeymaps(); // Refresh file list 18 | arm.ui.BoxPreferences.presetHandle.position = arm.ui.BoxPreferences.getPresetIndex(); 19 | Console.info(tr("Keymap imported:") + " " + filename); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportMesh.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import iron.data.SceneFormat; 4 | import iron.data.MeshData; 5 | import iron.data.Data; 6 | import iron.math.Vec4; 7 | import iron.Scene; 8 | import arm.util.MeshUtil; 9 | import arm.util.UVUtil; 10 | import arm.Viewport; 11 | import arm.sys.Path; 12 | import arm.ui.UIHeader; 13 | import arm.ui.UISidebar; 14 | import arm.Project; 15 | import arm.Enums; 16 | 17 | class ImportMesh { 18 | 19 | public static function run(path: String, replaceExisting = true) { 20 | if (!Path.isMesh(path)) { 21 | if (!Context.enableImportPlugin(path)) { 22 | Console.error(Strings.error1()); 23 | return; 24 | } 25 | } 26 | 27 | #if arm_debug 28 | var timer = iron.system.Time.realTime(); 29 | #end 30 | 31 | var p = path.toLowerCase(); 32 | if (p.endsWith(".obj")) ImportObj.run(path, replaceExisting); 33 | else if (p.endsWith(".fbx")) ImportFbx.run(path, replaceExisting); 34 | else if (p.endsWith(".blend")) ImportBlend.run(path, replaceExisting); 35 | else { 36 | var ext = path.substr(path.lastIndexOf(".") + 1); 37 | var importer = Path.meshImporters.get(ext); 38 | importer(path, function(mesh: Dynamic) { 39 | replaceExisting ? makeMesh(mesh, path) : addMesh(mesh); 40 | }); 41 | } 42 | 43 | Project.meshAssets = [path]; 44 | 45 | #if (krom_android || krom_ios) 46 | kha.Window.get(0).title = path.substring(path.lastIndexOf(Path.sep) + 1, path.lastIndexOf(".")); 47 | #end 48 | } 49 | 50 | static function finishImport() { 51 | if (Context.mergedObject != null) { 52 | Context.mergedObject.remove(); 53 | Data.deleteMesh(Context.mergedObject.data.handle); 54 | Context.mergedObject = null; 55 | } 56 | 57 | Context.selectPaintObject(Context.mainObject()); 58 | 59 | if (Project.paintObjects.length > 1) { 60 | // Sort by name 61 | Project.paintObjects.sort(function(a, b): Int { 62 | if (a.name < b.name) return -1; 63 | else if (a.name > b.name) return 1; 64 | return 0; 65 | }); 66 | 67 | // No mask by default 68 | for (p in Project.paintObjects) p.visible = true; 69 | if (Context.mergedObject == null) MeshUtil.mergeMesh(); 70 | Context.paintObject.skip_context = "paint"; 71 | Context.mergedObject.visible = true; 72 | } 73 | 74 | Viewport.scaleToBounds(); 75 | 76 | if (Context.paintObject.name == "") Context.paintObject.name = "Object"; 77 | arm.node.MakeMaterial.parsePaintMaterial(); 78 | arm.node.MakeMaterial.parseMeshMaterial(); 79 | 80 | #if arm_debug 81 | trace("Mesh imported in " + (iron.system.Time.realTime() - timer)); 82 | #end 83 | 84 | #if (kha_direct3d12 || kha_vulkan) 85 | arm.render.RenderPathRaytrace.ready = false; 86 | #end 87 | 88 | #if arm_physics 89 | Context.paintBody = null; 90 | #end 91 | } 92 | 93 | public static function makeMesh(mesh: Dynamic, path: String) { 94 | if (mesh == null || mesh.posa == null || mesh.nora == null || mesh.inda == null || mesh.posa.length == 0) { 95 | Console.error(Strings.error3()); 96 | return; 97 | } 98 | 99 | function _makeMesh() { 100 | var raw = rawMesh(mesh); 101 | if (mesh.cola != null) raw.vertex_arrays.push({ values: mesh.cola, attrib: "col", data: "short4norm", padding: 1 }); 102 | 103 | new MeshData(raw, function(md: MeshData) { 104 | Context.paintObject = Context.mainObject(); 105 | 106 | Context.selectPaintObject(Context.mainObject()); 107 | for (i in 0...Project.paintObjects.length) { 108 | var p = Project.paintObjects[i]; 109 | if (p == Context.paintObject) continue; 110 | Data.deleteMesh(p.data.handle); 111 | p.remove(); 112 | } 113 | var handle = Context.paintObject.data.handle; 114 | if (handle != "SceneSphere" && handle != "ScenePlane") { 115 | Data.deleteMesh(handle); 116 | } 117 | 118 | Context.paintObject.setData(md); 119 | Context.paintObject.name = mesh.name; 120 | Project.paintObjects = [Context.paintObject]; 121 | 122 | md.handle = raw.name; 123 | Data.cachedMeshes.set(md.handle, md); 124 | 125 | Context.ddirty = 4; 126 | UVUtil.uvmapCached = false; 127 | UVUtil.trianglemapCached = false; 128 | UVUtil.dilatemapCached = false; 129 | 130 | // Wait for addMesh calls to finish 131 | iron.App.notifyOnInit(finishImport); 132 | }); 133 | } 134 | 135 | if (mesh.texa == null) { 136 | Project.unwrapMeshBox(mesh, _makeMesh); 137 | } 138 | else { 139 | _makeMesh(); 140 | } 141 | } 142 | 143 | public static function addMesh(mesh: Dynamic) { 144 | 145 | function _addMesh() { 146 | var raw = rawMesh(mesh); 147 | if (mesh.cola != null) raw.vertex_arrays.push({ values: mesh.cola, attrib: "col", data: "short4norm", padding: 1 }); 148 | 149 | new MeshData(raw, function(md: MeshData) { 150 | 151 | var object = Scene.active.addMeshObject(md, Context.paintObject.materials, Context.paintObject); 152 | object.name = mesh.name; 153 | object.skip_context = "paint"; 154 | 155 | // Ensure unique names 156 | for (p in Project.paintObjects) { 157 | if (p.name == object.name) { 158 | p.name += ".001"; 159 | p.data.handle += ".001"; 160 | Data.cachedMeshes.set(p.data.handle, p.data); 161 | } 162 | } 163 | 164 | Project.paintObjects.push(object); 165 | 166 | md.handle = raw.name; 167 | Data.cachedMeshes.set(md.handle, md); 168 | 169 | Context.ddirty = 4; 170 | UVUtil.uvmapCached = false; 171 | UVUtil.trianglemapCached = false; 172 | UVUtil.dilatemapCached = false; 173 | }); 174 | } 175 | 176 | if (mesh.texa == null) { 177 | Project.unwrapMeshBox(mesh, _addMesh); 178 | } 179 | else { 180 | _addMesh(); 181 | } 182 | } 183 | 184 | static function rawMesh(mesh: Dynamic): TMeshData { 185 | return { 186 | name: mesh.name, 187 | vertex_arrays: [ 188 | { values: mesh.posa, attrib: "pos", data: "short4norm" }, 189 | { values: mesh.nora, attrib: "nor", data: "short2norm" }, 190 | { values: mesh.texa, attrib: "tex", data: "short2norm" } 191 | ], 192 | index_arrays: [ 193 | { values: mesh.inda, material: 0 } 194 | ], 195 | scale_pos: mesh.scalePos, 196 | scale_tex: mesh.scaleTex 197 | }; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportObj.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import kha.Blob; 4 | import iron.data.Data; 5 | import arm.format.ObjParser; 6 | import arm.ui.UISidebar; 7 | 8 | class ImportObj { 9 | 10 | public static function run(path: String, replaceExisting = true) { 11 | var i = Context.splitBy; 12 | var isUdim = i == SplitUdim; 13 | ObjParser.splitCode = 14 | (i == SplitObject || isUdim) ? "o".code : 15 | i == SplitGroup ? "g".code : 16 | "u".code; // usemtl 17 | Data.getBlob(path, function(b: Blob) { 18 | if (isUdim) { 19 | var obj = new ObjParser(b, 0, isUdim); 20 | var name = obj.name; 21 | for (i in 0...obj.udims.length) { 22 | if (obj.udims[i].length == 0) continue; 23 | var u = i % obj.udimsU; 24 | var v = Std.int(i / obj.udimsU); 25 | obj.name = name + "." + (1000 + v * 10 + u + 1); 26 | obj.inda = obj.udims[i]; 27 | i == 0 ? (replaceExisting ? ImportMesh.makeMesh(obj, path) : ImportMesh.addMesh(obj)) : ImportMesh.addMesh(obj); 28 | } 29 | } 30 | else { 31 | var parts: Array = []; 32 | var obj = new ObjParser(b); 33 | parts.push(obj); 34 | while (obj.hasNext) { 35 | obj = new ObjParser(b, obj.pos); 36 | parts.push(obj); 37 | } 38 | if (Context.splitBy == SplitMaterial) { 39 | var posa0; 40 | var posa1; 41 | var nora0; 42 | var nora1; 43 | var texa0; 44 | var texa1; 45 | var inda0; 46 | var inda1; 47 | // Merge to single object per material 48 | for (i in 0...parts.length) { 49 | var j = i + 1; 50 | while (j < parts.length) { 51 | if (parts[i].name == parts[j].name) { 52 | posa0 = parts[i].posa; 53 | posa1 = parts[j].posa; 54 | nora0 = parts[i].nora; 55 | nora1 = parts[j].nora; 56 | texa0 = parts[i].texa != null ? parts[i].texa : null; 57 | texa1 = parts[j].texa != null ? parts[j].texa : null; 58 | inda0 = parts[i].inda; 59 | inda1 = parts[j].inda; 60 | var voff = Std.int(posa0.length / 4); 61 | // Repack merged positions 62 | var posa32 = new kha.arrays.Float32Array(Std.int(posa0.length / 4) * 3 + Std.int(posa1.length / 4) * 3); 63 | for (k in 0...Std.int(posa0.length / 4)) { 64 | posa32[k * 3 ] = posa0[k * 4 ] / 32767 * parts[i].scalePos; 65 | posa32[k * 3 + 1] = posa0[k * 4 + 1] / 32767 * parts[i].scalePos; 66 | posa32[k * 3 + 2] = posa0[k * 4 + 2] / 32767 * parts[i].scalePos; 67 | } 68 | for (k in 0...Std.int(posa1.length / 4)) { 69 | posa32[voff * 3 + k * 3 ] = posa1[k * 4 ] / 32767 * parts[j].scalePos; 70 | posa32[voff * 3 + k * 3 + 1] = posa1[k * 4 + 1] / 32767 * parts[j].scalePos; 71 | posa32[voff * 3 + k * 3 + 2] = posa1[k * 4 + 2] / 32767 * parts[j].scalePos; 72 | } 73 | var scalePos = 0.0; 74 | for (k in 0...posa32.length) { 75 | var f = Math.abs(posa32[k]); 76 | if (scalePos < f) scalePos = f; 77 | } 78 | var inv = 32767 * (1 / scalePos); 79 | var posa = new kha.arrays.Int16Array(posa0.length + posa1.length); 80 | for (k in 0...Std.int(posa.length / 4)) { 81 | posa[k * 4 ] = Std.int(posa32[k * 3 ] * inv); 82 | posa[k * 4 + 1] = Std.int(posa32[k * 3 + 1] * inv); 83 | posa[k * 4 + 2] = Std.int(posa32[k * 3 + 2] * inv); 84 | } 85 | for (k in 0...Std.int(posa0.length / 4)) posa[k * 4 + 3] = posa0[k * 4 + 3]; 86 | for (k in 0...Std.int(posa1.length / 4)) posa[posa0.length + k * 4 + 3] = posa1[k * 4 + 3]; 87 | // Merge normals and uvs 88 | var nora = new kha.arrays.Int16Array(nora0.length + nora1.length); 89 | var texa = (texa0 != null && texa1 != null) ? new kha.arrays.Int16Array(texa0.length + texa1.length) : null; 90 | var inda = new kha.arrays.Uint32Array(inda0.length + inda1.length); 91 | js.Syntax.code("nora.set(nora0)"); 92 | js.Syntax.code("nora.set(nora1, nora0.length)"); 93 | if (texa != null) { 94 | js.Syntax.code("texa.set(texa0)"); 95 | js.Syntax.code("texa.set(texa1, texa0.length)"); 96 | } 97 | js.Syntax.code("inda.set(inda0)"); 98 | for (k in 0...inda1.length) inda[k + inda0.length] = inda1[k] + voff; 99 | parts[i].posa = posa; 100 | parts[i].nora = nora; 101 | parts[i].texa = texa; 102 | parts[i].inda = inda; 103 | parts[i].scalePos = scalePos; 104 | parts.splice(j, 1); 105 | } 106 | else j++; 107 | } 108 | } 109 | } 110 | replaceExisting ? ImportMesh.makeMesh(parts[0], path) : ImportMesh.addMesh(parts[0]); 111 | for (i in 1...parts.length) { 112 | ImportMesh.addMesh(parts[i]); 113 | } 114 | } 115 | Data.deleteBlob(path); 116 | }); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportPlugin.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import arm.sys.Path; 4 | import arm.sys.File; 5 | 6 | class ImportPlugin { 7 | 8 | public static function run(path: String) { 9 | if (!Path.isPlugin(path)) { 10 | Console.error(Strings.error1()); 11 | return; 12 | } 13 | 14 | var filename = path.substr(path.lastIndexOf(Path.sep) + 1); 15 | var dstPath = Path.data() + Path.sep + "plugins" + Path.sep + filename; 16 | File.copy(path, dstPath); // Copy to plugin folder 17 | arm.ui.BoxPreferences.filesPlugin = null; // Refresh file list 18 | Console.info(tr("Plugin imported:") + " " + filename); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportTexture.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import kha.Image; 4 | import iron.data.Data; 5 | import arm.ui.UIStatus; 6 | import arm.sys.Path; 7 | import arm.ProjectFormat; 8 | 9 | class ImportTexture { 10 | 11 | public static function run(path: String, hdrAsEnvmap = true) { 12 | if (!Path.isTexture(path)) { 13 | if (!Context.enableImportPlugin(path)) { 14 | Console.error(Strings.error1()); 15 | return; 16 | } 17 | } 18 | 19 | for (a in Project.assets) { 20 | // Already imported 21 | if (a.file == path) { 22 | // Set as envmap 23 | if (hdrAsEnvmap && path.toLowerCase().endsWith(".hdr")) { 24 | Data.getImage(path, function(image: kha.Image) { 25 | App.notifyOnNextFrame(function() { // Make sure file browser process did finish 26 | ImportEnvmap.run(path, image); 27 | }); 28 | }); 29 | } 30 | Console.info(Strings.info0()); 31 | return; 32 | } 33 | } 34 | 35 | var ext = path.substr(path.lastIndexOf(".") + 1); 36 | var importer = Path.textureImporters.get(ext); 37 | var cached = Data.cachedImages.get(path) != null; // Already loaded or pink texture for missing file 38 | if (importer == null || cached) importer = defaultImporter; 39 | 40 | importer(path, function(image: Image) { 41 | Data.cachedImages.set(path, image); 42 | var ar = path.split(Path.sep); 43 | var name = ar[ar.length - 1]; 44 | var asset: TAsset = {name: name, file: path, id: Project.assetId++}; 45 | Project.assets.push(asset); 46 | if (Context.texture == null) Context.texture = asset; 47 | Project.assetNames.push(name); 48 | Project.assetMap.set(asset.id, image); 49 | UIStatus.inst.statusHandle.redraws = 2; 50 | Console.info(tr("Texture imported:") + " " + name); 51 | 52 | // Set as envmap 53 | if (hdrAsEnvmap && path.toLowerCase().endsWith(".hdr")) { 54 | App.notifyOnNextFrame(function() { // Make sure file browser process did finish 55 | ImportEnvmap.run(path, image); 56 | }); 57 | } 58 | }); 59 | } 60 | 61 | static function defaultImporter(path: String, done: Image->Void) { 62 | Data.getImage(path, done); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportTheme.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import arm.sys.Path; 4 | import arm.sys.File; 5 | 6 | class ImportTheme { 7 | 8 | public static function run(path: String) { 9 | if (!Path.isJson(path)) { 10 | Console.error(Strings.error1()); 11 | return; 12 | } 13 | 14 | var filename = path.substr(path.lastIndexOf(Path.sep) + 1); 15 | var dstPath = Path.data() + Path.sep + "themes" + Path.sep + filename; 16 | File.copy(path, dstPath); // Copy to preset folder 17 | arm.ui.BoxPreferences.fetchThemes(); // Refresh file list 18 | Config.raw.theme = filename; 19 | arm.ui.BoxPreferences.themeHandle.position = arm.ui.BoxPreferences.getThemeIndex(); 20 | Config.loadTheme(Config.raw.theme); 21 | Console.info(tr("Theme imported:") + " " + filename); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/arm/node/LogicNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | class LogicNode { 4 | 5 | var tree: LogicTree; 6 | var inputs: Array = []; 7 | var outputs: Array> = []; 8 | 9 | public function new(tree: LogicTree) { 10 | this.tree = tree; 11 | } 12 | 13 | public function addInput(node: LogicNode, from: Int) { 14 | inputs.push(new LogicNodeInput(node, from)); 15 | } 16 | 17 | public function addOutputs(nodes: Array) { 18 | outputs.push(nodes); 19 | } 20 | 21 | @:allow(arm.node.LogicNodeInput) 22 | function get(from: Int, done: Dynamic->Void) { 23 | done(this); 24 | } 25 | 26 | @:allow(arm.node.LogicNodeInput) 27 | function set(value: Dynamic) {} 28 | 29 | public function getImage(): kha.Image { 30 | return null; 31 | } 32 | } 33 | 34 | class LogicNodeInput { 35 | 36 | @:allow(arm.node.LogicNode) 37 | var node: LogicNode; 38 | var from: Int; // Socket index 39 | 40 | public function new(node: LogicNode, from: Int) { 41 | this.node = node; 42 | this.from = from; 43 | } 44 | 45 | @:allow(arm.node.LogicNode) 46 | function get(done: Dynamic->Void) { 47 | node.get(from, done); 48 | } 49 | 50 | @:allow(arm.node.LogicNode) 51 | function set(value: Dynamic) { 52 | node.set(value); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/arm/node/LogicTree.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | class LogicTree extends iron.Trait { 4 | 5 | public var loopBreak = false; // Trigger break from loop nodes 6 | 7 | public function new() { 8 | super(); 9 | } 10 | 11 | public function add() {} 12 | 13 | var paused = false; 14 | 15 | public function pause() { 16 | if (paused) return; 17 | paused = true; 18 | 19 | if (_update != null) for (f in _update) iron.App.removeUpdate(f); 20 | if (_lateUpdate != null) for (f in _lateUpdate) iron.App.removeLateUpdate(f); 21 | } 22 | 23 | public function resume() { 24 | if (!paused) return; 25 | paused = false; 26 | 27 | if (_update != null) for (f in _update) iron.App.notifyOnUpdate(f); 28 | if (_lateUpdate != null) for (f in _lateUpdate) iron.App.notifyOnLateUpdate(f); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeMaterial.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | import zui.Nodes; 4 | import iron.data.SceneFormat; 5 | import iron.data.ShaderData; 6 | import iron.data.MaterialData; 7 | import iron.RenderPath; 8 | import arm.ui.UIHeader; 9 | import arm.ui.UINodes; 10 | import arm.ui.UISidebar; 11 | import arm.shader.NodeShader; 12 | import arm.shader.NodeShaderContext; 13 | import arm.shader.NodeShaderData; 14 | import arm.shader.ShaderFunctions; 15 | import arm.shader.MaterialParser; 16 | import arm.render.RenderPathPaint; 17 | import arm.Enums; 18 | 19 | class MakeMaterial { 20 | 21 | public static var defaultScon: ShaderContext = null; 22 | public static var defaultMcon: MaterialContext = null; 23 | public static var heightUsed = false; 24 | 25 | public static function parseMeshMaterial() { 26 | var m = Project.materialData; 27 | 28 | for (c in m.shader.contexts) { 29 | if (c.raw.name == "mesh") { 30 | m.shader.raw.contexts.remove(c.raw); 31 | m.shader.contexts.remove(c); 32 | deleteContext(c); 33 | break; 34 | } 35 | } 36 | 37 | var con = MakeMesh.run(new NodeShaderData({ name: "Material", canvas: null })); 38 | var scon = new ShaderContext(con.data, function(scon: ShaderContext){}); 39 | scon.overrideContext = {}; 40 | if (con.frag.sharedSamplers.length > 0) { 41 | var sampler = con.frag.sharedSamplers[0]; 42 | scon.overrideContext.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1); 43 | } 44 | if (!Context.textureFilter) { 45 | scon.overrideContext.filter = "point"; 46 | } 47 | scon.overrideContext.addressing = "repeat"; 48 | m.shader.raw.contexts.push(scon.raw); 49 | m.shader.contexts.push(scon); 50 | 51 | Context.ddirty = 2; 52 | 53 | #if rp_voxels 54 | makeVoxel(m); 55 | #end 56 | 57 | #if (kha_direct3d12 || kha_vulkan) 58 | arm.render.RenderPathRaytrace.dirty = 1; 59 | #end 60 | } 61 | 62 | #if rp_voxels 63 | static function makeVoxel(m: MaterialData) { 64 | // var rebuild = heightUsed; 65 | // if (Config.raw.rp_gi != false && rebuild) { 66 | // var scon: ShaderContext = null; 67 | // for (c in m.shader.contexts) { 68 | // if (c.raw.name == "voxel") { 69 | // scon = c; 70 | // break; 71 | // } 72 | // } 73 | // if (scon != null) MakeVoxel.run(scon); 74 | // } 75 | } 76 | #end 77 | 78 | public static function parsePaintMaterial() { 79 | var m = Project.materialData; 80 | var scon: ShaderContext = null; 81 | var mcon: MaterialContext = null; 82 | for (c in m.shader.contexts) { 83 | if (c.raw.name == "paint") { 84 | m.shader.raw.contexts.remove(c.raw); 85 | m.shader.contexts.remove(c); 86 | if (c != defaultScon) deleteContext(c); 87 | break; 88 | } 89 | } 90 | for (c in m.contexts) { 91 | if (c.raw.name == "paint") { 92 | m.raw.contexts.remove(c.raw); 93 | m.contexts.remove(c); 94 | break; 95 | } 96 | } 97 | 98 | var sdata = new NodeShaderData({ name: "Material", canvas: null }); 99 | var mcon: TMaterialContext = { name: "paint", bind_textures: [] }; 100 | var con = MakePaint.run(sdata, mcon); 101 | 102 | var compileError = false; 103 | var scon = new ShaderContext(con.data, function(scon: ShaderContext) { 104 | if (scon == null) compileError = true; 105 | }); 106 | if (compileError) return; 107 | scon.overrideContext = {}; 108 | scon.overrideContext.addressing = "repeat"; 109 | var mcon = new MaterialContext(mcon, function(mcon: MaterialContext) {}); 110 | 111 | m.shader.raw.contexts.push(scon.raw); 112 | m.shader.contexts.push(scon); 113 | m.raw.contexts.push(mcon.raw); 114 | m.contexts.push(mcon); 115 | 116 | if (defaultScon == null) defaultScon = scon; 117 | if (defaultMcon == null) defaultMcon = mcon; 118 | } 119 | 120 | public static inline function getDisplaceStrength():Float { 121 | var sc = Context.mainObject().transform.scale.x; 122 | return Config.raw.displace_strength * 0.02 * sc; 123 | } 124 | 125 | public static inline function voxelgiHalfExtents():String { 126 | var ext = Context.vxaoExt; 127 | return 'const vec3 voxelgiHalfExtents = vec3($ext, $ext, $ext);'; 128 | } 129 | 130 | static function deleteContext(c: ShaderContext) { 131 | arm.App.notifyOnNextFrame(function() { // Ensure pipeline is no longer in use 132 | c.delete(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/BooleanNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class BooleanNode extends LogicNode { 5 | 6 | public var value: Bool; 7 | 8 | public function new(tree: LogicTree, value = false) { 9 | super(tree); 10 | this.value = value; 11 | } 12 | 13 | override function get(from: Int, done: Dynamic->Void) { 14 | if (inputs.length > 0) inputs[0].get(done); 15 | else done(value); 16 | } 17 | 18 | override function set(value: Dynamic) { 19 | if (inputs.length > 0) inputs[0].set(value); 20 | else this.value = value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/BrushOutputNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | import kha.Image; 4 | import iron.RenderPath; 5 | import arm.Enums; 6 | 7 | @:keep 8 | class BrushOutputNode extends LogicNode { 9 | 10 | public var id = 0; 11 | public var texpaint: Image = null; 12 | public var texpaint_nor: Image = null; 13 | public var texpaint_pack: Image = null; 14 | public var texpaint_nor_empty: Image = null; 15 | public var texpaint_pack_empty: Image = null; 16 | 17 | public static var inst: BrushOutputNode = null; 18 | 19 | public function new(tree: LogicTree) { 20 | super(tree); 21 | 22 | if (inst == null) { 23 | 24 | { 25 | var t = new RenderTargetRaw(); 26 | t.name = "texpaint"; 27 | t.width = Config.getTextureResX(); 28 | t.height = Config.getTextureResY(); 29 | t.format = "RGBA32"; 30 | texpaint = RenderPath.active.createRenderTarget(t).image; 31 | } 32 | { 33 | var t = new RenderTargetRaw(); 34 | t.name = "texpaint_nor"; 35 | t.width = Config.getTextureResX(); 36 | t.height = Config.getTextureResY(); 37 | t.format = "RGBA32"; 38 | texpaint_nor = RenderPath.active.createRenderTarget(t).image; 39 | } 40 | { 41 | var t = new RenderTargetRaw(); 42 | t.name = "texpaint_pack"; 43 | t.width = Config.getTextureResX(); 44 | t.height = Config.getTextureResY(); 45 | t.format = "RGBA32"; 46 | texpaint_pack = RenderPath.active.createRenderTarget(t).image; 47 | } 48 | { 49 | var t = new RenderTargetRaw(); 50 | t.name = "texpaint_nor_empty"; 51 | t.width = 1; 52 | t.height = 1; 53 | t.format = "RGBA32"; 54 | texpaint_nor_empty = RenderPath.active.createRenderTarget(t).image; 55 | } 56 | { 57 | var t = new RenderTargetRaw(); 58 | t.name = "texpaint_pack_empty"; 59 | t.width = 1; 60 | t.height = 1; 61 | t.format = "RGBA32"; 62 | texpaint_pack_empty = RenderPath.active.createRenderTarget(t).image; 63 | } 64 | } 65 | else { 66 | texpaint = inst.texpaint; 67 | texpaint_nor = inst.texpaint_nor; 68 | texpaint_pack = inst.texpaint_pack; 69 | } 70 | 71 | inst = this; 72 | } 73 | 74 | override function get(from: Int, done: Dynamic->Void) { 75 | inputs[from].get(done); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/ColorNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | import iron.math.Vec4; 4 | 5 | @:keep 6 | class ColorNode extends LogicNode { 7 | 8 | var value = new Vec4(); 9 | var image: kha.Image = null; 10 | 11 | public function new(tree: LogicTree, r = 0.8, g = 0.8, b = 0.8, a = 1.0) { 12 | super(tree); 13 | 14 | value.set(r, g, b, a); 15 | } 16 | 17 | override function get(from: Int, done: Dynamic->Void) { 18 | if (inputs.length > 0) { inputs[0].get(done); return; } 19 | if (image != null) image.unload(); 20 | var b = haxe.io.Bytes.alloc(16); 21 | b.setFloat(0, value.x); 22 | b.setFloat(4, value.y); 23 | b.setFloat(8, value.z); 24 | b.setFloat(12, value.w); 25 | image = kha.Image.fromBytes(b, 1, 1, kha.graphics4.TextureFormat.RGBA128); 26 | done(image); 27 | } 28 | 29 | override function set(value: Dynamic) { 30 | if (inputs.length > 0) inputs[0].set(value); 31 | else this.value = value; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/FloatNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class FloatNode extends LogicNode { 5 | 6 | public var value: Float; 7 | var image: kha.Image = null; 8 | 9 | public function new(tree: LogicTree, value = 0.0) { 10 | super(tree); 11 | this.value = value; 12 | } 13 | 14 | override function get(from: Int, done: Dynamic->Void) { 15 | if (inputs.length > 0) { inputs[0].get(done); return; } 16 | if (image != null) image.unload(); 17 | var b = haxe.io.Bytes.alloc(16); 18 | b.setFloat(0, value); 19 | b.setFloat(4, value); 20 | b.setFloat(8, value); 21 | b.setFloat(12, 1.0); 22 | image = kha.Image.fromBytes(b, 1, 1, kha.graphics4.TextureFormat.RGBA128); 23 | done(image); 24 | } 25 | 26 | override function set(value: Dynamic) { 27 | if (inputs.length > 0) inputs[0].set(value); 28 | else this.value = value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/ImageTextureNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class ImageTextureNode extends LogicNode { 5 | 6 | public var file: String; 7 | public var color_space: String; 8 | 9 | public function new(tree: LogicTree) { 10 | super(tree); 11 | } 12 | 13 | override function get(from: Int, done: Dynamic->Void) { 14 | var index = Project.assetNames.indexOf(file); 15 | var asset = Project.assets[index]; 16 | done(Project.getImage(asset)); 17 | } 18 | 19 | override public function getImage(): kha.Image { 20 | var image: kha.Image; 21 | get(0, function(img: kha.Image) { image = img; }); 22 | return image; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/InpaintNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class InpaintNode extends LogicNode { 5 | 6 | static var image: kha.Image = null; 7 | static var mask: kha.Image = null; 8 | static var result: kha.Image = null; 9 | 10 | static var temp: kha.Image = null; 11 | static var prompt = ""; 12 | static var strength = 0.5; 13 | static var auto = true; 14 | 15 | public function new(tree: LogicTree) { 16 | super(tree); 17 | 18 | init(); 19 | } 20 | 21 | public static function init() { 22 | if (image == null) { 23 | image = kha.Image.createRenderTarget(Config.getTextureResX(), Config.getTextureResY()); 24 | } 25 | 26 | if (mask == null) { 27 | mask = kha.Image.createRenderTarget(Config.getTextureResX(), Config.getTextureResY(), kha.graphics4.TextureFormat.L8); 28 | App.notifyOnNextFrame(function() { 29 | mask.g4.begin(); 30 | mask.g4.clear(kha.Color.fromFloats(1.0, 1.0, 1.0, 1.0)); 31 | mask.g4.end(); 32 | }); 33 | } 34 | 35 | if (temp == null) { 36 | temp = kha.Image.createRenderTarget(512, 512); 37 | } 38 | 39 | if (result == null) { 40 | result = kha.Image.createRenderTarget(Config.getTextureResX(), Config.getTextureResY()); 41 | } 42 | } 43 | 44 | public static function buttons(ui: zui.Zui, nodes: zui.Nodes, node: zui.Nodes.TNode) { 45 | auto = node.buttons[0].default_value; 46 | if (!auto) { 47 | strength = ui.slider(zui.Id.handle({value: strength}), tr("strength"), 0, 1, true); 48 | prompt = zui.Ext.textArea(ui, zui.Id.handle(), true, tr("prompt"), true); 49 | node.buttons[1].height = 1 + prompt.split("\n").length; 50 | } 51 | else node.buttons[1].height = 0; 52 | } 53 | 54 | override function get(from: Int, done: Dynamic->Void) { 55 | inputs[0].get(function(source: Dynamic) { 56 | if (!Std.isOfType(source, kha.Image)) { done(null); return; } 57 | 58 | image.g2.begin(false); 59 | image.g2.drawScaledImage(source, 0, 0, Config.getTextureResX(), Config.getTextureResY()); 60 | image.g2.end(); 61 | 62 | result = auto ? texsynthInpaint(image, false, mask) : sdInpaint(image, mask); 63 | 64 | done(result); 65 | }); 66 | } 67 | 68 | override public function getImage(): kha.Image { 69 | App.notifyOnNextFrame(function() { 70 | inputs[0].get(function(source: Dynamic) { 71 | if (Layers.pipeCopy == null) Layers.makePipe(); 72 | if (iron.data.ConstData.screenAlignedVB == null) iron.data.ConstData.createScreenAlignedData(); 73 | image.g4.begin(); 74 | image.g4.setPipeline(Layers.pipeApplyMask); 75 | image.g4.setTexture(Layers.tex0Mask, source); 76 | image.g4.setTexture(Layers.texaMask, mask); 77 | image.g4.setVertexBuffer(iron.data.ConstData.screenAlignedVB); 78 | image.g4.setIndexBuffer(iron.data.ConstData.screenAlignedIB); 79 | image.g4.drawIndexedVertices(); 80 | image.g4.end(); 81 | }); 82 | }); 83 | return image; 84 | } 85 | 86 | public function getTarget(): kha.Image { 87 | return mask; 88 | } 89 | 90 | public static function texsynthInpaint(image: kha.Image, tiling: Bool, mask: kha.Image = null): kha.Image { 91 | var w = arm.Config.getTextureResX(); 92 | var h = arm.Config.getTextureResY(); 93 | 94 | var bytes_img = untyped image.getPixels().b.buffer; 95 | var bytes_mask = mask != null ? untyped mask.getPixels().b.buffer : new js.lib.ArrayBuffer(w * h); 96 | var bytes_out = haxe.io.Bytes.ofData(new js.lib.ArrayBuffer(w * h * 4)); 97 | untyped Krom_texsynth.inpaint(w, h, untyped bytes_out.b.buffer, bytes_img, bytes_mask, tiling); 98 | 99 | return kha.Image.fromBytes(bytes_out, w, h); 100 | } 101 | 102 | public static function sdInpaint(image: kha.Image, mask: kha.Image): kha.Image { 103 | init(); 104 | 105 | var bytes_img = untyped mask.getPixels().b.buffer; 106 | var u8 = new js.lib.Uint8Array(untyped bytes_img); 107 | var f32mask = new js.lib.Float32Array(4 * 64 * 64); 108 | 109 | kha.Assets.loadBlobFromPath("data/models/sd_vae_encoder.quant.onnx", function(vae_encoder_blob: kha.Blob) { 110 | // for (x in 0...Std.int(image.width / 512)) { 111 | // for (y in 0...Std.int(image.height / 512)) { 112 | var x = 0; 113 | var y = 0; 114 | 115 | for (xx in 0...64) { 116 | for (yy in 0...64) { 117 | // var step = Std.int(512 / 64); 118 | // var j = (yy * step * mask.width + xx * step) + (y * 512 * mask.width + x * 512); 119 | var step = Std.int(mask.width / 64); 120 | var j = (yy * step * mask.width + xx * step); 121 | var f = u8[j] / 255.0; 122 | var i = yy * 64 + xx; 123 | f32mask[i ] = f; 124 | f32mask[i + 64 * 64 ] = f; 125 | f32mask[i + 64 * 64 * 2] = f; 126 | f32mask[i + 64 * 64 * 3] = f; 127 | } 128 | } 129 | 130 | temp.g2.begin(false); 131 | // temp.g2.drawImage(image, -x * 512, -y * 512); 132 | temp.g2.drawScaledImage(image, 0, 0, 512, 512); 133 | temp.g2.end(); 134 | 135 | var bytes_img = untyped temp.getPixels().b.buffer; 136 | var u8 = new js.lib.Uint8Array(untyped bytes_img); 137 | var f32 = new js.lib.Float32Array(3 * 512 * 512); 138 | for (i in 0...(512 * 512)) { 139 | f32[i ] = (u8[i * 4 ] / 255.0) * 2.0 - 1.0; 140 | f32[i + 512 * 512 ] = (u8[i * 4 + 1] / 255.0) * 2.0 - 1.0; 141 | f32[i + 512 * 512 * 2] = (u8[i * 4 + 2] / 255.0) * 2.0 - 1.0; 142 | } 143 | 144 | var latents_buf = Krom.mlInference(untyped vae_encoder_blob.toBytes().b.buffer, [f32.buffer], [[1, 3, 512, 512]], [1, 4, 64, 64], Config.raw.gpu_inference); 145 | var latents = new js.lib.Float32Array(latents_buf); 146 | for (i in 0...latents.length) { 147 | latents[i] = 0.18215 * latents[i]; 148 | } 149 | var latents_orig = latents.slice(0); 150 | 151 | var noise = new js.lib.Float32Array(latents.length); 152 | for (i in 0...noise.length) noise[i] = Math.cos(2.0 * 3.14 * RandomNode.getFloat()) * Math.sqrt(-2.0 * Math.log(RandomNode.getFloat())); 153 | 154 | var num_inference_steps = 50; 155 | var init_timestep = Std.int(num_inference_steps * strength); 156 | var timestep = @:privateAccess TextToPhotoNode.timesteps[num_inference_steps - init_timestep]; 157 | var alphas_cumprod = @:privateAccess TextToPhotoNode.alphas_cumprod; 158 | var sqrt_alpha_prod = Math.pow(alphas_cumprod[timestep], 0.5); 159 | var sqrt_one_minus_alpha_prod = Math.pow(1.0 - alphas_cumprod[timestep], 0.5); 160 | for (i in 0...latents.length) { 161 | latents[i] = sqrt_alpha_prod * latents[i] + sqrt_one_minus_alpha_prod * noise[i]; 162 | } 163 | 164 | var start = num_inference_steps - init_timestep; 165 | 166 | TextToPhotoNode.stableDiffusion(prompt, function(img: kha.Image) { 167 | // result.g2.begin(false); 168 | // result.g2.drawImage(img, x * 512, y * 512); 169 | // result.g2.end(); 170 | result = img; 171 | }, latents, start, true, f32mask, latents_orig); 172 | // } 173 | // } 174 | }); 175 | 176 | return result; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/IntegerNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class IntegerNode extends LogicNode { 5 | 6 | public var value: Int; 7 | 8 | public function new(tree: LogicTree, value = 0) { 9 | super(tree); 10 | this.value = value; 11 | } 12 | 13 | override function get(from: Int, done: Dynamic->Void) { 14 | if (inputs.length > 0) inputs[0].get(done); 15 | else done(value); 16 | } 17 | 18 | override function set(value: Dynamic) { 19 | if (inputs.length > 0) inputs[0].set(value); 20 | else this.value = value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/MathNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class MathNode extends LogicNode { 5 | 6 | public var operation: String; 7 | public var use_clamp: Bool; 8 | 9 | public function new(tree: LogicTree) { 10 | super(tree); 11 | } 12 | 13 | override function get(from: Int, done: Dynamic->Void) { 14 | 15 | inputs[0].get(function(v1: Float) { 16 | inputs[1].get(function(v2: Float) { 17 | 18 | var f = 0.0; 19 | switch (operation) { 20 | case "Add": 21 | f = v1 + v2; 22 | case "Multiply": 23 | f = v1 * v2; 24 | case "Sine": 25 | f = Math.sin(v1); 26 | case "Cosine": 27 | f = Math.cos(v1); 28 | case "Max": 29 | f = Math.max(v1, v2); 30 | case "Min": 31 | f = Math.min(v1, v2); 32 | case "Absolute": 33 | f = Math.abs(v1); 34 | case "Subtract": 35 | f = v1 - v2; 36 | case "Divide": 37 | f = v1 / (v2 == 0.0 ? 0.000001 : v2); 38 | case "Tangent": 39 | f = Math.tan(v1); 40 | case "Arcsine": 41 | f = Math.asin(v1); 42 | case "Arccosine": 43 | f = Math.acos(v1); 44 | case "Arctangent": 45 | f = Math.atan(v1); 46 | case "Arctan2": 47 | f = Math.atan2(v2, v1); 48 | case "Power": 49 | f = Math.pow(v1, v2); 50 | case "Logarithm": 51 | f = Math.log(v1); 52 | case "Round": 53 | f = Math.round(v1); 54 | case "Floor": 55 | f = Math.floor(v1); 56 | case "Ceil": 57 | f = Math.ceil(v1); 58 | case "Truncate": 59 | f = Math.ffloor(v1); 60 | case "Fraction": 61 | f = v1 - Math.floor(v1); 62 | case "Less Than": 63 | f = v1 < v2 ? 1.0 : 0.0; 64 | case "Greater Than": 65 | f = v1 > v2 ? 1.0 : 0.0; 66 | case "Modulo": 67 | f = v1 % v2; 68 | case "Snap": 69 | f = Math.floor(v1 / v2) * v2; 70 | case "Square Root": 71 | f = Math.sqrt(v1); 72 | case "Inverse Square Root": 73 | f = 1.0 / Math.sqrt(v1); 74 | case "Exponent": 75 | f = Math.exp(v1); 76 | case "Sign": 77 | f = v1 > 0 ? 1.0 : (v1 < 0 ? -1.0 : 0); 78 | case "Ping-Pong": 79 | f = (v2 != 0.0) ? v2 - Math.abs((Math.abs(v1) % (2 * v2)) - v2) : 0.0; 80 | case "Hyperbolic Sine": 81 | f = (Math.exp(v1) - Math.exp(-v1)) / 2.0; 82 | case "Hyperbolic Cosine": 83 | f = (Math.exp(v1) + Math.exp(-v1)) / 2.0; 84 | case "Hyperbolic Tangent": 85 | f = 1.0 - (2.0 / (Math.exp(2 * v1) + 1)); 86 | case "To Radians": 87 | f = v1 / 180.0 * Math.PI; 88 | case "To Degrees": 89 | f = v1 / Math.PI * 180.0; 90 | } 91 | 92 | if (use_clamp) f = f < 0.0 ? 0.0 : (f > 1.0 ? 1.0 : f); 93 | 94 | done(f); 95 | }); 96 | }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/NullNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class NullNode extends LogicNode { 5 | 6 | public function new(tree: LogicTree) { 7 | super(tree); 8 | } 9 | 10 | override function get(from: Int, done: Dynamic->Void) { 11 | done(null); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/PhotoToPBRNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | import arm.Enums; 4 | 5 | @:keep 6 | class PhotoToPBRNode extends LogicNode { 7 | 8 | static var temp: kha.Image = null; 9 | static var images: Array = null; 10 | static var modelNames = ["base", "occlusion", "roughness", "metallic", "normal", "height"]; 11 | 12 | public static var cachedSource: Dynamic = null; 13 | 14 | public function new(tree: LogicTree) { 15 | super(tree); 16 | 17 | if (temp == null) { 18 | temp = kha.Image.createRenderTarget(2176, 2176); 19 | } 20 | 21 | init(); 22 | } 23 | 24 | public static function init() { 25 | if (images == null) { 26 | images = []; 27 | for (i in 0...modelNames.length) { 28 | images.push(kha.Image.createRenderTarget(Config.getTextureResX(), Config.getTextureResY())); 29 | } 30 | } 31 | } 32 | 33 | override function get(from: Int, done: Dynamic->Void) { 34 | function getSource(done: Dynamic->Void) { 35 | if (cachedSource != null) done(cachedSource); 36 | else inputs[0].get(done); 37 | } 38 | 39 | getSource(function(source: kha.Image) { 40 | cachedSource = source; 41 | 42 | if (!Std.isOfType(source, kha.Image)) { done(null); return; } 43 | 44 | var tilesX = Std.int(Config.getTextureResX() / 2048); 45 | var tilesY = Std.int(Config.getTextureResY() / 2048); 46 | for (i in 0...(tilesX * tilesY)) { 47 | var x = i % tilesX; 48 | var y = Std.int(i / tilesX); 49 | 50 | temp.g2.begin(false); 51 | temp.g2.drawScaledImage(source, 64 - x * 2048, 64 - y * 2048, -Config.getTextureResX(), Config.getTextureResY()); 52 | temp.g2.drawScaledImage(source, 64 - x * 2048, 64 - y * 2048, Config.getTextureResX(), -Config.getTextureResY()); 53 | temp.g2.drawScaledImage(source, 64 - x * 2048, 64 - y * 2048, -Config.getTextureResX(), -Config.getTextureResY()); 54 | temp.g2.drawScaledImage(source, 64 - x * 2048 + 2048, 64 - y * 2048 + 2048, Config.getTextureResX(), Config.getTextureResY()); 55 | temp.g2.drawScaledImage(source, 64 - x * 2048 + 2048, 64 - y * 2048 + 2048, -Config.getTextureResX(), Config.getTextureResY()); 56 | temp.g2.drawScaledImage(source, 64 - x * 2048 + 2048, 64 - y * 2048 + 2048, Config.getTextureResX(), -Config.getTextureResY()); 57 | temp.g2.drawScaledImage(source, 64 - x * 2048, 64 - y * 2048, Config.getTextureResX(), Config.getTextureResY()); 58 | temp.g2.end(); 59 | 60 | var bytes_img = untyped temp.getPixels().b.buffer; 61 | var u8 = new js.lib.Uint8Array(untyped bytes_img); 62 | var f32 = new js.lib.Float32Array(3 * 2176 * 2176); 63 | for (i in 0...(2176 * 2176)) { 64 | f32[i ] = (u8[i * 4 ] / 255 - 0.5) / 0.5; 65 | f32[i + 2176 * 2176 ] = (u8[i * 4 + 1] / 255 - 0.5) / 0.5; 66 | f32[i + 2176 * 2176 * 2] = (u8[i * 4 + 2] / 255 - 0.5) / 0.5; 67 | } 68 | 69 | kha.Assets.loadBlobFromPath("data/models/photo_to_" + modelNames[from] + ".quant.onnx", function(model_blob: kha.Blob) { 70 | var buf = Krom.mlInference(untyped model_blob.toBytes().b.buffer, [f32.buffer], null, null, Config.raw.gpu_inference); 71 | var ar = new js.lib.Float32Array(buf); 72 | var bytes = haxe.io.Bytes.alloc(4 * 2048 * 2048); 73 | var offsetG = (from == ChannelBaseColor || from == ChannelNormalMap) ? 2176 * 2176 : 0; 74 | var offsetB = (from == ChannelBaseColor || from == ChannelNormalMap) ? 2176 * 2176 * 2 : 0; 75 | for (i in 0...(2048 * 2048)) { 76 | var x = 64 + i % 2048; 77 | var y = 64 + Std.int(i / 2048); 78 | bytes.set(i * 4 , Std.int((ar[y * 2176 + x ] * 0.5 + 0.5) * 255)); 79 | bytes.set(i * 4 + 1, Std.int((ar[y * 2176 + x + offsetG] * 0.5 + 0.5) * 255)); 80 | bytes.set(i * 4 + 2, Std.int((ar[y * 2176 + x + offsetB] * 0.5 + 0.5) * 255)); 81 | bytes.set(i * 4 + 3, 255); 82 | } 83 | 84 | #if (kha_metal || kha_vulkan) 85 | if (from == ChannelBaseColor) bgraSwap(bytes); 86 | #end 87 | 88 | var temp2 = kha.Image.fromBytes(bytes, 2048, 2048); 89 | images[from].g2.begin(false); 90 | images[from].g2.drawImage(temp2, x * 2048, y * 2048); 91 | images[from].g2.end(); 92 | App.notifyOnNextFrame(function() { 93 | temp2.unload(); 94 | }); 95 | }); 96 | } 97 | 98 | done(images[from]); 99 | }); 100 | } 101 | 102 | #if (kha_metal || kha_vulkan) 103 | static function bgraSwap(bytes: haxe.io.Bytes) { 104 | for (i in 0...Std.int(bytes.length / 4)) { 105 | var r = bytes.get(i * 4); 106 | bytes.set(i * 4, bytes.get(i * 4 + 2)); 107 | bytes.set(i * 4 + 2, r); 108 | } 109 | return bytes; 110 | } 111 | #end 112 | } 113 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/RandomNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class RandomNode extends LogicNode { 5 | 6 | public function new(tree: LogicTree) { 7 | super(tree); 8 | } 9 | 10 | override function get(from: Int, done: Dynamic->Void) { 11 | inputs[0].get(function(min: Float) { 12 | inputs[1].get(function(max: Float) { 13 | done(min + getFloat() * (max - min)); 14 | }); 15 | }); 16 | } 17 | 18 | static var a: Int; 19 | static var b: Int; 20 | static var c: Int; 21 | static var d = setSeed(352124); 22 | 23 | public static function setSeed(seed: Int): Int { 24 | d = seed; 25 | a = 0x36aef51a; 26 | b = 0x21d4b3eb; 27 | c = 0xf2517abf; 28 | // Immediately skip a few possibly poor results the easy way 29 | for (i in 0...15) { 30 | getInt(); 31 | } 32 | return d; 33 | } 34 | 35 | public static function getSeed(): Int { 36 | return d; 37 | } 38 | 39 | // Courtesy of https://github.com/Kode/Kha/blob/main/Sources/kha/math/Random.hx 40 | public static function getInt(): Int { 41 | var t = (a + b | 0) + d | 0; 42 | d = d + 1 | 0; 43 | a = b ^ b >>> 9; 44 | b = c + (c << 3) | 0; 45 | c = c << 21 | c >>> 11; 46 | c = c + t | 0; 47 | return t & 0x7fffffff; 48 | } 49 | 50 | public static function getFloat(): Float { 51 | return getInt() / 0x7fffffff; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/SeparateVectorNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | import iron.math.Vec4; 4 | 5 | @:keep 6 | class SeparateVectorNode extends LogicNode { 7 | 8 | public function new(tree: LogicTree) { 9 | super(tree); 10 | } 11 | 12 | override function get(from: Int, done: Dynamic->Void) { 13 | inputs[0].get(function(vector: Vec4) { 14 | if (from == 0) done(vector.x); 15 | else if (from == 1) done(vector.y); 16 | else done(vector.z); 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/StringNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class StringNode extends LogicNode { 5 | 6 | public var value: String; 7 | 8 | public function new(tree: LogicTree, value = "") { 9 | super(tree); 10 | this.value = value; 11 | } 12 | 13 | override function get(from: Int, done: Dynamic->Void) { 14 | if (inputs.length > 0) inputs[0].get(done); 15 | else done(value); 16 | } 17 | 18 | override function set(value: Dynamic) { 19 | if (inputs.length > 0) inputs[0].set(value); 20 | else this.value = value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/TilingNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class TilingNode extends LogicNode { 5 | 6 | static var image: kha.Image = null; 7 | var result: kha.Image = null; 8 | static var prompt = ""; 9 | static var strength = 0.5; 10 | static var auto = true; 11 | 12 | public function new(tree: LogicTree) { 13 | super(tree); 14 | 15 | init(); 16 | } 17 | 18 | public static function init() { 19 | if (image == null) { 20 | image = kha.Image.createRenderTarget(Config.getTextureResX(), Config.getTextureResY()); 21 | } 22 | } 23 | 24 | public static function buttons(ui: zui.Zui, nodes: zui.Nodes, node: zui.Nodes.TNode) { 25 | auto = node.buttons[0].default_value; 26 | if (!auto) { 27 | strength = ui.slider(zui.Id.handle({value: strength}), tr("strength"), 0, 1, true); 28 | prompt = zui.Ext.textArea(ui, zui.Id.handle(), true, tr("prompt"), true); 29 | node.buttons[1].height = 1 + prompt.split("\n").length; 30 | } 31 | else node.buttons[1].height = 0; 32 | } 33 | 34 | override function get(from: Int, done: Dynamic->Void) { 35 | inputs[0].get(function(source: Dynamic) { 36 | if (!Std.isOfType(source, kha.Image)) { done(null); return; } 37 | 38 | image.g2.begin(false); 39 | image.g2.drawScaledImage(source, 0, 0, Config.getTextureResX(), Config.getTextureResY()); 40 | image.g2.end(); 41 | 42 | result = auto ? InpaintNode.texsynthInpaint(image, true) : sdTiling(image); 43 | done(result); 44 | }); 45 | } 46 | 47 | override public function getImage(): kha.Image { 48 | return result; 49 | } 50 | 51 | public static function sdTiling(image: kha.Image, seed = -1): kha.Image { 52 | @:privateAccess TextToPhotoNode.tiling = false; 53 | var tile = kha.Image.createRenderTarget(512, 512); 54 | tile.g2.begin(false); 55 | tile.g2.drawScaledImage(image, -256, -256, 512, 512); 56 | tile.g2.drawScaledImage(image, 256, -256, 512, 512); 57 | tile.g2.drawScaledImage(image, -256, 256, 512, 512); 58 | tile.g2.drawScaledImage(image, 256, 256, 512, 512); 59 | tile.g2.end(); 60 | 61 | var bytes = haxe.io.Bytes.alloc(512 * 512); 62 | for (i in 0...512 * 512) { 63 | var x = i % 512; 64 | var y = Std.int(i / 512); 65 | var l = y < 256 ? y : (511 - y); 66 | bytes.set(i, (x > 256 - l && x < 256 + l) ? 0 : 255); 67 | } 68 | // for (i in 0...512 * 512) bytes.set(i, 255); 69 | // for (x in (256 - 32)...(256 + 32)) { 70 | // for (y in 0...512) { 71 | // bytes.set(y * 512 + x, 0); 72 | // } 73 | // } 74 | // for (x in 0...512) { 75 | // for (y in (256 - 32)...(256 + 32)) { 76 | // bytes.set(y * 512 + x, 0); 77 | // } 78 | // } 79 | var mask = kha.Image.fromBytes(bytes, 512, 512, kha.graphics4.TextureFormat.L8); 80 | 81 | @:privateAccess InpaintNode.prompt = prompt; 82 | @:privateAccess InpaintNode.strength = strength; 83 | if (seed >= 0) RandomNode.setSeed(seed); 84 | return InpaintNode.sdInpaint(tile, mask); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/UpscaleNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class UpscaleNode extends LogicNode { 5 | 6 | static var temp: kha.Image = null; 7 | static var image: kha.Image = null; 8 | 9 | public function new(tree: LogicTree) { 10 | super(tree); 11 | } 12 | 13 | override function get(from: Int, done: Dynamic->Void) { 14 | inputs[0].get(function(img: kha.Image) { 15 | image = img; 16 | 17 | if (image.width < Config.getTextureResX()) { 18 | image = esrgan(image); 19 | while (image.width < Config.getTextureResX()) { 20 | var lastImage = image; 21 | image = esrgan(image); 22 | lastImage.unload(); 23 | } 24 | } 25 | 26 | done(image); 27 | }); 28 | } 29 | 30 | override public function getImage(): kha.Image { 31 | return image; 32 | } 33 | 34 | public static function esrgan(source: kha.Image): kha.Image { 35 | function doTile(source: kha.Image) { 36 | var result: kha.Image = null; 37 | var size1w = source.width; 38 | var size1h = source.height; 39 | var size2w = Std.int(size1w * 2); 40 | var size2h = Std.int(size1h * 2); 41 | if (temp != null) { 42 | temp.unload(); 43 | } 44 | temp = kha.Image.createRenderTarget(size1w, size1h); 45 | temp.g2.begin(false); 46 | temp.g2.drawScaledImage(source, 0, 0, size1w, size1h); 47 | temp.g2.end(); 48 | 49 | var bytes_img = untyped temp.getPixels().b.buffer; 50 | var u8 = new js.lib.Uint8Array(untyped bytes_img); 51 | var f32 = new js.lib.Float32Array(3 * size1w * size1h); 52 | for (i in 0...(size1w * size1h)) { 53 | f32[i ] = (u8[i * 4 ] / 255); 54 | f32[i + size1w * size1w ] = (u8[i * 4 + 1] / 255); 55 | f32[i + size1w * size1w * 2] = (u8[i * 4 + 2] / 255); 56 | } 57 | 58 | kha.Assets.loadBlobFromPath("data/models/esrgan.quant.onnx", function(esrgan_blob: kha.Blob) { 59 | var esrgan2x_buf = Krom.mlInference(untyped esrgan_blob.toBytes().b.buffer, [f32.buffer], [[1, 3, size1w, size1h]], [1, 3, size2w, size2h], Config.raw.gpu_inference, true); 60 | var esrgan2x = new js.lib.Float32Array(esrgan2x_buf); 61 | for (i in 0...esrgan2x.length) { 62 | if (esrgan2x[i] < 0) esrgan2x[i] = 0; 63 | else if (esrgan2x[i] > 1) esrgan2x[i] = 1; 64 | } 65 | 66 | var bytes = haxe.io.Bytes.alloc(4 * size2w * size2h); 67 | for (i in 0...(size2w * size2h)) { 68 | bytes.set(i * 4 , Std.int(esrgan2x[i ] * 255)); 69 | bytes.set(i * 4 + 1, Std.int(esrgan2x[i + size2w * size2w ] * 255)); 70 | bytes.set(i * 4 + 2, Std.int(esrgan2x[i + size2w * size2w * 2] * 255)); 71 | bytes.set(i * 4 + 3, 255); 72 | } 73 | 74 | result = kha.Image.fromBytes(bytes, size2w, size2h); 75 | }); 76 | 77 | return result; 78 | } 79 | 80 | var result: kha.Image = null; 81 | var size1w = source.width; 82 | var size1h = source.height; 83 | var size2w = Std.int(size1w * 2); 84 | var size2h = Std.int(size1h * 2); 85 | var tileSize = 512; 86 | // var tileSize = 1024; 87 | var tileSize2x = Std.int(tileSize * 2); 88 | 89 | if (size1w >= tileSize2x || size1h >= tileSize2x) { // Split into tiles 90 | result = kha.Image.createRenderTarget(size2w, size2h); 91 | var tileSource = kha.Image.createRenderTarget(tileSize, tileSize); 92 | for (x in 0...Std.int(size1w / tileSize)) { 93 | for (y in 0...Std.int(size1h / tileSize)) { 94 | tileSource.g2.begin(false); 95 | tileSource.g2.drawImage(source, -x * tileSize, -y * tileSize); 96 | tileSource.g2.end(); 97 | var tileResult = doTile(tileSource); 98 | result.g2.begin(false); 99 | result.g2.drawImage(tileResult, x * tileSize2x, y * tileSize2x); 100 | result.g2.end(); 101 | tileResult.unload(); 102 | } 103 | } 104 | tileSource.unload(); 105 | } 106 | else result = doTile(source); // Single tile 107 | return result; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/VarianceNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class VarianceNode extends LogicNode { 5 | 6 | static var temp: kha.Image = null; 7 | static var image: kha.Image = null; 8 | static var inst: VarianceNode = null; 9 | static var prompt = ""; 10 | 11 | public function new(tree: LogicTree) { 12 | super(tree); 13 | 14 | inst = this; 15 | 16 | init(); 17 | } 18 | 19 | public static function init() { 20 | if (temp == null) { 21 | temp = kha.Image.createRenderTarget(512, 512); 22 | } 23 | } 24 | 25 | public static function buttons(ui: zui.Zui, nodes: zui.Nodes, node: zui.Nodes.TNode) { 26 | prompt = zui.Ext.textArea(ui, zui.Id.handle(), true, tr("prompt"), true); 27 | node.buttons[0].height = prompt.split("\n").length; 28 | } 29 | 30 | override function get(from: Int, done: Dynamic->Void) { 31 | var strength = untyped inst.inputs[1].node.value; 32 | 33 | inst.inputs[0].get(function(source: kha.Image) { 34 | temp.g2.begin(false); 35 | temp.g2.drawScaledImage(source, 0, 0, 512, 512); 36 | temp.g2.end(); 37 | 38 | var bytes_img = untyped temp.getPixels().b.buffer; 39 | var u8 = new js.lib.Uint8Array(untyped bytes_img); 40 | var f32 = new js.lib.Float32Array(3 * 512 * 512); 41 | for (i in 0...(512 * 512)) { 42 | f32[i ] = (u8[i * 4 ] / 255) * 2.0 - 1.0; 43 | f32[i + 512 * 512 ] = (u8[i * 4 + 1] / 255) * 2.0 - 1.0; 44 | f32[i + 512 * 512 * 2] = (u8[i * 4 + 2] / 255) * 2.0 - 1.0; 45 | } 46 | 47 | kha.Assets.loadBlobFromPath("data/models/sd_vae_encoder.quant.onnx", function(vae_encoder_blob: kha.Blob) { 48 | var latents_buf = Krom.mlInference(untyped vae_encoder_blob.toBytes().b.buffer, [f32.buffer], [[1, 3, 512, 512]], [1, 4, 64, 64], Config.raw.gpu_inference); 49 | var latents = new js.lib.Float32Array(latents_buf); 50 | for (i in 0...latents.length) { 51 | latents[i] = 0.18215 * latents[i]; 52 | } 53 | 54 | var noise = new js.lib.Float32Array(latents.length); 55 | for (i in 0...noise.length) noise[i] = Math.cos(2.0 * 3.14 * RandomNode.getFloat()) * Math.sqrt(-2.0 * Math.log(RandomNode.getFloat())); 56 | var num_inference_steps = 50; 57 | var init_timestep = Std.int(num_inference_steps * strength); 58 | var timesteps = @:privateAccess TextToPhotoNode.timesteps[num_inference_steps - init_timestep]; 59 | var alphas_cumprod = @:privateAccess TextToPhotoNode.alphas_cumprod; 60 | var sqrt_alpha_prod = Math.pow(alphas_cumprod[timesteps], 0.5); 61 | var sqrt_one_minus_alpha_prod = Math.pow(1.0 - alphas_cumprod[timesteps], 0.5); 62 | for (i in 0...latents.length) { 63 | latents[i] = sqrt_alpha_prod * latents[i] + sqrt_one_minus_alpha_prod * noise[i]; 64 | } 65 | var t_start = num_inference_steps - init_timestep; 66 | 67 | TextToPhotoNode.stableDiffusion(prompt, function(img: kha.Image) { 68 | image = img; 69 | }, latents, t_start); 70 | }); 71 | 72 | done(image); 73 | }); 74 | } 75 | 76 | override public function getImage(): kha.Image { 77 | return image; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/VectorMathNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | import iron.math.Vec4; 4 | 5 | @:keep 6 | class VectorMathNode extends LogicNode { 7 | 8 | public var operation: String; 9 | var v = new Vec4(); 10 | 11 | public function new(tree: LogicTree) { 12 | super(tree); 13 | } 14 | 15 | override function get(from: Int, done: Dynamic->Void) { 16 | inputs[0].get(function(v1: Vec4) { 17 | inputs[1].get(function(v2: Vec4) { 18 | v.setFrom(v1); 19 | var f = 0.0; 20 | 21 | switch (operation) { 22 | case "Add": 23 | v.add(v2); 24 | case "Subtract": 25 | v.sub(v2); 26 | case "Average": 27 | v.add(v2); 28 | v.x *= 0.5; 29 | v.y *= 0.5; 30 | v.z *= 0.5; 31 | case "Dot Product": 32 | f = v.dot(v2); 33 | v.set(f, f, f); 34 | case "Cross Product": 35 | v.cross(v2); 36 | case "Normalize": 37 | v.normalize(); 38 | case "Multiply": 39 | v.x *= v2.x; 40 | v.y *= v2.y; 41 | v.z *= v2.z; 42 | case "Divide": 43 | v.x /= v2.x == 0.0 ? 0.000001 : v2.x; 44 | v.y /= v2.y == 0.0 ? 0.000001 : v2.y; 45 | v.z /= v2.z == 0.0 ? 0.000001 : v2.z; 46 | case "Length": 47 | f = v.length(); 48 | v.set(f, f, f); 49 | case "Distance": 50 | f = v.distanceTo(v2); 51 | v.set(f, f, f); 52 | case "Project": 53 | v.setFrom(v2); 54 | v.mult(v1.dot(v2) / v2.dot(v2)); 55 | case "Reflect": 56 | var tmp = new Vec4(); 57 | tmp.setFrom(v2); 58 | tmp.normalize(); 59 | v.reflect(tmp); 60 | case "Scale": 61 | v.x *= v2.x; 62 | v.y *= v2.x; 63 | v.z *= v2.x; 64 | case "Absolute": 65 | v.x = Math.abs(v.x); 66 | v.y = Math.abs(v.y); 67 | v.z = Math.abs(v.z); 68 | case "Minimum": 69 | v.x = Math.min(v1.x, v2.x); 70 | v.y = Math.min(v1.y, v2.y); 71 | v.z = Math.min(v1.z, v2.z); 72 | case "Maximum": 73 | v.x = Math.max(v1.x, v2.x); 74 | v.y = Math.max(v1.y, v2.y); 75 | v.z = Math.max(v1.z, v2.z); 76 | case "Floor": 77 | v.x = Math.floor(v1.x); 78 | v.y = Math.floor(v1.y); 79 | v.z = Math.floor(v1.z); 80 | case "Ceil": 81 | v.x = Math.ceil(v1.x); 82 | v.y = Math.ceil(v1.y); 83 | v.z = Math.ceil(v1.z); 84 | case "Fraction": 85 | v.x = v1.x - Math.floor(v1.x); 86 | v.y = v1.y - Math.floor(v1.y); 87 | v.z = v1.z - Math.floor(v1.z); 88 | case "Modulo": 89 | v.x = v1.x % v2.x; 90 | v.y = v1.y % v2.y; 91 | v.z = v1.z % v2.z; 92 | case "Snap": 93 | v.x = Math.floor(v1.x / v2.x) * v2.x; 94 | v.y = Math.floor(v1.y / v2.y) * v2.y; 95 | v.z = Math.floor(v1.z / v2.z) * v2.z; 96 | case "Sine": 97 | v.x = Math.sin(v1.x); 98 | v.y = Math.sin(v1.y); 99 | v.z = Math.sin(v1.z); 100 | case "Cosine": 101 | v.x = Math.cos(v1.x); 102 | v.y = Math.cos(v1.y); 103 | v.z = Math.cos(v1.z); 104 | case "Tangent": 105 | v.x = Math.tan(v1.x); 106 | v.y = Math.tan(v1.y); 107 | v.z = Math.tan(v1.z); 108 | } 109 | 110 | if (from == 0) done(v); 111 | else done(f); 112 | }); 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/VectorNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | import iron.math.Vec4; 4 | 5 | @:keep 6 | class VectorNode extends LogicNode { 7 | 8 | var value = new Vec4(); 9 | var image: kha.Image = null; 10 | 11 | public function new(tree: LogicTree, x: Null = null, y: Null = null, z: Null = null) { 12 | super(tree); 13 | 14 | if (x != null) { 15 | addInput(new FloatNode(tree, x), 0); 16 | addInput(new FloatNode(tree, y), 0); 17 | addInput(new FloatNode(tree, z), 0); 18 | } 19 | } 20 | 21 | override function get(from: Int, done: Dynamic->Void) { 22 | if (image != null) image.unload(); 23 | var b = haxe.io.Bytes.alloc(16); 24 | b.setFloat(0, untyped inputs[0].node.value); 25 | b.setFloat(4, untyped inputs[1].node.value); 26 | b.setFloat(8, untyped inputs[2].node.value); 27 | b.setFloat(12, 1.0); 28 | image = kha.Image.fromBytes(b, 1, 1, kha.graphics4.TextureFormat.RGBA128); 29 | done(image); 30 | } 31 | 32 | override function set(value: Dynamic) { 33 | inputs[0].set(value.x); 34 | inputs[1].set(value.y); 35 | inputs[2].set(value.z); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/arm/render/Inc.hx: -------------------------------------------------------------------------------- 1 | package arm.render; 2 | 3 | import iron.math.Vec4; 4 | import iron.math.Mat4; 5 | import iron.math.Quat; 6 | import iron.object.MeshObject; 7 | import iron.system.Input; 8 | import iron.RenderPath; 9 | import iron.Scene; 10 | import arm.ui.UIHeader; 11 | import arm.Enums; 12 | 13 | class Inc { 14 | 15 | static var path: RenderPath; 16 | public static var superSample = 1.0; 17 | 18 | static var lastX = -1.0; 19 | static var lastY = -1.0; 20 | 21 | #if rp_voxels 22 | static var voxelsCreated = false; 23 | #end 24 | 25 | public static function init(_path: RenderPath) { 26 | path = _path; 27 | var config = Config.raw; 28 | superSample = config.rp_supersample; 29 | } 30 | 31 | public static function applyConfig() { 32 | var config = Config.raw; 33 | if (superSample != config.rp_supersample) { 34 | superSample = config.rp_supersample; 35 | for (rt in path.renderTargets) { 36 | if (rt.raw.width == 0 && rt.raw.scale != null) { 37 | rt.raw.scale = getSuperSampling(); 38 | } 39 | } 40 | path.resize(); 41 | } 42 | #if rp_voxels 43 | if (!voxelsCreated) initGI(); 44 | #end 45 | } 46 | 47 | #if rp_voxels 48 | public static function initGI(tname = "voxels") { 49 | var config = Config.raw; 50 | if (config.rp_gi != true || voxelsCreated) return; 51 | voxelsCreated = true; 52 | 53 | var t = new RenderTargetRaw(); 54 | t.name = tname; 55 | t.format = "R8"; 56 | var res = 256; 57 | var resZ = 1.0; 58 | t.width = res; 59 | t.height = res; 60 | t.depth = Std.int(res * resZ); 61 | t.is_image = true; 62 | t.mipmaps = true; 63 | path.createRenderTarget(t); 64 | 65 | #if arm_voxelgi_temporal 66 | { 67 | var tB = new RenderTargetRaw(); 68 | tB.name = t.name + "B"; 69 | tB.format = t.format; 70 | tB.width = t.width; 71 | tB.height = t.height; 72 | tB.depth = t.depth; 73 | tB.is_image = t.is_image; 74 | tB.mipmaps = t.mipmaps; 75 | path.createRenderTarget(tB); 76 | } 77 | #end 78 | } 79 | #end 80 | 81 | public static inline function getSuperSampling(): Float { 82 | return superSample; 83 | } 84 | 85 | public static function drawCompass(currentG: kha.graphics4.Graphics) { 86 | if (Context.showCompass) { 87 | var scene = Scene.active; 88 | var cam = Scene.active.camera; 89 | var compass: MeshObject = cast scene.getChild(".Compass"); 90 | 91 | var visible = compass.visible; 92 | var parent = compass.parent; 93 | var loc = compass.transform.loc; 94 | var rot = compass.transform.rot; 95 | var crot = cam.transform.rot; 96 | var ratio = iron.App.w() / iron.App.h(); 97 | var P = cam.P; 98 | cam.P = Mat4.ortho(-8 * ratio, 8 * ratio, -8, 8, -2, 2); 99 | compass.visible = true; 100 | compass.parent = cam; 101 | compass.transform.loc = new Vec4(7.4 * ratio, 7.0, -1); 102 | compass.transform.rot = new Quat(-crot.x, -crot.y, -crot.z, crot.w); 103 | compass.transform.scale.set(0.4, 0.4, 0.4); 104 | compass.transform.buildMatrix(); 105 | 106 | compass.frustumCulling = false; 107 | compass.render(currentG, "overlay", []); 108 | 109 | cam.P = P; 110 | compass.visible = visible; 111 | compass.parent = parent; 112 | compass.transform.loc = loc; 113 | compass.transform.rot = rot; 114 | compass.transform.buildMatrix(); 115 | } 116 | } 117 | 118 | public static function end() { 119 | if (Context.foregroundEvent && !iron.system.Input.getMouse().down()) { 120 | Context.foregroundEvent = false; 121 | Context.pdirty = 0; 122 | } 123 | } 124 | 125 | public static function isCached(): Bool { 126 | var mouse = Input.getMouse(); 127 | var mx = lastX; 128 | var my = lastY; 129 | lastX = mouse.viewX; 130 | lastY = mouse.viewY; 131 | 132 | if (Context.ddirty <= 0 && Context.rdirty <= 0 && Context.pdirty <= 0) { 133 | if (mx != lastX || my != lastY || mouse.locked) Context.ddirty = 0; 134 | #if (kha_metal || krom_android) 135 | if (Context.ddirty > -6) { 136 | #else 137 | if (Context.ddirty > -2) { 138 | #end 139 | path.setTarget(""); 140 | path.bindTarget("taa", "tex"); 141 | path.drawShader("shader_datas/copy_pass/copy_pass"); 142 | RenderPathPaint.commandsCursor(); 143 | if (Context.ddirty <= 0) Context.ddirty--; 144 | } 145 | end(); 146 | return true; 147 | } 148 | return false; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Sources/arm/render/RenderPathForward.hx: -------------------------------------------------------------------------------- 1 | package arm.render; 2 | 3 | import kha.System; 4 | import iron.RenderPath; 5 | import iron.Scene; 6 | import arm.ui.UISidebar; 7 | import arm.node.MakeMesh; 8 | import arm.Enums; 9 | 10 | class RenderPathForward { 11 | 12 | public static var path: RenderPath; 13 | 14 | public static function init(_path: RenderPath) { 15 | path = _path; 16 | } 17 | 18 | @:access(iron.RenderPath) 19 | public static function commands() { 20 | if (System.windowWidth() == 0 || System.windowHeight() == 0) return; 21 | 22 | if (Inc.isCached()) return; 23 | 24 | // Match projection matrix jitter 25 | var skipTaa = (Context.tool == ToolClone || Context.tool == ToolBlur) && Context.pdirty > 0; 26 | @:privateAccess Scene.active.camera.frame = skipTaa ? 0 : RenderPathDeferred.taaFrame; 27 | @:privateAccess Scene.active.camera.projectionJitter(); 28 | Scene.active.camera.buildMatrix(); 29 | 30 | RenderPathPaint.begin(); 31 | RenderPathDeferred.drawGbuffer(); 32 | RenderPathPaint.draw(); 33 | 34 | #if (kha_direct3d12 || kha_vulkan) 35 | if (Context.viewportMode == ViewPathTrace) { 36 | RenderPathRaytrace.draw(false); 37 | return; 38 | } 39 | #end 40 | 41 | drawForward(); 42 | RenderPathPaint.end(); 43 | Inc.end(); 44 | RenderPathDeferred.taaFrame++; 45 | } 46 | 47 | public static function drawForward(eye = false, output = "", gbuffer0 = "gbuffer0", gbuffer1 = "gbuffer1", gbuffer2 = "gbuffer2", buf = "buf", bufa = "bufa", taa = "taa", taa2 = "taa2") { 48 | path.setDepthFrom(gbuffer1, gbuffer0); 49 | path.setTarget(gbuffer1); 50 | path.drawSkydome("shader_datas/world_pass/world_pass"); 51 | path.setDepthFrom(gbuffer1, gbuffer2); 52 | 53 | path.setTarget(buf); 54 | path.bindTarget(gbuffer1, "tex"); 55 | if (Context.viewportMode == ViewLit) { 56 | path.drawShader("shader_datas/compositor_pass/compositor_pass"); 57 | } 58 | else { 59 | path.drawShader("shader_datas/copy_pass/copy_pass"); 60 | } 61 | 62 | if (output == "") { 63 | path.setTarget(buf); 64 | var currentG = path.currentG; 65 | path.drawMeshes("overlay"); 66 | Inc.drawCompass(currentG); 67 | } 68 | 69 | var taaFrame = RenderPathDeferred.taaFrame; 70 | var current = taaFrame % 2 == 0 ? bufa : taa2; 71 | var last = taaFrame % 2 == 0 ? taa2 : bufa; 72 | 73 | path.setTarget(current); 74 | path.clearTarget(0x00000000); 75 | path.bindTarget(buf, "colorTex"); 76 | path.drawShader("shader_datas/smaa_edge_detect/smaa_edge_detect"); 77 | 78 | path.setTarget(taa); 79 | path.clearTarget(0x00000000); 80 | path.bindTarget(current, "edgesTex"); 81 | path.drawShader("shader_datas/smaa_blend_weight/smaa_blend_weight"); 82 | 83 | path.setTarget(current); 84 | path.bindTarget(buf, "colorTex"); 85 | path.bindTarget(taa, "blendTex"); 86 | path.bindTarget(gbuffer2, "sveloc"); 87 | path.drawShader("shader_datas/smaa_neighborhood_blend/smaa_neighborhood_blend"); 88 | 89 | var skipTaa = eye; 90 | if (skipTaa) { 91 | path.setTarget(taa); 92 | path.bindTarget(current, "tex"); 93 | path.drawShader("shader_datas/copy_pass/copy_pass"); 94 | } 95 | else { 96 | path.setTarget(taa); 97 | path.bindTarget(current, "tex"); 98 | path.bindTarget(last, "tex2"); 99 | path.bindTarget(gbuffer2, "sveloc"); 100 | path.drawShader("shader_datas/taa_pass/taa_pass"); 101 | } 102 | 103 | path.setTarget(output); 104 | path.bindTarget(taaFrame % 2 == 0 ? current : taa, "tex"); 105 | path.drawShader("shader_datas/copy_pass/copy_pass"); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/arm/render/Uniforms.hx: -------------------------------------------------------------------------------- 1 | package arm.render; 2 | 3 | import iron.data.MaterialData; 4 | import iron.object.Object; 5 | import iron.system.Input; 6 | import iron.math.Vec4; 7 | import iron.math.Mat4; 8 | import iron.RenderPath; 9 | import iron.Scene; 10 | import arm.ui.UISidebar; 11 | import arm.shader.MaterialParser; 12 | import arm.Enums; 13 | 14 | class Uniforms { 15 | 16 | static var vec = new Vec4(); 17 | static var orthoP = Mat4.ortho(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5); 18 | 19 | public static function init() { 20 | iron.object.Uniforms.externalFloatLinks = [linkFloat]; 21 | iron.object.Uniforms.externalVec2Links = [linkVec2]; 22 | iron.object.Uniforms.externalVec3Links = [linkVec3]; 23 | iron.object.Uniforms.externalVec4Links = [linkVec4]; 24 | iron.object.Uniforms.externalMat4Links = [linkMat4]; 25 | iron.object.Uniforms.externalTextureLinks = [linkTex]; 26 | } 27 | 28 | public static function linkFloat(object: Object, mat: MaterialData, link: String): Null { 29 | switch (link) { 30 | case "_brushRadius": { 31 | var radius = Context.brushRadius; 32 | var val = radius / 15.0; 33 | var pen = Input.getPen(); 34 | if (Config.raw.pressure_radius && pen.down()) { 35 | val *= pen.pressure * Config.raw.pressure_sensitivity; 36 | } 37 | val *= 2; 38 | return val; 39 | } 40 | case "_vignetteStrength": { 41 | return Config.raw.rp_vignette; 42 | } 43 | } 44 | if (MaterialParser.script_links != null) { 45 | for (key in MaterialParser.script_links.keys()) { 46 | var script = MaterialParser.script_links[key]; 47 | var result = 0.0; 48 | if (script != "") { 49 | try { 50 | result = js.Lib.eval(script); 51 | } 52 | catch(e: Dynamic) { 53 | Console.log(e); 54 | } 55 | } 56 | return result; 57 | } 58 | } 59 | return null; 60 | } 61 | 62 | public static function linkVec2(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { 63 | switch (link) { 64 | case "_gbufferSize": { 65 | vec.set(0, 0, 0); 66 | var gbuffer2 = RenderPath.active.renderTargets.get("gbuffer2"); 67 | vec.set(gbuffer2.image.width, gbuffer2.image.height, 0); 68 | return vec; 69 | } 70 | case "_cloneDelta": { 71 | vec.set(Context.cloneDeltaX, Context.cloneDeltaY, 0); 72 | return vec; 73 | } 74 | case "_texpaintSize": { 75 | vec.set(Config.getTextureResX(), Config.getTextureResY(), 0); 76 | return vec; 77 | } 78 | } 79 | return null; 80 | } 81 | 82 | public static function linkVec3(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { 83 | var v: Vec4 = null; 84 | switch (link) { 85 | } 86 | 87 | return v; 88 | } 89 | 90 | public static function linkVec4(object: Object, mat: MaterialData, link: String): iron.math.Vec4 { 91 | switch (link) { 92 | case "_inputBrush": { 93 | var down = Input.getMouse().down() || Input.getPen().down(); 94 | vec.set(Context.paintVec.x, Context.paintVec.y, down ? 1.0 : 0.0, 0.0); 95 | return vec; 96 | } 97 | case "_inputBrushLast": { 98 | var down = Input.getMouse().down() || Input.getPen().down(); 99 | vec.set(Context.lastPaintVecX, Context.lastPaintVecY, down ? 1.0 : 0.0, 0.0); 100 | return vec; 101 | } 102 | case "_envmapData": { 103 | vec.set(Context.envmapAngle, Math.sin(-Context.envmapAngle), Math.cos(-Context.envmapAngle), Scene.active.world.probe.raw.strength); 104 | return vec; 105 | } 106 | case "_envmapDataWorld": { 107 | vec.set(Context.envmapAngle, Math.sin(-Context.envmapAngle), Math.cos(-Context.envmapAngle), Context.showEnvmap ? Scene.active.world.probe.raw.strength : 4.0); 108 | return vec; 109 | } 110 | } 111 | return null; 112 | } 113 | 114 | public static function linkMat4(object: Object, mat: MaterialData, link: String): iron.math.Mat4 { 115 | switch (link) { 116 | } 117 | return null; 118 | } 119 | 120 | public static function linkTex(object: Object, mat: MaterialData, link: String): kha.Image { 121 | switch (link) { 122 | case "_texpaint_undo": { 123 | return null; 124 | } 125 | case "_texpaint_nor_undo": { 126 | return null; 127 | } 128 | case "_texpaint_pack_undo": { 129 | return null; 130 | } 131 | #if arm_ltc 132 | case "_ltcMat": { 133 | if (arm.data.ConstData.ltcMatTex == null) arm.data.ConstData.initLTC(); 134 | return arm.data.ConstData.ltcMatTex; 135 | } 136 | case "_ltcMag": { 137 | if (arm.data.ConstData.ltcMagTex == null) arm.data.ConstData.initLTC(); 138 | return arm.data.ConstData.ltcMagTex; 139 | } 140 | #end 141 | } 142 | 143 | if (link.startsWith("_texpaint_pack_vert")) { 144 | var tid = link.substr(link.length - 1); 145 | return RenderPath.active.renderTargets.get("texpaint_pack" + tid).image; 146 | } 147 | if (link.startsWith("_texpaint_vert")) { 148 | var tid = Std.parseInt(link.substr(link.length - 1)); 149 | return arm.node.brush.BrushOutputNode.inst.texpaint; 150 | } 151 | if (link.startsWith("_texpaint_nor")) { 152 | var tid = Std.parseInt(link.substr(link.length - 1)); 153 | return arm.node.brush.BrushOutputNode.inst.texpaint_nor; 154 | } 155 | if (link.startsWith("_texpaint_pack")) { 156 | var tid = Std.parseInt(link.substr(link.length - 1)); 157 | return arm.node.brush.BrushOutputNode.inst.texpaint_pack; 158 | } 159 | if (link.startsWith("_texpaint")) { 160 | var tid = Std.parseInt(link.substr(link.length - 1)); 161 | return arm.node.brush.BrushOutputNode.inst.texpaint; 162 | } 163 | 164 | return null; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Sources/arm/ui/BoxProjects.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import zui.Zui; 4 | import zui.Id; 5 | import arm.io.ImportArm; 6 | 7 | class BoxProjects { 8 | 9 | public static var htab = Id.handle(); 10 | 11 | static var iconMap: Map = null; 12 | 13 | @:access(zui.Zui) 14 | public static function show() { 15 | 16 | if (iconMap != null) { 17 | for (handle in iconMap.keys()) iron.data.Data.deleteImage(handle); 18 | iconMap = null; 19 | } 20 | 21 | UIBox.showCustom(function(ui: Zui) { 22 | alignToFullScreen(); 23 | 24 | if (ui.tab(htab, tr("Projects"), true)) { 25 | 26 | ui.beginSticky(); 27 | if (ui.button(tr("New"))) { 28 | Project.projectNew(); 29 | Viewport.scaleToBounds(); 30 | UIBox.hide(); 31 | // Pick unique name 32 | var i = 0; 33 | var j = 0; 34 | var title = tr("untitled") + i; 35 | while (j < Config.raw.recent_projects.length) { 36 | var base = Config.raw.recent_projects[j]; 37 | base = base.substring(base.lastIndexOf(arm.sys.Path.sep) + 1, base.lastIndexOf(".")); 38 | j++; 39 | if (title == base) { 40 | i++; 41 | title = tr("untitled") + i; 42 | j = 0; 43 | } 44 | } 45 | kha.Window.get(0).title = title; 46 | } 47 | ui.endSticky(); 48 | ui.separator(3, false); 49 | 50 | var slotw = Std.int(150 * ui.SCALE()); 51 | var num = Std.int(kha.System.windowWidth() / slotw); 52 | var recent_projects = Config.raw.recent_projects; 53 | var show_asset_names = true; 54 | 55 | for (row in 0...Std.int(Math.ceil(recent_projects.length / num))) { 56 | var mult = show_asset_names ? 2 : 1; 57 | ui.row([for (i in 0...num * mult) 1 / num]); 58 | 59 | ui._x += 2; 60 | var off = show_asset_names ? ui.ELEMENT_OFFSET() * 16.0 : 6; 61 | if (row > 0) ui._y += off; 62 | 63 | for (j in 0...num) { 64 | var imgw = Std.int(128 * ui.SCALE()); 65 | var i = j + row * num; 66 | if (i >= recent_projects.length) { 67 | @:privateAccess ui.endElement(imgw); 68 | if (show_asset_names) @:privateAccess ui.endElement(0); 69 | continue; 70 | } 71 | 72 | var path = recent_projects[i]; 73 | 74 | #if krom_ios 75 | var documentDirectory = Krom.saveDialog("", ""); 76 | documentDirectory = documentDirectory.substr(0, documentDirectory.length - 8); // Strip /'untitled' 77 | path = documentDirectory + path; 78 | #end 79 | 80 | var iconPath = path.substr(0, path.length - 4) + "_icon.png"; 81 | if (iconMap == null) iconMap = []; 82 | var icon = iconMap.get(iconPath); 83 | if (icon == null) { 84 | iron.data.Data.getImage(iconPath, function(image: kha.Image) { 85 | icon = image; 86 | iconMap.set(iconPath, icon); 87 | }); 88 | } 89 | 90 | var uix = ui._x; 91 | if (icon != null) { 92 | ui.fill(0, 0, 128, 128, ui.t.SEPARATOR_COL); 93 | 94 | var state = ui.image(icon, 0xffffffff, 128 * ui.SCALE()); 95 | if (state == Released) { 96 | var _uix = ui._x; 97 | ui._x = uix; 98 | ui.fill(0, 0, 128, 128, 0x66000000); 99 | ui._x = _uix; 100 | function doImport() { 101 | iron.App.notifyOnInit(function() { 102 | UIBox.hide(); 103 | ImportArm.runProject(path); 104 | }); 105 | } 106 | 107 | #if (krom_android || krom_ios) 108 | arm.App.notifyOnNextFrame(function() { 109 | Console.toast(tr("Opening project")); 110 | arm.App.notifyOnNextFrame(doImport); 111 | }); 112 | #else 113 | doImport(); 114 | #end 115 | } 116 | 117 | var name = path.substring(path.lastIndexOf(arm.sys.Path.sep) + 1, path.lastIndexOf(".")); 118 | if (ui.isHovered && ui.inputReleasedR) { 119 | UIMenu.draw(function(ui: Zui) { 120 | ui.text(name, Right, ui.t.HIGHLIGHT_COL); 121 | // if (ui.button(tr("Duplicate"), Left)) {} 122 | if (ui.button(tr("Delete"), Left)) { 123 | iron.App.notifyOnInit(function() { 124 | arm.sys.File.delete(path); 125 | arm.sys.File.delete(iconPath); 126 | var dataPath = path.substr(0, path.length - 4); 127 | arm.sys.File.delete(dataPath); 128 | recent_projects.splice(i, 1); 129 | }); 130 | } 131 | }, 2); 132 | } 133 | 134 | if (show_asset_names) { 135 | ui._x = uix - (150 - 128) / 2; 136 | ui._y += slotw * 0.9; 137 | ui.text(name, Center); 138 | if (ui.isHovered) ui.tooltip(name); 139 | ui._y -= slotw * 0.9; 140 | if (i == recent_projects.length - 1) { 141 | ui._y += j == num - 1 ? imgw : imgw + ui.ELEMENT_H() + ui.ELEMENT_OFFSET(); 142 | } 143 | } 144 | } 145 | else { 146 | @:privateAccess ui.endElement(0); 147 | if (show_asset_names) @:privateAccess ui.endElement(0); 148 | ui._x = uix; 149 | } 150 | } 151 | 152 | ui._y += 150; 153 | } 154 | } 155 | }, 600, 400, null, false); 156 | 157 | @:privateAccess alignToFullScreen(); 158 | } 159 | 160 | static function alignToFullScreen() { 161 | @:privateAccess UIBox.modalW = Std.int(kha.System.windowWidth() / App.uiBox.SCALE()); 162 | @:privateAccess UIBox.modalH = Std.int(kha.System.windowHeight() / App.uiBox.SCALE()); 163 | var appw = kha.System.windowWidth(); 164 | var apph = kha.System.windowHeight(); 165 | var mw = appw; 166 | var mh = apph; 167 | UIBox.hwnd.dragX = Std.int(-appw / 2 + mw / 2); 168 | UIBox.hwnd.dragY = Std.int(-apph / 2 + mh / 2); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Sources/arm/ui/TabBrowser.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import kha.input.KeyCode; 4 | import zui.Zui; 5 | import zui.Id; 6 | import arm.sys.Path; 7 | import arm.io.ImportAsset; 8 | import arm.Enums; 9 | 10 | class TabBrowser { 11 | 12 | static var hpath = new Handle(); 13 | static var hsearch = new Handle(); 14 | static var known = false; 15 | static var lastPath = ""; 16 | 17 | public static function showDirectory(directory: String) { 18 | hpath.text = directory; 19 | hsearch.text = ""; 20 | UIStatus.inst.statustab.position = 0; 21 | } 22 | 23 | @:access(zui.Zui) 24 | public static function draw() { 25 | var ui = UISidebar.inst.ui; 26 | var statush = Config.raw.layout[LayoutStatusH]; 27 | if (ui.tab(UIStatus.inst.statustab, tr("Browser")) && statush > UIStatus.defaultStatusH * ui.SCALE()) { 28 | 29 | if (Config.raw.bookmarks == null) { 30 | Config.raw.bookmarks = []; 31 | } 32 | 33 | var bookmarksW = Std.int(100 * ui.SCALE()); 34 | 35 | if (hpath.text == "" && Config.raw.bookmarks.length > 0) { // Init to first bookmark 36 | hpath.text = Config.raw.bookmarks[0]; 37 | } 38 | 39 | ui.beginSticky(); 40 | var step = (1 - bookmarksW / ui._w); 41 | if (hsearch.text != "") { 42 | ui.row([bookmarksW / ui._w, step * 0.73, step * 0.07, step * 0.17, step * 0.03]); 43 | } 44 | else { 45 | ui.row([bookmarksW / ui._w, step * 0.73, step * 0.07, step * 0.2]); 46 | } 47 | 48 | if (ui.button("+")) { 49 | Config.raw.bookmarks.push(hpath.text); 50 | Config.save(); 51 | } 52 | if (ui.isHovered) ui.tooltip(tr("Add bookmark")); 53 | 54 | #if krom_android 55 | var stripped = false; 56 | var strip = "/storage/emulated/0/"; 57 | if (hpath.text.startsWith(strip)) { 58 | hpath.text = hpath.text.substr(strip.length - 1); 59 | stripped = true; 60 | } 61 | #end 62 | 63 | hpath.text = ui.textInput(hpath, tr("Path")); 64 | 65 | #if krom_android 66 | if (stripped) { 67 | hpath.text = "/storage/emulated/0" + hpath.text; 68 | } 69 | #end 70 | 71 | var refresh = false; 72 | var inFocus = ui.inputX > ui._windowX && ui.inputX < ui._windowX + ui._windowW && 73 | ui.inputY > ui._windowY && ui.inputY < ui._windowY + ui._windowH; 74 | if (ui.button(tr("Refresh")) || (inFocus && ui.isKeyPressed && ui.key == kha.input.KeyCode.F5)) { 75 | refresh = true; 76 | } 77 | hsearch.text = ui.textInput(hsearch, tr("Search"), Align.Left, true, true); 78 | if (ui.isHovered) ui.tooltip(tr("ctrl+f to search") + "\n" + tr("esc to cancel")); 79 | if (ui.isCtrlDown && ui.isKeyPressed && ui.key == KeyCode.F) { // Start searching via ctrl+f 80 | ui.startTextEdit(hsearch); 81 | } 82 | if (hsearch.text != "" && (ui.button(tr("X")) || ui.isEscapeDown)) { 83 | hsearch.text = ""; 84 | } 85 | ui.endSticky(); 86 | 87 | if (lastPath != hpath.text) { 88 | hsearch.text = ""; 89 | } 90 | lastPath = hpath.text; 91 | 92 | var _y = ui._y; 93 | ui._x = bookmarksW; 94 | ui._w -= bookmarksW; 95 | UIFiles.fileBrowser(ui, hpath, false, true, hsearch.text, refresh); 96 | 97 | if (known) { 98 | var path = hpath.text; 99 | iron.App.notifyOnInit(function() { 100 | ImportAsset.run(path); 101 | }); 102 | hpath.text = hpath.text.substr(0, hpath.text.lastIndexOf(Path.sep)); 103 | } 104 | known = hpath.text.substr(hpath.text.lastIndexOf(Path.sep)).indexOf(".") > 0; 105 | #if krom_android 106 | if (hpath.text.endsWith(".armorlab")) known = false; 107 | #end 108 | 109 | var bottomY = ui._y; 110 | ui._x = 0; 111 | ui._y = _y; 112 | ui._w = bookmarksW; 113 | 114 | if (ui.button(tr("Cloud"), Left)) { 115 | hpath.text = "cloud"; 116 | } 117 | 118 | if (ui.button(tr("Disk"), Left)) { 119 | #if krom_android 120 | UIMenu.draw(function(ui: Zui) { 121 | ui.text(tr("Disk"), Right, ui.t.HIGHLIGHT_COL); 122 | if (ui.button(tr("Download"), Left)) { 123 | hpath.text = UIFiles.defaultPath; 124 | } 125 | if (ui.button(tr("Pictures"), Left)) { 126 | hpath.text = "/storage/emulated/0/Pictures"; 127 | } 128 | if (ui.button(tr("Camera"), Left)) { 129 | hpath.text = "/storage/emulated/0/DCIM/Camera"; 130 | } 131 | if (ui.button(tr("Projects"), Left)) { 132 | hpath.text = Krom.savePath(); 133 | } 134 | }, 5); 135 | #else 136 | hpath.text = UIFiles.defaultPath; 137 | #end 138 | } 139 | 140 | for (b in Config.raw.bookmarks) { 141 | var folder = b.substr(b.lastIndexOf(Path.sep) + 1); 142 | 143 | if (ui.button(folder, Left)) { 144 | hpath.text = b; 145 | } 146 | 147 | if (ui.isHovered && ui.inputReleasedR) { 148 | UIMenu.draw(function(ui: Zui) { 149 | ui.text(folder, Right, ui.t.HIGHLIGHT_COL); 150 | if (ui.button(tr("Delete"), Left)) { 151 | Config.raw.bookmarks.remove(b); 152 | Config.save(); 153 | } 154 | }, 2); 155 | } 156 | } 157 | 158 | if (ui._y < bottomY) ui._y = bottomY; 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Sources/arm/ui/TabConsole.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import haxe.io.Bytes; 4 | import zui.Zui; 5 | import zui.Id; 6 | import arm.sys.Path; 7 | import arm.io.ImportAsset; 8 | import arm.Enums; 9 | 10 | class TabConsole { 11 | 12 | @:access(zui.Zui) 13 | public static function draw() { 14 | var ui = UISidebar.inst.ui; 15 | 16 | var title = Console.messageTimer > 0 ? Console.message + " " : tr("Console"); 17 | var color = Console.messageTimer > 0 ? Console.messageColor : -1; 18 | 19 | var statush = Config.raw.layout[LayoutStatusH]; 20 | if (ui.tab(UIStatus.inst.statustab, title, false, color) && statush > UIStatus.defaultStatusH * ui.SCALE()) { 21 | 22 | ui.beginSticky(); 23 | #if (krom_windows || krom_linux || krom_darwin) // Copy 24 | if (Config.raw.touch_ui) { 25 | ui.row([1 / 4, 1 / 4, 1 / 4]); 26 | } 27 | else { 28 | ui.row([1 / 14, 1 / 14, 1 / 14]); 29 | } 30 | #else 31 | ui.row([1 / 14, 1 / 14]); 32 | if (Config.raw.touch_ui) { 33 | ui.row([1 / 4, 1 / 4]); 34 | } 35 | else { 36 | ui.row([1 / 14, 1 / 14]); 37 | } 38 | #end 39 | 40 | if (ui.button(tr("Clear"))) { 41 | Console.lastTraces = []; 42 | } 43 | #if (krom_windows || krom_linux || krom_darwin) 44 | if (ui.button(tr("Copy"))) { 45 | var str = Console.lastTraces.join("\n"); 46 | Krom.copyToClipboard(str); 47 | } 48 | #end 49 | if (ui.button(tr("Export"))) { 50 | var str = Console.lastTraces.join("\n"); 51 | UIFiles.show("txt", true, false, function(path: String) { 52 | var f = UIFiles.filename; 53 | if (f == "") f = tr("untitled"); 54 | path = path + Path.sep + f; 55 | if (!path.endsWith(".txt")) path += ".txt"; 56 | Krom.fileSaveBytes(path, Bytes.ofString(str).getData()); 57 | }); 58 | } 59 | 60 | ui.endSticky(); 61 | 62 | for (t in Console.lastTraces) { 63 | ui.text(t); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/arm/ui/TabMeshes.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import zui.Zui; 4 | import zui.Id; 5 | import iron.object.MeshObject; 6 | import arm.util.MeshUtil; 7 | import arm.Enums; 8 | 9 | class TabMeshes { 10 | 11 | @:access(zui.Zui) 12 | public static function draw() { 13 | var ui = UISidebar.inst.ui; 14 | var statush = Config.raw.layout[LayoutStatusH]; 15 | if (ui.tab(UIStatus.inst.statustab, tr("Meshes")) && statush > UIStatus.defaultStatusH * ui.SCALE()) { 16 | 17 | ui.beginSticky(); 18 | if (Config.raw.touch_ui) { 19 | ui.row([1 / 7, 1 / 7, 1 / 7, 1 / 7, 1 / 7, 1 / 7, 1 / 7]); 20 | } 21 | else { 22 | ui.row([1 / 14, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 14]); 23 | } 24 | 25 | if (ui.button(tr("Import"))) { 26 | UIMenu.draw(function(ui: Zui) { 27 | ui.text(tr("Import"), Right, ui.t.HIGHLIGHT_COL); 28 | if (ui.button(tr("Replace Existing"), Left, '${Config.keymap.file_import_assets}')) { 29 | Project.importMesh(true); 30 | } 31 | if (ui.button(tr("Append"), Left)) { 32 | Project.importMesh(false); 33 | } 34 | }, 3); 35 | } 36 | if (ui.isHovered) ui.tooltip(tr("Import mesh file")); 37 | 38 | if (ui.button(tr("Set Default"))) { 39 | UIMenu.draw(function(ui: Zui) { 40 | ui.text(tr("Meshes"), Right, ui.t.HIGHLIGHT_COL); 41 | if (ui.button(tr("Cube"), Left)) setDefaultMesh(".Cube"); 42 | if (ui.button(tr("Plane"), Left)) setDefaultMesh(".Plane"); 43 | if (ui.button(tr("Sphere"), Left)) setDefaultMesh(".Sphere"); 44 | if (ui.button(tr("Cylinder"), Left)) setDefaultMesh(".Cylinder"); 45 | }, 5); 46 | } 47 | 48 | if (ui.button(tr("Flip Normals"))) { 49 | MeshUtil.flipNormals(); 50 | Context.ddirty = 2; 51 | } 52 | 53 | if (ui.button(tr("Calculate Normals"))) { 54 | UIMenu.draw(function(ui: Zui) { 55 | ui.text(tr("Normals"), Right, ui.t.HIGHLIGHT_COL); 56 | if (ui.button(tr("Smooth"), Left)) { MeshUtil.calcNormals(true); Context.ddirty = 2; } 57 | if (ui.button(tr("Flat"), Left)) { MeshUtil.calcNormals(false); Context.ddirty = 2; } 58 | }, 3); 59 | } 60 | 61 | if (ui.button(tr("Geometry to Origin"))) { 62 | MeshUtil.toOrigin(); 63 | Context.ddirty = 2; 64 | } 65 | 66 | if (ui.button(tr("Apply Displacement"))) { 67 | MeshUtil.applyDisplacement(); 68 | MeshUtil.calcNormals(); 69 | Context.ddirty = 2; 70 | } 71 | 72 | if (ui.button(tr("Rotate"))) { 73 | UIMenu.draw(function(ui: Zui) { 74 | ui.text(tr("Rotate"), Right, ui.t.HIGHLIGHT_COL); 75 | 76 | if (ui.button(tr("Rotate X"), Left)) { 77 | MeshUtil.swapAxis(1, 2); 78 | Context.ddirty = 2; 79 | } 80 | 81 | if (ui.button(tr("Rotate Y"), Left)) { 82 | MeshUtil.swapAxis(2, 0); 83 | Context.ddirty = 2; 84 | } 85 | 86 | if (ui.button(tr("Rotate Z"), Left)) { 87 | MeshUtil.swapAxis(0, 1); 88 | Context.ddirty = 2; 89 | } 90 | }, 4); 91 | } 92 | 93 | ui.endSticky(); 94 | 95 | for (i in 0...Project.paintObjects.length) { 96 | var o = Project.paintObjects[i]; 97 | var h = Id.handle(); 98 | h.selected = o.visible; 99 | o.visible = ui.check(h, o.name); 100 | if (ui.isHovered && ui.inputReleasedR) { 101 | UIMenu.draw(function(ui: Zui) { 102 | ui.text(o.name, Right, ui.t.HIGHLIGHT_COL); 103 | if (ui.button(tr("Export"), Left)) { 104 | Context.exportMeshIndex = i + 1; 105 | BoxExport.showMesh(); 106 | } 107 | if (Project.paintObjects.length > 1 && ui.button(tr("Delete"), Left)) { 108 | Project.paintObjects.remove(o); 109 | while (o.children.length > 0) { 110 | var child = o.children[0]; 111 | child.setParent(null); 112 | if (Project.paintObjects[0] != child) { 113 | child.setParent(Project.paintObjects[0]); 114 | } 115 | if (o.children.length == 0) { 116 | Project.paintObjects[0].transform.scale.setFrom(o.transform.scale); 117 | Project.paintObjects[0].transform.buildMatrix(); 118 | } 119 | } 120 | iron.data.Data.deleteMesh(o.data.handle); 121 | o.remove(); 122 | Context.paintObject = Context.mainObject(); 123 | MeshUtil.mergeMesh(); 124 | Context.ddirty = 2; 125 | } 126 | }, Project.paintObjects.length > 1 ? 3 : 2); 127 | } 128 | if (h.changed) { 129 | var visibles: Array = []; 130 | for (p in Project.paintObjects) if (p.visible) visibles.push(p); 131 | MeshUtil.mergeMesh(visibles); 132 | Context.ddirty = 2; 133 | } 134 | } 135 | } 136 | } 137 | 138 | static function setDefaultMesh(name: String) { 139 | var mo: MeshObject = cast iron.Scene.active.getChild(name); 140 | mo.visible = true; 141 | iron.Scene.active.meshes = [mo]; 142 | Context.ddirty = 2; 143 | Context.paintObject = mo; 144 | #if (kha_direct3d12 || kha_vulkan) 145 | arm.render.RenderPathRaytrace.ready = false; 146 | #end 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Sources/arm/ui/TabPlugins.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import arm.Enums; 4 | 5 | class TabPlugins { 6 | 7 | public static function draw() { 8 | var ui = UISidebar.inst.ui; 9 | var statush = Config.raw.layout[LayoutStatusH]; 10 | if (ui.tab(UIStatus.inst.statustab, tr("Plugins")) && statush > UIStatus.defaultStatusH * ui.SCALE()) { 11 | 12 | ui.beginSticky(); 13 | ui.row([1 / 14]); 14 | if (ui.button(tr("Manager"))) { 15 | BoxPreferences.htab.position = 6; // Plugins 16 | BoxPreferences.show(); 17 | } 18 | ui.endSticky(); 19 | 20 | // Draw plugins 21 | for (p in Plugin.plugins) if (p.drawUI != null) p.drawUI(ui); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/arm/ui/TabScript.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import haxe.io.Bytes; 4 | import zui.Zui; 5 | import zui.Ext; 6 | import zui.Id; 7 | import kha.Blob; 8 | import iron.data.Data; 9 | import arm.sys.Path; 10 | import arm.io.ImportAsset; 11 | import arm.Enums; 12 | 13 | class TabScript { 14 | 15 | public static var hscript = Id.handle(); 16 | 17 | @:access(zui.Zui) 18 | public static function draw() { 19 | var ui = UISidebar.inst.ui; 20 | var statush = Config.raw.layout[LayoutStatusH]; 21 | if (ui.tab(UIStatus.inst.statustab, tr("Script")) && statush > UIStatus.defaultStatusH * ui.SCALE()) { 22 | 23 | ui.beginSticky(); 24 | if (Config.raw.touch_ui) { 25 | ui.row([1 / 4, 1 / 4, 1 / 4, 1 / 4]); 26 | } 27 | else { 28 | ui.row([1 / 14, 1 / 14, 1 / 14, 1 / 14]); 29 | } 30 | if (ui.button(tr("Run"))) { 31 | try { 32 | js.Lib.eval(hscript.text); 33 | } 34 | catch(e: Dynamic) { 35 | Console.log(e); 36 | } 37 | } 38 | if (ui.button(tr("Clear"))) { 39 | hscript.text = ""; 40 | } 41 | if (ui.button(tr("Import"))) { 42 | UIFiles.show("js", false, false, function(path: String) { 43 | Data.getBlob(path, function(b: Blob) { 44 | hscript.text = b.toString(); 45 | Data.deleteBlob(path); 46 | }); 47 | }); 48 | } 49 | if (ui.button(tr("Export"))) { 50 | var str = hscript.text; 51 | UIFiles.show("js", true, false, function(path: String) { 52 | var f = UIFiles.filename; 53 | if (f == "") f = tr("untitled"); 54 | path = path + Path.sep + f; 55 | if (!path.endsWith(".js")) path += ".js"; 56 | Krom.fileSaveBytes(path, Bytes.ofString(str).getData()); 57 | }); 58 | } 59 | ui.endSticky(); 60 | 61 | var _font = ui.ops.font; 62 | var _fontSize = ui.fontSize; 63 | Data.getFont("font_mono.ttf", function(f: kha.Font) { ui.ops.font = f; }); // Sync 64 | ui.fontSize = 15; 65 | Ext.textArea(ui, hscript); 66 | ui.ops.font = _font; 67 | ui.fontSize = _fontSize; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/arm/ui/UIBox.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import kha.System; 4 | import zui.Zui; 5 | import zui.Ext; 6 | import zui.Id; 7 | import iron.system.Input; 8 | 9 | @:access(zui.Zui) 10 | class UIBox { 11 | 12 | public static var show = false; 13 | public static var draggable = true; 14 | public static var hwnd = new Handle(); 15 | public static var boxTitle = ""; 16 | public static var boxText = ""; 17 | public static var boxCommands: Zui->Void = null; 18 | public static var clickToHide = true; 19 | static var modalW = 400; 20 | static var modalH = 170; 21 | static var modalOnHide: Void->Void = null; 22 | static var draws = 0; 23 | static var copyable = false; 24 | #if (krom_android || krom_ios) 25 | static var tweenAlpha = 0.0; 26 | #end 27 | 28 | public static function render(g: kha.graphics2.Graphics) { 29 | if (!UIMenu.show) { 30 | var mouse = Input.getMouse(); 31 | var kb = Input.getKeyboard(); 32 | var ui = App.uiBox; 33 | var inUse = ui.comboSelectedHandle != null; 34 | var isEscape = kb.started("escape"); 35 | if (draws > 2 && (ui.inputReleased || isEscape) && !inUse && !ui.isTyping) { 36 | var appw = System.windowWidth(); 37 | var apph = System.windowHeight(); 38 | var mw = Std.int(modalW * ui.SCALE()); 39 | var mh = Std.int(modalH * ui.SCALE()); 40 | var left = (appw / 2 - mw / 2) + hwnd.dragX; 41 | var right = (appw / 2 + mw / 2) + hwnd.dragX; 42 | var top = (apph / 2 - mh / 2) + hwnd.dragY; 43 | var bottom = (apph / 2 + mh / 2) + hwnd.dragY; 44 | var mx = mouse.x; 45 | var my = mouse.y; 46 | if ((clickToHide && (mx < left || mx > right || my < top || my > bottom)) || isEscape) { 47 | hide(); 48 | } 49 | } 50 | } 51 | 52 | if (Config.raw.touch_ui) { // Darken bg 53 | #if (krom_android || krom_ios) 54 | g.color = kha.Color.fromFloats(0, 0, 0, tweenAlpha); 55 | #else 56 | g.color = kha.Color.fromFloats(0, 0, 0, 0.5); 57 | #end 58 | g.fillRect(0, 0, kha.System.windowWidth(), kha.System.windowHeight()); 59 | } 60 | 61 | g.end(); 62 | 63 | var ui = App.uiBox; 64 | var appw = System.windowWidth(); 65 | var apph = System.windowHeight(); 66 | var mw = Std.int(modalW * ui.SCALE()); 67 | var mh = Std.int(modalH * ui.SCALE()); 68 | var left = Std.int(appw / 2 - mw / 2); 69 | var top = Std.int(apph / 2 - mh / 2); 70 | 71 | if (boxCommands == null) { 72 | ui.begin(g); 73 | if (ui.window(hwnd, left, top, mw, mh, draggable)) { 74 | ui._y += 10; 75 | var tabVertical = Config.raw.touch_ui; 76 | if (ui.tab(Id.handle(), boxTitle, tabVertical)) { 77 | var htext = Id.handle(); 78 | htext.text = boxText; 79 | copyable ? 80 | Ext.textArea(ui, htext, false) : 81 | ui.text(boxText); 82 | ui.endElement(); 83 | #if (krom_windows || krom_linux || krom_darwin) 84 | if (copyable) ui.row([1 / 3, 1 / 3, 1 / 3]); 85 | else ui.row([2 / 3, 1 / 3]); 86 | #else 87 | ui.row([2 / 3, 1 / 3]); 88 | #end 89 | 90 | ui.endElement(); 91 | 92 | #if (krom_windows || krom_linux || krom_darwin) 93 | if (copyable && ui.button(tr("Copy"))) { 94 | Krom.copyToClipboard(boxText); 95 | } 96 | #end 97 | if (ui.button(tr("OK"))) { 98 | hide(); 99 | } 100 | } 101 | windowBorder(ui); 102 | } 103 | ui.end(); 104 | } 105 | else { 106 | ui.begin(g); 107 | if (ui.window(hwnd, left, top, mw, mh, draggable)) { 108 | ui._y += 10; 109 | boxCommands(ui); 110 | windowBorder(ui); 111 | } 112 | ui.end(); 113 | } 114 | 115 | g.begin(false); 116 | 117 | draws++; 118 | } 119 | 120 | public static function showMessage(title: String, text: String, copyable = false) { 121 | init(); 122 | modalW = 400; 123 | modalH = 210; 124 | boxTitle = title; 125 | boxText = text; 126 | boxCommands = null; 127 | UIBox.copyable = copyable; 128 | draggable = true; 129 | #if (krom_android || krom_ios) 130 | tweenIn(); 131 | #end 132 | } 133 | 134 | public static function showCustom(commands: Zui->Void = null, mw = 400, mh = 200, onHide: Void->Void = null, draggable = true) { 135 | init(); 136 | modalW = mw; 137 | modalH = mh; 138 | modalOnHide = onHide; 139 | boxCommands = commands; 140 | UIBox.draggable = draggable; 141 | #if (krom_android || krom_ios) 142 | tweenIn(); 143 | #end 144 | } 145 | 146 | public static function hide() { 147 | #if (krom_android || krom_ios) 148 | tweenOut(); 149 | #else 150 | hideInternal(); 151 | #end 152 | } 153 | 154 | static function hideInternal() { 155 | if (modalOnHide != null) modalOnHide(); 156 | show = false; 157 | App.redrawUI(); 158 | } 159 | 160 | #if (krom_android || krom_ios) 161 | static function tweenIn() { 162 | iron.system.Tween.reset(); 163 | iron.system.Tween.to({target: UIBox, props: { tweenAlpha: 0.5 }, duration: 0.2, ease: iron.system.Tween.Ease.ExpoOut}); 164 | UIBox.hwnd.dragY = Std.int(kha.System.windowHeight() / 2); 165 | iron.system.Tween.to({target: UIBox.hwnd, props: { dragY: 0 }, duration: 0.2, ease: iron.system.Tween.Ease.ExpoOut, tick: function() { App.redrawUI(); }}); 166 | } 167 | 168 | static function tweenOut() { 169 | iron.system.Tween.to({target: UIBox, props: { tweenAlpha: 0.0 }, duration: 0.2, ease: iron.system.Tween.Ease.ExpoIn, done: hideInternal}); 170 | iron.system.Tween.to({target: UIBox.hwnd, props: { dragY: kha.System.windowHeight() / 2 }, duration: 0.2, ease: iron.system.Tween.Ease.ExpoIn}); 171 | } 172 | #end 173 | 174 | static function init() { 175 | hwnd.redraws = 2; 176 | hwnd.dragX = 0; 177 | hwnd.dragY = 0; 178 | show = true; 179 | draws = 0; 180 | clickToHide = true; 181 | } 182 | 183 | static function windowBorder(ui: Zui) { 184 | if (ui.scissor) { 185 | ui.scissor = false; 186 | ui.g.disableScissor(); 187 | } 188 | // Border 189 | ui.g.color = ui.t.SEPARATOR_COL; 190 | ui.g.fillRect(0, 0, 1, ui._windowH); 191 | ui.g.fillRect(0 + ui._windowW - 1, 0, 1, ui._windowH); 192 | ui.g.fillRect(0, 0 + ui._windowH - 1, ui._windowW, 1); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /Sources/arm/ui/UIHeader.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import kha.System; 4 | import zui.Zui; 5 | import zui.Id; 6 | import iron.RenderPath; 7 | import iron.system.Input; 8 | import arm.node.MakeMaterial; 9 | import arm.ProjectFormat.TSwatchColor; 10 | import arm.Enums; 11 | 12 | class UIHeader { 13 | 14 | public static var inst: UIHeader; 15 | 16 | #if (krom_android || krom_ios) 17 | public static inline var defaultHeaderH = 28 + 4; 18 | #else 19 | public static inline var defaultHeaderH = 28; 20 | #end 21 | 22 | public var headerHandle = new Handle({ layout: Horizontal }); 23 | public var headerh = defaultHeaderH; 24 | public var worktab = Id.handle(); 25 | 26 | public function new() { 27 | inst = this; 28 | } 29 | 30 | @:access(zui.Zui) 31 | public function renderUI(g: kha.graphics2.Graphics) { 32 | var ui = UISidebar.inst.ui; 33 | 34 | var panelx = iron.App.x(); 35 | if (ui.window(headerHandle, panelx, headerh, System.windowWidth(), Std.int(defaultHeaderH * ui.SCALE()))) { 36 | ui._y += 2; 37 | 38 | if (Context.tool == ToolPicker) { 39 | // var baseRPicked = Math.round(Context.pickedColor.base.R * 10) / 10; 40 | // var baseGPicked = Math.round(Context.pickedColor.base.G * 10) / 10; 41 | // var baseBPicked = Math.round(Context.pickedColor.base.B * 10) / 10; 42 | // var normalRPicked = Math.round(Context.pickedColor.normal.R * 10) / 10; 43 | // var normalGPicked = Math.round(Context.pickedColor.normal.G * 10) / 10; 44 | // var normalBPicked = Math.round(Context.pickedColor.normal.B * 10) / 10; 45 | // var occlusionPicked = Math.round(Context.pickedColor.occlusion * 100) / 100; 46 | // var roughnessPicked = Math.round(Context.pickedColor.roughness * 100) / 100; 47 | // var metallicPicked = Math.round(Context.pickedColor.metallic * 100) / 100; 48 | // var heightPicked = Math.round(Context.pickedColor.height * 100) / 100; 49 | // var opacityPicked = Math.round(Context.pickedColor.opacity * 100) / 100; 50 | 51 | // var h = Id.handle(); 52 | // h.color.R = baseRPicked; 53 | // h.color.G = baseGPicked; 54 | // h.color.B = baseBPicked; 55 | // var state = ui.text("", 0, h.color); 56 | // if (state == State.Started) { 57 | // var mouse = Input.getMouse(); 58 | // var uix = ui._x; 59 | // var uiy = ui._y; 60 | // App.dragOffX = -(mouse.x - uix - ui._windowX - 3); 61 | // App.dragOffY = -(mouse.y - uiy - ui._windowY + 1); 62 | // App.dragSwatch = Project.cloneSwatch(Context.pickedColor); 63 | // } 64 | // if (ui.isHovered) ui.tooltip(tr("Drag and drop picked color to swatches, materials, layers or to the node editor")); 65 | // if (ui.isHovered && ui.inputReleased) { 66 | // UIMenu.draw(function(ui) { 67 | // ui.fill(0, 0, ui._w / ui.ops.scaleFactor, ui.t.ELEMENT_H * 9, ui.t.SEPARATOR_COL); 68 | // ui.changed = false; 69 | // zui.Ext.colorWheel(ui, h, false, null, 10 * ui.t.ELEMENT_H * ui.SCALE(), false); 70 | // if (ui.changed) UIMenu.keepOpen = true; 71 | // }, 10); 72 | // } 73 | // if (ui.button(tr("Add Swatch"))) { 74 | // var newSwatch = Project.cloneSwatch(Context.pickedColor); 75 | // Context.setSwatch(newSwatch); 76 | // Project.raw.swatches.push(newSwatch); 77 | // UIStatus.inst.statusHandle.redraws = 1; 78 | // } 79 | // if (ui.isHovered) ui.tooltip(tr("Add picked color to swatches")); 80 | 81 | // ui.text(tr("Base") + ' ($baseRPicked,$baseGPicked,$baseBPicked)'); 82 | // ui.text(tr("Normal") + ' ($normalRPicked,$normalGPicked,$normalBPicked)'); 83 | // ui.text(tr("Occlusion") + ' ($occlusionPicked)'); 84 | // ui.text(tr("Roughness") + ' ($roughnessPicked)'); 85 | // ui.text(tr("Metallic") + ' ($metallicPicked)'); 86 | // ui.text(tr("Height") + ' ($heightPicked)'); 87 | // ui.text(tr("Opacity") + ' ($opacityPicked)'); 88 | // Context.pickerSelectMaterial = ui.check(Id.handle({ selected: Context.pickerSelectMaterial }), tr("Select Material")); 89 | // ui.combo(Context.pickerMaskHandle, [tr("None"), tr("Material")], tr("Mask"), true); 90 | // if (Context.pickerMaskHandle.changed) { 91 | // MakeMaterial.parsePaintMaterial(); 92 | // } 93 | } 94 | else if (Context.tool == ToolEraser || 95 | Context.tool == ToolClone || 96 | Context.tool == ToolBlur) { 97 | 98 | var inpaint = UINodes.inst.getNodes().nodesSelected.length > 0 && UINodes.inst.getNodes().nodesSelected[0].type == "InpaintNode"; 99 | if (inpaint) { 100 | Context.brushRadius = ui.slider(Context.brushRadiusHandle, tr("Radius"), 0.01, 2.0, true); 101 | if (ui.isHovered) ui.tooltip(tr("Hold {brush_radius} and move mouse to the left or press {brush_radius_decrease} to decrease the radius\nHold {brush_radius} and move mouse to the right or press {brush_radius_increase} to increase the radius", ["brush_radius" => Config.keymap.brush_radius, "brush_radius_decrease" => Config.keymap.brush_radius_decrease, "brush_radius_increase" => Config.keymap.brush_radius_increase])); 102 | } 103 | 104 | // if (Context.tool != ToolEraser) { 105 | // var brushBlendingHandle = Id.handle({ value: Context.brushBlending }); 106 | // Context.brushBlending = ui.combo(brushBlendingHandle, [ 107 | // tr("Mix"), 108 | // tr("Darken"), 109 | // tr("Multiply"), 110 | // tr("Burn"), 111 | // tr("Lighten"), 112 | // tr("Screen"), 113 | // tr("Dodge"), 114 | // tr("Add"), 115 | // tr("Overlay"), 116 | // tr("Soft Light"), 117 | // tr("Linear Light"), 118 | // tr("Difference"), 119 | // tr("Subtract"), 120 | // tr("Divide"), 121 | // tr("Hue"), 122 | // tr("Saturation"), 123 | // tr("Color"), 124 | // tr("Value"), 125 | // ], tr("Blending")); 126 | // if (brushBlendingHandle.changed) { 127 | // MakeMaterial.parsePaintMaterial(); 128 | // } 129 | // } 130 | 131 | if (Context.tool == ToolBlur) { 132 | ui._x += 10 * ui.SCALE(); 133 | var dirHandle = Id.handle({ selected: false }); 134 | Context.blurDirectional = ui.check(dirHandle, tr("Directional")); 135 | if (dirHandle.changed) { 136 | MakeMaterial.parsePaintMaterial(); 137 | } 138 | } 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Sources/arm/ui/UIMenubar.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import kha.System; 4 | import zui.Zui; 5 | import zui.Id; 6 | import zui.Ext; 7 | import iron.RenderPath; 8 | import iron.Scene; 9 | import iron.object.MeshObject; 10 | import arm.node.MakeMaterial; 11 | import arm.render.RenderPathPaint; 12 | import arm.Enums; 13 | 14 | @:access(zui.Zui) 15 | class UIMenubar { 16 | 17 | public static var inst: UIMenubar; 18 | public static inline var defaultMenubarW = 330; 19 | public var workspaceHandle = new Handle({ layout: Horizontal }); 20 | public var menuHandle = new Handle({ layout: Horizontal }); 21 | public var menubarw = defaultMenubarW; 22 | 23 | static var _meshes: Array = null; 24 | static var _savedCamera = iron.math.Mat4.identity(); 25 | 26 | public function new() { 27 | inst = this; 28 | } 29 | 30 | public function renderUI(g: kha.graphics2.Graphics) { 31 | var ui = UISidebar.inst.ui; 32 | 33 | var panelx = iron.App.x(); 34 | if (ui.window(menuHandle, panelx, 0, menubarw, Std.int(UIHeader.defaultHeaderH * ui.SCALE()))) { 35 | ui._x += 1; // Prevent "File" button highlight on startup 36 | 37 | Ext.beginMenu(ui); 38 | 39 | if (Config.raw.touch_ui) { 40 | ui._y += 4; 41 | 42 | #if (krom_android || krom_ios) 43 | var defaultToolbarW = 36 + 4; 44 | #else 45 | var defaultToolbarW = 36; 46 | #end 47 | 48 | ui._w = Std.int(defaultToolbarW * ui.SCALE()); 49 | if (iconButton(ui, 0, 2)) BoxPreferences.show(); 50 | if (iconButton(ui, 0, 3)) { 51 | #if (krom_android || krom_ios) 52 | Console.toast(tr("Saving project")); 53 | Project.projectSave(); 54 | #end 55 | App.notifyOnNextFrame(function() { 56 | BoxProjects.show(); 57 | }); 58 | } 59 | if (iconButton(ui, 4, 2)) Project.importAsset(); 60 | if (iconButton(ui, 5, 2)) BoxExport.showTextures(); 61 | var size = defaultToolbarW; 62 | if (UIMenu.show && UIMenu.menuCategory == MenuViewport) ui.fill(0, -6, size, size - 4, ui.t.HIGHLIGHT_COL); 63 | if (iconButton(ui, 8, 2)) showMenu(ui, MenuViewport); 64 | if (UIMenu.show && UIMenu.menuCategory == MenuMode) ui.fill(0, -6, size, size - 4, ui.t.HIGHLIGHT_COL); 65 | if (iconButton(ui, 9, 2)) showMenu(ui, MenuMode); 66 | if (UIMenu.show && UIMenu.menuCategory == MenuCamera) ui.fill(0, -6, size, size - 4, ui.t.HIGHLIGHT_COL); 67 | if (iconButton(ui, 10, 2)) showMenu(ui, MenuCamera); 68 | if (UIMenu.show && UIMenu.menuCategory == MenuHelp) ui.fill(0, -6, size, size - 4, ui.t.HIGHLIGHT_COL); 69 | if (iconButton(ui, 11, 2)) showMenu(ui, MenuHelp); 70 | // ui.enabled = History.undos > 0; 71 | if (iconButton(ui, 6, 2)) History.undo(); 72 | // ui.enabled = History.redos > 0; 73 | if (iconButton(ui, 7, 2)) History.redo(); 74 | // ui.enabled = true; 75 | } 76 | else { 77 | var categories = [tr("File"), tr("Edit"), tr("Viewport"), tr("Mode"), tr("Camera"), tr("Help")]; 78 | for (i in 0...categories.length) { 79 | if (Ext.menuButton(ui, categories[i]) || (UIMenu.show && UIMenu.menuCommands == null && ui.isHovered)) { 80 | showMenu(ui, i); 81 | } 82 | } 83 | } 84 | 85 | if (menubarw < ui._x + 10) { 86 | menubarw = Std.int(ui._x + 10); 87 | } 88 | 89 | Ext.endMenu(ui); 90 | } 91 | 92 | var panelx = (iron.App.x()) + menubarw; 93 | if (ui.window(workspaceHandle, panelx, 0, System.windowWidth() - menubarw, Std.int(UIHeader.defaultHeaderH * ui.SCALE()))) { 94 | ui.tab(UIHeader.inst.worktab, tr("3D")); 95 | ui.tab(UIHeader.inst.worktab, tr("2D")); 96 | if (UIHeader.inst.worktab.changed) { 97 | Context.ddirty = 2; 98 | Context.brushBlendDirty = true; 99 | UIHeader.inst.headerHandle.redraws = 2; 100 | 101 | Context.mainObject().skip_context = null; 102 | 103 | if (UIHeader.inst.worktab.position == Space3D) { 104 | if (_meshes != null) { 105 | Scene.active.meshes = _meshes; 106 | Scene.active.camera.transform.setMatrix(_savedCamera); 107 | _meshes = null; 108 | } 109 | } 110 | else { // Space2D 111 | var plane: MeshObject = cast Scene.active.getChild(".Plane"); 112 | plane.transform.scale.set(1, 1, 1); 113 | plane.transform.rot.fromEuler(-Math.PI / 2, 0, 0); 114 | plane.transform.buildMatrix(); 115 | plane.visible = true; 116 | if (_meshes == null) { 117 | _meshes = Scene.active.meshes; 118 | _savedCamera.setFrom(Scene.active.camera.transform.local); 119 | } 120 | Scene.active.meshes = [plane]; 121 | var m = iron.math.Mat4.identity(); 122 | m.translate(0, 0, 1.5); 123 | Scene.active.camera.transform.setMatrix(m); 124 | } 125 | } 126 | } 127 | } 128 | 129 | function showMenu(ui: Zui, category: Int) { 130 | UIMenu.show = true; 131 | UIMenu.menuCategory = category; 132 | UIMenu.menuX = Std.int(ui._x - ui._w); 133 | UIMenu.menuY = Std.int(Ext.MENUBAR_H(ui)); 134 | if (Config.raw.touch_ui) { 135 | var menuW = Std.int(App.defaultElementW * App.uiMenu.SCALE() * 2.0); 136 | UIMenu.menuX -= Std.int((menuW - ui._w) / 2) + Std.int(UIHeader.inst.headerh / 2); 137 | // UIMenu.menuY += 4; 138 | UIMenu.keepOpen = true; 139 | } 140 | } 141 | 142 | function iconButton(ui: Zui, i: Int, j: Int): Bool { 143 | var col = ui.t.WINDOW_BG_COL; 144 | if (col < 0) col += untyped 4294967296; 145 | var light = col > 0xff666666 + 4294967296; 146 | var iconAccent = light ? 0xff666666 : 0xff999999; 147 | var img = Res.get("icons.k"); 148 | var rect = Res.tile50(img, i, j); 149 | return ui.image(img, iconAccent, null, rect.x, rect.y, rect.w, rect.h) == State.Released; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Sources/arm/ui/UIStatus.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import kha.System; 4 | import zui.Zui; 5 | import zui.Id; 6 | import arm.Enums; 7 | 8 | class UIStatus { 9 | 10 | public static var inst: UIStatus; 11 | 12 | public static inline var defaultStatusH = 32; 13 | 14 | public var statusHandle = new Handle(); 15 | public var statustab = Id.handle(); 16 | 17 | public function new() { 18 | inst = this; 19 | } 20 | 21 | @:access(zui.Zui) 22 | public function renderUI(g: kha.graphics2.Graphics) { 23 | var ui = UISidebar.inst.ui; 24 | 25 | var statush = Config.raw.layout[LayoutStatusH]; 26 | if (ui.window(statusHandle, iron.App.x(), System.windowHeight() - statush, System.windowWidth(), statush)) { 27 | ui._y += 2; 28 | 29 | // Border 30 | ui.g.color = ui.t.SEPARATOR_COL; 31 | ui.g.fillRect(0, 0, 1, ui._windowH); 32 | ui.g.fillRect(ui._windowW - 1, 0, 1, ui._windowH); 33 | 34 | TabBrowser.draw(); 35 | TabTextures.draw(); 36 | TabMeshes.draw(); 37 | TabSwatches.draw(); 38 | TabPlugins.draw(); 39 | TabScript.draw(); 40 | TabConsole.draw(); 41 | 42 | var minimized = statush <= defaultStatusH * Config.raw.window_scale; 43 | if (statustab.changed && (statustab.position == Context.lastStatusPosition || minimized)) { 44 | UISidebar.inst.toggleBrowser(); 45 | } 46 | Context.lastStatusPosition = statustab.position; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/import.hx: -------------------------------------------------------------------------------- 1 | // Global imports 2 | 3 | #if (!macro) 4 | 5 | import arm.Translator.tr; 6 | using StringTools; 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /checkstyle.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "INFO", 3 | "checks": [ 4 | { 5 | "type": "CodeSimilarity" 6 | }, 7 | { 8 | "type": "DefaultComesLast" 9 | }, 10 | { 11 | "type": "DocCommentStyle" 12 | }, 13 | { 14 | "type": "ERegLiteral" 15 | }, 16 | { 17 | "props": { 18 | "tokens": [ 19 | "CLASS_DEF", 20 | "ENUM_DEF", 21 | "ABSTRACT_DEF", 22 | "TYPEDEF_DEF", 23 | "INTERFACE_DEF", 24 | "OBJECT_DECL", 25 | "FUNCTION", 26 | "FOR", 27 | "IF", 28 | "WHILE", 29 | "SWITCH", 30 | "TRY", 31 | "CATCH" 32 | ], 33 | "option": "empty" 34 | }, 35 | "type": "EmptyBlock" 36 | }, 37 | { 38 | "props": { 39 | "max": 1 40 | }, 41 | "type": "EmptyLines" 42 | }, 43 | { 44 | "props": { 45 | "option": "lowerCase" 46 | }, 47 | "type": "HexadecimalLiteral" 48 | }, 49 | { 50 | "type": "InnerAssignment" 51 | }, 52 | { 53 | "props": { 54 | "modifiers": [ 55 | "MACRO", 56 | "OVERRIDE", 57 | "PUBLIC_PRIVATE", 58 | "STATIC", 59 | "INLINE", 60 | "DYNAMIC" 61 | ] 62 | }, 63 | "type": "ModifierOrder" 64 | }, 65 | { 66 | "type": "MultipleVariableDeclarations" 67 | }, 68 | { 69 | "props": { 70 | "allowSingleLineStatement": true, 71 | "tokens": [ 72 | "FOR", 73 | "IF", 74 | "ELSE_IF", 75 | "WHILE", 76 | "DO_WHILE" 77 | ] 78 | }, 79 | "type": "NeedBraces" 80 | }, 81 | { 82 | "props": { 83 | "assignOpPolicy": "around", 84 | "unaryOpPolicy": "none", 85 | "ternaryOpPolicy": "around", 86 | "arithmeticOpPolicy": "around", 87 | "compareOpPolicy": "around", 88 | "bitwiseOpPolicy": "around", 89 | "boolOpPolicy": "around", 90 | "intervalOpPolicy": "none", 91 | "arrowPolicy": "none", 92 | "oldFunctionTypePolicy": "none", 93 | "newFunctionTypePolicy": "none", 94 | "arrowFunctionPolicy": "around" 95 | }, 96 | "type": "OperatorWhitespace" 97 | }, 98 | { 99 | "props": { 100 | "tokens": [ 101 | "=", 102 | "*", 103 | "/", 104 | "%", 105 | ">", 106 | "<", 107 | ">=", 108 | "<=", 109 | "==", 110 | "!=", 111 | "&", 112 | "|", 113 | "^", 114 | "<<", 115 | ">>", 116 | ">>>", 117 | "+=", 118 | "-=", 119 | "*=", 120 | "/=", 121 | "%=", 122 | "<<=", 123 | ">>=", 124 | ">>>=", 125 | "|=", 126 | "&=", 127 | "^=", 128 | "...", 129 | "=>", 130 | "++", 131 | "--", 132 | "+", 133 | "-", 134 | "&&", 135 | "||" 136 | ], 137 | "option": "eol" 138 | }, 139 | "type": "OperatorWrap" 140 | }, 141 | { 142 | "type": "RedundantModifier" 143 | }, 144 | { 145 | "type": "RedundantAllowMeta" 146 | }, 147 | { 148 | "type": "RedundantAccessMeta" 149 | }, 150 | { 151 | "props": { 152 | "allowEmptyReturn": true, 153 | "enforceReturnType": false 154 | }, 155 | "type": "Return" 156 | }, 157 | { 158 | "props": { 159 | "dotPolicy": "none", 160 | "commaPolicy": "after", 161 | "semicolonPolicy": "after" 162 | }, 163 | "type": "SeparatorWhitespace" 164 | }, 165 | { 166 | "props": { 167 | "tokens": [ 168 | "," 169 | ], 170 | "option": "eol" 171 | }, 172 | "type": "SeparatorWrap" 173 | }, 174 | { 175 | "props": { 176 | "spaceIfCondition": "should", 177 | "spaceAroundBinop": true, 178 | "spaceForLoop": "should", 179 | "ignoreRangeOperator": true, 180 | "spaceWhileLoop": "should", 181 | "spaceCatch": "should", 182 | "spaceSwitchCase": "should", 183 | "noSpaceAroundUnop": true 184 | }, 185 | "type": "Spacing" 186 | }, 187 | { 188 | "props": { 189 | "allowException": true, 190 | "policy": "doubleAndInterpolation" 191 | }, 192 | "type": "StringLiteral" 193 | }, 194 | { 195 | "type": "TrailingWhitespace" 196 | }, 197 | { 198 | "type": "UnusedImport" 199 | }, 200 | { 201 | "type": "UnusedLocalVar" 202 | }, 203 | { 204 | "props": { 205 | "tokens": [ 206 | ",", 207 | ";", 208 | ":" 209 | ] 210 | }, 211 | "type": "WhitespaceAfter" 212 | }, 213 | { 214 | "props": { 215 | "tokens": [ 216 | "=", 217 | "+", 218 | "-", 219 | "*", 220 | "/", 221 | "%", 222 | ">", 223 | "<", 224 | ">=", 225 | "<=", 226 | "==", 227 | "!=", 228 | "&", 229 | "|", 230 | "^", 231 | "&&", 232 | "||", 233 | "<<", 234 | ">>", 235 | ">>>", 236 | "+=", 237 | "-=", 238 | "*=", 239 | "/=", 240 | "%=", 241 | "<<=", 242 | ">>=", 243 | ">>>=", 244 | "|=", 245 | "&=", 246 | "^=", 247 | "=>" 248 | ] 249 | }, 250 | "type": "WhitespaceAround" 251 | } 252 | ] 253 | } 254 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armory3d/armorlab/91f3f3c3c5bc14a70d4129f6c997b6a2b3c53d58/icon.png -------------------------------------------------------------------------------- /khafile.js: -------------------------------------------------------------------------------- 1 | 2 | let debug = false; 3 | let android = process.argv.indexOf("android") >= 0; 4 | let ios = process.argv.indexOf("ios") >= 0; 5 | let win_hlsl = process.platform === "win32" && process.argv.indexOf("opengl") < 0; 6 | let d3d12 = process.argv.indexOf("direct3d12") >= 0; 7 | let vulkan = process.argv.indexOf("vulkan") >= 0; 8 | let raytrace = d3d12 || vulkan; 9 | let metal = process.argv.indexOf("metal") >= 0; 10 | let vr = process.argv.indexOf("--vr") >= 0; 11 | let snapshot = process.argv.indexOf("--snapshot") >= 0; 12 | 13 | let project = new Project("ArmorLab"); 14 | project.addSources("Sources"); 15 | project.addLibrary("iron"); 16 | project.addLibrary("zui"); 17 | project.addLibrary("armorbase"); 18 | project.addShaders("Shaders/*.glsl", { embed: snapshot }); 19 | project.addShaders("armorcore/Shaders/*.glsl", { embed: snapshot }); 20 | project.addAssets("Assets/*", { destination: "data/{name}", embed: snapshot }); 21 | project.addShaders("Libraries/armorbase/Shaders/common/*.glsl", { embed: snapshot }); 22 | project.addAssets("Libraries/armorbase/Assets/common/*", { destination: "data/{name}", embed: snapshot }); 23 | if (!snapshot) { 24 | project.addDefine("arm_noembed"); 25 | project.addAssets("Libraries/armorbase/Assets/common/extra/*", { destination: "data/{name}" }); 26 | } 27 | project.addAssets("Assets/export_presets/*", { destination: "data/export_presets/{name}" }); 28 | project.addAssets("Assets/keymap_presets/*", { destination: "data/keymap_presets/{name}" }); 29 | project.addAssets("Assets/locale/*", { destination: "data/locale/{name}" }); 30 | project.addAssets("Assets/licenses/**", { destination: "data/licenses/{name}" }); 31 | project.addAssets("Assets/plugins/*", { destination: "data/plugins/{name}" }); 32 | project.addAssets("Assets/meshes/*", { destination: "data/meshes/{name}" }); 33 | project.addAssets("Assets/models/*.onnx", { destination: "data/models/{name}" }); 34 | project.addAssets("Assets/models/LICENSE.txt", { destination: "data/models/LICENSE.txt" }); 35 | project.addAssets("Libraries/armorbase/Assets/licenses/**", { destination: "data/licenses/{name}" }); 36 | project.addAssets("Libraries/armorbase/Assets/themes/*.json", { destination: "data/themes/{name}" }); 37 | project.addDefine("js-es=6"); 38 | project.addParameter("--macro include('arm.node.brush')"); 39 | project.addDefine("kha_no_ogg"); 40 | project.addDefine("zui_translate"); 41 | project.addDefine("arm_data_dir"); 42 | project.addDefine("arm_ltc"); 43 | project.addDefine("arm_appwh"); 44 | project.addDefine("arm_skip_envmap"); 45 | project.addDefine("arm_resizable"); 46 | project.addDefine("arm_taa"); 47 | project.addDefine("arm_veloc"); 48 | project.addDefine("arm_particles"); 49 | 50 | if (android) { 51 | project.addDefine("krom_android"); 52 | project.addDefine("kha_android"); 53 | project.addDefine("kha_android_rmb"); 54 | } 55 | else if (ios) { 56 | project.addDefine("krom_ios"); 57 | project.addDefine("kha_ios"); 58 | } 59 | else if (process.platform === "win32") { 60 | project.addDefine("krom_windows"); 61 | project.addDefine("kha_windows"); 62 | project.addAssets("armorcore/Libraries/onnx/win32/*.dll", { destination: "{name}" }); 63 | } 64 | else if (process.platform === "linux") { 65 | project.addDefine("krom_linux"); 66 | project.addDefine("kha_linux"); 67 | project.addAssets("armorcore/Libraries/onnx/linux/*.so.*", { destination: "{name}" }); // Versioned lib 68 | } 69 | else if (process.platform === "darwin") { 70 | project.addDefine("krom_darwin"); 71 | project.addDefine("kha_darwin"); 72 | } 73 | 74 | if (debug) { 75 | project.addDefine("arm_debug"); 76 | project.addParameter("--times"); 77 | // project.addParameter("--no-inline"); 78 | } 79 | else { 80 | project.addParameter("-dce full"); 81 | project.addDefine("analyzer-optimize"); 82 | } 83 | 84 | if (vr) { 85 | project.addDefine("arm_vr"); 86 | project.addAssets("Assets/readme/readme_vr.txt", { destination: "{name}" }); 87 | } 88 | 89 | if (snapshot) { 90 | project.addDefine("arm_snapshot"); 91 | project.addDefine("arm_image_embed"); 92 | project.addDefine("arm_shader_embed"); 93 | project.addParameter("--no-traces"); 94 | } 95 | 96 | project.addAssets("Assets/readme/readme.txt", { destination: "{name}" }); 97 | 98 | if (raytrace) { 99 | project.addAssets("Libraries/armorbase/Assets/raytrace/*", { destination: "data/{name}", embed: snapshot }); 100 | if (d3d12) { 101 | project.addAssets("Libraries/armorbase/Shaders/raytrace/*.cso", { destination: "data/{name}", embed: snapshot }); 102 | project.addAssets("Assets/readme/readme_dxr.txt", { destination: "{name}" }); 103 | } 104 | else if (vulkan) { 105 | project.addAssets("Libraries/armorbase/Shaders/raytrace/*.spirv", { destination: "data/{name}", embed: snapshot }); 106 | project.addAssets("Assets/readme/readme_vkrt.txt", { destination: "{name}" }); 107 | } 108 | } 109 | 110 | if (android) { 111 | project.addAssets("Assets/readme/readme_android.txt", { destination: "{name}" }); 112 | } 113 | else if (ios) { 114 | project.addAssets("Assets/readme/readme_ios.txt", { destination: "{name}" }); 115 | } 116 | 117 | if (process.platform !== "darwin" && !raytrace && !android && !ios) { 118 | project.addDefine("rp_voxels"); 119 | project.addDefine("arm_voxelgi_revox"); 120 | 121 | if (process.platform === "win32" && win_hlsl) { 122 | project.addShaders("Libraries/armorbase/Shaders/voxel_hlsl/*.glsl", { embed: snapshot, noprocessing: true }); 123 | } 124 | else { 125 | project.addShaders("Libraries/armorbase/Shaders/voxel_glsl/*.glsl", { embed: snapshot }); 126 | } 127 | } 128 | 129 | resolve(project); 130 | -------------------------------------------------------------------------------- /kincflags.js: -------------------------------------------------------------------------------- 1 | 2 | // Imported by armorcore/kfile.js 3 | flags.name = 'ArmorLab'; 4 | flags.package = 'org.armorlab'; 5 | flags.with_d3dcompiler = true; 6 | flags.with_nfd = true; 7 | flags.with_tinydir = true; 8 | flags.with_zlib = true; 9 | flags.with_stb_image_write = true; 10 | flags.with_krafix = graphics === 'vulkan'; // glsl to spirv for vulkan 11 | flags.with_plugin_embed = true; // platform === 'ios'; 12 | flags.with_onnx = true; 13 | --------------------------------------------------------------------------------