Particle brush
This commit is contained in:
+13
-2
@@ -425,7 +425,7 @@ class App extends iron.Trait {
|
||||
var menuButtonW = Std.int(ui.ELEMENT_W() * 0.5);
|
||||
var px = panelx + menuButtonW * menuCategory;
|
||||
var py = UITrait.inst.headerh;
|
||||
var menuItems = [5, 2, 8, 2];
|
||||
var menuItems = [5, 2, 8, 4];
|
||||
var ph = 24 * menuItems[menuCategory] * ui.SCALE;
|
||||
|
||||
g.color = ui.t.SEPARATOR_COL;
|
||||
@@ -493,7 +493,7 @@ class App extends iron.Trait {
|
||||
// ui.button("Wireframe", Left);
|
||||
}
|
||||
else if (menuCategory == 3) {
|
||||
if (ui.button("Manual...", Left)) {
|
||||
if (ui.button("Manual", Left)) {
|
||||
if (kha.System.systemId == "Windows") {
|
||||
Krom.sysCommand('explorer "https://armorpaint.org/manual"');
|
||||
}
|
||||
@@ -504,6 +504,17 @@ class App extends iron.Trait {
|
||||
Krom.sysCommand('open "https://armorpaint.org/manual"');
|
||||
}
|
||||
}
|
||||
if (ui.button("Report Bug", Left)) {
|
||||
if (kha.System.systemId == "Windows") {
|
||||
Krom.sysCommand('explorer "https://github.com/armory3d/armorpaint/issues"');
|
||||
}
|
||||
else if (kha.System.systemId == "Linux") {
|
||||
Krom.sysCommand('xdg-explorer "https://github.com/armory3d/armorpaint/issues"');
|
||||
}
|
||||
else {
|
||||
Krom.sysCommand('open "https://github.com/armory3d/armorpaint/issues"');
|
||||
}
|
||||
}
|
||||
if (ui.button("Check for Updates...", Left)) {
|
||||
// Retrieve latest version number
|
||||
var outFile = Krom.getFilesLocation() + '/' + iron.data.Data.dataPath + "update.txt";
|
||||
|
||||
@@ -106,11 +106,6 @@ class MaterialBuilder {
|
||||
vert.add_out('vec4 ndc');
|
||||
vert.write_attrib('ndc = mul(vec4(pos.xyz, 1.0), WVP);');
|
||||
|
||||
if (UITrait.inst.selectedTool == ToolParticle) {
|
||||
frag.add_uniform('sampler2D texparticle', '_texparticle');
|
||||
|
||||
}
|
||||
|
||||
frag.write_attrib('vec3 sp = vec3((ndc.xyz / ndc.w) * 0.5 + 0.5);');
|
||||
frag.write_attrib('sp.y = 1.0 - sp.y;');
|
||||
frag.add_uniform('float paintDepthBias', '_paintDepthBias');
|
||||
@@ -142,15 +137,16 @@ class MaterialBuilder {
|
||||
UITrait.inst.selectedTool == ToolEraser ||
|
||||
UITrait.inst.selectedTool == ToolClone ||
|
||||
UITrait.inst.selectedTool == ToolBlur ||
|
||||
UITrait.inst.selectedTool == ToolParticle ||
|
||||
decal) {
|
||||
|
||||
#if (kha_opengl || kha_webgl)
|
||||
frag.write('if (sp.z > textureLod(paintdb, vec2(sp.x, 1.0 - bsp.y), 0.0).r) { discard; }');
|
||||
frag.write('if (sp.z > textureLod(paintdb, vec2(sp.x, 1.0 - sp.y), 0.0).r) { discard; }');
|
||||
#else
|
||||
frag.write('if (sp.z > textureLod(paintdb, vec2(sp.x, bsp.y), 0.0).r) { discard; }');
|
||||
frag.write('if (sp.z > textureLod(paintdb, vec2(sp.x, sp.y), 0.0).r) { discard; }');
|
||||
#end
|
||||
|
||||
if (decal) {
|
||||
if (decal || UITrait.inst.selectedTool == ToolParticle) {
|
||||
frag.write('float dist = 0.0;');
|
||||
}
|
||||
else {
|
||||
@@ -373,11 +369,20 @@ class MaterialBuilder {
|
||||
frag.write('opacity *= textureLod(textexttool, texCoord, 0.0).r;');
|
||||
}
|
||||
|
||||
frag.write('float str = clamp((brushRadius - dist) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
|
||||
|
||||
if (UITrait.inst.mirrorX && UITrait.inst.selectedTool == ToolBrush) {
|
||||
frag.write('str += clamp((brushRadius - dist2) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
|
||||
frag.write('str = clamp(str, 0.0, 1.0);');
|
||||
if (UITrait.inst.selectedTool == ToolParticle) { // particle mask
|
||||
frag.add_uniform('sampler2D texparticle', '_texparticle');
|
||||
#if (kha_opengl || kha_webgl)
|
||||
frag.write('float str = textureLod(texparticle, vec2(sp.x, (1.0 - sp.y)), 0).r;');
|
||||
#else
|
||||
frag.write('float str = textureLod(texparticle, sp.xy, 0).r;');
|
||||
#end
|
||||
}
|
||||
else { // brush cursor mask
|
||||
frag.write('float str = clamp((brushRadius - dist) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
|
||||
if (UITrait.inst.mirrorX && UITrait.inst.selectedTool == ToolBrush) {
|
||||
frag.write('str += clamp((brushRadius - dist2) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
|
||||
frag.write('str = clamp(str, 0.0, 1.0);');
|
||||
}
|
||||
}
|
||||
|
||||
// Manual blending to preserve memory
|
||||
@@ -610,11 +615,11 @@ class MaterialBuilder {
|
||||
}
|
||||
|
||||
public static function make_particle(data:CyclesShaderData):CyclesShaderContext {
|
||||
var context_id = 'particle';
|
||||
var context_id = 'mesh';
|
||||
var con_part:CyclesShaderContext = data.add_context({
|
||||
name: context_id,
|
||||
depth_write: false,
|
||||
compare_mode: 'less',
|
||||
compare_mode: 'always',
|
||||
cull_mode: 'clockwise',
|
||||
vertex_elements: [{name: "pos", data: 'short4norm'}]
|
||||
});
|
||||
@@ -625,6 +630,11 @@ class MaterialBuilder {
|
||||
|
||||
vert.write_attrib('vec4 spos = vec4(pos.xyz, 1.0);');
|
||||
|
||||
vert.add_uniform('float brushRadius', '_brushRadius');
|
||||
vert.write_attrib('vec3 emitFrom = vec3(fhash(gl_InstanceID), fhash(gl_InstanceID * 2), fhash(gl_InstanceID * 3));');
|
||||
vert.write_attrib('emitFrom = emitFrom * brushRadius - brushRadius / 2;');
|
||||
vert.write_attrib('spos.xyz += emitFrom * 256;');
|
||||
|
||||
vert.add_uniform('mat4 pd', '_particleData');
|
||||
|
||||
var str_tex_hash = "float fhash(float n) { return fract(sin(n) * 43758.5453); }\n";
|
||||
@@ -660,6 +670,24 @@ class MaterialBuilder {
|
||||
vert.add_uniform('mat4 WVP', '_worldViewProjectionMatrix');
|
||||
vert.write('gl_Position = mul(spos, WVP);');
|
||||
|
||||
vert.add_uniform('vec4 inp', '_inputBrush');
|
||||
vert.write('vec2 binp = vec2(inp.x, 1.0 - inp.y);');
|
||||
vert.write('binp = binp * 2.0 - 1.0;');
|
||||
vert.write('binp *= gl_Position.w;');
|
||||
vert.write('gl_Position.xy += binp;');
|
||||
|
||||
vert.add_out('float p_fade');
|
||||
vert.write('p_fade = sin(min((p_age / 8) * 3.141592, 3.141592));');
|
||||
|
||||
frag.add_out('float fragColor');
|
||||
frag.write('fragColor = p_fade;');
|
||||
|
||||
// vert.add_out('vec4 wvpposition');
|
||||
// vert.write('wvpposition = gl_Position;');
|
||||
// frag.write('vec2 texCoord = wvpposition.xy / wvpposition.w;');
|
||||
// frag.add_uniform('sampler2D paintdb');
|
||||
// frag.write('fragColor *= 1.0 - clamp(distance(textureLod(paintdb, texCoord, 0).r, wvpposition.z), 0.0, 1.0);');
|
||||
|
||||
// Cycles.finalize(con_part);
|
||||
con_part.data.shader_from_source = true;
|
||||
con_part.data.vertex_shader = vert.get();
|
||||
|
||||
@@ -45,6 +45,10 @@ class BrushOutputNode extends LogicNode {
|
||||
UITrait.inst.lastPaintX = UITrait.inst.paintVec.x;
|
||||
UITrait.inst.lastPaintY = UITrait.inst.paintVec.y;
|
||||
|
||||
if (UITrait.inst.selectedTool == ToolParticle) {
|
||||
UITrait.inst.painted = 0; // Always paint particles
|
||||
}
|
||||
|
||||
var decal = UITrait.inst.selectedTool == ToolDecal || UITrait.inst.selectedTool == ToolText;
|
||||
var paintFrames = decal ? 1 : 8;
|
||||
|
||||
|
||||
@@ -172,10 +172,10 @@ class RenderPathDeferred {
|
||||
|
||||
// Only enable vxao in scene mode
|
||||
var restoreGI = false;
|
||||
if (armory.data.Config.raw.rp_gi && UITrait.inst.worktab.position == 0) { // paint
|
||||
armory.data.Config.raw.rp_gi = false;
|
||||
restoreGI = true;
|
||||
}
|
||||
// if (armory.data.Config.raw.rp_gi && UITrait.inst.worktab.position == 0) { // paint
|
||||
// armory.data.Config.raw.rp_gi = false;
|
||||
// restoreGI = true;
|
||||
// }
|
||||
|
||||
var ssaa4 = armory.data.Config.raw.rp_supersample == 4 ? true : false;
|
||||
|
||||
@@ -216,6 +216,23 @@ class RenderPathDeferred {
|
||||
}
|
||||
|
||||
if (UITrait.inst.paintDirty()) {
|
||||
if (UITrait.inst.selectedTool == ToolParticle) {
|
||||
path.setTarget("texparticle");
|
||||
path.clearTarget(0x00000000);
|
||||
path.bindTarget("_paintdb", "paintdb");
|
||||
|
||||
var mo:iron.object.MeshObject = cast iron.Scene.active.getChild("ParticleEmitter");
|
||||
mo.visible = true;
|
||||
mo.render(path.currentG, "mesh", @:privateAccess path.bindParams);
|
||||
mo.visible = false;
|
||||
|
||||
mo = cast iron.Scene.active.getChild("Particle");
|
||||
mo.visible = true;
|
||||
mo.render(path.currentG, "mesh", @:privateAccess path.bindParams);
|
||||
mo.visible = false;
|
||||
@:privateAccess path.end(path.currentG);
|
||||
}
|
||||
|
||||
if (UITrait.inst.selectedTool == ToolColorId) {
|
||||
path.setTarget("texpaint_colorid");
|
||||
path.clearTarget(0xff000000);
|
||||
@@ -262,12 +279,6 @@ class RenderPathDeferred {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (UITrait.inst.selectedTool == ToolParticle) {
|
||||
path.setTarget("texparticle");
|
||||
path.clearTarget(0xff000000);
|
||||
path.bindTarget("_paintdb", "paintdb");
|
||||
path.drawMeshes("particle");
|
||||
}
|
||||
else {
|
||||
if (UITrait.inst.selectedTool == ToolBake) {
|
||||
if (initVoxels) {
|
||||
|
||||
+95
-60
@@ -123,7 +123,7 @@ class UITrait extends iron.Trait {
|
||||
public var textToolHandle = new Zui.Handle({position: 0});
|
||||
public var decalMaskImage:kha.Image = null;
|
||||
public var decalMaskHandle = new Zui.Handle({position: 0});
|
||||
public var particleMaterial:MaterialData;
|
||||
public var particleMaterial:MaterialData = null;
|
||||
|
||||
var _onBrush:Array<Int->Void> = [];
|
||||
|
||||
@@ -534,7 +534,8 @@ class UITrait extends iron.Trait {
|
||||
selectedTool == ToolDecal ||
|
||||
selectedTool == ToolText ||
|
||||
selectedTool == ToolClone ||
|
||||
selectedTool == ToolBlur) {
|
||||
selectedTool == ToolBlur ||
|
||||
selectedTool == ToolParticle) {
|
||||
if (kb.started("f")) {
|
||||
brushCanLock = true;
|
||||
mouse.lock();
|
||||
@@ -686,6 +687,14 @@ class UITrait extends iron.Trait {
|
||||
cloneDeltaY = (cloneStartY - mouse.y) / iron.App.h();
|
||||
cloneStartX = -1;
|
||||
}
|
||||
else if (selectedTool == ToolParticle) {
|
||||
// Reset particles
|
||||
var emitter:iron.object.MeshObject = cast iron.Scene.active.getChild("ParticleEmitter");
|
||||
var psys = emitter.particleSystems[0];
|
||||
@:privateAccess psys.time = 0;
|
||||
// @:privateAccess psys.time = @:privateAccess psys.seed * @:privateAccess psys.animtime;
|
||||
// @:privateAccess psys.seed++;
|
||||
}
|
||||
}
|
||||
brushTime += iron.system.Time.delta;
|
||||
for (f in _onBrush) f(0);
|
||||
@@ -980,12 +989,6 @@ class UITrait extends iron.Trait {
|
||||
arm.App.resize();
|
||||
}
|
||||
|
||||
function f32(ar:Array<kha.FastFloat>):kha.arrays.Float32Array {
|
||||
var res = new kha.arrays.Float32Array(ar.length);
|
||||
for (i in 0...ar.length) res[i] = ar[i];
|
||||
return res;
|
||||
}
|
||||
|
||||
function selectTool(i:Int) {
|
||||
selectedTool = i;
|
||||
autoFillHandle.selected = false; // Auto-disable
|
||||
@@ -1014,58 +1017,8 @@ class UITrait extends iron.Trait {
|
||||
}
|
||||
|
||||
if (selectedTool == ToolParticle) {
|
||||
var raw:TParticleData = {
|
||||
name: "Particles",
|
||||
type: 0,
|
||||
loop: true,
|
||||
render_emitter: false,
|
||||
count: 100,
|
||||
frame_start: 0,
|
||||
frame_end: 100,
|
||||
lifetime: 5,
|
||||
lifetime_random: 0,
|
||||
emit_from: 1,
|
||||
object_align_factor: f32([0, 0, -1]),
|
||||
factor_random: 0,
|
||||
physics_type: 0,
|
||||
particle_size: 1,
|
||||
size_random: 0,
|
||||
mass: 1,
|
||||
instance_object: "Particle",
|
||||
weight_gravity: 1
|
||||
};
|
||||
// iron.Scene.active.gravity = f32([0, 0, -9.8]);
|
||||
var pd = new iron.data.ParticleData(raw, function(pd:iron.data.ParticleData) {});
|
||||
var particle_refs:Array<TParticleReference> = [
|
||||
{
|
||||
name: "Particles",
|
||||
particle: "Particles",
|
||||
seed: 0
|
||||
}
|
||||
];
|
||||
// obj.setupParticleSystem("Scene", particle_refs[0]);
|
||||
// obj.is_particle = true;
|
||||
// obj.particle_refs = particle_refs;
|
||||
|
||||
{
|
||||
var t = new iron.RenderPath.RenderTargetRaw();
|
||||
t.name = "texparticle";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.format = 'R8';
|
||||
t.scale = armory.renderpath.Inc.getSuperSampling();
|
||||
iron.RenderPath.active.createRenderTarget(t);
|
||||
}
|
||||
|
||||
iron.data.Data.cachedMaterials.remove("SceneMaterial2");
|
||||
iron.data.Data.cachedShaders.remove("Material2_data");
|
||||
iron.data.Data.cachedSceneRaws.remove("Material2_data");
|
||||
// iron.data.Data.cachedBlobs.remove("Material2_data.arm");
|
||||
iron.data.Data.getMaterial("Scene", "Material2", function(md:iron.data.MaterialData) {
|
||||
md.name = "MaterialParticle";
|
||||
particleMaterial = md;
|
||||
UINodes.inst.parseParticleMaterial();
|
||||
});
|
||||
initParticle();
|
||||
UINodes.inst.parseParticleMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2419,6 +2372,88 @@ class UITrait extends iron.Trait {
|
||||
if (fontName == 'default.ttf') return UITrait.inst.ui.ops.font;
|
||||
return Importer.fontMap.get(fontName);
|
||||
}
|
||||
|
||||
function f32(ar:Array<kha.FastFloat>):kha.arrays.Float32Array {
|
||||
var res = new kha.arrays.Float32Array(ar.length);
|
||||
for (i in 0...ar.length) res[i] = ar[i];
|
||||
return res;
|
||||
}
|
||||
|
||||
function initParticle() {
|
||||
if (particleMaterial != null) return;
|
||||
|
||||
var raw:TParticleData = {
|
||||
name: "Particles",
|
||||
type: 0,
|
||||
loop: false,
|
||||
render_emitter: false,
|
||||
count: 1000,
|
||||
frame_start: 0,
|
||||
frame_end: 1000,
|
||||
lifetime: 400,
|
||||
lifetime_random: 0.5,
|
||||
emit_from: 1,
|
||||
object_align_factor: f32([0, 0, -40]),
|
||||
factor_random: 2.0,
|
||||
physics_type: 0,
|
||||
particle_size: 1.0,
|
||||
size_random: 0,
|
||||
mass: 1,
|
||||
instance_object: "Particle",
|
||||
weight_gravity: 1
|
||||
};
|
||||
// iron.Scene.active.raw.gravity = f32([0, 0, -9.8]);
|
||||
iron.Scene.active.raw.particle_datas = [raw];
|
||||
var particle_refs:Array<TParticleReference> = [
|
||||
{
|
||||
name: "Particles",
|
||||
particle: "Particles",
|
||||
seed: 0
|
||||
}
|
||||
];
|
||||
|
||||
{
|
||||
var t = new iron.RenderPath.RenderTargetRaw();
|
||||
t.name = "texparticle";
|
||||
t.width = 0;
|
||||
t.height = 0;
|
||||
t.format = 'R8';
|
||||
t.scale = armory.renderpath.Inc.getSuperSampling();
|
||||
iron.RenderPath.active.createRenderTarget(t);
|
||||
}
|
||||
|
||||
for (mat in iron.Scene.active.raw.material_datas) {
|
||||
if (mat.name == 'Material2') {
|
||||
var m = Reflect.copy(mat);
|
||||
m.name = 'MaterialParticle';
|
||||
iron.Scene.active.raw.material_datas.push(m);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
iron.data.Data.getMaterial("Scene", "MaterialParticle", function(md:iron.data.MaterialData) {
|
||||
particleMaterial = md;
|
||||
|
||||
for (obj in iron.Scene.active.raw.objects) {
|
||||
if (obj.name == '.Sphere') {
|
||||
var particle = Reflect.copy(obj);
|
||||
particle.name = 'Particle';
|
||||
particle.is_particle = true;
|
||||
particle.material_refs = ['MaterialParticle'];
|
||||
iron.Scene.active.raw.objects.push(particle);
|
||||
for (i in 0...16) particle.transform.values[i] *= 0.01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
iron.Scene.active.spawnObject(".Sphere", null, function(o:Object) {
|
||||
var mo:MeshObject = cast o;
|
||||
mo.name = "ParticleEmitter";
|
||||
mo.raw.particle_refs = particle_refs;
|
||||
mo.setupParticleSystem("Scene", particle_refs[0]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@:enum abstract PaintTool(Int) from Int to Int {
|
||||
|
||||
Reference in New Issue
Block a user