diff --git a/Assets/icon/icon.ico b/Assets/icon/icon.ico new file mode 100644 index 00000000..a4bc0ca5 Binary files /dev/null and b/Assets/icon/icon.ico differ diff --git a/Assets/icon/icon.png b/Assets/icon/icon.png new file mode 100644 index 00000000..6c45a6c8 Binary files /dev/null and b/Assets/icon/icon.png differ diff --git a/Assets/raw/tool_clone.png b/Assets/raw/tool_clone.png new file mode 100644 index 00000000..557c188b Binary files /dev/null and b/Assets/raw/tool_clone.png differ diff --git a/Bundled/icons/tool_clone.png b/Bundled/icons/tool_clone.png new file mode 100644 index 00000000..b5b6a2aa Binary files /dev/null and b/Bundled/icons/tool_clone.png differ diff --git a/Sources/arm/MaterialBuilder.hx b/Sources/arm/MaterialBuilder.hx index 4a789dc9..6fbf887c 100644 --- a/Sources/arm/MaterialBuilder.hx +++ b/Sources/arm/MaterialBuilder.hx @@ -119,7 +119,7 @@ class MaterialBuilder { frag.add_uniform('float brushOpacity', '_brushOpacity'); frag.add_uniform('float brushHardness', '_brushHardness'); - if (UITrait.inst.brushType == 0 || UITrait.inst.brushType == 1 || decal) { // Draw / Erase + if (UITrait.inst.brushType == 0 || UITrait.inst.brushType == 1 || decal || UITrait.inst.brushType == 8) { // Draw / Erase #if (kha_opengl || kha_webgl) frag.write('if (sp.z > textureLod(paintdb, vec2(sp.x, 1.0 - bsp.y), 0).r) { discard; }'); @@ -237,23 +237,55 @@ class MaterialBuilder { } } - Cycles.parse_height_as_channel = true; - var sout = Cycles.parse(UINodes.inst.canvas, con_paint, vert, frag, null, null, null, matcon); - Cycles.parse_height_as_channel = false; - var base = sout.out_basecol; - var rough = sout.out_roughness; - var met = sout.out_metallic; - var occ = sout.out_occlusion; - var nortan = Cycles.out_normaltan; - var height = sout.out_height; - var opac = sout.out_opacity; - frag.write('vec3 basecol = $base;'); - frag.write('float roughness = $rough;'); - frag.write('float metallic = $met;'); - frag.write('float occlusion = $occ;'); - frag.write('vec3 nortan = $nortan;'); - frag.write('float height = $height;'); - frag.write('float opacity = $opac * brushOpacity;'); + var clone = UITrait.inst.brushType == 8; + if (clone) { + frag.add_uniform('sampler2D gbuffer2'); + frag.add_uniform('vec2 gbufferSize', '_gbufferSize'); + frag.add_uniform('vec2 cloneDelta', '_cloneDelta'); + #if (kha_opengl || kha_webgl) + frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.x + cloneDelta.x) * gbufferSize.x, (1.0 - (sp.y + cloneDelta.y)) * gbufferSize.y), 0).ba;'); + #else + frag.write('vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.x + cloneDelta.x) * gbufferSize.x, (sp.y + cloneDelta.y) * gbufferSize.y), 0).ba;'); + #end + frag.add_uniform('sampler2D texpaint_undo', '_texpaint_undo'); + frag.add_uniform('sampler2D texpaint_nor_undo', '_texpaint_nor_undo'); + frag.add_uniform('sampler2D texpaint_pack_undo', '_texpaint_pack_undo'); + frag.write('vec3 texpaint_pack_sample = textureLod(texpaint_pack_undo, texCoordInp, 0).rgb;'); + + var base = 'textureLod(texpaint_undo, texCoordInp, 0).rgb;'; + var rough = 'texpaint_pack_sample.g;'; + var met = 'texpaint_pack_sample.b;'; + var occ = 'texpaint_pack_sample.r;'; + var nortan = 'textureLod(texpaint_nor_undo, texCoordInp, 0).rgb;'; + var height = '0.0'; + var opac = '1.0'; + frag.write('vec3 basecol = $base;'); + frag.write('float roughness = $rough;'); + frag.write('float metallic = $met;'); + frag.write('float occlusion = $occ;'); + frag.write('vec3 nortan = $nortan;'); + frag.write('float height = $height;'); + frag.write('float opacity = $opac * brushOpacity;'); + } + else { + Cycles.parse_height_as_channel = true; + var sout = Cycles.parse(UINodes.inst.canvas, con_paint, vert, frag, null, null, null, matcon); + Cycles.parse_height_as_channel = false; + var base = sout.out_basecol; + var rough = sout.out_roughness; + var met = sout.out_metallic; + var occ = sout.out_occlusion; + var nortan = Cycles.out_normaltan; + var height = sout.out_height; + var opac = sout.out_opacity; + frag.write('vec3 basecol = $base;'); + frag.write('float roughness = $rough;'); + frag.write('float metallic = $met;'); + frag.write('float occlusion = $occ;'); + frag.write('vec3 nortan = $nortan;'); + frag.write('float height = $height;'); + frag.write('float opacity = $opac * brushOpacity;'); + } if (UITrait.inst.brushType == 6) { // Text tool frag.add_uniform('sampler2D textexttool', '_textexttool'); @@ -673,7 +705,8 @@ class MaterialBuilder { frag.write('vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;'); var writeTC = (UITrait.inst.brushType == 2 && UITrait.inst.fillTypeHandle.position == 1) || // Face fill - UITrait.inst.brushType == 4; // Colorid pick + UITrait.inst.brushType == 4 || // Colorid pick + UITrait.inst.brushType == 8; // Clone if (writeTC) { frag.write('fragColor[2] = vec4(posa - posb, texCoord.xy);'); diff --git a/Sources/arm/Uniforms.hx b/Sources/arm/Uniforms.hx index 79403c5c..e737c700 100644 --- a/Sources/arm/Uniforms.hx +++ b/Sources/arm/Uniforms.hx @@ -54,14 +54,12 @@ class Uniforms { return vec2; } else if (link == '_texcoloridSize') { - vec2.set(0, 0, 0); if (UITrait.inst.assets.length == 0) return vec2; var img = UITrait.inst.getImage(UITrait.inst.assets[UITrait.inst.colorIdHandle.position]); vec2.set(img.width, img.height, 0); return vec2; } else if (link == '_textrianglemapSize') { - vec2.set(0, 0, 0); var res = Config.getTextureRes(); vec2.set(res, res, 0); return vec2; @@ -76,6 +74,10 @@ class Uniforms { // trace(iron.App.w()); // var f = armory.renderpath.Inc.getSuperSampling(); } + else if (link == '_cloneDelta') { + vec2.set(UITrait.inst.cloneDeltaX, UITrait.inst.cloneDeltaY, 0); + return vec2; + } return null; } @@ -113,6 +115,23 @@ class Uniforms { else if (link == "_texshapetool") { // Opacity map for shape return UITrait.inst.shapeToolImage; } + else if (link == "_texpaint_undo") { + // if (UITrait.inst.pushUndo && UITrait.inst.C.undo_steps > 0) + var i = UITrait.inst.undoI - 1 < 0 ? UITrait.inst.C.undo_steps - 1 : UITrait.inst.undoI - 1; + return iron.RenderPath.active.renderTargets.get("texpaint_undo" + i).image; + } + else if (link == "_texpaint_nor_undo") { + var i = UITrait.inst.undoI - 1 < 0 ? UITrait.inst.C.undo_steps - 1 : UITrait.inst.undoI - 1; + return iron.RenderPath.active.renderTargets.get("texpaint_nor_undo" + i).image; + } + else if (link == "_texpaint_pack_undo") { + var i = UITrait.inst.undoI - 1 < 0 ? UITrait.inst.C.undo_steps - 1 : UITrait.inst.undoI - 1; + return iron.RenderPath.active.renderTargets.get("texpaint_pack_undo" + i).image; + } + else if (link == "_texpaint_opt_undo") { + var i = UITrait.inst.undoI - 1 < 0 ? UITrait.inst.C.undo_steps - 1 : UITrait.inst.undoI - 1; + return iron.RenderPath.active.renderTargets.get("texpaint_opt_undo" + i).image; + } return null; } } diff --git a/Sources/arm/renderpath/RenderPathDeferred.hx b/Sources/arm/renderpath/RenderPathDeferred.hx index 3a84bee2..0109f089 100644 --- a/Sources/arm/renderpath/RenderPathDeferred.hx +++ b/Sources/arm/renderpath/RenderPathDeferred.hx @@ -246,7 +246,9 @@ class RenderPathDeferred { } // Read texcoords from gbuffer - if (UITrait.inst.brushType == 2 && UITrait.inst.fillTypeHandle.position == 1) { // Face fill + var readTC = (UITrait.inst.brushType == 2 && UITrait.inst.fillTypeHandle.position == 1) || // Face fill + UITrait.inst.brushType == 8; // Clone + if (readTC) { path.bindTarget("gbuffer2", "gbuffer2"); } diff --git a/Sources/arm/trait/FlyCamera.hx b/Sources/arm/trait/FlyCamera.hx index 0ddb6b1a..26d5df1d 100644 --- a/Sources/arm/trait/FlyCamera.hx +++ b/Sources/arm/trait/FlyCamera.hx @@ -30,7 +30,8 @@ class FlyCamera extends iron.Trait { UITrait.inst.isScrolling || UITrait.inst.cameraControls != 2 || mouse.x < 0 || - mouse.x > iron.App.w()) return; + mouse.x > iron.App.w() || + !mouse.down("right")) return; var camera = iron.Scene.active.camera; diff --git a/Sources/arm/ui/UITrait.hx b/Sources/arm/ui/UITrait.hx index 2cfd2fe1..b7c1a452 100644 --- a/Sources/arm/ui/UITrait.hx +++ b/Sources/arm/ui/UITrait.hx @@ -118,6 +118,10 @@ class UITrait extends iron.Trait { public var lastPaintY = -1.0; public var painted = 0; public var brushTime = 0.0; + public var cloneStartX = -1.0; + public var cloneStartY = -1.0; + public var cloneDeltaX = 0.0; + public var cloneDeltaY = 0.0; public var selectedObject:iron.object.Object; public var paintObject:iron.object.MeshObject; @@ -341,7 +345,20 @@ class UITrait extends iron.Trait { var scale = C.window_scale; ui = new Zui( { theme: arm.App.theme, font: arm.App.font, scaleFactor: scale, color_wheel: arm.App.color_wheel } ); - loadBundled(['cursor.png', 'empty.jpg', 'tool_draw.png', 'tool_eraser.png', 'tool_fill.png', 'tool_bake.png', 'tool_colorid.png', 'tool_decal.png', 'tool_text.png','tool_shape.png', 'tool_particle.png'], done); + loadBundled([ + 'cursor.png', + 'empty.jpg', + 'tool_draw.png', + 'tool_eraser.png', + 'tool_fill.png', + 'tool_bake.png', + 'tool_colorid.png', + 'tool_decal.png', + 'tool_text.png', + 'tool_shape.png', + 'tool_clone.png', + 'tool_particle.png' + ], done); } public function showMessage(s:String) { @@ -483,15 +500,17 @@ class UITrait extends iron.Trait { else if (kb.started("6")) selectMaterial(5); } - if (!ctrl) { + if (!ctrl && !mouse.down("right")) { if (kb.started("b")) selectTool(0); // Brush - else if (kb.started("r")) selectTool(1); // Erase + else if (kb.started("e")) selectTool(1); // Erase else if (kb.started("g")) selectTool(2); // Fill else if (kb.started("k")) selectTool(3); // Bake else if (kb.started("c")) selectTool(4); // Color id - else if (kb.started("l")) selectTool(5); // Decal + else if (kb.started("d")) selectTool(5); // Decal else if (kb.started("t")) selectTool(6); // Text - else if (kb.started("h")) selectTool(7); // Shape + else if (kb.started("s")) selectTool(7); // Shape + else if (kb.started("l")) selectTool(8); // Clone + // else if (kb.started("p")) selectTool(8); // Particle } // Radius @@ -631,14 +650,26 @@ class UITrait extends iron.Trait { if (down && !kb.down("control")) { if (mouse.x < iron.App.w() && mouse.x > iron.App.x() && mouse.y < iron.App.h() && mouse.y > iron.App.y()) { - if (brushTime == 0 && C.undo_steps > 0) { // Paint started - pushUndo = true; - if (projectPath != "") { - kha.Window.get(0).title = arm.App.filenameHandle.text + "* - ArmorPaint"; - } + + if (brushType == 8 && kb.down("alt")) { // Clone source + cloneStartX = mouse.x; + cloneStartY = mouse.y; + } + else { + if (brushTime == 0 && C.undo_steps > 0) { // Paint started + pushUndo = true; + if (projectPath != "") { + kha.Window.get(0).title = arm.App.filenameHandle.text + "* - ArmorPaint"; + } + if (brushType == 8 && cloneStartX >= 0.0) { // Clone delta + cloneDeltaX = (cloneStartX - mouse.x) / iron.App.w(); + cloneDeltaY = (cloneStartY - mouse.y) / iron.App.h(); + cloneStartX = -1; + } + } + brushTime += iron.system.Time.delta; + for (f in _onBrush) f(0); } - brushTime += iron.system.Time.delta; - for (f in _onBrush) f(0); } } @@ -1011,7 +1042,8 @@ class UITrait extends iron.Trait { var img6 = bundled.get("tool_decal.png"); var img7 = bundled.get("tool_text.png"); var img8 = bundled.get("tool_shape.png"); - var img9 = bundled.get("tool_particle.png"); + var img9 = bundled.get("tool_clone.png"); + // var img10 = bundled.get("tool_particle.png"); ui._x += 2; if (brushType == 0) ui.rect(-1, -1, img1.width + 2, img1.height + 2, ui.t.HIGHLIGHT_COL, 2); @@ -1023,7 +1055,7 @@ class UITrait extends iron.Trait { ui._x += 2; if (brushType == 1) ui.rect(-1, -1, img1.width + 2, img1.height + 2, ui.t.HIGHLIGHT_COL, 2); if (ui.image(img2) == State.Started) selectTool(1); - if (ui.isHovered) ui.tooltip("Eraser (R)"); + if (ui.isHovered) ui.tooltip("Eraser (E)"); ui._x -= 2; ui._y += 2; @@ -1044,14 +1076,14 @@ class UITrait extends iron.Trait { ui._x += 2; if (brushType == 4) ui.rect(-1, -1, img1.width + 2, img1.height + 2, ui.t.HIGHLIGHT_COL, 2); if (ui.image(img5) == State.Started) selectTool(4); - if (ui.isHovered) ui.tooltip("Color ID (F)"); + if (ui.isHovered) ui.tooltip("Color ID (C)"); ui._x -= 2; ui._y += 2; ui._x += 2; if (brushType == 5) ui.rect(-1, -1, img1.width + 2, img1.height + 2, ui.t.HIGHLIGHT_COL, 2); if (ui.image(img6) == State.Started) selectTool(5); - if (ui.isHovered) ui.tooltip("Decal (L)"); + if (ui.isHovered) ui.tooltip("Decal (D)"); ui._x -= 2; ui._y += 2; @@ -1065,7 +1097,14 @@ class UITrait extends iron.Trait { ui._x += 2; if (brushType == 7) ui.rect(-1, -1, img1.width + 2, img1.height + 2, ui.t.HIGHLIGHT_COL, 2); if (ui.image(img8) == State.Started) selectTool(7); - if (ui.isHovered) ui.tooltip("Shape (H)"); + if (ui.isHovered) ui.tooltip("Shape (S)"); + ui._x -= 2; + ui._y += 2; + + ui._x += 2; + if (brushType == 8) ui.rect(-1, -1, img1.width + 2, img1.height + 2, ui.t.HIGHLIGHT_COL, 2); + if (ui.image(img9) == State.Started) selectTool(8); + if (ui.isHovered) ui.tooltip("Clone (L) - Hold ALT to set source"); ui._x -= 2; ui._y += 2;