luboslenco 1 year ago
parent
commit
5927db63b3
100 changed files with 8761 additions and 10965 deletions
  1. 0 253
      armorforge/Sources/TabObjects.ts
  2. 250 0
      armorforge/Sources/tab_objects.ts
  3. 0 120
      armorlab/Sources/MakeMaterial.ts
  4. 0 237
      armorlab/Sources/MakeMesh.ts
  5. 0 277
      armorlab/Sources/MakePaint.ts
  6. 0 43
      armorlab/Sources/NodesBrush.ts
  7. 0 305
      armorlab/Sources/RenderPathPaint.ts
  8. 0 148
      armorlab/Sources/UINodesExt.ts
  9. 117 0
      armorlab/Sources/make_material.ts
  10. 234 0
      armorlab/Sources/make_mesh.ts
  11. 274 0
      armorlab/Sources/make_paint.ts
  12. 8 8
      armorlab/Sources/nodes/BrushOutputNode.ts
  13. 5 5
      armorlab/Sources/nodes/ImageTextureNode.ts
  14. 19 19
      armorlab/Sources/nodes/InpaintNode.ts
  15. 63 63
      armorlab/Sources/nodes/PhotoToPBRNode.ts
  16. 5 5
      armorlab/Sources/nodes/RGBNode.ts
  17. 15 15
      armorlab/Sources/nodes/TextToPhotoNode.ts
  18. 9 9
      armorlab/Sources/nodes/TilingNode.ts
  19. 11 11
      armorlab/Sources/nodes/UpscaleNode.ts
  20. 6 6
      armorlab/Sources/nodes/VarianceNode.ts
  21. 40 0
      armorlab/Sources/nodes_brush.ts
  22. 302 0
      armorlab/Sources/render_path_paint.ts
  23. 145 0
      armorlab/Sources/ui_nodes_ext.ts
  24. 0 131
      armorpaint/Sources/ImportFolder.ts
  25. 0 159
      armorpaint/Sources/MakeBake.ts
  26. 0 92
      armorpaint/Sources/MakeBlur.ts
  27. 0 94
      armorpaint/Sources/MakeBrush.ts
  28. 0 35
      armorpaint/Sources/MakeClone.ts
  29. 0 46
      armorpaint/Sources/MakeColorIdPicker.ts
  30. 0 50
      armorpaint/Sources/MakeDiscard.ts
  31. 0 493
      armorpaint/Sources/MakeMaterial.ts
  32. 0 488
      armorpaint/Sources/MakeMesh.ts
  33. 0 159
      armorpaint/Sources/MakeMeshPreview.ts
  34. 0 71
      armorpaint/Sources/MakeNodePreview.ts
  35. 0 483
      armorpaint/Sources/MakePaint.ts
  36. 0 118
      armorpaint/Sources/MakeParticle.ts
  37. 0 99
      armorpaint/Sources/MakeTexcoord.ts
  38. 0 43
      armorpaint/Sources/NodesBrush.ts
  39. 0 936
      armorpaint/Sources/RenderPathPaint.ts
  40. 0 152
      armorpaint/Sources/RenderPathPreview.ts
  41. 0 32
      armorpaint/Sources/SlotBrush.ts
  42. 0 21
      armorpaint/Sources/SlotFont.ts
  43. 0 692
      armorpaint/Sources/SlotLayer.ts
  44. 0 70
      armorpaint/Sources/SlotMaterial.ts
  45. 0 1078
      armorpaint/Sources/TabLayers.ts
  46. 128 0
      armorpaint/Sources/import_folder.ts
  47. 156 0
      armorpaint/Sources/make_bake.ts
  48. 89 0
      armorpaint/Sources/make_blur.ts
  49. 91 0
      armorpaint/Sources/make_brush.ts
  50. 32 0
      armorpaint/Sources/make_clone.ts
  51. 43 0
      armorpaint/Sources/make_colorid_picker.ts
  52. 47 0
      armorpaint/Sources/make_discard.ts
  53. 490 0
      armorpaint/Sources/make_material.ts
  54. 485 0
      armorpaint/Sources/make_mesh.ts
  55. 156 0
      armorpaint/Sources/make_mesh_preview.ts
  56. 68 0
      armorpaint/Sources/make_node_preview.ts
  57. 480 0
      armorpaint/Sources/make_paint.ts
  58. 115 0
      armorpaint/Sources/make_particle.ts
  59. 96 0
      armorpaint/Sources/make_texcoord.ts
  60. 3 3
      armorpaint/Sources/nodes/BrushOutputNode.ts
  61. 40 0
      armorpaint/Sources/nodes_brush.ts
  62. 933 0
      armorpaint/Sources/render_path_paint.ts
  63. 149 0
      armorpaint/Sources/render_path_preview.ts
  64. 30 0
      armorpaint/Sources/slot_brush.ts
  65. 18 0
      armorpaint/Sources/slot_font.ts
  66. 689 0
      armorpaint/Sources/slot_layer.ts
  67. 68 0
      armorpaint/Sources/slot_material.ts
  68. 1075 0
      armorpaint/Sources/tab_layers.ts
  69. 0 98
      armorsculpt/Sources/ExportObj.ts
  70. 0 192
      armorsculpt/Sources/ImportMesh.ts
  71. 0 39
      armorsculpt/Sources/MakeBrush.ts
  72. 0 325
      armorsculpt/Sources/MakeMaterial.ts
  73. 0 338
      armorsculpt/Sources/MakeMesh.ts
  74. 0 159
      armorsculpt/Sources/MakeMeshPreview.ts
  75. 0 91
      armorsculpt/Sources/MakeSculpt.ts
  76. 0 395
      armorsculpt/Sources/TabLayers.ts
  77. 95 0
      armorsculpt/Sources/export_obj.ts
  78. 189 0
      armorsculpt/Sources/import_mesh.ts
  79. 36 0
      armorsculpt/Sources/make_brush.ts
  80. 322 0
      armorsculpt/Sources/make_material.ts
  81. 335 0
      armorsculpt/Sources/make_mesh.ts
  82. 156 0
      armorsculpt/Sources/make_mesh_preview.ts
  83. 88 0
      armorsculpt/Sources/make_sculpt.ts
  84. 42 42
      armorsculpt/Sources/nodes/BrushOutputNode.ts
  85. 392 0
      armorsculpt/Sources/tab_layers.ts
  86. 0 307
      base/Sources/ContextFormat.ts
  87. 0 224
      base/Sources/TabBrowser.ts
  88. 0 150
      base/Sources/TabBrushes.ts
  89. 0 64
      base/Sources/TabConsole.ts
  90. 0 149
      base/Sources/TabFonts.ts
  91. 0 32
      base/Sources/TabHistory.ts
  92. 0 333
      base/Sources/TabMaterials.ts
  93. 0 187
      base/Sources/TabMeshes.ts
  94. 0 18
      base/Sources/TabParticles.ts
  95. 0 27
      base/Sources/TabPlugins.ts
  96. 0 77
      base/Sources/TabScript.ts
  97. 0 253
      base/Sources/TabSwatches.ts
  98. 0 268
      base/Sources/TabTextures.ts
  99. 110 116
      base/Sources/base.ts
  100. 12 12
      base/Sources/box_preferences.ts

+ 0 - 253
armorforge/Sources/TabObjects.ts

@@ -1,253 +0,0 @@
-
-class TabObjects {
-
-	static materialId = 0;
-
-	static roundfp = (f: f32, precision = 2): f32 => {
-		f *= math_pow(10, precision);
-		return math_round(f) / math_pow(10, precision);
-	}
-
-	static draw = (htab: zui_handle_t) => {
-		let ui = ui_base_ui;
-		if (zui_tab(htab, tr("Objects"))) {
-			zui_begin_sticky();
-			zui_row([1 / 4]);
-			if (zui_button("Import")) {
-				project_import_mesh(false, () => {
-					object_set_parent(project_paint_objects.pop().base, null);
-				});
-			}
-			zui_end_sticky();
-
-			if (zui_panel(zui_handle("tabobjects_0", {selected: true}), "Outliner")) {
-				// ui.indent();
-				ui._y -= zui_ELEMENT_OFFSET(ui);
-
-				let listX = ui._x;
-				let listW = ui._w;
-
-				let lineCounter = 0;
-				let drawList = (listHandle: zui_handle_t, currentObject: object_t) => {
-					if (currentObject.name.charAt(0) == ".") return; // Hidden
-					let b = false;
-
-					// Highlight every other line
-					if (lineCounter % 2 == 0) {
-						g2_set_color(ui.t.SEPARATOR_COL);
-						g2_fill_rect(0, ui._y, ui._window_w, zui_ELEMENT_H(ui));
-						g2_set_color(0xffffffff);
-					}
-
-					// Highlight selected line
-					if (currentObject == context_context_raw.selected_object) {
-						g2_set_color(0xff205d9c);
-						g2_fill_rect(0, ui._y, ui._window_w, zui_ELEMENT_H(ui));
-						g2_set_color(0xffffffff);
-					}
-
-					if (currentObject.children.length > 0) {
-						zui_row([1 / 13, 12 / 13]);
-						b = zui_panel(zui_nest(listHandle, lineCounter, {selected: true}), "", true, false, false);
-						zui_text(currentObject.name);
-					}
-					else {
-						ui._x += 18; // Sign offset
-
-						// Draw line that shows parent relations
-						g2_set_color(ui.t.ACCENT_COL);
-						g2_draw_line(ui._x - 10, ui._y + zui_ELEMENT_H(ui) / 2, ui._x, ui._y + zui_ELEMENT_H(ui) / 2);
-						g2_set_color(0xffffffff);
-
-						zui_text(currentObject.name);
-						ui._x -= 18;
-					}
-
-					lineCounter++;
-					// Undo applied offset for row drawing caused by endElement() in Zui.hx
-					ui._y -= zui_ELEMENT_OFFSET(ui);
-
-					if (ui.is_released) {
-						context_context_raw.selected_object = currentObject;
-					}
-
-					if (ui.is_hovered && ui.input_released_r) {
-						ui_menu_draw((ui: zui_t) => {
-							if (ui_menu_button(ui, "Assign Material")) {
-								TabObjects.materialId++;
-
-								for (let sh of _scene_raw.shader_datas) {
-									if (sh.name == "Material_data") {
-										let s: shader_data_t = json_parse(json_stringify(sh));
-										s.name = "TempMaterial_data" + TabObjects.materialId;
-										_scene_raw.shader_datas.push(s);
-										break;
-									}
-								}
-
-								for (let mat of _scene_raw.material_datas) {
-									if (mat.name == "Material") {
-										let m: material_data_t = json_parse(json_stringify(mat));
-										m.name = "TempMaterial" + TabObjects.materialId;
-										m.shader = "TempMaterial_data" + TabObjects.materialId;
-										_scene_raw.material_datas.push(m);
-										break;
-									}
-								}
-
-								let md: material_data_t = data_get_material("Scene", "TempMaterial" + TabObjects.materialId);
-								let mo: mesh_object_t = currentObject.ext;
-								mo.materials = [md];
-								MakeMaterial.make_material_parse_mesh_preview_material(md);
-							}
-						}, 1);
-					}
-
-					if (b) {
-						let currentY = ui._y;
-						for (let child of currentObject.children) {
-							// ui.indent();
-							drawList(listHandle, child);
-							// ui.unindent();
-						}
-
-						// Draw line that shows parent relations
-						g2_set_color(ui.t.ACCENT_COL);
-						g2_draw_line(ui._x + 14, currentY, ui._x + 14, ui._y - zui_ELEMENT_H(ui) / 2);
-						g2_set_color(0xffffffff);
-					}
-				}
-				for (let c of _scene_root.children) {
-					drawList(zui_handle("tabobjects_1"), c);
-				}
-
-				// ui.unindent();
-			}
-
-			if (zui_panel(zui_handle("tabobjects_2", {selected: true}), 'Properties')) {
-				// ui.indent();
-
-				if (context_context_raw.selected_object != null) {
-					let h = zui_handle("tabobjects_3");
-					h.selected = context_context_raw.selected_object.visible;
-					context_context_raw.selected_object.visible = zui_check(h, "Visible");
-
-					let t = context_context_raw.selected_object.transform;
-					let localPos = t.loc;
-					let worldPos = vec4_create(transform_world_x(t), transform_world_y(t), transform_world_z(t), 1.0);
-					let scale = t.scale;
-					let rot = quat_get_euler(t.rot);
-					let dim = t.dim;
-					vec4_mult(rot, 180 / 3.141592);
-					let f = 0.0;
-
-					zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
-					zui_text("Loc");
-
-					h = zui_handle("tabobjects_4");
-					h.text = TabObjects.roundfp(localPos.x) + "";
-					f = parseFloat(zui_text_input(h, "X"));
-					if (h.changed) localPos.x = f;
-
-					h = zui_handle("tabobjects_5");
-					h.text = TabObjects.roundfp(localPos.y) + "";
-					f = parseFloat(zui_text_input(h, "Y"));
-					if (h.changed) localPos.y = f;
-
-					h = zui_handle("tabobjects_6");
-					h.text = TabObjects.roundfp(localPos.z) + "";
-					f = parseFloat(zui_text_input(h, "Z"));
-					if (h.changed) localPos.z = f;
-
-					zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
-					zui_text("Rotation");
-
-					h = zui_handle("tabobjects_7");
-					h.text = TabObjects.roundfp(rot.x) + "";
-					f = parseFloat(zui_text_input(h, "X"));
-					let changed = false;
-					if (h.changed) { changed = true; rot.x = f; }
-
-					h = zui_handle("tabobjects_8");
-					h.text = TabObjects.roundfp(rot.y) + "";
-					f = parseFloat(zui_text_input(h, "Y"));
-					if (h.changed) { changed = true; rot.y = f; }
-
-					h = zui_handle("tabobjects_9");
-					h.text = TabObjects.roundfp(rot.z) + "";
-					f = parseFloat(zui_text_input(h, "Z"));
-					if (h.changed) { changed = true; rot.z = f; }
-
-					if (changed && context_context_raw.selected_object.name != "Scene") {
-						vec4_mult(rot, 3.141592 / 180);
-						quat_from_euler(context_context_raw.selected_object.transform.rot, rot.x, rot.y, rot.z);
-						transform_build_matrix(context_context_raw.selected_object.transform);
-						// ///if arm_physics
-						// if (rb != null) rb.syncTransform();
-						// ///end
-					}
-
-					zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
-					zui_text("Scale");
-
-					h = zui_handle("tabobjects_10");
-					h.text = TabObjects.roundfp(scale.x) + "";
-					f = parseFloat(zui_text_input(h, "X"));
-					if (h.changed) scale.x = f;
-
-					h = zui_handle("tabobjects_11");
-					h.text = TabObjects.roundfp(scale.y) + "";
-					f = parseFloat(zui_text_input(h, "Y"));
-					if (h.changed) scale.y = f;
-
-					h = zui_handle("tabobjects_12");
-					h.text = TabObjects.roundfp(scale.z) + "";
-					f = parseFloat(zui_text_input(h, "Z"));
-					if (h.changed) scale.z = f;
-
-					zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
-					zui_text("Dimensions");
-
-					h = zui_handle("tabobjects_13");
-					h.text = TabObjects.roundfp(dim.x) + "";
-					f = parseFloat(zui_text_input(h, "X"));
-					if (h.changed) dim.x = f;
-
-					h = zui_handle("tabobjects_14");
-					h.text = TabObjects.roundfp(dim.y) + "";
-					f = parseFloat(zui_text_input(h, "Y"));
-					if (h.changed) dim.y = f;
-
-					h = zui_handle("tabobjects_15");
-					h.text = TabObjects.roundfp(dim.z) + "";
-					f = parseFloat(zui_text_input(h, "Z"));
-					if (h.changed) dim.z = f;
-
-					context_context_raw.selected_object.transform.dirty = true;
-
-					if (context_context_raw.selected_object.name == "Scene") {
-						let p = scene_world;
-						p.strength = zui_slider(zui_handle("tabobjects_16", {value: p.strength}), "Environment", 0.0, 5.0, true);
-					}
-					else if (context_context_raw.selected_object.ext_type == "light_object_t") {
-						let light = context_context_raw.selected_object.ext;
-						let lightHandle = zui_handle("tabobjects_17");
-						lightHandle.value = light.data.strength / 10;
-						light.data.strength = zui_slider(lightHandle, "Strength", 0.0, 5.0, true) * 10;
-					}
-					else if (context_context_raw.selected_object.ext_type == "camera_object_t") {
-						let cam = context_context_raw.selected_object.ext;
-						let fovHandle = zui_handle("tabobjects_18");
-						fovHandle.value = math_floor(cam.data.fov * 100) / 100;
-						cam.data.fov = zui_slider(fovHandle, "FoV", 0.3, 2.0, true);
-						if (fovHandle.changed) {
-							camera_object_build_proj(cam);
-						}
-					}
-				}
-
-				// ui.unindent();
-			}
-		}
-	}
-}

+ 250 - 0
armorforge/Sources/tab_objects.ts

@@ -0,0 +1,250 @@
+
+let tab_objects_material_id = 0;
+
+function tab_objects_roundfp(f: f32, precision = 2): f32 {
+	f *= math_pow(10, precision);
+	return math_round(f) / math_pow(10, precision);
+}
+
+function tab_objects_draw(htab: zui_handle_t) {
+	let ui = ui_base_ui;
+	if (zui_tab(htab, tr("Objects"))) {
+		zui_begin_sticky();
+		zui_row([1 / 4]);
+		if (zui_button("Import")) {
+			project_import_mesh(false, () => {
+				object_set_parent(project_paint_objects.pop().base, null);
+			});
+		}
+		zui_end_sticky();
+
+		if (zui_panel(zui_handle("tabobjects_0", {selected: true}), "Outliner")) {
+			// ui.indent();
+			ui._y -= zui_ELEMENT_OFFSET(ui);
+
+			let listX = ui._x;
+			let listW = ui._w;
+
+			let lineCounter = 0;
+			let drawList = (listHandle: zui_handle_t, currentObject: object_t) => {
+				if (currentObject.name.charAt(0) == ".") return; // Hidden
+				let b = false;
+
+				// Highlight every other line
+				if (lineCounter % 2 == 0) {
+					g2_set_color(ui.t.SEPARATOR_COL);
+					g2_fill_rect(0, ui._y, ui._window_w, zui_ELEMENT_H(ui));
+					g2_set_color(0xffffffff);
+				}
+
+				// Highlight selected line
+				if (currentObject == context_context_raw.selected_object) {
+					g2_set_color(0xff205d9c);
+					g2_fill_rect(0, ui._y, ui._window_w, zui_ELEMENT_H(ui));
+					g2_set_color(0xffffffff);
+				}
+
+				if (currentObject.children.length > 0) {
+					zui_row([1 / 13, 12 / 13]);
+					b = zui_panel(zui_nest(listHandle, lineCounter, {selected: true}), "", true, false, false);
+					zui_text(currentObject.name);
+				}
+				else {
+					ui._x += 18; // Sign offset
+
+					// Draw line that shows parent relations
+					g2_set_color(ui.t.ACCENT_COL);
+					g2_draw_line(ui._x - 10, ui._y + zui_ELEMENT_H(ui) / 2, ui._x, ui._y + zui_ELEMENT_H(ui) / 2);
+					g2_set_color(0xffffffff);
+
+					zui_text(currentObject.name);
+					ui._x -= 18;
+				}
+
+				lineCounter++;
+				// Undo applied offset for row drawing caused by endElement() in Zui.hx
+				ui._y -= zui_ELEMENT_OFFSET(ui);
+
+				if (ui.is_released) {
+					context_context_raw.selected_object = currentObject;
+				}
+
+				if (ui.is_hovered && ui.input_released_r) {
+					ui_menu_draw((ui: zui_t) => {
+						if (ui_menu_button(ui, "Assign Material")) {
+							tab_objects_material_id++;
+
+							for (let sh of _scene_raw.shader_datas) {
+								if (sh.name == "Material_data") {
+									let s: shader_data_t = json_parse(json_stringify(sh));
+									s.name = "TempMaterial_data" + tab_objects_material_id;
+									_scene_raw.shader_datas.push(s);
+									break;
+								}
+							}
+
+							for (let mat of _scene_raw.material_datas) {
+								if (mat.name == "Material") {
+									let m: material_data_t = json_parse(json_stringify(mat));
+									m.name = "TempMaterial" + tab_objects_material_id;
+									m.shader = "TempMaterial_data" + tab_objects_material_id;
+									_scene_raw.material_datas.push(m);
+									break;
+								}
+							}
+
+							let md: material_data_t = data_get_material("Scene", "TempMaterial" + tab_objects_material_id);
+							let mo: mesh_object_t = currentObject.ext;
+							mo.materials = [md];
+							MakeMaterial.make_material_parse_mesh_preview_material(md);
+						}
+					}, 1);
+				}
+
+				if (b) {
+					let currentY = ui._y;
+					for (let child of currentObject.children) {
+						// ui.indent();
+						drawList(listHandle, child);
+						// ui.unindent();
+					}
+
+					// Draw line that shows parent relations
+					g2_set_color(ui.t.ACCENT_COL);
+					g2_draw_line(ui._x + 14, currentY, ui._x + 14, ui._y - zui_ELEMENT_H(ui) / 2);
+					g2_set_color(0xffffffff);
+				}
+			}
+			for (let c of _scene_root.children) {
+				drawList(zui_handle("tabobjects_1"), c);
+			}
+
+			// ui.unindent();
+		}
+
+		if (zui_panel(zui_handle("tabobjects_2", {selected: true}), 'Properties')) {
+			// ui.indent();
+
+			if (context_context_raw.selected_object != null) {
+				let h = zui_handle("tabobjects_3");
+				h.selected = context_context_raw.selected_object.visible;
+				context_context_raw.selected_object.visible = zui_check(h, "Visible");
+
+				let t = context_context_raw.selected_object.transform;
+				let localPos = t.loc;
+				let worldPos = vec4_create(transform_world_x(t), transform_world_y(t), transform_world_z(t), 1.0);
+				let scale = t.scale;
+				let rot = quat_get_euler(t.rot);
+				let dim = t.dim;
+				vec4_mult(rot, 180 / 3.141592);
+				let f = 0.0;
+
+				zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
+				zui_text("Loc");
+
+				h = zui_handle("tabobjects_4");
+				h.text = roundfp(localPos.x) + "";
+				f = parseFloat(zui_text_input(h, "X"));
+				if (h.changed) localPos.x = f;
+
+				h = zui_handle("tabobjects_5");
+				h.text = roundfp(localPos.y) + "";
+				f = parseFloat(zui_text_input(h, "Y"));
+				if (h.changed) localPos.y = f;
+
+				h = zui_handle("tabobjects_6");
+				h.text = roundfp(localPos.z) + "";
+				f = parseFloat(zui_text_input(h, "Z"));
+				if (h.changed) localPos.z = f;
+
+				zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
+				zui_text("Rotation");
+
+				h = zui_handle("tabobjects_7");
+				h.text = roundfp(rot.x) + "";
+				f = parseFloat(zui_text_input(h, "X"));
+				let changed = false;
+				if (h.changed) { changed = true; rot.x = f; }
+
+				h = zui_handle("tabobjects_8");
+				h.text = roundfp(rot.y) + "";
+				f = parseFloat(zui_text_input(h, "Y"));
+				if (h.changed) { changed = true; rot.y = f; }
+
+				h = zui_handle("tabobjects_9");
+				h.text = roundfp(rot.z) + "";
+				f = parseFloat(zui_text_input(h, "Z"));
+				if (h.changed) { changed = true; rot.z = f; }
+
+				if (changed && context_context_raw.selected_object.name != "Scene") {
+					vec4_mult(rot, 3.141592 / 180);
+					quat_from_euler(context_context_raw.selected_object.transform.rot, rot.x, rot.y, rot.z);
+					transform_build_matrix(context_context_raw.selected_object.transform);
+					// ///if arm_physics
+					// if (rb != null) rb.syncTransform();
+					// ///end
+				}
+
+				zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
+				zui_text("Scale");
+
+				h = zui_handle("tabobjects_10");
+				h.text = roundfp(scale.x) + "";
+				f = parseFloat(zui_text_input(h, "X"));
+				if (h.changed) scale.x = f;
+
+				h = zui_handle("tabobjects_11");
+				h.text = roundfp(scale.y) + "";
+				f = parseFloat(zui_text_input(h, "Y"));
+				if (h.changed) scale.y = f;
+
+				h = zui_handle("tabobjects_12");
+				h.text = roundfp(scale.z) + "";
+				f = parseFloat(zui_text_input(h, "Z"));
+				if (h.changed) scale.z = f;
+
+				zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
+				zui_text("Dimensions");
+
+				h = zui_handle("tabobjects_13");
+				h.text = roundfp(dim.x) + "";
+				f = parseFloat(zui_text_input(h, "X"));
+				if (h.changed) dim.x = f;
+
+				h = zui_handle("tabobjects_14");
+				h.text = roundfp(dim.y) + "";
+				f = parseFloat(zui_text_input(h, "Y"));
+				if (h.changed) dim.y = f;
+
+				h = zui_handle("tabobjects_15");
+				h.text = roundfp(dim.z) + "";
+				f = parseFloat(zui_text_input(h, "Z"));
+				if (h.changed) dim.z = f;
+
+				context_context_raw.selected_object.transform.dirty = true;
+
+				if (context_context_raw.selected_object.name == "Scene") {
+					let p = scene_world;
+					p.strength = zui_slider(zui_handle("tabobjects_16", {value: p.strength}), "Environment", 0.0, 5.0, true);
+				}
+				else if (context_context_raw.selected_object.ext_type == "light_object_t") {
+					let light = context_context_raw.selected_object.ext;
+					let lightHandle = zui_handle("tabobjects_17");
+					lightHandle.value = light.data.strength / 10;
+					light.data.strength = zui_slider(lightHandle, "Strength", 0.0, 5.0, true) * 10;
+				}
+				else if (context_context_raw.selected_object.ext_type == "camera_object_t") {
+					let cam = context_context_raw.selected_object.ext;
+					let fovHandle = zui_handle("tabobjects_18");
+					fovHandle.value = math_floor(cam.data.fov * 100) / 100;
+					cam.data.fov = zui_slider(fovHandle, "FoV", 0.3, 2.0, true);
+					if (fovHandle.changed) {
+						camera_object_build_proj(cam);
+					}
+				}
+			}
+
+			// ui.unindent();
+		}
+	}
+}

+ 0 - 120
armorlab/Sources/MakeMaterial.ts

@@ -1,120 +0,0 @@
-
-class MakeMaterial {
-
-	static defaultScon: shader_context_t = null;
-	static defaultMcon: material_context_t = null;
-	static heightUsed = false;
-
-	static parseMeshMaterial = () => {
-		let m = project_materialData;
-
-		for (let c of m._.shader.contexts) {
-			if (c.name == "mesh") {
-				array_remove(m._.shader.contexts, c);
-				array_remove(m._.shader._.contexts, c);
-				MakeMaterial.deleteContext(c);
-				break;
-			}
-		}
-
-		let con = MakeMesh.run({ name: "Material", canvas: null });
-		let scon: shader_context_t = shader_context_create(con.data);
-		scon._.override_context = {};
-		if (con.frag.sharedSamplers.length > 0) {
-			let sampler = con.frag.sharedSamplers[0];
-			scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
-		}
-		if (!context_raw.textureFilter) {
-			scon._.override_context.filter = "point";
-		}
-		scon._.override_context.addressing = "repeat";
-		m._.shader.contexts.push(scon);
-		m._.shader._.contexts.push(scon);
-
-		context_raw.ddirty = 2;
-
-		///if arm_voxels
-		MakeMaterial.makeVoxel(m);
-		///end
-
-		///if (krom_direct3d12 || krom_vulkan)
-		render_path_raytrace_dirty = 1;
-		///end
-	}
-
-	///if arm_voxels
-	static makeVoxel = (m: material_data_t) => {
-		let rebuild = true; // heightUsed;
-		if (config_raw.rp_gi != false && rebuild) {
-			let scon: shader_context_t = null;
-			for (let c of m._.shader._.contexts) {
-				if (c.name == "voxel") {
-					scon = c;
-					break;
-				}
-			}
-			if (scon != null) make_voxel_run(scon);
-		}
-	}
-	///end
-
-	static parsePaintMaterial = () => {
-		let m = project_materialData;
-		let scon: shader_context_t = null;
-		let mcon: material_context_t = null;
-		for (let c of m._.shader.contexts) {
-			if (c.name == "paint") {
-				array_remove(m._.shader.contexts, c);
-				array_remove(m._.shader._.contexts, c);
-				if (c != MakeMaterial.defaultScon) MakeMaterial.deleteContext(c);
-				break;
-			}
-		}
-		for (let c of m.contexts) {
-			if (c.name == "paint") {
-				array_remove(m.contexts, c);
-				array_remove(m._.contexts, c);
-				break;
-			}
-		}
-
-		let sdata: TMaterial = { name: "Material", canvas: null };
-		let mcon2: material_context_t = { name: "paint", bind_textures: [] };
-		let con = MakePaint.run(sdata, mcon2);
-
-		let compileError = false;
-		let scon2: shader_context_t;
-		let _scon: shader_context_t = shader_context_create(con.data);
-		if (_scon == null) compileError = true;
-		scon2 = _scon;
-
-		if (compileError) return;
-		scon2._.override_context = {};
-		scon2._.override_context.addressing = "repeat";
-		let mcon3: material_context_t = material_context_create(mcon2);
-
-		m._.shader.contexts.push(scon2);
-		m._.shader._.contexts.push(scon2);
-		m.contexts.push(mcon3);
-		m._.contexts.push(mcon3);
-
-		if (MakeMaterial.defaultScon == null) MakeMaterial.defaultScon = scon2;
-		if (MakeMaterial.defaultMcon == null) MakeMaterial.defaultMcon = mcon3;
-	}
-
-	static getDisplaceStrength = (): f32 => {
-		let sc = context_mainObject().base.transform.scale.x;
-		return config_raw.displace_strength * 0.02 * sc;
-	}
-
-	static voxelgiHalfExtents = (): string => {
-		let ext = context_raw.vxaoExt;
-		return `const vec3 voxelgiHalfExtents = vec3(${ext}, ${ext}, ${ext});`;
-	}
-
-	static deleteContext = (c: shader_context_t) => {
-		base_notifyOnNextFrame(() => { // Ensure pipeline is no longer in use
-			shader_context_delete(c);
-		});
-	}
-}

+ 0 - 237
armorlab/Sources/MakeMesh.ts

@@ -1,237 +0,0 @@
-
-class MakeMesh {
-
-	static layerPassCount = 1;
-
-	static run = (data: TMaterial, layerPass = 0): NodeShaderContextRaw => {
-		let con_mesh = NodeShadercontext_create(data, {
-			name: "mesh",
-			depth_write: layerPass == 0 ? true : false,
-			compare_mode: layerPass == 0 ? "less" : "equal",
-			cull_mode: (context_raw.cullBackfaces || layerPass > 0) ? "clockwise" : "none",
-			vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
-			color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
-			depth_attachment: "DEPTH32"
-		});
-
-		let vert = NodeShadercontext_make_vert(con_mesh);
-		let frag = NodeShadercontext_make_frag(con_mesh);
-		frag.ins = vert.outs;
-
-		node_shader_add_out(vert, 'vec2 texCoord');
-		frag.wvpposition = true;
-		node_shader_add_out(vert, 'vec4 prevwvpposition');
-		node_shader_add_uniform(vert, 'mat4 VP', '_view_proj_matrix');
-		node_shader_add_uniform(vert, 'mat4 prevWVP', '_prev_world_view_proj_matrix');
-		vert.wposition = true;
-
-		let textureCount = 0;
-		let displaceStrength = MakeMaterial.getDisplaceStrength();
-		if (MakeMaterial.heightUsed && displaceStrength > 0.0) {
-			vert.n = true;
-			node_shader_write(vert, 'float height = 0.0;');
-			let numLayers = 1;
-			node_shader_write(vert, `wposition += wnormal * vec3(height, height, height) * vec3(${displaceStrength}, ${displaceStrength}, ${displaceStrength});`);
-		}
-
-		node_shader_write(vert, 'gl_Position = mul(vec4(wposition.xyz, 1.0), VP);');
-		let brushScale = context_raw.brushScale;
-		node_shader_add_uniform(vert, 'float texScale', '_tex_unpack');
-		node_shader_write(vert, `texCoord = tex * ${brushScale} * texScale;`);
-		if (MakeMaterial.heightUsed && displaceStrength > 0) {
-			node_shader_add_uniform(vert, 'mat4 invW', '_inv_world_matrix');
-			node_shader_write(vert, 'prevwvpposition = mul(mul(vec4(wposition, 1.0), invW), prevWVP);');
-		}
-		else {
-			node_shader_write(vert, 'prevwvpposition = mul(vec4(pos.xyz, 1.0), prevWVP);');
-		}
-
-		node_shader_add_out(frag, 'vec4 fragColor[3]');
-		frag.n = true;
-		node_shader_add_function(frag, str_packFloatInt16);
-		node_shader_add_function(frag, str_octahedronWrap);
-		node_shader_add_function(frag, str_cotangentFrame);
-
-		node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
-		node_shader_write(frag, 'float roughness = 0.0;');
-		node_shader_write(frag, 'float metallic = 0.0;');
-		node_shader_write(frag, 'float occlusion = 1.0;');
-		node_shader_write(frag, 'float opacity = 1.0;');
-		node_shader_write(frag, 'float matid = 0.0;');
-		node_shader_write(frag, 'vec3 ntex = vec3(0.5, 0.5, 1.0);');
-		node_shader_write(frag, 'float height = 0.0;');
-
-		node_shader_write(frag, 'vec4 texpaint_sample = vec4(0.0, 0.0, 0.0, 1.0);');
-		node_shader_write(frag, 'vec4 texpaint_nor_sample;');
-		node_shader_write(frag, 'vec4 texpaint_pack_sample;');
-		node_shader_write(frag, 'float texpaint_opac;');
-
-		if (MakeMaterial.heightUsed) {
-			node_shader_write(frag, 'float height0 = 0.0;');
-			node_shader_write(frag, 'float height1 = 0.0;');
-			node_shader_write(frag, 'float height2 = 0.0;');
-			node_shader_write(frag, 'float height3 = 0.0;');
-		}
-
-		if (context_raw.viewportMode == ViewportMode.ViewLit && context_raw.renderMode == RenderMode.RenderForward) {
-			node_shader_add_uniform(frag, 'sampler2D senvmapBrdf', "$brdf.k");
-			node_shader_add_uniform(frag, 'sampler2D senvmapRadiance', '_envmap_radiance');
-			node_shader_add_uniform(frag, 'sampler2D sltcMat', '_ltcMat');
-			node_shader_add_uniform(frag, 'sampler2D sltcMag', '_ltcMag');
-		}
-
-		node_shader_add_shared_sampler(frag, 'sampler2D texpaint');
-		node_shader_write(frag, 'texpaint_sample = textureLodShared(texpaint' + ', texCoord, 0.0);');
-		node_shader_write(frag, 'texpaint_opac = texpaint_sample.a;');
-
-		node_shader_write(frag, 'basecol = texpaint_sample.rgb * texpaint_opac;');
-
-		node_shader_add_shared_sampler(frag, 'sampler2D texpaint_nor');
-		node_shader_write(frag, 'texpaint_nor_sample = textureLodShared(texpaint_nor' + ', texCoord, 0.0);');
-
-		node_shader_write(frag, 'ntex = mix(ntex, texpaint_nor_sample.rgb, texpaint_opac);');
-
-		node_shader_add_shared_sampler(frag, 'sampler2D texpaint_pack');
-		node_shader_write(frag, 'texpaint_pack_sample = textureLodShared(texpaint_pack' + ', texCoord, 0.0);');
-
-		node_shader_write(frag, 'occlusion = mix(occlusion, texpaint_pack_sample.r, texpaint_opac);');
-
-		node_shader_write(frag, 'roughness = mix(roughness, texpaint_pack_sample.g, texpaint_opac);');
-
-		node_shader_write(frag, 'metallic = mix(metallic, texpaint_pack_sample.b, texpaint_opac);');
-
-		node_shader_write(frag, 'height = texpaint_pack_sample.a * texpaint_opac;');
-
-		// if (l.paintHeight && MakeMaterial.heightUsed) {
-		// 	let assign = l.paintHeightBlend ? "+=" : "=";
-		// 	node_shader_write(frag, `height ${assign} texpaint_pack_sample.a * texpaint_opac;`);
-		// 	node_shader_write(frag, '{');
-		// 	node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
-		// 	node_shader_write(frag, 'float tex_step = 1.0 / texpaintSize.x;');
-		// 	node_shader_write(frag, `height0 ${assign} textureLodShared(texpaint_pack` + ', vec2(texCoord.x - tex_step, texCoord.y), 0.0).a * texpaint_opac;');
-		// 	node_shader_write(frag, `height1 ${assign} textureLodShared(texpaint_pack` + ', vec2(texCoord.x + tex_step, texCoord.y), 0.0).a * texpaint_opac;');
-		// 	node_shader_write(frag, `height2 ${assign} textureLodShared(texpaint_pack` + ', vec2(texCoord.x, texCoord.y - tex_step), 0.0).a * texpaint_opac;');
-		// 	node_shader_write(frag, `height3 ${assign} textureLodShared(texpaint_pack` + ', vec2(texCoord.x, texCoord.y + tex_step), 0.0).a * texpaint_opac;');
-		// 	node_shader_write(frag, '}');
-		// }
-
-		if (MakeMaterial.heightUsed) {
-			node_shader_write(frag, 'if (height > 0.0) {');
-			node_shader_write(frag, 'float height_dx = height0 - height1;');
-			node_shader_write(frag, 'float height_dy = height2 - height3;');
-			// Whiteout blend
-			node_shader_write(frag, 'vec3 n1 = ntex * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
-			node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
-			node_shader_write(frag, 'ntex = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
-			node_shader_write(frag, '}');
-		}
-
-		frag.vVec = true;
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
-		///else
-		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
-		///end
-		node_shader_write(frag, 'n = ntex * 2.0 - 1.0;');
-		node_shader_write(frag, 'n.y = -n.y;');
-		node_shader_write(frag, 'n = normalize(mul(n, TBN));');
-
-		if (context_raw.viewportMode == ViewportMode.ViewLit || context_raw.viewportMode == ViewportMode.ViewPathTrace) {
-
-			node_shader_write(frag, 'basecol = pow(basecol, vec3(2.2, 2.2, 2.2));');
-
-			if (context_raw.viewportShader != null) {
-				let color = context_raw.viewportShader(frag);
-				node_shader_write(frag, `fragColor[1] = vec4(${color}, 1.0);`);
-			}
-			else if (context_raw.renderMode == RenderMode.RenderForward && context_raw.viewportMode != ViewportMode.ViewPathTrace) {
-				frag.wposition = true;
-				node_shader_write(frag, 'vec3 albedo = mix(basecol, vec3(0.0, 0.0, 0.0), metallic);');
-				node_shader_write(frag, 'vec3 f0 = mix(vec3(0.04, 0.04, 0.04), basecol, metallic);');
-				frag.vVec = true;
-				node_shader_write(frag, 'float dotNV = max(0.0, dot(n, vVec));');
-				node_shader_write(frag, 'vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(roughness, 1.0 - dotNV) * 256.0), 0).xy;');
-				node_shader_add_uniform(frag, 'int envmapNumMipmaps', '_envmap_num_mipmaps');
-				node_shader_add_uniform(frag, 'vec4 envmapData', '_envmapData'); // angle, sin(angle), cos(angle), strength
-				node_shader_write(frag, 'vec3 wreflect = reflect(-vVec, n);');
-				node_shader_write(frag, 'float envlod = roughness * float(envmapNumMipmaps);');
-				node_shader_add_function(frag, str_envMapEquirect);
-				node_shader_write(frag, 'vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(wreflect, envmapData.x), envlod).rgb;');
-				node_shader_add_uniform(frag, 'vec3 lightArea0', '_light_area0');
-				node_shader_add_uniform(frag, 'vec3 lightArea1', '_light_area1');
-				node_shader_add_uniform(frag, 'vec3 lightArea2', '_light_area2');
-				node_shader_add_uniform(frag, 'vec3 lightArea3', '_light_area3');
-				node_shader_add_function(frag, str_ltcEvaluate);
-				node_shader_add_uniform(frag, 'vec3 lightPos', '_point_pos');
-				node_shader_add_uniform(frag, 'vec3 lightColor', '_point_color');
-				node_shader_write(frag, 'float ldist = distance(wposition, lightPos);');
-				node_shader_write(frag, 'const float LUT_SIZE = 64.0;');
-				node_shader_write(frag, 'const float LUT_SCALE = (LUT_SIZE - 1.0) / LUT_SIZE;');
-				node_shader_write(frag, 'const float LUT_BIAS = 0.5 / LUT_SIZE;');
-				node_shader_write(frag, 'float theta = acos(dotNV);');
-				node_shader_write(frag, 'vec2 tuv = vec2(roughness, theta / (0.5 * 3.14159265));');
-				node_shader_write(frag, 'tuv = tuv * LUT_SCALE + LUT_BIAS;');
-				node_shader_write(frag, 'vec4 t = textureLod(sltcMat, tuv, 0.0);');
-				node_shader_write(frag, 'mat3 minv = mat3(vec3(1.0, 0.0, t.y), vec3(0.0, t.z, 0.0), vec3(t.w, 0.0, t.x));');
-				node_shader_write(frag, 'float ltcspec = ltcEvaluate(n, vVec, dotNV, wposition, minv, lightArea0, lightArea1, lightArea2, lightArea3);');
-				node_shader_write(frag, 'ltcspec *= textureLod(sltcMag, tuv, 0.0).a;');
-				node_shader_write(frag, 'mat3 mident = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);');
-				node_shader_write(frag, 'float ltcdiff = ltcEvaluate(n, vVec, dotNV, wposition, mident, lightArea0, lightArea1, lightArea2, lightArea3);');
-				node_shader_write(frag, 'vec3 direct = albedo * ltcdiff + ltcspec * 0.05;');
-				node_shader_write(frag, 'direct *= lightColor * (1.0 / (ldist * ldist));');
-
-				node_shader_add_uniform(frag, 'vec4 shirr[7]', '_envmap_irradiance');
-				node_shader_add_function(frag, str_shIrradiance);
-				node_shader_write(frag, 'vec3 indirect = albedo * (shIrradiance(vec3(n.x * envmapData.z - n.y * envmapData.y, n.x * envmapData.y + n.y * envmapData.z, n.z), shirr) / 3.14159265);');
-				node_shader_write(frag, 'indirect += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5;');
-				node_shader_write(frag, 'indirect *= envmapData.w * occlusion;');
-				node_shader_write(frag, 'fragColor[1] = vec4(direct + indirect, 1.0);');
-			}
-			else { // Deferred, Pathtraced
-				node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
-			}
-		}
-		else if (context_raw.viewportMode == ViewportMode.ViewBaseColor) {
-			node_shader_write(frag, 'fragColor[1] = vec4(basecol, 1.0);');
-		}
-		else if (context_raw.viewportMode == ViewportMode.ViewNormalMap) {
-			node_shader_write(frag, 'fragColor[1] = vec4(ntex.rgb, 1.0);');
-		}
-		else if (context_raw.viewportMode == ViewportMode.ViewOcclusion) {
-			node_shader_write(frag, 'fragColor[1] = vec4(vec3(occlusion, occlusion, occlusion), 1.0);');
-		}
-		else if (context_raw.viewportMode == ViewportMode.ViewRoughness) {
-			node_shader_write(frag, 'fragColor[1] = vec4(vec3(roughness, roughness, roughness), 1.0);');
-		}
-		else if (context_raw.viewportMode == ViewportMode.ViewMetallic) {
-			node_shader_write(frag, 'fragColor[1] = vec4(vec3(metallic, metallic, metallic), 1.0);');
-		}
-		else if (context_raw.viewportMode == ViewportMode.ViewOpacity) {
-			node_shader_write(frag, 'fragColor[1] = vec4(vec3(texpaint_sample.a, texpaint_sample.a, texpaint_sample.a), 1.0);');
-		}
-		else if (context_raw.viewportMode == ViewportMode.ViewHeight) {
-			node_shader_write(frag, 'fragColor[1] = vec4(vec3(height, height, height), 1.0);');
-		}
-		else {
-			node_shader_write(frag, 'fragColor[1] = vec4(1.0, 0.0, 1.0, 1.0);'); // Pink
-		}
-
-		if (context_raw.viewportMode != ViewportMode.ViewLit && context_raw.viewportMode != ViewportMode.ViewPathTrace) {
-			node_shader_write(frag, 'fragColor[1].rgb = pow(fragColor[1].rgb, vec3(2.2, 2.2, 2.2));');
-		}
-
-		node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
-		node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
-		node_shader_write(frag, 'fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, uint(int(matid * 255.0) % 3)));');
-
-		node_shader_write(frag, 'vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
-		node_shader_write(frag, 'vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;');
-		node_shader_write(frag, 'fragColor[2] = vec4(posa - posb, texCoord.xy);');
-
-		parser_material_finalize(con_mesh);
-		con_mesh.data.shader_from_source = true;
-		con_mesh.data.vertex_shader = node_shader_get(vert);
-		con_mesh.data.fragment_shader = node_shader_get(frag);
-		return con_mesh;
-	}
-}

+ 0 - 277
armorlab/Sources/MakePaint.ts

@@ -1,277 +0,0 @@
-
-class MakePaint {
-
-	static run = (data: TMaterial, matcon: material_context_t): NodeShaderContextRaw => {
-		let con_paint = NodeShadercontext_create(data, {
-			name: "paint",
-			depth_write: false,
-			compare_mode: "always", // TODO: align texcoords winding order
-			// cull_mode: "counter_clockwise",
-			cull_mode: "none",
-			vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
-			color_attachments:
-				context_raw.tool == WorkspaceTool.ToolPicker ? ["RGBA32", "RGBA32", "RGBA32", "RGBA32"] :
-					["RGBA32", "RGBA32", "RGBA32", "R8"]
-		});
-
-		con_paint.data.color_writes_red = [true, true, true, true];
-		con_paint.data.color_writes_green = [true, true, true, true];
-		con_paint.data.color_writes_blue = [true, true, true, true];
-		con_paint.data.color_writes_alpha = [true, true, true, true];
-		con_paint.allow_vcols = mesh_data_get_vertex_array(context_raw.paintObject.data, "col") != null;
-
-		let vert = NodeShadercontext_make_vert(con_paint);
-		let frag = NodeShadercontext_make_frag(con_paint);
-		frag.ins = vert.outs;
-
-		if (context_raw.tool == WorkspaceTool.ToolPicker) {
-			// Mangle vertices to form full screen triangle
-			node_shader_write(vert, 'gl_Position = vec4(-1.0 + float((gl_VertexID & 1) << 2), -1.0 + float((gl_VertexID & 2) << 1), 0.0, 1.0);');
-
-			node_shader_add_uniform(frag, 'sampler2D gbuffer2');
-			node_shader_add_uniform(frag, 'vec2 gbufferSize', '_gbufferSize');
-			node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
-
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, inp.y * gbufferSize.y), 0).ba;');
-			///else
-			node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, (1.0 - inp.y) * gbufferSize.y), 0).ba;');
-			///end
-
-			node_shader_add_out(frag, 'vec4 fragColor[4]');
-			node_shader_add_uniform(frag, 'sampler2D texpaint');
-			node_shader_add_uniform(frag, 'sampler2D texpaint_nor');
-			node_shader_add_uniform(frag, 'sampler2D texpaint_pack');
-			node_shader_write(frag, 'fragColor[0] = textureLod(texpaint, texCoordInp, 0.0);');
-			node_shader_write(frag, 'fragColor[1] = textureLod(texpaint_nor, texCoordInp, 0.0);');
-			node_shader_write(frag, 'fragColor[2] = textureLod(texpaint_pack, texCoordInp, 0.0);');
-			node_shader_write(frag, 'fragColor[3].rg = texCoordInp.xy;');
-			con_paint.data.shader_from_source = true;
-			con_paint.data.vertex_shader = node_shader_get(vert);
-			con_paint.data.fragment_shader = node_shader_get(frag);
-			return con_paint;
-		}
-
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(vert, 'vec2 tpos = vec2(tex.x * 2.0 - 1.0, (1.0 - tex.y) * 2.0 - 1.0);');
-		// node_shader_write(vert, 'vec2 tpos = vec2(frac(tex.x * texScale) * 2.0 - 1.0, (1.0 - frac(tex.y * texScale)) * 2.0 - 1.0);'); // 3D View
-		///else
-		node_shader_write(vert, 'vec2 tpos = vec2(tex.xy * 2.0 - 1.0);');
-		///end
-
-		node_shader_write(vert, 'gl_Position = vec4(tpos, 0.0, 1.0);');
-
-		node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
-
-		node_shader_add_out(vert, 'vec4 ndc');
-		node_shader_write_attrib(vert, 'ndc = mul(vec4(pos.xyz, 1.0), WVP);');
-
-		node_shader_write_attrib(frag, 'vec3 sp = vec3((ndc.xyz / ndc.w) * 0.5 + 0.5);');
-		node_shader_write_attrib(frag, 'sp.y = 1.0 - sp.y;');
-		node_shader_write_attrib(frag, 'sp.z -= 0.0001;'); // small bias
-
-		node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
-		node_shader_add_uniform(frag, 'vec4 inplast', '_inputBrushLast');
-		node_shader_add_uniform(frag, 'float aspectRatio', '_aspect_ratio_window');
-		node_shader_write(frag, 'vec2 bsp = sp.xy * 2.0 - 1.0;');
-		node_shader_write(frag, 'bsp.x *= aspectRatio;');
-		node_shader_write(frag, 'bsp = bsp * 0.5 + 0.5;');
-
-		node_shader_add_uniform(frag, 'sampler2D gbufferD');
-
-		node_shader_add_out(frag, 'vec4 fragColor[4]');
-
-		node_shader_add_uniform(frag, 'float brushRadius', '_brushRadius');
-		node_shader_add_uniform(frag, 'float brushOpacity', '_brushOpacity');
-		node_shader_add_uniform(frag, 'float brushHardness', '_brushHardness');
-
-		if (context_raw.tool == WorkspaceTool.ToolEraser ||
-			context_raw.tool == WorkspaceTool.ToolClone  ||
-			context_raw.tool == WorkspaceTool.ToolBlur   ||
-			context_raw.tool == WorkspaceTool.ToolSmudge) {
-
-			node_shader_write(frag, 'float dist = 0.0;');
-
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'float depth = textureLod(gbufferD, inp.xy, 0.0).r;');
-			///else
-			node_shader_write(frag, 'float depth = textureLod(gbufferD, vec2(inp.x, 1.0 - inp.y), 0.0).r;');
-			///end
-
-			node_shader_add_uniform(frag, 'mat4 invVP', '_inv_view_proj_matrix');
-			node_shader_write(frag, 'vec4 winp = vec4(vec2(inp.x, 1.0 - inp.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);');
-			node_shader_write(frag, 'winp = mul(winp, invVP);');
-			node_shader_write(frag, 'winp.xyz /= winp.w;');
-			frag.wposition = true;
-
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'float depthlast = textureLod(gbufferD, inplast.xy, 0.0).r;');
-			///else
-			node_shader_write(frag, 'float depthlast = textureLod(gbufferD, vec2(inplast.x, 1.0 - inplast.y), 0.0).r;');
-			///end
-
-			node_shader_write(frag, 'vec4 winplast = vec4(vec2(inplast.x, 1.0 - inplast.y) * 2.0 - 1.0, depthlast * 2.0 - 1.0, 1.0);');
-			node_shader_write(frag, 'winplast = mul(winplast, invVP);');
-			node_shader_write(frag, 'winplast.xyz /= winplast.w;');
-
-			node_shader_write(frag, 'vec3 pa = wposition - winp.xyz;');
-			node_shader_write(frag, 'vec3 ba = winplast.xyz - winp.xyz;');
-
-			// Capsule
-			node_shader_write(frag, 'float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);');
-			node_shader_write(frag, 'dist = length(pa - ba * h);');
-
-			node_shader_write(frag, 'if (dist > brushRadius) discard;');
-		}
-
-		// node_shader_add_uniform(vert, 'float brushScale', '_brushScale');
-		// node_shader_add_uniform(vert, 'float texScale', '_tex_unpack');
-		// node_shader_add_out(vert, 'vec2 texCoord');
-		// node_shader_write(vert, 'texCoord = tex * brushScale * texScale;');
-
-		if (context_raw.tool == WorkspaceTool.ToolClone || context_raw.tool == WorkspaceTool.ToolBlur || context_raw.tool == WorkspaceTool.ToolSmudge) {
-			node_shader_add_uniform(frag, 'sampler2D gbuffer2');
-			node_shader_add_uniform(frag, 'vec2 gbufferSize', '_gbufferSize');
-			node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
-			node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
-			node_shader_add_uniform(frag, 'sampler2D texpaint_pack_undo', '_texpaint_pack_undo');
-
-			if (context_raw.tool == WorkspaceTool.ToolClone) {
-				// node_shader_add_uniform(frag, 'vec2 cloneDelta', '_cloneDelta');
-				// ///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-				// node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.xy + cloneDelta) * gbufferSize), 0).ba;');
-				// ///else
-				// node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.x + cloneDelta.x) * gbufferSize.x, (1.0 - (sp.y + cloneDelta.y)) * gbufferSize.y), 0).ba;');
-				// ///end
-
-				// node_shader_write(frag, 'vec3 texpaint_pack_sample = textureLod(texpaint_pack_undo, texCoordInp, 0.0).rgb;');
-				// let base = 'textureLod(texpaint_undo, texCoordInp, 0.0).rgb';
-				// let rough = 'texpaint_pack_sample.g';
-				// let met = 'texpaint_pack_sample.b';
-				// let occ = 'texpaint_pack_sample.r';
-				// let nortan = 'textureLod(texpaint_nor_undo, texCoordInp, 0.0).rgb';
-				// let height = '0.0';
-				// let opac = '1.0';
-				// node_shader_write(frag, `vec3 basecol = ${base};`);
-				// node_shader_write(frag, `float roughness = ${rough};`);
-				// node_shader_write(frag, `float metallic = ${met};`);
-				// node_shader_write(frag, `float occlusion = ${occ};`);
-				// node_shader_write(frag, `vec3 nortan = ${nortan};`);
-				// node_shader_write(frag, `float height = ${height};`);
-				// node_shader_write(frag, `float mat_opacity = ${opac};`);
-				// node_shader_write(frag, 'float opacity = mat_opacity * brushOpacity;');
-			}
-			else { // Blur
-				// ///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-				// node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, sp.y * gbufferSize.y), 0).ba;');
-				// ///else
-				// node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, (1.0 - sp.y) * gbufferSize.y), 0).ba;');
-				// ///end
-
-				// node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
-				// node_shader_write(frag, 'float roughness = 0.0;');
-				// node_shader_write(frag, 'float metallic = 0.0;');
-				// node_shader_write(frag, 'float occlusion = 0.0;');
-				// node_shader_write(frag, 'vec3 nortan = vec3(0.0, 0.0, 0.0);');
-				// node_shader_write(frag, 'float height = 0.0;');
-				// node_shader_write(frag, 'float mat_opacity = 1.0;');
-				// node_shader_write(frag, 'float opacity = 0.0;');
-
-				// node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
-				// node_shader_write(frag, 'float blur_step = 1.0 / texpaintSize.x;');
-				// if (context_raw.blurDirectional) {
-				// 	///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
-				// 	node_shader_write(frag, '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};');
-				// 	///else
-				// 	node_shader_write(frag, '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);');
-				// 	///end
-				// 	node_shader_add_uniform(frag, 'vec3 brushDirection', '_brushDirection');
-				// 	node_shader_write(frag, 'vec2 blur_direction = brushDirection.yx;');
-				// 	node_shader_write(frag, 'for (int i = 0; i < 7; ++i) {');
-				// 	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-				// 	node_shader_write(frag, 'vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSize.x, (sp.y + blur_direction.y * blur_step * float(i)) * gbufferSize.y), 0).ba;');
-				// 	///else
-				// 	node_shader_write(frag, 'vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSize.x, (1.0 - (sp.y + blur_direction.y * blur_step * float(i))) * gbufferSize.y), 0).ba;');
-				// 	///end
-				// 	node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp2);');
-				// 	node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[i];');
-				// 	node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[i];');
-				// 	node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp2) * blur_weight[i];');
-				// 	node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
-				// 	node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
-				// 	node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
-				// 	node_shader_write(frag, 'height += texpaint_pack_sample.a;');
-				// 	node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp2).rgb * blur_weight[i];');
-				// 	node_shader_write(frag, '}');
-				// }
-				// else {
-				// 	///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
-				// 	node_shader_write(frag, '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};');
-				// 	///else
-				// 	node_shader_write(frag, '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);');
-				// 	///end
-				// 	// X
-				// 	node_shader_write(frag, 'for (int i = -7; i <= 7; ++i) {');
-				// 	node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(blur_step * float(i), 0.0));');
-				// 	node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[i + 7];');
-				// 	node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[i + 7];');
-				// 	node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(blur_step * float(i), 0.0)) * blur_weight[i + 7];');
-				// 	node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
-				// 	node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
-				// 	node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
-				// 	node_shader_write(frag, 'height += texpaint_pack_sample.a;');
-				// 	node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp + vec2(blur_step * float(i), 0.0)).rgb * blur_weight[i + 7];');
-				// 	node_shader_write(frag, '}');
-				// 	// Y
-				// 	node_shader_write(frag, 'for (int j = -7; j <= 7; ++j) {');
-				// 	node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(0.0, blur_step * float(j)));');
-				// 	node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[j + 7];');
-				// 	node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[j + 7];');
-				// 	node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(0.0, blur_step * float(j))) * blur_weight[j + 7];');
-				// 	node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
-				// 	node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
-				// 	node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
-				// 	node_shader_write(frag, 'height += texpaint_pack_sample.a;');
-				// 	node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp + vec2(0.0, blur_step * float(j))).rgb * blur_weight[j + 7];');
-				// 	node_shader_write(frag, '}');
-				// }
-				// node_shader_write(frag, 'opacity *= brushOpacity;');
-			}
-		}
-
-		node_shader_write(frag, 'float opacity = 1.0;');
-		node_shader_write(frag, 'if (opacity == 0.0) discard;');
-
-		node_shader_write(frag, 'float str = clamp((brushRadius - dist) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
-
-		// Manual blending to preserve memory
-		frag.wvpposition = true;
-		node_shader_write(frag, 'vec2 sample_tc = vec2(wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(frag, 'sample_tc.y = 1.0 - sample_tc.y;');
-		///end
-		node_shader_add_uniform(frag, 'sampler2D paintmask');
-		node_shader_write(frag, 'float sample_mask = textureLod(paintmask, sample_tc, 0.0).r;');
-		node_shader_write(frag, 'str = max(str, sample_mask);');
-
-		node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
-		node_shader_write(frag, 'vec4 sample_undo = textureLod(texpaint_undo, sample_tc, 0.0);');
-
-		if (context_raw.tool == WorkspaceTool.ToolEraser) {
-			// node_shader_write(frag, 'fragColor[0] = vec4(mix(sample_undo.rgb, vec3(0.0, 0.0, 0.0), str), sample_undo.a - str);');
-			node_shader_write(frag, 'fragColor[0] = vec4(0.0, 0.0, 0.0, 0.0);');
-			node_shader_write(frag, 'fragColor[1] = vec4(0.5, 0.5, 1.0, 0.0);');
-			node_shader_write(frag, 'fragColor[2] = vec4(1.0, 0.0, 0.0, 0.0);');
-		}
-
-		node_shader_write(frag, 'fragColor[3] = vec4(str, 0.0, 0.0, 1.0);');
-
-		parser_material_finalize(con_paint);
-		parser_material_sample_keep_aspect = false;
-		con_paint.data.shader_from_source = true;
-		con_paint.data.vertex_shader = node_shader_get(vert);
-		con_paint.data.fragment_shader = node_shader_get(frag);
-
-		return con_paint;
-	}
-}

+ 0 - 43
armorlab/Sources/NodesBrush.ts

@@ -1,43 +0,0 @@
-/// <reference path='./nodes/ImageTextureNode.ts'/>
-/// <reference path='./nodes/RGBNode.ts'/>
-/// <reference path='./nodes/InpaintNode.ts'/>
-/// <reference path='./nodes/PhotoToPBRNode.ts'/>
-/// <reference path='./nodes/TextToPhotoNode.ts'/>
-/// <reference path='./nodes/TilingNode.ts'/>
-/// <reference path='./nodes/UpscaleNode.ts'/>
-/// <reference path='./nodes/VarianceNode.ts'/>
-
-class NodesBrush {
-
-	static categories = [_tr("Input"), _tr("Model")];
-
-	static list: zui_node_t[][] = [
-		[ // Input
-			ImageTextureNode.def,
-			RGBNode.def,
-		],
-		[ // Model
-			InpaintNode.def,
-			PhotoToPBRNode.def,
-			TextToPhotoNode.def,
-			TilingNode.def,
-			UpscaleNode.def,
-			VarianceNode.def,
-		]
-	];
-
-	static createNode = (nodeType: string): zui_node_t => {
-		for (let c of NodesBrush.list) {
-			for (let n of c) {
-				if (n.type == nodeType) {
-					let canvas = project_canvas;
-					let nodes = project_nodes;
-					let node = ui_nodes_makeNode(n, nodes, canvas);
-					canvas.nodes.push(node);
-					return node;
-				}
-			}
-		}
-		return null;
-	}
-}

+ 0 - 305
armorlab/Sources/RenderPathPaint.ts

@@ -1,305 +0,0 @@
-
-class RenderPathPaint {
-
-	static liveLayerDrawn = 0; ////
-
-	static init = () => {
-
-		{
-			let t = render_target_create();
-			t.name = "texpaint_blend0";
-			t.width = config_getTextureResX();
-			t.height = config_getTextureResY();
-			t.format = "R8";
-			render_path_create_render_target(t);
-		}
-		{
-			let t = render_target_create();
-			t.name = "texpaint_blend1";
-			t.width = config_getTextureResX();
-			t.height = config_getTextureResY();
-			t.format = "R8";
-			render_path_create_render_target(t);
-		}
-		{
-			let t = render_target_create();
-			t.name = "texpaint_picker";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-		{
-			let t = render_target_create();
-			t.name = "texpaint_nor_picker";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-		{
-			let t = render_target_create();
-			t.name = "texpaint_pack_picker";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-		{
-			let t = render_target_create();
-			t.name = "texpaint_uv_picker";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-
-		render_path_load_shader("shader_datas/copy_mrt3_pass/copy_mrt3_pass");
-	}
-
-	static commandsPaint = (dilation = true) => {
-		let tid = "";
-
-		if (context_raw.pdirty > 0) {
-
-			if (context_raw.tool == WorkspaceTool.ToolPicker) {
-
-					///if krom_metal
-					// render_path_set_target("texpaint_picker");
-					// render_path_clear_target(0xff000000);
-					// render_path_set_target("texpaint_nor_picker");
-					// render_path_clear_target(0xff000000);
-					// render_path_set_target("texpaint_pack_picker");
-					// render_path_clear_target(0xff000000);
-					render_path_set_target("texpaint_picker", ["texpaint_nor_picker", "texpaint_pack_picker", "texpaint_uv_picker"]);
-					///else
-					render_path_set_target("texpaint_picker", ["texpaint_nor_picker", "texpaint_pack_picker", "texpaint_uv_picker"]);
-					// render_path_clear_target(0xff000000);
-					///end
-					render_path_bind_target("gbuffer2", "gbuffer2");
-					// tid = context_raw.layer.id;
-					render_path_bind_target("texpaint" + tid, "texpaint");
-					render_path_bind_target("texpaint_nor" + tid, "texpaint_nor");
-					render_path_bind_target("texpaint_pack" + tid, "texpaint_pack");
-					render_path_draw_meshes("paint");
-					ui_header_headerHandle.redraws = 2;
-
-					let texpaint_picker = render_path_render_targets.get("texpaint_picker")._image;
-					let texpaint_nor_picker = render_path_render_targets.get("texpaint_nor_picker")._image;
-					let texpaint_pack_picker = render_path_render_targets.get("texpaint_pack_picker")._image;
-					let texpaint_uv_picker = render_path_render_targets.get("texpaint_uv_picker")._image;
-					let a = image_get_pixels(texpaint_picker);
-					let b = image_get_pixels(texpaint_nor_picker);
-					let c = image_get_pixels(texpaint_pack_picker);
-					let d = image_get_pixels(texpaint_uv_picker);
-
-					if (context_raw.colorPickerCallback != null) {
-						context_raw.colorPickerCallback(context_raw.pickedColor);
-					}
-
-					// Picked surface values
-					// ///if (krom_metal || krom_vulkan)
-					// context_raw.pickedColor.base.Rb = a.get(2);
-					// context_raw.pickedColor.base.Gb = a.get(1);
-					// context_raw.pickedColor.base.Bb = a.get(0);
-					// context_raw.pickedColor.normal.Rb = b.get(2);
-					// context_raw.pickedColor.normal.Gb = b.get(1);
-					// context_raw.pickedColor.normal.Bb = b.get(0);
-					// context_raw.pickedColor.occlusion = c.get(2) / 255;
-					// context_raw.pickedColor.roughness = c.get(1) / 255;
-					// context_raw.pickedColor.metallic = c.get(0) / 255;
-					// context_raw.pickedColor.height = c.get(3) / 255;
-					// context_raw.pickedColor.opacity = a.get(3) / 255;
-					// context_raw.uvxPicked = d.get(2) / 255;
-					// context_raw.uvyPicked = d.get(1) / 255;
-					// ///else
-					// context_raw.pickedColor.base.Rb = a.get(0);
-					// context_raw.pickedColor.base.Gb = a.get(1);
-					// context_raw.pickedColor.base.Bb = a.get(2);
-					// context_raw.pickedColor.normal.Rb = b.get(0);
-					// context_raw.pickedColor.normal.Gb = b.get(1);
-					// context_raw.pickedColor.normal.Bb = b.get(2);
-					// context_raw.pickedColor.occlusion = c.get(0) / 255;
-					// context_raw.pickedColor.roughness = c.get(1) / 255;
-					// context_raw.pickedColor.metallic = c.get(2) / 255;
-					// context_raw.pickedColor.height = c.get(3) / 255;
-					// context_raw.pickedColor.opacity = a.get(3) / 255;
-					// context_raw.uvxPicked = d.get(0) / 255;
-					// context_raw.uvyPicked = d.get(1) / 255;
-					// ///end
-			}
-			else {
-				let texpaint = "texpaint_node_target";
-
-				render_path_set_target("texpaint_blend1");
-				render_path_bind_target("texpaint_blend0", "tex");
-				render_path_draw_shader("shader_datas/copy_pass/copyR8_pass");
-
-				render_path_set_target(texpaint, ["texpaint_nor" + tid, "texpaint_pack" + tid, "texpaint_blend0"]);
-
-				render_path_bind_target("_main", "gbufferD");
-
-				render_path_bind_target("texpaint_blend1", "paintmask");
-
-				// Read texcoords from gbuffer
-				let readTC = context_raw.tool == WorkspaceTool.ToolClone ||
-							 context_raw.tool == WorkspaceTool.ToolBlur ||
-							 context_raw.tool == WorkspaceTool.ToolSmudge;
-				if (readTC) {
-					render_path_bind_target("gbuffer2", "gbuffer2");
-				}
-
-				render_path_draw_meshes("paint");
-			}
-		}
-	}
-
-	static commandsCursor = () => {
-		let tool = context_raw.tool;
-		if (tool != WorkspaceTool.ToolEraser &&
-			tool != WorkspaceTool.ToolClone &&
-			tool != WorkspaceTool.ToolBlur &&
-			tool != WorkspaceTool.ToolSmudge) {
-			return;
-		}
-
-		let nodes = ui_nodes_getNodes();
-		let canvas = ui_nodes_getCanvas(true);
-		let inpaint = nodes.nodesSelectedId.length > 0 && zui_get_node(canvas.nodes, nodes.nodesSelectedId[0]).type == "InpaintNode";
-
-		if (!base_uiEnabled || base_isDragging || !inpaint) {
-			return;
-		}
-
-		let mx = context_raw.paintVec.x;
-		let my = 1.0 - context_raw.paintVec.y;
-		if (context_raw.brushLocked) {
-			mx = (context_raw.lockStartedX - app_x()) / app_w();
-			my = 1.0 - (context_raw.lockStartedY - app_y()) / app_h();
-		}
-		let radius = context_raw.brushRadius;
-		RenderPathPaint.drawCursor(mx, my, radius / 3.4);
-	}
-
-	static drawCursor = (mx: f32, my: f32, radius: f32, tintR = 1.0, tintG = 1.0, tintB = 1.0) => {
-		let plane = scene_get_child(".Plane").ext;
-		let geom = plane.data;
-		if (base_pipeCursor == null) base_makeCursorPipe();
-
-		render_path_set_target("");
-		g4_set_pipeline(base_pipeCursor);
-		let img = resource_get("cursor.k");
-		g4_set_tex(base_cursorTex, img);
-		let gbuffer0 = render_path_render_targets.get("gbuffer0")._image;
-		g4_set_tex_depth(base_cursorGbufferD, gbuffer0);
-		g4_set_float2(base_cursorMouse, mx, my);
-		g4_set_float2(base_cursorTexStep, 1 / gbuffer0.width, 1 / gbuffer0.height);
-		g4_set_float(base_cursorRadius, radius);
-		let right = vec4_normalize(camera_object_right_world(scene_camera));
-		g4_set_float3(base_cursorCameraRight, right.x, right.y, right.z);
-		g4_set_float3(base_cursorTint, tintR, tintG, tintB);
-		g4_set_mat(base_cursorVP, scene_camera.vp);
-		let helpMat = mat4_identity();
-		mat4_get_inv(helpMat, scene_camera.vp);
-		g4_set_mat(base_cursorInvVP, helpMat);
-		///if (krom_metal || krom_vulkan)
-		g4_set_vertex_buffer(mesh_data_get(geom, [{name: "tex", data: "short2norm"}]));
-		///else
-		g4_set_vertex_buffer(geom._vertexBuffer);
-		///end
-		g4_set_index_buffer(geom._indexBuffers[0]);
-		g4_draw();
-
-		g4_disable_scissor();
-		render_path_end();
-	}
-
-	static paintEnabled = (): bool => {
-		return !context_raw.foregroundEvent;
-	}
-
-	static begin = () => {
-		if (!RenderPathPaint.paintEnabled()) return;
-	}
-
-	static end = () => {
-		RenderPathPaint.commandsCursor();
-		context_raw.ddirty--;
-		context_raw.rdirty--;
-
-		if (!RenderPathPaint.paintEnabled()) return;
-		context_raw.pdirty--;
-	}
-
-	static draw = () => {
-		if (!RenderPathPaint.paintEnabled()) return;
-
-		RenderPathPaint.commandsPaint();
-
-		if (context_raw.brushBlendDirty) {
-			context_raw.brushBlendDirty = false;
-			///if krom_metal
-			render_path_set_target("texpaint_blend0");
-			render_path_clear_target(0x00000000);
-			render_path_set_target("texpaint_blend1");
-			render_path_clear_target(0x00000000);
-			///else
-			render_path_set_target("texpaint_blend0", ["texpaint_blend1"]);
-			render_path_clear_target(0x00000000);
-			///end
-		}
-	}
-
-	static bindLayers = () => {
-		let image: image_t = null;
-		let nodes = ui_nodes_getNodes();
-		let canvas = ui_nodes_getCanvas(true);
-		if (nodes.nodesSelectedId.length > 0) {
-			let node = zui_get_node(canvas.nodes, nodes.nodesSelectedId[0]);
-			let brushNode = parser_logic_getLogicNode(node);
-			if (brushNode != null) {
-				image = brushNode.getCachedImage();
-			}
-		}
-		if (image != null) {
-			if (render_path_render_targets.get("texpaint_node") == null) {
-				let t = render_target_create();
-				t.name = "texpaint_node";
-				t.width = config_getTextureResX();
-				t.height = config_getTextureResY();
-				t.format = "RGBA32";
-				render_path_render_targets.set(t.name, t);
-			}
-			if (render_path_render_targets.get("texpaint_node_target") == null) {
-				let t = render_target_create();
-				t.name = "texpaint_node_target";
-				t.width = config_getTextureResX();
-				t.height = config_getTextureResY();
-				t.format = "RGBA32";
-				render_path_render_targets.set(t.name, t);
-			}
-			render_path_render_targets.get("texpaint_node")._image = image;
-			render_path_bind_target("texpaint_node", "texpaint");
-			render_path_bind_target("texpaint_nor_empty", "texpaint_nor");
-			render_path_bind_target("texpaint_pack_empty", "texpaint_pack");
-
-			let nodes = ui_nodes_getNodes();
-			let canvas = ui_nodes_getCanvas(true);
-			let node = zui_get_node(canvas.nodes, nodes.nodesSelectedId[0]);
-			let inpaint = node.type == "InpaintNode";
-			if (inpaint) {
-				let brushNode = parser_logic_getLogicNode(node);
-				render_path_render_targets.get("texpaint_node_target")._image = (brushNode as InpaintNode).getTarget();
-			}
-		}
-		else {
-			render_path_bind_target("texpaint", "texpaint");
-			render_path_bind_target("texpaint_nor", "texpaint_nor");
-			render_path_bind_target("texpaint_pack", "texpaint_pack");
-		}
-	}
-
-	static unbindLayers = () => {
-
-	}
-}

+ 0 - 148
armorlab/Sources/UINodesExt.ts

@@ -1,148 +0,0 @@
-
-class UINodesExt {
-
-	static lastVertices: DataView = null; // Before displacement
-
-	static drawButtons = (ew: f32, startY: f32) => {
-		let ui = ui_nodes_ui;
-		if (zui_button(tr("Run"))) {
-			console_progress(tr("Processing"));
-
-			let delayIdleSleep = () => {
-				krom_delay_idle_sleep();
-			}
-			app_notify_on_render_2d(delayIdleSleep);
-
-			let tasks = 1;
-
-			let taskDone = () => {
-				tasks--;
-				if (tasks == 0) {
-					console_progress(null);
-					context_raw.ddirty = 2;
-					app_remove_render_2d(delayIdleSleep);
-
-					///if (krom_direct3d12 || krom_vulkan || krom_metal)
-					render_path_raytrace_ready = false;
-					///end
-				}
-			}
-
-			base_notifyOnNextFrame(() => {
-				let timer = time_time();
-				parser_logic_parse(project_canvas);
-
-				PhotoToPBRNode.cachedSource = null;
-				BrushOutputNode.inst.getAsImage(ChannelType.ChannelBaseColor, (texbase: image_t) => {
-				BrushOutputNode.inst.getAsImage(ChannelType.ChannelOcclusion, (texocc: image_t) => {
-				BrushOutputNode.inst.getAsImage(ChannelType.ChannelRoughness, (texrough: image_t) => {
-				BrushOutputNode.inst.getAsImage(ChannelType.ChannelNormalMap, (texnor: image_t) => {
-				BrushOutputNode.inst.getAsImage(ChannelType.ChannelHeight, (texheight: image_t) => {
-
-					if (texbase != null) {
-						let texpaint = render_path_render_targets.get("texpaint")._image;
-						g2_begin(texpaint);
-						g2_draw_scaled_image(texbase, 0, 0, config_getTextureResX(), config_getTextureResY());
-						g2_end();
-					}
-
-					if (texnor != null) {
-						let texpaint_nor = render_path_render_targets.get("texpaint_nor")._image;
-						g2_begin(texpaint_nor);
-						g2_draw_scaled_image(texnor, 0, 0, config_getTextureResX(), config_getTextureResY());
-						g2_end();
-					}
-
-					if (base_pipeCopy == null) base_makePipe();
-					if (base_pipeCopyA == null) base_makePipeCopyA();
-					if (const_data_screen_aligned_vb == null) const_data_create_screen_aligned_data();
-
-					let texpaint_pack = render_path_render_targets.get("texpaint_pack")._image;
-
-					if (texocc != null) {
-						g2_begin(texpaint_pack);
-						g2_set_pipeline(base_pipeCopyR);
-						g2_draw_scaled_image(texocc, 0, 0, config_getTextureResX(), config_getTextureResY());
-						g2_set_pipeline(null);
-						g2_end();
-					}
-
-					if (texrough != null) {
-						g2_begin(texpaint_pack);
-						g2_set_pipeline(base_pipeCopyG);
-						g2_draw_scaled_image(texrough, 0, 0, config_getTextureResX(), config_getTextureResY());
-						g2_set_pipeline(null);
-						g2_end();
-					}
-
-					if (texheight != null) {
-						g4_begin(texpaint_pack);
-						g4_set_pipeline(base_pipeCopyA);
-						g4_set_tex(base_pipeCopyATex, texheight);
-						g4_set_vertex_buffer(const_data_screen_aligned_vb);
-						g4_set_index_buffer(const_data_screen_aligned_ib);
-						g4_draw();
-						g4_end();
-
-						if (ui_header_worktab.position == SpaceType.Space3D &&
-							BrushOutputNode.inst.inputs[ChannelType.ChannelHeight].node.constructor != FloatNode) {
-
-							// Make copy of vertices before displacement
-							let o = project_paintObjects[0];
-							let g = o.data;
-							let vertices = g4_vertex_buffer_lock(g._.vertex_buffer);
-							if (UINodesExt.lastVertices == null || UINodesExt.lastVertices.byteLength != vertices.byteLength) {
-								UINodesExt.lastVertices = new DataView(new ArrayBuffer(vertices.byteLength));
-								for (let i = 0; i < math_floor(vertices.byteLength / 2); ++i) {
-									UINodesExt.lastVertices.setInt16(i * 2, vertices.getInt16(i * 2, true), true);
-								}
-							}
-							else {
-								for (let i = 0; i < math_floor(vertices.byteLength / 2); ++i) {
-									vertices.setInt16(i * 2, UINodesExt.lastVertices.getInt16(i * 2, true), true);
-								}
-							}
-							g4_vertex_buffer_unlock(g._.vertex_buffer);
-
-							// Apply displacement
-							if (config_raw.displace_strength > 0) {
-								tasks++;
-								base_notifyOnNextFrame(() => {
-									console_progress(tr("Apply Displacement"));
-									base_notifyOnNextFrame(() => {
-										let uv_scale = scene_meshes[0].data.scale_tex * context_raw.brushScale;
-										util_mesh_applyDisplacement(texpaint_pack, 0.05 * config_raw.displace_strength, uv_scale);
-										util_mesh_calcNormals();
-										taskDone();
-									});
-								});
-							}
-						}
-					}
-
-					console_log("Processing finished in " + (time_time() - timer));
-					krom_ml_unload();
-
-					taskDone();
-				});
-				});
-				});
-				});
-				});
-			});
-		}
-		ui._x += ew + 3;
-		ui._y = 2 + startY;
-
-		///if (krom_android || krom_ios)
-		zui_combo(base_resHandle, ["2K", "4K"], tr("Resolution"));
-		///else
-		zui_combo(base_resHandle, ["2K", "4K", "8K", "16K"], tr("Resolution"));
-		///end
-		if (base_resHandle.changed) {
-			base_onLayersResized();
-		}
-		ui._x += ew + 3;
-		ui._y = 2 + startY;
-	}
-}

+ 117 - 0
armorlab/Sources/make_material.ts

@@ -0,0 +1,117 @@
+
+let make_material_default_scon: shader_context_t = null;
+let make_material_default_mcon: material_context_t = null;
+let make_material_height_used = false;
+
+function make_material_parse_mesh_material() {
+	let m = project_material_data;
+
+	for (let c of m._.shader.contexts) {
+		if (c.name == "mesh") {
+			array_remove(m._.shader.contexts, c);
+			array_remove(m._.shader._.contexts, c);
+			make_material_delete_context(c);
+			break;
+		}
+	}
+
+	let con = make_mesh_run({ name: "Material", canvas: null });
+	let scon: shader_context_t = shader_context_create(con.data);
+	scon._.override_context = {};
+	if (con.frag.shared_samplers.length > 0) {
+		let sampler = con.frag.shared_samplers[0];
+		scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
+	}
+	if (!context_raw.texture_filter) {
+		scon._.override_context.filter = "point";
+	}
+	scon._.override_context.addressing = "repeat";
+	m._.shader.contexts.push(scon);
+	m._.shader._.contexts.push(scon);
+
+	context_raw.ddirty = 2;
+
+	///if arm_voxels
+	make_material_make_voxel(m);
+	///end
+
+	///if (krom_direct3d12 || krom_vulkan)
+	render_path_raytrace_dirty = 1;
+	///end
+}
+
+///if arm_voxels
+function make_material_make_voxel(m: material_data_t) {
+	let rebuild = true; // heightUsed;
+	if (config_raw.rp_gi != false && rebuild) {
+		let scon: shader_context_t = null;
+		for (let c of m._.shader._.contexts) {
+			if (c.name == "voxel") {
+				scon = c;
+				break;
+			}
+		}
+		if (scon != null) make_voxel_run(scon);
+	}
+}
+///end
+
+function make_material_parse_paint_material() {
+	let m = project_material_data;
+	let scon: shader_context_t = null;
+	let mcon: material_context_t = null;
+	for (let c of m._.shader.contexts) {
+		if (c.name == "paint") {
+			array_remove(m._.shader.contexts, c);
+			array_remove(m._.shader._.contexts, c);
+			if (c != make_material_default_scon) make_material_delete_context(c);
+			break;
+		}
+	}
+	for (let c of m.contexts) {
+		if (c.name == "paint") {
+			array_remove(m.contexts, c);
+			array_remove(m._.contexts, c);
+			break;
+		}
+	}
+
+	let sdata: material_t = { name: "Material", canvas: null };
+	let mcon2: material_context_t = { name: "paint", bind_textures: [] };
+	let con = make_paint_run(sdata, mcon2);
+
+	let compileError = false;
+	let scon2: shader_context_t;
+	let _scon: shader_context_t = shader_context_create(con.data);
+	if (_scon == null) compileError = true;
+	scon2 = _scon;
+
+	if (compileError) return;
+	scon2._.override_context = {};
+	scon2._.override_context.addressing = "repeat";
+	let mcon3: material_context_t = material_context_create(mcon2);
+
+	m._.shader.contexts.push(scon2);
+	m._.shader._.contexts.push(scon2);
+	m.contexts.push(mcon3);
+	m._.contexts.push(mcon3);
+
+	if (make_material_default_scon == null) make_material_default_scon = scon2;
+	if (make_material_default_mcon == null) make_material_default_mcon = mcon3;
+}
+
+function make_material_get_displace_strength(): f32 {
+	let sc = context_main_object().base.transform.scale.x;
+	return config_raw.displace_strength * 0.02 * sc;
+}
+
+function make_material_voxelgi_half_extents(): string {
+	let ext = context_raw.vxao_ext;
+	return `const vec3 voxelgiHalfExtents = vec3(${ext}, ${ext}, ${ext});`;
+}
+
+function make_material_delete_context(c: shader_context_t) {
+	base_notify_on_next_frame(() => { // Ensure pipeline is no longer in use
+		shader_context_delete(c);
+	});
+}

+ 234 - 0
armorlab/Sources/make_mesh.ts

@@ -0,0 +1,234 @@
+
+let make_mesh_layer_pass_count: i32 = 1;
+
+function make_mesh_run(data: material_t, layer_pass: i32 = 0): NodeShaderContextRaw {
+	let con_mesh = node_shader_context_create(data, {
+		name: "mesh",
+		depth_write: layer_pass == 0 ? true : false,
+		compare_mode: layer_pass == 0 ? "less" : "equal",
+		cull_mode: (context_raw.cull_backfaces || layer_pass > 0) ? "clockwise" : "none",
+		vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
+		color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
+		depth_attachment: "DEPTH32"
+	});
+
+	let vert = node_shader_context_make_vert(con_mesh);
+	let frag = node_shader_context_make_frag(con_mesh);
+	frag.ins = vert.outs;
+
+	node_shader_add_out(vert, 'vec2 texCoord');
+	frag.wvpposition = true;
+	node_shader_add_out(vert, 'vec4 prevwvpposition');
+	node_shader_add_uniform(vert, 'mat4 VP', '_view_proj_matrix');
+	node_shader_add_uniform(vert, 'mat4 prevWVP', '_prev_world_view_proj_matrix');
+	vert.wposition = true;
+
+	let textureCount = 0;
+	let displaceStrength = make_material_get_displace_strength();
+	if (make_material_height_used && displaceStrength > 0.0) {
+		vert.n = true;
+		node_shader_write(vert, 'float height = 0.0;');
+		let numLayers = 1;
+		node_shader_write(vert, `wposition += wnormal * vec3(height, height, height) * vec3(${displaceStrength}, ${displaceStrength}, ${displaceStrength});`);
+	}
+
+	node_shader_write(vert, 'gl_Position = mul(vec4(wposition.xyz, 1.0), VP);');
+	let brushScale = context_raw.brush_scale;
+	node_shader_add_uniform(vert, 'float texScale', '_tex_unpack');
+	node_shader_write(vert, `texCoord = tex * ${brushScale} * texScale;`);
+	if (make_material_height_used && displaceStrength > 0) {
+		node_shader_add_uniform(vert, 'mat4 invW', '_inv_world_matrix');
+		node_shader_write(vert, 'prevwvpposition = mul(mul(vec4(wposition, 1.0), invW), prevWVP);');
+	}
+	else {
+		node_shader_write(vert, 'prevwvpposition = mul(vec4(pos.xyz, 1.0), prevWVP);');
+	}
+
+	node_shader_add_out(frag, 'vec4 fragColor[3]');
+	frag.n = true;
+	node_shader_add_function(frag, str_pack_float_int16);
+	node_shader_add_function(frag, str_octahedron_wrap);
+	node_shader_add_function(frag, str_cotangent_frame);
+
+	node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
+	node_shader_write(frag, 'float roughness = 0.0;');
+	node_shader_write(frag, 'float metallic = 0.0;');
+	node_shader_write(frag, 'float occlusion = 1.0;');
+	node_shader_write(frag, 'float opacity = 1.0;');
+	node_shader_write(frag, 'float matid = 0.0;');
+	node_shader_write(frag, 'vec3 ntex = vec3(0.5, 0.5, 1.0);');
+	node_shader_write(frag, 'float height = 0.0;');
+
+	node_shader_write(frag, 'vec4 texpaint_sample = vec4(0.0, 0.0, 0.0, 1.0);');
+	node_shader_write(frag, 'vec4 texpaint_nor_sample;');
+	node_shader_write(frag, 'vec4 texpaint_pack_sample;');
+	node_shader_write(frag, 'float texpaint_opac;');
+
+	if (make_material_height_used) {
+		node_shader_write(frag, 'float height0 = 0.0;');
+		node_shader_write(frag, 'float height1 = 0.0;');
+		node_shader_write(frag, 'float height2 = 0.0;');
+		node_shader_write(frag, 'float height3 = 0.0;');
+	}
+
+	if (context_raw.viewport_mode == viewport_mode_t.LIT && context_raw.render_mode == render_mode_t.FORWARD) {
+		node_shader_add_uniform(frag, 'sampler2D senvmapBrdf', "$brdf.k");
+		node_shader_add_uniform(frag, 'sampler2D senvmapRadiance', '_envmap_radiance');
+		node_shader_add_uniform(frag, 'sampler2D sltcMat', '_ltcMat');
+		node_shader_add_uniform(frag, 'sampler2D sltcMag', '_ltcMag');
+	}
+
+	node_shader_add_shared_sampler(frag, 'sampler2D texpaint');
+	node_shader_write(frag, 'texpaint_sample = textureLodShared(texpaint' + ', texCoord, 0.0);');
+	node_shader_write(frag, 'texpaint_opac = texpaint_sample.a;');
+
+	node_shader_write(frag, 'basecol = texpaint_sample.rgb * texpaint_opac;');
+
+	node_shader_add_shared_sampler(frag, 'sampler2D texpaint_nor');
+	node_shader_write(frag, 'texpaint_nor_sample = textureLodShared(texpaint_nor' + ', texCoord, 0.0);');
+
+	node_shader_write(frag, 'ntex = mix(ntex, texpaint_nor_sample.rgb, texpaint_opac);');
+
+	node_shader_add_shared_sampler(frag, 'sampler2D texpaint_pack');
+	node_shader_write(frag, 'texpaint_pack_sample = textureLodShared(texpaint_pack' + ', texCoord, 0.0);');
+
+	node_shader_write(frag, 'occlusion = mix(occlusion, texpaint_pack_sample.r, texpaint_opac);');
+
+	node_shader_write(frag, 'roughness = mix(roughness, texpaint_pack_sample.g, texpaint_opac);');
+
+	node_shader_write(frag, 'metallic = mix(metallic, texpaint_pack_sample.b, texpaint_opac);');
+
+	node_shader_write(frag, 'height = texpaint_pack_sample.a * texpaint_opac;');
+
+	// if (l.paintHeight && heightUsed) {
+	// 	let assign = l.paintHeightBlend ? "+=" : "=";
+	// 	node_shader_write(frag, `height ${assign} texpaint_pack_sample.a * texpaint_opac;`);
+	// 	node_shader_write(frag, '{');
+	// 	node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
+	// 	node_shader_write(frag, 'float tex_step = 1.0 / texpaintSize.x;');
+	// 	node_shader_write(frag, `height0 ${assign} textureLodShared(texpaint_pack` + ', vec2(texCoord.x - tex_step, texCoord.y), 0.0).a * texpaint_opac;');
+	// 	node_shader_write(frag, `height1 ${assign} textureLodShared(texpaint_pack` + ', vec2(texCoord.x + tex_step, texCoord.y), 0.0).a * texpaint_opac;');
+	// 	node_shader_write(frag, `height2 ${assign} textureLodShared(texpaint_pack` + ', vec2(texCoord.x, texCoord.y - tex_step), 0.0).a * texpaint_opac;');
+	// 	node_shader_write(frag, `height3 ${assign} textureLodShared(texpaint_pack` + ', vec2(texCoord.x, texCoord.y + tex_step), 0.0).a * texpaint_opac;');
+	// 	node_shader_write(frag, '}');
+	// }
+
+	if (make_material_height_used) {
+		node_shader_write(frag, 'if (height > 0.0) {');
+		node_shader_write(frag, 'float height_dx = height0 - height1;');
+		node_shader_write(frag, 'float height_dy = height2 - height3;');
+		// Whiteout blend
+		node_shader_write(frag, 'vec3 n1 = ntex * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
+		node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
+		node_shader_write(frag, 'ntex = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
+		node_shader_write(frag, '}');
+	}
+
+	frag.vvec = true;
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
+	///else
+	node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
+	///end
+	node_shader_write(frag, 'n = ntex * 2.0 - 1.0;');
+	node_shader_write(frag, 'n.y = -n.y;');
+	node_shader_write(frag, 'n = normalize(mul(n, TBN));');
+
+	if (context_raw.viewport_mode == viewport_mode_t.LIT || context_raw.viewport_mode == viewport_mode_t.PATH_TRACE) {
+
+		node_shader_write(frag, 'basecol = pow(basecol, vec3(2.2, 2.2, 2.2));');
+
+		if (context_raw.viewport_shader != null) {
+			let color = context_raw.viewport_shader(frag);
+			node_shader_write(frag, `fragColor[1] = vec4(${color}, 1.0);`);
+		}
+		else if (context_raw.render_mode == render_mode_t.FORWARD && context_raw.viewport_mode != viewport_mode_t.PATH_TRACE) {
+			frag.wposition = true;
+			node_shader_write(frag, 'vec3 albedo = mix(basecol, vec3(0.0, 0.0, 0.0), metallic);');
+			node_shader_write(frag, 'vec3 f0 = mix(vec3(0.04, 0.04, 0.04), basecol, metallic);');
+			frag.vvec = true;
+			node_shader_write(frag, 'float dotNV = max(0.0, dot(n, vVec));');
+			node_shader_write(frag, 'vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(roughness, 1.0 - dotNV) * 256.0), 0).xy;');
+			node_shader_add_uniform(frag, 'int envmapNumMipmaps', '_envmap_num_mipmaps');
+			node_shader_add_uniform(frag, 'vec4 envmapData', '_envmapData'); // angle, sin(angle), cos(angle), strength
+			node_shader_write(frag, 'vec3 wreflect = reflect(-vVec, n);');
+			node_shader_write(frag, 'float envlod = roughness * float(envmapNumMipmaps);');
+			node_shader_add_function(frag, str_envmap_equirect);
+			node_shader_write(frag, 'vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(wreflect, envmapData.x), envlod).rgb;');
+			node_shader_add_uniform(frag, 'vec3 lightArea0', '_light_area0');
+			node_shader_add_uniform(frag, 'vec3 lightArea1', '_light_area1');
+			node_shader_add_uniform(frag, 'vec3 lightArea2', '_light_area2');
+			node_shader_add_uniform(frag, 'vec3 lightArea3', '_light_area3');
+			node_shader_add_function(frag, str_ltc_evaluate);
+			node_shader_add_uniform(frag, 'vec3 lightPos', '_point_pos');
+			node_shader_add_uniform(frag, 'vec3 lightColor', '_point_color');
+			node_shader_write(frag, 'float ldist = distance(wposition, lightPos);');
+			node_shader_write(frag, 'const float LUT_SIZE = 64.0;');
+			node_shader_write(frag, 'const float LUT_SCALE = (LUT_SIZE - 1.0) / LUT_SIZE;');
+			node_shader_write(frag, 'const float LUT_BIAS = 0.5 / LUT_SIZE;');
+			node_shader_write(frag, 'float theta = acos(dotNV);');
+			node_shader_write(frag, 'vec2 tuv = vec2(roughness, theta / (0.5 * 3.14159265));');
+			node_shader_write(frag, 'tuv = tuv * LUT_SCALE + LUT_BIAS;');
+			node_shader_write(frag, 'vec4 t = textureLod(sltcMat, tuv, 0.0);');
+			node_shader_write(frag, 'mat3 minv = mat3(vec3(1.0, 0.0, t.y), vec3(0.0, t.z, 0.0), vec3(t.w, 0.0, t.x));');
+			node_shader_write(frag, 'float ltcspec = ltcEvaluate(n, vVec, dotNV, wposition, minv, lightArea0, lightArea1, lightArea2, lightArea3);');
+			node_shader_write(frag, 'ltcspec *= textureLod(sltcMag, tuv, 0.0).a;');
+			node_shader_write(frag, 'mat3 mident = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);');
+			node_shader_write(frag, 'float ltcdiff = ltcEvaluate(n, vVec, dotNV, wposition, mident, lightArea0, lightArea1, lightArea2, lightArea3);');
+			node_shader_write(frag, 'vec3 direct = albedo * ltcdiff + ltcspec * 0.05;');
+			node_shader_write(frag, 'direct *= lightColor * (1.0 / (ldist * ldist));');
+
+			node_shader_add_uniform(frag, 'vec4 shirr[7]', '_envmap_irradiance');
+			node_shader_add_function(frag, str_sh_irradiance());
+			node_shader_write(frag, 'vec3 indirect = albedo * (shIrradiance(vec3(n.x * envmapData.z - n.y * envmapData.y, n.x * envmapData.y + n.y * envmapData.z, n.z), shirr) / 3.14159265);');
+			node_shader_write(frag, 'indirect += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5;');
+			node_shader_write(frag, 'indirect *= envmapData.w * occlusion;');
+			node_shader_write(frag, 'fragColor[1] = vec4(direct + indirect, 1.0);');
+		}
+		else { // Deferred, Pathtraced
+			node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
+		}
+	}
+	else if (context_raw.viewport_mode == viewport_mode_t.BASE_COLOR) {
+		node_shader_write(frag, 'fragColor[1] = vec4(basecol, 1.0);');
+	}
+	else if (context_raw.viewport_mode == viewport_mode_t.NORMAL_MAP) {
+		node_shader_write(frag, 'fragColor[1] = vec4(ntex.rgb, 1.0);');
+	}
+	else if (context_raw.viewport_mode == viewport_mode_t.OCCLUSION) {
+		node_shader_write(frag, 'fragColor[1] = vec4(vec3(occlusion, occlusion, occlusion), 1.0);');
+	}
+	else if (context_raw.viewport_mode == viewport_mode_t.ROUGHNESS) {
+		node_shader_write(frag, 'fragColor[1] = vec4(vec3(roughness, roughness, roughness), 1.0);');
+	}
+	else if (context_raw.viewport_mode == viewport_mode_t.METALLIC) {
+		node_shader_write(frag, 'fragColor[1] = vec4(vec3(metallic, metallic, metallic), 1.0);');
+	}
+	else if (context_raw.viewport_mode == viewport_mode_t.OPACITY) {
+		node_shader_write(frag, 'fragColor[1] = vec4(vec3(texpaint_sample.a, texpaint_sample.a, texpaint_sample.a), 1.0);');
+	}
+	else if (context_raw.viewport_mode == viewport_mode_t.HEIGHT) {
+		node_shader_write(frag, 'fragColor[1] = vec4(vec3(height, height, height), 1.0);');
+	}
+	else {
+		node_shader_write(frag, 'fragColor[1] = vec4(1.0, 0.0, 1.0, 1.0);'); // Pink
+	}
+
+	if (context_raw.viewport_mode != viewport_mode_t.LIT && context_raw.viewport_mode != viewport_mode_t.PATH_TRACE) {
+		node_shader_write(frag, 'fragColor[1].rgb = pow(fragColor[1].rgb, vec3(2.2, 2.2, 2.2));');
+	}
+
+	node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
+	node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
+	node_shader_write(frag, 'fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, uint(int(matid * 255.0) % 3)));');
+
+	node_shader_write(frag, 'vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
+	node_shader_write(frag, 'vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;');
+	node_shader_write(frag, 'fragColor[2] = vec4(posa - posb, texCoord.xy);');
+
+	parser_material_finalize(con_mesh);
+	con_mesh.data.shader_from_source = true;
+	con_mesh.data.vertex_shader = node_shader_get(vert);
+	con_mesh.data.fragment_shader = node_shader_get(frag);
+	return con_mesh;
+}

+ 274 - 0
armorlab/Sources/make_paint.ts

@@ -0,0 +1,274 @@
+
+function make_paint_run(data: material_t, matcon: material_context_t): NodeShaderContextRaw {
+	let con_paint = node_shader_context_create(data, {
+		name: "paint",
+		depth_write: false,
+		compare_mode: "always", // TODO: align texcoords winding order
+		// cull_mode: "counter_clockwise",
+		cull_mode: "none",
+		vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
+		color_attachments:
+			context_raw.tool == workspace_tool_t.PICKER ? ["RGBA32", "RGBA32", "RGBA32", "RGBA32"] :
+				["RGBA32", "RGBA32", "RGBA32", "R8"]
+	});
+
+	con_paint.data.color_writes_red = [true, true, true, true];
+	con_paint.data.color_writes_green = [true, true, true, true];
+	con_paint.data.color_writes_blue = [true, true, true, true];
+	con_paint.data.color_writes_alpha = [true, true, true, true];
+	con_paint.allow_vcols = mesh_data_get_vertex_array(context_raw.paint_object.data, "col") != null;
+
+	let vert = node_shader_context_make_vert(con_paint);
+	let frag = node_shader_context_make_frag(con_paint);
+	frag.ins = vert.outs;
+
+	if (context_raw.tool == workspace_tool_t.PICKER) {
+		// Mangle vertices to form full screen triangle
+		node_shader_write(vert, 'gl_Position = vec4(-1.0 + float((gl_VertexID & 1) << 2), -1.0 + float((gl_VertexID & 2) << 1), 0.0, 1.0);');
+
+		node_shader_add_uniform(frag, 'sampler2D gbuffer2');
+		node_shader_add_uniform(frag, 'vec2 gbufferSize', '_gbufferSize');
+		node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
+
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, inp.y * gbufferSize.y), 0).ba;');
+		///else
+		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, (1.0 - inp.y) * gbufferSize.y), 0).ba;');
+		///end
+
+		node_shader_add_out(frag, 'vec4 fragColor[4]');
+		node_shader_add_uniform(frag, 'sampler2D texpaint');
+		node_shader_add_uniform(frag, 'sampler2D texpaint_nor');
+		node_shader_add_uniform(frag, 'sampler2D texpaint_pack');
+		node_shader_write(frag, 'fragColor[0] = textureLod(texpaint, texCoordInp, 0.0);');
+		node_shader_write(frag, 'fragColor[1] = textureLod(texpaint_nor, texCoordInp, 0.0);');
+		node_shader_write(frag, 'fragColor[2] = textureLod(texpaint_pack, texCoordInp, 0.0);');
+		node_shader_write(frag, 'fragColor[3].rg = texCoordInp.xy;');
+		con_paint.data.shader_from_source = true;
+		con_paint.data.vertex_shader = node_shader_get(vert);
+		con_paint.data.fragment_shader = node_shader_get(frag);
+		return con_paint;
+	}
+
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(vert, 'vec2 tpos = vec2(tex.x * 2.0 - 1.0, (1.0 - tex.y) * 2.0 - 1.0);');
+	// node_shader_write(vert, 'vec2 tpos = vec2(frac(tex.x * texScale) * 2.0 - 1.0, (1.0 - frac(tex.y * texScale)) * 2.0 - 1.0);'); // 3D View
+	///else
+	node_shader_write(vert, 'vec2 tpos = vec2(tex.xy * 2.0 - 1.0);');
+	///end
+
+	node_shader_write(vert, 'gl_Position = vec4(tpos, 0.0, 1.0);');
+
+	node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
+
+	node_shader_add_out(vert, 'vec4 ndc');
+	node_shader_write_attrib(vert, 'ndc = mul(vec4(pos.xyz, 1.0), WVP);');
+
+	node_shader_write_attrib(frag, 'vec3 sp = vec3((ndc.xyz / ndc.w) * 0.5 + 0.5);');
+	node_shader_write_attrib(frag, 'sp.y = 1.0 - sp.y;');
+	node_shader_write_attrib(frag, 'sp.z -= 0.0001;'); // small bias
+
+	node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
+	node_shader_add_uniform(frag, 'vec4 inplast', '_inputBrushLast');
+	node_shader_add_uniform(frag, 'float aspectRatio', '_aspect_ratio_window');
+	node_shader_write(frag, 'vec2 bsp = sp.xy * 2.0 - 1.0;');
+	node_shader_write(frag, 'bsp.x *= aspectRatio;');
+	node_shader_write(frag, 'bsp = bsp * 0.5 + 0.5;');
+
+	node_shader_add_uniform(frag, 'sampler2D gbufferD');
+
+	node_shader_add_out(frag, 'vec4 fragColor[4]');
+
+	node_shader_add_uniform(frag, 'float brushRadius', '_brushRadius');
+	node_shader_add_uniform(frag, 'float brushOpacity', '_brushOpacity');
+	node_shader_add_uniform(frag, 'float brushHardness', '_brushHardness');
+
+	if (context_raw.tool == workspace_tool_t.ERASER ||
+		context_raw.tool == workspace_tool_t.CLONE  ||
+		context_raw.tool == workspace_tool_t.BLUR   ||
+		context_raw.tool == workspace_tool_t.SMUDGE) {
+
+		node_shader_write(frag, 'float dist = 0.0;');
+
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'float depth = textureLod(gbufferD, inp.xy, 0.0).r;');
+		///else
+		node_shader_write(frag, 'float depth = textureLod(gbufferD, vec2(inp.x, 1.0 - inp.y), 0.0).r;');
+		///end
+
+		node_shader_add_uniform(frag, 'mat4 invVP', '_inv_view_proj_matrix');
+		node_shader_write(frag, 'vec4 winp = vec4(vec2(inp.x, 1.0 - inp.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);');
+		node_shader_write(frag, 'winp = mul(winp, invVP);');
+		node_shader_write(frag, 'winp.xyz /= winp.w;');
+		frag.wposition = true;
+
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'float depthlast = textureLod(gbufferD, inplast.xy, 0.0).r;');
+		///else
+		node_shader_write(frag, 'float depthlast = textureLod(gbufferD, vec2(inplast.x, 1.0 - inplast.y), 0.0).r;');
+		///end
+
+		node_shader_write(frag, 'vec4 winplast = vec4(vec2(inplast.x, 1.0 - inplast.y) * 2.0 - 1.0, depthlast * 2.0 - 1.0, 1.0);');
+		node_shader_write(frag, 'winplast = mul(winplast, invVP);');
+		node_shader_write(frag, 'winplast.xyz /= winplast.w;');
+
+		node_shader_write(frag, 'vec3 pa = wposition - winp.xyz;');
+		node_shader_write(frag, 'vec3 ba = winplast.xyz - winp.xyz;');
+
+		// Capsule
+		node_shader_write(frag, 'float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);');
+		node_shader_write(frag, 'dist = length(pa - ba * h);');
+
+		node_shader_write(frag, 'if (dist > brushRadius) discard;');
+	}
+
+	// node_shader_add_uniform(vert, 'float brushScale', '_brushScale');
+	// node_shader_add_uniform(vert, 'float texScale', '_tex_unpack');
+	// node_shader_add_out(vert, 'vec2 texCoord');
+	// node_shader_write(vert, 'texCoord = tex * brushScale * texScale;');
+
+	if (context_raw.tool == workspace_tool_t.CLONE || context_raw.tool == workspace_tool_t.BLUR || context_raw.tool == workspace_tool_t.SMUDGE) {
+		node_shader_add_uniform(frag, 'sampler2D BLUR');
+		node_shader_add_uniform(frag, 'vec2 SMUDGE', '_gbufferSize');
+		node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
+		node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
+		node_shader_add_uniform(frag, 'sampler2D texpaint_pack_undo', '_texpaint_pack_undo');
+
+		if (context_raw.tool == workspace_tool_t.CLONE) {
+			// node_shader_add_uniform(frag, 'vec2 BLUR', '_cloneDelta');
+			// ///if (krom_direct3d11 || SMUDGE || krom_metal || krom_vulkan)
+			// node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.xy + cloneDelta) * gbufferSize), 0).ba;');
+			// ///else
+			// node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.x + cloneDelta.x) * gbufferSize.x, (1.0 - (sp.y + cloneDelta.y)) * gbufferSize.y), 0).ba;');
+			// ///end
+
+			// node_shader_write(frag, 'vec3 texpaint_pack_sample = textureLod(texpaint_pack_undo, texCoordInp, 0.0).rgb;');
+			// let base = 'textureLod(texpaint_undo, texCoordInp, 0.0).rgb';
+			// let rough = 'texpaint_pack_sample.g';
+			// let met = 'texpaint_pack_sample.b';
+			// let occ = 'texpaint_pack_sample.r';
+			// let nortan = 'textureLod(texpaint_nor_undo, texCoordInp, 0.0).rgb';
+			// let height = '0.0';
+			// let opac = '1.0';
+			// node_shader_write(frag, `vec3 basecol = ${base};`);
+			// node_shader_write(frag, `float roughness = ${rough};`);
+			// node_shader_write(frag, `float metallic = ${met};`);
+			// node_shader_write(frag, `float occlusion = ${occ};`);
+			// node_shader_write(frag, `vec3 nortan = ${nortan};`);
+			// node_shader_write(frag, `float height = ${height};`);
+			// node_shader_write(frag, `float mat_opacity = ${opac};`);
+			// node_shader_write(frag, 'float opacity = mat_opacity * brushOpacity;');
+		}
+		else { // Blur
+			// ///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+			// node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, sp.y * gbufferSize.y), 0).ba;');
+			// ///else
+			// node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, (1.0 - sp.y) * gbufferSize.y), 0).ba;');
+			// ///end
+
+			// node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
+			// node_shader_write(frag, 'float roughness = 0.0;');
+			// node_shader_write(frag, 'float metallic = 0.0;');
+			// node_shader_write(frag, 'float occlusion = 0.0;');
+			// node_shader_write(frag, 'vec3 nortan = vec3(0.0, 0.0, 0.0);');
+			// node_shader_write(frag, 'float height = 0.0;');
+			// node_shader_write(frag, 'float mat_opacity = 1.0;');
+			// node_shader_write(frag, 'float opacity = 0.0;');
+
+			// node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
+			// node_shader_write(frag, 'float blur_step = 1.0 / texpaintSize.x;');
+			// if (context_raw.blurDirectional) {
+			// 	///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
+			// 	node_shader_write(frag, '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};');
+			// 	///else
+			// 	node_shader_write(frag, '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);');
+			// 	///end
+			// 	node_shader_add_uniform(frag, 'vec3 brushDirection', '_brushDirection');
+			// 	node_shader_write(frag, 'vec2 blur_direction = brushDirection.yx;');
+			// 	node_shader_write(frag, 'for (int i = 0; i < 7; ++i) {');
+			// 	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+			// 	node_shader_write(frag, 'vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSize.x, (sp.y + blur_direction.y * blur_step * float(i)) * gbufferSize.y), 0).ba;');
+			// 	///else
+			// 	node_shader_write(frag, 'vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSize.x, (1.0 - (sp.y + blur_direction.y * blur_step * float(i))) * gbufferSize.y), 0).ba;');
+			// 	///end
+			// 	node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp2);');
+			// 	node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[i];');
+			// 	node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[i];');
+			// 	node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp2) * blur_weight[i];');
+			// 	node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
+			// 	node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
+			// 	node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
+			// 	node_shader_write(frag, 'height += texpaint_pack_sample.a;');
+			// 	node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp2).rgb * blur_weight[i];');
+			// 	node_shader_write(frag, '}');
+			// }
+			// else {
+			// 	///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
+			// 	node_shader_write(frag, '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};');
+			// 	///else
+			// 	node_shader_write(frag, '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);');
+			// 	///end
+			// 	// X
+			// 	node_shader_write(frag, 'for (int i = -7; i <= 7; ++i) {');
+			// 	node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(blur_step * float(i), 0.0));');
+			// 	node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[i + 7];');
+			// 	node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[i + 7];');
+			// 	node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(blur_step * float(i), 0.0)) * blur_weight[i + 7];');
+			// 	node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
+			// 	node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
+			// 	node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
+			// 	node_shader_write(frag, 'height += texpaint_pack_sample.a;');
+			// 	node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp + vec2(blur_step * float(i), 0.0)).rgb * blur_weight[i + 7];');
+			// 	node_shader_write(frag, '}');
+			// 	// Y
+			// 	node_shader_write(frag, 'for (int j = -7; j <= 7; ++j) {');
+			// 	node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(0.0, blur_step * float(j)));');
+			// 	node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[j + 7];');
+			// 	node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[j + 7];');
+			// 	node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(0.0, blur_step * float(j))) * blur_weight[j + 7];');
+			// 	node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
+			// 	node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
+			// 	node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
+			// 	node_shader_write(frag, 'height += texpaint_pack_sample.a;');
+			// 	node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp + vec2(0.0, blur_step * float(j))).rgb * blur_weight[j + 7];');
+			// 	node_shader_write(frag, '}');
+			// }
+			// node_shader_write(frag, 'opacity *= brushOpacity;');
+		}
+	}
+
+	node_shader_write(frag, 'float opacity = 1.0;');
+	node_shader_write(frag, 'if (opacity == 0.0) discard;');
+
+	node_shader_write(frag, 'float str = clamp((brushRadius - dist) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
+
+	// Manual blending to preserve memory
+	frag.wvpposition = true;
+	node_shader_write(frag, 'vec2 sample_tc = vec2(wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(frag, 'sample_tc.y = 1.0 - sample_tc.y;');
+	///end
+	node_shader_add_uniform(frag, 'sampler2D paintmask');
+	node_shader_write(frag, 'float sample_mask = textureLod(paintmask, sample_tc, 0.0).r;');
+	node_shader_write(frag, 'str = max(str, sample_mask);');
+
+	node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
+	node_shader_write(frag, 'vec4 sample_undo = textureLod(texpaint_undo, sample_tc, 0.0);');
+
+	if (context_raw.tool == workspace_tool_t.ERASER) {
+		// node_shader_write(frag, 'fragColor[0] = vec4(mix(sample_undo.rgb, vec3(0.0, 0.0, 0.0), str), sample_undo.a - str);');
+		node_shader_write(frag, 'fragColor[0] = vec4(0.0, 0.0, 0.0, 0.0);');
+		node_shader_write(frag, 'fragColor[1] = vec4(0.5, 0.5, 1.0, 0.0);');
+		node_shader_write(frag, 'fragColor[2] = vec4(1.0, 0.0, 0.0, 0.0);');
+	}
+
+	node_shader_write(frag, 'fragColor[3] = vec4(str, 0.0, 0.0, 1.0);');
+
+	parser_material_finalize(con_paint);
+	parser_material_sample_keep_aspect = false;
+	con_paint.data.shader_from_source = true;
+	con_paint.data.vertex_shader = node_shader_get(vert);
+	con_paint.data.fragment_shader = node_shader_get(frag);
+
+	return con_paint;
+}

+ 8 - 8
armorlab/Sources/nodes/BrushOutputNode.ts

@@ -17,24 +17,24 @@ class BrushOutputNode extends LogicNode {
 			{
 				let t = render_target_create();
 				t.name = "texpaint";
-				t.width = config_getTextureResX();
-				t.height = config_getTextureResY();
+				t.width = config_get_texture_res_x();
+				t.height = config_get_texture_res_y();
 				t.format = "RGBA32";
 				this.texpaint = render_path_create_render_target(t)._image;
 			}
 			{
 				let t = render_target_create();
 				t.name = "texpaint_nor";
-				t.width = config_getTextureResX();
-				t.height = config_getTextureResY();
+				t.width = config_get_texture_res_x();
+				t.height = config_get_texture_res_y();
 				t.format = "RGBA32";
 				this.texpaint_nor = render_path_create_render_target(t)._image;
 			}
 			{
 				let t = render_target_create();
 				t.name = "texpaint_pack";
-				t.width = config_getTextureResX();
-				t.height = config_getTextureResY();
+				t.width = config_get_texture_res_x();
+				t.height = config_get_texture_res_y();
 				t.format = "RGBA32";
 				this.texpaint_pack = render_path_create_render_target(t)._image;
 			}
@@ -64,7 +64,7 @@ class BrushOutputNode extends LogicNode {
 		BrushOutputNode.inst = this;
 	}
 
-	override getAsImage = (from: i32, done: (img: image_t)=>void) => {
-		this.inputs[from].getAsImage(done);
+	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
+		this.inputs[from].get_as_image(done);
 	}
 }

+ 5 - 5
armorlab/Sources/nodes/ImageTextureNode.ts

@@ -8,15 +8,15 @@ class ImageTextureNode extends LogicNode {
 		super();
 	}
 
-	override getAsImage = (from: i32, done: (img: image_t)=>void) => {
-		let index = project_assetNames.indexOf(this.file);
+	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
+		let index = project_asset_names.indexOf(this.file);
 		let asset = project_assets[index];
-		done(project_getImage(asset));
+		done(project_get_image(asset));
 	}
 
-	override getCachedImage = (): image_t => {
+	override get_cached_image = (): image_t => {
 		let image: image_t;
-		this.getAsImage(0, (img: image_t) => { image = img; });
+		this.get_as_image(0, (img: image_t) => { image = img; });
 		return image;
 	}
 

+ 19 - 19
armorlab/Sources/nodes/InpaintNode.ts

@@ -19,12 +19,12 @@ class InpaintNode extends LogicNode {
 
 	static init = () => {
 		if (InpaintNode.image == null) {
-			InpaintNode.image = image_create_render_target(config_getTextureResX(), config_getTextureResY());
+			InpaintNode.image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
 		}
 
 		if (InpaintNode.mask == null) {
-			InpaintNode.mask = image_create_render_target(config_getTextureResX(), config_getTextureResY(), tex_format_t.R8);
-			base_notifyOnNextFrame(() => {
+			InpaintNode.mask = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y(), tex_format_t.R8);
+			base_notify_on_next_frame(() => {
 				g4_begin(InpaintNode.mask);
 				g4_clear(color_from_floats(1.0, 1.0, 1.0, 1.0));
 				g4_end();
@@ -36,7 +36,7 @@ class InpaintNode extends LogicNode {
 		}
 
 		if (InpaintNode.result == null) {
-			InpaintNode.result = image_create_render_target(config_getTextureResX(), config_getTextureResY());
+			InpaintNode.result = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
 		}
 	}
 
@@ -44,19 +44,19 @@ class InpaintNode extends LogicNode {
 		InpaintNode.auto = node.buttons[0].default_value == 0 ? false : true;
 		if (!InpaintNode.auto) {
 			InpaintNode.strength = zui_slider(zui_handle("inpaintnode_0", { value: InpaintNode.strength }), tr("strength"), 0, 1, true);
-			InpaintNode.prompt = zui_text_area(zui_handle("inpaintnode_1"), Align.Left, true, tr("prompt"), true);
+			InpaintNode.prompt = zui_text_area(zui_handle("inpaintnode_1"), zui_align_t.LEFT, true, tr("prompt"), true);
 			node.buttons[1].height = 1 + InpaintNode.prompt.split("\n").length;
 		}
 		else node.buttons[1].height = 0;
 	}
 
-	override getAsImage = (from: i32, done: (img: image_t)=>void) => {
-		this.inputs[0].getAsImage((source: image_t) => {
+	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
+		this.inputs[0].get_as_image((source: image_t) => {
 
 			console_progress(tr("Processing") + " - " + tr("Inpaint"));
-			base_notifyOnNextFrame(() => {
+			base_notify_on_next_frame(() => {
 				g2_begin(InpaintNode.image);
-				g2_draw_scaled_image(source, 0, 0, config_getTextureResX(), config_getTextureResY());
+				g2_draw_scaled_image(source, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
 				g2_end();
 
 				InpaintNode.auto ? InpaintNode.texsynthInpaint(InpaintNode.image, false, InpaintNode.mask, done) : InpaintNode.sdInpaint(InpaintNode.image, InpaintNode.mask, done);
@@ -64,15 +64,15 @@ class InpaintNode extends LogicNode {
 		});
 	}
 
-	override getCachedImage = (): image_t => {
-		base_notifyOnNextFrame(() => {
-			this.inputs[0].getAsImage((source: image_t) => {
-				if (base_pipeCopy == null) base_makePipe();
+	override get_cached_image = (): image_t => {
+		base_notify_on_next_frame(() => {
+			this.inputs[0].get_as_image((source: image_t) => {
+				if (base_pipe_copy == null) base_make_pipe();
 				if (const_data_screen_aligned_vb == null) const_data_create_screen_aligned_data();
 				g4_begin(InpaintNode.image);
-				g4_set_pipeline(base_pipeInpaintPreview);
-				g4_set_tex(base_tex0InpaintPreview, source);
-				g4_set_tex(base_texaInpaintPreview, InpaintNode.mask);
+				g4_set_pipeline(base_pipe_inpaint_preview);
+				g4_set_tex(base_tex0_inpaint_preview, source);
+				g4_set_tex(base_texa_inpaint_preview, InpaintNode.mask);
 				g4_set_vertex_buffer(const_data_screen_aligned_vb);
 				g4_set_index_buffer(const_data_screen_aligned_ib);
 				g4_draw();
@@ -87,8 +87,8 @@ class InpaintNode extends LogicNode {
 	}
 
 	static texsynthInpaint = (image: image_t, tiling: bool, mask: image_t/* = null*/, done: (img: image_t)=>void) => {
-		let w = config_getTextureResX();
-		let h = config_getTextureResY();
+		let w = config_get_texture_res_x();
+		let h = config_get_texture_res_y();
 
 		let bytes_img = image_get_pixels(image);
 		let bytes_mask = mask != null ? image_get_pixels(mask) : new ArrayBuffer(w * h);
@@ -163,7 +163,7 @@ class InpaintNode extends LogicNode {
 
 				let start = num_inference_steps - init_timestep;
 
-				TextToPhotoNode.stableDiffusion(InpaintNode.prompt, (img: image_t) => {
+				TextToPhotoNode.stable_diffusion(InpaintNode.prompt, (img: image_t) => {
 					// result.g2_begin();
 					// result.g2_draw_image(img, x * 512, y * 512);
 					// result.g2_end();

+ 63 - 63
armorlab/Sources/nodes/PhotoToPBRNode.ts

@@ -3,18 +3,18 @@ class PhotoToPBRNode extends LogicNode {
 
 	static temp: image_t = null;
 	static images: image_t[] = null;
-	static modelNames = ["base", "occlusion", "roughness", "metallic", "normal", "height"];
+	static model_names = ["base", "occlusion", "roughness", "metallic", "normal", "height"];
 
-	static cachedSource: image_t = null;
-	static borderW = 64;
-	static tileW = 2048;
-	static tileWithBorderW = PhotoToPBRNode.tileW + PhotoToPBRNode.borderW * 2;
+	static cached_source: image_t = null;
+	static border_w = 64;
+	static tile_w = 2048;
+	static tile_with_border_w = PhotoToPBRNode.tile_w + PhotoToPBRNode.border_w * 2;
 
 	constructor() {
 		super();
 
 		if (PhotoToPBRNode.temp == null) {
-			PhotoToPBRNode.temp = image_create_render_target(PhotoToPBRNode.tileWithBorderW, PhotoToPBRNode.tileWithBorderW);
+			PhotoToPBRNode.temp = image_create_render_target(PhotoToPBRNode.tile_with_border_w, PhotoToPBRNode.tile_with_border_w);
 		}
 
 		PhotoToPBRNode.init();
@@ -23,82 +23,82 @@ class PhotoToPBRNode extends LogicNode {
 	static init = () => {
 		if (PhotoToPBRNode.images == null) {
 			PhotoToPBRNode.images = [];
-			for (let i = 0; i < PhotoToPBRNode.modelNames.length; ++i) {
-				PhotoToPBRNode.images.push(image_create_render_target(config_getTextureResX(), config_getTextureResY()));
+			for (let i = 0; i < PhotoToPBRNode.model_names.length; ++i) {
+				PhotoToPBRNode.images.push(image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y()));
 			}
 		}
 	}
 
-	override getAsImage = (from: i32, done: (img: image_t)=>void) => {
+	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
 		let getSource = (done: (img: image_t)=>void) => {
-			if (PhotoToPBRNode.cachedSource != null) done(PhotoToPBRNode.cachedSource);
-			else this.inputs[0].getAsImage(done);
+			if (PhotoToPBRNode.cached_source != null) done(PhotoToPBRNode.cached_source);
+			else this.inputs[0].get_as_image(done);
 		}
 
 		getSource((source: image_t) => {
-			PhotoToPBRNode.cachedSource = source;
+			PhotoToPBRNode.cached_source = source;
 
 			console_progress(tr("Processing") + " - " + tr("Photo to PBR"));
-			base_notifyOnNextFrame(() => {
-				let tileFloats: Float32Array[] = [];
-				let tilesX = math_floor(config_getTextureResX() / PhotoToPBRNode.tileW);
-				let tilesY = math_floor(config_getTextureResY() / PhotoToPBRNode.tileW);
-				let numTiles = tilesX * tilesY;
-				for (let i = 0; i < numTiles; ++i) {
-					let x = i % tilesX;
-					let y = math_floor(i / tilesX);
+			base_notify_on_next_frame(() => {
+				let tile_floats: Float32Array[] = [];
+				let tiles_x = math_floor(config_get_texture_res_x() / PhotoToPBRNode.tile_w);
+				let tiles_y = math_floor(config_get_texture_res_y() / PhotoToPBRNode.tile_w);
+				let num_tiles = tiles_x * tiles_y;
+				for (let i = 0; i < num_tiles; ++i) {
+					let x = i % tiles_x;
+					let y = math_floor(i / tiles_x);
 
 					g2_begin(PhotoToPBRNode.temp);
-					g2_draw_scaled_image(source, PhotoToPBRNode.borderW - x * PhotoToPBRNode.tileW, PhotoToPBRNode.borderW - y * PhotoToPBRNode.tileW, -config_getTextureResX(), config_getTextureResY());
-					g2_draw_scaled_image(source, PhotoToPBRNode.borderW - x * PhotoToPBRNode.tileW, PhotoToPBRNode.borderW - y * PhotoToPBRNode.tileW, config_getTextureResX(), -config_getTextureResY());
-					g2_draw_scaled_image(source, PhotoToPBRNode.borderW - x * PhotoToPBRNode.tileW, PhotoToPBRNode.borderW - y * PhotoToPBRNode.tileW, -config_getTextureResX(), -config_getTextureResY());
-					g2_draw_scaled_image(source, PhotoToPBRNode.borderW - x * PhotoToPBRNode.tileW + PhotoToPBRNode.tileW, PhotoToPBRNode.borderW - y * PhotoToPBRNode.tileW + PhotoToPBRNode.tileW, config_getTextureResX(), config_getTextureResY());
-					g2_draw_scaled_image(source, PhotoToPBRNode.borderW - x * PhotoToPBRNode.tileW + PhotoToPBRNode.tileW, PhotoToPBRNode.borderW - y * PhotoToPBRNode.tileW + PhotoToPBRNode.tileW, -config_getTextureResX(), config_getTextureResY());
-					g2_draw_scaled_image(source, PhotoToPBRNode.borderW - x * PhotoToPBRNode.tileW + PhotoToPBRNode.tileW, PhotoToPBRNode.borderW - y * PhotoToPBRNode.tileW + PhotoToPBRNode.tileW, config_getTextureResX(), -config_getTextureResY());
-					g2_draw_scaled_image(source, PhotoToPBRNode.borderW - x * PhotoToPBRNode.tileW, PhotoToPBRNode.borderW - y * PhotoToPBRNode.tileW, config_getTextureResX(), config_getTextureResY());
+					g2_draw_scaled_image(source, PhotoToPBRNode.border_w - x * PhotoToPBRNode.tile_w, PhotoToPBRNode.border_w - y * PhotoToPBRNode.tile_w, -config_get_texture_res_x(), config_get_texture_res_y());
+					g2_draw_scaled_image(source, PhotoToPBRNode.border_w - x * PhotoToPBRNode.tile_w, PhotoToPBRNode.border_w - y * PhotoToPBRNode.tile_w, config_get_texture_res_x(), -config_get_texture_res_y());
+					g2_draw_scaled_image(source, PhotoToPBRNode.border_w - x * PhotoToPBRNode.tile_w, PhotoToPBRNode.border_w - y * PhotoToPBRNode.tile_w, -config_get_texture_res_x(), -config_get_texture_res_y());
+					g2_draw_scaled_image(source, PhotoToPBRNode.border_w - x * PhotoToPBRNode.tile_w + PhotoToPBRNode.tile_w, PhotoToPBRNode.border_w - y * PhotoToPBRNode.tile_w + PhotoToPBRNode.tile_w, config_get_texture_res_x(), config_get_texture_res_y());
+					g2_draw_scaled_image(source, PhotoToPBRNode.border_w - x * PhotoToPBRNode.tile_w + PhotoToPBRNode.tile_w, PhotoToPBRNode.border_w - y * PhotoToPBRNode.tile_w + PhotoToPBRNode.tile_w, -config_get_texture_res_x(), config_get_texture_res_y());
+					g2_draw_scaled_image(source, PhotoToPBRNode.border_w - x * PhotoToPBRNode.tile_w + PhotoToPBRNode.tile_w, PhotoToPBRNode.border_w - y * PhotoToPBRNode.tile_w + PhotoToPBRNode.tile_w, config_get_texture_res_x(), -config_get_texture_res_y());
+					g2_draw_scaled_image(source, PhotoToPBRNode.border_w - x * PhotoToPBRNode.tile_w, PhotoToPBRNode.border_w - y * PhotoToPBRNode.tile_w, config_get_texture_res_x(), config_get_texture_res_y());
 					g2_end();
 
 					let bytes_img = image_get_pixels(PhotoToPBRNode.temp);
 					let u8a = new Uint8Array(bytes_img);
-					let f32a = new Float32Array(3 * PhotoToPBRNode.tileWithBorderW * PhotoToPBRNode.tileWithBorderW);
-					for (let i = 0; i < (PhotoToPBRNode.tileWithBorderW * PhotoToPBRNode.tileWithBorderW); ++i) {
+					let f32a = new Float32Array(3 * PhotoToPBRNode.tile_with_border_w * PhotoToPBRNode.tile_with_border_w);
+					for (let i = 0; i < (PhotoToPBRNode.tile_with_border_w * PhotoToPBRNode.tile_with_border_w); ++i) {
 						f32a[i                                        ] = (u8a[i * 4    ] / 255 - 0.5) / 0.5;
-						f32a[i + PhotoToPBRNode.tileWithBorderW * PhotoToPBRNode.tileWithBorderW    ] = (u8a[i * 4 + 1] / 255 - 0.5) / 0.5;
-						f32a[i + PhotoToPBRNode.tileWithBorderW * PhotoToPBRNode.tileWithBorderW * 2] = (u8a[i * 4 + 2] / 255 - 0.5) / 0.5;
+						f32a[i + PhotoToPBRNode.tile_with_border_w * PhotoToPBRNode.tile_with_border_w    ] = (u8a[i * 4 + 1] / 255 - 0.5) / 0.5;
+						f32a[i + PhotoToPBRNode.tile_with_border_w * PhotoToPBRNode.tile_with_border_w * 2] = (u8a[i * 4 + 2] / 255 - 0.5) / 0.5;
 					}
 
-					let model_blob: ArrayBuffer = data_get_blob("models/photo_to_" + PhotoToPBRNode.modelNames[from] + ".quant.onnx");
+					let model_blob: ArrayBuffer = data_get_blob("models/photo_to_" + PhotoToPBRNode.model_names[from] + ".quant.onnx");
 					let buf = krom_ml_inference(model_blob, [f32a.buffer], null, null, config_raw.gpu_inference);
 					let ar = new Float32Array(buf);
-					u8a = new Uint8Array(4 * PhotoToPBRNode.tileW * PhotoToPBRNode.tileW);
-					let offsetG = (from == ChannelType.ChannelBaseColor || from == ChannelType.ChannelNormalMap) ? PhotoToPBRNode.tileWithBorderW * PhotoToPBRNode.tileWithBorderW : 0;
-					let offsetB = (from == ChannelType.ChannelBaseColor || from == ChannelType.ChannelNormalMap) ? PhotoToPBRNode.tileWithBorderW * PhotoToPBRNode.tileWithBorderW * 2 : 0;
-					for (let i = 0; i < (PhotoToPBRNode.tileW * PhotoToPBRNode.tileW); ++i) {
-						let x = PhotoToPBRNode.borderW + i % PhotoToPBRNode.tileW;
-						let y = PhotoToPBRNode.borderW + math_floor(i / PhotoToPBRNode.tileW);
-						u8a[i * 4    ] = math_floor((ar[y * PhotoToPBRNode.tileWithBorderW + x          ] * 0.5 + 0.5) * 255);
-						u8a[i * 4 + 1] = math_floor((ar[y * PhotoToPBRNode.tileWithBorderW + x + offsetG] * 0.5 + 0.5) * 255);
-						u8a[i * 4 + 2] = math_floor((ar[y * PhotoToPBRNode.tileWithBorderW + x + offsetB] * 0.5 + 0.5) * 255);
+					u8a = new Uint8Array(4 * PhotoToPBRNode.tile_w * PhotoToPBRNode.tile_w);
+					let offset_g = (from == channel_type_t.BASE_COLOR || from == channel_type_t.NORMAL_MAP) ? PhotoToPBRNode.tile_with_border_w * PhotoToPBRNode.tile_with_border_w : 0;
+					let offset_b = (from == channel_type_t.BASE_COLOR || from == channel_type_t.NORMAL_MAP) ? PhotoToPBRNode.tile_with_border_w * PhotoToPBRNode.tile_with_border_w * 2 : 0;
+					for (let i = 0; i < (PhotoToPBRNode.tile_w * PhotoToPBRNode.tile_w); ++i) {
+						let x = PhotoToPBRNode.border_w + i % PhotoToPBRNode.tile_w;
+						let y = PhotoToPBRNode.border_w + math_floor(i / PhotoToPBRNode.tile_w);
+						u8a[i * 4    ] = math_floor((ar[y * PhotoToPBRNode.tile_with_border_w + x          ] * 0.5 + 0.5) * 255);
+						u8a[i * 4 + 1] = math_floor((ar[y * PhotoToPBRNode.tile_with_border_w + x + offset_g] * 0.5 + 0.5) * 255);
+						u8a[i * 4 + 2] = math_floor((ar[y * PhotoToPBRNode.tile_with_border_w + x + offset_b] * 0.5 + 0.5) * 255);
 						u8a[i * 4 + 3] = 255;
 					}
-					tileFloats.push(ar);
+					tile_floats.push(ar);
 
 					// Use border pixels to blend seams
 					if (i > 0) {
 						if (x > 0) {
-							let ar = tileFloats[i - 1];
-							for (let yy = 0; yy < PhotoToPBRNode.tileW; ++yy) {
-								for (let xx = 0; xx < PhotoToPBRNode.borderW; ++xx) {
-									let i = yy * PhotoToPBRNode.tileW + xx;
+							let ar = tile_floats[i - 1];
+							for (let yy = 0; yy < PhotoToPBRNode.tile_w; ++yy) {
+								for (let xx = 0; xx < PhotoToPBRNode.border_w; ++xx) {
+									let i = yy * PhotoToPBRNode.tile_w + xx;
 									let a = u8a[i * 4];
 									let b = u8a[i * 4 + 1];
 									let c = u8a[i * 4 + 2];
 
-									let aa = math_floor((ar[(PhotoToPBRNode.borderW + yy) * PhotoToPBRNode.tileWithBorderW + PhotoToPBRNode.borderW + PhotoToPBRNode.tileW + xx          ] * 0.5 + 0.5) * 255);
-									let bb = math_floor((ar[(PhotoToPBRNode.borderW + yy) * PhotoToPBRNode.tileWithBorderW + PhotoToPBRNode.borderW + PhotoToPBRNode.tileW + xx + offsetG] * 0.5 + 0.5) * 255);
-									let cc = math_floor((ar[(PhotoToPBRNode.borderW + yy) * PhotoToPBRNode.tileWithBorderW + PhotoToPBRNode.borderW + PhotoToPBRNode.tileW + xx + offsetB] * 0.5 + 0.5) * 255);
+									let aa = math_floor((ar[(PhotoToPBRNode.border_w + yy) * PhotoToPBRNode.tile_with_border_w + PhotoToPBRNode.border_w + PhotoToPBRNode.tile_w + xx          ] * 0.5 + 0.5) * 255);
+									let bb = math_floor((ar[(PhotoToPBRNode.border_w + yy) * PhotoToPBRNode.tile_with_border_w + PhotoToPBRNode.border_w + PhotoToPBRNode.tile_w + xx + offset_g] * 0.5 + 0.5) * 255);
+									let cc = math_floor((ar[(PhotoToPBRNode.border_w + yy) * PhotoToPBRNode.tile_with_border_w + PhotoToPBRNode.border_w + PhotoToPBRNode.tile_w + xx + offset_b] * 0.5 + 0.5) * 255);
 
-									let f = xx / PhotoToPBRNode.borderW;
+									let f = xx / PhotoToPBRNode.border_w;
 									let invf = 1.0 - f;
 									a = math_floor(a * f + aa * invf);
 									b = math_floor(b * f + bb * invf);
@@ -111,19 +111,19 @@ class PhotoToPBRNode extends LogicNode {
 							}
 						}
 						if (y > 0) {
-							let ar = tileFloats[i - tilesX];
-							for (let xx = 0; xx < PhotoToPBRNode.tileW; ++xx) {
-								for (let yy = 0; yy < PhotoToPBRNode.borderW; ++yy) {
-									let i = yy * PhotoToPBRNode.tileW + xx;
+							let ar = tile_floats[i - tiles_x];
+							for (let xx = 0; xx < PhotoToPBRNode.tile_w; ++xx) {
+								for (let yy = 0; yy < PhotoToPBRNode.border_w; ++yy) {
+									let i = yy * PhotoToPBRNode.tile_w + xx;
 									let a = u8a[i * 4];
 									let b = u8a[i * 4 + 1];
 									let c = u8a[i * 4 + 2];
 
-									let aa = math_floor((ar[(PhotoToPBRNode.borderW + PhotoToPBRNode.tileW + yy) * PhotoToPBRNode.tileWithBorderW + PhotoToPBRNode.borderW + xx          ] * 0.5 + 0.5) * 255);
-									let bb = math_floor((ar[(PhotoToPBRNode.borderW + PhotoToPBRNode.tileW + yy) * PhotoToPBRNode.tileWithBorderW + PhotoToPBRNode.borderW + xx + offsetG] * 0.5 + 0.5) * 255);
-									let cc = math_floor((ar[(PhotoToPBRNode.borderW + PhotoToPBRNode.tileW + yy) * PhotoToPBRNode.tileWithBorderW + PhotoToPBRNode.borderW + xx + offsetB] * 0.5 + 0.5) * 255);
+									let aa = math_floor((ar[(PhotoToPBRNode.border_w + PhotoToPBRNode.tile_w + yy) * PhotoToPBRNode.tile_with_border_w + PhotoToPBRNode.border_w + xx          ] * 0.5 + 0.5) * 255);
+									let bb = math_floor((ar[(PhotoToPBRNode.border_w + PhotoToPBRNode.tile_w + yy) * PhotoToPBRNode.tile_with_border_w + PhotoToPBRNode.border_w + xx + offset_g] * 0.5 + 0.5) * 255);
+									let cc = math_floor((ar[(PhotoToPBRNode.border_w + PhotoToPBRNode.tile_w + yy) * PhotoToPBRNode.tile_with_border_w + PhotoToPBRNode.border_w + xx + offset_b] * 0.5 + 0.5) * 255);
 
-									let f = yy / PhotoToPBRNode.borderW;
+									let f = yy / PhotoToPBRNode.border_w;
 									let invf = 1.0 - f;
 									a = math_floor(a * f + aa * invf);
 									b = math_floor(b * f + bb * invf);
@@ -138,14 +138,14 @@ class PhotoToPBRNode extends LogicNode {
 					}
 
 					///if (krom_metal || krom_vulkan)
-					if (from == ChannelType.ChannelBaseColor) PhotoToPBRNode.bgraSwap(u8a.buffer);
+					if (from == channel_type_t.BASE_COLOR) PhotoToPBRNode.bgra_swap(u8a.buffer);
 					///end
 
-					let temp2 = image_from_bytes(u8a.buffer, PhotoToPBRNode.tileW, PhotoToPBRNode.tileW);
+					let temp2 = image_from_bytes(u8a.buffer, PhotoToPBRNode.tile_w, PhotoToPBRNode.tile_w);
 					g2_begin(PhotoToPBRNode.images[from]);
-					g2_draw_image(temp2, x * PhotoToPBRNode.tileW, y * PhotoToPBRNode.tileW);
+					g2_draw_image(temp2, x * PhotoToPBRNode.tile_w, y * PhotoToPBRNode.tile_w);
 					g2_end();
-					base_notifyOnNextFrame(() => {
+					base_notify_on_next_frame(() => {
 						image_unload(temp2);
 					});
 				}
@@ -156,7 +156,7 @@ class PhotoToPBRNode extends LogicNode {
 	}
 
 	///if (krom_metal || krom_vulkan)
-	static bgraSwap = (buffer: ArrayBuffer) => {
+	static bgra_swap = (buffer: ArrayBuffer) => {
 		let u8a = new Uint8Array(buffer);
 		for (let i = 0; i < math_floor(buffer.byteLength / 4); ++i) {
 			let r = u8a[i * 4];

+ 5 - 5
armorlab/Sources/nodes/RGBNode.ts

@@ -7,15 +7,15 @@ class RGBNode extends LogicNode {
 		super();
 	}
 
-	override getAsImage = (from: i32, done: (img: image_t)=>void) => {
+	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
 		if (this.image != null) {
-			base_notifyOnNextFrame(() => {
+			base_notify_on_next_frame(() => {
 				image_unload(this.image);
 			});
 		}
 
 		let f32a = new Float32Array(4);
-		let raw = parser_logic_getRawNode(this);
+		let raw = parser_logic_get_raw_node(this);
 		let default_value = raw.outputs[0].default_value;
 		f32a[0] = default_value[0];
 		f32a[1] = default_value[1];
@@ -25,8 +25,8 @@ class RGBNode extends LogicNode {
 		done(this.image);
 	}
 
-	override getCachedImage = (): image_t => {
-		this.getAsImage(0, (img: image_t) => {});
+	override get_cached_image = (): image_t => {
+		this.get_as_image(0, (img: image_t) => {});
 		return this.image;
 	}
 

+ 15 - 15
armorlab/Sources/nodes/TextToPhotoNode.ts

@@ -12,41 +12,41 @@ class TextToPhotoNode extends LogicNode {
 		super();
 	}
 
-	override getAsImage = (from: i32, done: (img: image_t)=>void) => {
-		TextToPhotoNode.stableDiffusion(TextToPhotoNode.prompt, (_image: image_t) => {
+	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
+		TextToPhotoNode.stable_diffusion(TextToPhotoNode.prompt, (_image: image_t) => {
 			TextToPhotoNode.image = _image;
 			done(TextToPhotoNode.image);
 		});
 	}
 
-	override getCachedImage = (): image_t => {
+	override get_cached_image = (): image_t => {
 		return TextToPhotoNode.image;
 	}
 
 	static buttons = (ui: zui_t, nodes: zui_nodes_t, node: zui_node_t) => {
 		TextToPhotoNode.tiling = node.buttons[0].default_value == 0 ? false : true;
-		TextToPhotoNode.prompt = zui_text_area(zui_handle("texttophotonode_0"), Align.Left, true, tr("prompt"), true);
+		TextToPhotoNode.prompt = zui_text_area(zui_handle("texttophotonode_0"), zui_align_t.LEFT, true, tr("prompt"), true);
 		node.buttons[1].height = TextToPhotoNode.prompt.split("\n").length;
 	}
 
-	static stableDiffusion = (prompt: string, done: (img: image_t)=>void, inpaintLatents: Float32Array = null, offset = 0, upscale = true, mask: Float32Array = null, latents_orig: Float32Array = null) => {
+	static stable_diffusion = (prompt: string, done: (img: image_t)=>void, inpaintLatents: Float32Array = null, offset = 0, upscale = true, mask: Float32Array = null, latents_orig: Float32Array = null) => {
 		let _text_encoder_blob: ArrayBuffer = data_get_blob("models/sd_text_encoder.quant.onnx");
 		let _unet_blob: ArrayBuffer = data_get_blob("models/sd_unet.quant.onnx");
 		let _vae_decoder_blob: ArrayBuffer = data_get_blob("models/sd_vae_decoder.quant.onnx");
 		TextToPhotoNode.text_encoder_blob = _text_encoder_blob;
 		TextToPhotoNode.unet_blob = _unet_blob;
 		TextToPhotoNode.vae_decoder_blob = _vae_decoder_blob;
-		TextToPhotoNode.textEncoder(prompt, inpaintLatents, (latents: Float32Array, text_embeddings: Float32Array) => {
+		TextToPhotoNode.text_encoder(prompt, inpaintLatents, (latents: Float32Array, text_embeddings: Float32Array) => {
 			TextToPhotoNode.unet(latents, text_embeddings, mask, latents_orig, offset, (latents: Float32Array) => {
-				TextToPhotoNode.vaeDecoder(latents, upscale, done);
+				TextToPhotoNode.vae_decoder(latents, upscale, done);
 			});
 		});
 	}
 
-	static textEncoder = (prompt: string, inpaintLatents: Float32Array, done: (a: Float32Array, b: Float32Array)=>void) => {
+	static text_encoder = (prompt: string, inpaintLatents: Float32Array, done: (a: Float32Array, b: Float32Array)=>void) => {
 		console_progress(tr("Processing") + " - " + tr("Text to Photo"));
-		base_notifyOnNextFrame(() => {
-			let words = prompt.replaceAll("\n", " ").replaceAll(",", " , ").replaceAll("  ", " ").trim().split(" ");
+		base_notify_on_next_frame(() => {
+			let words = string_replace_all(string_replace_all(string_replace_all(prompt, "\n", " "), ",", " , "), "  ", " ").trim().split(" ");
 			for (let i = 0; i < words.length; ++i) {
 				TextToPhotoNode.text_input_ids[i + 1] = TextToPhotoNode.vocab[words[i].toLowerCase() + "</w>"];
 			}
@@ -192,9 +192,9 @@ class TextToPhotoNode extends LogicNode {
 		app_notify_on_render_2d(processing);
 	}
 
-	static vaeDecoder = (latents: Float32Array, upscale: bool, done: (img: image_t)=>void) => {
+	static vae_decoder = (latents: Float32Array, upscale: bool, done: (img: image_t)=>void) => {
 		console_progress(tr("Processing") + " - " + tr("Text to Photo"));
-		base_notifyOnNextFrame(() => {
+		base_notify_on_next_frame(() => {
 			for (let i = 0; i < latents.length; ++i) {
 				latents[i] = 1.0 / 0.18215 * latents[i];
 			}
@@ -220,12 +220,12 @@ class TextToPhotoNode extends LogicNode {
 			if (TextToPhotoNode.tiling) {
 				TilingNode.prompt = TextToPhotoNode.prompt;
 				let seed = RandomNode.getSeed();
-				TilingNode.sdTiling(image, seed, done);
+				TilingNode.sd_tiling(image, seed, done);
 			}
 			else {
 				if (upscale) {
-					UpscaleNode.loadBlob(() => {
-						while (image.width < config_getTextureResX()) {
+					UpscaleNode.load_blob(() => {
+						while (image.width < config_get_texture_res_x()) {
 							let lastImage = image;
 							image = UpscaleNode.esrgan(image);
 							image_unload(lastImage);

+ 9 - 9
armorlab/Sources/nodes/TilingNode.ts

@@ -14,7 +14,7 @@ class TilingNode extends LogicNode {
 
 	static init = () => {
 		if (TilingNode.image == null) {
-			TilingNode.image = image_create_render_target(config_getTextureResX(), config_getTextureResY());
+			TilingNode.image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
 		}
 	}
 
@@ -22,34 +22,34 @@ class TilingNode extends LogicNode {
 		TilingNode.auto = node.buttons[0].default_value == 0 ? false : true;
 		if (!TilingNode.auto) {
 			TilingNode.strength = zui_slider(zui_handle("tilingnode_0", { value: TilingNode.strength }), tr("strength"), 0, 1, true);
-			TilingNode.prompt = zui_text_area(zui_handle("tilingnode_1"), Align.Left, true, tr("prompt"), true);
+			TilingNode.prompt = zui_text_area(zui_handle("tilingnode_1"), zui_align_t.LEFT, true, tr("prompt"), true);
 			node.buttons[1].height = 1 + TilingNode.prompt.split("\n").length;
 		}
 		else node.buttons[1].height = 0;
 	}
 
-	override getAsImage = (from: i32, done: (img: image_t)=>void) => {
-		this.inputs[0].getAsImage((source: image_t) => {
+	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
+		this.inputs[0].get_as_image((source: image_t) => {
 			g2_begin(TilingNode.image);
-			g2_draw_scaled_image(source, 0, 0, config_getTextureResX(), config_getTextureResY());
+			g2_draw_scaled_image(source, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
 			g2_end();
 
 			console_progress(tr("Processing") + " - " + tr("Tiling"));
-			base_notifyOnNextFrame(() => {
+			base_notify_on_next_frame(() => {
 				let _done = (image: image_t) => {
 					this.result = image;
 					done(image);
 				}
-				TilingNode.auto ? InpaintNode.texsynthInpaint(TilingNode.image, true, null, _done) : TilingNode.sdTiling(TilingNode.image, -1, _done);
+				TilingNode.auto ? InpaintNode.texsynthInpaint(TilingNode.image, true, null, _done) : TilingNode.sd_tiling(TilingNode.image, -1, _done);
 			});
 		});
 	}
 
-	override getCachedImage = (): image_t => {
+	override get_cached_image = (): image_t => {
 		return this.result;
 	}
 
-	static sdTiling = (image: image_t, seed: i32/* = -1*/, done: (img: image_t)=>void) => {
+	static sd_tiling = (image: image_t, seed: i32/* = -1*/, done: (img: image_t)=>void) => {
 		TextToPhotoNode.tiling = false;
 		let tile = image_create_render_target(512, 512);
 		g2_begin(tile);

+ 11 - 11
armorlab/Sources/nodes/UpscaleNode.ts

@@ -9,16 +9,16 @@ class UpscaleNode extends LogicNode {
 		super();
 	}
 
-	override getAsImage = (from: i32, done: (img: image_t)=>void) => {
-		this.inputs[0].getAsImage((_image: image_t) => {
+	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
+		this.inputs[0].get_as_image((_image: image_t) => {
 			UpscaleNode.image = _image;
 
 			console_progress(tr("Processing") + " - " + tr("Upscale"));
-			base_notifyOnNextFrame(() => {
-				UpscaleNode.loadBlob(() => {
-					if (UpscaleNode.image.width < config_getTextureResX()) {
+			base_notify_on_next_frame(() => {
+				UpscaleNode.load_blob(() => {
+					if (UpscaleNode.image.width < config_get_texture_res_x()) {
 						UpscaleNode.image = UpscaleNode.esrgan(UpscaleNode.image);
-						while (UpscaleNode.image.width < config_getTextureResX()) {
+						while (UpscaleNode.image.width < config_get_texture_res_x()) {
 							let lastImage = UpscaleNode.image;
 							UpscaleNode.image = UpscaleNode.esrgan(UpscaleNode.image);
 							image_unload(lastImage);
@@ -30,17 +30,17 @@ class UpscaleNode extends LogicNode {
 		});
 	}
 
-	static loadBlob = (done: ()=>void) => {
+	static load_blob = (done: ()=>void) => {
 		let _esrgan_blob: ArrayBuffer = data_get_blob("models/esrgan.quant.onnx");
 		UpscaleNode.esrgan_blob = _esrgan_blob;
 		done();
 	}
 
-	override getCachedImage = (): image_t => {
+	override get_cached_image = (): image_t => {
 		return UpscaleNode.image;
 	}
 
-	static doTile = (source: image_t) => {
+	static do_tile = (source: image_t) => {
 		let result: image_t = null;
 		let size1w = source.width;
 		let size1h = source.height;
@@ -105,7 +105,7 @@ class UpscaleNode extends LogicNode {
 					g2_draw_scaled_image(source, 32 - x * tileSize + tileSize, 32 - y * tileSize + tileSize, source.width, -source.height);
 					g2_draw_scaled_image(source, 32 - x * tileSize, 32 - y * tileSize, source.width, source.height);
 					g2_end();
-					let tileResult = UpscaleNode.doTile(tileSource);
+					let tileResult = UpscaleNode.do_tile(tileSource);
 					g2_begin(result);
 					g2_draw_sub_image(tileResult, x * tileSize2x, y * tileSize2x, 64, 64, tileSize2x, tileSize2x);
 					g2_end();
@@ -114,7 +114,7 @@ class UpscaleNode extends LogicNode {
 			}
 			image_unload(tileSource);
 		}
-		else result = UpscaleNode.doTile(source); // Single tile
+		else result = UpscaleNode.do_tile(source); // Single tile
 		return result;
 	}
 

+ 6 - 6
armorlab/Sources/nodes/VarianceNode.ts

@@ -19,14 +19,14 @@ class VarianceNode extends LogicNode {
 	}
 
 	static buttons = (ui: zui_t, nodes: zui_nodes_t, node: zui_node_t) => {
-		VarianceNode.prompt = zui_text_area(zui_handle("variancenode_0"), Align.Left, true, tr("prompt"), true);
+		VarianceNode.prompt = zui_text_area(zui_handle("variancenode_0"), zui_align_t.LEFT, true, tr("prompt"), true);
 		node.buttons[0].height = VarianceNode.prompt.split("\n").length;
 	}
 
-	override getAsImage = (from: i32, done: (img: image_t)=>void) => {
+	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
 		let strength = (VarianceNode.inst.inputs[1].node as any).value;
 
-		VarianceNode.inst.inputs[0].getAsImage((source: image_t) => {
+		VarianceNode.inst.inputs[0].get_as_image((source: image_t) => {
 			g2_begin(VarianceNode.temp);
 			g2_draw_scaled_image(source, 0, 0, 512, 512);
 			g2_end();
@@ -41,7 +41,7 @@ class VarianceNode extends LogicNode {
 			}
 
 			console_progress(tr("Processing") + " - " + tr("Variance"));
-			base_notifyOnNextFrame(() => {
+			base_notify_on_next_frame(() => {
 				let vae_encoder_blob: ArrayBuffer = data_get_blob("models/sd_vae_encoder.quant.onnx");
 				let latents_buf = krom_ml_inference(vae_encoder_blob, [f32a.buffer], [[1, 3, 512, 512]], [1, 4, 64, 64], config_raw.gpu_inference);
 				let latents = new Float32Array(latents_buf);
@@ -62,7 +62,7 @@ class VarianceNode extends LogicNode {
 				}
 				let t_start = num_inference_steps - init_timestep;
 
-				TextToPhotoNode.stableDiffusion(VarianceNode.prompt, (_image: image_t) => {
+				TextToPhotoNode.stable_diffusion(VarianceNode.prompt, (_image: image_t) => {
 					VarianceNode.image = _image;
 					done(VarianceNode.image);
 				}, latents, t_start);
@@ -70,7 +70,7 @@ class VarianceNode extends LogicNode {
 		});
 	}
 
-	override getCachedImage = (): image_t => {
+	override get_cached_image = (): image_t => {
 		return VarianceNode.image;
 	}
 

+ 40 - 0
armorlab/Sources/nodes_brush.ts

@@ -0,0 +1,40 @@
+/// <reference path='./nodes/ImageTextureNode.ts'/>
+/// <reference path='./nodes/RGBNode.ts'/>
+/// <reference path='./nodes/InpaintNode.ts'/>
+/// <reference path='./nodes/PhotoToPBRNode.ts'/>
+/// <reference path='./nodes/TextToPhotoNode.ts'/>
+/// <reference path='./nodes/TilingNode.ts'/>
+/// <reference path='./nodes/UpscaleNode.ts'/>
+/// <reference path='./nodes/VarianceNode.ts'/>
+
+let nodes_brush_categories: string[] = [_tr("Input"), _tr("Model")];
+
+let nodes_brush_list: zui_node_t[][] = [
+	[ // Input
+		ImageTextureNode.def,
+		RGBNode.def,
+	],
+	[ // Model
+		InpaintNode.def,
+		PhotoToPBRNode.def,
+		TextToPhotoNode.def,
+		TilingNode.def,
+		UpscaleNode.def,
+		VarianceNode.def,
+	]
+];
+
+function nodes_brush_create_node(nodeType: string): zui_node_t {
+	for (let c of nodes_brush_list) {
+		for (let n of c) {
+			if (n.type == nodeType) {
+				let canvas = project_canvas;
+				let nodes = project_nodes;
+				let node = ui_nodes_make_node(n, nodes, canvas);
+				canvas.nodes.push(node);
+				return node;
+			}
+		}
+	}
+	return null;
+}

+ 302 - 0
armorlab/Sources/render_path_paint.ts

@@ -0,0 +1,302 @@
+
+let render_path_paint_live_layer_drawn: i32 = 0; ////
+
+function render_path_paint_init() {
+
+	{
+		let t = render_target_create();
+		t.name = "texpaint_blend0";
+		t.width = config_get_texture_res_x();
+		t.height = config_get_texture_res_y();
+		t.format = "R8";
+		render_path_create_render_target(t);
+	}
+	{
+		let t = render_target_create();
+		t.name = "texpaint_blend1";
+		t.width = config_get_texture_res_x();
+		t.height = config_get_texture_res_y();
+		t.format = "R8";
+		render_path_create_render_target(t);
+	}
+	{
+		let t = render_target_create();
+		t.name = "texpaint_picker";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+	{
+		let t = render_target_create();
+		t.name = "texpaint_nor_picker";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+	{
+		let t = render_target_create();
+		t.name = "texpaint_pack_picker";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+	{
+		let t = render_target_create();
+		t.name = "texpaint_uv_picker";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+
+	render_path_load_shader("shader_datas/copy_mrt3_pass/copy_mrt3_pass");
+}
+
+function render_path_paint_commands_paint(dilation: bool = true) {
+	let tid = "";
+
+	if (context_raw.pdirty > 0) {
+
+		if (context_raw.tool == workspace_tool_t.PICKER) {
+
+				///if krom_metal
+				// render_path_set_target("texpaint_picker");
+				// render_path_clear_target(0xff000000);
+				// render_path_set_target("texpaint_nor_picker");
+				// render_path_clear_target(0xff000000);
+				// render_path_set_target("texpaint_pack_picker");
+				// render_path_clear_target(0xff000000);
+				render_path_set_target("texpaint_picker", ["texpaint_nor_picker", "texpaint_pack_picker", "texpaint_uv_picker"]);
+				///else
+				render_path_set_target("texpaint_picker", ["texpaint_nor_picker", "texpaint_pack_picker", "texpaint_uv_picker"]);
+				// render_path_clear_target(0xff000000);
+				///end
+				render_path_bind_target("gbuffer2", "gbuffer2");
+				// tid = context_raw.layer.id;
+				render_path_bind_target("texpaint" + tid, "texpaint");
+				render_path_bind_target("texpaint_nor" + tid, "texpaint_nor");
+				render_path_bind_target("texpaint_pack" + tid, "texpaint_pack");
+				render_path_draw_meshes("paint");
+				ui_header_handle.redraws = 2;
+
+				let texpaint_picker = render_path_render_targets.get("texpaint_picker")._image;
+				let texpaint_nor_picker = render_path_render_targets.get("texpaint_nor_picker")._image;
+				let texpaint_pack_picker = render_path_render_targets.get("texpaint_pack_picker")._image;
+				let texpaint_uv_picker = render_path_render_targets.get("texpaint_uv_picker")._image;
+				let a = image_get_pixels(texpaint_picker);
+				let b = image_get_pixels(texpaint_nor_picker);
+				let c = image_get_pixels(texpaint_pack_picker);
+				let d = image_get_pixels(texpaint_uv_picker);
+
+				if (context_raw.color_picker_callback != null) {
+					context_raw.color_picker_callback(context_raw.picked_color);
+				}
+
+				// Picked surface values
+				// ///if (krom_metal || krom_vulkan)
+				// context_raw.pickedColor.base.Rb = a.get(2);
+				// context_raw.pickedColor.base.Gb = a.get(1);
+				// context_raw.pickedColor.base.Bb = a.get(0);
+				// context_raw.pickedColor.normal.Rb = b.get(2);
+				// context_raw.pickedColor.normal.Gb = b.get(1);
+				// context_raw.pickedColor.normal.Bb = b.get(0);
+				// context_raw.pickedColor.occlusion = c.get(2) / 255;
+				// context_raw.pickedColor.roughness = c.get(1) / 255;
+				// context_raw.pickedColor.metallic = c.get(0) / 255;
+				// context_raw.pickedColor.height = c.get(3) / 255;
+				// context_raw.pickedColor.opacity = a.get(3) / 255;
+				// context_raw.uvxPicked = d.get(2) / 255;
+				// context_raw.uvyPicked = d.get(1) / 255;
+				// ///else
+				// context_raw.pickedColor.base.Rb = a.get(0);
+				// context_raw.pickedColor.base.Gb = a.get(1);
+				// context_raw.pickedColor.base.Bb = a.get(2);
+				// context_raw.pickedColor.normal.Rb = b.get(0);
+				// context_raw.pickedColor.normal.Gb = b.get(1);
+				// context_raw.pickedColor.normal.Bb = b.get(2);
+				// context_raw.pickedColor.occlusion = c.get(0) / 255;
+				// context_raw.pickedColor.roughness = c.get(1) / 255;
+				// context_raw.pickedColor.metallic = c.get(2) / 255;
+				// context_raw.pickedColor.height = c.get(3) / 255;
+				// context_raw.pickedColor.opacity = a.get(3) / 255;
+				// context_raw.uvxPicked = d.get(0) / 255;
+				// context_raw.uvyPicked = d.get(1) / 255;
+				// ///end
+		}
+		else {
+			let texpaint = "texpaint_node_target";
+
+			render_path_set_target("texpaint_blend1");
+			render_path_bind_target("texpaint_blend0", "tex");
+			render_path_draw_shader("shader_datas/copy_pass/copyR8_pass");
+
+			render_path_set_target(texpaint, ["texpaint_nor" + tid, "texpaint_pack" + tid, "texpaint_blend0"]);
+
+			render_path_bind_target("_main", "gbufferD");
+
+			render_path_bind_target("texpaint_blend1", "paintmask");
+
+			// Read texcoords from gbuffer
+			let readTC = context_raw.tool == workspace_tool_t.CLONE ||
+							context_raw.tool == workspace_tool_t.BLUR ||
+							context_raw.tool == workspace_tool_t.SMUDGE;
+			if (readTC) {
+				render_path_bind_target("gbuffer2", "gbuffer2");
+			}
+
+			render_path_draw_meshes("paint");
+		}
+	}
+}
+
+function render_path_paint_commands_cursor() {
+	let tool = context_raw.tool;
+	if (tool != workspace_tool_t.ERASER &&
+		tool != workspace_tool_t.CLONE &&
+		tool != workspace_tool_t.BLUR &&
+		tool != workspace_tool_t.SMUDGE) {
+		return;
+	}
+
+	let nodes = ui_nodes_get_nodes();
+	let canvas = ui_nodes_get_canvas(true);
+	let inpaint = nodes.nodes_selected_id.length > 0 && zui_get_node(canvas.nodes, nodes.nodes_selected_id[0]).type == "InpaintNode";
+
+	if (!base_ui_enabled || base_is_dragging || !inpaint) {
+		return;
+	}
+
+	let mx = context_raw.paint_vec.x;
+	let my = 1.0 - context_raw.paint_vec.y;
+	if (context_raw.brush_locked) {
+		mx = (context_raw.lock_started_x - app_x()) / app_w();
+		my = 1.0 - (context_raw.lock_started_y - app_y()) / app_h();
+	}
+	let radius = context_raw.brush_radius;
+	render_path_paint_draw_cursor(mx, my, radius / 3.4);
+}
+
+function render_path_paint_draw_cursor(mx: f32, my: f32, radius: f32, tint_r: f32 = 1.0, tint_g: f32 = 1.0, tint_b: f32 = 1.0) {
+	let plane = scene_get_child(".Plane").ext;
+	let geom = plane.data;
+	if (base_pipe_cursor == null) base_make_cursor_pipe();
+
+	render_path_set_target("");
+	g4_set_pipeline(base_pipe_cursor);
+	let img = resource_get("cursor.k");
+	g4_set_tex(base_cursor_tex, img);
+	let gbuffer0 = render_path_render_targets.get("gbuffer0")._image;
+	g4_set_tex_depth(base_cursor_gbufferd, gbuffer0);
+	g4_set_float2(base_cursor_mouse, mx, my);
+	g4_set_float2(base_cursor_tex_step, 1 / gbuffer0.width, 1 / gbuffer0.height);
+	g4_set_float(base_cursor_radius, radius);
+	let right = vec4_normalize(camera_object_right_world(scene_camera));
+	g4_set_float3(base_cursor_camera_right, right.x, right.y, right.z);
+	g4_set_float3(base_cursor_tint, tint_r, tint_g, tint_b);
+	g4_set_mat(base_cursor_vp, scene_camera.vp);
+	let helpMat = mat4_identity();
+	mat4_get_inv(helpMat, scene_camera.vp);
+	g4_set_mat(base_cursor_inv_vp, helpMat);
+	///if (krom_metal || krom_vulkan)
+	g4_set_vertex_buffer(mesh_data_get(geom, [{name: "tex", data: "short2norm"}]));
+	///else
+	g4_set_vertex_buffer(geom._vertexBuffer);
+	///end
+	g4_set_index_buffer(geom._indexBuffers[0]);
+	g4_draw();
+
+	g4_disable_scissor();
+	render_path_end();
+}
+
+function render_path_paint_paint_enabled(): bool {
+	return !context_raw.foreground_event;
+}
+
+function render_path_paint_begin() {
+	if (!render_path_paint_paint_enabled()) return;
+}
+
+function render_path_paint_end() {
+	render_path_paint_commands_cursor();
+	context_raw.ddirty--;
+	context_raw.rdirty--;
+
+	if (!render_path_paint_paint_enabled()) return;
+	context_raw.pdirty--;
+}
+
+function render_path_paint_draw() {
+	if (!render_path_paint_paint_enabled()) return;
+
+	render_path_paint_commands_paint();
+
+	if (context_raw.brush_blend_dirty) {
+		context_raw.brush_blend_dirty = false;
+		///if krom_metal
+		render_path_set_target("texpaint_blend0");
+		render_path_clear_target(0x00000000);
+		render_path_set_target("texpaint_blend1");
+		render_path_clear_target(0x00000000);
+		///else
+		render_path_set_target("texpaint_blend0", ["texpaint_blend1"]);
+		render_path_clear_target(0x00000000);
+		///end
+	}
+}
+
+function render_path_paint_bind_layers() {
+	let image: image_t = null;
+	let nodes = ui_nodes_get_nodes();
+	let canvas = ui_nodes_get_canvas(true);
+	if (nodes.nodes_selected_id.length > 0) {
+		let node = zui_get_node(canvas.nodes, nodes.nodes_selected_id[0]);
+		let brushNode = parser_logic_get_logic_node(node);
+		if (brushNode != null) {
+			image = brushNode.get_cached_image();
+		}
+	}
+	if (image != null) {
+		if (render_path_render_targets.get("texpaint_node") == null) {
+			let t = render_target_create();
+			t.name = "texpaint_node";
+			t.width = config_get_texture_res_x();
+			t.height = config_get_texture_res_y();
+			t.format = "RGBA32";
+			render_path_render_targets.set(t.name, t);
+		}
+		if (render_path_render_targets.get("texpaint_node_target") == null) {
+			let t = render_target_create();
+			t.name = "texpaint_node_target";
+			t.width = config_get_texture_res_x();
+			t.height = config_get_texture_res_y();
+			t.format = "RGBA32";
+			render_path_render_targets.set(t.name, t);
+		}
+		render_path_render_targets.get("texpaint_node")._image = image;
+		render_path_bind_target("texpaint_node", "texpaint");
+		render_path_bind_target("texpaint_nor_empty", "texpaint_nor");
+		render_path_bind_target("texpaint_pack_empty", "texpaint_pack");
+
+		let nodes = ui_nodes_get_nodes();
+		let canvas = ui_nodes_get_canvas(true);
+		let node = zui_get_node(canvas.nodes, nodes.nodes_selected_id[0]);
+		let inpaint = node.type == "InpaintNode";
+		if (inpaint) {
+			let brushNode = parser_logic_get_logic_node(node);
+			render_path_render_targets.get("texpaint_node_target")._image = (brushNode as InpaintNode).getTarget();
+		}
+	}
+	else {
+		render_path_bind_target("texpaint", "texpaint");
+		render_path_bind_target("texpaint_nor", "texpaint_nor");
+		render_path_bind_target("texpaint_pack", "texpaint_pack");
+	}
+}
+
+function render_path_paint_unbind_layers() {
+
+}

+ 145 - 0
armorlab/Sources/ui_nodes_ext.ts

@@ -0,0 +1,145 @@
+
+let ui_nodes_ext_last_vertices: DataView = null; // Before displacement
+
+function ui_nodes_ext_draw_buttons(ew: f32, start_y: f32) {
+	let ui = ui_nodes_ui;
+	if (zui_button(tr("Run"))) {
+		console_progress(tr("Processing"));
+
+		let delay_idle_sleep = () => {
+			krom_delay_idle_sleep();
+		}
+		app_notify_on_render_2d(delay_idle_sleep);
+
+		let tasks: i32 = 1;
+
+		let task_done = () => {
+			tasks--;
+			if (tasks == 0) {
+				console_progress(null);
+				context_raw.ddirty = 2;
+				app_remove_render_2d(delay_idle_sleep);
+
+				///if (krom_direct3d12 || krom_vulkan || krom_metal)
+				render_path_raytrace_ready = false;
+				///end
+			}
+		}
+
+		base_notify_on_next_frame(() => {
+			let timer = time_time();
+			parser_logic_parse(project_canvas);
+
+			PhotoToPBRNode.cached_source = null;
+			BrushOutputNode.inst.get_as_image(channel_type_t.BASE_COLOR, (texbase: image_t) => {
+			BrushOutputNode.inst.get_as_image(channel_type_t.OCCLUSION, (texocc: image_t) => {
+			BrushOutputNode.inst.get_as_image(channel_type_t.ROUGHNESS, (texrough: image_t) => {
+			BrushOutputNode.inst.get_as_image(channel_type_t.NORMAL_MAP, (texnor: image_t) => {
+			BrushOutputNode.inst.get_as_image(channel_type_t.HEIGHT, (texheight: image_t) => {
+
+				if (texbase != null) {
+					let texpaint = render_path_render_targets.get("texpaint")._image;
+					g2_begin(texpaint);
+					g2_draw_scaled_image(texbase, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+					g2_end();
+				}
+
+				if (texnor != null) {
+					let texpaint_nor = render_path_render_targets.get("texpaint_nor")._image;
+					g2_begin(texpaint_nor);
+					g2_draw_scaled_image(texnor, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+					g2_end();
+				}
+
+				if (base_pipe_copy == null) base_make_pipe();
+				if (base_pipe_copy_a == null) base_make_pipe_copy_a();
+				if (const_data_screen_aligned_vb == null) const_data_create_screen_aligned_data();
+
+				let texpaint_pack = render_path_render_targets.get("texpaint_pack")._image;
+
+				if (texocc != null) {
+					g2_begin(texpaint_pack);
+					g2_set_pipeline(base_pipe_copy_r);
+					g2_draw_scaled_image(texocc, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+					g2_set_pipeline(null);
+					g2_end();
+				}
+
+				if (texrough != null) {
+					g2_begin(texpaint_pack);
+					g2_set_pipeline(base_pipe_copy_g);
+					g2_draw_scaled_image(texrough, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+					g2_set_pipeline(null);
+					g2_end();
+				}
+
+				if (texheight != null) {
+					g4_begin(texpaint_pack);
+					g4_set_pipeline(base_pipe_copy_a);
+					g4_set_tex(base_pipe_copy_a_tex, texheight);
+					g4_set_vertex_buffer(const_data_screen_aligned_vb);
+					g4_set_index_buffer(const_data_screen_aligned_ib);
+					g4_draw();
+					g4_end();
+
+					if (ui_header_worktab.position == space_type_t.SPACE3D &&
+						BrushOutputNode.inst.inputs[channel_type_t.HEIGHT].node.constructor != FloatNode) {
+
+						// Make copy of vertices before displacement
+						let o = project_paint_objects[0];
+						let g = o.data;
+						let vertices = g4_vertex_buffer_lock(g._.vertex_buffer);
+						if (ui_nodes_ext_last_vertices == null || ui_nodes_ext_last_vertices.byteLength != vertices.byteLength) {
+							ui_nodes_ext_last_vertices = new DataView(new ArrayBuffer(vertices.byteLength));
+							for (let i = 0; i < math_floor(vertices.byteLength / 2); ++i) {
+								ui_nodes_ext_last_vertices.setInt16(i * 2, vertices.getInt16(i * 2, true), true);
+							}
+						}
+						else {
+							for (let i = 0; i < math_floor(vertices.byteLength / 2); ++i) {
+								vertices.setInt16(i * 2, ui_nodes_ext_last_vertices.getInt16(i * 2, true), true);
+							}
+						}
+						g4_vertex_buffer_unlock(g._.vertex_buffer);
+
+						// Apply displacement
+						if (config_raw.displace_strength > 0) {
+							tasks++;
+							base_notify_on_next_frame(() => {
+								console_progress(tr("Apply Displacement"));
+								base_notify_on_next_frame(() => {
+									let uv_scale = scene_meshes[0].data.scale_tex * context_raw.brush_scale;
+									util_mesh_apply_displacement(texpaint_pack, 0.05 * config_raw.displace_strength, uv_scale);
+									util_mesh_calc_normals();
+									task_done();
+								});
+							});
+						}
+					}
+				}
+
+				console_log("Processing finished in " + (time_time() - timer));
+				krom_ml_unload();
+
+				task_done();
+			});
+			});
+			});
+			});
+			});
+		});
+	}
+	ui._x += ew + 3;
+	ui._y = 2 + start_y;
+
+	///if (krom_android || krom_ios)
+	zui_combo(base_res_handle, ["2K", "4K"], tr("Resolution"));
+	///else
+	zui_combo(base_res_handle, ["2K", "4K", "8K", "16K"], tr("Resolution"));
+	///end
+	if (base_res_handle.changed) {
+		base_on_layers_resized();
+	}
+	ui._x += ew + 3;
+	ui._y = 2 + start_y;
+}

+ 0 - 131
armorpaint/Sources/ImportFolder.ts

@@ -1,131 +0,0 @@
-
-class ImportFolder {
-
-	static import_folder_run = (path: string) => {
-		let files: string[] = file_read_directory(path);
-		let mapbase: string = "";
-		let mapopac: string = "";
-		let mapnor: string = "";
-		let mapocc: string = "";
-		let maprough: string = "";
-		let mapmet: string = "";
-		let mapheight: string = "";
-
-		let found_texture: bool = false;
-		// Import maps
-		for (let f of files) {
-			if (!path_is_texture(f)) continue;
-
-			// TODO: handle -albedo
-
-			let base: string = f.substr(0, f.lastIndexOf(".")).toLowerCase();
-			let valid: bool = false;
-			if (mapbase == "" && path_is_base_color_tex(base)) {
-				mapbase = f;
-				valid = true;
-			}
-			if (mapopac == "" && path_is_opacity_tex(base)) {
-				mapopac = f;
-				valid = true;
-			}
-			if (mapnor == "" && path_is_normal_map_tex(base)) {
-				mapnor = f;
-				valid = true;
-			}
-			if (mapocc == "" && path_is_occlusion_tex(base)) {
-				mapocc = f;
-				valid = true;
-			}
-			if (maprough == "" && path_is_roughness_tex(base)) {
-				maprough = f;
-				valid = true;
-			}
-			if (mapmet == "" && path_is_metallic_tex(base)) {
-				mapmet = f;
-				valid = true;
-			}
-			if (mapheight == "" && path_is_displacement_tex(base)) {
-				mapheight = f;
-				valid = true;
-			}
-
-			if (valid) {
-				import_texture_run(path + path_sep + f, false);
-				found_texture = true;
-			}
-		}
-
-		if (!found_texture) {
-			console_info(tr("Folder does not contain textures"));
-			return;
-		}
-
-		// Create material
-		context_raw.material = SlotMaterial.slot_material_create(project_materials[0].data);
-		project_materials.push(context_raw.material);
-		let nodes: zui_nodes_t = context_raw.material.nodes;
-		let canvas: zui_node_canvas_t = context_raw.material.canvas;
-		let dirs: string[] = path.split(path_sep);
-		canvas.name = dirs[dirs.length - 1];
-		let nout: zui_node_t = null;
-		for (let n of canvas.nodes) {
-			if (n.type == "OUTPUT_MATERIAL_PBR") {
-				nout = n;
-				break;
-			}
-		}
-		for (let n of canvas.nodes) {
-			if (n.name == "RGB") {
-				zui_remove_node(n, canvas);
-				break;
-			}
-		}
-
-		// Place nodes
-		let pos: i32 = 0;
-		let start_y: i32 = 100;
-		let node_h: i32 = 164;
-		if (mapbase != "") {
-			ImportFolder.import_folder_place_image_node(nodes, canvas, mapbase, start_y + node_h * pos, nout.id, 0);
-			pos++;
-		}
-		if (mapopac != "") {
-			ImportFolder.import_folder_place_image_node(nodes, canvas, mapopac, start_y + node_h * pos, nout.id, 1);
-			pos++;
-		}
-		if (mapocc != "") {
-			ImportFolder.import_folder_place_image_node(nodes, canvas, mapocc, start_y + node_h * pos, nout.id, 2);
-			pos++;
-		}
-		if (maprough != "") {
-			ImportFolder.import_folder_place_image_node(nodes, canvas, maprough, start_y + node_h * pos, nout.id, 3);
-			pos++;
-		}
-		if (mapmet != "") {
-			ImportFolder.import_folder_place_image_node(nodes, canvas, mapmet, start_y + node_h * pos, nout.id, 4);
-			pos++;
-		}
-		if (mapnor != "") {
-			ImportFolder.import_folder_place_image_node(nodes, canvas, mapnor, start_y + node_h * pos, nout.id, 5);
-			pos++;
-		}
-		if (mapheight != "") {
-			ImportFolder.import_folder_place_image_node(nodes, canvas, mapheight, start_y + node_h * pos, nout.id, 7);
-			pos++;
-		}
-
-		MakeMaterial.make_material_parse_paint_material();
-		util_render_make_material_preview();
-		ui_base_hwnds[1].redraws = 2;
-		history_new_material();
-	}
-
-	static import_folder_place_image_node = (nodes: zui_nodes_t, canvas: zui_node_canvas_t, asset: string, ny: i32, to_id: i32, to_socket: i32) => {
-		let n: zui_node_t = nodes_material_create_node("TEX_IMAGE");
-		n.buttons[0].default_value = base_get_asset_index(asset);
-		n.x = 72;
-		n.y = ny;
-		let l: zui_node_link_t = { id: zui_get_link_id(canvas.links), from_id: n.id, from_socket: 0, to_id: to_id, to_socket: to_socket };
-		canvas.links.push(l);
-	}
-}

+ 0 - 159
armorpaint/Sources/MakeBake.ts

@@ -1,159 +0,0 @@
-
-class MakeBake {
-
-	static make_bake_run = (con: NodeShaderContextRaw, vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		if (context_raw.bake_type == bake_type_t.AO) { // Voxel
-			///if arm_voxels
-			// Apply normal channel
-			frag.wposition = true;
-			frag.n = true;
-			frag.vvec = true;
-			node_shader_add_function(frag, str_cotangent_frame);
-			///if krom_direct3d11
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
-			///else
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
-			///end
-			node_shader_write(frag, 'n = nortan * 2.0 - 1.0;');
-			node_shader_write(frag, 'n.y = -n.y;');
-			node_shader_write(frag, 'n = normalize(mul(n, TBN));');
-
-			node_shader_write(frag, MakeMaterial.make_material_voxelgi_half_extents());
-			node_shader_write(frag, 'vec3 voxpos = wposition / voxelgiHalfExtents;');
-			node_shader_add_uniform(frag, 'sampler3D voxels');
-			node_shader_add_function(frag, str_trace_ao);
-			frag.n = true;
-			let strength: f32 = context_raw.bake_ao_strength;
-			let radius: f32 = context_raw.bake_ao_radius;
-			let offset: f32 = context_raw.bake_ao_offset;
-			node_shader_write(frag, `float ao = traceAO(voxpos, n, ${radius}, ${offset}) * ${strength};`);
-			if (context_raw.bake_axis != bake_axis_t.XYZ) {
-				let axis: string = MakeBake.make_bake_axis_string(context_raw.bake_axis);
-				node_shader_write(frag, `ao *= dot(n, ${axis});`);
-			}
-			node_shader_write(frag, 'ao = 1.0 - ao;');
-			node_shader_write(frag, 'fragColor[0] = vec4(ao, ao, ao, 1.0);');
-			///end
-		}
-		else if (context_raw.bake_type == bake_type_t.CURVATURE) {
-			let pass: bool = parser_material_bake_passthrough;
-			let strength: string = pass ? parser_material_bake_passthrough_strength : context_raw.bake_curv_strength + "";
-			let radius: string = pass ? parser_material_bake_passthrough_radius : context_raw.bake_curv_radius + "";
-			let offset: string = pass ? parser_material_bake_passthrough_offset : context_raw.bake_curv_offset + "";
-			strength = `float(${strength})`;
-			radius = `float(${radius})`;
-			offset = `float(${offset})`;
-			frag.n = true;
-			node_shader_write(frag, 'vec3 dx = dFdx(n);');
-			node_shader_write(frag, 'vec3 dy = dFdy(n);');
-			node_shader_write(frag, 'float curvature = max(dot(dx, dx), dot(dy, dy));');
-			node_shader_write(frag, 'curvature = clamp(pow(curvature, (1.0 / ' + radius + ') * 0.25) * ' + strength + ' * 2.0 + ' + offset + ' / 10.0, 0.0, 1.0);');
-			if (context_raw.bake_axis != bake_axis_t.XYZ) {
-				let axis: string = MakeBake.make_bake_axis_string(context_raw.bake_axis);
-				node_shader_write(frag, `curvature *= dot(n, ${axis});`);
-			}
-			node_shader_write(frag, 'fragColor[0] = vec4(curvature, curvature, curvature, 1.0);');
-		}
-		else if (context_raw.bake_type == bake_type_t.NORMAL) { // Tangent
-			frag.n = true;
-			node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo'); // Baked high-poly normals
-			node_shader_write(frag, 'vec3 n0 = textureLod(texpaint_undo, texCoord, 0.0).rgb * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
-			node_shader_add_function(frag, str_cotangent_frame);
-			node_shader_write(frag, 'mat3 invTBN = transpose(cotangentFrame(n, n, texCoord));');
-			node_shader_write(frag, 'vec3 res = normalize(mul(n0, invTBN)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
-			node_shader_write(frag, 'fragColor[0] = vec4(res, 1.0);');
-		}
-		else if (context_raw.bake_type == bake_type_t.NORMAL_OBJECT) {
-			frag.n = true;
-			node_shader_write(frag, 'fragColor[0] = vec4(n * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5), 1.0);');
-			if (context_raw.bake_up_axis == bake_up_axis_t.Y) {
-				node_shader_write(frag, 'fragColor[0].rgb = vec3(fragColor[0].r, fragColor[0].b, 1.0 - fragColor[0].g);');
-			}
-		}
-		else if (context_raw.bake_type == bake_type_t.HEIGHT) {
-			frag.wposition = true;
-			node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo'); // Baked high-poly positions
-			node_shader_write(frag, 'vec3 wpos0 = textureLod(texpaint_undo, texCoord, 0.0).rgb * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
-			node_shader_write(frag, 'float res = distance(wpos0, wposition) * 10.0;');
-			node_shader_write(frag, 'fragColor[0] = vec4(res, res, res, 1.0);');
-		}
-		else if (context_raw.bake_type == bake_type_t.DERIVATIVE) {
-			node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo'); // Baked height
-			node_shader_write(frag, 'vec2 texDx = dFdx(texCoord);');
-			node_shader_write(frag, 'vec2 texDy = dFdy(texCoord);');
-			node_shader_write(frag, 'float h0 = textureLod(texpaint_undo, texCoord, 0.0).r * 100;');
-			node_shader_write(frag, 'float h1 = textureLod(texpaint_undo, texCoord + texDx, 0.0).r * 100;');
-			node_shader_write(frag, 'float h2 = textureLod(texpaint_undo, texCoord + texDy, 0.0).r * 100;');
-			node_shader_write(frag, 'fragColor[0] = vec4((h1 - h0) * 0.5 + 0.5, (h2 - h0) * 0.5 + 0.5, 0.0, 1.0);');
-		}
-		else if (context_raw.bake_type == bake_type_t.POSITION) {
-			frag.wposition = true;
-			node_shader_write(frag, 'fragColor[0] = vec4(wposition * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5), 1.0);');
-			if (context_raw.bake_up_axis == bake_up_axis_t.Y) {
-				node_shader_write(frag, 'fragColor[0].rgb = vec3(fragColor[0].r, fragColor[0].b, 1.0 - fragColor[0].g);');
-			}
-		}
-		else if (context_raw.bake_type == bake_type_t.TEXCOORD) {
-			node_shader_write(frag, 'fragColor[0] = vec4(texCoord.xy, 0.0, 1.0);');
-		}
-		else if (context_raw.bake_type == bake_type_t.MATERIALID) {
-			node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
-			node_shader_write(frag, 'float sample_matid = textureLod(texpaint_nor_undo, texCoord, 0.0).a + 1.0 / 255.0;');
-			node_shader_write(frag, 'float matid_r = fract(sin(dot(vec2(sample_matid, sample_matid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
-			node_shader_write(frag, 'float matid_g = fract(sin(dot(vec2(sample_matid * 20.0, sample_matid), vec2(12.9898, 78.233))) * 43758.5453);');
-			node_shader_write(frag, 'float matid_b = fract(sin(dot(vec2(sample_matid, sample_matid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
-			node_shader_write(frag, 'fragColor[0] = vec4(matid_r, matid_g, matid_b, 1.0);');
-		}
-		else if (context_raw.bake_type == bake_type_t.OBJECTID) {
-			node_shader_add_uniform(frag, 'float objectId', '_objectId');
-			node_shader_write(frag, 'float obid = objectId + 1.0 / 255.0;');
-			node_shader_write(frag, 'float id_r = fract(sin(dot(vec2(obid, obid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
-			node_shader_write(frag, 'float id_g = fract(sin(dot(vec2(obid * 20.0, obid), vec2(12.9898, 78.233))) * 43758.5453);');
-			node_shader_write(frag, 'float id_b = fract(sin(dot(vec2(obid, obid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
-			node_shader_write(frag, 'fragColor[0] = vec4(id_r, id_g, id_b, 1.0);');
-		}
-		else if (context_raw.bake_type == bake_type_t.VERTEX_COLOR) {
-			if (con.allow_vcols) {
-				node_shader_context_add_elem(con, "col", "short4norm");
-				node_shader_write(frag, 'fragColor[0] = vec4(vcolor.r, vcolor.g, vcolor.b, 1.0);');
-			}
-			else {
-				node_shader_write(frag, 'fragColor[0] = vec4(1.0, 1.0, 1.0, 1.0);');
-			}
-		}
-	}
-
-	static make_bake_position_normal = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		node_shader_add_out(vert, 'vec3 position');
-		node_shader_add_out(vert, 'vec3 normal');
-		node_shader_add_uniform(vert, 'mat4 W', '_world_matrix');
-		node_shader_write(vert, 'position = vec4(mul(vec4(pos.xyz, 1.0), W)).xyz;');
-		node_shader_write(vert, 'normal = vec3(nor.xy, pos.w);');
-		node_shader_write(vert, 'vec2 tpos = vec2(tex.x * 2.0 - 1.0, (1.0 - tex.y) * 2.0 - 1.0);');
-		node_shader_write(vert, 'gl_Position = vec4(tpos, 0.0, 1.0);');
-		node_shader_add_out(frag, 'vec4 fragColor[2]');
-		node_shader_write(frag, 'fragColor[0] = vec4(position, 1.0);');
-		node_shader_write(frag, 'fragColor[1] = vec4(normal, 1.0);');
-	}
-
-	static make_bake_set_color_writes = (con_paint: NodeShaderContextRaw) => {
-		// Bake into base color, disable other slots
-		con_paint.data.color_writes_red[1] = false;
-		con_paint.data.color_writes_green[1] = false;
-		con_paint.data.color_writes_blue[1] = false;
-		con_paint.data.color_writes_alpha[1] = false;
-		con_paint.data.color_writes_red[2] = false;
-		con_paint.data.color_writes_green[2] = false;
-		con_paint.data.color_writes_blue[2] = false;
-		con_paint.data.color_writes_alpha[2] = false;
-	}
-
-	static make_bake_axis_string = (i: i32): string => {
-		return i == bake_axis_t.X  ? "vec3(1,0,0)"  :
-			   i == bake_axis_t.Y  ? "vec3(0,1,0)"  :
-			   i == bake_axis_t.Z  ? "vec3(0,0,1)"  :
-			   i == bake_axis_t.MX ? "vec3(-1,0,0)" :
-			   i == bake_axis_t.MY ? "vec3(0,-1,0)" :
-							 		  "vec3(0,0,-1)";
-	}
-}

+ 0 - 92
armorpaint/Sources/MakeBlur.ts

@@ -1,92 +0,0 @@
-
-class MakeBlur {
-
-	static make_blur_run = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, sp.y * gbufferSize.y), 0).ba;');
-		///else
-		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, (1.0 - sp.y) * gbufferSize.y), 0).ba;');
-		///end
-
-		node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
-		node_shader_write(frag, 'float roughness = 0.0;');
-		node_shader_write(frag, 'float metallic = 0.0;');
-		node_shader_write(frag, 'float occlusion = 0.0;');
-		node_shader_write(frag, 'vec3 nortan = vec3(0.0, 0.0, 0.0);');
-		node_shader_write(frag, 'float height = 0.0;');
-		node_shader_write(frag, 'float mat_opacity = 1.0;');
-		let is_mask: bool = SlotLayer.slot_layer_is_mask(context_raw.layer);
-		if (is_mask) {
-			node_shader_write(frag, 'float opacity = 1.0;');
-		}
-		else {
-			node_shader_write(frag, 'float opacity = 0.0;');
-		}
-		if (context_raw.material.paint_emis) {
-			node_shader_write(frag, 'float emis = 0.0;');
-		}
-		if (context_raw.material.paint_subs) {
-			node_shader_write(frag, 'float subs = 0.0;');
-		}
-
-		node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
-		node_shader_write(frag, 'float blur_step = 1.0 / texpaintSize.x;');
-		if (context_raw.tool == workspace_tool_t.SMUDGE) {
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
-			node_shader_write(frag, '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};');
-			///else
-			node_shader_write(frag, '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);');
-			///end
-			node_shader_add_uniform(frag, 'vec3 brushDirection', '_brushDirection');
-			node_shader_write(frag, 'vec2 blur_direction = brushDirection.yx;');
-			node_shader_write(frag, 'for (int i = 0; i < 7; ++i) {');
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSize.x, (sp.y + blur_direction.y * blur_step * float(i)) * gbufferSize.y), 0).ba;');
-			///else
-			node_shader_write(frag, 'vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSize.x, (1.0 - (sp.y + blur_direction.y * blur_step * float(i))) * gbufferSize.y), 0).ba;');
-			///end
-			node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp2);');
-			node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[i];');
-			node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[i];');
-			node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp2) * blur_weight[i];');
-			node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
-			node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
-			node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
-			node_shader_write(frag, 'height += texpaint_pack_sample.a;');
-			node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp2).rgb * blur_weight[i];');
-			node_shader_write(frag, '}');
-		}
-		else {
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
-			node_shader_write(frag, '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};');
-			///else
-			node_shader_write(frag, '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);');
-			///end
-			// X
-			node_shader_write(frag, 'for (int i = -7; i <= 7; ++i) {');
-			node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(blur_step * float(i), 0.0));');
-			node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[i + 7];');
-			node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[i + 7];');
-			node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(blur_step * float(i), 0.0)) * blur_weight[i + 7];');
-			node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
-			node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
-			node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
-			node_shader_write(frag, 'height += texpaint_pack_sample.a;');
-			node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp + vec2(blur_step * float(i), 0.0)).rgb * blur_weight[i + 7];');
-			node_shader_write(frag, '}');
-			// Y
-			node_shader_write(frag, 'for (int j = -7; j <= 7; ++j) {');
-			node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(0.0, blur_step * float(j)));');
-			node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[j + 7];');
-			node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[j + 7];');
-			node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(0.0, blur_step * float(j))) * blur_weight[j + 7];');
-			node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
-			node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
-			node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
-			node_shader_write(frag, 'height += texpaint_pack_sample.a;');
-			node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp + vec2(0.0, blur_step * float(j))).rgb * blur_weight[j + 7];');
-			node_shader_write(frag, '}');
-		}
-		node_shader_write(frag, 'opacity *= brushOpacity;');
-	}
-}

+ 0 - 94
armorpaint/Sources/MakeBrush.ts

@@ -1,94 +0,0 @@
-
-class MakeBrush {
-
-	static make_brush_run = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-
-		node_shader_write(frag, 'float dist = 0.0;');
-
-		if (context_raw.tool == workspace_tool_t.PARTICLE) return;
-
-		let fill_layer: bool = context_raw.layer.fill_layer != null;
-		let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
-		if (decal && !fill_layer) node_shader_write(frag, 'if (decalMask.z > 0.0) {');
-
-		if (config_raw.brush_3d) {
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'float depth = textureLod(gbufferD, inp.xy, 0.0).r;');
-			///else
-			node_shader_write(frag, 'float depth = textureLod(gbufferD, vec2(inp.x, 1.0 - inp.y), 0.0).r;');
-			///end
-
-			node_shader_add_uniform(frag, 'mat4 invVP', '_inv_view_proj_matrix');
-			node_shader_write(frag, 'vec4 winp = vec4(vec2(inp.x, 1.0 - inp.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);');
-			node_shader_write(frag, 'winp = mul(winp, invVP);');
-			node_shader_write(frag, 'winp.xyz /= winp.w;');
-			frag.wposition = true;
-
-			if (config_raw.brush_angle_reject || context_raw.xray) {
-				node_shader_add_function(frag, str_octahedron_wrap);
-				node_shader_add_uniform(frag, 'sampler2D gbuffer0');
-				///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-				node_shader_write(frag, 'vec2 g0 = textureLod(gbuffer0, inp.xy, 0.0).rg;');
-				///else
-				node_shader_write(frag, 'vec2 g0 = textureLod(gbuffer0, vec2(inp.x, 1.0 - inp.y), 0.0).rg;');
-				///end
-				node_shader_write(frag, 'vec3 wn;');
-				node_shader_write(frag, 'wn.z = 1.0 - abs(g0.x) - abs(g0.y);');
-				node_shader_write(frag, 'wn.xy = wn.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);');
-				node_shader_write(frag, 'wn = normalize(wn);');
-				node_shader_write(frag, 'float planeDist = dot(wn, winp.xyz - wposition);');
-
-				if (config_raw.brush_angle_reject && !context_raw.xray) {
-					node_shader_write(frag, 'if (planeDist < -0.01) discard;');
-					frag.n = true;
-					let angle: f32 = context_raw.brush_angle_reject_dot;
-					node_shader_write(frag, `if (dot(wn, n) < ${angle}) discard;`);
-				}
-			}
-
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'float depthlast = textureLod(gbufferD, inplast.xy, 0.0).r;');
-			///else
-			node_shader_write(frag, 'float depthlast = textureLod(gbufferD, vec2(inplast.x, 1.0 - inplast.y), 0.0).r;');
-			///end
-
-			node_shader_write(frag, 'vec4 winplast = vec4(vec2(inplast.x, 1.0 - inplast.y) * 2.0 - 1.0, depthlast * 2.0 - 1.0, 1.0);');
-			node_shader_write(frag, 'winplast = mul(winplast, invVP);');
-			node_shader_write(frag, 'winplast.xyz /= winplast.w;');
-
-			node_shader_write(frag, 'vec3 pa = wposition - winp.xyz;');
-			if (context_raw.xray) {
-				node_shader_write(frag, 'pa += wn * vec3(planeDist, planeDist, planeDist);');
-			}
-			node_shader_write(frag, 'vec3 ba = winplast.xyz - winp.xyz;');
-
-			if (context_raw.brush_lazy_radius > 0 && context_raw.brush_lazy_step > 0) {
-				// Sphere
-				node_shader_write(frag, 'dist = distance(wposition, winp.xyz);');
-			}
-			else {
-				// Capsule
-				node_shader_write(frag, 'float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);');
-				node_shader_write(frag, 'dist = length(pa - ba * h);');
-			}
-		}
-		else { // !brush3d
-			node_shader_write(frag, 'vec2 binp = inp.xy * 2.0 - 1.0;');
-			node_shader_write(frag, 'binp.x *= aspectRatio;');
-			node_shader_write(frag, 'binp = binp * 0.5 + 0.5;');
-
-			node_shader_write(frag, 'vec2 binplast = inplast.xy * 2.0 - 1.0;');
-			node_shader_write(frag, 'binplast.x *= aspectRatio;');
-			node_shader_write(frag, 'binplast = binplast * 0.5 + 0.5;');
-
-			node_shader_write(frag, 'vec2 pa = bsp.xy - binp.xy;');
-			node_shader_write(frag, 'vec2 ba = binplast.xy - binp.xy;');
-			node_shader_write(frag, 'float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);');
-			node_shader_write(frag, 'dist = length(pa - ba * h);');
-		}
-
-		node_shader_write(frag, 'if (dist > brushRadius) discard;');
-
-		if (decal && !fill_layer) node_shader_write(frag, '}');
-	}
-}

+ 0 - 35
armorpaint/Sources/MakeClone.ts

@@ -1,35 +0,0 @@
-
-class MakeClone {
-
-	static make_clone_run = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		node_shader_add_uniform(frag, 'vec2 cloneDelta', '_cloneDelta');
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.xy + cloneDelta) * gbufferSize), 0).ba;');
-		///else
-		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.x + cloneDelta.x) * gbufferSize.x, (1.0 - (sp.y + cloneDelta.y)) * gbufferSize.y), 0).ba;');
-		///end
-
-		node_shader_write(frag, 'vec3 texpaint_pack_sample = textureLod(texpaint_pack_undo, texCoordInp, 0.0).rgb;');
-		let base: string = 'textureLod(texpaint_undo, texCoordInp, 0.0).rgb';
-		let rough: string = 'texpaint_pack_sample.g';
-		let met: string = 'texpaint_pack_sample.b';
-		let occ: string = 'texpaint_pack_sample.r';
-		let nortan: string = 'textureLod(texpaint_nor_undo, texCoordInp, 0.0).rgb';
-		let height: string = '0.0';
-		let opac: string = '1.0';
-		node_shader_write(frag, `vec3 basecol = ${base};`);
-		node_shader_write(frag, `float roughness = ${rough};`);
-		node_shader_write(frag, `float metallic = ${met};`);
-		node_shader_write(frag, `float occlusion = ${occ};`);
-		node_shader_write(frag, `vec3 nortan = ${nortan};`);
-		node_shader_write(frag, `float height = ${height};`);
-		node_shader_write(frag, `float mat_opacity = ${opac};`);
-		node_shader_write(frag, 'float opacity = mat_opacity * brushOpacity;');
-		if (context_raw.material.paint_emis) {
-			node_shader_write(frag, 'float emis = 0.0;');
-		}
-		if (context_raw.material.paint_subs) {
-			node_shader_write(frag, 'float subs = 0.0;');
-		}
-	}
-}

+ 0 - 46
armorpaint/Sources/MakeColorIdPicker.ts

@@ -1,46 +0,0 @@
-
-class MakeColorIdPicker {
-
-	static make_colorid_picker_run = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		// Mangle vertices to form full screen triangle
-		node_shader_write(vert, 'gl_Position = vec4(-1.0 + float((gl_VertexID & 1) << 2), -1.0 + float((gl_VertexID & 2) << 1), 0.0, 1.0);');
-
-		node_shader_add_uniform(frag, 'sampler2D gbuffer2');
-		node_shader_add_uniform(frag, 'vec2 gbufferSize', '_gbufferSize');
-		node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
-
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, inp.y * gbufferSize.y), 0).ba;');
-		///else
-		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, (1.0 - inp.y) * gbufferSize.y), 0).ba;');
-		///end
-
-		if (context_raw.tool == workspace_tool_t.COLORID) {
-			node_shader_add_out(frag, 'vec4 fragColor');
-			node_shader_add_uniform(frag, 'sampler2D texcolorid', '_texcolorid');
-			node_shader_write(frag, 'vec3 idcol = textureLod(texcolorid, texCoordInp, 0.0).rgb;');
-			node_shader_write(frag, 'fragColor = vec4(idcol, 1.0);');
-		}
-		else if (context_raw.tool == workspace_tool_t.PICKER || context_raw.tool == workspace_tool_t.MATERIAL) {
-			if (context_raw.pick_pos_nor_tex) {
-				node_shader_add_out(frag, 'vec4 fragColor[2]');
-				node_shader_add_uniform(frag, 'sampler2D gbufferD');
-				node_shader_add_uniform(frag, 'mat4 invVP', '_inv_view_proj_matrix');
-				node_shader_add_function(frag, str_get_pos_from_depth);
-				node_shader_add_function(frag, str_get_nor_from_depth);
-				node_shader_write(frag, 'fragColor[0] = vec4(get_pos_from_depth(vec2(inp.x, 1.0 - inp.y), invVP, texturePass(gbufferD)), texCoordInp.x);');
-				node_shader_write(frag, 'fragColor[1] = vec4(get_nor_from_depth(fragColor[0].rgb, vec2(inp.x, 1.0 - inp.y), invVP, vec2(1.0, 1.0) / gbufferSize, texturePass(gbufferD)), texCoordInp.y);');
-			}
-			else {
-				node_shader_add_out(frag, 'vec4 fragColor[4]');
-				node_shader_add_uniform(frag, 'sampler2D texpaint');
-				node_shader_add_uniform(frag, 'sampler2D texpaint_nor');
-				node_shader_add_uniform(frag, 'sampler2D texpaint_pack');
-				node_shader_write(frag, 'fragColor[0] = textureLod(texpaint, texCoordInp, 0.0);');
-				node_shader_write(frag, 'fragColor[1] = textureLod(texpaint_nor, texCoordInp, 0.0);');
-				node_shader_write(frag, 'fragColor[2] = textureLod(texpaint_pack, texCoordInp, 0.0);');
-				node_shader_write(frag, 'fragColor[3].rg = texCoordInp.xy;');
-			}
-		}
-	}
-}

+ 0 - 50
armorpaint/Sources/MakeDiscard.ts

@@ -1,50 +0,0 @@
-
-class MakeDiscard {
-
-	static make_discard_color_id = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		node_shader_add_uniform(frag, 'sampler2D texpaint_colorid'); // 1x1 picker
-		node_shader_add_uniform(frag, 'sampler2D texcolorid', '_texcolorid'); // color map
-		node_shader_write(frag, 'vec3 colorid_c1 = texelFetch(texpaint_colorid, ivec2(0, 0), 0).rgb;');
-		node_shader_write(frag, 'vec3 colorid_c2 = textureLod(texcolorid, texCoordPick, 0).rgb;');
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
-		node_shader_write(frag, 'if (any(colorid_c1 != colorid_c2)) discard;');
-		///else
-		node_shader_write(frag, 'if (colorid_c1 != colorid_c2) discard;');
-		///end
-	}
-
-	static make_discard_face = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		node_shader_add_uniform(frag, 'sampler2D gbuffer2');
-		node_shader_add_uniform(frag, 'sampler2D textrianglemap', '_textrianglemap');
-		node_shader_add_uniform(frag, 'vec2 textrianglemapSize', '_texpaintSize');
-		node_shader_add_uniform(frag, 'vec2 gbufferSize', '_gbufferSize');
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, inp.y * gbufferSize.y), 0).ba;');
-		///else
-		node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, (1.0 - inp.y) * gbufferSize.y), 0).ba;');
-		///end
-		node_shader_write(frag, 'vec4 face_c1 = texelFetch(textrianglemap, ivec2(texCoordInp * textrianglemapSize), 0);');
-		node_shader_write(frag, 'vec4 face_c2 = textureLod(textrianglemap, texCoordPick, 0);');
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
-		node_shader_write(frag, 'if (any(face_c1 != face_c2)) discard;');
-		///else
-		node_shader_write(frag, 'if (face_c1 != face_c2) discard;');
-		///end
-	}
-
-	static make_discard_uv_island = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		node_shader_add_uniform(frag, 'sampler2D texuvislandmap', '_texuvislandmap');
-		node_shader_write(frag, 'if (textureLod(texuvislandmap, texCoordPick, 0).r == 0.0) discard;');
-	}
-
-	static make_discard_material_id = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		frag.wvpposition = true;
-		node_shader_write(frag, 'vec2 picker_sample_tc = vec2(wvpposition.x / wvpposition.w, wvpposition.y / wvpposition.w) * 0.5 + 0.5;');
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(frag, 'picker_sample_tc.y = 1.0 - picker_sample_tc.y;');
-		///end
-		node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
-		let matid: i32 = context_raw.materialid_picked / 255;
-		node_shader_write(frag, `if (${matid} != textureLod(texpaint_nor_undo, picker_sample_tc, 0.0).a) discard;`);
-	}
-}

+ 0 - 493
armorpaint/Sources/MakeMaterial.ts

@@ -1,493 +0,0 @@
-
-class MakeMaterial {
-
-	static make_material_default_scon: shader_context_t = null;
-	static make_material_default_mcon: material_context_t = null;
-
-	static make_material_height_used = false;
-	static make_material_emis_used = false;
-	static make_material_subs_used = false;
-
-	static make_material_get_mout = (): bool => {
-		for (let n of ui_nodes_get_canvas_material().nodes) if (n.type == "OUTPUT_MATERIAL_PBR") return true;
-		return false;
-	}
-
-	static make_material_parse_mesh_material = () => {
-		let m: material_data_t = project_materials[0].data;
-
-		for (let c of m._.shader._.contexts) {
-			if (c.name == "mesh") {
-				array_remove(m._.shader.contexts, c);
-				array_remove(m._.shader._.contexts, c);
-				MakeMaterial.make_material_delete_context(c);
-				break;
-			}
-		}
-
-		if (MakeMesh.make_mesh_layer_pass_count > 1) {
-			let i: i32 = 0;
-			while (i < m._.shader._.contexts.length) {
-				let c: shader_context_t = m._.shader._.contexts[i];
-				for (let j: i32 = 1; j < MakeMesh.make_mesh_layer_pass_count; ++j) {
-					if (c.name == "mesh" + j) {
-						array_remove(m._.shader.contexts, c);
-						array_remove(m._.shader._.contexts, c);
-						MakeMaterial.make_material_delete_context(c);
-						i--;
-						break;
-					}
-				}
-				i++;
-			}
-
-			i = 0;
-			while (i < m._.contexts.length) {
-				let c: material_context_t = m._.contexts[i];
-				for (let j: i32 = 1; j < MakeMesh.make_mesh_layer_pass_count; ++j) {
-					if (c.name == "mesh" + j) {
-						array_remove(m.contexts, c);
-						array_remove(m._.contexts, c);
-						i--;
-						break;
-					}
-				}
-				i++;
-			}
-		}
-
-		let con: NodeShaderContextRaw = MakeMesh.make_mesh_run({ name: "Material", canvas: null });
-		let scon: shader_context_t = shader_context_create(con.data);
-		scon._.override_context = {};
-		if (con.frag.shared_samplers.length > 0) {
-			let sampler: string = con.frag.shared_samplers[0];
-			scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
-		}
-		if (!context_raw.texture_filter) {
-			scon._.override_context.filter = "point";
-		}
-		m._.shader.contexts.push(scon);
-		m._.shader._.contexts.push(scon);
-
-		for (let i: i32 = 1; i < MakeMesh.make_mesh_layer_pass_count; ++i) {
-			let con: NodeShaderContextRaw = MakeMesh.make_mesh_run({ name: "Material", canvas: null }, i);
-			let scon: shader_context_t = shader_context_create(con.data);
-			scon._.override_context = {};
-			if (con.frag.shared_samplers.length > 0) {
-				let sampler: string = con.frag.shared_samplers[0];
-				scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
-			}
-			if (!context_raw.texture_filter) {
-				scon._.override_context.filter = "point";
-			}
-			m._.shader.contexts.push(scon);
-			m._.shader._.contexts.push(scon);
-
-			let mcon: material_context_t;
-			mcon = material_context_create({ name: "mesh" + i, bind_textures: [] });
-			m.contexts.push(mcon);
-			m._.contexts.push(mcon);
-		}
-
-		context_raw.ddirty = 2;
-
-		///if arm_voxels
-		MakeMaterial.make_material_make_voxel(m);
-		///end
-
-		///if (krom_direct3d12 || krom_vulkan || krom_metal)
-		render_path_raytrace_dirty = 1;
-		///end
-	}
-
-	static make_material_parse_particle_material = () => {
-		let m: material_data_t = context_raw.particle_material;
-		let sc: shader_context_t = null;
-		for (let c of m._.shader._.contexts) {
-			if (c.name == "mesh") {
-				sc = c;
-				break;
-			}
-		}
-		if (sc != null) {
-			array_remove(m._.shader.contexts, sc);
-			array_remove(m._.shader._.contexts, sc);
-		}
-		let con: NodeShaderContextRaw = MakeParticle.make_particle_run({ name: "MaterialParticle", canvas: null });
-		if (sc != null) MakeMaterial.make_material_delete_context(sc);
-		sc = shader_context_create(con.data);
-		m._.shader.contexts.push(sc);
-		m._.shader._.contexts.push(sc);
-	}
-
-	static make_material_parse_mesh_preview_material = (md: material_data_t = null) => {
-		if (!MakeMaterial.make_material_get_mout()) return;
-
-		let m: material_data_t = md == null ? project_materials[0].data : md;
-		let scon: shader_context_t = null;
-		for (let c of m._.shader._.contexts) {
-			if (c.name == "mesh") {
-				scon = c;
-				break;
-			}
-		}
-
-		array_remove(m._.shader.contexts, scon);
-		array_remove(m._.shader._.contexts, scon);
-
-		let mcon: material_context_t = { name: "mesh", bind_textures: [] };
-
-		let sd: material_t = { name: "Material", canvas: null };
-		let con: NodeShaderContextRaw = MakeMeshPreview.make_mesh_preview_run(sd, mcon);
-
-		for (let i: i32 = 0; i < m._.contexts.length; ++i) {
-			if (m._.contexts[i].name == "mesh") {
-				m._.contexts[i] = material_context_create(mcon);
-				break;
-			}
-		}
-
-		if (scon != null) MakeMaterial.make_material_delete_context(scon);
-
-		let compile_error: bool = false;
-		let _scon: shader_context_t = shader_context_create(con.data);
-		if (_scon == null) compile_error = true;
-		scon = _scon;
-		if (compile_error) return;
-
-		m._.shader.contexts.push(scon);
-		m._.shader._.contexts.push(scon);
-	}
-
-	///if arm_voxels
-	static make_material_make_voxel = (m: material_data_t) => {
-		let rebuild: bool = MakeMaterial.make_material_height_used;
-		if (config_raw.rp_gi != false && rebuild) {
-			let scon: shader_context_t = null;
-			for (let c of m._.shader._.contexts) {
-				if (c.name == "voxel") {
-					scon = c;
-					break;
-				}
-			}
-			if (scon != null) make_voxel_run(scon);
-		}
-	}
-	///end
-
-	static make_material_parse_paint_material = (bake_previews = true) => {
-		if (!MakeMaterial.make_material_get_mout()) return;
-
-		if (bake_previews) {
-			let current: image_t = _g2_current;
-			let g2_in_use: bool = _g2_in_use;
-			if (g2_in_use) g2_end();
-			MakeMaterial.make_material_bake_node_previews();
-			if (g2_in_use) g2_begin(current);
-		}
-
-		let m: material_data_t = project_materials[0].data;
-		// let scon: TShaderContext = null;
-		// let mcon: TMaterialContext = null;
-		for (let c of m._.shader._.contexts) {
-			if (c.name == "paint") {
-				array_remove(m._.shader.contexts, c);
-				array_remove(m._.shader._.contexts, c);
-				if (c != MakeMaterial.make_material_default_scon) MakeMaterial.make_material_delete_context(c);
-				break;
-			}
-		}
-		for (let c of m._.contexts) {
-			if (c.name == "paint") {
-				array_remove(m.contexts, c);
-				array_remove(m._.contexts, c);
-				break;
-			}
-		}
-
-		let sdata: material_t = { name: "Material", canvas: ui_nodes_get_canvas_material() };
-		let tmcon: material_context_t = { name: "paint", bind_textures: [] };
-		let con: NodeShaderContextRaw = MakePaint.make_paint_run(sdata, tmcon);
-
-		let compile_error: bool = false;
-		let scon: shader_context_t;
-		let _scon: shader_context_t = shader_context_create(con.data);
-		if (_scon == null) compile_error = true;
-		scon = _scon;
-		if (compile_error) return;
-		scon._.override_context = {};
-		scon._.override_context.addressing = "repeat";
-		let mcon: material_context_t = material_context_create(tmcon);
-
-		m._.shader.contexts.push(scon);
-		m._.shader._.contexts.push(scon);
-		m.contexts.push(mcon);
-		m._.contexts.push(mcon);
-
-		if (MakeMaterial.make_material_default_scon == null) MakeMaterial.make_material_default_scon = scon;
-		if (MakeMaterial.make_material_default_mcon == null) MakeMaterial.make_material_default_mcon = mcon;
-	}
-
-	static make_material_bake_node_previews = () => {
-		context_raw.node_previews_used = [];
-		if (context_raw.node_previews == null) context_raw.node_previews = map_create();
-		MakeMaterial.make_material_traverse_nodes(ui_nodes_get_canvas_material().nodes, null, []);
-		for (let key of context_raw.node_previews.keys()) {
-			if (context_raw.node_previews_used.indexOf(key) == -1) {
-				let image: image_t = context_raw.node_previews.get(key);
-				base_notify_on_next_frame(function() { image_unload(image); });
-				context_raw.node_previews.delete(key);
-			}
-		}
-	}
-
-	static make_material_traverse_nodes = (nodes: zui_node_t[], group: zui_node_canvas_t, parents: zui_node_t[]) => {
-		for (let node of nodes) {
-			MakeMaterial.make_material_bake_node_preview(node, group, parents);
-			if (node.type == "GROUP") {
-				for (let g of project_material_groups) {
-					if (g.canvas.name == node.name) {
-						parents.push(node);
-						MakeMaterial.make_material_traverse_nodes(g.canvas.nodes, g.canvas, parents);
-						parents.pop();
-						break;
-					}
-				}
-			}
-		}
-	}
-
-	static make_material_bake_node_preview = (node: zui_node_t, group: zui_node_canvas_t, parents: zui_node_t[]) => {
-		if (node.type == "BLUR") {
-			let id: string = parser_material_node_name(node, parents);
-			let image: image_t = context_raw.node_previews.get(id);
-			context_raw.node_previews_used.push(id);
-			let resX: i32 = math_floor(config_get_texture_res_x() / 4);
-			let resY: i32 = math_floor(config_get_texture_res_y() / 4);
-			if (image == null || image.width != resX || image.height != resY) {
-				if (image != null) image_unload(image);
-				image = image_create_render_target(resX, resY);
-				context_raw.node_previews.set(id, image);
-			}
-
-			parser_material_blur_passthrough = true;
-			util_render_make_node_preview(ui_nodes_get_canvas_material(), node, image, group, parents);
-			parser_material_blur_passthrough = false;
-		}
-		else if (node.type == "DIRECT_WARP") {
-			let id: string = parser_material_node_name(node, parents);
-			let image: image_t = context_raw.node_previews.get(id);
-			context_raw.node_previews_used.push(id);
-			let resX: i32 = math_floor(config_get_texture_res_x());
-			let resY: i32 = math_floor(config_get_texture_res_y());
-			if (image == null || image.width != resX || image.height != resY) {
-				if (image != null) image_unload(image);
-				image = image_create_render_target(resX, resY);
-				context_raw.node_previews.set(id, image);
-			}
-
-			parser_material_warp_passthrough = true;
-			util_render_make_node_preview(ui_nodes_get_canvas_material(), node, image, group, parents);
-			parser_material_warp_passthrough = false;
-		}
-		else if (node.type == "BAKE_CURVATURE") {
-			let id: string = parser_material_node_name(node, parents);
-			let image: image_t = context_raw.node_previews.get(id);
-			context_raw.node_previews_used.push(id);
-			let resX: i32 = math_floor(config_get_texture_res_x());
-			let resY: i32 = math_floor(config_get_texture_res_y());
-			if (image == null || image.width != resX || image.height != resY) {
-				if (image != null) image_unload(image);
-				image = image_create_render_target(resX, resY, tex_format_t.R8);
-				context_raw.node_previews.set(id, image);
-			}
-
-			if (RenderPathPaint.render_path_paint_live_layer == null) {
-				RenderPathPaint.render_path_paint_live_layer = SlotLayer.slot_layer_create("_live");
-			}
-
-			let _space: i32 = ui_header_worktab.position;
-			let _tool: workspace_tool_t = context_raw.tool;
-			let _bake_type: bake_type_t = context_raw.bake_type;
-			ui_header_worktab.position = space_type_t.SPACE3D;
-			context_raw.tool = workspace_tool_t.BAKE;
-			context_raw.bake_type = bake_type_t.CURVATURE;
-
-			parser_material_bake_passthrough = true;
-			parser_material_start_node = node;
-			parser_material_start_group = group;
-			parser_material_start_parents = parents;
-			MakeMaterial.make_material_parse_paint_material(false);
-			parser_material_bake_passthrough = false;
-			parser_material_start_node = null;
-			parser_material_start_group = null;
-			parser_material_start_parents = null;
-			context_raw.pdirty = 1;
-			RenderPathPaint.render_path_paint_use_live_layer(true);
-			RenderPathPaint.render_path_paint_commands_paint(false);
-			RenderPathPaint.render_path_paint_dilate(true, false);
-			RenderPathPaint.render_path_paint_use_live_layer(false);
-			context_raw.pdirty = 0;
-
-			ui_header_worktab.position = _space;
-			context_raw.tool = _tool;
-			context_raw.bake_type = _bake_type;
-			MakeMaterial.make_material_parse_paint_material(false);
-
-			let rts: map_t<string, render_target_t> = render_path_render_targets;
-			let texpaint_live: render_target_t = rts.get("texpaint_live");
-
-			g2_begin(image);
-			g2_draw_image(texpaint_live._image, 0, 0);
-			g2_end();
-		}
-	}
-
-	static make_material_parse_node_preview_material = (node: zui_node_t, group: zui_node_canvas_t = null, parents: zui_node_t[] = null): { scon: shader_context_t, mcon: material_context_t } => {
-		if (node.outputs.length == 0) return null;
-		let sdata: material_t = { name: "Material", canvas: ui_nodes_get_canvas_material() };
-		let mcon_raw: material_context_t = { name: "mesh", bind_textures: [] };
-		let con: NodeShaderContextRaw = MakeNodePreview.make_node_preview_run(sdata, mcon_raw, node, group, parents);
-		let compile_error: bool = false;
-		let scon: shader_context_t;
-		let _scon: shader_context_t = shader_context_create(con.data);
-		if (_scon == null) compile_error = true;
-		scon = _scon;
-		if (compile_error) return null;
-		let mcon: material_context_t = material_context_create(mcon_raw);
-		return { scon: scon, mcon: mcon };
-	}
-
-	static make_material_parse_brush = () => {
-		parser_logic_parse(context_raw.brush.canvas);
-	}
-
-	static make_material_blend_mode = (frag: NodeShaderRaw, blending: i32, cola: string, colb: string, opac: string): string => {
-		if (blending == blend_type_t.MIX) {
-			return `mix(${cola}, ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.DARKEN) {
-			return `mix(${cola}, min(${cola}, ${colb}), ${opac})`;
-		}
-		else if (blending == blend_type_t.MULTIPLY) {
-			return `mix(${cola}, ${cola} * ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.BURN) {
-			return `mix(${cola}, vec3(1.0, 1.0, 1.0) - (vec3(1.0, 1.0, 1.0) - ${cola}) / ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.LIGHTEN) {
-			return `max(${cola}, ${colb} * ${opac})`;
-		}
-		else if (blending == blend_type_t.SCREEN) {
-			return `(vec3(1.0, 1.0, 1.0) - (vec3(1.0 - ${opac}, 1.0 - ${opac}, 1.0 - ${opac}) + ${opac} * (vec3(1.0, 1.0, 1.0) - ${colb})) * (vec3(1.0, 1.0, 1.0) - ${cola}))`;
-		}
-		else if (blending == blend_type_t.DODGE) {
-			return `mix(${cola}, ${cola} / (vec3(1.0, 1.0, 1.0) - ${colb}), ${opac})`;
-		}
-		else if (blending == blend_type_t.ADD) {
-			return `mix(${cola}, ${cola} + ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.OVERLAY) {
-			return `mix(${cola}, vec3(
-				${cola}.r < 0.5 ? 2.0 * ${cola}.r * ${colb}.r : 1.0 - 2.0 * (1.0 - ${cola}.r) * (1.0 - ${colb}.r),
-				${cola}.g < 0.5 ? 2.0 * ${cola}.g * ${colb}.g : 1.0 - 2.0 * (1.0 - ${cola}.g) * (1.0 - ${colb}.g),
-				${cola}.b < 0.5 ? 2.0 * ${cola}.b * ${colb}.b : 1.0 - 2.0 * (1.0 - ${cola}.b) * (1.0 - ${colb}.b)
-			), ${opac})`;
-		}
-		else if (blending == blend_type_t.SOFT_LIGHT) {
-			return `((1.0 - ${opac}) * ${cola} + ${opac} * ((vec3(1.0, 1.0, 1.0) - ${cola}) * ${colb} * ${cola} + ${cola} * (vec3(1.0, 1.0, 1.0) - (vec3(1.0, 1.0, 1.0) - ${colb}) * (vec3(1.0, 1.0, 1.0) - ${cola}))))`;
-		}
-		else if (blending == blend_type_t.LINEAR_LIGHT) {
-			return `(${cola} + ${opac} * (vec3(2.0, 2.0, 2.0) * (${colb} - vec3(0.5, 0.5, 0.5))))`;
-		}
-		else if (blending == blend_type_t.DIFFERENCE) {
-			return `mix(${cola}, abs(${cola} - ${colb}), ${opac})`;
-		}
-		else if (blending == blend_type_t.SUBTRACT) {
-			return `mix(${cola}, ${cola} - ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.DIVIDE) {
-			return `vec3(1.0 - ${opac}, 1.0 - ${opac}, 1.0 - ${opac}) * ${cola} + vec3(${opac}, ${opac}, ${opac}) * ${cola} / ${colb}`;
-		}
-		else if (blending == blend_type_t.HUE) {
-			node_shader_add_function(frag, str_hue_sat);
-			return `mix(${cola}, hsv_to_rgb(vec3(rgb_to_hsv(${colb}).r, rgb_to_hsv(${cola}).g, rgb_to_hsv(${cola}).b)), ${opac})`;
-		}
-		else if (blending == blend_type_t.SATURATION) {
-			node_shader_add_function(frag, str_hue_sat);
-			return `mix(${cola}, hsv_to_rgb(vec3(rgb_to_hsv(${cola}).r, rgb_to_hsv(${colb}).g, rgb_to_hsv(${cola}).b)), ${opac})`;
-		}
-		else if (blending == blend_type_t.COLOR) {
-			node_shader_add_function(frag, str_hue_sat);
-			return `mix(${cola}, hsv_to_rgb(vec3(rgb_to_hsv(${colb}).r, rgb_to_hsv(${colb}).g, rgb_to_hsv(${cola}).b)), ${opac})`;
-		}
-		else { // BlendValue
-			node_shader_add_function(frag, str_hue_sat);
-			return `mix(${cola}, hsv_to_rgb(vec3(rgb_to_hsv(${cola}).r, rgb_to_hsv(${cola}).g, rgb_to_hsv(${colb}).b)), ${opac})`;
-		}
-	}
-
-	static make_material_blend_mode_mask = (frag: NodeShaderRaw, blending: i32, cola: string, colb: string, opac: string): string => {
-		if (blending == blend_type_t.MIX) {
-			return `mix(${cola}, ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.DARKEN) {
-			return `mix(${cola}, min(${cola}, ${colb}), ${opac})`;
-		}
-		else if (blending == blend_type_t.MULTIPLY) {
-			return `mix(${cola}, ${cola} * ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.BURN) {
-			return `mix(${cola}, 1.0 - (1.0 - ${cola}) / ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.LIGHTEN) {
-			return `max(${cola}, ${colb} * ${opac})`;
-		}
-		else if (blending == blend_type_t.SCREEN) {
-			return `(1.0 - ((1.0 - ${opac}) + ${opac} * (1.0 - ${colb})) * (1.0 - ${cola}))`;
-		}
-		else if (blending == blend_type_t.DODGE) {
-			return `mix(${cola}, ${cola} / (1.0 - ${colb}), ${opac})`;
-		}
-		else if (blending == blend_type_t.ADD) {
-			return `mix(${cola}, ${cola} + ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.OVERLAY) {
-			return `mix(${cola}, ${cola} < 0.5 ? 2.0 * ${cola} * ${colb} : 1.0 - 2.0 * (1.0 - ${cola}) * (1.0 - ${colb}), ${opac})`;
-		}
-		else if (blending == blend_type_t.SOFT_LIGHT) {
-			return `((1.0 - ${opac}) * ${cola} + ${opac} * ((1.0 - ${cola}) * ${colb} * ${cola} + ${cola} * (1.0 - (1.0 - ${colb}) * (1.0 - ${cola}))))`;
-		}
-		else if (blending == blend_type_t.LINEAR_LIGHT) {
-			return `(${cola} + ${opac} * (2.0 * (${colb} - 0.5)))`;
-		}
-		else if (blending == blend_type_t.DIFFERENCE) {
-			return `mix(${cola}, abs(${cola} - ${colb}), ${opac})`;
-		}
-		else if (blending == blend_type_t.SUBTRACT) {
-			return `mix(${cola}, ${cola} - ${colb}, ${opac})`;
-		}
-		else if (blending == blend_type_t.DIVIDE) {
-			return `(1.0 - ${opac}) * ${cola} + ${opac} * ${cola} / ${colb}`;
-		}
-		else { // BlendHue, BlendSaturation, BlendColor, BlendValue
-			return `mix(${cola}, ${colb}, ${opac})`;
-		}
-	}
-
-	static make_material_get_displace_strength = (): f32 => {
-		let sc: f32 = context_main_object().base.transform.scale.x;
-		return config_raw.displace_strength * 0.02 * sc;
-	}
-
-	static make_material_voxelgi_half_extents = (): string => {
-		let ext: f32 = context_raw.vxao_ext;
-		return `const vec3 voxelgiHalfExtents = vec3(${ext}, ${ext}, ${ext});`;
-	}
-
-	static make_material_delete_context = (c: shader_context_t) => {
-		base_notify_on_next_frame(() => { // Ensure pipeline is no longer in use
-			shader_context_delete(c);
-		});
-	}
-}

+ 0 - 488
armorpaint/Sources/MakeMesh.ts

@@ -1,488 +0,0 @@
-
-class MakeMesh {
-
-	static make_mesh_layer_pass_count = 1;
-
-	static make_mesh_run = (data: material_t, layerPass = 0): NodeShaderContextRaw => {
-		let context_id: string = layerPass == 0 ? "mesh" : "mesh" + layerPass;
-		let con_mesh: NodeShaderContextRaw = node_shader_context_create(data, {
-			name: context_id,
-			depth_write: layerPass == 0 ? true : false,
-			compare_mode: layerPass == 0 ? "less" : "equal",
-			cull_mode: (context_raw.cull_backfaces || layerPass > 0) ? "clockwise" : "none",
-			vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
-			color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
-			depth_attachment: "DEPTH32"
-		});
-
-		let vert: NodeShaderRaw = node_shader_context_make_vert(con_mesh);
-		let frag: NodeShaderRaw = node_shader_context_make_frag(con_mesh);
-		frag.ins = vert.outs;
-
-		node_shader_add_out(vert, 'vec2 texCoord');
-		frag.wvpposition = true;
-		node_shader_add_out(vert, 'vec4 prevwvpposition');
-		node_shader_add_uniform(vert, 'mat4 VP', '_view_proj_matrix');
-		node_shader_add_uniform(vert, 'mat4 prevWVP', '_prev_world_view_proj_matrix');
-		vert.wposition = true;
-
-		let texture_count: i32 = 0;
-		let displace_strength: f32 = MakeMaterial.make_material_get_displace_strength();
-		if (MakeMaterial.make_material_height_used && displace_strength > 0.0) {
-			vert.n = true;
-			node_shader_write(vert, 'float height = 0.0;');
-			let num_layers: i32 = 0;
-			for (let l of project_layers) {
-				if (!SlotLayer.slot_layer_is_visible(l) || !l.paint_height || !SlotLayer.slot_layer_is_layer(l)) continue;
-				if (num_layers > 16) break;
-				num_layers++;
-				texture_count++;
-				node_shader_add_uniform(vert, 'sampler2D texpaint_pack_vert' + l.id, '_texpaint_pack_vert' + l.id);
-				node_shader_write(vert, 'height += textureLod(texpaint_pack_vert' + l.id + ', tex, 0.0).a;');
-				let masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(l);
-				if (masks != null) {
-					for (let m of masks) {
-						if (!SlotLayer.slot_layer_is_visible(m)) continue;
-						texture_count++;
-						node_shader_add_uniform(vert, 'sampler2D texpaint_vert' + m.id, '_texpaint_vert' + m.id);
-						node_shader_write(vert, 'height *= textureLod(texpaint_vert' + m.id + ', tex, 0.0).r;');
-					}
-				}
-			}
-			node_shader_write(vert, `wposition += wnormal * vec3(height, height, height) * vec3(${displace_strength}, ${displace_strength}, ${displace_strength});`);
-		}
-
-		node_shader_write(vert, 'gl_Position = mul(vec4(wposition.xyz, 1.0), VP);');
-		node_shader_write(vert, 'texCoord = tex;');
-		if (MakeMaterial.make_material_height_used && displace_strength > 0) {
-			node_shader_add_uniform(vert, 'mat4 invW', '_inv_world_matrix');
-			node_shader_write(vert, 'prevwvpposition = mul(mul(vec4(wposition, 1.0), invW), prevWVP);');
-		}
-		else {
-			node_shader_write(vert, 'prevwvpposition = mul(vec4(pos.xyz, 1.0), prevWVP);');
-		}
-
-		node_shader_add_out(frag, 'vec4 fragColor[3]');
-		frag.n = true;
-		node_shader_add_function(frag, str_pack_float_int16);
-
-		if (context_raw.tool == workspace_tool_t.COLORID) {
-			texture_count++;
-			node_shader_add_uniform(frag, 'sampler2D texcolorid', '_texcolorid');
-			node_shader_write(frag, 'fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));');
-			node_shader_write(frag, 'vec3 idcol = pow(textureLod(texcolorid, texCoord, 0.0).rgb, vec3(2.2, 2.2, 2.2));');
-			node_shader_write(frag, 'fragColor[1] = vec4(idcol.rgb, 1.0);'); // occ
-		}
-		else {
-			node_shader_add_function(frag, str_octahedron_wrap);
-			node_shader_add_function(frag, str_cotangent_frame);
-			if (layerPass > 0) {
-				node_shader_add_uniform(frag, 'sampler2D gbuffer0');
-				node_shader_add_uniform(frag, 'sampler2D gbuffer1');
-				node_shader_add_uniform(frag, 'sampler2D gbuffer2');
-				node_shader_write(frag, 'vec2 fragcoord = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
-				///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-				node_shader_write(frag, 'fragcoord.y = 1.0 - fragcoord.y;');
-				///end
-				node_shader_write(frag, 'vec4 gbuffer0_sample = textureLod(gbuffer0, fragcoord, 0.0);');
-				node_shader_write(frag, 'vec4 gbuffer1_sample = textureLod(gbuffer1, fragcoord, 0.0);');
-				node_shader_write(frag, 'vec4 gbuffer2_sample = textureLod(gbuffer2, fragcoord, 0.0);');
-				node_shader_write(frag, 'vec3 basecol = gbuffer0_sample.rgb;');
-				node_shader_write(frag, 'float roughness = gbuffer2_sample.g;');
-				node_shader_write(frag, 'float metallic = gbuffer2_sample.b;');
-				node_shader_write(frag, 'float occlusion = gbuffer2_sample.r;');
-				node_shader_write(frag, 'float opacity = 1.0;//gbuffer0_sample.a;');
-				node_shader_write(frag, 'float matid = gbuffer1_sample.a;');
-				node_shader_write(frag, 'vec3 ntex = gbuffer1_sample.rgb;');
-				node_shader_write(frag, 'float height = gbuffer2_sample.a;');
-			}
-			else {
-				node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
-				node_shader_write(frag, 'float roughness = 0.0;');
-				node_shader_write(frag, 'float metallic = 0.0;');
-				node_shader_write(frag, 'float occlusion = 1.0;');
-				node_shader_write(frag, 'float opacity = 1.0;');
-				node_shader_write(frag, 'float matid = 0.0;');
-				node_shader_write(frag, 'vec3 ntex = vec3(0.5, 0.5, 1.0);');
-				node_shader_write(frag, 'float height = 0.0;');
-			}
-			node_shader_write(frag, 'vec4 texpaint_sample = vec4(0.0, 0.0, 0.0, 1.0);');
-			node_shader_write(frag, 'vec4 texpaint_nor_sample;');
-			node_shader_write(frag, 'vec4 texpaint_pack_sample;');
-			node_shader_write(frag, 'float texpaint_opac;');
-
-			if (MakeMaterial.make_material_height_used) {
-				node_shader_write(frag, 'float height0 = 0.0;');
-				node_shader_write(frag, 'float height1 = 0.0;');
-				node_shader_write(frag, 'float height2 = 0.0;');
-				node_shader_write(frag, 'float height3 = 0.0;');
-			}
-
-			if (context_raw.draw_wireframe) {
-				texture_count++;
-				node_shader_add_uniform(frag, 'sampler2D texuvmap', '_texuvmap');
-			}
-
-			if (context_raw.viewport_mode == viewport_mode_t.MASK && SlotLayer.slot_layer_get_masks(context_raw.layer) != null) {
-				for (let m of SlotLayer.slot_layer_get_masks(context_raw.layer)) {
-					if (!SlotLayer.slot_layer_is_visible(m)) continue;
-					texture_count++;
-					node_shader_add_uniform(frag, 'sampler2D texpaint_view_mask' + m.id, '_texpaint' + project_layers.indexOf(m));
-				}
-			}
-
-			if (context_raw.viewport_mode == viewport_mode_t.LIT && context_raw.render_mode == render_mode_t.FORWARD) {
-				texture_count += 4;
-				node_shader_add_uniform(frag, 'sampler2D senvmapBrdf', "$brdf.k");
-				node_shader_add_uniform(frag, 'sampler2D senvmapRadiance', '_envmap_radiance');
-				node_shader_add_uniform(frag, 'sampler2D sltcMat', '_ltcMat');
-				node_shader_add_uniform(frag, 'sampler2D sltcMag', '_ltcMag');
-			}
-
-			// Get layers for this pass
-			MakeMesh.make_mesh_layer_pass_count = 1;
-			let layers: SlotLayerRaw[] = [];
-			let start_count: i32 = texture_count;
-			let is_material_tool: bool = context_raw.tool == workspace_tool_t.MATERIAL;
-			for (let l of project_layers) {
-				if (is_material_tool && l != context_raw.layer) continue;
-				if (!SlotLayer.slot_layer_is_layer(l) || !SlotLayer.slot_layer_is_visible(l)) continue;
-
-				let count: i32 = 3;
-				let masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(l);
-				if (masks != null) count += masks.length;
-				texture_count += count;
-				if (texture_count >= MakeMesh.make_mesh_get_max_textures()) {
-					texture_count = start_count + count + 3; // gbuffer0_copy, gbuffer1_copy, gbuffer2_copy
-					MakeMesh.make_mesh_layer_pass_count++;
-				}
-				if (layerPass == MakeMesh.make_mesh_layer_pass_count - 1) {
-					layers.push(l);
-				}
-			}
-
-			let last_pass: bool = layerPass == MakeMesh.make_mesh_layer_pass_count - 1;
-
-			for (let l of layers) {
-				if (SlotLayer.slot_layer_get_object_mask(l) > 0) {
-					node_shader_add_uniform(frag, 'int uid', '_uid');
-					if (SlotLayer.slot_layer_get_object_mask(l) > project_paint_objects.length) { // Atlas
-						let visibles: mesh_object_t[] = project_get_atlas_objects(SlotLayer.slot_layer_get_object_mask(l));
-						node_shader_write(frag, 'if (');
-						for (let i: i32 = 0; i < visibles.length; ++i) {
-							if (i > 0) node_shader_write(frag, ' || ');
-							node_shader_write(frag, `${visibles[i].base.uid} == uid`);
-						}
-						node_shader_write(frag, ') {');
-					}
-					else { // Object mask
-						let uid: i32 = project_paint_objects[SlotLayer.slot_layer_get_object_mask(l) - 1].base.uid;
-						node_shader_write(frag, `if (${uid} == uid) {`);
-					}
-				}
-
-				node_shader_add_shared_sampler(frag, 'sampler2D texpaint' + l.id);
-				node_shader_write(frag, 'texpaint_sample = textureLodShared(texpaint' + l.id + ', texCoord, 0.0);');
-				node_shader_write(frag, 'texpaint_opac = texpaint_sample.a;');
-				// ///if (krom_direct3d12 || krom_vulkan)
-				// if (raw.viewportMode == ViewLit) {
-				// 	write(frag, 'if (texpaint_opac < 0.1) discard;');
-				// }
-				// ///end
-
-				let masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(l);
-				if (masks != null) {
-					let has_visible: bool = false;
-					for (let m of masks) {
-						if (SlotLayer.slot_layer_is_visible(m)) {
-							has_visible = true;
-							break;
-						}
-					}
-					if (has_visible) {
-						let texpaint_mask: string = 'texpaint_mask' + l.id;
-						node_shader_write(frag, `float ${texpaint_mask} = 0.0;`);
-						for (let m of masks) {
-							if (!SlotLayer.slot_layer_is_visible(m)) continue;
-							node_shader_add_shared_sampler(frag, 'sampler2D texpaint' + m.id);
-							node_shader_write(frag, '{'); // Group mask is sampled across multiple layers
-							node_shader_write(frag, 'float texpaint_mask_sample' + m.id + ' = textureLodShared(texpaint' + m.id + ', texCoord, 0.0).r;');
-							node_shader_write(frag, `${texpaint_mask} = ` + MakeMaterial.make_material_blend_mode_mask(frag, m.blending, `${texpaint_mask}`, 'texpaint_mask_sample' + m.id, 'float(' + SlotLayer.slot_layer_get_opacity(m) + ')') + ';');
-							node_shader_write(frag, '}');
-						}
-						node_shader_write(frag, `texpaint_opac *= clamp(${texpaint_mask}, 0.0, 1.0);`);
-					}
-				}
-
-				if (SlotLayer.slot_layer_get_opacity(l) < 1) {
-					node_shader_write(frag, `texpaint_opac *= ${SlotLayer.slot_layer_get_opacity(l)};`);
-				}
-
-				if (l.paint_base) {
-					if (l == project_layers[0]) {
-						node_shader_write(frag, 'basecol = texpaint_sample.rgb * texpaint_opac;');
-					}
-					else {
-						node_shader_write(frag, 'basecol = ' + MakeMaterial.make_material_blend_mode(frag, l.blending, 'basecol', 'texpaint_sample.rgb', 'texpaint_opac') + ';');
-					}
-				}
-
-				if (l.paint_nor || MakeMaterial.make_material_emis_used) {
-					node_shader_add_shared_sampler(frag, 'sampler2D texpaint_nor' + l.id);
-					node_shader_write(frag, 'texpaint_nor_sample = textureLodShared(texpaint_nor' + l.id + ', texCoord, 0.0);');
-
-					if (MakeMaterial.make_material_emis_used) {
-						node_shader_write(frag, 'if (texpaint_opac > 0.0) matid = texpaint_nor_sample.a;');
-					}
-
-					if (l.paint_nor) {
-						if (l.paint_nor_blend) {
-							// Whiteout blend
-							node_shader_write(frag, '{');
-							node_shader_write(frag, 'vec3 n1 = ntex * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
-							node_shader_write(frag, 'vec3 n2 = mix(vec3(0.5, 0.5, 1.0), texpaint_nor_sample.rgb, texpaint_opac) * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
-							node_shader_write(frag, 'ntex = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
-							node_shader_write(frag, '}');
-						}
-						else {
-							node_shader_write(frag, 'ntex = mix(ntex, texpaint_nor_sample.rgb, texpaint_opac);');
-						}
-					}
-				}
-
-				if (l.paint_occ || l.paint_rough || l.paint_met || (l.paint_height && MakeMaterial.make_material_height_used)) {
-					node_shader_add_shared_sampler(frag, 'sampler2D texpaint_pack' + l.id);
-					node_shader_write(frag, 'texpaint_pack_sample = textureLodShared(texpaint_pack' + l.id + ', texCoord, 0.0);');
-
-					if (l.paint_occ) {
-						node_shader_write(frag, 'occlusion = mix(occlusion, texpaint_pack_sample.r, texpaint_opac);');
-					}
-					if (l.paint_rough) {
-						node_shader_write(frag, 'roughness = mix(roughness, texpaint_pack_sample.g, texpaint_opac);');
-					}
-					if (l.paint_met) {
-						node_shader_write(frag, 'metallic = mix(metallic, texpaint_pack_sample.b, texpaint_opac);');
-					}
-					if (l.paint_height && MakeMaterial.make_material_height_used) {
-						let assign: string = l.paint_height_blend ? "+=" : "=";
-						node_shader_write(frag, `height ${assign} texpaint_pack_sample.a * texpaint_opac;`);
-						node_shader_write(frag, '{');
-						node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
-						node_shader_write(frag, 'float tex_step = 1.0 / texpaintSize.x;');
-						node_shader_write(frag, `height0 ${assign} textureLodShared(texpaint_pack` + l.id + ', vec2(texCoord.x - tex_step, texCoord.y), 0.0).a * texpaint_opac;');
-						node_shader_write(frag, `height1 ${assign} textureLodShared(texpaint_pack` + l.id + ', vec2(texCoord.x + tex_step, texCoord.y), 0.0).a * texpaint_opac;');
-						node_shader_write(frag, `height2 ${assign} textureLodShared(texpaint_pack` + l.id + ', vec2(texCoord.x, texCoord.y - tex_step), 0.0).a * texpaint_opac;');
-						node_shader_write(frag, `height3 ${assign} textureLodShared(texpaint_pack` + l.id + ', vec2(texCoord.x, texCoord.y + tex_step), 0.0).a * texpaint_opac;');
-						node_shader_write(frag, '}');
-					}
-				}
-
-				if (SlotLayer.slot_layer_get_object_mask(l) > 0) {
-					node_shader_write(frag, '}');
-				}
-			}
-
-			if (last_pass && context_raw.draw_texels) {
-				node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
-				node_shader_write(frag, 'vec2 texel0 = texCoord * texpaintSize * 0.01;');
-				node_shader_write(frag, 'vec2 texel1 = texCoord * texpaintSize * 0.1;');
-				node_shader_write(frag, 'vec2 texel2 = texCoord * texpaintSize;');
-				node_shader_write(frag, 'basecol *= max(float(mod(int(texel0.x), 2.0) == mod(int(texel0.y), 2.0)), 0.9);');
-				node_shader_write(frag, 'basecol *= max(float(mod(int(texel1.x), 2.0) == mod(int(texel1.y), 2.0)), 0.9);');
-				node_shader_write(frag, 'basecol *= max(float(mod(int(texel2.x), 2.0) == mod(int(texel2.y), 2.0)), 0.9);');
-			}
-
-			if (last_pass && context_raw.draw_wireframe) {
-				node_shader_write(frag, 'basecol *= 1.0 - textureLod(texuvmap, texCoord, 0.0).r;');
-			}
-
-			if (MakeMaterial.make_material_height_used) {
-				node_shader_write(frag, 'if (height > 0.0) {');
-				// write(frag, 'float height_dx = dFdx(height * 16.0);');
-				// write(frag, 'float height_dy = dFdy(height * 16.0);');
-				node_shader_write(frag, 'float height_dx = height0 - height1;');
-				node_shader_write(frag, 'float height_dy = height2 - height3;');
-				// Whiteout blend
-				node_shader_write(frag, 'vec3 n1 = ntex * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
-				node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
-				node_shader_write(frag, 'ntex = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
-				node_shader_write(frag, '}');
-			}
-
-			if (!last_pass) {
-				node_shader_write(frag, 'fragColor[0] = vec4(basecol, opacity);');
-				node_shader_write(frag, 'fragColor[1] = vec4(ntex, matid);');
-				node_shader_write(frag, 'fragColor[2] = vec4(occlusion, roughness, metallic, height);');
-				parser_material_finalize(con_mesh);
-				con_mesh.data.shader_from_source = true;
-				con_mesh.data.vertex_shader = node_shader_get(vert);
-				con_mesh.data.fragment_shader = node_shader_get(frag);
-				return con_mesh;
-			}
-
-			frag.vvec = true;
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
-			///else
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
-			///end
-			node_shader_write(frag, 'n = ntex * 2.0 - 1.0;');
-			node_shader_write(frag, 'n.y = -n.y;');
-			node_shader_write(frag, 'n = normalize(mul(n, TBN));');
-
-			if (context_raw.viewport_mode == viewport_mode_t.LIT || context_raw.viewport_mode == viewport_mode_t.PATH_TRACE) {
-				node_shader_write(frag, 'basecol = pow(basecol, vec3(2.2, 2.2, 2.2));');
-
-				if (context_raw.viewport_shader != null) {
-					let color: string = context_raw.viewport_shader(frag);
-					node_shader_write(frag, `fragColor[1] = vec4(${color}, 1.0);`);
-				}
-				else if (context_raw.render_mode == render_mode_t.FORWARD && context_raw.viewport_mode != viewport_mode_t.PATH_TRACE) {
-					frag.wposition = true;
-					node_shader_write(frag, 'vec3 albedo = mix(basecol, vec3(0.0, 0.0, 0.0), metallic);');
-					node_shader_write(frag, 'vec3 f0 = mix(vec3(0.04, 0.04, 0.04), basecol, metallic);');
-					frag.vvec = true;
-					node_shader_write(frag, 'float dotNV = max(0.0, dot(n, vVec));');
-					node_shader_write(frag, 'vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(roughness, 1.0 - dotNV) * 256.0), 0).xy;');
-					node_shader_add_uniform(frag, 'int envmapNumMipmaps', '_envmap_num_mipmaps');
-					node_shader_add_uniform(frag, 'vec4 envmapData', '_envmapData'); // angle, sin(angle), cos(angle), strength
-					node_shader_write(frag, 'vec3 wreflect = reflect(-vVec, n);');
-					node_shader_write(frag, 'float envlod = roughness * float(envmapNumMipmaps);');
-					node_shader_add_function(frag, str_envmap_equirect);
-					node_shader_write(frag, 'vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(wreflect, envmapData.x), envlod).rgb;');
-					node_shader_add_uniform(frag, 'vec3 lightArea0', '_light_area0');
-					node_shader_add_uniform(frag, 'vec3 lightArea1', '_light_area1');
-					node_shader_add_uniform(frag, 'vec3 lightArea2', '_light_area2');
-					node_shader_add_uniform(frag, 'vec3 lightArea3', '_light_area3');
-					node_shader_add_function(frag, str_ltc_evaluate);
-					node_shader_add_uniform(frag, 'vec3 lightPos', '_point_pos');
-					node_shader_add_uniform(frag, 'vec3 lightColor', '_point_color');
-					// write(frag, 'float dotNL = max(dot(n, normalize(lightPos - wposition)), 0.0);');
-					// write(frag, 'vec3 direct = albedo * dotNL;');
-					node_shader_write(frag, 'float ldist = distance(wposition, lightPos);');
-					node_shader_write(frag, 'const float LUT_SIZE = 64.0;');
-					node_shader_write(frag, 'const float LUT_SCALE = (LUT_SIZE - 1.0) / LUT_SIZE;');
-					node_shader_write(frag, 'const float LUT_BIAS = 0.5 / LUT_SIZE;');
-					node_shader_write(frag, 'float theta = acos(dotNV);');
-					node_shader_write(frag, 'vec2 tuv = vec2(roughness, theta / (0.5 * 3.14159265));');
-					node_shader_write(frag, 'tuv = tuv * LUT_SCALE + LUT_BIAS;');
-					node_shader_write(frag, 'vec4 t = textureLod(sltcMat, tuv, 0.0);');
-					node_shader_write(frag, 'mat3 minv = mat3(vec3(1.0, 0.0, t.y), vec3(0.0, t.z, 0.0), vec3(t.w, 0.0, t.x));');
-					node_shader_write(frag, 'float ltcspec = ltcEvaluate(n, vVec, dotNV, wposition, minv, lightArea0, lightArea1, lightArea2, lightArea3);');
-					node_shader_write(frag, 'ltcspec *= textureLod(sltcMag, tuv, 0.0).a;');
-					node_shader_write(frag, 'mat3 mident = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);');
-					node_shader_write(frag, 'float ltcdiff = ltcEvaluate(n, vVec, dotNV, wposition, mident, lightArea0, lightArea1, lightArea2, lightArea3);');
-					node_shader_write(frag, 'vec3 direct = albedo * ltcdiff + ltcspec * 0.05;');
-					node_shader_write(frag, 'direct *= lightColor * (1.0 / (ldist * ldist));');
-
-					node_shader_add_uniform(frag, 'vec4 shirr[7]', '_envmap_irradiance');
-					node_shader_add_function(frag, str_sh_irradiance());
-					node_shader_write(frag, 'vec3 indirect = albedo * (shIrradiance(vec3(n.x * envmapData.z - n.y * envmapData.y, n.x * envmapData.y + n.y * envmapData.z, n.z), shirr) / 3.14159265);');
-					node_shader_write(frag, 'indirect += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5;');
-					node_shader_write(frag, 'indirect *= envmapData.w * occlusion;');
-					node_shader_write(frag, 'fragColor[1] = vec4(direct + indirect, 1.0);');
-				}
-				else { // Deferred, Pathtraced
-					if (MakeMaterial.make_material_emis_used) node_shader_write(frag, 'if (int(matid * 255.0) % 3 == 1) basecol *= 10.0;'); // Boost for bloom
-					node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
-				}
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.BASE_COLOR && context_raw.layer.paint_base) {
-				node_shader_write(frag, 'fragColor[1] = vec4(basecol, 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.NORMAL_MAP && context_raw.layer.paint_nor) {
-				node_shader_write(frag, 'fragColor[1] = vec4(ntex.rgb, 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.OCCLUSION && context_raw.layer.paint_occ) {
-				node_shader_write(frag, 'fragColor[1] = vec4(vec3(occlusion, occlusion, occlusion), 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.ROUGHNESS && context_raw.layer.paint_rough) {
-				node_shader_write(frag, 'fragColor[1] = vec4(vec3(roughness, roughness, roughness), 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.METALLIC && context_raw.layer.paint_met) {
-				node_shader_write(frag, 'fragColor[1] = vec4(vec3(metallic, metallic, metallic), 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.OPACITY && context_raw.layer.paint_opac) {
-				node_shader_write(frag, 'fragColor[1] = vec4(vec3(texpaint_sample.a, texpaint_sample.a, texpaint_sample.a), 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.HEIGHT && context_raw.layer.paint_height) {
-				node_shader_write(frag, 'fragColor[1] = vec4(vec3(height, height, height), 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.EMISSION) {
-				node_shader_write(frag, 'float emis = int(matid * 255.0) % 3 == 1 ? 1.0 : 0.0;');
-				node_shader_write(frag, 'fragColor[1] = vec4(vec3(emis, emis, emis), 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.SUBSURFACE) {
-				node_shader_write(frag, 'float subs = int(matid * 255.0) % 3 == 2 ? 1.0 : 0.0;');
-				node_shader_write(frag, 'fragColor[1] = vec4(vec3(subs, subs, subs), 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.TEXCOORD) {
-				node_shader_write(frag, 'fragColor[1] = vec4(texCoord, 0.0, 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.OBJECT_NORMAL) {
-				frag.nattr = true;
-				node_shader_write(frag, 'fragColor[1] = vec4(nAttr, 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.MATERIAL_ID) {
-				node_shader_add_shared_sampler(frag, 'sampler2D texpaint_nor' + context_raw.layer.id);
-				node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
-				node_shader_write(frag, 'float sample_matid = texelFetch(texpaint_nor' + context_raw.layer.id + ', ivec2(texCoord * texpaintSize), 0).a + 1.0 / 255.0;');
-				node_shader_write(frag, 'float matid_r = fract(sin(dot(vec2(sample_matid, sample_matid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
-				node_shader_write(frag, 'float matid_g = fract(sin(dot(vec2(sample_matid * 20.0, sample_matid), vec2(12.9898, 78.233))) * 43758.5453);');
-				node_shader_write(frag, 'float matid_b = fract(sin(dot(vec2(sample_matid, sample_matid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
-				node_shader_write(frag, 'fragColor[1] = vec4(matid_r, matid_g, matid_b, 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.OBJECT_ID) {
-				node_shader_add_uniform(frag, 'float objectId', '_objectId');
-				node_shader_write(frag, 'float obid = objectId + 1.0 / 255.0;');
-				node_shader_write(frag, 'float id_r = fract(sin(dot(vec2(obid, obid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
-				node_shader_write(frag, 'float id_g = fract(sin(dot(vec2(obid * 20.0, obid), vec2(12.9898, 78.233))) * 43758.5453);');
-				node_shader_write(frag, 'float id_b = fract(sin(dot(vec2(obid, obid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
-				node_shader_write(frag, 'fragColor[1] = vec4(id_r, id_g, id_b, 1.0);');
-			}
-			else if (context_raw.viewport_mode == viewport_mode_t.MASK && (SlotLayer.slot_layer_get_masks(context_raw.layer) != null || SlotLayer.slot_layer_is_mask(context_raw.layer))) {
-				if (SlotLayer.slot_layer_is_mask(context_raw.layer)) {
-					node_shader_write(frag, 'float mask_view = textureLodShared(texpaint' + context_raw.layer.id + ', texCoord, 0.0).r;');
-				}
-				else {
-					node_shader_write(frag, 'float mask_view = 0.0;');
-					for (let m of SlotLayer.slot_layer_get_masks(context_raw.layer)) {
-						if (!SlotLayer.slot_layer_is_visible(m)) continue;
-						node_shader_write(frag, 'float mask_sample' + m.id + ' = textureLodShared(texpaint_view_mask' + m.id + ', texCoord, 0.0).r;');
-						node_shader_write(frag, 'mask_view = ' + MakeMaterial.make_material_blend_mode_mask(frag, m.blending, 'mask_view', 'mask_sample' + m.id, 'float(' + SlotLayer.slot_layer_get_opacity(m) + ')') + ';');
-					}
-				}
-				node_shader_write(frag, 'fragColor[1] = vec4(mask_view, mask_view, mask_view, 1.0);');
-			}
-			else {
-				node_shader_write(frag, 'fragColor[1] = vec4(1.0, 0.0, 1.0, 1.0);'); // Pink
-			}
-
-			if (context_raw.viewport_mode != viewport_mode_t.LIT && context_raw.viewport_mode != viewport_mode_t.PATH_TRACE) {
-				node_shader_write(frag, 'fragColor[1].rgb = pow(fragColor[1].rgb, vec3(2.2, 2.2, 2.2));');
-			}
-
-			node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
-			node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
-			node_shader_write(frag, 'fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, uint(int(matid * 255.0) % 3)));');
-		}
-
-		node_shader_write(frag, 'vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
-		node_shader_write(frag, 'vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;');
-		node_shader_write(frag, 'fragColor[2] = vec4(posa - posb, texCoord.xy);');
-
-		parser_material_finalize(con_mesh);
-		con_mesh.data.shader_from_source = true;
-		con_mesh.data.vertex_shader = node_shader_get(vert);
-		con_mesh.data.fragment_shader = node_shader_get(frag);
-		return con_mesh;
-	}
-
-	static make_mesh_get_max_textures = (): i32 => {
-		///if krom_direct3d11
-		return 128 - 66;
-		///else
-		return 16 - 3; // G4onG5/G4.c.h MAX_TEXTURES
-		///end
-	}
-}

+ 0 - 159
armorpaint/Sources/MakeMeshPreview.ts

@@ -1,159 +0,0 @@
-
-class MakeMeshPreview {
-
-	static make_mesh_preview_opacity_discard_decal: f32 = 0.05;
-
-	static make_mesh_preview_run = (data: material_t, matcon: material_context_t): NodeShaderContextRaw => {
-		let context_id: string = "mesh";
-		let con_mesh: NodeShaderContextRaw = node_shader_context_create(data, {
-			name: context_id,
-			depth_write: true,
-			compare_mode: "less",
-			cull_mode: "clockwise",
-			vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
-			color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
-			depth_attachment: "DEPTH32"
-		});
-
-		let vert: NodeShaderRaw = node_shader_context_make_vert(con_mesh);
-		let frag: NodeShaderRaw = node_shader_context_make_frag(con_mesh);
-		frag.ins = vert.outs;
-		let pos: string = "pos";
-
-		///if arm_skin
-		let skin: bool = mesh_data_get_vertex_array(context_raw.paint_object.data, "bone") != null;
-		if (skin) {
-			pos = "spos";
-			node_shader_context_add_elem(con_mesh, "bone", 'short4norm');
-			node_shader_context_add_elem(con_mesh, "weight", 'short4norm');
-			node_shader_add_function(vert, str_get_skinning_dual_quat);
-			node_shader_add_uniform(vert, 'vec4 skinBones[128 * 2]', '_skin_bones');
-			node_shader_add_uniform(vert, 'float posUnpack', '_pos_unpack');
-			node_shader_write_attrib(vert, 'vec4 skinA;');
-			node_shader_write_attrib(vert, 'vec4 skinB;');
-			node_shader_write_attrib(vert, 'getSkinningDualQuat(ivec4(bone * 32767), weight, skinA, skinB);');
-			node_shader_write_attrib(vert, 'vec3 spos = pos.xyz;');
-			node_shader_write_attrib(vert, 'spos.xyz *= posUnpack;');
-			node_shader_write_attrib(vert, 'spos.xyz += 2.0 * cross(skinA.xyz, cross(skinA.xyz, spos.xyz) + skinA.w * spos.xyz);');
-			node_shader_write_attrib(vert, 'spos.xyz += 2.0 * (skinA.w * skinB.xyz - skinB.w * skinA.xyz + cross(skinA.xyz, skinB.xyz));');
-			node_shader_write_attrib(vert, 'spos.xyz /= posUnpack;');
-		}
-		///end
-
-		node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
-		node_shader_write_attrib(vert, `gl_Position = mul(vec4(${pos}.xyz, 1.0), WVP);`);
-
-		let brush_scale: string = (context_raw.brush_scale * context_raw.brush_nodes_scale) + "";
-		node_shader_add_out(vert, 'vec2 texCoord');
-		node_shader_write_attrib(vert, `texCoord = tex * float(${brush_scale});`);
-
-		let decal: bool = context_raw.decal_preview;
-		parser_material_sample_keep_aspect = decal;
-		parser_material_sample_uv_scale = brush_scale;
-		parser_material_parse_height = MakeMaterial.make_material_height_used;
-		parser_material_parse_height_as_channel = true;
-		let sout: shader_out_t = parser_material_parse(ui_nodes_get_canvas_material(), con_mesh, vert, frag, matcon);
-		parser_material_parse_height = false;
-		parser_material_parse_height_as_channel = false;
-		parser_material_sample_keep_aspect = false;
-		let base: string = sout.out_basecol;
-		let rough: string = sout.out_roughness;
-		let met: string = sout.out_metallic;
-		let occ: string = sout.out_occlusion;
-		let opac: string = sout.out_opacity;
-		let height: string = sout.out_height;
-		let nortan: string = parser_material_out_normaltan;
-		node_shader_write(frag, `vec3 basecol = pow(${base}, vec3(2.2, 2.2, 2.2));`);
-		node_shader_write(frag, `float roughness = ${rough};`);
-		node_shader_write(frag, `float metallic = ${met};`);
-		node_shader_write(frag, `float occlusion = ${occ};`);
-		node_shader_write(frag, `float opacity = ${opac};`);
-		node_shader_write(frag, `vec3 nortan = ${nortan};`);
-		node_shader_write(frag, `float height = ${height};`);
-
-		// parse_height_as_channel = false;
-		// write(vert, `float vheight = ${height};`);
-		// add_out(vert, 'float height');
-		// write(vert, 'height = vheight;');
-		// let displaceStrength: f32 = 0.1;
-		// if (MakeMaterial.heightUsed && displaceStrength > 0.0) {
-		// 	write(vert, `vec3 pos2 = ${pos}.xyz + vec3(nor.xy, pos.w) * vec3(${height}, ${height}, ${height}) * vec3(${displaceStrength}, ${displaceStrength}, ${displaceStrength});`);
-		// 	write(vert, 'gl_Position = mul(vec4(pos2.xyz, 1.0), WVP);');
-		// }
-
-		if (decal) {
-			if (context_raw.tool == workspace_tool_t.TEXT) {
-				node_shader_add_uniform(frag, 'sampler2D textexttool', '_textexttool');
-				node_shader_write(frag, `opacity *= textureLod(textexttool, texCoord / float(${brush_scale}), 0.0).r;`);
-			}
-		}
-		if (decal) {
-			let opac: f32 = MakeMeshPreview.make_mesh_preview_opacity_discard_decal;
-			node_shader_write(frag, `if (opacity < ${opac}) discard;`);
-		}
-
-		node_shader_add_out(frag, 'vec4 fragColor[3]');
-		frag.n = true;
-
-		node_shader_add_function(frag, str_pack_float_int16);
-		node_shader_add_function(frag, str_cotangent_frame);
-		node_shader_add_function(frag, str_octahedron_wrap);
-
-		if (MakeMaterial.make_material_height_used) {
-			node_shader_write(frag, 'if (height > 0.0) {');
-			node_shader_write(frag, 'float height_dx = dFdx(height * 2.0);');
-			node_shader_write(frag, 'float height_dy = dFdy(height * 2.0);');
-			// write(frag, 'float height_dx = height0 - height1;');
-			// write(frag, 'float height_dy = height2 - height3;');
-			// Whiteout blend
-			node_shader_write(frag, 'vec3 n1 = nortan * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
-			node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
-			node_shader_write(frag, 'nortan = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
-			node_shader_write(frag, '}');
-		}
-
-		// Apply normal channel
-		if (decal) {
-			// TODO
-		}
-		else {
-			frag.vvec = true;
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
-			///else
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
-			///end
-			node_shader_write(frag, 'n = nortan * 2.0 - 1.0;');
-			node_shader_write(frag, 'n.y = -n.y;');
-			node_shader_write(frag, 'n = normalize(mul(n, TBN));');
-		}
-
-		node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
-		node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
-		// uint matid = 0;
-
-		if (decal) {
-			node_shader_write(frag, 'fragColor[0] = vec4(n.x, n.y, roughness, packFloatInt16(metallic, uint(0)));'); // metallic/matid
-			node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
-		}
-		else {
-			node_shader_write(frag, 'fragColor[0] = vec4(n.x, n.y, mix(1.0, roughness, opacity), packFloatInt16(mix(1.0, metallic, opacity), uint(0)));'); // metallic/matid
-			node_shader_write(frag, 'fragColor[1] = vec4(mix(vec3(0.0, 0.0, 0.0), basecol, opacity), occlusion);');
-		}
-		node_shader_write(frag, 'fragColor[2] = vec4(0.0, 0.0, 0.0, 0.0);'); // veloc
-
-		parser_material_finalize(con_mesh);
-
-		///if arm_skin
-		if (skin) {
-			node_shader_write(vert, 'wnormal = normalize(mul(vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w)), N));');
-		}
-		///end
-
-		con_mesh.data.shader_from_source = true;
-		con_mesh.data.vertex_shader = node_shader_get(vert);
-		con_mesh.data.fragment_shader = node_shader_get(frag);
-
-		return con_mesh;
-	}
-}

+ 0 - 71
armorpaint/Sources/MakeNodePreview.ts

@@ -1,71 +0,0 @@
-
-class MakeNodePreview {
-
-	static make_node_preview_run = (data: material_t, matcon: material_context_t, node: zui_node_t, group: zui_node_canvas_t, parents: zui_node_t[]): NodeShaderContextRaw => {
-		let context_id: string = "mesh";
-		let con_mesh: NodeShaderContextRaw = node_shader_context_create(data, {
-			name: context_id,
-			depth_write: false,
-			compare_mode: "always",
-			cull_mode: "clockwise",
-			vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}, {name: "col", data: "short4norm"}],
-			color_attachments: ["RGBA32"]
-		});
-
-		con_mesh.allow_vcols = true;
-		let vert: NodeShaderRaw = node_shader_context_make_vert(con_mesh);
-		let frag: NodeShaderRaw = node_shader_context_make_frag(con_mesh);
-		frag.ins = vert.outs;
-
-		node_shader_write_attrib(vert, 'gl_Position = vec4(pos.xy * 3.0, 0.0, 1.0);'); // Pos unpack
-		node_shader_write_attrib(vert, 'const vec2 madd = vec2(0.5, 0.5);');
-		node_shader_add_out(vert, 'vec2 texCoord');
-		node_shader_write_attrib(vert, 'texCoord = gl_Position.xy * madd + madd;');
-		///if (!krom_opengl)
-		node_shader_write_attrib(vert, 'texCoord.y = 1.0 - texCoord.y;');
-		///end
-
-		parser_material_init();
-		parser_material_canvases = [context_raw.material.canvas];
-		parser_material_nodes = context_raw.material.canvas.nodes;
-		parser_material_links = context_raw.material.canvas.links;
-		if (group != null) {
-			parser_material_push_group(group);
-			parser_material_parents = parents;
-		}
-		let links: zui_node_link_t[] = parser_material_links;
-		let link: zui_node_link_t = { id: zui_get_link_id(links), from_id: node.id, from_socket: context_raw.node_preview_socket, to_id: -1, to_socket: -1 };
-		links.push(link);
-
-		parser_material_con = con_mesh;
-		parser_material_vert = vert;
-		parser_material_frag = frag;
-		parser_material_curshader = frag;
-		parser_material_matcon = matcon;
-
-		parser_material_transform_color_space = false;
-		let res: string = parser_material_write_result(link);
-		parser_material_transform_color_space = true;
-		let st: string = node.outputs[link.from_socket].type;
-		if (st != "RGB" && st != "RGBA" && st != "VECTOR") {
-			res = parser_material_to_vec3(res);
-		}
-		array_remove(links, link);
-
-		node_shader_add_out(frag, 'vec4 fragColor');
-		node_shader_write(frag, `vec3 basecol = ${res};`);
-		node_shader_write(frag, 'fragColor = vec4(basecol.rgb, 1.0);');
-
-		// frag.ndcpos = true;
-		// add_out(vert, 'vec4 ndc');
-		// write_attrib(vert, 'ndc = vec4(gl_Position.xyz * vec3(0.5, 0.5, 0.0) + vec3(0.5, 0.5, 0.0), 1.0);');
-
-		parser_material_finalize(con_mesh);
-
-		con_mesh.data.shader_from_source = true;
-		con_mesh.data.vertex_shader = node_shader_get(vert);
-		con_mesh.data.fragment_shader = node_shader_get(frag);
-
-		return con_mesh;
-	}
-}

+ 0 - 483
armorpaint/Sources/MakePaint.ts

@@ -1,483 +0,0 @@
-
-///if (is_paint || is_forge)
-
-class MakePaint {
-
-	static get make_paint_is_raytraced_bake(): bool {
-		///if (krom_direct3d12 || krom_vulkan || krom_metal)
-		return context_raw.bake_type == bake_type_t.INIT;
-		///else
-		return false;
-		///end
-	}
-
-	static make_paint_run = (data: material_t, matcon: material_context_t): NodeShaderContextRaw => {
-		let context_id: string = "paint";
-
-		let con_paint: NodeShaderContextRaw = node_shader_context_create(data, {
-			name: context_id,
-			depth_write: false,
-			compare_mode: "always", // TODO: align texcoords winding order
-			// cull_mode: "counter_clockwise",
-			cull_mode: "none",
-			vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
-			color_attachments:
-				context_raw.tool == workspace_tool_t.COLORID ? ["RGBA32"] :
-				(context_raw.tool == workspace_tool_t.PICKER && context_raw.pick_pos_nor_tex) ? ["RGBA128", "RGBA128"] :
-				(context_raw.tool == workspace_tool_t.PICKER || context_raw.tool == workspace_tool_t.MATERIAL) ? ["RGBA32", "RGBA32", "RGBA32", "RGBA32"] :
-				(context_raw.tool == workspace_tool_t.BAKE && MakePaint.make_paint_is_raytraced_bake) ? ["RGBA64", "RGBA64"] :
-					["RGBA32", "RGBA32", "RGBA32", "R8"]
-		});
-
-		con_paint.data.color_writes_red = [true, true, true, true];
-		con_paint.data.color_writes_green = [true, true, true, true];
-		con_paint.data.color_writes_blue = [true, true, true, true];
-		con_paint.data.color_writes_alpha = [true, true, true, true];
-		con_paint.allow_vcols = mesh_data_get_vertex_array(context_raw.paint_object.data, 'col') != null;
-
-		let vert: NodeShaderRaw = node_shader_context_make_vert(con_paint);
-		let frag: NodeShaderRaw = node_shader_context_make_frag(con_paint);
-		frag.ins = vert.outs;
-
-		///if (krom_direct3d12 || krom_vulkan || krom_metal)
-		if (context_raw.tool == workspace_tool_t.BAKE && context_raw.bake_type == bake_type_t.INIT) {
-			// Init raytraced bake
-			MakeBake.make_bake_position_normal(vert, frag);
-			con_paint.data.shader_from_source = true;
-			con_paint.data.vertex_shader = node_shader_get(vert);
-			con_paint.data.fragment_shader = node_shader_get(frag);
-			return con_paint;
-		}
-		///end
-
-		if (context_raw.tool == workspace_tool_t.BAKE) {
-			MakeBake.make_bake_set_color_writes(con_paint);
-		}
-
-		if (context_raw.tool == workspace_tool_t.COLORID || context_raw.tool == workspace_tool_t.PICKER || context_raw.tool == workspace_tool_t.MATERIAL) {
-			MakeColorIdPicker.make_colorid_picker_run(vert, frag);
-			con_paint.data.shader_from_source = true;
-			con_paint.data.vertex_shader = node_shader_get(vert);
-			con_paint.data.fragment_shader = node_shader_get(frag);
-			return con_paint;
-		}
-
-		let face_fill: bool = context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.FACE;
-		let uv_island_fill: bool = context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.UV_ISLAND;
-		let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
-
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(vert, 'vec2 tpos = vec2(tex.x * 2.0 - 1.0, (1.0 - tex.y) * 2.0 - 1.0);');
-		///else
-		node_shader_write(vert, 'vec2 tpos = vec2(tex.xy * 2.0 - 1.0);');
-		///end
-
-		node_shader_write(vert, 'gl_Position = vec4(tpos, 0.0, 1.0);');
-
-		let decal_layer: bool = context_raw.layer.fill_layer != null && context_raw.layer.uv_type == uv_type_t.PROJECT;
-		if (decal_layer) {
-			node_shader_add_uniform(vert, 'mat4 WVP', '_decalLayerMatrix');
-		}
-		else {
-			node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
-		}
-
-		node_shader_add_out(vert, 'vec4 ndc');
-		node_shader_write_attrib(vert, 'ndc = mul(vec4(pos.xyz, 1.0), WVP);');
-
-		node_shader_write_attrib(frag, 'vec3 sp = vec3((ndc.xyz / ndc.w) * 0.5 + 0.5);');
-		node_shader_write_attrib(frag, 'sp.y = 1.0 - sp.y;');
-		node_shader_write_attrib(frag, 'sp.z -= 0.0001;'); // small bias
-
-		let uv_type: uv_type_t = context_raw.layer.fill_layer != null ? context_raw.layer.uv_type : context_raw.brush_paint;
-		if (uv_type == uv_type_t.PROJECT) frag.ndcpos = true;
-
-		node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
-		node_shader_add_uniform(frag, 'vec4 inplast', '_inputBrushLast');
-		node_shader_add_uniform(frag, 'float aspectRatio', '_aspect_ratio_window');
-		node_shader_write(frag, 'vec2 bsp = sp.xy * 2.0 - 1.0;');
-		node_shader_write(frag, 'bsp.x *= aspectRatio;');
-		node_shader_write(frag, 'bsp = bsp * 0.5 + 0.5;');
-
-		node_shader_add_uniform(frag, 'sampler2D gbufferD');
-
-		node_shader_add_out(frag, 'vec4 fragColor[4]');
-
-		node_shader_add_uniform(frag, 'float brushRadius', '_brushRadius');
-		node_shader_add_uniform(frag, 'float brushOpacity', '_brushOpacity');
-		node_shader_add_uniform(frag, 'float brushHardness', '_brushHardness');
-
-		if (context_raw.tool == workspace_tool_t.BRUSH  ||
-			context_raw.tool == workspace_tool_t.ERASER ||
-			context_raw.tool == workspace_tool_t.CLONE  ||
-			context_raw.tool == workspace_tool_t.BLUR   ||
-			context_raw.tool == workspace_tool_t.SMUDGE   ||
-			context_raw.tool == workspace_tool_t.PARTICLE ||
-			decal) {
-
-			let depth_reject: bool = !context_raw.xray;
-			if (config_raw.brush_3d && !config_raw.brush_depth_reject) depth_reject = false;
-
-			// TODO: sp.z needs to take height channel into account
-			let particle: bool = context_raw.tool == workspace_tool_t.PARTICLE;
-			if (config_raw.brush_3d && !decal && !particle) {
-				if (MakeMaterial.make_material_height_used || context_raw.sym_x || context_raw.sym_y || context_raw.sym_z) depth_reject = false;
-			}
-
-			if (depth_reject) {
-				///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-				node_shader_write(frag, 'if (sp.z > textureLod(gbufferD, sp.xy, 0.0).r + 0.0005) discard;');
-				///else
-				node_shader_write(frag, 'if (sp.z > textureLod(gbufferD, vec2(sp.x, 1.0 - sp.y), 0.0).r + 0.0005) discard;');
-				///end
-			}
-
-			MakeBrush.make_brush_run(vert, frag);
-		}
-		else { // Fill, Bake
-			node_shader_write(frag, 'float dist = 0.0;');
-			let angle_fill: bool = context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.ANGLE;
-			if (angle_fill) {
-				node_shader_add_function(frag, str_octahedron_wrap);
-				node_shader_add_uniform(frag, 'sampler2D gbuffer0');
-				node_shader_write(frag, 'vec2 g0 = textureLod(gbuffer0, inp.xy, 0.0).rg;');
-				node_shader_write(frag, 'vec3 wn;');
-				node_shader_write(frag, 'wn.z = 1.0 - abs(g0.x) - abs(g0.y);');
-				node_shader_write(frag, 'wn.xy = wn.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);');
-				node_shader_write(frag, 'wn = normalize(wn);');
-				frag.n = true;
-				let angle: f32 = context_raw.brush_angle_reject_dot;
-				node_shader_write(frag, `if (dot(wn, n) < ${angle}) discard;`);
-			}
-			let stencil_fill: bool = context_raw.tool == workspace_tool_t.FILL && context_raw.brush_stencil_image != null;
-			if (stencil_fill) {
-				///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-				node_shader_write(frag, 'if (sp.z > textureLod(gbufferD, sp.xy, 0.0).r + 0.0005) discard;');
-				///else
-				node_shader_write(frag, 'if (sp.z > textureLod(gbufferD, vec2(sp.x, 1.0 - sp.y), 0.0).r + 0.0005) discard;');
-				///end
-			}
-		}
-
-		if (context_raw.colorid_picked || face_fill || uv_island_fill) {
-			node_shader_add_out(vert, 'vec2 texCoordPick');
-			node_shader_write(vert, 'texCoordPick = tex;');
-			if (context_raw.colorid_picked) {
-				MakeDiscard.make_discard_color_id(vert, frag);
-			}
-			if (face_fill) {
-				MakeDiscard.make_discard_face(vert, frag);
-			}
-			else if (uv_island_fill) {
-				MakeDiscard.make_discard_uv_island(vert, frag);
-			}
-		}
-
-		if (context_raw.picker_mask_handle.position == picker_mask_t.MATERIAL) {
-			MakeDiscard.make_discard_material_id(vert, frag);
-		}
-
-		MakeTexcoord.make_texcoord_run(vert, frag);
-
-		if (context_raw.tool == workspace_tool_t.CLONE || context_raw.tool == workspace_tool_t.BLUR || context_raw.tool == workspace_tool_t.SMUDGE) {
-			node_shader_add_uniform(frag, 'sampler2D gbuffer2');
-			node_shader_add_uniform(frag, 'vec2 gbufferSize', '_gbufferSize');
-			node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
-			node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
-			node_shader_add_uniform(frag, 'sampler2D texpaint_pack_undo', '_texpaint_pack_undo');
-
-			if (context_raw.tool == workspace_tool_t.CLONE) {
-				MakeClone.make_clone_run(vert, frag);
-			}
-			else { // Blur, Smudge
-				MakeBlur.make_blur_run(vert, frag);
-			}
-		}
-		else {
-			parser_material_parse_emission = context_raw.material.paint_emis;
-			parser_material_parse_subsurface = context_raw.material.paint_subs;
-			parser_material_parse_height = context_raw.material.paint_height;
-			parser_material_parse_height_as_channel = true;
-			let uv_type: uv_type_t = context_raw.layer.fill_layer != null ? context_raw.layer.uv_type : context_raw.brush_paint;
-			parser_material_triplanar = uv_type == uv_type_t.TRIPLANAR && !decal;
-			parser_material_sample_keep_aspect = decal;
-			parser_material_sample_uv_scale = 'brushScale';
-			let sout: shader_out_t = parser_material_parse(ui_nodes_get_canvas_material(), con_paint, vert, frag, matcon);
-			parser_material_parse_emission = false;
-			parser_material_parse_subsurface = false;
-			parser_material_parse_height_as_channel = false;
-			parser_material_parse_height = false;
-			let base: string = sout.out_basecol;
-			let rough: string = sout.out_roughness;
-			let met: string = sout.out_metallic;
-			let occ: string = sout.out_occlusion;
-			let nortan: string = parser_material_out_normaltan;
-			let height: string = sout.out_height;
-			let opac: string = sout.out_opacity;
-			let emis: string = sout.out_emission;
-			let subs: string = sout.out_subsurface;
-			node_shader_write(frag, `vec3 basecol = ${base};`);
-			node_shader_write(frag, `float roughness = ${rough};`);
-			node_shader_write(frag, `float metallic = ${met};`);
-			node_shader_write(frag, `float occlusion = ${occ};`);
-			node_shader_write(frag, `vec3 nortan = ${nortan};`);
-			node_shader_write(frag, `float height = ${height};`);
-			node_shader_write(frag, `float mat_opacity = ${opac};`);
-			node_shader_write(frag, 'float opacity = mat_opacity;');
-			if (context_raw.layer.fill_layer == null) {
-				node_shader_write(frag, 'opacity *= brushOpacity;');
-			}
-			if (context_raw.material.paint_emis) {
-				node_shader_write(frag, `float emis = ${emis};`);
-			}
-			if (context_raw.material.paint_subs) {
-				node_shader_write(frag, `float subs = ${subs};`);
-			}
-			if (parseFloat(height) != 0.0 && !MakeMaterial.make_material_height_used) {
-				MakeMaterial.make_material_height_used = true;
-				// Height used for the first time, also rebuild vertex shader
-				return MakePaint.make_paint_run(data, matcon);
-			}
-			if (parseFloat(emis) != 0.0) MakeMaterial.make_material_emis_used = true;
-			if (parseFloat(subs) != 0.0) MakeMaterial.make_material_subs_used = true;
-		}
-
-		if (context_raw.brush_mask_image != null && context_raw.tool == workspace_tool_t.DECAL) {
-			node_shader_add_uniform(frag, 'sampler2D texbrushmask', '_texbrushmask');
-			node_shader_write(frag, 'vec4 mask_sample = textureLod(texbrushmask, uvsp, 0.0);');
-			if (context_raw.brush_mask_image_is_alpha) {
-				node_shader_write(frag, 'opacity *= mask_sample.a;');
-			}
-			else {
-				node_shader_write(frag, 'opacity *= mask_sample.r * mask_sample.a;');
-			}
-		}
-		else if (context_raw.tool == workspace_tool_t.TEXT) {
-			node_shader_add_uniform(frag, 'sampler2D textexttool', '_textexttool');
-			node_shader_write(frag, 'opacity *= textureLod(textexttool, uvsp, 0.0).r;');
-		}
-
-		if (context_raw.brush_stencil_image != null && (
-			context_raw.tool == workspace_tool_t.BRUSH  ||
-			context_raw.tool == workspace_tool_t.ERASER ||
-			context_raw.tool == workspace_tool_t.FILL ||
-			context_raw.tool == workspace_tool_t.CLONE  ||
-			context_raw.tool == workspace_tool_t.BLUR   ||
-			context_raw.tool == workspace_tool_t.SMUDGE   ||
-			context_raw.tool == workspace_tool_t.PARTICLE ||
-			decal)) {
-			node_shader_add_uniform(frag, 'sampler2D texbrushstencil', '_texbrushstencil');
-			node_shader_add_uniform(frag, 'vec4 stencilTransform', '_stencilTransform');
-			node_shader_write(frag, 'vec2 stencil_uv = vec2((sp.xy - stencilTransform.xy) / stencilTransform.z * vec2(aspectRatio, 1.0));');
-			node_shader_write(frag, 'vec2 stencil_size = vec2(textureSize(texbrushstencil, 0));');
-			node_shader_write(frag, 'float stencil_ratio = stencil_size.y / stencil_size.x;');
-			node_shader_write(frag, 'stencil_uv -= vec2(0.5 / stencil_ratio, 0.5);');
-			node_shader_write(frag, `stencil_uv = vec2(stencil_uv.x * cos(stencilTransform.w) - stencil_uv.y * sin(stencilTransform.w),
-										  stencil_uv.x * sin(stencilTransform.w) + stencil_uv.y * cos(stencilTransform.w));`);
-			node_shader_write(frag, 'stencil_uv += vec2(0.5 / stencil_ratio, 0.5);');
-			node_shader_write(frag, 'stencil_uv.x *= stencil_ratio;');
-			node_shader_write(frag, 'if (stencil_uv.x < 0 || stencil_uv.x > 1 || stencil_uv.y < 0 || stencil_uv.y > 1) discard;');
-			node_shader_write(frag, 'vec4 texbrushstencil_sample = textureLod(texbrushstencil, stencil_uv, 0.0);');
-			if (context_raw.brush_stencil_image_is_alpha) {
-				node_shader_write(frag, 'opacity *= texbrushstencil_sample.a;');
-			}
-			else {
-				node_shader_write(frag, 'opacity *= texbrushstencil_sample.r * texbrushstencil_sample.a;');
-			}
-		}
-
-		if (context_raw.brush_mask_image != null && (context_raw.tool == workspace_tool_t.BRUSH || context_raw.tool == workspace_tool_t.ERASER)) {
-			node_shader_add_uniform(frag, 'sampler2D texbrushmask', '_texbrushmask');
-			node_shader_write(frag, 'vec2 binp_mask = inp.xy * 2.0 - 1.0;');
-			node_shader_write(frag, 'binp_mask.x *= aspectRatio;');
-			node_shader_write(frag, 'binp_mask = binp_mask * 0.5 + 0.5;');
-			node_shader_write(frag, 'vec2 pa_mask = bsp.xy - binp_mask.xy;');
-			if (context_raw.brush_directional) {
-				node_shader_add_uniform(frag, 'vec3 brushDirection', '_brushDirection');
-				node_shader_write(frag, 'if (brushDirection.z == 0.0) discard;');
-				node_shader_write(frag, 'pa_mask = vec2(pa_mask.x * brushDirection.x - pa_mask.y * brushDirection.y, pa_mask.x * brushDirection.y + pa_mask.y * brushDirection.x);');
-			}
-			let angle: f32 = context_raw.brush_angle + context_raw.brush_nodes_angle;
-			if (angle != 0.0) {
-				node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
-				node_shader_write(frag, 'pa_mask.xy = vec2(pa_mask.x * brushAngle.x - pa_mask.y * brushAngle.y, pa_mask.x * brushAngle.y + pa_mask.y * brushAngle.x);');
-			}
-			node_shader_write(frag, 'pa_mask /= brushRadius;');
-			if (config_raw.brush_3d) {
-				node_shader_add_uniform(frag, 'vec3 eye', '_camera_pos');
-				node_shader_write(frag, 'pa_mask *= distance(eye, winp.xyz) / 1.5;');
-			}
-			node_shader_write(frag, 'pa_mask = pa_mask.xy * 0.5 + 0.5;');
-			node_shader_write(frag, 'vec4 mask_sample = textureLod(texbrushmask, pa_mask, 0.0);');
-			if (context_raw.brush_mask_image_is_alpha) {
-				node_shader_write(frag, 'opacity *= mask_sample.a;');
-			}
-			else {
-				node_shader_write(frag, 'opacity *= mask_sample.r * mask_sample.a;');
-			}
-		}
-
-		node_shader_write(frag, 'if (opacity == 0.0) discard;');
-
-		if (context_raw.tool == workspace_tool_t.PARTICLE) { // Particle mask
-			MakeParticle.mask(vert, frag);
-		}
-		else { // Brush cursor mask
-			node_shader_write(frag, 'float str = clamp((brushRadius - dist) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
-		}
-
-		// Manual blending to preserve memory
-		frag.wvpposition = true;
-		node_shader_write(frag, 'vec2 sample_tc = vec2(wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(frag, 'sample_tc.y = 1.0 - sample_tc.y;');
-		///end
-		node_shader_add_uniform(frag, 'sampler2D paintmask');
-		node_shader_write(frag, 'float sample_mask = textureLod(paintmask, sample_tc, 0.0).r;');
-		node_shader_write(frag, 'str = max(str, sample_mask);');
-		// write(frag, 'str = clamp(str + sample_mask, 0.0, 1.0);');
-
-		node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
-		node_shader_write(frag, 'vec4 sample_undo = textureLod(texpaint_undo, sample_tc, 0.0);');
-
-		let matid: f32 = context_raw.material.id / 255;
-		if (context_raw.picker_mask_handle.position == picker_mask_t.MATERIAL) {
-			matid = context_raw.materialid_picked / 255; // Keep existing material id in place when mask is set
-		}
-		let matid_string: string = parser_material_vec1(matid * 3.0);
-		node_shader_write(frag, `float matid = ${matid_string};`);
-
-		// matid % 3 == 0 - normal, 1 - emission, 2 - subsurface
-		if (context_raw.material.paint_emis) {
-			node_shader_write(frag, 'if (emis > 0.0) {');
-			node_shader_write(frag, '	matid += 1.0 / 255.0;');
-			node_shader_write(frag, '	if (str == 0.0) discard;');
-			node_shader_write(frag, '}');
-		}
-		else if (context_raw.material.paint_subs) {
-			node_shader_write(frag, 'if (subs > 0.0) {');
-			node_shader_write(frag, '    matid += 2.0 / 255.0;');
-			node_shader_write(frag, '	if (str == 0.0) discard;');
-			node_shader_write(frag, '}');
-		}
-
-		let is_mask: bool = SlotLayer.slot_layer_is_mask(context_raw.layer);
-		let layered: bool = context_raw.layer != project_layers[0];
-		if (layered && !is_mask) {
-			if (context_raw.tool == workspace_tool_t.ERASER) {
-				node_shader_write(frag, 'fragColor[0] = vec4(mix(sample_undo.rgb, vec3(0.0, 0.0, 0.0), str), sample_undo.a - str);');
-				node_shader_write(frag, 'nortan = vec3(0.5, 0.5, 1.0);');
-				node_shader_write(frag, 'occlusion = 1.0;');
-				node_shader_write(frag, 'roughness = 0.0;');
-				node_shader_write(frag, 'metallic = 0.0;');
-				node_shader_write(frag, 'matid = 0.0;');
-			}
-			else if (context_raw.tool == workspace_tool_t.PARTICLE || decal || context_raw.brush_mask_image != null) {
-				node_shader_write(frag, 'fragColor[0] = vec4(' + MakeMaterial.make_material_blend_mode(frag, context_raw.brush_blending, 'sample_undo.rgb', 'basecol', 'str') + ', max(str, sample_undo.a));');
-			}
-			else {
-				if (context_raw.layer.fill_layer != null) {
-					node_shader_write(frag, 'fragColor[0] = vec4(' + MakeMaterial.make_material_blend_mode(frag, context_raw.brush_blending, 'sample_undo.rgb', 'basecol', 'opacity') + ', mat_opacity);');
-				}
-				else {
-					node_shader_write(frag, 'fragColor[0] = vec4(' + MakeMaterial.make_material_blend_mode(frag, context_raw.brush_blending, 'sample_undo.rgb', 'basecol', 'opacity') + ', max(str, sample_undo.a));');
-				}
-			}
-			node_shader_write(frag, 'fragColor[1] = vec4(nortan, matid);');
-
-			let height: string = "0.0";
-			if (context_raw.material.paint_height && MakeMaterial.make_material_height_used) {
-				height = "height";
-			}
-
-			if (decal) {
-				node_shader_add_uniform(frag, 'sampler2D texpaint_pack_undo', '_texpaint_pack_undo');
-				node_shader_write(frag, 'vec4 sample_pack_undo = textureLod(texpaint_pack_undo, sample_tc, 0.0);');
-				node_shader_write(frag, `fragColor[2] = mix(sample_pack_undo, vec4(occlusion, roughness, metallic, ${height}), str);`);
-			}
-			else {
-				node_shader_write(frag, `fragColor[2] = vec4(occlusion, roughness, metallic, ${height});`);
-			}
-		}
-		else {
-			if (context_raw.tool == workspace_tool_t.ERASER) {
-				node_shader_write(frag, 'fragColor[0] = vec4(mix(sample_undo.rgb, vec3(0.0, 0.0, 0.0), str), sample_undo.a - str);');
-				node_shader_write(frag, 'fragColor[1] = vec4(0.5, 0.5, 1.0, 0.0);');
-				node_shader_write(frag, 'fragColor[2] = vec4(1.0, 0.0, 0.0, 0.0);');
-			}
-			else {
-				node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
-				node_shader_add_uniform(frag, 'sampler2D texpaint_pack_undo', '_texpaint_pack_undo');
-				node_shader_write(frag, 'vec4 sample_nor_undo = textureLod(texpaint_nor_undo, sample_tc, 0.0);');
-				node_shader_write(frag, 'vec4 sample_pack_undo = textureLod(texpaint_pack_undo, sample_tc, 0.0);');
-				node_shader_write(frag, 'fragColor[0] = vec4(' + MakeMaterial.make_material_blend_mode(frag, context_raw.brush_blending, 'sample_undo.rgb', 'basecol', 'str') + ', max(str, sample_undo.a));');
-				node_shader_write(frag, 'fragColor[1] = vec4(mix(sample_nor_undo.rgb, nortan, str), matid);');
-				if (context_raw.material.paint_height && MakeMaterial.make_material_height_used) {
-					node_shader_write(frag, 'fragColor[2] = mix(sample_pack_undo, vec4(occlusion, roughness, metallic, height), str);');
-				}
-				else {
-					node_shader_write(frag, 'fragColor[2] = vec4(mix(sample_pack_undo.rgb, vec3(occlusion, roughness, metallic), str), 0.0);');
-				}
-			}
-		}
-		node_shader_write(frag, 'fragColor[3] = vec4(str, 0.0, 0.0, 1.0);');
-
-		if (!context_raw.material.paint_base) {
-			con_paint.data.color_writes_red[0] = false;
-			con_paint.data.color_writes_green[0] = false;
-			con_paint.data.color_writes_blue[0] = false;
-		}
-		if (!context_raw.material.paint_opac) {
-			con_paint.data.color_writes_alpha[0] = false;
-		}
-		if (!context_raw.material.paint_nor) {
-			con_paint.data.color_writes_red[1] = false;
-			con_paint.data.color_writes_green[1] = false;
-			con_paint.data.color_writes_blue[1] = false;
-		}
-		if (!context_raw.material.paint_occ) {
-			con_paint.data.color_writes_red[2] = false;
-		}
-		if (!context_raw.material.paint_rough) {
-			con_paint.data.color_writes_green[2] = false;
-		}
-		if (!context_raw.material.paint_met) {
-			con_paint.data.color_writes_blue[2] = false;
-		}
-		if (!context_raw.material.paint_height) {
-			con_paint.data.color_writes_alpha[2] = false;
-		}
-
-		// Base color only as mask
-		if (is_mask) {
-			// TODO: Apply opacity into base
-			// write(frag, 'fragColor[0].rgb *= fragColor[0].a;');
-			con_paint.data.color_writes_green[0] = false;
-			con_paint.data.color_writes_blue[0] = false;
-			con_paint.data.color_writes_alpha[0] = false;
-			con_paint.data.color_writes_red[1] = false;
-			con_paint.data.color_writes_green[1] = false;
-			con_paint.data.color_writes_blue[1] = false;
-			con_paint.data.color_writes_alpha[1] = false;
-			con_paint.data.color_writes_red[2] = false;
-			con_paint.data.color_writes_green[2] = false;
-			con_paint.data.color_writes_blue[2] = false;
-			con_paint.data.color_writes_alpha[2] = false;
-		}
-
-		if (context_raw.tool == workspace_tool_t.BAKE) {
-			MakeBake.make_bake_run(con_paint, vert, frag);
-		}
-
-		parser_material_finalize(con_paint);
-		parser_material_triplanar = false;
-		parser_material_sample_keep_aspect = false;
-		con_paint.data.shader_from_source = true;
-		con_paint.data.vertex_shader = node_shader_get(vert);
-		con_paint.data.fragment_shader = node_shader_get(frag);
-
-		return con_paint;
-	}
-}
-
-///end

+ 0 - 118
armorpaint/Sources/MakeParticle.ts

@@ -1,118 +0,0 @@
-
-class MakeParticle {
-
-	static make_particle_run = (data: material_t): NodeShaderContextRaw => {
-		let context_id: string = "mesh";
-		let con_part: NodeShaderContextRaw = node_shader_context_create(data, {
-			name: context_id,
-			depth_write: false,
-			compare_mode: "always",
-			cull_mode: "clockwise",
-			vertex_elements: [{name: "pos", data: "short4norm"}],
-			color_attachments: ["R8"]
-		});
-
-		let vert: NodeShaderRaw = node_shader_context_make_vert(con_part);
-		let frag: NodeShaderRaw = node_shader_context_make_frag(con_part);
-		frag.ins = vert.outs;
-
-		node_shader_write_attrib(vert, 'vec4 spos = vec4(pos.xyz, 1.0);');
-
-		node_shader_add_uniform(vert, 'float brushRadius', '_brushRadius');
-		node_shader_write_attrib(vert, 'vec3 emitFrom = vec3(fhash(gl_InstanceID), fhash(gl_InstanceID * 2), fhash(gl_InstanceID * 3));');
-		node_shader_write_attrib(vert, 'emitFrom = emitFrom * brushRadius - brushRadius / 2.0;');
-		node_shader_write_attrib(vert, 'spos.xyz += emitFrom * vec3(256.0, 256.0, 256.0);');
-
-		node_shader_add_uniform(vert, 'mat4 pd', '_particle_data');
-
-		let str_tex_hash: string = "float fhash(int n) { return fract(sin(float(n)) * 43758.5453); }\n";
-		node_shader_add_function(vert, str_tex_hash);
-		node_shader_add_out(vert, 'float p_age');
-		node_shader_write(vert, 'p_age = pd[3][3] - float(gl_InstanceID) * pd[0][1];');
-		node_shader_write(vert, 'p_age -= p_age * fhash(gl_InstanceID) * pd[2][3];');
-
-		node_shader_write(vert, 'if (pd[0][0] > 0.0 && p_age < 0.0) p_age += float(int(-p_age / pd[0][0]) + 1) * pd[0][0];');
-
-		node_shader_add_out(vert, 'float p_lifetime');
-		node_shader_write(vert, 'p_lifetime = pd[0][2];');
-		node_shader_write(vert, 'if (p_age < 0.0 || p_age > p_lifetime) {');
-		// write(vert, 'SPIRV_Cross_Output stage_output;');
-		// write(vert, 'stage_output.svpos /= 0.0;');
-		// write(vert, 'return stage_output;');
-		node_shader_write(vert, 'spos /= 0.0;');
-		node_shader_write(vert, '}');
-
-		node_shader_add_out(vert, 'vec3 p_velocity');
-		node_shader_write(vert, 'p_velocity = vec3(pd[1][0], pd[1][1], pd[1][2]);');
-		node_shader_write(vert, 'p_velocity.x += fhash(gl_InstanceID)                     * pd[1][3] - pd[1][3] / 2.0;');
-		node_shader_write(vert, 'p_velocity.y += fhash(gl_InstanceID +     int(pd[0][3])) * pd[1][3] - pd[1][3] / 2.0;');
-		node_shader_write(vert, 'p_velocity.z += fhash(gl_InstanceID + 2 * int(pd[0][3])) * pd[1][3] - pd[1][3] / 2.0;');
-		node_shader_write(vert, 'p_velocity.x += (pd[2][0] * p_age) / 5.0;');
-		node_shader_write(vert, 'p_velocity.y += (pd[2][1] * p_age) / 5.0;');
-		node_shader_write(vert, 'p_velocity.z += (pd[2][2] * p_age) / 5.0;');
-
-		node_shader_add_out(vert, 'vec3 p_location');
-		node_shader_write(vert, 'p_location = p_velocity * p_age;');
-		node_shader_write(vert, 'spos.xyz += p_location;');
-		node_shader_write(vert, 'spos.xyz *= vec3(0.01, 0.01, 0.01);');
-
-		node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
-		node_shader_write(vert, 'gl_Position = mul(spos, WVP);');
-
-		node_shader_add_uniform(vert, 'vec4 inp', '_inputBrush');
-		node_shader_write(vert, 'vec2 binp = vec2(inp.x, 1.0 - inp.y);');
-		node_shader_write(vert, 'binp = binp * 2.0 - 1.0;');
-		node_shader_write(vert, 'binp *= gl_Position.w;');
-		node_shader_write(vert, 'gl_Position.xy += binp;');
-
-		node_shader_add_out(vert, 'float p_fade');
-		node_shader_write(vert, 'p_fade = sin(min((p_age / 8.0) * 3.141592, 3.141592));');
-
-		node_shader_add_out(frag, 'float fragColor');
-		node_shader_write(frag, 'fragColor = p_fade;');
-
-		// add_out(vert, 'vec4 wvpposition');
-		// write(vert, 'wvpposition = gl_Position;');
-		// write(frag, 'vec2 texCoord = wvpposition.xy / wvpposition.w;');
-		// add_uniform(frag, 'sampler2D gbufferD');
-		// write(frag, 'fragColor *= 1.0 - clamp(distance(textureLod(gbufferD, texCoord, 0.0).r, wvpposition.z), 0.0, 1.0);');
-
-		// Material.finalize(con_part);
-		con_part.data.shader_from_source = true;
-		con_part.data.vertex_shader = node_shader_get(vert);
-		con_part.data.fragment_shader = node_shader_get(frag);
-
-		return con_part;
-	}
-
-	static mask = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-		///if arm_physics
-		if (context_raw.particle_physics) {
-			node_shader_add_out(vert, 'vec4 wpos');
-			node_shader_add_uniform(vert, 'mat4 W', '_world_matrix');
-			node_shader_write_attrib(vert, 'wpos = mul(vec4(pos.xyz, 1.0), W);');
-			node_shader_add_uniform(frag, 'vec3 particleHit', '_particleHit');
-			node_shader_add_uniform(frag, 'vec3 particleHitLast', '_particleHitLast');
-
-			node_shader_write(frag, 'vec3 pa = wpos.xyz - particleHit;');
-			node_shader_write(frag, 'vec3 ba = particleHitLast - particleHit;');
-			node_shader_write(frag, 'float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);');
-			node_shader_write(frag, 'dist = length(pa - ba * h) * 10.0;');
-			// write(frag, 'dist = distance(particleHit, wpos.xyz) * 10.0;');
-
-			node_shader_write(frag, 'if (dist > 1.0) discard;');
-			node_shader_write(frag, 'float str = clamp(pow(1.0 / dist * brushHardness * 0.2, 4.0), 0.0, 1.0) * opacity;');
-			node_shader_write(frag, 'if (particleHit.x == 0.0 && particleHit.y == 0.0 && particleHit.z == 0.0) str = 0.0;');
-			node_shader_write(frag, 'if (str == 0.0) discard;');
-			return;
-		}
-		///end
-
-		node_shader_add_uniform(frag, 'sampler2D texparticle', '_texparticle');
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(frag, 'float str = textureLod(texparticle, sp.xy, 0.0).r;');
-		///else
-		node_shader_write(frag, 'float str = textureLod(texparticle, vec2(sp.x, (1.0 - sp.y)), 0.0).r;');
-		///end
-	}
-}

+ 0 - 99
armorpaint/Sources/MakeTexcoord.ts

@@ -1,99 +0,0 @@
-
-class MakeTexcoord {
-
-	static make_texcoord_run = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-
-		let fill_layer: bool = context_raw.layer.fill_layer != null;
-		let uv_type: uv_type_t = fill_layer ? context_raw.layer.uv_type : context_raw.brush_paint;
-		let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
-		let angle: f32 = context_raw.brush_angle + context_raw.brush_nodes_angle;
-		let uvAngle: f32 = fill_layer ? context_raw.layer.angle : angle;
-
-		if (uv_type == uv_type_t.PROJECT || decal) { // TexCoords - project
-			node_shader_add_uniform(frag, 'float brushScale', '_brushScale');
-			node_shader_write_attrib(frag, 'vec2 uvsp = sp.xy;');
-
-			if (fill_layer) { // Decal layer
-				node_shader_write_attrib(frag, 'if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) discard;');
-
-				if (uvAngle != 0.0) {
-					node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
-					node_shader_write_attrib(frag, 'uvsp = vec2(uvsp.x * brushAngle.x - uvsp.y * brushAngle.y, uvsp.x * brushAngle.y + uvsp.y * brushAngle.x);');
-				}
-
-				frag.n = true;
-				node_shader_add_uniform(frag, 'vec3 decalLayerNor', '_decalLayerNor');
-				let dot_angle: f32 = context_raw.brush_angle_reject_dot;
-				node_shader_write(frag, `if (abs(dot(n, decalLayerNor) - 1.0) > ${dot_angle}) discard;`);
-
-				frag.wposition = true;
-				node_shader_add_uniform(frag, 'vec3 decalLayerLoc', '_decalLayerLoc');
-				node_shader_add_uniform(frag, 'float decalLayerDim', '_decalLayerDim');
-				node_shader_write_attrib(frag, 'if (abs(dot(decalLayerNor, decalLayerLoc - wposition)) > decalLayerDim) discard;');
-			}
-			else if (decal) {
-				node_shader_add_uniform(frag, 'vec4 decalMask', '_decalMask');
-				node_shader_write_attrib(frag, 'uvsp -= decalMask.xy;');
-				node_shader_write_attrib(frag, 'uvsp.x *= aspectRatio;');
-				node_shader_write_attrib(frag, 'uvsp *= 0.21 / (decalMask.w * 0.9);'); // Decal radius
-
-				if (context_raw.brush_directional) {
-					node_shader_add_uniform(frag, 'vec3 brushDirection', '_brushDirection');
-					node_shader_write_attrib(frag, 'if (brushDirection.z == 0.0) discard;');
-					node_shader_write_attrib(frag, 'uvsp = vec2(uvsp.x * brushDirection.x - uvsp.y * brushDirection.y, uvsp.x * brushDirection.y + uvsp.y * brushDirection.x);');
-				}
-
-				if (uvAngle != 0.0) {
-					node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
-					node_shader_write_attrib(frag, 'uvsp = vec2(uvsp.x * brushAngle.x - uvsp.y * brushAngle.y, uvsp.x * brushAngle.y + uvsp.y * brushAngle.x);');
-				}
-
-				node_shader_add_uniform(frag, 'float brushScaleX', '_brushScaleX');
-				node_shader_write_attrib(frag, 'uvsp.x *= brushScaleX;');
-
-				node_shader_write_attrib(frag, 'uvsp += vec2(0.5, 0.5);');
-
-				node_shader_write_attrib(frag, 'if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) discard;');
-			}
-			else {
-				node_shader_write_attrib(frag, 'uvsp.x *= aspectRatio;');
-
-				if (uvAngle != 0.0) {
-					node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
-					node_shader_write_attrib(frag, 'uvsp = vec2(uvsp.x * brushAngle.x - uvsp.y * brushAngle.y, uvsp.x * brushAngle.y + uvsp.y * brushAngle.x);');
-				}
-			}
-
-			node_shader_write_attrib(frag, 'vec2 texCoord = uvsp * brushScale;');
-		}
-		else if (uv_type == uv_type_t.UVMAP) { // TexCoords - uvmap
-			node_shader_add_uniform(vert, 'float brushScale', '_brushScale');
-			node_shader_add_out(vert, 'vec2 texCoord');
-			node_shader_write(vert, 'texCoord = tex * brushScale;');
-
-			if (uvAngle > 0.0) {
-				node_shader_add_uniform(vert, 'vec2 brushAngle', '_brushAngle');
-				node_shader_write(vert, 'texCoord = vec2(texCoord.x * brushAngle.x - texCoord.y * brushAngle.y, texCoord.x * brushAngle.y + texCoord.y * brushAngle.x);');
-			}
-		}
-		else { // TexCoords - triplanar
-			frag.wposition = true;
-			frag.n = true;
-			node_shader_add_uniform(frag, 'float brushScale', '_brushScale');
-			node_shader_write_attrib(frag, 'vec3 triWeight = wnormal * wnormal;'); // n * n
-			node_shader_write_attrib(frag, 'float triMax = max(triWeight.x, max(triWeight.y, triWeight.z));');
-			node_shader_write_attrib(frag, 'triWeight = max(triWeight - triMax * 0.75, 0.0);');
-			node_shader_write_attrib(frag, 'vec3 texCoordBlend = triWeight * (1.0 / (triWeight.x + triWeight.y + triWeight.z));');
-			node_shader_write_attrib(frag, 'vec2 texCoord = wposition.yz * brushScale * 0.5;');
-			node_shader_write_attrib(frag, 'vec2 texCoord1 = wposition.xz * brushScale * 0.5;');
-			node_shader_write_attrib(frag, 'vec2 texCoord2 = wposition.xy * brushScale * 0.5;');
-
-			if (uvAngle != 0.0) {
-				node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
-				node_shader_write_attrib(frag, 'texCoord = vec2(texCoord.x * brushAngle.x - texCoord.y * brushAngle.y, texCoord.x * brushAngle.y + texCoord.y * brushAngle.x);');
-				node_shader_write_attrib(frag, 'texCoord1 = vec2(texCoord1.x * brushAngle.x - texCoord1.y * brushAngle.y, texCoord1.x * brushAngle.y + texCoord1.y * brushAngle.x);');
-				node_shader_write_attrib(frag, 'texCoord2 = vec2(texCoord2.x * brushAngle.x - texCoord2.y * brushAngle.y, texCoord2.x * brushAngle.y + texCoord2.y * brushAngle.x);');
-			}
-		}
-	}
-}

+ 0 - 43
armorpaint/Sources/NodesBrush.ts

@@ -1,43 +0,0 @@
-/// <reference path='./nodes/TEX_IMAGE.ts'/>
-/// <reference path='./nodes/InputNode.ts'/>
-/// <reference path='./../../base/Sources/nodes/MathNode.ts'/>
-/// <reference path='./../../base/Sources/nodes/RandomNode.ts'/>
-/// <reference path='./../../base/Sources/nodes/SeparateVectorNode.ts'/>
-/// <reference path='./../../base/Sources/nodes/TimeNode.ts'/>
-/// <reference path='./../../base/Sources/nodes/FloatNode.ts'/>
-/// <reference path='./../../base/Sources/nodes/VectorNode.ts'/>
-/// <reference path='./../../base/Sources/nodes/VectorMathNode.ts'/>
-
-class NodesBrush {
-
-	static nodes_brush_categories = [_tr("Nodes")];
-
-	static nodes_brush_list: zui_node_t[][] = [
-		[ // Category 0
-			TEX_IMAGE.def,
-			InputNode.def,
-			MathNode.def,
-			RandomNode.def,
-			SeparateVectorNode.def,
-			TimeNode.def,
-			FloatNode.def,
-			VectorNode.def,
-			VectorMathNode.def
-		]
-	];
-
-	static nodes_brush_create_node = (nodeType: string): zui_node_t => {
-		for (let c of NodesBrush.nodes_brush_list) {
-			for (let n of c) {
-				if (n.type == nodeType) {
-					let canvas: zui_node_canvas_t = context_raw.brush.canvas;
-					let nodes: zui_nodes_t = context_raw.brush.nodes;
-					let node: zui_node_t = ui_nodes_make_node(n, nodes, canvas);
-					canvas.nodes.push(node);
-					return node;
-				}
-			}
-		}
-		return null;
-	}
-}

+ 0 - 936
armorpaint/Sources/RenderPathPaint.ts

@@ -1,936 +0,0 @@
-
-class RenderPathPaint {
-
-	static render_path_paint_live_layer: SlotLayerRaw = null;
-	static render_path_paint_live_layer_drawn: i32 = 0;
-	static render_path_paint_live_layer_locked: bool = false;
-	static render_path_paint_dilated: bool = true;
-	static render_path_paint_init_voxels: bool = true; // Bake AO
-	static render_path_paint_push_undo_last: bool;
-	static render_path_paint_painto: mesh_object_t = null;
-	static render_path_paint_planeo: mesh_object_t = null;
-	static render_path_paint_visibles: bool[] = null;
-	static render_path_paint_merged_object_visible: bool = false;
-	static render_path_paint_saved_fov: f32 = 0.0;
-	static render_path_paint_baking: bool = false;
-	static _render_path_paint_texpaint: render_target_t;
-	static _render_path_paint_texpaint_nor: render_target_t;
-	static _render_path_paint_texpaint_pack: render_target_t;
-	static _render_path_paint_texpaint_undo: render_target_t;
-	static _render_path_paint_texpaint_nor_undo: render_target_t;
-	static _render_path_paint_texpaint_pack_undo: render_target_t;
-	static render_path_paint_last_x: f32 = -1.0;
-	static render_path_paint_last_y: f32 = -1.0;
-
-	static render_path_paint_init = () => {
-
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpaint_blend0";
-			t.width = config_get_texture_res_x();
-			t.height = config_get_texture_res_y();
-			t.format = "R8";
-			render_path_create_render_target(t);
-		}
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpaint_blend1";
-			t.width = config_get_texture_res_x();
-			t.height = config_get_texture_res_y();
-			t.format = "R8";
-			render_path_create_render_target(t);
-		}
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpaint_colorid";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpaint_picker";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpaint_nor_picker";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpaint_pack_picker";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpaint_uv_picker";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpaint_posnortex_picker0";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA128";
-			render_path_create_render_target(t);
-		}
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpaint_posnortex_picker1";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA128";
-			render_path_create_render_target(t);
-		}
-
-		render_path_load_shader("shader_datas/copy_mrt3_pass/copy_mrt3_pass");
-		render_path_load_shader("shader_datas/copy_mrt3_pass/copy_mrt3RGBA64_pass");
-		///if is_paint
-		render_path_load_shader("shader_datas/dilate_pass/dilate_pass");
-		///end
-	}
-
-	static render_path_paint_commands_paint = (dilation = true) => {
-		let tid: i32 = context_raw.layer.id;
-
-		if (context_raw.pdirty > 0) {
-			///if arm_physics
-			let particle_physics: bool = context_raw.particle_physics;
-			///else
-			let particle_physics: bool = false;
-			///end
-			if (context_raw.tool == workspace_tool_t.PARTICLE && !particle_physics) {
-				render_path_set_target("texparticle");
-				render_path_clear_target(0x00000000);
-				render_path_bind_target("_main", "gbufferD");
-				if ((context_raw.xray || config_raw.brush_angle_reject) && config_raw.brush_3d) {
-					render_path_bind_target("gbuffer0", "gbuffer0");
-				}
-
-				let mo: mesh_object_t = scene_get_child(".ParticleEmitter").ext;
-				mo.base.visible = true;
-				mesh_object_render(mo, "mesh",_render_path_bind_params);
-				mo.base.visible = false;
-
-				mo = scene_get_child(".Particle").ext;
-				mo.base.visible = true;
-				mesh_object_render(mo, "mesh",_render_path_bind_params);
-				mo.base.visible = false;
-				render_path_end();
-			}
-
-			///if is_paint
-			if (context_raw.tool == workspace_tool_t.COLORID) {
-				render_path_set_target("texpaint_colorid");
-				render_path_clear_target(0xff000000);
-				render_path_bind_target("gbuffer2", "gbuffer2");
-				render_path_draw_meshes("paint");
-				ui_header_handle.redraws = 2;
-			}
-			else if (context_raw.tool == workspace_tool_t.PICKER || context_raw.tool == workspace_tool_t.MATERIAL) {
-				if (context_raw.pick_pos_nor_tex) {
-					if (context_raw.paint2d) {
-						render_path_set_target("gbuffer0", ["gbuffer1", "gbuffer2"]);
-						render_path_draw_meshes("mesh");
-					}
-					render_path_set_target("texpaint_posnortex_picker0", ["texpaint_posnortex_picker1"]);
-					render_path_bind_target("gbuffer2", "gbuffer2");
-					render_path_bind_target("_main", "gbufferD");
-					render_path_draw_meshes("paint");
-					let texpaint_posnortex_picker0: image_t = render_path_render_targets.get("texpaint_posnortex_picker0")._image;
-					let texpaint_posnortex_picker1: image_t = render_path_render_targets.get("texpaint_posnortex_picker1")._image;
-					let a: DataView = new DataView(image_get_pixels(texpaint_posnortex_picker0));
-					let b: DataView = new DataView(image_get_pixels(texpaint_posnortex_picker1));
-					context_raw.posx_picked = a.getFloat32(0, true);
-					context_raw.posy_picked = a.getFloat32(4, true);
-					context_raw.posz_picked = a.getFloat32(8, true);
-					context_raw.uvx_picked = a.getFloat32(12, true);
-					context_raw.norx_picked = b.getFloat32(0, true);
-					context_raw.nory_picked = b.getFloat32(4, true);
-					context_raw.norz_picked = b.getFloat32(8, true);
-					context_raw.uvy_picked = b.getFloat32(12, true);
-				}
-				else {
-					render_path_set_target("texpaint_picker", ["texpaint_nor_picker", "texpaint_pack_picker", "texpaint_uv_picker"]);
-					render_path_bind_target("gbuffer2", "gbuffer2");
-					tid = context_raw.layer.id;
-					let use_live_layer: bool = context_raw.tool == workspace_tool_t.MATERIAL;
-					if (use_live_layer) RenderPathPaint.render_path_paint_use_live_layer(true);
-					render_path_bind_target("texpaint" + tid, "texpaint");
-					render_path_bind_target("texpaint_nor" + tid, "texpaint_nor");
-					render_path_bind_target("texpaint_pack" + tid, "texpaint_pack");
-					render_path_draw_meshes("paint");
-					if (use_live_layer) RenderPathPaint.render_path_paint_use_live_layer(false);
-					ui_header_handle.redraws = 2;
-					ui_base_hwnds[2].redraws = 2;
-
-					let texpaint_picker: image_t = render_path_render_targets.get("texpaint_picker")._image;
-					let texpaint_nor_picker: image_t = render_path_render_targets.get("texpaint_nor_picker")._image;
-					let texpaint_pack_picker: image_t = render_path_render_targets.get("texpaint_pack_picker")._image;
-					let texpaint_uv_picker: image_t = render_path_render_targets.get("texpaint_uv_picker")._image;
-					let a: DataView = new DataView(image_get_pixels(texpaint_picker));
-					let b: DataView = new DataView(image_get_pixels(texpaint_nor_picker));
-					let c: DataView = new DataView(image_get_pixels(texpaint_pack_picker));
-					let d: DataView = new DataView(image_get_pixels(texpaint_uv_picker));
-
-					if (context_raw.color_picker_callback != null) {
-						context_raw.color_picker_callback(context_raw.picked_color);
-					}
-
-					// Picked surface values
-					///if (krom_metal || krom_vulkan)
-					let i0: i32 = 2;
-					let i1: i32 = 1;
-					let i2: i32 = 0;
-					///else
-					let i0: i32 = 0;
-					let i1: i32 = 1;
-					let i2: i32 = 2;
-					///end
-					let i3: i32 = 3;
-					context_raw.picked_color.base = color_set_rb(context_raw.picked_color.base, a.getUint8(i0));
-					context_raw.picked_color.base = color_set_gb(context_raw.picked_color.base, a.getUint8(i1));
-					context_raw.picked_color.base = color_set_bb(context_raw.picked_color.base, a.getUint8(i2));
-					context_raw.picked_color.normal = color_set_rb(context_raw.picked_color.normal, b.getUint8(i0));
-					context_raw.picked_color.normal = color_set_gb(context_raw.picked_color.normal, b.getUint8(i1));
-					context_raw.picked_color.normal = color_set_bb(context_raw.picked_color.normal, b.getUint8(i2));
-					context_raw.picked_color.occlusion = c.getUint8(i0) / 255;
-					context_raw.picked_color.roughness = c.getUint8(i1) / 255;
-					context_raw.picked_color.metallic = c.getUint8(i2) / 255;
-					context_raw.picked_color.height = c.getUint8(i3) / 255;
-					context_raw.picked_color.opacity = a.getUint8(i3) / 255;
-					context_raw.uvx_picked = d.getUint8(i0) / 255;
-					context_raw.uvy_picked = d.getUint8(i1) / 255;
-					// Pick material
-					if (context_raw.picker_select_material && context_raw.color_picker_callback == null) {
-						// matid % 3 == 0 - normal, 1 - emission, 2 - subsurface
-						let matid: i32 = math_floor((b.getUint8(3) - (b.getUint8(3) % 3)) / 3);
-						for (let m of project_materials) {
-							if (m.id == matid) {
-								context_set_material(m);
-								context_raw.materialid_picked = matid;
-								break;
-							}
-						}
-					}
-				}
-			}
-			else {
-				///if arm_voxels
-				if (context_raw.tool == workspace_tool_t.BAKE && context_raw.bake_type == bake_type_t.AO) {
-					if (RenderPathPaint.render_path_paint_init_voxels) {
-						RenderPathPaint.render_path_paint_init_voxels = false;
-						let _rp_gi: bool = config_raw.rp_gi;
-						config_raw.rp_gi = true;
-						render_path_base_init_voxels();
-						config_raw.rp_gi = _rp_gi;
-					}
-					render_path_clear_image("voxels", 0x00000000);
-					render_path_set_target("");
-					render_path_set_viewport(256, 256);
-					render_path_bind_target("voxels", "voxels");
-					render_path_draw_meshes("voxel");
-					render_path_gen_mipmaps("voxels");
-				}
-				///end
-
-				let texpaint: string = "texpaint" + tid;
-				if (context_raw.tool == workspace_tool_t.BAKE && context_raw.brush_time == time_delta()) {
-					// Clear to black on bake start
-					render_path_set_target(texpaint);
-					render_path_clear_target(0xff000000);
-				}
-
-				render_path_set_target("texpaint_blend1");
-				render_path_bind_target("texpaint_blend0", "tex");
-				render_path_draw_shader("shader_datas/copy_pass/copyR8_pass");
-				let is_mask: bool = SlotLayer.slot_layer_is_mask(context_raw.layer);
-				if (is_mask) {
-					let ptid: i32 = context_raw.layer.parent.id;
-					if (SlotLayer.slot_layer_is_group(context_raw.layer.parent)) { // Group mask
-						for (let c of SlotLayer.slot_layer_get_children(context_raw.layer.parent)) {
-							ptid = c.id;
-							break;
-						}
-					}
-					render_path_set_target(texpaint, ["texpaint_nor" + ptid, "texpaint_pack" + ptid, "texpaint_blend0"]);
-				}
-				else {
-					render_path_set_target(texpaint, ["texpaint_nor" + tid, "texpaint_pack" + tid, "texpaint_blend0"]);
-				}
-				render_path_bind_target("_main", "gbufferD");
-				if ((context_raw.xray || config_raw.brush_angle_reject) && config_raw.brush_3d) {
-					render_path_bind_target("gbuffer0", "gbuffer0");
-				}
-				render_path_bind_target("texpaint_blend1", "paintmask");
-				///if arm_voxels
-				if (context_raw.tool == workspace_tool_t.BAKE && context_raw.bake_type == bake_type_t.AO) {
-					render_path_bind_target("voxels", "voxels");
-				}
-				///end
-				if (context_raw.colorid_picked) {
-					render_path_bind_target("texpaint_colorid", "texpaint_colorid");
-				}
-
-				// Read texcoords from gbuffer
-				let read_tc: bool = (context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.FACE) ||
-							  		 context_raw.tool == workspace_tool_t.CLONE ||
-									 context_raw.tool == workspace_tool_t.BLUR ||
-									 context_raw.tool == workspace_tool_t.SMUDGE;
-				if (read_tc) {
-					render_path_bind_target("gbuffer2", "gbuffer2");
-				}
-
-				render_path_draw_meshes("paint");
-
-				if (context_raw.tool == workspace_tool_t.BAKE && context_raw.bake_type == bake_type_t.CURVATURE && context_raw.bake_curv_smooth > 0) {
-					if (render_path_render_targets.get("texpaint_blur") == null) {
-						let t = render_target_create();
-						t.name = "texpaint_blur";
-						t.width = math_floor(config_get_texture_res_x() * 0.95);
-						t.height = math_floor(config_get_texture_res_y() * 0.95);
-						t.format = "RGBA32";
-						render_path_create_render_target(t);
-					}
-					let blurs: i32 = math_round(context_raw.bake_curv_smooth);
-					for (let i: i32 = 0; i < blurs; ++i) {
-						render_path_set_target("texpaint_blur");
-						render_path_bind_target(texpaint, "tex");
-						render_path_draw_shader("shader_datas/copy_pass/copy_pass");
-						render_path_set_target(texpaint);
-						render_path_bind_target("texpaint_blur", "tex");
-						render_path_draw_shader("shader_datas/copy_pass/copy_pass");
-					}
-				}
-
-				if (dilation && config_raw.dilate == dilate_type_t.INSTANT) {
-					RenderPathPaint.render_path_paint_dilate(true, false);
-				}
-			}
-			///end
-
-			///if is_sculpt
-			let texpaint: string = "texpaint" + tid;
-			render_path_set_target("texpaint_blend1");
-			render_path_bind_target("texpaint_blend0", "tex");
-			render_path_draw_shader("shader_datas/copy_pass/copyR8_pass");
-			render_path_set_target(texpaint, ["texpaint_blend0"]);
-			render_path_bind_target("gbufferD_undo", "gbufferD");
-			if ((context_raw.xray || config_raw.brush_angle_reject) && config_raw.brush_3d) {
-				render_path_bind_target("gbuffer0", "gbuffer0");
-			}
-			render_path_bind_target("texpaint_blend1", "paintmask");
-
-			// Read texcoords from gbuffer
-			let read_tc: bool = (context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.FACE) ||
-						  		 context_raw.tool == workspace_tool_t.CLONE ||
-						  		 context_raw.tool == workspace_tool_t.BLUR ||
-						  		 context_raw.tool == workspace_tool_t.SMUDGE;
-			if (read_tc) {
-				render_path_bind_target("gbuffer2", "gbuffer2");
-			}
-			render_path_bind_target("gbuffer0_undo", "gbuffer0_undo");
-
-			let material_contexts: material_context_t[] = [];
-			let shader_contexts: shader_context_t[] = [];
-			let mats: material_data_t[] = project_paint_objects[0].materials;
-			mesh_object_get_contexts(project_paint_objects[0], "paint", mats, material_contexts, shader_contexts);
-
-			let cc_context: shader_context_t = shader_contexts[0];
-			if (const_data_screen_aligned_vb == null) const_data_create_screen_aligned_data();
-			g4_set_pipeline(cc_context._.pipe_state);
-			uniforms_set_context_consts(cc_context,_render_path_bind_params);
-			uniforms_set_obj_consts(cc_context, project_paint_objects[0].base);
-			uniforms_set_material_consts(cc_context, material_contexts[0]);
-			g4_set_vertex_buffer(const_data_screen_aligned_vb);
-			g4_set_index_buffer(const_data_screen_aligned_ib);
-			g4_draw();
-			render_path_end();
-			///end
-		}
-	}
-
-	static render_path_paint_use_live_layer = (use: bool) => {
-		let tid: i32 = context_raw.layer.id;
-		let hid: i32 = history_undo_i - 1 < 0 ? config_raw.undo_steps - 1 : history_undo_i - 1;
-		if (use) {
-			RenderPathPaint._render_path_paint_texpaint = render_path_render_targets.get("texpaint" + tid);
-			RenderPathPaint._render_path_paint_texpaint_undo = render_path_render_targets.get("texpaint_undo" + hid);
-			RenderPathPaint._render_path_paint_texpaint_nor_undo = render_path_render_targets.get("texpaint_nor_undo" + hid);
-			RenderPathPaint._render_path_paint_texpaint_pack_undo = render_path_render_targets.get("texpaint_pack_undo" + hid);
-			RenderPathPaint._render_path_paint_texpaint_nor = render_path_render_targets.get("texpaint_nor" + tid);
-			RenderPathPaint._render_path_paint_texpaint_pack = render_path_render_targets.get("texpaint_pack" + tid);
-			render_path_render_targets.set("texpaint_undo" + hid,render_path_render_targets.get("texpaint" + tid));
-			render_path_render_targets.set("texpaint" + tid,render_path_render_targets.get("texpaint_live"));
-			if (SlotLayer.slot_layer_is_layer(context_raw.layer)) {
-				render_path_render_targets.set("texpaint_nor_undo" + hid,render_path_render_targets.get("texpaint_nor" + tid));
-				render_path_render_targets.set("texpaint_pack_undo" + hid,render_path_render_targets.get("texpaint_pack" + tid));
-				render_path_render_targets.set("texpaint_nor" + tid,render_path_render_targets.get("texpaint_nor_live"));
-				render_path_render_targets.set("texpaint_pack" + tid,render_path_render_targets.get("texpaint_pack_live"));
-			}
-		}
-		else {
-			render_path_render_targets.set("texpaint" + tid, RenderPathPaint._render_path_paint_texpaint);
-			render_path_render_targets.set("texpaint_undo" + hid, RenderPathPaint._render_path_paint_texpaint_undo);
-			if (SlotLayer.slot_layer_is_layer(context_raw.layer)) {
-				render_path_render_targets.set("texpaint_nor_undo" + hid, RenderPathPaint._render_path_paint_texpaint_nor_undo);
-				render_path_render_targets.set("texpaint_pack_undo" + hid, RenderPathPaint._render_path_paint_texpaint_pack_undo);
-				render_path_render_targets.set("texpaint_nor" + tid, RenderPathPaint._render_path_paint_texpaint_nor);
-				render_path_render_targets.set("texpaint_pack" + tid, RenderPathPaint._render_path_paint_texpaint_pack);
-			}
-		}
-		RenderPathPaint.render_path_paint_live_layer_locked = use;
-	}
-
-	static render_path_paint_commands_live_brush = () => {
-		let tool: workspace_tool_t = context_raw.tool;
-		if (tool != workspace_tool_t.BRUSH &&
-			tool != workspace_tool_t.ERASER &&
-			tool != workspace_tool_t.CLONE &&
-			tool != workspace_tool_t.DECAL &&
-			tool != workspace_tool_t.TEXT &&
-			tool != workspace_tool_t.BLUR &&
-			tool != workspace_tool_t.SMUDGE) {
-				return;
-		}
-
-		if (RenderPathPaint.render_path_paint_live_layer_locked) return;
-
-		if (RenderPathPaint.render_path_paint_live_layer == null) {
-			RenderPathPaint.render_path_paint_live_layer = SlotLayer.slot_layer_create("_live");
-		}
-
-		let tid: i32 = context_raw.layer.id;
-		if (SlotLayer.slot_layer_is_mask(context_raw.layer)) {
-			render_path_set_target("texpaint_live");
-			render_path_bind_target("texpaint" + tid, "tex");
-			render_path_draw_shader("shader_datas/copy_pass/copy_pass");
-		}
-		else {
-			render_path_set_target("texpaint_live", ["texpaint_nor_live", "texpaint_pack_live"]);
-			render_path_bind_target("texpaint" + tid, "tex0");
-			render_path_bind_target("texpaint_nor" + tid, "tex1");
-			render_path_bind_target("texpaint_pack" + tid, "tex2");
-			render_path_draw_shader("shader_datas/copy_mrt3_pass/copy_mrt3_pass");
-		}
-
-		RenderPathPaint.render_path_paint_use_live_layer(true);
-
-		RenderPathPaint.render_path_paint_live_layer_drawn = 2;
-
-		ui_view2d_hwnd.redraws = 2;
-		let _x: f32 = context_raw.paint_vec.x;
-		let _y: f32 = context_raw.paint_vec.y;
-		if (context_raw.brush_locked) {
-			context_raw.paint_vec.x = (context_raw.lock_started_x - app_x()) / app_w();
-			context_raw.paint_vec.y = (context_raw.lock_started_y - app_y()) / app_h();
-		}
-		let _last_x: f32 = context_raw.last_paint_vec_x;
-		let _last_y: f32 = context_raw.last_paint_vec_y;
-		let _pdirty: i32 = context_raw.pdirty;
-		context_raw.last_paint_vec_x = context_raw.paint_vec.x;
-		context_raw.last_paint_vec_y = context_raw.paint_vec.y;
-		if (operator_shortcut(config_keymap.brush_ruler)) {
-			context_raw.last_paint_vec_x = context_raw.last_paint_x;
-			context_raw.last_paint_vec_y = context_raw.last_paint_y;
-		}
-		context_raw.pdirty = 2;
-
-		RenderPathPaint.render_path_paint_commands_symmetry();
-		RenderPathPaint.render_path_paint_commands_paint();
-
-		RenderPathPaint.render_path_paint_use_live_layer(false);
-
-		context_raw.paint_vec.x = _x;
-		context_raw.paint_vec.y = _y;
-		context_raw.last_paint_vec_x = _last_x;
-		context_raw.last_paint_vec_y = _last_y;
-		context_raw.pdirty = _pdirty;
-		context_raw.brush_blend_dirty = true;
-	}
-
-	static render_path_paint_commands_cursor = () => {
-		if (!config_raw.brush_3d) return;
-		let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
-		let decal_mask: bool = decal && operator_shortcut(config_keymap.decal_mask, shortcut_type_t.DOWN);
-		let tool: workspace_tool_t = context_raw.tool;
-		if (tool != workspace_tool_t.BRUSH &&
-			tool != workspace_tool_t.ERASER &&
-			tool != workspace_tool_t.CLONE &&
-			tool != workspace_tool_t.BLUR &&
-			tool != workspace_tool_t.SMUDGE &&
-			tool != workspace_tool_t.PARTICLE &&
-			!decal_mask) {
-				return;
-		}
-
-		let fill_layer: bool = context_raw.layer.fill_layer != null;
-		let group_layer: bool = SlotLayer.slot_layer_is_group(context_raw.layer);
-		if (!base_ui_enabled || base_is_dragging || fill_layer || group_layer) {
-			return;
-		}
-
-		let mx: f32 = context_raw.paint_vec.x;
-		let my: f32 = 1.0 - context_raw.paint_vec.y;
-		if (context_raw.brush_locked) {
-			mx = (context_raw.lock_started_x - app_x()) / app_w();
-			my = 1.0 - (context_raw.lock_started_y - app_y()) / app_h();
-		}
-		let radius: f32 = decal_mask ? context_raw.brush_decal_mask_radius : context_raw.brush_radius;
-		RenderPathPaint.render_path_paint_draw_cursor(mx, my, context_raw.brush_nodes_radius * radius / 3.4);
-	}
-
-	static render_path_paint_draw_cursor = (mx: f32, my: f32, radius: f32, tint_r: f32 = 1.0, tint_g: f32 = 1.0, tint_b: f32 = 1.0) => {
-		let plane: mesh_object_t = scene_get_child(".Plane").ext;
-		let geom: mesh_data_t = plane.data;
-
-		if (base_pipe_cursor == null) base_make_cursor_pipe();
-
-		render_path_set_target("");
-		g4_set_pipeline(base_pipe_cursor);
-		let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
-		let decal_mask: bool = decal && operator_shortcut(config_keymap.decal_mask, shortcut_type_t.DOWN);
-		let img: image_t = (decal && !decal_mask) ? context_raw.decal_image : resource_get("cursor.k");
-		g4_set_tex(base_cursor_tex, img);
-		let gbuffer0: image_t = render_path_render_targets.get("gbuffer0")._image;
-		g4_set_tex_depth(base_cursor_gbufferd, gbuffer0);
-		g4_set_float2(base_cursor_mouse, mx, my);
-		g4_set_float2(base_cursor_tex_step, 1 / gbuffer0.width, 1 / gbuffer0.height);
-		g4_set_float(base_cursor_radius, radius);
-		let right: vec4_t = vec4_normalize(camera_object_right_world(scene_camera));
-		g4_set_float3(base_cursor_camera_right, right.x, right.y, right.z);
-		g4_set_float3(base_cursor_tint, tint_r, tint_g, tint_b);
-		g4_set_mat(base_cursor_vp, scene_camera.vp);
-		let help_mat: mat4_t = mat4_identity();
-		mat4_get_inv(help_mat, scene_camera.vp);
-		g4_set_mat(base_cursor_inv_vp, help_mat);
-		///if (krom_metal || krom_vulkan)
-		g4_set_vertex_buffer(mesh_data_get(geom, [{name: "tex", data: "short2norm"}]));
-		///else
-		g4_set_vertex_buffer(geom._.vertex_buffer);
-		///end
-		g4_set_index_buffer(geom._.index_buffers[0]);
-		g4_draw();
-
-		g4_disable_scissor();
-		render_path_end();
-	}
-
-	static render_path_paint_commands_symmetry = () => {
-		if (context_raw.sym_x || context_raw.sym_y || context_raw.sym_z) {
-			context_raw.ddirty = 2;
-			let t: transform_t = context_raw.paint_object.base.transform;
-			let sx: f32 = t.scale.x;
-			let sy: f32 = t.scale.y;
-			let sz: f32 = t.scale.z;
-			if (context_raw.sym_x) {
-				vec4_set(t.scale, -sx, sy, sz);
-				transform_build_matrix(t);
-				RenderPathPaint.render_path_paint_commands_paint(false);
-			}
-			if (context_raw.sym_y) {
-				vec4_set(t.scale, sx, -sy, sz);
-				transform_build_matrix(t);
-				RenderPathPaint.render_path_paint_commands_paint(false);
-			}
-			if (context_raw.sym_z) {
-				vec4_set(t.scale, sx, sy, -sz);
-				transform_build_matrix(t);
-				RenderPathPaint.render_path_paint_commands_paint(false);
-			}
-			if (context_raw.sym_x && context_raw.sym_y) {
-				vec4_set(t.scale, -sx, -sy, sz);
-				transform_build_matrix(t);
-				RenderPathPaint.render_path_paint_commands_paint(false);
-			}
-			if (context_raw.sym_x && context_raw.sym_z) {
-				vec4_set(t.scale, -sx, sy, -sz);
-				transform_build_matrix(t);
-				RenderPathPaint.render_path_paint_commands_paint(false);
-			}
-			if (context_raw.sym_y && context_raw.sym_z) {
-				vec4_set(t.scale, sx, -sy, -sz);
-				transform_build_matrix(t);
-				RenderPathPaint.render_path_paint_commands_paint(false);
-			}
-			if (context_raw.sym_x && context_raw.sym_y && context_raw.sym_z) {
-				vec4_set(t.scale, -sx, -sy, -sz);
-				transform_build_matrix(t);
-				RenderPathPaint.render_path_paint_commands_paint(false);
-			}
-			vec4_set(t.scale, sx, sy, sz);
-			transform_build_matrix(t);
-		}
-	}
-
-	static render_path_paint_paint_enabled = (): bool => {
-		///if is_paint
-		let fill_layer: bool = context_raw.layer.fill_layer != null && context_raw.tool != workspace_tool_t.PICKER && context_raw.tool != workspace_tool_t.MATERIAL && context_raw.tool != workspace_tool_t.COLORID;
-		///end
-
-		///if is_sculpt
-		let fill_layer: bool = context_raw.layer.fill_layer != null && context_raw.tool != workspace_tool_t.PICKER && context_raw.tool != workspace_tool_t.MATERIAL;
-		///end
-
-		let group_layer: bool = SlotLayer.slot_layer_is_group(context_raw.layer);
-		return !fill_layer && !group_layer && !context_raw.foreground_event;
-	}
-
-	static render_path_paint_live_brush_dirty = () => {
-		let mx: f32 = RenderPathPaint.render_path_paint_last_x;
-		let my: f32 = RenderPathPaint.render_path_paint_last_y;
-		RenderPathPaint.render_path_paint_last_x = mouse_view_x();
-		RenderPathPaint.render_path_paint_last_y = mouse_view_y();
-		if (config_raw.brush_live && context_raw.pdirty <= 0) {
-			let moved: bool = (mx != RenderPathPaint.render_path_paint_last_x || my != RenderPathPaint.render_path_paint_last_y) && (context_in_viewport() || context_in_2d_view());
-			if (moved || context_raw.brush_locked) {
-				context_raw.rdirty = 2;
-			}
-		}
-	}
-
-	static render_path_paint_begin = () => {
-
-		///if is_paint
-		if (!RenderPathPaint.render_path_paint_dilated) {
-			RenderPathPaint.render_path_paint_dilate(config_raw.dilate == dilate_type_t.DELAYED, true);
-			RenderPathPaint.render_path_paint_dilated = true;
-		}
-		///end
-
-		if (!RenderPathPaint.render_path_paint_paint_enabled()) return;
-
-		///if is_paint
-		RenderPathPaint.render_path_paint_push_undo_last = history_push_undo;
-		///end
-
-		if (history_push_undo && history_undo_layers != null) {
-			history_paint();
-
-			///if is_sculpt
-			render_path_set_target("gbuffer0_undo");
-			render_path_bind_target("gbuffer0", "tex");
-			render_path_draw_shader("shader_datas/copy_pass/copy_pass");
-
-			render_path_set_target("gbufferD_undo");
-			render_path_bind_target("_main", "tex");
-			render_path_draw_shader("shader_datas/copy_pass/copy_pass");
-			///end
-		}
-
-		///if is_sculpt
-		if (history_push_undo2 && history_undo_layers != null) {
-			history_paint();
-		}
-		///end
-
-		if (context_raw.paint2d) {
-			RenderPathPaint.render_path_paint_set_plane_mesh();
-		}
-
-		if (RenderPathPaint.render_path_paint_live_layer_drawn > 0) RenderPathPaint.render_path_paint_live_layer_drawn--;
-
-		if (config_raw.brush_live && context_raw.pdirty <= 0 && context_raw.ddirty <= 0 && context_raw.brush_time == 0) {
-			// Depth is unchanged, draw before gbuffer gets updated
-			RenderPathPaint.render_path_paint_commands_live_brush();
-		}
-	}
-
-	static render_path_paint_end = () => {
-		RenderPathPaint.render_path_paint_commands_cursor();
-		context_raw.ddirty--;
-		context_raw.rdirty--;
-
-		if (!RenderPathPaint.render_path_paint_paint_enabled()) return;
-		context_raw.pdirty--;
-	}
-
-	static render_path_paint_draw = () => {
-		if (!RenderPathPaint.render_path_paint_paint_enabled()) return;
-
-		///if (!krom_ios) // No hover on iPad, decals are painted by pen release
-		if (config_raw.brush_live && context_raw.pdirty <= 0 && context_raw.ddirty > 0 && context_raw.brush_time == 0) {
-			// gbuffer has been updated now but brush will lag 1 frame
-			RenderPathPaint.render_path_paint_commands_live_brush();
-		}
-		///end
-
-		if (history_undo_layers != null) {
-			RenderPathPaint.render_path_paint_commands_symmetry();
-
-			if (context_raw.pdirty > 0) RenderPathPaint.render_path_paint_dilated = false;
-
-			///if is_paint
-			if (context_raw.tool == workspace_tool_t.BAKE) {
-
-				///if (krom_direct3d12 || krom_vulkan || krom_metal)
-				let is_raytraced_bake: bool = (context_raw.bake_type == bake_type_t.AO  ||
-					context_raw.bake_type == bake_type_t.LIGHTMAP ||
-					context_raw.bake_type == bake_type_t.BENT_NORMAL ||
-					context_raw.bake_type == bake_type_t.THICKNESS);
-				///end
-
-				if (context_raw.bake_type == bake_type_t.NORMAL || context_raw.bake_type == bake_type_t.HEIGHT || context_raw.bake_type == bake_type_t.DERIVATIVE) {
-					if (!RenderPathPaint.render_path_paint_baking && context_raw.pdirty > 0) {
-						RenderPathPaint.render_path_paint_baking = true;
-						let _bake_type: bake_type_t = context_raw.bake_type;
-						context_raw.bake_type = context_raw.bake_type == bake_type_t.NORMAL ? bake_type_t.NORMAL_OBJECT : bake_type_t.POSITION; // Bake high poly data
-						MakeMaterial.make_material_parse_paint_material();
-						let _paint_object: mesh_object_t = context_raw.paint_object;
-						let high_poly: mesh_object_t = project_paint_objects[context_raw.bake_high_poly];
-						let _visible: bool = high_poly.base.visible;
-						high_poly.base.visible = true;
-						context_select_paint_object(high_poly);
-						RenderPathPaint.render_path_paint_commands_paint();
-						high_poly.base.visible = _visible;
-						if (RenderPathPaint.render_path_paint_push_undo_last) history_paint();
-						context_select_paint_object(_paint_object);
-
-						let _render_final = () => {
-							context_raw.bake_type = _bake_type;
-							MakeMaterial.make_material_parse_paint_material();
-							context_raw.pdirty = 1;
-							RenderPathPaint.render_path_paint_commands_paint();
-							context_raw.pdirty = 0;
-							RenderPathPaint.render_path_paint_baking = false;
-						}
-						let _render_deriv = () => {
-							context_raw.bake_type = bake_type_t.HEIGHT;
-							MakeMaterial.make_material_parse_paint_material();
-							context_raw.pdirty = 1;
-							RenderPathPaint.render_path_paint_commands_paint();
-							context_raw.pdirty = 0;
-							if (RenderPathPaint.render_path_paint_push_undo_last) history_paint();
-							app_notify_on_init(_render_final);
-						}
-						let bake_type: bake_type_t = context_raw.bake_type as bake_type_t;
-						app_notify_on_init(bake_type == bake_type_t.DERIVATIVE ? _render_deriv : _render_final);
-					}
-				}
-				else if (context_raw.bake_type == bake_type_t.OBJECTID) {
-					let _layer_filter: i32 = context_raw.layer_filter;
-					let _paint_object: mesh_object_t = context_raw.paint_object;
-					let is_merged: bool = context_raw.merged_object != null;
-					let _visible: bool = is_merged && context_raw.merged_object.base.visible;
-					context_raw.layer_filter = 1;
-					if (is_merged) context_raw.merged_object.base.visible = false;
-
-					for (let p of project_paint_objects) {
-						context_select_paint_object(p);
-						RenderPathPaint.render_path_paint_commands_paint();
-					}
-
-					context_raw.layer_filter = _layer_filter;
-					context_select_paint_object(_paint_object);
-					if (is_merged) context_raw.merged_object.base.visible = _visible;
-				}
-				///if (krom_direct3d12 || krom_vulkan || krom_metal)
-				else if (is_raytraced_bake) {
-					let dirty: bool = render_path_raytrace_bake_commands(MakeMaterial.make_material_parse_paint_material);
-					if (dirty) ui_header_handle.redraws = 2;
-					if (config_raw.dilate == dilate_type_t.INSTANT) { // && raw.pdirty == 1
-						RenderPathPaint.render_path_paint_dilate(true, false);
-					}
-				}
-				///end
-				else {
-					RenderPathPaint.render_path_paint_commands_paint();
-				}
-			}
-			else { // Paint
-				RenderPathPaint.render_path_paint_commands_paint();
-			}
-			///end
-
-			///if is_sculpt
-			RenderPathPaint.render_path_paint_commands_paint();
-			///end
-		}
-
-		if (context_raw.brush_blend_dirty) {
-			context_raw.brush_blend_dirty = false;
-			///if krom_metal
-			render_path_set_target("texpaint_blend0");
-			render_path_clear_target(0x00000000);
-			render_path_set_target("texpaint_blend1");
-			render_path_clear_target(0x00000000);
-			///else
-			render_path_set_target("texpaint_blend0", ["texpaint_blend1"]);
-			render_path_clear_target(0x00000000);
-			///end
-		}
-
-		if (context_raw.paint2d) {
-			RenderPathPaint.render_path_paint_restore_plane_mesh();
-		}
-	}
-
-	static render_path_paint_set_plane_mesh = () => {
-		context_raw.paint2d_view = true;
-		RenderPathPaint.render_path_paint_painto = context_raw.paint_object;
-		RenderPathPaint.render_path_paint_visibles = [];
-		for (let p of project_paint_objects) {
-			RenderPathPaint.render_path_paint_visibles.push(p.base.visible);
-			p.base.visible = false;
-		}
-		if (context_raw.merged_object != null) {
-			RenderPathPaint.render_path_paint_merged_object_visible = context_raw.merged_object.base.visible;
-			context_raw.merged_object.base.visible = false;
-		}
-
-		let cam: camera_object_t = scene_camera;
-		mat4_set_from(context_raw.saved_camera, cam.base.transform.local);
-		RenderPathPaint.render_path_paint_saved_fov = cam.data.fov;
-		viewport_update_camera_type(camera_type_t.PERSPECTIVE);
-		let m: mat4_t = mat4_identity();
-		mat4_translate(m, 0, 0, 0.5);
-		transform_set_matrix(cam.base.transform, m);
-		cam.data.fov = base_default_fov;
-		camera_object_build_proj(cam);
-		camera_object_build_mat(cam);
-
-		let tw: f32 = 0.95 * ui_view2d_pan_scale;
-		let tx: f32 = ui_view2d_pan_x / ui_view2d_ww;
-		let ty: f32 = ui_view2d_pan_y / app_h();
-		mat4_set_identity(m);
-		mat4_scale(m, vec4_create(tw, tw, 1));
-		mat4_set_loc(m, vec4_create(tx, ty, 0));
-		let m2: mat4_t = mat4_identity();
-		mat4_get_inv(m2, scene_camera.vp);
-		mat4_mult_mat(m, m2);
-
-		let tiled: bool = ui_view2d_tiled_show;
-		if (tiled && scene_get_child(".PlaneTiled") == null) {
-			// 3x3 planes
-			let posa: i32[] = [32767,0,-32767,0,10922,0,-10922,0,10922,0,-32767,0,10922,0,-10922,0,-10922,0,10922,0,-10922,0,-10922,0,-10922,0,10922,0,-32767,0,32767,0,-32767,0,10922,0,10922,0,10922,0,-10922,0,32767,0,-10922,0,10922,0,32767,0,10922,0,10922,0,32767,0,10922,0,10922,0,-10922,0,-10922,0,-32767,0,10922,0,-32767,0,-10922,0,32767,0,-10922,0,10922,0,10922,0,10922,0,-10922,0,-10922,0,-32767,0,-32767,0,-10922,0,-32767,0,-32767,0,10922,0,-32767,0,-10922,0,-10922,0,-10922,0,-32767,0,32767,0,-32767,0,32767,0,-10922,0,10922,0,-10922,0,10922,0,-10922,0,10922,0,10922,0,-10922,0,10922,0,-10922,0,10922,0,-10922,0,32767,0,-32767,0,32767,0,10922,0,10922,0,10922,0,32767,0,-10922,0,32767,0,32767,0,10922,0,32767,0,32767,0,10922,0,32767,0,-10922,0,-10922,0,-10922,0,10922,0,-32767,0,10922,0,32767,0,-10922,0,32767,0,10922,0,10922,0,10922,0,-10922,0,-32767,0,-10922,0,-10922,0,-32767,0,-10922,0,10922,0,-32767,0,10922,0,-10922,0,-10922,0,-10922,0];
-			let nora: i32[] = [0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767];
-			let texa: i32[] = [32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0];
-			let inda: i32[] = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53];
-			let raw: mesh_data_t = {
-				name: ".PlaneTiled",
-				vertex_arrays: [
-					{ attrib: "pos", values: new Int16Array(posa), data: "short4norm" },
-					{ attrib: "nor", values: new Int16Array(nora), data: "short2norm" },
-					{ attrib: "tex", values: new Int16Array(texa), data: "short2norm" }
-				],
-				index_arrays: [
-					{ values: new Uint32Array(inda), material: 0 }
-				],
-				scale_pos: 1.5,
-				scale_tex: 1.0
-			};
-			let md: mesh_data_t = mesh_data_create(raw);
-			let materials: material_data_t[] = scene_get_child(".Plane").ext.materials;
-			let o: mesh_object_t = scene_add_mesh_object(md, materials);
-			o.base.name = ".PlaneTiled";
-		}
-
-		RenderPathPaint.render_path_paint_planeo = scene_get_child(tiled ? ".PlaneTiled" : ".Plane").ext;
-		RenderPathPaint.render_path_paint_planeo.base.visible = true;
-		context_raw.paint_object = RenderPathPaint.render_path_paint_planeo;
-
-		let v: vec4_t = vec4_create();
-		let sx: f32 = vec4_len(vec4_set(v, m.m[0], m.m[1], m.m[2]));
-		quat_from_euler(RenderPathPaint.render_path_paint_planeo.base.transform.rot, -math_pi() / 2, 0, 0);
-		vec4_set(RenderPathPaint.render_path_paint_planeo.base.transform.scale, sx, 1.0, sx);
-		RenderPathPaint.render_path_paint_planeo.base.transform.scale.z *= config_get_texture_res_y() / config_get_texture_res_x();
-		vec4_set(RenderPathPaint.render_path_paint_planeo.base.transform.loc, m.m[12], -m.m[13], 0.0);
-		transform_build_matrix(RenderPathPaint.render_path_paint_planeo.base.transform);
-	}
-
-	static render_path_paint_restore_plane_mesh = () => {
-		context_raw.paint2d_view = false;
-		RenderPathPaint.render_path_paint_planeo.base.visible = false;
-		vec4_set(RenderPathPaint.render_path_paint_planeo.base.transform.loc, 0.0, 0.0, 0.0);
-		for (let i: i32 = 0; i < project_paint_objects.length; ++i) {
-			project_paint_objects[i].base.visible = RenderPathPaint.render_path_paint_visibles[i];
-		}
-		if (context_raw.merged_object != null) {
-			context_raw.merged_object.base.visible = RenderPathPaint.render_path_paint_merged_object_visible;
-		}
-		context_raw.paint_object = RenderPathPaint.render_path_paint_painto;
-		transform_set_matrix(scene_camera.base.transform, context_raw.saved_camera);
-		scene_camera.data.fov = RenderPathPaint.render_path_paint_saved_fov;
-		viewport_update_camera_type(context_raw.camera_type);
-		camera_object_build_proj(scene_camera);
-		camera_object_build_mat(scene_camera);
-
-		render_path_base_draw_gbuffer();
-	}
-
-	static render_path_paint_bind_layers = () => {
-		///if is_paint
-		let is_live: bool = config_raw.brush_live && RenderPathPaint.render_path_paint_live_layer_drawn > 0;
-		let is_material_tool: bool = context_raw.tool == workspace_tool_t.MATERIAL;
-		if (is_live || is_material_tool) RenderPathPaint.render_path_paint_use_live_layer(true);
-		///end
-
-		for (let i: i32 = 0; i < project_layers.length; ++i) {
-			let l: SlotLayerRaw = project_layers[i];
-			render_path_bind_target("texpaint" + l.id, "texpaint" + l.id);
-
-			///if is_paint
-			if (SlotLayer.slot_layer_is_layer(l)) {
-				render_path_bind_target("texpaint_nor" + l.id, "texpaint_nor" + l.id);
-				render_path_bind_target("texpaint_pack" + l.id, "texpaint_pack" + l.id);
-			}
-			///end
-		}
-	}
-
-	static render_path_paint_unbind_layers = () => {
-		///if is_paint
-		let is_live: bool = config_raw.brush_live && RenderPathPaint.render_path_paint_live_layer_drawn > 0;
-		let is_material_tool: bool = context_raw.tool == workspace_tool_t.MATERIAL;
-		if (is_live || is_material_tool) RenderPathPaint.render_path_paint_use_live_layer(false);
-		///end
-	}
-
-	static render_path_paint_dilate = (base: bool, nor_pack: bool) => {
-		///if is_paint
-		if (config_raw.dilate_radius > 0 && !context_raw.paint2d) {
-			util_uv_cache_dilate_map();
-			base_make_temp_img();
-			let tid: i32 = context_raw.layer.id;
-			if (base) {
-				let texpaint: string = "texpaint";
-				render_path_set_target("temptex0");
-				render_path_bind_target(texpaint + tid, "tex");
-				render_path_draw_shader("shader_datas/copy_pass/copy_pass");
-				render_path_set_target(texpaint + tid);
-				render_path_bind_target("temptex0", "tex");
-				render_path_draw_shader("shader_datas/dilate_pass/dilate_pass");
-			}
-			if (nor_pack && !SlotLayer.slot_layer_is_mask(context_raw.layer)) {
-				render_path_set_target("temptex0");
-				render_path_bind_target("texpaint_nor" + tid, "tex");
-				render_path_draw_shader("shader_datas/copy_pass/copy_pass");
-				render_path_set_target("texpaint_nor" + tid);
-				render_path_bind_target("temptex0", "tex");
-				render_path_draw_shader("shader_datas/dilate_pass/dilate_pass");
-
-				render_path_set_target("temptex0");
-				render_path_bind_target("texpaint_pack" + tid, "tex");
-				render_path_draw_shader("shader_datas/copy_pass/copy_pass");
-				render_path_set_target("texpaint_pack" + tid);
-				render_path_bind_target("temptex0", "tex");
-				render_path_draw_shader("shader_datas/dilate_pass/dilate_pass");
-			}
-		}
-		///end
-	}
-}

+ 0 - 152
armorpaint/Sources/RenderPathPreview.ts

@@ -1,152 +0,0 @@
-
-class RenderPathPreview {
-
-	static render_path_preview_init = () => {
-
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpreview";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "texpreview_icon";
-			t.width = 1;
-			t.height = 1;
-			t.format = "RGBA32";
-			render_path_create_render_target(t);
-		}
-
-		render_path_create_depth_buffer("mmain", "DEPTH24");
-
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "mtex";
-			t.width = math_floor(util_render_material_preview_size * 2.0);
-			t.height = math_floor(util_render_material_preview_size * 2.0);
-			t.format = "RGBA64";
-			t.scale = render_path_base_get_super_sampling();
-			///if krom_opengl
-			t.depth_buffer = "mmain";
-			///end
-			render_path_create_render_target(t);
-		}
-
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "mgbuffer0";
-			t.width = math_floor(util_render_material_preview_size * 2.0);
-			t.height = math_floor(util_render_material_preview_size * 2.0);
-			t.format = "RGBA64";
-			t.scale = render_path_base_get_super_sampling();
-			t.depth_buffer = "mmain";
-			render_path_create_render_target(t);
-		}
-
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "mgbuffer1";
-			t.width = math_floor(util_render_material_preview_size * 2.0);
-			t.height = math_floor(util_render_material_preview_size * 2.0);
-			t.format = "RGBA64";
-			t.scale = render_path_base_get_super_sampling();
-			render_path_create_render_target(t);
-		}
-
-		{
-			let t: render_target_t = render_target_create();
-			t.name = "mgbuffer2";
-			t.width = math_floor(util_render_material_preview_size * 2.0);
-			t.height = math_floor(util_render_material_preview_size * 2.0);
-			t.format = "RGBA64";
-			t.scale = render_path_base_get_super_sampling();
-			render_path_create_render_target(t);
-		}
-	}
-
-	static render_path_preview_commands_preview = () => {
-		render_path_set_target("mgbuffer2");
-		render_path_clear_target(0xff000000);
-
-		render_path_set_target("mgbuffer0");
-		render_path_clear_target(0xffffffff, 1.0, clear_flag_t.COLOR | clear_flag_t.DEPTH);
-		render_path_set_target("mgbuffer0", ["mgbuffer1", "mgbuffer2"]);
-		render_path_draw_meshes("mesh");
-
-		// Deferred light
-		render_path_set_target("mtex");
-		render_path_bind_target("_mmain", "gbufferD");
-		render_path_bind_target("mgbuffer0", "gbuffer0");
-		render_path_bind_target("mgbuffer1", "gbuffer1");
-		{
-			render_path_bind_target("empty_white", "ssaotex");
-		}
-		render_path_draw_shader("shader_datas/deferred_light/deferred_light");
-
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		render_path_set_depth_from("mtex", "mgbuffer0"); // Bind depth for world pass
-		///end
-
-		render_path_set_target("mtex"); // Re-binds depth
-		render_path_draw_skydome("shader_datas/world_pass/world_pass");
-
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		render_path_set_depth_from("mtex", "mgbuffer1"); // Unbind depth
-		///end
-
-		let framebuffer: string = "texpreview";
-		let selected_mat: SlotMaterialRaw = context_raw.material;
-		render_path_render_targets.get("texpreview")._image = selected_mat.image;
-		render_path_render_targets.get("texpreview_icon")._image = selected_mat.image_icon;
-
-		render_path_set_target(framebuffer);
-		render_path_bind_target("mtex", "tex");
-		render_path_draw_shader("shader_datas/compositor_pass/compositor_pass");
-
-		render_path_set_target("texpreview_icon");
-		render_path_bind_target("texpreview", "tex");
-		render_path_draw_shader("shader_datas/supersample_resolve/supersample_resolve");
-	}
-
-	static render_path_preview_commands_decal = () => {
-		render_path_set_target("gbuffer2");
-		render_path_clear_target(0xff000000);
-
-		render_path_set_target("gbuffer0");
-		render_path_clear_target(0xffffffff, 1.0, clear_flag_t.COLOR | clear_flag_t.DEPTH);
-		render_path_set_target("gbuffer0", ["gbuffer1", "gbuffer2"]);
-		render_path_draw_meshes("mesh");
-
-		// Deferred light
-		render_path_set_target("tex");
-		render_path_bind_target("_main", "gbufferD");
-		render_path_bind_target("gbuffer0", "gbuffer0");
-		render_path_bind_target("gbuffer1", "gbuffer1");
-		{
-			render_path_bind_target("empty_white", "ssaotex");
-		}
-		render_path_draw_shader("shader_datas/deferred_light/deferred_light");
-
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		render_path_set_depth_from("tex", "gbuffer0"); // Bind depth for world pass
-		///end
-
-		render_path_set_target("tex");
-		render_path_draw_skydome("shader_datas/world_pass/world_pass");
-
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		render_path_set_depth_from("tex", "gbuffer1"); // Unbind depth
-		///end
-
-		let framebuffer: string = "texpreview";
-		render_path_render_targets.get("texpreview")._image = context_raw.decal_image;
-
-		render_path_set_target(framebuffer);
-
-		render_path_bind_target("tex", "tex");
-		render_path_draw_shader("shader_datas/compositor_pass/compositor_pass");
-	}
-}

+ 0 - 32
armorpaint/Sources/SlotBrush.ts

@@ -1,32 +0,0 @@
-
-class SlotBrushRaw {
-	nodes: zui_nodes_t = zui_nodes_create();
-	canvas: zui_node_canvas_t;
-	image: image_t = null; // 200px
-	image_icon: image_t = null; // 50px
-	preview_ready: bool = false;
-	id: i32 = 0;
-}
-
-class SlotBrush {
-	static slot_brush_default_canvas: ArrayBuffer = null;
-
-	static slot_brush_create(c: zui_node_canvas_t = null): SlotBrushRaw {
-		let raw: SlotBrushRaw = new SlotBrushRaw();
-		for (let brush of project_brushes) if (brush.id >= raw.id) raw.id = brush.id + 1;
-
-		if (c == null) {
-			if (SlotBrush.slot_brush_default_canvas == null) { // Synchronous
-				let b: ArrayBuffer = data_get_blob("default_brush.arm")
-				SlotBrush.slot_brush_default_canvas = b;
-			}
-			raw.canvas = armpack_decode(SlotBrush.slot_brush_default_canvas);
-			raw.canvas.name = "Brush " + (raw.id + 1);
-		}
-		else {
-			raw.canvas = c;
-		}
-
-		return raw;
-	}
-}

+ 0 - 21
armorpaint/Sources/SlotFont.ts

@@ -1,21 +0,0 @@
-
-class SlotFontRaw {
-	image: image_t = null; // 200px
-	preview_ready: bool = false;
-	id: i32 = 0;
-	font: g2_font_t;
-	name: string;
-	file: string;
-}
-
-class SlotFont {
-
-	static slot_font_create(name: string, font: g2_font_t, file = ""): SlotFontRaw {
-		let raw: SlotFontRaw = new SlotFontRaw();
-		for (let slot of project_fonts) if (slot.id >= raw.id) raw.id = slot.id + 1;
-		raw.name = name;
-		raw.font = font;
-		raw.file = file;
-		return raw;
-	}
-}

+ 0 - 692
armorpaint/Sources/SlotLayer.ts

@@ -1,692 +0,0 @@
-
-class SlotLayerRaw {
-	id: i32 = 0;
-	name: string;
-	ext: string = "";
-	visible: bool = true;
-	parent: SlotLayerRaw = null; // Group (for layers) or layer (for masks)
-
-	texpaint: image_t = null; // Base or mask
-	///if is_paint
-	texpaint_nor: image_t = null;
-	texpaint_pack: image_t = null;
-	texpaint_preview: image_t = null; // Layer preview
-	///end
-
-	mask_opacity: f32 = 1.0; // Opacity mask
-	fill_layer: SlotMaterialRaw = null;
-	show_panel: bool = true;
-	blending = blend_type_t.MIX;
-	object_mask: i32 = 0;
-	scale: f32 = 1.0;
-	angle: f32 = 0.0;
-	uv_type = uv_type_t.UVMAP;
-	paint_base: bool = true;
-	paint_opac: bool = true;
-	paint_occ: bool = true;
-	paint_rough: bool = true;
-	paint_met: bool = true;
-	paint_nor: bool = true;
-	paint_nor_blend: bool = true;
-	paint_height: bool = true;
-	paint_height_blend: bool = true;
-	paint_emis: bool = true;
-	paint_subs: bool = true;
-	decal_mat: mat4_t = mat4_identity(); // Decal layer
-}
-
-class SlotLayer {
-
-	static slot_layer_create(ext = "", type = layer_slot_type_t.LAYER, parent: SlotLayerRaw = null): SlotLayerRaw {
-		let raw: SlotLayerRaw = new SlotLayerRaw();
-		if (ext == "") {
-			raw.id = 0;
-			for (let l of project_layers) if (l.id >= raw.id) raw.id = l.id + 1;
-			ext = raw.id + "";
-		}
-		raw.ext = ext;
-		raw.parent = parent;
-
-		if (type == layer_slot_type_t.GROUP) {
-			raw.name = "Group " + (raw.id + 1);
-		}
-		else if (type == layer_slot_type_t.LAYER) {
-			raw.name = "Layer " + (raw.id + 1);
-			///if is_paint
-			let format: string = base_bits_handle.position == texture_bits_t.BITS8  ? "RGBA32" :
-						 		 base_bits_handle.position == texture_bits_t.BITS16 ? "RGBA64" :
-						 									  			  			  "RGBA128";
-			///end
-
-			///if is_sculpt
-			let format: string = "RGBA128";
-			///end
-
-			{
-				let t: render_target_t = render_target_create();
-				t.name = "texpaint" + ext;
-				t.width = config_get_texture_res_x();
-				t.height = config_get_texture_res_y();
-				t.format = format;
-				raw.texpaint = render_path_create_render_target(t)._image;
-			}
-
-			///if is_paint
-			{
-				let t: render_target_t = render_target_create();
-				t.name = "texpaint_nor" + ext;
-				t.width = config_get_texture_res_x();
-				t.height = config_get_texture_res_y();
-				t.format = format;
-				raw.texpaint_nor = render_path_create_render_target(t)._image;
-			}
-			{
-				let t: render_target_t = render_target_create();
-				t.name = "texpaint_pack" + ext;
-				t.width = config_get_texture_res_x();
-				t.height = config_get_texture_res_y();
-				t.format = format;
-				raw.texpaint_pack = render_path_create_render_target(t)._image;
-			}
-
-			raw.texpaint_preview = image_create_render_target(util_render_layer_preview_size, util_render_layer_preview_size, tex_format_t.RGBA32);
-			///end
-		}
-
-		///if is_paint
-		else { // Mask
-			raw.name = "Mask " + (raw.id + 1);
-			let format: string = "RGBA32"; // Full bits for undo support, R8 is used
-			raw.blending = blend_type_t.ADD;
-
-			{
-				let t: render_target_t = render_target_create();
-				t.name = "texpaint" + ext;
-				t.width = config_get_texture_res_x();
-				t.height = config_get_texture_res_y();
-				t.format = format;
-				raw.texpaint = render_path_create_render_target(t)._image;
-			}
-
-			raw.texpaint_preview = image_create_render_target(util_render_layer_preview_size, util_render_layer_preview_size, tex_format_t.RGBA32);
-		}
-		///end
-
-		return raw;
-	}
-
-	static slot_layer_delete = (raw: SlotLayerRaw) => {
-		SlotLayer.slot_layer_unload(raw);
-
-		if (SlotLayer.slot_layer_is_layer(raw)) {
-			let masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(raw, false); // Prevents deleting group masks
-			if (masks != null) for (let m of masks) SlotLayer.slot_layer_delete(m);
-		}
-		else if (SlotLayer.slot_layer_is_group(raw)) {
-			let children: SlotLayerRaw[] = SlotLayer.slot_layer_get_children(raw);
-			if (children != null) for (let c of children) SlotLayer.slot_layer_delete(c);
-			let masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(raw);
-			if (masks != null) for (let m of masks) SlotLayer.slot_layer_delete(m);
-		}
-
-		let lpos: i32 = project_layers.indexOf(raw);
-		array_remove(project_layers, raw);
-		// Undo can remove base layer and then restore it from undo layers
-		if (project_layers.length > 0) {
-			context_set_layer(project_layers[lpos > 0 ? lpos - 1 : 0]);
-		}
-
-		// Do not remove empty groups if the last layer is deleted as this prevents redo from working properly
-	}
-
-	static slot_layer_unload = (raw: SlotLayerRaw) => {
-		if (SlotLayer.slot_layer_is_group(raw)) return;
-
-		let _texpaint: image_t = raw.texpaint;
-		///if is_paint
-		let _texpaint_nor: image_t = raw.texpaint_nor;
-		let _texpaint_pack: image_t = raw.texpaint_pack;
-		let _texpaint_preview: image_t = raw.texpaint_preview;
-		///end
-
-		let _next = () => {
-			image_unload(_texpaint);
-			///if is_paint
-			if (_texpaint_nor != null) image_unload(_texpaint_nor);
-			if (_texpaint_pack != null) image_unload(_texpaint_pack);
-			image_unload(_texpaint_preview);
-			///end
-		}
-		base_notify_on_next_frame(_next);
-
-		render_path_render_targets.delete("texpaint" + raw.ext);
-		///if is_paint
-		if (SlotLayer.slot_layer_is_layer(raw)) {
-			render_path_render_targets.delete("texpaint_nor" + raw.ext);
-			render_path_render_targets.delete("texpaint_pack" + raw.ext);
-		}
-		///end
-	}
-
-	static slot_layer_swap = (raw: SlotLayerRaw, other: SlotLayerRaw) => {
-		if ((SlotLayer.slot_layer_is_layer(raw) || SlotLayer.slot_layer_is_mask(raw)) && (SlotLayer.slot_layer_is_layer(other) || SlotLayer.slot_layer_is_mask(other))) {
-			render_path_render_targets.get("texpaint" + raw.ext)._image = other.texpaint;
-			render_path_render_targets.get("texpaint" + other.ext)._image = raw.texpaint;
-			let _texpaint: image_t = raw.texpaint;
-			raw.texpaint = other.texpaint;
-			other.texpaint = _texpaint;
-
-			///if is_paint
-			let _texpaint_preview: image_t = raw.texpaint_preview;
-			raw.texpaint_preview = other.texpaint_preview;
-			other.texpaint_preview = _texpaint_preview;
-			///end
-		}
-
-		///if is_paint
-		if (SlotLayer.slot_layer_is_layer(raw) && SlotLayer.slot_layer_is_layer(other)) {
-			render_path_render_targets.get("texpaint_nor" + raw.ext)._image = other.texpaint_nor;
-			render_path_render_targets.get("texpaint_pack" + raw.ext)._image = other.texpaint_pack;
-			render_path_render_targets.get("texpaint_nor" + other.ext)._image = raw.texpaint_nor;
-			render_path_render_targets.get("texpaint_pack" + other.ext)._image = raw.texpaint_pack;
-			let _texpaint_nor: image_t = raw.texpaint_nor;
-			let _texpaint_pack: image_t = raw.texpaint_pack;
-			raw.texpaint_nor = other.texpaint_nor;
-			raw.texpaint_pack = other.texpaint_pack;
-			other.texpaint_nor = _texpaint_nor;
-			other.texpaint_pack = _texpaint_pack;
-		}
-		///end
-	}
-
-	static slot_layer_clear = (raw: SlotLayerRaw, baseColor = 0x00000000, baseImage: image_t = null, occlusion = 1.0, roughness = base_default_rough, metallic = 0.0) => {
-		g4_begin(raw.texpaint);
-		g4_clear(baseColor); // Base
-		g4_end();
-		if (baseImage != null) {
-			g2_begin(raw.texpaint);
-			g2_draw_scaled_image(baseImage, 0, 0, raw.texpaint.width, raw.texpaint.height);
-			g2_end();
-		}
-
-		///if is_paint
-		if (SlotLayer.slot_layer_is_layer(raw)) {
-			g4_begin(raw.texpaint_nor);
-			g4_clear(color_from_floats(0.5, 0.5, 1.0, 0.0)); // Nor
-			g4_end();
-			g4_begin(raw.texpaint_pack);
-			g4_clear(color_from_floats(occlusion, roughness, metallic, 0.0)); // Occ, rough, met
-			g4_end();
-		}
-		///end
-
-		context_raw.layer_preview_dirty = true;
-		context_raw.ddirty = 3;
-	}
-
-	static slot_layer_invert_mask = (raw: SlotLayerRaw) => {
-		if (base_pipe_invert8 == null) base_make_pipe();
-		let inverted: image_t = image_create_render_target(raw.texpaint.width, raw.texpaint.height, tex_format_t.RGBA32);
-		g2_begin(inverted);
-		g2_set_pipeline(base_pipe_invert8);
-		g2_draw_image(raw.texpaint, 0, 0);
-		g2_set_pipeline(null);
-		g2_end();
-		let _texpaint: image_t = raw.texpaint;
-		let _next = () => {
-			image_unload(_texpaint);
-		}
-		base_notify_on_next_frame(_next);
-		raw.texpaint = render_path_render_targets.get("texpaint" + raw.id)._image = inverted;
-		context_raw.layer_preview_dirty = true;
-		context_raw.ddirty = 3;
-	}
-
-	static slot_layer_apply_mask = (raw: SlotLayerRaw) => {
-		if (raw.parent.fill_layer != null) {
-			SlotLayer.slot_layer_to_paint_layer(raw.parent);
-		}
-		if (SlotLayer.slot_layer_is_group(raw.parent)) {
-			for (let c of SlotLayer.slot_layer_get_children(raw.parent)) {
-				base_apply_mask(c, raw);
-			}
-		}
-		else {
-			base_apply_mask(raw.parent, raw);
-		}
-		SlotLayer.slot_layer_delete(raw);
-	}
-
-	static slot_layer_duplicate = (raw: SlotLayerRaw): SlotLayerRaw => {
-		let layers: SlotLayerRaw[] = project_layers;
-		let i: i32 = layers.indexOf(raw) + 1;
-		let l: SlotLayerRaw = SlotLayer.slot_layer_create("", SlotLayer.slot_layer_is_layer(raw) ? layer_slot_type_t.LAYER : SlotLayer.slot_layer_is_mask(raw) ? layer_slot_type_t.MASK : layer_slot_type_t.GROUP, raw.parent);
-		layers.splice(i, 0, l);
-
-		if (base_pipe_merge == null) base_make_pipe();
-		if (SlotLayer.slot_layer_is_layer(raw)) {
-			g2_begin(l.texpaint);
-			g2_set_pipeline(base_pipe_copy);
-			g2_draw_image(raw.texpaint, 0, 0);
-			g2_set_pipeline(null);
-			g2_end();
-			///if is_paint
-			g2_begin(l.texpaint_nor);
-			g2_set_pipeline(base_pipe_copy);
-			g2_draw_image(raw.texpaint_nor, 0, 0);
-			g2_set_pipeline(null);
-			g2_end();
-			g2_begin(l.texpaint_pack);
-			g2_set_pipeline(base_pipe_copy);
-			g2_draw_image(raw.texpaint_pack, 0, 0);
-			g2_set_pipeline(null);
-			g2_end();
-			///end
-		}
-		else if (SlotLayer.slot_layer_is_mask(raw)) {
-			g2_begin(l.texpaint);
-			g2_set_pipeline(base_pipe_copy8);
-			g2_draw_image(raw.texpaint, 0, 0);
-			g2_set_pipeline(null);
-			g2_end();
-		}
-
-		///if is_paint
-		g2_begin(l.texpaint_preview);
-		g2_clear(0x00000000);
-		g2_set_pipeline(base_pipe_copy);
-		g2_draw_scaled_image(raw.texpaint_preview, 0, 0, raw.texpaint_preview.width, raw.texpaint_preview.height);
-		g2_set_pipeline(null);
-		g2_end();
-		///end
-
-		l.visible = raw.visible;
-		l.mask_opacity = raw.mask_opacity;
-		l.fill_layer = raw.fill_layer;
-		l.object_mask = raw.object_mask;
-		l.blending = raw.blending;
-		l.uv_type = raw.uv_type;
-		l.scale = raw.scale;
-		l.angle = raw.angle;
-		l.paint_base = raw.paint_base;
-		l.paint_opac = raw.paint_opac;
-		l.paint_occ = raw.paint_occ;
-		l.paint_rough = raw.paint_rough;
-		l.paint_met = raw.paint_met;
-		l.paint_nor = raw.paint_nor;
-		l.paint_nor_blend = raw.paint_nor_blend;
-		l.paint_height = raw.paint_height;
-		l.paint_height_blend = raw.paint_height_blend;
-		l.paint_emis = raw.paint_emis;
-		l.paint_subs = raw.paint_subs;
-
-		return l;
-	}
-
-	static slot_layer_resize_and_set_bits = (raw: SlotLayerRaw) => {
-		let res_x: i32 = config_get_texture_res_x();
-		let res_y: i32 = config_get_texture_res_y();
-		let rts: map_t<string, render_target_t> = render_path_render_targets;
-		if (base_pipe_merge == null) base_make_pipe();
-
-		if (SlotLayer.slot_layer_is_layer(raw)) {
-			///if is_paint
-			let format: tex_format_t = base_bits_handle.position == texture_bits_t.BITS8  ? tex_format_t.RGBA32 :
-				base_bits_handle.position == texture_bits_t.BITS16 ? tex_format_t.RGBA64 :
-				tex_format_t.RGBA128;
-			///end
-
-			///if is_sculpt
-			let format: tex_format_t = tex_format_t.RGBA128;
-			///end
-
-			let _texpaint: image_t = raw.texpaint;
-			raw.texpaint = image_create_render_target(res_x, res_y, format);
-			g2_begin(raw.texpaint);
-			g2_set_pipeline(base_pipe_copy);
-			g2_draw_scaled_image(_texpaint, 0, 0, res_x, res_y);
-			g2_set_pipeline(null);
-			g2_end();
-
-			///if is_paint
-			let _texpaint_nor: image_t = raw.texpaint_nor;
-			let _texpaint_pack: image_t = raw.texpaint_pack;
-			raw.texpaint_nor = image_create_render_target(res_x, res_y, format);
-			raw.texpaint_pack = image_create_render_target(res_x, res_y, format);
-
-			g2_begin(raw.texpaint_nor);
-			g2_set_pipeline(base_pipe_copy);
-			g2_draw_scaled_image(_texpaint_nor, 0, 0, res_x, res_y);
-			g2_set_pipeline(null);
-			g2_end();
-
-			g2_begin(raw.texpaint_pack);
-			g2_set_pipeline(base_pipe_copy);
-			g2_draw_scaled_image(_texpaint_pack, 0, 0, res_x, res_y);
-			g2_set_pipeline(null);
-			g2_end();
-			///end
-
-			let _next = () => {
-				image_unload(_texpaint);
-				///if is_paint
-				image_unload(_texpaint_nor);
-				image_unload(_texpaint_pack);
-				///end
-			}
-			base_notify_on_next_frame(_next);
-
-			rts.get("texpaint" + raw.ext)._image = raw.texpaint;
-			///if is_paint
-			rts.get("texpaint_nor" + raw.ext)._image = raw.texpaint_nor;
-			rts.get("texpaint_pack" + raw.ext)._image = raw.texpaint_pack;
-			///end
-		}
-		else if (SlotLayer.slot_layer_is_mask(raw)) {
-			let _texpaint: image_t = raw.texpaint;
-			raw.texpaint = image_create_render_target(res_x, res_y, tex_format_t.RGBA32);
-
-			g2_begin(raw.texpaint);
-			g2_set_pipeline(base_pipe_copy8);
-			g2_draw_scaled_image(_texpaint, 0, 0, res_x, res_y);
-			g2_set_pipeline(null);
-			g2_end();
-
-			let _next = () => {
-				image_unload(_texpaint);
-			}
-			base_notify_on_next_frame(_next);
-
-			rts.get("texpaint" + raw.ext)._image = raw.texpaint;
-		}
-	}
-
-	static slot_layer_to_fill_layer = (raw: SlotLayerRaw) => {
-		context_set_layer(raw);
-		raw.fill_layer = context_raw.material;
-		base_update_fill_layer();
-		let _next = () => {
-			MakeMaterial.make_material_parse_paint_material();
-			context_raw.layer_preview_dirty = true;
-			ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
-		}
-		base_notify_on_next_frame(_next);
-	}
-
-	static slot_layer_to_paint_layer = (raw: SlotLayerRaw) => {
-		context_set_layer(raw);
-		raw.fill_layer = null;
-		MakeMaterial.make_material_parse_paint_material();
-		context_raw.layer_preview_dirty = true;
-		ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
-	}
-
-	static slot_layer_is_visible = (raw: SlotLayerRaw): bool => {
-		return raw.visible && (raw.parent == null || raw.parent.visible);
-	}
-
-	static slot_layer_get_children = (raw: SlotLayerRaw): SlotLayerRaw[] => {
-		let children: SlotLayerRaw[] = null; // Child layers of a group
-		for (let l of project_layers) {
-			if (l.parent == raw && SlotLayer.slot_layer_is_layer(l)) {
-				if (children == null) children = [];
-				children.push(l);
-			}
-		}
-		return children;
-	}
-
-	static slot_layer_get_recursive_children = (raw: SlotLayerRaw): SlotLayerRaw[] => {
-		let children: SlotLayerRaw[] = null;
-		for (let l of project_layers) {
-			if (l.parent == raw) { // Child layers and group masks
-				if (children == null) children = [];
-				children.push(l);
-			}
-			if (l.parent != null && l.parent.parent == raw) { // Layer masks
-				if (children == null) children = [];
-				children.push(l);
-			}
-		}
-		return children;
-	}
-
-	static slot_layer_get_masks = (raw: SlotLayerRaw, includeGroupMasks = true): SlotLayerRaw[] => {
-		if (SlotLayer.slot_layer_is_mask(raw)) return null;
-
-		let children: SlotLayerRaw[] = null;
-		// Child masks of a layer
-		for (let l of project_layers) {
-			if (l.parent == raw && SlotLayer.slot_layer_is_mask(l)) {
-				if (children == null) children = [];
-				children.push(l);
-			}
-		}
-		// Child masks of a parent group
-		if (includeGroupMasks) {
-			if (raw.parent != null && SlotLayer.slot_layer_is_group(raw.parent)) {
-				for (let l of project_layers) {
-					if (l.parent == raw.parent && SlotLayer.slot_layer_is_mask(l)) {
-						if (children == null) children = [];
-						children.push(l);
-					}
-				}
-			}
-		}
-		return children;
-	}
-
-	static slot_layer_has_masks = (raw: SlotLayerRaw, includeGroupMasks = true): bool => {
-		// Layer mask
-		for (let l of project_layers) {
-			if (l.parent == raw && SlotLayer.slot_layer_is_mask(l)) {
-				return true;
-			}
-		}
-		// Group mask
-		if (includeGroupMasks && raw.parent != null && SlotLayer.slot_layer_is_group(raw.parent)) {
-			for (let l of project_layers) {
-				if (l.parent == raw.parent && SlotLayer.slot_layer_is_mask(l)) {
-					return true;
-				}
-			}
-		}
-		return false;
-	}
-
-	static slot_layer_get_opacity = (raw: SlotLayerRaw): f32 => {
-		let f: f32 = raw.mask_opacity;
-		if (SlotLayer.slot_layer_is_layer(raw) && raw.parent != null) f *= raw.parent.mask_opacity;
-		return f;
-	}
-
-	static slot_layer_get_object_mask = (raw: SlotLayerRaw): i32 => {
-		return SlotLayer.slot_layer_is_mask(raw) ? raw.parent.object_mask : raw.object_mask;
-	}
-
-	static slot_layer_is_layer = (raw: SlotLayerRaw): bool => {
-		///if is_paint
-		return raw.texpaint != null && raw.texpaint_nor != null;
-		///end
-		///if is_sculpt
-		return raw.texpaint != null;
-		///end
-	}
-
-	static slot_layer_is_group = (raw: SlotLayerRaw): bool => {
-		return raw.texpaint == null;
-	}
-
-	static slot_layer_get_containing_group = (raw: SlotLayerRaw): SlotLayerRaw => {
-		if (raw.parent != null && SlotLayer.slot_layer_is_group(raw.parent))
-			return raw.parent;
-		else if (raw.parent != null && raw.parent.parent != null && SlotLayer.slot_layer_is_group(raw.parent.parent))
-			return raw.parent.parent;
-		else return null;
-	}
-
-	static slot_layer_is_mask = (raw: SlotLayerRaw): bool => {
-		///if is_paint
-		return raw.texpaint != null && raw.texpaint_nor == null;
-		///end
-		///if is_sculpt
-		return false;
-		///end
-	}
-
-	static slot_layer_is_group_mask = (raw: SlotLayerRaw): bool => {
-		///if is_paint
-		return raw.texpaint != null && raw.texpaint_nor == null && SlotLayer.slot_layer_is_group(raw.parent);
-		///end
-		///if is_sculpt
-		return false;
-		///end
-	}
-
-	static slot_layer_is_layer_mask = (raw: SlotLayerRaw): bool => {
-		///if is_paint
-		return raw.texpaint != null && raw.texpaint_nor == null && SlotLayer.slot_layer_is_layer(raw.parent);
-		///end
-		///if is_sculpt
-		return false;
-		///end
-	}
-
-	static slot_layer_is_in_group = (raw: SlotLayerRaw): bool => {
-		return raw.parent != null && (SlotLayer.slot_layer_is_group(raw.parent) || (raw.parent.parent != null && SlotLayer.slot_layer_is_group(raw.parent.parent)));
-	}
-
-	static slot_layer_can_move = (raw: SlotLayerRaw, to: i32): bool => {
-		let old_index: i32 = project_layers.indexOf(raw);
-
-		let delta: i32 = to - old_index; // If delta > 0 the layer is moved up, otherwise down
-		if (to < 0 || to > project_layers.length - 1 || delta == 0) return false;
-
-		// If the layer is moved up, all layers between the old position and the new one move one down.
-		// The layers above the new position stay where they are.
-		// If the new position is on top or on bottom no upper resp. lower layer exists.
-		let new_upper_layer: SlotLayerRaw = delta > 0 ? (to < project_layers.length - 1 ? project_layers[to + 1] : null) : project_layers[to];
-
-		// Group or layer is collapsed so we check below and update the upper layer.
-		if (new_upper_layer != null && !new_upper_layer.show_panel) {
-			let children: SlotLayerRaw[] = SlotLayer.slot_layer_get_recursive_children(new_upper_layer);
-			to -= children != null ? children.length : 0;
-			delta = to - old_index;
-			new_upper_layer = delta > 0 ? (to < project_layers.length - 1 ? project_layers[to + 1] : null) : project_layers[to];
-		}
-
-		let new_lower_layer: SlotLayerRaw = delta > 0 ? project_layers[to] : (to > 0 ? project_layers[to - 1] : null);
-
-		if (SlotLayer.slot_layer_is_mask(raw)) {
-			// Masks can not be on top.
-			if (new_upper_layer == null) return false;
-			// Masks should not be placed below a collapsed group. This condition can be savely removed.
-			if (SlotLayer.slot_layer_is_in_group(new_upper_layer) && !SlotLayer.slot_layer_get_containing_group(new_upper_layer).show_panel) return false;
-			// Masks should not be placed below a collapsed layer. This condition can be savely removed.
-			if (SlotLayer.slot_layer_is_mask(new_upper_layer) && !new_upper_layer.parent.show_panel) return false;
-		}
-
-		if (SlotLayer.slot_layer_is_layer(raw)) {
-			// Layers can not be moved directly below its own mask(s).
-			if (new_upper_layer != null && SlotLayer.slot_layer_is_mask(new_upper_layer) && new_upper_layer.parent == raw) return false;
-			// Layers can not be placed above a mask as the mask would be reparented.
-			if (new_lower_layer != null && SlotLayer.slot_layer_is_mask(new_lower_layer)) return false;
-		}
-
-		// Currently groups can not be nested. Thus valid positions for groups are:
-		if (SlotLayer.slot_layer_is_group(raw)) {
-			// At the top.
-			if (new_upper_layer == null) return true;
-			// NOT below its own children.
-			if (SlotLayer.slot_layer_get_containing_group(new_upper_layer) == raw) return false;
-			// At the bottom.
-			if (new_lower_layer == null) return true;
-			// Above a group.
-			if (SlotLayer.slot_layer_is_group(new_lower_layer)) return true;
-			// Above a non-grouped layer.
-			if (SlotLayer.slot_layer_is_layer(new_lower_layer) && !SlotLayer.slot_layer_is_in_group(new_lower_layer)) return true;
-			else return false;
-		}
-
-		return true;
-	}
-
-	static slot_layer_move = (raw: SlotLayerRaw, to: i32) => {
-		if (!SlotLayer.slot_layer_can_move(raw, to)) {
-			return;
-		}
-
-		let pointers: map_t<SlotLayerRaw, i32> = TabLayers.tab_layers_init_layer_map();
-		let old_index: i32 = project_layers.indexOf(raw);
-		let delta: i32 = to - old_index;
-		let new_upper_layer: SlotLayerRaw = delta > 0 ? (to < project_layers.length - 1 ? project_layers[to + 1] : null) : project_layers[to];
-
-		// Group or layer is collapsed so we check below and update the upper layer.
-		if (new_upper_layer != null && !new_upper_layer.show_panel) {
-			let children: SlotLayerRaw[] = SlotLayer.slot_layer_get_recursive_children(new_upper_layer);
-			to -= children != null ? children.length : 0;
-			delta = to - old_index;
-			new_upper_layer = delta > 0 ? (to < project_layers.length - 1 ? project_layers[to + 1] : null) : project_layers[to];
-		}
-
-		context_set_layer(raw);
-		history_order_layers(to);
-		ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
-
-		array_remove(project_layers, raw);
-		project_layers.splice(to, 0, raw);
-
-		if (SlotLayer.slot_layer_is_layer(raw)) {
-			let old_parent: SlotLayerRaw = raw.parent;
-
-			if (new_upper_layer == null)
-				raw.parent = null; // Placed on top.
-			else if (SlotLayer.slot_layer_is_in_group(new_upper_layer) && !SlotLayer.slot_layer_get_containing_group(new_upper_layer).show_panel)
-				raw.parent = null; // Placed below a collapsed group.
-			else if (SlotLayer.slot_layer_is_layer(new_upper_layer))
-				raw.parent = new_upper_layer.parent; // Placed below a layer, use the same parent.
-			else if (SlotLayer.slot_layer_is_group(new_upper_layer))
-				raw.parent = new_upper_layer; // Placed as top layer in a group.
-			else if (SlotLayer.slot_layer_is_group_mask(new_upper_layer))
-				raw.parent = new_upper_layer.parent; // Placed in a group below the lowest group mask.
-			else if (SlotLayer.slot_layer_is_layer_mask(new_upper_layer))
-				raw.parent = SlotLayer.slot_layer_get_containing_group(new_upper_layer); // Either the group the mask belongs to or null.
-
-			// Layers can have masks as children. These have to be moved, too.
-			let layer_masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(raw, false);
-			if (layer_masks != null) {
-				for (let idx: i32 = 0; idx < layer_masks.length; ++idx) {
-					let mask: SlotLayerRaw = layer_masks[idx];
-					array_remove(project_layers, mask);
-					// If the masks are moved down each step increases the index below the layer by one.
-					project_layers.splice(delta > 0 ? old_index + delta - 1 : old_index + delta + idx, 0, mask);
-				}
-			}
-
-			// The layer is the last layer in the group, remove it. Notice that this might remove group masks.
-			if (old_parent != null && SlotLayer.slot_layer_get_children(old_parent) == null)
-				SlotLayer.slot_layer_delete(old_parent);
-		}
-		else if (SlotLayer.slot_layer_is_mask(raw)) {
-			// Precondition newUpperLayer != null, ensured in canMove.
-			if (SlotLayer.slot_layer_is_layer(new_upper_layer) || SlotLayer.slot_layer_is_group(new_upper_layer))
-				raw.parent = new_upper_layer;
-			else if (SlotLayer.slot_layer_is_mask(new_upper_layer)) { // Group mask or layer mask.
-				raw.parent = new_upper_layer.parent;
-			}
-		}
-		else if (SlotLayer.slot_layer_is_group(raw)) {
-			let children: SlotLayerRaw[] = SlotLayer.slot_layer_get_recursive_children(raw);
-			if (children != null) {
-				for (let idx: i32 = 0; idx < children.length; ++idx) {
-					let child: SlotLayerRaw = children[idx];
-					array_remove(project_layers, child);
-					// If the children are moved down each step increases the index below the layer by one.
-					project_layers.splice(delta > 0 ? old_index + delta - 1 : old_index + delta + idx, 0, child);
-				}
-			}
-		}
-
-		for (let m of project_materials) TabLayers.tab_layers_remap_layer_pointers(m.canvas.nodes, TabLayers.tab_layers_fill_layer_map(pointers));
-	}
-}

+ 0 - 70
armorpaint/Sources/SlotMaterial.ts

@@ -1,70 +0,0 @@
-
-class SlotMaterialRaw {
-	nodes: zui_nodes_t = zui_nodes_create();
-	canvas: zui_node_canvas_t;
-	image: image_t = null;
-	image_icon: image_t = null;
-	preview_ready: bool = false;
-	data: material_data_t;
-	id: i32 = 0;
-
-	paint_base: bool = true;
-	paint_opac: bool = true;
-	paint_occ: bool = true;
-	paint_rough: bool = true;
-	paint_met: bool = true;
-	paint_nor: bool = true;
-	paint_height: bool = true;
-	paint_emis: bool = true;
-	paint_subs: bool = true;
-}
-
-class SlotMaterial {
-	static slot_material_default_canvas: ArrayBuffer = null;
-
-	static slot_material_create(m: material_data_t = null, c: zui_node_canvas_t = null): SlotMaterialRaw {
-		let raw: SlotMaterialRaw = new SlotMaterialRaw();
-		for (let mat of project_materials) if (mat.id >= raw.id) raw.id = mat.id + 1;
-		raw.data = m;
-
-		let w: i32 = util_render_material_preview_size;
-		let w_icon: i32 = 50;
-		raw.image = image_create_render_target(w, w);
-		raw.image_icon = image_create_render_target(w_icon, w_icon);
-
-		if (c == null) {
-			if (SlotMaterial.slot_material_default_canvas == null) { // Synchronous
-				let b: ArrayBuffer = data_get_blob("default_material.arm");
-				SlotMaterial.slot_material_default_canvas = b;
-			}
-			raw.canvas = armpack_decode(SlotMaterial.slot_material_default_canvas);
-			raw.canvas.name = "Material " + (raw.id + 1);
-		}
-		else {
-			raw.canvas = c;
-		}
-
-		///if (krom_android || krom_ios)
-		raw.nodes.panX -= 50; // Center initial position
-		///end
-
-		return raw;
-	}
-
-	static slot_material_unload = (raw: SlotMaterialRaw) => {
-		let _next = () => {
-			image_unload(raw.image);
-			image_unload(raw.image_icon);
-		}
-		base_notify_on_next_frame(_next);
-	}
-
-	static slot_material_delete = (raw: SlotMaterialRaw) => {
-		SlotMaterial.slot_material_unload(raw);
-		let mpos: i32 = project_materials.indexOf(raw);
-		array_remove(project_materials, this);
-		if (project_materials.length > 0) {
-			context_set_material(project_materials[mpos > 0 ? mpos - 1 : 0]);
-		}
-	}
-}

+ 0 - 1078
armorpaint/Sources/TabLayers.ts

@@ -1,1078 +0,0 @@
-
-class TabLayers {
-
-	static tab_layers_layer_name_edit: i32 = -1;
-	static tab_layers_layer_name_handle: zui_handle_t = zui_handle_create();
-	static tab_layers_show_context_menu: bool = false;
-
-	static tab_layers_draw = (htab: zui_handle_t) => {
-		let mini: bool = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_base_sidebar_mini_w;
-		mini ? TabLayers.tab_layers_draw_mini(htab) : TabLayers.tab_layers_draw_full(htab);
-	}
-
-	static tab_layers_draw_mini = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		zui_set_hovered_tab_name(tr("Layers"));
-
-		let _ELEMENT_H: i32 = ui.t.ELEMENT_H;
-		ui.t.ELEMENT_H = math_floor(ui_base_sidebar_mini_w / 2 / zui_SCALE(ui));
-
-		zui_begin_sticky();
-		zui_separator(5);
-
-		TabLayers.tab_layers_combo_filter();
-		TabLayers.tab_layers_button_2d_view();
-		TabLayers.tab_layers_button_new("+");
-
-		zui_end_sticky();
-		ui._y += 2;
-
-		TabLayers.tab_layers_highlight_odd_lines();
-		TabLayers.tab_layers_draw_slots(true);
-
-		ui.t.ELEMENT_H = _ELEMENT_H;
-	}
-
-	static tab_layers_draw_full = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		if (zui_tab(htab, tr("Layers"))) {
-			zui_begin_sticky();
-			zui_row([1 / 4, 1 / 4, 1 / 2]);
-
-			TabLayers.tab_layers_button_new(tr("New"));
-			TabLayers.tab_layers_button_2d_view();
-			TabLayers.tab_layers_combo_filter();
-
-			zui_end_sticky();
-			ui._y += 2;
-
-			TabLayers.tab_layers_highlight_odd_lines();
-			TabLayers.tab_layers_draw_slots(false);
-		}
-	}
-
-	static tab_layers_button_2d_view = () => {
-		let ui: zui_t = ui_base_ui;
-		if (zui_button(tr("2D View"))) {
-			ui_base_show_2d_view(view_2d_type_t.LAYER);
-		}
-		else if (ui.is_hovered) zui_tooltip(tr("Show 2D View") + ` (${config_keymap.toggle_2d_view})`);
-	}
-
-	static tab_layers_draw_slots = (mini: bool) => {
-		for (let i: i32 = 0; i < project_layers.length; ++i) {
-			if (i >= project_layers.length) break; // Layer was deleted
-			let j: i32 = project_layers.length - 1 - i;
-			let l: SlotLayerRaw = project_layers[j];
-			TabLayers.tab_layers_draw_layer_slot(l, j, mini);
-		}
-	}
-
-	static tab_layers_highlight_odd_lines = () => {
-		let ui: zui_t = ui_base_ui;
-		let step: i32 = ui.t.ELEMENT_H * 2;
-		let full_h: i32 = ui._window_h - ui_base_hwnds[0].scroll_offset;
-		for (let i: i32 = 0; i < math_floor(full_h / step); ++i) {
-			if (i % 2 == 0) {
-				zui_fill(0, i * step, (ui._w / zui_SCALE(ui) - 2), step, ui.t.WINDOW_BG_COL - 0x00040404);
-			}
-		}
-	}
-
-	static tab_layers_button_new = (text: string) => {
-		if (zui_button(text)) {
-			ui_menu_draw((ui: zui_t) => {
-				let l: SlotLayerRaw = context_raw.layer;
-				if (ui_menu_button(ui, tr("Paint Layer"))) {
-					base_new_layer();
-					history_new_layer();
-				}
-				if (ui_menu_button(ui, tr("Fill Layer"))) {
-					base_create_fill_layer(uv_type_t.UVMAP);
-				}
-				if (ui_menu_button(ui, tr("Decal Layer"))) {
-					base_create_fill_layer(uv_type_t.PROJECT);
-				}
-				if (ui_menu_button(ui, tr("Black Mask"))) {
-					if (SlotLayer.slot_layer_is_mask(l)) context_set_layer(l.parent);
-					// let l: SlotLayerRaw = raw.layer;
-
-					let m: SlotLayerRaw = base_new_mask(false, l);
-					let _next = () => {
-						SlotLayer.slot_layer_clear(m, 0x00000000);
-					}
-					base_notify_on_next_frame(_next);
-					context_raw.layer_preview_dirty = true;
-					history_new_black_mask();
-					base_update_fill_layers();
-				}
-				if (ui_menu_button(ui, tr("White Mask"))) {
-					if (SlotLayer.slot_layer_is_mask(l)) context_set_layer(l.parent);
-					// let l: SlotLayerRaw = raw.layer;
-
-					let m: SlotLayerRaw = base_new_mask(false, l);
-					let _next = () => {
-						SlotLayer.slot_layer_clear(m, 0xffffffff);
-					}
-					base_notify_on_next_frame(_next);
-					context_raw.layer_preview_dirty = true;
-					history_new_white_mask();
-					base_update_fill_layers();
-				}
-				if (ui_menu_button(ui, tr("Fill Mask"))) {
-					if (SlotLayer.slot_layer_is_mask(l)) context_set_layer(l.parent);
-					// let l: SlotLayerRaw = raw.layer;
-
-					let m: SlotLayerRaw = base_new_mask(false, l);
-					let _init = () => {
-						SlotLayer.slot_layer_to_fill_layer(m);
-					}
-					app_notify_on_init(_init);
-					context_raw.layer_preview_dirty = true;
-					history_new_fill_mask();
-					base_update_fill_layers();
-				}
-				ui.enabled = !SlotLayer.slot_layer_is_group(context_raw.layer) && !SlotLayer.slot_layer_is_in_group(context_raw.layer);
-				if (ui_menu_button(ui, tr("Group"))) {
-					if (SlotLayer.slot_layer_is_group(l) || SlotLayer.slot_layer_is_in_group(l)) return;
-
-					if (SlotLayer.slot_layer_is_layer_mask(l)) l = l.parent;
-
-					let pointers: map_t<SlotLayerRaw, i32> = TabLayers.tab_layers_init_layer_map();
-					let group = base_new_group();
-					context_set_layer(l);
-					array_remove(project_layers, group);
-					project_layers.splice(project_layers.indexOf(l) + 1, 0, group);
-					l.parent = group;
-					for (let m of project_materials) TabLayers.tab_layers_remap_layer_pointers(m.canvas.nodes, TabLayers.tab_layers_fill_layer_map(pointers));
-					context_set_layer(group);
-					history_new_group();
-				}
-				ui.enabled = true;
-			}, 7);
-		}
-	}
-
-	static tab_layers_combo_filter = () => {
-		let ar: string[] = [tr("All")];
-		for (let p of project_paint_objects) ar.push(p.base.name);
-		let atlases: string[] = project_get_used_atlases();
-		if (atlases != null) for (let a of atlases) ar.push(a);
-		let filter_handle: zui_handle_t = zui_handle("tablayers_0");
-		filter_handle.position = context_raw.layer_filter;
-		context_raw.layer_filter = zui_combo(filter_handle, ar, tr("Filter"), false, zui_align_t.LEFT);
-		if (filter_handle.changed) {
-			for (let p of project_paint_objects) {
-				p.base.visible = context_raw.layer_filter == 0 || p.base.name == ar[context_raw.layer_filter] || project_is_atlas_object(p);
-			}
-			if (context_raw.layer_filter == 0 && context_raw.merged_object_is_atlas) { // All
-				util_mesh_merge();
-			}
-			else if (context_raw.layer_filter > project_paint_objects.length) { // Atlas
-				let visibles: mesh_object_t[] = [];
-				for (let p of project_paint_objects) if (p.base.visible) visibles.push(p);
-				util_mesh_merge(visibles);
-			}
-			base_set_object_mask();
-			util_uv_uvmap_cached = false;
-			context_raw.ddirty = 2;
-			///if (krom_direct3d12 || krom_vulkan || krom_metal)
-			render_path_raytrace_ready = false;
-			///end
-		}
-	}
-
-	static tab_layers_remap_layer_pointers = (nodes: zui_node_t[], pointer_map: map_t<i32, i32>) => {
-		for (let n of nodes) {
-			if (n.type == "LAYER" || n.type == "LAYER_MASK") {
-				let i: any = n.buttons[0].default_value;
-				if (pointer_map.has(i)) {
-					n.buttons[0].default_value = pointer_map.get(i);
-				}
-			}
-		}
-	}
-
-	static tab_layers_init_layer_map = (): map_t<SlotLayerRaw, i32> => {
-		let res: map_t<SlotLayerRaw, i32> = map_create();
-		for (let i: i32 = 0; i < project_layers.length; ++i) res.set(project_layers[i], i);
-		return res;
-	}
-
-	static tab_layers_fill_layer_map = (map: map_t<SlotLayerRaw, i32>): map_t<i32, i32> => {
-		let res: map_t<i32, i32> = map_create();
-		for (let l of map.keys()) res.set(map.get(l), project_layers.indexOf(l) > -1 ? project_layers.indexOf(l) : 9999);
-		return res;
-	}
-
-	static tab_layers_set_drag_layer = (layer: SlotLayerRaw, off_x: f32, off_y: f32) => {
-		base_drag_off_x = off_x;
-		base_drag_off_y = off_y;
-		base_drag_layer = layer;
-		context_raw.drag_dest = project_layers.indexOf(layer);
-	}
-
-	static tab_layers_draw_layer_slot = (l: SlotLayerRaw, i: i32, mini: bool) => {
-		let ui: zui_t = ui_base_ui;
-
-		if (context_raw.layer_filter > 0 &&
-			SlotLayer.slot_layer_get_object_mask(l) > 0 &&
-			SlotLayer.slot_layer_get_object_mask(l) != context_raw.layer_filter) {
-			return;
-		}
-
-		if (l.parent != null && !l.parent.show_panel) { // Group closed
-			return;
-		}
-		if (l.parent != null && l.parent.parent != null && !l.parent.parent.show_panel) {
-			return;
-		}
-
-		let step: i32 = ui.t.ELEMENT_H;
-		let checkw: f32 = (ui._window_w / 100 * 8) / zui_SCALE(ui);
-
-		// Highlight drag destination
-		let absy: f32 = ui._window_y + ui._y;
-		if (base_is_dragging && base_drag_layer != null && context_in_layers()) {
-			if (mouse_y > absy + step && mouse_y < absy + step * 3) {
-				let down: bool = project_layers.indexOf(base_drag_layer) >= i;
-				context_raw.drag_dest = down ? i : i - 1;
-
-				let ls: SlotLayerRaw[] = project_layers;
-				let dest: i32 = context_raw.drag_dest;
-				let to_group: bool = down ? dest > 0 && ls[dest - 1].parent != null && ls[dest - 1].parent.show_panel : dest < ls.length && ls[dest].parent != null && ls[dest].parent.show_panel;
-				let nested_group: bool = SlotLayer.slot_layer_is_group(base_drag_layer) && to_group;
-				if (!nested_group) {
-					if (SlotLayer.slot_layer_can_move(context_raw.layer, context_raw.drag_dest)) {
-						zui_fill(checkw, step * 2, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
-					}
-				}
-			}
-			else if (i == project_layers.length - 1 && mouse_y < absy + step) {
-				context_raw.drag_dest = project_layers.length - 1;
-				if (SlotLayer.slot_layer_can_move(context_raw.layer, context_raw.drag_dest)) {
-					zui_fill(checkw, 0, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
-				}
-			}
-		}
-		if (base_is_dragging && (base_drag_material != null || base_drag_swatch != null) && context_in_layers()) {
-			if (mouse_y > absy + step && mouse_y < absy + step * 3) {
-				context_raw.drag_dest = i;
-				if (TabLayers.tab_layers_can_drop_new_layer(i))
-					zui_fill(checkw, 2 * step, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
-			}
-			else if (i == project_layers.length - 1 && mouse_y < absy + step) {
-				context_raw.drag_dest = project_layers.length;
-				if (TabLayers.tab_layers_can_drop_new_layer(project_layers.length))
-					zui_fill(checkw, 0, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
-			}
-		}
-
-		mini ? TabLayers.tab_layers_draw_layer_slot_mini(l, i) : TabLayers.tab_layers_draw_layer_slot_full(l, i);
-
-		TabLayers.tab_layers_draw_layer_highlight(l, mini);
-
-		if (TabLayers.tab_layers_show_context_menu) {
-			TabLayers.tab_layers_draw_layer_context_menu(l, mini);
-		}
-	}
-
-	static tab_layers_draw_layer_slot_mini = (l: SlotLayerRaw, i: i32) => {
-		let ui = ui_base_ui;
-
-		zui_row([1, 1]);
-		let uix: f32 = ui._x;
-		let uiy: f32 = ui._y;
-		let state: zui_state_t = TabLayers.tab_layers_draw_layer_icon(l, i, uix, uiy, true);
-		TabLayers.tab_layers_handle_layer_icon_state(l, i, state, uix, uiy);
-		zui_end_element();
-
-		ui._y += zui_ELEMENT_H(ui);
-		ui._y -= zui_ELEMENT_OFFSET(ui);
-	}
-
-	static tab_layers_draw_layer_slot_full = (l: SlotLayerRaw, i: i32) => {
-		let ui: zui_t = ui_base_ui;
-
-		let step: i32 = ui.t.ELEMENT_H;
-
-		let has_panel: bool = SlotLayer.slot_layer_is_group(l) || (SlotLayer.slot_layer_is_layer(l) && SlotLayer.slot_layer_get_masks(l, false) != null);
-		if (has_panel) {
-			zui_row([8 / 100, 16 / 100, 36 / 100, 30 / 100, 10 / 100]);
-		}
-		else {
-			zui_row([8 / 100, 16 / 100, 36 / 100, 30 / 100]);
-		}
-
-		// Draw eye icon
-		let icons: image_t = resource_get("icons.k");
-		let r: rect_t = resource_tile18(icons, l.visible ? 0 : 1, 0);
-		let center: f32 = (step / 2) * zui_SCALE(ui);
-		ui._x += 2;
-		ui._y += 3;
-		ui._y += center;
-		let col: i32 = ui.t.ACCENT_SELECT_COL;
-		let parent_hidden: bool = l.parent != null && (!l.parent.visible || (l.parent.parent != null && !l.parent.parent.visible));
-		if (parent_hidden) col -= 0x99000000;
-
-		if (zui_image(icons, col, -1.0, r.x, r.y, r.w, r.h) == zui_state_t.RELEASED) {
-			TabLayers.tab_layers_layer_toggle_visible(l);
-		}
-		ui._x -= 2;
-		ui._y -= 3;
-		ui._y -= center;
-
-		///if krom_opengl
-		ui.image_invert_y = l.fill_layer != null;
-		///end
-
-		let uix: f32 = ui._x;
-		let uiy: f32 = ui._y;
-		ui._x += 2;
-		ui._y += 3;
-		if (l.parent != null) {
-			ui._x += 10 * zui_SCALE(ui);
-			if (l.parent.parent != null) ui._x += 10 * zui_SCALE(ui);
-		}
-
-		let state: zui_state_t = TabLayers.tab_layers_draw_layer_icon(l, i, uix, uiy, false);
-
-		ui._x -= 2;
-		ui._y -= 3;
-
-		if (config_raw.touch_ui) {
-			ui._x += 12 * zui_SCALE(ui);
-		}
-
-		///if krom_opengl
-		ui.image_invert_y = false;
-		///end
-
-		TabLayers.tab_layers_handle_layer_icon_state(l, i, state, uix, uiy);
-
-		// Draw layer name
-		ui._y += center;
-		if (TabLayers.tab_layers_layer_name_edit == l.id) {
-			TabLayers.tab_layers_layer_name_handle.text = l.name;
-			l.name = zui_text_input(TabLayers.tab_layers_layer_name_handle);
-			if (ui.text_selected_handle_ptr != TabLayers.tab_layers_layer_name_handle.ptr) TabLayers.tab_layers_layer_name_edit = -1;
-		}
-		else {
-			if (ui.enabled && ui.input_enabled && ui.combo_selected_handle_ptr == 0 &&
-				ui.input_x > ui._window_x + ui._x && ui.input_x < ui._window_x + ui._window_w &&
-				ui.input_y > ui._window_y + ui._y - center && ui.input_y < ui._window_y + ui._y - center + (step * zui_SCALE(ui)) * 2) {
-				if (ui.input_started) {
-					context_set_layer(l);
-					TabLayers.tab_layers_set_drag_layer(context_raw.layer, -(mouse_x - uix - ui._window_x - 3), -(mouse_y - uiy - ui._window_y + 1));
-				}
-				else if (ui.input_released_r) {
-					context_set_layer(l);
-					TabLayers.tab_layers_show_context_menu = true;
-				}
-			}
-
-			let state: zui_state_t = zui_text(l.name);
-			if (state == zui_state_t.RELEASED) {
-				if (time_time() - context_raw.select_time < 0.25) {
-					TabLayers.tab_layers_layer_name_edit = l.id;
-					TabLayers.tab_layers_layer_name_handle.text = l.name;
-					zui_start_text_edit(TabLayers.tab_layers_layer_name_handle);
-				}
-				context_raw.select_time = time_time();
-			}
-
-			let in_focus: bool = ui.input_x > ui._window_x && ui.input_x < ui._window_x + ui._window_w &&
-						  		 ui.input_y > ui._window_y && ui.input_y < ui._window_y + ui._window_h;
-			if (in_focus && ui.is_delete_down && TabLayers.tab_layers_can_delete(context_raw.layer)) {
-				ui.is_delete_down = false;
-				let _init = () => {
-					TabLayers.tab_layers_delete_layer(context_raw.layer);
-				}
-				app_notify_on_init(_init);
-			}
-		}
-		ui._y -= center;
-
-		if (l.parent != null) {
-			ui._x -= 10 * zui_SCALE(ui);
-			if (l.parent.parent != null) ui._x -= 10 * zui_SCALE(ui);
-		}
-
-		if (SlotLayer.slot_layer_is_group(l)) {
-			zui_end_element();
-		}
-		else {
-			if (SlotLayer.slot_layer_is_mask(l)) {
-				ui._y += center;
-			}
-
-			TabLayers.tab_layers_combo_blending(ui, l);
-
-			if (SlotLayer.slot_layer_is_mask(l)) {
-				ui._y -= center;
-			}
-		}
-
-		if (has_panel) {
-			ui._y += center;
-			let layer_panel: zui_handle_t = zui_nest(zui_handle("tablayers_1"), l.id);
-			layer_panel.selected = l.show_panel;
-			l.show_panel = zui_panel(layer_panel, "", true, false, false);
-			ui._y -= center;
-		}
-
-		if (SlotLayer.slot_layer_is_group(l) || SlotLayer.slot_layer_is_mask(l)) {
-			ui._y -= zui_ELEMENT_OFFSET(ui);
-			zui_end_element();
-		}
-		else {
-			ui._y -= zui_ELEMENT_OFFSET(ui);
-
-			zui_row([8 / 100, 16 / 100, 36 / 100, 30 / 100, 10 / 100]);
-			zui_end_element();
-			zui_end_element();
-			zui_end_element();
-
-			if (config_raw.touch_ui) {
-				ui._x += 12 * zui_SCALE(ui);
-			}
-
-			TabLayers.tab_layers_combo_object(ui, l);
-			zui_end_element();
-		}
-
-		ui._y -= zui_ELEMENT_OFFSET(ui);
-	}
-
-	static tab_layers_combo_object = (ui: zui_t, l: SlotLayerRaw, label = false): zui_handle_t => {
-		let ar: string[] = [tr("Shared")];
-		for (let p of project_paint_objects) ar.push(p.base.name);
-		let atlases: string[] = project_get_used_atlases();
-		if (atlases != null) for (let a of atlases) ar.push(a);
-		let object_handle: zui_handle_t = zui_nest(zui_handle("tablayers_2"), l.id);
-		object_handle.position = l.object_mask;
-		l.object_mask = zui_combo(object_handle, ar, tr("Object"), label, zui_align_t.LEFT);
-		if (object_handle.changed) {
-			context_set_layer(l);
-			MakeMaterial.make_material_parse_mesh_material();
-			if (l.fill_layer != null) { // Fill layer
-				let _init = () => {
-					context_raw.material = l.fill_layer;
-					SlotLayer.slot_layer_clear(l);
-					base_update_fill_layers();
-				}
-				app_notify_on_init(_init);
-			}
-			else {
-				base_set_object_mask();
-			}
-		}
-		return object_handle;
-	}
-
-	static tab_layers_combo_blending = (ui: zui_t, l: SlotLayerRaw, label = false): zui_handle_t => {
-		let blending_handle: zui_handle_t = zui_nest(zui_handle("tablayers_3"), l.id);
-		blending_handle.position = l.blending;
-		zui_combo(blending_handle, [
-			tr("Mix"),
-			tr("Darken"),
-			tr("Multiply"),
-			tr("Burn"),
-			tr("Lighten"),
-			tr("Screen"),
-			tr("Dodge"),
-			tr("Add"),
-			tr("Overlay"),
-			tr("Soft Light"),
-			tr("Linear Light"),
-			tr("Difference"),
-			tr("Subtract"),
-			tr("Divide"),
-			tr("Hue"),
-			tr("Saturation"),
-			tr("Color"),
-			tr("Value"),
-		], tr("Blending"), label);
-		if (blending_handle.changed) {
-			context_set_layer(l);
-			history_layer_blending();
-			l.blending = blending_handle.position;
-			MakeMaterial.make_material_parse_mesh_material();
-		}
-		return blending_handle;
-	}
-
-	static tab_layers_layer_toggle_visible = (l: SlotLayerRaw) => {
-		l.visible = !l.visible;
-		ui_view2d_hwnd.redraws = 2;
-		MakeMaterial.make_material_parse_mesh_material();
-	}
-
-	static tab_layers_draw_layer_highlight = (l: SlotLayerRaw, mini: bool) => {
-		let ui: zui_t = ui_base_ui;
-		let step: i32 = ui.t.ELEMENT_H;
-
-		// Separator line
-		zui_fill(0, 0, (ui._w / zui_SCALE(ui) - 2), 1 * zui_SCALE(ui), ui.t.SEPARATOR_COL);
-
-		// Highlight selected
-		if (context_raw.layer == l) {
-			if (mini) {
-				zui_rect(1, -step * 2, ui._w / zui_SCALE(ui) - 1, step * 2 + (mini ? -1 : 1), ui.t.HIGHLIGHT_COL, 3);
-			}
-			else {
-				zui_rect(1, -step * 2 - 1, ui._w / zui_SCALE(ui) - 2, step * 2 + (mini ? -2 : 1), ui.t.HIGHLIGHT_COL, 2);
-			}
-		}
-	}
-
-	static tab_layers_handle_layer_icon_state = (l: SlotLayerRaw, i: i32, state: zui_state_t, uix: f32, uiy: f32) => {
-		let ui: zui_t = ui_base_ui;
-
-		///if is_paint
-		let texpaint_preview: image_t = l.texpaint_preview;
-		///end
-		///if is_sculpt
-		let texpaint_preview: image_t = l.texpaint;
-		///end
-
-		TabLayers.tab_layers_show_context_menu = false;
-
-		// Layer preview tooltip
-		if (ui.is_hovered && texpaint_preview != null) {
-			if (SlotLayer.slot_layer_is_mask(l)) {
-				TabLayers.tab_layers_make_mask_preview_rgba32(l);
-				zui_tooltip_image(context_raw.mask_preview_rgba32);
-			}
-			else {
-				zui_tooltip_image(texpaint_preview);
-			}
-			if (i < 9) zui_tooltip(l.name + " - (" + config_keymap.select_layer + " " + (i + 1) + ")");
-			else zui_tooltip(l.name);
-		}
-
-		// Show context menu
-		if (ui.is_hovered && ui.input_released_r) {
-			context_set_layer(l);
-			TabLayers.tab_layers_show_context_menu = true;
-		}
-
-		if (state == zui_state_t.STARTED) {
-			context_set_layer(l);
-			TabLayers.tab_layers_set_drag_layer(context_raw.layer, -(mouse_x - uix - ui._window_x - 3), -(mouse_y - uiy - ui._window_y + 1));
-		}
-		else if (state == zui_state_t.RELEASED) {
-			if (time_time() - context_raw.select_time < 0.2) {
-				ui_base_show_2d_view(view_2d_type_t.LAYER);
-			}
-			if (time_time() - context_raw.select_time > 0.2) {
-				context_raw.select_time = time_time();
-			}
-			if (l.fill_layer != null) context_set_material(l.fill_layer);
-		}
-	}
-
-	static tab_layers_draw_layer_icon = (l: SlotLayerRaw, i: i32, uix: f32, uiy: f32, mini: bool) => {
-		let ui: zui_t = ui_base_ui;
-		let icons: image_t = resource_get("icons.k");
-		let icon_h: i32 = (zui_ELEMENT_H(ui) - (mini ? 2 : 3)) * 2;
-
-		if (mini && zui_SCALE(ui) > 1) {
-			ui._x -= 1 * zui_SCALE(ui);
-		}
-
-		if (l.parent != null) {
-			ui._x += (icon_h - icon_h * 0.9) / 2;
-			icon_h *= 0.9;
-			if (l.parent.parent != null) {
-				ui._x += (icon_h - icon_h * 0.9) / 2;
-				icon_h *= 0.9;
-			}
-		}
-
-		if (!SlotLayer.slot_layer_is_group(l)) {
-			///if is_paint
-			let texpaint_preview: image_t = l.texpaint_preview;
-			///end
-			///if is_sculpt
-			let texpaint_preview: image_t = l.texpaint;
-			///end
-
-			let icon: image_t = l.fill_layer == null ? texpaint_preview : l.fill_layer.image_icon;
-			if (l.fill_layer == null) {
-				// Checker
-				let r: rect_t = resource_tile50(icons, 4, 1);
-				let _x: f32 = ui._x;
-				let _y: f32 = ui._y;
-				let _w: f32 = ui._w;
-				zui_image(icons, 0xffffffff, icon_h, r.x, r.y, r.w, r.h);
-				ui.cur_ratio--;
-				ui._x = _x;
-				ui._y = _y;
-				ui._w = _w;
-			}
-			if (l.fill_layer == null && SlotLayer.slot_layer_is_mask(l)) {
-				g2_set_pipeline(ui_view2d_pipe);
-				///if krom_opengl
-				krom_g4_set_pipeline(ui_view2d_pipe.pipeline_);
-				///end
-				krom_g4_set_int(ui_view2d_channel_loc, 1);
-			}
-
-			let state: zui_state_t = zui_image(icon, 0xffffffff, icon_h);
-
-			if (l.fill_layer == null && SlotLayer.slot_layer_is_mask(l)) {
-				g2_set_pipeline(null);
-			}
-
-			// Draw layer numbers when selecting a layer via keyboard shortcut
-			let is_typing: bool = ui.is_typing || ui_view2d_ui.is_typing || ui_nodes_ui.is_typing;
-			if (!is_typing) {
-				if (i < 9 && operator_shortcut(config_keymap.select_layer, shortcut_type_t.DOWN)) {
-					let number: string = String(i + 1) ;
-					let width: i32 = g2_font_width(ui.font, ui.font_size, number) + 10;
-					let height: i32 = g2_font_height(ui.font, ui.font_size);
-					g2_set_color(ui.t.TEXT_COL);
-					g2_fill_rect(uix, uiy, width, height);
-					g2_set_color(ui.t.ACCENT_COL);
-					g2_draw_string(number, uix + 5, uiy);
-				}
-			}
-
-			return state;
-		}
-		else { // Group
-			let folder_closed: rect_t = resource_tile50(icons, 2, 1);
-			let folder_open: rect_t = resource_tile50(icons, 8, 1);
-			let folder: rect_t = l.show_panel ? folder_open : folder_closed;
-			return zui_image(icons, ui.t.LABEL_COL - 0x00202020, icon_h, folder.x, folder.y, folder.w, folder.h);
-		}
-	}
-
-	static tab_layers_can_merge_down = (l: SlotLayerRaw) : bool => {
-		let index: i32 = project_layers.indexOf(l);
-		// Lowest layer
-		if (index == 0) return false;
-		// Lowest layer that has masks
-		if (SlotLayer.slot_layer_is_layer(l) && SlotLayer.slot_layer_is_mask(project_layers[0]) && project_layers[0].parent == l) return false;
-		// The lowest toplevel layer is a group
-		if (SlotLayer.slot_layer_is_group(l) && SlotLayer.slot_layer_is_in_group(project_layers[0]) && SlotLayer.slot_layer_get_containing_group(project_layers[0]) == l) return false;
-		// Masks must be merged down to masks
-		if (SlotLayer.slot_layer_is_mask(l) && !SlotLayer.slot_layer_is_mask(project_layers[index - 1])) return false;
-		return true;
-	}
-
-	static tab_layers_draw_layer_context_menu = (l: SlotLayerRaw, mini: bool) => {
-		let add: i32 = 0;
-
-		if (l.fill_layer == null) add += 1; // Clear
-		if (l.fill_layer != null && !SlotLayer.slot_layer_is_mask(l)) add += 3;
-		if (l.fill_layer != null && SlotLayer.slot_layer_is_mask(l)) add += 2;
-		if (SlotLayer.slot_layer_is_mask(l)) add += 2;
-		if (mini) {
-			add += 1;
-			if (!SlotLayer.slot_layer_is_group(l)) add += 1;
-			if (SlotLayer.slot_layer_is_layer(l)) add += 1;
-		}
-		let menu_elements: i32 = SlotLayer.slot_layer_is_group(l) ? 7 : (19 + add);
-
-		ui_menu_draw((ui: zui_t) => {
-
-			if (mini) {
-				let visible_handle: zui_handle_t = zui_handle("tablayers_4");
-				visible_handle.selected = l.visible;
-				ui_menu_fill(ui);
-				zui_check(visible_handle, tr("Visible"));
-				if (visible_handle.changed) {
-					TabLayers.tab_layers_layer_toggle_visible(l);
-					ui_menu_keep_open = true;
-				}
-
-				if (!SlotLayer.slot_layer_is_group(l)) {
-					ui_menu_fill(ui);
-					if (TabLayers.tab_layers_combo_blending(ui, l, true).changed) {
-						ui_menu_keep_open = true;
-					}
-				}
-				if (SlotLayer.slot_layer_is_layer(l)) {
-					ui_menu_fill(ui);
-					if (TabLayers.tab_layers_combo_object(ui, l, true).changed) {
-						ui_menu_keep_open = true;
-					}
-				}
-			}
-
-			if (ui_menu_button(ui, tr("Export"))) {
-				if (SlotLayer.slot_layer_is_mask(l)) {
-					ui_files_show("png", true, false, (path: string) => {
-						let f: string = ui_files_filename;
-						if (f == "") f = tr("untitled");
-						if (!f.endsWith(".png")) f += ".png";
-						krom_write_png(path + path_sep + f, image_get_pixels(l.texpaint), l.texpaint.width, l.texpaint.height, 3); // RRR1
-					});
-				}
-				else {
-					///if is_paint
-					context_raw.layers_export = export_mode_t.SELECTED;
-					box_export_show_textures();
-					///end
-				}
-			}
-
-			if (!SlotLayer.slot_layer_is_group(l)) {
-				let to_fill_string: string = SlotLayer.slot_layer_is_layer(l) ? tr("To Fill Layer") : tr("To Fill Mask");
-				let to_paint_string: string = SlotLayer.slot_layer_is_layer(l) ? tr("To Paint Layer") : tr("To Paint Mask");
-
-				if (l.fill_layer == null && ui_menu_button(ui, to_fill_string)) {
-					let _init = () => {
-						SlotLayer.slot_layer_is_layer(l) ? history_to_fill_layer() : history_to_fill_mask();
-						SlotLayer.slot_layer_to_fill_layer(l);
-					}
-					app_notify_on_init(_init);
-				}
-				if (l.fill_layer != null && ui_menu_button(ui, to_paint_string)) {
-					let _init = () => {
-						SlotLayer.slot_layer_is_layer(l) ? history_to_paint_layer() : history_to_paint_mask();
-						SlotLayer.slot_layer_to_paint_layer(l);
-					}
-					app_notify_on_init(_init);
-				}
-			}
-
-			ui.enabled = TabLayers.tab_layers_can_delete(l);
-			if (ui_menu_button(ui, tr("Delete"), "delete")) {
-				let _init = () => {
-					TabLayers.tab_layers_delete_layer(context_raw.layer);
-				}
-				app_notify_on_init(_init);
-			}
-			ui.enabled = true;
-
-			if (l.fill_layer == null && ui_menu_button(ui, tr("Clear"))) {
-				context_set_layer(l);
-				let _init = () => {
-					if (!SlotLayer.slot_layer_is_group(l)) {
-						history_clear_layer();
-						SlotLayer.slot_layer_clear(l);
-					}
-					else {
-						for (let c of SlotLayer.slot_layer_get_children(l)) {
-							context_raw.layer = c;
-							history_clear_layer();
-							SlotLayer.slot_layer_clear(c);
-						}
-						context_raw.layers_preview_dirty = true;
-						context_raw.layer = l;
-					}
-				}
-				app_notify_on_init(_init);
-			}
-			if (SlotLayer.slot_layer_is_mask(l) && l.fill_layer == null && ui_menu_button(ui, tr("Invert"))) {
-				let _init = () => {
-					context_set_layer(l);
-					history_invert_mask();
-					SlotLayer.slot_layer_invert_mask(l);
-				}
-				app_notify_on_init(_init);
-			}
-			if (SlotLayer.slot_layer_is_mask(l) && ui_menu_button(ui, tr("Apply"))) {
-				let _init = () => {
-					context_raw.layer = l;
-					history_apply_mask();
-					SlotLayer.slot_layer_apply_mask(l);
-					context_set_layer(l.parent);
-					MakeMaterial.make_material_parse_mesh_material();
-					context_raw.layers_preview_dirty = true;
-				}
-				app_notify_on_init(_init);
-			}
-			if (SlotLayer.slot_layer_is_group(l) && ui_menu_button(ui, tr("Merge Group"))) {
-				let _init = () => {
-					base_merge_group(l);
-				}
-				app_notify_on_init(_init);
-			}
-			ui.enabled = TabLayers.tab_layers_can_merge_down(l);
-			if (ui_menu_button(ui, tr("Merge Down"))) {
-				let _init = () => {
-					context_set_layer(l);
-					history_merge_layers();
-					base_merge_down();
-					if (context_raw.layer.fill_layer != null) SlotLayer.slot_layer_to_paint_layer(context_raw.layer);
-				}
-				app_notify_on_init(_init);
-			}
-			ui.enabled = true;
-			if (ui_menu_button(ui, tr("Duplicate"))) {
-				let _init = () => {
-					context_set_layer(l);
-					history_duplicate_layer();
-					base_duplicate_layer(l);
-				}
-				app_notify_on_init(_init);
-			}
-
-			ui_menu_fill(ui);
-			ui_menu_align(ui);
-			let layer_opac_handle: zui_handle_t = zui_nest(zui_handle("tablayers_5"), l.id);
-			layer_opac_handle.value = l.mask_opacity;
-			zui_slider(layer_opac_handle, tr("Opacity"), 0.0, 1.0, true);
-			if (layer_opac_handle.changed) {
-				if (ui.input_started) history_layer_opacity();
-				l.mask_opacity = layer_opac_handle.value;
-				MakeMaterial.make_material_parse_mesh_material();
-				ui_menu_keep_open = true;
-			}
-
-			if (!SlotLayer.slot_layer_is_group(l)) {
-				ui_menu_fill(ui);
-				ui_menu_align(ui);
-				let res_handle_changed_last: bool = base_res_handle.changed;
-				///if (krom_android || krom_ios)
-				let ar: string[] = ["128", "256", "512", "1K", "2K", "4K"];
-				///else
-				let ar: string[] = ["128", "256", "512", "1K", "2K", "4K", "8K", "16K"];
-				///end
-				let _y: i32 = ui._y;
-				base_res_handle.value = base_res_handle.position;
-				base_res_handle.position = math_floor(zui_slider(base_res_handle, ar[base_res_handle.position], 0, ar.length - 1, false, 1, false, zui_align_t.LEFT, false));
-				if (base_res_handle.changed) {
-					ui_menu_keep_open = true;
-				}
-				if (res_handle_changed_last && !base_res_handle.changed) {
-					base_on_layers_resized();
-				}
-				ui._y = _y;
-				zui_draw_string(tr("Res"), null, 0, zui_align_t.RIGHT);
-				zui_end_element();
-
-				ui_menu_fill(ui);
-				ui_menu_align(ui);
-				///if (krom_android || krom_ios)
-				zui_inline_radio(base_bits_handle, ["8bit"]);
-				///else
-				zui_inline_radio(base_bits_handle, ["8bit", "16bit", "32bit"]);
-				///end
-				if (base_bits_handle.changed) {
-					app_notify_on_init(base_set_layer_bits);
-					ui_menu_keep_open = true;
-				}
-			}
-
-			if (l.fill_layer != null) {
-				ui_menu_fill(ui);
-				ui_menu_align(ui);
-				let scale_handle: zui_handle_t = zui_nest(zui_handle("tablayers_6"), l.id);
-				scale_handle.value = l.scale;
-				l.scale = zui_slider(scale_handle, tr("UV Scale"), 0.0, 5.0, true);
-				if (scale_handle.changed) {
-					context_set_material(l.fill_layer);
-					context_set_layer(l);
-					let _init = () => {
-						base_update_fill_layers();
-					}
-					app_notify_on_init(_init);
-					ui_menu_keep_open = true;
-				}
-
-				ui_menu_fill(ui);
-				ui_menu_align(ui);
-				let angle_handle: zui_handle_t = zui_nest(zui_handle("tablayers_7"), l.id);
-				angle_handle.value = l.angle;
-				l.angle = zui_slider(angle_handle, tr("Angle"), 0.0, 360, true, 1);
-				if (angle_handle.changed) {
-					context_set_material(l.fill_layer);
-					context_set_layer(l);
-					MakeMaterial.make_material_parse_paint_material();
-					let _init = () => {
-						base_update_fill_layers();
-					}
-					app_notify_on_init(_init);
-					ui_menu_keep_open = true;
-				}
-
-				ui_menu_fill(ui);
-				ui_menu_align(ui);
-				let uv_type_handle: zui_handle_t = zui_nest(zui_handle("tablayers_8"), l.id);
-				uv_type_handle.position = l.uv_type;
-				l.uv_type = zui_inline_radio(uv_type_handle, [tr("UV Map"), tr("Triplanar"), tr("Project")], zui_align_t.LEFT);
-				if (uv_type_handle.changed) {
-					context_set_material(l.fill_layer);
-					context_set_layer(l);
-					MakeMaterial.make_material_parse_paint_material();
-					let _init = () => {
-						base_update_fill_layers();
-					}
-					app_notify_on_init(_init);
-					ui_menu_keep_open = true;
-				}
-			}
-
-			if (!SlotLayer.slot_layer_is_group(l)) {
-				let base_handle: zui_handle_t = zui_nest(zui_handle("tablayers_9"), l.id);
-				let opac_handle: zui_handle_t = zui_nest(zui_handle("tablayers_10"), l.id);
-				let nor_handle: zui_handle_t = zui_nest(zui_handle("tablayers_11"), l.id);
-				let nor_blend_handle: zui_handle_t = zui_nest(zui_handle("tablayers_12"), l.id);
-				let occ_handle: zui_handle_t = zui_nest(zui_handle("tablayers_13"), l.id);
-				let rough_handle: zui_handle_t = zui_nest(zui_handle("tablayers_14"), l.id);
-				let met_handle: zui_handle_t = zui_nest(zui_handle("tablayers_15"), l.id);
-				let height_handle: zui_handle_t = zui_nest(zui_handle("tablayers_16"), l.id);
-				let height_blend_handle: zui_handle_t = zui_nest(zui_handle("tablayers_17"), l.id);
-				let emis_handle: zui_handle_t = zui_nest(zui_handle("tablayers_18"), l.id);
-				let subs_handle: zui_handle_t = zui_nest(zui_handle("tablayers_19"), l.id);
-				base_handle.selected = l.paint_base;
-				opac_handle.selected = l.paint_opac;
-				nor_handle.selected = l.paint_nor;
-				nor_blend_handle.selected = l.paint_nor_blend;
-				occ_handle.selected = l.paint_occ;
-				rough_handle.selected = l.paint_rough;
-				met_handle.selected = l.paint_met;
-				height_handle.selected = l.paint_height;
-				height_blend_handle.selected = l.paint_height_blend;
-				emis_handle.selected = l.paint_emis;
-				subs_handle.selected = l.paint_subs;
-				ui_menu_fill(ui);
-				l.paint_base = zui_check(base_handle, tr("Base Color"));
-				ui_menu_fill(ui);
-				l.paint_opac = zui_check(opac_handle, tr("Opacity"));
-				ui_menu_fill(ui);
-				l.paint_nor = zui_check(nor_handle, tr("Normal"));
-				ui_menu_fill(ui);
-				l.paint_nor_blend = zui_check(nor_blend_handle, tr("Normal Blending"));
-				ui_menu_fill(ui);
-				l.paint_occ = zui_check(occ_handle, tr("Occlusion"));
-				ui_menu_fill(ui);
-				l.paint_rough = zui_check(rough_handle, tr("Roughness"));
-				ui_menu_fill(ui);
-				l.paint_met = zui_check(met_handle, tr("Metallic"));
-				ui_menu_fill(ui);
-				l.paint_height = zui_check(height_handle, tr("Height"));
-				ui_menu_fill(ui);
-				l.paint_height_blend = zui_check(height_blend_handle, tr("Height Blending"));
-				ui_menu_fill(ui);
-				l.paint_emis = zui_check(emis_handle, tr("Emission"));
-				ui_menu_fill(ui);
-				l.paint_subs = zui_check(subs_handle, tr("Subsurface"));
-				if (base_handle.changed ||
-					opac_handle.changed ||
-					nor_handle.changed ||
-					nor_blend_handle.changed ||
-					occ_handle.changed ||
-					rough_handle.changed ||
-					met_handle.changed ||
-					height_handle.changed ||
-					height_blend_handle.changed ||
-					emis_handle.changed ||
-					subs_handle.changed) {
-					MakeMaterial.make_material_parse_mesh_material();
-					ui_menu_keep_open = true;
-				}
-			}
-		}, menu_elements);
-	}
-
-	static tab_layers_make_mask_preview_rgba32 = (l: SlotLayerRaw) => {
-		///if is_paint
-		if (context_raw.mask_preview_rgba32 == null) {
-			context_raw.mask_preview_rgba32 = image_create_render_target(util_render_layer_preview_size, util_render_layer_preview_size);
-		}
-		// Convert from R8 to RGBA32 for tooltip display
-		if (context_raw.mask_preview_last != l) {
-			context_raw.mask_preview_last = l;
-			app_notify_on_init(() => {
-				g2_begin(context_raw.mask_preview_rgba32);
-				g2_set_pipeline(ui_view2d_pipe);
-				g4_set_int(ui_view2d_channel_loc, 1);
-				g2_draw_image(l.texpaint_preview, 0, 0);
-				g2_end();
-				g2_set_pipeline(null);
-			});
-		}
-		///end
-	}
-
-	static tab_layers_delete_layer = (l: SlotLayerRaw) => {
-		let pointers: map_t<SlotLayerRaw, i32> = TabLayers.tab_layers_init_layer_map();
-
-		if (SlotLayer.slot_layer_is_layer(l) && SlotLayer.slot_layer_has_masks(l, false)) {
-			for (let m of SlotLayer.slot_layer_get_masks(l, false)) {
-				context_raw.layer = m;
-				history_delete_layer();
-				SlotLayer.slot_layer_delete(m);
-			}
-		}
-		if (SlotLayer.slot_layer_is_group(l)) {
-			for (let c of SlotLayer.slot_layer_get_children(l)) {
-				if (SlotLayer.slot_layer_has_masks(c, false)) {
-					for (let m of SlotLayer.slot_layer_get_masks(c, false)) {
-						context_raw.layer = m;
-						history_delete_layer();
-						SlotLayer.slot_layer_delete(m);
-					}
-				}
-				context_raw.layer = c;
-				history_delete_layer();
-				SlotLayer.slot_layer_delete(c);
-			}
-			if (SlotLayer.slot_layer_has_masks(l)) {
-				for (let m of SlotLayer.slot_layer_get_masks(l)) {
-					context_raw.layer = m;
-					history_delete_layer();
-					SlotLayer.slot_layer_delete(m);
-				}
-			}
-		}
-
-		context_raw.layer = l;
-		history_delete_layer();
-		SlotLayer.slot_layer_delete(l);
-
-		if (SlotLayer.slot_layer_is_mask(l)) {
-			context_raw.layer = l.parent;
-			base_update_fill_layers();
-		}
-
-		// Remove empty group
-		if (SlotLayer.slot_layer_is_in_group(l) && SlotLayer.slot_layer_get_children(SlotLayer.slot_layer_get_containing_group(l)) == null) {
-			let g: SlotLayerRaw = SlotLayer.slot_layer_get_containing_group(l);
-			// Maybe some group masks are left
-			if (SlotLayer.slot_layer_has_masks(g)) {
-				for (let m of SlotLayer.slot_layer_get_masks(g)) {
-					context_raw.layer = m;
-					history_delete_layer();
-					SlotLayer.slot_layer_delete(m);
-				}
-			}
-			context_raw.layer = l.parent;
-			history_delete_layer();
-			SlotLayer.slot_layer_delete(l.parent);
-		}
-		context_raw.ddirty = 2;
-		for (let m of project_materials) TabLayers.tab_layers_remap_layer_pointers(m.canvas.nodes, TabLayers.tab_layers_fill_layer_map(pointers));
-	}
-
-	static tab_layers_can_delete = (l: SlotLayerRaw) => {
-		let num_layers: i32 = 0;
-
-		if (SlotLayer.slot_layer_is_mask(l)) return true;
-
-		for (let slot of project_layers) {
-			if (SlotLayer.slot_layer_is_layer(slot)) ++num_layers;
-		}
-
-		// All layers are in one group
-		if (SlotLayer.slot_layer_is_group(l) && SlotLayer.slot_layer_get_children(l).length == num_layers) return false;
-
-		// Do not delete last layer
-		return num_layers > 1;
-	}
-
-	static tab_layers_can_drop_new_layer = (position: i32) => {
-		if (position > 0 && position < project_layers.length && SlotLayer.slot_layer_is_mask(project_layers[position - 1])) {
-			// 1. The layer to insert is inserted in the middle
-			// 2. The layer below is a mask, i.e. the layer would have to be a (group) mask, too.
-			return false;
-		}
-		return true;
-	}
-}

+ 128 - 0
armorpaint/Sources/import_folder.ts

@@ -0,0 +1,128 @@
+
+function import_folder_run(path: string) {
+	let files: string[] = file_read_directory(path);
+	let mapbase: string = "";
+	let mapopac: string = "";
+	let mapnor: string = "";
+	let mapocc: string = "";
+	let maprough: string = "";
+	let mapmet: string = "";
+	let mapheight: string = "";
+
+	let found_texture: bool = false;
+	// Import maps
+	for (let f of files) {
+		if (!path_is_texture(f)) continue;
+
+		// TODO: handle -albedo
+
+		let base: string = f.substr(0, f.lastIndexOf(".")).toLowerCase();
+		let valid: bool = false;
+		if (mapbase == "" && path_is_base_color_tex(base)) {
+			mapbase = f;
+			valid = true;
+		}
+		if (mapopac == "" && path_is_opacity_tex(base)) {
+			mapopac = f;
+			valid = true;
+		}
+		if (mapnor == "" && path_is_normal_map_tex(base)) {
+			mapnor = f;
+			valid = true;
+		}
+		if (mapocc == "" && path_is_occlusion_tex(base)) {
+			mapocc = f;
+			valid = true;
+		}
+		if (maprough == "" && path_is_roughness_tex(base)) {
+			maprough = f;
+			valid = true;
+		}
+		if (mapmet == "" && path_is_metallic_tex(base)) {
+			mapmet = f;
+			valid = true;
+		}
+		if (mapheight == "" && path_is_displacement_tex(base)) {
+			mapheight = f;
+			valid = true;
+		}
+
+		if (valid) {
+			import_texture_run(path + path_sep + f, false);
+			found_texture = true;
+		}
+	}
+
+	if (!found_texture) {
+		console_info(tr("Folder does not contain textures"));
+		return;
+	}
+
+	// Create material
+	context_raw.material = slot_material_create(project_materials[0].data);
+	project_materials.push(context_raw.material);
+	let nodes: zui_nodes_t = context_raw.material.nodes;
+	let canvas: zui_node_canvas_t = context_raw.material.canvas;
+	let dirs: string[] = path.split(path_sep);
+	canvas.name = dirs[dirs.length - 1];
+	let nout: zui_node_t = null;
+	for (let n of canvas.nodes) {
+		if (n.type == "OUTPUT_MATERIAL_PBR") {
+			nout = n;
+			break;
+		}
+	}
+	for (let n of canvas.nodes) {
+		if (n.name == "RGB") {
+			zui_remove_node(n, canvas);
+			break;
+		}
+	}
+
+	// Place nodes
+	let pos: i32 = 0;
+	let start_y: i32 = 100;
+	let node_h: i32 = 164;
+	if (mapbase != "") {
+		import_folder_place_image_node(nodes, canvas, mapbase, start_y + node_h * pos, nout.id, 0);
+		pos++;
+	}
+	if (mapopac != "") {
+		import_folder_place_image_node(nodes, canvas, mapopac, start_y + node_h * pos, nout.id, 1);
+		pos++;
+	}
+	if (mapocc != "") {
+		import_folder_place_image_node(nodes, canvas, mapocc, start_y + node_h * pos, nout.id, 2);
+		pos++;
+	}
+	if (maprough != "") {
+		import_folder_place_image_node(nodes, canvas, maprough, start_y + node_h * pos, nout.id, 3);
+		pos++;
+	}
+	if (mapmet != "") {
+		import_folder_place_image_node(nodes, canvas, mapmet, start_y + node_h * pos, nout.id, 4);
+		pos++;
+	}
+	if (mapnor != "") {
+		import_folder_place_image_node(nodes, canvas, mapnor, start_y + node_h * pos, nout.id, 5);
+		pos++;
+	}
+	if (mapheight != "") {
+		import_folder_place_image_node(nodes, canvas, mapheight, start_y + node_h * pos, nout.id, 7);
+		pos++;
+	}
+
+	make_material_parse_paint_material();
+	util_render_make_material_preview();
+	ui_base_hwnds[1].redraws = 2;
+	history_new_material();
+}
+
+function import_folder_place_image_node(nodes: zui_nodes_t, canvas: zui_node_canvas_t, asset: string, ny: i32, to_id: i32, to_socket: i32) {
+	let n: zui_node_t = nodes_material_create_node("TEX_IMAGE");
+	n.buttons[0].default_value = base_get_asset_index(asset);
+	n.x = 72;
+	n.y = ny;
+	let l: zui_node_link_t = { id: zui_get_link_id(canvas.links), from_id: n.id, from_socket: 0, to_id: to_id, to_socket: to_socket };
+	canvas.links.push(l);
+}

+ 156 - 0
armorpaint/Sources/make_bake.ts

@@ -0,0 +1,156 @@
+
+function make_bake_run(con: NodeShaderContextRaw, vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	if (context_raw.bake_type == bake_type_t.AO) { // Voxel
+		///if arm_voxels
+		// Apply normal channel
+		frag.wposition = true;
+		frag.n = true;
+		frag.vvec = true;
+		node_shader_add_function(frag, str_cotangent_frame);
+		///if krom_direct3d11
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
+		///else
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
+		///end
+		node_shader_write(frag, 'n = nortan * 2.0 - 1.0;');
+		node_shader_write(frag, 'n.y = -n.y;');
+		node_shader_write(frag, 'n = normalize(mul(n, TBN));');
+
+		node_shader_write(frag, make_material_voxelgi_half_extents());
+		node_shader_write(frag, 'vec3 voxpos = wposition / voxelgiHalfExtents;');
+		node_shader_add_uniform(frag, 'sampler3D voxels');
+		node_shader_add_function(frag, str_trace_ao);
+		frag.n = true;
+		let strength: f32 = context_raw.bake_ao_strength;
+		let radius: f32 = context_raw.bake_ao_radius;
+		let offset: f32 = context_raw.bake_ao_offset;
+		node_shader_write(frag, `float ao = traceAO(voxpos, n, ${radius}, ${offset}) * ${strength};`);
+		if (context_raw.bake_axis != bake_axis_t.XYZ) {
+			let axis: string = make_bake_axis_string(context_raw.bake_axis);
+			node_shader_write(frag, `ao *= dot(n, ${axis});`);
+		}
+		node_shader_write(frag, 'ao = 1.0 - ao;');
+		node_shader_write(frag, 'fragColor[0] = vec4(ao, ao, ao, 1.0);');
+		///end
+	}
+	else if (context_raw.bake_type == bake_type_t.CURVATURE) {
+		let pass: bool = parser_material_bake_passthrough;
+		let strength: string = pass ? parser_material_bake_passthrough_strength : context_raw.bake_curv_strength + "";
+		let radius: string = pass ? parser_material_bake_passthrough_radius : context_raw.bake_curv_radius + "";
+		let offset: string = pass ? parser_material_bake_passthrough_offset : context_raw.bake_curv_offset + "";
+		strength = `float(${strength})`;
+		radius = `float(${radius})`;
+		offset = `float(${offset})`;
+		frag.n = true;
+		node_shader_write(frag, 'vec3 dx = dFdx(n);');
+		node_shader_write(frag, 'vec3 dy = dFdy(n);');
+		node_shader_write(frag, 'float curvature = max(dot(dx, dx), dot(dy, dy));');
+		node_shader_write(frag, 'curvature = clamp(pow(curvature, (1.0 / ' + radius + ') * 0.25) * ' + strength + ' * 2.0 + ' + offset + ' / 10.0, 0.0, 1.0);');
+		if (context_raw.bake_axis != bake_axis_t.XYZ) {
+			let axis: string = make_bake_axis_string(context_raw.bake_axis);
+			node_shader_write(frag, `curvature *= dot(n, ${axis});`);
+		}
+		node_shader_write(frag, 'fragColor[0] = vec4(curvature, curvature, curvature, 1.0);');
+	}
+	else if (context_raw.bake_type == bake_type_t.NORMAL) { // Tangent
+		frag.n = true;
+		node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo'); // Baked high-poly normals
+		node_shader_write(frag, 'vec3 n0 = textureLod(texpaint_undo, texCoord, 0.0).rgb * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
+		node_shader_add_function(frag, str_cotangent_frame);
+		node_shader_write(frag, 'mat3 invTBN = transpose(cotangentFrame(n, n, texCoord));');
+		node_shader_write(frag, 'vec3 res = normalize(mul(n0, invTBN)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
+		node_shader_write(frag, 'fragColor[0] = vec4(res, 1.0);');
+	}
+	else if (context_raw.bake_type == bake_type_t.NORMAL_OBJECT) {
+		frag.n = true;
+		node_shader_write(frag, 'fragColor[0] = vec4(n * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5), 1.0);');
+		if (context_raw.bake_up_axis == bake_up_axis_t.Y) {
+			node_shader_write(frag, 'fragColor[0].rgb = vec3(fragColor[0].r, fragColor[0].b, 1.0 - fragColor[0].g);');
+		}
+	}
+	else if (context_raw.bake_type == bake_type_t.HEIGHT) {
+		frag.wposition = true;
+		node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo'); // Baked high-poly positions
+		node_shader_write(frag, 'vec3 wpos0 = textureLod(texpaint_undo, texCoord, 0.0).rgb * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
+		node_shader_write(frag, 'float res = distance(wpos0, wposition) * 10.0;');
+		node_shader_write(frag, 'fragColor[0] = vec4(res, res, res, 1.0);');
+	}
+	else if (context_raw.bake_type == bake_type_t.DERIVATIVE) {
+		node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo'); // Baked height
+		node_shader_write(frag, 'vec2 texDx = dFdx(texCoord);');
+		node_shader_write(frag, 'vec2 texDy = dFdy(texCoord);');
+		node_shader_write(frag, 'float h0 = textureLod(texpaint_undo, texCoord, 0.0).r * 100;');
+		node_shader_write(frag, 'float h1 = textureLod(texpaint_undo, texCoord + texDx, 0.0).r * 100;');
+		node_shader_write(frag, 'float h2 = textureLod(texpaint_undo, texCoord + texDy, 0.0).r * 100;');
+		node_shader_write(frag, 'fragColor[0] = vec4((h1 - h0) * 0.5 + 0.5, (h2 - h0) * 0.5 + 0.5, 0.0, 1.0);');
+	}
+	else if (context_raw.bake_type == bake_type_t.POSITION) {
+		frag.wposition = true;
+		node_shader_write(frag, 'fragColor[0] = vec4(wposition * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5), 1.0);');
+		if (context_raw.bake_up_axis == bake_up_axis_t.Y) {
+			node_shader_write(frag, 'fragColor[0].rgb = vec3(fragColor[0].r, fragColor[0].b, 1.0 - fragColor[0].g);');
+		}
+	}
+	else if (context_raw.bake_type == bake_type_t.TEXCOORD) {
+		node_shader_write(frag, 'fragColor[0] = vec4(texCoord.xy, 0.0, 1.0);');
+	}
+	else if (context_raw.bake_type == bake_type_t.MATERIALID) {
+		node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
+		node_shader_write(frag, 'float sample_matid = textureLod(texpaint_nor_undo, texCoord, 0.0).a + 1.0 / 255.0;');
+		node_shader_write(frag, 'float matid_r = fract(sin(dot(vec2(sample_matid, sample_matid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
+		node_shader_write(frag, 'float matid_g = fract(sin(dot(vec2(sample_matid * 20.0, sample_matid), vec2(12.9898, 78.233))) * 43758.5453);');
+		node_shader_write(frag, 'float matid_b = fract(sin(dot(vec2(sample_matid, sample_matid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
+		node_shader_write(frag, 'fragColor[0] = vec4(matid_r, matid_g, matid_b, 1.0);');
+	}
+	else if (context_raw.bake_type == bake_type_t.OBJECTID) {
+		node_shader_add_uniform(frag, 'float objectId', '_objectId');
+		node_shader_write(frag, 'float obid = objectId + 1.0 / 255.0;');
+		node_shader_write(frag, 'float id_r = fract(sin(dot(vec2(obid, obid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
+		node_shader_write(frag, 'float id_g = fract(sin(dot(vec2(obid * 20.0, obid), vec2(12.9898, 78.233))) * 43758.5453);');
+		node_shader_write(frag, 'float id_b = fract(sin(dot(vec2(obid, obid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
+		node_shader_write(frag, 'fragColor[0] = vec4(id_r, id_g, id_b, 1.0);');
+	}
+	else if (context_raw.bake_type == bake_type_t.VERTEX_COLOR) {
+		if (con.allow_vcols) {
+			node_shader_context_add_elem(con, "col", "short4norm");
+			node_shader_write(frag, 'fragColor[0] = vec4(vcolor.r, vcolor.g, vcolor.b, 1.0);');
+		}
+		else {
+			node_shader_write(frag, 'fragColor[0] = vec4(1.0, 1.0, 1.0, 1.0);');
+		}
+	}
+}
+
+function make_bake_position_normal(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	node_shader_add_out(vert, 'vec3 position');
+	node_shader_add_out(vert, 'vec3 normal');
+	node_shader_add_uniform(vert, 'mat4 W', '_world_matrix');
+	node_shader_write(vert, 'position = vec4(mul(vec4(pos.xyz, 1.0), W)).xyz;');
+	node_shader_write(vert, 'normal = vec3(nor.xy, pos.w);');
+	node_shader_write(vert, 'vec2 tpos = vec2(tex.x * 2.0 - 1.0, (1.0 - tex.y) * 2.0 - 1.0);');
+	node_shader_write(vert, 'gl_Position = vec4(tpos, 0.0, 1.0);');
+	node_shader_add_out(frag, 'vec4 fragColor[2]');
+	node_shader_write(frag, 'fragColor[0] = vec4(position, 1.0);');
+	node_shader_write(frag, 'fragColor[1] = vec4(normal, 1.0);');
+}
+
+function make_bake_set_color_writes(con_paint: NodeShaderContextRaw) {
+	// Bake into base color, disable other slots
+	con_paint.data.color_writes_red[1] = false;
+	con_paint.data.color_writes_green[1] = false;
+	con_paint.data.color_writes_blue[1] = false;
+	con_paint.data.color_writes_alpha[1] = false;
+	con_paint.data.color_writes_red[2] = false;
+	con_paint.data.color_writes_green[2] = false;
+	con_paint.data.color_writes_blue[2] = false;
+	con_paint.data.color_writes_alpha[2] = false;
+}
+
+function make_bake_axis_string(i: i32): string {
+	return i == bake_axis_t.X  ? "vec3(1,0,0)"  :
+		   i == bake_axis_t.Y  ? "vec3(0,1,0)"  :
+		   i == bake_axis_t.Z  ? "vec3(0,0,1)"  :
+		   i == bake_axis_t.MX ? "vec3(-1,0,0)" :
+		   i == bake_axis_t.MY ? "vec3(0,-1,0)" :
+								 "vec3(0,0,-1)";
+}

+ 89 - 0
armorpaint/Sources/make_blur.ts

@@ -0,0 +1,89 @@
+
+function make_blur_run(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, sp.y * gbufferSize.y), 0).ba;');
+	///else
+	node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(sp.x * gbufferSize.x, (1.0 - sp.y) * gbufferSize.y), 0).ba;');
+	///end
+
+	node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
+	node_shader_write(frag, 'float roughness = 0.0;');
+	node_shader_write(frag, 'float metallic = 0.0;');
+	node_shader_write(frag, 'float occlusion = 0.0;');
+	node_shader_write(frag, 'vec3 nortan = vec3(0.0, 0.0, 0.0);');
+	node_shader_write(frag, 'float height = 0.0;');
+	node_shader_write(frag, 'float mat_opacity = 1.0;');
+	let is_mask: bool = slot_layer_is_mask(context_raw.layer);
+	if (is_mask) {
+		node_shader_write(frag, 'float opacity = 1.0;');
+	}
+	else {
+		node_shader_write(frag, 'float opacity = 0.0;');
+	}
+	if (context_raw.material.paint_emis) {
+		node_shader_write(frag, 'float emis = 0.0;');
+	}
+	if (context_raw.material.paint_subs) {
+		node_shader_write(frag, 'float subs = 0.0;');
+	}
+
+	node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
+	node_shader_write(frag, 'float blur_step = 1.0 / texpaintSize.x;');
+	if (context_raw.tool == workspace_tool_t.SMUDGE) {
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
+		node_shader_write(frag, '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};');
+		///else
+		node_shader_write(frag, '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);');
+		///end
+		node_shader_add_uniform(frag, 'vec3 brushDirection', '_brushDirection');
+		node_shader_write(frag, 'vec2 blur_direction = brushDirection.yx;');
+		node_shader_write(frag, 'for (int i = 0; i < 7; ++i) {');
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSize.x, (sp.y + blur_direction.y * blur_step * float(i)) * gbufferSize.y), 0).ba;');
+		///else
+		node_shader_write(frag, 'vec2 texCoordInp2 = texelFetch(gbuffer2, ivec2((sp.x + blur_direction.x * blur_step * float(i)) * gbufferSize.x, (1.0 - (sp.y + blur_direction.y * blur_step * float(i))) * gbufferSize.y), 0).ba;');
+		///end
+		node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp2);');
+		node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[i];');
+		node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[i];');
+		node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp2) * blur_weight[i];');
+		node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
+		node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
+		node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
+		node_shader_write(frag, 'height += texpaint_pack_sample.a;');
+		node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp2).rgb * blur_weight[i];');
+		node_shader_write(frag, '}');
+	}
+	else {
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
+		node_shader_write(frag, '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};');
+		///else
+		node_shader_write(frag, '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);');
+		///end
+		// X
+		node_shader_write(frag, 'for (int i = -7; i <= 7; ++i) {');
+		node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(blur_step * float(i), 0.0));');
+		node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[i + 7];');
+		node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[i + 7];');
+		node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(blur_step * float(i), 0.0)) * blur_weight[i + 7];');
+		node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
+		node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
+		node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
+		node_shader_write(frag, 'height += texpaint_pack_sample.a;');
+		node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp + vec2(blur_step * float(i), 0.0)).rgb * blur_weight[i + 7];');
+		node_shader_write(frag, '}');
+		// Y
+		node_shader_write(frag, 'for (int j = -7; j <= 7; ++j) {');
+		node_shader_write(frag, 'vec4 texpaint_sample = texture(texpaint_undo, texCoordInp + vec2(0.0, blur_step * float(j)));');
+		node_shader_write(frag, 'opacity += texpaint_sample.a * blur_weight[j + 7];');
+		node_shader_write(frag, 'basecol += texpaint_sample.rgb * blur_weight[j + 7];');
+		node_shader_write(frag, 'vec4 texpaint_pack_sample = texture(texpaint_pack_undo, texCoordInp + vec2(0.0, blur_step * float(j))) * blur_weight[j + 7];');
+		node_shader_write(frag, 'roughness += texpaint_pack_sample.g;');
+		node_shader_write(frag, 'metallic += texpaint_pack_sample.b;');
+		node_shader_write(frag, 'occlusion += texpaint_pack_sample.r;');
+		node_shader_write(frag, 'height += texpaint_pack_sample.a;');
+		node_shader_write(frag, 'nortan += texture(texpaint_nor_undo, texCoordInp + vec2(0.0, blur_step * float(j))).rgb * blur_weight[j + 7];');
+		node_shader_write(frag, '}');
+	}
+	node_shader_write(frag, 'opacity *= brushOpacity;');
+}

+ 91 - 0
armorpaint/Sources/make_brush.ts

@@ -0,0 +1,91 @@
+
+function make_brush_run(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+
+	node_shader_write(frag, 'float dist = 0.0;');
+
+	if (context_raw.tool == workspace_tool_t.PARTICLE) return;
+
+	let fill_layer: bool = context_raw.layer.fill_layer != null;
+	let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
+	if (decal && !fill_layer) node_shader_write(frag, 'if (decalMask.z > 0.0) {');
+
+	if (config_raw.brush_3d) {
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'float depth = textureLod(gbufferD, inp.xy, 0.0).r;');
+		///else
+		node_shader_write(frag, 'float depth = textureLod(gbufferD, vec2(inp.x, 1.0 - inp.y), 0.0).r;');
+		///end
+
+		node_shader_add_uniform(frag, 'mat4 invVP', '_inv_view_proj_matrix');
+		node_shader_write(frag, 'vec4 winp = vec4(vec2(inp.x, 1.0 - inp.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);');
+		node_shader_write(frag, 'winp = mul(winp, invVP);');
+		node_shader_write(frag, 'winp.xyz /= winp.w;');
+		frag.wposition = true;
+
+		if (config_raw.brush_angle_reject || context_raw.xray) {
+			node_shader_add_function(frag, str_octahedron_wrap);
+			node_shader_add_uniform(frag, 'sampler2D gbuffer0');
+			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+			node_shader_write(frag, 'vec2 g0 = textureLod(gbuffer0, inp.xy, 0.0).rg;');
+			///else
+			node_shader_write(frag, 'vec2 g0 = textureLod(gbuffer0, vec2(inp.x, 1.0 - inp.y), 0.0).rg;');
+			///end
+			node_shader_write(frag, 'vec3 wn;');
+			node_shader_write(frag, 'wn.z = 1.0 - abs(g0.x) - abs(g0.y);');
+			node_shader_write(frag, 'wn.xy = wn.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);');
+			node_shader_write(frag, 'wn = normalize(wn);');
+			node_shader_write(frag, 'float planeDist = dot(wn, winp.xyz - wposition);');
+
+			if (config_raw.brush_angle_reject && !context_raw.xray) {
+				node_shader_write(frag, 'if (planeDist < -0.01) discard;');
+				frag.n = true;
+				let angle: f32 = context_raw.brush_angle_reject_dot;
+				node_shader_write(frag, `if (dot(wn, n) < ${angle}) discard;`);
+			}
+		}
+
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'float depthlast = textureLod(gbufferD, inplast.xy, 0.0).r;');
+		///else
+		node_shader_write(frag, 'float depthlast = textureLod(gbufferD, vec2(inplast.x, 1.0 - inplast.y), 0.0).r;');
+		///end
+
+		node_shader_write(frag, 'vec4 winplast = vec4(vec2(inplast.x, 1.0 - inplast.y) * 2.0 - 1.0, depthlast * 2.0 - 1.0, 1.0);');
+		node_shader_write(frag, 'winplast = mul(winplast, invVP);');
+		node_shader_write(frag, 'winplast.xyz /= winplast.w;');
+
+		node_shader_write(frag, 'vec3 pa = wposition - winp.xyz;');
+		if (context_raw.xray) {
+			node_shader_write(frag, 'pa += wn * vec3(planeDist, planeDist, planeDist);');
+		}
+		node_shader_write(frag, 'vec3 ba = winplast.xyz - winp.xyz;');
+
+		if (context_raw.brush_lazy_radius > 0 && context_raw.brush_lazy_step > 0) {
+			// Sphere
+			node_shader_write(frag, 'dist = distance(wposition, winp.xyz);');
+		}
+		else {
+			// Capsule
+			node_shader_write(frag, 'float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);');
+			node_shader_write(frag, 'dist = length(pa - ba * h);');
+		}
+	}
+	else { // !brush3d
+		node_shader_write(frag, 'vec2 binp = inp.xy * 2.0 - 1.0;');
+		node_shader_write(frag, 'binp.x *= aspectRatio;');
+		node_shader_write(frag, 'binp = binp * 0.5 + 0.5;');
+
+		node_shader_write(frag, 'vec2 binplast = inplast.xy * 2.0 - 1.0;');
+		node_shader_write(frag, 'binplast.x *= aspectRatio;');
+		node_shader_write(frag, 'binplast = binplast * 0.5 + 0.5;');
+
+		node_shader_write(frag, 'vec2 pa = bsp.xy - binp.xy;');
+		node_shader_write(frag, 'vec2 ba = binplast.xy - binp.xy;');
+		node_shader_write(frag, 'float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);');
+		node_shader_write(frag, 'dist = length(pa - ba * h);');
+	}
+
+	node_shader_write(frag, 'if (dist > brushRadius) discard;');
+
+	if (decal && !fill_layer) node_shader_write(frag, '}');
+}

+ 32 - 0
armorpaint/Sources/make_clone.ts

@@ -0,0 +1,32 @@
+
+function make_clone_run(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	node_shader_add_uniform(frag, 'vec2 cloneDelta', '_cloneDelta');
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.xy + cloneDelta) * gbufferSize), 0).ba;');
+	///else
+	node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2((sp.x + cloneDelta.x) * gbufferSize.x, (1.0 - (sp.y + cloneDelta.y)) * gbufferSize.y), 0).ba;');
+	///end
+
+	node_shader_write(frag, 'vec3 texpaint_pack_sample = textureLod(texpaint_pack_undo, texCoordInp, 0.0).rgb;');
+	let base: string = 'textureLod(texpaint_undo, texCoordInp, 0.0).rgb';
+	let rough: string = 'texpaint_pack_sample.g';
+	let met: string = 'texpaint_pack_sample.b';
+	let occ: string = 'texpaint_pack_sample.r';
+	let nortan: string = 'textureLod(texpaint_nor_undo, texCoordInp, 0.0).rgb';
+	let height: string = '0.0';
+	let opac: string = '1.0';
+	node_shader_write(frag, `vec3 basecol = ${base};`);
+	node_shader_write(frag, `float roughness = ${rough};`);
+	node_shader_write(frag, `float metallic = ${met};`);
+	node_shader_write(frag, `float occlusion = ${occ};`);
+	node_shader_write(frag, `vec3 nortan = ${nortan};`);
+	node_shader_write(frag, `float height = ${height};`);
+	node_shader_write(frag, `float mat_opacity = ${opac};`);
+	node_shader_write(frag, 'float opacity = mat_opacity * brushOpacity;');
+	if (context_raw.material.paint_emis) {
+		node_shader_write(frag, 'float emis = 0.0;');
+	}
+	if (context_raw.material.paint_subs) {
+		node_shader_write(frag, 'float subs = 0.0;');
+	}
+}

+ 43 - 0
armorpaint/Sources/make_colorid_picker.ts

@@ -0,0 +1,43 @@
+
+function make_colorid_picker_run(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	// Mangle vertices to form full screen triangle
+	node_shader_write(vert, 'gl_Position = vec4(-1.0 + float((gl_VertexID & 1) << 2), -1.0 + float((gl_VertexID & 2) << 1), 0.0, 1.0);');
+
+	node_shader_add_uniform(frag, 'sampler2D gbuffer2');
+	node_shader_add_uniform(frag, 'vec2 gbufferSize', '_gbufferSize');
+	node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
+
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, inp.y * gbufferSize.y), 0).ba;');
+	///else
+	node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, (1.0 - inp.y) * gbufferSize.y), 0).ba;');
+	///end
+
+	if (context_raw.tool == workspace_tool_t.COLORID) {
+		node_shader_add_out(frag, 'vec4 fragColor');
+		node_shader_add_uniform(frag, 'sampler2D texcolorid', '_texcolorid');
+		node_shader_write(frag, 'vec3 idcol = textureLod(texcolorid, texCoordInp, 0.0).rgb;');
+		node_shader_write(frag, 'fragColor = vec4(idcol, 1.0);');
+	}
+	else if (context_raw.tool == workspace_tool_t.PICKER || context_raw.tool == workspace_tool_t.MATERIAL) {
+		if (context_raw.pick_pos_nor_tex) {
+			node_shader_add_out(frag, 'vec4 fragColor[2]');
+			node_shader_add_uniform(frag, 'sampler2D gbufferD');
+			node_shader_add_uniform(frag, 'mat4 invVP', '_inv_view_proj_matrix');
+			node_shader_add_function(frag, str_get_pos_from_depth);
+			node_shader_add_function(frag, str_get_nor_from_depth);
+			node_shader_write(frag, 'fragColor[0] = vec4(get_pos_from_depth(vec2(inp.x, 1.0 - inp.y), invVP, texturePass(gbufferD)), texCoordInp.x);');
+			node_shader_write(frag, 'fragColor[1] = vec4(get_nor_from_depth(fragColor[0].rgb, vec2(inp.x, 1.0 - inp.y), invVP, vec2(1.0, 1.0) / gbufferSize, texturePass(gbufferD)), texCoordInp.y);');
+		}
+		else {
+			node_shader_add_out(frag, 'vec4 fragColor[4]');
+			node_shader_add_uniform(frag, 'sampler2D texpaint');
+			node_shader_add_uniform(frag, 'sampler2D texpaint_nor');
+			node_shader_add_uniform(frag, 'sampler2D texpaint_pack');
+			node_shader_write(frag, 'fragColor[0] = textureLod(texpaint, texCoordInp, 0.0);');
+			node_shader_write(frag, 'fragColor[1] = textureLod(texpaint_nor, texCoordInp, 0.0);');
+			node_shader_write(frag, 'fragColor[2] = textureLod(texpaint_pack, texCoordInp, 0.0);');
+			node_shader_write(frag, 'fragColor[3].rg = texCoordInp.xy;');
+		}
+	}
+}

+ 47 - 0
armorpaint/Sources/make_discard.ts

@@ -0,0 +1,47 @@
+
+function make_discard_color_id(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	node_shader_add_uniform(frag, 'sampler2D texpaint_colorid'); // 1x1 picker
+	node_shader_add_uniform(frag, 'sampler2D texcolorid', '_texcolorid'); // color map
+	node_shader_write(frag, 'vec3 colorid_c1 = texelFetch(texpaint_colorid, ivec2(0, 0), 0).rgb;');
+	node_shader_write(frag, 'vec3 colorid_c2 = textureLod(texcolorid, texCoordPick, 0).rgb;');
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
+	node_shader_write(frag, 'if (any(colorid_c1 != colorid_c2)) discard;');
+	///else
+	node_shader_write(frag, 'if (colorid_c1 != colorid_c2) discard;');
+	///end
+}
+
+function make_discard_face(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	node_shader_add_uniform(frag, 'sampler2D gbuffer2');
+	node_shader_add_uniform(frag, 'sampler2D textrianglemap', '_textrianglemap');
+	node_shader_add_uniform(frag, 'vec2 textrianglemapSize', '_texpaintSize');
+	node_shader_add_uniform(frag, 'vec2 gbufferSize', '_gbufferSize');
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, inp.y * gbufferSize.y), 0).ba;');
+	///else
+	node_shader_write(frag, 'vec2 texCoordInp = texelFetch(gbuffer2, ivec2(inp.x * gbufferSize.x, (1.0 - inp.y) * gbufferSize.y), 0).ba;');
+	///end
+	node_shader_write(frag, 'vec4 face_c1 = texelFetch(textrianglemap, ivec2(texCoordInp * textrianglemapSize), 0);');
+	node_shader_write(frag, 'vec4 face_c2 = textureLod(textrianglemap, texCoordPick, 0);');
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal)
+	node_shader_write(frag, 'if (any(face_c1 != face_c2)) discard;');
+	///else
+	node_shader_write(frag, 'if (face_c1 != face_c2) discard;');
+	///end
+}
+
+function make_discard_uv_island(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	node_shader_add_uniform(frag, 'sampler2D texuvislandmap', '_texuvislandmap');
+	node_shader_write(frag, 'if (textureLod(texuvislandmap, texCoordPick, 0).r == 0.0) discard;');
+}
+
+function make_discard_material_id(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	frag.wvpposition = true;
+	node_shader_write(frag, 'vec2 picker_sample_tc = vec2(wvpposition.x / wvpposition.w, wvpposition.y / wvpposition.w) * 0.5 + 0.5;');
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(frag, 'picker_sample_tc.y = 1.0 - picker_sample_tc.y;');
+	///end
+	node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
+	let matid: i32 = context_raw.materialid_picked / 255;
+	node_shader_write(frag, `if (${matid} != textureLod(texpaint_nor_undo, picker_sample_tc, 0.0).a) discard;`);
+}

+ 490 - 0
armorpaint/Sources/make_material.ts

@@ -0,0 +1,490 @@
+
+let make_material_default_scon: shader_context_t = null;
+let make_material_default_mcon: material_context_t = null;
+
+let make_material_height_used = false;
+let make_material_emis_used = false;
+let make_material_subs_used = false;
+
+function make_material_get_mout(): bool {
+	for (let n of ui_nodes_get_canvas_material().nodes) if (n.type == "OUTPUT_MATERIAL_PBR") return true;
+	return false;
+}
+
+function make_material_parse_mesh_material() {
+	let m: material_data_t = project_materials[0].data;
+
+	for (let c of m._.shader._.contexts) {
+		if (c.name == "mesh") {
+			array_remove(m._.shader.contexts, c);
+			array_remove(m._.shader._.contexts, c);
+			make_material_delete_context(c);
+			break;
+		}
+	}
+
+	if (make_mesh_layer_pass_count > 1) {
+		let i: i32 = 0;
+		while (i < m._.shader._.contexts.length) {
+			let c: shader_context_t = m._.shader._.contexts[i];
+			for (let j: i32 = 1; j < make_mesh_layer_pass_count; ++j) {
+				if (c.name == "mesh" + j) {
+					array_remove(m._.shader.contexts, c);
+					array_remove(m._.shader._.contexts, c);
+					make_material_delete_context(c);
+					i--;
+					break;
+				}
+			}
+			i++;
+		}
+
+		i = 0;
+		while (i < m._.contexts.length) {
+			let c: material_context_t = m._.contexts[i];
+			for (let j: i32 = 1; j < make_mesh_layer_pass_count; ++j) {
+				if (c.name == "mesh" + j) {
+					array_remove(m.contexts, c);
+					array_remove(m._.contexts, c);
+					i--;
+					break;
+				}
+			}
+			i++;
+		}
+	}
+
+	let con: NodeShaderContextRaw = make_mesh_run({ name: "Material", canvas: null });
+	let scon: shader_context_t = shader_context_create(con.data);
+	scon._.override_context = {};
+	if (con.frag.shared_samplers.length > 0) {
+		let sampler: string = con.frag.shared_samplers[0];
+		scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
+	}
+	if (!context_raw.texture_filter) {
+		scon._.override_context.filter = "point";
+	}
+	m._.shader.contexts.push(scon);
+	m._.shader._.contexts.push(scon);
+
+	for (let i: i32 = 1; i < make_mesh_layer_pass_count; ++i) {
+		let con: NodeShaderContextRaw = make_mesh_run({ name: "Material", canvas: null }, i);
+		let scon: shader_context_t = shader_context_create(con.data);
+		scon._.override_context = {};
+		if (con.frag.shared_samplers.length > 0) {
+			let sampler: string = con.frag.shared_samplers[0];
+			scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
+		}
+		if (!context_raw.texture_filter) {
+			scon._.override_context.filter = "point";
+		}
+		m._.shader.contexts.push(scon);
+		m._.shader._.contexts.push(scon);
+
+		let mcon: material_context_t;
+		mcon = material_context_create({ name: "mesh" + i, bind_textures: [] });
+		m.contexts.push(mcon);
+		m._.contexts.push(mcon);
+	}
+
+	context_raw.ddirty = 2;
+
+	///if arm_voxels
+	make_material_make_voxel(m);
+	///end
+
+	///if (krom_direct3d12 || krom_vulkan || krom_metal)
+	render_path_raytrace_dirty = 1;
+	///end
+}
+
+function make_material_parse_particle_material() {
+	let m: material_data_t = context_raw.particle_material;
+	let sc: shader_context_t = null;
+	for (let c of m._.shader._.contexts) {
+		if (c.name == "mesh") {
+			sc = c;
+			break;
+		}
+	}
+	if (sc != null) {
+		array_remove(m._.shader.contexts, sc);
+		array_remove(m._.shader._.contexts, sc);
+	}
+	let con: NodeShaderContextRaw = make_particle_run({ name: "MaterialParticle", canvas: null });
+	if (sc != null) make_material_delete_context(sc);
+	sc = shader_context_create(con.data);
+	m._.shader.contexts.push(sc);
+	m._.shader._.contexts.push(sc);
+}
+
+function make_material_parse_mesh_preview_material(md: material_data_t = null) {
+	if (!make_material_get_mout()) return;
+
+	let m: material_data_t = md == null ? project_materials[0].data : md;
+	let scon: shader_context_t = null;
+	for (let c of m._.shader._.contexts) {
+		if (c.name == "mesh") {
+			scon = c;
+			break;
+		}
+	}
+
+	array_remove(m._.shader.contexts, scon);
+	array_remove(m._.shader._.contexts, scon);
+
+	let mcon: material_context_t = { name: "mesh", bind_textures: [] };
+
+	let sd: material_t = { name: "Material", canvas: null };
+	let con: NodeShaderContextRaw = make_mesh_preview_run(sd, mcon);
+
+	for (let i: i32 = 0; i < m._.contexts.length; ++i) {
+		if (m._.contexts[i].name == "mesh") {
+			m._.contexts[i] = material_context_create(mcon);
+			break;
+		}
+	}
+
+	if (scon != null) make_material_delete_context(scon);
+
+	let compile_error: bool = false;
+	let _scon: shader_context_t = shader_context_create(con.data);
+	if (_scon == null) compile_error = true;
+	scon = _scon;
+	if (compile_error) return;
+
+	m._.shader.contexts.push(scon);
+	m._.shader._.contexts.push(scon);
+}
+
+///if arm_voxels
+function make_material_make_voxel(m: material_data_t) {
+	let rebuild: bool = make_material_height_used;
+	if (config_raw.rp_gi != false && rebuild) {
+		let scon: shader_context_t = null;
+		for (let c of m._.shader._.contexts) {
+			if (c.name == "voxel") {
+				scon = c;
+				break;
+			}
+		}
+		if (scon != null) make_voxel_run(scon);
+	}
+}
+///end
+
+function make_material_parse_paint_material(bake_previews = true) {
+	if (!make_material_get_mout()) return;
+
+	if (bake_previews) {
+		let current: image_t = _g2_current;
+		let g2_in_use: bool = _g2_in_use;
+		if (g2_in_use) g2_end();
+		make_material_bake_node_previews();
+		if (g2_in_use) g2_begin(current);
+	}
+
+	let m: material_data_t = project_materials[0].data;
+	// let scon: TShaderContext = null;
+	// let mcon: TMaterialContext = null;
+	for (let c of m._.shader._.contexts) {
+		if (c.name == "paint") {
+			array_remove(m._.shader.contexts, c);
+			array_remove(m._.shader._.contexts, c);
+			if (c != make_material_default_scon) make_material_delete_context(c);
+			break;
+		}
+	}
+	for (let c of m._.contexts) {
+		if (c.name == "paint") {
+			array_remove(m.contexts, c);
+			array_remove(m._.contexts, c);
+			break;
+		}
+	}
+
+	let sdata: material_t = { name: "Material", canvas: ui_nodes_get_canvas_material() };
+	let tmcon: material_context_t = { name: "paint", bind_textures: [] };
+	let con: NodeShaderContextRaw = make_paint_run(sdata, tmcon);
+
+	let compile_error: bool = false;
+	let scon: shader_context_t;
+	let _scon: shader_context_t = shader_context_create(con.data);
+	if (_scon == null) compile_error = true;
+	scon = _scon;
+	if (compile_error) return;
+	scon._.override_context = {};
+	scon._.override_context.addressing = "repeat";
+	let mcon: material_context_t = material_context_create(tmcon);
+
+	m._.shader.contexts.push(scon);
+	m._.shader._.contexts.push(scon);
+	m.contexts.push(mcon);
+	m._.contexts.push(mcon);
+
+	if (make_material_default_scon == null) make_material_default_scon = scon;
+	if (make_material_default_mcon == null) make_material_default_mcon = mcon;
+}
+
+function make_material_bake_node_previews() {
+	context_raw.node_previews_used = [];
+	if (context_raw.node_previews == null) context_raw.node_previews = map_create();
+	make_material_traverse_nodes(ui_nodes_get_canvas_material().nodes, null, []);
+	for (let key of context_raw.node_previews.keys()) {
+		if (context_raw.node_previews_used.indexOf(key) == -1) {
+			let image: image_t = context_raw.node_previews.get(key);
+			base_notify_on_next_frame(function() { image_unload(image); });
+			context_raw.node_previews.delete(key);
+		}
+	}
+}
+
+function make_material_traverse_nodes(nodes: zui_node_t[], group: zui_node_canvas_t, parents: zui_node_t[]) {
+	for (let node of nodes) {
+		make_material_bake_node_preview(node, group, parents);
+		if (node.type == "GROUP") {
+			for (let g of project_material_groups) {
+				if (g.canvas.name == node.name) {
+					parents.push(node);
+					make_material_traverse_nodes(g.canvas.nodes, g.canvas, parents);
+					parents.pop();
+					break;
+				}
+			}
+		}
+	}
+}
+
+function make_material_bake_node_preview(node: zui_node_t, group: zui_node_canvas_t, parents: zui_node_t[]) {
+	if (node.type == "BLUR") {
+		let id: string = parser_material_node_name(node, parents);
+		let image: image_t = context_raw.node_previews.get(id);
+		context_raw.node_previews_used.push(id);
+		let resX: i32 = math_floor(config_get_texture_res_x() / 4);
+		let resY: i32 = math_floor(config_get_texture_res_y() / 4);
+		if (image == null || image.width != resX || image.height != resY) {
+			if (image != null) image_unload(image);
+			image = image_create_render_target(resX, resY);
+			context_raw.node_previews.set(id, image);
+		}
+
+		parser_material_blur_passthrough = true;
+		util_render_make_node_preview(ui_nodes_get_canvas_material(), node, image, group, parents);
+		parser_material_blur_passthrough = false;
+	}
+	else if (node.type == "DIRECT_WARP") {
+		let id: string = parser_material_node_name(node, parents);
+		let image: image_t = context_raw.node_previews.get(id);
+		context_raw.node_previews_used.push(id);
+		let resX: i32 = math_floor(config_get_texture_res_x());
+		let resY: i32 = math_floor(config_get_texture_res_y());
+		if (image == null || image.width != resX || image.height != resY) {
+			if (image != null) image_unload(image);
+			image = image_create_render_target(resX, resY);
+			context_raw.node_previews.set(id, image);
+		}
+
+		parser_material_warp_passthrough = true;
+		util_render_make_node_preview(ui_nodes_get_canvas_material(), node, image, group, parents);
+		parser_material_warp_passthrough = false;
+	}
+	else if (node.type == "BAKE_CURVATURE") {
+		let id: string = parser_material_node_name(node, parents);
+		let image: image_t = context_raw.node_previews.get(id);
+		context_raw.node_previews_used.push(id);
+		let resX: i32 = math_floor(config_get_texture_res_x());
+		let resY: i32 = math_floor(config_get_texture_res_y());
+		if (image == null || image.width != resX || image.height != resY) {
+			if (image != null) image_unload(image);
+			image = image_create_render_target(resX, resY, tex_format_t.R8);
+			context_raw.node_previews.set(id, image);
+		}
+
+		if (render_path_paint_live_layer == null) {
+			render_path_paint_live_layer = slot_layer_create("_live");
+		}
+
+		let _space: i32 = ui_header_worktab.position;
+		let _tool: workspace_tool_t = context_raw.tool;
+		let _bake_type: bake_type_t = context_raw.bake_type;
+		ui_header_worktab.position = space_type_t.SPACE3D;
+		context_raw.tool = workspace_tool_t.BAKE;
+		context_raw.bake_type = bake_type_t.CURVATURE;
+
+		parser_material_bake_passthrough = true;
+		parser_material_start_node = node;
+		parser_material_start_group = group;
+		parser_material_start_parents = parents;
+		make_material_parse_paint_material(false);
+		parser_material_bake_passthrough = false;
+		parser_material_start_node = null;
+		parser_material_start_group = null;
+		parser_material_start_parents = null;
+		context_raw.pdirty = 1;
+		render_path_paint_use_live_layer(true);
+		render_path_paint_commands_paint(false);
+		render_path_paint_dilate(true, false);
+		render_path_paint_use_live_layer(false);
+		context_raw.pdirty = 0;
+
+		ui_header_worktab.position = _space;
+		context_raw.tool = _tool;
+		context_raw.bake_type = _bake_type;
+		make_material_parse_paint_material(false);
+
+		let rts: map_t<string, render_target_t> = render_path_render_targets;
+		let texpaint_live: render_target_t = rts.get("texpaint_live");
+
+		g2_begin(image);
+		g2_draw_image(texpaint_live._image, 0, 0);
+		g2_end();
+	}
+}
+
+function make_material_parse_node_preview_material(node: zui_node_t, group: zui_node_canvas_t = null, parents: zui_node_t[] = null): { scon: shader_context_t, mcon: material_context_t } {
+	if (node.outputs.length == 0) return null;
+	let sdata: material_t = { name: "Material", canvas: ui_nodes_get_canvas_material() };
+	let mcon_raw: material_context_t = { name: "mesh", bind_textures: [] };
+	let con: NodeShaderContextRaw = make_node_preview_run(sdata, mcon_raw, node, group, parents);
+	let compile_error: bool = false;
+	let scon: shader_context_t;
+	let _scon: shader_context_t = shader_context_create(con.data);
+	if (_scon == null) compile_error = true;
+	scon = _scon;
+	if (compile_error) return null;
+	let mcon: material_context_t = material_context_create(mcon_raw);
+	return { scon: scon, mcon: mcon };
+}
+
+function make_material_parse_brush() {
+	parser_logic_parse(context_raw.brush.canvas);
+}
+
+function make_material_blend_mode(frag: NodeShaderRaw, blending: i32, cola: string, colb: string, opac: string): string {
+	if (blending == blend_type_t.MIX) {
+		return `mix(${cola}, ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.DARKEN) {
+		return `mix(${cola}, min(${cola}, ${colb}), ${opac})`;
+	}
+	else if (blending == blend_type_t.MULTIPLY) {
+		return `mix(${cola}, ${cola} * ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.BURN) {
+		return `mix(${cola}, vec3(1.0, 1.0, 1.0) - (vec3(1.0, 1.0, 1.0) - ${cola}) / ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.LIGHTEN) {
+		return `max(${cola}, ${colb} * ${opac})`;
+	}
+	else if (blending == blend_type_t.SCREEN) {
+		return `(vec3(1.0, 1.0, 1.0) - (vec3(1.0 - ${opac}, 1.0 - ${opac}, 1.0 - ${opac}) + ${opac} * (vec3(1.0, 1.0, 1.0) - ${colb})) * (vec3(1.0, 1.0, 1.0) - ${cola}))`;
+	}
+	else if (blending == blend_type_t.DODGE) {
+		return `mix(${cola}, ${cola} / (vec3(1.0, 1.0, 1.0) - ${colb}), ${opac})`;
+	}
+	else if (blending == blend_type_t.ADD) {
+		return `mix(${cola}, ${cola} + ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.OVERLAY) {
+		return `mix(${cola}, vec3(
+			${cola}.r < 0.5 ? 2.0 * ${cola}.r * ${colb}.r : 1.0 - 2.0 * (1.0 - ${cola}.r) * (1.0 - ${colb}.r),
+			${cola}.g < 0.5 ? 2.0 * ${cola}.g * ${colb}.g : 1.0 - 2.0 * (1.0 - ${cola}.g) * (1.0 - ${colb}.g),
+			${cola}.b < 0.5 ? 2.0 * ${cola}.b * ${colb}.b : 1.0 - 2.0 * (1.0 - ${cola}.b) * (1.0 - ${colb}.b)
+		), ${opac})`;
+	}
+	else if (blending == blend_type_t.SOFT_LIGHT) {
+		return `((1.0 - ${opac}) * ${cola} + ${opac} * ((vec3(1.0, 1.0, 1.0) - ${cola}) * ${colb} * ${cola} + ${cola} * (vec3(1.0, 1.0, 1.0) - (vec3(1.0, 1.0, 1.0) - ${colb}) * (vec3(1.0, 1.0, 1.0) - ${cola}))))`;
+	}
+	else if (blending == blend_type_t.LINEAR_LIGHT) {
+		return `(${cola} + ${opac} * (vec3(2.0, 2.0, 2.0) * (${colb} - vec3(0.5, 0.5, 0.5))))`;
+	}
+	else if (blending == blend_type_t.DIFFERENCE) {
+		return `mix(${cola}, abs(${cola} - ${colb}), ${opac})`;
+	}
+	else if (blending == blend_type_t.SUBTRACT) {
+		return `mix(${cola}, ${cola} - ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.DIVIDE) {
+		return `vec3(1.0 - ${opac}, 1.0 - ${opac}, 1.0 - ${opac}) * ${cola} + vec3(${opac}, ${opac}, ${opac}) * ${cola} / ${colb}`;
+	}
+	else if (blending == blend_type_t.HUE) {
+		node_shader_add_function(frag, str_hue_sat);
+		return `mix(${cola}, hsv_to_rgb(vec3(rgb_to_hsv(${colb}).r, rgb_to_hsv(${cola}).g, rgb_to_hsv(${cola}).b)), ${opac})`;
+	}
+	else if (blending == blend_type_t.SATURATION) {
+		node_shader_add_function(frag, str_hue_sat);
+		return `mix(${cola}, hsv_to_rgb(vec3(rgb_to_hsv(${cola}).r, rgb_to_hsv(${colb}).g, rgb_to_hsv(${cola}).b)), ${opac})`;
+	}
+	else if (blending == blend_type_t.COLOR) {
+		node_shader_add_function(frag, str_hue_sat);
+		return `mix(${cola}, hsv_to_rgb(vec3(rgb_to_hsv(${colb}).r, rgb_to_hsv(${colb}).g, rgb_to_hsv(${cola}).b)), ${opac})`;
+	}
+	else { // BlendValue
+		node_shader_add_function(frag, str_hue_sat);
+		return `mix(${cola}, hsv_to_rgb(vec3(rgb_to_hsv(${cola}).r, rgb_to_hsv(${cola}).g, rgb_to_hsv(${colb}).b)), ${opac})`;
+	}
+}
+
+function make_material_blend_mode_mask(frag: NodeShaderRaw, blending: i32, cola: string, colb: string, opac: string): string {
+	if (blending == blend_type_t.MIX) {
+		return `mix(${cola}, ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.DARKEN) {
+		return `mix(${cola}, min(${cola}, ${colb}), ${opac})`;
+	}
+	else if (blending == blend_type_t.MULTIPLY) {
+		return `mix(${cola}, ${cola} * ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.BURN) {
+		return `mix(${cola}, 1.0 - (1.0 - ${cola}) / ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.LIGHTEN) {
+		return `max(${cola}, ${colb} * ${opac})`;
+	}
+	else if (blending == blend_type_t.SCREEN) {
+		return `(1.0 - ((1.0 - ${opac}) + ${opac} * (1.0 - ${colb})) * (1.0 - ${cola}))`;
+	}
+	else if (blending == blend_type_t.DODGE) {
+		return `mix(${cola}, ${cola} / (1.0 - ${colb}), ${opac})`;
+	}
+	else if (blending == blend_type_t.ADD) {
+		return `mix(${cola}, ${cola} + ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.OVERLAY) {
+		return `mix(${cola}, ${cola} < 0.5 ? 2.0 * ${cola} * ${colb} : 1.0 - 2.0 * (1.0 - ${cola}) * (1.0 - ${colb}), ${opac})`;
+	}
+	else if (blending == blend_type_t.SOFT_LIGHT) {
+		return `((1.0 - ${opac}) * ${cola} + ${opac} * ((1.0 - ${cola}) * ${colb} * ${cola} + ${cola} * (1.0 - (1.0 - ${colb}) * (1.0 - ${cola}))))`;
+	}
+	else if (blending == blend_type_t.LINEAR_LIGHT) {
+		return `(${cola} + ${opac} * (2.0 * (${colb} - 0.5)))`;
+	}
+	else if (blending == blend_type_t.DIFFERENCE) {
+		return `mix(${cola}, abs(${cola} - ${colb}), ${opac})`;
+	}
+	else if (blending == blend_type_t.SUBTRACT) {
+		return `mix(${cola}, ${cola} - ${colb}, ${opac})`;
+	}
+	else if (blending == blend_type_t.DIVIDE) {
+		return `(1.0 - ${opac}) * ${cola} + ${opac} * ${cola} / ${colb}`;
+	}
+	else { // BlendHue, BlendSaturation, BlendColor, BlendValue
+		return `mix(${cola}, ${colb}, ${opac})`;
+	}
+}
+
+function make_material_get_displace_strength(): f32 {
+	let sc: f32 = context_main_object().base.transform.scale.x;
+	return config_raw.displace_strength * 0.02 * sc;
+}
+
+function make_material_voxelgi_half_extents(): string {
+	let ext: f32 = context_raw.vxao_ext;
+	return `const vec3 voxelgiHalfExtents = vec3(${ext}, ${ext}, ${ext});`;
+}
+
+function make_material_delete_context(c: shader_context_t) {
+	base_notify_on_next_frame(() => { // Ensure pipeline is no longer in use
+		shader_context_delete(c);
+	});
+}

+ 485 - 0
armorpaint/Sources/make_mesh.ts

@@ -0,0 +1,485 @@
+
+let make_mesh_layer_pass_count = 1;
+
+function make_mesh_run(data: material_t, layerPass = 0): NodeShaderContextRaw {
+	let context_id: string = layerPass == 0 ? "mesh" : "mesh" + layerPass;
+	let con_mesh: NodeShaderContextRaw = node_shader_context_create(data, {
+		name: context_id,
+		depth_write: layerPass == 0 ? true : false,
+		compare_mode: layerPass == 0 ? "less" : "equal",
+		cull_mode: (context_raw.cull_backfaces || layerPass > 0) ? "clockwise" : "none",
+		vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
+		color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
+		depth_attachment: "DEPTH32"
+	});
+
+	let vert: NodeShaderRaw = node_shader_context_make_vert(con_mesh);
+	let frag: NodeShaderRaw = node_shader_context_make_frag(con_mesh);
+	frag.ins = vert.outs;
+
+	node_shader_add_out(vert, 'vec2 texCoord');
+	frag.wvpposition = true;
+	node_shader_add_out(vert, 'vec4 prevwvpposition');
+	node_shader_add_uniform(vert, 'mat4 VP', '_view_proj_matrix');
+	node_shader_add_uniform(vert, 'mat4 prevWVP', '_prev_world_view_proj_matrix');
+	vert.wposition = true;
+
+	let texture_count: i32 = 0;
+	let displace_strength: f32 = make_material_get_displace_strength();
+	if (make_material_height_used && displace_strength > 0.0) {
+		vert.n = true;
+		node_shader_write(vert, 'float height = 0.0;');
+		let num_layers: i32 = 0;
+		for (let l of project_layers) {
+			if (!slot_layer_is_visible(l) || !l.paint_height || !slot_layer_is_layer(l)) continue;
+			if (num_layers > 16) break;
+			num_layers++;
+			texture_count++;
+			node_shader_add_uniform(vert, 'sampler2D texpaint_pack_vert' + l.id, '_texpaint_pack_vert' + l.id);
+			node_shader_write(vert, 'height += textureLod(texpaint_pack_vert' + l.id + ', tex, 0.0).a;');
+			let masks: SlotLayerRaw[] = slot_layer_get_masks(l);
+			if (masks != null) {
+				for (let m of masks) {
+					if (!slot_layer_is_visible(m)) continue;
+					texture_count++;
+					node_shader_add_uniform(vert, 'sampler2D texpaint_vert' + m.id, '_texpaint_vert' + m.id);
+					node_shader_write(vert, 'height *= textureLod(texpaint_vert' + m.id + ', tex, 0.0).r;');
+				}
+			}
+		}
+		node_shader_write(vert, `wposition += wnormal * vec3(height, height, height) * vec3(${displace_strength}, ${displace_strength}, ${displace_strength});`);
+	}
+
+	node_shader_write(vert, 'gl_Position = mul(vec4(wposition.xyz, 1.0), VP);');
+	node_shader_write(vert, 'texCoord = tex;');
+	if (make_material_height_used && displace_strength > 0) {
+		node_shader_add_uniform(vert, 'mat4 invW', '_inv_world_matrix');
+		node_shader_write(vert, 'prevwvpposition = mul(mul(vec4(wposition, 1.0), invW), prevWVP);');
+	}
+	else {
+		node_shader_write(vert, 'prevwvpposition = mul(vec4(pos.xyz, 1.0), prevWVP);');
+	}
+
+	node_shader_add_out(frag, 'vec4 fragColor[3]');
+	frag.n = true;
+	node_shader_add_function(frag, str_pack_float_int16);
+
+	if (context_raw.tool == workspace_tool_t.COLORID) {
+		texture_count++;
+		node_shader_add_uniform(frag, 'sampler2D texcolorid', '_texcolorid');
+		node_shader_write(frag, 'fragColor[0] = vec4(n.xy, 1.0, packFloatInt16(0.0, uint(0)));');
+		node_shader_write(frag, 'vec3 idcol = pow(textureLod(texcolorid, texCoord, 0.0).rgb, vec3(2.2, 2.2, 2.2));');
+		node_shader_write(frag, 'fragColor[1] = vec4(idcol.rgb, 1.0);'); // occ
+	}
+	else {
+		node_shader_add_function(frag, str_octahedron_wrap);
+		node_shader_add_function(frag, str_cotangent_frame);
+		if (layerPass > 0) {
+			node_shader_add_uniform(frag, 'sampler2D gbuffer0');
+			node_shader_add_uniform(frag, 'sampler2D gbuffer1');
+			node_shader_add_uniform(frag, 'sampler2D gbuffer2');
+			node_shader_write(frag, 'vec2 fragcoord = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
+			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+			node_shader_write(frag, 'fragcoord.y = 1.0 - fragcoord.y;');
+			///end
+			node_shader_write(frag, 'vec4 gbuffer0_sample = textureLod(gbuffer0, fragcoord, 0.0);');
+			node_shader_write(frag, 'vec4 gbuffer1_sample = textureLod(gbuffer1, fragcoord, 0.0);');
+			node_shader_write(frag, 'vec4 gbuffer2_sample = textureLod(gbuffer2, fragcoord, 0.0);');
+			node_shader_write(frag, 'vec3 basecol = gbuffer0_sample.rgb;');
+			node_shader_write(frag, 'float roughness = gbuffer2_sample.g;');
+			node_shader_write(frag, 'float metallic = gbuffer2_sample.b;');
+			node_shader_write(frag, 'float occlusion = gbuffer2_sample.r;');
+			node_shader_write(frag, 'float opacity = 1.0;//gbuffer0_sample.a;');
+			node_shader_write(frag, 'float matid = gbuffer1_sample.a;');
+			node_shader_write(frag, 'vec3 ntex = gbuffer1_sample.rgb;');
+			node_shader_write(frag, 'float height = gbuffer2_sample.a;');
+		}
+		else {
+			node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
+			node_shader_write(frag, 'float roughness = 0.0;');
+			node_shader_write(frag, 'float metallic = 0.0;');
+			node_shader_write(frag, 'float occlusion = 1.0;');
+			node_shader_write(frag, 'float opacity = 1.0;');
+			node_shader_write(frag, 'float matid = 0.0;');
+			node_shader_write(frag, 'vec3 ntex = vec3(0.5, 0.5, 1.0);');
+			node_shader_write(frag, 'float height = 0.0;');
+		}
+		node_shader_write(frag, 'vec4 texpaint_sample = vec4(0.0, 0.0, 0.0, 1.0);');
+		node_shader_write(frag, 'vec4 texpaint_nor_sample;');
+		node_shader_write(frag, 'vec4 texpaint_pack_sample;');
+		node_shader_write(frag, 'float texpaint_opac;');
+
+		if (make_material_height_used) {
+			node_shader_write(frag, 'float height0 = 0.0;');
+			node_shader_write(frag, 'float height1 = 0.0;');
+			node_shader_write(frag, 'float height2 = 0.0;');
+			node_shader_write(frag, 'float height3 = 0.0;');
+		}
+
+		if (context_raw.draw_wireframe) {
+			texture_count++;
+			node_shader_add_uniform(frag, 'sampler2D texuvmap', '_texuvmap');
+		}
+
+		if (context_raw.viewport_mode == viewport_mode_t.MASK && slot_layer_get_masks(context_raw.layer) != null) {
+			for (let m of slot_layer_get_masks(context_raw.layer)) {
+				if (!slot_layer_is_visible(m)) continue;
+				texture_count++;
+				node_shader_add_uniform(frag, 'sampler2D texpaint_view_mask' + m.id, '_texpaint' + project_layers.indexOf(m));
+			}
+		}
+
+		if (context_raw.viewport_mode == viewport_mode_t.LIT && context_raw.render_mode == render_mode_t.FORWARD) {
+			texture_count += 4;
+			node_shader_add_uniform(frag, 'sampler2D senvmapBrdf', "$brdf.k");
+			node_shader_add_uniform(frag, 'sampler2D senvmapRadiance', '_envmap_radiance');
+			node_shader_add_uniform(frag, 'sampler2D sltcMat', '_ltcMat');
+			node_shader_add_uniform(frag, 'sampler2D sltcMag', '_ltcMag');
+		}
+
+		// Get layers for this pass
+		make_mesh_layer_pass_count = 1;
+		let layers: SlotLayerRaw[] = [];
+		let start_count: i32 = texture_count;
+		let is_material_tool: bool = context_raw.tool == workspace_tool_t.MATERIAL;
+		for (let l of project_layers) {
+			if (is_material_tool && l != context_raw.layer) continue;
+			if (!slot_layer_is_layer(l) || !slot_layer_is_visible(l)) continue;
+
+			let count: i32 = 3;
+			let masks: SlotLayerRaw[] = slot_layer_get_masks(l);
+			if (masks != null) count += masks.length;
+			texture_count += count;
+			if (texture_count >= make_mesh_get_max_textures()) {
+				texture_count = start_count + count + 3; // gbuffer0_copy, gbuffer1_copy, gbuffer2_copy
+				make_mesh_layer_pass_count++;
+			}
+			if (layerPass == make_mesh_layer_pass_count - 1) {
+				layers.push(l);
+			}
+		}
+
+		let last_pass: bool = layerPass == make_mesh_layer_pass_count - 1;
+
+		for (let l of layers) {
+			if (slot_layer_get_object_mask(l) > 0) {
+				node_shader_add_uniform(frag, 'int uid', '_uid');
+				if (slot_layer_get_object_mask(l) > project_paint_objects.length) { // Atlas
+					let visibles: mesh_object_t[] = project_get_atlas_objects(slot_layer_get_object_mask(l));
+					node_shader_write(frag, 'if (');
+					for (let i: i32 = 0; i < visibles.length; ++i) {
+						if (i > 0) node_shader_write(frag, ' || ');
+						node_shader_write(frag, `${visibles[i].base.uid} == uid`);
+					}
+					node_shader_write(frag, ') {');
+				}
+				else { // Object mask
+					let uid: i32 = project_paint_objects[slot_layer_get_object_mask(l) - 1].base.uid;
+					node_shader_write(frag, `if (${uid} == uid) {`);
+				}
+			}
+
+			node_shader_add_shared_sampler(frag, 'sampler2D texpaint' + l.id);
+			node_shader_write(frag, 'texpaint_sample = textureLodShared(texpaint' + l.id + ', texCoord, 0.0);');
+			node_shader_write(frag, 'texpaint_opac = texpaint_sample.a;');
+			// ///if (krom_direct3d12 || krom_vulkan)
+			// if (raw.viewportMode == ViewLit) {
+			// 	write(frag, 'if (texpaint_opac < 0.1) discard;');
+			// }
+			// ///end
+
+			let masks: SlotLayerRaw[] = slot_layer_get_masks(l);
+			if (masks != null) {
+				let has_visible: bool = false;
+				for (let m of masks) {
+					if (slot_layer_is_visible(m)) {
+						has_visible = true;
+						break;
+					}
+				}
+				if (has_visible) {
+					let texpaint_mask: string = 'texpaint_mask' + l.id;
+					node_shader_write(frag, `float ${texpaint_mask} = 0.0;`);
+					for (let m of masks) {
+						if (!slot_layer_is_visible(m)) continue;
+						node_shader_add_shared_sampler(frag, 'sampler2D texpaint' + m.id);
+						node_shader_write(frag, '{'); // Group mask is sampled across multiple layers
+						node_shader_write(frag, 'float texpaint_mask_sample' + m.id + ' = textureLodShared(texpaint' + m.id + ', texCoord, 0.0).r;');
+						node_shader_write(frag, `${texpaint_mask} = ` + make_material_blend_mode_mask(frag, m.blending, `${texpaint_mask}`, 'texpaint_mask_sample' + m.id, 'float(' + slot_layer_get_opacity(m) + ')') + ';');
+						node_shader_write(frag, '}');
+					}
+					node_shader_write(frag, `texpaint_opac *= clamp(${texpaint_mask}, 0.0, 1.0);`);
+				}
+			}
+
+			if (slot_layer_get_opacity(l) < 1) {
+				node_shader_write(frag, `texpaint_opac *= ${slot_layer_get_opacity(l)};`);
+			}
+
+			if (l.paint_base) {
+				if (l == project_layers[0]) {
+					node_shader_write(frag, 'basecol = texpaint_sample.rgb * texpaint_opac;');
+				}
+				else {
+					node_shader_write(frag, 'basecol = ' + make_material_blend_mode(frag, l.blending, 'basecol', 'texpaint_sample.rgb', 'texpaint_opac') + ';');
+				}
+			}
+
+			if (l.paint_nor || make_material_emis_used) {
+				node_shader_add_shared_sampler(frag, 'sampler2D texpaint_nor' + l.id);
+				node_shader_write(frag, 'texpaint_nor_sample = textureLodShared(texpaint_nor' + l.id + ', texCoord, 0.0);');
+
+				if (make_material_emis_used) {
+					node_shader_write(frag, 'if (texpaint_opac > 0.0) matid = texpaint_nor_sample.a;');
+				}
+
+				if (l.paint_nor) {
+					if (l.paint_nor_blend) {
+						// Whiteout blend
+						node_shader_write(frag, '{');
+						node_shader_write(frag, 'vec3 n1 = ntex * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
+						node_shader_write(frag, 'vec3 n2 = mix(vec3(0.5, 0.5, 1.0), texpaint_nor_sample.rgb, texpaint_opac) * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
+						node_shader_write(frag, 'ntex = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
+						node_shader_write(frag, '}');
+					}
+					else {
+						node_shader_write(frag, 'ntex = mix(ntex, texpaint_nor_sample.rgb, texpaint_opac);');
+					}
+				}
+			}
+
+			if (l.paint_occ || l.paint_rough || l.paint_met || (l.paint_height && make_material_height_used)) {
+				node_shader_add_shared_sampler(frag, 'sampler2D texpaint_pack' + l.id);
+				node_shader_write(frag, 'texpaint_pack_sample = textureLodShared(texpaint_pack' + l.id + ', texCoord, 0.0);');
+
+				if (l.paint_occ) {
+					node_shader_write(frag, 'occlusion = mix(occlusion, texpaint_pack_sample.r, texpaint_opac);');
+				}
+				if (l.paint_rough) {
+					node_shader_write(frag, 'roughness = mix(roughness, texpaint_pack_sample.g, texpaint_opac);');
+				}
+				if (l.paint_met) {
+					node_shader_write(frag, 'metallic = mix(metallic, texpaint_pack_sample.b, texpaint_opac);');
+				}
+				if (l.paint_height && make_material_height_used) {
+					let assign: string = l.paint_height_blend ? "+=" : "=";
+					node_shader_write(frag, `height ${assign} texpaint_pack_sample.a * texpaint_opac;`);
+					node_shader_write(frag, '{');
+					node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
+					node_shader_write(frag, 'float tex_step = 1.0 / texpaintSize.x;');
+					node_shader_write(frag, `height0 ${assign} textureLodShared(texpaint_pack` + l.id + ', vec2(texCoord.x - tex_step, texCoord.y), 0.0).a * texpaint_opac;');
+					node_shader_write(frag, `height1 ${assign} textureLodShared(texpaint_pack` + l.id + ', vec2(texCoord.x + tex_step, texCoord.y), 0.0).a * texpaint_opac;');
+					node_shader_write(frag, `height2 ${assign} textureLodShared(texpaint_pack` + l.id + ', vec2(texCoord.x, texCoord.y - tex_step), 0.0).a * texpaint_opac;');
+					node_shader_write(frag, `height3 ${assign} textureLodShared(texpaint_pack` + l.id + ', vec2(texCoord.x, texCoord.y + tex_step), 0.0).a * texpaint_opac;');
+					node_shader_write(frag, '}');
+				}
+			}
+
+			if (slot_layer_get_object_mask(l) > 0) {
+				node_shader_write(frag, '}');
+			}
+		}
+
+		if (last_pass && context_raw.draw_texels) {
+			node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
+			node_shader_write(frag, 'vec2 texel0 = texCoord * texpaintSize * 0.01;');
+			node_shader_write(frag, 'vec2 texel1 = texCoord * texpaintSize * 0.1;');
+			node_shader_write(frag, 'vec2 texel2 = texCoord * texpaintSize;');
+			node_shader_write(frag, 'basecol *= max(float(mod(int(texel0.x), 2.0) == mod(int(texel0.y), 2.0)), 0.9);');
+			node_shader_write(frag, 'basecol *= max(float(mod(int(texel1.x), 2.0) == mod(int(texel1.y), 2.0)), 0.9);');
+			node_shader_write(frag, 'basecol *= max(float(mod(int(texel2.x), 2.0) == mod(int(texel2.y), 2.0)), 0.9);');
+		}
+
+		if (last_pass && context_raw.draw_wireframe) {
+			node_shader_write(frag, 'basecol *= 1.0 - textureLod(texuvmap, texCoord, 0.0).r;');
+		}
+
+		if (make_material_height_used) {
+			node_shader_write(frag, 'if (height > 0.0) {');
+			// write(frag, 'float height_dx = dFdx(height * 16.0);');
+			// write(frag, 'float height_dy = dFdy(height * 16.0);');
+			node_shader_write(frag, 'float height_dx = height0 - height1;');
+			node_shader_write(frag, 'float height_dy = height2 - height3;');
+			// Whiteout blend
+			node_shader_write(frag, 'vec3 n1 = ntex * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
+			node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
+			node_shader_write(frag, 'ntex = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
+			node_shader_write(frag, '}');
+		}
+
+		if (!last_pass) {
+			node_shader_write(frag, 'fragColor[0] = vec4(basecol, opacity);');
+			node_shader_write(frag, 'fragColor[1] = vec4(ntex, matid);');
+			node_shader_write(frag, 'fragColor[2] = vec4(occlusion, roughness, metallic, height);');
+			parser_material_finalize(con_mesh);
+			con_mesh.data.shader_from_source = true;
+			con_mesh.data.vertex_shader = node_shader_get(vert);
+			con_mesh.data.fragment_shader = node_shader_get(frag);
+			return con_mesh;
+		}
+
+		frag.vvec = true;
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
+		///else
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
+		///end
+		node_shader_write(frag, 'n = ntex * 2.0 - 1.0;');
+		node_shader_write(frag, 'n.y = -n.y;');
+		node_shader_write(frag, 'n = normalize(mul(n, TBN));');
+
+		if (context_raw.viewport_mode == viewport_mode_t.LIT || context_raw.viewport_mode == viewport_mode_t.PATH_TRACE) {
+			node_shader_write(frag, 'basecol = pow(basecol, vec3(2.2, 2.2, 2.2));');
+
+			if (context_raw.viewport_shader != null) {
+				let color: string = context_raw.viewport_shader(frag);
+				node_shader_write(frag, `fragColor[1] = vec4(${color}, 1.0);`);
+			}
+			else if (context_raw.render_mode == render_mode_t.FORWARD && context_raw.viewport_mode != viewport_mode_t.PATH_TRACE) {
+				frag.wposition = true;
+				node_shader_write(frag, 'vec3 albedo = mix(basecol, vec3(0.0, 0.0, 0.0), metallic);');
+				node_shader_write(frag, 'vec3 f0 = mix(vec3(0.04, 0.04, 0.04), basecol, metallic);');
+				frag.vvec = true;
+				node_shader_write(frag, 'float dotNV = max(0.0, dot(n, vVec));');
+				node_shader_write(frag, 'vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(roughness, 1.0 - dotNV) * 256.0), 0).xy;');
+				node_shader_add_uniform(frag, 'int envmapNumMipmaps', '_envmap_num_mipmaps');
+				node_shader_add_uniform(frag, 'vec4 envmapData', '_envmapData'); // angle, sin(angle), cos(angle), strength
+				node_shader_write(frag, 'vec3 wreflect = reflect(-vVec, n);');
+				node_shader_write(frag, 'float envlod = roughness * float(envmapNumMipmaps);');
+				node_shader_add_function(frag, str_envmap_equirect);
+				node_shader_write(frag, 'vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(wreflect, envmapData.x), envlod).rgb;');
+				node_shader_add_uniform(frag, 'vec3 lightArea0', '_light_area0');
+				node_shader_add_uniform(frag, 'vec3 lightArea1', '_light_area1');
+				node_shader_add_uniform(frag, 'vec3 lightArea2', '_light_area2');
+				node_shader_add_uniform(frag, 'vec3 lightArea3', '_light_area3');
+				node_shader_add_function(frag, str_ltc_evaluate);
+				node_shader_add_uniform(frag, 'vec3 lightPos', '_point_pos');
+				node_shader_add_uniform(frag, 'vec3 lightColor', '_point_color');
+				// write(frag, 'float dotNL = max(dot(n, normalize(lightPos - wposition)), 0.0);');
+				// write(frag, 'vec3 direct = albedo * dotNL;');
+				node_shader_write(frag, 'float ldist = distance(wposition, lightPos);');
+				node_shader_write(frag, 'const float LUT_SIZE = 64.0;');
+				node_shader_write(frag, 'const float LUT_SCALE = (LUT_SIZE - 1.0) / LUT_SIZE;');
+				node_shader_write(frag, 'const float LUT_BIAS = 0.5 / LUT_SIZE;');
+				node_shader_write(frag, 'float theta = acos(dotNV);');
+				node_shader_write(frag, 'vec2 tuv = vec2(roughness, theta / (0.5 * 3.14159265));');
+				node_shader_write(frag, 'tuv = tuv * LUT_SCALE + LUT_BIAS;');
+				node_shader_write(frag, 'vec4 t = textureLod(sltcMat, tuv, 0.0);');
+				node_shader_write(frag, 'mat3 minv = mat3(vec3(1.0, 0.0, t.y), vec3(0.0, t.z, 0.0), vec3(t.w, 0.0, t.x));');
+				node_shader_write(frag, 'float ltcspec = ltcEvaluate(n, vVec, dotNV, wposition, minv, lightArea0, lightArea1, lightArea2, lightArea3);');
+				node_shader_write(frag, 'ltcspec *= textureLod(sltcMag, tuv, 0.0).a;');
+				node_shader_write(frag, 'mat3 mident = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);');
+				node_shader_write(frag, 'float ltcdiff = ltcEvaluate(n, vVec, dotNV, wposition, mident, lightArea0, lightArea1, lightArea2, lightArea3);');
+				node_shader_write(frag, 'vec3 direct = albedo * ltcdiff + ltcspec * 0.05;');
+				node_shader_write(frag, 'direct *= lightColor * (1.0 / (ldist * ldist));');
+
+				node_shader_add_uniform(frag, 'vec4 shirr[7]', '_envmap_irradiance');
+				node_shader_add_function(frag, str_sh_irradiance());
+				node_shader_write(frag, 'vec3 indirect = albedo * (shIrradiance(vec3(n.x * envmapData.z - n.y * envmapData.y, n.x * envmapData.y + n.y * envmapData.z, n.z), shirr) / 3.14159265);');
+				node_shader_write(frag, 'indirect += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5;');
+				node_shader_write(frag, 'indirect *= envmapData.w * occlusion;');
+				node_shader_write(frag, 'fragColor[1] = vec4(direct + indirect, 1.0);');
+			}
+			else { // Deferred, Pathtraced
+				if (make_material_emis_used) node_shader_write(frag, 'if (int(matid * 255.0) % 3 == 1) basecol *= 10.0;'); // Boost for bloom
+				node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
+			}
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.BASE_COLOR && context_raw.layer.paint_base) {
+			node_shader_write(frag, 'fragColor[1] = vec4(basecol, 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.NORMAL_MAP && context_raw.layer.paint_nor) {
+			node_shader_write(frag, 'fragColor[1] = vec4(ntex.rgb, 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.OCCLUSION && context_raw.layer.paint_occ) {
+			node_shader_write(frag, 'fragColor[1] = vec4(vec3(occlusion, occlusion, occlusion), 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.ROUGHNESS && context_raw.layer.paint_rough) {
+			node_shader_write(frag, 'fragColor[1] = vec4(vec3(roughness, roughness, roughness), 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.METALLIC && context_raw.layer.paint_met) {
+			node_shader_write(frag, 'fragColor[1] = vec4(vec3(metallic, metallic, metallic), 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.OPACITY && context_raw.layer.paint_opac) {
+			node_shader_write(frag, 'fragColor[1] = vec4(vec3(texpaint_sample.a, texpaint_sample.a, texpaint_sample.a), 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.HEIGHT && context_raw.layer.paint_height) {
+			node_shader_write(frag, 'fragColor[1] = vec4(vec3(height, height, height), 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.EMISSION) {
+			node_shader_write(frag, 'float emis = int(matid * 255.0) % 3 == 1 ? 1.0 : 0.0;');
+			node_shader_write(frag, 'fragColor[1] = vec4(vec3(emis, emis, emis), 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.SUBSURFACE) {
+			node_shader_write(frag, 'float subs = int(matid * 255.0) % 3 == 2 ? 1.0 : 0.0;');
+			node_shader_write(frag, 'fragColor[1] = vec4(vec3(subs, subs, subs), 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.TEXCOORD) {
+			node_shader_write(frag, 'fragColor[1] = vec4(texCoord, 0.0, 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.OBJECT_NORMAL) {
+			frag.nattr = true;
+			node_shader_write(frag, 'fragColor[1] = vec4(nAttr, 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.MATERIAL_ID) {
+			node_shader_add_shared_sampler(frag, 'sampler2D texpaint_nor' + context_raw.layer.id);
+			node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
+			node_shader_write(frag, 'float sample_matid = texelFetch(texpaint_nor' + context_raw.layer.id + ', ivec2(texCoord * texpaintSize), 0).a + 1.0 / 255.0;');
+			node_shader_write(frag, 'float matid_r = fract(sin(dot(vec2(sample_matid, sample_matid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
+			node_shader_write(frag, 'float matid_g = fract(sin(dot(vec2(sample_matid * 20.0, sample_matid), vec2(12.9898, 78.233))) * 43758.5453);');
+			node_shader_write(frag, 'float matid_b = fract(sin(dot(vec2(sample_matid, sample_matid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
+			node_shader_write(frag, 'fragColor[1] = vec4(matid_r, matid_g, matid_b, 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.OBJECT_ID) {
+			node_shader_add_uniform(frag, 'float objectId', '_objectId');
+			node_shader_write(frag, 'float obid = objectId + 1.0 / 255.0;');
+			node_shader_write(frag, 'float id_r = fract(sin(dot(vec2(obid, obid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
+			node_shader_write(frag, 'float id_g = fract(sin(dot(vec2(obid * 20.0, obid), vec2(12.9898, 78.233))) * 43758.5453);');
+			node_shader_write(frag, 'float id_b = fract(sin(dot(vec2(obid, obid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
+			node_shader_write(frag, 'fragColor[1] = vec4(id_r, id_g, id_b, 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.MASK && (slot_layer_get_masks(context_raw.layer) != null || slot_layer_is_mask(context_raw.layer))) {
+			if (slot_layer_is_mask(context_raw.layer)) {
+				node_shader_write(frag, 'float mask_view = textureLodShared(texpaint' + context_raw.layer.id + ', texCoord, 0.0).r;');
+			}
+			else {
+				node_shader_write(frag, 'float mask_view = 0.0;');
+				for (let m of slot_layer_get_masks(context_raw.layer)) {
+					if (!slot_layer_is_visible(m)) continue;
+					node_shader_write(frag, 'float mask_sample' + m.id + ' = textureLodShared(texpaint_view_mask' + m.id + ', texCoord, 0.0).r;');
+					node_shader_write(frag, 'mask_view = ' + make_material_blend_mode_mask(frag, m.blending, 'mask_view', 'mask_sample' + m.id, 'float(' + slot_layer_get_opacity(m) + ')') + ';');
+				}
+			}
+			node_shader_write(frag, 'fragColor[1] = vec4(mask_view, mask_view, mask_view, 1.0);');
+		}
+		else {
+			node_shader_write(frag, 'fragColor[1] = vec4(1.0, 0.0, 1.0, 1.0);'); // Pink
+		}
+
+		if (context_raw.viewport_mode != viewport_mode_t.LIT && context_raw.viewport_mode != viewport_mode_t.PATH_TRACE) {
+			node_shader_write(frag, 'fragColor[1].rgb = pow(fragColor[1].rgb, vec3(2.2, 2.2, 2.2));');
+		}
+
+		node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
+		node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
+		node_shader_write(frag, 'fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, uint(int(matid * 255.0) % 3)));');
+	}
+
+	node_shader_write(frag, 'vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
+	node_shader_write(frag, 'vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;');
+	node_shader_write(frag, 'fragColor[2] = vec4(posa - posb, texCoord.xy);');
+
+	parser_material_finalize(con_mesh);
+	con_mesh.data.shader_from_source = true;
+	con_mesh.data.vertex_shader = node_shader_get(vert);
+	con_mesh.data.fragment_shader = node_shader_get(frag);
+	return con_mesh;
+}
+
+function make_mesh_get_max_textures(): i32 {
+	///if krom_direct3d11
+	return 128 - 66;
+	///else
+	return 16 - 3; // G4onG5/G4.c.h MAX_TEXTURES
+	///end
+}

+ 156 - 0
armorpaint/Sources/make_mesh_preview.ts

@@ -0,0 +1,156 @@
+
+let make_mesh_preview_opacity_discard_decal: f32 = 0.05;
+
+function make_mesh_preview_run(data: material_t, matcon: material_context_t): NodeShaderContextRaw {
+	let context_id: string = "mesh";
+	let con_mesh: NodeShaderContextRaw = node_shader_context_create(data, {
+		name: context_id,
+		depth_write: true,
+		compare_mode: "less",
+		cull_mode: "clockwise",
+		vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
+		color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
+		depth_attachment: "DEPTH32"
+	});
+
+	let vert: NodeShaderRaw = node_shader_context_make_vert(con_mesh);
+	let frag: NodeShaderRaw = node_shader_context_make_frag(con_mesh);
+	frag.ins = vert.outs;
+	let pos: string = "pos";
+
+	///if arm_skin
+	let skin: bool = mesh_data_get_vertex_array(context_raw.paint_object.data, "bone") != null;
+	if (skin) {
+		pos = "spos";
+		node_shader_context_add_elem(con_mesh, "bone", 'short4norm');
+		node_shader_context_add_elem(con_mesh, "weight", 'short4norm');
+		node_shader_add_function(vert, str_get_skinning_dual_quat);
+		node_shader_add_uniform(vert, 'vec4 skinBones[128 * 2]', '_skin_bones');
+		node_shader_add_uniform(vert, 'float posUnpack', '_pos_unpack');
+		node_shader_write_attrib(vert, 'vec4 skinA;');
+		node_shader_write_attrib(vert, 'vec4 skinB;');
+		node_shader_write_attrib(vert, 'getSkinningDualQuat(ivec4(bone * 32767), weight, skinA, skinB);');
+		node_shader_write_attrib(vert, 'vec3 spos = pos.xyz;');
+		node_shader_write_attrib(vert, 'spos.xyz *= posUnpack;');
+		node_shader_write_attrib(vert, 'spos.xyz += 2.0 * cross(skinA.xyz, cross(skinA.xyz, spos.xyz) + skinA.w * spos.xyz);');
+		node_shader_write_attrib(vert, 'spos.xyz += 2.0 * (skinA.w * skinB.xyz - skinB.w * skinA.xyz + cross(skinA.xyz, skinB.xyz));');
+		node_shader_write_attrib(vert, 'spos.xyz /= posUnpack;');
+	}
+	///end
+
+	node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
+	node_shader_write_attrib(vert, `gl_Position = mul(vec4(${pos}.xyz, 1.0), WVP);`);
+
+	let brush_scale: string = (context_raw.brush_scale * context_raw.brush_nodes_scale) + "";
+	node_shader_add_out(vert, 'vec2 texCoord');
+	node_shader_write_attrib(vert, `texCoord = tex * float(${brush_scale});`);
+
+	let decal: bool = context_raw.decal_preview;
+	parser_material_sample_keep_aspect = decal;
+	parser_material_sample_uv_scale = brush_scale;
+	parser_material_parse_height = make_material_height_used;
+	parser_material_parse_height_as_channel = true;
+	let sout: shader_out_t = parser_material_parse(ui_nodes_get_canvas_material(), con_mesh, vert, frag, matcon);
+	parser_material_parse_height = false;
+	parser_material_parse_height_as_channel = false;
+	parser_material_sample_keep_aspect = false;
+	let base: string = sout.out_basecol;
+	let rough: string = sout.out_roughness;
+	let met: string = sout.out_metallic;
+	let occ: string = sout.out_occlusion;
+	let opac: string = sout.out_opacity;
+	let height: string = sout.out_height;
+	let nortan: string = parser_material_out_normaltan;
+	node_shader_write(frag, `vec3 basecol = pow(${base}, vec3(2.2, 2.2, 2.2));`);
+	node_shader_write(frag, `float roughness = ${rough};`);
+	node_shader_write(frag, `float metallic = ${met};`);
+	node_shader_write(frag, `float occlusion = ${occ};`);
+	node_shader_write(frag, `float opacity = ${opac};`);
+	node_shader_write(frag, `vec3 nortan = ${nortan};`);
+	node_shader_write(frag, `float height = ${height};`);
+
+	// parse_height_as_channel = false;
+	// write(vert, `float vheight = ${height};`);
+	// add_out(vert, 'float height');
+	// write(vert, 'height = vheight;');
+	// let displaceStrength: f32 = 0.1;
+	// if (heightUsed && displaceStrength > 0.0) {
+	// 	write(vert, `vec3 pos2 = ${pos}.xyz + vec3(nor.xy, pos.w) * vec3(${height}, ${height}, ${height}) * vec3(${displaceStrength}, ${displaceStrength}, ${displaceStrength});`);
+	// 	write(vert, 'gl_Position = mul(vec4(pos2.xyz, 1.0), WVP);');
+	// }
+
+	if (decal) {
+		if (context_raw.tool == workspace_tool_t.TEXT) {
+			node_shader_add_uniform(frag, 'sampler2D textexttool', '_textexttool');
+			node_shader_write(frag, `opacity *= textureLod(textexttool, texCoord / float(${brush_scale}), 0.0).r;`);
+		}
+	}
+	if (decal) {
+		let opac: f32 = make_mesh_preview_opacity_discard_decal;
+		node_shader_write(frag, `if (opacity < ${opac}) discard;`);
+	}
+
+	node_shader_add_out(frag, 'vec4 fragColor[3]');
+	frag.n = true;
+
+	node_shader_add_function(frag, str_pack_float_int16);
+	node_shader_add_function(frag, str_cotangent_frame);
+	node_shader_add_function(frag, str_octahedron_wrap);
+
+	if (make_material_height_used) {
+		node_shader_write(frag, 'if (height > 0.0) {');
+		node_shader_write(frag, 'float height_dx = dFdx(height * 2.0);');
+		node_shader_write(frag, 'float height_dy = dFdy(height * 2.0);');
+		// write(frag, 'float height_dx = height0 - height1;');
+		// write(frag, 'float height_dy = height2 - height3;');
+		// Whiteout blend
+		node_shader_write(frag, 'vec3 n1 = nortan * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
+		node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
+		node_shader_write(frag, 'nortan = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
+		node_shader_write(frag, '}');
+	}
+
+	// Apply normal channel
+	if (decal) {
+		// TODO
+	}
+	else {
+		frag.vvec = true;
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
+		///else
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
+		///end
+		node_shader_write(frag, 'n = nortan * 2.0 - 1.0;');
+		node_shader_write(frag, 'n.y = -n.y;');
+		node_shader_write(frag, 'n = normalize(mul(n, TBN));');
+	}
+
+	node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
+	node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
+	// uint matid = 0;
+
+	if (decal) {
+		node_shader_write(frag, 'fragColor[0] = vec4(n.x, n.y, roughness, packFloatInt16(metallic, uint(0)));'); // metallic/matid
+		node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
+	}
+	else {
+		node_shader_write(frag, 'fragColor[0] = vec4(n.x, n.y, mix(1.0, roughness, opacity), packFloatInt16(mix(1.0, metallic, opacity), uint(0)));'); // metallic/matid
+		node_shader_write(frag, 'fragColor[1] = vec4(mix(vec3(0.0, 0.0, 0.0), basecol, opacity), occlusion);');
+	}
+	node_shader_write(frag, 'fragColor[2] = vec4(0.0, 0.0, 0.0, 0.0);'); // veloc
+
+	parser_material_finalize(con_mesh);
+
+	///if arm_skin
+	if (skin) {
+		node_shader_write(vert, 'wnormal = normalize(mul(vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w)), N));');
+	}
+	///end
+
+	con_mesh.data.shader_from_source = true;
+	con_mesh.data.vertex_shader = node_shader_get(vert);
+	con_mesh.data.fragment_shader = node_shader_get(frag);
+
+	return con_mesh;
+}

+ 68 - 0
armorpaint/Sources/make_node_preview.ts

@@ -0,0 +1,68 @@
+
+function make_node_preview_run(data: material_t, matcon: material_context_t, node: zui_node_t, group: zui_node_canvas_t, parents: zui_node_t[]): NodeShaderContextRaw {
+	let context_id: string = "mesh";
+	let con_mesh: NodeShaderContextRaw = node_shader_context_create(data, {
+		name: context_id,
+		depth_write: false,
+		compare_mode: "always",
+		cull_mode: "clockwise",
+		vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}, {name: "col", data: "short4norm"}],
+		color_attachments: ["RGBA32"]
+	});
+
+	con_mesh.allow_vcols = true;
+	let vert: NodeShaderRaw = node_shader_context_make_vert(con_mesh);
+	let frag: NodeShaderRaw = node_shader_context_make_frag(con_mesh);
+	frag.ins = vert.outs;
+
+	node_shader_write_attrib(vert, 'gl_Position = vec4(pos.xy * 3.0, 0.0, 1.0);'); // Pos unpack
+	node_shader_write_attrib(vert, 'const vec2 madd = vec2(0.5, 0.5);');
+	node_shader_add_out(vert, 'vec2 texCoord');
+	node_shader_write_attrib(vert, 'texCoord = gl_Position.xy * madd + madd;');
+	///if (!krom_opengl)
+	node_shader_write_attrib(vert, 'texCoord.y = 1.0 - texCoord.y;');
+	///end
+
+	parser_material_init();
+	parser_material_canvases = [context_raw.material.canvas];
+	parser_material_nodes = context_raw.material.canvas.nodes;
+	parser_material_links = context_raw.material.canvas.links;
+	if (group != null) {
+		parser_material_push_group(group);
+		parser_material_parents = parents;
+	}
+	let links: zui_node_link_t[] = parser_material_links;
+	let link: zui_node_link_t = { id: zui_get_link_id(links), from_id: node.id, from_socket: context_raw.node_preview_socket, to_id: -1, to_socket: -1 };
+	links.push(link);
+
+	parser_material_con = con_mesh;
+	parser_material_vert = vert;
+	parser_material_frag = frag;
+	parser_material_curshader = frag;
+	parser_material_matcon = matcon;
+
+	parser_material_transform_color_space = false;
+	let res: string = parser_material_write_result(link);
+	parser_material_transform_color_space = true;
+	let st: string = node.outputs[link.from_socket].type;
+	if (st != "RGB" && st != "RGBA" && st != "VECTOR") {
+		res = parser_material_to_vec3(res);
+	}
+	array_remove(links, link);
+
+	node_shader_add_out(frag, 'vec4 fragColor');
+	node_shader_write(frag, `vec3 basecol = ${res};`);
+	node_shader_write(frag, 'fragColor = vec4(basecol.rgb, 1.0);');
+
+	// frag.ndcpos = true;
+	// add_out(vert, 'vec4 ndc');
+	// write_attrib(vert, 'ndc = vec4(gl_Position.xyz * vec3(0.5, 0.5, 0.0) + vec3(0.5, 0.5, 0.0), 1.0);');
+
+	parser_material_finalize(con_mesh);
+
+	con_mesh.data.shader_from_source = true;
+	con_mesh.data.vertex_shader = node_shader_get(vert);
+	con_mesh.data.fragment_shader = node_shader_get(frag);
+
+	return con_mesh;
+}

+ 480 - 0
armorpaint/Sources/make_paint.ts

@@ -0,0 +1,480 @@
+
+///if (is_paint || is_forge)
+
+function make_paint_is_raytraced_bake(): bool {
+	///if (krom_direct3d12 || krom_vulkan || krom_metal)
+	return context_raw.bake_type == bake_type_t.INIT;
+	///else
+	return false;
+	///end
+}
+
+function make_paint_run(data: material_t, matcon: material_context_t): NodeShaderContextRaw {
+	let context_id: string = "paint";
+
+	let con_paint: NodeShaderContextRaw = node_shader_context_create(data, {
+		name: context_id,
+		depth_write: false,
+		compare_mode: "always", // TODO: align texcoords winding order
+		// cull_mode: "counter_clockwise",
+		cull_mode: "none",
+		vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
+		color_attachments:
+			context_raw.tool == workspace_tool_t.COLORID ? ["RGBA32"] :
+			(context_raw.tool == workspace_tool_t.PICKER && context_raw.pick_pos_nor_tex) ? ["RGBA128", "RGBA128"] :
+			(context_raw.tool == workspace_tool_t.PICKER || context_raw.tool == workspace_tool_t.MATERIAL) ? ["RGBA32", "RGBA32", "RGBA32", "RGBA32"] :
+			(context_raw.tool == workspace_tool_t.BAKE && make_paint_is_raytraced_bake()) ? ["RGBA64", "RGBA64"] :
+				["RGBA32", "RGBA32", "RGBA32", "R8"]
+	});
+
+	con_paint.data.color_writes_red = [true, true, true, true];
+	con_paint.data.color_writes_green = [true, true, true, true];
+	con_paint.data.color_writes_blue = [true, true, true, true];
+	con_paint.data.color_writes_alpha = [true, true, true, true];
+	con_paint.allow_vcols = mesh_data_get_vertex_array(context_raw.paint_object.data, 'col') != null;
+
+	let vert: NodeShaderRaw = node_shader_context_make_vert(con_paint);
+	let frag: NodeShaderRaw = node_shader_context_make_frag(con_paint);
+	frag.ins = vert.outs;
+
+	///if (krom_direct3d12 || krom_vulkan || krom_metal)
+	if (context_raw.tool == workspace_tool_t.BAKE && context_raw.bake_type == bake_type_t.INIT) {
+		// Init raytraced bake
+		make_bake_position_normal(vert, frag);
+		con_paint.data.shader_from_source = true;
+		con_paint.data.vertex_shader = node_shader_get(vert);
+		con_paint.data.fragment_shader = node_shader_get(frag);
+		return con_paint;
+	}
+	///end
+
+	if (context_raw.tool == workspace_tool_t.BAKE) {
+		make_bake_set_color_writes(con_paint);
+	}
+
+	if (context_raw.tool == workspace_tool_t.COLORID || context_raw.tool == workspace_tool_t.PICKER || context_raw.tool == workspace_tool_t.MATERIAL) {
+		make_colorid_picker_run(vert, frag);
+		con_paint.data.shader_from_source = true;
+		con_paint.data.vertex_shader = node_shader_get(vert);
+		con_paint.data.fragment_shader = node_shader_get(frag);
+		return con_paint;
+	}
+
+	let face_fill: bool = context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.FACE;
+	let uv_island_fill: bool = context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.UV_ISLAND;
+	let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
+
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(vert, 'vec2 tpos = vec2(tex.x * 2.0 - 1.0, (1.0 - tex.y) * 2.0 - 1.0);');
+	///else
+	node_shader_write(vert, 'vec2 tpos = vec2(tex.xy * 2.0 - 1.0);');
+	///end
+
+	node_shader_write(vert, 'gl_Position = vec4(tpos, 0.0, 1.0);');
+
+	let decal_layer: bool = context_raw.layer.fill_layer != null && context_raw.layer.uv_type == uv_type_t.PROJECT;
+	if (decal_layer) {
+		node_shader_add_uniform(vert, 'mat4 WVP', '_decalLayerMatrix');
+	}
+	else {
+		node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
+	}
+
+	node_shader_add_out(vert, 'vec4 ndc');
+	node_shader_write_attrib(vert, 'ndc = mul(vec4(pos.xyz, 1.0), WVP);');
+
+	node_shader_write_attrib(frag, 'vec3 sp = vec3((ndc.xyz / ndc.w) * 0.5 + 0.5);');
+	node_shader_write_attrib(frag, 'sp.y = 1.0 - sp.y;');
+	node_shader_write_attrib(frag, 'sp.z -= 0.0001;'); // small bias
+
+	let uv_type: uv_type_t = context_raw.layer.fill_layer != null ? context_raw.layer.uv_type : context_raw.brush_paint;
+	if (uv_type == uv_type_t.PROJECT) frag.ndcpos = true;
+
+	node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
+	node_shader_add_uniform(frag, 'vec4 inplast', '_inputBrushLast');
+	node_shader_add_uniform(frag, 'float aspectRatio', '_aspect_ratio_window');
+	node_shader_write(frag, 'vec2 bsp = sp.xy * 2.0 - 1.0;');
+	node_shader_write(frag, 'bsp.x *= aspectRatio;');
+	node_shader_write(frag, 'bsp = bsp * 0.5 + 0.5;');
+
+	node_shader_add_uniform(frag, 'sampler2D gbufferD');
+
+	node_shader_add_out(frag, 'vec4 fragColor[4]');
+
+	node_shader_add_uniform(frag, 'float brushRadius', '_brushRadius');
+	node_shader_add_uniform(frag, 'float brushOpacity', '_brushOpacity');
+	node_shader_add_uniform(frag, 'float brushHardness', '_brushHardness');
+
+	if (context_raw.tool == workspace_tool_t.BRUSH  ||
+		context_raw.tool == workspace_tool_t.ERASER ||
+		context_raw.tool == workspace_tool_t.CLONE  ||
+		context_raw.tool == workspace_tool_t.BLUR   ||
+		context_raw.tool == workspace_tool_t.SMUDGE   ||
+		context_raw.tool == workspace_tool_t.PARTICLE ||
+		decal) {
+
+		let depth_reject: bool = !context_raw.xray;
+		if (config_raw.brush_3d && !config_raw.brush_depth_reject) depth_reject = false;
+
+		// TODO: sp.z needs to take height channel into account
+		let particle: bool = context_raw.tool == workspace_tool_t.PARTICLE;
+		if (config_raw.brush_3d && !decal && !particle) {
+			if (make_material_height_used || context_raw.sym_x || context_raw.sym_y || context_raw.sym_z) depth_reject = false;
+		}
+
+		if (depth_reject) {
+			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+			node_shader_write(frag, 'if (sp.z > textureLod(gbufferD, sp.xy, 0.0).r + 0.0005) discard;');
+			///else
+			node_shader_write(frag, 'if (sp.z > textureLod(gbufferD, vec2(sp.x, 1.0 - sp.y), 0.0).r + 0.0005) discard;');
+			///end
+		}
+
+		make_brush_run(vert, frag);
+	}
+	else { // Fill, Bake
+		node_shader_write(frag, 'float dist = 0.0;');
+		let angle_fill: bool = context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.ANGLE;
+		if (angle_fill) {
+			node_shader_add_function(frag, str_octahedron_wrap);
+			node_shader_add_uniform(frag, 'sampler2D gbuffer0');
+			node_shader_write(frag, 'vec2 g0 = textureLod(gbuffer0, inp.xy, 0.0).rg;');
+			node_shader_write(frag, 'vec3 wn;');
+			node_shader_write(frag, 'wn.z = 1.0 - abs(g0.x) - abs(g0.y);');
+			node_shader_write(frag, 'wn.xy = wn.z >= 0.0 ? g0.xy : octahedronWrap(g0.xy);');
+			node_shader_write(frag, 'wn = normalize(wn);');
+			frag.n = true;
+			let angle: f32 = context_raw.brush_angle_reject_dot;
+			node_shader_write(frag, `if (dot(wn, n) < ${angle}) discard;`);
+		}
+		let stencil_fill: bool = context_raw.tool == workspace_tool_t.FILL && context_raw.brush_stencil_image != null;
+		if (stencil_fill) {
+			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+			node_shader_write(frag, 'if (sp.z > textureLod(gbufferD, sp.xy, 0.0).r + 0.0005) discard;');
+			///else
+			node_shader_write(frag, 'if (sp.z > textureLod(gbufferD, vec2(sp.x, 1.0 - sp.y), 0.0).r + 0.0005) discard;');
+			///end
+		}
+	}
+
+	if (context_raw.colorid_picked || face_fill || uv_island_fill) {
+		node_shader_add_out(vert, 'vec2 texCoordPick');
+		node_shader_write(vert, 'texCoordPick = tex;');
+		if (context_raw.colorid_picked) {
+			make_discard_color_id(vert, frag);
+		}
+		if (face_fill) {
+			make_discard_face(vert, frag);
+		}
+		else if (uv_island_fill) {
+			make_discard_uv_island(vert, frag);
+		}
+	}
+
+	if (context_raw.picker_mask_handle.position == picker_mask_t.MATERIAL) {
+		make_discard_material_id(vert, frag);
+	}
+
+	make_texcoord_run(vert, frag);
+
+	if (context_raw.tool == workspace_tool_t.CLONE || context_raw.tool == workspace_tool_t.BLUR || context_raw.tool == workspace_tool_t.SMUDGE) {
+		node_shader_add_uniform(frag, 'sampler2D gbuffer2');
+		node_shader_add_uniform(frag, 'vec2 gbufferSize', '_gbufferSize');
+		node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
+		node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
+		node_shader_add_uniform(frag, 'sampler2D texpaint_pack_undo', '_texpaint_pack_undo');
+
+		if (context_raw.tool == workspace_tool_t.CLONE) {
+			make_clone_run(vert, frag);
+		}
+		else { // Blur, Smudge
+			make_blur_run(vert, frag);
+		}
+	}
+	else {
+		parser_material_parse_emission = context_raw.material.paint_emis;
+		parser_material_parse_subsurface = context_raw.material.paint_subs;
+		parser_material_parse_height = context_raw.material.paint_height;
+		parser_material_parse_height_as_channel = true;
+		let uv_type: uv_type_t = context_raw.layer.fill_layer != null ? context_raw.layer.uv_type : context_raw.brush_paint;
+		parser_material_triplanar = uv_type == uv_type_t.TRIPLANAR && !decal;
+		parser_material_sample_keep_aspect = decal;
+		parser_material_sample_uv_scale = 'brushScale';
+		let sout: shader_out_t = parser_material_parse(ui_nodes_get_canvas_material(), con_paint, vert, frag, matcon);
+		parser_material_parse_emission = false;
+		parser_material_parse_subsurface = false;
+		parser_material_parse_height_as_channel = false;
+		parser_material_parse_height = false;
+		let base: string = sout.out_basecol;
+		let rough: string = sout.out_roughness;
+		let met: string = sout.out_metallic;
+		let occ: string = sout.out_occlusion;
+		let nortan: string = parser_material_out_normaltan;
+		let height: string = sout.out_height;
+		let opac: string = sout.out_opacity;
+		let emis: string = sout.out_emission;
+		let subs: string = sout.out_subsurface;
+		node_shader_write(frag, `vec3 basecol = ${base};`);
+		node_shader_write(frag, `float roughness = ${rough};`);
+		node_shader_write(frag, `float metallic = ${met};`);
+		node_shader_write(frag, `float occlusion = ${occ};`);
+		node_shader_write(frag, `vec3 nortan = ${nortan};`);
+		node_shader_write(frag, `float height = ${height};`);
+		node_shader_write(frag, `float mat_opacity = ${opac};`);
+		node_shader_write(frag, 'float opacity = mat_opacity;');
+		if (context_raw.layer.fill_layer == null) {
+			node_shader_write(frag, 'opacity *= brushOpacity;');
+		}
+		if (context_raw.material.paint_emis) {
+			node_shader_write(frag, `float emis = ${emis};`);
+		}
+		if (context_raw.material.paint_subs) {
+			node_shader_write(frag, `float subs = ${subs};`);
+		}
+		if (parseFloat(height) != 0.0 && !make_material_height_used) {
+			make_material_height_used = true;
+			// Height used for the first time, also rebuild vertex shader
+			return make_paint_run(data, matcon);
+		}
+		if (parseFloat(emis) != 0.0) make_material_emis_used = true;
+		if (parseFloat(subs) != 0.0) make_material_subs_used = true;
+	}
+
+	if (context_raw.brush_mask_image != null && context_raw.tool == workspace_tool_t.DECAL) {
+		node_shader_add_uniform(frag, 'sampler2D texbrushmask', '_texbrushmask');
+		node_shader_write(frag, 'vec4 mask_sample = textureLod(texbrushmask, uvsp, 0.0);');
+		if (context_raw.brush_mask_image_is_alpha) {
+			node_shader_write(frag, 'opacity *= mask_sample.a;');
+		}
+		else {
+			node_shader_write(frag, 'opacity *= mask_sample.r * mask_sample.a;');
+		}
+	}
+	else if (context_raw.tool == workspace_tool_t.TEXT) {
+		node_shader_add_uniform(frag, 'sampler2D textexttool', '_textexttool');
+		node_shader_write(frag, 'opacity *= textureLod(textexttool, uvsp, 0.0).r;');
+	}
+
+	if (context_raw.brush_stencil_image != null && (
+		context_raw.tool == workspace_tool_t.BRUSH  ||
+		context_raw.tool == workspace_tool_t.ERASER ||
+		context_raw.tool == workspace_tool_t.FILL ||
+		context_raw.tool == workspace_tool_t.CLONE  ||
+		context_raw.tool == workspace_tool_t.BLUR   ||
+		context_raw.tool == workspace_tool_t.SMUDGE   ||
+		context_raw.tool == workspace_tool_t.PARTICLE ||
+		decal)) {
+		node_shader_add_uniform(frag, 'sampler2D texbrushstencil', '_texbrushstencil');
+		node_shader_add_uniform(frag, 'vec4 stencilTransform', '_stencilTransform');
+		node_shader_write(frag, 'vec2 stencil_uv = vec2((sp.xy - stencilTransform.xy) / stencilTransform.z * vec2(aspectRatio, 1.0));');
+		node_shader_write(frag, 'vec2 stencil_size = vec2(textureSize(texbrushstencil, 0));');
+		node_shader_write(frag, 'float stencil_ratio = stencil_size.y / stencil_size.x;');
+		node_shader_write(frag, 'stencil_uv -= vec2(0.5 / stencil_ratio, 0.5);');
+		node_shader_write(frag, `stencil_uv = vec2(stencil_uv.x * cos(stencilTransform.w) - stencil_uv.y * sin(stencilTransform.w),
+										stencil_uv.x * sin(stencilTransform.w) + stencil_uv.y * cos(stencilTransform.w));`);
+		node_shader_write(frag, 'stencil_uv += vec2(0.5 / stencil_ratio, 0.5);');
+		node_shader_write(frag, 'stencil_uv.x *= stencil_ratio;');
+		node_shader_write(frag, 'if (stencil_uv.x < 0 || stencil_uv.x > 1 || stencil_uv.y < 0 || stencil_uv.y > 1) discard;');
+		node_shader_write(frag, 'vec4 texbrushstencil_sample = textureLod(texbrushstencil, stencil_uv, 0.0);');
+		if (context_raw.brush_stencil_image_is_alpha) {
+			node_shader_write(frag, 'opacity *= texbrushstencil_sample.a;');
+		}
+		else {
+			node_shader_write(frag, 'opacity *= texbrushstencil_sample.r * texbrushstencil_sample.a;');
+		}
+	}
+
+	if (context_raw.brush_mask_image != null && (context_raw.tool == workspace_tool_t.BRUSH || context_raw.tool == workspace_tool_t.ERASER)) {
+		node_shader_add_uniform(frag, 'sampler2D texbrushmask', '_texbrushmask');
+		node_shader_write(frag, 'vec2 binp_mask = inp.xy * 2.0 - 1.0;');
+		node_shader_write(frag, 'binp_mask.x *= aspectRatio;');
+		node_shader_write(frag, 'binp_mask = binp_mask * 0.5 + 0.5;');
+		node_shader_write(frag, 'vec2 pa_mask = bsp.xy - binp_mask.xy;');
+		if (context_raw.brush_directional) {
+			node_shader_add_uniform(frag, 'vec3 brushDirection', '_brushDirection');
+			node_shader_write(frag, 'if (brushDirection.z == 0.0) discard;');
+			node_shader_write(frag, 'pa_mask = vec2(pa_mask.x * brushDirection.x - pa_mask.y * brushDirection.y, pa_mask.x * brushDirection.y + pa_mask.y * brushDirection.x);');
+		}
+		let angle: f32 = context_raw.brush_angle + context_raw.brush_nodes_angle;
+		if (angle != 0.0) {
+			node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
+			node_shader_write(frag, 'pa_mask.xy = vec2(pa_mask.x * brushAngle.x - pa_mask.y * brushAngle.y, pa_mask.x * brushAngle.y + pa_mask.y * brushAngle.x);');
+		}
+		node_shader_write(frag, 'pa_mask /= brushRadius;');
+		if (config_raw.brush_3d) {
+			node_shader_add_uniform(frag, 'vec3 eye', '_camera_pos');
+			node_shader_write(frag, 'pa_mask *= distance(eye, winp.xyz) / 1.5;');
+		}
+		node_shader_write(frag, 'pa_mask = pa_mask.xy * 0.5 + 0.5;');
+		node_shader_write(frag, 'vec4 mask_sample = textureLod(texbrushmask, pa_mask, 0.0);');
+		if (context_raw.brush_mask_image_is_alpha) {
+			node_shader_write(frag, 'opacity *= mask_sample.a;');
+		}
+		else {
+			node_shader_write(frag, 'opacity *= mask_sample.r * mask_sample.a;');
+		}
+	}
+
+	node_shader_write(frag, 'if (opacity == 0.0) discard;');
+
+	if (context_raw.tool == workspace_tool_t.PARTICLE) { // Particle mask
+		make_particle_mask(vert, frag);
+	}
+	else { // Brush cursor mask
+		node_shader_write(frag, 'float str = clamp((brushRadius - dist) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
+	}
+
+	// Manual blending to preserve memory
+	frag.wvpposition = true;
+	node_shader_write(frag, 'vec2 sample_tc = vec2(wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(frag, 'sample_tc.y = 1.0 - sample_tc.y;');
+	///end
+	node_shader_add_uniform(frag, 'sampler2D paintmask');
+	node_shader_write(frag, 'float sample_mask = textureLod(paintmask, sample_tc, 0.0).r;');
+	node_shader_write(frag, 'str = max(str, sample_mask);');
+	// write(frag, 'str = clamp(str + sample_mask, 0.0, 1.0);');
+
+	node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
+	node_shader_write(frag, 'vec4 sample_undo = textureLod(texpaint_undo, sample_tc, 0.0);');
+
+	let matid: f32 = context_raw.material.id / 255;
+	if (context_raw.picker_mask_handle.position == picker_mask_t.MATERIAL) {
+		matid = context_raw.materialid_picked / 255; // Keep existing material id in place when mask is set
+	}
+	let matid_string: string = parser_material_vec1(matid * 3.0);
+	node_shader_write(frag, `float matid = ${matid_string};`);
+
+	// matid % 3 == 0 - normal, 1 - emission, 2 - subsurface
+	if (context_raw.material.paint_emis) {
+		node_shader_write(frag, 'if (emis > 0.0) {');
+		node_shader_write(frag, '	matid += 1.0 / 255.0;');
+		node_shader_write(frag, '	if (str == 0.0) discard;');
+		node_shader_write(frag, '}');
+	}
+	else if (context_raw.material.paint_subs) {
+		node_shader_write(frag, 'if (subs > 0.0) {');
+		node_shader_write(frag, '    matid += 2.0 / 255.0;');
+		node_shader_write(frag, '	if (str == 0.0) discard;');
+		node_shader_write(frag, '}');
+	}
+
+	let is_mask: bool = slot_layer_is_mask(context_raw.layer);
+	let layered: bool = context_raw.layer != project_layers[0];
+	if (layered && !is_mask) {
+		if (context_raw.tool == workspace_tool_t.ERASER) {
+			node_shader_write(frag, 'fragColor[0] = vec4(mix(sample_undo.rgb, vec3(0.0, 0.0, 0.0), str), sample_undo.a - str);');
+			node_shader_write(frag, 'nortan = vec3(0.5, 0.5, 1.0);');
+			node_shader_write(frag, 'occlusion = 1.0;');
+			node_shader_write(frag, 'roughness = 0.0;');
+			node_shader_write(frag, 'metallic = 0.0;');
+			node_shader_write(frag, 'matid = 0.0;');
+		}
+		else if (context_raw.tool == workspace_tool_t.PARTICLE || decal || context_raw.brush_mask_image != null) {
+			node_shader_write(frag, 'fragColor[0] = vec4(' + make_material_blend_mode(frag, context_raw.brush_blending, 'sample_undo.rgb', 'basecol', 'str') + ', max(str, sample_undo.a));');
+		}
+		else {
+			if (context_raw.layer.fill_layer != null) {
+				node_shader_write(frag, 'fragColor[0] = vec4(' + make_material_blend_mode(frag, context_raw.brush_blending, 'sample_undo.rgb', 'basecol', 'opacity') + ', mat_opacity);');
+			}
+			else {
+				node_shader_write(frag, 'fragColor[0] = vec4(' + make_material_blend_mode(frag, context_raw.brush_blending, 'sample_undo.rgb', 'basecol', 'opacity') + ', max(str, sample_undo.a));');
+			}
+		}
+		node_shader_write(frag, 'fragColor[1] = vec4(nortan, matid);');
+
+		let height: string = "0.0";
+		if (context_raw.material.paint_height && make_material_height_used) {
+			height = "height";
+		}
+
+		if (decal) {
+			node_shader_add_uniform(frag, 'sampler2D texpaint_pack_undo', '_texpaint_pack_undo');
+			node_shader_write(frag, 'vec4 sample_pack_undo = textureLod(texpaint_pack_undo, sample_tc, 0.0);');
+			node_shader_write(frag, `fragColor[2] = mix(sample_pack_undo, vec4(occlusion, roughness, metallic, ${height}), str);`);
+		}
+		else {
+			node_shader_write(frag, `fragColor[2] = vec4(occlusion, roughness, metallic, ${height});`);
+		}
+	}
+	else {
+		if (context_raw.tool == workspace_tool_t.ERASER) {
+			node_shader_write(frag, 'fragColor[0] = vec4(mix(sample_undo.rgb, vec3(0.0, 0.0, 0.0), str), sample_undo.a - str);');
+			node_shader_write(frag, 'fragColor[1] = vec4(0.5, 0.5, 1.0, 0.0);');
+			node_shader_write(frag, 'fragColor[2] = vec4(1.0, 0.0, 0.0, 0.0);');
+		}
+		else {
+			node_shader_add_uniform(frag, 'sampler2D texpaint_nor_undo', '_texpaint_nor_undo');
+			node_shader_add_uniform(frag, 'sampler2D texpaint_pack_undo', '_texpaint_pack_undo');
+			node_shader_write(frag, 'vec4 sample_nor_undo = textureLod(texpaint_nor_undo, sample_tc, 0.0);');
+			node_shader_write(frag, 'vec4 sample_pack_undo = textureLod(texpaint_pack_undo, sample_tc, 0.0);');
+			node_shader_write(frag, 'fragColor[0] = vec4(' + make_material_blend_mode(frag, context_raw.brush_blending, 'sample_undo.rgb', 'basecol', 'str') + ', max(str, sample_undo.a));');
+			node_shader_write(frag, 'fragColor[1] = vec4(mix(sample_nor_undo.rgb, nortan, str), matid);');
+			if (context_raw.material.paint_height && make_material_height_used) {
+				node_shader_write(frag, 'fragColor[2] = mix(sample_pack_undo, vec4(occlusion, roughness, metallic, height), str);');
+			}
+			else {
+				node_shader_write(frag, 'fragColor[2] = vec4(mix(sample_pack_undo.rgb, vec3(occlusion, roughness, metallic), str), 0.0);');
+			}
+		}
+	}
+	node_shader_write(frag, 'fragColor[3] = vec4(str, 0.0, 0.0, 1.0);');
+
+	if (!context_raw.material.paint_base) {
+		con_paint.data.color_writes_red[0] = false;
+		con_paint.data.color_writes_green[0] = false;
+		con_paint.data.color_writes_blue[0] = false;
+	}
+	if (!context_raw.material.paint_opac) {
+		con_paint.data.color_writes_alpha[0] = false;
+	}
+	if (!context_raw.material.paint_nor) {
+		con_paint.data.color_writes_red[1] = false;
+		con_paint.data.color_writes_green[1] = false;
+		con_paint.data.color_writes_blue[1] = false;
+	}
+	if (!context_raw.material.paint_occ) {
+		con_paint.data.color_writes_red[2] = false;
+	}
+	if (!context_raw.material.paint_rough) {
+		con_paint.data.color_writes_green[2] = false;
+	}
+	if (!context_raw.material.paint_met) {
+		con_paint.data.color_writes_blue[2] = false;
+	}
+	if (!context_raw.material.paint_height) {
+		con_paint.data.color_writes_alpha[2] = false;
+	}
+
+	// Base color only as mask
+	if (is_mask) {
+		// TODO: Apply opacity into base
+		// write(frag, 'fragColor[0].rgb *= fragColor[0].a;');
+		con_paint.data.color_writes_green[0] = false;
+		con_paint.data.color_writes_blue[0] = false;
+		con_paint.data.color_writes_alpha[0] = false;
+		con_paint.data.color_writes_red[1] = false;
+		con_paint.data.color_writes_green[1] = false;
+		con_paint.data.color_writes_blue[1] = false;
+		con_paint.data.color_writes_alpha[1] = false;
+		con_paint.data.color_writes_red[2] = false;
+		con_paint.data.color_writes_green[2] = false;
+		con_paint.data.color_writes_blue[2] = false;
+		con_paint.data.color_writes_alpha[2] = false;
+	}
+
+	if (context_raw.tool == workspace_tool_t.BAKE) {
+		make_bake_run(con_paint, vert, frag);
+	}
+
+	parser_material_finalize(con_paint);
+	parser_material_triplanar = false;
+	parser_material_sample_keep_aspect = false;
+	con_paint.data.shader_from_source = true;
+	con_paint.data.vertex_shader = node_shader_get(vert);
+	con_paint.data.fragment_shader = node_shader_get(frag);
+
+	return con_paint;
+}
+
+///end

+ 115 - 0
armorpaint/Sources/make_particle.ts

@@ -0,0 +1,115 @@
+
+function make_particle_run(data: material_t): NodeShaderContextRaw {
+	let context_id: string = "mesh";
+	let con_part: NodeShaderContextRaw = node_shader_context_create(data, {
+		name: context_id,
+		depth_write: false,
+		compare_mode: "always",
+		cull_mode: "clockwise",
+		vertex_elements: [{name: "pos", data: "short4norm"}],
+		color_attachments: ["R8"]
+	});
+
+	let vert: NodeShaderRaw = node_shader_context_make_vert(con_part);
+	let frag: NodeShaderRaw = node_shader_context_make_frag(con_part);
+	frag.ins = vert.outs;
+
+	node_shader_write_attrib(vert, 'vec4 spos = vec4(pos.xyz, 1.0);');
+
+	node_shader_add_uniform(vert, 'float brushRadius', '_brushRadius');
+	node_shader_write_attrib(vert, 'vec3 emitFrom = vec3(fhash(gl_InstanceID), fhash(gl_InstanceID * 2), fhash(gl_InstanceID * 3));');
+	node_shader_write_attrib(vert, 'emitFrom = emitFrom * brushRadius - brushRadius / 2.0;');
+	node_shader_write_attrib(vert, 'spos.xyz += emitFrom * vec3(256.0, 256.0, 256.0);');
+
+	node_shader_add_uniform(vert, 'mat4 pd', '_particle_data');
+
+	let str_tex_hash: string = "float fhash(int n) { return fract(sin(float(n)) * 43758.5453); }\n";
+	node_shader_add_function(vert, str_tex_hash);
+	node_shader_add_out(vert, 'float p_age');
+	node_shader_write(vert, 'p_age = pd[3][3] - float(gl_InstanceID) * pd[0][1];');
+	node_shader_write(vert, 'p_age -= p_age * fhash(gl_InstanceID) * pd[2][3];');
+
+	node_shader_write(vert, 'if (pd[0][0] > 0.0 && p_age < 0.0) p_age += float(int(-p_age / pd[0][0]) + 1) * pd[0][0];');
+
+	node_shader_add_out(vert, 'float p_lifetime');
+	node_shader_write(vert, 'p_lifetime = pd[0][2];');
+	node_shader_write(vert, 'if (p_age < 0.0 || p_age > p_lifetime) {');
+	// write(vert, 'SPIRV_Cross_Output stage_output;');
+	// write(vert, 'stage_output.svpos /= 0.0;');
+	// write(vert, 'return stage_output;');
+	node_shader_write(vert, 'spos /= 0.0;');
+	node_shader_write(vert, '}');
+
+	node_shader_add_out(vert, 'vec3 p_velocity');
+	node_shader_write(vert, 'p_velocity = vec3(pd[1][0], pd[1][1], pd[1][2]);');
+	node_shader_write(vert, 'p_velocity.x += fhash(gl_InstanceID)                     * pd[1][3] - pd[1][3] / 2.0;');
+	node_shader_write(vert, 'p_velocity.y += fhash(gl_InstanceID +     int(pd[0][3])) * pd[1][3] - pd[1][3] / 2.0;');
+	node_shader_write(vert, 'p_velocity.z += fhash(gl_InstanceID + 2 * int(pd[0][3])) * pd[1][3] - pd[1][3] / 2.0;');
+	node_shader_write(vert, 'p_velocity.x += (pd[2][0] * p_age) / 5.0;');
+	node_shader_write(vert, 'p_velocity.y += (pd[2][1] * p_age) / 5.0;');
+	node_shader_write(vert, 'p_velocity.z += (pd[2][2] * p_age) / 5.0;');
+
+	node_shader_add_out(vert, 'vec3 p_location');
+	node_shader_write(vert, 'p_location = p_velocity * p_age;');
+	node_shader_write(vert, 'spos.xyz += p_location;');
+	node_shader_write(vert, 'spos.xyz *= vec3(0.01, 0.01, 0.01);');
+
+	node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
+	node_shader_write(vert, 'gl_Position = mul(spos, WVP);');
+
+	node_shader_add_uniform(vert, 'vec4 inp', '_inputBrush');
+	node_shader_write(vert, 'vec2 binp = vec2(inp.x, 1.0 - inp.y);');
+	node_shader_write(vert, 'binp = binp * 2.0 - 1.0;');
+	node_shader_write(vert, 'binp *= gl_Position.w;');
+	node_shader_write(vert, 'gl_Position.xy += binp;');
+
+	node_shader_add_out(vert, 'float p_fade');
+	node_shader_write(vert, 'p_fade = sin(min((p_age / 8.0) * 3.141592, 3.141592));');
+
+	node_shader_add_out(frag, 'float fragColor');
+	node_shader_write(frag, 'fragColor = p_fade;');
+
+	// add_out(vert, 'vec4 wvpposition');
+	// write(vert, 'wvpposition = gl_Position;');
+	// write(frag, 'vec2 texCoord = wvpposition.xy / wvpposition.w;');
+	// add_uniform(frag, 'sampler2D gbufferD');
+	// write(frag, 'fragColor *= 1.0 - clamp(distance(textureLod(gbufferD, texCoord, 0.0).r, wvpposition.z), 0.0, 1.0);');
+
+	// Material.finalize(con_part);
+	con_part.data.shader_from_source = true;
+	con_part.data.vertex_shader = node_shader_get(vert);
+	con_part.data.fragment_shader = node_shader_get(frag);
+
+	return con_part;
+}
+
+function make_particle_mask(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+	///if arm_physics
+	if (context_raw.particle_physics) {
+		node_shader_add_out(vert, 'vec4 wpos');
+		node_shader_add_uniform(vert, 'mat4 W', '_world_matrix');
+		node_shader_write_attrib(vert, 'wpos = mul(vec4(pos.xyz, 1.0), W);');
+		node_shader_add_uniform(frag, 'vec3 particleHit', '_particleHit');
+		node_shader_add_uniform(frag, 'vec3 particleHitLast', '_particleHitLast');
+
+		node_shader_write(frag, 'vec3 pa = wpos.xyz - particleHit;');
+		node_shader_write(frag, 'vec3 ba = particleHitLast - particleHit;');
+		node_shader_write(frag, 'float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);');
+		node_shader_write(frag, 'dist = length(pa - ba * h) * 10.0;');
+		// write(frag, 'dist = distance(particleHit, wpos.xyz) * 10.0;');
+
+		node_shader_write(frag, 'if (dist > 1.0) discard;');
+		node_shader_write(frag, 'float str = clamp(pow(1.0 / dist * brushHardness * 0.2, 4.0), 0.0, 1.0) * opacity;');
+		node_shader_write(frag, 'if (particleHit.x == 0.0 && particleHit.y == 0.0 && particleHit.z == 0.0) str = 0.0;');
+		node_shader_write(frag, 'if (str == 0.0) discard;');
+		return;
+	}
+	///end
+
+	node_shader_add_uniform(frag, 'sampler2D texparticle', '_texparticle');
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(frag, 'float str = textureLod(texparticle, sp.xy, 0.0).r;');
+	///else
+	node_shader_write(frag, 'float str = textureLod(texparticle, vec2(sp.x, (1.0 - sp.y)), 0.0).r;');
+	///end
+}

+ 96 - 0
armorpaint/Sources/make_texcoord.ts

@@ -0,0 +1,96 @@
+
+function make_texcoord_run(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+
+	let fill_layer: bool = context_raw.layer.fill_layer != null;
+	let uv_type: uv_type_t = fill_layer ? context_raw.layer.uv_type : context_raw.brush_paint;
+	let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
+	let angle: f32 = context_raw.brush_angle + context_raw.brush_nodes_angle;
+	let uvAngle: f32 = fill_layer ? context_raw.layer.angle : angle;
+
+	if (uv_type == uv_type_t.PROJECT || decal) { // TexCoords - project
+		node_shader_add_uniform(frag, 'float brushScale', '_brushScale');
+		node_shader_write_attrib(frag, 'vec2 uvsp = sp.xy;');
+
+		if (fill_layer) { // Decal layer
+			node_shader_write_attrib(frag, 'if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) discard;');
+
+			if (uvAngle != 0.0) {
+				node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
+				node_shader_write_attrib(frag, 'uvsp = vec2(uvsp.x * brushAngle.x - uvsp.y * brushAngle.y, uvsp.x * brushAngle.y + uvsp.y * brushAngle.x);');
+			}
+
+			frag.n = true;
+			node_shader_add_uniform(frag, 'vec3 decalLayerNor', '_decalLayerNor');
+			let dot_angle: f32 = context_raw.brush_angle_reject_dot;
+			node_shader_write(frag, `if (abs(dot(n, decalLayerNor) - 1.0) > ${dot_angle}) discard;`);
+
+			frag.wposition = true;
+			node_shader_add_uniform(frag, 'vec3 decalLayerLoc', '_decalLayerLoc');
+			node_shader_add_uniform(frag, 'float decalLayerDim', '_decalLayerDim');
+			node_shader_write_attrib(frag, 'if (abs(dot(decalLayerNor, decalLayerLoc - wposition)) > decalLayerDim) discard;');
+		}
+		else if (decal) {
+			node_shader_add_uniform(frag, 'vec4 decalMask', '_decalMask');
+			node_shader_write_attrib(frag, 'uvsp -= decalMask.xy;');
+			node_shader_write_attrib(frag, 'uvsp.x *= aspectRatio;');
+			node_shader_write_attrib(frag, 'uvsp *= 0.21 / (decalMask.w * 0.9);'); // Decal radius
+
+			if (context_raw.brush_directional) {
+				node_shader_add_uniform(frag, 'vec3 brushDirection', '_brushDirection');
+				node_shader_write_attrib(frag, 'if (brushDirection.z == 0.0) discard;');
+				node_shader_write_attrib(frag, 'uvsp = vec2(uvsp.x * brushDirection.x - uvsp.y * brushDirection.y, uvsp.x * brushDirection.y + uvsp.y * brushDirection.x);');
+			}
+
+			if (uvAngle != 0.0) {
+				node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
+				node_shader_write_attrib(frag, 'uvsp = vec2(uvsp.x * brushAngle.x - uvsp.y * brushAngle.y, uvsp.x * brushAngle.y + uvsp.y * brushAngle.x);');
+			}
+
+			node_shader_add_uniform(frag, 'float brushScaleX', '_brushScaleX');
+			node_shader_write_attrib(frag, 'uvsp.x *= brushScaleX;');
+
+			node_shader_write_attrib(frag, 'uvsp += vec2(0.5, 0.5);');
+
+			node_shader_write_attrib(frag, 'if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) discard;');
+		}
+		else {
+			node_shader_write_attrib(frag, 'uvsp.x *= aspectRatio;');
+
+			if (uvAngle != 0.0) {
+				node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
+				node_shader_write_attrib(frag, 'uvsp = vec2(uvsp.x * brushAngle.x - uvsp.y * brushAngle.y, uvsp.x * brushAngle.y + uvsp.y * brushAngle.x);');
+			}
+		}
+
+		node_shader_write_attrib(frag, 'vec2 texCoord = uvsp * brushScale;');
+	}
+	else if (uv_type == uv_type_t.UVMAP) { // TexCoords - uvmap
+		node_shader_add_uniform(vert, 'float brushScale', '_brushScale');
+		node_shader_add_out(vert, 'vec2 texCoord');
+		node_shader_write(vert, 'texCoord = tex * brushScale;');
+
+		if (uvAngle > 0.0) {
+			node_shader_add_uniform(vert, 'vec2 brushAngle', '_brushAngle');
+			node_shader_write(vert, 'texCoord = vec2(texCoord.x * brushAngle.x - texCoord.y * brushAngle.y, texCoord.x * brushAngle.y + texCoord.y * brushAngle.x);');
+		}
+	}
+	else { // TexCoords - triplanar
+		frag.wposition = true;
+		frag.n = true;
+		node_shader_add_uniform(frag, 'float brushScale', '_brushScale');
+		node_shader_write_attrib(frag, 'vec3 triWeight = wnormal * wnormal;'); // n * n
+		node_shader_write_attrib(frag, 'float triMax = max(triWeight.x, max(triWeight.y, triWeight.z));');
+		node_shader_write_attrib(frag, 'triWeight = max(triWeight - triMax * 0.75, 0.0);');
+		node_shader_write_attrib(frag, 'vec3 texCoordBlend = triWeight * (1.0 / (triWeight.x + triWeight.y + triWeight.z));');
+		node_shader_write_attrib(frag, 'vec2 texCoord = wposition.yz * brushScale * 0.5;');
+		node_shader_write_attrib(frag, 'vec2 texCoord1 = wposition.xz * brushScale * 0.5;');
+		node_shader_write_attrib(frag, 'vec2 texCoord2 = wposition.xy * brushScale * 0.5;');
+
+		if (uvAngle != 0.0) {
+			node_shader_add_uniform(frag, 'vec2 brushAngle', '_brushAngle');
+			node_shader_write_attrib(frag, 'texCoord = vec2(texCoord.x * brushAngle.x - texCoord.y * brushAngle.y, texCoord.x * brushAngle.y + texCoord.y * brushAngle.x);');
+			node_shader_write_attrib(frag, 'texCoord1 = vec2(texCoord1.x * brushAngle.x - texCoord1.y * brushAngle.y, texCoord1.x * brushAngle.y + texCoord1.y * brushAngle.x);');
+			node_shader_write_attrib(frag, 'texCoord2 = vec2(texCoord2.x * brushAngle.x - texCoord2.y * brushAngle.y, texCoord2.x * brushAngle.y + texCoord2.y * brushAngle.x);');
+		}
+	}
+}

+ 3 - 3
armorpaint/Sources/nodes/BrushOutputNode.ts

@@ -70,7 +70,7 @@ class BrushOutputNode extends LogicNode {
 
 		if (last_mask != context_raw.brush_mask_image ||
 			last_stencil != context_raw.brush_stencil_image) {
-			MakeMaterial.make_material_parse_paint_material();
+			make_material_parse_paint_material();
 		}
 
 		context_raw.brush_directional = this.Directional;
@@ -94,7 +94,7 @@ class BrushOutputNode extends LogicNode {
 		let fill_layer: bool = context_raw.layer.fill_layer != null && context_raw.tool != workspace_tool_t.PICKER && context_raw.tool != workspace_tool_t.MATERIAL && context_raw.tool != workspace_tool_t.COLORID;
 
 		// Do not paint over groups
-		let group_layer: bool = SlotLayer.slot_layer_is_group(context_raw.layer);
+		let group_layer: bool = slot_layer_is_group(context_raw.layer);
 
 		// Paint bounds
 		if (context_raw.paint_vec.x > left &&
@@ -103,7 +103,7 @@ class BrushOutputNode extends LogicNode {
 			context_raw.paint_vec.y < 1 &&
 			!fill_layer &&
 			!group_layer &&
-			(SlotLayer.slot_layer_is_visible(context_raw.layer) || context_raw.paint2d) &&
+			(slot_layer_is_visible(context_raw.layer) || context_raw.paint2d) &&
 			!ui_base_ui.is_hovered &&
 			!base_is_dragging &&
 			!base_is_resizing &&

+ 40 - 0
armorpaint/Sources/nodes_brush.ts

@@ -0,0 +1,40 @@
+/// <reference path='./nodes/TEX_IMAGE.ts'/>
+/// <reference path='./nodes/InputNode.ts'/>
+/// <reference path='./../../base/Sources/nodes/MathNode.ts'/>
+/// <reference path='./../../base/Sources/nodes/RandomNode.ts'/>
+/// <reference path='./../../base/Sources/nodes/SeparateVectorNode.ts'/>
+/// <reference path='./../../base/Sources/nodes/TimeNode.ts'/>
+/// <reference path='./../../base/Sources/nodes/FloatNode.ts'/>
+/// <reference path='./../../base/Sources/nodes/VectorNode.ts'/>
+/// <reference path='./../../base/Sources/nodes/VectorMathNode.ts'/>
+
+let nodes_brush_categories = [_tr("Nodes")];
+
+let nodes_brush_list: zui_node_t[][] = [
+	[ // Category 0
+		TEX_IMAGE.def,
+		InputNode.def,
+		MathNode.def,
+		RandomNode.def,
+		SeparateVectorNode.def,
+		TimeNode.def,
+		FloatNode.def,
+		VectorNode.def,
+		VectorMathNode.def
+	]
+];
+
+function nodes_brush_create_node(nodeType: string): zui_node_t {
+	for (let c of nodes_brush_list) {
+		for (let n of c) {
+			if (n.type == nodeType) {
+				let canvas: zui_node_canvas_t = context_raw.brush.canvas;
+				let nodes: zui_nodes_t = context_raw.brush.nodes;
+				let node: zui_node_t = ui_nodes_make_node(n, nodes, canvas);
+				canvas.nodes.push(node);
+				return node;
+			}
+		}
+	}
+	return null;
+}

+ 933 - 0
armorpaint/Sources/render_path_paint.ts

@@ -0,0 +1,933 @@
+
+let render_path_paint_live_layer: SlotLayerRaw = null;
+let render_path_paint_live_layer_drawn: i32 = 0;
+let render_path_paint_live_layer_locked: bool = false;
+let render_path_paint_dilated: bool = true;
+let render_path_paint_init_voxels: bool = true; // Bake AO
+let render_path_paint_push_undo_last: bool;
+let render_path_paint_painto: mesh_object_t = null;
+let render_path_paint_planeo: mesh_object_t = null;
+let render_path_paint_visibles: bool[] = null;
+let render_path_paint_merged_object_visible: bool = false;
+let render_path_paint_saved_fov: f32 = 0.0;
+let render_path_paint_baking: bool = false;
+let _render_path_paint_texpaint: render_target_t;
+let _render_path_paint_texpaint_nor: render_target_t;
+let _render_path_paint_texpaint_pack: render_target_t;
+let _render_path_paint_texpaint_undo: render_target_t;
+let _render_path_paint_texpaint_nor_undo: render_target_t;
+let _render_path_paint_texpaint_pack_undo: render_target_t;
+let render_path_paint_last_x: f32 = -1.0;
+let render_path_paint_last_y: f32 = -1.0;
+
+function render_path_paint_init() {
+
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpaint_blend0";
+		t.width = config_get_texture_res_x();
+		t.height = config_get_texture_res_y();
+		t.format = "R8";
+		render_path_create_render_target(t);
+	}
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpaint_blend1";
+		t.width = config_get_texture_res_x();
+		t.height = config_get_texture_res_y();
+		t.format = "R8";
+		render_path_create_render_target(t);
+	}
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpaint_colorid";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpaint_picker";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpaint_nor_picker";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpaint_pack_picker";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpaint_uv_picker";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpaint_posnortex_picker0";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA128";
+		render_path_create_render_target(t);
+	}
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpaint_posnortex_picker1";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA128";
+		render_path_create_render_target(t);
+	}
+
+	render_path_load_shader("shader_datas/copy_mrt3_pass/copy_mrt3_pass");
+	render_path_load_shader("shader_datas/copy_mrt3_pass/copy_mrt3RGBA64_pass");
+	///if is_paint
+	render_path_load_shader("shader_datas/dilate_pass/dilate_pass");
+	///end
+}
+
+function render_path_paint_commands_paint(dilation = true) {
+	let tid: i32 = context_raw.layer.id;
+
+	if (context_raw.pdirty > 0) {
+		///if arm_physics
+		let particle_physics: bool = context_raw.particle_physics;
+		///else
+		let particle_physics: bool = false;
+		///end
+		if (context_raw.tool == workspace_tool_t.PARTICLE && !particle_physics) {
+			render_path_set_target("texparticle");
+			render_path_clear_target(0x00000000);
+			render_path_bind_target("_main", "gbufferD");
+			if ((context_raw.xray || config_raw.brush_angle_reject) && config_raw.brush_3d) {
+				render_path_bind_target("gbuffer0", "gbuffer0");
+			}
+
+			let mo: mesh_object_t = scene_get_child(".ParticleEmitter").ext;
+			mo.base.visible = true;
+			mesh_object_render(mo, "mesh",_render_path_bind_params);
+			mo.base.visible = false;
+
+			mo = scene_get_child(".Particle").ext;
+			mo.base.visible = true;
+			mesh_object_render(mo, "mesh",_render_path_bind_params);
+			mo.base.visible = false;
+			render_path_end();
+		}
+
+		///if is_paint
+		if (context_raw.tool == workspace_tool_t.COLORID) {
+			render_path_set_target("texpaint_colorid");
+			render_path_clear_target(0xff000000);
+			render_path_bind_target("gbuffer2", "gbuffer2");
+			render_path_draw_meshes("paint");
+			ui_header_handle.redraws = 2;
+		}
+		else if (context_raw.tool == workspace_tool_t.PICKER || context_raw.tool == workspace_tool_t.MATERIAL) {
+			if (context_raw.pick_pos_nor_tex) {
+				if (context_raw.paint2d) {
+					render_path_set_target("gbuffer0", ["gbuffer1", "gbuffer2"]);
+					render_path_draw_meshes("mesh");
+				}
+				render_path_set_target("texpaint_posnortex_picker0", ["texpaint_posnortex_picker1"]);
+				render_path_bind_target("gbuffer2", "gbuffer2");
+				render_path_bind_target("_main", "gbufferD");
+				render_path_draw_meshes("paint");
+				let texpaint_posnortex_picker0: image_t = render_path_render_targets.get("texpaint_posnortex_picker0")._image;
+				let texpaint_posnortex_picker1: image_t = render_path_render_targets.get("texpaint_posnortex_picker1")._image;
+				let a: DataView = new DataView(image_get_pixels(texpaint_posnortex_picker0));
+				let b: DataView = new DataView(image_get_pixels(texpaint_posnortex_picker1));
+				context_raw.posx_picked = a.getFloat32(0, true);
+				context_raw.posy_picked = a.getFloat32(4, true);
+				context_raw.posz_picked = a.getFloat32(8, true);
+				context_raw.uvx_picked = a.getFloat32(12, true);
+				context_raw.norx_picked = b.getFloat32(0, true);
+				context_raw.nory_picked = b.getFloat32(4, true);
+				context_raw.norz_picked = b.getFloat32(8, true);
+				context_raw.uvy_picked = b.getFloat32(12, true);
+			}
+			else {
+				render_path_set_target("texpaint_picker", ["texpaint_nor_picker", "texpaint_pack_picker", "texpaint_uv_picker"]);
+				render_path_bind_target("gbuffer2", "gbuffer2");
+				tid = context_raw.layer.id;
+				let use_live_layer: bool = context_raw.tool == workspace_tool_t.MATERIAL;
+				if (use_live_layer) render_path_paint_use_live_layer(true);
+				render_path_bind_target("texpaint" + tid, "texpaint");
+				render_path_bind_target("texpaint_nor" + tid, "texpaint_nor");
+				render_path_bind_target("texpaint_pack" + tid, "texpaint_pack");
+				render_path_draw_meshes("paint");
+				if (use_live_layer) render_path_paint_use_live_layer(false);
+				ui_header_handle.redraws = 2;
+				ui_base_hwnds[2].redraws = 2;
+
+				let texpaint_picker: image_t = render_path_render_targets.get("texpaint_picker")._image;
+				let texpaint_nor_picker: image_t = render_path_render_targets.get("texpaint_nor_picker")._image;
+				let texpaint_pack_picker: image_t = render_path_render_targets.get("texpaint_pack_picker")._image;
+				let texpaint_uv_picker: image_t = render_path_render_targets.get("texpaint_uv_picker")._image;
+				let a: DataView = new DataView(image_get_pixels(texpaint_picker));
+				let b: DataView = new DataView(image_get_pixels(texpaint_nor_picker));
+				let c: DataView = new DataView(image_get_pixels(texpaint_pack_picker));
+				let d: DataView = new DataView(image_get_pixels(texpaint_uv_picker));
+
+				if (context_raw.color_picker_callback != null) {
+					context_raw.color_picker_callback(context_raw.picked_color);
+				}
+
+				// Picked surface values
+				///if (krom_metal || krom_vulkan)
+				let i0: i32 = 2;
+				let i1: i32 = 1;
+				let i2: i32 = 0;
+				///else
+				let i0: i32 = 0;
+				let i1: i32 = 1;
+				let i2: i32 = 2;
+				///end
+				let i3: i32 = 3;
+				context_raw.picked_color.base = color_set_rb(context_raw.picked_color.base, a.getUint8(i0));
+				context_raw.picked_color.base = color_set_gb(context_raw.picked_color.base, a.getUint8(i1));
+				context_raw.picked_color.base = color_set_bb(context_raw.picked_color.base, a.getUint8(i2));
+				context_raw.picked_color.normal = color_set_rb(context_raw.picked_color.normal, b.getUint8(i0));
+				context_raw.picked_color.normal = color_set_gb(context_raw.picked_color.normal, b.getUint8(i1));
+				context_raw.picked_color.normal = color_set_bb(context_raw.picked_color.normal, b.getUint8(i2));
+				context_raw.picked_color.occlusion = c.getUint8(i0) / 255;
+				context_raw.picked_color.roughness = c.getUint8(i1) / 255;
+				context_raw.picked_color.metallic = c.getUint8(i2) / 255;
+				context_raw.picked_color.height = c.getUint8(i3) / 255;
+				context_raw.picked_color.opacity = a.getUint8(i3) / 255;
+				context_raw.uvx_picked = d.getUint8(i0) / 255;
+				context_raw.uvy_picked = d.getUint8(i1) / 255;
+				// Pick material
+				if (context_raw.picker_select_material && context_raw.color_picker_callback == null) {
+					// matid % 3 == 0 - normal, 1 - emission, 2 - subsurface
+					let matid: i32 = math_floor((b.getUint8(3) - (b.getUint8(3) % 3)) / 3);
+					for (let m of project_materials) {
+						if (m.id == matid) {
+							context_set_material(m);
+							context_raw.materialid_picked = matid;
+							break;
+						}
+					}
+				}
+			}
+		}
+		else {
+			///if arm_voxels
+			if (context_raw.tool == workspace_tool_t.BAKE && context_raw.bake_type == bake_type_t.AO) {
+				if (render_path_paint_init_voxels) {
+					render_path_paint_init_voxels = false;
+					let _rp_gi: bool = config_raw.rp_gi;
+					config_raw.rp_gi = true;
+					render_path_base_init_voxels();
+					config_raw.rp_gi = _rp_gi;
+				}
+				render_path_clear_image("voxels", 0x00000000);
+				render_path_set_target("");
+				render_path_set_viewport(256, 256);
+				render_path_bind_target("voxels", "voxels");
+				render_path_draw_meshes("voxel");
+				render_path_gen_mipmaps("voxels");
+			}
+			///end
+
+			let texpaint: string = "texpaint" + tid;
+			if (context_raw.tool == workspace_tool_t.BAKE && context_raw.brush_time == time_delta()) {
+				// Clear to black on bake start
+				render_path_set_target(texpaint);
+				render_path_clear_target(0xff000000);
+			}
+
+			render_path_set_target("texpaint_blend1");
+			render_path_bind_target("texpaint_blend0", "tex");
+			render_path_draw_shader("shader_datas/copy_pass/copyR8_pass");
+			let is_mask: bool = slot_layer_is_mask(context_raw.layer);
+			if (is_mask) {
+				let ptid: i32 = context_raw.layer.parent.id;
+				if (slot_layer_is_group(context_raw.layer.parent)) { // Group mask
+					for (let c of slot_layer_get_children(context_raw.layer.parent)) {
+						ptid = c.id;
+						break;
+					}
+				}
+				render_path_set_target(texpaint, ["texpaint_nor" + ptid, "texpaint_pack" + ptid, "texpaint_blend0"]);
+			}
+			else {
+				render_path_set_target(texpaint, ["texpaint_nor" + tid, "texpaint_pack" + tid, "texpaint_blend0"]);
+			}
+			render_path_bind_target("_main", "gbufferD");
+			if ((context_raw.xray || config_raw.brush_angle_reject) && config_raw.brush_3d) {
+				render_path_bind_target("gbuffer0", "gbuffer0");
+			}
+			render_path_bind_target("texpaint_blend1", "paintmask");
+			///if arm_voxels
+			if (context_raw.tool == workspace_tool_t.BAKE && context_raw.bake_type == bake_type_t.AO) {
+				render_path_bind_target("voxels", "voxels");
+			}
+			///end
+			if (context_raw.colorid_picked) {
+				render_path_bind_target("texpaint_colorid", "texpaint_colorid");
+			}
+
+			// Read texcoords from gbuffer
+			let read_tc: bool = (context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.FACE) ||
+									context_raw.tool == workspace_tool_t.CLONE ||
+									context_raw.tool == workspace_tool_t.BLUR ||
+									context_raw.tool == workspace_tool_t.SMUDGE;
+			if (read_tc) {
+				render_path_bind_target("gbuffer2", "gbuffer2");
+			}
+
+			render_path_draw_meshes("paint");
+
+			if (context_raw.tool == workspace_tool_t.BAKE && context_raw.bake_type == bake_type_t.CURVATURE && context_raw.bake_curv_smooth > 0) {
+				if (render_path_render_targets.get("texpaint_blur") == null) {
+					let t = render_target_create();
+					t.name = "texpaint_blur";
+					t.width = math_floor(config_get_texture_res_x() * 0.95);
+					t.height = math_floor(config_get_texture_res_y() * 0.95);
+					t.format = "RGBA32";
+					render_path_create_render_target(t);
+				}
+				let blurs: i32 = math_round(context_raw.bake_curv_smooth);
+				for (let i: i32 = 0; i < blurs; ++i) {
+					render_path_set_target("texpaint_blur");
+					render_path_bind_target(texpaint, "tex");
+					render_path_draw_shader("shader_datas/copy_pass/copy_pass");
+					render_path_set_target(texpaint);
+					render_path_bind_target("texpaint_blur", "tex");
+					render_path_draw_shader("shader_datas/copy_pass/copy_pass");
+				}
+			}
+
+			if (dilation && config_raw.dilate == dilate_type_t.INSTANT) {
+				render_path_paint_dilate(true, false);
+			}
+		}
+		///end
+
+		///if is_sculpt
+		let texpaint: string = "texpaint" + tid;
+		render_path_set_target("texpaint_blend1");
+		render_path_bind_target("texpaint_blend0", "tex");
+		render_path_draw_shader("shader_datas/copy_pass/copyR8_pass");
+		render_path_set_target(texpaint, ["texpaint_blend0"]);
+		render_path_bind_target("gbufferD_undo", "gbufferD");
+		if ((context_raw.xray || config_raw.brush_angle_reject) && config_raw.brush_3d) {
+			render_path_bind_target("gbuffer0", "gbuffer0");
+		}
+		render_path_bind_target("texpaint_blend1", "paintmask");
+
+		// Read texcoords from gbuffer
+		let read_tc: bool = (context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.FACE) ||
+								context_raw.tool == workspace_tool_t.CLONE ||
+								context_raw.tool == workspace_tool_t.BLUR ||
+								context_raw.tool == workspace_tool_t.SMUDGE;
+		if (read_tc) {
+			render_path_bind_target("gbuffer2", "gbuffer2");
+		}
+		render_path_bind_target("gbuffer0_undo", "gbuffer0_undo");
+
+		let material_contexts: material_context_t[] = [];
+		let shader_contexts: shader_context_t[] = [];
+		let mats: material_data_t[] = project_paint_objects[0].materials;
+		mesh_object_get_contexts(project_paint_objects[0], "paint", mats, material_contexts, shader_contexts);
+
+		let cc_context: shader_context_t = shader_contexts[0];
+		if (const_data_screen_aligned_vb == null) const_data_create_screen_aligned_data();
+		g4_set_pipeline(cc_context._.pipe_state);
+		uniforms_set_context_consts(cc_context,_render_path_bind_params);
+		uniforms_set_obj_consts(cc_context, project_paint_objects[0].base);
+		uniforms_set_material_consts(cc_context, material_contexts[0]);
+		g4_set_vertex_buffer(const_data_screen_aligned_vb);
+		g4_set_index_buffer(const_data_screen_aligned_ib);
+		g4_draw();
+		render_path_end();
+		///end
+	}
+}
+
+function render_path_paint_use_live_layer(use: bool) {
+	let tid: i32 = context_raw.layer.id;
+	let hid: i32 = history_undo_i - 1 < 0 ? config_raw.undo_steps - 1 : history_undo_i - 1;
+	if (use) {
+		_render_path_paint_texpaint = render_path_render_targets.get("texpaint" + tid);
+		_render_path_paint_texpaint_undo = render_path_render_targets.get("texpaint_undo" + hid);
+		_render_path_paint_texpaint_nor_undo = render_path_render_targets.get("texpaint_nor_undo" + hid);
+		_render_path_paint_texpaint_pack_undo = render_path_render_targets.get("texpaint_pack_undo" + hid);
+		_render_path_paint_texpaint_nor = render_path_render_targets.get("texpaint_nor" + tid);
+		_render_path_paint_texpaint_pack = render_path_render_targets.get("texpaint_pack" + tid);
+		render_path_render_targets.set("texpaint_undo" + hid,render_path_render_targets.get("texpaint" + tid));
+		render_path_render_targets.set("texpaint" + tid,render_path_render_targets.get("texpaint_live"));
+		if (slot_layer_is_layer(context_raw.layer)) {
+			render_path_render_targets.set("texpaint_nor_undo" + hid,render_path_render_targets.get("texpaint_nor" + tid));
+			render_path_render_targets.set("texpaint_pack_undo" + hid,render_path_render_targets.get("texpaint_pack" + tid));
+			render_path_render_targets.set("texpaint_nor" + tid,render_path_render_targets.get("texpaint_nor_live"));
+			render_path_render_targets.set("texpaint_pack" + tid,render_path_render_targets.get("texpaint_pack_live"));
+		}
+	}
+	else {
+		render_path_render_targets.set("texpaint" + tid, _render_path_paint_texpaint);
+		render_path_render_targets.set("texpaint_undo" + hid, _render_path_paint_texpaint_undo);
+		if (slot_layer_is_layer(context_raw.layer)) {
+			render_path_render_targets.set("texpaint_nor_undo" + hid, _render_path_paint_texpaint_nor_undo);
+			render_path_render_targets.set("texpaint_pack_undo" + hid, _render_path_paint_texpaint_pack_undo);
+			render_path_render_targets.set("texpaint_nor" + tid, _render_path_paint_texpaint_nor);
+			render_path_render_targets.set("texpaint_pack" + tid, _render_path_paint_texpaint_pack);
+		}
+	}
+	render_path_paint_live_layer_locked = use;
+}
+
+function render_path_paint_commands_live_brush() {
+	let tool: workspace_tool_t = context_raw.tool;
+	if (tool != workspace_tool_t.BRUSH &&
+		tool != workspace_tool_t.ERASER &&
+		tool != workspace_tool_t.CLONE &&
+		tool != workspace_tool_t.DECAL &&
+		tool != workspace_tool_t.TEXT &&
+		tool != workspace_tool_t.BLUR &&
+		tool != workspace_tool_t.SMUDGE) {
+			return;
+	}
+
+	if (render_path_paint_live_layer_locked) return;
+
+	if (render_path_paint_live_layer == null) {
+		render_path_paint_live_layer = slot_layer_create("_live");
+	}
+
+	let tid: i32 = context_raw.layer.id;
+	if (slot_layer_is_mask(context_raw.layer)) {
+		render_path_set_target("texpaint_live");
+		render_path_bind_target("texpaint" + tid, "tex");
+		render_path_draw_shader("shader_datas/copy_pass/copy_pass");
+	}
+	else {
+		render_path_set_target("texpaint_live", ["texpaint_nor_live", "texpaint_pack_live"]);
+		render_path_bind_target("texpaint" + tid, "tex0");
+		render_path_bind_target("texpaint_nor" + tid, "tex1");
+		render_path_bind_target("texpaint_pack" + tid, "tex2");
+		render_path_draw_shader("shader_datas/copy_mrt3_pass/copy_mrt3_pass");
+	}
+
+	render_path_paint_use_live_layer(true);
+
+	render_path_paint_live_layer_drawn = 2;
+
+	ui_view2d_hwnd.redraws = 2;
+	let _x: f32 = context_raw.paint_vec.x;
+	let _y: f32 = context_raw.paint_vec.y;
+	if (context_raw.brush_locked) {
+		context_raw.paint_vec.x = (context_raw.lock_started_x - app_x()) / app_w();
+		context_raw.paint_vec.y = (context_raw.lock_started_y - app_y()) / app_h();
+	}
+	let _last_x: f32 = context_raw.last_paint_vec_x;
+	let _last_y: f32 = context_raw.last_paint_vec_y;
+	let _pdirty: i32 = context_raw.pdirty;
+	context_raw.last_paint_vec_x = context_raw.paint_vec.x;
+	context_raw.last_paint_vec_y = context_raw.paint_vec.y;
+	if (operator_shortcut(config_keymap.brush_ruler)) {
+		context_raw.last_paint_vec_x = context_raw.last_paint_x;
+		context_raw.last_paint_vec_y = context_raw.last_paint_y;
+	}
+	context_raw.pdirty = 2;
+
+	render_path_paint_commands_symmetry();
+	render_path_paint_commands_paint();
+
+	render_path_paint_use_live_layer(false);
+
+	context_raw.paint_vec.x = _x;
+	context_raw.paint_vec.y = _y;
+	context_raw.last_paint_vec_x = _last_x;
+	context_raw.last_paint_vec_y = _last_y;
+	context_raw.pdirty = _pdirty;
+	context_raw.brush_blend_dirty = true;
+}
+
+function render_path_paint_commands_cursor() {
+	if (!config_raw.brush_3d) return;
+	let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
+	let decal_mask: bool = decal && operator_shortcut(config_keymap.decal_mask, shortcut_type_t.DOWN);
+	let tool: workspace_tool_t = context_raw.tool;
+	if (tool != workspace_tool_t.BRUSH &&
+		tool != workspace_tool_t.ERASER &&
+		tool != workspace_tool_t.CLONE &&
+		tool != workspace_tool_t.BLUR &&
+		tool != workspace_tool_t.SMUDGE &&
+		tool != workspace_tool_t.PARTICLE &&
+		!decal_mask) {
+			return;
+	}
+
+	let fill_layer: bool = context_raw.layer.fill_layer != null;
+	let group_layer: bool = slot_layer_is_group(context_raw.layer);
+	if (!base_ui_enabled || base_is_dragging || fill_layer || group_layer) {
+		return;
+	}
+
+	let mx: f32 = context_raw.paint_vec.x;
+	let my: f32 = 1.0 - context_raw.paint_vec.y;
+	if (context_raw.brush_locked) {
+		mx = (context_raw.lock_started_x - app_x()) / app_w();
+		my = 1.0 - (context_raw.lock_started_y - app_y()) / app_h();
+	}
+	let radius: f32 = decal_mask ? context_raw.brush_decal_mask_radius : context_raw.brush_radius;
+	render_path_paint_draw_cursor(mx, my, context_raw.brush_nodes_radius * radius / 3.4);
+}
+
+function render_path_paint_draw_cursor(mx: f32, my: f32, radius: f32, tint_r: f32 = 1.0, tint_g: f32 = 1.0, tint_b: f32 = 1.0) {
+	let plane: mesh_object_t = scene_get_child(".Plane").ext;
+	let geom: mesh_data_t = plane.data;
+
+	if (base_pipe_cursor == null) base_make_cursor_pipe();
+
+	render_path_set_target("");
+	g4_set_pipeline(base_pipe_cursor);
+	let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
+	let decal_mask: bool = decal && operator_shortcut(config_keymap.decal_mask, shortcut_type_t.DOWN);
+	let img: image_t = (decal && !decal_mask) ? context_raw.decal_image : resource_get("cursor.k");
+	g4_set_tex(base_cursor_tex, img);
+	let gbuffer0: image_t = render_path_render_targets.get("gbuffer0")._image;
+	g4_set_tex_depth(base_cursor_gbufferd, gbuffer0);
+	g4_set_float2(base_cursor_mouse, mx, my);
+	g4_set_float2(base_cursor_tex_step, 1 / gbuffer0.width, 1 / gbuffer0.height);
+	g4_set_float(base_cursor_radius, radius);
+	let right: vec4_t = vec4_normalize(camera_object_right_world(scene_camera));
+	g4_set_float3(base_cursor_camera_right, right.x, right.y, right.z);
+	g4_set_float3(base_cursor_tint, tint_r, tint_g, tint_b);
+	g4_set_mat(base_cursor_vp, scene_camera.vp);
+	let help_mat: mat4_t = mat4_identity();
+	mat4_get_inv(help_mat, scene_camera.vp);
+	g4_set_mat(base_cursor_inv_vp, help_mat);
+	///if (krom_metal || krom_vulkan)
+	g4_set_vertex_buffer(mesh_data_get(geom, [{name: "tex", data: "short2norm"}]));
+	///else
+	g4_set_vertex_buffer(geom._.vertex_buffer);
+	///end
+	g4_set_index_buffer(geom._.index_buffers[0]);
+	g4_draw();
+
+	g4_disable_scissor();
+	render_path_end();
+}
+
+function render_path_paint_commands_symmetry() {
+	if (context_raw.sym_x || context_raw.sym_y || context_raw.sym_z) {
+		context_raw.ddirty = 2;
+		let t: transform_t = context_raw.paint_object.base.transform;
+		let sx: f32 = t.scale.x;
+		let sy: f32 = t.scale.y;
+		let sz: f32 = t.scale.z;
+		if (context_raw.sym_x) {
+			vec4_set(t.scale, -sx, sy, sz);
+			transform_build_matrix(t);
+			render_path_paint_commands_paint(false);
+		}
+		if (context_raw.sym_y) {
+			vec4_set(t.scale, sx, -sy, sz);
+			transform_build_matrix(t);
+			render_path_paint_commands_paint(false);
+		}
+		if (context_raw.sym_z) {
+			vec4_set(t.scale, sx, sy, -sz);
+			transform_build_matrix(t);
+			render_path_paint_commands_paint(false);
+		}
+		if (context_raw.sym_x && context_raw.sym_y) {
+			vec4_set(t.scale, -sx, -sy, sz);
+			transform_build_matrix(t);
+			render_path_paint_commands_paint(false);
+		}
+		if (context_raw.sym_x && context_raw.sym_z) {
+			vec4_set(t.scale, -sx, sy, -sz);
+			transform_build_matrix(t);
+			render_path_paint_commands_paint(false);
+		}
+		if (context_raw.sym_y && context_raw.sym_z) {
+			vec4_set(t.scale, sx, -sy, -sz);
+			transform_build_matrix(t);
+			render_path_paint_commands_paint(false);
+		}
+		if (context_raw.sym_x && context_raw.sym_y && context_raw.sym_z) {
+			vec4_set(t.scale, -sx, -sy, -sz);
+			transform_build_matrix(t);
+			render_path_paint_commands_paint(false);
+		}
+		vec4_set(t.scale, sx, sy, sz);
+		transform_build_matrix(t);
+	}
+}
+
+function render_path_paint_paint_enabled(): bool {
+	///if is_paint
+	let fill_layer: bool = context_raw.layer.fill_layer != null && context_raw.tool != workspace_tool_t.PICKER && context_raw.tool != workspace_tool_t.MATERIAL && context_raw.tool != workspace_tool_t.COLORID;
+	///end
+
+	///if is_sculpt
+	let fill_layer: bool = context_raw.layer.fill_layer != null && context_raw.tool != workspace_tool_t.PICKER && context_raw.tool != workspace_tool_t.MATERIAL;
+	///end
+
+	let group_layer: bool = slot_layer_is_group(context_raw.layer);
+	return !fill_layer && !group_layer && !context_raw.foreground_event;
+}
+
+function render_path_paint_live_brush_dirty() {
+	let mx: f32 = render_path_paint_last_x;
+	let my: f32 = render_path_paint_last_y;
+	render_path_paint_last_x = mouse_view_x();
+	render_path_paint_last_y = mouse_view_y();
+	if (config_raw.brush_live && context_raw.pdirty <= 0) {
+		let moved: bool = (mx != render_path_paint_last_x || my != render_path_paint_last_y) && (context_in_viewport() || context_in_2d_view());
+		if (moved || context_raw.brush_locked) {
+			context_raw.rdirty = 2;
+		}
+	}
+}
+
+function render_path_paint_begin() {
+
+	///if is_paint
+	if (!render_path_paint_dilated) {
+		render_path_paint_dilate(config_raw.dilate == dilate_type_t.DELAYED, true);
+		render_path_paint_dilated = true;
+	}
+	///end
+
+	if (!render_path_paint_paint_enabled()) return;
+
+	///if is_paint
+	render_path_paint_push_undo_last = history_push_undo;
+	///end
+
+	if (history_push_undo && history_undo_layers != null) {
+		history_paint();
+
+		///if is_sculpt
+		render_path_set_target("gbuffer0_undo");
+		render_path_bind_target("gbuffer0", "tex");
+		render_path_draw_shader("shader_datas/copy_pass/copy_pass");
+
+		render_path_set_target("gbufferD_undo");
+		render_path_bind_target("_main", "tex");
+		render_path_draw_shader("shader_datas/copy_pass/copy_pass");
+		///end
+	}
+
+	///if is_sculpt
+	if (history_push_undo2 && history_undo_layers != null) {
+		history_paint();
+	}
+	///end
+
+	if (context_raw.paint2d) {
+		render_path_paint_set_plane_mesh();
+	}
+
+	if (render_path_paint_live_layer_drawn > 0) render_path_paint_live_layer_drawn--;
+
+	if (config_raw.brush_live && context_raw.pdirty <= 0 && context_raw.ddirty <= 0 && context_raw.brush_time == 0) {
+		// Depth is unchanged, draw before gbuffer gets updated
+		render_path_paint_commands_live_brush();
+	}
+}
+
+function render_path_paint_end() {
+	render_path_paint_commands_cursor();
+	context_raw.ddirty--;
+	context_raw.rdirty--;
+
+	if (!render_path_paint_paint_enabled()) return;
+	context_raw.pdirty--;
+}
+
+function render_path_paint_draw() {
+	if (!render_path_paint_paint_enabled()) return;
+
+	///if (!krom_ios) // No hover on iPad, decals are painted by pen release
+	if (config_raw.brush_live && context_raw.pdirty <= 0 && context_raw.ddirty > 0 && context_raw.brush_time == 0) {
+		// gbuffer has been updated now but brush will lag 1 frame
+		render_path_paint_commands_live_brush();
+	}
+	///end
+
+	if (history_undo_layers != null) {
+		render_path_paint_commands_symmetry();
+
+		if (context_raw.pdirty > 0) render_path_paint_dilated = false;
+
+		///if is_paint
+		if (context_raw.tool == workspace_tool_t.BAKE) {
+
+			///if (krom_direct3d12 || krom_vulkan || krom_metal)
+			let is_raytraced_bake: bool = (context_raw.bake_type == bake_type_t.AO  ||
+				context_raw.bake_type == bake_type_t.LIGHTMAP ||
+				context_raw.bake_type == bake_type_t.BENT_NORMAL ||
+				context_raw.bake_type == bake_type_t.THICKNESS);
+			///end
+
+			if (context_raw.bake_type == bake_type_t.NORMAL || context_raw.bake_type == bake_type_t.HEIGHT || context_raw.bake_type == bake_type_t.DERIVATIVE) {
+				if (!render_path_paint_baking && context_raw.pdirty > 0) {
+					render_path_paint_baking = true;
+					let _bake_type: bake_type_t = context_raw.bake_type;
+					context_raw.bake_type = context_raw.bake_type == bake_type_t.NORMAL ? bake_type_t.NORMAL_OBJECT : bake_type_t.POSITION; // Bake high poly data
+					make_material_parse_paint_material();
+					let _paint_object: mesh_object_t = context_raw.paint_object;
+					let high_poly: mesh_object_t = project_paint_objects[context_raw.bake_high_poly];
+					let _visible: bool = high_poly.base.visible;
+					high_poly.base.visible = true;
+					context_select_paint_object(high_poly);
+					render_path_paint_commands_paint();
+					high_poly.base.visible = _visible;
+					if (render_path_paint_push_undo_last) history_paint();
+					context_select_paint_object(_paint_object);
+
+					let _render_final = () => {
+						context_raw.bake_type = _bake_type;
+						make_material_parse_paint_material();
+						context_raw.pdirty = 1;
+						render_path_paint_commands_paint();
+						context_raw.pdirty = 0;
+						render_path_paint_baking = false;
+					}
+					let _render_deriv = () => {
+						context_raw.bake_type = bake_type_t.HEIGHT;
+						make_material_parse_paint_material();
+						context_raw.pdirty = 1;
+						render_path_paint_commands_paint();
+						context_raw.pdirty = 0;
+						if (render_path_paint_push_undo_last) history_paint();
+						app_notify_on_init(_render_final);
+					}
+					let bake_type: bake_type_t = context_raw.bake_type as bake_type_t;
+					app_notify_on_init(bake_type == bake_type_t.DERIVATIVE ? _render_deriv : _render_final);
+				}
+			}
+			else if (context_raw.bake_type == bake_type_t.OBJECTID) {
+				let _layer_filter: i32 = context_raw.layer_filter;
+				let _paint_object: mesh_object_t = context_raw.paint_object;
+				let is_merged: bool = context_raw.merged_object != null;
+				let _visible: bool = is_merged && context_raw.merged_object.base.visible;
+				context_raw.layer_filter = 1;
+				if (is_merged) context_raw.merged_object.base.visible = false;
+
+				for (let p of project_paint_objects) {
+					context_select_paint_object(p);
+					render_path_paint_commands_paint();
+				}
+
+				context_raw.layer_filter = _layer_filter;
+				context_select_paint_object(_paint_object);
+				if (is_merged) context_raw.merged_object.base.visible = _visible;
+			}
+			///if (krom_direct3d12 || krom_vulkan || krom_metal)
+			else if (is_raytraced_bake) {
+				let dirty: bool = render_path_raytrace_bake_commands(make_material_parse_paint_material);
+				if (dirty) ui_header_handle.redraws = 2;
+				if (config_raw.dilate == dilate_type_t.INSTANT) { // && raw.pdirty == 1
+					render_path_paint_dilate(true, false);
+				}
+			}
+			///end
+			else {
+				render_path_paint_commands_paint();
+			}
+		}
+		else { // Paint
+			render_path_paint_commands_paint();
+		}
+		///end
+
+		///if is_sculpt
+		render_path_paint_commands_paint();
+		///end
+	}
+
+	if (context_raw.brush_blend_dirty) {
+		context_raw.brush_blend_dirty = false;
+		///if krom_metal
+		render_path_set_target("texpaint_blend0");
+		render_path_clear_target(0x00000000);
+		render_path_set_target("texpaint_blend1");
+		render_path_clear_target(0x00000000);
+		///else
+		render_path_set_target("texpaint_blend0", ["texpaint_blend1"]);
+		render_path_clear_target(0x00000000);
+		///end
+	}
+
+	if (context_raw.paint2d) {
+		render_path_paint_restore_plane_mesh();
+	}
+}
+
+function render_path_paint_set_plane_mesh() {
+	context_raw.paint2d_view = true;
+	render_path_paint_painto = context_raw.paint_object;
+	render_path_paint_visibles = [];
+	for (let p of project_paint_objects) {
+		render_path_paint_visibles.push(p.base.visible);
+		p.base.visible = false;
+	}
+	if (context_raw.merged_object != null) {
+		render_path_paint_merged_object_visible = context_raw.merged_object.base.visible;
+		context_raw.merged_object.base.visible = false;
+	}
+
+	let cam: camera_object_t = scene_camera;
+	mat4_set_from(context_raw.saved_camera, cam.base.transform.local);
+	render_path_paint_saved_fov = cam.data.fov;
+	viewport_update_camera_type(camera_type_t.PERSPECTIVE);
+	let m: mat4_t = mat4_identity();
+	mat4_translate(m, 0, 0, 0.5);
+	transform_set_matrix(cam.base.transform, m);
+	cam.data.fov = base_default_fov;
+	camera_object_build_proj(cam);
+	camera_object_build_mat(cam);
+
+	let tw: f32 = 0.95 * ui_view2d_pan_scale;
+	let tx: f32 = ui_view2d_pan_x / ui_view2d_ww;
+	let ty: f32 = ui_view2d_pan_y / app_h();
+	mat4_set_identity(m);
+	mat4_scale(m, vec4_create(tw, tw, 1));
+	mat4_set_loc(m, vec4_create(tx, ty, 0));
+	let m2: mat4_t = mat4_identity();
+	mat4_get_inv(m2, scene_camera.vp);
+	mat4_mult_mat(m, m2);
+
+	let tiled: bool = ui_view2d_tiled_show;
+	if (tiled && scene_get_child(".PlaneTiled") == null) {
+		// 3x3 planes
+		let posa: i32[] = [32767,0,-32767,0,10922,0,-10922,0,10922,0,-32767,0,10922,0,-10922,0,-10922,0,10922,0,-10922,0,-10922,0,-10922,0,10922,0,-32767,0,32767,0,-32767,0,10922,0,10922,0,10922,0,-10922,0,32767,0,-10922,0,10922,0,32767,0,10922,0,10922,0,32767,0,10922,0,10922,0,-10922,0,-10922,0,-32767,0,10922,0,-32767,0,-10922,0,32767,0,-10922,0,10922,0,10922,0,10922,0,-10922,0,-10922,0,-32767,0,-32767,0,-10922,0,-32767,0,-32767,0,10922,0,-32767,0,-10922,0,-10922,0,-10922,0,-32767,0,32767,0,-32767,0,32767,0,-10922,0,10922,0,-10922,0,10922,0,-10922,0,10922,0,10922,0,-10922,0,10922,0,-10922,0,10922,0,-10922,0,32767,0,-32767,0,32767,0,10922,0,10922,0,10922,0,32767,0,-10922,0,32767,0,32767,0,10922,0,32767,0,32767,0,10922,0,32767,0,-10922,0,-10922,0,-10922,0,10922,0,-32767,0,10922,0,32767,0,-10922,0,32767,0,10922,0,10922,0,10922,0,-10922,0,-32767,0,-10922,0,-10922,0,-32767,0,-10922,0,10922,0,-32767,0,10922,0,-10922,0,-10922,0,-10922,0];
+		let nora: i32[] = [0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767,0,-32767];
+		let texa: i32[] = [32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0,32767,32767,32767,0,0,0];
+		let inda: i32[] = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53];
+		let raw: mesh_data_t = {
+			name: ".PlaneTiled",
+			vertex_arrays: [
+				{ attrib: "pos", values: new Int16Array(posa), data: "short4norm" },
+				{ attrib: "nor", values: new Int16Array(nora), data: "short2norm" },
+				{ attrib: "tex", values: new Int16Array(texa), data: "short2norm" }
+			],
+			index_arrays: [
+				{ values: new Uint32Array(inda), material: 0 }
+			],
+			scale_pos: 1.5,
+			scale_tex: 1.0
+		};
+		let md: mesh_data_t = mesh_data_create(raw);
+		let materials: material_data_t[] = scene_get_child(".Plane").ext.materials;
+		let o: mesh_object_t = scene_add_mesh_object(md, materials);
+		o.base.name = ".PlaneTiled";
+	}
+
+	render_path_paint_planeo = scene_get_child(tiled ? ".PlaneTiled" : ".Plane").ext;
+	render_path_paint_planeo.base.visible = true;
+	context_raw.paint_object = render_path_paint_planeo;
+
+	let v: vec4_t = vec4_create();
+	let sx: f32 = vec4_len(vec4_set(v, m.m[0], m.m[1], m.m[2]));
+	quat_from_euler(render_path_paint_planeo.base.transform.rot, -math_pi() / 2, 0, 0);
+	vec4_set(render_path_paint_planeo.base.transform.scale, sx, 1.0, sx);
+	render_path_paint_planeo.base.transform.scale.z *= config_get_texture_res_y() / config_get_texture_res_x();
+	vec4_set(render_path_paint_planeo.base.transform.loc, m.m[12], -m.m[13], 0.0);
+	transform_build_matrix(render_path_paint_planeo.base.transform);
+}
+
+function render_path_paint_restore_plane_mesh() {
+	context_raw.paint2d_view = false;
+	render_path_paint_planeo.base.visible = false;
+	vec4_set(render_path_paint_planeo.base.transform.loc, 0.0, 0.0, 0.0);
+	for (let i: i32 = 0; i < project_paint_objects.length; ++i) {
+		project_paint_objects[i].base.visible = render_path_paint_visibles[i];
+	}
+	if (context_raw.merged_object != null) {
+		context_raw.merged_object.base.visible = render_path_paint_merged_object_visible;
+	}
+	context_raw.paint_object = render_path_paint_painto;
+	transform_set_matrix(scene_camera.base.transform, context_raw.saved_camera);
+	scene_camera.data.fov = render_path_paint_saved_fov;
+	viewport_update_camera_type(context_raw.camera_type);
+	camera_object_build_proj(scene_camera);
+	camera_object_build_mat(scene_camera);
+
+	render_path_base_draw_gbuffer();
+}
+
+function render_path_paint_bind_layers() {
+	///if is_paint
+	let is_live: bool = config_raw.brush_live && render_path_paint_live_layer_drawn > 0;
+	let is_material_tool: bool = context_raw.tool == workspace_tool_t.MATERIAL;
+	if (is_live || is_material_tool) render_path_paint_use_live_layer(true);
+	///end
+
+	for (let i: i32 = 0; i < project_layers.length; ++i) {
+		let l: SlotLayerRaw = project_layers[i];
+		render_path_bind_target("texpaint" + l.id, "texpaint" + l.id);
+
+		///if is_paint
+		if (slot_layer_is_layer(l)) {
+			render_path_bind_target("texpaint_nor" + l.id, "texpaint_nor" + l.id);
+			render_path_bind_target("texpaint_pack" + l.id, "texpaint_pack" + l.id);
+		}
+		///end
+	}
+}
+
+function render_path_paint_unbind_layers() {
+	///if is_paint
+	let is_live: bool = config_raw.brush_live && render_path_paint_live_layer_drawn > 0;
+	let is_material_tool: bool = context_raw.tool == workspace_tool_t.MATERIAL;
+	if (is_live || is_material_tool) render_path_paint_use_live_layer(false);
+	///end
+}
+
+function render_path_paint_dilate(base: bool, nor_pack: bool) {
+	///if is_paint
+	if (config_raw.dilate_radius > 0 && !context_raw.paint2d) {
+		util_uv_cache_dilate_map();
+		base_make_temp_img();
+		let tid: i32 = context_raw.layer.id;
+		if (base) {
+			let texpaint: string = "texpaint";
+			render_path_set_target("temptex0");
+			render_path_bind_target(texpaint + tid, "tex");
+			render_path_draw_shader("shader_datas/copy_pass/copy_pass");
+			render_path_set_target(texpaint + tid);
+			render_path_bind_target("temptex0", "tex");
+			render_path_draw_shader("shader_datas/dilate_pass/dilate_pass");
+		}
+		if (nor_pack && !slot_layer_is_mask(context_raw.layer)) {
+			render_path_set_target("temptex0");
+			render_path_bind_target("texpaint_nor" + tid, "tex");
+			render_path_draw_shader("shader_datas/copy_pass/copy_pass");
+			render_path_set_target("texpaint_nor" + tid);
+			render_path_bind_target("temptex0", "tex");
+			render_path_draw_shader("shader_datas/dilate_pass/dilate_pass");
+
+			render_path_set_target("temptex0");
+			render_path_bind_target("texpaint_pack" + tid, "tex");
+			render_path_draw_shader("shader_datas/copy_pass/copy_pass");
+			render_path_set_target("texpaint_pack" + tid);
+			render_path_bind_target("temptex0", "tex");
+			render_path_draw_shader("shader_datas/dilate_pass/dilate_pass");
+		}
+	}
+	///end
+}

+ 149 - 0
armorpaint/Sources/render_path_preview.ts

@@ -0,0 +1,149 @@
+
+function render_path_preview_init() {
+
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpreview";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "texpreview_icon";
+		t.width = 1;
+		t.height = 1;
+		t.format = "RGBA32";
+		render_path_create_render_target(t);
+	}
+
+	render_path_create_depth_buffer("mmain", "DEPTH24");
+
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "mtex";
+		t.width = math_floor(util_render_material_preview_size * 2.0);
+		t.height = math_floor(util_render_material_preview_size * 2.0);
+		t.format = "RGBA64";
+		t.scale = render_path_base_get_super_sampling();
+		///if krom_opengl
+		t.depth_buffer = "mmain";
+		///end
+		render_path_create_render_target(t);
+	}
+
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "mgbuffer0";
+		t.width = math_floor(util_render_material_preview_size * 2.0);
+		t.height = math_floor(util_render_material_preview_size * 2.0);
+		t.format = "RGBA64";
+		t.scale = render_path_base_get_super_sampling();
+		t.depth_buffer = "mmain";
+		render_path_create_render_target(t);
+	}
+
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "mgbuffer1";
+		t.width = math_floor(util_render_material_preview_size * 2.0);
+		t.height = math_floor(util_render_material_preview_size * 2.0);
+		t.format = "RGBA64";
+		t.scale = render_path_base_get_super_sampling();
+		render_path_create_render_target(t);
+	}
+
+	{
+		let t: render_target_t = render_target_create();
+		t.name = "mgbuffer2";
+		t.width = math_floor(util_render_material_preview_size * 2.0);
+		t.height = math_floor(util_render_material_preview_size * 2.0);
+		t.format = "RGBA64";
+		t.scale = render_path_base_get_super_sampling();
+		render_path_create_render_target(t);
+	}
+}
+
+function render_path_preview_commands_preview() {
+	render_path_set_target("mgbuffer2");
+	render_path_clear_target(0xff000000);
+
+	render_path_set_target("mgbuffer0");
+	render_path_clear_target(0xffffffff, 1.0, clear_flag_t.COLOR | clear_flag_t.DEPTH);
+	render_path_set_target("mgbuffer0", ["mgbuffer1", "mgbuffer2"]);
+	render_path_draw_meshes("mesh");
+
+	// Deferred light
+	render_path_set_target("mtex");
+	render_path_bind_target("_mmain", "gbufferD");
+	render_path_bind_target("mgbuffer0", "gbuffer0");
+	render_path_bind_target("mgbuffer1", "gbuffer1");
+	{
+		render_path_bind_target("empty_white", "ssaotex");
+	}
+	render_path_draw_shader("shader_datas/deferred_light/deferred_light");
+
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	render_path_set_depth_from("mtex", "mgbuffer0"); // Bind depth for world pass
+	///end
+
+	render_path_set_target("mtex"); // Re-binds depth
+	render_path_draw_skydome("shader_datas/world_pass/world_pass");
+
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	render_path_set_depth_from("mtex", "mgbuffer1"); // Unbind depth
+	///end
+
+	let framebuffer: string = "texpreview";
+	let selected_mat: SlotMaterialRaw = context_raw.material;
+	render_path_render_targets.get("texpreview")._image = selected_mat.image;
+	render_path_render_targets.get("texpreview_icon")._image = selected_mat.image_icon;
+
+	render_path_set_target(framebuffer);
+	render_path_bind_target("mtex", "tex");
+	render_path_draw_shader("shader_datas/compositor_pass/compositor_pass");
+
+	render_path_set_target("texpreview_icon");
+	render_path_bind_target("texpreview", "tex");
+	render_path_draw_shader("shader_datas/supersample_resolve/supersample_resolve");
+}
+
+function render_path_preview_commands_decal() {
+	render_path_set_target("gbuffer2");
+	render_path_clear_target(0xff000000);
+
+	render_path_set_target("gbuffer0");
+	render_path_clear_target(0xffffffff, 1.0, clear_flag_t.COLOR | clear_flag_t.DEPTH);
+	render_path_set_target("gbuffer0", ["gbuffer1", "gbuffer2"]);
+	render_path_draw_meshes("mesh");
+
+	// Deferred light
+	render_path_set_target("tex");
+	render_path_bind_target("_main", "gbufferD");
+	render_path_bind_target("gbuffer0", "gbuffer0");
+	render_path_bind_target("gbuffer1", "gbuffer1");
+	{
+		render_path_bind_target("empty_white", "ssaotex");
+	}
+	render_path_draw_shader("shader_datas/deferred_light/deferred_light");
+
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	render_path_set_depth_from("tex", "gbuffer0"); // Bind depth for world pass
+	///end
+
+	render_path_set_target("tex");
+	render_path_draw_skydome("shader_datas/world_pass/world_pass");
+
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	render_path_set_depth_from("tex", "gbuffer1"); // Unbind depth
+	///end
+
+	let framebuffer: string = "texpreview";
+	render_path_render_targets.get("texpreview")._image = context_raw.decal_image;
+
+	render_path_set_target(framebuffer);
+
+	render_path_bind_target("tex", "tex");
+	render_path_draw_shader("shader_datas/compositor_pass/compositor_pass");
+}

+ 30 - 0
armorpaint/Sources/slot_brush.ts

@@ -0,0 +1,30 @@
+
+class SlotBrushRaw {
+	nodes: zui_nodes_t = zui_nodes_create();
+	canvas: zui_node_canvas_t;
+	image: image_t = null; // 200px
+	image_icon: image_t = null; // 50px
+	preview_ready: bool = false;
+	id: i32 = 0;
+}
+
+let slot_brush_default_canvas: ArrayBuffer = null;
+
+function slot_brush_create(c: zui_node_canvas_t = null): SlotBrushRaw {
+	let raw: SlotBrushRaw = new SlotBrushRaw();
+	for (let brush of project_brushes) if (brush.id >= raw.id) raw.id = brush.id + 1;
+
+	if (c == null) {
+		if (slot_brush_default_canvas == null) { // Synchronous
+			let b: ArrayBuffer = data_get_blob("default_brush.arm")
+			slot_brush_default_canvas = b;
+		}
+		raw.canvas = armpack_decode(slot_brush_default_canvas);
+		raw.canvas.name = "Brush " + (raw.id + 1);
+	}
+	else {
+		raw.canvas = c;
+	}
+
+	return raw;
+}

+ 18 - 0
armorpaint/Sources/slot_font.ts

@@ -0,0 +1,18 @@
+
+class SlotFontRaw {
+	image: image_t = null; // 200px
+	preview_ready: bool = false;
+	id: i32 = 0;
+	font: g2_font_t;
+	name: string;
+	file: string;
+}
+
+function slot_font_create(name: string, font: g2_font_t, file = ""): SlotFontRaw {
+	let raw: SlotFontRaw = new SlotFontRaw();
+	for (let slot of project_fonts) if (slot.id >= raw.id) raw.id = slot.id + 1;
+	raw.name = name;
+	raw.font = font;
+	raw.file = file;
+	return raw;
+}

+ 689 - 0
armorpaint/Sources/slot_layer.ts

@@ -0,0 +1,689 @@
+
+class SlotLayerRaw {
+	id: i32 = 0;
+	name: string;
+	ext: string = "";
+	visible: bool = true;
+	parent: SlotLayerRaw = null; // Group (for layers) or layer (for masks)
+
+	texpaint: image_t = null; // Base or mask
+	///if is_paint
+	texpaint_nor: image_t = null;
+	texpaint_pack: image_t = null;
+	texpaint_preview: image_t = null; // Layer preview
+	///end
+
+	mask_opacity: f32 = 1.0; // Opacity mask
+	fill_layer: SlotMaterialRaw = null;
+	show_panel: bool = true;
+	blending = blend_type_t.MIX;
+	object_mask: i32 = 0;
+	scale: f32 = 1.0;
+	angle: f32 = 0.0;
+	uv_type = uv_type_t.UVMAP;
+	paint_base: bool = true;
+	paint_opac: bool = true;
+	paint_occ: bool = true;
+	paint_rough: bool = true;
+	paint_met: bool = true;
+	paint_nor: bool = true;
+	paint_nor_blend: bool = true;
+	paint_height: bool = true;
+	paint_height_blend: bool = true;
+	paint_emis: bool = true;
+	paint_subs: bool = true;
+	decal_mat: mat4_t = mat4_identity(); // Decal layer
+}
+
+function slot_layer_create(ext: string = "", type: layer_slot_type_t = layer_slot_type_t.LAYER, parent: SlotLayerRaw = null): SlotLayerRaw {
+	let raw: SlotLayerRaw = new SlotLayerRaw();
+	if (ext == "") {
+		raw.id = 0;
+		for (let l of project_layers) if (l.id >= raw.id) raw.id = l.id + 1;
+		ext = raw.id + "";
+	}
+	raw.ext = ext;
+	raw.parent = parent;
+
+	if (type == layer_slot_type_t.GROUP) {
+		raw.name = "Group " + (raw.id + 1);
+	}
+	else if (type == layer_slot_type_t.LAYER) {
+		raw.name = "Layer " + (raw.id + 1);
+		///if is_paint
+		let format: string = base_bits_handle.position == texture_bits_t.BITS8  ? "RGBA32" :
+								base_bits_handle.position == texture_bits_t.BITS16 ? "RGBA64" :
+																					"RGBA128";
+		///end
+
+		///if is_sculpt
+		let format: string = "RGBA128";
+		///end
+
+		{
+			let t: render_target_t = render_target_create();
+			t.name = "texpaint" + ext;
+			t.width = config_get_texture_res_x();
+			t.height = config_get_texture_res_y();
+			t.format = format;
+			raw.texpaint = render_path_create_render_target(t)._image;
+		}
+
+		///if is_paint
+		{
+			let t: render_target_t = render_target_create();
+			t.name = "texpaint_nor" + ext;
+			t.width = config_get_texture_res_x();
+			t.height = config_get_texture_res_y();
+			t.format = format;
+			raw.texpaint_nor = render_path_create_render_target(t)._image;
+		}
+		{
+			let t: render_target_t = render_target_create();
+			t.name = "texpaint_pack" + ext;
+			t.width = config_get_texture_res_x();
+			t.height = config_get_texture_res_y();
+			t.format = format;
+			raw.texpaint_pack = render_path_create_render_target(t)._image;
+		}
+
+		raw.texpaint_preview = image_create_render_target(util_render_layer_preview_size, util_render_layer_preview_size, tex_format_t.RGBA32);
+		///end
+	}
+
+	///if is_paint
+	else { // Mask
+		raw.name = "Mask " + (raw.id + 1);
+		let format: string = "RGBA32"; // Full bits for undo support, R8 is used
+		raw.blending = blend_type_t.ADD;
+
+		{
+			let t: render_target_t = render_target_create();
+			t.name = "texpaint" + ext;
+			t.width = config_get_texture_res_x();
+			t.height = config_get_texture_res_y();
+			t.format = format;
+			raw.texpaint = render_path_create_render_target(t)._image;
+		}
+
+		raw.texpaint_preview = image_create_render_target(util_render_layer_preview_size, util_render_layer_preview_size, tex_format_t.RGBA32);
+	}
+	///end
+
+	return raw;
+}
+
+function slot_layer_delete(raw: SlotLayerRaw) {
+	slot_layer_unload(raw);
+
+	if (slot_layer_is_layer(raw)) {
+		let masks: SlotLayerRaw[] = slot_layer_get_masks(raw, false); // Prevents deleting group masks
+		if (masks != null) for (let m of masks) slot_layer_delete(m);
+	}
+	else if (slot_layer_is_group(raw)) {
+		let children: SlotLayerRaw[] = slot_layer_get_children(raw);
+		if (children != null) for (let c of children) slot_layer_delete(c);
+		let masks: SlotLayerRaw[] = slot_layer_get_masks(raw);
+		if (masks != null) for (let m of masks) slot_layer_delete(m);
+	}
+
+	let lpos: i32 = project_layers.indexOf(raw);
+	array_remove(project_layers, raw);
+	// Undo can remove base layer and then restore it from undo layers
+	if (project_layers.length > 0) {
+		context_set_layer(project_layers[lpos > 0 ? lpos - 1 : 0]);
+	}
+
+	// Do not remove empty groups if the last layer is deleted as this prevents redo from working properly
+}
+
+function slot_layer_unload(raw: SlotLayerRaw) {
+	if (slot_layer_is_group(raw)) return;
+
+	let _texpaint: image_t = raw.texpaint;
+	///if is_paint
+	let _texpaint_nor: image_t = raw.texpaint_nor;
+	let _texpaint_pack: image_t = raw.texpaint_pack;
+	let _texpaint_preview: image_t = raw.texpaint_preview;
+	///end
+
+	let _next = () => {
+		image_unload(_texpaint);
+		///if is_paint
+		if (_texpaint_nor != null) image_unload(_texpaint_nor);
+		if (_texpaint_pack != null) image_unload(_texpaint_pack);
+		image_unload(_texpaint_preview);
+		///end
+	}
+	base_notify_on_next_frame(_next);
+
+	render_path_render_targets.delete("texpaint" + raw.ext);
+	///if is_paint
+	if (slot_layer_is_layer(raw)) {
+		render_path_render_targets.delete("texpaint_nor" + raw.ext);
+		render_path_render_targets.delete("texpaint_pack" + raw.ext);
+	}
+	///end
+}
+
+function slot_layer_swap(raw: SlotLayerRaw, other: SlotLayerRaw) {
+	if ((slot_layer_is_layer(raw) || slot_layer_is_mask(raw)) && (slot_layer_is_layer(other) || slot_layer_is_mask(other))) {
+		render_path_render_targets.get("texpaint" + raw.ext)._image = other.texpaint;
+		render_path_render_targets.get("texpaint" + other.ext)._image = raw.texpaint;
+		let _texpaint: image_t = raw.texpaint;
+		raw.texpaint = other.texpaint;
+		other.texpaint = _texpaint;
+
+		///if is_paint
+		let _texpaint_preview: image_t = raw.texpaint_preview;
+		raw.texpaint_preview = other.texpaint_preview;
+		other.texpaint_preview = _texpaint_preview;
+		///end
+	}
+
+	///if is_paint
+	if (slot_layer_is_layer(raw) && slot_layer_is_layer(other)) {
+		render_path_render_targets.get("texpaint_nor" + raw.ext)._image = other.texpaint_nor;
+		render_path_render_targets.get("texpaint_pack" + raw.ext)._image = other.texpaint_pack;
+		render_path_render_targets.get("texpaint_nor" + other.ext)._image = raw.texpaint_nor;
+		render_path_render_targets.get("texpaint_pack" + other.ext)._image = raw.texpaint_pack;
+		let _texpaint_nor: image_t = raw.texpaint_nor;
+		let _texpaint_pack: image_t = raw.texpaint_pack;
+		raw.texpaint_nor = other.texpaint_nor;
+		raw.texpaint_pack = other.texpaint_pack;
+		other.texpaint_nor = _texpaint_nor;
+		other.texpaint_pack = _texpaint_pack;
+	}
+	///end
+}
+
+function slot_layer_clear(raw: SlotLayerRaw, baseColor = 0x00000000, baseImage: image_t = null, occlusion = 1.0, roughness = base_default_rough, metallic = 0.0) {
+	g4_begin(raw.texpaint);
+	g4_clear(baseColor); // Base
+	g4_end();
+	if (baseImage != null) {
+		g2_begin(raw.texpaint);
+		g2_draw_scaled_image(baseImage, 0, 0, raw.texpaint.width, raw.texpaint.height);
+		g2_end();
+	}
+
+	///if is_paint
+	if (slot_layer_is_layer(raw)) {
+		g4_begin(raw.texpaint_nor);
+		g4_clear(color_from_floats(0.5, 0.5, 1.0, 0.0)); // Nor
+		g4_end();
+		g4_begin(raw.texpaint_pack);
+		g4_clear(color_from_floats(occlusion, roughness, metallic, 0.0)); // Occ, rough, met
+		g4_end();
+	}
+	///end
+
+	context_raw.layer_preview_dirty = true;
+	context_raw.ddirty = 3;
+}
+
+function slot_layer_invert_mask(raw: SlotLayerRaw) {
+	if (base_pipe_invert8 == null) base_make_pipe();
+	let inverted: image_t = image_create_render_target(raw.texpaint.width, raw.texpaint.height, tex_format_t.RGBA32);
+	g2_begin(inverted);
+	g2_set_pipeline(base_pipe_invert8);
+	g2_draw_image(raw.texpaint, 0, 0);
+	g2_set_pipeline(null);
+	g2_end();
+	let _texpaint: image_t = raw.texpaint;
+	let _next = () => {
+		image_unload(_texpaint);
+	}
+	base_notify_on_next_frame(_next);
+	raw.texpaint = render_path_render_targets.get("texpaint" + raw.id)._image = inverted;
+	context_raw.layer_preview_dirty = true;
+	context_raw.ddirty = 3;
+}
+
+function slot_layer_apply_mask(raw: SlotLayerRaw) {
+	if (raw.parent.fill_layer != null) {
+		slot_layer_to_paint_layer(raw.parent);
+	}
+	if (slot_layer_is_group(raw.parent)) {
+		for (let c of slot_layer_get_children(raw.parent)) {
+			base_apply_mask(c, raw);
+		}
+	}
+	else {
+		base_apply_mask(raw.parent, raw);
+	}
+	slot_layer_delete(raw);
+}
+
+function slot_layer_duplicate(raw: SlotLayerRaw): SlotLayerRaw {
+	let layers: SlotLayerRaw[] = project_layers;
+	let i: i32 = layers.indexOf(raw) + 1;
+	let l: SlotLayerRaw = slot_layer_create("", slot_layer_is_layer(raw) ? layer_slot_type_t.LAYER : slot_layer_is_mask(raw) ? layer_slot_type_t.MASK : layer_slot_type_t.GROUP, raw.parent);
+	layers.splice(i, 0, l);
+
+	if (base_pipe_merge == null) base_make_pipe();
+	if (slot_layer_is_layer(raw)) {
+		g2_begin(l.texpaint);
+		g2_set_pipeline(base_pipe_copy);
+		g2_draw_image(raw.texpaint, 0, 0);
+		g2_set_pipeline(null);
+		g2_end();
+		///if is_paint
+		g2_begin(l.texpaint_nor);
+		g2_set_pipeline(base_pipe_copy);
+		g2_draw_image(raw.texpaint_nor, 0, 0);
+		g2_set_pipeline(null);
+		g2_end();
+		g2_begin(l.texpaint_pack);
+		g2_set_pipeline(base_pipe_copy);
+		g2_draw_image(raw.texpaint_pack, 0, 0);
+		g2_set_pipeline(null);
+		g2_end();
+		///end
+	}
+	else if (slot_layer_is_mask(raw)) {
+		g2_begin(l.texpaint);
+		g2_set_pipeline(base_pipe_copy8);
+		g2_draw_image(raw.texpaint, 0, 0);
+		g2_set_pipeline(null);
+		g2_end();
+	}
+
+	///if is_paint
+	g2_begin(l.texpaint_preview);
+	g2_clear(0x00000000);
+	g2_set_pipeline(base_pipe_copy);
+	g2_draw_scaled_image(raw.texpaint_preview, 0, 0, raw.texpaint_preview.width, raw.texpaint_preview.height);
+	g2_set_pipeline(null);
+	g2_end();
+	///end
+
+	l.visible = raw.visible;
+	l.mask_opacity = raw.mask_opacity;
+	l.fill_layer = raw.fill_layer;
+	l.object_mask = raw.object_mask;
+	l.blending = raw.blending;
+	l.uv_type = raw.uv_type;
+	l.scale = raw.scale;
+	l.angle = raw.angle;
+	l.paint_base = raw.paint_base;
+	l.paint_opac = raw.paint_opac;
+	l.paint_occ = raw.paint_occ;
+	l.paint_rough = raw.paint_rough;
+	l.paint_met = raw.paint_met;
+	l.paint_nor = raw.paint_nor;
+	l.paint_nor_blend = raw.paint_nor_blend;
+	l.paint_height = raw.paint_height;
+	l.paint_height_blend = raw.paint_height_blend;
+	l.paint_emis = raw.paint_emis;
+	l.paint_subs = raw.paint_subs;
+
+	return l;
+}
+
+function slot_layer_resize_and_set_bits(raw: SlotLayerRaw) {
+	let res_x: i32 = config_get_texture_res_x();
+	let res_y: i32 = config_get_texture_res_y();
+	let rts: map_t<string, render_target_t> = render_path_render_targets;
+	if (base_pipe_merge == null) base_make_pipe();
+
+	if (slot_layer_is_layer(raw)) {
+		///if is_paint
+		let format: tex_format_t = base_bits_handle.position == texture_bits_t.BITS8  ? tex_format_t.RGBA32 :
+			base_bits_handle.position == texture_bits_t.BITS16 ? tex_format_t.RGBA64 :
+			tex_format_t.RGBA128;
+		///end
+
+		///if is_sculpt
+		let format: tex_format_t = tex_format_t.RGBA128;
+		///end
+
+		let _texpaint: image_t = raw.texpaint;
+		raw.texpaint = image_create_render_target(res_x, res_y, format);
+		g2_begin(raw.texpaint);
+		g2_set_pipeline(base_pipe_copy);
+		g2_draw_scaled_image(_texpaint, 0, 0, res_x, res_y);
+		g2_set_pipeline(null);
+		g2_end();
+
+		///if is_paint
+		let _texpaint_nor: image_t = raw.texpaint_nor;
+		let _texpaint_pack: image_t = raw.texpaint_pack;
+		raw.texpaint_nor = image_create_render_target(res_x, res_y, format);
+		raw.texpaint_pack = image_create_render_target(res_x, res_y, format);
+
+		g2_begin(raw.texpaint_nor);
+		g2_set_pipeline(base_pipe_copy);
+		g2_draw_scaled_image(_texpaint_nor, 0, 0, res_x, res_y);
+		g2_set_pipeline(null);
+		g2_end();
+
+		g2_begin(raw.texpaint_pack);
+		g2_set_pipeline(base_pipe_copy);
+		g2_draw_scaled_image(_texpaint_pack, 0, 0, res_x, res_y);
+		g2_set_pipeline(null);
+		g2_end();
+		///end
+
+		let _next = () => {
+			image_unload(_texpaint);
+			///if is_paint
+			image_unload(_texpaint_nor);
+			image_unload(_texpaint_pack);
+			///end
+		}
+		base_notify_on_next_frame(_next);
+
+		rts.get("texpaint" + raw.ext)._image = raw.texpaint;
+		///if is_paint
+		rts.get("texpaint_nor" + raw.ext)._image = raw.texpaint_nor;
+		rts.get("texpaint_pack" + raw.ext)._image = raw.texpaint_pack;
+		///end
+	}
+	else if (slot_layer_is_mask(raw)) {
+		let _texpaint: image_t = raw.texpaint;
+		raw.texpaint = image_create_render_target(res_x, res_y, tex_format_t.RGBA32);
+
+		g2_begin(raw.texpaint);
+		g2_set_pipeline(base_pipe_copy8);
+		g2_draw_scaled_image(_texpaint, 0, 0, res_x, res_y);
+		g2_set_pipeline(null);
+		g2_end();
+
+		let _next = () => {
+			image_unload(_texpaint);
+		}
+		base_notify_on_next_frame(_next);
+
+		rts.get("texpaint" + raw.ext)._image = raw.texpaint;
+	}
+}
+
+function slot_layer_to_fill_layer(raw: SlotLayerRaw) {
+	context_set_layer(raw);
+	raw.fill_layer = context_raw.material;
+	base_update_fill_layer();
+	let _next = () => {
+		make_material_parse_paint_material();
+		context_raw.layer_preview_dirty = true;
+		ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
+	}
+	base_notify_on_next_frame(_next);
+}
+
+function slot_layer_to_paint_layer(raw: SlotLayerRaw) {
+	context_set_layer(raw);
+	raw.fill_layer = null;
+	make_material_parse_paint_material();
+	context_raw.layer_preview_dirty = true;
+	ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
+}
+
+function slot_layer_is_visible(raw: SlotLayerRaw): bool {
+	return raw.visible && (raw.parent == null || raw.parent.visible);
+}
+
+function slot_layer_get_children(raw: SlotLayerRaw): SlotLayerRaw[] {
+	let children: SlotLayerRaw[] = null; // Child layers of a group
+	for (let l of project_layers) {
+		if (l.parent == raw && slot_layer_is_layer(l)) {
+			if (children == null) children = [];
+			children.push(l);
+		}
+	}
+	return children;
+}
+
+function slot_layer_get_recursive_children(raw: SlotLayerRaw): SlotLayerRaw[] {
+	let children: SlotLayerRaw[] = null;
+	for (let l of project_layers) {
+		if (l.parent == raw) { // Child layers and group masks
+			if (children == null) children = [];
+			children.push(l);
+		}
+		if (l.parent != null && l.parent.parent == raw) { // Layer masks
+			if (children == null) children = [];
+			children.push(l);
+		}
+	}
+	return children;
+}
+
+function slot_layer_get_masks(raw: SlotLayerRaw, includeGroupMasks = true): SlotLayerRaw[] {
+	if (slot_layer_is_mask(raw)) return null;
+
+	let children: SlotLayerRaw[] = null;
+	// Child masks of a layer
+	for (let l of project_layers) {
+		if (l.parent == raw && slot_layer_is_mask(l)) {
+			if (children == null) children = [];
+			children.push(l);
+		}
+	}
+	// Child masks of a parent group
+	if (includeGroupMasks) {
+		if (raw.parent != null && slot_layer_is_group(raw.parent)) {
+			for (let l of project_layers) {
+				if (l.parent == raw.parent && slot_layer_is_mask(l)) {
+					if (children == null) children = [];
+					children.push(l);
+				}
+			}
+		}
+	}
+	return children;
+}
+
+function slot_layer_has_masks(raw: SlotLayerRaw, includeGroupMasks = true): bool {
+	// Layer mask
+	for (let l of project_layers) {
+		if (l.parent == raw && slot_layer_is_mask(l)) {
+			return true;
+		}
+	}
+	// Group mask
+	if (includeGroupMasks && raw.parent != null && slot_layer_is_group(raw.parent)) {
+		for (let l of project_layers) {
+			if (l.parent == raw.parent && slot_layer_is_mask(l)) {
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+function slot_layer_get_opacity(raw: SlotLayerRaw): f32 {
+	let f: f32 = raw.mask_opacity;
+	if (slot_layer_is_layer(raw) && raw.parent != null) f *= raw.parent.mask_opacity;
+	return f;
+}
+
+function slot_layer_get_object_mask(raw: SlotLayerRaw): i32 {
+	return slot_layer_is_mask(raw) ? raw.parent.object_mask : raw.object_mask;
+}
+
+function slot_layer_is_layer(raw: SlotLayerRaw): bool {
+	///if is_paint
+	return raw.texpaint != null && raw.texpaint_nor != null;
+	///end
+	///if is_sculpt
+	return raw.texpaint != null;
+	///end
+}
+
+function slot_layer_is_group(raw: SlotLayerRaw): bool {
+	return raw.texpaint == null;
+}
+
+function slot_layer_get_containing_group(raw: SlotLayerRaw): SlotLayerRaw {
+	if (raw.parent != null && slot_layer_is_group(raw.parent))
+		return raw.parent;
+	else if (raw.parent != null && raw.parent.parent != null && slot_layer_is_group(raw.parent.parent))
+		return raw.parent.parent;
+	else return null;
+}
+
+function slot_layer_is_mask(raw: SlotLayerRaw): bool {
+	///if is_paint
+	return raw.texpaint != null && raw.texpaint_nor == null;
+	///end
+	///if is_sculpt
+	return false;
+	///end
+}
+
+function slot_layer_is_group_mask(raw: SlotLayerRaw): bool {
+	///if is_paint
+	return raw.texpaint != null && raw.texpaint_nor == null && slot_layer_is_group(raw.parent);
+	///end
+	///if is_sculpt
+	return false;
+	///end
+}
+
+function slot_layer_is_layer_mask(raw: SlotLayerRaw): bool {
+	///if is_paint
+	return raw.texpaint != null && raw.texpaint_nor == null && slot_layer_is_layer(raw.parent);
+	///end
+	///if is_sculpt
+	return false;
+	///end
+}
+
+function slot_layer_is_in_group(raw: SlotLayerRaw): bool {
+	return raw.parent != null && (slot_layer_is_group(raw.parent) || (raw.parent.parent != null && slot_layer_is_group(raw.parent.parent)));
+}
+
+function slot_layer_can_move(raw: SlotLayerRaw, to: i32): bool {
+	let old_index: i32 = project_layers.indexOf(raw);
+
+	let delta: i32 = to - old_index; // If delta > 0 the layer is moved up, otherwise down
+	if (to < 0 || to > project_layers.length - 1 || delta == 0) return false;
+
+	// If the layer is moved up, all layers between the old position and the new one move one down.
+	// The layers above the new position stay where they are.
+	// If the new position is on top or on bottom no upper resp. lower layer exists.
+	let new_upper_layer: SlotLayerRaw = delta > 0 ? (to < project_layers.length - 1 ? project_layers[to + 1] : null) : project_layers[to];
+
+	// Group or layer is collapsed so we check below and update the upper layer.
+	if (new_upper_layer != null && !new_upper_layer.show_panel) {
+		let children: SlotLayerRaw[] = slot_layer_get_recursive_children(new_upper_layer);
+		to -= children != null ? children.length : 0;
+		delta = to - old_index;
+		new_upper_layer = delta > 0 ? (to < project_layers.length - 1 ? project_layers[to + 1] : null) : project_layers[to];
+	}
+
+	let new_lower_layer: SlotLayerRaw = delta > 0 ? project_layers[to] : (to > 0 ? project_layers[to - 1] : null);
+
+	if (slot_layer_is_mask(raw)) {
+		// Masks can not be on top.
+		if (new_upper_layer == null) return false;
+		// Masks should not be placed below a collapsed group. This condition can be savely removed.
+		if (slot_layer_is_in_group(new_upper_layer) && !slot_layer_get_containing_group(new_upper_layer).show_panel) return false;
+		// Masks should not be placed below a collapsed layer. This condition can be savely removed.
+		if (slot_layer_is_mask(new_upper_layer) && !new_upper_layer.parent.show_panel) return false;
+	}
+
+	if (slot_layer_is_layer(raw)) {
+		// Layers can not be moved directly below its own mask(s).
+		if (new_upper_layer != null && slot_layer_is_mask(new_upper_layer) && new_upper_layer.parent == raw) return false;
+		// Layers can not be placed above a mask as the mask would be reparented.
+		if (new_lower_layer != null && slot_layer_is_mask(new_lower_layer)) return false;
+	}
+
+	// Currently groups can not be nested. Thus valid positions for groups are:
+	if (slot_layer_is_group(raw)) {
+		// At the top.
+		if (new_upper_layer == null) return true;
+		// NOT below its own children.
+		if (slot_layer_get_containing_group(new_upper_layer) == raw) return false;
+		// At the bottom.
+		if (new_lower_layer == null) return true;
+		// Above a group.
+		if (slot_layer_is_group(new_lower_layer)) return true;
+		// Above a non-grouped layer.
+		if (slot_layer_is_layer(new_lower_layer) && !slot_layer_is_in_group(new_lower_layer)) return true;
+		else return false;
+	}
+
+	return true;
+}
+
+function slot_layer_move(raw: SlotLayerRaw, to: i32) {
+	if (!slot_layer_can_move(raw, to)) {
+		return;
+	}
+
+	let pointers: map_t<SlotLayerRaw, i32> = tab_layers_init_layer_map();
+	let old_index: i32 = project_layers.indexOf(raw);
+	let delta: i32 = to - old_index;
+	let new_upper_layer: SlotLayerRaw = delta > 0 ? (to < project_layers.length - 1 ? project_layers[to + 1] : null) : project_layers[to];
+
+	// Group or layer is collapsed so we check below and update the upper layer.
+	if (new_upper_layer != null && !new_upper_layer.show_panel) {
+		let children: SlotLayerRaw[] = slot_layer_get_recursive_children(new_upper_layer);
+		to -= children != null ? children.length : 0;
+		delta = to - old_index;
+		new_upper_layer = delta > 0 ? (to < project_layers.length - 1 ? project_layers[to + 1] : null) : project_layers[to];
+	}
+
+	context_set_layer(raw);
+	history_order_layers(to);
+	ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
+
+	array_remove(project_layers, raw);
+	project_layers.splice(to, 0, raw);
+
+	if (slot_layer_is_layer(raw)) {
+		let old_parent: SlotLayerRaw = raw.parent;
+
+		if (new_upper_layer == null)
+			raw.parent = null; // Placed on top.
+		else if (slot_layer_is_in_group(new_upper_layer) && !slot_layer_get_containing_group(new_upper_layer).show_panel)
+			raw.parent = null; // Placed below a collapsed group.
+		else if (slot_layer_is_layer(new_upper_layer))
+			raw.parent = new_upper_layer.parent; // Placed below a layer, use the same parent.
+		else if (slot_layer_is_group(new_upper_layer))
+			raw.parent = new_upper_layer; // Placed as top layer in a group.
+		else if (slot_layer_is_group_mask(new_upper_layer))
+			raw.parent = new_upper_layer.parent; // Placed in a group below the lowest group mask.
+		else if (slot_layer_is_layer_mask(new_upper_layer))
+			raw.parent = slot_layer_get_containing_group(new_upper_layer); // Either the group the mask belongs to or null.
+
+		// Layers can have masks as children. These have to be moved, too.
+		let layer_masks: SlotLayerRaw[] = slot_layer_get_masks(raw, false);
+		if (layer_masks != null) {
+			for (let idx: i32 = 0; idx < layer_masks.length; ++idx) {
+				let mask: SlotLayerRaw = layer_masks[idx];
+				array_remove(project_layers, mask);
+				// If the masks are moved down each step increases the index below the layer by one.
+				project_layers.splice(delta > 0 ? old_index + delta - 1 : old_index + delta + idx, 0, mask);
+			}
+		}
+
+		// The layer is the last layer in the group, remove it. Notice that this might remove group masks.
+		if (old_parent != null && slot_layer_get_children(old_parent) == null)
+			slot_layer_delete(old_parent);
+	}
+	else if (slot_layer_is_mask(raw)) {
+		// Precondition newUpperLayer != null, ensured in canMove.
+		if (slot_layer_is_layer(new_upper_layer) || slot_layer_is_group(new_upper_layer))
+			raw.parent = new_upper_layer;
+		else if (slot_layer_is_mask(new_upper_layer)) { // Group mask or layer mask.
+			raw.parent = new_upper_layer.parent;
+		}
+	}
+	else if (slot_layer_is_group(raw)) {
+		let children: SlotLayerRaw[] = slot_layer_get_recursive_children(raw);
+		if (children != null) {
+			for (let idx: i32 = 0; idx < children.length; ++idx) {
+				let child: SlotLayerRaw = children[idx];
+				array_remove(project_layers, child);
+				// If the children are moved down each step increases the index below the layer by one.
+				project_layers.splice(delta > 0 ? old_index + delta - 1 : old_index + delta + idx, 0, child);
+			}
+		}
+	}
+
+	for (let m of project_materials) tab_layers_remap_layer_pointers(m.canvas.nodes, tab_layers_fill_layer_map(pointers));
+}

+ 68 - 0
armorpaint/Sources/slot_material.ts

@@ -0,0 +1,68 @@
+
+class SlotMaterialRaw {
+	nodes: zui_nodes_t = zui_nodes_create();
+	canvas: zui_node_canvas_t;
+	image: image_t = null;
+	image_icon: image_t = null;
+	preview_ready: bool = false;
+	data: material_data_t;
+	id: i32 = 0;
+
+	paint_base: bool = true;
+	paint_opac: bool = true;
+	paint_occ: bool = true;
+	paint_rough: bool = true;
+	paint_met: bool = true;
+	paint_nor: bool = true;
+	paint_height: bool = true;
+	paint_emis: bool = true;
+	paint_subs: bool = true;
+}
+
+let slot_material_default_canvas: ArrayBuffer = null;
+
+function slot_material_create(m: material_data_t = null, c: zui_node_canvas_t = null): SlotMaterialRaw {
+	let raw: SlotMaterialRaw = new SlotMaterialRaw();
+	for (let mat of project_materials) if (mat.id >= raw.id) raw.id = mat.id + 1;
+	raw.data = m;
+
+	let w: i32 = util_render_material_preview_size;
+	let w_icon: i32 = 50;
+	raw.image = image_create_render_target(w, w);
+	raw.image_icon = image_create_render_target(w_icon, w_icon);
+
+	if (c == null) {
+		if (slot_material_default_canvas == null) { // Synchronous
+			let b: ArrayBuffer = data_get_blob("default_material.arm");
+			slot_material_default_canvas = b;
+		}
+		raw.canvas = armpack_decode(slot_material_default_canvas);
+		raw.canvas.name = "Material " + (raw.id + 1);
+	}
+	else {
+		raw.canvas = c;
+	}
+
+	///if (krom_android || krom_ios)
+	raw.nodes.panX -= 50; // Center initial position
+	///end
+
+	return raw;
+}
+
+function slot_material_unload(raw: SlotMaterialRaw) {
+	let _next = () => {
+		image_unload(raw.image);
+		image_unload(raw.image_icon);
+	}
+	base_notify_on_next_frame(_next);
+}
+
+function slot_material_delete(raw: SlotMaterialRaw) {
+	slot_material_unload(raw);
+	let mpos: i32 = project_materials.indexOf(raw);
+	array_remove(project_materials, this);
+	if (project_materials.length > 0) {
+		context_set_material(project_materials[mpos > 0 ? mpos - 1 : 0]);
+	}
+}

+ 1075 - 0
armorpaint/Sources/tab_layers.ts

@@ -0,0 +1,1075 @@
+
+let tab_layers_layer_name_edit: i32 = -1;
+let tab_layers_layer_name_handle: zui_handle_t = zui_handle_create();
+let tab_layers_show_context_menu: bool = false;
+
+function tab_layers_draw(htab: zui_handle_t) {
+	let mini: bool = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_base_sidebar_mini_w;
+	mini ? tab_layers_draw_mini(htab) : tab_layers_draw_full(htab);
+}
+
+function tab_layers_draw_mini(htab: zui_handle_t) {
+	let ui: zui_t = ui_base_ui;
+	zui_set_hovered_tab_name(tr("Layers"));
+
+	let _ELEMENT_H: i32 = ui.t.ELEMENT_H;
+	ui.t.ELEMENT_H = math_floor(ui_base_sidebar_mini_w / 2 / zui_SCALE(ui));
+
+	zui_begin_sticky();
+	zui_separator(5);
+
+	tab_layers_combo_filter();
+	tab_layers_button_2d_view();
+	tab_layers_button_new("+");
+
+	zui_end_sticky();
+	ui._y += 2;
+
+	tab_layers_highlight_odd_lines();
+	tab_layers_draw_slots(true);
+
+	ui.t.ELEMENT_H = _ELEMENT_H;
+}
+
+function tab_layers_draw_full(htab: zui_handle_t) {
+	let ui: zui_t = ui_base_ui;
+	if (zui_tab(htab, tr("Layers"))) {
+		zui_begin_sticky();
+		zui_row([1 / 4, 1 / 4, 1 / 2]);
+
+		tab_layers_button_new(tr("New"));
+		tab_layers_button_2d_view();
+		tab_layers_combo_filter();
+
+		zui_end_sticky();
+		ui._y += 2;
+
+		tab_layers_highlight_odd_lines();
+		tab_layers_draw_slots(false);
+	}
+}
+
+function tab_layers_button_2d_view() {
+	let ui: zui_t = ui_base_ui;
+	if (zui_button(tr("2D View"))) {
+		ui_base_show_2d_view(view_2d_type_t.LAYER);
+	}
+	else if (ui.is_hovered) zui_tooltip(tr("Show 2D View") + ` (${config_keymap.toggle_2d_view})`);
+}
+
+function tab_layers_draw_slots(mini: bool) {
+	for (let i: i32 = 0; i < project_layers.length; ++i) {
+		if (i >= project_layers.length) break; // Layer was deleted
+		let j: i32 = project_layers.length - 1 - i;
+		let l: SlotLayerRaw = project_layers[j];
+		tab_layers_draw_layer_slot(l, j, mini);
+	}
+}
+
+function tab_layers_highlight_odd_lines() {
+	let ui: zui_t = ui_base_ui;
+	let step: i32 = ui.t.ELEMENT_H * 2;
+	let full_h: i32 = ui._window_h - ui_base_hwnds[0].scroll_offset;
+	for (let i: i32 = 0; i < math_floor(full_h / step); ++i) {
+		if (i % 2 == 0) {
+			zui_fill(0, i * step, (ui._w / zui_SCALE(ui) - 2), step, ui.t.WINDOW_BG_COL - 0x00040404);
+		}
+	}
+}
+
+function tab_layers_button_new(text: string) {
+	if (zui_button(text)) {
+		ui_menu_draw((ui: zui_t) => {
+			let l: SlotLayerRaw = context_raw.layer;
+			if (ui_menu_button(ui, tr("Paint Layer"))) {
+				base_new_layer();
+				history_new_layer();
+			}
+			if (ui_menu_button(ui, tr("Fill Layer"))) {
+				base_create_fill_layer(uv_type_t.UVMAP);
+			}
+			if (ui_menu_button(ui, tr("Decal Layer"))) {
+				base_create_fill_layer(uv_type_t.PROJECT);
+			}
+			if (ui_menu_button(ui, tr("Black Mask"))) {
+				if (slot_layer_is_mask(l)) context_set_layer(l.parent);
+				// let l: SlotLayerRaw = raw.layer;
+
+				let m: SlotLayerRaw = base_new_mask(false, l);
+				let _next = () => {
+					slot_layer_clear(m, 0x00000000);
+				}
+				base_notify_on_next_frame(_next);
+				context_raw.layer_preview_dirty = true;
+				history_new_black_mask();
+				base_update_fill_layers();
+			}
+			if (ui_menu_button(ui, tr("White Mask"))) {
+				if (slot_layer_is_mask(l)) context_set_layer(l.parent);
+				// let l: SlotLayerRaw = raw.layer;
+
+				let m: SlotLayerRaw = base_new_mask(false, l);
+				let _next = () => {
+					slot_layer_clear(m, 0xffffffff);
+				}
+				base_notify_on_next_frame(_next);
+				context_raw.layer_preview_dirty = true;
+				history_new_white_mask();
+				base_update_fill_layers();
+			}
+			if (ui_menu_button(ui, tr("Fill Mask"))) {
+				if (slot_layer_is_mask(l)) context_set_layer(l.parent);
+				// let l: SlotLayerRaw = raw.layer;
+
+				let m: SlotLayerRaw = base_new_mask(false, l);
+				let _init = () => {
+					slot_layer_to_fill_layer(m);
+				}
+				app_notify_on_init(_init);
+				context_raw.layer_preview_dirty = true;
+				history_new_fill_mask();
+				base_update_fill_layers();
+			}
+			ui.enabled = !slot_layer_is_group(context_raw.layer) && !slot_layer_is_in_group(context_raw.layer);
+			if (ui_menu_button(ui, tr("Group"))) {
+				if (slot_layer_is_group(l) || slot_layer_is_in_group(l)) return;
+
+				if (slot_layer_is_layer_mask(l)) l = l.parent;
+
+				let pointers: map_t<SlotLayerRaw, i32> = tab_layers_init_layer_map();
+				let group = base_new_group();
+				context_set_layer(l);
+				array_remove(project_layers, group);
+				project_layers.splice(project_layers.indexOf(l) + 1, 0, group);
+				l.parent = group;
+				for (let m of project_materials) tab_layers_remap_layer_pointers(m.canvas.nodes, tab_layers_fill_layer_map(pointers));
+				context_set_layer(group);
+				history_new_group();
+			}
+			ui.enabled = true;
+		}, 7);
+	}
+}
+
+function tab_layers_combo_filter() {
+	let ar: string[] = [tr("All")];
+	for (let p of project_paint_objects) ar.push(p.base.name);
+	let atlases: string[] = project_get_used_atlases();
+	if (atlases != null) for (let a of atlases) ar.push(a);
+	let filter_handle: zui_handle_t = zui_handle("tablayers_0");
+	filter_handle.position = context_raw.layer_filter;
+	context_raw.layer_filter = zui_combo(filter_handle, ar, tr("Filter"), false, zui_align_t.LEFT);
+	if (filter_handle.changed) {
+		for (let p of project_paint_objects) {
+			p.base.visible = context_raw.layer_filter == 0 || p.base.name == ar[context_raw.layer_filter] || project_is_atlas_object(p);
+		}
+		if (context_raw.layer_filter == 0 && context_raw.merged_object_is_atlas) { // All
+			util_mesh_merge();
+		}
+		else if (context_raw.layer_filter > project_paint_objects.length) { // Atlas
+			let visibles: mesh_object_t[] = [];
+			for (let p of project_paint_objects) if (p.base.visible) visibles.push(p);
+			util_mesh_merge(visibles);
+		}
+		base_set_object_mask();
+		util_uv_uvmap_cached = false;
+		context_raw.ddirty = 2;
+		///if (krom_direct3d12 || krom_vulkan || krom_metal)
+		render_path_raytrace_ready = false;
+		///end
+	}
+}
+
+function tab_layers_remap_layer_pointers(nodes: zui_node_t[], pointer_map: map_t<i32, i32>) {
+	for (let n of nodes) {
+		if (n.type == "LAYER" || n.type == "LAYER_MASK") {
+			let i: any = n.buttons[0].default_value;
+			if (pointer_map.has(i)) {
+				n.buttons[0].default_value = pointer_map.get(i);
+			}
+		}
+	}
+}
+
+function tab_layers_init_layer_map(): map_t<SlotLayerRaw, i32> {
+	let res: map_t<SlotLayerRaw, i32> = map_create();
+	for (let i: i32 = 0; i < project_layers.length; ++i) res.set(project_layers[i], i);
+	return res;
+}
+
+function tab_layers_fill_layer_map(map: map_t<SlotLayerRaw, i32>): map_t<i32, i32> {
+	let res: map_t<i32, i32> = map_create();
+	for (let l of map.keys()) res.set(map.get(l), project_layers.indexOf(l) > -1 ? project_layers.indexOf(l) : 9999);
+	return res;
+}
+
+function tab_layers_set_drag_layer(layer: SlotLayerRaw, off_x: f32, off_y: f32) {
+	base_drag_off_x = off_x;
+	base_drag_off_y = off_y;
+	base_drag_layer = layer;
+	context_raw.drag_dest = project_layers.indexOf(layer);
+}
+
+function tab_layers_draw_layer_slot(l: SlotLayerRaw, i: i32, mini: bool) {
+	let ui: zui_t = ui_base_ui;
+
+	if (context_raw.layer_filter > 0 &&
+		slot_layer_get_object_mask(l) > 0 &&
+		slot_layer_get_object_mask(l) != context_raw.layer_filter) {
+		return;
+	}
+
+	if (l.parent != null && !l.parent.show_panel) { // Group closed
+		return;
+	}
+	if (l.parent != null && l.parent.parent != null && !l.parent.parent.show_panel) {
+		return;
+	}
+
+	let step: i32 = ui.t.ELEMENT_H;
+	let checkw: f32 = (ui._window_w / 100 * 8) / zui_SCALE(ui);
+
+	// Highlight drag destination
+	let absy: f32 = ui._window_y + ui._y;
+	if (base_is_dragging && base_drag_layer != null && context_in_layers()) {
+		if (mouse_y > absy + step && mouse_y < absy + step * 3) {
+			let down: bool = project_layers.indexOf(base_drag_layer) >= i;
+			context_raw.drag_dest = down ? i : i - 1;
+
+			let ls: SlotLayerRaw[] = project_layers;
+			let dest: i32 = context_raw.drag_dest;
+			let to_group: bool = down ? dest > 0 && ls[dest - 1].parent != null && ls[dest - 1].parent.show_panel : dest < ls.length && ls[dest].parent != null && ls[dest].parent.show_panel;
+			let nested_group: bool = slot_layer_is_group(base_drag_layer) && to_group;
+			if (!nested_group) {
+				if (slot_layer_can_move(context_raw.layer, context_raw.drag_dest)) {
+					zui_fill(checkw, step * 2, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
+				}
+			}
+		}
+		else if (i == project_layers.length - 1 && mouse_y < absy + step) {
+			context_raw.drag_dest = project_layers.length - 1;
+			if (slot_layer_can_move(context_raw.layer, context_raw.drag_dest)) {
+				zui_fill(checkw, 0, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
+			}
+		}
+	}
+	if (base_is_dragging && (base_drag_material != null || base_drag_swatch != null) && context_in_layers()) {
+		if (mouse_y > absy + step && mouse_y < absy + step * 3) {
+			context_raw.drag_dest = i;
+			if (tab_layers_can_drop_new_layer(i))
+				zui_fill(checkw, 2 * step, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
+		}
+		else if (i == project_layers.length - 1 && mouse_y < absy + step) {
+			context_raw.drag_dest = project_layers.length;
+			if (tab_layers_can_drop_new_layer(project_layers.length))
+				zui_fill(checkw, 0, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
+		}
+	}
+
+	mini ? tab_layers_draw_layer_slot_mini(l, i) : tab_layers_draw_layer_slot_full(l, i);
+
+	tab_layers_draw_layer_highlight(l, mini);
+
+	if (tab_layers_show_context_menu) {
+		tab_layers_draw_layer_context_menu(l, mini);
+	}
+}
+
+function tab_layers_draw_layer_slot_mini(l: SlotLayerRaw, i: i32) {
+	let ui = ui_base_ui;
+
+	zui_row([1, 1]);
+	let uix: f32 = ui._x;
+	let uiy: f32 = ui._y;
+	let state: zui_state_t = tab_layers_draw_layer_icon(l, i, uix, uiy, true);
+	tab_layers_handle_layer_icon_state(l, i, state, uix, uiy);
+	zui_end_element();
+
+	ui._y += zui_ELEMENT_H(ui);
+	ui._y -= zui_ELEMENT_OFFSET(ui);
+}
+
+function tab_layers_draw_layer_slot_full(l: SlotLayerRaw, i: i32) {
+	let ui: zui_t = ui_base_ui;
+
+	let step: i32 = ui.t.ELEMENT_H;
+
+	let has_panel: bool = slot_layer_is_group(l) || (slot_layer_is_layer(l) && slot_layer_get_masks(l, false) != null);
+	if (has_panel) {
+		zui_row([8 / 100, 16 / 100, 36 / 100, 30 / 100, 10 / 100]);
+	}
+	else {
+		zui_row([8 / 100, 16 / 100, 36 / 100, 30 / 100]);
+	}
+
+	// Draw eye icon
+	let icons: image_t = resource_get("icons.k");
+	let r: rect_t = resource_tile18(icons, l.visible ? 0 : 1, 0);
+	let center: f32 = (step / 2) * zui_SCALE(ui);
+	ui._x += 2;
+	ui._y += 3;
+	ui._y += center;
+	let col: i32 = ui.t.ACCENT_SELECT_COL;
+	let parent_hidden: bool = l.parent != null && (!l.parent.visible || (l.parent.parent != null && !l.parent.parent.visible));
+	if (parent_hidden) col -= 0x99000000;
+
+	if (zui_image(icons, col, -1.0, r.x, r.y, r.w, r.h) == zui_state_t.RELEASED) {
+		tab_layers_layer_toggle_visible(l);
+	}
+	ui._x -= 2;
+	ui._y -= 3;
+	ui._y -= center;
+
+	///if krom_opengl
+	ui.image_invert_y = l.fill_layer != null;
+	///end
+
+	let uix: f32 = ui._x;
+	let uiy: f32 = ui._y;
+	ui._x += 2;
+	ui._y += 3;
+	if (l.parent != null) {
+		ui._x += 10 * zui_SCALE(ui);
+		if (l.parent.parent != null) ui._x += 10 * zui_SCALE(ui);
+	}
+
+	let state: zui_state_t = tab_layers_draw_layer_icon(l, i, uix, uiy, false);
+
+	ui._x -= 2;
+	ui._y -= 3;
+
+	if (config_raw.touch_ui) {
+		ui._x += 12 * zui_SCALE(ui);
+	}
+
+	///if krom_opengl
+	ui.image_invert_y = false;
+	///end
+
+	tab_layers_handle_layer_icon_state(l, i, state, uix, uiy);
+
+	// Draw layer name
+	ui._y += center;
+	if (tab_layers_layer_name_edit == l.id) {
+		tab_layers_layer_name_handle.text = l.name;
+		l.name = zui_text_input(tab_layers_layer_name_handle);
+		if (ui.text_selected_handle_ptr != tab_layers_layer_name_handle.ptr) tab_layers_layer_name_edit = -1;
+	}
+	else {
+		if (ui.enabled && ui.input_enabled && ui.combo_selected_handle_ptr == 0 &&
+			ui.input_x > ui._window_x + ui._x && ui.input_x < ui._window_x + ui._window_w &&
+			ui.input_y > ui._window_y + ui._y - center && ui.input_y < ui._window_y + ui._y - center + (step * zui_SCALE(ui)) * 2) {
+			if (ui.input_started) {
+				context_set_layer(l);
+				tab_layers_set_drag_layer(context_raw.layer, -(mouse_x - uix - ui._window_x - 3), -(mouse_y - uiy - ui._window_y + 1));
+			}
+			else if (ui.input_released_r) {
+				context_set_layer(l);
+				tab_layers_show_context_menu = true;
+			}
+		}
+
+		let state: zui_state_t = zui_text(l.name);
+		if (state == zui_state_t.RELEASED) {
+			if (time_time() - context_raw.select_time < 0.25) {
+				tab_layers_layer_name_edit = l.id;
+				tab_layers_layer_name_handle.text = l.name;
+				zui_start_text_edit(tab_layers_layer_name_handle);
+			}
+			context_raw.select_time = time_time();
+		}
+
+		let in_focus: bool = ui.input_x > ui._window_x && ui.input_x < ui._window_x + ui._window_w &&
+								ui.input_y > ui._window_y && ui.input_y < ui._window_y + ui._window_h;
+		if (in_focus && ui.is_delete_down && tab_layers_can_delete(context_raw.layer)) {
+			ui.is_delete_down = false;
+			let _init = () => {
+				tab_layers_delete_layer(context_raw.layer);
+			}
+			app_notify_on_init(_init);
+		}
+	}
+	ui._y -= center;
+
+	if (l.parent != null) {
+		ui._x -= 10 * zui_SCALE(ui);
+		if (l.parent.parent != null) ui._x -= 10 * zui_SCALE(ui);
+	}
+
+	if (slot_layer_is_group(l)) {
+		zui_end_element();
+	}
+	else {
+		if (slot_layer_is_mask(l)) {
+			ui._y += center;
+		}
+
+		tab_layers_combo_blending(ui, l);
+
+		if (slot_layer_is_mask(l)) {
+			ui._y -= center;
+		}
+	}
+
+	if (has_panel) {
+		ui._y += center;
+		let layer_panel: zui_handle_t = zui_nest(zui_handle("tablayers_1"), l.id);
+		layer_panel.selected = l.show_panel;
+		l.show_panel = zui_panel(layer_panel, "", true, false, false);
+		ui._y -= center;
+	}
+
+	if (slot_layer_is_group(l) || slot_layer_is_mask(l)) {
+		ui._y -= zui_ELEMENT_OFFSET(ui);
+		zui_end_element();
+	}
+	else {
+		ui._y -= zui_ELEMENT_OFFSET(ui);
+
+		zui_row([8 / 100, 16 / 100, 36 / 100, 30 / 100, 10 / 100]);
+		zui_end_element();
+		zui_end_element();
+		zui_end_element();
+
+		if (config_raw.touch_ui) {
+			ui._x += 12 * zui_SCALE(ui);
+		}
+
+		tab_layers_combo_object(ui, l);
+		zui_end_element();
+	}
+
+	ui._y -= zui_ELEMENT_OFFSET(ui);
+}
+
+function tab_layers_combo_object(ui: zui_t, l: SlotLayerRaw, label = false): zui_handle_t {
+	let ar: string[] = [tr("Shared")];
+	for (let p of project_paint_objects) ar.push(p.base.name);
+	let atlases: string[] = project_get_used_atlases();
+	if (atlases != null) for (let a of atlases) ar.push(a);
+	let object_handle: zui_handle_t = zui_nest(zui_handle("tablayers_2"), l.id);
+	object_handle.position = l.object_mask;
+	l.object_mask = zui_combo(object_handle, ar, tr("Object"), label, zui_align_t.LEFT);
+	if (object_handle.changed) {
+		context_set_layer(l);
+		make_material_parse_mesh_material();
+		if (l.fill_layer != null) { // Fill layer
+			let _init = () => {
+				context_raw.material = l.fill_layer;
+				slot_layer_clear(l);
+				base_update_fill_layers();
+			}
+			app_notify_on_init(_init);
+		}
+		else {
+			base_set_object_mask();
+		}
+	}
+	return object_handle;
+}
+
+function tab_layers_combo_blending(ui: zui_t, l: SlotLayerRaw, label = false): zui_handle_t {
+	let blending_handle: zui_handle_t = zui_nest(zui_handle("tablayers_3"), l.id);
+	blending_handle.position = l.blending;
+	zui_combo(blending_handle, [
+		tr("Mix"),
+		tr("Darken"),
+		tr("Multiply"),
+		tr("Burn"),
+		tr("Lighten"),
+		tr("Screen"),
+		tr("Dodge"),
+		tr("Add"),
+		tr("Overlay"),
+		tr("Soft Light"),
+		tr("Linear Light"),
+		tr("Difference"),
+		tr("Subtract"),
+		tr("Divide"),
+		tr("Hue"),
+		tr("Saturation"),
+		tr("Color"),
+		tr("Value"),
+	], tr("Blending"), label);
+	if (blending_handle.changed) {
+		context_set_layer(l);
+		history_layer_blending();
+		l.blending = blending_handle.position;
+		make_material_parse_mesh_material();
+	}
+	return blending_handle;
+}
+
+function tab_layers_layer_toggle_visible(l: SlotLayerRaw) {
+	l.visible = !l.visible;
+	ui_view2d_hwnd.redraws = 2;
+	make_material_parse_mesh_material();
+}
+
+function tab_layers_draw_layer_highlight(l: SlotLayerRaw, mini: bool) {
+	let ui: zui_t = ui_base_ui;
+	let step: i32 = ui.t.ELEMENT_H;
+
+	// Separator line
+	zui_fill(0, 0, (ui._w / zui_SCALE(ui) - 2), 1 * zui_SCALE(ui), ui.t.SEPARATOR_COL);
+
+	// Highlight selected
+	if (context_raw.layer == l) {
+		if (mini) {
+			zui_rect(1, -step * 2, ui._w / zui_SCALE(ui) - 1, step * 2 + (mini ? -1 : 1), ui.t.HIGHLIGHT_COL, 3);
+		}
+		else {
+			zui_rect(1, -step * 2 - 1, ui._w / zui_SCALE(ui) - 2, step * 2 + (mini ? -2 : 1), ui.t.HIGHLIGHT_COL, 2);
+		}
+	}
+}
+
+function tab_layers_handle_layer_icon_state(l: SlotLayerRaw, i: i32, state: zui_state_t, uix: f32, uiy: f32) {
+	let ui: zui_t = ui_base_ui;
+
+	///if is_paint
+	let texpaint_preview: image_t = l.texpaint_preview;
+	///end
+	///if is_sculpt
+	let texpaint_preview: image_t = l.texpaint;
+	///end
+
+	tab_layers_show_context_menu = false;
+
+	// Layer preview tooltip
+	if (ui.is_hovered && texpaint_preview != null) {
+		if (slot_layer_is_mask(l)) {
+			tab_layers_make_mask_preview_rgba32(l);
+			zui_tooltip_image(context_raw.mask_preview_rgba32);
+		}
+		else {
+			zui_tooltip_image(texpaint_preview);
+		}
+		if (i < 9) zui_tooltip(l.name + " - (" + config_keymap.select_layer + " " + (i + 1) + ")");
+		else zui_tooltip(l.name);
+	}
+
+	// Show context menu
+	if (ui.is_hovered && ui.input_released_r) {
+		context_set_layer(l);
+		tab_layers_show_context_menu = true;
+	}
+
+	if (state == zui_state_t.STARTED) {
+		context_set_layer(l);
+		tab_layers_set_drag_layer(context_raw.layer, -(mouse_x - uix - ui._window_x - 3), -(mouse_y - uiy - ui._window_y + 1));
+	}
+	else if (state == zui_state_t.RELEASED) {
+		if (time_time() - context_raw.select_time < 0.2) {
+			ui_base_show_2d_view(view_2d_type_t.LAYER);
+		}
+		if (time_time() - context_raw.select_time > 0.2) {
+			context_raw.select_time = time_time();
+		}
+		if (l.fill_layer != null) context_set_material(l.fill_layer);
+	}
+}
+
+function tab_layers_draw_layer_icon(l: SlotLayerRaw, i: i32, uix: f32, uiy: f32, mini: bool) {
+	let ui: zui_t = ui_base_ui;
+	let icons: image_t = resource_get("icons.k");
+	let icon_h: i32 = (zui_ELEMENT_H(ui) - (mini ? 2 : 3)) * 2;
+
+	if (mini && zui_SCALE(ui) > 1) {
+		ui._x -= 1 * zui_SCALE(ui);
+	}
+
+	if (l.parent != null) {
+		ui._x += (icon_h - icon_h * 0.9) / 2;
+		icon_h *= 0.9;
+		if (l.parent.parent != null) {
+			ui._x += (icon_h - icon_h * 0.9) / 2;
+			icon_h *= 0.9;
+		}
+	}
+
+	if (!slot_layer_is_group(l)) {
+		///if is_paint
+		let texpaint_preview: image_t = l.texpaint_preview;
+		///end
+		///if is_sculpt
+		let texpaint_preview: image_t = l.texpaint;
+		///end
+
+		let icon: image_t = l.fill_layer == null ? texpaint_preview : l.fill_layer.image_icon;
+		if (l.fill_layer == null) {
+			// Checker
+			let r: rect_t = resource_tile50(icons, 4, 1);
+			let _x: f32 = ui._x;
+			let _y: f32 = ui._y;
+			let _w: f32 = ui._w;
+			zui_image(icons, 0xffffffff, icon_h, r.x, r.y, r.w, r.h);
+			ui.cur_ratio--;
+			ui._x = _x;
+			ui._y = _y;
+			ui._w = _w;
+		}
+		if (l.fill_layer == null && slot_layer_is_mask(l)) {
+			g2_set_pipeline(ui_view2d_pipe);
+			///if krom_opengl
+			krom_g4_set_pipeline(ui_view2d_pipe.pipeline_);
+			///end
+			krom_g4_set_int(ui_view2d_channel_loc, 1);
+		}
+
+		let state: zui_state_t = zui_image(icon, 0xffffffff, icon_h);
+
+		if (l.fill_layer == null && slot_layer_is_mask(l)) {
+			g2_set_pipeline(null);
+		}
+
+		// Draw layer numbers when selecting a layer via keyboard shortcut
+		let is_typing: bool = ui.is_typing || ui_view2d_ui.is_typing || ui_nodes_ui.is_typing;
+		if (!is_typing) {
+			if (i < 9 && operator_shortcut(config_keymap.select_layer, shortcut_type_t.DOWN)) {
+				let number: string = String(i + 1) ;
+				let width: i32 = g2_font_width(ui.font, ui.font_size, number) + 10;
+				let height: i32 = g2_font_height(ui.font, ui.font_size);
+				g2_set_color(ui.t.TEXT_COL);
+				g2_fill_rect(uix, uiy, width, height);
+				g2_set_color(ui.t.ACCENT_COL);
+				g2_draw_string(number, uix + 5, uiy);
+			}
+		}
+
+		return state;
+	}
+	else { // Group
+		let folder_closed: rect_t = resource_tile50(icons, 2, 1);
+		let folder_open: rect_t = resource_tile50(icons, 8, 1);
+		let folder: rect_t = l.show_panel ? folder_open : folder_closed;
+		return zui_image(icons, ui.t.LABEL_COL - 0x00202020, icon_h, folder.x, folder.y, folder.w, folder.h);
+	}
+}
+
+function tab_layers_can_merge_down(l: SlotLayerRaw) : bool {
+	let index: i32 = project_layers.indexOf(l);
+	// Lowest layer
+	if (index == 0) return false;
+	// Lowest layer that has masks
+	if (slot_layer_is_layer(l) && slot_layer_is_mask(project_layers[0]) && project_layers[0].parent == l) return false;
+	// The lowest toplevel layer is a group
+	if (slot_layer_is_group(l) && slot_layer_is_in_group(project_layers[0]) && slot_layer_get_containing_group(project_layers[0]) == l) return false;
+	// Masks must be merged down to masks
+	if (slot_layer_is_mask(l) && !slot_layer_is_mask(project_layers[index - 1])) return false;
+	return true;
+}
+
+function tab_layers_draw_layer_context_menu(l: SlotLayerRaw, mini: bool) {
+	let add: i32 = 0;
+
+	if (l.fill_layer == null) add += 1; // Clear
+	if (l.fill_layer != null && !slot_layer_is_mask(l)) add += 3;
+	if (l.fill_layer != null && slot_layer_is_mask(l)) add += 2;
+	if (slot_layer_is_mask(l)) add += 2;
+	if (mini) {
+		add += 1;
+		if (!slot_layer_is_group(l)) add += 1;
+		if (slot_layer_is_layer(l)) add += 1;
+	}
+	let menu_elements: i32 = slot_layer_is_group(l) ? 7 : (19 + add);
+
+	ui_menu_draw((ui: zui_t) => {
+
+		if (mini) {
+			let visible_handle: zui_handle_t = zui_handle("tablayers_4");
+			visible_handle.selected = l.visible;
+			ui_menu_fill(ui);
+			zui_check(visible_handle, tr("Visible"));
+			if (visible_handle.changed) {
+				tab_layers_layer_toggle_visible(l);
+				ui_menu_keep_open = true;
+			}
+
+			if (!slot_layer_is_group(l)) {
+				ui_menu_fill(ui);
+				if (tab_layers_combo_blending(ui, l, true).changed) {
+					ui_menu_keep_open = true;
+				}
+			}
+			if (slot_layer_is_layer(l)) {
+				ui_menu_fill(ui);
+				if (tab_layers_combo_object(ui, l, true).changed) {
+					ui_menu_keep_open = true;
+				}
+			}
+		}
+
+		if (ui_menu_button(ui, tr("Export"))) {
+			if (slot_layer_is_mask(l)) {
+				ui_files_show("png", true, false, (path: string) => {
+					let f: string = ui_files_filename;
+					if (f == "") f = tr("untitled");
+					if (!f.endsWith(".png")) f += ".png";
+					krom_write_png(path + path_sep + f, image_get_pixels(l.texpaint), l.texpaint.width, l.texpaint.height, 3); // RRR1
+				});
+			}
+			else {
+				///if is_paint
+				context_raw.layers_export = export_mode_t.SELECTED;
+				box_export_show_textures();
+				///end
+			}
+		}
+
+		if (!slot_layer_is_group(l)) {
+			let to_fill_string: string = slot_layer_is_layer(l) ? tr("To Fill Layer") : tr("To Fill Mask");
+			let to_paint_string: string = slot_layer_is_layer(l) ? tr("To Paint Layer") : tr("To Paint Mask");
+
+			if (l.fill_layer == null && ui_menu_button(ui, to_fill_string)) {
+				let _init = () => {
+					slot_layer_is_layer(l) ? history_to_fill_layer() : history_to_fill_mask();
+					slot_layer_to_fill_layer(l);
+				}
+				app_notify_on_init(_init);
+			}
+			if (l.fill_layer != null && ui_menu_button(ui, to_paint_string)) {
+				let _init = () => {
+					slot_layer_is_layer(l) ? history_to_paint_layer() : history_to_paint_mask();
+					slot_layer_to_paint_layer(l);
+				}
+				app_notify_on_init(_init);
+			}
+		}
+
+		ui.enabled = tab_layers_can_delete(l);
+		if (ui_menu_button(ui, tr("Delete"), "delete")) {
+			let _init = () => {
+				tab_layers_delete_layer(context_raw.layer);
+			}
+			app_notify_on_init(_init);
+		}
+		ui.enabled = true;
+
+		if (l.fill_layer == null && ui_menu_button(ui, tr("Clear"))) {
+			context_set_layer(l);
+			let _init = () => {
+				if (!slot_layer_is_group(l)) {
+					history_clear_layer();
+					slot_layer_clear(l);
+				}
+				else {
+					for (let c of slot_layer_get_children(l)) {
+						context_raw.layer = c;
+						history_clear_layer();
+						slot_layer_clear(c);
+					}
+					context_raw.layers_preview_dirty = true;
+					context_raw.layer = l;
+				}
+			}
+			app_notify_on_init(_init);
+		}
+		if (slot_layer_is_mask(l) && l.fill_layer == null && ui_menu_button(ui, tr("Invert"))) {
+			let _init = () => {
+				context_set_layer(l);
+				history_invert_mask();
+				slot_layer_invert_mask(l);
+			}
+			app_notify_on_init(_init);
+		}
+		if (slot_layer_is_mask(l) && ui_menu_button(ui, tr("Apply"))) {
+			let _init = () => {
+				context_raw.layer = l;
+				history_apply_mask();
+				slot_layer_apply_mask(l);
+				context_set_layer(l.parent);
+				make_material_parse_mesh_material();
+				context_raw.layers_preview_dirty = true;
+			}
+			app_notify_on_init(_init);
+		}
+		if (slot_layer_is_group(l) && ui_menu_button(ui, tr("Merge Group"))) {
+			let _init = () => {
+				base_merge_group(l);
+			}
+			app_notify_on_init(_init);
+		}
+		ui.enabled = tab_layers_can_merge_down(l);
+		if (ui_menu_button(ui, tr("Merge Down"))) {
+			let _init = () => {
+				context_set_layer(l);
+				history_merge_layers();
+				base_merge_down();
+				if (context_raw.layer.fill_layer != null) slot_layer_to_paint_layer(context_raw.layer);
+			}
+			app_notify_on_init(_init);
+		}
+		ui.enabled = true;
+		if (ui_menu_button(ui, tr("Duplicate"))) {
+			let _init = () => {
+				context_set_layer(l);
+				history_duplicate_layer();
+				base_duplicate_layer(l);
+			}
+			app_notify_on_init(_init);
+		}
+
+		ui_menu_fill(ui);
+		ui_menu_align(ui);
+		let layer_opac_handle: zui_handle_t = zui_nest(zui_handle("tablayers_5"), l.id);
+		layer_opac_handle.value = l.mask_opacity;
+		zui_slider(layer_opac_handle, tr("Opacity"), 0.0, 1.0, true);
+		if (layer_opac_handle.changed) {
+			if (ui.input_started) history_layer_opacity();
+			l.mask_opacity = layer_opac_handle.value;
+			make_material_parse_mesh_material();
+			ui_menu_keep_open = true;
+		}
+
+		if (!slot_layer_is_group(l)) {
+			ui_menu_fill(ui);
+			ui_menu_align(ui);
+			let res_handle_changed_last: bool = base_res_handle.changed;
+			///if (krom_android || krom_ios)
+			let ar: string[] = ["128", "256", "512", "1K", "2K", "4K"];
+			///else
+			let ar: string[] = ["128", "256", "512", "1K", "2K", "4K", "8K", "16K"];
+			///end
+			let _y: i32 = ui._y;
+			base_res_handle.value = base_res_handle.position;
+			base_res_handle.position = math_floor(zui_slider(base_res_handle, ar[base_res_handle.position], 0, ar.length - 1, false, 1, false, zui_align_t.LEFT, false));
+			if (base_res_handle.changed) {
+				ui_menu_keep_open = true;
+			}
+			if (res_handle_changed_last && !base_res_handle.changed) {
+				base_on_layers_resized();
+			}
+			ui._y = _y;
+			zui_draw_string(tr("Res"), null, 0, zui_align_t.RIGHT);
+			zui_end_element();
+
+			ui_menu_fill(ui);
+			ui_menu_align(ui);
+			///if (krom_android || krom_ios)
+			zui_inline_radio(base_bits_handle, ["8bit"]);
+			///else
+			zui_inline_radio(base_bits_handle, ["8bit", "16bit", "32bit"]);
+			///end
+			if (base_bits_handle.changed) {
+				app_notify_on_init(base_set_layer_bits);
+				ui_menu_keep_open = true;
+			}
+		}
+
+		if (l.fill_layer != null) {
+			ui_menu_fill(ui);
+			ui_menu_align(ui);
+			let scale_handle: zui_handle_t = zui_nest(zui_handle("tablayers_6"), l.id);
+			scale_handle.value = l.scale;
+			l.scale = zui_slider(scale_handle, tr("UV Scale"), 0.0, 5.0, true);
+			if (scale_handle.changed) {
+				context_set_material(l.fill_layer);
+				context_set_layer(l);
+				let _init = () => {
+					base_update_fill_layers();
+				}
+				app_notify_on_init(_init);
+				ui_menu_keep_open = true;
+			}
+
+			ui_menu_fill(ui);
+			ui_menu_align(ui);
+			let angle_handle: zui_handle_t = zui_nest(zui_handle("tablayers_7"), l.id);
+			angle_handle.value = l.angle;
+			l.angle = zui_slider(angle_handle, tr("Angle"), 0.0, 360, true, 1);
+			if (angle_handle.changed) {
+				context_set_material(l.fill_layer);
+				context_set_layer(l);
+				make_material_parse_paint_material();
+				let _init = () => {
+					base_update_fill_layers();
+				}
+				app_notify_on_init(_init);
+				ui_menu_keep_open = true;
+			}
+
+			ui_menu_fill(ui);
+			ui_menu_align(ui);
+			let uv_type_handle: zui_handle_t = zui_nest(zui_handle("tablayers_8"), l.id);
+			uv_type_handle.position = l.uv_type;
+			l.uv_type = zui_inline_radio(uv_type_handle, [tr("UV Map"), tr("Triplanar"), tr("Project")], zui_align_t.LEFT);
+			if (uv_type_handle.changed) {
+				context_set_material(l.fill_layer);
+				context_set_layer(l);
+				make_material_parse_paint_material();
+				let _init = () => {
+					base_update_fill_layers();
+				}
+				app_notify_on_init(_init);
+				ui_menu_keep_open = true;
+			}
+		}
+
+		if (!slot_layer_is_group(l)) {
+			let base_handle: zui_handle_t = zui_nest(zui_handle("tablayers_9"), l.id);
+			let opac_handle: zui_handle_t = zui_nest(zui_handle("tablayers_10"), l.id);
+			let nor_handle: zui_handle_t = zui_nest(zui_handle("tablayers_11"), l.id);
+			let nor_blend_handle: zui_handle_t = zui_nest(zui_handle("tablayers_12"), l.id);
+			let occ_handle: zui_handle_t = zui_nest(zui_handle("tablayers_13"), l.id);
+			let rough_handle: zui_handle_t = zui_nest(zui_handle("tablayers_14"), l.id);
+			let met_handle: zui_handle_t = zui_nest(zui_handle("tablayers_15"), l.id);
+			let height_handle: zui_handle_t = zui_nest(zui_handle("tablayers_16"), l.id);
+			let height_blend_handle: zui_handle_t = zui_nest(zui_handle("tablayers_17"), l.id);
+			let emis_handle: zui_handle_t = zui_nest(zui_handle("tablayers_18"), l.id);
+			let subs_handle: zui_handle_t = zui_nest(zui_handle("tablayers_19"), l.id);
+			base_handle.selected = l.paint_base;
+			opac_handle.selected = l.paint_opac;
+			nor_handle.selected = l.paint_nor;
+			nor_blend_handle.selected = l.paint_nor_blend;
+			occ_handle.selected = l.paint_occ;
+			rough_handle.selected = l.paint_rough;
+			met_handle.selected = l.paint_met;
+			height_handle.selected = l.paint_height;
+			height_blend_handle.selected = l.paint_height_blend;
+			emis_handle.selected = l.paint_emis;
+			subs_handle.selected = l.paint_subs;
+			ui_menu_fill(ui);
+			l.paint_base = zui_check(base_handle, tr("Base Color"));
+			ui_menu_fill(ui);
+			l.paint_opac = zui_check(opac_handle, tr("Opacity"));
+			ui_menu_fill(ui);
+			l.paint_nor = zui_check(nor_handle, tr("Normal"));
+			ui_menu_fill(ui);
+			l.paint_nor_blend = zui_check(nor_blend_handle, tr("Normal Blending"));
+			ui_menu_fill(ui);
+			l.paint_occ = zui_check(occ_handle, tr("Occlusion"));
+			ui_menu_fill(ui);
+			l.paint_rough = zui_check(rough_handle, tr("Roughness"));
+			ui_menu_fill(ui);
+			l.paint_met = zui_check(met_handle, tr("Metallic"));
+			ui_menu_fill(ui);
+			l.paint_height = zui_check(height_handle, tr("Height"));
+			ui_menu_fill(ui);
+			l.paint_height_blend = zui_check(height_blend_handle, tr("Height Blending"));
+			ui_menu_fill(ui);
+			l.paint_emis = zui_check(emis_handle, tr("Emission"));
+			ui_menu_fill(ui);
+			l.paint_subs = zui_check(subs_handle, tr("Subsurface"));
+			if (base_handle.changed ||
+				opac_handle.changed ||
+				nor_handle.changed ||
+				nor_blend_handle.changed ||
+				occ_handle.changed ||
+				rough_handle.changed ||
+				met_handle.changed ||
+				height_handle.changed ||
+				height_blend_handle.changed ||
+				emis_handle.changed ||
+				subs_handle.changed) {
+				make_material_parse_mesh_material();
+				ui_menu_keep_open = true;
+			}
+		}
+	}, menu_elements);
+}
+
+function tab_layers_make_mask_preview_rgba32(l: SlotLayerRaw) {
+	///if is_paint
+	if (context_raw.mask_preview_rgba32 == null) {
+		context_raw.mask_preview_rgba32 = image_create_render_target(util_render_layer_preview_size, util_render_layer_preview_size);
+	}
+	// Convert from R8 to RGBA32 for tooltip display
+	if (context_raw.mask_preview_last != l) {
+		context_raw.mask_preview_last = l;
+		app_notify_on_init(() => {
+			g2_begin(context_raw.mask_preview_rgba32);
+			g2_set_pipeline(ui_view2d_pipe);
+			g4_set_int(ui_view2d_channel_loc, 1);
+			g2_draw_image(l.texpaint_preview, 0, 0);
+			g2_end();
+			g2_set_pipeline(null);
+		});
+	}
+	///end
+}
+
+function tab_layers_delete_layer(l: SlotLayerRaw) {
+	let pointers: map_t<SlotLayerRaw, i32> = tab_layers_init_layer_map();
+
+	if (slot_layer_is_layer(l) && slot_layer_has_masks(l, false)) {
+		for (let m of slot_layer_get_masks(l, false)) {
+			context_raw.layer = m;
+			history_delete_layer();
+			slot_layer_delete(m);
+		}
+	}
+	if (slot_layer_is_group(l)) {
+		for (let c of slot_layer_get_children(l)) {
+			if (slot_layer_has_masks(c, false)) {
+				for (let m of slot_layer_get_masks(c, false)) {
+					context_raw.layer = m;
+					history_delete_layer();
+					slot_layer_delete(m);
+				}
+			}
+			context_raw.layer = c;
+			history_delete_layer();
+			slot_layer_delete(c);
+		}
+		if (slot_layer_has_masks(l)) {
+			for (let m of slot_layer_get_masks(l)) {
+				context_raw.layer = m;
+				history_delete_layer();
+				slot_layer_delete(m);
+			}
+		}
+	}
+
+	context_raw.layer = l;
+	history_delete_layer();
+	slot_layer_delete(l);
+
+	if (slot_layer_is_mask(l)) {
+		context_raw.layer = l.parent;
+		base_update_fill_layers();
+	}
+
+	// Remove empty group
+	if (slot_layer_is_in_group(l) && slot_layer_get_children(slot_layer_get_containing_group(l)) == null) {
+		let g: SlotLayerRaw = slot_layer_get_containing_group(l);
+		// Maybe some group masks are left
+		if (slot_layer_has_masks(g)) {
+			for (let m of slot_layer_get_masks(g)) {
+				context_raw.layer = m;
+				history_delete_layer();
+				slot_layer_delete(m);
+			}
+		}
+		context_raw.layer = l.parent;
+		history_delete_layer();
+		slot_layer_delete(l.parent);
+	}
+	context_raw.ddirty = 2;
+	for (let m of project_materials) tab_layers_remap_layer_pointers(m.canvas.nodes, tab_layers_fill_layer_map(pointers));
+}
+
+function tab_layers_can_delete(l: SlotLayerRaw) {
+	let num_layers: i32 = 0;
+
+	if (slot_layer_is_mask(l)) return true;
+
+	for (let slot of project_layers) {
+		if (slot_layer_is_layer(slot)) ++num_layers;
+	}
+
+	// All layers are in one group
+	if (slot_layer_is_group(l) && slot_layer_get_children(l).length == num_layers) return false;
+
+	// Do not delete last layer
+	return num_layers > 1;
+}
+
+function tab_layers_can_drop_new_layer(position: i32) {
+	if (position > 0 && position < project_layers.length && slot_layer_is_mask(project_layers[position - 1])) {
+		// 1. The layer to insert is inserted in the middle
+		// 2. The layer below is a mask, i.e. the layer would have to be a (group) mask, too.
+		return false;
+	}
+	return true;
+}

+ 0 - 98
armorsculpt/Sources/ExportObj.ts

@@ -1,98 +0,0 @@
-
-class ExportObj {
-
-	static writeString = (out: i32[], str: string) => {
-		for (let i = 0; i < str.length; ++i) {
-			out.push(str.charCodeAt(i));
-		}
-	}
-
-	static run = (path: string, paintObjects: mesh_object_t[], applyDisplacement = false) => {
-		let o: i32[] = [];
-		ExportObj.writeString(o, "# armorsculpt.org\n");
-
-		let texpaint = project_layers[0].texpaint;
-		let pixels = image_get_pixels(texpaint);
-		let pixelsView = new DataView(pixels);
-		let mesh = paintObjects[0].data;
-		let inda = mesh.index_arrays[0].values;
-
-		let posa = new Int16Array(inda.length * 4);
-		for (let i = 0; i < inda.length; ++i) {
-			let index = inda[i];
-			posa[index * 4    ] = math_floor(pixelsView.getFloat32(i * 16    , true) * 32767);
-			posa[index * 4 + 1] = math_floor(pixelsView.getFloat32(i * 16 + 4, true) * 32767);
-			posa[index * 4 + 2] = math_floor(pixelsView.getFloat32(i * 16 + 8, true) * 32767);
-		}
-
-		let poff = 0;
-		// for (let p of paintObjects) {
-			let p = paintObjects[0];
-			// let mesh = p.data.raw;
-			let inv = 1 / 32767;
-			let sc = p.data.scale_pos * inv;
-			// let posa = mesh.vertex_arrays[0].values;
-			let len = math_floor(posa.length / 4);
-			// let len = math_floor(inda.length);
-
-			// Merge shared vertices and remap indices
-			let posa2 = new Int16Array(len * 3);
-			let posmap = new map_t<i32, i32>();
-
-			let pi = 0;
-			for (let i = 0; i < len; ++i) {
-				let found = false;
-				for (let j = 0; j < pi; ++j) {
-					if (posa2[j * 3    ] == posa[i * 4    ] &&
-						posa2[j * 3 + 1] == posa[i * 4 + 1] &&
-						posa2[j * 3 + 2] == posa[i * 4 + 2]) {
-						posmap.set(i, j);
-						found = true;
-						break;
-					}
-				}
-				if (!found) {
-					posmap.set(i, pi);
-					posa2[pi * 3    ] = posa[i * 4    ];
-					posa2[pi * 3 + 1] = posa[i * 4 + 1];
-					posa2[pi * 3 + 2] = posa[i * 4 + 2];
-					pi++;
-				}
-			}
-
-			ExportObj.writeString(o, "o " + p.base.name + "\n");
-			for (let i = 0; i < pi; ++i) {
-				ExportObj.writeString(o, "v ");
-				let vx = posa2[i * 3] * sc + "";
-				ExportObj.writeString(o, vx.substr(0, vx.indexOf(".") + 7));
-				ExportObj.writeString(o, " ");
-				let vy = posa2[i * 3 + 2] * sc + "";
-				ExportObj.writeString(o, vy.substr(0, vy.indexOf(".") + 7));
-				ExportObj.writeString(o, " ");
-				let vz = -posa2[i * 3 + 1] * sc + "";
-				ExportObj.writeString(o, vz.substr(0, vz.indexOf(".") + 7));
-				ExportObj.writeString(o, "\n");
-			}
-
-			// let inda = mesh.index_arrays[0].values;
-			for (let i = 0; i < math_floor(inda.length / 3); ++i) {
-				let pi1 = posmap.get(inda[i * 3    ]) + 1 + poff;
-				let pi2 = posmap.get(inda[i * 3 + 1]) + 1 + poff;
-				let pi3 = posmap.get(inda[i * 3 + 2]) + 1 + poff;
-				ExportObj.writeString(o, "f ");
-				ExportObj.writeString(o, pi1 + "");
-				ExportObj.writeString(o, " ");
-				ExportObj.writeString(o, pi2 + "");
-				ExportObj.writeString(o, " ");
-				ExportObj.writeString(o, pi3 + "");
-				ExportObj.writeString(o, "\n");
-			}
-			poff += pi;
-		// }
-
-		if (!path.endsWith(".obj")) path += ".obj";
-
-		let b = Uint8Array.from(o).buffer;
-		krom_file_save_bytes(path, b, b.byteLength);
-	}
-}

+ 0 - 192
armorsculpt/Sources/ImportMesh.ts

@@ -1,192 +0,0 @@
-
-class ImportMesh {
-
-	static clearLayers = true;
-
-	static run = (path: string, _clearLayers = true, replaceExisting = true) => {
-		if (!path_isMesh(path)) {
-			if (!context_enableImportPlugin(path)) {
-				console_error(Strings.error1());
-				return;
-			}
-		}
-
-		ImportMesh.clearLayers = _clearLayers;
-		context_raw.layerFilter = 0;
-
-		let p = path.toLowerCase();
-		if (p.endsWith(".obj")) ImportObj.run(path, replaceExisting);
-		else if (p.endsWith(".blend")) ImportBlendMesh.run(path, replaceExisting);
-		else {
-			let ext = path.substr(path.lastIndexOf(".") + 1);
-			let importer = path_meshImporters.get(ext);
-			importer(path, (mesh: any) => {
-				replaceExisting ? ImportMesh.makeMesh(mesh, path) : ImportMesh.addMesh(mesh);
-			});
-		}
-
-		project_meshAssets = [path];
-
-		///if (krom_android || krom_ios)
-		sys_title_set(path.substring(path.lastIndexOf(path_sep) + 1, path.lastIndexOf(".")));
-		///end
-	}
-
-	static finishImport = () => {
-		if (context_raw.mergedObject != null) {
-			mesh_object_remove(context_raw.mergedObject);
-			data_delete_mesh(context_raw.mergedObject.data._.handle);
-			context_raw.mergedObject = null;
-		}
-
-		context_selectPaintObject(context_mainObject());
-
-		if (project_paintObjects.length > 1) {
-			// Sort by name
-			project_paintObjects.sort((a, b): i32 => {
-				if (a.base.name < b.base.name) return -1;
-				else if (a.base.name > b.base.name) return 1;
-				return 0;
-			});
-
-			// No mask by default
-			for (let p of project_paintObjects) p.base.visible = true;
-			if (context_raw.mergedObject == null) util_mesh_mergeMesh();
-			context_raw.paintObject.skip_context = "paint";
-			context_raw.mergedObject.base.visible = true;
-		}
-
-		Viewport.scaleToBounds();
-
-		if (context_raw.paintObject.base.name == "") context_raw.paintObject.base.name = "Object";
-		MakeMaterial.parsePaintMaterial();
-		MakeMaterial.parseMeshMaterial();
-
-		ui_view2d_hwnd.redraws = 2;
-
-		///if arm_physics
-		context_raw.paintBody = null;
-		///end
-	}
-
-	static makeMesh = (mesh: any, path: string) => {
-		if (mesh == null || mesh.posa == null || mesh.nora == null || mesh.inda == null || mesh.posa.length == 0) {
-			console_error(Strings.error3());
-			return;
-		}
-
-		let _makeMesh = () => {
-			let raw = ImportMesh.rawMesh(mesh);
-			if (mesh.cola != null) raw.vertex_arrays.push({ values: mesh.cola, attrib: "col", data: "short4norm" });
-
-			let md: mesh_data_t = mesh_data_create(raw);
-			context_raw.paintObject = context_mainObject();
-
-			context_selectPaintObject(context_mainObject());
-			for (let i = 0; i < project_paintObjects.length; ++i) {
-				let p = project_paintObjects[i];
-				if (p == context_raw.paintObject) continue;
-				data_delete_mesh(p.data._.handle);
-				mesh_object_remove(p);
-			}
-			let handle = context_raw.paintObject.data._.handle;
-			if (handle != "SceneSphere" && handle != "ScenePlane") {
-				data_delete_mesh(handle);
-			}
-
-			if (ImportMesh.clearLayers) {
-				while (project_layers.length > 0) {
-					let l = project_layers.pop();
-					SlotLayer.unload(l);
-				}
-				base_newLayer(false);
-				app_notify_on_init(base_initLayers);
-				history_reset();
-			}
-
-			mesh_object_set_data(context_raw.paintObject, md);
-			context_raw.paintObject.base.name = mesh.name;
-			project_paintObjects = [context_raw.paintObject];
-
-			md._.handle = raw.name;
-			data_cached_meshes.set(md._.handle, md);
-
-			context_raw.ddirty = 4;
-			ui_base_hwnds[TabArea.TabSidebar0].redraws = 2;
-			ui_base_hwnds[TabArea.TabSidebar1].redraws = 2;
-
-			// Wait for addMesh calls to finish
-			app_notify_on_init(ImportMesh.finishImport);
-
-			base_notifyOnNextFrame(() => {
-				let f32 = new Float32Array(config_getTextureResX() * config_getTextureResY() * 4);
-				for (let i = 0; i < math_floor(mesh.inda.length); ++i) {
-					let index = mesh.inda[i];
-					f32[i * 4]     = mesh.posa[index * 4]     / 32767;
-					f32[i * 4 + 1] = mesh.posa[index * 4 + 1] / 32767;
-					f32[i * 4 + 2] = mesh.posa[index * 4 + 2] / 32767;
-					f32[i * 4 + 3] = 1.0;
-				}
-				let imgmesh = image_from_bytes(f32.buffer, config_getTextureResX(), config_getTextureResY(), tex_format_t.RGBA128);
-				let texpaint = project_layers[0].texpaint;
-				g2_begin(texpaint);
-				g2_set_pipeline(base_pipeCopy128);
-				g2_draw_scaled_image(imgmesh, 0, 0, config_getTextureResX(), config_getTextureResY());
-				g2_set_pipeline(null);
-				g2_end();
-			});
-		}
-
-		_makeMesh();
-	}
-
-	static addMesh = (mesh: any) => {
-
-		let _addMesh = () => {
-			let raw = ImportMesh.rawMesh(mesh);
-			if (mesh.cola != null) raw.vertex_arrays.push({ values: mesh.cola, attrib: "col", data: "short4norm" });
-
-			let md: mesh_data_t = mesh_data_create(raw);
-
-			let object = scene_add_mesh_object(md, context_raw.paintObject.materials, context_raw.paintObject.base);
-			object.base.name = mesh.base.name;
-			object.skip_context = "paint";
-
-			// Ensure unique names
-			for (let p of project_paintObjects) {
-				if (p.base.name == object.base.name) {
-					p.base.name += ".001";
-					p.data._.handle += ".001";
-					data_cached_meshes.set(p.data._.handle, p.data);
-				}
-			}
-
-			project_paintObjects.push(object);
-
-			md._.handle = raw.name;
-			data_cached_meshes.set(md._.handle, md);
-
-			context_raw.ddirty = 4;
-			ui_base_hwnds[TabArea.TabSidebar0].redraws = 2;
-		}
-
-		_addMesh();
-	}
-
-	static rawMesh = (mesh: any): mesh_data_t => {
-		let posa = new Int16Array(math_floor(mesh.inda.length * 4));
-		for (let i = 0; i < posa.length; ++i) posa[i] = 32767;
-		let inda = new Uint32Array(mesh.inda.length);
-		for (let i = 0; i < inda.length; ++i) inda[i] = i;
-		return {
-			name: mesh.name,
-			vertex_arrays: [
-				{ values: posa, attrib: "pos", data: "short4norm" }
-			],
-			index_arrays: [
-				{ values: inda, material: 0 }
-			],
-			scale_pos: 1.0
-		};
-	}
-}

+ 0 - 39
armorsculpt/Sources/MakeBrush.ts

@@ -1,39 +0,0 @@
-
-class MakeBrush {
-
-	static run = (vert: NodeShaderRaw, frag: NodeShaderRaw) => {
-
-		node_shader_write(frag, 'float dist = 0.0;');
-
-		if (config_raw.brush_3d) {
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'float depth = textureLod(gbufferD, inp.xy, 0.0).r;');
-			///else
-			node_shader_write(frag, 'float depth = textureLod(gbufferD, vec2(inp.x, 1.0 - inp.y), 0.0).r;');
-			///end
-
-			node_shader_add_uniform(frag, 'mat4 invVP', '_inv_view_proj_matrix');
-			node_shader_write(frag, 'vec4 winp = vec4(vec2(inp.x, 1.0 - inp.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);');
-			node_shader_write(frag, 'winp = mul(winp, invVP);');
-			node_shader_write(frag, 'winp.xyz /= winp.w;');
-
-			node_shader_add_uniform(frag, 'mat4 W', '_world_matrix');
-
-			node_shader_write_attrib(frag, 'vec3 wposition = mul(texelFetch(texpaint_undo, ivec2(texCoord.x * textureSize(texpaint_undo, 0).x, texCoord.y * textureSize(texpaint_undo, 0).y), 0), W).xyz;');
-
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'float depthlast = textureLod(gbufferD, inplast.xy, 0.0).r;');
-			///else
-			node_shader_write(frag, 'float depthlast = textureLod(gbufferD, vec2(inplast.x, 1.0 - inplast.y), 0.0).r;');
-			///end
-
-			node_shader_write(frag, 'vec4 winplast = vec4(vec2(inplast.x, 1.0 - inplast.y) * 2.0 - 1.0, depthlast * 2.0 - 1.0, 1.0);');
-			node_shader_write(frag, 'winplast = mul(winplast, invVP);');
-			node_shader_write(frag, 'winplast.xyz /= winplast.w;');
-
-			node_shader_write(frag, 'dist = distance(wposition, winp.xyz);');
-		}
-
-		node_shader_write(frag, 'if (dist > brushRadius) discard;');
-	}
-}

+ 0 - 325
armorsculpt/Sources/MakeMaterial.ts

@@ -1,325 +0,0 @@
-
-class MakeMaterial {
-
-	static defaultScon: shader_context_t = null;
-	static defaultMcon: material_context_t = null;
-
-	static heightUsed = false;
-	static emisUsed = false;
-	static subsUsed = false;
-
-	static getMOut = (): bool => {
-		for (let n of ui_nodes_getCanvasMaterial().nodes) if (n.type == "OUTPUT_MATERIAL_PBR") return true;
-		return false;
-	}
-
-	static parseMeshMaterial = () => {
-		let m = project_materials[0].data;
-
-		for (let c of m._.shader._.contexts) {
-			if (c.name == "mesh") {
-				array_remove(m._.shader.contexts, c);
-				array_remove(m._.shader._.contexts, c);
-				MakeMaterial.deleteContext(c);
-				break;
-			}
-		}
-
-		if (MakeMesh.layerPassCount > 1) {
-			let i = 0;
-			while (i < m._.shader._.contexts.length) {
-				let c = m._.shader._.contexts[i];
-				for (let j = 1; j < MakeMesh.layerPassCount; ++j) {
-					if (c.name == "mesh" + j) {
-						array_remove(m._.shader.contexts, c);
-						array_remove(m._.shader._.contexts, c);
-						MakeMaterial.deleteContext(c);
-						i--;
-						break;
-					}
-				}
-				i++;
-			}
-
-			i = 0;
-			while (i < m.contexts.length) {
-				let c = m.contexts[i];
-				for (let j = 1; j < MakeMesh.layerPassCount; ++j) {
-					if (c.name == "mesh" + j) {
-						array_remove(m.contexts, c);
-						array_remove(m._.contexts, c);
-						i--;
-						break;
-					}
-				}
-				i++;
-			}
-		}
-
-		let con = MakeMesh.run({ name: "Material", canvas: null });
-		let scon: shader_context_t = shader_context_create(con.data);
-		scon._.override_context = {};
-		if (con.frag.sharedSamplers.length > 0) {
-			let sampler = con.frag.sharedSamplers[0];
-			scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
-		}
-		if (!context_raw.textureFilter) {
-			scon._.override_context.filter = "point";
-		}
-		m._.shader.contexts.push(scon);
-		m._.shader._.contexts.push(scon);
-
-		for (let i = 1; i < MakeMesh.layerPassCount; ++i) {
-			let con = MakeMesh.run({ name: "Material", canvas: null }, i);
-			let scon: shader_context_t = shader_context_create(con.data);
-			scon._.override_context = {};
-			if (con.frag.sharedSamplers.length > 0) {
-				let sampler = con.frag.sharedSamplers[0];
-				scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
-			}
-			if (!context_raw.textureFilter) {
-				scon._.override_context.filter = "point";
-			}
-			m._.shader.contexts.push(scon);
-			m._.shader._.contexts.push(scon);
-
-			let mcon: material_context_t = material_context_create({ name: "mesh" + i, bind_textures: [] });
-			m.contexts.push(mcon);
-			m._.contexts.push(mcon);
-		}
-
-		context_raw.ddirty = 2;
-
-		///if arm_voxels
-		MakeMaterial.makeVoxel(m);
-		///end
-	}
-
-	static parseParticleMaterial = () => {
-		let m = context_raw.particleMaterial;
-		let sc: shader_context_t = null;
-		for (let c of m._.shader._.contexts) {
-			if (c.name == "mesh") {
-				sc = c;
-				break;
-			}
-		}
-		if (sc != null) {
-			array_remove(m._.shader.contexts, sc);
-			array_remove(m._.shader._.contexts, sc);
-		}
-		let con = MakeParticle.run({ name: "MaterialParticle", canvas: null });
-		if (sc != null) MakeMaterial.deleteContext(sc);
-		sc = shader_context_create(con.data);
-		m._.shader.contexts.push(sc);
-		m._.shader._.contexts.push(sc);
-	}
-
-	static parseMeshPreviewMaterial = () => {
-		if (!MakeMaterial.getMOut()) return;
-
-		let m = project_materials[0].data;
-		let scon: shader_context_t = null;
-		for (let c of m._.shader._.contexts) {
-			if (c.name == "mesh") {
-				scon = c;
-				break;
-			}
-		}
-		array_remove(m._.shader.contexts, scon);
-		array_remove(m._.shader._.contexts, scon);
-
-		let mcon: material_context_t = { name: "mesh", bind_textures: [] };
-
-		let sd: TMaterial = { name: "Material", canvas: null };
-		let con = MakeMeshPreview.run(sd, mcon);
-
-		for (let i = 0; i < m.contexts.length; ++i) {
-			if (m.contexts[i].name == "mesh") {
-				m.contexts[i] = material_context_create(mcon);
-				break;
-			}
-		}
-
-		if (scon != null) MakeMaterial.deleteContext(scon);
-
-		let compileError = false;
-		let _scon: shader_context_t = shader_context_create(con.data);
-		if (_scon == null) compileError = true;
-		scon = _scon;
-		if (compileError) return;
-
-		m._.shader.contexts.push(scon);
-		m._.shader._.contexts.push(scon);
-	}
-
-	///if arm_voxels
-	static makeVoxel = (m: material_data_t) => {
-		let rebuild = MakeMaterial.heightUsed;
-		if (config_raw.rp_gi != false && rebuild) {
-			let scon: shader_context_t = null;
-			for (let c of m._.shader._.contexts) {
-				if (c.name == "voxel") {
-					scon = c;
-					break;
-				}
-			}
-			if (scon != null) make_voxel_run(scon);
-		}
-	}
-	///end
-
-	static parsePaintMaterial = (bakePreviews = true) => {
-		if (!MakeMaterial.getMOut()) return;
-
-		if (bakePreviews) {
-			let current = _g2_current;
-			let g2_in_use: bool = _g2_in_use;
-			if (g2_in_use) g2_end();
-			MakeMaterial.bakeNodePreviews();
-			if (g2_in_use) g2_begin(current);
-		}
-
-		let m = project_materials[0].data;
-		let scon: shader_context_t = null;
-		let mcon: material_context_t = null;
-		for (let c of m._.shader._.contexts) {
-			if (c.name == "paint") {
-				array_remove(m._.shader.contexts, c);
-				array_remove(m._.shader._.contexts, c);
-				if (c != MakeMaterial.defaultScon) MakeMaterial.deleteContext(c);
-				break;
-			}
-		}
-		for (let c of m.contexts) {
-			if (c.name == "paint") {
-				array_remove(m.contexts, c);
-				array_remove(m._.contexts, c);
-				break;
-			}
-		}
-
-		let sdata: TMaterial = { name: "Material", canvas: ui_nodes_getCanvasMaterial() };
-		let mcon2: material_context_t = { name: "paint", bind_textures: [] };
-		let con = MakeSculpt.run(sdata, mcon2);
-
-		let compileError = false;
-		let scon2: shader_context_t;
-		let _scon: shader_context_t = shader_context_create(con.data);
-		if (_scon == null) compileError = true;
-		scon2 = _scon;
-
-		if (compileError) return;
-		scon2._.override_context = {};
-		scon2._.override_context.addressing = "repeat";
-		let mcon3: material_context_t = material_context_create(mcon2);
-
-		m._.shader.contexts.push(scon2);
-		m._.shader._.contexts.push(scon2);
-		m.contexts.push(mcon3);
-		m._.contexts.push(mcon3);
-
-		if (MakeMaterial.defaultScon == null) MakeMaterial.defaultScon = scon2;
-		if (MakeMaterial.defaultMcon == null) MakeMaterial.defaultMcon = mcon3;
-	}
-
-	static bakeNodePreviews = () => {
-		context_raw.nodePreviewsUsed = [];
-		if (context_raw.nodePreviews == null) context_raw.nodePreviews = map_create();
-		MakeMaterial.traverseNodes(ui_nodes_getCanvasMaterial().nodes, null, []);
-		for (let key of context_raw.nodePreviews.keys()) {
-			if (context_raw.nodePreviewsUsed.indexOf(key) == -1) {
-				let image = context_raw.nodePreviews.get(key);
-				base_notifyOnNextFrame(function() { image_unload(image); });
-				context_raw.nodePreviews.delete(key);
-			}
-		}
-	}
-
-	static traverseNodes = (nodes: zui_node_t[], group: zui_node_canvas_t, parents: zui_node_t[]) => {
-		for (let node of nodes) {
-			MakeMaterial.bakeNodePreview(node, group, parents);
-			if (node.type == "GROUP") {
-				for (let g of project_materialGroups) {
-					if (g.canvas.name == node.name) {
-						parents.push(node);
-						MakeMaterial.traverseNodes(g.canvas.nodes, g.canvas, parents);
-						parents.pop();
-						break;
-					}
-				}
-			}
-		}
-	}
-
-	static bakeNodePreview = (node: zui_node_t, group: zui_node_canvas_t, parents: zui_node_t[]) => {
-		if (node.type == "BLUR") {
-			let id = parser_material_node_name(node, parents);
-			let image = context_raw.nodePreviews.get(id);
-			context_raw.nodePreviewsUsed.push(id);
-			let resX = math_floor(config_getTextureResX() / 4);
-			let resY = math_floor(config_getTextureResY() / 4);
-			if (image == null || image.width != resX || image.height != resY) {
-				if (image != null) image_unload(image);
-				image = image_create_render_target(resX, resY);
-				context_raw.nodePreviews.set(id, image);
-			}
-
-			parser_material_blur_passthrough = true;
-			UtilRender.makeNodePreview(ui_nodes_getCanvasMaterial(), node, image, group, parents);
-			parser_material_blur_passthrough = false;
-		}
-		else if (node.type == "DIRECT_WARP") {
-			let id = parser_material_node_name(node, parents);
-			let image = context_raw.nodePreviews.get(id);
-			context_raw.nodePreviewsUsed.push(id);
-			let resX = math_floor(config_getTextureResX());
-			let resY = math_floor(config_getTextureResY());
-			if (image == null || image.width != resX || image.height != resY) {
-				if (image != null) image_unload(image);
-				image = image_create_render_target(resX, resY);
-				context_raw.nodePreviews.set(id, image);
-			}
-
-			parser_material_warp_passthrough = true;
-			UtilRender.makeNodePreview(ui_nodes_getCanvasMaterial(), node, image, group, parents);
-			parser_material_warp_passthrough = false;
-		}
-	}
-
-	static parseNodePreviewMaterial = (node: zui_node_t, group: zui_node_canvas_t = null, parents: zui_node_t[] = null): { scon: shader_context_t, mcon: material_context_t } => {
-		if (node.outputs.length == 0) return null;
-		let sdata: TMaterial = { name: "Material", canvas: ui_nodes_getCanvasMaterial() };
-		let mcon_raw: material_context_t = { name: "mesh", bind_textures: [] };
-		let con = MakeNodePreview.run(sdata, mcon_raw, node, group, parents);
-		let compileError = false;
-		let scon: shader_context_t;
-		let _scon: shader_context_t = shader_context_create(con.data);
-		if (_scon == null) compileError = true;
-		scon = _scon;
-
-		if (compileError) return null;
-		let mcon: material_context_t = material_context_create(mcon_raw);
-		return { scon: scon, mcon: mcon };
-	}
-
-	static parseBrush = () => {
-		parser_logic_parse(context_raw.brush.canvas);
-	}
-
-	static getDisplaceStrength = (): f32 => {
-		let sc = context_mainObject().base.transform.scale.x;
-		return config_raw.displace_strength * 0.02 * sc;
-	}
-
-	static voxelgiHalfExtents = (): string => {
-		let ext = context_raw.vxaoExt;
-		return `const vec3 voxelgiHalfExtents = vec3(${ext}, ${ext}, ${ext});`;
-	}
-
-	static deleteContext = (c: shader_context_t) => {
-		base_notifyOnNextFrame(() => { // Ensure pipeline is no longer in use
-			shader_context_delete(c);
-		});
-	}
-}

+ 0 - 338
armorsculpt/Sources/MakeMesh.ts

@@ -1,338 +0,0 @@
-
-class MakeMesh {
-
-	static layerPassCount = 1;
-
-	static run = (data: TMaterial, layerPass = 0): NodeShaderContextRaw => {
-		let context_id = layerPass == 0 ? "mesh" : "mesh" + layerPass;
-		let con_mesh = NodeShadercontext_create(data, {
-			name: context_id,
-			depth_write: layerPass == 0 ? true : false,
-			compare_mode: layerPass == 0 ? "less" : "equal",
-			cull_mode: (context_raw.cullBackfaces || layerPass > 0) ? "clockwise" : "none",
-			vertex_elements: [{name: "pos", data: "short4norm"}],
-			color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
-			depth_attachment: "DEPTH32"
-		});
-
-		let vert = NodeShadercontext_make_vert(con_mesh);
-		let frag = NodeShadercontext_make_frag(con_mesh);
-		frag.ins = vert.outs;
-
-		node_shader_add_out(vert, 'vec2 texCoord');
-		frag.wvpposition = true;
-		node_shader_add_out(vert, 'vec4 prevwvpposition');
-		node_shader_add_uniform(vert, 'mat4 VP', '_view_proj_matrix');
-		node_shader_add_uniform(vert, 'mat4 prevWVP', '_prev_world_view_proj_matrix');
-		vert.wposition = true;
-
-		let textureCount = 0;
-
-		node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
-		node_shader_add_uniform(vert, 'sampler2D texpaint_vert', '_texpaint_vert' + project_layers[0].id);
-		node_shader_write(vert, 'vec3 meshpos = texelFetch(texpaint_vert, ivec2(gl_VertexID % textureSize(texpaint_vert, 0).x, gl_VertexID / textureSize(texpaint_vert, 0).y), 0).xyz;');
-		// + pos.xyz * 0.000001
-		node_shader_write(vert, 'gl_Position = mul(vec4(meshpos.xyz, 1.0), WVP);');
-
-		node_shader_write(vert, 'texCoord = vec2(0.0, 0.0);');
-
-		node_shader_write(vert, 'prevwvpposition = mul(vec4(pos.xyz, 1.0), prevWVP);');
-
-		node_shader_add_out(frag, 'vec4 fragColor[3]');
-
-		node_shader_add_uniform(vert, "mat3 N", "_normal_matrix");
-		node_shader_add_out(vert, "vec3 wnormal");
-
-		node_shader_write_attrib(vert, 'int baseVertex0 = gl_VertexID - (gl_VertexID % 3);');
-		node_shader_write_attrib(vert, 'int baseVertex1 = baseVertex0 + 1;');
-		node_shader_write_attrib(vert, 'int baseVertex2 = baseVertex0 + 2;');
-		node_shader_write_attrib(vert, 'vec3 meshpos0 = texelFetch(texpaint_vert, ivec2(baseVertex0 % textureSize(texpaint_vert, 0).x, baseVertex0 / textureSize(texpaint_vert, 0).y), 0).xyz;');
-		node_shader_write_attrib(vert, 'vec3 meshpos1 = texelFetch(texpaint_vert, ivec2(baseVertex1 % textureSize(texpaint_vert, 0).x, baseVertex1 / textureSize(texpaint_vert, 0).y), 0).xyz;');
-		node_shader_write_attrib(vert, 'vec3 meshpos2 = texelFetch(texpaint_vert, ivec2(baseVertex2 % textureSize(texpaint_vert, 0).x, baseVertex2 / textureSize(texpaint_vert, 0).y), 0).xyz;');
-		node_shader_write_attrib(vert, 'vec3 meshnor = normalize(cross(meshpos2 - meshpos1, meshpos0 - meshpos1));');
-		node_shader_write_attrib(vert, 'wnormal = mul(meshnor, N);');
-		node_shader_write_attrib(frag, 'vec3 n = normalize(wnormal);');
-
-		node_shader_add_function(frag, str_packFloatInt16);
-		node_shader_add_function(frag, str_octahedronWrap);
-		node_shader_add_function(frag, str_cotangentFrame);
-		if (layerPass > 0) {
-			node_shader_add_uniform(frag, 'sampler2D gbuffer0');
-			node_shader_add_uniform(frag, 'sampler2D gbuffer1');
-			node_shader_add_uniform(frag, 'sampler2D gbuffer2');
-			node_shader_write(frag, 'vec2 fragcoord = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'fragcoord.y = 1.0 - fragcoord.y;');
-			///end
-			node_shader_write(frag, 'vec4 gbuffer0_sample = textureLod(gbuffer0, fragcoord, 0.0);');
-			node_shader_write(frag, 'vec4 gbuffer1_sample = textureLod(gbuffer1, fragcoord, 0.0);');
-			node_shader_write(frag, 'vec4 gbuffer2_sample = textureLod(gbuffer2, fragcoord, 0.0);');
-			node_shader_write(frag, 'vec3 basecol = gbuffer0_sample.rgb;');
-			node_shader_write(frag, 'float roughness = gbuffer2_sample.g;');
-			node_shader_write(frag, 'float metallic = gbuffer2_sample.b;');
-			node_shader_write(frag, 'float occlusion = gbuffer2_sample.r;');
-			node_shader_write(frag, 'float opacity = 1.0;//gbuffer0_sample.a;');
-			node_shader_write(frag, 'float matid = gbuffer1_sample.a;');
-			node_shader_write(frag, 'vec3 ntex = gbuffer1_sample.rgb;');
-			node_shader_write(frag, 'float height = gbuffer2_sample.a;');
-		}
-		else {
-			node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
-			node_shader_write(frag, 'float roughness = 0.3;');
-			node_shader_write(frag, 'float metallic = 0.0;');
-			node_shader_write(frag, 'float occlusion = 1.0;');
-			node_shader_write(frag, 'float opacity = 1.0;');
-			node_shader_write(frag, 'float matid = 0.0;');
-			node_shader_write(frag, 'vec3 ntex = vec3(0.5, 0.5, 1.0);');
-			node_shader_write(frag, 'float height = 0.0;');
-		}
-		node_shader_write(frag, 'vec4 texpaint_sample = vec4(0.0, 0.0, 0.0, 1.0);');
-		node_shader_write(frag, 'vec4 texpaint_nor_sample;');
-		node_shader_write(frag, 'vec4 texpaint_pack_sample;');
-		node_shader_write(frag, 'float texpaint_opac;');
-
-		if (MakeMaterial.heightUsed) {
-			node_shader_write(frag, 'float height0 = 0.0;');
-			node_shader_write(frag, 'float height1 = 0.0;');
-			node_shader_write(frag, 'float height2 = 0.0;');
-			node_shader_write(frag, 'float height3 = 0.0;');
-		}
-
-		if (context_raw.drawWireframe) {
-			textureCount++;
-			node_shader_add_uniform(frag, 'sampler2D texuvmap', '_texuvmap');
-		}
-
-		if (context_raw.viewportMode == ViewportMode.ViewLit && context_raw.renderMode == RenderMode.RenderForward) {
-			textureCount += 4;
-			node_shader_add_uniform(frag, 'sampler2D senvmapBrdf', "$brdf.k");
-			node_shader_add_uniform(frag, 'sampler2D senvmapRadiance', '_envmap_radiance');
-			node_shader_add_uniform(frag, 'sampler2D sltcMat', '_ltcMat');
-			node_shader_add_uniform(frag, 'sampler2D sltcMag', '_ltcMag');
-		}
-
-		// Get layers for this pass
-		MakeMesh.layerPassCount = 1;
-		let layers: SlotLayerRaw[] = [];
-		let startCount = textureCount;
-		for (let l of project_layers) {
-			if (!SlotLayer.isLayer(l) || !SlotLayer.isVisible(l)) continue;
-
-			let count = 3;
-			let masks = SlotLayer.getMasks(l);
-			if (masks != null) count += masks.length;
-			textureCount += count;
-			if (textureCount >= MakeMesh.getMaxTextures()) {
-				textureCount = startCount + count + 3; // gbuffer0_copy, gbuffer1_copy, gbuffer2_copy
-				MakeMesh.layerPassCount++;
-			}
-			if (layerPass == MakeMesh.layerPassCount - 1) {
-				layers.push(l);
-			}
-		}
-
-		let lastPass = layerPass == MakeMesh.layerPassCount - 1;
-
-		for (let l of layers) {
-			if (SlotLayer.getObjectMask(l) > 0) {
-				node_shader_add_uniform(frag, 'int uid', '_uid');
-				if (SlotLayer.getObjectMask(l) > project_paintObjects.length) { // Atlas
-					let visibles = project_getAtlasObjects(SlotLayer.getObjectMask(l));
-					node_shader_write(frag, 'if (');
-					for (let i = 0; i < visibles.length; ++i) {
-						if (i > 0) node_shader_write(frag, ' || ');
-						node_shader_write(frag, `${visibles[i].base.uid} == uid`);
-					}
-					node_shader_write(frag, ') {');
-				}
-				else { // Object mask
-					let uid = project_paintObjects[SlotLayer.getObjectMask(l) - 1].base.uid;
-					node_shader_write(frag, `if (${uid} == uid) {`);
-				}
-			}
-
-			node_shader_add_shared_sampler(frag, 'sampler2D texpaint' + l.id);
-			node_shader_write(frag, 'texpaint_sample = vec4(0.8, 0.8, 0.8, 1.0);');
-			node_shader_write(frag, 'texpaint_opac = texpaint_sample.a;');
-
-			let masks = SlotLayer.getMasks(l);
-			if (masks != null) {
-				let hasVisible = false;
-				for (let m of masks) {
-					if (SlotLayer.isVisible(m)) {
-						hasVisible = true;
-						break;
-					}
-				}
-				if (hasVisible) {
-					let texpaint_mask = 'texpaint_mask' + l.id;
-					node_shader_write(frag, `float ${texpaint_mask} = 0.0;`);
-					for (let m of masks) {
-						if (!SlotLayer.isVisible(m)) continue;
-						node_shader_add_shared_sampler(frag, 'sampler2D texpaint' + m.id);
-						node_shader_write(frag, '{'); // Group mask is sampled across multiple layers
-						node_shader_write(frag, 'float texpaint_mask_sample' + m.id + ' = textureLodShared(texpaint' + m.id + ', texCoord, 0.0).r;');
-						node_shader_write(frag, '}');
-					}
-					node_shader_write(frag, `texpaint_opac *= clamp(${texpaint_mask}, 0.0, 1.0);`);
-				}
-			}
-
-			if (SlotLayer.getOpacity(l) < 1) {
-				node_shader_write(frag, `texpaint_opac *= ${SlotLayer.getOpacity(l)};`);
-			}
-
-			if (l == project_layers[0]) {
-				node_shader_write(frag, 'basecol = vec3(0.8, 0.8, 0.8);// texpaint_sample.rgb * texpaint_opac;');
-			}
-
-			if (SlotLayer.getObjectMask(l) > 0) {
-				node_shader_write(frag, '}');
-			}
-
-			if (lastPass && context_raw.drawTexels) {
-				node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
-				node_shader_write(frag, 'vec2 texel0 = texCoord * texpaintSize * 0.01;');
-				node_shader_write(frag, 'vec2 texel1 = texCoord * texpaintSize * 0.1;');
-				node_shader_write(frag, 'vec2 texel2 = texCoord * texpaintSize;');
-				node_shader_write(frag, 'basecol *= max(float(mod(int(texel0.x), 2.0) == mod(int(texel0.y), 2.0)), 0.9);');
-				node_shader_write(frag, 'basecol *= max(float(mod(int(texel1.x), 2.0) == mod(int(texel1.y), 2.0)), 0.9);');
-				node_shader_write(frag, 'basecol *= max(float(mod(int(texel2.x), 2.0) == mod(int(texel2.y), 2.0)), 0.9);');
-			}
-
-			if (lastPass && context_raw.drawWireframe) {
-				node_shader_write(frag, 'basecol *= 1.0 - textureLod(texuvmap, texCoord, 0.0).r;');
-			}
-
-			if (MakeMaterial.heightUsed) {
-				node_shader_write(frag, 'if (height > 0.0) {');
-				node_shader_write(frag, 'float height_dx = height0 - height1;');
-				node_shader_write(frag, 'float height_dy = height2 - height3;');
-				// Whiteout blend
-				node_shader_write(frag, 'vec3 n1 = ntex * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
-				node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
-				node_shader_write(frag, 'ntex = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
-				node_shader_write(frag, '}');
-			}
-
-			if (!lastPass) {
-				node_shader_write(frag, 'fragColor[0] = vec4(basecol, opacity);');
-				node_shader_write(frag, 'fragColor[1] = vec4(ntex, matid);');
-				node_shader_write(frag, 'fragColor[2] = vec4(occlusion, roughness, metallic, height);');
-				parser_material_finalize(con_mesh);
-				con_mesh.data.shader_from_source = true;
-				con_mesh.data.vertex_shader = node_shader_get(vert);
-				con_mesh.data.fragment_shader = node_shader_get(frag);
-				return con_mesh;
-			}
-
-			frag.vVec = true;
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
-			///else
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
-			///end
-			node_shader_write(frag, 'n = ntex * 2.0 - 1.0;');
-			node_shader_write(frag, 'n.y = -n.y;');
-			node_shader_write(frag, 'n = normalize(mul(n, TBN));');
-
-			if (context_raw.viewportMode == ViewportMode.ViewLit) {
-
-				node_shader_write(frag, 'basecol = pow(basecol, vec3(2.2, 2.2, 2.2));');
-
-				if (context_raw.viewportShader != null) {
-					let color = context_raw.viewportShader(frag);
-					node_shader_write(frag, `fragColor[1] = vec4(${color}, 1.0);`);
-				}
-				else if (context_raw.renderMode == RenderMode.RenderForward) {
-					frag.wposition = true;
-					node_shader_write(frag, 'vec3 albedo = mix(basecol, vec3(0.0, 0.0, 0.0), metallic);');
-					node_shader_write(frag, 'vec3 f0 = mix(vec3(0.04, 0.04, 0.04), basecol, metallic);');
-					frag.vVec = true;
-					node_shader_write(frag, 'float dotNV = max(0.0, dot(n, vVec));');
-					node_shader_write(frag, 'vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(roughness, 1.0 - dotNV) * 256.0), 0).xy;');
-					node_shader_add_uniform(frag, 'int envmapNumMipmaps', '_envmap_num_mipmaps');
-					node_shader_add_uniform(frag, 'vec4 envmapData', '_envmapData'); // angle, sin(angle), cos(angle), strength
-					node_shader_write(frag, 'vec3 wreflect = reflect(-vVec, n);');
-					node_shader_write(frag, 'float envlod = roughness * float(envmapNumMipmaps);');
-					node_shader_add_function(frag, str_envMapEquirect);
-					node_shader_write(frag, 'vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(wreflect, envmapData.x), envlod).rgb;');
-					node_shader_add_uniform(frag, 'vec3 lightArea0', '_light_area0');
-					node_shader_add_uniform(frag, 'vec3 lightArea1', '_light_area1');
-					node_shader_add_uniform(frag, 'vec3 lightArea2', '_light_area2');
-					node_shader_add_uniform(frag, 'vec3 lightArea3', '_light_area3');
-					node_shader_add_function(frag, str_ltcEvaluate);
-					node_shader_add_uniform(frag, 'vec3 lightPos', '_point_pos');
-					node_shader_add_uniform(frag, 'vec3 lightColor', '_point_color');
-					node_shader_write(frag, 'float ldist = distance(wposition, lightPos);');
-					node_shader_write(frag, 'const float LUT_SIZE = 64.0;');
-					node_shader_write(frag, 'const float LUT_SCALE = (LUT_SIZE - 1.0) / LUT_SIZE;');
-					node_shader_write(frag, 'const float LUT_BIAS = 0.5 / LUT_SIZE;');
-					node_shader_write(frag, 'float theta = acos(dotNV);');
-					node_shader_write(frag, 'vec2 tuv = vec2(roughness, theta / (0.5 * 3.14159265));');
-					node_shader_write(frag, 'tuv = tuv * LUT_SCALE + LUT_BIAS;');
-					node_shader_write(frag, 'vec4 t = textureLod(sltcMat, tuv, 0.0);');
-					node_shader_write(frag, 'mat3 minv = mat3(vec3(1.0, 0.0, t.y), vec3(0.0, t.z, 0.0), vec3(t.w, 0.0, t.x));');
-					node_shader_write(frag, 'float ltcspec = ltcEvaluate(n, vVec, dotNV, wposition, minv, lightArea0, lightArea1, lightArea2, lightArea3);');
-					node_shader_write(frag, 'ltcspec *= textureLod(sltcMag, tuv, 0.0).a;');
-					node_shader_write(frag, 'mat3 mident = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);');
-					node_shader_write(frag, 'float ltcdiff = ltcEvaluate(n, vVec, dotNV, wposition, mident, lightArea0, lightArea1, lightArea2, lightArea3);');
-					node_shader_write(frag, 'vec3 direct = albedo * ltcdiff + ltcspec * 0.05;');
-					node_shader_write(frag, 'direct *= lightColor * (1.0 / (ldist * ldist));');
-
-					node_shader_add_uniform(frag, 'vec4 shirr[7]', '_envmap_irradiance');
-					node_shader_add_function(frag, str_shIrradiance);
-					node_shader_write(frag, 'vec3 indirect = albedo * (shIrradiance(vec3(n.x * envmapData.z - n.y * envmapData.y, n.x * envmapData.y + n.y * envmapData.z, n.z), shirr) / 3.14159265);');
-					node_shader_write(frag, 'indirect += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5;');
-					node_shader_write(frag, 'indirect *= envmapData.w * occlusion;');
-					node_shader_write(frag, 'fragColor[1] = vec4(direct + indirect, 1.0);');
-				}
-				else { // Deferred, Pathtraced
-					if (MakeMaterial.emisUsed) node_shader_write(frag, 'if (int(matid * 255.0) % 3 == 1) basecol *= 10.0;'); // Boost for bloom
-					node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
-				}
-			}
-			else if (context_raw.viewportMode == ViewportMode.ViewObjectNormal) {
-				frag.nAttr = true;
-				node_shader_write(frag, 'fragColor[1] = vec4(nAttr, 1.0);');
-			}
-			else if (context_raw.viewportMode == ViewportMode.ViewObjectID) {
-				node_shader_add_uniform(frag, 'float objectId', '_objectId');
-				node_shader_write(frag, 'float obid = objectId + 1.0 / 255.0;');
-				node_shader_write(frag, 'float id_r = fract(sin(dot(vec2(obid, obid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
-				node_shader_write(frag, 'float id_g = fract(sin(dot(vec2(obid * 20.0, obid), vec2(12.9898, 78.233))) * 43758.5453);');
-				node_shader_write(frag, 'float id_b = fract(sin(dot(vec2(obid, obid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
-				node_shader_write(frag, 'fragColor[1] = vec4(id_r, id_g, id_b, 1.0);');
-			}
-			else {
-				node_shader_write(frag, 'fragColor[1] = vec4(1.0, 0.0, 1.0, 1.0);'); // Pink
-			}
-
-			if (context_raw.viewportMode != ViewportMode.ViewLit && context_raw.viewportMode != ViewportMode.ViewPathTrace) {
-				node_shader_write(frag, 'fragColor[1].rgb = pow(fragColor[1].rgb, vec3(2.2, 2.2, 2.2));');
-			}
-
-			node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
-			node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
-			node_shader_write(frag, 'fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, uint(int(matid * 255.0) % 3)));');
-		}
-
-		node_shader_write(frag, 'vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
-		node_shader_write(frag, 'vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;');
-		node_shader_write(frag, 'fragColor[2] = vec4(posa - posb, texCoord.xy);');
-
-		parser_material_finalize(con_mesh);
-		con_mesh.data.shader_from_source = true;
-		con_mesh.data.vertex_shader = node_shader_get(vert);
-		con_mesh.data.fragment_shader = node_shader_get(frag);
-		return con_mesh;
-	}
-
-	static getMaxTextures = (): i32 => {
-		///if krom_direct3d11
-		return 128 - 66;
-		///else
-		return 16 - 3; // G4onG5/G4.c.h MAX_TEXTURES
-		///end
-	}
-}

+ 0 - 159
armorsculpt/Sources/MakeMeshPreview.ts

@@ -1,159 +0,0 @@
-
-class MakeMeshPreview {
-
-	static opacityDiscardDecal = 0.05;
-
-	static run = (data: TMaterial, matcon: material_context_t): NodeShaderContextRaw => {
-		let context_id = "mesh";
-		let con_mesh = NodeShadercontext_create(data, {
-			name: context_id,
-			depth_write: true,
-			compare_mode: "less",
-			cull_mode: "clockwise",
-			vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
-			color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
-			depth_attachment: "DEPTH32"
-		});
-
-		let vert = NodeShadercontext_make_vert(con_mesh);
-		let frag = NodeShadercontext_make_frag(con_mesh);
-		frag.ins = vert.outs;
-		let pos = "pos";
-
-		///if arm_skin
-		let skin = mesh_data_get_vertex_array(context_raw.paintObject.data, "bone") != null;
-		if (skin) {
-			pos = "spos";
-			NodeShadercontext_add_elem(con_mesh, "bone", 'short4norm');
-			NodeShadercontext_add_elem(con_mesh, "weight", 'short4norm');
-			node_shader_add_function(vert, str_getSkinningDualQuat);
-			node_shader_add_uniform(vert, 'vec4 skinBones[128 * 2]', '_skin_bones');
-			node_shader_add_uniform(vert, 'float posUnpack', '_pos_unpack');
-			node_shader_write_attrib(vert, 'vec4 skinA;');
-			node_shader_write_attrib(vert, 'vec4 skinB;');
-			node_shader_write_attrib(vert, 'getSkinningDualQuat(ivec4(bone * 32767), weight, skinA, skinB);');
-			node_shader_write_attrib(vert, 'vec3 spos = pos.xyz;');
-			node_shader_write_attrib(vert, 'spos.xyz *= posUnpack;');
-			node_shader_write_attrib(vert, 'spos.xyz += 2.0 * cross(skinA.xyz, cross(skinA.xyz, spos.xyz) + skinA.w * spos.xyz);');
-			node_shader_write_attrib(vert, 'spos.xyz += 2.0 * (skinA.w * skinB.xyz - skinB.w * skinA.xyz + cross(skinA.xyz, skinB.xyz));');
-			node_shader_write_attrib(vert, 'spos.xyz /= posUnpack;');
-		}
-		///end
-
-		node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
-		node_shader_write_attrib(vert, `gl_Position = mul(vec4(${pos}.xyz, 1.0), WVP);`);
-
-		let brushScale = (context_raw.brushScale * context_raw.brushNodesScale) + "";
-		node_shader_add_out(vert, 'vec2 texCoord');
-		node_shader_write_attrib(vert, `texCoord = tex * float(${brushScale});`);
-
-		let decal = context_raw.decalPreview;
-		parser_material_sample_keep_aspect = decal;
-		parser_material_sample_uv_scale = brushScale;
-		parser_material_parse_height = MakeMaterial.heightUsed;
-		parser_material_parse_height_as_channel = true;
-		// let sout = parser_material_parse(ui_nodes_getCanvasMaterial(), con_mesh, vert, frag, matcon);
-		parser_material_parse_height = false;
-		parser_material_parse_height_as_channel = false;
-		parser_material_sample_keep_aspect = false;
-		let base = "vec3(1.0, 1.0, 1.0)";//sout.out_basecol;
-		let rough = "0.0";//sout.out_roughness;
-		let met = "0.0";//sout.out_metallic;
-		let occ = "0.0";//sout.out_occlusion;
-		let opac = "0.0";//sout.out_opacity;
-		let height = "0.0";//sout.out_height;
-		let nortan = "vec3(1.0, 1.0, 1.0)";//parser_material_out_normaltan;
-		node_shader_write(frag, `vec3 basecol = pow(${base}, vec3(2.2, 2.2, 2.2));`);
-		node_shader_write(frag, `float roughness = ${rough};`);
-		node_shader_write(frag, `float metallic = ${met};`);
-		node_shader_write(frag, `float occlusion = ${occ};`);
-		node_shader_write(frag, `float opacity = ${opac};`);
-		node_shader_write(frag, `vec3 nortan = ${nortan};`);
-		node_shader_write(frag, `float height = ${height};`);
-
-		// parser_material_parse_height_as_channel = false;
-		// node_shader_write(vert, `float vheight = ${height};`);
-		// node_shader_add_out(vert, 'float height');
-		// node_shader_write(vert, 'height = vheight;');
-		// let displaceStrength = 0.1;
-		// if (MakeMaterial.heightUsed && displaceStrength > 0.0) {
-		// 	node_shader_write(vert, `vec3 pos2 = ${pos}.xyz + vec3(nor.xy, pos.w) * vec3(${height}, ${height}, ${height}) * vec3(${displaceStrength}, ${displaceStrength}, ${displaceStrength});`);
-		// 	node_shader_write(vert, 'gl_Position = mul(vec4(pos2.xyz, 1.0), WVP);');
-		// }
-
-		if (decal) {
-			if (context_raw.tool == WorkspaceTool.ToolText) {
-				node_shader_add_uniform(frag, 'sampler2D textexttool', '_textexttool');
-				node_shader_write(frag, `opacity *= textureLod(textexttool, texCoord / float(${brushScale}), 0.0).r;`);
-			}
-		}
-		if (decal) {
-			let opac = MakeMeshPreview.opacityDiscardDecal;
-			node_shader_write(frag, `if (opacity < ${opac}) discard;`);
-		}
-
-		node_shader_add_out(frag, 'vec4 fragColor[3]');
-		frag.n = true;
-
-		node_shader_add_function(frag, str_packFloatInt16);
-		node_shader_add_function(frag, str_cotangentFrame);
-		node_shader_add_function(frag, str_octahedronWrap);
-
-		if (MakeMaterial.heightUsed) {
-			node_shader_write(frag, 'if (height > 0.0) {');
-			node_shader_write(frag, 'float height_dx = dFdx(height * 2.0);');
-			node_shader_write(frag, 'float height_dy = dFdy(height * 2.0);');
-			// node_shader_write(frag, 'float height_dx = height0 - height1;');
-			// node_shader_write(frag, 'float height_dy = height2 - height3;');
-			// Whiteout blend
-			node_shader_write(frag, 'vec3 n1 = nortan * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
-			node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
-			node_shader_write(frag, 'nortan = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
-			node_shader_write(frag, '}');
-		}
-
-		// Apply normal channel
-		if (decal) {
-			// TODO
-		}
-		else {
-			frag.vVec = true;
-			///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
-			///else
-			node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
-			///end
-			node_shader_write(frag, 'n = nortan * 2.0 - 1.0;');
-			node_shader_write(frag, 'n.y = -n.y;');
-			node_shader_write(frag, 'n = normalize(mul(n, TBN));');
-		}
-
-		node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
-		node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
-		// uint matid = 0;
-
-		if (decal) {
-			node_shader_write(frag, 'fragColor[0] = vec4(n.x, n.y, roughness, packFloatInt16(metallic, uint(0)));'); // metallic/matid
-			node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
-		}
-		else {
-			node_shader_write(frag, 'fragColor[0] = vec4(n.x, n.y, mix(1.0, roughness, opacity), packFloatInt16(mix(1.0, metallic, opacity), uint(0)));'); // metallic/matid
-			node_shader_write(frag, 'fragColor[1] = vec4(mix(vec3(0.0, 0.0, 0.0), basecol, opacity), occlusion);');
-		}
-		node_shader_write(frag, 'fragColor[2] = vec4(0.0, 0.0, 0.0, 0.0);'); // veloc
-
-		parser_material_finalize(con_mesh);
-
-		///if arm_skin
-		if (skin) {
-			node_shader_write(vert, 'wnormal = normalize(mul(vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w)), N));');
-		}
-		///end
-
-		con_mesh.data.shader_from_source = true;
-		con_mesh.data.vertex_shader = node_shader_get(vert);
-		con_mesh.data.fragment_shader = node_shader_get(frag);
-
-		return con_mesh;
-	}
-}

+ 0 - 91
armorsculpt/Sources/MakeSculpt.ts

@@ -1,91 +0,0 @@
-
-class MakeSculpt {
-
-	static run = (data: TMaterial, matcon: material_context_t): NodeShaderContextRaw => {
-		let context_id = "paint";
-		let con_paint = NodeShadercontext_create(data, {
-			name: context_id,
-			depth_write: false,
-			compare_mode: "always", // TODO: align texcoords winding order
-			// cull_mode: "counter_clockwise",
-			cull_mode: "none",
-			vertex_elements: [{name: "pos", data: "float2"}],
-			color_attachments: ["RGBA32", "RGBA32", "RGBA32", "R8"]
-		});
-
-		con_paint.data.color_writes_red = [true, true, true, true];
-		con_paint.data.color_writes_green = [true, true, true, true];
-		con_paint.data.color_writes_blue = [true, true, true, true];
-		con_paint.data.color_writes_alpha = [true, true, true, true];
-		con_paint.allow_vcols = mesh_data_get_vertex_array(context_raw.paintObject.data, "col") != null;
-
-		let vert = NodeShadercontext_make_vert(con_paint);
-		let frag = NodeShadercontext_make_frag(con_paint);
-		frag.ins = vert.outs;
-
-		let faceFill = context_raw.tool == WorkspaceTool.ToolFill && context_raw.fillTypeHandle.position == FillType.FillFace;
-		let decal = context_raw.tool == WorkspaceTool.ToolDecal || context_raw.tool == WorkspaceTool.ToolText;
-
-		node_shader_add_out(vert, 'vec2 texCoord');
-		node_shader_write(vert, 'const vec2 madd = vec2(0.5, 0.5);');
-		node_shader_write(vert, 'texCoord = pos.xy * madd + madd;');
-		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
-		node_shader_write(vert, 'texCoord.y = 1.0 - texCoord.y;');
-		///end
-		node_shader_write(vert, 'gl_Position = vec4(pos.xy, 0.0, 1.0);');
-
-		node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
-		node_shader_add_uniform(frag, 'vec4 inplast', '_inputBrushLast');
-
-		node_shader_add_uniform(frag, 'sampler2D gbufferD');
-
-		node_shader_add_out(frag, 'vec4 fragColor[2]');
-
-		node_shader_add_uniform(frag, 'float brushRadius', '_brushRadius');
-		node_shader_add_uniform(frag, 'float brushOpacity', '_brushOpacity');
-		node_shader_add_uniform(frag, 'float brushHardness', '_brushHardness');
-
-		if (context_raw.tool == WorkspaceTool.ToolBrush  ||
-			context_raw.tool == WorkspaceTool.ToolEraser ||
-			context_raw.tool == WorkspaceTool.ToolClone  ||
-			context_raw.tool == WorkspaceTool.ToolBlur   ||
-			context_raw.tool == WorkspaceTool.ToolSmudge   ||
-			context_raw.tool == WorkspaceTool.ToolParticle ||
-			decal) {
-
-			let depthReject = !context_raw.xray;
-
-			MakeBrush.run(vert, frag);
-		}
-
-		node_shader_write(frag, 'vec3 basecol = vec3(1.0, 1.0, 1.0);');
-		node_shader_write(frag, 'float opacity = 1.0;');
-		node_shader_write(frag, 'if (opacity == 0.0) discard;');
-
-		node_shader_write(frag, 'float str = clamp((brushRadius - dist) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
-
-		node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
-		node_shader_write(frag, 'vec4 sample_undo = textureLod(texpaint_undo, texCoord, 0.0);');
-
-		node_shader_write(frag, 'if (sample_undo.r == 0 && sample_undo.g == 0 && sample_undo.b == 0) discard;');
-
-		node_shader_add_function(frag, str_octahedronWrap);
-		node_shader_add_uniform(frag, 'sampler2D gbuffer0_undo');
-		node_shader_write(frag, 'vec2 g0_undo = textureLod(gbuffer0_undo, inp.xy, 0.0).rg;');
-		node_shader_write(frag, 'vec3 wn;');
-		node_shader_write(frag, 'wn.z = 1.0 - abs(g0_undo.x) - abs(g0_undo.y);');
-		node_shader_write(frag, 'wn.xy = wn.z >= 0.0 ? g0_undo.xy : octahedronWrap(g0_undo.xy);');
-		node_shader_write(frag, 'vec3 n = normalize(wn);');
-
-		node_shader_write(frag, 'fragColor[0] = vec4(sample_undo.rgb + n * 0.1 * str, 1.0);');
-
-		node_shader_write(frag, 'fragColor[1] = vec4(str, 0.0, 0.0, 1.0);');
-
-		parser_material_finalize(con_paint);
-		con_paint.data.shader_from_source = true;
-		con_paint.data.vertex_shader = node_shader_get(vert);
-		con_paint.data.fragment_shader = node_shader_get(frag);
-
-		return con_paint;
-	}
-}

+ 0 - 395
armorsculpt/Sources/TabLayers.ts

@@ -1,395 +0,0 @@
-
-class TabLayers {
-
-	static layerNameEdit = -1;
-	static layerNameHandle = zui_handle_create();
-	static showContextMenu = false;
-
-	static draw = (htab: zui_handle_t) => {
-		let mini = config_config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_base_ui_base_sidebar_mini_w;
-		mini ? TabLayers.tab_layers_draw_mini(htab) : TabLayers.tab_layers_draw_full(htab);
-	}
-
-	static drawMini = (htab: zui_handle_t) => {
-		let ui = ui_base_ui_base_ui;
-		zui_set_hovered_tab_name(tr("Layers"));
-
-		let _ELEMENT_H = ui.t.ELEMENT_H;
-		ui.t.ELEMENT_H = math_floor(ui_base_ui_base_sidebar_mini_w / 2 / zui_SCALE(ui));
-
-		zui_begin_sticky();
-		zui_separator(5);
-
-		TabLayers.tab_layers_combo_filter();
-		TabLayers.tab_layers_button_new("+");
-
-		zui_end_sticky();
-		ui._y += 2;
-
-		TabLayers.tab_layers_highlight_odd_lines();
-		TabLayers.tab_layers_draw_slots(true);
-
-		ui.t.ELEMENT_H = _ELEMENT_H;
-	}
-
-	static drawFull = (htab: zui_handle_t) => {
-		let ui = ui_base_ui_base_ui;
-		if (zui_tab(htab, tr("Layers"))) {
-			zui_begin_sticky();
-			zui_row([1 / 4, 3 / 4]);
-
-			TabLayers.tab_layers_button_new(tr("New"));
-			TabLayers.tab_layers_combo_filter();
-
-			zui_end_sticky();
-			ui._y += 2;
-
-			TabLayers.tab_layers_highlight_odd_lines();
-			TabLayers.tab_layers_draw_slots(false);
-		}
-	}
-
-	static drawSlots = (mini: bool) => {
-		for (let i = 0; i < project_project_layers.length; ++i) {
-			if (i >= project_project_layers.length) break; // Layer was deleted
-			let j = project_project_layers.length - 1 - i;
-			let l = project_project_layers[j];
-			TabLayers.tab_layers_draw_layer_slot(l, j, mini);
-		}
-	}
-
-	static highlightOddLines = () => {
-		let ui = ui_base_ui_base_ui;
-		let step = ui.t.ELEMENT_H * 2;
-		let fullH = ui._window_h - ui_base_ui_base_hwnds[0].scroll_offset;
-		for (let i = 0; i < math_floor(fullH / step); ++i) {
-			if (i % 2 == 0) {
-				zui_fill(0, i * step, (ui._w / zui_SCALE(ui) - 2), step, ui.t.WINDOW_BG_COL - 0x00040404);
-			}
-		}
-	}
-
-	static buttonNew = (text: string) => {
-		let ui = ui_base_ui_base_ui;
-		if (zui_button(text)) {
-			ui_menu_draw((ui: zui_t) => {
-				let l = context_context_raw.layer;
-				if (ui_menu_button(ui, tr("Paint Layer"))) {
-					base_base_new_layer();
-					history_new_layer();
-				}
-			}, 1);
-		}
-	}
-
-	static comboFilter = () => {
-		let ui = ui_base_ui_base_ui;
-		let ar = [tr("All")];
-		let filterHandle = zui_handle("tablayers_0");
-		filterHandle.position = context_context_raw.layer_filter;
-		context_context_raw.layer_filter = zui_combo(filterHandle, ar, tr("Filter"), false, zui_align_t.LEFT);
-	}
-
-	static remapLayerPointers = (nodes: zui_node_t[], pointerMap: map_t<i32, i32>) => {
-		for (let n of nodes) {
-			if (n.type == "LAYER" || n.type == "LAYER_MASK") {
-				let i = n.buttons[0].default_value;
-				if (pointerMap.has(i)) {
-					n.buttons[0].default_value = pointerMap.get(i);
-				}
-			}
-		}
-	}
-
-	static initLayerMap = (): map_t<SlotLayerRaw, i32> => {
-		let res: map_t<SlotLayerRaw, i32> = map_create();
-		for (let i = 0; i < project_project_layers.length; ++i) res.set(project_project_layers[i], i);
-		return res;
-	}
-
-	static fillLayerMap = (map: map_t<SlotLayerRaw, i32>): map_t<i32, i32> => {
-		let res: map_t<i32, i32> = map_create();
-		for (let l of map.keys()) res.set(map.get(l), project_project_layers.indexOf(l) > -1 ? project_project_layers.indexOf(l) : 9999);
-		return res;
-	}
-
-	static setDragLayer = (layer: SlotLayerRaw, offX: f32, offY: f32) => {
-		base_base_drag_off_x = offX;
-		base_base_drag_off_y = offY;
-		base_base_drag_layer = layer;
-		context_context_raw.drag_dest = project_project_layers.indexOf(layer);
-	}
-
-	static drawLayerSlot = (l: SlotLayerRaw, i: i32, mini: bool) => {
-		let ui = ui_base_ui_base_ui;
-
-		if (context_context_raw.layer_filter > 0 &&
-			SlotLayer.slot_layer_get_object_mask(l) > 0 &&
-			SlotLayer.slot_layer_get_object_mask(l) != context_context_raw.layer_filter) {
-			return;
-		}
-
-		if (l.parent != null && !l.parent.show_panel) { // Group closed
-			return;
-		}
-		if (l.parent != null && l.parent.parent != null && !l.parent.parent.show_panel) {
-			return;
-		}
-
-		let step = ui.t.ELEMENT_H;
-		let checkw = (ui._window_w / 100 * 8) / zui_SCALE(ui);
-
-		// Highlight drag destination
-		let absy = ui._window_y + ui._y;
-		if (base_base_is_dragging && base_base_drag_layer != null && context_context_in_layers()) {
-			if (mouse_y > absy + step && mouse_y < absy + step * 3) {
-				let down = project_project_layers.indexOf(base_base_drag_layer) >= i;
-				context_context_raw.drag_dest = down ? i : i - 1;
-
-				let ls = project_project_layers;
-				let dest = context_context_raw.drag_dest;
-				let toGroup = down ? dest > 0 && ls[dest - 1].parent != null && ls[dest - 1].parent.show_panel : dest < ls.length && ls[dest].parent != null && ls[dest].parent.show_panel;
-				let nestedGroup = SlotLayer.slot_layer_is_group(base_base_drag_layer) && toGroup;
-				if (!nestedGroup) {
-					if (SlotLayer.slot_layer_can_move(context_context_raw.layer, context_context_raw.drag_dest)) {
-						zui_fill(checkw, step * 2, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
-					}
-				}
-			}
-			else if (i == project_project_layers.length - 1 && mouse_y < absy + step) {
-				context_context_raw.drag_dest = project_project_layers.length - 1;
-				if (SlotLayer.slot_layer_can_move(context_context_raw.layer, context_context_raw.drag_dest)) {
-					zui_fill(checkw, 0, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
-				}
-			}
-		}
-		if (base_base_is_dragging && (base_base_drag_material != null || base_base_drag_swatch != null) && context_context_in_layers()) {
-			if (mouse_y > absy + step && mouse_y < absy + step * 3) {
-				context_context_raw.drag_dest = i;
-				if (TabLayers.tab_layers_can_drop_new_layer(i))
-					zui_fill(checkw, 2 * step, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
-			}
-			else if (i == project_project_layers.length - 1 && mouse_y < absy + step) {
-				context_context_raw.drag_dest = project_project_layers.length;
-				if (TabLayers.tab_layers_can_drop_new_layer(project_project_layers.length))
-					zui_fill(checkw, 0, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
-			}
-		}
-
-		mini ? TabLayers.tab_layers_draw_layer_slot_mini(l, i) : TabLayers.tab_layers_draw_layer_slot_full(l, i);
-
-		TabLayers.tab_layers_draw_layer_highlight(l, mini);
-
-		if (TabLayers.tab_layers_show_context_menu) {
-			TabLayers.tab_layers_draw_layer_context_menu(l, mini);
-		}
-	}
-
-	static drawLayerSlotMini = (l: SlotLayerRaw, i: i32) => {
-		let ui = ui_base_ui_base_ui;
-
-		zui_row([1, 1]);
-		let uix = ui._x;
-		let uiy = ui._y;
-		zui_end_element();
-		zui_end_element();
-
-		ui._y += zui_ELEMENT_H(ui);
-		ui._y -= zui_ELEMENT_OFFSET(ui);
-	}
-
-	static drawLayerSlotFull = (l: SlotLayerRaw, i: i32) => {
-		let ui = ui_base_ui_base_ui;
-
-		let step = ui.t.ELEMENT_H;
-
-		let hasPanel = SlotLayer.slot_layer_is_group(l) || (SlotLayer.slot_layer_is_layer(l) && SlotLayer.slot_layer_get_masks(l, false) != null);
-		if (hasPanel) {
-			zui_row([8 / 100, 52 / 100, 30 / 100, 10 / 100]);
-		}
-		else {
-			zui_row([8 / 100, 52 / 100, 30 / 100]);
-		}
-
-		// Draw eye icon
-		let icons = resource_get("icons.k");
-		let r = resource_tile18(icons, l.visible ? 0 : 1, 0);
-		let center = (step / 2) * zui_SCALE(ui);
-		ui._x += 2;
-		ui._y += 3;
-		ui._y += center;
-		let col = ui.t.ACCENT_SELECT_COL;
-		let parentHidden = l.parent != null && (!l.parent.visible || (l.parent.parent != null && !l.parent.parent.visible));
-		if (parentHidden) col -= 0x99000000;
-
-		if (zui_image(icons, col, -1.0, r.x, r.y, r.w, r.h) == zui_state_t.RELEASED) {
-			TabLayers.tab_layers_layer_toggle_visible(l);
-		}
-		ui._x -= 2;
-		ui._y -= 3;
-		ui._y -= center;
-
-		let uix = ui._x;
-		let uiy = ui._y;
-
-		// Draw layer name
-		ui._y += center;
-		if (TabLayers.tab_layers_layer_name_edit == l.id) {
-			TabLayers.tab_layers_layer_name_handle.text = l.name;
-			l.name = zui_text_input(TabLayers.tab_layers_layer_name_handle);
-			if (ui.text_selected_handle_ptr != TabLayers.tab_layers_layer_name_handle.ptr) TabLayers.tab_layers_layer_name_edit = -1;
-		}
-		else {
-			if (ui.enabled && ui.input_enabled && ui.combo_selected_handle_ptr == 0 &&
-				ui.input_x > ui._window_x + ui._x && ui.input_x < ui._window_x + ui._window_w &&
-				ui.input_y > ui._window_y + ui._y - center && ui.input_y < ui._window_y + ui._y - center + (step * zui_SCALE(ui)) * 2) {
-				if (ui.input_started) {
-					context_context_set_layer(l);
-					TabLayers.tab_layers_set_drag_layer(context_context_raw.layer, -(mouse_x - uix - ui._window_x - 3), -(mouse_y - uiy - ui._window_y + 1));
-				}
-				else if (ui.input_released) {
-					if (time_time() - context_context_raw.select_time > 0.2) {
-						context_context_raw.select_time = time_time();
-					}
-				}
-				else if (ui.input_released_r) {
-					context_context_set_layer(l);
-					TabLayers.tab_layers_show_context_menu = true;
-				}
-			}
-
-			let state = zui_text(l.name);
-			if (state == zui_state_t.RELEASED) {
-				let td = time_time() - context_context_raw.select_time;
-				if (td < 0.2 && td > 0.0) {
-					TabLayers.tab_layers_layer_name_edit = l.id;
-					TabLayers.tab_layers_layer_name_handle.text = l.name;
-					zui_start_text_edit(TabLayers.tab_layers_layer_name_handle);
-				}
-			}
-
-			// let inFocus = ui.inputX > ui._windowX && ui.inputX < ui._windowX + ui._windowW &&
-			// 			  ui.inputY > ui._windowY && ui.inputY < ui._windowY + ui._windowH;
-			// if (inFocus && ui.isDeleteDown && canDelete(context_raw.layer)) {
-			// 	ui.isDeleteDown = false;
-			// 	let _init() = () => {
-			// 		deleteLayer(context_raw.layer);
-			// 	}
-			// 	app_notify_on_init(_init);
-			// }
-		}
-		ui._y -= center;
-
-		if (l.parent != null) {
-			ui._x -= 10 * zui_SCALE(ui);
-			if (l.parent.parent != null) ui._x -= 10 * zui_SCALE(ui);
-		}
-
-		if (SlotLayer.slot_layer_is_group(l)) {
-			zui_end_element();
-		}
-		else {
-			if (SlotLayer.slot_layer_is_mask(l)) {
-				ui._y += center;
-			}
-
-			// comboBlending(ui, l);
-			zui_end_element();
-
-			if (SlotLayer.slot_layer_is_mask(l)) {
-				ui._y -= center;
-			}
-		}
-
-		if (hasPanel) {
-			ui._y += center;
-			let layerPanel = zui_nest(zui_handle("tablayers_1"), l.id);
-			layerPanel.selected = l.show_panel;
-			l.show_panel = zui_panel(layerPanel, "", true, false, false);
-			ui._y -= center;
-		}
-
-		if (SlotLayer.slot_layer_is_group(l) || SlotLayer.slot_layer_is_mask(l)) {
-			ui._y -= zui_ELEMENT_OFFSET(ui);
-			zui_end_element();
-		}
-		else {
-			ui._y -= zui_ELEMENT_OFFSET(ui);
-
-			zui_row([8 / 100, 16 / 100, 36 / 100, 30 / 100, 10 / 100]);
-			zui_end_element();
-			zui_end_element();
-			zui_end_element();
-
-			if (config_config_raw.touch_ui) {
-				ui._x += 12 * zui_SCALE(ui);
-			}
-
-			ui._y -= center;
-			TabLayers.tab_layers_combo_object(ui, l);
-			ui._y += center;
-
-			zui_end_element();
-		}
-
-		ui._y -= zui_ELEMENT_OFFSET(ui);
-	}
-
-	static comboObject = (ui: zui_t, l: SlotLayerRaw, label = false): zui_handle_t => {
-		let ar = [tr("Shared")];
-		let objectHandle = zui_nest(zui_handle("tablayers_2"), l.id);
-		objectHandle.position = l.object_mask;
-		l.object_mask = zui_combo(objectHandle, ar, tr("Object"), label, zui_align_t.LEFT);
-		return objectHandle;
-	}
-
-	static layerToggleVisible = (l: SlotLayerRaw) => {
-		l.visible = !l.visible;
-		ui_view2d_ui_view2d_hwnd.redraws = 2;
-		MakeMaterial.make_material_parse_mesh_material();
-	}
-
-	static drawLayerHighlight = (l: SlotLayerRaw, mini: bool) => {
-		let ui = ui_base_ui_base_ui;
-		let step = ui.t.ELEMENT_H;
-
-		// Separator line
-		zui_fill(0, 0, (ui._w / zui_SCALE(ui) - 2), 1 * zui_SCALE(ui), ui.t.SEPARATOR_COL);
-
-		// Highlight selected
-		if (context_context_raw.layer == l) {
-			if (mini) {
-				zui_rect(1, -step * 2, ui._w / zui_SCALE(ui) - 1, step * 2 + (mini ? -1 : 1), ui.t.HIGHLIGHT_COL, 3);
-			}
-			else {
-				zui_rect(1, -step * 2 - 1, ui._w / zui_SCALE(ui) - 2, step * 2 + (mini ? -2 : 1), ui.t.HIGHLIGHT_COL, 2);
-			}
-		}
-	}
-
-	static canMergeDown = (l: SlotLayerRaw) : bool => {
-		let index = project_project_layers.indexOf(l);
-		// Lowest layer
-		if (index == 0) return false;
-		// Lowest layer that has masks
-		if (SlotLayer.slot_layer_is_layer(l) && SlotLayer.slot_layer_is_mask(project_project_layers[0]) && project_project_layers[0].parent == l) return false;
-		// The lowest toplevel layer is a group
-		if (SlotLayer.slot_layer_is_group(l) && SlotLayer.slot_layer_is_in_group(project_project_layers[0]) && SlotLayer.slot_layer_get_containing_group(project_project_layers[0]) == l) return false;
-		// Masks must be merged down to masks
-		if (SlotLayer.slot_layer_is_mask(l) && !SlotLayer.slot_layer_is_mask(project_project_layers[index - 1])) return false;
-		return true;
-	}
-
-	static drawLayerContextMenu = (l: SlotLayerRaw, mini: bool) => {
-
-	}
-
-	static canDropNewLayer = (position: i32) => {
-		if (position > 0 && position < project_project_layers.length && SlotLayer.slot_layer_is_mask(project_project_layers[position - 1])) {
-			// 1. The layer to insert is inserted in the middle
-			// 2. The layer below is a mask, i.e. the layer would have to be a (group) mask, too.
-			return false;
-		}
-		return true;
-	}
-}

+ 95 - 0
armorsculpt/Sources/export_obj.ts

@@ -0,0 +1,95 @@
+
+function export_obj_write_string(out: i32[], str: string) {
+	for (let i = 0; i < str.length; ++i) {
+		out.push(str.charCodeAt(i));
+	}
+}
+
+function export_obj_run(path: string, paint_objects: mesh_object_t[], apply_displacement = false) {
+	let o: i32[] = [];
+	export_obj_write_string(o, "# armorsculpt.org\n");
+
+	let texpaint = project_layers[0].texpaint;
+	let pixels = image_get_pixels(texpaint);
+	let pixelsView = new DataView(pixels);
+	let mesh = paint_objects[0].data;
+	let inda = mesh.index_arrays[0].values;
+
+	let posa = new Int16Array(inda.length * 4);
+	for (let i = 0; i < inda.length; ++i) {
+		let index = inda[i];
+		posa[index * 4    ] = math_floor(pixelsView.getFloat32(i * 16    , true) * 32767);
+		posa[index * 4 + 1] = math_floor(pixelsView.getFloat32(i * 16 + 4, true) * 32767);
+		posa[index * 4 + 2] = math_floor(pixelsView.getFloat32(i * 16 + 8, true) * 32767);
+	}
+
+	let poff = 0;
+	// for (let p of paintObjects) {
+		let p = paint_objects[0];
+		// let mesh = p.data.raw;
+		let inv = 1 / 32767;
+		let sc = p.data.scale_pos * inv;
+		// let posa = mesh.vertex_arrays[0].values;
+		let len = math_floor(posa.length / 4);
+		// let len = math_floor(inda.length);
+
+		// Merge shared vertices and remap indices
+		let posa2 = new Int16Array(len * 3);
+		let posmap = new map_t<i32, i32>();
+
+		let pi = 0;
+		for (let i = 0; i < len; ++i) {
+			let found = false;
+			for (let j = 0; j < pi; ++j) {
+				if (posa2[j * 3    ] == posa[i * 4    ] &&
+					posa2[j * 3 + 1] == posa[i * 4 + 1] &&
+					posa2[j * 3 + 2] == posa[i * 4 + 2]) {
+					posmap.set(i, j);
+					found = true;
+					break;
+				}
+			}
+			if (!found) {
+				posmap.set(i, pi);
+				posa2[pi * 3    ] = posa[i * 4    ];
+				posa2[pi * 3 + 1] = posa[i * 4 + 1];
+				posa2[pi * 3 + 2] = posa[i * 4 + 2];
+				pi++;
+			}
+		}
+
+		export_obj_write_string(o, "o " + p.base.name + "\n");
+		for (let i = 0; i < pi; ++i) {
+			export_obj_write_string(o, "v ");
+			let vx = posa2[i * 3] * sc + "";
+			export_obj_write_string(o, vx.substr(0, vx.indexOf(".") + 7));
+			export_obj_write_string(o, " ");
+			let vy = posa2[i * 3 + 2] * sc + "";
+			export_obj_write_string(o, vy.substr(0, vy.indexOf(".") + 7));
+			export_obj_write_string(o, " ");
+			let vz = -posa2[i * 3 + 1] * sc + "";
+			export_obj_write_string(o, vz.substr(0, vz.indexOf(".") + 7));
+			export_obj_write_string(o, "\n");
+		}
+
+		// let inda = mesh.index_arrays[0].values;
+		for (let i = 0; i < math_floor(inda.length / 3); ++i) {
+			let pi1 = posmap.get(inda[i * 3    ]) + 1 + poff;
+			let pi2 = posmap.get(inda[i * 3 + 1]) + 1 + poff;
+			let pi3 = posmap.get(inda[i * 3 + 2]) + 1 + poff;
+			export_obj_write_string(o, "f ");
+			export_obj_write_string(o, pi1 + "");
+			export_obj_write_string(o, " ");
+			export_obj_write_string(o, pi2 + "");
+			export_obj_write_string(o, " ");
+			export_obj_write_string(o, pi3 + "");
+			export_obj_write_string(o, "\n");
+		}
+		poff += pi;
+	// }
+
+	if (!path.endsWith(".obj")) path += ".obj";
+
+	let b = Uint8Array.from(o).buffer;
+	krom_file_save_bytes(path, b, b.byteLength);
+}

+ 189 - 0
armorsculpt/Sources/import_mesh.ts

@@ -0,0 +1,189 @@
+
+let import_mesh_clear_layers: bool = true;
+
+function import_mesh_run(path: string, _clear_layers = true, replace_existing = true) {
+	if (!path_is_mesh(path)) {
+		if (!context_enable_import_plugin(path)) {
+			console_error(strings_error1());
+			return;
+		}
+	}
+
+	import_mesh_clear_layers = _clear_layers;
+	context_raw.layer_filter = 0;
+
+	let p = path.toLowerCase();
+	if (p.endsWith(".obj")) import_obj_run(path, replace_existing);
+	else if (p.endsWith(".blend")) import_blend_mesh_run(path, replace_existing);
+	else {
+		let ext = path.substr(path.lastIndexOf(".") + 1);
+		let importer = path_mesh_importers.get(ext);
+		importer(path, (mesh: any) => {
+			replace_existing ? import_mesh_make_mesh(mesh, path) : import_mesh_add_mesh(mesh);
+		});
+	}
+
+	project_mesh_assets = [path];
+
+	///if (krom_android || krom_ios)
+	sys_title_set(path.substring(path.lastIndexOf(path_sep) + 1, path.lastIndexOf(".")));
+	///end
+}
+
+function import_mesh_finish_import() {
+	if (context_raw.merged_object != null) {
+		mesh_object_remove(context_raw.merged_object);
+		data_delete_mesh(context_raw.merged_object.data._.handle);
+		context_raw.merged_object = null;
+	}
+
+	context_select_paint_object(context_main_object());
+
+	if (project_paint_objects.length > 1) {
+		// Sort by name
+		project_paint_objects.sort((a, b): i32 => {
+			if (a.base.name < b.base.name) return -1;
+			else if (a.base.name > b.base.name) return 1;
+			return 0;
+		});
+
+		// No mask by default
+		for (let p of project_paint_objects) p.base.visible = true;
+		if (context_raw.merged_object == null) util_mesh_merge();
+		context_raw.paint_object.skip_context = "paint";
+		context_raw.merged_object.base.visible = true;
+	}
+
+	viewport_scale_to_bounds();
+
+	if (context_raw.paint_object.base.name == "") context_raw.paint_object.base.name = "Object";
+	make_material_parse_paint_material();
+	make_material_parse_mesh_material();
+
+	ui_view2d_hwnd.redraws = 2;
+
+	///if arm_physics
+	context_raw.paint_body = null;
+	///end
+}
+
+function import_mesh_make_mesh(mesh: any, path: string) {
+	if (mesh == null || mesh.posa == null || mesh.nora == null || mesh.inda == null || mesh.posa.length == 0) {
+		console_error(strings_error3());
+		return;
+	}
+
+	let _makeMesh = () => {
+		let raw = import_mesh_raw_mesh(mesh);
+		if (mesh.cola != null) raw.vertex_arrays.push({ values: mesh.cola, attrib: "col", data: "short4norm" });
+
+		let md: mesh_data_t = mesh_data_create(raw);
+		context_raw.paint_object = context_main_object();
+
+		context_select_paint_object(context_main_object());
+		for (let i = 0; i < project_paint_objects.length; ++i) {
+			let p = project_paint_objects[i];
+			if (p == context_raw.paint_object) continue;
+			data_delete_mesh(p.data._.handle);
+			mesh_object_remove(p);
+		}
+		let handle = context_raw.paint_object.data._.handle;
+		if (handle != "SceneSphere" && handle != "ScenePlane") {
+			data_delete_mesh(handle);
+		}
+
+		if (import_mesh_clear_layers) {
+			while (project_layers.length > 0) {
+				let l = project_layers.pop();
+				slot_layer_unload(l);
+			}
+			base_new_layer(false);
+			app_notify_on_init(base_init_layers);
+			history_reset();
+		}
+
+		mesh_object_set_data(context_raw.paint_object, md);
+		context_raw.paint_object.base.name = mesh.name;
+		project_paint_objects = [context_raw.paint_object];
+
+		md._.handle = raw.name;
+		data_cached_meshes.set(md._.handle, md);
+
+		context_raw.ddirty = 4;
+		ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
+		ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
+
+		// Wait for addMesh calls to finish
+		app_notify_on_init(import_mesh_finish_import);
+
+		base_notify_on_next_frame(() => {
+			let f32 = new Float32Array(config_get_texture_res_x() * config_get_texture_res_y() * 4);
+			for (let i = 0; i < math_floor(mesh.inda.length); ++i) {
+				let index = mesh.inda[i];
+				f32[i * 4]     = mesh.posa[index * 4]     / 32767;
+				f32[i * 4 + 1] = mesh.posa[index * 4 + 1] / 32767;
+				f32[i * 4 + 2] = mesh.posa[index * 4 + 2] / 32767;
+				f32[i * 4 + 3] = 1.0;
+			}
+			let imgmesh = image_from_bytes(f32.buffer, config_get_texture_res_x(), config_get_texture_res_y(), tex_format_t.RGBA128);
+			let texpaint = project_layers[0].texpaint;
+			g2_begin(texpaint);
+			g2_set_pipeline(base_pipe_copy128);
+			g2_draw_scaled_image(imgmesh, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+			g2_set_pipeline(null);
+			g2_end();
+		});
+	}
+
+	_makeMesh();
+}
+
+function import_mesh_add_mesh(mesh: any) {
+
+	let _addMesh = () => {
+		let raw = import_mesh_raw_mesh(mesh);
+		if (mesh.cola != null) raw.vertex_arrays.push({ values: mesh.cola, attrib: "col", data: "short4norm" });
+
+		let md: mesh_data_t = mesh_data_create(raw);
+
+		let object = scene_add_mesh_object(md, context_raw.paint_object.materials, context_raw.paint_object.base);
+		object.base.name = mesh.base.name;
+		object.skip_context = "paint";
+
+		// Ensure unique names
+		for (let p of project_paint_objects) {
+			if (p.base.name == object.base.name) {
+				p.base.name += ".001";
+				p.data._.handle += ".001";
+				data_cached_meshes.set(p.data._.handle, p.data);
+			}
+		}
+
+		project_paint_objects.push(object);
+
+		md._.handle = raw.name;
+		data_cached_meshes.set(md._.handle, md);
+
+		context_raw.ddirty = 4;
+		ui_base_hwnds[tab_area_t.SIDEBAR0].redraws = 2;
+	}
+
+	_addMesh();
+}
+
+function import_mesh_raw_mesh(mesh: any): mesh_data_t {
+	let posa = new Int16Array(math_floor(mesh.inda.length * 4));
+	for (let i = 0; i < posa.length; ++i) posa[i] = 32767;
+	let inda = new Uint32Array(mesh.inda.length);
+	for (let i = 0; i < inda.length; ++i) inda[i] = i;
+	return {
+		name: mesh.name,
+		vertex_arrays: [
+			{ values: posa, attrib: "pos", data: "short4norm" }
+		],
+		index_arrays: [
+			{ values: inda, material: 0 }
+		],
+		scale_pos: 1.0
+	};
+}

+ 36 - 0
armorsculpt/Sources/make_brush.ts

@@ -0,0 +1,36 @@
+
+function make_brush_run(vert: NodeShaderRaw, frag: NodeShaderRaw) {
+
+	node_shader_write(frag, 'float dist = 0.0;');
+
+	if (config_raw.brush_3d) {
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'float depth = textureLod(gbufferD, inp.xy, 0.0).r;');
+		///else
+		node_shader_write(frag, 'float depth = textureLod(gbufferD, vec2(inp.x, 1.0 - inp.y), 0.0).r;');
+		///end
+
+		node_shader_add_uniform(frag, 'mat4 invVP', '_inv_view_proj_matrix');
+		node_shader_write(frag, 'vec4 winp = vec4(vec2(inp.x, 1.0 - inp.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);');
+		node_shader_write(frag, 'winp = mul(winp, invVP);');
+		node_shader_write(frag, 'winp.xyz /= winp.w;');
+
+		node_shader_add_uniform(frag, 'mat4 W', '_world_matrix');
+
+		node_shader_write_attrib(frag, 'vec3 wposition = mul(texelFetch(texpaint_undo, ivec2(texCoord.x * textureSize(texpaint_undo, 0).x, texCoord.y * textureSize(texpaint_undo, 0).y), 0), W).xyz;');
+
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'float depthlast = textureLod(gbufferD, inplast.xy, 0.0).r;');
+		///else
+		node_shader_write(frag, 'float depthlast = textureLod(gbufferD, vec2(inplast.x, 1.0 - inplast.y), 0.0).r;');
+		///end
+
+		node_shader_write(frag, 'vec4 winplast = vec4(vec2(inplast.x, 1.0 - inplast.y) * 2.0 - 1.0, depthlast * 2.0 - 1.0, 1.0);');
+		node_shader_write(frag, 'winplast = mul(winplast, invVP);');
+		node_shader_write(frag, 'winplast.xyz /= winplast.w;');
+
+		node_shader_write(frag, 'dist = distance(wposition, winp.xyz);');
+	}
+
+	node_shader_write(frag, 'if (dist > brushRadius) discard;');
+}

+ 322 - 0
armorsculpt/Sources/make_material.ts

@@ -0,0 +1,322 @@
+
+let make_material_default_scon: shader_context_t = null;
+let make_material_default_mcon: material_context_t = null;
+
+let make_material_height_used: bool = false;
+let make_material_emis_used: bool = false;
+let make_material_subs_used: bool = false;
+
+function make_material_get_mout(): bool {
+	for (let n of ui_nodes_get_canvas_material().nodes) if (n.type == "OUTPUT_MATERIAL_PBR") return true;
+	return false;
+}
+
+function make_material_parse_mesh_material() {
+	let m = project_materials[0].data;
+
+	for (let c of m._.shader._.contexts) {
+		if (c.name == "mesh") {
+			array_remove(m._.shader.contexts, c);
+			array_remove(m._.shader._.contexts, c);
+			make_material_delete_context(c);
+			break;
+		}
+	}
+
+	if (make_mesh_layer_pass_count > 1) {
+		let i = 0;
+		while (i < m._.shader._.contexts.length) {
+			let c = m._.shader._.contexts[i];
+			for (let j = 1; j < make_mesh_layer_pass_count; ++j) {
+				if (c.name == "mesh" + j) {
+					array_remove(m._.shader.contexts, c);
+					array_remove(m._.shader._.contexts, c);
+					make_material_delete_context(c);
+					i--;
+					break;
+				}
+			}
+			i++;
+		}
+
+		i = 0;
+		while (i < m.contexts.length) {
+			let c = m.contexts[i];
+			for (let j = 1; j < make_mesh_layer_pass_count; ++j) {
+				if (c.name == "mesh" + j) {
+					array_remove(m.contexts, c);
+					array_remove(m._.contexts, c);
+					i--;
+					break;
+				}
+			}
+			i++;
+		}
+	}
+
+	let con = make_mesh_run({ name: "Material", canvas: null });
+	let scon: shader_context_t = shader_context_create(con.data);
+	scon._.override_context = {};
+	if (con.frag.shared_samplers.length > 0) {
+		let sampler = con.frag.shared_samplers[0];
+		scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
+	}
+	if (!context_raw.texture_filter) {
+		scon._.override_context.filter = "point";
+	}
+	m._.shader.contexts.push(scon);
+	m._.shader._.contexts.push(scon);
+
+	for (let i = 1; i < make_mesh_layer_pass_count; ++i) {
+		let con = make_mesh_run({ name: "Material", canvas: null }, i);
+		let scon: shader_context_t = shader_context_create(con.data);
+		scon._.override_context = {};
+		if (con.frag.shared_samplers.length > 0) {
+			let sampler = con.frag.shared_samplers[0];
+			scon._.override_context.shared_sampler = sampler.substr(sampler.lastIndexOf(" ") + 1);
+		}
+		if (!context_raw.texture_filter) {
+			scon._.override_context.filter = "point";
+		}
+		m._.shader.contexts.push(scon);
+		m._.shader._.contexts.push(scon);
+
+		let mcon: material_context_t = material_context_create({ name: "mesh" + i, bind_textures: [] });
+		m.contexts.push(mcon);
+		m._.contexts.push(mcon);
+	}
+
+	context_raw.ddirty = 2;
+
+	///if arm_voxels
+	make_material_make_voxel(m);
+	///end
+}
+
+function make_material_parse_particle_material() {
+	let m = context_raw.particle_material;
+	let sc: shader_context_t = null;
+	for (let c of m._.shader._.contexts) {
+		if (c.name == "mesh") {
+			sc = c;
+			break;
+		}
+	}
+	if (sc != null) {
+		array_remove(m._.shader.contexts, sc);
+		array_remove(m._.shader._.contexts, sc);
+	}
+	let con = make_particle_run({ name: "MaterialParticle", canvas: null });
+	if (sc != null) make_material_delete_context(sc);
+	sc = shader_context_create(con.data);
+	m._.shader.contexts.push(sc);
+	m._.shader._.contexts.push(sc);
+}
+
+function make_material_parse_mesh_preview_material() {
+	if (!make_material_get_mout()) return;
+
+	let m = project_materials[0].data;
+	let scon: shader_context_t = null;
+	for (let c of m._.shader._.contexts) {
+		if (c.name == "mesh") {
+			scon = c;
+			break;
+		}
+	}
+	array_remove(m._.shader.contexts, scon);
+	array_remove(m._.shader._.contexts, scon);
+
+	let mcon: material_context_t = { name: "mesh", bind_textures: [] };
+
+	let sd: material_t = { name: "Material", canvas: null };
+	let con = make_mesh_preview_run(sd, mcon);
+
+	for (let i = 0; i < m.contexts.length; ++i) {
+		if (m.contexts[i].name == "mesh") {
+			m.contexts[i] = material_context_create(mcon);
+			break;
+		}
+	}
+
+	if (scon != null) make_material_delete_context(scon);
+
+	let compileError = false;
+	let _scon: shader_context_t = shader_context_create(con.data);
+	if (_scon == null) compileError = true;
+	scon = _scon;
+	if (compileError) return;
+
+	m._.shader.contexts.push(scon);
+	m._.shader._.contexts.push(scon);
+}
+
+///if arm_voxels
+function make_material_make_voxel(m: material_data_t) {
+	let rebuild = make_material_height_used;
+	if (config_raw.rp_gi != false && rebuild) {
+		let scon: shader_context_t = null;
+		for (let c of m._.shader._.contexts) {
+			if (c.name == "voxel") {
+				scon = c;
+				break;
+			}
+		}
+		if (scon != null) make_voxel_run(scon);
+	}
+}
+///end
+
+function make_material_parse_paint_material(bake_previews: bool = true) {
+	if (!make_material_get_mout()) return;
+
+	if (bake_previews) {
+		let current = _g2_current;
+		let g2_in_use: bool = _g2_in_use;
+		if (g2_in_use) g2_end();
+		make_material_bake_node_previews();
+		if (g2_in_use) g2_begin(current);
+	}
+
+	let m = project_materials[0].data;
+	let scon: shader_context_t = null;
+	let mcon: material_context_t = null;
+	for (let c of m._.shader._.contexts) {
+		if (c.name == "paint") {
+			array_remove(m._.shader.contexts, c);
+			array_remove(m._.shader._.contexts, c);
+			if (c != make_material_default_scon) make_material_delete_context(c);
+			break;
+		}
+	}
+	for (let c of m.contexts) {
+		if (c.name == "paint") {
+			array_remove(m.contexts, c);
+			array_remove(m._.contexts, c);
+			break;
+		}
+	}
+
+	let sdata: material_t = { name: "Material", canvas: ui_nodes_get_canvas_material() };
+	let mcon2: material_context_t = { name: "paint", bind_textures: [] };
+	let con = make_sculpt_run(sdata, mcon2);
+
+	let compileError = false;
+	let scon2: shader_context_t;
+	let _scon: shader_context_t = shader_context_create(con.data);
+	if (_scon == null) compileError = true;
+	scon2 = _scon;
+
+	if (compileError) return;
+	scon2._.override_context = {};
+	scon2._.override_context.addressing = "repeat";
+	let mcon3: material_context_t = material_context_create(mcon2);
+
+	m._.shader.contexts.push(scon2);
+	m._.shader._.contexts.push(scon2);
+	m.contexts.push(mcon3);
+	m._.contexts.push(mcon3);
+
+	if (make_material_default_scon == null) make_material_default_scon = scon2;
+	if (make_material_default_mcon == null) make_material_default_mcon = mcon3;
+}
+
+function make_material_bake_node_previews() {
+	context_raw.node_previews_used = [];
+	if (context_raw.node_previews == null) context_raw.node_previews = map_create();
+	make_material_traverse_nodes(ui_nodes_get_canvas_material().nodes, null, []);
+	for (let key of context_raw.node_previews.keys()) {
+		if (context_raw.node_previews_used.indexOf(key) == -1) {
+			let image = context_raw.node_previews.get(key);
+			base_notify_on_next_frame(function() { image_unload(image); });
+			context_raw.node_previews.delete(key);
+		}
+	}
+}
+
+function make_material_traverse_nodes(nodes: zui_node_t[], group: zui_node_canvas_t, parents: zui_node_t[]) {
+	for (let node of nodes) {
+		make_material_bake_node_preview(node, group, parents);
+		if (node.type == "GROUP") {
+			for (let g of project_material_groups) {
+				if (g.canvas.name == node.name) {
+					parents.push(node);
+					make_material_traverse_nodes(g.canvas.nodes, g.canvas, parents);
+					parents.pop();
+					break;
+				}
+			}
+		}
+	}
+}
+
+function make_material_bake_node_preview(node: zui_node_t, group: zui_node_canvas_t, parents: zui_node_t[]) {
+	if (node.type == "BLUR") {
+		let id = parser_material_node_name(node, parents);
+		let image = context_raw.node_previews.get(id);
+		context_raw.node_previews_used.push(id);
+		let resX = math_floor(config_get_texture_res_x() / 4);
+		let resY = math_floor(config_get_texture_res_y() / 4);
+		if (image == null || image.width != resX || image.height != resY) {
+			if (image != null) image_unload(image);
+			image = image_create_render_target(resX, resY);
+			context_raw.node_previews.set(id, image);
+		}
+
+		parser_material_blur_passthrough = true;
+		util_render_make_node_preview(ui_nodes_get_canvas_material(), node, image, group, parents);
+		parser_material_blur_passthrough = false;
+	}
+	else if (node.type == "DIRECT_WARP") {
+		let id = parser_material_node_name(node, parents);
+		let image = context_raw.node_previews.get(id);
+		context_raw.node_previews_used.push(id);
+		let resX = math_floor(config_get_texture_res_x());
+		let resY = math_floor(config_get_texture_res_y());
+		if (image == null || image.width != resX || image.height != resY) {
+			if (image != null) image_unload(image);
+			image = image_create_render_target(resX, resY);
+			context_raw.node_previews.set(id, image);
+		}
+
+		parser_material_warp_passthrough = true;
+		util_render_make_node_preview(ui_nodes_get_canvas_material(), node, image, group, parents);
+		parser_material_warp_passthrough = false;
+	}
+}
+
+function make_material_parse_node_preview_material(node: zui_node_t, group: zui_node_canvas_t = null, parents: zui_node_t[] = null): { scon: shader_context_t, mcon: material_context_t } {
+	if (node.outputs.length == 0) return null;
+	let sdata: material_t = { name: "Material", canvas: ui_nodes_get_canvas_material() };
+	let mcon_raw: material_context_t = { name: "mesh", bind_textures: [] };
+	let con = make_node_preview_run(sdata, mcon_raw, node, group, parents);
+	let compileError = false;
+	let scon: shader_context_t;
+	let _scon: shader_context_t = shader_context_create(con.data);
+	if (_scon == null) compileError = true;
+	scon = _scon;
+
+	if (compileError) return null;
+	let mcon: material_context_t = material_context_create(mcon_raw);
+	return { scon: scon, mcon: mcon };
+}
+
+function make_material_parse_brush() {
+	parser_logic_parse(context_raw.brush.canvas);
+}
+
+function make_material_get_displace_strength(): f32 {
+	let sc = context_main_object().base.transform.scale.x;
+	return config_raw.displace_strength * 0.02 * sc;
+}
+
+function make_material_voxelgi_half_extents(): string {
+	let ext = context_raw.vxao_ext;
+	return `const vec3 voxelgiHalfExtents = vec3(${ext}, ${ext}, ${ext});`;
+}
+
+function make_material_delete_context(c: shader_context_t) {
+	base_notify_on_next_frame(() => { // Ensure pipeline is no longer in use
+		shader_context_delete(c);
+	});
+}

+ 335 - 0
armorsculpt/Sources/make_mesh.ts

@@ -0,0 +1,335 @@
+
+let make_mesh_layer_pass_count = 1;
+
+function make_mesh_run(data: material_t, layer_pass: i32 = 0): NodeShaderContextRaw {
+	let context_id = layer_pass == 0 ? "mesh" : "mesh" + layer_pass;
+	let con_mesh = node_shader_context_create(data, {
+		name: context_id,
+		depth_write: layer_pass == 0 ? true : false,
+		compare_mode: layer_pass == 0 ? "less" : "equal",
+		cull_mode: (context_raw.cull_backfaces || layer_pass > 0) ? "clockwise" : "none",
+		vertex_elements: [{name: "pos", data: "short4norm"}],
+		color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
+		depth_attachment: "DEPTH32"
+	});
+
+	let vert = node_shader_context_make_vert(con_mesh);
+	let frag = node_shader_context_make_frag(con_mesh);
+	frag.ins = vert.outs;
+
+	node_shader_add_out(vert, 'vec2 texCoord');
+	frag.wvpposition = true;
+	node_shader_add_out(vert, 'vec4 prevwvpposition');
+	node_shader_add_uniform(vert, 'mat4 VP', '_view_proj_matrix');
+	node_shader_add_uniform(vert, 'mat4 prevWVP', '_prev_world_view_proj_matrix');
+	vert.wposition = true;
+
+	let textureCount = 0;
+
+	node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
+	node_shader_add_uniform(vert, 'sampler2D texpaint_vert', '_texpaint_vert' + project_layers[0].id);
+	node_shader_write(vert, 'vec3 meshpos = texelFetch(texpaint_vert, ivec2(gl_VertexID % textureSize(texpaint_vert, 0).x, gl_VertexID / textureSize(texpaint_vert, 0).y), 0).xyz;');
+	// + pos.xyz * 0.000001
+	node_shader_write(vert, 'gl_Position = mul(vec4(meshpos.xyz, 1.0), WVP);');
+
+	node_shader_write(vert, 'texCoord = vec2(0.0, 0.0);');
+
+	node_shader_write(vert, 'prevwvpposition = mul(vec4(pos.xyz, 1.0), prevWVP);');
+
+	node_shader_add_out(frag, 'vec4 fragColor[3]');
+
+	node_shader_add_uniform(vert, "mat3 N", "_normal_matrix");
+	node_shader_add_out(vert, "vec3 wnormal");
+
+	node_shader_write_attrib(vert, 'int baseVertex0 = gl_VertexID - (gl_VertexID % 3);');
+	node_shader_write_attrib(vert, 'int baseVertex1 = baseVertex0 + 1;');
+	node_shader_write_attrib(vert, 'int baseVertex2 = baseVertex0 + 2;');
+	node_shader_write_attrib(vert, 'vec3 meshpos0 = texelFetch(texpaint_vert, ivec2(baseVertex0 % textureSize(texpaint_vert, 0).x, baseVertex0 / textureSize(texpaint_vert, 0).y), 0).xyz;');
+	node_shader_write_attrib(vert, 'vec3 meshpos1 = texelFetch(texpaint_vert, ivec2(baseVertex1 % textureSize(texpaint_vert, 0).x, baseVertex1 / textureSize(texpaint_vert, 0).y), 0).xyz;');
+	node_shader_write_attrib(vert, 'vec3 meshpos2 = texelFetch(texpaint_vert, ivec2(baseVertex2 % textureSize(texpaint_vert, 0).x, baseVertex2 / textureSize(texpaint_vert, 0).y), 0).xyz;');
+	node_shader_write_attrib(vert, 'vec3 meshnor = normalize(cross(meshpos2 - meshpos1, meshpos0 - meshpos1));');
+	node_shader_write_attrib(vert, 'wnormal = mul(meshnor, N);');
+	node_shader_write_attrib(frag, 'vec3 n = normalize(wnormal);');
+
+	node_shader_add_function(frag, str_pack_float_int16);
+	node_shader_add_function(frag, str_octahedron_wrap);
+	node_shader_add_function(frag, str_cotangent_frame);
+	if (layer_pass > 0) {
+		node_shader_add_uniform(frag, 'sampler2D gbuffer0');
+		node_shader_add_uniform(frag, 'sampler2D gbuffer1');
+		node_shader_add_uniform(frag, 'sampler2D gbuffer2');
+		node_shader_write(frag, 'vec2 fragcoord = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'fragcoord.y = 1.0 - fragcoord.y;');
+		///end
+		node_shader_write(frag, 'vec4 gbuffer0_sample = textureLod(gbuffer0, fragcoord, 0.0);');
+		node_shader_write(frag, 'vec4 gbuffer1_sample = textureLod(gbuffer1, fragcoord, 0.0);');
+		node_shader_write(frag, 'vec4 gbuffer2_sample = textureLod(gbuffer2, fragcoord, 0.0);');
+		node_shader_write(frag, 'vec3 basecol = gbuffer0_sample.rgb;');
+		node_shader_write(frag, 'float roughness = gbuffer2_sample.g;');
+		node_shader_write(frag, 'float metallic = gbuffer2_sample.b;');
+		node_shader_write(frag, 'float occlusion = gbuffer2_sample.r;');
+		node_shader_write(frag, 'float opacity = 1.0;//gbuffer0_sample.a;');
+		node_shader_write(frag, 'float matid = gbuffer1_sample.a;');
+		node_shader_write(frag, 'vec3 ntex = gbuffer1_sample.rgb;');
+		node_shader_write(frag, 'float height = gbuffer2_sample.a;');
+	}
+	else {
+		node_shader_write(frag, 'vec3 basecol = vec3(0.0, 0.0, 0.0);');
+		node_shader_write(frag, 'float roughness = 0.3;');
+		node_shader_write(frag, 'float metallic = 0.0;');
+		node_shader_write(frag, 'float occlusion = 1.0;');
+		node_shader_write(frag, 'float opacity = 1.0;');
+		node_shader_write(frag, 'float matid = 0.0;');
+		node_shader_write(frag, 'vec3 ntex = vec3(0.5, 0.5, 1.0);');
+		node_shader_write(frag, 'float height = 0.0;');
+	}
+	node_shader_write(frag, 'vec4 texpaint_sample = vec4(0.0, 0.0, 0.0, 1.0);');
+	node_shader_write(frag, 'vec4 texpaint_nor_sample;');
+	node_shader_write(frag, 'vec4 texpaint_pack_sample;');
+	node_shader_write(frag, 'float texpaint_opac;');
+
+	if (make_material_height_used) {
+		node_shader_write(frag, 'float height0 = 0.0;');
+		node_shader_write(frag, 'float height1 = 0.0;');
+		node_shader_write(frag, 'float height2 = 0.0;');
+		node_shader_write(frag, 'float height3 = 0.0;');
+	}
+
+	if (context_raw.draw_wireframe) {
+		textureCount++;
+		node_shader_add_uniform(frag, 'sampler2D texuvmap', '_texuvmap');
+	}
+
+	if (context_raw.viewport_mode == viewport_mode_t.LIT && context_raw.render_mode == render_mode_t.FORWARD) {
+		textureCount += 4;
+		node_shader_add_uniform(frag, 'sampler2D senvmapBrdf', "$brdf.k");
+		node_shader_add_uniform(frag, 'sampler2D senvmapRadiance', '_envmap_radiance');
+		node_shader_add_uniform(frag, 'sampler2D sltcMat', '_ltcMat');
+		node_shader_add_uniform(frag, 'sampler2D sltcMag', '_ltcMag');
+	}
+
+	// Get layers for this pass
+	make_mesh_layer_pass_count = 1;
+	let layers: SlotLayerRaw[] = [];
+	let startCount = textureCount;
+	for (let l of project_layers) {
+		if (!slot_layer_is_layer(l) || !slot_layer_is_visible(l)) continue;
+
+		let count = 3;
+		let masks = slot_layer_get_masks(l);
+		if (masks != null) count += masks.length;
+		textureCount += count;
+		if (textureCount >= make_mesh_get_max_textures()) {
+			textureCount = startCount + count + 3; // gbuffer0_copy, gbuffer1_copy, gbuffer2_copy
+			make_mesh_layer_pass_count++;
+		}
+		if (layer_pass == make_mesh_layer_pass_count - 1) {
+			layers.push(l);
+		}
+	}
+
+	let lastPass = layer_pass == make_mesh_layer_pass_count - 1;
+
+	for (let l of layers) {
+		if (slot_layer_get_object_mask(l) > 0) {
+			node_shader_add_uniform(frag, 'int uid', '_uid');
+			if (slot_layer_get_object_mask(l) > project_paint_objects.length) { // Atlas
+				let visibles = project_get_atlas_objects(slot_layer_get_object_mask(l));
+				node_shader_write(frag, 'if (');
+				for (let i = 0; i < visibles.length; ++i) {
+					if (i > 0) node_shader_write(frag, ' || ');
+					node_shader_write(frag, `${visibles[i].base.uid} == uid`);
+				}
+				node_shader_write(frag, ') {');
+			}
+			else { // Object mask
+				let uid = project_paint_objects[slot_layer_get_object_mask(l) - 1].base.uid;
+				node_shader_write(frag, `if (${uid} == uid) {`);
+			}
+		}
+
+		node_shader_add_shared_sampler(frag, 'sampler2D texpaint' + l.id);
+		node_shader_write(frag, 'texpaint_sample = vec4(0.8, 0.8, 0.8, 1.0);');
+		node_shader_write(frag, 'texpaint_opac = texpaint_sample.a;');
+
+		let masks = slot_layer_get_masks(l);
+		if (masks != null) {
+			let hasVisible = false;
+			for (let m of masks) {
+				if (slot_layer_is_visible(m)) {
+					hasVisible = true;
+					break;
+				}
+			}
+			if (hasVisible) {
+				let texpaint_mask = 'texpaint_mask' + l.id;
+				node_shader_write(frag, `float ${texpaint_mask} = 0.0;`);
+				for (let m of masks) {
+					if (!slot_layer_is_visible(m)) continue;
+					node_shader_add_shared_sampler(frag, 'sampler2D texpaint' + m.id);
+					node_shader_write(frag, '{'); // Group mask is sampled across multiple layers
+					node_shader_write(frag, 'float texpaint_mask_sample' + m.id + ' = textureLodShared(texpaint' + m.id + ', texCoord, 0.0).r;');
+					node_shader_write(frag, '}');
+				}
+				node_shader_write(frag, `texpaint_opac *= clamp(${texpaint_mask}, 0.0, 1.0);`);
+			}
+		}
+
+		if (slot_layer_get_opacity(l) < 1) {
+			node_shader_write(frag, `texpaint_opac *= ${slot_layer_get_opacity(l)};`);
+		}
+
+		if (l == project_layers[0]) {
+			node_shader_write(frag, 'basecol = vec3(0.8, 0.8, 0.8);// texpaint_sample.rgb * texpaint_opac;');
+		}
+
+		if (slot_layer_get_object_mask(l) > 0) {
+			node_shader_write(frag, '}');
+		}
+
+		if (lastPass && context_raw.draw_texels) {
+			node_shader_add_uniform(frag, 'vec2 texpaintSize', '_texpaintSize');
+			node_shader_write(frag, 'vec2 texel0 = texCoord * texpaintSize * 0.01;');
+			node_shader_write(frag, 'vec2 texel1 = texCoord * texpaintSize * 0.1;');
+			node_shader_write(frag, 'vec2 texel2 = texCoord * texpaintSize;');
+			node_shader_write(frag, 'basecol *= max(float(mod(int(texel0.x), 2.0) == mod(int(texel0.y), 2.0)), 0.9);');
+			node_shader_write(frag, 'basecol *= max(float(mod(int(texel1.x), 2.0) == mod(int(texel1.y), 2.0)), 0.9);');
+			node_shader_write(frag, 'basecol *= max(float(mod(int(texel2.x), 2.0) == mod(int(texel2.y), 2.0)), 0.9);');
+		}
+
+		if (lastPass && context_raw.draw_wireframe) {
+			node_shader_write(frag, 'basecol *= 1.0 - textureLod(texuvmap, texCoord, 0.0).r;');
+		}
+
+		if (make_material_height_used) {
+			node_shader_write(frag, 'if (height > 0.0) {');
+			node_shader_write(frag, 'float height_dx = height0 - height1;');
+			node_shader_write(frag, 'float height_dy = height2 - height3;');
+			// Whiteout blend
+			node_shader_write(frag, 'vec3 n1 = ntex * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
+			node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
+			node_shader_write(frag, 'ntex = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
+			node_shader_write(frag, '}');
+		}
+
+		if (!lastPass) {
+			node_shader_write(frag, 'fragColor[0] = vec4(basecol, opacity);');
+			node_shader_write(frag, 'fragColor[1] = vec4(ntex, matid);');
+			node_shader_write(frag, 'fragColor[2] = vec4(occlusion, roughness, metallic, height);');
+			parser_material_finalize(con_mesh);
+			con_mesh.data.shader_from_source = true;
+			con_mesh.data.vertex_shader = node_shader_get(vert);
+			con_mesh.data.fragment_shader = node_shader_get(frag);
+			return con_mesh;
+		}
+
+		frag.vvec = true;
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
+		///else
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
+		///end
+		node_shader_write(frag, 'n = ntex * 2.0 - 1.0;');
+		node_shader_write(frag, 'n.y = -n.y;');
+		node_shader_write(frag, 'n = normalize(mul(n, TBN));');
+
+		if (context_raw.viewport_mode == viewport_mode_t.LIT) {
+
+			node_shader_write(frag, 'basecol = pow(basecol, vec3(2.2, 2.2, 2.2));');
+
+			if (context_raw.viewport_shader != null) {
+				let color = context_raw.viewport_shader(frag);
+				node_shader_write(frag, `fragColor[1] = vec4(${color}, 1.0);`);
+			}
+			else if (context_raw.render_mode == render_mode_t.FORWARD) {
+				frag.wposition = true;
+				node_shader_write(frag, 'vec3 albedo = mix(basecol, vec3(0.0, 0.0, 0.0), metallic);');
+				node_shader_write(frag, 'vec3 f0 = mix(vec3(0.04, 0.04, 0.04), basecol, metallic);');
+				frag.vvec = true;
+				node_shader_write(frag, 'float dotNV = max(0.0, dot(n, vVec));');
+				node_shader_write(frag, 'vec2 envBRDF = texelFetch(senvmapBrdf, ivec2(vec2(roughness, 1.0 - dotNV) * 256.0), 0).xy;');
+				node_shader_add_uniform(frag, 'int envmapNumMipmaps', '_envmap_num_mipmaps');
+				node_shader_add_uniform(frag, 'vec4 envmapData', '_envmapData'); // angle, sin(angle), cos(angle), strength
+				node_shader_write(frag, 'vec3 wreflect = reflect(-vVec, n);');
+				node_shader_write(frag, 'float envlod = roughness * float(envmapNumMipmaps);');
+				node_shader_add_function(frag, str_envmap_equirect);
+				node_shader_write(frag, 'vec3 prefilteredColor = textureLod(senvmapRadiance, envMapEquirect(wreflect, envmapData.x), envlod).rgb;');
+				node_shader_add_uniform(frag, 'vec3 lightArea0', '_light_area0');
+				node_shader_add_uniform(frag, 'vec3 lightArea1', '_light_area1');
+				node_shader_add_uniform(frag, 'vec3 lightArea2', '_light_area2');
+				node_shader_add_uniform(frag, 'vec3 lightArea3', '_light_area3');
+				node_shader_add_function(frag, str_ltc_evaluate);
+				node_shader_add_uniform(frag, 'vec3 lightPos', '_point_pos');
+				node_shader_add_uniform(frag, 'vec3 lightColor', '_point_color');
+				node_shader_write(frag, 'float ldist = distance(wposition, lightPos);');
+				node_shader_write(frag, 'const float LUT_SIZE = 64.0;');
+				node_shader_write(frag, 'const float LUT_SCALE = (LUT_SIZE - 1.0) / LUT_SIZE;');
+				node_shader_write(frag, 'const float LUT_BIAS = 0.5 / LUT_SIZE;');
+				node_shader_write(frag, 'float theta = acos(dotNV);');
+				node_shader_write(frag, 'vec2 tuv = vec2(roughness, theta / (0.5 * 3.14159265));');
+				node_shader_write(frag, 'tuv = tuv * LUT_SCALE + LUT_BIAS;');
+				node_shader_write(frag, 'vec4 t = textureLod(sltcMat, tuv, 0.0);');
+				node_shader_write(frag, 'mat3 minv = mat3(vec3(1.0, 0.0, t.y), vec3(0.0, t.z, 0.0), vec3(t.w, 0.0, t.x));');
+				node_shader_write(frag, 'float ltcspec = ltcEvaluate(n, vVec, dotNV, wposition, minv, lightArea0, lightArea1, lightArea2, lightArea3);');
+				node_shader_write(frag, 'ltcspec *= textureLod(sltcMag, tuv, 0.0).a;');
+				node_shader_write(frag, 'mat3 mident = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);');
+				node_shader_write(frag, 'float ltcdiff = ltcEvaluate(n, vVec, dotNV, wposition, mident, lightArea0, lightArea1, lightArea2, lightArea3);');
+				node_shader_write(frag, 'vec3 direct = albedo * ltcdiff + ltcspec * 0.05;');
+				node_shader_write(frag, 'direct *= lightColor * (1.0 / (ldist * ldist));');
+
+				node_shader_add_uniform(frag, 'vec4 shirr[7]', '_envmap_irradiance');
+				node_shader_add_function(frag, str_sh_irradiance());
+				node_shader_write(frag, 'vec3 indirect = albedo * (shIrradiance(vec3(n.x * envmapData.z - n.y * envmapData.y, n.x * envmapData.y + n.y * envmapData.z, n.z), shirr) / 3.14159265);');
+				node_shader_write(frag, 'indirect += prefilteredColor * (f0 * envBRDF.x + envBRDF.y) * 1.5;');
+				node_shader_write(frag, 'indirect *= envmapData.w * occlusion;');
+				node_shader_write(frag, 'fragColor[1] = vec4(direct + indirect, 1.0);');
+			}
+			else { // Deferred, Pathtraced
+				if (make_material_emis_used) node_shader_write(frag, 'if (int(matid * 255.0) % 3 == 1) basecol *= 10.0;'); // Boost for bloom
+				node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
+			}
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.OBJECT_NORMAL) {
+			frag.nattr = true;
+			node_shader_write(frag, 'fragColor[1] = vec4(nAttr, 1.0);');
+		}
+		else if (context_raw.viewport_mode == viewport_mode_t.OBJECT_ID) {
+			node_shader_add_uniform(frag, 'float objectId', '_objectId');
+			node_shader_write(frag, 'float obid = objectId + 1.0 / 255.0;');
+			node_shader_write(frag, 'float id_r = fract(sin(dot(vec2(obid, obid * 20.0), vec2(12.9898, 78.233))) * 43758.5453);');
+			node_shader_write(frag, 'float id_g = fract(sin(dot(vec2(obid * 20.0, obid), vec2(12.9898, 78.233))) * 43758.5453);');
+			node_shader_write(frag, 'float id_b = fract(sin(dot(vec2(obid, obid * 40.0), vec2(12.9898, 78.233))) * 43758.5453);');
+			node_shader_write(frag, 'fragColor[1] = vec4(id_r, id_g, id_b, 1.0);');
+		}
+		else {
+			node_shader_write(frag, 'fragColor[1] = vec4(1.0, 0.0, 1.0, 1.0);'); // Pink
+		}
+
+		if (context_raw.viewport_mode != viewport_mode_t.LIT && context_raw.viewport_mode != viewport_mode_t.PATH_TRACE) {
+			node_shader_write(frag, 'fragColor[1].rgb = pow(fragColor[1].rgb, vec3(2.2, 2.2, 2.2));');
+		}
+
+		node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
+		node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
+		node_shader_write(frag, 'fragColor[0] = vec4(n.xy, roughness, packFloatInt16(metallic, uint(int(matid * 255.0) % 3)));');
+	}
+
+	node_shader_write(frag, 'vec2 posa = (wvpposition.xy / wvpposition.w) * 0.5 + 0.5;');
+	node_shader_write(frag, 'vec2 posb = (prevwvpposition.xy / prevwvpposition.w) * 0.5 + 0.5;');
+	node_shader_write(frag, 'fragColor[2] = vec4(posa - posb, texCoord.xy);');
+
+	parser_material_finalize(con_mesh);
+	con_mesh.data.shader_from_source = true;
+	con_mesh.data.vertex_shader = node_shader_get(vert);
+	con_mesh.data.fragment_shader = node_shader_get(frag);
+	return con_mesh;
+}
+
+function make_mesh_get_max_textures(): i32 {
+	///if krom_direct3d11
+	return 128 - 66;
+	///else
+	return 16 - 3; // G4onG5/G4.c.h MAX_TEXTURES
+	///end
+}

+ 156 - 0
armorsculpt/Sources/make_mesh_preview.ts

@@ -0,0 +1,156 @@
+
+let make_mesh_preview_opacity_discard_decal: f32 = 0.05;
+
+function make_mesh_preview_run(data: material_t, matcon: material_context_t): NodeShaderContextRaw {
+	let context_id = "mesh";
+	let con_mesh = node_shader_context_create(data, {
+		name: context_id,
+		depth_write: true,
+		compare_mode: "less",
+		cull_mode: "clockwise",
+		vertex_elements: [{name: "pos", data: "short4norm"}, {name: "nor", data: "short2norm"}, {name: "tex", data: "short2norm"}],
+		color_attachments: ["RGBA64", "RGBA64", "RGBA64"],
+		depth_attachment: "DEPTH32"
+	});
+
+	let vert = node_shader_context_make_vert(con_mesh);
+	let frag = node_shader_context_make_frag(con_mesh);
+	frag.ins = vert.outs;
+	let pos = "pos";
+
+	///if arm_skin
+	let skin = mesh_data_get_vertex_array(context_raw.paint_object.data, "bone") != null;
+	if (skin) {
+		pos = "spos";
+		node_shader_context_add_elem(con_mesh, "bone", 'short4norm');
+		node_shader_context_add_elem(con_mesh, "weight", 'short4norm');
+		node_shader_add_function(vert, str_get_skinning_dual_quat);
+		node_shader_add_uniform(vert, 'vec4 skinBones[128 * 2]', '_skin_bones');
+		node_shader_add_uniform(vert, 'float posUnpack', '_pos_unpack');
+		node_shader_write_attrib(vert, 'vec4 skinA;');
+		node_shader_write_attrib(vert, 'vec4 skinB;');
+		node_shader_write_attrib(vert, 'getSkinningDualQuat(ivec4(bone * 32767), weight, skinA, skinB);');
+		node_shader_write_attrib(vert, 'vec3 spos = pos.xyz;');
+		node_shader_write_attrib(vert, 'spos.xyz *= posUnpack;');
+		node_shader_write_attrib(vert, 'spos.xyz += 2.0 * cross(skinA.xyz, cross(skinA.xyz, spos.xyz) + skinA.w * spos.xyz);');
+		node_shader_write_attrib(vert, 'spos.xyz += 2.0 * (skinA.w * skinB.xyz - skinB.w * skinA.xyz + cross(skinA.xyz, skinB.xyz));');
+		node_shader_write_attrib(vert, 'spos.xyz /= posUnpack;');
+	}
+	///end
+
+	node_shader_add_uniform(vert, 'mat4 WVP', '_world_view_proj_matrix');
+	node_shader_write_attrib(vert, `gl_Position = mul(vec4(${pos}.xyz, 1.0), WVP);`);
+
+	let brush_scale = (context_raw.brush_scale * context_raw.brush_nodes_scale) + "";
+	node_shader_add_out(vert, 'vec2 texCoord');
+	node_shader_write_attrib(vert, `texCoord = tex * float(${brush_scale});`);
+
+	let decal = context_raw.decal_preview;
+	parser_material_sample_keep_aspect = decal;
+	parser_material_sample_uv_scale = brush_scale;
+	parser_material_parse_height = make_material_height_used;
+	parser_material_parse_height_as_channel = true;
+	// let sout = parser_material_parse(ui_nodes_getCanvasMaterial(), con_mesh, vert, frag, matcon);
+	parser_material_parse_height = false;
+	parser_material_parse_height_as_channel = false;
+	parser_material_sample_keep_aspect = false;
+	let base = "vec3(1.0, 1.0, 1.0)";//sout.out_basecol;
+	let rough = "0.0";//sout.out_roughness;
+	let met = "0.0";//sout.out_metallic;
+	let occ = "0.0";//sout.out_occlusion;
+	let opac = "0.0";//sout.out_opacity;
+	let height = "0.0";//sout.out_height;
+	let nortan = "vec3(1.0, 1.0, 1.0)";//parser_material_out_normaltan;
+	node_shader_write(frag, `vec3 basecol = pow(${base}, vec3(2.2, 2.2, 2.2));`);
+	node_shader_write(frag, `float roughness = ${rough};`);
+	node_shader_write(frag, `float metallic = ${met};`);
+	node_shader_write(frag, `float occlusion = ${occ};`);
+	node_shader_write(frag, `float opacity = ${opac};`);
+	node_shader_write(frag, `vec3 nortan = ${nortan};`);
+	node_shader_write(frag, `float height = ${height};`);
+
+	// parser_material_parse_height_as_channel = false;
+	// node_shader_write(vert, `float vheight = ${height};`);
+	// node_shader_add_out(vert, 'float height');
+	// node_shader_write(vert, 'height = vheight;');
+	// let displaceStrength = 0.1;
+	// if (heightUsed && displaceStrength > 0.0) {
+	// 	node_shader_write(vert, `vec3 pos2 = ${pos}.xyz + vec3(nor.xy, pos.w) * vec3(${height}, ${height}, ${height}) * vec3(${displaceStrength}, ${displaceStrength}, ${displaceStrength});`);
+	// 	node_shader_write(vert, 'gl_Position = mul(vec4(pos2.xyz, 1.0), WVP);');
+	// }
+
+	if (decal) {
+		if (context_raw.tool == workspace_tool_t.TEXT) {
+			node_shader_add_uniform(frag, 'sampler2D textexttool', '_textexttool');
+			node_shader_write(frag, `opacity *= textureLod(textexttool, texCoord / float(${brush_scale}), 0.0).r;`);
+		}
+	}
+	if (decal) {
+		let opac = make_mesh_preview_opacity_discard_decal;
+		node_shader_write(frag, `if (opacity < ${opac}) discard;`);
+	}
+
+	node_shader_add_out(frag, 'vec4 fragColor[3]');
+	frag.n = true;
+
+	node_shader_add_function(frag, str_pack_float_int16);
+	node_shader_add_function(frag, str_cotangent_frame);
+	node_shader_add_function(frag, str_octahedron_wrap);
+
+	if (make_material_height_used) {
+		node_shader_write(frag, 'if (height > 0.0) {');
+		node_shader_write(frag, 'float height_dx = dFdx(height * 2.0);');
+		node_shader_write(frag, 'float height_dy = dFdy(height * 2.0);');
+		// node_shader_write(frag, 'float height_dx = height0 - height1;');
+		// node_shader_write(frag, 'float height_dy = height2 - height3;');
+		// Whiteout blend
+		node_shader_write(frag, 'vec3 n1 = nortan * vec3(2.0, 2.0, 2.0) - vec3(1.0, 1.0, 1.0);');
+		node_shader_write(frag, 'vec3 n2 = normalize(vec3(height_dx * 16.0, height_dy * 16.0, 1.0));');
+		node_shader_write(frag, 'nortan = normalize(vec3(n1.xy + n2.xy, n1.z * n2.z)) * vec3(0.5, 0.5, 0.5) + vec3(0.5, 0.5, 0.5);');
+		node_shader_write(frag, '}');
+	}
+
+	// Apply normal channel
+	if (decal) {
+		// TODO
+	}
+	else {
+		frag.vvec = true;
+		///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, vVec, texCoord);');
+		///else
+		node_shader_write(frag, 'mat3 TBN = cotangentFrame(n, -vVec, texCoord);');
+		///end
+		node_shader_write(frag, 'n = nortan * 2.0 - 1.0;');
+		node_shader_write(frag, 'n.y = -n.y;');
+		node_shader_write(frag, 'n = normalize(mul(n, TBN));');
+	}
+
+	node_shader_write(frag, 'n /= (abs(n.x) + abs(n.y) + abs(n.z));');
+	node_shader_write(frag, 'n.xy = n.z >= 0.0 ? n.xy : octahedronWrap(n.xy);');
+	// uint matid = 0;
+
+	if (decal) {
+		node_shader_write(frag, 'fragColor[0] = vec4(n.x, n.y, roughness, packFloatInt16(metallic, uint(0)));'); // metallic/matid
+		node_shader_write(frag, 'fragColor[1] = vec4(basecol, occlusion);');
+	}
+	else {
+		node_shader_write(frag, 'fragColor[0] = vec4(n.x, n.y, mix(1.0, roughness, opacity), packFloatInt16(mix(1.0, metallic, opacity), uint(0)));'); // metallic/matid
+		node_shader_write(frag, 'fragColor[1] = vec4(mix(vec3(0.0, 0.0, 0.0), basecol, opacity), occlusion);');
+	}
+	node_shader_write(frag, 'fragColor[2] = vec4(0.0, 0.0, 0.0, 0.0);'); // veloc
+
+	parser_material_finalize(con_mesh);
+
+	///if arm_skin
+	if (skin) {
+		node_shader_write(vert, 'wnormal = normalize(mul(vec3(nor.xy, pos.w) + 2.0 * cross(skinA.xyz, cross(skinA.xyz, vec3(nor.xy, pos.w)) + skinA.w * vec3(nor.xy, pos.w)), N));');
+	}
+	///end
+
+	con_mesh.data.shader_from_source = true;
+	con_mesh.data.vertex_shader = node_shader_get(vert);
+	con_mesh.data.fragment_shader = node_shader_get(frag);
+
+	return con_mesh;
+}

+ 88 - 0
armorsculpt/Sources/make_sculpt.ts

@@ -0,0 +1,88 @@
+
+function make_sculpt_run(data: material_t, matcon: material_context_t): NodeShaderContextRaw {
+	let context_id = "paint";
+	let con_paint = node_shader_context_create(data, {
+		name: context_id,
+		depth_write: false,
+		compare_mode: "always", // TODO: align texcoords winding order
+		// cull_mode: "counter_clockwise",
+		cull_mode: "none",
+		vertex_elements: [{name: "pos", data: "float2"}],
+		color_attachments: ["RGBA32", "RGBA32", "RGBA32", "R8"]
+	});
+
+	con_paint.data.color_writes_red = [true, true, true, true];
+	con_paint.data.color_writes_green = [true, true, true, true];
+	con_paint.data.color_writes_blue = [true, true, true, true];
+	con_paint.data.color_writes_alpha = [true, true, true, true];
+	con_paint.allow_vcols = mesh_data_get_vertex_array(context_raw.paint_object.data, "col") != null;
+
+	let vert = node_shader_context_make_vert(con_paint);
+	let frag = node_shader_context_make_frag(con_paint);
+	frag.ins = vert.outs;
+
+	let faceFill = context_raw.tool == workspace_tool_t.FILL && context_raw.fill_type_handle.position == fill_type_t.FACE;
+	let decal = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
+
+	node_shader_add_out(vert, 'vec2 texCoord');
+	node_shader_write(vert, 'const vec2 madd = vec2(0.5, 0.5);');
+	node_shader_write(vert, 'texCoord = pos.xy * madd + madd;');
+	///if (krom_direct3d11 || krom_direct3d12 || krom_metal || krom_vulkan)
+	node_shader_write(vert, 'texCoord.y = 1.0 - texCoord.y;');
+	///end
+	node_shader_write(vert, 'gl_Position = vec4(pos.xy, 0.0, 1.0);');
+
+	node_shader_add_uniform(frag, 'vec4 inp', '_inputBrush');
+	node_shader_add_uniform(frag, 'vec4 inplast', '_inputBrushLast');
+
+	node_shader_add_uniform(frag, 'sampler2D gbufferD');
+
+	node_shader_add_out(frag, 'vec4 fragColor[2]');
+
+	node_shader_add_uniform(frag, 'float brushRadius', '_brushRadius');
+	node_shader_add_uniform(frag, 'float brushOpacity', '_brushOpacity');
+	node_shader_add_uniform(frag, 'float brushHardness', '_brushHardness');
+
+	if (context_raw.tool == workspace_tool_t.BRUSH  ||
+		context_raw.tool == workspace_tool_t.ERASER ||
+		context_raw.tool == workspace_tool_t.CLONE  ||
+		context_raw.tool == workspace_tool_t.BLUR   ||
+		context_raw.tool == workspace_tool_t.SMUDGE   ||
+		context_raw.tool == workspace_tool_t.PARTICLE ||
+		decal) {
+
+		let depthReject = !context_raw.xray;
+
+		make_brush_run(vert, frag);
+	}
+
+	node_shader_write(frag, 'vec3 basecol = vec3(1.0, 1.0, 1.0);');
+	node_shader_write(frag, 'float opacity = 1.0;');
+	node_shader_write(frag, 'if (opacity == 0.0) discard;');
+
+	node_shader_write(frag, 'float str = clamp((brushRadius - dist) * brushHardness * 400.0, 0.0, 1.0) * opacity;');
+
+	node_shader_add_uniform(frag, 'sampler2D texpaint_undo', '_texpaint_undo');
+	node_shader_write(frag, 'vec4 sample_undo = textureLod(texpaint_undo, texCoord, 0.0);');
+
+	node_shader_write(frag, 'if (sample_undo.r == 0 && sample_undo.g == 0 && sample_undo.b == 0) discard;');
+
+	node_shader_add_function(frag, str_octahedron_wrap);
+	node_shader_add_uniform(frag, 'sampler2D gbuffer0_undo');
+	node_shader_write(frag, 'vec2 g0_undo = textureLod(gbuffer0_undo, inp.xy, 0.0).rg;');
+	node_shader_write(frag, 'vec3 wn;');
+	node_shader_write(frag, 'wn.z = 1.0 - abs(g0_undo.x) - abs(g0_undo.y);');
+	node_shader_write(frag, 'wn.xy = wn.z >= 0.0 ? g0_undo.xy : octahedronWrap(g0_undo.xy);');
+	node_shader_write(frag, 'vec3 n = normalize(wn);');
+
+	node_shader_write(frag, 'fragColor[0] = vec4(sample_undo.rgb + n * 0.1 * str, 1.0);');
+
+	node_shader_write(frag, 'fragColor[1] = vec4(str, 0.0, 0.0, 1.0);');
+
+	parser_material_finalize(con_paint);
+	con_paint.data.shader_from_source = true;
+	con_paint.data.vertex_shader = node_shader_get(vert);
+	con_paint.data.fragment_shader = node_shader_get(frag);
+
+	return con_paint;
+}

+ 42 - 42
armorsculpt/Sources/nodes/BrushOutputNode.ts

@@ -5,13 +5,13 @@ class BrushOutputNode extends LogicNode {
 
 	constructor() {
 		super();
-		context_raw.runBrush = this.run;
-		context_raw.parseBrushInputs = this.parseInputs;
+		context_raw.run_brush = this.run;
+		context_raw.parse_brush_inputs = this.parse_inputs;
 	}
 
-	parseInputs = () => {
-		let lastMask = context_raw.brushMaskImage;
-		let lastStencil = context_raw.brushStencilImage;
+	parse_inputs = () => {
+		let last_mask = context_raw.brush_mask_image;
+		let last_stencil = context_raw.brush_stencil_image;
 
 		let input0: any;
 		let input1: any;
@@ -29,45 +29,45 @@ class BrushOutputNode extends LogicNode {
 			return;
 		}
 
-		context_raw.paintVec = input0;
-		context_raw.brushNodesRadius = input1;
+		context_raw.paint_vec = input0;
+		context_raw.brush_nodes_radius = input1;
 
 		let opac: any = input2; // Float or texture name
 		if (opac == null) opac = 1.0;
 		if (typeof opac == "string") {
-			context_raw.brushMaskImageIsAlpha = opac.endsWith(".a");
+			context_raw.brush_mask_image_is_alpha = opac.endsWith(".a");
 			opac = opac.substr(0, opac.lastIndexOf("."));
-			context_raw.brushNodesOpacity = 1.0;
-			let index = project_assetNames.indexOf(opac);
+			context_raw.brush_nodes_opacity = 1.0;
+			let index = project_asset_names.indexOf(opac);
 			let asset = project_assets[index];
-			context_raw.brushMaskImage = project_getImage(asset);
+			context_raw.brush_mask_image = project_get_image(asset);
 		}
 		else {
-			context_raw.brushNodesOpacity = opac;
-			context_raw.brushMaskImage = null;
+			context_raw.brush_nodes_opacity = opac;
+			context_raw.brush_mask_image = null;
 		}
 
-		context_raw.brushNodesHardness = input3;
+		context_raw.brush_nodes_hardness = input3;
 
 		let stencil: any = input4; // Float or texture name
 		if (stencil == null) stencil = 1.0;
 		if (typeof stencil == "string") {
-			context_raw.brushStencilImageIsAlpha = stencil.endsWith(".a");
+			context_raw.brush_stencil_image_is_alpha = stencil.endsWith(".a");
 			stencil = stencil.substr(0, stencil.lastIndexOf("."));
-			let index = project_assetNames.indexOf(stencil);
+			let index = project_asset_names.indexOf(stencil);
 			let asset = project_assets[index];
-			context_raw.brushStencilImage = project_getImage(asset);
+			context_raw.brush_stencil_image = project_get_image(asset);
 		}
 		else {
-			context_raw.brushStencilImage = null;
+			context_raw.brush_stencil_image = null;
 		}
 
-		if (lastMask != context_raw.brushMaskImage ||
-			lastStencil != context_raw.brushStencilImage) {
-			MakeMaterial.parsePaintMaterial();
+		if (last_mask != context_raw.brush_mask_image ||
+			last_stencil != context_raw.brush_stencil_image) {
+			make_material_parse_paint_material();
 		}
 
-		context_raw.brushDirectional = this.Directional;
+		context_raw.brush_directional = this.Directional;
 	}
 
 	run = (from: i32) => {
@@ -75,55 +75,55 @@ class BrushOutputNode extends LogicNode {
 		let right = 1.0;
 		if (context_raw.paint2d) {
 			left = 1.0;
-			right = (context_raw.splitView ? 2.0 : 1.0) + ui_view2d_ww / base_w();
+			right = (context_raw.split_view ? 2.0 : 1.0) + ui_view2d_ww / base_w();
 		}
 
 		// First time init
-		if (context_raw.lastPaintX < 0 || context_raw.lastPaintY < 0) {
-			context_raw.lastPaintVecX = context_raw.paintVec.x;
-			context_raw.lastPaintVecY = context_raw.paintVec.y;
+		if (context_raw.last_paint_x < 0 || context_raw.last_paint_y < 0) {
+			context_raw.last_paint_vec_x = context_raw.paint_vec.x;
+			context_raw.last_paint_vec_y = context_raw.paint_vec.y;
 		}
 
 		// Do not paint over fill layer
 		let fillLayer = context_raw.layer.fill_layer != null;
 
 		// Do not paint over groups
-		let groupLayer = SlotLayer.isGroup(context_raw.layer);
+		let groupLayer = slot_layer_is_group(context_raw.layer);
 
 		// Paint bounds
-		if (context_raw.paintVec.x > left &&
-			context_raw.paintVec.x < right &&
-			context_raw.paintVec.y > 0 &&
-			context_raw.paintVec.y < 1 &&
+		if (context_raw.paint_vec.x > left &&
+			context_raw.paint_vec.x < right &&
+			context_raw.paint_vec.y > 0 &&
+			context_raw.paint_vec.y < 1 &&
 			!fillLayer &&
 			!groupLayer &&
-			(SlotLayer.isVisible(context_raw.layer) || context_raw.paint2d) &&
+			(slot_layer_is_visible(context_raw.layer) || context_raw.paint2d) &&
 			!ui_base_ui.is_hovered &&
-			!base_isDragging &&
-			!base_isResizing &&
-			!base_isScrolling() &&
-			!base_isComboSelected()) {
+			!base_is_dragging &&
+			!base_is_resizing &&
+			!base_is_scrolling() &&
+			!base_is_combo_selected()) {
 
 			let down = mouse_down() || pen_down();
 
 			// Prevent painting the same spot
-			let sameSpot = context_raw.paintVec.x == context_raw.lastPaintX && context_raw.paintVec.y == context_raw.lastPaintY;
-			let lazy = context_raw.tool == WorkspaceTool.ToolBrush && context_raw.brushLazyRadius > 0;
+			let sameSpot = context_raw.paint_vec.x == context_raw.last_paint_x && context_raw.paint_vec.y == context_raw.last_paint_y;
+			let lazy = context_raw.tool == workspace_tool_t.BRUSH && context_raw.brush_lazy_radius > 0;
 			if (down && (sameSpot || lazy)) {
 				context_raw.painted++;
 			}
 			else {
 				context_raw.painted = 0;
 			}
-			context_raw.lastPaintX = context_raw.paintVec.x;
-			context_raw.lastPaintY = context_raw.paintVec.y;
+			context_raw.last_paint_x = context_raw.paint_vec.x;
+			context_raw.last_paint_y = context_raw.paint_vec.y;
 
-			if (context_raw.tool == WorkspaceTool.ToolParticle) {
+			if (context_raw.tool == workspace_tool_t.PARTICLE) {
 				context_raw.painted = 0; // Always paint particles
 			}
 
 			if (context_raw.painted == 0) {
-				this.parseInputs();
+				this.parse_inputs();
 			}
 
 			if (context_raw.painted == 0) {

+ 392 - 0
armorsculpt/Sources/tab_layers.ts

@@ -0,0 +1,392 @@
+
+let tab_layers_layer_name_edit: i32 = -1;
+let tab_layers_layer_name_handle: zui_handle_t = zui_handle_create();
+let tab_layers_show_context_menu: bool = false;
+
+function tab_layers_draw(htab: zui_handle_t) {
+	let mini = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_base_sidebar_mini_w;
+	mini ? tab_layers_draw_mini(htab) : tab_layers_draw_full(htab);
+}
+
+function tab_layers_draw_mini(htab: zui_handle_t) {
+	let ui = ui_base_ui;
+	zui_set_hovered_tab_name(tr("Layers"));
+
+	let _ELEMENT_H = ui.t.ELEMENT_H;
+	ui.t.ELEMENT_H = math_floor(ui_base_sidebar_mini_w / 2 / zui_SCALE(ui));
+
+	zui_begin_sticky();
+	zui_separator(5);
+
+	tab_layers_combo_filter();
+	tab_layers_button_new("+");
+
+	zui_end_sticky();
+	ui._y += 2;
+
+	tab_layers_highlight_odd_lines();
+	tab_layers_draw_slots(true);
+
+	ui.t.ELEMENT_H = _ELEMENT_H;
+}
+
+function tab_layers_draw_full(htab: zui_handle_t) {
+	let ui = ui_base_ui;
+	if (zui_tab(htab, tr("Layers"))) {
+		zui_begin_sticky();
+		zui_row([1 / 4, 3 / 4]);
+
+		tab_layers_button_new(tr("New"));
+		tab_layers_combo_filter();
+
+		zui_end_sticky();
+		ui._y += 2;
+
+		tab_layers_highlight_odd_lines();
+		tab_layers_draw_slots(false);
+	}
+}
+
+function tab_layers_draw_slots(mini: bool) {
+	for (let i = 0; i < project_layers.length; ++i) {
+		if (i >= project_layers.length) break; // Layer was deleted
+		let j = project_layers.length - 1 - i;
+		let l = project_layers[j];
+		tab_layers_draw_layer_slot(l, j, mini);
+	}
+}
+
+function tab_layers_highlight_odd_lines() {
+	let ui = ui_base_ui;
+	let step = ui.t.ELEMENT_H * 2;
+	let fullH = ui._window_h - ui_base_hwnds[0].scroll_offset;
+	for (let i = 0; i < math_floor(fullH / step); ++i) {
+		if (i % 2 == 0) {
+			zui_fill(0, i * step, (ui._w / zui_SCALE(ui) - 2), step, ui.t.WINDOW_BG_COL - 0x00040404);
+		}
+	}
+}
+
+function tab_layers_button_new(text: string) {
+	let ui = ui_base_ui;
+	if (zui_button(text)) {
+		ui_menu_draw((ui: zui_t) => {
+			let l = context_raw.layer;
+			if (ui_menu_button(ui, tr("Paint Layer"))) {
+				base_new_layer();
+				history_new_layer();
+			}
+		}, 1);
+	}
+}
+
+function tab_layers_combo_filter() {
+	let ui = ui_base_ui;
+	let ar = [tr("All")];
+	let filterHandle = zui_handle("tablayers_0");
+	filterHandle.position = context_raw.layer_filter;
+	context_raw.layer_filter = zui_combo(filterHandle, ar, tr("Filter"), false, zui_align_t.LEFT);
+}
+
+function tab_layers_remap_layer_pointers(nodes: zui_node_t[], pointerMap: map_t<i32, i32>) {
+	for (let n of nodes) {
+		if (n.type == "LAYER" || n.type == "LAYER_MASK") {
+			let i = n.buttons[0].default_value;
+			if (pointerMap.has(i)) {
+				n.buttons[0].default_value = pointerMap.get(i);
+			}
+		}
+	}
+}
+
+function tab_layers_init_layer_map(): map_t<SlotLayerRaw, i32> {
+	let res: map_t<SlotLayerRaw, i32> = map_create();
+	for (let i = 0; i < project_layers.length; ++i) res.set(project_layers[i], i);
+	return res;
+}
+
+function tab_layers_fill_layer_map(map: map_t<SlotLayerRaw, i32>): map_t<i32, i32> {
+	let res: map_t<i32, i32> = map_create();
+	for (let l of map.keys()) res.set(map.get(l), project_layers.indexOf(l) > -1 ? project_layers.indexOf(l) : 9999);
+	return res;
+}
+
+function tab_layers_set_drag_layer(layer: SlotLayerRaw, off_x: f32, off_y: f32) {
+	base_drag_off_x = off_x;
+	base_drag_off_y = off_y;
+	base_drag_layer = layer;
+	context_raw.drag_dest = project_layers.indexOf(layer);
+}
+
+function tab_layers_draw_layer_slot(l: SlotLayerRaw, i: i32, mini: bool) {
+	let ui = ui_base_ui;
+
+	if (context_raw.layer_filter > 0 &&
+		slot_layer_get_object_mask(l) > 0 &&
+		slot_layer_get_object_mask(l) != context_raw.layer_filter) {
+		return;
+	}
+
+	if (l.parent != null && !l.parent.show_panel) { // Group closed
+		return;
+	}
+	if (l.parent != null && l.parent.parent != null && !l.parent.parent.show_panel) {
+		return;
+	}
+
+	let step = ui.t.ELEMENT_H;
+	let checkw = (ui._window_w / 100 * 8) / zui_SCALE(ui);
+
+	// Highlight drag destination
+	let absy = ui._window_y + ui._y;
+	if (base_is_dragging && base_drag_layer != null && context_in_layers()) {
+		if (mouse_y > absy + step && mouse_y < absy + step * 3) {
+			let down = project_layers.indexOf(base_drag_layer) >= i;
+			context_raw.drag_dest = down ? i : i - 1;
+
+			let ls = project_layers;
+			let dest = context_raw.drag_dest;
+			let toGroup = down ? dest > 0 && ls[dest - 1].parent != null && ls[dest - 1].parent.show_panel : dest < ls.length && ls[dest].parent != null && ls[dest].parent.show_panel;
+			let nestedGroup = slot_layer_is_group(base_drag_layer) && toGroup;
+			if (!nestedGroup) {
+				if (slot_layer_can_move(context_raw.layer, context_raw.drag_dest)) {
+					zui_fill(checkw, step * 2, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
+				}
+			}
+		}
+		else if (i == project_layers.length - 1 && mouse_y < absy + step) {
+			context_raw.drag_dest = project_layers.length - 1;
+			if (slot_layer_can_move(context_raw.layer, context_raw.drag_dest)) {
+				zui_fill(checkw, 0, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
+			}
+		}
+	}
+	if (base_is_dragging && (base_drag_material != null || base_drag_swatch != null) && context_in_layers()) {
+		if (mouse_y > absy + step && mouse_y < absy + step * 3) {
+			context_raw.drag_dest = i;
+			if (tab_layers_can_drop_new_layer(i))
+				zui_fill(checkw, 2 * step, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
+		}
+		else if (i == project_layers.length - 1 && mouse_y < absy + step) {
+			context_raw.drag_dest = project_layers.length;
+			if (tab_layers_can_drop_new_layer(project_layers.length))
+				zui_fill(checkw, 0, (ui._window_w / zui_SCALE(ui) - 2) - checkw, 2 * zui_SCALE(ui), ui.t.HIGHLIGHT_COL);
+		}
+	}
+
+	mini ? tab_layers_draw_layer_slot_mini(l, i) : tab_layers_draw_layer_slot_full(l, i);
+
+	tab_layers_draw_layer_highlight(l, mini);
+
+	if (tab_layers_show_context_menu) {
+		tab_layers_draw_layer_context_menu(l, mini);
+	}
+}
+
+function tab_layers_draw_layer_slot_mini(l: SlotLayerRaw, i: i32) {
+	let ui = ui_base_ui;
+
+	zui_row([1, 1]);
+	let uix = ui._x;
+	let uiy = ui._y;
+	zui_end_element();
+	zui_end_element();
+
+	ui._y += zui_ELEMENT_H(ui);
+	ui._y -= zui_ELEMENT_OFFSET(ui);
+}
+
+function tab_layers_draw_layer_slot_full(l: SlotLayerRaw, i: i32) {
+	let ui = ui_base_ui;
+
+	let step = ui.t.ELEMENT_H;
+
+	let hasPanel = slot_layer_is_group(l) || (slot_layer_is_layer(l) && slot_layer_get_masks(l, false) != null);
+	if (hasPanel) {
+		zui_row([8 / 100, 52 / 100, 30 / 100, 10 / 100]);
+	}
+	else {
+		zui_row([8 / 100, 52 / 100, 30 / 100]);
+	}
+
+	// Draw eye icon
+	let icons = resource_get("icons.k");
+	let r = resource_tile18(icons, l.visible ? 0 : 1, 0);
+	let center = (step / 2) * zui_SCALE(ui);
+	ui._x += 2;
+	ui._y += 3;
+	ui._y += center;
+	let col = ui.t.ACCENT_SELECT_COL;
+	let parentHidden = l.parent != null && (!l.parent.visible || (l.parent.parent != null && !l.parent.parent.visible));
+	if (parentHidden) col -= 0x99000000;
+
+	if (zui_image(icons, col, -1.0, r.x, r.y, r.w, r.h) == zui_state_t.RELEASED) {
+		tab_layers_layer_toggle_visible(l);
+	}
+	ui._x -= 2;
+	ui._y -= 3;
+	ui._y -= center;
+
+	let uix = ui._x;
+	let uiy = ui._y;
+
+	// Draw layer name
+	ui._y += center;
+	if (tab_layers_layer_name_edit == l.id) {
+		tab_layers_layer_name_handle.text = l.name;
+		l.name = zui_text_input(tab_layers_layer_name_handle);
+		if (ui.text_selected_handle_ptr != tab_layers_layer_name_handle.ptr) tab_layers_layer_name_edit = -1;
+	}
+	else {
+		if (ui.enabled && ui.input_enabled && ui.combo_selected_handle_ptr == 0 &&
+			ui.input_x > ui._window_x + ui._x && ui.input_x < ui._window_x + ui._window_w &&
+			ui.input_y > ui._window_y + ui._y - center && ui.input_y < ui._window_y + ui._y - center + (step * zui_SCALE(ui)) * 2) {
+			if (ui.input_started) {
+				context_set_layer(l);
+				tab_layers_set_drag_layer(context_raw.layer, -(mouse_x - uix - ui._window_x - 3), -(mouse_y - uiy - ui._window_y + 1));
+			}
+			else if (ui.input_released) {
+				if (time_time() - context_raw.select_time > 0.2) {
+					context_raw.select_time = time_time();
+				}
+			}
+			else if (ui.input_released_r) {
+				context_set_layer(l);
+				tab_layers_show_context_menu = true;
+			}
+		}
+
+		let state = zui_text(l.name);
+		if (state == zui_state_t.RELEASED) {
+			let td = time_time() - context_raw.select_time;
+			if (td < 0.2 && td > 0.0) {
+				tab_layers_layer_name_edit = l.id;
+				tab_layers_layer_name_handle.text = l.name;
+				zui_start_text_edit(tab_layers_layer_name_handle);
+			}
+		}
+
+		// let inFocus = ui.inputX > ui._windowX && ui.inputX < ui._windowX + ui._windowW &&
+		// 			  ui.inputY > ui._windowY && ui.inputY < ui._windowY + ui._windowH;
+		// if (inFocus && ui.isDeleteDown && canDelete(context_raw.layer)) {
+		// 	ui.isDeleteDown = false;
+		// 	let _init() = () => {
+		// 		deleteLayer(context_raw.layer);
+		// 	}
+		// 	app_notify_on_init(_init);
+		// }
+	}
+	ui._y -= center;
+
+	if (l.parent != null) {
+		ui._x -= 10 * zui_SCALE(ui);
+		if (l.parent.parent != null) ui._x -= 10 * zui_SCALE(ui);
+	}
+
+	if (slot_layer_is_group(l)) {
+		zui_end_element();
+	}
+	else {
+		if (slot_layer_is_mask(l)) {
+			ui._y += center;
+		}
+
+		// comboBlending(ui, l);
+		zui_end_element();
+
+		if (slot_layer_is_mask(l)) {
+			ui._y -= center;
+		}
+	}
+
+	if (hasPanel) {
+		ui._y += center;
+		let layerPanel = zui_nest(zui_handle("tablayers_1"), l.id);
+		layerPanel.selected = l.show_panel;
+		l.show_panel = zui_panel(layerPanel, "", true, false, false);
+		ui._y -= center;
+	}
+
+	if (slot_layer_is_group(l) || slot_layer_is_mask(l)) {
+		ui._y -= zui_ELEMENT_OFFSET(ui);
+		zui_end_element();
+	}
+	else {
+		ui._y -= zui_ELEMENT_OFFSET(ui);
+
+		zui_row([8 / 100, 16 / 100, 36 / 100, 30 / 100, 10 / 100]);
+		zui_end_element();
+		zui_end_element();
+		zui_end_element();
+
+		if (config_raw.touch_ui) {
+			ui._x += 12 * zui_SCALE(ui);
+		}
+
+		ui._y -= center;
+		tab_layers_combo_object(ui, l);
+		ui._y += center;
+
+		zui_end_element();
+	}
+
+	ui._y -= zui_ELEMENT_OFFSET(ui);
+}
+
+function tab_layers_combo_object(ui: zui_t, l: SlotLayerRaw, label: bool = false): zui_handle_t {
+	let ar = [tr("Shared")];
+	let objectHandle = zui_nest(zui_handle("tablayers_2"), l.id);
+	objectHandle.position = l.object_mask;
+	l.object_mask = zui_combo(objectHandle, ar, tr("Object"), label, zui_align_t.LEFT);
+	return objectHandle;
+}
+
+function tab_layers_layer_toggle_visible(l: SlotLayerRaw) {
+	l.visible = !l.visible;
+	ui_view2d_hwnd.redraws = 2;
+	make_material_parse_mesh_material();
+}
+
+function tab_layers_draw_layer_highlight(l: SlotLayerRaw, mini: bool) {
+	let ui = ui_base_ui;
+	let step = ui.t.ELEMENT_H;
+
+	// Separator line
+	zui_fill(0, 0, (ui._w / zui_SCALE(ui) - 2), 1 * zui_SCALE(ui), ui.t.SEPARATOR_COL);
+
+	// Highlight selected
+	if (context_raw.layer == l) {
+		if (mini) {
+			zui_rect(1, -step * 2, ui._w / zui_SCALE(ui) - 1, step * 2 + (mini ? -1 : 1), ui.t.HIGHLIGHT_COL, 3);
+		}
+		else {
+			zui_rect(1, -step * 2 - 1, ui._w / zui_SCALE(ui) - 2, step * 2 + (mini ? -2 : 1), ui.t.HIGHLIGHT_COL, 2);
+		}
+	}
+}
+
+function tab_layers_can_merge_down(l: SlotLayerRaw) : bool {
+	let index = project_layers.indexOf(l);
+	// Lowest layer
+	if (index == 0) return false;
+	// Lowest layer that has masks
+	if (slot_layer_is_layer(l) && slot_layer_is_mask(project_layers[0]) && project_layers[0].parent == l) return false;
+	// The lowest toplevel layer is a group
+	if (slot_layer_is_group(l) && slot_layer_is_in_group(project_layers[0]) && slot_layer_get_containing_group(project_layers[0]) == l) return false;
+	// Masks must be merged down to masks
+	if (slot_layer_is_mask(l) && !slot_layer_is_mask(project_layers[index - 1])) return false;
+	return true;
+}
+
+function tab_layers_draw_layer_context_menu(l: SlotLayerRaw, mini: bool) {
+
+}
+
+function tab_layers_can_drop_new_layer(position: i32) {
+	if (position > 0 && position < project_layers.length && slot_layer_is_mask(project_layers[position - 1])) {
+		// 1. The layer to insert is inserted in the middle
+		// 2. The layer below is a mask, i.e. the layer would have to be a (group) mask, too.
+		return false;
+	}
+	return true;
+}

+ 0 - 307
base/Sources/ContextFormat.ts

@@ -1,307 +0,0 @@
-/// <reference path='./project.ts'/>
-/// <reference path='./enums.ts'/>
-
-// type context_t = {
-class context_t {
-
-	texture?: asset_t = null;
-	paint_object?: mesh_object_t;
-	merged_object?: mesh_object_t = null; // For object mask
-	merged_object_is_atlas?: bool = false; // Only objects referenced by atlas are merged
-
-	ddirty?: i32 = 0; // depth
-	pdirty?: i32 = 0; // paint
-	rdirty?: i32 = 0; // render
-	brush_blend_dirty?: bool = true;
-	node_preview_socket?: i32 = 0;
-
-	split_view?: bool = false;
-	view_index?: i32 = -1;
-	view_index_last?: i32 = -1;
-
-	swatch?: swatch_color_t;
-	picked_color?: swatch_color_t = make_swatch();
-	color_picker_callback?: (sc: swatch_color_t)=>void = null;
-
-	default_irradiance?: Float32Array = null;
-	default_radiance?: image_t = null;
-	default_radiance_mipmaps?: image_t[] = null;
-	saved_envmap?: image_t = null;
-	empty_envmap?: image_t = null;
-	preview_envmap?: image_t = null;
-	envmap_loaded?: bool = false;
-	show_envmap?: bool = false;
-	show_envmap_handle?: zui_handle_t = zui_handle_create({ selected: false });
-	show_envmap_blur?: bool = false;
-	show_envmap_blur_handle? = zui_handle_create({ selected: false });
-	envmap_angle?: f32 = 0.0;
-	light_angle?: f32 = 0.0;
-	cull_backfaces?: bool = true;
-	texture_filter?: bool = true;
-
-	format_type?: texture_ldr_format_t = texture_ldr_format_t.PNG;
-	format_quality?: f32 = 100.0;
-	layers_destination? = export_destination_t.DISK;
-	split_by?: split_type_t = split_type_t.OBJECT;
-	parse_transform?: bool = true;
-	parse_vcols?: bool = false;
-
-	select_time?: f32 = 0.0;
-	///if (krom_direct3d12 || krom_vulkan || krom_metal)
-	pathtrace_mode?: path_trace_mode_t = path_trace_mode_t.CORE;
-	///end
-	///if (krom_direct3d12 || krom_vulkan) // || krom_metal)
-	viewport_mode?: viewport_mode_t = viewport_mode_t.PATH_TRACE;
-	///else
-	viewport_mode?: viewport_mode_t = viewport_mode_t.LIT;
-	///end
-	///if (krom_android || krom_ios)
-	render_mode?: render_mode_t = render_mode_t.FORWARD;
-	///else
-	render_mode?: render_mode_t = render_mode_t.DEFERRED;
-	///end
-
-	viewport_shader?: (ns: NodeShaderRaw)=>string = null;
-	hscale_was_changed?: bool = false;
-	export_mesh_format?: mesh_format_t = mesh_format_t.OBJ;
-	export_mesh_index?: i32 = 0;
-	pack_assets_on_export?: bool = true;
-
-	paint_vec?: vec4_t = vec4_create();
-	last_paint_x?: f32 = -1.0;
-	last_paint_y?: f32 = -1.0;
-	foreground_event?: bool = false;
-	painted?: i32 = 0;
-	brush_time?: f32 = 0.0;
-	clone_start_x?: f32 = -1.0;
-	clone_start_y?: f32 = -1.0;
-	clone_delta_x?: f32 = 0.0;
-	clone_delta_y?: f32 = 0.0;
-
-	show_compass?: bool = true;
-	project_type?: project_model_t = project_model_t.ROUNDED_CUBE;
-	project_aspect_ratio?: i32 = 0; // 1:1, 2:1, 1:2
-	project_objects?: mesh_object_t[];
-
-	last_paint_vec_x?: f32 = -1.0;
-	last_paint_vec_y?: f32 = -1.0;
-	prev_paint_vec_x?: f32 = -1.0;
-	prev_paint_vec_y?: f32 = -1.0;
-	frame?: i32 = 0;
-	paint2d_view?: bool = false;
-
-	lock_started_x?: f32 = -1.0;
-	lock_started_y?: f32 = -1.0;
-	brush_locked?: bool = false;
-	brush_can_lock?: bool = false;
-	brush_can_unlock?: bool = false;
-	camera_type?: camera_type_t = camera_type_t.PERSPECTIVE;
-	cam_handle?: zui_handle_t = zui_handle_create();
-	fov_handle?: zui_handle_t = null;
-	undo_handle?: zui_handle_t = null;
-	hssao?: zui_handle_t = null;
-	hssr?: zui_handle_t = null;
-	hbloom?: zui_handle_t = null;
-	hsupersample?: zui_handle_t = null;
-	hvxao?: zui_handle_t = null;
-	///if is_forge
-	vxao_ext?: f32 = 2.0;
-	///else
-	vxao_ext?: f32 = 1.0;
-	///end
-	vxao_offset?: f32 = 1.5;
-	vxao_aperture?: f32 = 1.2;
-	texture_export_path?: string = "";
-	last_status_position?: i32 = 0;
-	camera_controls?: camera_controls_t = camera_controls_t.ORBIT;
-	pen_painting_only?: bool = false; // Reject painting with finger when using pen
-
-	///if (is_paint || is_sculpt)
-	material?: SlotMaterialRaw;
-	layer?: SlotLayerRaw;
-	brush?: SlotBrushRaw;
-	font?: SlotFontRaw;
-	tool?: workspace_tool_t = workspace_tool_t.BRUSH;
-
-	layer_preview_dirty?: bool = true;
-	layers_preview_dirty?: bool = false;
-	node_preview_dirty?: bool = false;
-	node_preview?: image_t = null;
-	node_previews?: map_t<string, image_t> = null;
-	node_previews_used?: string[] = null;
-	node_preview_name?: string = "";
-	mask_preview_rgba32?: image_t = null;
-	mask_preview_last?: SlotLayerRaw = null;
-
-	colorid_picked?: bool = false;
-	material_preview?: bool = false; // Drawing material previews
-	saved_camera?: mat4_t = mat4_identity();
-
-	color_picker_previous_tool? = workspace_tool_t.BRUSH;
-	materialid_picked?: i32 = 0;
-	uvx_picked?: f32 = 0.0;
-	uvy_picked?: f32 = 0.0;
-	picker_select_material?: bool = true;
-	picker_mask_handle?: zui_handle_t = zui_handle_create();
-	pick_pos_nor_tex?: bool = false;
-	posx_picked?: f32 = 0.0;
-	posy_picked?: f32 = 0.0;
-	posz_picked?: f32 = 0.0;
-	norx_picked?: f32 = 0.0;
-	nory_picked?: f32 = 0.0;
-	norz_picked?: f32 = 0.0;
-
-	draw_wireframe?: bool = false;
-	wireframe_handle?: zui_handle_t = zui_handle_create({ selected: false });
-	draw_texels?: bool = false;
-	texels_handle?: zui_handle_t = zui_handle_create({ selected: false });
-
-	colorid_handle?: zui_handle_t = zui_handle_create();
-	layers_export?: export_mode_t = export_mode_t.VISIBLE;
-
-	decal_image?: image_t = null;
-	decal_preview?: bool = false;
-	decal_x?: f32 = 0.0;
-	decal_y?: f32 = 0.0;
-
-	cache_draws?: bool = false;
-	write_icon_on_export?: bool = false;
-
-	text_tool_image?: image_t = null;
-	text_tool_text?: string;
-	particle_material?: material_data_t = null;
-	///if arm_physics
-	particle_physics?: bool = false;
-	particle_hit_x?: f32 = 0.0;
-	particle_hit_y?: f32 = 0.0;
-	particle_hit_z?: f32 = 0.0;
-	last_particle_hit_x?: f32 = 0.0;
-	last_particle_hit_y?: f32 = 0.0;
-	last_particle_hit_z?: f32 = 0.0;
-	particle_timer?: tween_anim_t = null;
-	paint_body?: PhysicsBodyRaw = null;
-	///end
-
-	layer_filter?: i32 = 0;
-	run_brush?: (i: i32)=>void = null;
-	parse_brush_inputs?: ()=>void = null;
-
-	gizmo?: object_t = null;
-	gizmo_translate_x?: object_t = null;
-	gizmo_translate_y?: object_t = null;
-	gizmo_translate_z?: object_t = null;
-	gizmo_scale_x?: object_t = null;
-	gizmo_scale_y?: object_t = null;
-	gizmo_scale_z?: object_t = null;
-	gizmo_rotate_x?: object_t = null;
-	gizmo_rotate_y?: object_t = null;
-	gizmo_rotate_z?: object_t = null;
-	gizmo_started?: bool = false;
-	gizmo_offset?: f32 = 0.0;
-	gizmo_drag?: f32 = 0.0;
-	gizmo_drag_last?: f32 = 0.0;
-	translate_x?: bool = false;
-	translate_y?: bool = false;
-	translate_z?: bool = false;
-	scale_x?: bool = false;
-	scale_y?: bool = false;
-	scale_z?: bool = false;
-	rotate_x?: bool = false;
-	rotate_y?: bool = false;
-	rotate_z?: bool = false;
-
-	brush_nodes_radius?: f32 = 1.0;
-	brush_nodes_opacity?: f32 = 1.0;
-	brush_mask_image?: image_t = null;
-	brush_mask_image_is_alpha?: bool = false;
-	brush_stencil_image?: image_t = null;
-	brush_stencil_image_is_alpha?: bool = false;
-	brush_stencil_x?: f32 = 0.02;
-	brush_stencil_y?: f32 = 0.02;
-	brush_stencil_scale?: f32 = 0.9;
-	brush_stencil_scaling?: bool = false;
-	brush_stencil_angle?: f32 = 0.0;
-	brush_stencil_rotating?: bool = false;
-	brush_nodes_scale?: f32 = 1.0;
-	brush_nodes_angle?: f32 = 0.0;
-	brush_nodes_hardness?: f32 = 1.0;
-	brush_directional?: bool = false;
-
-	brush_radius?: f32 = 0.5;
-	brush_radius_handle?: zui_handle_t = zui_handle_create({ value: 0.5 });
-	brush_scale_x?: f32 = 1.0;
-	brush_decal_mask_radius?: f32 = 0.5;
-	brush_decal_mask_radius_handle?: zui_handle_t = zui_handle_create({ value: 0.5 });
-	brush_scale_x_handle?: zui_handle_t = zui_handle_create({ value: 1.0 });
-	brush_blending?: blend_type_t = blend_type_t.MIX;
-	brush_opacity?: f32 = 1.0;
-	brush_opacity_handle?: zui_handle_t = zui_handle_create({ value: 1.0 });
-	brush_scale?: f32 = 1.0;
-	brush_angle?: f32 = 0.0;
-	brush_angle_handle?: zui_handle_t = zui_handle_create({ value: 0.0 });
-	///if is_paint
-	brush_hardness?: f32 = 0.8;
-	///end
-	///if is_sculpt
-	brush_hardness?: f32 = 0.05;
-	///end
-	brush_lazy_radius?: f32 = 0.0;
-	brush_lazy_step?: f32 = 0.0;
-	brush_lazy_x?: f32 = 0.0;
-	brush_lazy_y?: f32 = 0.0;
-	brush_paint?: uv_type_t = uv_type_t.UVMAP;
-	brush_angle_reject_dot?: f32 = 0.5;
-	bake_type?: bake_type_t = bake_type_t.AO;
-	bake_axis?: bake_axis_t = bake_axis_t.XYZ;
-	bake_up_axis?: bake_up_axis_t = bake_up_axis_t.Z;
-	bake_samples?: i32 = 128;
-	bake_ao_strength?: f32 = 1.0;
-	bake_ao_radius?: f32 = 1.0;
-	bake_ao_offset?: f32 = 1.0;
-	bake_curv_strength?: f32 = 1.0;
-	bake_curv_radius?: f32 = 1.0;
-	bake_curv_offset?: f32 = 0.0;
-	bake_curv_smooth?: i32 = 1;
-	bake_high_poly?: i32 = 0;
-
-	xray?: bool = false;
-	sym_x?: bool = false;
-	sym_y?: bool = false;
-	sym_z?: bool = false;
-	fill_type_handle?: zui_handle_t = zui_handle_create();
-
-	paint2d?: bool = false;
-
-	last_htab0_pos?: i32 = 0;
-	maximized_sidebar_width?: i32 = 0;
-	drag_dest?: i32 = 0;
-	///end
-
-	///if is_lab
-	material?: any; ////
-	layer?: any; ////
-	tool?: workspace_tool_t = workspace_tool_t.ERASER;
-
-	color_picker_previous_tool?: workspace_tool_t = workspace_tool_t.ERASER;
-
-	brush_radius?: f32 = 0.25;
-	brush_radius_handle?: zui_handle_t = zui_handle_create({ value: 0.25 });
-	brush_scale?: f32 = 1.0;
-
-	coords?: vec4_t = vec4_create();
-	start_x?: f32 = 0.0;
-	start_y?: f32 = 0.0;
-
-	// Brush ruler
-	lock_begin?: bool = false;
-	lock_x?: bool = false;
-	lock_y?: bool = false;
-	lock_start_x?: f32 = 0.0;
-	lock_start_y?: f32 = 0.0;
-	registered?: bool = false;
-	///end
-
-	///if is_forge
-	selected_object?: object_t = null;
-	///end
-}

+ 0 - 224
base/Sources/TabBrowser.ts

@@ -1,224 +0,0 @@
-
-class TabBrowser {
-
-	static tab_browser_hpath: zui_handle_t = zui_handle_create();
-	static tab_browser_hsearch: zui_handle_t = zui_handle_create();
-	static tab_browser_known: bool = false;
-	static tab_browser_last_path: string =  "";
-
-	static tab_browser_show_directory = (directory: string) => {
-		TabBrowser.tab_browser_hpath.text = directory;
-		TabBrowser.tab_browser_hsearch.text = "";
-		ui_base_htabs[tab_area_t.STATUS].position = 0;
-	}
-
-	static tab_browser_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-		if (zui_tab(htab, tr("Browser")) && statush > ui_status_default_status_h * zui_SCALE(ui)) {
-
-			if (config_raw.bookmarks == null) {
-				config_raw.bookmarks = [];
-			}
-
-			let bookmarks_w: i32 = math_floor(100 * zui_SCALE(ui));
-
-			if (TabBrowser.tab_browser_hpath.text == "" && config_raw.bookmarks.length > 0) { // Init to first bookmark
-				TabBrowser.tab_browser_hpath.text = config_raw.bookmarks[0];
-			}
-
-			zui_begin_sticky();
-			let step: i32 = (1 - bookmarks_w / ui._w);
-			if (TabBrowser.tab_browser_hsearch.text != "") {
-				zui_row([bookmarks_w / ui._w, step * 0.73, step * 0.07, step * 0.17, step * 0.03]);
-			}
-			else {
-				zui_row([bookmarks_w / ui._w, step * 0.73, step * 0.07, step * 0.2]);
-			}
-
-			if (zui_button("+")) {
-				config_raw.bookmarks.push(TabBrowser.tab_browser_hpath.text);
-				config_save();
-			}
-			if (ui.is_hovered) zui_tooltip(tr("Add bookmark"));
-
-			///if krom_android
-			let stripped: bool = false;
-			let strip: string = "/storage/emulated/0/";
-			if (TabBrowser.tab_browser_hpath.text.startsWith(strip)) {
-				TabBrowser.tab_browser_hpath.text = TabBrowser.tab_browser_hpath.text.substr(strip.length - 1);
-				stripped = true;
-			}
-			///end
-
-			TabBrowser.tab_browser_hpath.text = zui_text_input(TabBrowser.tab_browser_hpath, tr("Path"));
-
-			///if krom_android
-			if (stripped) {
-				TabBrowser.tab_browser_hpath.text = "/storage/emulated/0" + TabBrowser.tab_browser_hpath.text;
-			}
-			///end
-
-			let refresh: bool = false;
-			let in_focus: bool = ui.input_x > ui._window_x && ui.input_x < ui._window_x + ui._window_w &&
-						  ui.input_y > ui._window_y && ui.input_y < ui._window_y + ui._window_h;
-			if (zui_button(tr("Refresh")) || (in_focus && ui.is_key_pressed && ui.key == key_code_t.F5)) {
-				refresh = true;
-			}
-			TabBrowser.tab_browser_hsearch.text = zui_text_input(TabBrowser.tab_browser_hsearch, tr("Search"), zui_align_t.LEFT, true, true);
-			if (ui.is_hovered) zui_tooltip(tr("ctrl+f to search") + "\n" + tr("esc to cancel"));
-			if (ui.is_ctrl_down && ui.is_key_pressed && ui.key == key_code_t.F) { // Start searching via ctrl+f
-				zui_start_text_edit(TabBrowser.tab_browser_hsearch);
-			}
-			if (TabBrowser.tab_browser_hsearch.text != "" && (zui_button(tr("X")) || ui.is_escape_down)) {
-				TabBrowser.tab_browser_hsearch.text = "";
-			}
-			zui_end_sticky();
-
-			if (TabBrowser.tab_browser_last_path != TabBrowser.tab_browser_hpath.text) {
-				TabBrowser.tab_browser_hsearch.text = "";
-			}
-			TabBrowser.tab_browser_last_path = TabBrowser.tab_browser_hpath.text;
-
-			let _y: f32 = ui._y;
-			ui._x = bookmarks_w;
-			ui._w -= bookmarks_w;
-			ui_files_file_browser(ui, TabBrowser.tab_browser_hpath, false, true, TabBrowser.tab_browser_hsearch.text, refresh, (file: string) => {
-				let file_name: string = file.substr(file.lastIndexOf(path_sep) + 1);
-				if (file_name != "..") {
-					ui_menu_draw((ui: zui_t) => {
-						if (ui_menu_button(ui, tr("Import"))) {
-							import_asset_run(file);
-						}
-						if (path_is_texture(file)) {
-							if (ui_menu_button(ui, tr("Set as Envmap"))) {
-								import_asset_run(file, -1.0, -1.0, true, true, () => {
-									base_notify_on_next_frame(() => {
-										let asset_index: i32 = -1;
-										for (let i: i32 = 0; i < project_assets.length; ++i) {
-											if (project_assets[i].file == file) {
-												asset_index = i;
-												break;
-											}
-										}
-										if (asset_index != -1) {
-											import_envmap_run(file, project_get_image(project_assets[asset_index]));
-										}
-									});
-								});
-							}
-
-							///if (is_paint || is_sculpt)
-							if (ui_menu_button(ui, tr("Set as Mask"))) {
-								import_asset_run(file, -1.0, -1.0, true, true, () => {
-									base_notify_on_next_frame(() => {
-										let asset_index: i32 = -1;
-										for (let i: i32 = 0; i < project_assets.length; ++i) {
-											if (project_assets[i].file == file) {
-												asset_index = i;
-												break;
-											}
-										}
-										if (asset_index != -1) {
-											base_create_image_mask(project_assets[asset_index]);
-										}
-									});
-								});
-							}
-							///end
-
-							///if is_paint
-							if (ui_menu_button(ui, tr("Set as Color ID Map"))) {
-								import_asset_run(file, -1.0, -1.0, true, true, () => {
-									base_notify_on_next_frame(() => {
-										let asset_index: i32 = -1;
-										for (let i: i32 = 0; i < project_assets.length; ++i) {
-											if (project_assets[i].file == file) {
-												asset_index = i;
-												break;
-											}
-										}
-										if (asset_index != -1) {
-											context_raw.colorid_handle.position = asset_index;
-											context_raw.colorid_picked = false;
-											ui_toolbar_handle.redraws = 1;
-											if (context_raw.tool == workspace_tool_t.COLORID) {
-												ui_header_handle.redraws = 2;
-												context_raw.ddirty = 2;
-											}
-										}
-									});
-								});
-							}
-							///end
-						}
-						if (ui_menu_button(ui, tr("Open Externally"))) {
-							file_start(file);
-						}
-					}, path_is_texture(file) ? 5 : 2);
-				}
-			});
-
-			if (TabBrowser.tab_browser_known) {
-				let path: string = TabBrowser.tab_browser_hpath.text;
-				app_notify_on_init(() => {
-					import_asset_run(path);
-				});
-				TabBrowser.tab_browser_hpath.text = TabBrowser.tab_browser_hpath.text.substr(0, TabBrowser.tab_browser_hpath.text.lastIndexOf(path_sep));
-			}
-			TabBrowser.tab_browser_known = TabBrowser.tab_browser_hpath.text.substr(TabBrowser.tab_browser_hpath.text.lastIndexOf(path_sep)).indexOf(".") > 0;
-			///if krom_android
-			if (TabBrowser.tab_browser_hpath.text.endsWith("." + manifest_title.toLowerCase())) TabBrowser.tab_browser_known = false;
-			///end
-
-			let bottom_y: i32 = ui._y;
-			ui._x = 0;
-			ui._y = _y;
-			ui._w = bookmarks_w;
-
-			if (zui_button(tr("Cloud"), zui_align_t.LEFT)) {
-				TabBrowser.tab_browser_hpath.text = "cloud";
-			}
-
-			if (zui_button(tr("Disk"), zui_align_t.LEFT)) {
-				///if krom_android
-				ui_menu_draw((ui: zui_t) => {
-					if (ui_menu_button(ui, tr("Download"))) {
-						TabBrowser.tab_browser_hpath.text = ui_files_default_path;
-					}
-					if (ui_menu_button(ui, tr("Pictures"))) {
-						TabBrowser.tab_browser_hpath.text = "/storage/emulated/0/Pictures";
-					}
-					if (ui_menu_button(ui, tr("Camera"))) {
-						TabBrowser.tab_browser_hpath.text = "/storage/emulated/0/DCIM/Camera";
-					}
-					if (ui_menu_button(ui, tr("Projects"))) {
-						TabBrowser.tab_browser_hpath.text = krom_save_path();
-					}
-				}, 4);
-				///else
-				TabBrowser.tab_browser_hpath.text = ui_files_default_path;
-				///end
-			}
-
-			for (let b of config_raw.bookmarks) {
-				let folder: string = b.substr(b.lastIndexOf(path_sep) + 1);
-
-				if (zui_button(folder, zui_align_t.LEFT)) {
-					TabBrowser.tab_browser_hpath.text = b;
-				}
-
-				if (ui.is_hovered && ui.input_released_r) {
-					ui_menu_draw((ui: zui_t) => {
-						if (ui_menu_button(ui, tr("Delete"))) {
-							array_remove(config_raw.bookmarks, b);
-							config_save();
-						}
-					}, 1);
-				}
-			}
-
-			if (ui._y < bottom_y) ui._y = bottom_y;
-		}
-	}
-}

+ 0 - 150
base/Sources/TabBrushes.ts

@@ -1,150 +0,0 @@
-
-///if (is_paint || is_sculpt)
-
-class TabBrushes {
-
-	static tab_brushes_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		if (zui_tab(htab, tr("Brushes"))) {
-			zui_begin_sticky();
-			zui_row([1 / 4, 1 / 4, 1 / 4]);
-			if (zui_button(tr("New"))) {
-				context_raw.brush = SlotBrush.slot_brush_create();
-				project_brushes.push(context_raw.brush);
-				MakeMaterial.make_material_parse_brush();
-				ui_nodes_hwnd.redraws = 2;
-			}
-			if (zui_button(tr("Import"))) {
-				project_import_brush();
-			}
-			if (zui_button(tr("Nodes"))) {
-				ui_base_show_brush_nodes();
-			}
-			zui_end_sticky();
-			zui_separator(3, false);
-
-			let slotw: i32 = math_floor(51 * zui_SCALE(ui));
-			let num: i32 = math_floor(config_raw.layout[layout_size_t.SIDEBAR_W] / slotw);
-
-			for (let row: i32 = 0; row < math_floor(math_ceil(project_brushes.length / num)); ++row) {
-				let mult: i32 = config_raw.show_asset_names ? 2 : 1;
-				let ar: f32[] = [];
-				for (let i: i32 = 0; i < num * mult; ++i) ar.push(1 / num);
-				zui_row(ar);
-
-				ui._x += 2;
-				let off: f32 = config_raw.show_asset_names ? zui_ELEMENT_OFFSET(ui) * 10.0 : 6;
-				if (row > 0) ui._y += off;
-
-				for (let j: i32 = 0; j < num; ++j) {
-					let imgw: i32 = math_floor(50 * zui_SCALE(ui));
-					let i: i32 = j + row * num;
-					if (i >= project_brushes.length) {
-						zui_end_element(imgw);
-						if (config_raw.show_asset_names) zui_end_element(0);
-						continue;
-					}
-					let img: image_t = zui_SCALE(ui) > 1 ? project_brushes[i].image : project_brushes[i].image_icon;
-					let img_full: image_t = project_brushes[i].image;
-
-					if (context_raw.brush == project_brushes[i]) {
-						// Zui.fill(1, -2, img.width + 3, img.height + 3, ui.t.HIGHLIGHT_COL); // TODO
-						let off: i32 = row % 2 == 1 ? 1 : 0;
-						let w: i32 = 50;
-						if (config_raw.window_scale > 1) w += math_floor(config_raw.window_scale * 2);
-						zui_fill(-1,         -2, w + 3,       2, ui.t.HIGHLIGHT_COL);
-						zui_fill(-1,    w - off, w + 3, 2 + off, ui.t.HIGHLIGHT_COL);
-						zui_fill(-1,         -2,     2,   w + 3, ui.t.HIGHLIGHT_COL);
-						zui_fill(w + 1,      -2,     2,   w + 4, ui.t.HIGHLIGHT_COL);
-					}
-
-					let uix: f32 = ui._x;
-					//let uiy: f32 = ui._y;
-					let tile: i32 = zui_SCALE(ui) > 1 ? 100 : 50;
-					let state: zui_state_t = project_brushes[i].preview_ready ? zui_image(img) : zui_image(resource_get("icons.k"), -1, -1.0, tile * 5, tile, tile, tile);
-					if (state == zui_state_t.STARTED) {
-						if (context_raw.brush != project_brushes[i]) context_select_brush(i);
-						if (time_time() - context_raw.select_time < 0.25) ui_base_show_brush_nodes();
-						context_raw.select_time = time_time();
-						// app_drag_off_x = -(mouse_x - uix - ui._windowX - 3);
-						// app_drag_off_y = -(mouse_y - uiy - ui._windowY + 1);
-						// app_drag_brush = raw.brush;
-					}
-					if (ui.is_hovered && ui.input_released_r) {
-						context_select_brush(i);
-						let add: i32 = project_brushes.length > 1 ? 1 : 0;
-						ui_menu_draw((ui: zui_t) => {
-							//let b: SlotBrushRaw = brushes[i];
-
-							if (ui_menu_button(ui, tr("Export"))) {
-								context_select_brush(i);
-								box_export_show_brush();
-							}
-
-							if (ui_menu_button(ui, tr("Duplicate"))) {
-								let _init = () => {
-									context_raw.brush = SlotBrush.slot_brush_create();
-									project_brushes.push(context_raw.brush);
-									let cloned: any = json_parse(json_stringify(project_brushes[i].canvas));
-									context_raw.brush.canvas = cloned;
-									context_set_brush(context_raw.brush);
-									util_render_make_brush_preview();
-								}
-								app_notify_on_init(_init);
-							}
-
-							if (project_brushes.length > 1 && ui_menu_button(ui, tr("Delete"), "delete")) {
-								TabBrushes.tab_brushes_delete_brush(project_brushes[i]);
-							}
-						}, 2 + add);
-					}
-
-					if (ui.is_hovered) {
-						if (img_full == null) {
-							app_notify_on_init(() => {
-								let _brush: SlotBrushRaw = context_raw.brush;
-								context_raw.brush = project_brushes[i];
-								MakeMaterial.make_material_parse_brush();
-								util_render_make_brush_preview();
-								context_raw.brush = _brush;
-							});
-						}
-						else {
-							zui_tooltip_image(img_full);
-							zui_tooltip(project_brushes[i].canvas.name);
-						}
-					}
-
-					if (config_raw.show_asset_names) {
-						ui._x = uix;
-						ui._y += slotw * 0.9;
-						zui_text(project_brushes[i].canvas.name, zui_align_t.CENTER);
-						if (ui.is_hovered) zui_tooltip(project_brushes[i].canvas.name);
-						ui._y -= slotw * 0.9;
-						if (i == project_brushes.length - 1) {
-							ui._y += j == num - 1 ? imgw : imgw + zui_ELEMENT_H(ui) + zui_ELEMENT_OFFSET(ui);
-						}
-					}
-				}
-
-				ui._y += 6;
-			}
-
-			let in_focus: bool = ui.input_x > ui._window_x && ui.input_x < ui._window_x + ui._window_w &&
-						  		 ui.input_y > ui._window_y && ui.input_y < ui._window_y + ui._window_h;
-			if (in_focus && ui.is_delete_down && project_brushes.length > 1) {
-				ui.is_delete_down = false;
-				TabBrushes.tab_brushes_delete_brush(context_raw.brush);
-			}
-		}
-	}
-
-	static tab_brushes_delete_brush = (b: SlotBrushRaw) => {
-		let i: i32 = project_brushes.indexOf(b);
-		context_select_brush(i == project_brushes.length - 1 ? i - 1 : i + 1);
-		project_brushes.splice(i, 1);
-		ui_base_hwnds[1].redraws = 2;
-	}
-}
-
-///end

+ 0 - 64
base/Sources/TabConsole.ts

@@ -1,64 +0,0 @@
-
-class TabConsole {
-
-	static tab_console_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-
-		let title: string = console_message_timer > 0 ? console_message + "        " : tr("Console");
-		let color: i32 = console_message_timer > 0 ? console_message_color : -1;
-
-		let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-		if (zui_tab(htab, title, false, color) && statush > ui_status_default_status_h * zui_SCALE(ui)) {
-
-			zui_begin_sticky();
-			///if (krom_windows || krom_linux || krom_darwin) // Copy
-			if (config_raw.touch_ui) {
-				zui_row([1 / 4, 1 / 4, 1 / 4]);
-			}
-			else {
-				zui_row([1 / 14, 1 / 14, 1 / 14]);
-			}
-			///else
-			if (config_raw.touch_ui) {
-				zui_row([1 / 4, 1 / 4]);
-			}
-			else {
-				zui_row([1 / 14, 1 / 14]);
-			}
-			///end
-
-			if (zui_button(tr("Clear"))) {
-				console_last_traces = [];
-			}
-			if (zui_button(tr("Export"))) {
-				let str: string = console_last_traces.join("\n");
-				ui_files_show("txt", true, false, (path: string) => {
-					let f: string = ui_files_filename;
-					if (f == "") f = tr("untitled");
-					path = path + path_sep + f;
-					if (!path.endsWith(".txt")) path += ".txt";
-					krom_file_save_bytes(path, sys_string_to_buffer(str));
-				});
-			}
-			///if (krom_windows || krom_linux || krom_darwin)
-			if (zui_button(tr("Copy"))) {
-				let str: string = console_last_traces.join("\n");
-				krom_copy_to_clipboard(str);
-			}
-			///end
-
-			zui_end_sticky();
-
-			let _font: g2_font_t = ui.font;
-			let _fontSize: i32 = ui.font_size;
-			let f: g2_font_t = data_get_font("font_mono.ttf");
-			zui_set_font(ui, f);
-			ui.font_size = math_floor(15 * zui_SCALE(ui));
-			for (let t of console_last_traces) {
-				zui_text(t);
-			}
-			zui_set_font(ui, _font);
-			ui.font_size = _fontSize;
-		}
-	}
-}

+ 0 - 149
base/Sources/TabFonts.ts

@@ -1,149 +0,0 @@
-
-///if (is_paint || is_sculpt)
-
-class TabFonts {
-
-	static tab_fonts_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-		if (zui_tab(htab, tr("Fonts")) && statush > ui_status_default_status_h * zui_SCALE(ui)) {
-
-			zui_begin_sticky();
-			if (config_raw.touch_ui) {
-				zui_row([1 / 4, 1 / 4]);
-			}
-			else {
-				zui_row([1 / 14, 1 / 14]);
-			}
-
-			if (zui_button(tr("Import"))) project_import_asset("ttf,ttc,otf");
-			if (ui.is_hovered) zui_tooltip(tr("Import font file"));
-
-			if (zui_button(tr("2D View"))) {
-				ui_base_show_2d_view(view_2d_type_t.FONT);
-			}
-			zui_end_sticky();
-			zui_separator(3, false);
-
-			let statusw: i32 = sys_width() - ui_toolbar_w - config_raw.layout[layout_size_t.SIDEBAR_W];
-			let slotw: i32 = math_floor(51 * zui_SCALE(ui));
-			let num: i32 = math_floor(statusw / slotw);
-
-			for (let row: i32 = 0; row < math_floor(math_ceil(project_fonts.length / num)); ++row) {
-				let mult: i32 = config_raw.show_asset_names ? 2 : 1;
-				let ar: f32[] = [];
-				for (let i: i32 = 0; i < num * mult; ++i) ar.push(1 / num);
-				zui_row(ar);
-
-				ui._x += 2;
-				let off: f32 = config_raw.show_asset_names ? zui_ELEMENT_OFFSET(ui) * 10.0 : 6;
-				if (row > 0) ui._y += off;
-
-				for (let j: i32 = 0; j < num; ++j) {
-					let imgw: i32 = math_floor(50 * zui_SCALE(ui));
-					let i: i32 = j + row * num;
-					if (i >= project_fonts.length) {
-						zui_end_element(imgw);
-						if (config_raw.show_asset_names) zui_end_element(0);
-						continue;
-					}
-					let img: image_t = project_fonts[i].image;
-
-					if (context_raw.font == project_fonts[i]) {
-						// Zui.fill(1, -2, img.width + 3, img.height + 3, ui.t.HIGHLIGHT_COL); // TODO
-						let off: i32 = row % 2 == 1 ? 1 : 0;
-						let w: i32 = 50;
-						if (config_raw.window_scale > 1) w += math_floor(config_raw.window_scale * 2);
-						zui_fill(-1,         -2, w + 3,       2, ui.t.HIGHLIGHT_COL);
-						zui_fill(-1,    w - off, w + 3, 2 + off, ui.t.HIGHLIGHT_COL);
-						zui_fill(-1,         -2,     2,   w + 3, ui.t.HIGHLIGHT_COL);
-						zui_fill(w + 1,      -2,     2,   w + 4, ui.t.HIGHLIGHT_COL);
-					}
-
-					let uix: f32 = ui._x;
-					let tile: i32 = zui_SCALE(ui) > 1 ? 100 : 50;
-					let state: zui_state_t = zui_state_t.IDLE;
-					if (project_fonts[i].preview_ready) {
-						// g2_set_pipeline(pipe); // L8
-						// ///if krom_opengl
-						// g4_set_pipeline(pipe);
-						// ///end
-						// g4_set_int(channelLocation, 1);
-						state = zui_image(img);
-						// g2_set_pipeline(null);
-					}
-					else {
-						state = zui_image(resource_get("icons.k"), -1, -1.0, tile * 6, tile, tile, tile);
-					}
-
-					if (state == zui_state_t.STARTED) {
-						if (context_raw.font != project_fonts[i]) {
-							let _init = () => {
-								context_select_font(i);
-							}
-							app_notify_on_init(_init);
-						}
-						if (time_time() - context_raw.select_time < 0.25) ui_base_show_2d_view(view_2d_type_t.FONT);
-						context_raw.select_time = time_time();
-					}
-					if (ui.is_hovered && ui.input_released_r) {
-						context_select_font(i);
-						let add: i32 = project_fonts.length > 1 ? 1 : 0;
-						ui_menu_draw((ui: zui_t) => {
-							if (project_fonts.length > 1 && ui_menu_button(ui, tr("Delete"), "delete") && project_fonts[i].file != "") {
-								TabFonts.tab_fonts_delete_font(project_fonts[i]);
-							}
-						}, 0 + add);
-					}
-					if (ui.is_hovered) {
-						if (img == null) {
-							app_notify_on_init(() => {
-								let _font: SlotFontRaw = context_raw.font;
-								context_raw.font = project_fonts[i];
-								util_render_make_font_preview();
-								context_raw.font = _font;
-							});
-						}
-						else {
-							zui_tooltip_image(img);
-							zui_tooltip(project_fonts[i].name);
-						}
-					}
-
-					if (config_raw.show_asset_names) {
-						ui._x = uix;
-						ui._y += slotw * 0.9;
-						zui_text(project_fonts[i].name, zui_align_t.CENTER);
-						if (ui.is_hovered) zui_tooltip(project_fonts[i].name);
-						ui._y -= slotw * 0.9;
-						if (i == project_fonts.length - 1) {
-							ui._y += j == num - 1 ? imgw : imgw + zui_ELEMENT_H(ui) + zui_ELEMENT_OFFSET(ui);
-						}
-					}
-				}
-
-				ui._y += 6;
-			}
-
-			let in_focus: bool = ui.input_x > ui._window_x && ui.input_x < ui._window_x + ui._window_w &&
-						    	 ui.input_y > ui._window_y && ui.input_y < ui._window_y + ui._window_h;
-			if (in_focus && ui.is_delete_down && project_fonts.length > 1 && context_raw.font.file != "") {
-				ui.is_delete_down = false;
-				TabFonts.tab_fonts_delete_font(context_raw.font);
-			}
-		}
-	}
-
-	static tab_fonts_delete_font = (font: SlotFontRaw) => {
-		let i: i32 = project_fonts.indexOf(font);
-		let _init = () => {
-			context_select_font(i == project_fonts.length - 1 ? i - 1 : i + 1);
-			data_delete_font(project_fonts[i].file);
-			project_fonts.splice(i, 1);
-		}
-		app_notify_on_init(_init);
-		ui_base_hwnds[2].redraws = 2;
-	}
-}
-
-///end

+ 0 - 32
base/Sources/TabHistory.ts

@@ -1,32 +0,0 @@
-
-///if (is_paint || is_sculpt)
-
-class TabHistory {
-
-	static tab_history_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		if (zui_tab(htab, tr("History"))) {
-			for (let i: i32 = 0; i < history_steps.length; ++i) {
-				let active: i32 = history_steps.length - 1 - history_redos;
-				if (i == active) {
-					zui_fill(0, 0, ui._window_w, ui.t.ELEMENT_H, ui.t.HIGHLIGHT_COL);
-				}
-				zui_text(history_steps[i].name);
-				if (ui.is_released) { // Jump to undo step
-					let diff: i32 = i - active;
-					while (diff > 0) {
-						diff--;
-						history_redo();
-					}
-					while (diff < 0) {
-						diff++;
-						history_undo();
-					}
-				}
-				zui_fill(0, 0, (ui._window_w / zui_SCALE(ui) - 2), 1 * zui_SCALE(ui), ui.t.SEPARATOR_COL);
-			}
-		}
-	}
-}
-
-///end

+ 0 - 333
base/Sources/TabMaterials.ts

@@ -1,333 +0,0 @@
-
-///if (is_paint || is_sculpt)
-
-class TabMaterials {
-
-	static tab_materials_draw = (htab: zui_handle_t) => {
-		let mini: bool = config_raw.layout[layout_size_t.SIDEBAR_W] <= ui_base_sidebar_mini_w;
-		mini ? TabMaterials.tab_materials_draw_mini(htab) : TabMaterials.tab_materials_draw_full(htab);
-	}
-
-	static tab_materials_draw_mini = (htab: zui_handle_t) => {
-		zui_set_hovered_tab_name(tr("Materials"));
-
-		zui_begin_sticky();
-		zui_separator(5);
-
-		TabMaterials.tab_materials_button_nodes();
-		TabMaterials.tab_materials_button_new("+");
-
-		zui_end_sticky();
-		zui_separator(3, false);
-		TabMaterials.tab_materials_draw_slots(true);
-	}
-
-	static tab_materials_draw_full = (htab: zui_handle_t) => {
-		if (zui_tab(htab, tr("Materials"))) {
-			zui_begin_sticky();
-			zui_row([1 / 4, 1 / 4, 1 / 4]);
-
-			TabMaterials.tab_materials_button_new(tr("New"));
-			if (zui_button(tr("Import"))) {
-				project_import_material();
-			}
-			TabMaterials.tab_materials_button_nodes();
-
-			zui_end_sticky();
-			zui_separator(3, false);
-			TabMaterials.tab_materials_draw_slots(false);
-		}
-	}
-
-	static tab_materials_button_nodes = () => {
-		let ui: zui_t = ui_base_ui;
-		if (zui_button(tr("Nodes"))) {
-			ui_base_show_material_nodes();
-		}
-		else if (ui.is_hovered) zui_tooltip(tr("Show Node Editor") + ` (${config_keymap.toggle_node_editor})`);
-	}
-
-	static tab_materials_draw_slots = (mini: bool) => {
-		let ui: zui_t = ui_base_ui;
-		let slotw: i32 = math_floor(51 * zui_SCALE(ui));
-		let num: i32 = math_floor(config_raw.layout[layout_size_t.SIDEBAR_W] / slotw);
-
-		for (let row: i32 = 0; row < math_floor(math_ceil(project_materials.length / num)); ++row) {
-			let mult: i32 = config_raw.show_asset_names ? 2 : 1;
-			let ar: f32[] = [];
-			for (let i = 0; i < num * mult; ++i) ar.push(1 / num);
-			zui_row(ar);
-
-			ui._x += 2;
-			let off: f32 = config_raw.show_asset_names ? zui_ELEMENT_OFFSET(ui) * 10.0 : 6;
-			if (row > 0) ui._y += off;
-
-			for (let j: i32 = 0; j < num; ++j) {
-				let imgw: i32 = math_floor(50 * zui_SCALE(ui));
-				let i: i32 = j + row * num;
-				if (i >= project_materials.length) {
-					zui_end_element(imgw);
-					if (config_raw.show_asset_names) zui_end_element(0);
-					continue;
-				}
-				let img: image_t = zui_SCALE(ui) > 1 ? project_materials[i].image : project_materials[i].image_icon;
-				let imgFull: image_t = project_materials[i].image;
-
-				// Highligh selected
-				if (context_raw.material == project_materials[i]) {
-					if (mini) {
-						let w: f32 = ui._w / zui_SCALE(ui);
-						zui_rect(0, -2, w - 2, w - 4, ui.t.HIGHLIGHT_COL, 3);
-					}
-					else {
-						let off: i32 = row % 2 == 1 ? 1 : 0;
-						let w: i32 = 50;
-						if (config_raw.window_scale > 1) w += math_floor(config_raw.window_scale * 2);
-						zui_fill(-1,         -2, w + 3,       2, ui.t.HIGHLIGHT_COL);
-						zui_fill(-1,    w - off, w + 3, 2 + off, ui.t.HIGHLIGHT_COL);
-						zui_fill(-1,         -2,     2,   w + 3, ui.t.HIGHLIGHT_COL);
-						zui_fill(w + 1,      -2,     2,   w + 4, ui.t.HIGHLIGHT_COL);
-					}
-				}
-
-				///if krom_opengl
-				ui.image_invert_y = project_materials[i].preview_ready;
-				///end
-
-				// Draw material icon
-				let uix: f32 = ui._x;
-				let uiy: f32 = ui._y;
-				let tile: i32 = zui_SCALE(ui) > 1 ? 100 : 50;
-				let imgh: f32 = mini ? ui_base_default_sidebar_mini_w * 0.85 * zui_SCALE(ui) : -1.0;
-				let state = project_materials[i].preview_ready ?
-					zui_image(img, 0xffffffff, imgh) :
-					zui_image(resource_get("icons.k"), 0xffffffff, -1.0, tile, tile, tile, tile);
-
-				// Draw material numbers when selecting a material via keyboard shortcut
-				let is_typing: bool = ui.is_typing || ui_view2d_ui.is_typing || ui_nodes_ui.is_typing;
-				if (!is_typing) {
-					if (i < 9 && operator_shortcut(config_keymap.select_material, shortcut_type_t.DOWN)) {
-						let number: string = String(i + 1);
-						let width: i32 = g2_font_width(ui.font, ui.font_size, number) + 10;
-						let height: i32 = g2_font_height(ui.font, ui.font_size);
-						g2_set_color(ui.t.TEXT_COL);
-						g2_fill_rect(uix, uiy, width, height);
-						g2_set_color(ui.t.ACCENT_COL);
-						g2_draw_string(number, uix + 5, uiy);
-					}
-				}
-
-				// Select material
-				if (state == zui_state_t.STARTED && ui.input_y > ui._window_y) {
-					if (context_raw.material != project_materials[i]) {
-						context_select_material(i);
-						///if is_paint
-						if (context_raw.tool == workspace_tool_t.MATERIAL) {
-							let _init = () => {
-								base_update_fill_layers();
-							}
-							app_notify_on_init(_init);
-						}
-						///end
-					}
-					base_drag_off_x = -(mouse_x - uix - ui._window_x - 3);
-					base_drag_off_y = -(mouse_y - uiy - ui._window_y + 1);
-					base_drag_material = context_raw.material;
-					// Double click to show nodes
-					if (time_time() - context_raw.select_time < 0.25) {
-						ui_base_show_material_nodes();
-						base_drag_material = null;
-						base_is_dragging = false;
-					}
-					context_raw.select_time = time_time();
-				}
-
-				// Context menu
-				if (ui.is_hovered && ui.input_released_r) {
-					context_select_material(i);
-					let add: i32 = project_materials.length > 1 ? 1 : 0;
-
-					ui_menu_draw((ui: zui_t) => {
-						let m: SlotMaterialRaw = project_materials[i];
-
-						if (ui_menu_button(ui, tr("To Fill Layer"))) {
-							context_select_material(i);
-							base_create_fill_layer();
-						}
-
-						if (ui_menu_button(ui, tr("Export"))) {
-							context_select_material(i);
-							box_export_show_material();
-						}
-
-						///if is_paint
-						if (ui_menu_button(ui, tr("Bake"))) {
-							context_select_material(i);
-							box_export_show_bake_material();
-						}
-						///end
-
-						if (ui_menu_button(ui, tr("Duplicate"))) {
-							let _init = () => {
-								context_raw.material = SlotMaterial.slot_material_create(project_materials[0].data);
-								project_materials.push(context_raw.material);
-								let cloned: zui_node_canvas_t = json_parse(json_stringify(project_materials[i].canvas));
-								context_raw.material.canvas = cloned;
-								TabMaterials.tab_materials_update_material();
-								history_duplicate_material();
-							}
-							app_notify_on_init(_init);
-						}
-
-						if (project_materials.length > 1 && ui_menu_button(ui, tr("Delete"), "delete")) {
-							TabMaterials.tab_materials_delete_material(m);
-						}
-
-						let base_handle: zui_handle_t = zui_nest(zui_handle("tabmaterials_0"), m.id, {selected: m.paint_base});
-						let opac_handle: zui_handle_t = zui_nest(zui_handle("tabmaterials_1"), m.id, {selected: m.paint_opac});
-						let nor_handle: zui_handle_t = zui_nest(zui_handle("tabmaterials_2"), m.id, {selected: m.paint_nor});
-						let occ_handle: zui_handle_t = zui_nest(zui_handle("tabmaterials_3"), m.id, {selected: m.paint_occ});
-						let rough_handle: zui_handle_t = zui_nest(zui_handle("tabmaterials_4"), m.id, {selected: m.paint_rough});
-						let met_handle: zui_handle_t = zui_nest(zui_handle("tabmaterials_5"), m.id, {selected: m.paint_met});
-						let height_handle: zui_handle_t = zui_nest(zui_handle("tabmaterials_6"), m.id, {selected: m.paint_height});
-						let emis_handle: zui_handle_t = zui_nest(zui_handle("tabmaterials_7"), m.id, {selected: m.paint_emis});
-						let subs_handle: zui_handle_t = zui_nest(zui_handle("tabmaterials_8"), m.id, {selected: m.paint_subs});
-						ui_menu_fill(ui);
-						m.paint_base = zui_check(base_handle, tr("Base Color"));
-						ui_menu_fill(ui);
-						m.paint_opac = zui_check(opac_handle, tr("Opacity"));
-						ui_menu_fill(ui);
-						m.paint_nor = zui_check(nor_handle, tr("Normal"));
-						ui_menu_fill(ui);
-						m.paint_occ = zui_check(occ_handle, tr("Occlusion"));
-						ui_menu_fill(ui);
-						m.paint_rough = zui_check(rough_handle, tr("Roughness"));
-						ui_menu_fill(ui);
-						m.paint_met = zui_check(met_handle, tr("Metallic"));
-						ui_menu_fill(ui);
-						m.paint_height = zui_check(height_handle, tr("Height"));
-						ui_menu_fill(ui);
-						m.paint_emis = zui_check(emis_handle, tr("Emission"));
-						ui_menu_fill(ui);
-						m.paint_subs = zui_check(subs_handle, tr("Subsurface"));
-						if (base_handle.changed ||
-							opac_handle.changed ||
-							nor_handle.changed ||
-							occ_handle.changed ||
-							rough_handle.changed ||
-							met_handle.changed ||
-							height_handle.changed ||
-							emis_handle.changed ||
-							subs_handle.changed) {
-							MakeMaterial.make_material_parse_paint_material();
-							ui_menu_keep_open = true;
-						}
-					}, 13 + add);
-				}
-				if (ui.is_hovered) {
-					zui_tooltip_image(imgFull);
-					if (i < 9) zui_tooltip(project_materials[i].canvas.name + " - (" + config_keymap.select_material + " " + (i + 1) + ")");
-					else zui_tooltip(project_materials[i].canvas.name);
-				}
-
-				if (config_raw.show_asset_names) {
-					ui._x = uix;
-					ui._y += slotw * 0.9;
-					zui_text(project_materials[i].canvas.name, zui_align_t.CENTER);
-					if (ui.is_hovered) {
-						if (i < 9) zui_tooltip(project_materials[i].canvas.name + " - (" + config_keymap.select_material + " " + (i + 1) + ")");
-						else zui_tooltip(project_materials[i].canvas.name);
-					}
-					ui._y -= slotw * 0.9;
-					if (i == project_materials.length - 1) {
-						ui._y += j == num - 1 ? imgw : imgw + zui_ELEMENT_H(ui) + zui_ELEMENT_OFFSET(ui);
-					}
-				}
-			}
-
-			ui._y += mini ? 0 : 6;
-
-			///if krom_opengl
-			ui.image_invert_y = false; // Material preview
-			///end
-		}
-
-		let in_focus: bool = ui.input_x > ui._window_x && ui.input_x < ui._window_x + ui._window_w &&
-					    	 ui.input_y > ui._window_y && ui.input_y < ui._window_y + ui._window_h;
-		if (in_focus && ui.is_delete_down && project_materials.length > 1) {
-			ui.is_delete_down = false;
-			TabMaterials.tab_materials_delete_material(context_raw.material);
-		}
-	}
-
-	static tab_materials_button_new = (text: string) => {
-		if (zui_button(text)) {
-			let current: image_t = _g2_current;
-			g2_end();
-			context_raw.material = SlotMaterial.slot_material_create(project_materials[0].data);
-			project_materials.push(context_raw.material);
-			TabMaterials.tab_materials_update_material();
-			g2_begin(current);
-			history_new_material();
-		}
-	}
-
-	static tab_materials_update_material = () => {
-		ui_header_handle.redraws = 2;
-		ui_nodes_hwnd.redraws = 2;
-		ui_nodes_group_stack = [];
-		MakeMaterial.make_material_parse_paint_material();
-		util_render_make_material_preview();
-		let decal: bool = context_raw.tool == workspace_tool_t.DECAL || context_raw.tool == workspace_tool_t.TEXT;
-		if (decal) util_render_make_decal_preview();
-	}
-
-	static tab_materials_update_material_pointers = (nodes: zui_node_t[], i: i32) => {
-		for (let n of nodes) {
-			if (n.type == "MATERIAL") {
-				if (n.buttons[0].default_value == i) {
-					n.buttons[0].default_value = 9999; // Material deleted
-				}
-				else if (n.buttons[0].default_value > i) {
-					n.buttons[0].default_value--; // Offset by deleted material
-				}
-			}
-		}
-	}
-
-	static tab_materials_accept_swatch_drag = (swatch: swatch_color_t) => {
-		context_raw.material = SlotMaterial.slot_material_create(project_materials[0].data);
-		for (let node of context_raw.material.canvas.nodes) {
-			if (node.type == "RGB" ) {
-				node.outputs[0].default_value = [
-					color_get_rb(swatch.base) / 255,
-					color_get_gb(swatch.base) / 255,
-					color_get_bb(swatch.base) / 255,
-					color_get_ab(swatch.base) / 255
-				];
-			}
-			else if (node.type == "OUTPUT_MATERIAL_PBR") {
-				node.inputs[1].default_value = swatch.opacity;
-				node.inputs[2].default_value = swatch.occlusion;
-				node.inputs[3].default_value = swatch.roughness;
-				node.inputs[4].default_value = swatch.metallic;
-				node.inputs[7].default_value = swatch.height;
-			}
-		}
-		project_materials.push(context_raw.material);
-		TabMaterials.tab_materials_update_material();
-		history_new_material();
-	}
-
-	static tab_materials_delete_material = (m: SlotMaterialRaw) => {
-		let i: i32 = project_materials.indexOf(m);
-		for (let l of project_layers) if (l.fill_layer == m) l.fill_layer = null;
-		history_delete_material();
-		context_select_material(i == project_materials.length - 1 ? i - 1 : i + 1);
-		project_materials.splice(i, 1);
-		ui_base_hwnds[1].redraws = 2;
-		for (let m of project_materials) TabMaterials.tab_materials_update_material_pointers(m.canvas.nodes, i);
-	}
-}
-
-///end

+ 0 - 187
base/Sources/TabMeshes.ts

@@ -1,187 +0,0 @@
-
-class TabMeshes {
-
-	static tab_meshes_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-		if (zui_tab(htab, tr("Meshes")) && statush > ui_status_default_status_h * zui_SCALE(ui)) {
-
-			zui_begin_sticky();
-
-			///if (is_paint || is_sculpt)
-			if (config_raw.touch_ui) {
-				zui_row([1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6, 1 / 6]);
-			}
-			else {
-				zui_row([1 / 14, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 14]);
-			}
-			///end
-
-			///if is_lab
-			if (config_raw.touch_ui) {
-				zui_row([1 / 7, 1 / 7, 1 / 7, 1 / 7, 1 / 7, 1 / 7, 1 / 7]);
-			}
-			else {
-				zui_row([1 / 14, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 14]);
-			}
-			///end
-
-			if (zui_button(tr("Import"))) {
-				ui_menu_draw((ui: zui_t) => {
-					if (ui_menu_button(ui, tr("Replace Existing"), `${config_keymap.file_import_assets}`)) {
-						project_import_mesh(true);
-					}
-					if (ui_menu_button(ui, tr("Append"))) {
-						project_import_mesh(false);
-					}
-				}, 2);
-			}
-			if (ui.is_hovered) zui_tooltip(tr("Import mesh file"));
-
-			///if is_lab
-			if (zui_button(tr("Set Default"))) {
-				ui_menu_draw((ui: zui_t) => {
-					if (ui_menu_button(ui, tr("Cube"))) TabMeshes.tab_meshes_set_default_mesh(".Cube");
-					if (ui_menu_button(ui, tr("Plane"))) TabMeshes.tab_meshes_set_default_mesh(".Plane");
-					if (ui_menu_button(ui, tr("Sphere"))) TabMeshes.tab_meshes_set_default_mesh(".Sphere");
-					if (ui_menu_button(ui, tr("Cylinder"))) TabMeshes.tab_meshes_set_default_mesh(".Cylinder");
-				}, 4);
-			}
-			///end
-
-			if (zui_button(tr("Flip Normals"))) {
-				util_mesh_flip_normals();
-				context_raw.ddirty = 2;
-			}
-
-			if (zui_button(tr("Calculate Normals"))) {
-				ui_menu_draw((ui: zui_t) => {
-					if (ui_menu_button(ui, tr("Smooth"))) { util_mesh_calc_normals(true); context_raw.ddirty = 2; }
-					if (ui_menu_button(ui, tr("Flat"))) { util_mesh_calc_normals(false); context_raw.ddirty = 2; }
-				}, 2);
-			}
-
-			if (zui_button(tr("Geometry to Origin"))) {
-				util_mesh_to_origin();
-				context_raw.ddirty = 2;
-			}
-
-			if (zui_button(tr("Apply Displacement"))) {
-				///if is_paint
-				util_mesh_apply_displacement(project_layers[0].texpaint_pack);
-				///end
-				///if is_lab
-				let displace_strength: f32 = config_raw.displace_strength > 0 ? config_raw.displace_strength : 1.0;
-				let uv_scale: f32 = scene_meshes[0].data.scale_tex * context_raw.brush_scale;
-				util_mesh_apply_displacement(BrushOutputNode.inst.texpaint_pack, 0.05 * displace_strength, uv_scale);
-				///end
-
-				util_mesh_calc_normals();
-				context_raw.ddirty = 2;
-			}
-
-			if (zui_button(tr("Rotate"))) {
-				ui_menu_draw((ui: zui_t) => {
-					if (ui_menu_button(ui, tr("Rotate X"))) {
-						util_mesh_swap_axis(1, 2);
-						context_raw.ddirty = 2;
-					}
-
-					if (ui_menu_button(ui, tr("Rotate Y"))) {
-						util_mesh_swap_axis(2, 0);
-						context_raw.ddirty = 2;
-					}
-
-					if (ui_menu_button(ui, tr("Rotate Z"))) {
-						util_mesh_swap_axis(0, 1);
-						context_raw.ddirty = 2;
-					}
-				}, 3);
-			}
-
-			zui_end_sticky();
-
-			for (let i: i32 = 0; i < project_paint_objects.length; ++i) {
-				let o: mesh_object_t = project_paint_objects[i];
-				let h: zui_handle_t = zui_handle("tabmeshes_0");
-				h.selected = o.base.visible;
-				o.base.visible = zui_check(h, o.base.name);
-				if (ui.is_hovered && ui.input_released_r) {
-					ui_menu_draw((ui: zui_t) => {
-						if (ui_menu_button(ui, tr("Export"))) {
-							context_raw.export_mesh_index = i + 1;
-							box_export_show_mesh();
-						}
-						if (project_paint_objects.length > 1 && ui_menu_button(ui, tr("Delete"))) {
-							array_remove(project_paint_objects, o);
-							while (o.base.children.length > 0) {
-								let child: object_t = o.base.children[0];
-								object_set_parent(child, null);
-								if (project_paint_objects[0].base != child) {
-									object_set_parent(child, project_paint_objects[0].base);
-								}
-								if (o.base.children.length == 0) {
-									vec4_set_from(project_paint_objects[0].base.transform.scale, o.base.transform.scale);
-									transform_build_matrix(project_paint_objects[0].base.transform);
-								}
-							}
-							data_delete_mesh(o.data._.handle);
-							mesh_object_remove(o);
-							context_raw.paint_object = context_main_object();
-							util_mesh_merge();
-							context_raw.ddirty = 2;
-						}
-					}, project_paint_objects.length > 1 ? 2 : 1);
-				}
-				if (h.changed) {
-					let visibles: mesh_object_t[] = [];
-					for (let p of project_paint_objects) if (p.base.visible) visibles.push(p);
-					util_mesh_merge(visibles);
-					context_raw.ddirty = 2;
-				}
-			}
-		}
-	}
-
-	///if is_lab
-	static tab_meshes_set_default_mesh = (name: string) => {
-		let mo: mesh_object_t = null;
-		if (name == ".Plane" || name == ".Sphere") {
-			let res: i32 = config_raw.rp_supersample > 1.0 ? 2048 : 1024;
-			let mesh: any = name == ".Plane" ? geom_make_plane(1, 1, res, res) : geom_make_uv_sphere(1.0, res, math_floor(res / 2), false, 2.0);
-			let raw: any = {
-				name: "Tessellated",
-				vertex_arrays: [
-					{ values: mesh.posa, attrib: "pos", data: "short4norm" },
-					{ values: mesh.nora, attrib: "nor", data: "short2norm" },
-					{ values: mesh.texa, attrib: "tex", data: "short2norm" }
-				],
-				index_arrays: [
-					{ values: mesh.inda, material: 0 }
-				],
-				scale_pos: mesh.scale_pos,
-				scale_tex: mesh.scale_tex
-			};
-			let md: mesh_data_t = mesh_data_create(raw);
-			mo = mesh_object_create(md, context_raw.paint_object.materials);
-			array_remove(scene_meshes, mo);
-			mo.base.name = "Tessellated";
-		}
-		else {
-			mo = scene_get_child(name).ext;
-		}
-
-		mo.base.visible = true;
-		context_raw.ddirty = 2;
-		context_raw.paint_object = mo;
-		project_paint_objects[0] = mo;
-		if (ui_header_worktab.position == space_type_t.SPACE3D) {
-			scene_meshes = [mo];
-		}
-
-		///if (krom_direct3d12 || krom_vulkan || krom_metal)
-		render_path_raytrace_ready = false;
-		///end
-	}
-	///end
-}

+ 0 - 18
base/Sources/TabParticles.ts

@@ -1,18 +0,0 @@
-
-///if (is_paint || is_sculpt)
-
-class TabParticles {
-
-	static tab_particles_draw = (htab: zui_handle_t) => {
-		if (zui_tab(htab, tr("Particles"))) {
-			zui_begin_sticky();
-			zui_row([1 / 4, 1 / 4, 1 / 4]);
-			if (zui_button(tr("New"))) {}
-			if (zui_button(tr("Import"))) {}
-			if (zui_button(tr("Nodes"))) {}
-			zui_end_sticky();
-		}
-	}
-}
-
-///end

+ 0 - 27
base/Sources/TabPlugins.ts

@@ -1,27 +0,0 @@
-
-class TabPlugins {
-
-	static tab_plugins_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		if (zui_tab(htab, tr("Plugins"))) {
-
-			zui_begin_sticky();
-
-			///if (is_paint || is_sculpt)
-			zui_row([1 / 4]);
-			///end
-			///if is_lab
-			zui_row([1 / 14]);
-			///end
-
-			if (zui_button(tr("Manager"))) {
-				box_preferences_htab.position = 6; // Plugins
-				box_preferences_show();
-			}
-			zui_end_sticky();
-
-			// Draw plugins
-			for (let p of plugin_map.values()) if (p.draw_ui != null) p.draw_ui(ui);
-		}
-	}
-}

+ 0 - 77
base/Sources/TabScript.ts

@@ -1,77 +0,0 @@
-
-class TabScript {
-
-	static tab_script_hscript: zui_handle_t = zui_handle_create();
-	static tab_script_text_coloring: zui_text_coloring_t = null;
-
-	static tab_script_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-		if (zui_tab(htab, tr("Script")) && statush > ui_status_default_status_h * zui_SCALE(ui)) {
-
-			zui_begin_sticky();
-			if (config_raw.touch_ui) {
-				zui_row([1 / 4, 1 / 4, 1 / 4, 1 / 4]);
-			}
-			else {
-				zui_row([1 / 14, 1 / 14, 1 / 14, 1 / 14]);
-			}
-			if (zui_button(tr("Run"))) {
-				try {
-					eval(TabScript.tab_script_hscript.text);
-				}
-				catch(e: any) {
-					console_log(e);
-				}
-			}
-			if (zui_button(tr("Clear"))) {
-				TabScript.tab_script_hscript.text = "";
-			}
-			if (zui_button(tr("Import"))) {
-				ui_files_show("js", false, false, (path: string) => {
-					let b: ArrayBuffer = data_get_blob(path);
-					TabScript.tab_script_hscript.text = sys_buffer_to_string(b);
-					data_delete_blob(path);
-				});
-			}
-			if (zui_button(tr("Export"))) {
-				let str: string = TabScript.tab_script_hscript.text;
-				ui_files_show("js", true, false, (path: string) => {
-					let f: string = ui_files_filename;
-					if (f == "") f = tr("untitled");
-					path = path + path_sep + f;
-					if (!path.endsWith(".js")) path += ".js";
-					krom_file_save_bytes(path, sys_string_to_buffer(str));
-				});
-			}
-			zui_end_sticky();
-
-			let _font: g2_font_t = ui.font;
-			let _font_size: i32 = ui.font_size;
-			let f: g2_font_t = data_get_font("font_mono.ttf");
-			zui_set_font(ui, f);
-			ui.font_size = math_floor(15 * zui_SCALE(ui));
-			zui_set_text_area_line_numbers(true);
-			zui_set_text_area_scroll_past_end(true);
-			zui_set_text_area_coloring(TabScript.tab_script_get_text_coloring());
-			zui_text_area(TabScript.tab_script_hscript);
-			zui_set_text_area_line_numbers(false);
-			zui_set_text_area_scroll_past_end(false);
-			zui_set_text_area_coloring(null);
-			zui_set_font(ui, _font);
-			ui.font_size = _font_size;
-		}
-	}
-
-	static tab_script_get_text_coloring = (): zui_text_coloring_t => {
-		if (TabScript.tab_script_text_coloring == null) {
-			let blob: ArrayBuffer = data_get_blob("text_coloring.json");
-			TabScript.tab_script_text_coloring = json_parse(sys_buffer_to_string(blob));
-			TabScript.tab_script_text_coloring.default_color = math_floor(TabScript.tab_script_text_coloring.default_color);
-			for (let coloring of TabScript.tab_script_text_coloring.colorings) {
-				coloring.color = math_floor(coloring.color);
-			}
-		}
-		return TabScript.tab_script_text_coloring;
-	}
-}

+ 0 - 253
base/Sources/TabSwatches.ts

@@ -1,253 +0,0 @@
-
-class TabSwatches {
-
-	static _tab_swatches_empty: image_t;
-	static tab_swatches_drag_pos: i32 = -1;
-
-	static set tab_swatches_empty(image: image_t) {
-		TabSwatches._tab_swatches_empty = image;
-	}
-
-	static get tab_swatches_empty(): image_t {
-		if (TabSwatches._tab_swatches_empty == null) {
-			let b: Uint8Array = new Uint8Array(4);
-			b[0] = 255;
-			b[1] = 255;
-			b[2] = 255;
-			b[3] = 255;
-			TabSwatches._tab_swatches_empty = image_from_bytes(b.buffer, 1, 1);
-		}
-		return TabSwatches._tab_swatches_empty;
-	}
-
-	static tab_swatches_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-		if (zui_tab(htab, tr("Swatches")) && statush > ui_status_default_status_h * zui_SCALE(ui)) {
-
-			zui_begin_sticky();
-			if (config_raw.touch_ui) {
-				zui_row([1 / 5, 1 / 5, 1 / 5, 1 / 5, 1 / 5]);
-			}
-			else {
-				zui_row([1 / 14, 1 / 14, 1 / 14, 1 / 14, 1 / 14]);
-			}
-
-			if (zui_button(tr("New"))) {
-				context_set_swatch(make_swatch());
-				project_raw.swatches.push(context_raw.swatch);
-			}
-			if (ui.is_hovered) zui_tooltip(tr("Add new swatch"));
-
-			if (zui_button(tr("Import"))) {
-				ui_menu_draw((ui: zui_t) => {
-					if (ui_menu_button(ui, tr("Replace Existing"))) {
-						project_import_swatches(true);
-						context_set_swatch(project_raw.swatches[0]);
-					}
-					if (ui_menu_button(ui, tr("Append"))) {
-						project_import_swatches(false);
-					}
-				}, 2);
-			}
-			if (ui.is_hovered) zui_tooltip(tr("Import swatches"));
-
-			if (zui_button(tr("Export"))) project_export_swatches();
-			if (ui.is_hovered) zui_tooltip(tr("Export swatches"));
-
-			if (zui_button(tr("Clear"))) {
-				context_set_swatch(make_swatch());
-				project_raw.swatches = [context_raw.swatch];
-			}
-
-			if (zui_button(tr("Restore"))) {
-				project_set_default_swatches();
-				context_set_swatch(project_raw.swatches[0]);
-			}
-			if (ui.is_hovered) zui_tooltip(tr("Restore default swatches"));
-
-			zui_end_sticky();
-			zui_separator(3, false);
-
-			let slotw: i32 = math_floor(26 * zui_SCALE(ui));
-			let num: i32 = math_floor(ui._w / (slotw + 3));
-			let drag_pos_set: bool = false;
-
-			let uix: f32 = 0.0;
-			let uiy: f32 = 0.0;
-			for (let row: i32 = 0; row < math_floor(math_ceil(project_raw.swatches.length / num)); ++row) {
-				let ar: f32[] = [];
-				for (let i: i32 = 0; i < num; ++i) ar.push(1 / num);
-				zui_row(ar);
-
-				ui._x += 2;
-				if (row > 0) ui._y += 6;
-
-				for (let j: i32 = 0; j < num; ++j) {
-					let i: i32 = j + row * num;
-					if (i >= project_raw.swatches.length) {
-						zui_end_element(slotw);
-						continue;
-					}
-
-					if (context_raw.swatch == project_raw.swatches[i]) {
-						let w: i32 = 32;
-						zui_fill(-2, -2, w, w, ui.t.HIGHLIGHT_COL);
-					}
-
-					uix = ui._x;
-					uiy = ui._y;
-
-					// Draw the drag position indicator
-					if (base_drag_swatch != null && TabSwatches.tab_swatches_drag_pos == i) {
-						zui_fill(-1, -2 , 2, 32, ui.t.HIGHLIGHT_COL);
-					}
-
-					let state: zui_state_t = zui_image(TabSwatches.tab_swatches_empty, project_raw.swatches[i].base, slotw);
-
-					if (state == zui_state_t.STARTED) {
-						context_set_swatch(project_raw.swatches[i]);
-
-						base_drag_off_x = -(mouse_x - uix - ui._window_x - 2 * slotw);
-						base_drag_off_y = -(mouse_y - uiy - ui._window_y + 1);
-						base_drag_swatch = context_raw.swatch;
-					}
-					else if (state == zui_state_t.HOVERED) {
-						TabSwatches.tab_swatches_drag_pos = (mouse_x > uix + ui._window_x + slotw / 2) ? i + 1 : i; // Switch to the next position if the mouse crosses the swatch rectangle center
-						drag_pos_set = true;
-					}
-					else if (state == zui_state_t.RELEASED) {
-						if (time_time() - context_raw.select_time < 0.25) {
-							ui_menu_draw((ui: zui_t) => {
-								ui.changed = false;
-								let h: zui_handle_t = zui_handle("tabswatches_0");
-								h.color = context_raw.swatch.base;
-
-								context_raw.swatch.base = zui_color_wheel(h, false, null, 11 * ui.t.ELEMENT_H * zui_SCALE(ui), true, () => {
-									context_raw.color_picker_previous_tool = context_raw.tool;
-									context_select_tool(workspace_tool_t.PICKER);
-									context_raw.color_picker_callback = (color: swatch_color_t) => {
-										project_raw.swatches[i] = project_clone_swatch(color);
-									};
-								});
-								let hopacity: zui_handle_t = zui_handle("tabswatches_1");
-								hopacity.value = context_raw.swatch.opacity;
-								context_raw.swatch.opacity = zui_slider(hopacity, "Opacity", 0, 1, true);
-								let hocclusion: zui_handle_t = zui_handle("tabswatches_2");
-								hocclusion.value = context_raw.swatch.occlusion;
-								context_raw.swatch.occlusion = zui_slider(hocclusion, "Occlusion", 0, 1, true);
-								let hroughness: zui_handle_t = zui_handle("tabswatches_3");
-								hroughness.value = context_raw.swatch.roughness;
-								context_raw.swatch.roughness = zui_slider(hroughness, "Roughness", 0, 1, true);
-								let hmetallic: zui_handle_t = zui_handle("tabswatches_4");
-								hmetallic.value = context_raw.swatch.metallic;
-								context_raw.swatch.metallic = zui_slider(hmetallic, "Metallic", 0, 1, true);
-								let hheight: zui_handle_t = zui_handle("tabswatches_5");
-								hheight.value = context_raw.swatch.height;
-								context_raw.swatch.height = zui_slider(hheight, "Height", 0, 1, true);
-
-								if (ui.changed || ui.is_typing) ui_menu_keep_open = true;
-								if (ui.input_released) context_set_swatch(context_raw.swatch); // Trigger material preview update
-							}, 16, math_floor(mouse_x - 200 * zui_SCALE(ui)), math_floor(mouse_y - 250 * zui_SCALE(ui)));
-						}
-
-						context_raw.select_time = time_time();
-					}
-					if (ui.is_hovered && ui.input_released_r) {
-						context_set_swatch(project_raw.swatches[i]);
-						let add: i32 = project_raw.swatches.length > 1 ? 1 : 0;
-						///if (krom_windows || krom_linux || krom_darwin)
-						add += 1; // Copy
-						///end
-
-						///if (is_paint || is_sculpt)
-						add += 3;
-						///end
-						///if is_lav
-						add += 1;
-						///end
-
-						ui_menu_draw((ui: zui_t) => {
-							if (ui_menu_button(ui, tr("Duplicate"))) {
-								context_set_swatch(project_clone_swatch(context_raw.swatch));
-								project_raw.swatches.push(context_raw.swatch);
-							}
-							///if (krom_windows || krom_linux || krom_darwin)
-							else if (ui_menu_button(ui, tr("Copy Hex Code"))) {
-								let color: i32 = context_raw.swatch.base;
-								color = color_set_ab(color, context_raw.swatch.opacity * 255);
-								let val: i32 = color;
-								if (val < 0) val += 4294967296;
-								krom_copy_to_clipboard(val.toString(16));
-							}
-							///end
-							else if (project_raw.swatches.length > 1 && ui_menu_button(ui, tr("Delete"), "delete")) {
-								TabSwatches.tab_swatches_delete_swatch(project_raw.swatches[i]);
-							}
-							///if (is_paint || is_sculpt)
-							else if (ui_menu_button(ui, tr("Create Material"))) {
-								TabMaterials.tab_materials_accept_swatch_drag(project_raw.swatches[i]);
-							}
-							else if (ui_menu_button(ui, tr("Create Color Layer"))) {
-								let color: i32 = project_raw.swatches[i].base;
-								color = color_set_ab(color, project_raw.swatches[i].opacity * 255);
-								base_create_color_layer(color, project_raw.swatches[i].occlusion, project_raw.swatches[i].roughness, project_raw.swatches[i].metallic);
-							}
-							///end
-						}, add);
-					}
-					if (ui.is_hovered) {
-						let color: i32 = project_raw.swatches[i].base;
-						color = color_set_ab(color, project_raw.swatches[i].opacity * 255);
-						let val: i32 = color;
-						if (val < 0) val += 4294967296;
-						zui_tooltip("#" + val.toString(16));
-					}
-				}
-			}
-
-			// Draw the rightmost line next to the last swatch
-			if (base_drag_swatch != null && TabSwatches.tab_swatches_drag_pos == project_raw.swatches.length) {
-				ui._x = uix; // Reset the position because otherwise it would start in the row below
-				ui._y = uiy;
-				zui_fill(28, -2, 2, 32, ui.t.HIGHLIGHT_COL);
-			}
-
-			// Currently there is no valid dragPosition so reset it
-			if (!drag_pos_set) {
-				TabSwatches.tab_swatches_drag_pos = -1;
-			}
-
-			let in_focus: bool = ui.input_x > ui._window_x && ui.input_x < ui._window_x + ui._window_w &&
-								 ui.input_y > ui._window_y && ui.input_y < ui._window_y + ui._window_h;
-			if (in_focus && ui.is_delete_down && project_raw.swatches.length > 1) {
-				ui.is_delete_down = false;
-				TabSwatches.tab_swatches_delete_swatch(context_raw.swatch);
-			}
-		}
-	}
-
-	static tab_swatches_accept_swatch_drag = (swatch: swatch_color_t) => {
-		// No valid position available
-		if (TabSwatches.tab_swatches_drag_pos == -1) return;
-
-		let swatch_pos: i32 = project_raw.swatches.indexOf(swatch);
-		// A new swatch from color picker
-		if (swatch_pos == -1) {
-			project_raw.swatches.splice(TabSwatches.tab_swatches_drag_pos, 0, swatch);
-		}
-		else if (math_abs(swatch_pos - TabSwatches.tab_swatches_drag_pos) > 0) { // Existing swatch is reordered
-			array_remove(project_raw.swatches, swatch);
-			// If the new position is after the old one, decrease by one because the swatch has been deleted
-			let new_pos: i32 = TabSwatches.tab_swatches_drag_pos - swatch_pos > 0 ? TabSwatches.tab_swatches_drag_pos -1 : TabSwatches.tab_swatches_drag_pos;
-			project_raw.swatches.splice(new_pos, 0, swatch);
-		}
-	}
-
-	static tab_swatches_delete_swatch = (swatch: swatch_color_t) => {
-		let i: i32 = project_raw.swatches.indexOf(swatch);
-		context_set_swatch(project_raw.swatches[i == project_raw.swatches.length - 1 ? i - 1 : i + 1]);
-		project_raw.swatches.splice(i, 1);
-		ui_base_hwnds[tab_area_t.STATUS].redraws = 2;
-	}
-}

+ 0 - 268
base/Sources/TabTextures.ts

@@ -1,268 +0,0 @@
-
-class TabTextures {
-
-	static tab_textures_draw = (htab: zui_handle_t) => {
-		let ui: zui_t = ui_base_ui;
-		let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
-		if (zui_tab(htab, tr("Textures")) && statush > ui_status_default_status_h * zui_SCALE(ui)) {
-
-			zui_begin_sticky();
-
-			if (config_raw.touch_ui) {
-				zui_row([1 / 4, 1 / 4]);
-			}
-			else {
-				zui_row([1 / 14, 1 / 14]);
-			}
-
-			if (zui_button(tr("Import"))) {
-				ui_files_show(path_texture_formats.join(","), false, true, (path: string) => {
-					import_asset_run(path, -1.0, -1.0, true, false);
-					ui_base_hwnds[tab_area_t.STATUS].redraws = 2;
-				});
-			}
-			if (ui.is_hovered) zui_tooltip(tr("Import texture file") + ` (${config_keymap.file_import_assets})`);
-
-			if (zui_button(tr("2D View"))) ui_base_show_2d_view(view_2d_type_t.ASSET);
-
-			zui_end_sticky();
-
-			if (project_assets.length > 0) {
-
-				///if (is_paint || is_sculpt)
-				let statusw: i32 = sys_width() - ui_toolbar_w - config_raw.layout[layout_size_t.SIDEBAR_W];
-				///end
-				///if is_lab
-				let statusw: i32 = sys_width();
-				///end
-
-				let slotw: i32 = math_floor(52 * zui_SCALE(ui));
-				let num: i32 = math_floor(statusw / slotw);
-
-				for (let row: i32 = 0; row < math_floor(math_ceil(project_assets.length / num)); ++row) {
-					let mult: i32 = config_raw.show_asset_names ? 2 : 1;
-					let ar: f32[] = [];
-					for (let i: i32 = 0; i < num * mult; ++i) ar.push(1 / num);
-					zui_row(ar);
-
-					ui._x += 2;
-					let off: f32 = config_raw.show_asset_names ? zui_ELEMENT_OFFSET(ui) * 10.0 : 6;
-					if (row > 0) ui._y += off;
-
-					for (let j: i32 = 0; j < num; ++j) {
-						let imgw: i32 = math_floor(50 * zui_SCALE(ui));
-						let i: i32 = j + row * num;
-						if (i >= project_assets.length) {
-							zui_end_element(imgw);
-							if (config_raw.show_asset_names) zui_end_element(0);
-							continue;
-						}
-
-						let asset: asset_t = project_assets[i];
-						let img: image_t = project_get_image(asset);
-						let uix: f32 = ui._x;
-						let uiy: f32 = ui._y;
-						let sw: i32 = img.height < img.width ? img.height : 0;
-						if (zui_image(img, 0xffffffff, slotw, 0, 0, sw, sw) == zui_state_t.STARTED && ui.input_y > ui._window_y) {
-							base_drag_off_x = -(mouse_x - uix - ui._window_x - 3);
-							base_drag_off_y = -(mouse_y - uiy - ui._window_y + 1);
-							base_drag_asset = asset;
-							context_raw.texture = asset;
-
-							if (time_time() - context_raw.select_time < 0.25) ui_base_show_2d_view(view_2d_type_t.ASSET);
-							context_raw.select_time = time_time();
-							ui_view2d_hwnd.redraws = 2;
-						}
-
-						if (asset == context_raw.texture) {
-							let _uix: f32 = ui._x;
-							let _uiy: f32 = ui._y;
-							ui._x = uix;
-							ui._y = uiy;
-							let off: i32 = i % 2 == 1 ? 1 : 0;
-							let w: i32 = 50;
-							zui_fill(0,               0, w + 3,       2, ui.t.HIGHLIGHT_COL);
-							zui_fill(0,     w - off + 2, w + 3, 2 + off, ui.t.HIGHLIGHT_COL);
-							zui_fill(0,               0,     2,   w + 3, ui.t.HIGHLIGHT_COL);
-							zui_fill(w + 2,           0,     2,   w + 4, ui.t.HIGHLIGHT_COL);
-							ui._x = _uix;
-							ui._y = _uiy;
-						}
-
-						let is_packed: bool = project_raw.packed_assets != null && project_packed_asset_exists(project_raw.packed_assets, asset.file);
-
-						if (ui.is_hovered) {
-							zui_tooltip_image(img, 256);
-							zui_tooltip(asset.name + (is_packed ? " " + tr("(packed)") : ""));
-						}
-
-						if (ui.is_hovered && ui.input_released_r) {
-							context_raw.texture = asset;
-
-							let count: i32 = 0;
-
-							///if (is_paint || is_sculpt)
-							count = is_packed ? 6 : 8;
-							///end
-							///if is_lab
-							count = is_packed ? 6 : 6;
-							///end
-
-							ui_menu_draw((ui: zui_t) => {
-								if (ui_menu_button(ui, tr("Export"))) {
-									ui_files_show("png", true, false, (path: string) => {
-										base_notify_on_next_frame(() => {
-											///if (is_paint || is_sculpt)
-											if (base_pipe_merge == null) base_make_pipe();
-											///end
-											///if is_lab
-											if (base_pipe_copy == null) base_make_pipe();
-											///end
-
-											let target: image_t = image_create_render_target(TabTextures.tab_textures_to_pow2(img.width), TabTextures.tab_textures_to_pow2(img.height));
-											g2_begin(target);
-											g2_set_pipeline(base_pipe_copy);
-											g2_draw_scaled_image(img, 0, 0, target.width, target.height);
-											g2_set_pipeline(null);
-											g2_end();
-											base_notify_on_next_frame(() => {
-												let f: string = ui_files_filename;
-												if (f == "") f = tr("untitled");
-												if (!f.endsWith(".png")) f += ".png";
-												krom_write_png(path + path_sep + f, image_get_pixels(target), target.width, target.height, 0);
-												image_unload(target);
-											});
-										});
-									});
-								}
-								if (ui_menu_button(ui, tr("Reimport"))) {
-									project_reimport_texture(asset);
-								}
-
-								///if (is_paint || is_sculpt)
-								if (ui_menu_button(ui, tr("To Mask"))) {
-									base_notify_on_next_frame(() => {
-										base_create_image_mask(asset);
-									});
-								}
-								///end
-
-								if (ui_menu_button(ui, tr("Set as Envmap"))) {
-									base_notify_on_next_frame(() => {
-										import_envmap_run(asset.file, img);
-									});
-								}
-
-								///if is_paint
-								if (ui_menu_button(ui, tr("Set as Color ID Map"))) {
-									context_raw.colorid_handle.position = i;
-									context_raw.colorid_picked = false;
-									ui_toolbar_handle.redraws = 1;
-									if (context_raw.tool == workspace_tool_t.COLORID) {
-										ui_header_handle.redraws = 2;
-										context_raw.ddirty = 2;
-									}
-								}
-								///end
-
-								if (ui_menu_button(ui, tr("Delete"), "delete")) {
-									TabTextures.tab_textures_delete_texture(asset);
-								}
-								if (!is_packed && ui_menu_button(ui, tr("Open Containing Directory..."))) {
-									file_start(asset.file.substr(0, asset.file.lastIndexOf(path_sep)));
-								}
-								if (!is_packed && ui_menu_button(ui, tr("Open in Browser"))) {
-									TabBrowser.tab_browser_show_directory(asset.file.substr(0, asset.file.lastIndexOf(path_sep)));
-								}
-							}, count);
-						}
-
-						if (config_raw.show_asset_names) {
-							ui._x = uix;
-							ui._y += slotw * 0.9;
-							zui_text(project_assets[i].name, zui_align_t.CENTER);
-							if (ui.is_hovered) zui_tooltip(project_assets[i].name);
-							ui._y -= slotw * 0.9;
-							if (i == project_assets.length - 1) {
-								ui._y += j == num - 1 ? imgw : imgw + zui_ELEMENT_H(ui) + zui_ELEMENT_OFFSET(ui);
-							}
-						}
-					}
-				}
-			}
-			else {
-				let img: image_t = resource_get("icons.k");
-				let r: rect_t = resource_tile50(img, 0, 1);
-				zui_image(img, ui.t.BUTTON_COL, r.h, r.x, r.y, r.w, r.h);
-				if (ui.is_hovered) zui_tooltip(tr("Drag and drop files here"));
-			}
-
-			let in_focus: bool = ui.input_x > ui._window_x && ui.input_x < ui._window_x + ui._window_w &&
-						 	 	 ui.input_y > ui._window_y && ui.input_y < ui._window_y + ui._window_h;
-			if (in_focus && ui.is_delete_down && project_assets.length > 0 && project_assets.indexOf(context_raw.texture) >= 0) {
-				ui.is_delete_down = false;
-				TabTextures.tab_textures_delete_texture(context_raw.texture);
-			}
-		}
-	}
-
-	static tab_textures_to_pow2 = (i: i32): i32 => {
-		i--;
-		i |= i >> 1;
-		i |= i >> 2;
-		i |= i >> 4;
-		i |= i >> 8;
-		i |= i >> 16;
-		i++;
-		return i;
-	}
-
-	static tab_textures_update_texture_pointers = (nodes: zui_node_t[], i: i32) => {
-		for (let n of nodes) {
-			if (n.type == "TEX_IMAGE") {
-				if (n.buttons[0].default_value == i) {
-					n.buttons[0].default_value = 9999; // Texture deleted, use pink now
-				}
-				else if (n.buttons[0].default_value > i) {
-					n.buttons[0].default_value--; // Offset by deleted texture
-				}
-			}
-		}
-	}
-
-	static tab_textures_delete_texture = (asset: asset_t) => {
-		let i: i32 = project_assets.indexOf(asset);
-		if (project_assets.length > 1) {
-			context_raw.texture = project_assets[i == project_assets.length - 1 ? i - 1 : i + 1];
-		}
-		ui_base_hwnds[tab_area_t.STATUS].redraws = 2;
-
-		///if is_paint
-		if (context_raw.tool == workspace_tool_t.COLORID && i == context_raw.colorid_handle.position) {
-			ui_header_handle.redraws = 2;
-			context_raw.ddirty = 2;
-			context_raw.colorid_picked = false;
-			ui_toolbar_handle.redraws = 1;
-		}
-		///end
-
-		data_delete_image(asset.file);
-		project_asset_map.delete(asset.id);
-		project_assets.splice(i, 1);
-		project_asset_names.splice(i, 1);
-		let _next = () => {
-			MakeMaterial.make_material_parse_paint_material();
-
-			///if (is_paint || is_sculpt)
-			util_render_make_material_preview();
-			ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
-			///end
-		}
-		base_notify_on_next_frame(_next);
-
-		for (let m of project_materials) TabTextures.tab_textures_update_texture_pointers(m.canvas.nodes, i);
-		///if (is_paint || is_sculpt)
-		for (let b of project_brushes) TabTextures.tab_textures_update_texture_pointers(b.canvas.nodes, i);
-		///end
-	}
-}

+ 110 - 116
base/Sources/base.ts

@@ -378,10 +378,7 @@ function base_h(): i32 {
 ///if is_lab
 function base_w(): i32 {
 	let res: i32 = 0;
-	if (UINodes == null) {
-		res = sys_width();
-	}
-	else if (ui_nodes_show || ui_view2d_show) {
+	if (ui_nodes_show || ui_view2d_show) {
 		res = sys_width() - config_raw.layout[layout_size_t.NODES_W];
 	}
 	else { // Distract free
@@ -393,10 +390,7 @@ function base_w(): i32 {
 
 function base_h(): i32 {
 	let res: i32 = sys_height();
-	if (UIBase == null) {
-		res -= ui_header_default_h * 2 + ui_status_default_status_h;
-	}
-	else if (res > 0) {
+	if (res > 0) {
 		let statush: i32 = config_raw.layout[layout_size_t.STATUS_H];
 		res -= math_floor(ui_header_default_h * 2 * config_raw.window_scale) + statush;
 	}
@@ -575,18 +569,18 @@ function base_update() {
 				ui_nodes_accept_swatch_drag(base_drag_swatch);
 			}
 			else if (context_in_swatches()) {
-				TabSwatches.tab_swatches_accept_swatch_drag(base_drag_swatch);
+				tab_swatches_accept_swatch_drag(base_drag_swatch);
 			}
 			///if (is_paint || is_sculpt)
 			else if (context_in_materials()) {
-				TabMaterials.tab_materials_accept_swatch_drag(base_drag_swatch);
+				tab_materials_accept_swatch_drag(base_drag_swatch);
 			}
 			else if (context_in_viewport()) {
 				let color: i32 = base_drag_swatch.base;
 				color = color_set_ab(color, base_drag_swatch.opacity * 255);
 				base_create_color_layer(color, base_drag_swatch.occlusion, base_drag_swatch.roughness, base_drag_swatch.metallic);
 			}
-			else if (context_in_layers() && TabLayers.tab_layers_can_drop_new_layer(context_raw.drag_dest)) {
+			else if (context_in_layers() && tab_layers_can_drop_new_layer(context_raw.drag_dest)) {
 				let color: i32 = base_drag_swatch.base;
 				color = color_set_ab(color, base_drag_swatch.opacity * 255);
 				base_create_color_layer(color, base_drag_swatch.occlusion, base_drag_swatch.roughness, base_drag_swatch.metallic, context_raw.drag_dest);
@@ -627,8 +621,8 @@ function base_update() {
 				ui_nodes_accept_layer_drag(project_layers.indexOf(base_drag_layer));
 			}
 			else if (context_in_layers() && base_is_dragging) {
-				SlotLayer.slot_layer_move(base_drag_layer, context_raw.drag_dest);
-				MakeMaterial.make_material_parse_mesh_material();
+				slot_layer_move(base_drag_layer, context_raw.drag_dest);
+				make_material_parse_mesh_material();
 			}
 			base_drag_layer = null;
 		}
@@ -671,7 +665,7 @@ function base_material_dropped() {
 		let decal_mat: mat4_t = uv_type == uv_type_t.PROJECT ? util_render_get_decal_mat() : null;
 		base_create_fill_layer(uv_type, decal_mat);
 	}
-	if (context_in_layers() && TabLayers.tab_layers_can_drop_new_layer(context_raw.drag_dest)) {
+	if (context_in_layers() && tab_layers_can_drop_new_layer(context_raw.drag_dest)) {
 		let uv_type: uv_type_t = keyboard_down("control") ? uv_type_t.PROJECT : uv_type_t.UVMAP;
 		let decal_mat: mat4_t = uv_type == uv_type_t.PROJECT ? util_render_get_decal_mat() : null;
 		base_create_fill_layer(uv_type, decal_mat, context_raw.drag_dest);
@@ -702,7 +696,7 @@ function base_handle_drop_paths() {
 ///if (is_paint || is_sculpt)
 function base_get_drag_background(): rect_t {
 	let icons: image_t = resource_get("icons.k");
-	if (base_drag_layer != null && !SlotLayer.slot_layer_is_group(base_drag_layer) && base_drag_layer.fill_layer == null) {
+	if (base_drag_layer != null && !slot_layer_is_group(base_drag_layer) && base_drag_layer.fill_layer == null) {
 		return resource_tile50(icons, 4, 1);
 	}
 	return null;
@@ -719,7 +713,7 @@ function base_get_drag_image(): image_t {
 	if (base_drag_swatch != null) {
 		base_drag_tint = base_drag_swatch.base;
 		base_drag_size = 26;
-		return TabSwatches.tab_swatches_empty;
+		return tab_swatches_empty_get()
 	}
 	if (base_drag_file != null) {
 		if (base_drag_file_icon != null) return base_drag_file_icon;
@@ -733,7 +727,7 @@ function base_get_drag_image(): image_t {
 	if (base_drag_material != null) {
 		return base_drag_material.image_icon;
 	}
-	if (base_drag_layer != null && SlotLayer.slot_layer_is_group(base_drag_layer)) {
+	if (base_drag_layer != null && slot_layer_is_group(base_drag_layer)) {
 		let icons: image_t = resource_get("icons.k");
 		let folder_closed: rect_t = resource_tile50(icons, 2, 1);
 		let folder_open: rect_t = resource_tile50(icons, 8, 1);
@@ -741,8 +735,8 @@ function base_get_drag_image(): image_t {
 		base_drag_tint = ui_base_ui.t.LABEL_COL - 0x00202020;
 		return icons;
 	}
-	if (base_drag_layer != null && SlotLayer.slot_layer_is_mask(base_drag_layer) && base_drag_layer.fill_layer == null) {
-		TabLayers.tab_layers_make_mask_preview_rgba32(base_drag_layer);
+	if (base_drag_layer != null && slot_layer_is_mask(base_drag_layer) && base_drag_layer.fill_layer == null) {
+		tab_layers_make_mask_preview_rgba32(base_drag_layer);
 		return context_raw.mask_preview_rgba32;
 	}
 	if (base_drag_layer != null) {
@@ -762,15 +756,15 @@ function base_render() {
 		ui_base_hwnds[tab_area_t.SIDEBAR1].redraws = 2;
 		///end
 
-		MakeMaterial.make_material_parse_mesh_material();
-		MakeMaterial.make_material_parse_paint_material();
+		make_material_parse_mesh_material();
+		make_material_parse_paint_material();
 		context_raw.ddirty = 0;
 
 		///if (is_paint || is_sculpt)
 		if (history_undo_layers == null) {
 			history_undo_layers = [];
 			for (let i: i32 = 0; i < config_raw.undo_steps; ++i) {
-				let l: SlotLayerRaw = SlotLayer.slot_layer_create("_undo" + history_undo_layers.length);
+				let l: SlotLayerRaw = slot_layer_create("_undo" + history_undo_layers.length);
 				history_undo_layers.push(l);
 			}
 		}
@@ -789,7 +783,7 @@ function base_render() {
 		///if is_lab
 		base_notify_on_next_frame(function() {
 			base_notify_on_next_frame(function() {
-				TabMeshes.tab_meshes_set_default_mesh(".Sphere");
+				tab_meshes_set_default_mesh(".Sphere");
 			});
 		});
 		///end
@@ -1079,7 +1073,7 @@ function base_init_config() {
 
 function base_init_layers() {
 	///if (is_paint || is_sculpt)
-	SlotLayer.slot_layer_clear(project_layers[0], color_from_floats(base_default_base, base_default_base, base_default_base, 1.0));
+	slot_layer_clear(project_layers[0], color_from_floats(base_default_base, base_default_base, base_default_base, 1.0));
 	///end
 
 	///if is_lab
@@ -1117,12 +1111,12 @@ function base_resize_layers() {
 		while (history_undo_layers.length > conf.undo_steps) {
 			let l: SlotLayerRaw = history_undo_layers.pop();
 			base_notify_on_next_frame(function() {
-				SlotLayer.slot_layer_unload(l);
+				slot_layer_unload(l);
 			});
 		}
 	}
-	for (let l of project_layers) SlotLayer.slot_layer_resize_and_set_bits(l);
-	for (let l of history_undo_layers) SlotLayer.slot_layer_resize_and_set_bits(l);
+	for (let l of project_layers) slot_layer_resize_and_set_bits(l);
+	for (let l of history_undo_layers) slot_layer_resize_and_set_bits(l);
 	let rts: map_t<string, render_target_t> = render_path_render_targets;
 	let _texpaint_blend0: image_t = rts.get("texpaint_blend0")._image;
 	base_notify_on_next_frame(function() {
@@ -1150,7 +1144,7 @@ function base_resize_layers() {
 		rts.get("texpaint_blur").height = size_y;
 		rts.get("texpaint_blur")._image = image_create_render_target(size_x, size_y);
 	}
-	if (RenderPathPaint.render_path_paint_live_layer != null) SlotLayer.slot_layer_resize_and_set_bits(RenderPathPaint.render_path_paint_live_layer);
+	if (render_path_paint_live_layer != null) slot_layer_resize_and_set_bits(render_path_paint_live_layer);
 	///if (krom_direct3d12 || krom_vulkan || krom_metal)
 	render_path_raytrace_ready = false; // Rebuild baketex
 	///end
@@ -1158,8 +1152,8 @@ function base_resize_layers() {
 }
 
 function base_set_layer_bits() {
-	for (let l of project_layers) SlotLayer.slot_layer_resize_and_set_bits(l);
-	for (let l of history_undo_layers) SlotLayer.slot_layer_resize_and_set_bits(l);
+	for (let l of project_layers) slot_layer_resize_and_set_bits(l);
+	for (let l of history_undo_layers) slot_layer_resize_and_set_bits(l);
 }
 
 function base_make_merge_pipe(red: bool, green: bool, blue: bool, alpha: bool): pipeline_t {
@@ -1541,13 +1535,13 @@ function base_make_export_img() {
 
 ///if (is_paint || is_sculpt)
 function base_duplicate_layer(l: SlotLayerRaw) {
-	if (!SlotLayer.slot_layer_is_group(l)) {
-		let new_layer: SlotLayerRaw = SlotLayer.slot_layer_duplicate(l);
+	if (!slot_layer_is_group(l)) {
+		let new_layer: SlotLayerRaw = slot_layer_duplicate(l);
 		context_set_layer(new_layer);
-		let masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(l, false);
+		let masks: SlotLayerRaw[] = slot_layer_get_masks(l, false);
 		if (masks != null) {
 			for (let m of masks) {
-				m = SlotLayer.slot_layer_duplicate(m);
+				m = slot_layer_duplicate(m);
 				m.parent = new_layer;
 				array_remove(project_layers, m);
 				project_layers.splice(project_layers.indexOf(new_layer), 0, m);
@@ -1560,25 +1554,25 @@ function base_duplicate_layer(l: SlotLayerRaw) {
 		array_remove(project_layers, new_group);
 		project_layers.splice(project_layers.indexOf(l) + 1, 0, new_group);
 		// group.show_panel = true;
-		for (let c of SlotLayer.slot_layer_get_children(l)) {
-			let masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(c, false);
-			let new_layer: SlotLayerRaw = SlotLayer.slot_layer_duplicate(c);
+		for (let c of slot_layer_get_children(l)) {
+			let masks: SlotLayerRaw[] = slot_layer_get_masks(c, false);
+			let new_layer: SlotLayerRaw = slot_layer_duplicate(c);
 			new_layer.parent = new_group;
 			array_remove(project_layers, new_layer);
 			project_layers.splice(project_layers.indexOf(new_group), 0, new_layer);
 			if (masks != null) {
 				for (let m of masks) {
-					let new_mask: SlotLayerRaw = SlotLayer.slot_layer_duplicate(m);
+					let new_mask: SlotLayerRaw = slot_layer_duplicate(m);
 					new_mask.parent = new_layer;
 					array_remove(project_layers, new_mask);
 					project_layers.splice(project_layers.indexOf(new_layer), 0, new_mask);
 				}
 			}
 		}
-		let group_masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(l);
+		let group_masks: SlotLayerRaw[] = slot_layer_get_masks(l);
 		if (group_masks != null) {
 			for (let m of group_masks) {
-				let new_mask: SlotLayerRaw = SlotLayer.slot_layer_duplicate(m);
+				let new_mask: SlotLayerRaw = slot_layer_duplicate(m);
 				new_mask.parent = new_group;
 				array_remove(project_layers, new_mask);
 				project_layers.splice(project_layers.indexOf(new_group), 0, new_mask);
@@ -1589,14 +1583,14 @@ function base_duplicate_layer(l: SlotLayerRaw) {
 }
 
 function base_apply_masks(l: SlotLayerRaw) {
-	let masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(l);
+	let masks: SlotLayerRaw[] = slot_layer_get_masks(l);
 
 	if (masks != null) {
 		for (let i: i32 = 0; i < masks.length - 1; ++i) {
 			base_merge_layer(masks[i + 1], masks[i]);
-			SlotLayer.slot_layer_delete(masks[i]);
+			slot_layer_delete(masks[i]);
 		}
-		SlotLayer.slot_layer_apply_mask(masks[masks.length - 1]);
+		slot_layer_apply_mask(masks[masks.length - 1]);
 		context_raw.layer_preview_dirty = true;
 	}
 }
@@ -1604,36 +1598,36 @@ function base_apply_masks(l: SlotLayerRaw) {
 function base_merge_down() {
 	let l1: SlotLayerRaw = context_raw.layer;
 
-	if (SlotLayer.slot_layer_is_group(l1)) {
+	if (slot_layer_is_group(l1)) {
 		l1 = base_merge_group(l1);
 	}
-	else if (SlotLayer.slot_layer_has_masks(l1)) { // It is a layer
+	else if (slot_layer_has_masks(l1)) { // It is a layer
 		base_apply_masks(l1);
 		context_set_layer(l1);
 	}
 
 	let l0: SlotLayerRaw = project_layers[project_layers.indexOf(l1) - 1];
 
-	if (SlotLayer.slot_layer_is_group(l0)) {
+	if (slot_layer_is_group(l0)) {
 		l0 = base_merge_group(l0);
 	}
-	else if (SlotLayer.slot_layer_has_masks(l0)) { // It is a layer
+	else if (slot_layer_has_masks(l0)) { // It is a layer
 		base_apply_masks(l0);
 		context_set_layer(l0);
 	}
 
 	base_merge_layer(l0, l1);
-	SlotLayer.slot_layer_delete(l1);
+	slot_layer_delete(l1);
 	context_set_layer(l0);
 	context_raw.layer_preview_dirty = true;
 }
 
 function base_merge_group(l: SlotLayerRaw) {
-	if (!SlotLayer.slot_layer_is_group(l)) return null;
+	if (!slot_layer_is_group(l)) return null;
 
-	let children: SlotLayerRaw[] = SlotLayer.slot_layer_get_children(l);
+	let children: SlotLayerRaw[] = slot_layer_get_children(l);
 
-	if (children.length == 1 && SlotLayer.slot_layer_has_masks(children[0], false)) {
+	if (children.length == 1 && slot_layer_has_masks(children[0], false)) {
 		base_apply_masks(children[0]);
 	}
 
@@ -1644,24 +1638,24 @@ function base_merge_group(l: SlotLayerRaw) {
 	}
 
 	// Now apply the group masks
-	let masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(l);
+	let masks: SlotLayerRaw[] = slot_layer_get_masks(l);
 	if (masks != null) {
 		for (let i: i32 = 0; i < masks.length - 1; ++i) {
 			base_merge_layer(masks[i + 1], masks[i]);
-			SlotLayer.slot_layer_delete(masks[i]);
+			slot_layer_delete(masks[i]);
 		}
 		base_apply_mask(children[0], masks[masks.length - 1]);
 	}
 
 	children[0].parent = null;
 	children[0].name = l.name;
-	if (children[0].fill_layer != null) SlotLayer.slot_layer_to_paint_layer(children[0]);
-	SlotLayer.slot_layer_delete(l);
+	if (children[0].fill_layer != null) slot_layer_to_paint_layer(children[0]);
+	slot_layer_delete(l);
 	return children[0];
 }
 
 function base_merge_layer(l0 : SlotLayerRaw, l1: SlotLayerRaw, use_mask: bool = false) {
-	if (!l1.visible || SlotLayer.slot_layer_is_group(l1)) return;
+	if (!l1.visible || slot_layer_is_group(l1)) return;
 
 	if (base_pipe_merge == null) base_make_pipe();
 	base_make_temp_img();
@@ -1675,7 +1669,7 @@ function base_merge_layer(l0 : SlotLayerRaw, l1: SlotLayerRaw, use_mask: bool =
 
 	let empty: image_t = render_path_render_targets.get("empty_white")._image;
 	let mask: image_t = empty;
-	let l1masks: SlotLayerRaw[] =  use_mask ? SlotLayer.slot_layer_get_masks(l1) : null;
+	let l1masks: SlotLayerRaw[] =  use_mask ? slot_layer_get_masks(l1) : null;
 	if (l1masks != null) {
 		// for (let i: i32 = 1; i < l1masks.length - 1; ++i) {
 		// 	mergeLayer(l1masks[i + 1], l1masks[i]);
@@ -1683,12 +1677,12 @@ function base_merge_layer(l0 : SlotLayerRaw, l1: SlotLayerRaw, use_mask: bool =
 		mask = l1masks[0].texpaint;
 	}
 
-	if (SlotLayer.slot_layer_is_mask(l1)) {
+	if (slot_layer_is_mask(l1)) {
 		g4_begin(l0.texpaint);
 		g4_set_pipeline(base_pipe_merge_mask);
 		g4_set_tex(base_tex0_merge_mask, l1.texpaint);
 		g4_set_tex(base_texa_merge_mask, base_temp_image);
-		g4_set_float(base_opac_merge_mask, SlotLayer.slot_layer_get_opacity(l1));
+		g4_set_float(base_opac_merge_mask, slot_layer_get_opacity(l1));
 		g4_set_int(base_blending_merge_mask, l1.blending);
 		g4_set_vertex_buffer(const_data_screen_aligned_vb);
 		g4_set_index_buffer(const_data_screen_aligned_ib);
@@ -1696,7 +1690,7 @@ function base_merge_layer(l0 : SlotLayerRaw, l1: SlotLayerRaw, use_mask: bool =
 		g4_end();
 	}
 
-	if (SlotLayer.slot_layer_is_layer(l1)) {
+	if (slot_layer_is_layer(l1)) {
 		if (l1.paint_base) {
 			g4_begin(l0.texpaint);
 			g4_set_pipeline(base_pipe_merge);
@@ -1704,7 +1698,7 @@ function base_merge_layer(l0 : SlotLayerRaw, l1: SlotLayerRaw, use_mask: bool =
 			g4_set_tex(base_tex1, empty);
 			g4_set_tex(base_texmask, mask);
 			g4_set_tex(base_texa, base_temp_image);
-			g4_set_float(base_opac, SlotLayer.slot_layer_get_opacity(l1));
+			g4_set_float(base_opac, slot_layer_get_opacity(l1));
 			g4_set_int(base_blending, l1.blending);
 			g4_set_vertex_buffer(const_data_screen_aligned_vb);
 			g4_set_index_buffer(const_data_screen_aligned_ib);
@@ -1726,7 +1720,7 @@ function base_merge_layer(l0 : SlotLayerRaw, l1: SlotLayerRaw, use_mask: bool =
 			g4_set_tex(base_tex1, l1.texpaint_nor);
 			g4_set_tex(base_texmask, mask);
 			g4_set_tex(base_texa, base_temp_image);
-			g4_set_float(base_opac, SlotLayer.slot_layer_get_opacity(l1));
+			g4_set_float(base_opac, slot_layer_get_opacity(l1));
 			g4_set_int(base_blending, l1.paint_nor_blend ? -2 : -1);
 			g4_set_vertex_buffer(const_data_screen_aligned_vb);
 			g4_set_index_buffer(const_data_screen_aligned_ib);
@@ -1742,12 +1736,12 @@ function base_merge_layer(l0 : SlotLayerRaw, l1: SlotLayerRaw, use_mask: bool =
 
 		if (l1.paint_occ || l1.paint_rough || l1.paint_met || l1.paint_height) {
 			if (l1.paint_occ && l1.paint_rough && l1.paint_met && l1.paint_height) {
-				base_commands_merge_pack(base_pipe_merge, l0.texpaint_pack, l1.texpaint, l1.texpaint_pack, SlotLayer.slot_layer_get_opacity(l1), mask, l1.paint_height_blend ? -3 : -1);
+				base_commands_merge_pack(base_pipe_merge, l0.texpaint_pack, l1.texpaint, l1.texpaint_pack, slot_layer_get_opacity(l1), mask, l1.paint_height_blend ? -3 : -1);
 			}
 			else {
-				if (l1.paint_occ) base_commands_merge_pack(base_pipe_merge_r, l0.texpaint_pack, l1.texpaint, l1.texpaint_pack, SlotLayer.slot_layer_get_opacity(l1), mask);
-				if (l1.paint_rough) base_commands_merge_pack(base_pipe_merge_g, l0.texpaint_pack, l1.texpaint, l1.texpaint_pack, SlotLayer.slot_layer_get_opacity(l1), mask);
-				if (l1.paint_met) base_commands_merge_pack(base_pipe_merge_b, l0.texpaint_pack, l1.texpaint, l1.texpaint_pack, SlotLayer.slot_layer_get_opacity(l1), mask);
+				if (l1.paint_occ) base_commands_merge_pack(base_pipe_merge_r, l0.texpaint_pack, l1.texpaint, l1.texpaint_pack, slot_layer_get_opacity(l1), mask);
+				if (l1.paint_rough) base_commands_merge_pack(base_pipe_merge_g, l0.texpaint_pack, l1.texpaint, l1.texpaint_pack, slot_layer_get_opacity(l1), mask);
+				if (l1.paint_met) base_commands_merge_pack(base_pipe_merge_b, l0.texpaint_pack, l1.texpaint, l1.texpaint_pack, slot_layer_get_opacity(l1), mask);
 			}
 		}
 		///end
@@ -1775,11 +1769,11 @@ function base_flatten(height_to_normal: bool = false, layers: SlotLayerRaw[] = n
 
 	// Flatten layers
 	for (let l1 of layers) {
-		if (!SlotLayer.slot_layer_is_visible(l1)) continue;
-		if (!SlotLayer.slot_layer_is_layer(l1)) continue;
+		if (!slot_layer_is_visible(l1)) continue;
+		if (!slot_layer_is_layer(l1)) continue;
 
 		let mask: image_t = empty;
-		let l1masks: SlotLayerRaw[] = SlotLayer.slot_layer_get_masks(l1);
+		let l1masks: SlotLayerRaw[] = slot_layer_get_masks(l1);
 		if (l1masks != null) {
 			if (l1masks.length > 1) {
 				base_make_temp_mask_img();
@@ -1808,7 +1802,7 @@ function base_flatten(height_to_normal: bool = false, layers: SlotLayerRaw[] = n
 			g4_set_tex(base_tex1, empty);
 			g4_set_tex(base_texmask, mask);
 			g4_set_tex(base_texa, base_temp_image);
-			g4_set_float(base_opac, SlotLayer.slot_layer_get_opacity(l1));
+			g4_set_float(base_opac, slot_layer_get_opacity(l1));
 			g4_set_int(base_blending, layers.length > 1 ? l1.blending : 0);
 			g4_set_vertex_buffer(const_data_screen_aligned_vb);
 			g4_set_index_buffer(const_data_screen_aligned_ib);
@@ -1830,7 +1824,7 @@ function base_flatten(height_to_normal: bool = false, layers: SlotLayerRaw[] = n
 			g4_set_tex(base_tex1, l1.texpaint_nor);
 			g4_set_tex(base_texmask, mask);
 			g4_set_tex(base_texa, base_temp_image);
-			g4_set_float(base_opac, SlotLayer.slot_layer_get_opacity(l1));
+			g4_set_float(base_opac, slot_layer_get_opacity(l1));
 			g4_set_int(base_blending, l1.paint_nor_blend ? -2 : -1);
 			g4_set_vertex_buffer(const_data_screen_aligned_vb);
 			g4_set_index_buffer(const_data_screen_aligned_ib);
@@ -1846,12 +1840,12 @@ function base_flatten(height_to_normal: bool = false, layers: SlotLayerRaw[] = n
 			g2_end();
 
 			if (l1.paint_occ && l1.paint_rough && l1.paint_met && l1.paint_height) {
-				base_commands_merge_pack(base_pipe_merge, base_expc, l1.texpaint, l1.texpaint_pack, SlotLayer.slot_layer_get_opacity(l1), mask, l1.paint_height_blend ? -3 : -1);
+				base_commands_merge_pack(base_pipe_merge, base_expc, l1.texpaint, l1.texpaint_pack, slot_layer_get_opacity(l1), mask, l1.paint_height_blend ? -3 : -1);
 			}
 			else {
-				if (l1.paint_occ) base_commands_merge_pack(base_pipe_merge_r, base_expc, l1.texpaint, l1.texpaint_pack, SlotLayer.slot_layer_get_opacity(l1), mask);
-				if (l1.paint_rough) base_commands_merge_pack(base_pipe_merge_g, base_expc, l1.texpaint, l1.texpaint_pack, SlotLayer.slot_layer_get_opacity(l1), mask);
-				if (l1.paint_met) base_commands_merge_pack(base_pipe_merge_b, base_expc, l1.texpaint, l1.texpaint_pack, SlotLayer.slot_layer_get_opacity(l1), mask);
+				if (l1.paint_occ) base_commands_merge_pack(base_pipe_merge_r, base_expc, l1.texpaint, l1.texpaint_pack, slot_layer_get_opacity(l1), mask);
+				if (l1.paint_rough) base_commands_merge_pack(base_pipe_merge_g, base_expc, l1.texpaint, l1.texpaint_pack, slot_layer_get_opacity(l1), mask);
+				if (l1.paint_met) base_commands_merge_pack(base_pipe_merge_b, base_expc, l1.texpaint, l1.texpaint_pack, slot_layer_get_opacity(l1), mask);
 			}
 		}
 		///end
@@ -1870,7 +1864,7 @@ function base_flatten(height_to_normal: bool = false, layers: SlotLayerRaw[] = n
 	let l0: any = { texpaint: base_expa, texpaint_nor: base_expb, texpaint_pack: base_expc };
 
 	// Merge height map into normal map
-	if (height_to_normal && MakeMaterial.make_material_height_used) {
+	if (height_to_normal && make_material_height_used) {
 
 		g2_begin(base_temp_image);
 		g2_set_pipeline(base_pipe_copy);
@@ -1896,7 +1890,7 @@ function base_flatten(height_to_normal: bool = false, layers: SlotLayerRaw[] = n
 }
 
 function base_apply_mask(l: SlotLayerRaw, m: SlotLayerRaw) {
-	if (!SlotLayer.slot_layer_is_layer(l) || !SlotLayer.slot_layer_is_mask(m)) return;
+	if (!slot_layer_is_layer(l) || !slot_layer_is_mask(m)) return;
 
 	if (base_pipe_merge == null) base_make_pipe();
 	base_make_temp_img();
@@ -1953,8 +1947,8 @@ function base_update_fill_layers() {
 
 	///if is_paint
 	if (context_raw.tool == workspace_tool_t.MATERIAL) {
-		if (RenderPathPaint.render_path_paint_live_layer == null) {
-			RenderPathPaint.render_path_paint_live_layer = SlotLayer.slot_layer_create("_live");
+		if (render_path_paint_live_layer == null) {
+			render_path_paint_live_layer = slot_layer_create("_live");
 		}
 
 		current = _g2_current;
@@ -1962,12 +1956,12 @@ function base_update_fill_layers() {
 
 		context_raw.tool = workspace_tool_t.FILL;
 		context_raw.fill_type_handle.position = fill_type_t.OBJECT;
-		MakeMaterial.make_material_parse_paint_material(false);
+		make_material_parse_paint_material(false);
 		context_raw.pdirty = 1;
-		RenderPathPaint.render_path_paint_use_live_layer(true);
-		RenderPathPaint.render_path_paint_commands_paint(false);
-		RenderPathPaint.render_path_paint_dilate(true, true);
-		RenderPathPaint.render_path_paint_use_live_layer(false);
+		render_path_paint_use_live_layer(true);
+		render_path_paint_commands_paint(false);
+		render_path_paint_dilate(true, true);
+		render_path_paint_use_live_layer(false);
 		context_raw.tool = _tool;
 		context_raw.fill_type_handle.position = _fill_type;
 		context_raw.pdirty = 0;
@@ -1980,8 +1974,8 @@ function base_update_fill_layers() {
 
 	let has_fill_layer: bool = false;
 	let has_fill_mask: bool = false;
-	for (let l of project_layers) if (SlotLayer.slot_layer_is_layer(l) && l.fill_layer == context_raw.material) has_fill_layer = true;
-	for (let l of project_layers) if (SlotLayer.slot_layer_is_mask(l) && l.fill_layer == context_raw.material) has_fill_mask = true;
+	for (let l of project_layers) if (slot_layer_is_layer(l) && l.fill_layer == context_raw.material) has_fill_layer = true;
+	for (let l of project_layers) if (slot_layer_is_mask(l) && l.fill_layer == context_raw.material) has_fill_mask = true;
 
 	if (has_fill_layer || has_fill_mask) {
 		current = _g2_current;
@@ -1993,32 +1987,32 @@ function base_update_fill_layers() {
 		if (has_fill_layer) {
 			let first: bool = true;
 			for (let l of project_layers) {
-				if (SlotLayer.slot_layer_is_layer(l) && l.fill_layer == context_raw.material) {
+				if (slot_layer_is_layer(l) && l.fill_layer == context_raw.material) {
 					context_raw.layer = l;
 					if (first) {
 						first = false;
-						MakeMaterial.make_material_parse_paint_material(false);
+						make_material_parse_paint_material(false);
 					}
 					base_set_object_mask();
-					SlotLayer.slot_layer_clear(l);
-					RenderPathPaint.render_path_paint_commands_paint(false);
-					RenderPathPaint.render_path_paint_dilate(true, true);
+					slot_layer_clear(l);
+					render_path_paint_commands_paint(false);
+					render_path_paint_dilate(true, true);
 				}
 			}
 		}
 		if (has_fill_mask) {
 			let first: bool = true;
 			for (let l of project_layers) {
-				if (SlotLayer.slot_layer_is_mask(l) && l.fill_layer == context_raw.material) {
+				if (slot_layer_is_mask(l) && l.fill_layer == context_raw.material) {
 					context_raw.layer = l;
 					if (first) {
 						first = false;
-						MakeMaterial.make_material_parse_paint_material(false);
+						make_material_parse_paint_material(false);
 					}
 					base_set_object_mask();
-					SlotLayer.slot_layer_clear(l);
-					RenderPathPaint.render_path_paint_commands_paint(false);
-					RenderPathPaint.render_path_paint_dilate(true, true);
+					slot_layer_clear(l);
+					render_path_paint_commands_paint(false);
+					render_path_paint_dilate(true, true);
 				}
 			}
 		}
@@ -2032,7 +2026,7 @@ function base_update_fill_layers() {
 		base_set_object_mask();
 		context_raw.tool = _tool;
 		context_raw.fill_type_handle.position = _fill_type;
-		MakeMaterial.make_material_parse_paint_material(false);
+		make_material_parse_paint_material(false);
 	}
 }
 
@@ -2047,11 +2041,11 @@ function base_update_fill_layer(parse_paint: bool = true) {
 	context_raw.fill_type_handle.position = fill_type_t.OBJECT;
 	context_raw.pdirty = 1;
 
-	SlotLayer.slot_layer_clear(context_raw.layer);
+	slot_layer_clear(context_raw.layer);
 
-	if (parse_paint) MakeMaterial.make_material_parse_paint_material(false);
-	RenderPathPaint.render_path_paint_commands_paint(false);
-	RenderPathPaint.render_path_paint_dilate(true, true);
+	if (parse_paint) make_material_parse_paint_material(false);
+	render_path_paint_commands_paint(false);
+	render_path_paint_dilate(true, true);
 
 	context_raw.rdirty = 2;
 	context_raw.tool = _tool;
@@ -2067,7 +2061,7 @@ function base_set_object_mask() {
 	let ar: string[] = [tr("None")];
 	for (let p of project_paint_objects) ar.push(p.base.name);
 
-	let mask: i32 = context_object_mask_used() ? SlotLayer.slot_layer_get_object_mask(context_raw.layer) : 0;
+	let mask: i32 = context_object_mask_used() ? slot_layer_get_object_mask(context_raw.layer) : 0;
 	if (context_layer_filter_used()) mask = context_raw.layer_filter;
 	if (mask > 0) {
 		if (context_raw.merged_object != null) {
@@ -2083,9 +2077,9 @@ function base_set_object_mask() {
 		context_select_paint_object(o);
 	}
 	else {
-		let is_atlas: bool = SlotLayer.slot_layer_get_object_mask(context_raw.layer) > 0 && SlotLayer.slot_layer_get_object_mask(context_raw.layer) <= project_paint_objects.length;
+		let is_atlas: bool = slot_layer_get_object_mask(context_raw.layer) > 0 && slot_layer_get_object_mask(context_raw.layer) <= project_paint_objects.length;
 		if (context_raw.merged_object == null || is_atlas || context_raw.merged_object_is_atlas) {
-			let visibles: mesh_object_t[] = is_atlas ? project_get_atlas_objects(SlotLayer.slot_layer_get_object_mask(context_raw.layer)) : null;
+			let visibles: mesh_object_t[] = is_atlas ? project_get_atlas_objects(slot_layer_get_object_mask(context_raw.layer)) : null;
 			util_mesh_merge(visibles);
 		}
 		context_select_paint_object(context_main_object());
@@ -2097,10 +2091,10 @@ function base_set_object_mask() {
 
 function base_new_layer(clear: bool = true, position: i32 = -1): SlotLayerRaw {
 	if (project_layers.length > base_max_layers) return null;
-	let l: SlotLayerRaw = SlotLayer.slot_layer_create();
+	let l: SlotLayerRaw = slot_layer_create();
 	l.object_mask = context_raw.layer_filter;
 	if (position == -1) {
-		if (SlotLayer.slot_layer_is_mask(context_raw.layer)) context_set_layer(context_raw.layer.parent);
+		if (slot_layer_is_mask(context_raw.layer)) context_set_layer(context_raw.layer.parent);
 		project_layers.splice(project_layers.indexOf(context_raw.layer) + 1, 0, l);
 	}
 	else {
@@ -2111,29 +2105,29 @@ function base_new_layer(clear: bool = true, position: i32 = -1): SlotLayerRaw {
 	let li: i32 = project_layers.indexOf(context_raw.layer);
 	if (li > 0) {
 		let below: SlotLayerRaw = project_layers[li - 1];
-		if (SlotLayer.slot_layer_is_layer(below)) {
+		if (slot_layer_is_layer(below)) {
 			context_raw.layer.parent = below.parent;
 		}
 	}
-	if (clear) app_notify_on_init(function() { SlotLayer.slot_layer_clear(l); });
+	if (clear) app_notify_on_init(function() { slot_layer_clear(l); });
 	context_raw.layer_preview_dirty = true;
 	return l;
 }
 
 function base_new_mask(clear: bool = true, parent: SlotLayerRaw, position: i32 = -1): SlotLayerRaw {
 	if (project_layers.length > base_max_layers) return null;
-	let l: SlotLayerRaw = SlotLayer.slot_layer_create("", layer_slot_type_t.MASK, parent);
+	let l: SlotLayerRaw = slot_layer_create("", layer_slot_type_t.MASK, parent);
 	if (position == -1) position = project_layers.indexOf(parent);
 	project_layers.splice(position, 0, l);
 	context_set_layer(l);
-	if (clear) app_notify_on_init(function() { SlotLayer.slot_layer_clear(l); });
+	if (clear) app_notify_on_init(function() { slot_layer_clear(l); });
 	context_raw.layer_preview_dirty = true;
 	return l;
 }
 
 function base_new_group(): SlotLayerRaw {
 	if (project_layers.length > base_max_layers) return null;
-	let l: SlotLayerRaw = SlotLayer.slot_layer_create("", layer_slot_type_t.GROUP);
+	let l: SlotLayerRaw = slot_layer_create("", layer_slot_type_t.GROUP);
 	project_layers.push(l);
 	context_set_layer(l);
 	return l;
@@ -2147,20 +2141,20 @@ function base_create_fill_layer(uv_type: uv_type_t = uv_type_t.UVMAP, decal_mat:
 		if (decal_mat != null) l.decal_mat = decal_mat;
 		l.object_mask = context_raw.layer_filter;
 		history_to_fill_layer();
-		SlotLayer.slot_layer_to_fill_layer(l);
+		slot_layer_to_fill_layer(l);
 	}
 	app_notify_on_init(_init);
 }
 
 function base_create_image_mask(asset: asset_t) {
 	let l: SlotLayerRaw = context_raw.layer;
-	if (SlotLayer.slot_layer_is_mask(l) || SlotLayer.slot_layer_is_group(l)) {
+	if (slot_layer_is_mask(l) || slot_layer_is_group(l)) {
 		return;
 	}
 
 	history_new_layer();
 	let m: SlotLayerRaw = base_new_mask(false, l);
-	SlotLayer.slot_layer_clear(m, 0x00000000, project_get_image(asset));
+	slot_layer_clear(m, 0x00000000, project_get_image(asset));
 	context_raw.layer_preview_dirty = true;
 }
 
@@ -2170,7 +2164,7 @@ function base_create_color_layer(baseColor: i32, occlusion: f32 = 1.0, roughness
 		history_new_layer();
 		l.uv_type = uv_type_t.UVMAP;
 		l.object_mask = context_raw.layer_filter;
-		SlotLayer.slot_layer_clear(l, baseColor, null, occlusion, roughness, metallic);
+		slot_layer_clear(l, baseColor, null, occlusion, roughness, metallic);
 	}
 	app_notify_on_init(_init);
 }
@@ -2189,7 +2183,7 @@ function base_on_layers_resized() {
 		}
 		context_raw.layer = _layer;
 		context_raw.material = _material;
-		MakeMaterial.make_material_parse_paint_material();
+		make_material_parse_paint_material();
 	});
 	util_uv_uvmap = null;
 	util_uv_uvmap_cached = false;

+ 12 - 12
base/Sources/box_preferences.ts

@@ -91,8 +91,8 @@ function box_preferences_show() {
 							if (box_preferences_files_plugin != null) for (let f of box_preferences_files_plugin) plugin_stop(f);
 							box_preferences_files_plugin = null;
 							box_preferences_files_keymap = null;
-							MakeMaterial.make_material_parse_mesh_material();
-							MakeMaterial.make_material_parse_paint_material();
+							make_material_parse_mesh_material();
+							make_material_parse_paint_material();
 						});
 					}
 					if (ui_menu_button(ui, tr("Import..."))) {
@@ -103,8 +103,8 @@ function box_preferences_show() {
 								ui.t.ELEMENT_H = base_default_element_h;
 								config_import_from(raw);
 								box_preferences_set_scale();
-								MakeMaterial.make_material_parse_mesh_material();
-								MakeMaterial.make_material_parse_paint_material();
+								make_material_parse_mesh_material();
+								make_material_parse_paint_material();
 							});
 						});
 					}
@@ -272,12 +272,12 @@ function box_preferences_show() {
 
 				///if (is_paint || is_sculpt)
 				while (history_undo_layers.length < config_raw.undo_steps) {
-					let l: SlotLayerRaw = SlotLayer.slot_layer_create("_undo" + history_undo_layers.length);
+					let l: SlotLayerRaw = slot_layer_create("_undo" + history_undo_layers.length);
 					history_undo_layers.push(l);
 				}
 				while (history_undo_layers.length > config_raw.undo_steps) {
 					let l: SlotLayerRaw = history_undo_layers.pop();
-					SlotLayer.slot_layer_unload(l);
+					slot_layer_unload(l);
 				}
 				///end
 
@@ -347,24 +347,24 @@ function box_preferences_show() {
 
 			let brush_3d_handle: zui_handle_t = zui_handle("boxpreferences_25", { selected: config_raw.brush_3d });
 			config_raw.brush_3d = zui_check(brush_3d_handle, tr("3D Cursor"));
-			if (brush_3d_handle.changed) MakeMaterial.make_material_parse_paint_material();
+			if (brush_3d_handle.changed) make_material_parse_paint_material();
 
 			ui.enabled = config_raw.brush_3d;
 			let brush_depth_reject_handle: zui_handle_t = zui_handle("boxpreferences_26", { selected: config_raw.brush_depth_reject });
 			config_raw.brush_depth_reject = zui_check(brush_depth_reject_handle, tr("Depth Reject"));
-			if (brush_depth_reject_handle.changed) MakeMaterial.make_material_parse_paint_material();
+			if (brush_depth_reject_handle.changed) make_material_parse_paint_material();
 
 			zui_row([0.5, 0.5]);
 
 			let brush_angle_reject_handle: zui_handle_t = zui_handle("boxpreferences_27", { selected: config_raw.brush_angle_reject });
 			config_raw.brush_angle_reject = zui_check(brush_angle_reject_handle, tr("Angle Reject"));
-			if (brush_angle_reject_handle.changed) MakeMaterial.make_material_parse_paint_material();
+			if (brush_angle_reject_handle.changed) make_material_parse_paint_material();
 
 			if (!config_raw.brush_angle_reject) ui.enabled = false;
 			let angle_dot_handle: zui_handle_t = zui_handle("boxpreferences_28", { value: context_raw.brush_angle_reject_dot });
 			context_raw.brush_angle_reject_dot = zui_slider(angle_dot_handle, tr("Angle"), 0.0, 1.0, true);
 			if (angle_dot_handle.changed) {
-				MakeMaterial.make_material_parse_paint_material();
+				make_material_parse_paint_material();
 			}
 			ui.enabled = true;
 			///end
@@ -483,7 +483,7 @@ function box_preferences_show() {
 			config_raw.displace_strength = zui_slider(disp_handle, tr("Displacement Strength"), 0.0, 10.0, true);
 			if (disp_handle.changed) {
 				context_raw.ddirty = 2;
-				MakeMaterial.make_material_parse_mesh_material();
+				make_material_parse_mesh_material();
 			}
 		}
 		if (zui_tab(box_preferences_htab, tr("Keymap"), true)) {
@@ -617,7 +617,7 @@ if (Zui.panel(h1, 'New Plugin')) {
 						}
 						if (ui_menu_button(ui, tr("Edit in Script Tab"))) {
 							let blob: ArrayBuffer = data_get_blob("plugins/" + f);
-							TabScript.tab_script_hscript.text = sys_buffer_to_string(blob);
+							tab_script_hscript.text = sys_buffer_to_string(blob);
 							data_delete_blob("plugins/" + f);
 							console_info(tr("Script opened"));
 						}

Some files were not shown because too many files changed in this diff