├── Assets ├── readme │ ├── readme_ios.txt │ ├── readme_vr.txt │ ├── readme.txt │ ├── readme_dxr.txt │ ├── readme_vkrt.txt │ └── readme_android.txt ├── raw │ ├── icon │ │ ├── armory_svg.md │ │ ├── icon.ico │ │ ├── icon.png │ │ ├── icon_raw.png │ │ ├── icon_macos.png │ │ └── armory.svg │ ├── gizmo.blend │ ├── tool_bake.png │ ├── tool_blur.png │ ├── tool_fill.png │ ├── tool_text.png │ ├── tool_brush.png │ ├── tool_clone.png │ ├── tool_colorid.png │ ├── tool_decal.png │ ├── tool_eraser.png │ ├── tool_picker.png │ ├── icon_settings.png │ └── tool_particle.png ├── Scene.arm ├── icons.png ├── icons2x.png ├── default_brush.arm ├── default_material.arm ├── meshes │ └── cube_shared_uvs.arm ├── plugins │ ├── wasm │ │ ├── uv_unwrap.wasm │ │ ├── import_svg.wasm │ │ ├── import_usdc.wasm │ │ ├── ammo │ │ │ └── ammo.wasm.wasm │ │ └── import_gltf_glb.wasm │ ├── viewport_celshade.js │ ├── autosave.js │ ├── import_txt.js │ ├── hello_world.js │ ├── embed │ │ ├── import_svg.js │ │ ├── import_usdc.js │ │ ├── import_gltf_glb.js │ │ └── uv_unwrap.js │ ├── hello_node_brush.js │ ├── hello_node.js │ ├── dev │ │ ├── profiler.js │ │ └── converter.js │ ├── import_stl.js │ └── texture_breakdown.js ├── export_presets │ ├── base_color.json │ ├── xplane.json │ ├── minecraft_mer.json │ ├── unreal.json │ ├── unigine.json │ ├── unity.json │ └── generic.json ├── licenses │ ├── license.md │ └── plugins │ │ ├── license_nanosvg.md │ │ ├── license_cgltf.md │ │ ├── license_tinyusdz.md │ │ ├── license_texsynth.md │ │ ├── license_utif.md │ │ └── license_xatlas.md ├── keymap_presets │ ├── blender.json │ └── default.json └── locale │ └── tools │ └── extract_locales.py ├── .gitattributes ├── .gitignore ├── icon.png ├── icon_macos.png ├── Shaders ├── dilate_map.frag.glsl ├── layer_copy.frag.glsl ├── layer_copy_bgra.frag.glsl ├── layer_invert.frag.glsl ├── layer_view.vert.glsl ├── mask_apply.frag.glsl ├── layer_merge.vert.glsl ├── armdefault_mesh.vert.glsl ├── dilate_map.vert.glsl ├── Material_mesh.vert.glsl ├── layer_view.frag.glsl ├── Material2_mesh.frag.glsl ├── armdefault_mesh.frag.glsl ├── Material_mesh.frag.glsl ├── dilate_pass.frag.glsl └── mask_merge.frag.glsl ├── Sources ├── import.hx ├── arm │ ├── node │ │ ├── brush │ │ │ ├── NullNode.hx │ │ │ ├── RandomNode.hx │ │ │ ├── TEX_IMAGE.hx │ │ │ ├── TimeNode.hx │ │ │ ├── SeparateVectorNode.hx │ │ │ ├── FloatNode.hx │ │ │ ├── IntegerNode.hx │ │ │ ├── StringNode.hx │ │ │ ├── BooleanNode.hx │ │ │ ├── ColorNode.hx │ │ │ ├── VectorNode.hx │ │ │ ├── MathNode.hx │ │ │ ├── VectorMathNode.hx │ │ │ ├── InputNode.hx │ │ │ └── BrushOutputNode.hx │ │ ├── LogicTree.hx │ │ ├── LogicNode.hx │ │ ├── MakeClone.hx │ │ ├── MakeDiscard.hx │ │ ├── MakeColorIdPicker.hx │ │ ├── MakeNodePreview.hx │ │ ├── MakeVoxel.hx │ │ ├── MakeBrush.hx │ │ ├── MakeTexcoord.hx │ │ ├── MakeParticle.hx │ │ └── MakeBlur.hx │ ├── ui │ │ ├── TabParticles.hx │ │ ├── TabPlugins.hx │ │ ├── TabHistory.hx │ │ ├── UIStatus.hx │ │ ├── TabConsole.hx │ │ ├── TabScript.hx │ │ ├── TabMeshes.hx │ │ ├── UIBox.hx │ │ ├── TabBrowser.hx │ │ ├── UIToolbar.hx │ │ └── UIMenubar.hx │ ├── io │ │ ├── ExportMesh.hx │ │ ├── ImportPlugin.hx │ │ ├── ImportFbx.hx │ │ ├── ExportGpl.hx │ │ ├── ImportKeymap.hx │ │ ├── ImportTheme.hx │ │ ├── ImportFont.hx │ │ ├── ImportGpl.hx │ │ ├── ImportTexture.hx │ │ ├── ImportAsset.hx │ │ ├── ImportFolder.hx │ │ └── ImportObj.hx │ ├── data │ │ ├── FontSlot.hx │ │ ├── BrushSlot.hx │ │ └── MaterialSlot.hx │ ├── Strings.hx │ ├── PluginAPI.hx │ ├── KeymapFormat.hx │ ├── ConfigFormat.hx │ ├── ProjectFormat.hx │ ├── util │ │ └── ParticleUtil.hx │ └── Args.hx └── Main.hx ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── ask_question.md │ ├── feature_request.md │ └── bug_report.md ├── FUNDING.yml └── workflows │ ├── macos_metal.yml │ ├── android_opengl.yml │ ├── windows_direct3d11.yml │ ├── windows_direct3d12.yml │ ├── ios_metal.yml │ ├── linux_opengl.yml │ └── linux_vulkan.yml ├── kincflags.js ├── .gitmodules ├── LICENSE.md └── checkstyle.json /Assets/readme/readme_ios.txt: -------------------------------------------------------------------------------- 1 | keepme 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.hdr binary 2 | *.bin binary 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | .vscode 3 | *.DS_Store 4 | *.blend1 5 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/icon.png -------------------------------------------------------------------------------- /Assets/raw/icon/armory_svg.md: -------------------------------------------------------------------------------- 1 | SVG logo version created by Edwin van het Bolscher 2 | -------------------------------------------------------------------------------- /icon_macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/icon_macos.png -------------------------------------------------------------------------------- /Assets/Scene.arm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/Scene.arm -------------------------------------------------------------------------------- /Assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/icons.png -------------------------------------------------------------------------------- /Assets/icons2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/icons2x.png -------------------------------------------------------------------------------- /Assets/raw/gizmo.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/gizmo.blend -------------------------------------------------------------------------------- /Assets/default_brush.arm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/default_brush.arm -------------------------------------------------------------------------------- /Assets/raw/icon/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/icon/icon.ico -------------------------------------------------------------------------------- /Assets/raw/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/icon/icon.png -------------------------------------------------------------------------------- /Assets/raw/tool_bake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_bake.png -------------------------------------------------------------------------------- /Assets/raw/tool_blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_blur.png -------------------------------------------------------------------------------- /Assets/raw/tool_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_fill.png -------------------------------------------------------------------------------- /Assets/raw/tool_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_text.png -------------------------------------------------------------------------------- /Assets/default_material.arm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/default_material.arm -------------------------------------------------------------------------------- /Assets/raw/tool_brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_brush.png -------------------------------------------------------------------------------- /Assets/raw/tool_clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_clone.png -------------------------------------------------------------------------------- /Assets/raw/tool_colorid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_colorid.png -------------------------------------------------------------------------------- /Assets/raw/tool_decal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_decal.png -------------------------------------------------------------------------------- /Assets/raw/tool_eraser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_eraser.png -------------------------------------------------------------------------------- /Assets/raw/tool_picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_picker.png -------------------------------------------------------------------------------- /Shaders/dilate_map.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | out float fragColor; 3 | void main() { 4 | fragColor = 1.0; 5 | } 6 | -------------------------------------------------------------------------------- /Assets/raw/icon/icon_raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/icon/icon_raw.png -------------------------------------------------------------------------------- /Assets/raw/icon_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/icon_settings.png -------------------------------------------------------------------------------- /Assets/raw/tool_particle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/tool_particle.png -------------------------------------------------------------------------------- /Assets/raw/icon/icon_macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/raw/icon/icon_macos.png -------------------------------------------------------------------------------- /Assets/meshes/cube_shared_uvs.arm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/meshes/cube_shared_uvs.arm -------------------------------------------------------------------------------- /Assets/plugins/wasm/uv_unwrap.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/plugins/wasm/uv_unwrap.wasm -------------------------------------------------------------------------------- /Sources/import.hx: -------------------------------------------------------------------------------- 1 | // Global imports 2 | 3 | #if (!macro) 4 | 5 | import arm.Translator.tr; 6 | using StringTools; 7 | 8 | #end 9 | -------------------------------------------------------------------------------- /Assets/plugins/wasm/import_svg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/plugins/wasm/import_svg.wasm -------------------------------------------------------------------------------- /Assets/plugins/wasm/import_usdc.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/plugins/wasm/import_usdc.wasm -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Disallow blank issues to make sure people follow one of the templates. 2 | blank_issues_enabled: false 3 | -------------------------------------------------------------------------------- /Assets/plugins/wasm/ammo/ammo.wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/plugins/wasm/ammo/ammo.wasm.wasm -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [luboslenco] 4 | custom: ['https://armorpaint.org/download'] 5 | -------------------------------------------------------------------------------- /Assets/plugins/wasm/import_gltf_glb.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmoreloss/armorpaintCompiled/HEAD/Assets/plugins/wasm/import_gltf_glb.wasm -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_invert.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 = vec4(1.0) - textureLod(tex, texCoord, 0).rgba * color; 8 | } 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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): Dynamic { 11 | return null; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a question 3 | about: Post a question about ArmorPaint 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | --- 8 | 9 | 12 | -------------------------------------------------------------------------------- /Shaders/layer_merge.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 | -------------------------------------------------------------------------------- /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": ["metal", "emis", "rough", "1.0"], "color_space": "linear" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /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", "metal", "1.0"], "color_space": "linear" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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): Dynamic { 11 | var min = inputs[0].get(); 12 | var max = inputs[1].get(); 13 | return min + (Math.random() * (max - min)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/TEX_IMAGE.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class TEX_IMAGE 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): Dynamic { 14 | if (from == 0) return file + ".rgb"; 15 | return file + ".a"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/TimeNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | @:keep 4 | class TimeNode extends LogicNode { 5 | 6 | public function new(tree: LogicTree) { 7 | super(tree); 8 | } 9 | 10 | override function get(from: Int): Dynamic { 11 | if (from == 0) return iron.system.Time.time(); 12 | else if (from == 1) return iron.system.Time.delta; 13 | else return Context.brushTime; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /kincflags.js: -------------------------------------------------------------------------------- 1 | 2 | // Imported by armorcore/kfile.js 3 | flags.name = 'ArmorPaint'; 4 | flags.package = 'org.armorpaint'; 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 = platform === 'ios'; 12 | -------------------------------------------------------------------------------- /Sources/arm/ui/TabParticles.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | class TabParticles { 4 | 5 | public static function draw() { 6 | var ui = UISidebar.inst.ui; 7 | if (ui.tab(UISidebar.inst.htab1, tr("Particles"))) { 8 | ui.beginSticky(); 9 | ui.row([1 / 4, 1 / 4, 1 / 4]); 10 | if (ui.button(tr("New"))) {} 11 | if (ui.button(tr("Import"))) {} 12 | if (ui.button(tr("Nodes"))) {} 13 | ui.endSticky(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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": ["metal", "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 | -------------------------------------------------------------------------------- /Shaders/armdefault_mesh.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | in vec4 pos; 3 | in vec2 nor; 4 | out vec3 wnormal; 5 | out vec4 wvpposition; 6 | out vec4 prevwvpposition; 7 | uniform mat3 N; 8 | uniform mat4 WVP; 9 | uniform mat4 prevWVP; 10 | void main() { 11 | vec4 spos = vec4(pos.xyz, 1.0); 12 | wnormal = normalize(N * vec3(nor.xy, pos.w)); 13 | gl_Position = WVP * spos; 14 | wvpposition = gl_Position; 15 | prevwvpposition = prevWVP * spos; 16 | } 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Libraries/iron"] 2 | path = Libraries/iron 3 | url = https://github.com/armory3d/iron 4 | [submodule "Libraries/zui"] 5 | path = Libraries/zui 6 | url = https://github.com/armory3d/zui 7 | [submodule "armorcore"] 8 | path = armorcore 9 | url = https://github.com/armory3d/armorcore 10 | shallow = true 11 | [submodule "Libraries/armorbase"] 12 | path = Libraries/armorbase 13 | url = https://github.com/armory3d/armorbase 14 | -------------------------------------------------------------------------------- /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": ["metal", "occ", "1.0", "smooth"], "color_space": "linear" }, 6 | { "name": "height", "channels": ["0.0", "height", "0.0", "1.0"], "color_space": "linear" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /Shaders/dilate_map.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | in vec4 pos; 4 | in vec2 nor; 5 | in vec2 tex; 6 | // uniform float texUnpack; 7 | void main() { 8 | #if defined(HLSL) || defined(METAL) || defined(SPIRV) 9 | vec2 texCoord = vec2(tex.x * 2.0 - 1.0, (1.0 - tex.y) * 2.0 - 1.0); 10 | #else 11 | vec2 texCoord = tex * 2.0 - 1.0; 12 | #endif 13 | gl_Position = vec4(texCoord, 0.0, 1.0); 14 | #ifdef HLSL 15 | float keep = pos.x + nor.x; 16 | #endif 17 | } 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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): Dynamic { 13 | var vector: Vec4 = inputs[0].get(); 14 | 15 | if (from == 0) return vector.x; 16 | else if (from == 1) return vector.y; 17 | else return vector.z; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /Assets/plugins/viewport_celshade.js: -------------------------------------------------------------------------------- 1 | 2 | let plugin = new arm.Plugin(); 3 | 4 | // Register custom viewport shader 5 | arm.Context.setViewportShader(function(shader) { 6 | shader.add_uniform('vec3 lightDir', '_lightDirection'); 7 | shader.write(` 8 | float dotNL = max(dot(n, lightDir), 0.0); 9 | vec3 outputColor = basecol * step(0.5, dotNL) + basecol; 10 | `); 11 | return 'outputColor'; 12 | }); 13 | 14 | plugin.delete = function() { 15 | arm.Context.setViewportShader(null); 16 | } 17 | -------------------------------------------------------------------------------- /Assets/readme/readme_vr.txt: -------------------------------------------------------------------------------- 1 | ! This is an experimental build with with support for virtual reality headsets ! 2 | 3 | Supported headsets: 4 | - Oculus Rift 5 | - Oculus Quest via Oculus Link 6 | - SteamVR support will be added in the future 7 | 8 | This build is only intended for testing VR features and 9 | may be sub-par in other areas compared to regular builds. 10 | 11 | Currently exposed VR features: 12 | - Preview the model in VR 13 | - Painting the model in VR is planned but not yet present 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/raw/icon/armory.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Sources/arm/ui/TabPlugins.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | class TabPlugins { 4 | 5 | public static function draw() { 6 | var ui = UISidebar.inst.ui; 7 | if (ui.tab(UISidebar.inst.htab0, tr("Plugins"))) { 8 | 9 | ui.beginSticky(); 10 | ui.row([1 / 4]); 11 | if (ui.button(tr("Manager"))) { 12 | BoxPreferences.htab.position = 6; // Plugins 13 | BoxPreferences.show(); 14 | } 15 | ui.endSticky(); 16 | 17 | // Draw plugins 18 | for (p in Plugin.plugins) if (p.drawUI != null) p.drawUI(ui); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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": "metal", "channels": ["metal", "metal", "metal", "1.0"], "color_space": "linear" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /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 | 8 | public function new(tree: LogicTree, value = 0.0) { 9 | super(tree); 10 | this.value = value; 11 | } 12 | 13 | override function get(from: Int): Dynamic { 14 | if (inputs.length > 0) return inputs[0].get(); 15 | return 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/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): Dynamic { 14 | if (inputs.length > 0) return inputs[0].get(); 15 | return 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/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): Dynamic { 14 | if (inputs.length > 0) return inputs[0].get(); 15 | return 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/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): Dynamic { 14 | if (inputs.length > 0) return inputs[0].get(); 15 | return 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 | -------------------------------------------------------------------------------- /.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: Clone plugins 21 | run: git clone https://github.com/armory3d/armorpaint_plugins Libraries/plugins 22 | - name: Compile 23 | run: armorcore/Kinc/make --from armorcore ios -g metal 24 | -------------------------------------------------------------------------------- /Assets/readme/readme.txt: -------------------------------------------------------------------------------- 1 | Thank you for supporting ArmorPaint! 2 | 3 | Quick start: 4 | https://armorpaint.org/manual 5 | 6 | ( 7 | ( )\ ) ) 8 | )\ ( ) ( (()/( ) ( ( /( 9 | ((((_)( )( ( ( )( /(_))( /( )\ ( )\()) 10 | )\ _ )\(()\ )\ ' )\ (()\ (_)) )(_))((_) )\ ) (_))/ 11 | (_)_\(_)((_) _((_)) ((_) ((_)| _ \((_)_ (_) _(_/( | |_ 12 | / _ \ | '_|| ' \()/ _ \| '_|| _// _` | | || ' \))| _| 13 | /_/ \_\|_| |_|_|_| \___/|_| |_| \__,_| |_||_||_| \__| 14 | -------------------------------------------------------------------------------- /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 | 10 | public function new(tree: LogicTree, r = 0.8, g = 0.8, b = 0.8, a = 1.0) { 11 | super(tree); 12 | 13 | value.set(r, g, b, a); 14 | } 15 | 16 | override function get(from: Int): Dynamic { 17 | if (inputs.length > 0) return inputs[0].get(); 18 | return value; 19 | } 20 | 21 | override function set(value: Dynamic) { 22 | if (inputs.length > 0) inputs[0].set(value); 23 | else this.value = value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/arm/data/FontSlot.hx: -------------------------------------------------------------------------------- 1 | package arm.data; 2 | 3 | import haxe.Json; 4 | import kha.Image; 5 | import kha.Blob; 6 | import zui.Nodes; 7 | import iron.data.Data; 8 | 9 | class FontSlot { 10 | public var image: Image = null; // 200px 11 | public var previewReady = false; 12 | public var id = 0; 13 | public var font: kha.Font; 14 | public var name: String; 15 | public var file: String; 16 | 17 | public function new(name: String, font: kha.Font, file = "") { 18 | for (slot in Project.fonts) if (slot.id >= id) id = slot.id + 1; 19 | this.name = name; 20 | this.font = font; 21 | this.file = file; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Assets/readme/readme_dxr.txt: -------------------------------------------------------------------------------- 1 | ! This is an experimental build with hardware accelerated ray-tracing using DXR and Direct3D12 ! 2 | 3 | Supported GPUs: 4 | - NVIDIA RTX 20 Series or better 5 | - AMD RX 6800 XT or better 6 | 7 | Up to date Windows 10/11 and graphics drivers required. 8 | 9 | Learn more: 10 | https://en.wikipedia.org/wiki/DirectX_Raytracing 11 | 12 | This build is only intended for testing ray-tracing features and 13 | may be sub-par in other areas compared to regular builds. 14 | 15 | Currently exposed ray-tracing features: 16 | - Bake Tool - AO 17 | - Bake Tool - Bent Normal 18 | - Bake Tool - Lightmap 19 | - Bake Tool - Thickness 20 | - Viewport Mode - Path-Trace 21 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Assets/readme/readme_vkrt.txt: -------------------------------------------------------------------------------- 1 | ! This is an experimental build with hardware accelerated ray-tracing powered by Vulkan Ray Tracing ! 2 | 3 | Supported GPUs: 4 | - NVIDIA RTX 20 Series or better 5 | - AMD RX 6800 XT or better 6 | 7 | Requires Vulkan driver 455.46.04 or newer on NVIDIA cards: 8 | https://developer.nvidia.com/vulkan-driver 9 | 10 | Learn more: 11 | https://www.khronos.org/blog/ray-tracing-in-vulkan 12 | 13 | This build is only intended for testing ray-tracing features and 14 | may be sub-par in other areas compared to regular builds. 15 | 16 | Currently exposed ray-tracing features: 17 | - Bake Tool - AO 18 | - Bake Tool - Bent Normal 19 | - Bake Tool - Lightmap 20 | - Bake Tool - Thickness 21 | - Viewport Mode - Path-Trace 22 | -------------------------------------------------------------------------------- /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 error4(): String { return tr("Error: Mesh has no UVs, generating defaults"); } 9 | public static function error5(): String { return tr("Error: Check internet connection to access the cloud"); } 10 | public static function info0(): String { return tr("Info: Asset already imported"); } 11 | } 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 | **ArmorPaint version:** 15 | 16 | 17 | 18 | **OS/device including version:** 19 | 20 | 21 | 22 | **Issue description:** 23 | 24 | 25 | 26 | **Steps to reproduce:** 27 | 28 | -------------------------------------------------------------------------------- /Assets/readme/readme_android.txt: -------------------------------------------------------------------------------- 1 | ! This is an experimental build for Android ! 2 | 3 | Recommended: 4 | - Android: 10.0 or newer 5 | - GPU: Adreno 530 / Mali T880MP12 or faster 6 | - CPU: 64bit (arm64-v8a) 7 | 8 | Installation: 9 | - Copy .apk file to Android device 10 | - Open the .apk with file browser to install it 11 | - Allow installing from unknown sources 12 | 13 | Controls: 14 | - Paint with one finger, rotate and scroll with two fingers 15 | - Support for pen with pressure sensitivity is included 16 | - Support for mouse and keyboard is included 17 | 18 | This build is only intended for testing and 19 | may be sub-par in other areas compared to regular builds. 20 | 21 | Please report bugs at: 22 | https://github.com/armory3d/armorpaint/issues 23 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /Shaders/layer_view.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform sampler2D tex; 3 | uniform int channel; 4 | in vec2 texCoord; 5 | in vec4 color; 6 | out vec4 FragColor; 7 | void main() { 8 | if (channel == 1) { 9 | FragColor = textureLod(tex, texCoord, 0).rrra * color; 10 | } 11 | else if (channel == 2) { 12 | FragColor = textureLod(tex, texCoord, 0).ggga * color; 13 | } 14 | else if (channel == 3) { 15 | FragColor = textureLod(tex, texCoord, 0).bbba * color; 16 | } 17 | else if (channel == 4) { 18 | FragColor = textureLod(tex, texCoord, 0).aaaa * color; 19 | } 20 | else if (channel == 5) { 21 | FragColor = textureLod(tex, texCoord, 0).rgba * color; 22 | } 23 | else { 24 | vec4 tex_sample = textureLod(tex, texCoord, 0).rgba; 25 | tex_sample.rgb *= tex_sample.a; 26 | FragColor = tex_sample * color; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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 | 10 | public function new(tree: LogicTree, x: Null = null, y: Null = null, z: Null = null) { 11 | super(tree); 12 | 13 | if (x != null) { 14 | addInput(new FloatNode(tree, x), 0); 15 | addInput(new FloatNode(tree, y), 0); 16 | addInput(new FloatNode(tree, z), 0); 17 | } 18 | } 19 | 20 | override function get(from: Int): Dynamic { 21 | value.x = inputs[0].get(); 22 | value.y = inputs[1].get(); 23 | value.z = inputs[2].get(); 24 | return value; 25 | } 26 | 27 | override function set(value: Dynamic) { 28 | inputs[0].set(value.x); 29 | inputs[1].set(value.y); 30 | inputs[2].set(value.z); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Sources/arm/ui/TabHistory.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | class TabHistory { 4 | 5 | @:access(zui.Zui) 6 | public static function draw() { 7 | var ui = UISidebar.inst.ui; 8 | if (ui.tab(UISidebar.inst.htab0, tr("History"))) { 9 | for (i in 0...History.steps.length) { 10 | var active = History.steps.length - 1 - History.redos; 11 | if (i == active) { 12 | ui.fill(0, 0, ui._windowW, ui.t.ELEMENT_H, ui.t.HIGHLIGHT_COL); 13 | } 14 | ui.text(History.steps[i].name); 15 | if (ui.isReleased) { // Jump to undo step 16 | var diff = i - active; 17 | while (diff > 0) { 18 | diff--; 19 | History.redo(); 20 | } 21 | while (diff < 0) { 22 | diff++; 23 | History.undo(); 24 | } 25 | } 26 | ui.fill(0, 0, (ui._windowW / ui.SCALE() - 2), 1 * ui.SCALE(), ui.t.SEPARATOR_COL); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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/armorpaint 10 | -------------------------------------------------------------------------------- /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/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/armorpaint 10 | -------------------------------------------------------------------------------- /Sources/arm/data/BrushSlot.hx: -------------------------------------------------------------------------------- 1 | package arm.data; 2 | 3 | import haxe.Json; 4 | import kha.Image; 5 | import kha.Blob; 6 | import zui.Nodes; 7 | import iron.data.Data; 8 | 9 | class BrushSlot { 10 | public var nodes = new Nodes(); 11 | public var canvas: TNodeCanvas; 12 | public var image: Image = null; // 200px 13 | public var imageIcon: Image = null; // 50px 14 | public var previewReady = false; 15 | public var id = 0; 16 | static var defaultCanvas: Blob = null; 17 | 18 | public function new(c: TNodeCanvas = null) { 19 | for (brush in Project.brushes) if (brush.id >= id) id = brush.id + 1; 20 | 21 | if (c == null) { 22 | if (defaultCanvas == null) { // Synchronous 23 | Data.getBlob("default_brush.arm", function(b: Blob) { 24 | defaultCanvas = b; 25 | }); 26 | } 27 | canvas = iron.system.ArmPack.decode(defaultCanvas.toBytes()); 28 | canvas.name = "Brush " + (id + 1); 29 | } 30 | else { 31 | canvas = c; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Shaders/Material2_mesh.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #include "../Libraries/armorbase/Shaders/std/gbuffer.glsl" 3 | in vec3 wnormal; 4 | in vec4 wvpposition; 5 | in vec4 prevwvpposition; 6 | out vec4 fragColor[3]; 7 | void main() { 8 | vec3 n = normalize(wnormal); 9 | vec3 basecol; 10 | float roughness; 11 | float metallic; 12 | float occlusion; 13 | float emission; 14 | basecol = vec3(0.800000011920929, 0.800000011920929, 0.800000011920929); 15 | roughness = 0.0; 16 | metallic = 0.0; 17 | occlusion = 1.0; 18 | emission = 0.0; 19 | n /= (abs(n.x) + abs(n.y) + abs(n.z)); 20 | n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy); 21 | uint matid = 0; 22 | if (emission > 0) { basecol *= emission; matid = 1; } 23 | fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, matid)); 24 | fragColor[1] = vec4(basecol, occlusion); 25 | vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5; 26 | vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5; 27 | fragColor[2].rg = vec2(posa - posb); 28 | } 29 | -------------------------------------------------------------------------------- /Shaders/armdefault_mesh.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | #include "../Libraries/armorbase/Shaders/std/gbuffer.glsl" 3 | in vec3 wnormal; 4 | in vec4 wvpposition; 5 | in vec4 prevwvpposition; 6 | out vec4 fragColor[3]; 7 | void main() { 8 | vec3 n = normalize(wnormal); 9 | vec3 basecol; 10 | float roughness; 11 | float metallic; 12 | float occlusion; 13 | float emission; 14 | basecol = vec3(0.800000011920929, 0.800000011920929, 0.800000011920929); 15 | roughness = 0.25; 16 | metallic = 0.0; 17 | occlusion = 1.0; 18 | emission = 0.0; 19 | n /= (abs(n.x) + abs(n.y) + abs(n.z)); 20 | n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy); 21 | uint matid = 0; 22 | if (emission > 0) { basecol *= emission; matid = 1; } 23 | fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, matid)); 24 | fragColor[1] = vec4(basecol, occlusion); 25 | vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5; 26 | vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5; 27 | fragColor[2].rg = vec2(posa - posb); 28 | } 29 | -------------------------------------------------------------------------------- /Assets/licenses/plugins/license_nanosvg.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-14 Mikko Mononen memon@inside.org 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | 19 | https://github.com/memononen/nanosvg 20 | -------------------------------------------------------------------------------- /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/licenses/plugins/license_cgltf.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Johannes Kuhlmann 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | https://github.com/jkuhlmann/cgltf 10 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportFont.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import kha.Font; 4 | import iron.data.Data; 5 | import arm.sys.Path; 6 | import arm.ui.UIStatus; 7 | import arm.data.FontSlot; 8 | import arm.util.RenderUtil; 9 | 10 | class ImportFont { 11 | 12 | public static function run(path: String) { 13 | for (f in Project.fonts) { 14 | if (f.file == path) { 15 | Console.info(Strings.info0()); 16 | return; 17 | } 18 | } 19 | Data.getFont(path, function(font: Font) { 20 | var fn = font.getFontNames(); 21 | var fontSlots = new Array(); 22 | for (i in 0...fn.length) { 23 | var ar = path.split(Path.sep); 24 | var name = fn[i] != null ? fn[i] : ar[ar.length - 1]; 25 | var f = font.clone(); 26 | f.setFontIndex(i); 27 | var fontSlot = new FontSlot(name, f, path); 28 | fontSlots.push(fontSlot); 29 | } 30 | 31 | function _init() { 32 | for (f in fontSlots) { 33 | Context.font = f; 34 | Project.fonts.push(f); 35 | RenderUtil.makeFontPreview(); 36 | } 37 | } 38 | iron.App.notifyOnInit(_init); 39 | 40 | UIStatus.inst.statusHandle.redraws = 2; 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Assets/licenses/plugins/license_tinyusdz.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Syoyo Fujita 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 | -------------------------------------------------------------------------------- /Assets/licenses/plugins/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Assets/licenses/plugins/license_utif.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Photopea 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/photopea/UTIF.js 24 | -------------------------------------------------------------------------------- /Assets/licenses/plugins/license_xatlas.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2020 Jonathan Young 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/jpcy/xatlas 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 NodesMaterial = arm.shader.NodesMaterial; 17 | public static var NodesBrush = arm.node.NodesBrush; 18 | public static var MaterialParser = arm.shader.MaterialParser; 19 | public static var MakeMaterial = arm.node.MakeMaterial; 20 | public static var Brush = arm.node.Brush; 21 | public static var UISidebar = arm.ui.UISidebar; 22 | public static var UINodes = arm.ui.UINodes; 23 | public static var UIFiles = arm.ui.UIFiles; 24 | public static var UIMenu = arm.ui.UIMenu; 25 | public static var UIView2D = arm.ui.UIView2D; 26 | public static var UIBox = arm.ui.UIBox; 27 | public static var MeshUtil = arm.util.MeshUtil; 28 | public static var RenderUtil = arm.util.RenderUtil; 29 | public static var UVUtil = arm.util.UVUtil; 30 | public static var Viewport = arm.Viewport; 31 | } 32 | -------------------------------------------------------------------------------- /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() - UIToolbar.inst.toolbarw - Config.raw.layout[LayoutSidebarW], 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 | TabFonts.draw(); 38 | TabSwatches.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 | -------------------------------------------------------------------------------- /Assets/plugins/embed/import_usdc.js: -------------------------------------------------------------------------------- 1 | 2 | let a = Krom_import_usdc; 3 | class R { 4 | get buffer() { return Krom_import_usdc._buffer(); } 5 | } 6 | let r = new R(); 7 | 8 | // import_usdc.js 9 | let import_usdc = function(path, done) { 10 | iron.Data.getBlob(path, function(b) { 11 | let buf = new Uint8Array(r.buffer, a._init(b.bytes.length), b.bytes.length); 12 | for (let i = 0; i < b.bytes.length; ++i) buf[i] = b.readU8(i); 13 | a._parse(); 14 | let vertex_count = a._get_vertex_count(); 15 | let index_count = a._get_index_count(); 16 | let inda = new Uint32Array(r.buffer, a._get_indices(), index_count); 17 | let posa = new Int16Array(r.buffer, a._get_positions(), vertex_count * 4); 18 | let nora = new Int16Array(r.buffer, a._get_normals(), vertex_count * 2); 19 | let texa = new Int16Array(r.buffer, a._get_uvs(), vertex_count * 2); 20 | let name = path.split("\\").pop().split("/").pop().split(".").shift(); 21 | done({ 22 | name: name, 23 | posa: posa, 24 | nora: nora, 25 | texa: texa, 26 | inda: inda, 27 | scale_pos: a._get_scale_pos(), 28 | scale_tex: 1.0 29 | }); 30 | a._destroy(); 31 | iron.Data.deleteBlob(path); 32 | }); 33 | } 34 | 35 | let plugin = new arm.Plugin(); 36 | let formats = arm.Path.meshFormats; 37 | let importers = arm.Path.meshImporters; 38 | formats.push("usdc"); 39 | importers.h["usdc"] = import_usdc; 40 | 41 | plugin.delete = function() { 42 | formats.splice(formats.indexOf("usdc"), 1); 43 | importers.h["usdc"] = null; 44 | }; 45 | -------------------------------------------------------------------------------- /Shaders/dilate_pass.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | uniform sampler2D tex; 4 | uniform sampler2D texdilate; 5 | uniform float dilateRadius; 6 | in vec2 texCoord; 7 | out vec4 fragColor; 8 | 9 | const vec2 offsets[8] = vec2[] ( 10 | vec2(-1, 0), 11 | vec2( 1, 0), 12 | vec2( 0, 1), 13 | vec2( 0,-1), 14 | vec2(-1, 1), 15 | vec2( 1, 1), 16 | vec2( 1,-1), 17 | vec2(-1,-1) 18 | ); 19 | 20 | void main() { 21 | // Based on https://shaderbits.com/blog/uv-dilation by Ryan Brucks 22 | vec2 size = textureSize(tex, 0).xy; 23 | vec2 texelSize = 1.0 / size; 24 | float minDist = 10000000; 25 | ivec2 coord = ivec2(texCoord * size); 26 | float mask = texelFetch(texdilate, coord, 0).r; 27 | if (mask > 0) discard; 28 | 29 | fragColor = texelFetch(tex, coord, 0); 30 | int i = 0; 31 | while (i < dilateRadius) { 32 | i++; 33 | int j = 0; 34 | while (j < 8) { 35 | vec2 curUV = texCoord + offsets[j] * texelSize * i; 36 | coord = ivec2(curUV * size); 37 | float offsetMask = texelFetch(texdilate, coord, 0).r; 38 | vec4 offsetCol = texelFetch(tex, coord, 0); 39 | 40 | if (offsetMask != 0) { 41 | float curDist = length(texCoord - curUV); 42 | if (curDist < minDist) { 43 | vec2 projectUV = curUV + offsets[j] * texelSize * i * 0.25; 44 | vec4 direction = textureLod(tex, projectUV, 0.0); 45 | minDist = curDist; 46 | if (direction.x != 0 || direction.y != 0 || direction.z != 0) { 47 | vec4 delta = offsetCol - direction; 48 | fragColor = offsetCol + delta * 4; 49 | } 50 | else { 51 | fragColor = offsetCol; 52 | } 53 | } 54 | } 55 | j++; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Assets/plugins/embed/import_gltf_glb.js: -------------------------------------------------------------------------------- 1 | 2 | let a = Krom_import_gltf; 3 | class R { 4 | get buffer() { return Krom_import_gltf._buffer(); } 5 | } 6 | let r = new R(); 7 | 8 | // uv_unwrap.js 9 | let import_gltf_glb = function(path, done) { 10 | iron.Data.getBlob(path, function(b) { 11 | let buf = new Uint8Array(r.buffer, a._init(b.bytes.length), b.bytes.length); 12 | for (let i = 0; i < b.bytes.length; ++i) buf[i] = b.readU8(i); 13 | a._parse(); 14 | let vertex_count = a._get_vertex_count(); 15 | let index_count = a._get_index_count(); 16 | let inda = new Uint32Array(r.buffer, a._get_indices(), index_count); 17 | let posa = new Int16Array(r.buffer, a._get_positions(), vertex_count * 4); 18 | let nora = new Int16Array(r.buffer, a._get_normals(), vertex_count * 2); 19 | let texa = new Int16Array(r.buffer, a._get_uvs(), vertex_count * 2); 20 | let name = path.split("\\").pop().split("/").pop().split(".").shift(); 21 | done({ 22 | name: name, 23 | posa: posa, 24 | nora: nora, 25 | texa: texa, 26 | inda: inda, 27 | scale_pos: a._get_scale_pos(), 28 | scale_tex: 1.0 29 | }); 30 | a._destroy(); 31 | iron.Data.deleteBlob(path); 32 | }); 33 | } 34 | 35 | let plugin = new arm.Plugin(); 36 | let formats = arm.Path.meshFormats; 37 | let importers = arm.Path.meshImporters; 38 | formats.push("gltf"); 39 | formats.push("glb"); 40 | importers.h["gltf"] = import_gltf_glb; 41 | importers.h["glb"] = import_gltf_glb; 42 | 43 | plugin.delete = function() { 44 | formats.splice(formats.indexOf("gltf"), 1); 45 | formats.splice(formats.indexOf("glb"), 1); 46 | importers.h["gltf"] = null; 47 | importers.h["glb"] = null; 48 | }; 49 | -------------------------------------------------------------------------------- /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/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 | /** 22 | Called when this node is activated. 23 | @param from impulse index 24 | **/ 25 | function run(from: Int) {} 26 | 27 | /** 28 | Call to activate node connected to the output. 29 | @param i output index 30 | **/ 31 | function runOutput(i: Int) { 32 | if (i >= outputs.length) return; 33 | for (o in outputs[i]) { 34 | // Check which input activated the node 35 | for (j in 0...o.inputs.length) { 36 | if (o.inputs[j].node == this) { 37 | o.run(j); 38 | break; 39 | } 40 | } 41 | } 42 | } 43 | 44 | @:allow(arm.node.LogicNodeInput) 45 | function get(from: Int): Dynamic { 46 | return this; 47 | } 48 | 49 | @:allow(arm.node.LogicNodeInput) 50 | function set(value: Dynamic) {} 51 | } 52 | 53 | class LogicNodeInput { 54 | 55 | @:allow(arm.node.LogicNode) 56 | var node: LogicNode; 57 | var from: Int; // Socket index 58 | 59 | public function new(node: LogicNode, from: Int) { 60 | this.node = node; 61 | this.from = from; 62 | } 63 | 64 | @:allow(arm.node.LogicNode) 65 | function get(): Dynamic { 66 | return node.get(from); 67 | } 68 | 69 | @:allow(arm.node.LogicNode) 70 | function set(value: Dynamic) { 71 | node.set(value); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeClone.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | import arm.shader.NodeShader; 4 | 5 | class MakeClone { 6 | 7 | public static function run(vert: NodeShader, frag: NodeShader) { 8 | frag.add_uniform('vec2 cloneDelta', '_cloneDelta'); 9 | frag.write('vec2 cloneDeltaLocal = cloneDelta;'); // TODO: spirv workaround 10 | frag.write('vec2 gbufferSizeLocal = gbufferSize;'); // TODO: spirv workaround 11 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 12 | frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.xy + cloneDeltaLocal) * gbufferSizeLocal), 0).ba;'); 13 | #else 14 | frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.x + cloneDeltaLocal.x) * gbufferSizeLocal.x, (1.0 - (sp.y + cloneDeltaLocal.y)) * gbufferSizeLocal.y), 0).ba;'); 15 | #end 16 | 17 | frag.write('vec3 texpaint_pack_sample = textureLod(texpaint_pack_undo, texCoordInp, 0.0).rgb;'); 18 | var base = 'textureLod(texpaint_undo, texCoordInp, 0.0).rgb'; 19 | var rough = 'texpaint_pack_sample.g'; 20 | var met = 'texpaint_pack_sample.b'; 21 | var occ = 'texpaint_pack_sample.r'; 22 | var nortan = 'textureLod(texpaint_nor_undo, texCoordInp, 0.0).rgb'; 23 | var height = '0.0'; 24 | var opac = '1.0'; 25 | frag.write('vec3 basecol = $base;'); 26 | frag.write('float roughness = $rough;'); 27 | frag.write('float metallic = $met;'); 28 | frag.write('float occlusion = $occ;'); 29 | frag.write('vec3 nortan = $nortan;'); 30 | frag.write('float height = $height;'); 31 | frag.write('float mat_opacity = $opac;'); 32 | frag.write('float opacity = mat_opacity * brushOpacity;'); 33 | if (Context.material.paintEmis) { 34 | frag.write('float emis = 0.0;'); 35 | } 36 | if (Context.material.paintSubs) { 37 | frag.write('float subs = 0.0;'); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 | if (Config.raw.touch_ui) { 32 | ui.row([1 / 4, 1 / 4]); 33 | } 34 | else { 35 | ui.row([1 / 14, 1 / 14]); 36 | } 37 | #end 38 | 39 | if (ui.button(tr("Clear"))) { 40 | Console.lastTraces = []; 41 | } 42 | if (ui.button(tr("Export"))) { 43 | var str = Console.lastTraces.join("\n"); 44 | UIFiles.show("txt", true, false, function(path: String) { 45 | var f = UIFiles.filename; 46 | if (f == "") f = tr("untitled"); 47 | path = path + Path.sep + f; 48 | if (!path.endsWith(".txt")) path += ".txt"; 49 | Krom.fileSaveBytes(path, Bytes.ofString(str).getData()); 50 | }); 51 | } 52 | #if (krom_windows || krom_linux || krom_darwin) 53 | if (ui.button(tr("Copy"))) { 54 | var str = Console.lastTraces.join("\n"); 55 | Krom.copyToClipboard(str); 56 | } 57 | #end 58 | 59 | ui.endSticky(); 60 | 61 | for (t in Console.lastTraces) { 62 | ui.text(t); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Assets/plugins/hello_node.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.NodesMaterial.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, 29 | min: 0.0, 30 | max: 5.0 31 | } 32 | ], 33 | outputs: [ 34 | { 35 | id: 0, 36 | node_id: 0, 37 | name: "Color", 38 | type: "RGBA", 39 | color: 0xffc7c729, 40 | default_value: new Float32Array([0.8, 0.8, 0.8, 1.0]) 41 | }, 42 | { 43 | id: 1, 44 | node_id: 0, 45 | name: "Fac", 46 | type: "VALUE", 47 | color: 0xffa1a1a1, 48 | default_value: 1.0 49 | } 50 | ], 51 | buttons: [] 52 | } 53 | ]; 54 | arm.NodesMaterial.list.push(nodes); 55 | 56 | // Node shader 57 | arm.MaterialParser.customNodes.set(nodeType, function(node, socket) { 58 | let frag = arm.MaterialParser.frag; 59 | let scale = arm.MaterialParser.parse_value_input(node.inputs[0]); 60 | let my_out = arm.MaterialParser.node_name(node) + "_out"; 61 | 62 | frag.write(` 63 | float ${my_out} = cos(sin(texCoord.x * 200.0 * ${scale}) + cos(texCoord.y * 200.0 * ${scale})); 64 | `); 65 | 66 | if (socket.name == "Color") { 67 | return `vec3(${my_out}, ${my_out}, ${my_out})`; 68 | } 69 | else if (socket.name == "Fac") { 70 | return my_out; 71 | } 72 | }); 73 | 74 | // Cleanup 75 | plugin.delete = function() { 76 | arm.MaterialParser.customNodes.delete(nodeType); 77 | arm.NodesMaterial.list.splice(arm.NodesMaterial.list.indexOf(nodes), 1); 78 | categories.splice(categories.indexOf(categoryName), 1); 79 | }; 80 | -------------------------------------------------------------------------------- /Assets/keymap_presets/blender.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_paint": "left", 3 | "action_rotate": "middle", 4 | "action_pan": "shift+middle", 5 | "action_zoom": "ctrl+middle", 6 | "rotate_light": "shift+right", 7 | "rotate_envmap": "ctrl+right", 8 | "set_clone_source": "alt", 9 | "stencil_transform": "ctrl", 10 | "stencil_hide": "z", 11 | "decal_mask": "ctrl", 12 | "select_material": "shift+number", 13 | "select_layer": "alt+number", 14 | "brush_radius": "f", 15 | "brush_radius_decrease": "[", 16 | "brush_radius_increase": "]", 17 | "brush_opacity": "shift+f", 18 | "brush_angle": "alt+f", 19 | "brush_ruler": "shift", 20 | "file_new": "ctrl+n", 21 | "file_open": "ctrl+o", 22 | "file_open_recent": "ctrl+shift+o", 23 | "file_save": "ctrl+s", 24 | "file_save_as": "ctrl+shift+s", 25 | "file_reimport_mesh": "ctrl+r", 26 | "file_reimport_textures": "ctrl+shift+r", 27 | "file_import_assets": "ctrl+i", 28 | "file_export_textures": "ctrl+e", 29 | "file_export_textures_as": "ctrl+shift+e", 30 | "edit_undo": "ctrl+z", 31 | "edit_redo": "ctrl+shift+z", 32 | "edit_prefs": "ctrl+k", 33 | "view_reset": "0", 34 | "view_front": "1", 35 | "view_back": "ctrl+1", 36 | "view_right": "3", 37 | "view_left": "ctrl+3", 38 | "view_top": "7", 39 | "view_bottom": "ctrl+7", 40 | "view_camera_type": "5", 41 | "view_orbit_left": "4", 42 | "view_orbit_right": "6", 43 | "view_orbit_up": "8", 44 | "view_orbit_down": "2", 45 | "view_orbit_opposite": "9", 46 | "view_zoom_in": "", 47 | "view_zoom_out": "", 48 | "view_distract_free": "f11", 49 | "viewport_mode": "ctrl+m", 50 | "tool_brush": "b", 51 | "tool_eraser": "e", 52 | "tool_fill": "g", 53 | "tool_decal": "d", 54 | "tool_text": "t", 55 | "tool_clone": "l", 56 | "tool_blur": "u", 57 | "tool_particle": "p", 58 | "tool_colorid": "c", 59 | "tool_picker": "v", 60 | "swap_brush_eraser": "", 61 | "toggle_2d_view": "shift+tab", 62 | "toggle_node_editor": "tab", 63 | "toggle_browser": "`", 64 | "node_search": "space" 65 | } 66 | -------------------------------------------------------------------------------- /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 | "stencil_transform": "ctrl", 10 | "stencil_hide": "z", 11 | "decal_mask": "ctrl", 12 | "select_material": "shift+number", 13 | "select_layer": "alt+number", 14 | "brush_radius": "f", 15 | "brush_radius_decrease": "[", 16 | "brush_radius_increase": "]", 17 | "brush_opacity": "shift+f", 18 | "brush_angle": "alt+f", 19 | "brush_ruler": "shift", 20 | "file_new": "ctrl+n", 21 | "file_open": "ctrl+o", 22 | "file_open_recent": "ctrl+shift+o", 23 | "file_save": "ctrl+s", 24 | "file_save_as": "ctrl+shift+s", 25 | "file_reimport_mesh": "ctrl+r", 26 | "file_reimport_textures": "ctrl+shift+r", 27 | "file_import_assets": "ctrl+i", 28 | "file_export_textures": "ctrl+e", 29 | "file_export_textures_as": "ctrl+shift+e", 30 | "edit_undo": "ctrl+z", 31 | "edit_redo": "ctrl+shift+z", 32 | "edit_prefs": "ctrl+k", 33 | "view_reset": "0", 34 | "view_front": "1", 35 | "view_back": "ctrl+1", 36 | "view_right": "3", 37 | "view_left": "ctrl+3", 38 | "view_top": "7", 39 | "view_bottom": "ctrl+7", 40 | "view_camera_type": "5", 41 | "view_orbit_left": "4", 42 | "view_orbit_right": "6", 43 | "view_orbit_up": "8", 44 | "view_orbit_down": "2", 45 | "view_orbit_opposite": "9", 46 | "view_zoom_in": "", 47 | "view_zoom_out": "", 48 | "view_distract_free": "f11", 49 | "viewport_mode": "ctrl+m", 50 | "tool_brush": "b", 51 | "tool_eraser": "e", 52 | "tool_fill": "g", 53 | "tool_decal": "d", 54 | "tool_text": "t", 55 | "tool_clone": "l", 56 | "tool_blur": "u", 57 | "tool_particle": "p", 58 | "tool_colorid": "c", 59 | "tool_picker": "v", 60 | "swap_brush_eraser": "", 61 | "toggle_2d_view": "shift+tab", 62 | "toggle_node_editor": "tab", 63 | "toggle_browser": "`", 64 | "node_search": "space" 65 | } 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Assets/plugins/dev/profiler.js: -------------------------------------------------------------------------------- 1 | 2 | let plugin = new arm.Plugin(); 3 | 4 | let h1 = new zui.Handle(); 5 | let first = true; 6 | let lastTime = 0.0; 7 | let frameTime = 0.0; 8 | let totalTime = 0.0; 9 | let frames = 0; 10 | let frameTimeAvg = 0.0; 11 | let graph = null; 12 | let graphA = null; 13 | let graphB = null; 14 | let lrow = [1/2, 1/2]; 15 | 16 | plugin.drawUI = function(ui) { 17 | if (ui.panel(h1, "Profiler")) { 18 | let avg = Math.round(frameTimeAvg * 10000) / 10; 19 | let fpsAvg = avg > 0 ? Math.round(1000 / avg) : 0; 20 | 21 | if (first) { 22 | first = false; 23 | iron.App.notifyOnRender2D(tick); 24 | } 25 | 26 | if (graph != null) ui.image(graph); 27 | ui.indent(); 28 | ui.row(lrow); 29 | ui.text('Frame'); 30 | ui.text(`${avg} ms / ${fpsAvg} fps`, 2); // Align.Right 31 | ui.unindent(); 32 | } 33 | } 34 | 35 | let updateGraph = function() { 36 | if (graph === null) { 37 | graphA = core.Image.createRenderTarget(280, 33); 38 | graphB = core.Image.createRenderTarget(280, 33); 39 | graph = graphA; 40 | } 41 | else graph = graph === graphA ? graphB : graphA; 42 | let graphPrev = graph === graphA ? graphB : graphA; 43 | 44 | let g2 = graph.get_g2(); 45 | g2.begin(true, 0x00000000); 46 | g2.set_color(0xffffffff); 47 | g2.drawImage(graphPrev, -3, 0); 48 | 49 | let avg = Math.round(frameTimeAvg * 1000); 50 | let miss = avg > 16.7 ? (avg - 16.7) / 16.7 : 0.0; 51 | g2.set_color(core.colorFromFloats(miss, 1 - miss, 0, 1.0)); 52 | g2.fillRect(280 - 3, 33 - avg, 3, avg); 53 | 54 | g2.set_color(0xff000000); 55 | g2.fillRect(280 - 3, 33 - 17, 3, 1); 56 | 57 | g2.end(); 58 | } 59 | 60 | let tick = function(g2) { 61 | totalTime += frameTime; 62 | frames++; 63 | if (totalTime > 1.0) { 64 | arm.UISidebar.inst.hwnd0.redraws = 1; 65 | frameTimeAvg = totalTime / frames; 66 | totalTime = 0; 67 | frames = 0; 68 | g2.end(); 69 | updateGraph(); 70 | g2.begin(false); 71 | } 72 | frameTime = core.Scheduler.realTime() - lastTime; 73 | lastTime = core.Scheduler.realTime(); 74 | } 75 | -------------------------------------------------------------------------------- /Sources/arm/data/MaterialSlot.hx: -------------------------------------------------------------------------------- 1 | package arm.data; 2 | 3 | import haxe.Json; 4 | import kha.Image; 5 | import kha.Blob; 6 | import zui.Nodes; 7 | import iron.data.MaterialData; 8 | import iron.data.Data; 9 | import arm.util.RenderUtil; 10 | 11 | class MaterialSlot { 12 | public var nodes = new Nodes(); 13 | public var canvas: TNodeCanvas; 14 | public var image: Image = null; 15 | public var imageIcon: Image = null; 16 | public var previewReady = false; 17 | public var data: MaterialData; 18 | public var id = 0; 19 | static var defaultCanvas: Blob = null; 20 | 21 | public var paintBase = true; 22 | public var paintOpac = true; 23 | public var paintOcc = true; 24 | public var paintRough = true; 25 | public var paintMet = true; 26 | public var paintNor = true; 27 | public var paintHeight = true; 28 | public var paintEmis = true; 29 | public var paintSubs = true; 30 | 31 | public function new(m: MaterialData = null, c: TNodeCanvas = null) { 32 | for (mat in Project.materials) if (mat.id >= id) id = mat.id + 1; 33 | data = m; 34 | 35 | var w = RenderUtil.matPreviewSize; 36 | var wIcon = 50; 37 | image = Image.createRenderTarget(w, w); 38 | imageIcon = Image.createRenderTarget(wIcon, wIcon); 39 | 40 | if (c == null) { 41 | if (defaultCanvas == null) { // Synchronous 42 | Data.getBlob("default_material.arm", function(b: Blob) { 43 | defaultCanvas = b; 44 | }); 45 | } 46 | canvas = iron.system.ArmPack.decode(defaultCanvas.toBytes()); 47 | canvas.name = "Material " + (id + 1); 48 | } 49 | else { 50 | canvas = c; 51 | } 52 | } 53 | 54 | public function unload() { 55 | function _next() { 56 | image.unload(); 57 | imageIcon.unload(); 58 | } 59 | App.notifyOnNextFrame(_next); 60 | } 61 | 62 | public function delete() { 63 | unload(); 64 | var mpos = Project.materials.indexOf(this); 65 | Project.materials.remove(this); 66 | if (Project.materials.length > 0) { 67 | Context.setMaterial(Project.materials[mpos > 0 ? mpos - 1 : 0]); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Shaders/mask_merge.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | uniform sampler2D tex0; 3 | uniform sampler2D texa; 4 | uniform float opac; 5 | uniform int blending; 6 | in vec2 texCoord; 7 | out vec4 FragColor; 8 | void main() { 9 | float col0 = textureLod(tex0, texCoord, 0).r; 10 | float cola = textureLod(texa, texCoord, 0).r; 11 | float str = opac; 12 | float outColor = 0.0; 13 | if (blending == 0) { // Mix 14 | outColor = mix(cola, col0, str); 15 | } 16 | else if (blending == 1) { // Darken 17 | outColor = mix(cola, min(cola, col0), str); 18 | } 19 | else if (blending == 2) { // Multiply 20 | outColor = mix(cola, cola * col0, str); 21 | } 22 | else if (blending == 3) { // Burn 23 | outColor = mix(cola, 1.0 - (1.0 - cola) / col0, str); 24 | } 25 | else if (blending == 4) { // Lighten 26 | outColor = max(cola, col0 * str); 27 | } 28 | else if (blending == 5) { // Screen 29 | outColor = (1.0 - ((1.0 - str) + str * (1.0 - col0)) * (1.0 - cola)); 30 | } 31 | else if (blending == 6) { // Dodge 32 | outColor = mix(cola, cola / (1.0 - col0), str); 33 | } 34 | else if (blending == 7) { // Add 35 | outColor = mix(cola, cola + col0, str); 36 | } 37 | else if (blending == 8) { // Overlay 38 | outColor = mix(cola, cola < 0.5 ? 2.0 * cola * col0 : 1.0 - 2.0 * (1.0 - cola) * (1.0 - col0), str); 39 | } 40 | else if (blending == 9) { // Soft Light 41 | outColor = ((1.0 - str) * cola + str * ((1.0 - cola) * col0 * cola + cola * (1.0 - (1.0 - col0) * (1.0 - cola)))); 42 | } 43 | else if (blending == 10) { // Linear Light 44 | outColor = (cola + str * (2.0 * (col0 - 0.5))); 45 | } 46 | else if (blending == 11) { // Difference 47 | outColor = mix(cola, abs(cola - col0), str); 48 | } 49 | else if (blending == 12) { // Subtract 50 | outColor = mix(cola, cola - col0, str); 51 | } 52 | else if (blending == 13) { // Divide 53 | outColor = (1.0 - str) * cola + str * cola / col0; 54 | } 55 | else { // Hue, Saturation, Color, Value 56 | outColor = mix(cola, col0, str); 57 | } 58 | FragColor = vec4(outColor, outColor, outColor, 1.0); 59 | } 60 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Assets/plugins/dev/converter.js: -------------------------------------------------------------------------------- 1 | 2 | let plugin = new arm.Plugin(); 3 | 4 | let h1 = new zui.Handle(); 5 | 6 | plugin.drawUI = function(ui) { 7 | if (ui.panel(h1, "Converter")) { 8 | ui.row([1/2, 1/2]); 9 | if (ui.button(".arm to .json")) { 10 | arm.UIFiles.show("arm", false, true, function(path) { 11 | iron.Data.getBlob(path, function(b) { 12 | let parsed = iron.ArmPack.decode(b.bytes); 13 | let out = core.Bytes.ofString(core.Json.stringify(parsed, function(key, value) { 14 | if (core.StdIs(value, Float32Array)) { 15 | let ar = Array.from(value); 16 | ar.unshift(0); // Annotate array type 17 | return ar; 18 | } 19 | else if (core.StdIs(value, Uint32Array)) { 20 | let ar = Array.from(value); 21 | ar.unshift(1); 22 | return ar; 23 | } 24 | else if (core.StdIs(value, Int16Array)) { 25 | let ar = Array.from(value); 26 | ar.unshift(2); 27 | return ar; 28 | } 29 | return value; 30 | }, " ")).b.bufferValue; 31 | Krom.fileSaveBytes(path.substr(0, path.length - 3) + "json", out); 32 | }); 33 | }); 34 | } 35 | if (ui.button(".json to .arm")) { 36 | arm.UIFiles.show("json", false, true, function(path) { 37 | iron.Data.getBlob(path, function(b) { 38 | let parsed = core.Json.parse(b.toString()); 39 | function iterate(d) { 40 | for (const n of core.ReflectFields(d)) { 41 | let v = core.ReflectField(d, n); 42 | if (core.StdIs(v, Array)) { 43 | if (core.StdIs(v[0], Number)) { 44 | console.log(n); 45 | let ar = null; 46 | if (v[0] === 0) ar = new Float32Array(v.length - 1); 47 | else if (v[0] === 1) ar = new Uint32Array(v.length - 1); 48 | else if (v[0] === 2) ar = new Int16Array(v.length - 1); 49 | for (let i = 0; i < v.length - 1; ++i) ar[i] = v[i + 1]; 50 | core.ReflectSetField(d, n, ar); 51 | } 52 | else for (const e of v) if (typeof e === 'object') iterate(e); 53 | } 54 | else if (typeof v === 'object') iterate(v); 55 | } 56 | } 57 | iterate(parsed); 58 | let out = iron.ArmPack.encode(parsed); 59 | Krom.fileSaveBytes(path.substr(0, path.length - 4) + "arm", out.b.bufferValue, out.length + 1); 60 | }); 61 | }); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Assets/plugins/import_stl.js: -------------------------------------------------------------------------------- 1 | 2 | let import_stl = function(path, done) { 3 | iron.Data.getBlob(path, function(b) { 4 | let input = new core.BytesInput(b.bytes); 5 | let header = input.read(80); 6 | if (header.getString(0, 5) == "solid") { 7 | return; // ascii not supported 8 | } 9 | let faces = input.readInt32(); 10 | let posTemp = new Float32Array(faces * 9); 11 | let norTemp = new Float32Array(faces * 3); 12 | for (let i = 0; i < faces; ++i) { 13 | let i3 = i * 3; 14 | norTemp[i3 ] = input.readFloat(); 15 | norTemp[i3 + 1] = input.readFloat(); 16 | norTemp[i3 + 2] = input.readFloat(); 17 | let i9 = i * 9; 18 | posTemp[i9 ] = input.readFloat(); 19 | posTemp[i9 + 1] = input.readFloat(); 20 | posTemp[i9 + 2] = input.readFloat(); 21 | posTemp[i9 + 3] = input.readFloat(); 22 | posTemp[i9 + 4] = input.readFloat(); 23 | posTemp[i9 + 5] = input.readFloat(); 24 | posTemp[i9 + 6] = input.readFloat(); 25 | posTemp[i9 + 7] = input.readFloat(); 26 | posTemp[i9 + 8] = input.readFloat(); 27 | input.readInt16(); // attribute 28 | } 29 | 30 | let scalePos = 0.0; 31 | for (let i = 0; i < posTemp.length; ++i) { 32 | let f = Math.abs(posTemp[i]); 33 | if (scalePos < f) scalePos = f; 34 | } 35 | let inv = 32767 * (1 / scalePos); 36 | 37 | let verts = posTemp.length / 3; 38 | let posa = new Int16Array(verts * 4); 39 | let nora = new Int16Array(verts * 2); 40 | let inda = new Uint32Array(verts); 41 | for (let i = 0; i < verts; ++i) { 42 | posa[i * 4 ] = posTemp[i * 3 ] * inv; 43 | posa[i * 4 + 1] = -posTemp[i * 3 + 2] * inv; 44 | posa[i * 4 + 2] = posTemp[i * 3 + 1] * inv; 45 | nora[i * 2 ] = norTemp[i ] * 32767; 46 | nora[i * 2 + 1] = -norTemp[i + 2] * 32767; 47 | posa[i * 4 + 3] = norTemp[i + 1] * 32767; 48 | inda[i] = i; 49 | } 50 | 51 | let name = path.split("\\").pop().split("/").pop().split(".").shift(); 52 | done({ 53 | name: name, 54 | posa: posa, 55 | nora: nora, 56 | inda: inda, 57 | scale_pos: scalePos, 58 | scale_tex: 1.0 59 | }); 60 | 61 | iron.Data.deleteBlob(path); 62 | }); 63 | } 64 | 65 | let plugin = new arm.Plugin(); 66 | let formats = arm.Path.meshFormats; 67 | let importers = arm.Path.meshImporters; 68 | formats.push("stl"); 69 | importers.h["stl"] = import_stl; 70 | 71 | plugin.delete = function() { 72 | formats.splice(formats.indexOf("stl"), 1); 73 | importers.h["stl"] = null; 74 | }; 75 | -------------------------------------------------------------------------------- /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): Dynamic { 14 | 15 | var v1: Float = inputs[0].get(); 16 | var v2: Float = inputs[1].get(); 17 | var f = 0.0; 18 | switch (operation) { 19 | case "Add": 20 | f = v1 + v2; 21 | case "Multiply": 22 | f = v1 * v2; 23 | case "Sine": 24 | f = Math.sin(v1); 25 | case "Cosine": 26 | f = Math.cos(v1); 27 | case "Max": 28 | f = Math.max(v1, v2); 29 | case "Min": 30 | f = Math.min(v1, v2); 31 | case "Absolute": 32 | f = Math.abs(v1); 33 | case "Subtract": 34 | f = v1 - v2; 35 | case "Divide": 36 | f = v1 / (v2 == 0.0 ? 0.000001 : v2); 37 | case "Tangent": 38 | f = Math.tan(v1); 39 | case "Arcsine": 40 | f = Math.asin(v1); 41 | case "Arccosine": 42 | f = Math.acos(v1); 43 | case "Arctangent": 44 | f = Math.atan(v1); 45 | case "Arctan2": 46 | f = Math.atan2(v2, v1); 47 | case "Power": 48 | f = Math.pow(v1, v2); 49 | case "Logarithm": 50 | f = Math.log(v1); 51 | case "Round": 52 | f = Math.round(v1); 53 | case "Floor": 54 | f = Math.floor(v1); 55 | case "Ceil": 56 | f = Math.ceil(v1); 57 | case "Truncate": 58 | f = Math.ffloor(v1); 59 | case "Fraction": 60 | f = v1 - Math.floor(v1); 61 | case "Less Than": 62 | f = v1 < v2 ? 1.0 : 0.0; 63 | case "Greater Than": 64 | f = v1 > v2 ? 1.0 : 0.0; 65 | case "Modulo": 66 | f = v1 % v2; 67 | case "Snap": 68 | f = Math.floor(v1 / v2) * v2; 69 | case "Square Root": 70 | f = Math.sqrt(v1); 71 | case "Inverse Square Root": 72 | f = 1.0 / Math.sqrt(v1); 73 | case "Exponent": 74 | f = Math.exp(v1); 75 | case "Sign": 76 | f = v1 > 0 ? 1.0 : (v1 < 0 ? -1.0 : 0); 77 | case "Ping-Pong": 78 | f = (v2 != 0.0) ? v2 - Math.abs((Math.abs(v1) % (2 * v2)) - v2) : 0.0; 79 | case "Hyperbolic Sine": 80 | f = (Math.exp(v1) - Math.exp(-v1)) / 2.0; 81 | case "Hyperbolic Cosine": 82 | f = (Math.exp(v1) + Math.exp(-v1)) / 2.0; 83 | case "Hyperbolic Tangent": 84 | f = 1.0 - (2.0 / (Math.exp(2 * v1) + 1)); 85 | case "To Radians": 86 | f = v1 / 180.0 * Math.PI; 87 | case "To Degrees": 88 | f = v1 / Math.PI * 180.0; 89 | } 90 | 91 | if (use_clamp) f = f < 0.0 ? 0.0 : (f > 1.0 ? 1.0 : f); 92 | 93 | return f; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /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.ui.UIHeader; 8 | import arm.Project; 9 | 10 | class ImportAsset { 11 | 12 | public static function run(path: String, dropX = -1.0, dropY = -1.0, showBox = true, hdrAsEnvmap = true, done: Void->Void = null) { 13 | 14 | if (path.startsWith("cloud")) { 15 | function doCacheCloud() { 16 | File.cacheCloud(path, function(abs: String) { 17 | if (abs == null) return; 18 | run(abs, dropX, dropY, showBox, hdrAsEnvmap, done); 19 | }); 20 | } 21 | 22 | #if (krom_android || krom_ios) 23 | arm.App.notifyOnNextFrame(function() { 24 | Console.toast(tr("Downloading")); 25 | arm.App.notifyOnNextFrame(doCacheCloud); 26 | }); 27 | #else 28 | doCacheCloud(); 29 | #end 30 | 31 | return; 32 | } 33 | 34 | if (Path.isMesh(path)) { 35 | showBox ? Project.importMeshBox(path) : ImportMesh.run(path); 36 | if (dropX > 0) UIBox.clickToHide = false; // Prevent closing when going back to window after drag and drop 37 | } 38 | else if (Path.isTexture(path)) { 39 | ImportTexture.run(path, hdrAsEnvmap); 40 | // Place image node 41 | var x0 = UINodes.inst.wx; 42 | var x1 = UINodes.inst.wx + UINodes.inst.ww; 43 | if (UINodes.inst.show && dropX > x0 && dropX < x1) { 44 | var assetIndex = 0; 45 | for (i in 0...Project.assets.length) { 46 | if (Project.assets[i].file == path) { 47 | assetIndex = i; 48 | break; 49 | } 50 | } 51 | UINodes.inst.acceptAssetDrag(assetIndex); 52 | UINodes.inst.getNodes().nodesDrag = false; 53 | UINodes.inst.hwnd.redraws = 2; 54 | } 55 | if (Context.tool == ToolColorId && Project.assetNames.length == 1) { 56 | UIHeader.inst.headerHandle.redraws = 2; 57 | Context.ddirty = 2; 58 | } 59 | } 60 | else if (Path.isFont(path)) { 61 | ImportFont.run(path); 62 | } 63 | else if (Path.isProject(path)) { 64 | ImportArm.runProject(path); 65 | } 66 | else if (Path.isPlugin(path)) { 67 | ImportPlugin.run(path); 68 | } 69 | else if (Path.isFolder(path)) { 70 | ImportFolder.run(path); 71 | } 72 | else if (Path.isGimpColorPalette(path)) { 73 | ImportGpl.run(path, false); 74 | } 75 | else { 76 | if (Context.enableImportPlugin(path)) { 77 | run(path, dropX, dropY, showBox); 78 | } 79 | else { 80 | Console.error(Strings.error1()); 81 | } 82 | } 83 | 84 | if (done != null) done(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /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 stencil_transform: String; 12 | public var stencil_hide: String; 13 | public var decal_mask: String; 14 | public var select_material: String; 15 | public var select_layer: String; 16 | public var brush_radius: String; 17 | public var brush_radius_decrease: String; 18 | public var brush_radius_increase: String; 19 | public var brush_opacity: String; 20 | public var brush_angle: String; 21 | public var brush_ruler: String; 22 | public var file_new: String; 23 | public var file_open: String; 24 | public var file_open_recent: String; 25 | public var file_save: String; 26 | public var file_save_as: String; 27 | public var file_reimport_mesh: String; 28 | public var file_reimport_textures: String; 29 | public var file_import_assets: String; 30 | public var file_export_textures: String; 31 | public var file_export_textures_as: String; 32 | public var edit_undo: String; 33 | public var edit_redo: String; 34 | public var edit_prefs: String; 35 | public var view_reset: String; 36 | public var view_front: String; 37 | public var view_back: String; 38 | public var view_right: String; 39 | public var view_left: String; 40 | public var view_top: String; 41 | public var view_bottom: String; 42 | public var view_camera_type: String; 43 | public var view_orbit_left: String; 44 | public var view_orbit_right: String; 45 | public var view_orbit_up: String; 46 | public var view_orbit_down: String; 47 | public var view_orbit_opposite: String; 48 | public var view_zoom_in: String; 49 | public var view_zoom_out: String; 50 | public var view_distract_free: String; 51 | public var viewport_mode: String; 52 | public var tool_brush: String; 53 | public var tool_eraser: String; 54 | public var tool_fill: String; 55 | public var tool_decal: String; 56 | public var tool_text: String; 57 | public var tool_clone: String; 58 | public var tool_blur: String; 59 | public var tool_particle: String; 60 | public var tool_colorid: String; 61 | public var tool_picker: String; 62 | public var swap_brush_eraser: String; 63 | public var toggle_2d_view: String; 64 | public var toggle_node_editor: String; 65 | public var toggle_browser: String; 66 | public var node_search: String; 67 | } 68 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeDiscard.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | import arm.ui.UISidebar; 4 | import arm.shader.NodeShader; 5 | 6 | class MakeDiscard { 7 | 8 | public static function colorId(vert: NodeShader, frag: NodeShader) { 9 | frag.add_uniform('sampler2D texpaint_colorid'); // 1x1 picker 10 | frag.add_uniform('sampler2D texcolorid', '_texcolorid'); // color map 11 | frag.write('vec3 colorid_c1 = texelFetch(texpaint_colorid, ivec2(0, 0), 0).rgb;'); 12 | frag.write('vec3 colorid_c2 = textureLod(texcolorid, texCoordPick, 0).rgb;'); 13 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal) 14 | frag.write('if (any(colorid_c1 != colorid_c2)) discard;'); 15 | #else 16 | frag.write('if (colorid_c1 != colorid_c2) discard;'); 17 | #end 18 | } 19 | 20 | public static function face(vert: NodeShader, frag: NodeShader) { 21 | frag.add_uniform('sampler2D gbuffer2'); 22 | frag.add_uniform('sampler2D textrianglemap', '_textrianglemap'); 23 | frag.add_uniform('vec2 textrianglemapSize', '_texpaintSize'); 24 | frag.add_uniform('vec2 gbufferSize', '_gbufferSize'); 25 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 26 | frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, inp.y * gbufferSize.y), 0).ba;'); 27 | #else 28 | frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, (1.0 - inp.y) * gbufferSize.y), 0).ba;'); 29 | #end 30 | frag.write('vec4 face_c1 = texelFetch(textrianglemap, ivec2(texCoordInp * textrianglemapSize), 0);'); 31 | frag.write('vec4 face_c2 = textureLod(textrianglemap, texCoordPick, 0);'); 32 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal) 33 | frag.write('if (any(face_c1 != face_c2)) discard;'); 34 | #else 35 | frag.write('if (face_c1 != face_c2) discard;'); 36 | #end 37 | } 38 | 39 | public static function uvIsland(vert: NodeShader, frag: NodeShader) { 40 | frag.add_uniform('sampler2D texuvislandmap', '_texuvislandmap'); 41 | frag.write('if (textureLod(texuvislandmap, texCoordPick, 0).r == 0.0) discard;'); 42 | } 43 | 44 | public static function materialId(vert: NodeShader, frag: NodeShader) { 45 | frag.wvpposition = true; 46 | frag.write('vec2 picker_sample_tc = vec2(wvpposition.x / wvpposition.w, wvpposition.y / wvpposition.w) * 0.5 + 0.5;'); 47 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 48 | frag.write('picker_sample_tc.y = 1.0 - picker_sample_tc.y;'); 49 | #end 50 | frag.add_uniform('sampler2D texpaint_nor_undo', '_texpaint_nor_undo'); 51 | var matid = Context.materialIdPicked / 255; 52 | frag.write('if ($matid != textureLod(texpaint_nor_undo, picker_sample_tc, 0.0).a) discard;'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeColorIdPicker.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | import arm.shader.ShaderFunctions; 4 | import arm.shader.NodeShader; 5 | import arm.Enums; 6 | 7 | class MakeColorIdPicker { 8 | 9 | public static function run(vert: NodeShader, frag: NodeShader) { 10 | // Mangle vertices to form full screen triangle 11 | vert.write('gl_Position = vec4(-1.0 + float((gl_VertexID & 1) << 2), -1.0 + float((gl_VertexID & 2) << 1), 0.0, 1.0);'); 12 | 13 | frag.add_uniform('sampler2D gbuffer2'); 14 | frag.add_uniform('vec2 gbufferSize', '_gbufferSize'); 15 | frag.add_uniform('vec4 inp', '_inputBrush'); 16 | frag.write('vec4 inpLocal = inp;'); // TODO: spirv workaround 17 | frag.write('vec2 gbufferSizeLocal = gbufferSize;'); // TODO: spirv workaround 18 | 19 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 20 | frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inpLocal.x * gbufferSizeLocal.x, inpLocal.y * gbufferSizeLocal.y), 0).ba;'); 21 | #else 22 | frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inpLocal.x * gbufferSizeLocal.x, (1.0 - inpLocal.y) * gbufferSizeLocal.y), 0).ba;'); 23 | #end 24 | 25 | if (Context.tool == ToolColorId) { 26 | frag.add_out('vec4 fragColor'); 27 | frag.add_uniform('sampler2D texcolorid', '_texcolorid'); 28 | frag.write('vec3 idcol = textureLod(texcolorid, texCoordInp, 0.0).rgb;'); 29 | frag.write('fragColor = vec4(idcol, 1.0);'); 30 | } 31 | else if (Context.tool == ToolPicker) { 32 | if (Context.pickPosNorTex) { 33 | frag.add_out('vec4 fragColor[2]'); 34 | frag.add_uniform('sampler2D gbufferD'); 35 | frag.add_uniform('mat4 invVP', '_inverseViewProjectionMatrix'); 36 | frag.add_function(ShaderFunctions.str_get_pos_from_depth); 37 | frag.add_function(ShaderFunctions.str_get_nor_from_depth); 38 | frag.write('fragColor[0] = vec4(get_pos_from_depth(vec2(inpLocal.x, 1.0 - inpLocal.y), invVP, texturePass(gbufferD)), texCoordInp.x);'); 39 | frag.write('fragColor[1] = vec4(get_nor_from_depth(fragColor[0].rgb, vec2(inpLocal.x, 1.0 - inpLocal.y), invVP, vec2(1.0, 1.0) / gbufferSize, texturePass(gbufferD)), texCoordInp.y);'); 40 | } 41 | else { 42 | frag.add_out('vec4 fragColor[4]'); 43 | frag.add_uniform('sampler2D texpaint'); 44 | frag.add_uniform('sampler2D texpaint_nor'); 45 | frag.add_uniform('sampler2D texpaint_pack'); 46 | frag.write('fragColor[0] = textureLod(texpaint, texCoordInp, 0.0);'); 47 | frag.write('fragColor[1] = textureLod(texpaint_nor, texCoordInp, 0.0);'); 48 | frag.write('fragColor[2] = textureLod(texpaint_pack, texCoordInp, 0.0);'); 49 | frag.write('fragColor[3].rg = texCoordInp.xy;'); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /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; // ArmorPaint 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_hardness: Null; 38 | @:optional var pressure_angle: Null; 39 | @:optional var pressure_opacity: Null; 40 | @:optional var pressure_sensitivity: Null; 41 | @:optional var material_live: Null; 42 | @:optional var brush_live: Null; 43 | @:optional var brush_3d: Null; 44 | @:optional var brush_depth_reject: Null; 45 | @:optional var brush_angle_reject: Null; 46 | @:optional var wrap_mouse: Null; 47 | @:optional var node_preview: Null; 48 | @:optional var camera_pan_speed: Null; 49 | @:optional var camera_zoom_speed: Null; 50 | @:optional var camera_rotation_speed: Null; 51 | @:optional var zoom_direction: Null; 52 | @:optional var displace_strength: Null; 53 | @:optional var show_asset_names: Null; 54 | @:optional var touch_ui: Null; 55 | @:optional var layout: Array; 56 | @:optional var workspace: Null; 57 | @:optional var layer_res: Null; 58 | @:optional var dilate: Null; 59 | @:optional var dilate_radius: Null; 60 | @:optional var server: String; 61 | } 62 | -------------------------------------------------------------------------------- /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 brush_nodes: Array; 9 | @:optional public var brush_icons: Array; 10 | @:optional public var material_nodes: Array; 11 | @:optional public var material_groups: Array; 12 | @:optional public var material_icons: Array; 13 | @:optional public var assets: Array; // texture_assets 14 | @:optional public var font_assets: Array; 15 | @:optional public var layer_datas: Array; 16 | @:optional public var mesh_datas: Array; 17 | @:optional public var mesh_assets: Array; 18 | @:optional public var mesh_icons: Array; 19 | @:optional public var atlas_objects: Array; 20 | @:optional public var atlas_names: Array; 21 | @:optional public var swatches: Array; 22 | @:optional public var is_bgra: Null; // Swapped red and blue channels for layer textures 23 | @:optional public var packed_assets: Array; 24 | @:optional public var envmap: String; // Asset name 25 | @:optional public var envmap_strength: Null; 26 | @:optional public var camera_world: kha.arrays.Float32Array; 27 | @:optional public var camera_origin: kha.arrays.Float32Array; 28 | @:optional public var camera_fov: Null; 29 | } 30 | 31 | typedef TLayerData = { 32 | public var name: String; 33 | public var res: Int; // Width pixels 34 | public var bpp: Int; // Bits per pixel 35 | public var texpaint: haxe.io.Bytes; 36 | public var texpaint_nor: haxe.io.Bytes; 37 | public var texpaint_pack: haxe.io.Bytes; 38 | public var uv_scale: Float; 39 | public var uv_rot: Float; 40 | public var uv_type: Int; 41 | public var decal_mat: kha.arrays.Float32Array; 42 | public var opacity_mask: Float; 43 | public var fill_layer: Int; 44 | public var object_mask: Int; 45 | public var blending: Int; 46 | public var parent: Int; 47 | public var visible: Bool; 48 | public var paint_base: Bool; 49 | public var paint_opac: Bool; 50 | public var paint_occ: Bool; 51 | public var paint_rough: Bool; 52 | public var paint_met: Bool; 53 | public var paint_nor: Bool; 54 | public var paint_nor_blend: Bool; 55 | public var paint_height: Bool; 56 | public var paint_height_blend: Bool; 57 | public var paint_emis: Bool; 58 | public var paint_subs: Bool; 59 | } 60 | 61 | typedef TAsset = { 62 | public var id: Int; 63 | public var name: String; 64 | public var file: String; 65 | } 66 | 67 | typedef TPackedAsset = { 68 | public var name: String; 69 | public var bytes: haxe.io.Bytes; 70 | } 71 | 72 | typedef TSwatchColor = { 73 | public var base: kha.Color; 74 | public var opacity: Float; 75 | public var occlusion: Float; 76 | public var roughness: Float; 77 | public var metallic: Float; 78 | public var normal: kha.Color; 79 | public var emission: Float; 80 | public var height: Float; 81 | public var subsurface: Float; 82 | } 83 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeNodePreview.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | import zui.Nodes; 4 | import iron.data.SceneFormat; 5 | import arm.shader.MaterialParser; 6 | import arm.shader.NodeShader; 7 | import arm.shader.NodeShaderData; 8 | import arm.shader.NodeShaderContext; 9 | 10 | class MakeNodePreview { 11 | 12 | @:access(arm.shader.MaterialParser) 13 | public static function run(data: NodeShaderData, matcon: TMaterialContext, node: TNode, group: TNodeCanvas, parents: Array): NodeShaderContext { 14 | var context_id = "mesh"; 15 | var con_mesh: NodeShaderContext = data.add_context({ 16 | name: context_id, 17 | depth_write: false, 18 | compare_mode: "always", 19 | cull_mode: "clockwise", 20 | vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}, {name: "col", data: "short4norm"}], 21 | color_attachments: ["RGBA32"] 22 | }); 23 | 24 | con_mesh.allow_vcols = true; 25 | var vert = con_mesh.make_vert(); 26 | var frag = con_mesh.make_frag(); 27 | frag.ins = vert.outs; 28 | 29 | vert.write_attrib('gl_Position = vec4(pos.xy * 3.0, 0.0, 1.0);'); // Pos unpack 30 | vert.write_attrib('const vec2 madd = vec2(0.5, 0.5);'); 31 | vert.add_out('vec2 texCoord'); 32 | vert.write_attrib('texCoord = gl_Position.xy * madd + madd;'); 33 | #if (!kha_opengl) 34 | vert.write_attrib('texCoord.y = 1.0 - texCoord.y;'); 35 | #end 36 | 37 | MaterialParser.init(); 38 | MaterialParser.canvases = [Context.material.canvas]; 39 | MaterialParser.nodes = Context.material.canvas.nodes; 40 | MaterialParser.links = Context.material.canvas.links; 41 | if (group != null) { 42 | MaterialParser.push_group(group); 43 | MaterialParser.parents = parents; 44 | } 45 | var links = MaterialParser.links; 46 | var nodes = Context.material.nodes; 47 | 48 | var link: TNodeLink = { id: nodes.getLinkId(links), from_id: node.id, from_socket: Context.nodePreviewSocket, to_id: -1, to_socket: -1 }; 49 | links.push(link); 50 | 51 | MaterialParser.con = con_mesh; 52 | MaterialParser.vert = vert; 53 | MaterialParser.frag = frag; 54 | MaterialParser.curshader = frag; 55 | MaterialParser.matcon = matcon; 56 | 57 | MaterialParser.transform_color_space = false; 58 | var res = MaterialParser.write_result(link); 59 | MaterialParser.transform_color_space = true; 60 | var st = node.outputs[link.from_socket].type; 61 | if (st != "RGB" && st != "RGBA" && st != "VECTOR") { 62 | res = MaterialParser.to_vec3(res); 63 | } 64 | links.remove(link); 65 | 66 | frag.add_out('vec4 fragColor'); 67 | frag.write('vec3 basecol = $res;'); 68 | frag.write('fragColor = vec4(basecol.rgb, 1.0);'); 69 | 70 | // frag.ndcpos = true; 71 | // vert.add_out('vec4 ndc'); 72 | // vert.write_attrib('ndc = vec4(gl_Position.xyz * vec3(0.5, 0.5, 0.0) + vec3(0.5, 0.5, 0.0), 1.0);'); 73 | 74 | MaterialParser.finalize(con_mesh); 75 | 76 | con_mesh.data.shader_from_source = true; 77 | con_mesh.data.vertex_shader = vert.get(); 78 | con_mesh.data.fragment_shader = frag.get(); 79 | 80 | return con_mesh; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /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): Dynamic { 16 | var v1: Vec4 = inputs[0].get(); 17 | var v2: Vec4 = inputs[1].get(); 18 | v.setFrom(v1); 19 | var f = 0.0; 20 | switch (operation) { 21 | case "Add": 22 | v.add(v2); 23 | case "Subtract": 24 | v.sub(v2); 25 | case "Average": 26 | v.add(v2); 27 | v.x *= 0.5; 28 | v.y *= 0.5; 29 | v.z *= 0.5; 30 | case "Dot Product": 31 | f = v.dot(v2); 32 | v.set(f, f, f); 33 | case "Cross Product": 34 | v.cross(v2); 35 | case "Normalize": 36 | v.normalize(); 37 | case "Multiply": 38 | v.x *= v2.x; 39 | v.y *= v2.y; 40 | v.z *= v2.z; 41 | case "Divide": 42 | v.x /= v2.x == 0.0 ? 0.000001 : v2.x; 43 | v.y /= v2.y == 0.0 ? 0.000001 : v2.y; 44 | v.z /= v2.z == 0.0 ? 0.000001 : v2.z; 45 | case "Length": 46 | f = v.length(); 47 | v.set(f, f, f); 48 | case "Distance": 49 | f = v.distanceTo(v2); 50 | v.set(f, f, f); 51 | case "Project": 52 | v.setFrom(v2); 53 | v.mult(v1.dot(v2) / v2.dot(v2)); 54 | case "Reflect": 55 | var tmp = new Vec4(); 56 | tmp.setFrom(v2); 57 | tmp.normalize(); 58 | v.reflect(tmp); 59 | case "Scale": 60 | v.x *= v2.x; 61 | v.y *= v2.x; 62 | v.z *= v2.x; 63 | case "Absolute": 64 | v.x = Math.abs(v.x); 65 | v.y = Math.abs(v.y); 66 | v.z = Math.abs(v.z); 67 | case "Minimum": 68 | v.x = Math.min(v1.x, v2.x); 69 | v.y = Math.min(v1.y, v2.y); 70 | v.z = Math.min(v1.z, v2.z); 71 | case "Maximum": 72 | v.x = Math.max(v1.x, v2.x); 73 | v.y = Math.max(v1.y, v2.y); 74 | v.z = Math.max(v1.z, v2.z); 75 | case "Floor": 76 | v.x = Math.floor(v1.x); 77 | v.y = Math.floor(v1.y); 78 | v.z = Math.floor(v1.z); 79 | case "Ceil": 80 | v.x = Math.ceil(v1.x); 81 | v.y = Math.ceil(v1.y); 82 | v.z = Math.ceil(v1.z); 83 | case "Fraction": 84 | v.x = v1.x - Math.floor(v1.x); 85 | v.y = v1.y - Math.floor(v1.y); 86 | v.z = v1.z - Math.floor(v1.z); 87 | case "Modulo": 88 | v.x = v1.x % v2.x; 89 | v.y = v1.y % v2.y; 90 | v.z = v1.z % v2.z; 91 | case "Snap": 92 | v.x = Math.floor(v1.x / v2.x) * v2.x; 93 | v.y = Math.floor(v1.y / v2.y) * v2.y; 94 | v.z = Math.floor(v1.z / v2.z) * v2.z; 95 | case "Sine": 96 | v.x = Math.sin(v1.x); 97 | v.y = Math.sin(v1.y); 98 | v.z = Math.sin(v1.z); 99 | case "Cosine": 100 | v.x = Math.cos(v1.x); 101 | v.y = Math.cos(v1.y); 102 | v.z = Math.cos(v1.z); 103 | case "Tangent": 104 | v.x = Math.tan(v1.x); 105 | v.y = Math.tan(v1.y); 106 | v.z = Math.tan(v1.z); 107 | } 108 | 109 | if (from == 0) return v; 110 | else return f; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Assets/plugins/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.Context.layer; 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 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeVoxel.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | class MakeVoxel { 4 | 5 | #if rp_voxels 6 | public static function run(data: iron.data.ShaderData.ShaderContext) { 7 | var structure = new kha.graphics4.VertexStructure(); 8 | structure.add("pos", kha.graphics4.VertexData.Short4Norm); 9 | structure.add("nor", kha.graphics4.VertexData.Short2Norm); 10 | structure.add("tex", kha.graphics4.VertexData.Short2Norm); 11 | 12 | var pipeState = data.pipeState; 13 | pipeState.inputLayout = [structure]; 14 | data.raw.vertex_elements = [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}]; 15 | 16 | // #if arm_skin 17 | // var isMesh = Std.isOfType(Context.object, MeshObject); 18 | // var skin = isMesh && cast(Context.object, MeshObject).data.geom.bones != null; 19 | // if (skin) { 20 | // structure.add("bone", kha.graphics4.VertexData.Short4Norm); 21 | // structure.add("weight", kha.graphics4.VertexData.Short4Norm); 22 | // data.raw.vertex_elements.push({ name: "bone", data: 'short4norm' }); 23 | // data.raw.vertex_elements.push({ name: "weight", data: 'short4norm' }); 24 | // } 25 | // #end 26 | 27 | var ds = MakeMaterial.getDisplaceStrength(); 28 | pipeState.vertexShader = kha.graphics4.VertexShader.fromSource( 29 | #if kha_direct3d11 30 | "#define vec3 float3 31 | uniform float4x4 W; 32 | uniform float3x3 N; 33 | Texture2D texpaint_pack; 34 | SamplerState _texpaint_pack_sampler; 35 | struct SPIRV_Cross_Input { float4 pos : TEXCOORD1; float2 nor : TEXCOORD0; float2 tex : TEXCOORD2; }; 36 | struct SPIRV_Cross_Output { float4 svpos : SV_POSITION; }; 37 | SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) { 38 | SPIRV_Cross_Output stage_output; 39 | " + MakeMaterial.voxelgiHalfExtents() + " 40 | stage_output.svpos.xyz = mul(float4(stage_input.pos.xyz, 1.0), W).xyz / voxelgiHalfExtents.xxx; 41 | float3 wnormal = normalize(mul(float3(stage_input.nor.xy, stage_input.pos.w), N)); 42 | float height = texpaint_pack.SampleLevel(_texpaint_pack_sampler, stage_input.tex, 0.0).a; 43 | stage_output.svpos.xyz += wnormal * height.xxx * float3(" + ds + "," + ds + "," + ds + "); 44 | stage_output.svpos.w = 1.0; 45 | return stage_output; 46 | }" 47 | #else 48 | "#version 450 49 | in vec4 pos; 50 | in vec2 nor; 51 | in vec2 tex; 52 | out vec3 voxpositionGeom; 53 | uniform mat4 W; 54 | uniform mat3 N; 55 | uniform sampler2D texpaint_pack; 56 | void main() { 57 | " + MakeMaterial.voxelgiHalfExtents() + " 58 | voxpositionGeom = vec3(W * vec4(pos.xyz, 1.0)) / voxelgiHalfExtents; 59 | vec3 wnormal = normalize(N * vec3(nor.xy, pos.w)); 60 | float height = textureLod(texpaint_pack, tex, 0.0).a; 61 | voxpositionGeom += wnormal * vec3(height) * vec3(" + ds + "); 62 | }" 63 | #end 64 | ); 65 | 66 | pipeState.compile(); 67 | data.raw.constants = [{ name: "W", type: "mat4", link: "_worldMatrix" }, { name: "N", type: "mat3", link: "_normalMatrix" }]; 68 | data.constants = [pipeState.getConstantLocation("W"), pipeState.getConstantLocation("N")]; 69 | data.raw.texture_units = [{ name: "texpaint_pack" }, { name: "voxels", is_image: true }]; 70 | data.textureUnits = [pipeState.getTextureUnit("texpaint_pack"), pipeState.getTextureUnit("voxels")]; 71 | } 72 | #end 73 | } 74 | -------------------------------------------------------------------------------- /Assets/plugins/embed/uv_unwrap.js: -------------------------------------------------------------------------------- 1 | 2 | let a = Krom_uv_unwrap; 3 | class R { 4 | get buffer() { return Krom_uv_unwrap._buffer(); } 5 | } 6 | let r = new R(); 7 | 8 | // uv_unwrap.js 9 | function unwrap_mesh(mesh) { 10 | let positions = mesh.posa; 11 | let normals = mesh.nora; 12 | let indices = mesh.inda; 13 | let vertexCount = positions.length / 4; 14 | let indexCount = indices.length; 15 | 16 | a._setVertexCount(vertexCount); 17 | a._setIndexCount(indexCount); 18 | let pa = new Float32Array(r.buffer, a._setPositions(), vertexCount * 3); 19 | let na = new Float32Array(r.buffer, a._setNormals(), vertexCount * 3); 20 | let ia = new Uint32Array(r.buffer, a._setIndices(), indexCount); 21 | 22 | let inv = 1 / 32767; 23 | 24 | for (let i = 0; i < vertexCount; i++) { 25 | pa[i * 3 ] = positions[i * 4 ] * inv; 26 | pa[i * 3 + 1] = positions[i * 4 + 1] * inv; 27 | pa[i * 3 + 2] = positions[i * 4 + 2] * inv; 28 | na[i * 3 ] = normals [i * 2 ] * inv; 29 | na[i * 3 + 1] = normals [i * 2 + 1] * inv; 30 | na[i * 3 + 2] = positions[i * 4 + 3] * inv; 31 | } 32 | for (let i = 0; i < indexCount; i++) { 33 | ia[i] = indices[i]; 34 | } 35 | 36 | a._unwrap(); 37 | 38 | vertexCount = a._getVertexCount(); 39 | indexCount = a._getIndexCount(); 40 | pa = new Float32Array(r.buffer, a._getPositions(), vertexCount * 3); 41 | na = new Float32Array(r.buffer, a._getNormals(), vertexCount * 3); 42 | let ua = new Float32Array(r.buffer, a._getUVs(), vertexCount * 2); 43 | ia = new Uint32Array(r.buffer, a._getIndices(), indexCount); 44 | 45 | let pa16 = new Int16Array(vertexCount * 4); 46 | let na16 = new Int16Array(vertexCount * 2); 47 | let ua16 = new Int16Array(vertexCount * 2); 48 | let ia32 = new Uint32Array(indexCount); 49 | 50 | for (let i = 0; i < vertexCount; i++) { 51 | pa16[i * 4 ] = pa[i * 3 ] / inv; 52 | pa16[i * 4 + 1] = pa[i * 3 + 1] / inv; 53 | pa16[i * 4 + 2] = pa[i * 3 + 2] / inv; 54 | pa16[i * 4 + 3] = na[i * 3 + 2] / inv; 55 | na16[i * 2 ] = na[i * 3 ] / inv; 56 | na16[i * 2 + 1] = na[i * 3 + 1] / inv; 57 | ua16[i * 2 ] = ua[i * 2 ] / inv; 58 | ua16[i * 2 + 1] = ua[i * 2 + 1] / inv; 59 | } 60 | for (let i = 0; i < indexCount; i++) { 61 | ia32[i] = ia[i]; 62 | } 63 | 64 | mesh.posa = pa16; 65 | mesh.nora = na16; 66 | mesh.texa = ua16; 67 | mesh.inda = ia32; 68 | 69 | a._destroy(); 70 | } 71 | 72 | let plugin = new arm.Plugin(); 73 | let h1 = new zui.Handle(); 74 | plugin.drawUI = function(ui) { 75 | if (ui.panel(h1, "UV Unwrap")) { 76 | if (ui.button("Unwrap Mesh")) { 77 | for (const po of arm.Project.paintObjects) { 78 | let raw = po.data.raw; 79 | var mesh = { 80 | posa: raw.vertex_arrays[0].values, 81 | nora: raw.vertex_arrays[1].values, 82 | texa: null, 83 | inda: raw.index_arrays[0].values 84 | }; 85 | unwrap_mesh(mesh); 86 | raw.vertex_arrays[0].values = mesh.posa; 87 | raw.vertex_arrays[1].values = mesh.nora; 88 | raw.vertex_arrays[2].values = mesh.texa; 89 | raw.index_arrays[0].values = mesh.inda; 90 | let geom = po.data.geom; 91 | geom.indices[0] = mesh.inda; 92 | geom.ready = false; 93 | geom.build(); 94 | } 95 | arm.MeshUtil.mergeMesh(); 96 | } 97 | } 98 | } 99 | 100 | let unwrappers = arm.MeshUtil.unwrappers; 101 | unwrappers.h["uv_unwrap.js"] = unwrap_mesh; 102 | plugin.delete = function() { 103 | unwrappers.h["uv_unwrap.js"] = null; 104 | }; 105 | -------------------------------------------------------------------------------- /Sources/arm/util/ParticleUtil.hx: -------------------------------------------------------------------------------- 1 | package arm.util; 2 | 3 | import haxe.Json; 4 | import kha.arrays.Float32Array; 5 | import iron.RenderPath; 6 | import iron.Scene; 7 | import iron.data.SceneFormat; 8 | import iron.data.MaterialData; 9 | import iron.object.Object; 10 | import iron.object.MeshObject; 11 | import arm.ui.UISidebar; 12 | 13 | class ParticleUtil { 14 | 15 | static function f32(ar: Array): Float32Array { 16 | var res = new Float32Array(ar.length); 17 | for (i in 0...ar.length) res[i] = ar[i]; 18 | return res; 19 | } 20 | 21 | public static function initParticle() { 22 | if (Context.particleMaterial != null) return; 23 | 24 | var raw: TParticleData = { 25 | name: "Particles", 26 | type: 0, 27 | loop: false, 28 | count: 1000, 29 | frame_start: 0, 30 | frame_end: 1000, 31 | lifetime: 400, 32 | lifetime_random: 0.5, 33 | emit_from: 1, 34 | object_align_factor: f32([0, 0, -40]), 35 | factor_random: 2.0, 36 | physics_type: 0, 37 | particle_size: 1.0, 38 | size_random: 0, 39 | mass: 1, 40 | instance_object: ".Particle", 41 | weight_gravity: 1 42 | }; 43 | Scene.active.raw.particle_datas = [raw]; 44 | var particle_refs: Array = [ 45 | { 46 | name: "Particles", 47 | particle: "Particles", 48 | seed: 0 49 | } 50 | ]; 51 | 52 | { 53 | var t = new RenderTargetRaw(); 54 | t.name = "texparticle"; 55 | t.width = 0; 56 | t.height = 0; 57 | t.format = "R8"; 58 | t.scale = arm.render.Inc.getSuperSampling(); 59 | RenderPath.active.createRenderTarget(t); 60 | } 61 | 62 | for (mat in Scene.active.raw.material_datas) { 63 | if (mat.name == "Material2") { 64 | var m: TMaterialData = Json.parse(Json.stringify(mat)); 65 | m.name = "MaterialParticle"; 66 | Scene.active.raw.material_datas.push(m); 67 | break; 68 | } 69 | } 70 | 71 | iron.data.Data.getMaterial("Scene", "MaterialParticle", function(md: MaterialData) { 72 | Context.particleMaterial = md; 73 | 74 | for (obj in Scene.active.raw.objects) { 75 | if (obj.name == ".Sphere") { 76 | var particle: TObj = Json.parse(Json.stringify(obj)); 77 | particle.name = ".Particle"; 78 | particle.is_particle = true; 79 | particle.material_refs = ["MaterialParticle"]; 80 | Scene.active.raw.objects.push(particle); 81 | for (i in 0...16) particle.transform.values[i] *= 0.01; 82 | break; 83 | } 84 | } 85 | 86 | Scene.active.spawnObject(".Sphere", null, function(o: Object) { 87 | var mo: MeshObject = cast o; 88 | mo.name = ".ParticleEmitter"; 89 | mo.raw = Json.parse(Json.stringify(mo.raw)); 90 | mo.raw.particle_refs = particle_refs; 91 | #if arm_particles 92 | mo.setupParticleSystem("Scene", particle_refs[0]); 93 | #end 94 | }); 95 | }); 96 | } 97 | 98 | #if arm_physics 99 | 100 | public static function initParticlePhysics() { 101 | if (arm.plugin.PhysicsWorld.active != null) { 102 | initParticleMesh(); 103 | return; 104 | } 105 | 106 | arm.plugin.PhysicsWorld.load(function() { 107 | Scene.active.sceneParent.addTrait(new arm.plugin.PhysicsWorld()); 108 | initParticleMesh(); 109 | }); 110 | } 111 | 112 | static function initParticleMesh() { 113 | if (Context.paintBody != null) return; 114 | 115 | var po = Context.mergedObject != null ? Context.mergedObject : Context.paintObject; 116 | 117 | po.transform.scale.x = po.parent.transform.scale.x; 118 | po.transform.scale.y = po.parent.transform.scale.y; 119 | po.transform.scale.z = po.parent.transform.scale.z; 120 | 121 | Context.paintBody = new arm.plugin.PhysicsBody(); 122 | Context.paintBody.shape = arm.plugin.PhysicsBody.ShapeType.ShapeMesh; 123 | po.addTrait(Context.paintBody); 124 | } 125 | 126 | #end 127 | } 128 | -------------------------------------------------------------------------------- /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 / 8, 1 / 8, 1 / 8, 1 / 8, 1 / 8, 1 / 8, 1 / 8, 1 / 8]); 20 | } 21 | else { 22 | ui.row([1 / 14, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 14, 1 / 14, 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("Flip Normals"))) { 39 | MeshUtil.flipNormals(); 40 | Context.ddirty = 2; 41 | } 42 | 43 | if (ui.button(tr("Calculate Normals"))) { 44 | UIMenu.draw(function(ui: Zui) { 45 | ui.text(tr("Normals"), Right, ui.t.HIGHLIGHT_COL); 46 | if (ui.button(tr("Smooth"), Left)) { MeshUtil.calcNormals(true); Context.ddirty = 2; } 47 | if (ui.button(tr("Flat"), Left)) { MeshUtil.calcNormals(false); Context.ddirty = 2; } 48 | }, 3); 49 | } 50 | 51 | if (ui.button(tr("Geometry to Origin"))) { 52 | MeshUtil.toOrigin(); 53 | Context.ddirty = 2; 54 | } 55 | 56 | if (ui.button(tr("Apply Displacement"))) { 57 | MeshUtil.applyDisplacement(); 58 | MeshUtil.calcNormals(); 59 | Context.ddirty = 2; 60 | } 61 | 62 | if (ui.button(tr("Rotate X"))) { 63 | MeshUtil.swapAxis(1, 2); 64 | Context.ddirty = 2; 65 | } 66 | 67 | if (ui.button(tr("Rotate Y"))) { 68 | MeshUtil.swapAxis(2, 0); 69 | Context.ddirty = 2; 70 | } 71 | 72 | if (ui.button(tr("Rotate Z"))) { 73 | MeshUtil.swapAxis(0, 1); 74 | Context.ddirty = 2; 75 | } 76 | 77 | ui.endSticky(); 78 | 79 | for (i in 0...Project.paintObjects.length) { 80 | var o = Project.paintObjects[i]; 81 | var h = Id.handle(); 82 | h.selected = o.visible; 83 | o.visible = ui.check(h, o.name); 84 | if (ui.isHovered && ui.inputReleasedR) { 85 | UIMenu.draw(function(ui: Zui) { 86 | ui.text(o.name, Right, ui.t.HIGHLIGHT_COL); 87 | if (ui.button(tr("Export"), Left)) { 88 | Context.exportMeshIndex = i + 1; 89 | BoxExport.showMesh(); 90 | } 91 | if (Project.paintObjects.length > 1 && ui.button(tr("Delete"), Left)) { 92 | Project.paintObjects.remove(o); 93 | while (o.children.length > 0) { 94 | var child = o.children[0]; 95 | child.setParent(null); 96 | if (Project.paintObjects[0] != child) { 97 | child.setParent(Project.paintObjects[0]); 98 | } 99 | if (o.children.length == 0) { 100 | Project.paintObjects[0].transform.scale.setFrom(o.transform.scale); 101 | Project.paintObjects[0].transform.buildMatrix(); 102 | } 103 | } 104 | iron.data.Data.deleteMesh(o.data.handle); 105 | o.remove(); 106 | Context.paintObject = Context.mainObject(); 107 | MeshUtil.mergeMesh(); 108 | Context.ddirty = 2; 109 | } 110 | }, Project.paintObjects.length > 1 ? 3 : 2); 111 | } 112 | if (h.changed) { 113 | var visibles: Array = []; 114 | for (p in Project.paintObjects) if (p.visible) visibles.push(p); 115 | MeshUtil.mergeMesh(visibles); 116 | Context.ddirty = 2; 117 | } 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/InputNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | import iron.math.Vec4; 4 | import arm.ui.UISidebar; 5 | import arm.Enums; 6 | 7 | @:keep 8 | class InputNode extends LogicNode { 9 | 10 | static var coords = new Vec4(); 11 | 12 | static var startX = 0.0; 13 | static var startY = 0.0; 14 | 15 | // Brush ruler 16 | static var lockBegin = false; 17 | static var lockX = false; 18 | static var lockY = false; 19 | static var lockStartX = 0.0; 20 | static var lockStartY = 0.0; 21 | 22 | static var registered = false; 23 | 24 | public function new(tree: LogicTree) { 25 | super(tree); 26 | 27 | if (!registered) { 28 | registered = true; 29 | tree.notifyOnUpdate(update); 30 | } 31 | } 32 | 33 | function update() { 34 | if (Context.splitView) { 35 | Context.viewIndex = iron.system.Input.getMouse().viewX > arm.App.w() / 2 ? 1 : 0; 36 | } 37 | 38 | var decal = Context.tool == ToolDecal || Context.tool == ToolText; 39 | var decalMask = decal && Operator.shortcut(Config.keymap.decal_mask + "+" + Config.keymap.action_paint, ShortcutDown); 40 | 41 | var lazyPaint = Context.brushLazyRadius > 0 && 42 | (Operator.shortcut(Config.keymap.action_paint, ShortcutDown) || 43 | Operator.shortcut(Config.keymap.brush_ruler + "+" + Config.keymap.action_paint, ShortcutDown) || 44 | decalMask); 45 | 46 | var mouse = iron.system.Input.getMouse(); 47 | var paintX = mouse.viewX / iron.App.w(); 48 | var paintY = mouse.viewY / iron.App.h(); 49 | if (mouse.started()) { 50 | startX = mouse.viewX / iron.App.w(); 51 | startY = mouse.viewY / iron.App.h(); 52 | } 53 | 54 | var pen = iron.system.Input.getPen(); 55 | if (pen.down()) { 56 | paintX = pen.viewX / iron.App.w(); 57 | paintY = pen.viewY / iron.App.h(); 58 | } 59 | if (pen.started()) { 60 | startX = pen.viewX / iron.App.w(); 61 | startY = pen.viewY / iron.App.h(); 62 | } 63 | 64 | if (Operator.shortcut(Config.keymap.brush_ruler + "+" + Config.keymap.action_paint, ShortcutDown)) { 65 | if (lockX) paintX = startX; 66 | if (lockY) paintY = startY; 67 | } 68 | 69 | if (Context.brushLazyRadius > 0) { 70 | Context.brushLazyX = paintX; 71 | Context.brushLazyY = paintY; 72 | } 73 | if (!lazyPaint) { 74 | coords.x = paintX; 75 | coords.y = paintY; 76 | } 77 | 78 | if (Context.splitView) { 79 | Context.viewIndex = -1; 80 | } 81 | 82 | if (lockBegin) { 83 | var dx = Math.abs(lockStartX - mouse.viewX); 84 | var dy = Math.abs(lockStartY - mouse.viewY); 85 | if (dx > 1 || dy > 1) { 86 | lockBegin = false; 87 | dx > dy ? lockY = true : lockX = true; 88 | } 89 | } 90 | 91 | var kb = iron.system.Input.getKeyboard(); 92 | if (kb.started(Config.keymap.brush_ruler)) { 93 | lockStartX = mouse.viewX; 94 | lockStartY = mouse.viewY; 95 | lockBegin = true; 96 | } 97 | else if (kb.released(Config.keymap.brush_ruler)) { 98 | lockX = lockY = lockBegin = false; 99 | } 100 | 101 | if (Context.brushLazyRadius > 0) { 102 | var v1 = new Vec4(Context.brushLazyX * iron.App.w(), Context.brushLazyY * iron.App.h(), 0.0); 103 | var v2 = new Vec4(coords.x * iron.App.w(), coords.y * iron.App.h(), 0.0); 104 | var d = Vec4.distance(v1, v2); 105 | var r = Context.brushLazyRadius * 85; 106 | if (d > r) { 107 | var v3 = new Vec4(); 108 | v3.subvecs(v2, v1); 109 | v3.normalize(); 110 | v3.mult(1.0 - Context.brushLazyStep); 111 | v3.mult(r); 112 | v2.addvecs(v1, v3); 113 | coords.x = v2.x / iron.App.w(); 114 | coords.y = v2.y / iron.App.h(); 115 | // Parse brush inputs once on next draw 116 | Context.painted = -1; 117 | } 118 | Context.lastPaintX = -1; 119 | Context.lastPaintY = -1; 120 | } 121 | 122 | Context.parseBrushInputs(); 123 | } 124 | 125 | override function get(from: Int): Dynamic { 126 | Context.brushLazyRadius = inputs[0].get(); 127 | Context.brushLazyStep = inputs[1].get(); 128 | return coords; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeBrush.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | import arm.ui.UISidebar; 4 | import arm.shader.NodeShader; 5 | import arm.shader.ShaderFunctions; 6 | import arm.Enums; 7 | 8 | class MakeBrush { 9 | 10 | public static function run(vert: NodeShader, frag: NodeShader) { 11 | 12 | frag.write('float dist = 0.0;'); 13 | 14 | if (Context.tool == ToolParticle) return; 15 | 16 | var decal = Context.tool == ToolDecal || Context.tool == ToolText; 17 | if (decal) frag.write('if (decalMaskLocal.z > 0.0) {'); 18 | 19 | if (Config.raw.brush_3d) { 20 | frag.write('vec4 inpLocal = inp;'); // TODO: spirv workaround 21 | frag.write('vec4 inplastLocal = inplast;'); // TODO: spirv workaround 22 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 23 | frag.write('float depth = textureLod(gbufferD, inpLocal.xy, 0.0).r;'); 24 | #else 25 | frag.write('float depth = textureLod(gbufferD, vec2(inpLocal.x, 1.0 - inpLocal.y), 0.0).r;'); 26 | #end 27 | 28 | frag.add_uniform('mat4 invVP', '_inverseViewProjectionMatrix'); 29 | frag.write('vec4 winp = vec4(vec2(inpLocal.x, 1.0 - inpLocal.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);'); 30 | frag.write('winp = mul(winp, invVP);'); 31 | frag.write('winp.xyz /= winp.w;'); 32 | frag.wposition = true; 33 | 34 | if (Config.raw.brush_angle_reject || Context.xray) { 35 | frag.add_function(ShaderFunctions.str_octahedronWrap); 36 | frag.add_uniform('sampler2D gbuffer0'); 37 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 38 | frag.write('vec2 g0 = textureLod(gbuffer0, inpLocal.xy, 0.0).rg;'); 39 | #else 40 | frag.write('vec2 g0 = textureLod(gbuffer0, vec2(inpLocal.x, 1.0 - inpLocal.y), 0.0).rg;'); 41 | #end 42 | frag.write('vec3 wn;'); 43 | frag.write('wn.z = 1.0 - abs(g0.x) - abs(g0.y);'); 44 | frag.write('wn.xy = wn.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);'); 45 | frag.write('wn = normalize(wn);'); 46 | frag.write('float planeDist = dot(wn, winp.xyz - wposition);'); 47 | 48 | if (Config.raw.brush_angle_reject && !Context.xray) { 49 | frag.write('if (planeDist < -0.01) discard;'); 50 | frag.n = true; 51 | var angle = Context.brushAngleRejectDot; 52 | frag.write('if (dot(wn, n) < $angle) discard;'); 53 | } 54 | } 55 | 56 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 57 | frag.write('float depthlast = textureLod(gbufferD, inplastLocal.xy, 0.0).r;'); 58 | #else 59 | frag.write('float depthlast = textureLod(gbufferD, vec2(inplastLocal.x, 1.0 - inplastLocal.y), 0.0).r;'); 60 | #end 61 | 62 | frag.write('vec4 winplast = vec4(vec2(inplastLocal.x, 1.0 - inplastLocal.y) * 2.0 - 1.0, depthlast * 2.0 - 1.0, 1.0);'); 63 | frag.write('winplast = mul(winplast, invVP);'); 64 | frag.write('winplast.xyz /= winplast.w;'); 65 | 66 | frag.write('vec3 pa = wposition - winp.xyz;'); 67 | if (Context.xray) { 68 | frag.write('pa += wn * vec3(planeDist, planeDist, planeDist);'); 69 | } 70 | frag.write('vec3 ba = winplast.xyz - winp.xyz;'); 71 | 72 | if (Context.brushLazyRadius > 0 && Context.brushLazyStep > 0) { 73 | // Sphere 74 | frag.write('dist = distance(wposition, winp.xyz);'); 75 | } 76 | else { 77 | // Capsule 78 | frag.write('float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);'); 79 | frag.write('dist = length(pa - ba * h);'); 80 | } 81 | } 82 | else { // !brush3d 83 | frag.write('vec2 binp = inp.xy * 2.0 - 1.0;'); 84 | frag.write('binp.x *= aspectRatio;'); 85 | frag.write('binp = binp * 0.5 + 0.5;'); 86 | 87 | frag.write('vec2 binplast = inplast.xy * 2.0 - 1.0;'); 88 | frag.write('binplast.x *= aspectRatio;'); 89 | frag.write('binplast = binplast * 0.5 + 0.5;'); 90 | 91 | frag.write('vec2 pa = bsp.xy - binp.xy;'); 92 | frag.write('vec2 ba = binplast.xy - binp.xy;'); 93 | frag.write('float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);'); 94 | frag.write('dist = length(pa - ba * h);'); 95 | } 96 | 97 | frag.write('if (dist > brushRadius) discard;'); 98 | 99 | if (decal) frag.write('}'); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/arm/io/ImportFolder.hx: -------------------------------------------------------------------------------- 1 | package arm.io; 2 | 3 | import zui.Nodes; 4 | import iron.data.Data; 5 | import iron.data.MaterialData; 6 | import arm.ui.UIHeader; 7 | import arm.ui.UISidebar; 8 | import arm.util.RenderUtil; 9 | import arm.sys.Path; 10 | import arm.sys.File; 11 | import arm.shader.NodesMaterial; 12 | import arm.node.MakeMaterial; 13 | import arm.data.MaterialSlot; 14 | import arm.Enums; 15 | 16 | class ImportFolder { 17 | 18 | public static function run(path: String) { 19 | var files = File.readDirectory(path); 20 | var mapbase = ""; 21 | var mapopac = ""; 22 | var mapnor = ""; 23 | var mapocc = ""; 24 | var maprough = ""; 25 | var mapmet = ""; 26 | var mapheight = ""; 27 | 28 | var foundTexture = false; 29 | // Import maps 30 | for (f in files) { 31 | if (!Path.isTexture(f)) continue; 32 | 33 | // TODO: handle -albedo 34 | 35 | var base = f.substr(0, f.lastIndexOf(".")).toLowerCase(); 36 | var valid = false; 37 | if (mapbase == "" && Path.isBaseColorTex(base)) { 38 | mapbase = f; 39 | valid = true; 40 | } 41 | if (mapopac == "" && Path.isOpacityTex(base)) { 42 | mapopac = f; 43 | valid = true; 44 | } 45 | if (mapnor == "" && Path.isNormalMapTex(base)) { 46 | mapnor = f; 47 | valid = true; 48 | } 49 | if (mapocc == "" && Path.isOcclusionTex(base)) { 50 | mapocc = f; 51 | valid = true; 52 | } 53 | if (maprough == "" && Path.isRoughnessTex(base)) { 54 | maprough = f; 55 | valid = true; 56 | } 57 | if (mapmet == "" && Path.isMetallicTex(base)) { 58 | mapmet = f; 59 | valid = true; 60 | } 61 | if (mapheight == "" && Path.isDisplacementTex(base)) { 62 | mapheight = f; 63 | valid = true; 64 | } 65 | 66 | if (valid) { 67 | ImportTexture.run(path + Path.sep + f, false); 68 | foundTexture = true; 69 | } 70 | } 71 | 72 | if (!foundTexture) { 73 | Console.info(tr("Folder does not contain textures")); 74 | return; 75 | } 76 | 77 | // Create material 78 | Context.material = new MaterialSlot(Project.materials[0].data); 79 | Project.materials.push(Context.material); 80 | var nodes = Context.material.nodes; 81 | var canvas = Context.material.canvas; 82 | var dirs = path.split(Path.sep); 83 | canvas.name = dirs[dirs.length - 1]; 84 | var nout: TNode = null; 85 | for (n in canvas.nodes) { 86 | if (n.type == "OUTPUT_MATERIAL_PBR") { 87 | nout = n; 88 | break; 89 | } 90 | } 91 | for (n in canvas.nodes) { 92 | if (n.name == "RGB") { 93 | nodes.removeNode(n, canvas); 94 | break; 95 | } 96 | } 97 | 98 | // Place nodes 99 | var pos = 0; 100 | var startY = 100; 101 | var nodeH = 164; 102 | if (mapbase != "") { 103 | placeImageNode(nodes, canvas, mapbase, startY + nodeH * pos, nout.id, 0); 104 | pos++; 105 | } 106 | if (mapopac != "") { 107 | placeImageNode(nodes, canvas, mapopac, startY + nodeH * pos, nout.id, 1); 108 | pos++; 109 | } 110 | if (mapocc != "") { 111 | placeImageNode(nodes, canvas, mapocc, startY + nodeH * pos, nout.id, 2); 112 | pos++; 113 | } 114 | if (maprough != "") { 115 | placeImageNode(nodes, canvas, maprough, startY + nodeH * pos, nout.id, 3); 116 | pos++; 117 | } 118 | if (mapmet != "") { 119 | placeImageNode(nodes, canvas, mapmet, startY + nodeH * pos, nout.id, 4); 120 | pos++; 121 | } 122 | if (mapnor != "") { 123 | placeImageNode(nodes, canvas, mapnor, startY + nodeH * pos, nout.id, 5); 124 | pos++; 125 | } 126 | if (mapheight != "") { 127 | placeImageNode(nodes, canvas, mapheight, startY + nodeH * pos, nout.id, 7); 128 | pos++; 129 | } 130 | 131 | MakeMaterial.parsePaintMaterial(); 132 | RenderUtil.makeMaterialPreview(); 133 | UISidebar.inst.hwnd1.redraws = 2; 134 | History.newMaterial(); 135 | } 136 | 137 | static function placeImageNode(nodes: Nodes, canvas: TNodeCanvas, asset: String, ny: Int, to_id: Int, to_socket: Int) { 138 | var n = NodesMaterial.createNode("TEX_IMAGE"); 139 | n.buttons[0].default_value = App.getAssetIndex(asset); 140 | n.x = 72; 141 | n.y = ny; 142 | var l: TNodeLink = { id: nodes.getLinkId(canvas.links), from_id: n.id, from_socket: 0, to_id: to_id, to_socket: to_socket }; 143 | canvas.links.push(l); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /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 | 25 | public static function render(g: kha.graphics2.Graphics) { 26 | if (!UIMenu.show) { 27 | var mouse = Input.getMouse(); 28 | var kb = Input.getKeyboard(); 29 | var ui = App.uiBox; 30 | var inUse = ui.comboSelectedHandle != null; 31 | var isEscape = kb.started("escape"); 32 | if (draws > 2 && (ui.inputReleased || isEscape) && !inUse && !ui.isTyping) { 33 | var appw = System.windowWidth(); 34 | var apph = System.windowHeight(); 35 | var mw = Std.int(modalW * ui.SCALE()); 36 | var mh = Std.int(modalH * ui.SCALE()); 37 | var left = (appw / 2 - mw / 2) + hwnd.dragX; 38 | var right = (appw / 2 + mw / 2) + hwnd.dragX; 39 | var top = (apph / 2 - mh / 2) + hwnd.dragY; 40 | var bottom = (apph / 2 + mh / 2) + hwnd.dragY; 41 | var mx = mouse.x; 42 | var my = mouse.y; 43 | if ((clickToHide && (mx < left || mx > right || my < top || my > bottom)) || isEscape) { 44 | if (modalOnHide != null) modalOnHide(); 45 | show = false; 46 | App.redrawUI(); 47 | } 48 | } 49 | } 50 | 51 | g.end(); 52 | 53 | var ui = App.uiBox; 54 | var appw = System.windowWidth(); 55 | var apph = System.windowHeight(); 56 | var mw = Std.int(modalW * ui.SCALE()); 57 | var mh = Std.int(modalH * ui.SCALE()); 58 | var left = Std.int(appw / 2 - mw / 2); 59 | var top = Std.int(apph / 2 - mh / 2); 60 | 61 | if (boxCommands == null) { 62 | ui.begin(g); 63 | if (ui.window(hwnd, left, top, mw, mh, draggable)) { 64 | ui._y += 10; 65 | if (ui.tab(Id.handle(), boxTitle)) { 66 | var htext = Id.handle(); 67 | htext.text = boxText; 68 | copyable ? 69 | Ext.textArea(ui, htext, false) : 70 | ui.text(boxText); 71 | ui.endElement(); 72 | 73 | #if (krom_windows || krom_linux || krom_darwin) 74 | if (copyable) ui.row([1 / 3, 1 / 3, 1 / 3]); 75 | else ui.row([2 / 3, 1 / 3]); 76 | #else 77 | ui.row([2 / 3, 1 / 3]); 78 | #end 79 | 80 | ui.endElement(); 81 | 82 | #if (krom_windows || krom_linux || krom_darwin) 83 | if (copyable && ui.button(tr("Copy"))) { 84 | Krom.copyToClipboard(boxText); 85 | } 86 | #end 87 | if (ui.button(tr("OK"))) { 88 | show = false; 89 | App.redrawUI(); 90 | } 91 | } 92 | windowBorder(ui); 93 | } 94 | ui.end(); 95 | } 96 | else { 97 | ui.begin(g); 98 | if (ui.window(hwnd, left, top, mw, mh, draggable)) { 99 | ui._y += 10; 100 | boxCommands(ui); 101 | windowBorder(ui); 102 | } 103 | ui.end(); 104 | } 105 | 106 | g.begin(false); 107 | 108 | draws++; 109 | } 110 | 111 | public static function showMessage(title: String, text: String, copyable = false) { 112 | init(); 113 | modalW = 400; 114 | modalH = 210; 115 | boxTitle = title; 116 | boxText = text; 117 | boxCommands = null; 118 | UIBox.copyable = copyable; 119 | draggable = true; 120 | } 121 | 122 | public static function showCustom(commands: Zui->Void = null, mw = 400, mh = 200, onHide: Void->Void = null, draggable = true) { 123 | init(); 124 | modalW = mw; 125 | modalH = mh; 126 | modalOnHide = onHide; 127 | boxCommands = commands; 128 | UIBox.draggable = draggable; 129 | } 130 | 131 | static function init() { 132 | hwnd.redraws = 2; 133 | hwnd.dragX = 0; 134 | hwnd.dragY = 0; 135 | show = true; 136 | draws = 0; 137 | clickToHide = true; 138 | } 139 | 140 | static function windowBorder(ui: Zui) { 141 | if (ui.scissor) { 142 | ui.scissor = false; 143 | ui.g.disableScissor(); 144 | } 145 | // Border 146 | ui.g.color = ui.t.SEPARATOR_COL; 147 | ui.g.fillRect(0, 0, 1, ui._windowH); 148 | ui.g.fillRect(0 + ui._windowW - 1, 0, 1, ui._windowH); 149 | ui.g.fillRect(0, 0 + ui._windowH - 1, ui._windowW, 1); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /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 | #if arm_vr 17 | import arm.render.RenderPathForwardVR; 18 | #end 19 | 20 | class Main { 21 | 22 | public static inline var title = "ArmorPaint"; 23 | public static var version = "0.9"; 24 | public static var sha = BuildMacros.sha().substr(1, 7); 25 | public static var date = BuildMacros.date().split(" ")[0]; 26 | static var tasks: Int; 27 | 28 | public static function main() { 29 | #if arm_snapshot 30 | 31 | var global = js.Syntax.code("globalThis"); 32 | global.kickstart = kickstart; 33 | 34 | Res.embedRaw("Scene", "Scene.arm", untyped global['data/Scene.arm']); 35 | untyped global['data/Scene.arm'] = null; 36 | 37 | Res.embedRaw("shader_datas", "shader_datas.arm", untyped global['data/shader_datas.arm']); 38 | untyped global['data/shader_datas.arm'] = null; 39 | 40 | Res.embedFont("font.ttf", untyped global['data/font.ttf']); 41 | untyped global['data/font.ttf'] = null; 42 | 43 | Res.embedFont("font_mono.ttf", untyped global['data/font_mono.ttf']); 44 | untyped global['data/font_mono.ttf'] = null; 45 | 46 | var files = [ 47 | "font13.bin", 48 | "ltc_mag.arm", 49 | "ltc_mat.arm", 50 | "default_brush.arm", 51 | "default_material.arm", 52 | "World_irradiance.arm", 53 | "World_radiance.k", 54 | "World_radiance_0.k", 55 | "World_radiance_1.k", 56 | "World_radiance_2.k", 57 | "World_radiance_3.k", 58 | "World_radiance_4.k", 59 | "World_radiance_5.k", 60 | "World_radiance_6.k", 61 | "World_radiance_7.k", 62 | "World_radiance_8.k", 63 | "brdf.k", 64 | "color_wheel.k", 65 | "black_white_gradient.k", 66 | "cursor.k", 67 | "icons.k", 68 | "icons2x.k", 69 | "noise256.k", 70 | "smaa_search.k", 71 | "smaa_area.k" 72 | ]; 73 | 74 | for (file in files) { 75 | Res.embedBlob(file, untyped global['data/' + file]); 76 | untyped global['data/' + file] = null; 77 | } 78 | 79 | #if (kha_direct3d12 || kha_vulkan) 80 | 81 | var ext = #if kha_direct3d12 ".cso" #else ".spirv" #end ; 82 | 83 | var files_renderlib = [ 84 | "bnoise_rank.k", 85 | "bnoise_scramble.k", 86 | "bnoise_sobol.k", 87 | "raytrace_bake_ao" + ext, 88 | "raytrace_bake_bent" + ext, 89 | "raytrace_bake_light" + ext, 90 | "raytrace_bake_thick" + ext, 91 | "raytrace_brute_core" + ext, 92 | "raytrace_brute_full" + ext 93 | ]; 94 | 95 | for (file in files_renderlib) { 96 | Res.embedBlob(file, untyped global['data/' + file]); 97 | untyped global['data/' + file] = null; 98 | } 99 | 100 | #end 101 | 102 | #end // arm_snapshot 103 | 104 | #if (!arm_snapshot) 105 | iron.data.ShaderData.shaderPath = ""; // Use arm_data_dir 106 | kickstart(); 107 | #end 108 | } 109 | 110 | @:keep 111 | public static function kickstart() { 112 | // Used to locate external application data folder 113 | Krom.setApplicationName(Main.title); 114 | 115 | tasks = 1; 116 | tasks++; Config.load(function() { tasks--; start(); }); 117 | tasks--; start(); 118 | } 119 | 120 | static function start() { 121 | if (tasks > 0) return; 122 | 123 | Config.init(); 124 | System.start(Config.getOptions(), function(window: Window) { 125 | if (Config.raw.layout == null) arm.App.initLayout(); 126 | Krom.setApplicationName(Main.title); 127 | iron.App.init(function() { 128 | Scene.setActive("Scene", function(o: Object) { 129 | Uniforms.init(); 130 | var path = new RenderPath(); 131 | Inc.init(path); 132 | 133 | #if arm_vr 134 | RenderPathDeferred.init(path); // Allocate gbuffer 135 | RenderPathForward.init(path); 136 | RenderPathForwardVR.init(path); 137 | path.commands = RenderPathForwardVR.commands; 138 | #else 139 | if (Context.renderMode == RenderForward) { 140 | RenderPathDeferred.init(path); // Allocate gbuffer 141 | RenderPathForward.init(path); 142 | path.commands = RenderPathForward.commands; 143 | } 144 | else { 145 | RenderPathDeferred.init(path); 146 | path.commands = RenderPathDeferred.commands; 147 | } 148 | #end 149 | 150 | RenderPath.setActive(path); 151 | new arm.App(); 152 | }); 153 | }); 154 | }); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Sources/arm/node/brush/BrushOutputNode.hx: -------------------------------------------------------------------------------- 1 | package arm.node.brush; 2 | 3 | import arm.ui.UIToolbar; 4 | import arm.ui.UISidebar; 5 | import arm.ui.UIView2D; 6 | import arm.Enums; 7 | 8 | @:keep 9 | class BrushOutputNode extends LogicNode { 10 | 11 | public var Directional = false; // button 0 12 | 13 | public function new(tree: LogicTree) { 14 | super(tree); 15 | Context.runBrush = run; 16 | Context.parseBrushInputs = parseInputs; 17 | } 18 | 19 | function parseInputs() { 20 | var lastMask = Context.brushMaskImage; 21 | var lastStencil = Context.brushStencilImage; 22 | 23 | var input0: Dynamic; 24 | var input1: Dynamic; 25 | var input2: Dynamic; 26 | var input3: Dynamic; 27 | var input4: Dynamic; 28 | var input5: Dynamic; 29 | var input6: Dynamic; 30 | try { 31 | input0 = inputs[0].get(); 32 | input1 = inputs[1].get(); 33 | input2 = inputs[2].get(); 34 | input3 = inputs[3].get(); 35 | input4 = inputs[4].get(); 36 | input5 = inputs[5].get(); 37 | input6 = inputs[6].get(); 38 | } 39 | catch (_) { 40 | return; 41 | } 42 | 43 | Context.paintVec = input0; 44 | Context.brushNodesRadius = input1; 45 | Context.brushNodesScale = input2; 46 | Context.brushNodesAngle = input3; 47 | 48 | var opac: Dynamic = input4; // Float or texture name 49 | if (opac == null) opac = 1.0; 50 | if (Std.isOfType(opac, String)) { 51 | Context.brushMaskImageIsAlpha = opac.endsWith(".a"); 52 | opac = opac.substr(0, opac.lastIndexOf(".")); 53 | Context.brushNodesOpacity = 1.0; 54 | var index = Project.assetNames.indexOf(opac); 55 | var asset = Project.assets[index]; 56 | Context.brushMaskImage = Project.getImage(asset); 57 | } 58 | else { 59 | Context.brushNodesOpacity = opac; 60 | Context.brushMaskImage = null; 61 | } 62 | 63 | Context.brushNodesHardness = input5; 64 | 65 | var stencil: Dynamic = input6; // Float or texture name 66 | if (stencil == null) stencil = 1.0; 67 | if (Std.isOfType(stencil, String)) { 68 | Context.brushStencilImageIsAlpha = stencil.endsWith(".a"); 69 | stencil = stencil.substr(0, stencil.lastIndexOf(".")); 70 | var index = Project.assetNames.indexOf(stencil); 71 | var asset = Project.assets[index]; 72 | Context.brushStencilImage = Project.getImage(asset); 73 | } 74 | else { 75 | Context.brushStencilImage = null; 76 | } 77 | 78 | if (lastMask != Context.brushMaskImage || 79 | lastStencil != Context.brushStencilImage) { 80 | MakeMaterial.parsePaintMaterial(); 81 | } 82 | 83 | Context.brushDirectional = Directional; 84 | } 85 | 86 | override function run(from: Int) { 87 | var left = 0.0; 88 | var right = 1.0; 89 | if (Context.paint2d) { 90 | left = 1.0; 91 | right = (Context.splitView ? 2.0 : 1.0) + UIView2D.inst.ww / App.w(); 92 | } 93 | 94 | // First time init 95 | if (Context.lastPaintX < 0 || Context.lastPaintY < 0) { 96 | Context.lastPaintVecX = Context.paintVec.x; 97 | Context.lastPaintVecY = Context.paintVec.y; 98 | } 99 | 100 | // Do not paint over fill layer 101 | var fillLayer = Context.layer.fill_layer != null && Context.tool != ToolPicker && Context.tool != ToolColorId; 102 | 103 | // Do not paint over groups 104 | var groupLayer = Context.layer.isGroup(); 105 | 106 | // Paint bounds 107 | if (Context.paintVec.x > left && 108 | Context.paintVec.x < right && 109 | Context.paintVec.y > 0 && 110 | Context.paintVec.y < 1 && 111 | !fillLayer && 112 | !groupLayer && 113 | (Context.layer.isVisible() || Context.paint2d) && 114 | !UISidebar.inst.ui.isHovered && 115 | !arm.App.isDragging && 116 | !arm.App.isResizing && 117 | !arm.App.isScrolling() && 118 | !arm.App.isComboSelected()) { 119 | 120 | // Set color pick 121 | var down = iron.system.Input.getMouse().down() || iron.system.Input.getPen().down(); 122 | if (down && Context.tool == ToolColorId && Project.assets.length > 0) { 123 | Context.colorIdPicked = true; 124 | UIToolbar.inst.toolbarHandle.redraws = 1; 125 | } 126 | 127 | // Prevent painting the same spot 128 | var sameSpot = Context.paintVec.x == Context.lastPaintX && Context.paintVec.y == Context.lastPaintY; 129 | var lazy = Context.tool == ToolBrush && Context.brushLazyRadius > 0; 130 | if (down && (sameSpot || lazy)) { 131 | Context.painted++; 132 | } 133 | else { 134 | Context.painted = 0; 135 | } 136 | Context.lastPaintX = Context.paintVec.x; 137 | Context.lastPaintY = Context.paintVec.y; 138 | 139 | if (Context.tool == ToolParticle) { 140 | Context.painted = 0; // Always paint particles 141 | } 142 | 143 | if (Context.painted == 0) { 144 | parseInputs(); 145 | } 146 | 147 | if (Context.painted <= 1) { 148 | Context.pdirty = 1; 149 | Context.rdirty = 2; 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.745, step * 0.055, step * 0.17, step * 0.03]); 43 | } 44 | else { 45 | ui.row([bookmarksW / ui._w, step * 0.745, step * 0.055, 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(".armorpaint")) 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/UIToolbar.hx: -------------------------------------------------------------------------------- 1 | package arm.ui; 2 | 3 | import kha.System; 4 | import zui.Zui; 5 | import arm.Enums; 6 | import iron.RenderPath; 7 | import arm.Translator._tr; 8 | 9 | class UIToolbar { 10 | 11 | public static var inst: UIToolbar; 12 | 13 | public static inline var defaultToolbarW = 36; 14 | 15 | public var toolbarHandle = new Handle(); 16 | public var toolbarw = defaultToolbarW; 17 | 18 | public var toolNames = [ 19 | _tr("Brush"), 20 | _tr("Eraser"), 21 | _tr("Fill"), 22 | _tr("Decal"), 23 | _tr("Text"), 24 | _tr("Clone"), 25 | _tr("Blur"), 26 | _tr("Particle"), 27 | _tr("ColorID"), 28 | _tr("Picker"), 29 | _tr("Gizmo"), 30 | _tr("Bake") 31 | ]; 32 | var toolCount = [10, 2, 1, 1]; 33 | 34 | public function new() { 35 | inst = this; 36 | } 37 | 38 | @:access(zui.Zui) 39 | public function renderUI(g: kha.graphics2.Graphics) { 40 | var ui = UISidebar.inst.ui; 41 | 42 | if (ui.window(toolbarHandle, 0, UIHeader.inst.headerh, toolbarw, System.windowHeight() - UIHeader.inst.headerh)) { 43 | ui._y -= 4 * ui.SCALE(); 44 | 45 | ui.imageScrollAlign = false; 46 | var img = Res.get("icons.k"); 47 | var imgw = ui.SCALE() > 1 ? 100 : 50; 48 | 49 | var col = ui.t.WINDOW_BG_COL; 50 | if (col < 0) col += untyped 4294967296; 51 | var light = col > 0xff666666 + 4294967296; 52 | var iconAccent = light ? 0xff666666 : -1; 53 | 54 | var rect = Res.tile50(img, 7, 1); 55 | ui.image(img, light ? 0xff666666 : ui.t.BUTTON_COL, null, rect.x, rect.y, rect.w, rect.h); 56 | ui._y -= 4 * ui.SCALE(); 57 | 58 | if (UIHeader.inst.worktab.position == SpacePaint) { 59 | var keys = [ 60 | "(" + Config.keymap.tool_brush + ") - " + tr("Hold {action_paint} to paint\nHold {key} and press {action_paint} to paint a straight line (ruler mode)", ["key" => Config.keymap.brush_ruler, "action_paint" => Config.keymap.action_paint]), 61 | "(" + Config.keymap.tool_eraser + ") - " + tr("Hold {action_paint} to erase\nHold {key} and press {action_paint} to erase a straight line (ruler mode)", ["key" => Config.keymap.brush_ruler, "action_paint" => Config.keymap.action_paint]), 62 | "(" + Config.keymap.tool_fill + ")", 63 | "(" + Config.keymap.tool_decal + ") - " + tr("Hold {key} to paint on a decal mask", ["key" => Config.keymap.decal_mask]), 64 | "(" + Config.keymap.tool_text + ") - " + tr("Hold {key} to use the text as a mask", ["key" => Config.keymap.decal_mask]), 65 | "(" + Config.keymap.tool_clone + ") - " + tr("Hold {key} to set source", ["key" => Config.keymap.set_clone_source]), 66 | "(" + Config.keymap.tool_blur + ")", 67 | "(" + Config.keymap.tool_particle + ")", 68 | "(" + Config.keymap.tool_colorid + ")", 69 | "(" + Config.keymap.tool_picker + ")" 70 | ]; 71 | 72 | for (i in 0...toolCount[SpacePaint]) { 73 | ui._x += 2; 74 | if (Context.tool == i) ui.fill(-1, 2, 32 + 2, 32 + 2, ui.t.HIGHLIGHT_COL); 75 | var rect = Res.tile50(img, i, 0); 76 | var _y = ui._y; 77 | if (ui.image(img, iconAccent, null, rect.x, rect.y, rect.w, rect.h) == State.Started) Context.selectTool(i); 78 | if (i == 8 && Context.colorIdPicked) { 79 | ui.g.drawScaledSubImage(RenderPath.active.renderTargets.get("texpaint_colorid").image, 0, 0, 1, 1, 0, _y + 1.5 * ui.SCALE(), 5 * ui.SCALE(), 34 * ui.SCALE()); 80 | } 81 | if (ui.isHovered) ui.tooltip(tr(toolNames[i]) + " " + keys[i]); 82 | ui._x -= 2; 83 | ui._y += 2; 84 | } 85 | 86 | // ui._x += 2; 87 | // if (Context.tool == ToolGizmo) ui.fill(-1, 2, 32 + 2, 32 + 2, ui.t.HIGHLIGHT_COL); 88 | // if (ui.image(img, iconAccent, null, imgw * 10, 0, imgw, imgw) == State.Started) Context.selectTool(ToolGizmo); 89 | // if (ui.isHovered) ui.tooltip(tr("Gizmo") + " (G)"); 90 | // ui._x -= 2; 91 | // ui._y += 2; 92 | } 93 | else if (UIHeader.inst.worktab.position == SpaceMaterial) { 94 | ui._x += 2; 95 | if (Context.tool == ToolPicker) ui.fill(-1, 2, 32 + 2, 32 + 2, ui.t.HIGHLIGHT_COL); 96 | if (ui.image(img, iconAccent, null, imgw * 9, 0, imgw, imgw) == State.Started) Context.selectTool(ToolPicker); 97 | if (ui.isHovered) ui.tooltip(tr("Picker") + " (V)"); 98 | ui._x -= 2; 99 | ui._y += 2; 100 | } 101 | else if (UIHeader.inst.worktab.position == SpaceBake) { 102 | ui._x += 2; 103 | if (Context.tool == ToolBake) ui.fill(-1, 2, 32 + 2, 32 + 2, ui.t.HIGHLIGHT_COL); 104 | if (ui.image(img, iconAccent, null, imgw * 11, 0, imgw, imgw) == State.Started) Context.selectTool(ToolBake); 105 | if (ui.isHovered) ui.tooltip(tr("Bake") + " (K)"); 106 | ui._x -= 2; 107 | ui._y += 2; 108 | 109 | ui._x += 2; 110 | if (Context.tool == ToolPicker) ui.fill(-1, 2, 32 + 2, 32 + 2, ui.t.HIGHLIGHT_COL); 111 | if (ui.image(img, iconAccent, null, imgw * 9, 0, imgw, imgw) == State.Started) Context.selectTool(ToolPicker); 112 | if (ui.isHovered) ui.tooltip(tr("Picker") + " (V)"); 113 | ui._x -= 2; 114 | ui._y += 2; 115 | } 116 | 117 | ui.imageScrollAlign = true; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeTexcoord.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | import arm.ui.UISidebar; 4 | import arm.shader.NodeShader; 5 | import arm.Enums; 6 | 7 | class MakeTexcoord { 8 | 9 | public static function run(vert: NodeShader, frag: NodeShader) { 10 | 11 | var fillLayer = Context.layer.fill_layer != null; 12 | var uvType = fillLayer ? Context.layer.uvType : Context.brushPaint; 13 | var decal = Context.tool == ToolDecal || Context.tool == ToolText; 14 | var angle = Context.brushAngle + Context.brushNodesAngle; 15 | var uvAngle = fillLayer ? Context.layer.angle : angle; 16 | 17 | if (uvType == UVProject || decal) { // TexCoords - project 18 | frag.add_uniform('float brushScale', '_brushScale'); 19 | frag.write_attrib('vec2 uvsp = sp.xy;'); 20 | 21 | if (fillLayer) { // Decal layer 22 | frag.write_attrib('if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) discard;'); 23 | 24 | if (uvAngle != 0.0) { 25 | frag.add_uniform('vec2 brushAngle', '_brushAngle'); 26 | frag.write_attrib('uvsp = vec2(uvsp.x * brushAngle.x - uvsp.y * brushAngle.y, uvsp.x * brushAngle.y + uvsp.y * brushAngle.x);'); 27 | } 28 | 29 | frag.n = true; 30 | frag.add_uniform('vec3 decalLayerNor', '_decalLayerNor'); 31 | var dotAngle = Context.brushAngleRejectDot; 32 | frag.write('if (abs(dot(n, decalLayerNor) - 1.0) > $dotAngle) discard;'); 33 | 34 | frag.wposition = true; 35 | frag.add_uniform('vec3 decalLayerLoc', '_decalLayerLoc'); 36 | frag.add_uniform('float decalLayerDim', '_decalLayerDim'); 37 | frag.write_attrib('if (abs(dot(decalLayerNor, decalLayerLoc - wposition)) > decalLayerDim) discard;'); 38 | } 39 | else if (decal) { 40 | frag.add_uniform('vec4 decalMask', '_decalMask'); 41 | frag.write_attrib('vec4 decalMaskLocal = decalMask;'); // TODO: spirv workaround 42 | frag.write_attrib('uvsp -= decalMaskLocal.xy;'); 43 | frag.write_attrib('uvsp.x *= aspectRatio;'); 44 | frag.write_attrib('uvsp *= 0.21 / (decalMaskLocal.w * 0.9);'); // Decal radius 45 | 46 | if (Context.brushDirectional) { 47 | frag.add_uniform('vec3 brushDirection', '_brushDirection'); 48 | frag.write_attrib('if (brushDirection.z == 0.0) discard;'); 49 | frag.write_attrib('uvsp = vec2(uvsp.x * brushDirection.x - uvsp.y * brushDirection.y, uvsp.x * brushDirection.y + uvsp.y * brushDirection.x);'); 50 | } 51 | 52 | if (uvAngle != 0.0) { 53 | frag.add_uniform('vec2 brushAngle', '_brushAngle'); 54 | frag.write_attrib('uvsp = vec2(uvsp.x * brushAngle.x - uvsp.y * brushAngle.y, uvsp.x * brushAngle.y + uvsp.y * brushAngle.x);'); 55 | } 56 | 57 | frag.add_uniform('float brushScaleX', '_brushScaleX'); 58 | frag.write_attrib('uvsp.x *= brushScaleX;'); 59 | 60 | frag.write_attrib('uvsp += vec2(0.5, 0.5);'); 61 | 62 | frag.write_attrib('if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) discard;'); 63 | } 64 | else { 65 | frag.write_attrib('uvsp.x *= aspectRatio;'); 66 | 67 | if (uvAngle != 0.0) { 68 | frag.add_uniform('vec2 brushAngle', '_brushAngle'); 69 | frag.write_attrib('uvsp = vec2(uvsp.x * brushAngle.x - uvsp.y * brushAngle.y, uvsp.x * brushAngle.y + uvsp.y * brushAngle.x);'); 70 | } 71 | } 72 | 73 | frag.write_attrib('vec2 texCoord = uvsp * brushScale;'); 74 | } 75 | else if (uvType == UVMap) { // TexCoords - uvmap 76 | vert.add_uniform('float brushScale', '_brushScale'); 77 | vert.add_out('vec2 texCoord'); 78 | vert.write('texCoord = tex * brushScale;'); 79 | 80 | if (uvAngle > 0.0) { 81 | vert.add_uniform('vec2 brushAngle', '_brushAngle'); 82 | vert.write('texCoord = vec2(texCoord.x * brushAngle.x - texCoord.y * brushAngle.y, texCoord.x * brushAngle.y + texCoord.y * brushAngle.x);'); 83 | } 84 | } 85 | else { // TexCoords - triplanar 86 | frag.wposition = true; 87 | frag.n = true; 88 | frag.add_uniform('float brushScale', '_brushScale'); 89 | frag.write_attrib('vec3 triWeight = wnormal * wnormal;'); // n * n 90 | frag.write_attrib('float triMax = max(triWeight.x, max(triWeight.y, triWeight.z));'); 91 | frag.write_attrib('triWeight = max(triWeight - triMax * 0.75, 0.0);'); 92 | frag.write_attrib('vec3 texCoordBlend = triWeight * (1.0 / (triWeight.x + triWeight.y + triWeight.z));'); 93 | frag.write_attrib('vec2 texCoord = wposition.yz * brushScale * 0.5;'); 94 | frag.write_attrib('vec2 texCoord1 = wposition.xz * brushScale * 0.5;'); 95 | frag.write_attrib('vec2 texCoord2 = wposition.xy * brushScale * 0.5;'); 96 | 97 | if (uvAngle != 0.0) { 98 | frag.add_uniform('vec2 brushAngle', '_brushAngle'); 99 | frag.write_attrib('texCoord = vec2(texCoord.x * brushAngle.x - texCoord.y * brushAngle.y, texCoord.x * brushAngle.y + texCoord.y * brushAngle.x);'); 100 | frag.write_attrib('texCoord1 = vec2(texCoord1.x * brushAngle.x - texCoord1.y * brushAngle.y, texCoord1.x * brushAngle.y + texCoord1.y * brushAngle.x);'); 101 | frag.write_attrib('texCoord2 = vec2(texCoord2.x * brushAngle.x - texCoord2.y * brushAngle.y, texCoord2.x * brushAngle.y + texCoord2.y * brushAngle.x);'); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /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 arm.node.MakeMaterial; 9 | import arm.render.RenderPathPaint; 10 | import arm.Enums; 11 | 12 | @:access(zui.Zui) 13 | class UIMenubar { 14 | 15 | public static var inst: UIMenubar; 16 | public static inline var defaultMenubarW = 330; 17 | public var workspaceHandle = new Handle({ layout: Horizontal }); 18 | public var menuHandle = new Handle({ layout: Horizontal }); 19 | public var menubarw = defaultMenubarW; 20 | 21 | public function new() { 22 | inst = this; 23 | } 24 | 25 | public function renderUI(g: kha.graphics2.Graphics) { 26 | var ui = UISidebar.inst.ui; 27 | 28 | var panelx = iron.App.x() - UIToolbar.inst.toolbarw; 29 | if (ui.window(menuHandle, panelx, 0, menubarw, Std.int(UIHeader.defaultHeaderH * ui.SCALE()))) { 30 | ui._x += 1; // Prevent "File" button highlight on startup 31 | 32 | Ext.beginMenu(ui); 33 | 34 | if (Config.raw.touch_ui) { 35 | ui._w = Std.int(UIToolbar.defaultToolbarW * ui.SCALE()); 36 | if (iconButton(ui, 0, 2)) BoxPreferences.show(); 37 | if (iconButton(ui, 0, 3)) { 38 | ui.fill(0, 2, -(32 + 4), 32, 0x66000000); 39 | #if (krom_android || krom_ios) 40 | Console.toast(tr("Saving project")); 41 | Project.projectSave(); 42 | #end 43 | App.notifyOnNextFrame(function() { 44 | BoxProjects.show(); 45 | }); 46 | } 47 | if (iconButton(ui, 4, 2)) Project.importAsset(); 48 | if (iconButton(ui, 5, 2)) BoxExport.showTextures(); 49 | if (UIMenu.show && UIMenu.menuCategory == MenuViewport) ui.fill(0, 2, 32 + 4, 32, ui.t.HIGHLIGHT_COL); 50 | if (iconButton(ui, 8, 2)) showMenu(ui, MenuViewport); 51 | if (UIMenu.show && UIMenu.menuCategory == MenuMode) ui.fill(0, 2, 32 + 4, 32, ui.t.HIGHLIGHT_COL); 52 | if (iconButton(ui, 9, 2)) showMenu(ui, MenuMode); 53 | if (UIMenu.show && UIMenu.menuCategory == MenuCamera) ui.fill(0, 2, 32 + 4, 32, ui.t.HIGHLIGHT_COL); 54 | if (iconButton(ui, 10, 2)) showMenu(ui, MenuCamera); 55 | if (UIMenu.show && UIMenu.menuCategory == MenuHelp) ui.fill(0, 2, 32 + 4, 32, ui.t.HIGHLIGHT_COL); 56 | if (iconButton(ui, 11, 2)) showMenu(ui, MenuHelp); 57 | // ui.enabled = History.undos > 0; 58 | if (iconButton(ui, 6, 2)) History.undo(); 59 | // ui.enabled = History.redos > 0; 60 | if (iconButton(ui, 7, 2)) History.redo(); 61 | // ui.enabled = true; 62 | } 63 | else { 64 | var categories = [tr("File"), tr("Edit"), tr("Viewport"), tr("Mode"), tr("Camera"), tr("Help")]; 65 | for (i in 0...categories.length) { 66 | if (Ext.menuButton(ui, categories[i]) || (UIMenu.show && UIMenu.menuCommands == null && ui.isHovered)) { 67 | showMenu(ui, i); 68 | } 69 | } 70 | } 71 | 72 | if (menubarw < ui._x + 10) { 73 | menubarw = Std.int(ui._x + 10); 74 | UIToolbar.inst.toolbarHandle.redraws = 2; 75 | } 76 | 77 | Ext.endMenu(ui); 78 | } 79 | 80 | var panelx = (iron.App.x() - UIToolbar.inst.toolbarw) + menubarw; 81 | if (ui.window(workspaceHandle, panelx, 0, System.windowWidth() - Config.raw.layout[LayoutSidebarW] - menubarw, Std.int(UIHeader.defaultHeaderH * ui.SCALE()))) { 82 | ui.tab(UIHeader.inst.worktab, tr("Paint")); 83 | ui.tab(UIHeader.inst.worktab, tr("Material")); 84 | ui.tab(UIHeader.inst.worktab, tr("Bake")); 85 | if (UIHeader.inst.worktab.changed) { 86 | Context.ddirty = 2; 87 | Context.brushBlendDirty = true; 88 | UIToolbar.inst.toolbarHandle.redraws = 2; 89 | UIHeader.inst.headerHandle.redraws = 2; 90 | UISidebar.inst.hwnd0.redraws = 2; 91 | UISidebar.inst.hwnd1.redraws = 2; 92 | 93 | if (UIHeader.inst.worktab.position == SpacePaint) { 94 | Context.selectTool(ToolBrush); 95 | } 96 | else if (UIHeader.inst.worktab.position == SpaceBake) { 97 | Context.selectTool(ToolBake); 98 | #if (kha_direct3d12 || kha_vulkan) 99 | // Bake in lit mode for now 100 | if (Context.viewportMode == ViewPathTrace) { 101 | Context.viewportMode = ViewLit; 102 | } 103 | #end 104 | } 105 | else if (UIHeader.inst.worktab.position == SpaceMaterial) { 106 | Context.selectTool(ToolPicker); 107 | Layers.updateFillLayers(); 108 | } 109 | 110 | Context.mainObject().skip_context = null; 111 | } 112 | } 113 | } 114 | 115 | function showMenu(ui: Zui, category: Int) { 116 | UIMenu.show = true; 117 | UIMenu.menuCategory = category; 118 | UIMenu.menuX = Std.int(ui._x - ui._w); 119 | UIMenu.menuY = Std.int(Ext.MENUBAR_H(ui)); 120 | if (Config.raw.touch_ui) { 121 | var menuW = Std.int(App.defaultElementW * App.uiMenu.SCALE() * 2.0); 122 | UIMenu.menuX -= Std.int((menuW - ui._w) / 2) + Std.int(UIHeader.inst.headerh / 2); 123 | UIMenu.menuY += 4; 124 | UIMenu.keepOpen = true; 125 | } 126 | } 127 | 128 | function iconButton(ui: Zui, i: Int, j: Int): Bool { 129 | var col = ui.t.WINDOW_BG_COL; 130 | if (col < 0) col += untyped 4294967296; 131 | var light = col > 0xff666666 + 4294967296; 132 | var iconAccent = light ? 0xff666666 : 0xffaaaaaa; 133 | var img = Res.get("icons.k"); 134 | var rect = Res.tile50(img, i, j); 135 | return ui.image(img, iconAccent, null, rect.x, rect.y, rect.w, rect.h) == State.Released; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeParticle.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | import arm.shader.NodeShader; 4 | import arm.shader.NodeShaderContext; 5 | import arm.shader.NodeShaderData; 6 | 7 | class MakeParticle { 8 | 9 | public static function run(data: NodeShaderData): NodeShaderContext { 10 | var context_id = "mesh"; 11 | var con_part:NodeShaderContext = data.add_context({ 12 | name: context_id, 13 | depth_write: false, 14 | compare_mode: "always", 15 | cull_mode: "clockwise", 16 | vertex_elements: [{name: "pos", data: "short4norm"}], 17 | color_attachments: ["R8"] 18 | }); 19 | 20 | var vert = con_part.make_vert(); 21 | var frag = con_part.make_frag(); 22 | frag.ins = vert.outs; 23 | 24 | vert.write_attrib('vec4 spos = vec4(pos.xyz, 1.0);'); 25 | 26 | vert.add_uniform('float brushRadius', '_brushRadius'); 27 | vert.write_attrib('vec3 emitFrom = vec3(fhash(gl_InstanceID), fhash(gl_InstanceID * 2), fhash(gl_InstanceID * 3));'); 28 | vert.write_attrib('emitFrom = emitFrom * brushRadius - brushRadius / 2.0;'); 29 | vert.write_attrib('spos.xyz += emitFrom * vec3(256.0, 256.0, 256.0);'); 30 | 31 | vert.add_uniform('mat4 pd', '_particleData'); 32 | 33 | var str_tex_hash = "float fhash(int n) { return fract(sin(float(n)) * 43758.5453); }\n"; 34 | vert.add_function(str_tex_hash); 35 | vert.add_out('float p_age'); 36 | vert.write('p_age = pd[3][3] - float(gl_InstanceID) * pd[0][1];'); 37 | vert.write('p_age -= p_age * fhash(gl_InstanceID) * pd[2][3];'); 38 | 39 | vert.write('if (pd[0][0] > 0.0 && p_age < 0.0) p_age += float(int(-p_age / pd[0][0]) + 1) * pd[0][0];'); 40 | 41 | vert.add_out('float p_lifetime'); 42 | vert.write('p_lifetime = pd[0][2];'); 43 | vert.write('if (p_age < 0.0 || p_age > p_lifetime) {'); 44 | // vert.write('SPIRV_Cross_Output stage_output;'); 45 | // vert.write('stage_output.svpos /= 0.0;'); 46 | // vert.write('return stage_output;'); 47 | vert.write('spos /= 0.0;'); 48 | vert.write('}'); 49 | 50 | vert.add_out('vec3 p_velocity'); 51 | vert.write('p_velocity = vec3(pd[1][0], pd[1][1], pd[1][2]);'); 52 | vert.write('p_velocity.x += fhash(gl_InstanceID) * pd[1][3] - pd[1][3] / 2.0;'); 53 | vert.write('p_velocity.y += fhash(gl_InstanceID + int(pd[0][3])) * pd[1][3] - pd[1][3] / 2.0;'); 54 | vert.write('p_velocity.z += fhash(gl_InstanceID + 2 * int(pd[0][3])) * pd[1][3] - pd[1][3] / 2.0;'); 55 | vert.write('p_velocity.x += (pd[2][0] * p_age) / 5.0;'); 56 | vert.write('p_velocity.y += (pd[2][1] * p_age) / 5.0;'); 57 | vert.write('p_velocity.z += (pd[2][2] * p_age) / 5.0;'); 58 | 59 | vert.add_out('vec3 p_location'); 60 | vert.write('p_location = p_velocity * p_age;'); 61 | vert.write('spos.xyz += p_location;'); 62 | vert.write('spos.xyz *= vec3(0.01, 0.01, 0.01);'); 63 | 64 | vert.add_uniform('mat4 WVP', '_worldViewProjectionMatrix'); 65 | vert.write('gl_Position = mul(spos, WVP);'); 66 | 67 | vert.add_uniform('vec4 inp', '_inputBrush'); 68 | vert.write('vec2 binp = vec2(inp.x, 1.0 - inp.y);'); 69 | vert.write('binp = binp * 2.0 - 1.0;'); 70 | vert.write('binp *= gl_Position.w;'); 71 | vert.write('gl_Position.xy += binp;'); 72 | 73 | vert.add_out('float p_fade'); 74 | vert.write('p_fade = sin(min((p_age / 8.0) * 3.141592, 3.141592));'); 75 | 76 | frag.add_out('float fragColor'); 77 | frag.write('fragColor = p_fade;'); 78 | 79 | // vert.add_out('vec4 wvpposition'); 80 | // vert.write('wvpposition = gl_Position;'); 81 | // frag.write('vec2 texCoord = wvpposition.xy / wvpposition.w;'); 82 | // frag.add_uniform('sampler2D gbufferD'); 83 | // frag.write('fragColor *= 1.0 - clamp(distance(textureLod(gbufferD, texCoord, 0.0).r, wvpposition.z), 0.0, 1.0);'); 84 | 85 | // Material.finalize(con_part); 86 | con_part.data.shader_from_source = true; 87 | con_part.data.vertex_shader = vert.get(); 88 | con_part.data.fragment_shader = frag.get(); 89 | 90 | return con_part; 91 | } 92 | 93 | public static function mask(vert: NodeShader, frag: NodeShader) { 94 | #if arm_physics 95 | if (Context.particlePhysics) { 96 | vert.add_out('vec4 wpos'); 97 | vert.add_uniform('mat4 W', '_worldMatrix'); 98 | vert.write_attrib('wpos = mul(vec4(pos.xyz, 1.0), W);'); 99 | frag.add_uniform('vec3 particleHit', '_particleHit'); 100 | frag.add_uniform('vec3 particleHitLast', '_particleHitLast'); 101 | 102 | frag.write('vec3 pa = wpos.xyz - particleHit;'); 103 | frag.write('vec3 ba = particleHitLast - particleHit;'); 104 | frag.write('float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);'); 105 | frag.write('dist = length(pa - ba * h) * 10.0;'); 106 | // frag.write('dist = distance(particleHit, wpos.xyz) * 10.0;'); 107 | 108 | frag.write('if (dist > 1.0) discard;'); 109 | frag.write('float str = clamp(pow(1.0 / dist * brushHardness * 0.2, 4.0), 0.0, 1.0) * opacity;'); 110 | frag.write('if (particleHit.x == 0.0 && particleHit.y == 0.0 && particleHit.z == 0.0) str = 0.0;'); 111 | frag.write('if (str == 0.0) discard;'); 112 | return; 113 | } 114 | #end 115 | 116 | frag.add_uniform('sampler2D texparticle', '_texparticle'); 117 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 118 | frag.write('float str = textureLod(texparticle, sp.xy, 0.0).r;'); 119 | #else 120 | frag.write('float str = textureLod(texparticle, vec2(sp.x, (1.0 - sp.y)), 0.0).r;'); 121 | #end 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /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.ExportMesh; 7 | import arm.io.ExportArm; 8 | import arm.io.ImportAsset; 9 | import arm.io.ImportArm; 10 | import arm.ui.BoxExport; 11 | import arm.ui.UIFiles; 12 | import arm.ui.UISidebar; 13 | import arm.Enums; 14 | 15 | class Args { 16 | 17 | static var useArgs = false; 18 | static var assetPath = ""; 19 | static var reimportMesh = false; 20 | static var exportTextures = false; 21 | static var exportTexturesType = ""; 22 | static var exportTexturesPreset = ""; 23 | static var exportTexturesPath = ""; 24 | static var exportMesh = false; 25 | static var exportMeshPath = ""; 26 | static var exportMaterial = false; 27 | static var exportMaterialPath = ""; 28 | static var background = false; 29 | 30 | public static function parse() { 31 | if (Krom.getArgCount() > 1) { 32 | useArgs = true; 33 | 34 | var i = 0; 35 | while (i < Krom.getArgCount()) { 36 | // Process each arg 37 | var currentArg = Krom.getArg(i); 38 | if (currentArg == "--reload-mesh") { 39 | reimportMesh = true; 40 | } 41 | else if (currentArg == "--export-textures" && (i + 3) <= Krom.getArgCount()) { 42 | exportTextures = true; 43 | ++i; 44 | exportTexturesType = Krom.getArg(i); 45 | ++i; 46 | exportTexturesPreset = Krom.getArg(i); 47 | ++i; 48 | exportTexturesPath = Krom.getArg(i); 49 | 50 | } 51 | else if (currentArg == "--export-mesh" && (i + 1) <= Krom.getArgCount()) { 52 | exportMesh = true; 53 | ++i; 54 | exportMeshPath = Krom.getArg(i); 55 | } 56 | else if (currentArg == "--export-material" && (i + 1) <= Krom.getArgCount()) { 57 | exportMaterial = true; 58 | ++i; 59 | exportMaterialPath = Krom.getArg(i); 60 | } 61 | else if (currentArg == "--b" || currentArg == "--background") { 62 | background = true; 63 | } 64 | else if (Path.isProject(currentArg)) { 65 | Project.filepath = currentArg; 66 | } 67 | else if (Path.isMesh(currentArg) || Path.isTexture(currentArg) || (i > 1 && !currentArg.startsWith("-") && Path.isFolder(currentArg))) { 68 | assetPath = currentArg; 69 | } 70 | ++i; 71 | } 72 | } 73 | } 74 | 75 | public static function run() { 76 | if (useArgs) { 77 | iron.App.notifyOnInit(function() { 78 | if (Project.filepath != "") { 79 | ImportArm.runProject(Project.filepath); 80 | } 81 | else if (assetPath != "") { 82 | ImportAsset.run(assetPath, -1, -1, false); 83 | if (Path.isTexture(assetPath)) UISidebar.inst.show2DView(View2DAsset); 84 | } 85 | else if (reimportMesh) { 86 | Project.reimportMesh(); 87 | } 88 | 89 | if (exportTextures) { 90 | if (exportTexturesType == "png" || 91 | exportTexturesType == "jpg" || 92 | exportTexturesType == "exr16" || 93 | exportTexturesType == "exr32") { 94 | if (Path.isFolder(exportTexturesPath)) { 95 | // Applying the correct format type from args 96 | if (exportTexturesType == "png") { 97 | App.bitsHandle.position = Bits8; 98 | Context.formatType = FormatPng; 99 | } 100 | else if (exportTexturesType == "jpg") { 101 | App.bitsHandle.position = Bits8; 102 | Context.formatType = FormatJpg; 103 | } 104 | else if (exportTexturesType == "exr16") { 105 | App.bitsHandle.position = Bits16; 106 | } 107 | else if (exportTexturesType == "exr32") { 108 | App.bitsHandle.position = Bits32; 109 | } 110 | 111 | Context.layersExport = ExportVisible; 112 | 113 | // Get export preset and apply the correct one from args 114 | BoxExport.files = File.readDirectory(Path.data() + Path.sep + "export_presets"); 115 | for (i in 0...BoxExport.files.length) { 116 | BoxExport.files[i] = BoxExport.files[i].substr(0, BoxExport.files[i].length - 5); // Strip .json 117 | } 118 | 119 | var file = "export_presets/" + BoxExport.files[0] + ".json"; 120 | for (f in BoxExport.files) if (f == exportTexturesPreset) { 121 | file = "export_presets/" + BoxExport.files[BoxExport.files.indexOf(f)] + ".json"; 122 | } 123 | 124 | iron.data.Data.getBlob(file, function(blob: kha.Blob) { 125 | BoxExport.preset = haxe.Json.parse(blob.toString()); 126 | iron.data.Data.deleteBlob("export_presets/" + file); 127 | }); 128 | 129 | // Export queue 130 | function _init() { 131 | ExportTexture.run(exportTexturesPath); 132 | } 133 | iron.App.notifyOnInit(_init); 134 | } 135 | else { 136 | trace("Invalid export directory"); 137 | } 138 | } 139 | else { 140 | trace("Invalid texture type"); 141 | } 142 | } 143 | else if (exportMesh) { 144 | if (Path.isFolder(exportMeshPath)) { 145 | var f = UIFiles.filename; 146 | if (f == "") f = tr("untitled"); 147 | ExportMesh.run(exportMeshPath + Path.sep + f, null, false); 148 | } 149 | else { 150 | trace("Invalid export directory"); 151 | } 152 | } 153 | else if (exportMaterial) { 154 | Context.writeIconOnExport = true; 155 | ExportArm.runMaterial(exportMaterialPath); 156 | } 157 | 158 | if (background) kha.System.stop(); 159 | }); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Sources/arm/node/MakeBlur.hx: -------------------------------------------------------------------------------- 1 | package arm.node; 2 | 3 | import arm.shader.NodeShader; 4 | 5 | class MakeBlur { 6 | 7 | public static function run(vert: NodeShader, frag: NodeShader) { 8 | frag.write('vec2 gbufferSizeLocal = gbufferSize;'); // TODO: spirv workaround 9 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 10 | frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSizeLocal.x, sp.y * gbufferSizeLocal.y), 0).ba;'); 11 | #else 12 | frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSizeLocal.x, (1.0 - sp.y) * gbufferSizeLocal.y), 0).ba;'); 13 | #end 14 | 15 | frag.write('vec3 basecol = vec3(0.0, 0.0, 0.0);'); 16 | frag.write('float roughness = 0.0;'); 17 | frag.write('float metallic = 0.0;'); 18 | frag.write('float occlusion = 0.0;'); 19 | frag.write('vec3 nortan = vec3(0.0, 0.0, 0.0);'); 20 | frag.write('float height = 0.0;'); 21 | frag.write('float mat_opacity = 1.0;'); 22 | frag.write('float opacity = 0.0;'); 23 | if (Context.material.paintEmis) { 24 | frag.write('float emis = 0.0;'); 25 | } 26 | if (Context.material.paintSubs) { 27 | frag.write('float subs = 0.0;'); 28 | } 29 | 30 | frag.add_uniform('vec2 texpaintSize', '_texpaintSize'); 31 | frag.write('vec2 texpaintSizeLocal = texpaintSize;'); // TODO: spirv workaround 32 | frag.write('float blur_step = 1.0 / texpaintSizeLocal.x;'); 33 | if (Context.blurDirectional) { 34 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal) 35 | frag.write('const float blur_weight[7] = {1.0 / 28.0, 2.0 / 28.0, 3.0 / 28.0, 4.0 / 28.0, 5.0 / 28.0, 6.0 / 28.0, 7.0 / 28.0};'); 36 | #else 37 | frag.write('const float blur_weight[7] = float[](1.0 / 28.0, 2.0 / 28.0, 3.0 / 28.0, 4.0 / 28.0, 5.0 / 28.0, 6.0 / 28.0, 7.0 / 28.0);'); 38 | #end 39 | frag.add_uniform('vec3 brushDirection', '_brushDirection'); 40 | frag.write('vec2 blur_direction = brushDirection.yx;'); 41 | frag.write('for (int i = 0; i < 7; ++i) {'); 42 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal || kha_vulkan) 43 | frag.write('vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSizeLocal.x, (sp.y + blur_direction.y * blur_step * float(i)) * gbufferSizeLocal.y), 0).ba;'); 44 | #else 45 | frag.write('vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSizeLocal.x, (1.0 - (sp.y + blur_direction.y * blur_step * float(i))) * gbufferSizeLocal.y), 0).ba;'); 46 | #end 47 | frag.write('vec4 texpaint_sample = texture(texpaint_undo, texCoordInp2);'); 48 | frag.write('opacity += texpaint_sample.a * blur_weight[i];'); 49 | frag.write('basecol += texpaint_sample.rgb * blur_weight[i];'); 50 | frag.write('vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp2) * blur_weight[i];'); 51 | frag.write('roughness += texpaint_pack_sample.g;'); 52 | frag.write('metallic += texpaint_pack_sample.b;'); 53 | frag.write('occlusion += texpaint_pack_sample.r;'); 54 | frag.write('height += texpaint_pack_sample.a;'); 55 | frag.write('nortan += texture(texpaint_nor_undo, texCoordInp2).rgb * blur_weight[i];'); 56 | frag.write('}'); 57 | } 58 | else { 59 | #if (kha_direct3d11 || kha_direct3d12 || kha_metal) 60 | frag.write('const float blur_weight[15] = {0.034619 / 2.0, 0.044859 / 2.0, 0.055857 / 2.0, 0.066833 / 2.0, 0.076841 / 2.0, 0.084894 / 2.0, 0.090126 / 2.0, 0.09194 / 2.0, 0.090126 / 2.0, 0.084894 / 2.0, 0.076841 / 2.0, 0.066833 / 2.0, 0.055857 / 2.0, 0.044859 / 2.0, 0.034619 / 2.0};'); 61 | #else 62 | frag.write('const float blur_weight[15] = float[](0.034619 / 2.0, 0.044859 / 2.0, 0.055857 / 2.0, 0.066833 / 2.0, 0.076841 / 2.0, 0.084894 / 2.0, 0.090126 / 2.0, 0.09194 / 2.0, 0.090126 / 2.0, 0.084894 / 2.0, 0.076841 / 2.0, 0.066833 / 2.0, 0.055857 / 2.0, 0.044859 / 2.0, 0.034619 / 2.0);'); 63 | #end 64 | // X 65 | frag.write('for (int i = -7; i <= 7; ++i) {'); 66 | frag.write('vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(blur_step * float(i), 0.0));'); 67 | frag.write('opacity += texpaint_sample.a * blur_weight[i + 7];'); 68 | frag.write('basecol += texpaint_sample.rgb * blur_weight[i + 7];'); 69 | frag.write('vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(blur_step * float(i), 0.0)) * blur_weight[i + 7];'); 70 | frag.write('roughness += texpaint_pack_sample.g;'); 71 | frag.write('metallic += texpaint_pack_sample.b;'); 72 | frag.write('occlusion += texpaint_pack_sample.r;'); 73 | frag.write('height += texpaint_pack_sample.a;'); 74 | frag.write('nortan += texture(texpaint_nor_undo, texCoordInp + vec2(blur_step * float(i), 0.0)).rgb * blur_weight[i + 7];'); 75 | frag.write('}'); 76 | // Y 77 | frag.write('for (int j = -7; j <= 7; ++j) {'); 78 | frag.write('vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(0.0, blur_step * float(j)));'); 79 | frag.write('opacity += texpaint_sample.a * blur_weight[j + 7];'); 80 | frag.write('basecol += texpaint_sample.rgb * blur_weight[j + 7];'); 81 | frag.write('vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(0.0, blur_step * float(j))) * blur_weight[j + 7];'); 82 | frag.write('roughness += texpaint_pack_sample.g;'); 83 | frag.write('metallic += texpaint_pack_sample.b;'); 84 | frag.write('occlusion += texpaint_pack_sample.r;'); 85 | frag.write('height += texpaint_pack_sample.a;'); 86 | frag.write('nortan += texture(texpaint_nor_undo, texCoordInp + vec2(0.0, blur_step * float(j))).rgb * blur_weight[j + 7];'); 87 | frag.write('}'); 88 | } 89 | frag.write('opacity *= brushOpacity;'); 90 | } 91 | } 92 | --------------------------------------------------------------------------------