luboslenco 1 år sedan
förälder
incheckning
5edfd09550
70 ändrade filer med 3228 tillägg och 3098 borttagningar
  1. BIN
      armorforge/Assets/default_brush.arm
  2. BIN
      armorlab/Assets/default_brush.arm
  3. 0 70
      armorlab/Sources/nodes/BrushOutputNode.ts
  4. 0 73
      armorlab/Sources/nodes/ImageTextureNode.ts
  5. 0 218
      armorlab/Sources/nodes/InpaintNode.ts
  6. 0 239
      armorlab/Sources/nodes/PhotoToPBRNode.ts
  7. 0 60
      armorlab/Sources/nodes/RGBNode.ts
  8. 0 465
      armorlab/Sources/nodes/TextToPhotoNode.ts
  9. 0 129
      armorlab/Sources/nodes/TilingNode.ts
  10. 0 150
      armorlab/Sources/nodes/UpscaleNode.ts
  11. 0 120
      armorlab/Sources/nodes/VarianceNode.ts
  12. 72 0
      armorlab/Sources/nodes/brush_output_node.ts
  13. 77 0
      armorlab/Sources/nodes/image_texture_node.ts
  14. 225 0
      armorlab/Sources/nodes/inpaint_node.ts
  15. 244 0
      armorlab/Sources/nodes/photo_to_pbr_node.ts
  16. 64 0
      armorlab/Sources/nodes/rgb_node.ts
  17. 471 0
      armorlab/Sources/nodes/text_to_photo_node.ts
  18. 134 0
      armorlab/Sources/nodes/tiling_node.ts
  19. 155 0
      armorlab/Sources/nodes/upscale_node.ts
  20. 127 0
      armorlab/Sources/nodes/variance_node.ts
  21. BIN
      armorpaint/Assets/default_brush.arm
  22. 0 222
      armorpaint/Sources/nodes/BrushOutputNode.ts
  23. 0 163
      armorpaint/Sources/nodes/InputNode.ts
  24. 0 66
      armorpaint/Sources/nodes/TEX_IMAGE.ts
  25. 227 0
      armorpaint/Sources/nodes/brush_output_node.ts
  26. 165 0
      armorpaint/Sources/nodes/input_node.ts
  27. 69 0
      armorpaint/Sources/nodes/tex_image_node.ts
  28. 13 20
      armorpaint/Sources/nodes_brush.ts
  29. BIN
      armorsculpt/Assets/default_brush.arm
  30. 0 136
      armorsculpt/Sources/nodes/BrushOutputNode.ts
  31. 138 0
      armorsculpt/Sources/nodes/brush_output_node.ts
  32. 0 51
      base/Sources/LogicNode.ts
  33. 12 12
      base/Sources/base.ts
  34. 2 0
      base/Sources/config.ts
  35. 3 2
      base/Sources/context.ts
  36. 4 4
      base/Sources/export_texture.ts
  37. 1 1
      base/Sources/import_asset.ts
  38. 66 0
      base/Sources/logic_node.ts
  39. 0 20
      base/Sources/nodes/BooleanNode.ts
  40. 0 34
      base/Sources/nodes/ColorNode.ts
  41. 0 66
      base/Sources/nodes/FloatNode.ts
  42. 0 20
      base/Sources/nodes/IntegerNode.ts
  43. 0 181
      base/Sources/nodes/MathNode.ts
  44. 0 11
      base/Sources/nodes/NullNode.ts
  45. 0 91
      base/Sources/nodes/RandomNode.ts
  46. 0 61
      base/Sources/nodes/SeparateVectorNode.ts
  47. 0 20
      base/Sources/nodes/StringNode.ts
  48. 0 50
      base/Sources/nodes/TimeNode.ts
  49. 0 189
      base/Sources/nodes/VectorMathNode.ts
  50. 0 99
      base/Sources/nodes/VectorNode.ts
  51. 24 0
      base/Sources/nodes/boolean_node.ts
  52. 39 0
      base/Sources/nodes/color_node.ts
  53. 71 0
      base/Sources/nodes/float_node.ts
  54. 24 0
      base/Sources/nodes/integer_node.ts
  55. 184 0
      base/Sources/nodes/math_node.ts
  56. 15 0
      base/Sources/nodes/null_node.ts
  57. 96 0
      base/Sources/nodes/random_node.ts
  58. 65 0
      base/Sources/nodes/separate_vector_node.ts
  59. 24 0
      base/Sources/nodes/string_node.ts
  60. 54 0
      base/Sources/nodes/time_node.ts
  61. 193 0
      base/Sources/nodes/vector_math_node.ts
  62. 106 0
      base/Sources/nodes/vector_node.ts
  63. 1 1
      base/Sources/nodes_material.ts
  64. 34 26
      base/Sources/parser_logic.ts
  65. 0 0
      base/Sources/physics_bullet.ts
  66. 1 1
      base/Sources/tab_meshes.ts
  67. 2 2
      base/Sources/ui_base.ts
  68. 6 6
      base/Sources/ui_box.ts
  69. 16 15
      base/Sources/ui_nodes.ts
  70. 4 4
      base/Sources/uniforms_ext.ts

BIN
armorforge/Assets/default_brush.arm


BIN
armorlab/Assets/default_brush.arm


+ 0 - 70
armorlab/Sources/nodes/BrushOutputNode.ts

@@ -1,70 +0,0 @@
-
-class BrushOutputNode extends LogicNode {
-
-	id = 0;
-	texpaint: image_t = null;
-	texpaint_nor: image_t = null;
-	texpaint_pack: image_t = null;
-	texpaint_nor_empty: image_t = null;
-	texpaint_pack_empty: image_t = null;
-
-	static inst: BrushOutputNode = null;
-
-	constructor() {
-		super();
-
-		if (BrushOutputNode.inst == null) {
-			{
-				let t = render_target_create();
-				t.name = "texpaint";
-				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_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_get_texture_res_x();
-				t.height = config_get_texture_res_y();
-				t.format = "RGBA32";
-				this.texpaint_pack = render_path_create_render_target(t)._image;
-			}
-			{
-				let t = render_target_create();
-				t.name = "texpaint_nor_empty";
-				t.width = 1;
-				t.height = 1;
-				t.format = "RGBA32";
-				this.texpaint_nor_empty = render_path_create_render_target(t)._image;
-			}
-			{
-				let t = render_target_create();
-				t.name = "texpaint_pack_empty";
-				t.width = 1;
-				t.height = 1;
-				t.format = "RGBA32";
-				this.texpaint_pack_empty = render_path_create_render_target(t)._image;
-			}
-		}
-		else {
-			this.texpaint = BrushOutputNode.inst.texpaint;
-			this.texpaint_nor = BrushOutputNode.inst.texpaint_nor;
-			this.texpaint_pack = BrushOutputNode.inst.texpaint_pack;
-		}
-
-		BrushOutputNode.inst = this;
-	}
-
-	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
-		this.inputs[from].get_as_image(done);
-	}
-}

+ 0 - 73
armorlab/Sources/nodes/ImageTextureNode.ts

@@ -1,73 +0,0 @@
-
-class ImageTextureNode extends LogicNode {
-
-	file: string;
-	color_space: string;
-
-	constructor() {
-		super();
-	}
-
-	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_get_image(asset));
-	}
-
-	override get_cached_image = (): image_t => {
-		let image: image_t;
-		this.get_as_image(0, (img: image_t) => { image = img; });
-		return image;
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Image Texture"),
-		type: "ImageTextureNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Vector"),
-				type: "VECTOR",
-				color: 0xff6363c7,
-				default_value: new Float32Array([0.0, 0.0, 0.0])
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Alpha"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 1.0
-			}
-		],
-		buttons: [
-			{
-				name: _tr("file"),
-				type: "ENUM",
-				default_value: 0,
-				data: ""
-			},
-			{
-				name: _tr("color_space"),
-				type: "ENUM",
-				default_value: 0,
-				data: ["linear", "srgb"]
-			}
-		]
-	};
-}

+ 0 - 218
armorlab/Sources/nodes/InpaintNode.ts

@@ -1,218 +0,0 @@
-
-declare let Krom_texsynth: any;
-
-class InpaintNode extends LogicNode {
-
-	static image: image_t = null;
-	static mask: image_t = null;
-	static result: image_t = null;
-
-	static temp: image_t = null;
-	static prompt = "";
-	static strength = 0.5;
-	static auto = true;
-
-	constructor() {
-		super();
-		InpaintNode.init();
-	}
-
-	static init = () => {
-		if (InpaintNode.image == null) {
-			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_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();
-			});
-		}
-
-		if (InpaintNode.temp == null) {
-			InpaintNode.temp = image_create_render_target(512, 512);
-		}
-
-		if (InpaintNode.result == null) {
-			InpaintNode.result = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
-		}
-	}
-
-	static buttons = (ui: zui_t, nodes: zui_nodes_t, node: zui_node_t) => {
-		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"), 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 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_notify_on_next_frame(() => {
-				g2_begin(InpaintNode.image);
-				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);
-			});
-		});
-	}
-
-	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_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();
-				g4_end();
-			});
-		});
-		return InpaintNode.image;
-	}
-
-	getTarget = (): image_t => {
-		return InpaintNode.mask;
-	}
-
-	static texsynthInpaint = (image: image_t, tiling: bool, mask: image_t/* = null*/, done: (img: image_t)=>void) => {
-		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);
-		let bytes_out = new ArrayBuffer(w * h * 4);
-		Krom_texsynth.inpaint(w, h, bytes_out, bytes_img, bytes_mask, tiling);
-
-		InpaintNode.result = image_from_bytes(bytes_out, w, h);
-		done(InpaintNode.result);
-	}
-
-	static sdInpaint = (image: image_t, mask: image_t, done: (img: image_t)=>void) => {
-		InpaintNode.init();
-
-		let bytes_img = image_get_pixels(mask);
-		let u8 = new Uint8Array(bytes_img);
-		let f32mask = new Float32Array(4 * 64 * 64);
-
-		let vae_encoder_blob: ArrayBuffer = data_get_blob("models/sd_vae_encoder.quant.onnx");
-		// for (let x = 0; x < math_floor(image.width / 512); ++x) {
-			// for (let y = 0; y < math_floor(image.height / 512); ++y) {
-				let x = 0;
-				let y = 0;
-
-				for (let xx = 0; xx < 64; ++xx) {
-					for (let yy = 0; yy < 64; ++yy) {
-						// let step = math_floor(512 / 64);
-						// let j = (yy * step * mask.width + xx * step) + (y * 512 * mask.width + x * 512);
-						let step = math_floor(mask.width / 64);
-						let j = (yy * step * mask.width + xx * step);
-						let f = u8[j] / 255.0;
-						let i = yy * 64 + xx;
-						f32mask[i              ] = f;
-						f32mask[i + 64 * 64    ] = f;
-						f32mask[i + 64 * 64 * 2] = f;
-						f32mask[i + 64 * 64 * 3] = f;
-					}
-				}
-
-				g2_begin(InpaintNode.temp);
-				// g2_drawImage(image, -x * 512, -y * 512);
-				g2_draw_scaled_image(image, 0, 0, 512, 512);
-				g2_end();
-
-				bytes_img = image_get_pixels(InpaintNode.temp);
-				let u8a = new Uint8Array(bytes_img);
-				let f32a = new Float32Array(3 * 512 * 512);
-				for (let i = 0; i < (512 * 512); ++i) {
-					f32a[i                ] = (u8a[i * 4    ] / 255.0) * 2.0 - 1.0;
-					f32a[i + 512 * 512    ] = (u8a[i * 4 + 1] / 255.0) * 2.0 - 1.0;
-					f32a[i + 512 * 512 * 2] = (u8a[i * 4 + 2] / 255.0) * 2.0 - 1.0;
-				}
-
-				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);
-				for (let i = 0; i < latents.length; ++i) {
-					latents[i] = 0.18215 * latents[i];
-				}
-				let latents_orig = latents.slice(0);
-
-				let noise = new Float32Array(latents.length);
-				for (let i = 0; i < noise.length; ++i) noise[i] = math_cos(2.0 * 3.14 * RandomNode.getFloat()) * math_sqrt(-2.0 * math_log(RandomNode.getFloat()));
-
-				let num_inference_steps = 50;
-				let init_timestep = math_floor(num_inference_steps * InpaintNode.strength);
-				let timestep = TextToPhotoNode.timesteps[num_inference_steps - init_timestep];
-				let alphas_cumprod = TextToPhotoNode.alphas_cumprod;
-				let sqrt_alpha_prod = math_pow(alphas_cumprod[timestep], 0.5);
-				let sqrt_one_minus_alpha_prod = math_pow(1.0 - alphas_cumprod[timestep], 0.5);
-				for (let i = 0; i < latents.length; ++i) {
-					latents[i] = sqrt_alpha_prod * latents[i] + sqrt_one_minus_alpha_prod * noise[i];
-				}
-
-				let start = num_inference_steps - init_timestep;
-
-				TextToPhotoNode.stable_diffusion(InpaintNode.prompt, (img: image_t) => {
-					// result.g2_begin();
-					// result.g2_draw_image(img, x * 512, y * 512);
-					// result.g2_end();
-					InpaintNode.result = img;
-					done(img);
-				}, latents, start, true, f32mask, latents_orig);
-			// }
-		// }
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Inpaint"),
-		type: "InpaintNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([1.0, 1.0, 1.0, 1.0])
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			}
-		],
-		buttons: [
-			{
-				name: _tr("auto"),
-				type: "BOOL",
-				default_value: true,
-				output: 0
-			},
-			{
-				name: "InpaintNode.buttons",
-				type: "CUSTOM",
-				height: 0
-			}
-		]
-	};
-}

+ 0 - 239
armorlab/Sources/nodes/PhotoToPBRNode.ts

@@ -1,239 +0,0 @@
-
-class PhotoToPBRNode extends LogicNode {
-
-	static temp: image_t = null;
-	static images: image_t[] = null;
-	static model_names = ["base", "occlusion", "roughness", "metallic", "normal", "height"];
-
-	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.tile_with_border_w, PhotoToPBRNode.tile_with_border_w);
-		}
-
-		PhotoToPBRNode.init();
-	}
-
-	static init = () => {
-		if (PhotoToPBRNode.images == null) {
-			PhotoToPBRNode.images = [];
-			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 get_as_image = (from: i32, done: (img: image_t)=>void) => {
-		let getSource = (done: (img: image_t)=>void) => {
-			if (PhotoToPBRNode.cached_source != null) done(PhotoToPBRNode.cached_source);
-			else this.inputs[0].get_as_image(done);
-		}
-
-		getSource((source: image_t) => {
-			PhotoToPBRNode.cached_source = source;
-
-			console_progress(tr("Processing") + " - " + tr("Photo to PBR"));
-			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.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.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.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.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.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;
-					}
-					tile_floats.push(ar);
-
-					// Use border pixels to blend seams
-					if (i > 0) {
-						if (x > 0) {
-							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.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.border_w;
-									let invf = 1.0 - f;
-									a = math_floor(a * f + aa * invf);
-									b = math_floor(b * f + bb * invf);
-									c = math_floor(c * f + cc * invf);
-
-									u8a[i * 4    ] = a;
-									u8a[i * 4 + 1] = b;
-									u8a[i * 4 + 2] = c;
-								}
-							}
-						}
-						if (y > 0) {
-							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.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.border_w;
-									let invf = 1.0 - f;
-									a = math_floor(a * f + aa * invf);
-									b = math_floor(b * f + bb * invf);
-									c = math_floor(c * f + cc * invf);
-
-									u8a[i * 4    ] = a;
-									u8a[i * 4 + 1] = b;
-									u8a[i * 4 + 2] = c;
-								}
-							}
-						}
-					}
-
-					///if (krom_metal || krom_vulkan)
-					if (from == channel_type_t.BASE_COLOR) PhotoToPBRNode.bgra_swap(u8a.buffer);
-					///end
-
-					let temp2 = image_from_bytes(u8a.buffer, PhotoToPBRNode.tile_w, PhotoToPBRNode.tile_w);
-					g2_begin(PhotoToPBRNode.images[from]);
-					g2_draw_image(temp2, x * PhotoToPBRNode.tile_w, y * PhotoToPBRNode.tile_w);
-					g2_end();
-					base_notify_on_next_frame(() => {
-						image_unload(temp2);
-					});
-				}
-
-				done(PhotoToPBRNode.images[from]);
-			});
-		});
-	}
-
-	///if (krom_metal || krom_vulkan)
-	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];
-			u8a[i * 4] = u8a[i * 4 + 2];
-			u8a[i * 4 + 2] = r;
-		}
-		return buffer;
-	}
-	///end
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Photo to PBR"),
-		type: "PhotoToPBRNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Base Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Occlusion"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 1.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Roughness"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 1.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Metallic"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Normal Map"),
-				type: "VECTOR",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Height"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 1.0
-			}
-		],
-		buttons: []
-	};
-}

+ 0 - 60
armorlab/Sources/nodes/RGBNode.ts

@@ -1,60 +0,0 @@
-
-class RGBNode extends LogicNode {
-
-	image: image_t = null;
-
-	constructor() {
-		super();
-	}
-
-	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
-		if (this.image != null) {
-			base_notify_on_next_frame(() => {
-				image_unload(this.image);
-			});
-		}
-
-		let f32a = new Float32Array(4);
-		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];
-		f32a[2] = default_value[2];
-		f32a[3] = default_value[3];
-		this.image = image_from_bytes(f32a.buffer, 1, 1, tex_format_t.RGBA128);
-		done(this.image);
-	}
-
-	override get_cached_image = (): image_t => {
-		this.get_as_image(0, (img: image_t) => {});
-		return this.image;
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("RGB"),
-		type: "RGBNode",
-		x: 0,
-		y: 0,
-		color: 0xffb34f5a,
-		inputs: [],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.8, 0.8, 0.8, 1.0])
-			}
-		],
-		buttons: [
-			{
-				name: _tr("default_value"),
-				type: "RGBA",
-				output: 0,
-				default_value: new Float32Array([0.8, 0.8, 0.8, 1.0])
-			}
-		]
-	};
-}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 465
armorlab/Sources/nodes/TextToPhotoNode.ts


+ 0 - 129
armorlab/Sources/nodes/TilingNode.ts

@@ -1,129 +0,0 @@
-
-class TilingNode extends LogicNode {
-
-	result: image_t = null;
-	static image: image_t = null;
-	static prompt = "";
-	static strength = 0.5;
-	static auto = true;
-
-	constructor() {
-		super();
-		TilingNode.init();
-	}
-
-	static init = () => {
-		if (TilingNode.image == null) {
-			TilingNode.image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
-		}
-	}
-
-	static buttons = (ui: zui_t, nodes: zui_nodes_t, node: zui_node_t) => {
-		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"), 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 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_get_texture_res_x(), config_get_texture_res_y());
-			g2_end();
-
-			console_progress(tr("Processing") + " - " + tr("Tiling"));
-			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.sd_tiling(TilingNode.image, -1, _done);
-			});
-		});
-	}
-
-	override get_cached_image = (): image_t => {
-		return this.result;
-	}
-
-	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);
-		g2_draw_scaled_image(image, -256, -256, 512, 512);
-		g2_draw_scaled_image(image, 256, -256, 512, 512);
-		g2_draw_scaled_image(image, -256, 256, 512, 512);
-		g2_draw_scaled_image(image, 256, 256, 512, 512);
-		g2_end();
-
-		let u8a = new Uint8Array(512 * 512);
-		for (let i = 0; i < 512 * 512; ++i) {
-			let x = i % 512;
-			let y = math_floor(i / 512);
-			let l = y < 256 ? y : (511 - y);
-			u8a[i] = (x > 256 - l && x < 256 + l) ? 0 : 255;
-		}
-		// for (let i = 0; i < 512 * 512; ++i) u8a[i] = 255;
-		// for (let x = (256 - 32); x < (256 + 32); ++x) {
-		// 	for (let y = 0; y < 512; ++y) {
-		// 		u8a[y * 512 + x] = 0;
-		// 	}
-		// }
-		// for (let x = 0; x < 512; ++x) {
-		// 	for (let y = (256 - 32); y < 256 + 32); ++y) {
-		// 		u8a[y * 512 + x] = 0;
-		// 	}
-		// }
-		let mask = image_from_bytes(u8a.buffer, 512, 512, tex_format_t.R8);
-
-		InpaintNode.prompt = TilingNode.prompt;
-		InpaintNode.strength = TilingNode.strength;
-		if (seed >= 0) RandomNode.setSeed(seed);
-		InpaintNode.sdInpaint(tile, mask, done);
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Tiling"),
-		type: "TilingNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			}
-		],
-		buttons: [
-			{
-				name: _tr("auto"),
-				type: "BOOL",
-				default_value: true,
-				output: 0
-			},
-			{
-				name: "TilingNode.buttons",
-				type: "CUSTOM",
-				height: 0
-			}
-		]
-	};
-}

+ 0 - 150
armorlab/Sources/nodes/UpscaleNode.ts

@@ -1,150 +0,0 @@
-
-class UpscaleNode extends LogicNode {
-
-	static temp: image_t = null;
-	static image: image_t = null;
-	static esrgan_blob: ArrayBuffer;
-
-	constructor() {
-		super();
-	}
-
-	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_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_get_texture_res_x()) {
-							let lastImage = UpscaleNode.image;
-							UpscaleNode.image = UpscaleNode.esrgan(UpscaleNode.image);
-							image_unload(lastImage);
-						}
-					}
-					done(UpscaleNode.image);
-				});
-			});
-		});
-	}
-
-	static load_blob = (done: ()=>void) => {
-		let _esrgan_blob: ArrayBuffer = data_get_blob("models/esrgan.quant.onnx");
-		UpscaleNode.esrgan_blob = _esrgan_blob;
-		done();
-	}
-
-	override get_cached_image = (): image_t => {
-		return UpscaleNode.image;
-	}
-
-	static do_tile = (source: image_t) => {
-		let result: image_t = null;
-		let size1w = source.width;
-		let size1h = source.height;
-		let size2w = math_floor(size1w * 2);
-		let size2h = math_floor(size1h * 2);
-		if (UpscaleNode.temp != null) {
-			image_unload(UpscaleNode.temp);
-		}
-		UpscaleNode.temp = image_create_render_target(size1w, size1h);
-		g2_begin(UpscaleNode.temp);
-		g2_draw_scaled_image(source, 0, 0, size1w, size1h);
-		g2_end();
-
-		let bytes_img = image_get_pixels(UpscaleNode.temp);
-		let u8a = new Uint8Array(bytes_img);
-		let f32a = new Float32Array(3 * size1w * size1h);
-		for (let i = 0; i < (size1w * size1h); ++i) {
-			f32a[i                      ] = (u8a[i * 4    ] / 255);
-			f32a[i + size1w * size1w    ] = (u8a[i * 4 + 1] / 255);
-			f32a[i + size1w * size1w * 2] = (u8a[i * 4 + 2] / 255);
-		}
-
-		let esrgan2x_buf = krom_ml_inference(UpscaleNode.esrgan_blob, [f32a.buffer], [[1, 3, size1w, size1h]], [1, 3, size2w, size2h], config_raw.gpu_inference);
-		let esrgan2x = new Float32Array(esrgan2x_buf);
-		for (let i = 0; i < esrgan2x.length; ++i) {
-			if (esrgan2x[i] < 0) esrgan2x[i] = 0;
-			else if (esrgan2x[i] > 1) esrgan2x[i] = 1;
-		}
-
-		u8a = new Uint8Array(4 * size2w * size2h);
-		for (let i = 0; i < (size2w * size2h); ++i) {
-			u8a[i * 4    ] = math_floor(esrgan2x[i                      ] * 255);
-			u8a[i * 4 + 1] = math_floor(esrgan2x[i + size2w * size2w    ] * 255);
-			u8a[i * 4 + 2] = math_floor(esrgan2x[i + size2w * size2w * 2] * 255);
-			u8a[i * 4 + 3] = 255;
-		}
-
-		result = image_from_bytes(u8a.buffer, size2w, size2h);
-		return result;
-	}
-
-	static esrgan = (source: image_t): image_t => {
-		let result: image_t = null;
-		let size1w = source.width;
-		let size1h = source.height;
-		let tileSize = 512;
-		let tileSize2x = math_floor(tileSize * 2);
-
-		if (size1w >= tileSize2x || size1h >= tileSize2x) { // Split into tiles
-			let size2w = math_floor(size1w * 2);
-			let size2h = math_floor(size1h * 2);
-			result = image_create_render_target(size2w, size2h);
-			let tileSource = image_create_render_target(tileSize + 32 * 2, tileSize + 32 * 2);
-			for (let x = 0; x < math_floor(size1w / tileSize); ++x) {
-				for (let y = 0; y < math_floor(size1h / tileSize); ++y) {
-					g2_begin(tileSource);
-					g2_draw_scaled_image(source, 32 - x * tileSize, 32 - y * tileSize, -source.width, source.height);
-					g2_draw_scaled_image(source, 32 - x * tileSize, 32 - y * tileSize, source.width, -source.height);
-					g2_draw_scaled_image(source, 32 - x * tileSize, 32 - y * tileSize, -source.width, -source.height);
-					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 + tileSize, 32 - y * tileSize + tileSize, -source.width, source.height);
-					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.do_tile(tileSource);
-					g2_begin(result);
-					g2_draw_sub_image(tileResult, x * tileSize2x, y * tileSize2x, 64, 64, tileSize2x, tileSize2x);
-					g2_end();
-					image_unload(tileResult);
-				}
-			}
-			image_unload(tileSource);
-		}
-		else result = UpscaleNode.do_tile(source); // Single tile
-		return result;
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Upscale"),
-		type: "UpscaleNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			}
-		],
-		buttons: []
-	};
-}

+ 0 - 120
armorlab/Sources/nodes/VarianceNode.ts

@@ -1,120 +0,0 @@
-
-class VarianceNode extends LogicNode {
-
-	static temp: image_t = null;
-	static image: image_t = null;
-	static inst: VarianceNode = null;
-	static prompt = "";
-
-	constructor() {
-		super();
-		VarianceNode.inst = this;
-		VarianceNode.init();
-	}
-
-	static init = () => {
-		if (VarianceNode.temp == null) {
-			VarianceNode.temp = image_create_render_target(512, 512);
-		}
-	}
-
-	static buttons = (ui: zui_t, nodes: zui_nodes_t, node: zui_node_t) => {
-		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 get_as_image = (from: i32, done: (img: image_t)=>void) => {
-		let strength = (VarianceNode.inst.inputs[1].node as any).value;
-
-		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();
-
-			let bytes_img = image_get_pixels(VarianceNode.temp);
-			let u8a = new Uint8Array(bytes_img);
-			let f32a = new Float32Array(3 * 512 * 512);
-			for (let i = 0; i < (512 * 512); ++i) {
-				f32a[i                ] = (u8a[i * 4    ] / 255) * 2.0 - 1.0;
-				f32a[i + 512 * 512    ] = (u8a[i * 4 + 1] / 255) * 2.0 - 1.0;
-				f32a[i + 512 * 512 * 2] = (u8a[i * 4 + 2] / 255) * 2.0 - 1.0;
-			}
-
-			console_progress(tr("Processing") + " - " + tr("Variance"));
-			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);
-				for (let i = 0; i < latents.length; ++i) {
-					latents[i] = 0.18215 * latents[i];
-				}
-
-				let noise = new Float32Array(latents.length);
-				for (let i = 0; i < noise.length; ++i) noise[i] = math_cos(2.0 * 3.14 * RandomNode.getFloat()) * math_sqrt(-2.0 * math_log(RandomNode.getFloat()));
-				let num_inference_steps = 50;
-				let init_timestep = math_floor(num_inference_steps * strength);
-				let timesteps = TextToPhotoNode.timesteps[num_inference_steps - init_timestep];
-				let alphas_cumprod = TextToPhotoNode.alphas_cumprod;
-				let sqrt_alpha_prod = math_pow(alphas_cumprod[timesteps], 0.5);
-				let sqrt_one_minus_alpha_prod = math_pow(1.0 - alphas_cumprod[timesteps], 0.5);
-				for (let i = 0; i < latents.length; ++i) {
-					latents[i] = sqrt_alpha_prod * latents[i] + sqrt_one_minus_alpha_prod * noise[i];
-				}
-				let t_start = num_inference_steps - init_timestep;
-
-				TextToPhotoNode.stable_diffusion(VarianceNode.prompt, (_image: image_t) => {
-					VarianceNode.image = _image;
-					done(VarianceNode.image);
-				}, latents, t_start);
-			});
-		});
-	}
-
-	override get_cached_image = (): image_t => {
-		return VarianceNode.image;
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Variance"),
-		type: "VarianceNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Strength"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.5
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "RGBA",
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			}
-		],
-		buttons: [
-			{
-				name: "VarianceNode.buttons",
-				type: "CUSTOM",
-				height: 1
-			}
-		]
-	};
-}

+ 72 - 0
armorlab/Sources/nodes/brush_output_node.ts

@@ -0,0 +1,72 @@
+
+type brush_output_node_t = {
+	base?: logic_node_t;
+	id?: i32;
+	texpaint?: image_t;
+	texpaint_nor?: image_t;
+	texpaint_pack?: image_t;
+	texpaint_nor_empty?: image_t;
+	texpaint_pack_empty?: image_t;
+};
+
+let brush_output_node_inst: brush_output_node_t = null;
+
+function brush_output_node_create(): brush_output_node_t {
+	let n: brush_output_node_t = {};
+	n.base = logic_node_create();
+
+	if (brush_output_node_inst == null) {
+		{
+			let t = render_target_create();
+			t.name = "texpaint";
+			t.width = config_get_texture_res_x();
+			t.height = config_get_texture_res_y();
+			t.format = "RGBA32";
+			n.texpaint = render_path_create_render_target(t)._image;
+		}
+		{
+			let t = render_target_create();
+			t.name = "texpaint_nor";
+			t.width = config_get_texture_res_x();
+			t.height = config_get_texture_res_y();
+			t.format = "RGBA32";
+			n.texpaint_nor = render_path_create_render_target(t)._image;
+		}
+		{
+			let t = render_target_create();
+			t.name = "texpaint_pack";
+			t.width = config_get_texture_res_x();
+			t.height = config_get_texture_res_y();
+			t.format = "RGBA32";
+			n.texpaint_pack = render_path_create_render_target(t)._image;
+		}
+		{
+			let t = render_target_create();
+			t.name = "texpaint_nor_empty";
+			t.width = 1;
+			t.height = 1;
+			t.format = "RGBA32";
+			n.texpaint_nor_empty = render_path_create_render_target(t)._image;
+		}
+		{
+			let t = render_target_create();
+			t.name = "texpaint_pack_empty";
+			t.width = 1;
+			t.height = 1;
+			t.format = "RGBA32";
+			n.texpaint_pack_empty = render_path_create_render_target(t)._image;
+		}
+	}
+	else {
+		n.texpaint = brush_output_node_inst.texpaint;
+		n.texpaint_nor = brush_output_node_inst.texpaint_nor;
+		n.texpaint_pack = brush_output_node_inst.texpaint_pack;
+	}
+
+	brush_output_node_inst = n;
+	return n;
+}
+
+function brush_output_node_get_as_image(self: brush_output_node_t, from: i32, done: (img: image_t)=>void) {
+	self.base.inputs[from].get_as_image(done);
+}

+ 77 - 0
armorlab/Sources/nodes/image_texture_node.ts

@@ -0,0 +1,77 @@
+
+type image_texture_node_t = {
+	base?: logic_node_t;
+	file?: string;
+	color_space?: string;
+};
+
+function image_texture_node_create(): image_texture_node_t {
+	let n: image_texture_node_t = {};
+	n.base = logic_node_create();
+	n.base.get_as_image = image_texture_node_get_as_image;
+	n.base.get_cached_image = image_texture_node_get_cached_image;
+	return n;
+}
+
+function image_texture_node_get_as_image(self: image_texture_node_t, from: i32, done: (img: image_t)=>void) {
+	let index = project_asset_names.indexOf(self.file);
+	let asset = project_assets[index];
+	done(project_get_image(asset));
+}
+
+function image_texture_node_get_cached_image(self: image_texture_node_t): image_t {
+	let image: image_t;
+	self.base.get_as_image(self, 0, (img: image_t) => { image = img; });
+	return image;
+}
+
+let image_texture_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Image Texture"),
+	type: "image_texture_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Vector"),
+			type: "VECTOR",
+			color: 0xff6363c7,
+			default_value: new Float32Array([0.0, 0.0, 0.0])
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Alpha"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 1.0
+		}
+	],
+	buttons: [
+		{
+			name: _tr("file"),
+			type: "ENUM",
+			default_value: 0,
+			data: ""
+		},
+		{
+			name: _tr("color_space"),
+			type: "ENUM",
+			default_value: 0,
+			data: ["linear", "srgb"]
+		}
+	]
+};

+ 225 - 0
armorlab/Sources/nodes/inpaint_node.ts

@@ -0,0 +1,225 @@
+
+declare let Krom_texsynth: any;
+
+type inpaint_node_t = {
+	base?: logic_node_t;
+};
+
+let inpaint_node_image: image_t = null;
+let inpaint_node_mask: image_t = null;
+let inpaint_node_result: image_t = null;
+
+let inpaint_node_temp: image_t = null;
+let inpaint_node_prompt = "";
+let inpaint_node_strength = 0.5;
+let inpaint_node_auto = true;
+
+function inpaint_node_create(): inpaint_node_t {
+	let n: inpaint_node_t = {};
+	n.base = logic_node_create();
+	n.base.get_as_image = inpaint_node_get_as_image;
+	n.base.get_cached_image = inpaint_node_get_cached_image;
+
+	inpaint_node_init();
+
+	return n;
+}
+
+function inpaint_node_init() {
+	if (inpaint_node_image == null) {
+		inpaint_node_image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
+	}
+
+	if (inpaint_node_mask == null) {
+		inpaint_node_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(inpaint_node_mask);
+			g4_clear(color_from_floats(1.0, 1.0, 1.0, 1.0));
+			g4_end();
+		});
+	}
+
+	if (inpaint_node_temp == null) {
+		inpaint_node_temp = image_create_render_target(512, 512);
+	}
+
+	if (inpaint_node_result == null) {
+		inpaint_node_result = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
+	}
+}
+
+function inpaint_node_buttons(ui: zui_t, nodes: zui_nodes_t, node: zui_node_t) {
+	inpaint_node_auto = node.buttons[0].default_value == 0 ? false : true;
+	if (!inpaint_node_auto) {
+		inpaint_node_strength = zui_slider(zui_handle("inpaintnode_0", { value: inpaint_node_strength }), tr("strength"), 0, 1, true);
+		inpaint_node_prompt = zui_text_area(zui_handle("inpaintnode_1"), zui_align_t.LEFT, true, tr("prompt"), true);
+		node.buttons[1].height = 1 + inpaint_node_prompt.split("\n").length;
+	}
+	else node.buttons[1].height = 0;
+}
+
+function inpaint_node_get_as_image(self: inpaint_node_t, from: i32, done: (img: image_t)=>void) {
+	self.base.inputs[0].get_as_image((source: image_t) => {
+
+		console_progress(tr("Processing") + " - " + tr("Inpaint"));
+		base_notify_on_next_frame(() => {
+			g2_begin(inpaint_node_image);
+			g2_draw_scaled_image(source, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
+			g2_end();
+
+			inpaint_node_auto ? inpaint_node_texsynth_inpaint(inpaint_node_image, false, inpaint_node_mask, done) : inpaint_node_inpaint_node_sd_inpaint(inpaint_node_image, inpaint_node_mask, done);
+		});
+	});
+}
+
+function inpaint_node_get_cached_image(self: inpaint_node_t): image_t {
+	base_notify_on_next_frame(() => {
+		self.base.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(inpaint_node_image);
+			g4_set_pipeline(base_pipe_inpaint_preview);
+			g4_set_tex(base_tex0_inpaint_preview, source);
+			g4_set_tex(base_texa_inpaint_preview, inpaint_node_mask);
+			g4_set_vertex_buffer(const_data_screen_aligned_vb);
+			g4_set_index_buffer(const_data_screen_aligned_ib);
+			g4_draw();
+			g4_end();
+		});
+	});
+	return inpaint_node_image;
+}
+
+function inpaint_node_get_target(): image_t {
+	return inpaint_node_mask;
+}
+
+function inpaint_node_texsynth_inpaint(image: image_t, tiling: bool, mask: image_t/* = null*/, done: (img: image_t)=>void) {
+	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);
+	let bytes_out = new ArrayBuffer(w * h * 4);
+	Krom_texsynth.inpaint(w, h, bytes_out, bytes_img, bytes_mask, tiling);
+
+	inpaint_node_result = image_from_bytes(bytes_out, w, h);
+	done(inpaint_node_result);
+}
+
+function inpaint_node_sd_inpaint(image: image_t, mask: image_t, done: (img: image_t)=>void) {
+	inpaint_node_init();
+
+	let bytes_img = image_get_pixels(mask);
+	let u8 = new Uint8Array(bytes_img);
+	let f32mask = new Float32Array(4 * 64 * 64);
+
+	let vae_encoder_blob: ArrayBuffer = data_get_blob("models/sd_vae_encoder.quant.onnx");
+	// for (let x = 0; x < math_floor(image.width / 512); ++x) {
+		// for (let y = 0; y < math_floor(image.height / 512); ++y) {
+			let x = 0;
+			let y = 0;
+
+			for (let xx = 0; xx < 64; ++xx) {
+				for (let yy = 0; yy < 64; ++yy) {
+					// let step = math_floor(512 / 64);
+					// let j = (yy * step * mask.width + xx * step) + (y * 512 * mask.width + x * 512);
+					let step = math_floor(mask.width / 64);
+					let j = (yy * step * mask.width + xx * step);
+					let f = u8[j] / 255.0;
+					let i = yy * 64 + xx;
+					f32mask[i              ] = f;
+					f32mask[i + 64 * 64    ] = f;
+					f32mask[i + 64 * 64 * 2] = f;
+					f32mask[i + 64 * 64 * 3] = f;
+				}
+			}
+
+			g2_begin(inpaint_node_temp);
+			// g2_drawImage(image, -x * 512, -y * 512);
+			g2_draw_scaled_image(image, 0, 0, 512, 512);
+			g2_end();
+
+			bytes_img = image_get_pixels(inpaint_node_temp);
+			let u8a = new Uint8Array(bytes_img);
+			let f32a = new Float32Array(3 * 512 * 512);
+			for (let i = 0; i < (512 * 512); ++i) {
+				f32a[i                ] = (u8a[i * 4    ] / 255.0) * 2.0 - 1.0;
+				f32a[i + 512 * 512    ] = (u8a[i * 4 + 1] / 255.0) * 2.0 - 1.0;
+				f32a[i + 512 * 512 * 2] = (u8a[i * 4 + 2] / 255.0) * 2.0 - 1.0;
+			}
+
+			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);
+			for (let i = 0; i < latents.length; ++i) {
+				latents[i] = 0.18215 * latents[i];
+			}
+			let latents_orig = latents.slice(0);
+
+			let noise = new Float32Array(latents.length);
+			for (let i = 0; i < noise.length; ++i) noise[i] = math_cos(2.0 * 3.14 * RandomNode.getFloat()) * math_sqrt(-2.0 * math_log(RandomNode.getFloat()));
+
+			let num_inference_steps = 50;
+			let init_timestep = math_floor(num_inference_steps * inpaint_node_strength);
+			let timestep = TextToPhotoNode.timesteps[num_inference_steps - init_timestep];
+			let alphas_cumprod = TextToPhotoNode.alphas_cumprod;
+			let sqrt_alpha_prod = math_pow(alphas_cumprod[timestep], 0.5);
+			let sqrt_one_minus_alpha_prod = math_pow(1.0 - alphas_cumprod[timestep], 0.5);
+			for (let i = 0; i < latents.length; ++i) {
+				latents[i] = sqrt_alpha_prod * latents[i] + sqrt_one_minus_alpha_prod * noise[i];
+			}
+
+			let start = num_inference_steps - init_timestep;
+
+			TextToPhotoNode.stable_diffusion(inpaint_node_prompt, (img: image_t) => {
+				// result.g2_begin();
+				// result.g2_draw_image(img, x * 512, y * 512);
+				// result.g2_end();
+				inpaint_node_result = img;
+				done(img);
+			}, latents, start, true, f32mask, latents_orig);
+		// }
+	// }
+}
+
+let inpaint_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Inpaint"),
+	type: "inpaint_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([1.0, 1.0, 1.0, 1.0])
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		}
+	],
+	buttons: [
+		{
+			name: _tr("auto"),
+			type: "BOOL",
+			default_value: true,
+			output: 0
+		},
+		{
+			name: "inpaint_node_buttons",
+			type: "CUSTOM",
+			height: 0
+		}
+	]
+};

+ 244 - 0
armorlab/Sources/nodes/photo_to_pbr_node.ts

@@ -0,0 +1,244 @@
+
+type photo_to_pbr_node_t = {
+	base?: logic_node_t;
+};
+
+let photo_to_pbr_node_temp: image_t = null;
+let photo_to_pbr_node_images: image_t[] = null;
+let photo_to_pbr_node_model_names: string[] = ["base", "occlusion", "roughness", "metallic", "normal", "height"];
+
+let photo_to_pbr_node_cached_source: image_t = null;
+let photo_to_pbr_node_border_w: i32 = 64;
+let photo_to_pbr_node_tile_w: i32 = 2048;
+let photo_to_pbr_node_tile_with_border_w: i32 = photo_to_pbr_node_tile_w + photo_to_pbr_node_border_w * 2;
+
+function photo_to_pbr_node_create(): photo_to_pbr_node_t {
+	let n: photo_to_pbr_node_t = {};
+	n.base = logic_node_create();
+	n.base.get_as_image = photo_to_pbr_node_get_as_image;
+
+	if (photo_to_pbr_node_temp == null) {
+		photo_to_pbr_node_temp = image_create_render_target(photo_to_pbr_node_tile_with_border_w, photo_to_pbr_node_tile_with_border_w);
+	}
+
+	photo_to_pbr_node_init();
+
+	return n;
+}
+
+function photo_to_pbr_node_init() {
+	if (photo_to_pbr_node_images == null) {
+		photo_to_pbr_node_images = [];
+		for (let i = 0; i < photo_to_pbr_node_model_names.length; ++i) {
+			photo_to_pbr_node_images.push(image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y()));
+		}
+	}
+}
+
+function photo_to_pbr_node_get_as_image(self: photo_to_pbr_node_t, from: i32, done: (img: image_t)=>void) {
+	let get_source = function (done: (img: image_t)=>void) {
+		if (photo_to_pbr_node_cached_source != null) done(photo_to_pbr_node_cached_source);
+		else self.base.inputs[0].get_as_image(done);
+	}
+
+	get_source(function (source: image_t) {
+		photo_to_pbr_node_cached_source = source;
+
+		console_progress(tr("Processing") + " - " + tr("Photo to PBR"));
+		base_notify_on_next_frame(() => {
+			let tile_floats: Float32Array[] = [];
+			let tiles_x = math_floor(config_get_texture_res_x() / photo_to_pbr_node_tile_w);
+			let tiles_y = math_floor(config_get_texture_res_y() / photo_to_pbr_node_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(photo_to_pbr_node_temp);
+				g2_draw_scaled_image(source, photo_to_pbr_node_border_w - x * photo_to_pbr_node_tile_w, photo_to_pbr_node_border_w - y * photo_to_pbr_node_tile_w, -config_get_texture_res_x(), config_get_texture_res_y());
+				g2_draw_scaled_image(source, photo_to_pbr_node_border_w - x * photo_to_pbr_node_tile_w, photo_to_pbr_node_border_w - y * photo_to_pbr_node_tile_w, config_get_texture_res_x(), -config_get_texture_res_y());
+				g2_draw_scaled_image(source, photo_to_pbr_node_border_w - x * photo_to_pbr_node_tile_w, photo_to_pbr_node_border_w - y * photo_to_pbr_node_tile_w, -config_get_texture_res_x(), -config_get_texture_res_y());
+				g2_draw_scaled_image(source, photo_to_pbr_node_border_w - x * photo_to_pbr_node_tile_w + photo_to_pbr_node_tile_w, photo_to_pbr_node_border_w - y * photo_to_pbr_node_tile_w + photo_to_pbr_node_tile_w, config_get_texture_res_x(), config_get_texture_res_y());
+				g2_draw_scaled_image(source, photo_to_pbr_node_border_w - x * photo_to_pbr_node_tile_w + photo_to_pbr_node_tile_w, photo_to_pbr_node_border_w - y * photo_to_pbr_node_tile_w + photo_to_pbr_node_tile_w, -config_get_texture_res_x(), config_get_texture_res_y());
+				g2_draw_scaled_image(source, photo_to_pbr_node_border_w - x * photo_to_pbr_node_tile_w + photo_to_pbr_node_tile_w, photo_to_pbr_node_border_w - y * photo_to_pbr_node_tile_w + photo_to_pbr_node_tile_w, config_get_texture_res_x(), -config_get_texture_res_y());
+				g2_draw_scaled_image(source, photo_to_pbr_node_border_w - x * photo_to_pbr_node_tile_w, photo_to_pbr_node_border_w - y * photo_to_pbr_node_tile_w, config_get_texture_res_x(), config_get_texture_res_y());
+				g2_end();
+
+				let bytes_img = image_get_pixels(photo_to_pbr_node_temp);
+				let u8a = new Uint8Array(bytes_img);
+				let f32a = new Float32Array(3 * photo_to_pbr_node_tile_with_border_w * photo_to_pbr_node_tile_with_border_w);
+				for (let i = 0; i < (photo_to_pbr_node_tile_with_border_w * photo_to_pbr_node_tile_with_border_w); ++i) {
+					f32a[i                                        ] = (u8a[i * 4    ] / 255 - 0.5) / 0.5;
+					f32a[i + photo_to_pbr_node_tile_with_border_w * photo_to_pbr_node_tile_with_border_w    ] = (u8a[i * 4 + 1] / 255 - 0.5) / 0.5;
+					f32a[i + photo_to_pbr_node_tile_with_border_w * photo_to_pbr_node_tile_with_border_w * 2] = (u8a[i * 4 + 2] / 255 - 0.5) / 0.5;
+				}
+
+				let model_blob: ArrayBuffer = data_get_blob("models/photo_to_" + photo_to_pbr_node_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 * photo_to_pbr_node_tile_w * photo_to_pbr_node_tile_w);
+				let offset_g = (from == channel_type_t.BASE_COLOR || from == channel_type_t.NORMAL_MAP) ? photo_to_pbr_node_tile_with_border_w * photo_to_pbr_node_tile_with_border_w : 0;
+				let offset_b = (from == channel_type_t.BASE_COLOR || from == channel_type_t.NORMAL_MAP) ? photo_to_pbr_node_tile_with_border_w * photo_to_pbr_node_tile_with_border_w * 2 : 0;
+				for (let i = 0; i < (photo_to_pbr_node_tile_w * photo_to_pbr_node_tile_w); ++i) {
+					let x = photo_to_pbr_node_border_w + i % photo_to_pbr_node_tile_w;
+					let y = photo_to_pbr_node_border_w + math_floor(i / photo_to_pbr_node_tile_w);
+					u8a[i * 4    ] = math_floor((ar[y * photo_to_pbr_node_tile_with_border_w + x          ] * 0.5 + 0.5) * 255);
+					u8a[i * 4 + 1] = math_floor((ar[y * photo_to_pbr_node_tile_with_border_w + x + offset_g] * 0.5 + 0.5) * 255);
+					u8a[i * 4 + 2] = math_floor((ar[y * photo_to_pbr_node_tile_with_border_w + x + offset_b] * 0.5 + 0.5) * 255);
+					u8a[i * 4 + 3] = 255;
+				}
+				tile_floats.push(ar);
+
+				// Use border pixels to blend seams
+				if (i > 0) {
+					if (x > 0) {
+						let ar = tile_floats[i - 1];
+						for (let yy = 0; yy < photo_to_pbr_node_tile_w; ++yy) {
+							for (let xx = 0; xx < photo_to_pbr_node_border_w; ++xx) {
+								let i = yy * photo_to_pbr_node_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[(photo_to_pbr_node_border_w + yy) * photo_to_pbr_node_tile_with_border_w + photo_to_pbr_node_border_w + photo_to_pbr_node_tile_w + xx          ] * 0.5 + 0.5) * 255);
+								let bb = math_floor((ar[(photo_to_pbr_node_border_w + yy) * photo_to_pbr_node_tile_with_border_w + photo_to_pbr_node_border_w + photo_to_pbr_node_tile_w + xx + offset_g] * 0.5 + 0.5) * 255);
+								let cc = math_floor((ar[(photo_to_pbr_node_border_w + yy) * photo_to_pbr_node_tile_with_border_w + photo_to_pbr_node_border_w + photo_to_pbr_node_tile_w + xx + offset_b] * 0.5 + 0.5) * 255);
+
+								let f = xx / photo_to_pbr_node_border_w;
+								let invf = 1.0 - f;
+								a = math_floor(a * f + aa * invf);
+								b = math_floor(b * f + bb * invf);
+								c = math_floor(c * f + cc * invf);
+
+								u8a[i * 4    ] = a;
+								u8a[i * 4 + 1] = b;
+								u8a[i * 4 + 2] = c;
+							}
+						}
+					}
+					if (y > 0) {
+						let ar = tile_floats[i - tiles_x];
+						for (let xx = 0; xx < photo_to_pbr_node_tile_w; ++xx) {
+							for (let yy = 0; yy < photo_to_pbr_node_border_w; ++yy) {
+								let i = yy * photo_to_pbr_node_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[(photo_to_pbr_node_border_w + photo_to_pbr_node_tile_w + yy) * photo_to_pbr_node_tile_with_border_w + photo_to_pbr_node_border_w + xx          ] * 0.5 + 0.5) * 255);
+								let bb = math_floor((ar[(photo_to_pbr_node_border_w + photo_to_pbr_node_tile_w + yy) * photo_to_pbr_node_tile_with_border_w + photo_to_pbr_node_border_w + xx + offset_g] * 0.5 + 0.5) * 255);
+								let cc = math_floor((ar[(photo_to_pbr_node_border_w + photo_to_pbr_node_tile_w + yy) * photo_to_pbr_node_tile_with_border_w + photo_to_pbr_node_border_w + xx + offset_b] * 0.5 + 0.5) * 255);
+
+								let f = yy / photo_to_pbr_node_border_w;
+								let invf = 1.0 - f;
+								a = math_floor(a * f + aa * invf);
+								b = math_floor(b * f + bb * invf);
+								c = math_floor(c * f + cc * invf);
+
+								u8a[i * 4    ] = a;
+								u8a[i * 4 + 1] = b;
+								u8a[i * 4 + 2] = c;
+							}
+						}
+					}
+				}
+
+				///if (krom_metal || krom_vulkan)
+				if (from == channel_type_t.BASE_COLOR) photo_to_pbr_node_bgra_swap(u8a.buffer);
+				///end
+
+				let temp2 = image_from_bytes(u8a.buffer, photo_to_pbr_node_tile_w, photo_to_pbr_node_tile_w);
+				g2_begin(photo_to_pbr_node_images[from]);
+				g2_draw_image(temp2, x * photo_to_pbr_node_tile_w, y * photo_to_pbr_node_tile_w);
+				g2_end();
+				base_notify_on_next_frame(() => {
+					image_unload(temp2);
+				});
+			}
+
+			done(photo_to_pbr_node_images[from]);
+		});
+	});
+}
+
+///if (krom_metal || krom_vulkan)
+function photo_to_pbr_node_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];
+		u8a[i * 4] = u8a[i * 4 + 2];
+		u8a[i * 4 + 2] = r;
+	}
+	return buffer;
+}
+///end
+
+let photo_to_pbr_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Photo to PBR"),
+	type: "photo_to_pbr_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Base Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Occlusion"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 1.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Roughness"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 1.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Metallic"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Normal Map"),
+			type: "VECTOR",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Height"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 1.0
+		}
+	],
+	buttons: []
+};

+ 64 - 0
armorlab/Sources/nodes/rgb_node.ts

@@ -0,0 +1,64 @@
+
+type rgb_node_t = {
+	base?: logic_node_t;
+	image?: image_t;
+};
+
+function rgb_node_create(): rgb_node_t {
+	let n: rgb_node_t = {};
+	n.base = logic_node_create();
+	n.base.get_as_image = rgb_node_get_as_image;
+	n.base.get_cached_image = rgb_node_get_cached_image;
+	return n;
+}
+
+function rgb_node_get_as_image(self: rgb_node_t, from: i32, done: (img: image_t)=>void) {
+	if (self.image != null) {
+		base_notify_on_next_frame(() => {
+			image_unload(self.image);
+		});
+	}
+
+	let f32a = new Float32Array(4);
+	let raw = parser_logic_get_raw_node(self);
+	let default_value = raw.outputs[0].default_value;
+	f32a[0] = default_value[0];
+	f32a[1] = default_value[1];
+	f32a[2] = default_value[2];
+	f32a[3] = default_value[3];
+	self.image = image_from_bytes(f32a.buffer, 1, 1, tex_format_t.RGBA128);
+	done(self.image);
+}
+
+function rgb_node_get_cached_image(self: rgb_node_t): image_t {
+	self.base.get_as_image(self, 0, (img: image_t) => {});
+	return self.image;
+}
+
+let rgb_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("RGB"),
+	type: "rgb_node",
+	x: 0,
+	y: 0,
+	color: 0xffb34f5a,
+	inputs: [],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.8, 0.8, 0.8, 1.0])
+		}
+	],
+	buttons: [
+		{
+			name: _tr("default_value"),
+			type: "RGBA",
+			output: 0,
+			default_value: new Float32Array([0.8, 0.8, 0.8, 1.0])
+		}
+	]
+};

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 471 - 0
armorlab/Sources/nodes/text_to_photo_node.ts


+ 134 - 0
armorlab/Sources/nodes/tiling_node.ts

@@ -0,0 +1,134 @@
+
+type tiling_node_t = {
+	base?: logic_node_t;
+	result?: image_t;
+};
+
+let tiling_node_image: image_t = null;
+let tiling_node_prompt: string = "";
+let tiling_node_strength: f32 = 0.5;
+let tiling_node_auto: bool = true;
+
+function tiling_node_create(): tiling_node_t {
+	let n: float_node_t = {};
+	n.base = logic_node_create();
+	n.base.get_as_image = tiling_node_get_as_image;
+	n.base.get_cached_image = tiling_node_get_cached_image;
+	tiling_node_init();
+	return n;
+}
+
+function tiling_node_init() {
+	if (tiling_node_image == null) {
+		tiling_node_image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
+	}
+}
+
+function tiling_node_buttons(ui: zui_t, nodes: zui_nodes_t, node: zui_node_t) {
+	tiling_node_auto = node.buttons[0].default_value == 0 ? false : true;
+	if (!tiling_node_auto) {
+		tiling_node_strength = zui_slider(zui_handle("tilingnode_0", { value: tiling_node_strength }), tr("strength"), 0, 1, true);
+		tiling_node_prompt = zui_text_area(zui_handle("tilingnode_1"), zui_align_t.LEFT, true, tr("prompt"), true);
+		node.buttons[1].height = 1 + tiling_node_prompt.split("\n").length;
+	}
+	else node.buttons[1].height = 0;
+}
+
+function tiling_node_get_as_image(self: tiling_node_t, from: i32, done: (img: image_t)=>void) {
+	self.base.inputs[0].get_as_image((source: image_t) => {
+		g2_begin(tiling_node_image);
+		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_notify_on_next_frame(function () {
+			let _done = function (image: image_t) {
+				self.result = image;
+				done(image);
+			}
+			tiling_node_auto ? inpaint_node_texsynth_inpaint(tiling_node_image, true, null, _done) : tiling_node_sd_tiling(tiling_node_image, -1, _done);
+		});
+	});
+}
+
+function tiling_node_get_cached_image(self: tiling_node_t): image_t => {
+	return self.result;
+}
+
+function tiling_node_sd_tiling(image: image_t, seed: i32/* = -1*/, done: (img: image_t)=>void) {
+	text_to_photo_node_tiling = false;
+	let tile = image_create_render_target(512, 512);
+	g2_begin(tile);
+	g2_draw_scaled_image(image, -256, -256, 512, 512);
+	g2_draw_scaled_image(image, 256, -256, 512, 512);
+	g2_draw_scaled_image(image, -256, 256, 512, 512);
+	g2_draw_scaled_image(image, 256, 256, 512, 512);
+	g2_end();
+
+	let u8a = new Uint8Array(512 * 512);
+	for (let i = 0; i < 512 * 512; ++i) {
+		let x = i % 512;
+		let y = math_floor(i / 512);
+		let l = y < 256 ? y : (511 - y);
+		u8a[i] = (x > 256 - l && x < 256 + l) ? 0 : 255;
+	}
+	// for (let i = 0; i < 512 * 512; ++i) u8a[i] = 255;
+	// for (let x = (256 - 32); x < (256 + 32); ++x) {
+	// 	for (let y = 0; y < 512; ++y) {
+	// 		u8a[y * 512 + x] = 0;
+	// 	}
+	// }
+	// for (let x = 0; x < 512; ++x) {
+	// 	for (let y = (256 - 32); y < 256 + 32); ++y) {
+	// 		u8a[y * 512 + x] = 0;
+	// 	}
+	// }
+	let mask = image_from_bytes(u8a.buffer, 512, 512, tex_format_t.R8);
+
+	inpaint_node_prompt = tiling_node_prompt;
+	inpaint_node_strength = tiling_node_strength;
+	if (seed >= 0) random_node_set_seed(seed);
+	inpaint_node_sd_inpaint(tile, mask, done);
+}
+
+let tiling_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Tiling"),
+	type: "tiling_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		}
+	],
+	buttons: [
+		{
+			name: _tr("auto"),
+			type: "BOOL",
+			default_value: true,
+			output: 0
+		},
+		{
+			name: "tiling_node_buttons",
+			type: "CUSTOM",
+			height: 0
+		}
+	]
+};

+ 155 - 0
armorlab/Sources/nodes/upscale_node.ts

@@ -0,0 +1,155 @@
+
+type upscale_node_t = {
+	base?: logic_node_t;
+};
+
+let upscale_node_temp: image_t = null;
+let upscale_node_image: image_t = null;
+let upscale_node_esrgan_blob: ArrayBuffer;
+
+function upscale_node_create(): upscale_node_t {
+	let n: float_node_t = {};
+	n.base = logic_node_create();
+	n.base.get_as_image = upscale_node_get_as_image;
+	n.base.get_cached_image = upscale_node_get_cached_image;
+	return n;
+}
+
+function upscale_node_get_as_image(self: upscale_node_t, from: i32, done: (img: image_t)=>void) {
+	self.base.inputs[0].get_as_image((_image: image_t) => {
+		upscale_node_image = _image;
+
+		console_progress(tr("Processing") + " - " + tr("Upscale"));
+		base_notify_on_next_frame(function () {
+			upscale_node_load_blob(function () {
+				if (upscale_node_image.width < config_get_texture_res_x()) {
+					upscale_node_image = upscale_node_esrgan(upscale_node_image);
+					while (upscale_node_image.width < config_get_texture_res_x()) {
+						let lastImage = upscale_node_image;
+						upscale_node_image = upscale_node_esrgan(upscale_node_image);
+						image_unload(lastImage);
+					}
+				}
+				done(upscale_node_image);
+			});
+		});
+	});
+}
+
+function upscale_node_load_blob(done: ()=>void) {
+	let _esrgan_blob: ArrayBuffer = data_get_blob("models/esrgan.quant.onnx");
+	upscale_node_esrgan_blob = _esrgan_blob;
+	done();
+}
+
+function upscale_node_get_cached_image(self: upscale_node_t): image_t {
+	return upscale_node_image;
+}
+
+function upscale_node_do_tile(source: image_t) {
+	let result: image_t = null;
+	let size1w = source.width;
+	let size1h = source.height;
+	let size2w = math_floor(size1w * 2);
+	let size2h = math_floor(size1h * 2);
+	if (upscale_node_temp != null) {
+		image_unload(upscale_node_temp);
+	}
+	upscale_node_temp = image_create_render_target(size1w, size1h);
+	g2_begin(upscale_node_temp);
+	g2_draw_scaled_image(source, 0, 0, size1w, size1h);
+	g2_end();
+
+	let bytes_img = image_get_pixels(upscale_node_temp);
+	let u8a = new Uint8Array(bytes_img);
+	let f32a = new Float32Array(3 * size1w * size1h);
+	for (let i = 0; i < (size1w * size1h); ++i) {
+		f32a[i                      ] = (u8a[i * 4    ] / 255);
+		f32a[i + size1w * size1w    ] = (u8a[i * 4 + 1] / 255);
+		f32a[i + size1w * size1w * 2] = (u8a[i * 4 + 2] / 255);
+	}
+
+	let esrgan2x_buf = krom_ml_inference(upscale_node_esrgan_blob, [f32a.buffer], [[1, 3, size1w, size1h]], [1, 3, size2w, size2h], config_raw.gpu_inference);
+	let esrgan2x = new Float32Array(esrgan2x_buf);
+	for (let i = 0; i < esrgan2x.length; ++i) {
+		if (esrgan2x[i] < 0) esrgan2x[i] = 0;
+		else if (esrgan2x[i] > 1) esrgan2x[i] = 1;
+	}
+
+	u8a = new Uint8Array(4 * size2w * size2h);
+	for (let i = 0; i < (size2w * size2h); ++i) {
+		u8a[i * 4    ] = math_floor(esrgan2x[i                      ] * 255);
+		u8a[i * 4 + 1] = math_floor(esrgan2x[i + size2w * size2w    ] * 255);
+		u8a[i * 4 + 2] = math_floor(esrgan2x[i + size2w * size2w * 2] * 255);
+		u8a[i * 4 + 3] = 255;
+	}
+
+	result = image_from_bytes(u8a.buffer, size2w, size2h);
+	return result;
+}
+
+function upscale_node_esrgan(source: image_t): image_t {
+	let result: image_t = null;
+	let size1w = source.width;
+	let size1h = source.height;
+	let tile_size = 512;
+	let tile_size2x = math_floor(tile_size * 2);
+
+	if (size1w >= tile_size2x || size1h >= tile_size2x) { // Split into tiles
+		let size2w = math_floor(size1w * 2);
+		let size2h = math_floor(size1h * 2);
+		result = image_create_render_target(size2w, size2h);
+		let tile_source = image_create_render_target(tile_size + 32 * 2, tile_size + 32 * 2);
+		for (let x: i32 = 0; x < math_floor(size1w / tile_size); ++x) {
+			for (let y: i32 = 0; y < math_floor(size1h / tile_size); ++y) {
+				g2_begin(tile_source);
+				g2_draw_scaled_image(source, 32 - x * tile_size, 32 - y * tile_size, -source.width, source.height);
+				g2_draw_scaled_image(source, 32 - x * tile_size, 32 - y * tile_size, source.width, -source.height);
+				g2_draw_scaled_image(source, 32 - x * tile_size, 32 - y * tile_size, -source.width, -source.height);
+				g2_draw_scaled_image(source, 32 - x * tile_size + tile_size, 32 - y * tile_size + tile_size, source.width, source.height);
+				g2_draw_scaled_image(source, 32 - x * tile_size + tile_size, 32 - y * tile_size + tile_size, -source.width, source.height);
+				g2_draw_scaled_image(source, 32 - x * tile_size + tile_size, 32 - y * tile_size + tile_size, source.width, -source.height);
+				g2_draw_scaled_image(source, 32 - x * tile_size, 32 - y * tile_size, source.width, source.height);
+				g2_end();
+				let tileResult = upscale_node_do_tile(tile_source);
+				g2_begin(result);
+				g2_draw_sub_image(tileResult, x * tile_size2x, y * tile_size2x, 64, 64, tile_size2x, tile_size2x);
+				g2_end();
+				image_unload(tileResult);
+			}
+		}
+		image_unload(tile_source);
+	}
+	else result = upscale_node_do_tile(source); // Single tile
+	return result;
+}
+
+let upscale_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Upscale"),
+	type: "upscale_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		}
+	],
+	buttons: []
+};

+ 127 - 0
armorlab/Sources/nodes/variance_node.ts

@@ -0,0 +1,127 @@
+
+type variance_node_t = {
+	base?: logic_node_t;
+};
+
+let variance_node_temp: image_t = null;
+let variance_node_image: image_t = null;
+let variance_node_inst: variance_node_t = null;
+let variance_node_prompt: string = "";
+
+function variance_node_create(): variance_node_t {
+	let n: variance_node_t = {};
+	n.base = logic_node_create();
+	n.base.get_as_image = variance_node_get_as_image;
+	n.base.get_cached_image = variance_node_get_cached_image;
+
+	variance_node_inst = n;
+	variance_node_init();
+
+	return n;
+}
+
+function variance_node_init() {
+	if (variance_node_temp == null) {
+		variance_node_temp = image_create_render_target(512, 512);
+	}
+}
+
+function variance_node_buttons(ui: zui_t, nodes: zui_nodes_t, node: zui_node_t) {
+	variance_node_prompt = zui_text_area(zui_handle("variancenode_0"), zui_align_t.LEFT, true, tr("prompt"), true);
+	node.buttons[0].height = variance_node_prompt.split("\n").length;
+}
+
+function variance_node_get_as_image(self: variance_node_t, from: i32, done: (img: image_t)=>void) {
+	let strength = (variance_node_inst.inputs[1].node as any).value;
+
+	variance_node_inst.inputs[0].get_as_image((source: image_t) => {
+		g2_begin(variance_node_temp);
+		g2_draw_scaled_image(source, 0, 0, 512, 512);
+		g2_end();
+
+		let bytes_img = image_get_pixels(variance_node_temp);
+		let u8a = new Uint8Array(bytes_img);
+		let f32a = new Float32Array(3 * 512 * 512);
+		for (let i = 0; i < (512 * 512); ++i) {
+			f32a[i                ] = (u8a[i * 4    ] / 255) * 2.0 - 1.0;
+			f32a[i + 512 * 512    ] = (u8a[i * 4 + 1] / 255) * 2.0 - 1.0;
+			f32a[i + 512 * 512 * 2] = (u8a[i * 4 + 2] / 255) * 2.0 - 1.0;
+		}
+
+		console_progress(tr("Processing") + " - " + tr("Variance"));
+		base_notify_on_next_frame(function () {
+			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);
+			for (let i = 0; i < latents.length; ++i) {
+				latents[i] = 0.18215 * latents[i];
+			}
+
+			let noise = new Float32Array(latents.length);
+			for (let i = 0; i < noise.length; ++i) noise[i] = math_cos(2.0 * 3.14 * random_node_get_float()) * math_sqrt(-2.0 * math_log(random_node_get_float()));
+			let num_inference_steps = 50;
+			let init_timestep = math_floor(num_inference_steps * strength);
+			let timesteps = text_to_photo_node_timesteps[num_inference_steps - init_timestep];
+			let alphas_cumprod = text_to_photo_node_alphas_cumprod;
+			let sqrt_alpha_prod = math_pow(alphas_cumprod[timesteps], 0.5);
+			let sqrt_one_minus_alpha_prod = math_pow(1.0 - alphas_cumprod[timesteps], 0.5);
+			for (let i = 0; i < latents.length; ++i) {
+				latents[i] = sqrt_alpha_prod * latents[i] + sqrt_one_minus_alpha_prod * noise[i];
+			}
+			let t_start = num_inference_steps - init_timestep;
+
+			text_to_photo_node_stable_diffusion(variance_node_prompt, (_image: image_t) => {
+				variance_node_image = _image;
+				done(variance_node_image);
+			}, latents, t_start);
+		});
+	});
+}
+
+function variance_node_get_cached_image(self: variance_node_t): image_t {
+	return variance_node_image;
+}
+
+let variance_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Variance"),
+	type: "variance_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Strength"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.5
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "RGBA",
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		}
+	],
+	buttons: [
+		{
+			name: "variance_node_buttons",
+			type: "CUSTOM",
+			height: 1
+		}
+	]
+};

BIN
armorpaint/Assets/default_brush.arm


+ 0 - 222
armorpaint/Sources/nodes/BrushOutputNode.ts

@@ -1,222 +0,0 @@
-
-class BrushOutputNode extends LogicNode {
-
-	Directional: bool = false; // button 0
-
-	constructor() {
-		super();
-		context_raw.run_brush = this.run;
-		context_raw.parse_brush_inputs = this.parse_inputs;
-	}
-
-	parse_inputs = () => {
-		let last_mask: image_t = context_raw.brush_mask_image;
-		let last_stencil: image_t = context_raw.brush_stencil_image;
-
-		let input0: any;
-		let input1: any;
-		let input2: any;
-		let input3: any;
-		let input4: any;
-		let input5: any;
-		let input6: any;
-		try {
-			this.inputs[0].get((value) => { input0 = value; });
-			this.inputs[1].get((value) => { input1 = value; });
-			this.inputs[2].get((value) => { input2 = value; });
-			this.inputs[3].get((value) => { input3 = value; });
-			this.inputs[4].get((value) => { input4 = value; });
-			this.inputs[5].get((value) => { input5 = value; });
-			this.inputs[6].get((value) => { input6 = value; });
-		}
-		catch (_) {
-			return;
-		}
-
-		context_raw.paint_vec = input0;
-		context_raw.brush_nodes_radius = input1;
-		context_raw.brush_nodes_scale = input2;
-		context_raw.brush_nodes_angle = input3;
-
-		let opac: any = input4; // Float or texture name
-		if (opac == null) opac = 1.0;
-		if (typeof opac == "string") {
-			context_raw.brush_mask_image_is_alpha = opac.endsWith(".a");
-			opac = opac.substr(0, opac.lastIndexOf("."));
-			context_raw.brush_nodes_opacity = 1.0;
-			let index: i32 = project_asset_names.indexOf(opac);
-			let asset: asset_t = project_assets[index];
-			context_raw.brush_mask_image = project_get_image(asset);
-		}
-		else {
-			context_raw.brush_nodes_opacity = opac;
-			context_raw.brush_mask_image = null;
-		}
-
-		context_raw.brush_nodes_hardness = input5;
-
-		let stencil: any = input6; // Float or texture name
-		if (stencil == null) stencil = 1.0;
-		if (typeof stencil == "string") {
-			context_raw.brush_stencil_image_is_alpha = stencil.endsWith(".a");
-			stencil = stencil.substr(0, stencil.lastIndexOf("."));
-			let index: i32 = project_asset_names.indexOf(stencil);
-			let asset: asset_t = project_assets[index];
-			context_raw.brush_stencil_image = project_get_image(asset);
-		}
-		else {
-			context_raw.brush_stencil_image = null;
-		}
-
-		if (last_mask != context_raw.brush_mask_image ||
-			last_stencil != context_raw.brush_stencil_image) {
-			make_material_parse_paint_material();
-		}
-
-		context_raw.brush_directional = this.Directional;
-	}
-
-	run = (from: i32) => {
-		let left: f32 = 0.0;
-		let right: f32 = 1.0;
-		if (context_raw.paint2d) {
-			left = 1.0;
-			right = (context_raw.split_view ? 2.0 : 1.0) + ui_view2d_ww / base_w();
-		}
-
-		// First time init
-		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 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 = slot_layer_is_group(context_raw.layer);
-
-		// Paint bounds
-		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 &&
-			!fill_layer &&
-			!group_layer &&
-			(slot_layer_is_visible(context_raw.layer) || context_raw.paint2d) &&
-			!ui_base_ui.is_hovered &&
-			!base_is_dragging &&
-			!base_is_resizing &&
-			!base_is_scrolling() &&
-			!base_is_combo_selected()) {
-
-			// Set color pick
-			let down: bool = mouse_down() || pen_down();
-			if (down && context_raw.tool == workspace_tool_t.COLORID && project_assets.length > 0) {
-				context_raw.colorid_picked = true;
-				ui_toolbar_handle.redraws = 1;
-			}
-
-			// Prevent painting the same spot
-			let same_spot: bool = context_raw.paint_vec.x == context_raw.last_paint_x && context_raw.paint_vec.y == context_raw.last_paint_y;
-			let lazy: bool = context_raw.tool == workspace_tool_t.BRUSH && context_raw.brush_lazy_radius > 0;
-			if (down && (same_spot || lazy)) {
-				context_raw.painted++;
-			}
-			else {
-				context_raw.painted = 0;
-			}
-			context_raw.last_paint_x = context_raw.paint_vec.x;
-			context_raw.last_paint_y = context_raw.paint_vec.y;
-
-			if (context_raw.tool == workspace_tool_t.PARTICLE) {
-				context_raw.painted = 0; // Always paint particles
-			}
-
-			if (context_raw.painted == 0) {
-				this.parse_inputs();
-			}
-
-			if (context_raw.painted <= 1) {
-				context_raw.pdirty = 1;
-				context_raw.rdirty = 2;
-			}
-		}
-	}
-
-	// static def: TNode = {
-	// 	id: 0,
-	// 	name: _tr("Brush Output"),
-	// 	type: "BrushOutputNode",
-	// 	x: 0,
-	// 	y: 0,
-	// 	color: 0xff4982a0,
-	// 	inputs: [
-	// 		{
-	// 			id: 0,
-	// 			node_id: 0,
-	// 			name: _tr("Position"),
-	// 			type: "VECTOR",
-	// 			color: 0xff63c763,
-	// 			default_value: f32([0.0, 0.0, 0.0])
-	// 		},
-	// 		{
-	// 			id: 0,
-	// 			node_id: 0,
-	// 			name: _tr("Radius"),
-	// 			type: "VALUE",
-	// 			color: 0xffa1a1a1,
-	// 			default_value: 1.0
-	// 		},
-	// 		{
-	// 			id: 0,
-	// 			node_id: 0,
-	// 			name: _tr("Scale"),
-	// 			type: "VALUE",
-	// 			color: 0xffa1a1a1,
-	// 			default_value: 1.0
-	// 		},
-	// 		{
-	// 			id: 0,
-	// 			node_id: 0,
-	// 			name: _tr("Angle"),
-	// 			type: "VALUE",
-	// 			color: 0xffa1a1a1,
-	// 			default_value: 0.0
-	// 		},
-	// 		{
-	// 			id: 0,
-	// 			node_id: 0,
-	// 			name: _tr("Opacity"),
-	// 			type: "VALUE",
-	// 			color: 0xffa1a1a1,
-	// 			default_value: 1.0
-	// 		},
-	// 		{
-	// 			id: 0,
-	// 			node_id: 0,
-	// 			name: _tr("Hardness"),
-	// 			type: "VALUE",
-	// 			color: 0xffa1a1a1,
-	// 			default_value: 1.0
-	// 		},
-	// 		{
-	// 			id: 0,
-	// 			node_id: 0,
-	// 			name: _tr("Stencil"),
-	// 			type: "VALUE",
-	// 			color: 0xffa1a1a1,
-	// 			default_value: 1.0
-	// 		}
-	// 	],
-	// 	outputs: [],
-	// 	buttons: [
-	// 		{
-	// 			name: _tr("Directional"),
-	// 			type: "BOOL",
-	// 			default_value: false,
-	// 			output: 0
-	// 		}
-	// 	]
-	// };
-}

+ 0 - 163
armorpaint/Sources/nodes/InputNode.ts

@@ -1,163 +0,0 @@
-
-class InputNode extends LogicNode {
-
-	static coords: vec4_t = vec4_create();
-
-	static startX: f32 = 0.0;
-	static startY: f32 = 0.0;
-
-	// Brush ruler
-	static lock_begin: bool = false;
-	static lock_x: bool = false;
-	static lock_y: bool = false;
-	static lock_start_x: f32 = 0.0;
-	static lock_start_y: f32 = 0.0;
-
-	static registered: bool = false;
-
-	constructor() {
-		super();
-
-		if (!InputNode.registered) {
-			InputNode.registered = true;
-			app_notify_on_update(this.update);
-		}
-	}
-
-	update = () => {
-		if (context_raw.split_view) {
-			context_raw.view_index = mouse_view_x() > base_w() / 2 ? 1 : 0;
-		}
-
-		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 + "+" + config_keymap.action_paint, shortcut_type_t.DOWN);
-
-		let lazy_paint: bool = context_raw.brush_lazy_radius > 0 &&
-			(operator_shortcut(config_keymap.action_paint, shortcut_type_t.DOWN) ||
-			 operator_shortcut(config_keymap.brush_ruler + "+" + config_keymap.action_paint, shortcut_type_t.DOWN) ||
-			 decal_mask);
-
-		let paint_x: f32 = mouse_view_x() / app_w();
-		let paint_y: f32 = mouse_view_y() / app_h();
-		if (mouse_started()) {
-			InputNode.startX = mouse_view_x() / app_w();
-			InputNode.startY = mouse_view_y() / app_h();
-		}
-
-		if (pen_down()) {
-			paint_x = pen_view_x() / app_w();
-			paint_y = pen_view_y() / app_h();
-		}
-		if (pen_started()) {
-			InputNode.startX = pen_view_x() / app_w();
-			InputNode.startY = pen_view_y() / app_h();
-		}
-
-		if (operator_shortcut(config_keymap.brush_ruler + "+" + config_keymap.action_paint, shortcut_type_t.DOWN)) {
-			if (InputNode.lock_x) paint_x = InputNode.startX;
-			if (InputNode.lock_y) paint_y = InputNode.startY;
-		}
-
-		if (context_raw.brush_lazy_radius > 0) {
-			context_raw.brush_lazy_x = paint_x;
-			context_raw.brush_lazy_y = paint_y;
-		}
-		if (!lazy_paint) {
-			InputNode.coords.x = paint_x;
-			InputNode.coords.y = paint_y;
-		}
-
-		if (context_raw.split_view) {
-			context_raw.view_index = -1;
-		}
-
-		if (InputNode.lock_begin) {
-			let dx: f32 = math_abs(InputNode.lock_start_x - mouse_view_x());
-			let dy: f32 = math_abs(InputNode.lock_start_y - mouse_view_y());
-			if (dx > 1 || dy > 1) {
-				InputNode.lock_begin = false;
-				dx > dy ? InputNode.lock_y = true : InputNode.lock_x = true;
-			}
-		}
-
-		if (keyboard_started(config_keymap.brush_ruler)) {
-			InputNode.lock_start_x = mouse_view_x();
-			InputNode.lock_start_y = mouse_view_y();
-			InputNode.lock_begin = true;
-		}
-		else if (keyboard_released(config_keymap.brush_ruler)) {
-			InputNode.lock_x = InputNode.lock_y = InputNode.lock_begin = false;
-		}
-
-		if (context_raw.brush_lazy_radius > 0) {
-			let v1: vec4_t = vec4_create(context_raw.brush_lazy_x * app_w(), context_raw.brush_lazy_y * app_h(), 0.0);
-			let v2: vec4_t = vec4_create(InputNode.coords.x * app_w(), InputNode.coords.y * app_h(), 0.0);
-			let d: f32 = vec4_dist(v1, v2);
-			let r: f32 = context_raw.brush_lazy_radius * 85;
-			if (d > r) {
-				let v3: vec4_t = vec4_create();
-				vec4_sub_vecs(v3, v2, v1);
-				vec4_normalize(v3, );
-				vec4_mult(v3, 1.0 - context_raw.brush_lazy_step);
-				vec4_mult(v3, r);
-				vec4_add_vecs(v2, v1, v3);
-				InputNode.coords.x = v2.x / app_w();
-				InputNode.coords.y = v2.y / app_h();
-				// Parse brush inputs once on next draw
-				context_raw.painted = -1;
-			}
-			context_raw.last_paint_x = -1;
-			context_raw.last_paint_y = -1;
-		}
-
-		context_raw.parse_brush_inputs();
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		this.inputs[0].get((value) => {
-			context_raw.brush_lazy_radius = value;
-			this.inputs[1].get((value) => {
-				context_raw.brush_lazy_step = value;
-				done(InputNode.coords);
-			});
-		});
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Input"),
-		type: "InputNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Lazy Radius"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Lazy Step"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Position"),
-				type: "VECTOR",
-				color: 0xff63c763,
-				default_value: null
-			}
-		],
-		buttons: []
-	};
-}

+ 0 - 66
armorpaint/Sources/nodes/TEX_IMAGE.ts

@@ -1,66 +0,0 @@
-
-class TEX_IMAGE extends LogicNode {
-
-	file: string;
-	color_space: string;
-
-	constructor() {
-		super();
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		if (from == 0) done(this.file + ".rgb");
-		else done(this.file + ".a");
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Image Texture"),
-		type: "TEX_IMAGE",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Vector"),
-				type: "VECTOR",
-				color: 0xff6363c7,
-				default_value: new Float32Array([0.0, 0.0, 0.0])
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Color"),
-				type: "VALUE", // Match brush output socket type
-				color: 0xffc7c729,
-				default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Alpha"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 1.0
-			}
-		],
-		buttons: [
-			{
-				name: _tr("file"),
-				type: "ENUM",
-				default_value: 0,
-				data: ""
-			},
-			{
-				name: _tr("color_space"),
-				type: "ENUM",
-				default_value: 0,
-				data: ["linear", "srgb"]
-			}
-		]
-	};
-}

+ 227 - 0
armorpaint/Sources/nodes/brush_output_node.ts

@@ -0,0 +1,227 @@
+
+type brush_output_node_t = {
+	base?: logic_node_t;
+	Directional?: bool; // button 0
+};
+
+function brush_output_node_create(): brush_output_node_t {
+	let n: brush_output_node_t = {};
+	n.base = logic_node_create();
+	context_raw.run_brush = brush_output_node_run;
+	context_raw.parse_brush_inputs = brush_output_node_parse_inputs;
+	context_raw.brush_output_node_inst = n;
+	return n;
+}
+
+function brush_output_node_parse_inputs(self: brush_output_node_t) {
+
+	let last_mask: image_t = context_raw.brush_mask_image;
+	let last_stencil: image_t = context_raw.brush_stencil_image;
+
+	let input0: any;
+	let input1: any;
+	let input2: any;
+	let input3: any;
+	let input4: any;
+	let input5: any;
+	let input6: any;
+	try {
+		logic_node_input_get(self.base.inputs[0], function (value: any) { input0 = value; });
+		logic_node_input_get(self.base.inputs[1], function (value: any) { input1 = value; });
+		logic_node_input_get(self.base.inputs[2], function (value: any) { input2 = value; });
+		logic_node_input_get(self.base.inputs[3], function (value: any) { input3 = value; });
+		logic_node_input_get(self.base.inputs[4], function (value: any) { input4 = value; });
+		logic_node_input_get(self.base.inputs[5], function (value: any) { input5 = value; });
+		logic_node_input_get(self.base.inputs[6], function (value: any) { input6 = value; });
+	}
+	catch (e: any) {
+		krom_log(e);
+		return;
+	}
+
+	context_raw.paint_vec = input0;
+	context_raw.brush_nodes_radius = input1;
+	context_raw.brush_nodes_scale = input2;
+	context_raw.brush_nodes_angle = input3;
+
+	let opac: any = input4; // Float or texture name
+	if (opac == null) opac = 1.0;
+	if (typeof opac == "string") {
+		context_raw.brush_mask_image_is_alpha = opac.endsWith(".a");
+		opac = opac.substr(0, opac.lastIndexOf("."));
+		context_raw.brush_nodes_opacity = 1.0;
+		let index: i32 = project_asset_names.indexOf(opac);
+		let asset: asset_t = project_assets[index];
+		context_raw.brush_mask_image = project_get_image(asset);
+	}
+	else {
+		context_raw.brush_nodes_opacity = opac;
+		context_raw.brush_mask_image = null;
+	}
+
+	context_raw.brush_nodes_hardness = input5;
+
+	let stencil: any = input6; // Float or texture name
+	if (stencil == null) stencil = 1.0;
+	if (typeof stencil == "string") {
+		context_raw.brush_stencil_image_is_alpha = stencil.endsWith(".a");
+		stencil = stencil.substr(0, stencil.lastIndexOf("."));
+		let index: i32 = project_asset_names.indexOf(stencil);
+		let asset: asset_t = project_assets[index];
+		context_raw.brush_stencil_image = project_get_image(asset);
+	}
+	else {
+		context_raw.brush_stencil_image = null;
+	}
+
+	if (last_mask != context_raw.brush_mask_image ||
+		last_stencil != context_raw.brush_stencil_image) {
+		make_material_parse_paint_material();
+	}
+
+	context_raw.brush_directional = self.Directional;
+}
+
+function brush_output_node_run(self: brush_output_node_t, from: i32) {
+	let left: f32 = 0.0;
+	let right: f32 = 1.0;
+	if (context_raw.paint2d) {
+		left = 1.0;
+		right = (context_raw.split_view ? 2.0 : 1.0) + ui_view2d_ww / base_w();
+	}
+
+	// First time init
+	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 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 = slot_layer_is_group(context_raw.layer);
+
+	// Paint bounds
+	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 &&
+		!fill_layer &&
+		!group_layer &&
+		(slot_layer_is_visible(context_raw.layer) || context_raw.paint2d) &&
+		!ui_base_ui.is_hovered &&
+		!base_is_dragging &&
+		!base_is_resizing &&
+		!base_is_scrolling() &&
+		!base_is_combo_selected()) {
+
+		// Set color pick
+		let down: bool = mouse_down() || pen_down();
+		if (down && context_raw.tool == workspace_tool_t.COLORID && project_assets.length > 0) {
+			context_raw.colorid_picked = true;
+			ui_toolbar_handle.redraws = 1;
+		}
+
+		// Prevent painting the same spot
+		let same_spot: bool = context_raw.paint_vec.x == context_raw.last_paint_x && context_raw.paint_vec.y == context_raw.last_paint_y;
+		let lazy: bool = context_raw.tool == workspace_tool_t.BRUSH && context_raw.brush_lazy_radius > 0;
+		if (down && (same_spot || lazy)) {
+			context_raw.painted++;
+		}
+		else {
+			context_raw.painted = 0;
+		}
+		context_raw.last_paint_x = context_raw.paint_vec.x;
+		context_raw.last_paint_y = context_raw.paint_vec.y;
+
+		if (context_raw.tool == workspace_tool_t.PARTICLE) {
+			context_raw.painted = 0; // Always paint particles
+		}
+
+		if (context_raw.painted == 0) {
+			brush_output_node_parse_inputs(self);
+		}
+
+		if (context_raw.painted <= 1) {
+			context_raw.pdirty = 1;
+			context_raw.rdirty = 2;
+		}
+	}
+}
+
+// let brush_output_node_def: node_t = {
+// 	id: 0,
+// 	name: _tr("Brush Output"),
+// 	type: "brush_output_node",
+// 	x: 0,
+// 	y: 0,
+// 	color: 0xff4982a0,
+// 	inputs: [
+// 		{
+// 			id: 0,
+// 			node_id: 0,
+// 			name: _tr("Position"),
+// 			type: "VECTOR",
+// 			color: 0xff63c763,
+// 			default_value: f32([0.0, 0.0, 0.0])
+// 		},
+// 		{
+// 			id: 0,
+// 			node_id: 0,
+// 			name: _tr("Radius"),
+// 			type: "VALUE",
+// 			color: 0xffa1a1a1,
+// 			default_value: 1.0
+// 		},
+// 		{
+// 			id: 0,
+// 			node_id: 0,
+// 			name: _tr("Scale"),
+// 			type: "VALUE",
+// 			color: 0xffa1a1a1,
+// 			default_value: 1.0
+// 		},
+// 		{
+// 			id: 0,
+// 			node_id: 0,
+// 			name: _tr("Angle"),
+// 			type: "VALUE",
+// 			color: 0xffa1a1a1,
+// 			default_value: 0.0
+// 		},
+// 		{
+// 			id: 0,
+// 			node_id: 0,
+// 			name: _tr("Opacity"),
+// 			type: "VALUE",
+// 			color: 0xffa1a1a1,
+// 			default_value: 1.0
+// 		},
+// 		{
+// 			id: 0,
+// 			node_id: 0,
+// 			name: _tr("Hardness"),
+// 			type: "VALUE",
+// 			color: 0xffa1a1a1,
+// 			default_value: 1.0
+// 		},
+// 		{
+// 			id: 0,
+// 			node_id: 0,
+// 			name: _tr("Stencil"),
+// 			type: "VALUE",
+// 			color: 0xffa1a1a1,
+// 			default_value: 1.0
+// 		}
+// 	],
+// 	outputs: [],
+// 	buttons: [
+// 		{
+// 			name: _tr("Directional"),
+// 			type: "BOOL",
+// 			default_value: false,
+// 			output: 0
+// 		}
+// 	]
+// };

+ 165 - 0
armorpaint/Sources/nodes/input_node.ts

@@ -0,0 +1,165 @@
+
+type input_node_t = {
+	base?: logic_node_t;
+};
+
+let input_node_coords: vec4_t = vec4_create();
+let input_node_start_x: f32 = 0.0;
+let input_node_start_y: f32 = 0.0;
+// Brush ruler
+let input_node_lock_begin: bool = false;
+let input_node_lock_x: bool = false;
+let input_node_lock_y: bool = false;
+let input_node_lock_start_x: f32 = 0.0;
+let input_node_lock_start_y: f32 = 0.0;
+let input_node_registered: bool = false;
+
+function input_node_create(): input_node_t {
+	let n: float_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = input_node_get;
+
+	if (!input_node_registered) {
+		input_node_registered = true;
+		app_notify_on_update(input_node_update, n);
+	}
+
+	return n;
+}
+
+function input_node_update(self: float_node_t) {
+	if (context_raw.split_view) {
+		context_raw.view_index = mouse_view_x() > base_w() / 2 ? 1 : 0;
+	}
+
+	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 + "+" + config_keymap.action_paint, shortcut_type_t.DOWN);
+
+	let lazy_paint: bool = context_raw.brush_lazy_radius > 0 &&
+		(operator_shortcut(config_keymap.action_paint, shortcut_type_t.DOWN) ||
+			operator_shortcut(config_keymap.brush_ruler + "+" + config_keymap.action_paint, shortcut_type_t.DOWN) ||
+			decal_mask);
+
+	let paint_x: f32 = mouse_view_x() / app_w();
+	let paint_y: f32 = mouse_view_y() / app_h();
+	if (mouse_started()) {
+		input_node_start_x = mouse_view_x() / app_w();
+		input_node_start_y = mouse_view_y() / app_h();
+	}
+
+	if (pen_down()) {
+		paint_x = pen_view_x() / app_w();
+		paint_y = pen_view_y() / app_h();
+	}
+	if (pen_started()) {
+		input_node_start_x = pen_view_x() / app_w();
+		input_node_start_y = pen_view_y() / app_h();
+	}
+
+	if (operator_shortcut(config_keymap.brush_ruler + "+" + config_keymap.action_paint, shortcut_type_t.DOWN)) {
+		if (input_node_lock_x) paint_x = input_node_start_x;
+		if (input_node_lock_y) paint_y = input_node_start_y;
+	}
+
+	if (context_raw.brush_lazy_radius > 0) {
+		context_raw.brush_lazy_x = paint_x;
+		context_raw.brush_lazy_y = paint_y;
+	}
+	if (!lazy_paint) {
+		input_node_coords.x = paint_x;
+		input_node_coords.y = paint_y;
+	}
+
+	if (context_raw.split_view) {
+		context_raw.view_index = -1;
+	}
+
+	if (input_node_lock_begin) {
+		let dx: f32 = math_abs(input_node_lock_start_x - mouse_view_x());
+		let dy: f32 = math_abs(input_node_lock_start_y - mouse_view_y());
+		if (dx > 1 || dy > 1) {
+			input_node_lock_begin = false;
+			dx > dy ? input_node_lock_y = true : input_node_lock_x = true;
+		}
+	}
+
+	if (keyboard_started(config_keymap.brush_ruler)) {
+		input_node_lock_start_x = mouse_view_x();
+		input_node_lock_start_y = mouse_view_y();
+		input_node_lock_begin = true;
+	}
+	else if (keyboard_released(config_keymap.brush_ruler)) {
+		input_node_lock_x = input_node_lock_y = input_node_lock_begin = false;
+	}
+
+	if (context_raw.brush_lazy_radius > 0) {
+		let v1: vec4_t = vec4_create(context_raw.brush_lazy_x * app_w(), context_raw.brush_lazy_y * app_h(), 0.0);
+		let v2: vec4_t = vec4_create(input_node_coords.x * app_w(), input_node_coords.y * app_h(), 0.0);
+		let d: f32 = vec4_dist(v1, v2);
+		let r: f32 = context_raw.brush_lazy_radius * 85;
+		if (d > r) {
+			let v3: vec4_t = vec4_create();
+			vec4_sub_vecs(v3, v2, v1);
+			vec4_normalize(v3);
+			vec4_mult(v3, 1.0 - context_raw.brush_lazy_step);
+			vec4_mult(v3, r);
+			vec4_add_vecs(v2, v1, v3);
+			input_node_coords.x = v2.x / app_w();
+			input_node_coords.y = v2.y / app_h();
+			// Parse brush inputs once on next draw
+			context_raw.painted = -1;
+		}
+		context_raw.last_paint_x = -1;
+		context_raw.last_paint_y = -1;
+	}
+
+	context_raw.parse_brush_inputs(context_raw.brush_output_node_inst);
+}
+
+function input_node_get(self: input_node_t, from: i32, done: (a: any)=>void) {
+	logic_node_input_get(self.base.inputs[0], (value) => {
+		context_raw.brush_lazy_radius = value;
+		logic_node_input_get(self.base.inputs[1], (value) => {
+			context_raw.brush_lazy_step = value;
+			done(input_node_coords);
+		});
+	});
+}
+
+let input_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Input"),
+	type: "input_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Lazy Radius"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Lazy Step"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Position"),
+			type: "VECTOR",
+			color: 0xff63c763,
+			default_value: null
+		}
+	],
+	buttons: []
+};

+ 69 - 0
armorpaint/Sources/nodes/tex_image_node.ts

@@ -0,0 +1,69 @@
+
+type tex_image_node_t = {
+	base?: logic_node_t;
+	file?: string;
+	color_space?: string;
+};
+
+function tex_image_node_create(): tex_image_node_t {
+	let n: tex_image_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = tex_image_node_get;
+	return n;
+}
+
+function tex_image_node_get(self: tex_image_node_t, from: i32, done: (a: any)=>void) {
+	if (from == 0) done(self.file + ".rgb");
+	else done(self.file + ".a");
+}
+
+let tex_image_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Image Texture"),
+	type: "tex_image_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Vector"),
+			type: "VECTOR",
+			color: 0xff6363c7,
+			default_value: new Float32Array([0.0, 0.0, 0.0])
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Color"),
+			type: "VALUE", // Match brush output socket type
+			color: 0xffc7c729,
+			default_value: new Float32Array([0.0, 0.0, 0.0, 1.0])
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Alpha"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 1.0
+		}
+	],
+	buttons: [
+		{
+			name: _tr("file"),
+			type: "ENUM",
+			default_value: 0,
+			data: ""
+		},
+		{
+			name: _tr("color_space"),
+			type: "ENUM",
+			default_value: 0,
+			data: ["linear", "srgb"]
+		}
+	]
+};

+ 13 - 20
armorpaint/Sources/nodes_brush.ts

@@ -1,33 +1,26 @@
-/// <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'/>
+/// <reference path='./nodes/tex_image_node.ts'/>
+/// <reference path='./nodes/input_node.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
+		tex_image_node_def,
+		input_node_def,
+		math_node_def,
+		random_node_def,
+		separate_vector_node_def,
+		time_node_def,
+		float_node_def,
+		vector_node_def,
+		vector_math_node_def
 	]
 ];
 
-function nodes_brush_create_node(nodeType: string): zui_node_t {
+function nodes_brush_create_node(node_type: string): zui_node_t {
 	for (let c of nodes_brush_list) {
 		for (let n of c) {
-			if (n.type == nodeType) {
+			if (n.type == node_type) {
 				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);

BIN
armorsculpt/Assets/default_brush.arm


+ 0 - 136
armorsculpt/Sources/nodes/BrushOutputNode.ts

@@ -1,136 +0,0 @@
-
-class BrushOutputNode extends LogicNode {
-
-	Directional = false; // button 0
-
-	constructor() {
-		super();
-		context_raw.run_brush = this.run;
-		context_raw.parse_brush_inputs = this.parse_inputs;
-	}
-
-	parse_inputs = () => {
-		let last_mask = context_raw.brush_mask_image;
-		let last_stencil = context_raw.brush_stencil_image;
-
-		let input0: any;
-		let input1: any;
-		let input2: any;
-		let input3: any;
-		let input4: any;
-		try {
-			this.inputs[0].get((value) => { input0 = value; });
-			this.inputs[1].get((value) => { input1 = value; });
-			this.inputs[2].get((value) => { input2 = value; });
-			this.inputs[3].get((value) => { input3 = value; });
-			this.inputs[4].get((value) => { input4 = value; });
-		}
-		catch (_) {
-			return;
-		}
-
-		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.brush_mask_image_is_alpha = opac.endsWith(".a");
-			opac = opac.substr(0, opac.lastIndexOf("."));
-			context_raw.brush_nodes_opacity = 1.0;
-			let index = project_asset_names.indexOf(opac);
-			let asset = project_assets[index];
-			context_raw.brush_mask_image = project_get_image(asset);
-		}
-		else {
-			context_raw.brush_nodes_opacity = opac;
-			context_raw.brush_mask_image = null;
-		}
-
-		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.brush_stencil_image_is_alpha = stencil.endsWith(".a");
-			stencil = stencil.substr(0, stencil.lastIndexOf("."));
-			let index = project_asset_names.indexOf(stencil);
-			let asset = project_assets[index];
-			context_raw.brush_stencil_image = project_get_image(asset);
-		}
-		else {
-			context_raw.brush_stencil_image = null;
-		}
-
-		if (last_mask != context_raw.brush_mask_image ||
-			last_stencil != context_raw.brush_stencil_image) {
-			make_material_parse_paint_material();
-		}
-
-		context_raw.brush_directional = this.Directional;
-	}
-
-	run = (from: i32) => {
-		let left = 0.0;
-		let right = 1.0;
-		if (context_raw.paint2d) {
-			left = 1.0;
-			right = (context_raw.split_view ? 2.0 : 1.0) + ui_view2d_ww / base_w();
-		}
-
-		// First time init
-		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 = slot_layer_is_group(context_raw.layer);
-
-		// Paint bounds
-		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 &&
-			(slot_layer_is_visible(context_raw.layer) || context_raw.paint2d) &&
-			!ui_base_ui.is_hovered &&
-			!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.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.last_paint_x = context_raw.paint_vec.x;
-			context_raw.last_paint_y = context_raw.paint_vec.y;
-
-			if (context_raw.tool == workspace_tool_t.PARTICLE) {
-				context_raw.painted = 0; // Always paint particles
-			}
-
-			if (context_raw.painted == 0) {
-				this.parse_inputs();
-			}
-
-			if (context_raw.painted == 0) {
-				context_raw.pdirty = 1;
-				context_raw.rdirty = 2;
-				history_push_undo2 = true; ////
-			}
-		}
-	}
-}

+ 138 - 0
armorsculpt/Sources/nodes/brush_output_node.ts

@@ -0,0 +1,138 @@
+
+type brush_output_node_t = {
+	base?: logic_node_t;
+	Directional?: bool;
+};
+
+function brush_output_node_create(): brush_output_node_t {
+	let n: brush_output_node_t = {};
+	n.base = logic_node_create();
+	context_raw.run_brush = brush_output_node_run;
+	context_raw.parse_brush_inputs = brush_output_node_parse_inputs;
+	return n;
+}
+
+function brush_output_node_parse_inputs(self: brush_output_node_t) {
+	let last_mask = context_raw.brush_mask_image;
+	let last_stencil = context_raw.brush_stencil_image;
+
+	let input0: any;
+	let input1: any;
+	let input2: any;
+	let input3: any;
+	let input4: any;
+	try {
+		logic_node_input_get(self.base.inputs[0], (value) => { input0 = value; });
+		logic_node_input_get(self.base.inputs[1], (value) => { input1 = value; });
+		logic_node_input_get(self.base.inputs[2], (value) => { input2 = value; });
+		logic_node_input_get(self.base.inputs[3], (value) => { input3 = value; });
+		logic_node_input_get(self.base.inputs[4], (value) => { input4 = value; });
+	}
+	catch (_) {
+		return;
+	}
+
+	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.brush_mask_image_is_alpha = opac.endsWith(".a");
+		opac = opac.substr(0, opac.lastIndexOf("."));
+		context_raw.brush_nodes_opacity = 1.0;
+		let index = project_asset_names.indexOf(opac);
+		let asset = project_assets[index];
+		context_raw.brush_mask_image = project_get_image(asset);
+	}
+	else {
+		context_raw.brush_nodes_opacity = opac;
+		context_raw.brush_mask_image = null;
+	}
+
+	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.brush_stencil_image_is_alpha = stencil.endsWith(".a");
+		stencil = stencil.substr(0, stencil.lastIndexOf("."));
+		let index = project_asset_names.indexOf(stencil);
+		let asset = project_assets[index];
+		context_raw.brush_stencil_image = project_get_image(asset);
+	}
+	else {
+		context_raw.brush_stencil_image = null;
+	}
+
+	if (last_mask != context_raw.brush_mask_image ||
+		last_stencil != context_raw.brush_stencil_image) {
+		make_material_parse_paint_material();
+	}
+
+	context_raw.brush_directional = self.Directional;
+}
+
+function brush_output_node_run(self: brush_output_node_t, from: i32) {
+	let left: f32 = 0.0;
+	let right: f32 = 1.0;
+	if (context_raw.paint2d) {
+		left = 1.0;
+		right = (context_raw.split_view ? 2.0 : 1.0) + ui_view2d_ww / base_w();
+	}
+
+	// First time init
+	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 fill_layer: bool = context_raw.layer.fill_layer != null;
+
+	// Do not paint over groups
+	let group_layer: bool = slot_layer_is_group(context_raw.layer);
+
+	// Paint bounds
+	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 &&
+		!fill_layer &&
+		!group_layer &&
+		(slot_layer_is_visible(context_raw.layer) || context_raw.paint2d) &&
+		!ui_base_ui.is_hovered &&
+		!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 same_spot = 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 && (same_spot || lazy)) {
+			context_raw.painted++;
+		}
+		else {
+			context_raw.painted = 0;
+		}
+		context_raw.last_paint_x = context_raw.paint_vec.x;
+		context_raw.last_paint_y = context_raw.paint_vec.y;
+
+		if (context_raw.tool == workspace_tool_t.PARTICLE) {
+			context_raw.painted = 0; // Always paint particles
+		}
+
+		if (context_raw.painted == 0) {
+			brush_output_node_parse_inputs(self);
+		}
+
+		if (context_raw.painted == 0) {
+			context_raw.pdirty = 1;
+			context_raw.rdirty = 2;
+			history_push_undo2 = true; ////
+		}
+	}
+}

+ 0 - 51
base/Sources/LogicNode.ts

@@ -1,51 +0,0 @@
-
-class LogicNode {
-	inputs: LogicNodeInput[] = [];
-	outputs: LogicNode[][] = [];
-
-	constructor() {}
-
-	add_input = (node: LogicNode, from: i32) => {
-		this.inputs.push(new LogicNodeInput(node, from));
-	}
-
-	add_outputs = (nodes: LogicNode[]) => {
-		this.outputs.push(nodes);
-	}
-
-	get = (from: i32, done: (a: any)=>void) => {
-		done(null);
-	}
-
-	get_as_image = (from: i32, done: (img: image_t)=>void) => {
-		done(null);
-	}
-
-	get_cached_image = (): image_t => {
-		return null;
-	}
-
-	set = (value: any) => {}
-}
-
-class LogicNodeInput {
-	node: LogicNode;
-	from: i32; // Socket index
-
-	constructor(node: LogicNode, from: i32) {
-		this.node = node;
-		this.from = from;
-	}
-
-	get = (done: (a: any)=>void) => {
-		this.node.get(this.from, done);
-	}
-
-	get_as_image = (done: (img: image_t)=>void) => {
-		this.node.get_as_image(this.from, done);
-	}
-
-	set = (value: any) => {
-		this.node.set(value);
-	}
-}

+ 12 - 12
base/Sources/base.ts

@@ -253,7 +253,7 @@ function base_init() {
 	ui_view2d_init();
 
 	///if is_lab
-	RandomNode.setSeed(math_floor(time_time() * 4294967295));
+	random_node_set_seed(math_floor(time_time() * 4294967295));
 	///end
 
 	app_notify_on_update(base_update);
@@ -1421,7 +1421,7 @@ function base_make_temp_img() {
 	let l: SlotLayerRaw = project_layers[0];
 	///end
 	///if is_lab
-	let l: any = BrushOutputNode.inst;
+	let l: any = brush_output_node_inst;
 	///end
 
 	if (base_temp_image != null && (base_temp_image.width != l.texpaint.width || base_temp_image.height != l.texpaint.height || base_temp_image.format != l.texpaint.format)) {
@@ -1472,7 +1472,7 @@ function base_make_export_img() {
 	let l: SlotLayerRaw = project_layers[0];
 	///end
 	///if is_lab
-	let l: any = BrushOutputNode.inst;
+	let l: any = brush_output_node_inst;
 	///end
 
 	if (base_expa != null && (base_expa.width != l.texpaint.width || base_expa.height != l.texpaint.height || base_expa.format != l.texpaint.format)) {
@@ -2198,9 +2198,9 @@ function base_on_layers_resized() {
 
 ///if is_lab
 function base_flatten(heightToNormal: bool = false): any {
-	let texpaint: image_t = BrushOutputNode.inst.texpaint;
-	let texpaint_nor: image_t = BrushOutputNode.inst.texpaint_nor;
-	let texpaint_pack: image_t = BrushOutputNode.inst.texpaint_pack;
+	let texpaint: image_t = brush_output_node_inst.texpaint;
+	let texpaint_nor: image_t = brush_output_node_inst.texpaint_nor;
+	let texpaint_pack: image_t = brush_output_node_inst.texpaint_pack;
 
 	let nodes: zui_nodes_t = ui_nodes_get_nodes();
 	let canvas: zui_node_canvas_t = ui_nodes_get_canvas(true);
@@ -2218,12 +2218,12 @@ function base_flatten(heightToNormal: bool = false): any {
 }
 
 function base_on_layers_resized() {
-	image_unload(BrushOutputNode.inst.texpaint);
-	BrushOutputNode.inst.texpaint = render_path_render_targets.get("texpaint")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
-	image_unload(BrushOutputNode.inst.texpaint_nor);
-	BrushOutputNode.inst.texpaint_nor = render_path_render_targets.get("texpaint_nor")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
-	image_unload(BrushOutputNode.inst.texpaint_pack);
-	BrushOutputNode.inst.texpaint_pack = render_path_render_targets.get("texpaint_pack")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
+	image_unload(brush_output_node_inst.texpaint);
+	brush_output_node_inst.texpaint = render_path_render_targets.get("texpaint")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
+	image_unload(brush_output_node_inst.texpaint_nor);
+	brush_output_node_inst.texpaint_nor = render_path_render_targets.get("texpaint_nor")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
+	image_unload(brush_output_node_inst.texpaint_pack);
+	brush_output_node_inst.texpaint_pack = render_path_render_targets.get("texpaint_pack")._image = image_create_render_target(config_get_texture_res_x(), config_get_texture_res_y());
 
 	if (InpaintNode.image != null) {
 		image_unload(InpaintNode.image);

+ 2 - 0
base/Sources/config.ts

@@ -14,6 +14,8 @@ function config_load(done: ()=>void) {
 		done();
 	}
 	catch (e: any) {
+		krom_log(e);
+
 		///if krom_linux
 		try { // Protected directory
 			let blob: ArrayBuffer = data_get_blob(krom_save_path() + "config.json");

+ 3 - 2
base/Sources/context.ts

@@ -179,8 +179,9 @@ type context_t = {
 	///end
 
 	layer_filter?: i32;
-	run_brush?: (i: i32)=>void;
-	parse_brush_inputs?: ()=>void;
+	brush_output_node_inst?: brush_output_node_t;
+	run_brush?: (self: any, i: i32)=>void;
+	parse_brush_inputs?: (self: any)=>void;
 
 	gizmo?: object_t;
 	gizmo_translate_x?: object_t;

+ 4 - 4
base/Sources/export_texture.ts

@@ -69,7 +69,7 @@ function export_texture_run(path: string, bake_material: bool = false) {
 	///end
 
 	///if is_lab
-	export_texture_run_layers(path, [BrushOutputNode.inst]);
+	export_texture_run_layers(path, [brush_output_node_inst]);
 	///end
 
 	///if krom_ios
@@ -266,9 +266,9 @@ function export_texture_run_layers(path: string, layers: any[], object_name: str
 	///end
 
 	///if is_lab
-	let texpaint: image_t = BrushOutputNode.inst.texpaint;
-	let texpaint_nor: image_t = BrushOutputNode.inst.texpaint_nor;
-	let texpaint_pack: image_t = BrushOutputNode.inst.texpaint_pack;
+	let texpaint: image_t = brush_output_node_inst.texpaint;
+	let texpaint_nor: image_t = brush_output_node_inst.texpaint_nor;
+	let texpaint_pack: image_t = brush_output_node_inst.texpaint_pack;
 	///end
 
 	let pixpaint: ArrayBuffer = null;

+ 1 - 1
base/Sources/import_asset.ts

@@ -39,7 +39,7 @@ function import_asset_run(path: string, drop_x: f32 = -1.0, drop_y: f32 = -1.0,
 				}
 			}
 			ui_nodes_accept_asset_drag(asset_index);
-			ui_nodes_get_nodes().nodesDrag = false;
+			ui_nodes_get_nodes().nodes_drag = false;
 			ui_nodes_hwnd.redraws = 2;
 		}
 

+ 66 - 0
base/Sources/logic_node.ts

@@ -0,0 +1,66 @@
+
+type logic_node_t = {
+	inputs?: logic_node_input_t[];
+	outputs?: logic_node_t[][];
+	get?: (self: any, from: i32, done: (a: any)=>void)=>void;
+	get_as_image?: (self: any, from: i32, done: (img: image_t)=>void)=>void;
+	get_cached_image?: (self: any)=>image_t ;
+	set?: (self: any, value: any)=>void;
+};
+
+type logic_node_input_t = {
+	node?: any; // logic_node_t
+	from?: i32; // Socket index
+};
+
+function logic_node_create(): logic_node_t {
+	let n: logic_node_t = {};
+	n.inputs = [];
+	n.outputs = [];
+	n.get = logic_node_get;
+	n.get_as_image = logic_node_get_as_image;
+	n.get_cached_image = logic_node_get_cached_image;
+	n.set = logic_node_set;
+	return n;
+}
+
+function logic_node_add_input(self: logic_node_t, node: logic_node_t, from: i32) {
+	self.inputs.push(logic_node_input_create(node, from));
+}
+
+function logic_node_add_outputs(self: logic_node_t, nodes: logic_node_t[]) {
+	self.outputs.push(nodes);
+}
+
+function logic_node_get(self: logic_node_t, from: i32, done: (a: any)=>void) {
+	done(null);
+}
+
+function logic_node_get_as_image(self: logic_node_t, from: i32, done: (img: image_t)=>void) {
+	done(null);
+}
+
+function logic_node_get_cached_image(self: logic_node_t): image_t {
+	return null;
+}
+
+function logic_node_set(self: logic_node_t, value: any) {}
+
+function logic_node_input_create(node: logic_node_t, from: i32): logic_node_input_t {
+	let inp: logic_node_input_t = {};
+	inp.node = node;
+	inp.from = from;
+	return inp;
+}
+
+function logic_node_input_get(self: logic_node_input_t, done: (a: any)=>void) {
+	self.node.base.get(self.node, self.from, done);
+}
+
+function logic_node_input_get_as_image(self: logic_node_input_t, done: (img: image_t)=>void) {
+	self.node.base.get_as_image(self.node, self.from, done);
+}
+
+function logic_node_input_set(self: logic_node_input_t, value: any) {
+	self.node.base.set(self.node, value);
+}

+ 0 - 20
base/Sources/nodes/BooleanNode.ts

@@ -1,20 +0,0 @@
-
-class BooleanNode extends LogicNode {
-
-	value: bool;
-
-	constructor(value = false) {
-		super();
-		this.value = value;
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		if (this.inputs.length > 0) this.inputs[0].get(done);
-		else done(this.value);
-	}
-
-	override set = (value: any) => {
-		if (this.inputs.length > 0) this.inputs[0].set(value);
-		else this.value = value;
-	}
-}

+ 0 - 34
base/Sources/nodes/ColorNode.ts

@@ -1,34 +0,0 @@
-
-class ColorNode extends LogicNode {
-
-	value: vec4_t = vec4_create();
-	image: image_t = null;
-
-	constructor(r: f32 = 0.8, g: f32 = 0.8, b: f32 = 0.8, a: f32 = 1.0) {
-		super();
-		vec4_set(this.value, r, g, b, a);
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		if (this.inputs.length > 0) this.inputs[0].get(done);
-		else done(this.value);
-	}
-
-	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
-		if (this.inputs.length > 0) { this.inputs[0].get_as_image(done); return; }
-		if (this.image != null) image_unload(this.image);
-		let b: ArrayBuffer = new ArrayBuffer(16);
-		let v: DataView = new DataView(b);
-		v.setFloat32(0, this.value.x, true);
-		v.setFloat32(4, this.value.y, true);
-		v.setFloat32(8, this.value.z, true);
-		v.setFloat32(12, this.value.w, true);
-		this.image = image_from_bytes(b, 1, 1, tex_format_t.RGBA128);
-		done(this.image);
-	}
-
-	override set = (value: any) => {
-		if (this.inputs.length > 0) this.inputs[0].set(value);
-		else this.value = value;
-	}
-}

+ 0 - 66
base/Sources/nodes/FloatNode.ts

@@ -1,66 +0,0 @@
-
-class FloatNode extends LogicNode {
-
-	value: f32;
-	image: image_t = null;
-
-	constructor(value: f32 = 0.0) {
-		super();
-		this.value = value;
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		if (this.inputs.length > 0) this.inputs[0].get(done);
-		else done(this.value);
-	}
-
-	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
-		if (this.inputs.length > 0) { this.inputs[0].get_as_image(done); return; }
-		if (this.image != null) image_unload(this.image);
-		let b: ArrayBuffer = new ArrayBuffer(16);
-		let v: DataView = new DataView(b);
-		v.setFloat32(0, this.value, true);
-		v.setFloat32(4, this.value, true);
-		v.setFloat32(8, this.value, true);
-		v.setFloat32(12, 1.0, true);
-		this.image = image_from_bytes(b, 1, 1, tex_format_t.RGBA128);
-		done(this.image);
-	}
-
-	override set = (value: any) => {
-		if (this.inputs.length > 0) this.inputs[0].set(value);
-		else this.value = value;
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Value"),
-		type: "FloatNode",
-		x: 0,
-		y: 0,
-		color: 0xffb34f5a,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Value"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.5,
-				min: 0.0,
-				max: 10.0
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Value"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.5
-			}
-		],
-		buttons: []
-	};
-}

+ 0 - 20
base/Sources/nodes/IntegerNode.ts

@@ -1,20 +0,0 @@
-
-class IntegerNode extends LogicNode {
-
-	value: i32;
-
-	constructor(value: i32 = 0) {
-		super();
-		this.value = value;
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		if (this.inputs.length > 0) this.inputs[0].get(done);
-		else done(this.value);
-	}
-
-	override set = (value: any) => {
-		if (this.inputs.length > 0) this.inputs[0].set(value);
-		else this.value = value;
-	}
-}

+ 0 - 181
base/Sources/nodes/MathNode.ts

@@ -1,181 +0,0 @@
-
-class MathNode extends LogicNode {
-
-	operation: string;
-	use_clamp: bool;
-
-	constructor() {
-		super();
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		this.inputs[0].get((v1: f32) => {
-			this.inputs[1].get((v2: f32) => {
-				let f: f32 = 0.0;
-				switch (this.operation) {
-					case "Add":
-						f = v1 + v2;
-						break;
-					case "Multiply":
-						f = v1 * v2;
-						break;
-					case "Sine":
-						f = math_sin(v1);
-						break;
-					case "Cosine":
-						f = math_cos(v1);
-						break;
-					case "Max":
-						f = math_max(v1, v2);
-						break;
-					case "Min":
-						f = math_min(v1, v2);
-						break;
-					case "Absolute":
-						f = math_abs(v1);
-						break;
-					case "Subtract":
-						f = v1 - v2;
-						break;
-					case "Divide":
-						f = v1 / (v2 == 0.0 ? 0.000001 : v2);
-						break;
-					case "Tangent":
-						f = math_tan(v1);
-						break;
-					case "Arcsine":
-						f = math_asin(v1);
-						break;
-					case "Arccosine":
-						f = math_acos(v1);
-						break;
-					case "Arctangent":
-						f = math_atan(v1);
-						break;
-					case "Arctan2":
-					    f = math_atan2(v2, v1);
-						break;
-					case "Power":
-						f = math_pow(v1, v2);
-						break;
-					case "Logarithm":
-						f = math_log(v1);
-						break;
-					case "Round":
-						f = math_round(v1);
-						break;
-					case "Floor":
-					    f = math_floor(v1);
-						break;
-					case "Ceil":
-					    f = math_ceil(v1);
-						break;
-					case "Truncate":
-						f = math_floor(v1);
-						break;
-					case "Fraction":
-					    f = v1 - math_floor(v1);
-						break;
-					case "Less Than":
-						f = v1 < v2 ? 1.0 : 0.0;
-						break;
-					case "Greater Than":
-						f = v1 > v2 ? 1.0 : 0.0;
-						break;
-					case "Modulo":
-						f = v1 % v2;
-						break;
-					case "Snap":
-						f = math_floor(v1 / v2) * v2;
-						break;
-					case "Square Root":
-					    f = math_sqrt(v1);
-						break;
-					case "Inverse Square Root":
-						f = 1.0 / math_sqrt(v1);
-						break;
-					case "Exponent":
-						f = math_exp(v1);
-						break;
-					case "Sign":
-						f = v1 > 0 ? 1.0 : (v1 < 0 ? -1.0 : 0);
-						break;
-					case "Ping-Pong":
-					    f = (v2 != 0.0) ? v2 - math_abs((math_abs(v1) % (2 * v2)) - v2) : 0.0;
-						break;
-					case "Hyperbolic Sine":
-						f = (math_exp(v1) - math_exp(-v1)) / 2.0;
-						break;
-					case "Hyperbolic Cosine":
-						f = (math_exp(v1) + math_exp(-v1)) / 2.0;
-						break;
-					case "Hyperbolic Tangent":
-						f = 1.0 - (2.0 / (math_exp(2 * v1) + 1));
-						break;
-					case "To Radians":
-						f = v1 / 180.0 * math_pi();
-						break;
-					case "To Degrees":
-						f = v1 / math_pi() * 180.0;
-						break;
-				}
-
-				if (this.use_clamp) f = f < 0.0 ? 0.0 : (f > 1.0 ? 1.0 : f);
-
-				done(f);
-			});
-		});
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Math"),
-		type: "MathNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Value"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.5
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Value"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.5
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Value"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			}
-		],
-		buttons: [
-			{
-				name: _tr("operation"),
-				type: "ENUM",
-				data: ["Add", "Subtract", "Multiply", "Divide", "Power", "Logarithm", "Square Root", "Inverse Square Root", "Absolute", "Exponent", "Minimum", "Maximum", "Less Than", "Greater Than", "Sign", "Round", "Floor", "Ceil", "Truncate", "Fraction", "Modulo", "Snap", "Ping-Pong", "Sine", "Cosine", "Tangent", "Arcsine", "Arccosine", "Arctangent", "Arctan2", "Hyperbolic Sine", "Hyperbolic Cosine", "Hyperbolic Tangent", "To Radians", "To Degrees"],
-				default_value: 0,
-				output: 0
-			},
-			{
-				name: _tr("use_clamp"),
-				type: "BOOL",
-				default_value: false,
-				output: 0
-			}
-		]
-	};
-}

+ 0 - 11
base/Sources/nodes/NullNode.ts

@@ -1,11 +0,0 @@
-
-class NullNode extends LogicNode {
-
-	constructor() {
-		super();
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		done(null);
-	}
-}

+ 0 - 91
base/Sources/nodes/RandomNode.ts

@@ -1,91 +0,0 @@
-
-class RandomNode extends LogicNode {
-
-	constructor() {
-		super();
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		this.inputs[0].get((min: f32) => {
-			this.inputs[1].get((max: f32) => {
-				done(min + RandomNode.getFloat() * (max - min));
-			});
-		});
-	}
-
-	// Courtesy of https://github.com/Kode/Kha/blob/main/Sources/kha/math/Random.hx
-	static getInt = (): i32 => {
-		let t: i32 = (RandomNode.a + RandomNode.b | 0) + RandomNode.d | 0;
-		RandomNode.d = RandomNode.d + 1 | 0;
-		RandomNode.a = RandomNode.b ^ RandomNode.b >>> 9;
-		RandomNode.b = RandomNode.c + (RandomNode.c << 3) | 0;
-		RandomNode.c = RandomNode.c << 21 | RandomNode.c >>> 11;
-		RandomNode.c = RandomNode.c + t | 0;
-		return t & 0x7fffffff;
-	}
-
-	static setSeed = (seed: i32): i32 => {
-		RandomNode.d = seed;
-		RandomNode.a = 0x36aef51a;
-		RandomNode.b = 0x21d4b3eb;
-		RandomNode.c = 0xf2517abf;
-		// Immediately skip a few possibly poor results the easy way
-		for (let i: i32 = 0; i < 15; ++i) {
-			RandomNode.getInt();
-		}
-		return RandomNode.d;
-	}
-
-	static getSeed = (): i32 => {
-		return RandomNode.d;
-	}
-
-	static a: i32;
-	static b: i32;
-	static c: i32;
-	static d = RandomNode.setSeed(352124);
-
-
-
-	static getFloat = (): f32 => {
-		return RandomNode.getInt() / 0x7fffffff;
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Random"),
-		type: "RandomNode",
-		x: 0,
-		y: 0,
-		color: 0xffb34f5a,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Min"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Max"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 1.0
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Value"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.5
-			}
-		],
-		buttons: []
-	};
-}

+ 0 - 61
base/Sources/nodes/SeparateVectorNode.ts

@@ -1,61 +0,0 @@
-
-class SeparateVectorNode extends LogicNode {
-
-	constructor() {
-		super();
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		this.inputs[0].get((vector: vec4_t) => {
-			if (from == 0) done(vector.x);
-			else if (from == 1) done(vector.y);
-			else done(vector.z);
-		});
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Separate Vector"),
-		type: "SeparateVectorNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Vector"),
-				type: "VECTOR",
-				color: 0xff6363c7,
-				default_value: new Float32Array([0.0, 0.0, 0.0])
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("X"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Y"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Z"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			}
-		],
-		buttons: []
-	};
-}

+ 0 - 20
base/Sources/nodes/StringNode.ts

@@ -1,20 +0,0 @@
-
-class StringNode extends LogicNode {
-
-	value: string;
-
-	constructor(value: string = "") {
-		super();
-		this.value = value;
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		if (this.inputs.length > 0) this.inputs[0].get(done);
-		else done(this.value);
-	}
-
-	override set = (value: any) => {
-		if (this.inputs.length > 0) this.inputs[0].set(value);
-		else this.value = value;
-	}
-}

+ 0 - 50
base/Sources/nodes/TimeNode.ts

@@ -1,50 +0,0 @@
-
-class TimeNode extends LogicNode {
-
-	constructor() {
-		super();
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		if (from == 0) done(time_time());
-		else if (from == 1) done(time_delta());
-		else done(context_raw.brush_time);
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Time"),
-		type: "TimeNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Time"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Delta"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Brush"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			}
-		],
-		buttons: []
-	};
-}

+ 0 - 189
base/Sources/nodes/VectorMathNode.ts

@@ -1,189 +0,0 @@
-
-class VectorMathNode extends LogicNode {
-
-	operation: string;
-	v: vec4_t = vec4_create();
-
-	constructor() {
-		super();
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		this.inputs[0].get((v1: vec4_t) => {
-			this.inputs[1].get((v2: vec4_t) => {
-				vec4_set_from(this.v, v1);
-				let f: f32 = 0.0;
-
-				switch (this.operation) {
-					case "Add":
-						vec4_add(this.v, v2);
-						break;
-					case "Subtract":
-						vec4_sub(this.v, v2);
-						break;
-					case "Average":
-						vec4_add(this.v, v2);
-						this.v.x *= 0.5;
-						this.v.y *= 0.5;
-						this.v.z *= 0.5;
-						break;
-					case "Dot Product":
-						f = vec4_dot(this.v, v2);
-						vec4_set(this.v, f, f, f);
-						break;
-					case "Cross Product":
-						vec4_cross(this.v, v2);
-						break;
-					case "Normalize":
-						vec4_normalize(this.v, );
-						break;
-					case "Multiply":
-						this.v.x *= v2.x;
-						this.v.y *= v2.y;
-						this.v.z *= v2.z;
-						break;
-					case "Divide":
-						this.v.x /= v2.x == 0.0 ? 0.000001 : v2.x;
-						this.v.y /= v2.y == 0.0 ? 0.000001 : v2.y;
-						this.v.z /= v2.z == 0.0 ? 0.000001 : v2.z;
-						break;
-					case "Length":
-						f = vec4_len(this.v);
-						vec4_set(this.v, f, f, f);
-						break;
-					case "Distance":
-						f = vec4_dist_to(this.v, v2);
-						vec4_set(this.v, f, f, f);
-						break;
-					case "Project":
-						vec4_set_from(this.v, v2);
-						vec4_mult(this.v, vec4_dot(v1, v2) / vec4_dot(v2, v2));
-						break;
-					case "Reflect":
-						let tmp: vec4_t = vec4_create();
-						vec4_set_from(tmp, v2);
-						vec4_normalize(tmp);
-						vec4_reflect(this.v, tmp);
-						break;
-					case "Scale":
-						this.v.x *= v2.x;
-						this.v.y *= v2.x;
-						this.v.z *= v2.x;
-						break;
-					case "Absolute":
-						this.v.x = math_abs(this.v.x);
-						this.v.y = math_abs(this.v.y);
-						this.v.z = math_abs(this.v.z);
-						break;
-					case "Minimum":
-						this.v.x = math_min(v1.x, v2.x);
-						this.v.y = math_min(v1.y, v2.y);
-						this.v.z = math_min(v1.z, v2.z);
-						break;
-					case "Maximum":
-						this.v.x = math_max(v1.x, v2.x);
-						this.v.y = math_max(v1.y, v2.y);
-						this.v.z = math_max(v1.z, v2.z);
-						break;
-					case "Floor":
-						this.v.x = math_floor(v1.x);
-						this.v.y = math_floor(v1.y);
-						this.v.z = math_floor(v1.z);
-						break;
-					case "Ceil":
-						this.v.x = math_ceil(v1.x);
-						this.v.y = math_ceil(v1.y);
-						this.v.z = math_ceil(v1.z);
-						break;
-					case "Fraction":
-						this.v.x = v1.x - math_floor(v1.x);
-						this.v.y = v1.y - math_floor(v1.y);
-						this.v.z = v1.z - math_floor(v1.z);
-						break;
-					case "Modulo":
-						this.v.x = v1.x % v2.x;
-						this.v.y = v1.y % v2.y;
-						this.v.z = v1.z % v2.z;
-						break;
-					case "Snap":
-						this.v.x = math_floor(v1.x / v2.x) * v2.x;
-						this.v.y = math_floor(v1.y / v2.y) * v2.y;
-						this.v.z = math_floor(v1.z / v2.z) * v2.z;
-						break;
-					case "Sine":
-						this.v.x = math_sin(v1.x);
-						this.v.y = math_sin(v1.y);
-						this.v.z = math_sin(v1.z);
-						break;
-					case "Cosine":
-						this.v.x = math_cos(v1.x);
-						this.v.y = math_cos(v1.y);
-						this.v.z = math_cos(v1.z);
-						break;
-					case "Tangent":
-						this.v.x = math_tan(v1.x);
-						this.v.y = math_tan(v1.y);
-						this.v.z = math_tan(v1.z);
-						break;
-				}
-
-				if (from == 0) done(this.v);
-				else done(f);
-			});
-		});
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Vector Math"),
-		type: "VectorMathNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Vector"),
-				type: "VECTOR",
-				color: 0xff6363c7,
-				default_value: new Float32Array([0.0, 0.0, 0.0])
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Vector"),
-				type: "VECTOR",
-				color: 0xff6363c7,
-				default_value: new Float32Array([0.0, 0.0, 0.0])
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Vector"),
-				type: "VECTOR",
-				color: 0xff6363c7,
-				default_value: new Float32Array([0.0, 0.0, 0.0])
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Value"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			}
-		],
-		buttons: [
-			{
-				name: _tr("operation"),
-				type: "ENUM",
-				data: ["Add", "Subtract", "Multiply", "Divide", "Average", "Cross Product", "Project", "Reflect", "Dot Product", "Distance", "Length", "Scale", "Normalize", "Absolute", "Minimum", "Maximum", "Floor", "Ceil", "Fraction", "Modulo", "Snap", "Sine", "Cosine", "Tangent"],
-				default_value: 0,
-				output: 0
-			}
-		]
-	};
-}

+ 0 - 99
base/Sources/nodes/VectorNode.ts

@@ -1,99 +0,0 @@
-
-class VectorNode extends LogicNode {
-
-	value: vec4_t = vec4_create();
-	image: image_t = null;
-
-	constructor(x: Null<f32> = null, y: Null<f32> = null, z: Null<f32> = null) {
-		super();
-
-		if (x != null) {
-			this.add_input(new FloatNode(x), 0);
-			this.add_input(new FloatNode(y), 0);
-			this.add_input(new FloatNode(z), 0);
-		}
-	}
-
-	override get = (from: i32, done: (a: any)=>void) => {
-		this.inputs[0].get((x: f32) => {
-			this.inputs[1].get((y: f32) => {
-				this.inputs[2].get((z: f32) => {
-					this.value.x = x;
-					this.value.y = y;
-					this.value.z = z;
-					done(this.value);
-				});
-			});
-		});
-	}
-
-	override get_as_image = (from: i32, done: (img: image_t)=>void) => {
-		this.inputs[0].get((x: f32) => {
-			this.inputs[1].get((y: f32) => {
-				this.inputs[2].get((z: f32) => {
-					if (this.image != null) image_unload(this.image);
-					let b: ArrayBuffer = new ArrayBuffer(16);
-					let v: DataView = new DataView(b);
-					v.setFloat32(0, (this.inputs[0].node as any).value, true);
-					v.setFloat32(4, (this.inputs[1].node as any).value, true);
-					v.setFloat32(8, (this.inputs[2].node as any).value, true);
-					v.setFloat32(12, 1.0, true);
-					this.image = image_from_bytes(b, 1, 1, tex_format_t.RGBA128);
-					done(this.image);
-				});
-			});
-		});
-	}
-
-	override set = (value: any) => {
-		this.inputs[0].set(value.x);
-		this.inputs[1].set(value.y);
-		this.inputs[2].set(value.z);
-	}
-
-	static def: zui_node_t = {
-		id: 0,
-		name: _tr("Vector"),
-		type: "VectorNode",
-		x: 0,
-		y: 0,
-		color: 0xff4982a0,
-		inputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("X"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Y"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			},
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Z"),
-				type: "VALUE",
-				color: 0xffa1a1a1,
-				default_value: 0.0
-			}
-		],
-		outputs: [
-			{
-				id: 0,
-				node_id: 0,
-				name: _tr("Vector"),
-				type: "VECTOR",
-				color: 0xff6363c7,
-				default_value: new Float32Array([0.0, 0.0, 0.0])
-			}
-		],
-		buttons: []
-	};
-}

+ 24 - 0
base/Sources/nodes/boolean_node.ts

@@ -0,0 +1,24 @@
+
+type boolean_node_t = {
+	base?: logic_node_t;
+	value?: bool;
+};
+
+function boolean_node_create(value: bool = false): boolean_node_t {
+	let n: boolean_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = boolean_node_get;
+	n.base.set = boolean_node_set;
+	n.value = value;
+	return n;
+}
+
+function boolean_node_get(self: boolean_node_t, from: i32, done: (a: any)=>void) {
+	if (self.base.inputs.length > 0) logic_node_input_get(self.base.inputs[0], done);
+	else done(self.value);
+}
+
+function boolean_node_set(self: boolean_node_t, value: any) {
+	if (self.base.inputs.length > 0) logic_node_input_set(self.base.inputs[0], value);
+	else self.value = value;
+}

+ 39 - 0
base/Sources/nodes/color_node.ts

@@ -0,0 +1,39 @@
+
+type color_node_t = {
+	base?: logic_node_t;
+	value?: vec4_t;
+	image?: image_t;
+};
+
+function color_node_create(r: f32 = 0.8, g: f32 = 0.8, b: f32 = 0.8, a: f32 = 1.0): color_node_t {
+	let n: color_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = color_node_get;
+	n.base.get_as_image = color_node_get_as_image;
+	n.base.set = color_node_set;
+	n.value = vec4_create(r, g, b, a);
+	return n;
+}
+
+function color_node_get(self: color_node_t, from: i32, done: (a: any)=>void) {
+	if (self.base.inputs.length > 0) logic_node_input_get(self.base.inputs[0], done);
+	else done(self.value);
+}
+
+function color_node_get_as_image(self: color_node_t, from: i32, done: (img: image_t)=>void) {
+	if (self.base.inputs.length > 0) { logic_node_input_get_as_image(self.base.inputs[0], done); return; }
+	if (self.image != null) image_unload(self.image);
+	let b: ArrayBuffer = new ArrayBuffer(16);
+	let v: DataView = new DataView(b);
+	v.setFloat32(0, self.value.x, true);
+	v.setFloat32(4, self.value.y, true);
+	v.setFloat32(8, self.value.z, true);
+	v.setFloat32(12, self.value.w, true);
+	self.image = image_from_bytes(b, 1, 1, tex_format_t.RGBA128);
+	done(self.image);
+}
+
+function color_node_set(self: color_node_t, value: any) {
+	if (self.base.inputs.length > 0) logic_node_input_set(self.base.inputs[0], value);
+	else self.value = value;
+}

+ 71 - 0
base/Sources/nodes/float_node.ts

@@ -0,0 +1,71 @@
+
+type float_node_t = {
+	base?: logic_node_t;
+	value?: f32;
+	image?: image_t;
+};
+
+function float_node_create(value: f32 = 0.0): float_node_t {
+	let n: float_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = float_node_get;
+	n.base.get_as_image = float_node_get_as_image;
+	n.base.set = float_node_set;
+	n.value = value;
+	return n;
+}
+
+function float_node_get(self: float_node_t, from: i32, done: (a: any)=>void) {
+	if (self.base.inputs.length > 0) logic_node_input_get(self.base.inputs[0], done);
+	else done(self.value);
+}
+
+function float_node_get_as_image(self: float_node_t, from: i32, done: (img: image_t)=>void) {
+	if (self.base.inputs.length > 0) { logic_node_input_get_as_image(self.base.inputs[0], done); return; }
+	if (self.image != null) image_unload(self.image);
+	let b: ArrayBuffer = new ArrayBuffer(16);
+	let v: DataView = new DataView(b);
+	v.setFloat32(0, self.value, true);
+	v.setFloat32(4, self.value, true);
+	v.setFloat32(8, self.value, true);
+	v.setFloat32(12, 1.0, true);
+	self.image = image_from_bytes(b, 1, 1, tex_format_t.RGBA128);
+	done(self.image);
+}
+
+function float_node_set(self: float_node_t, value: any) {
+	if (self.base.inputs.length > 0) logic_node_input_set(self.base.inputs[0], value);
+	else self.value = value;
+}
+
+let float_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Value"),
+	type: "float_node",
+	x: 0,
+	y: 0,
+	color: 0xffb34f5a,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Value"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.5,
+			min: 0.0,
+			max: 10.0
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Value"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.5
+		}
+	],
+	buttons: []
+};

+ 24 - 0
base/Sources/nodes/integer_node.ts

@@ -0,0 +1,24 @@
+
+type integer_node_t = {
+	base?: logic_node_t;
+	value?: i32;
+};
+
+function integer_node_create(value: i32 = 0): integer_node_t {
+	let n: float_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = integer_node_get;
+	n.base.set = integer_node_set;
+	this.value = value;
+	return n;
+}
+
+function integer_node_get(self: integer_node_t, from: i32, done: (a: any)=>void) {
+	if (self.base.inputs.length > 0) logic_node_input_get(self.base.inputs[0], done);
+	else done(self.value);
+}
+
+function integer_node_set(self: integer_node_t, value: any) {
+	if (self.base.inputs.length > 0) logic_node_input_set(self.base.inputs[0], value);
+	else self.value = value;
+}

+ 184 - 0
base/Sources/nodes/math_node.ts

@@ -0,0 +1,184 @@
+
+type math_node_t = {
+	base?: logic_node_t;
+	operation?: string;
+	use_clamp?: bool;
+};
+
+function math_node_create(): math_node_t {
+	let n: math_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = math_node_get;
+	return n;
+}
+
+function math_node_get(self: math_node_t, from: i32, done: (a: any)=>void) {
+	logic_node_input_get(self.base.inputs[0], (v1: f32) => {
+		logic_node_input_get(self.base.inputs[1], (v2: f32) => {
+			let f: f32 = 0.0;
+			switch (self.operation) {
+				case "Add":
+					f = v1 + v2;
+					break;
+				case "Multiply":
+					f = v1 * v2;
+					break;
+				case "Sine":
+					f = math_sin(v1);
+					break;
+				case "Cosine":
+					f = math_cos(v1);
+					break;
+				case "Max":
+					f = math_max(v1, v2);
+					break;
+				case "Min":
+					f = math_min(v1, v2);
+					break;
+				case "Absolute":
+					f = math_abs(v1);
+					break;
+				case "Subtract":
+					f = v1 - v2;
+					break;
+				case "Divide":
+					f = v1 / (v2 == 0.0 ? 0.000001 : v2);
+					break;
+				case "Tangent":
+					f = math_tan(v1);
+					break;
+				case "Arcsine":
+					f = math_asin(v1);
+					break;
+				case "Arccosine":
+					f = math_acos(v1);
+					break;
+				case "Arctangent":
+					f = math_atan(v1);
+					break;
+				case "Arctan2":
+					f = math_atan2(v2, v1);
+					break;
+				case "Power":
+					f = math_pow(v1, v2);
+					break;
+				case "Logarithm":
+					f = math_log(v1);
+					break;
+				case "Round":
+					f = math_round(v1);
+					break;
+				case "Floor":
+					f = math_floor(v1);
+					break;
+				case "Ceil":
+					f = math_ceil(v1);
+					break;
+				case "Truncate":
+					f = math_floor(v1);
+					break;
+				case "Fraction":
+					f = v1 - math_floor(v1);
+					break;
+				case "Less Than":
+					f = v1 < v2 ? 1.0 : 0.0;
+					break;
+				case "Greater Than":
+					f = v1 > v2 ? 1.0 : 0.0;
+					break;
+				case "Modulo":
+					f = v1 % v2;
+					break;
+				case "Snap":
+					f = math_floor(v1 / v2) * v2;
+					break;
+				case "Square Root":
+					f = math_sqrt(v1);
+					break;
+				case "Inverse Square Root":
+					f = 1.0 / math_sqrt(v1);
+					break;
+				case "Exponent":
+					f = math_exp(v1);
+					break;
+				case "Sign":
+					f = v1 > 0 ? 1.0 : (v1 < 0 ? -1.0 : 0);
+					break;
+				case "Ping-Pong":
+					f = (v2 != 0.0) ? v2 - math_abs((math_abs(v1) % (2 * v2)) - v2) : 0.0;
+					break;
+				case "Hyperbolic Sine":
+					f = (math_exp(v1) - math_exp(-v1)) / 2.0;
+					break;
+				case "Hyperbolic Cosine":
+					f = (math_exp(v1) + math_exp(-v1)) / 2.0;
+					break;
+				case "Hyperbolic Tangent":
+					f = 1.0 - (2.0 / (math_exp(2 * v1) + 1));
+					break;
+				case "To Radians":
+					f = v1 / 180.0 * math_pi();
+					break;
+				case "To Degrees":
+					f = v1 / math_pi() * 180.0;
+					break;
+			}
+
+			if (self.use_clamp) f = f < 0.0 ? 0.0 : (f > 1.0 ? 1.0 : f);
+
+			done(f);
+		});
+	});
+}
+
+let math_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Math"),
+	type: "math_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Value"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.5
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Value"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.5
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Value"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		}
+	],
+	buttons: [
+		{
+			name: _tr("operation"),
+			type: "ENUM",
+			data: ["Add", "Subtract", "Multiply", "Divide", "Power", "Logarithm", "Square Root", "Inverse Square Root", "Absolute", "Exponent", "Minimum", "Maximum", "Less Than", "Greater Than", "Sign", "Round", "Floor", "Ceil", "Truncate", "Fraction", "Modulo", "Snap", "Ping-Pong", "Sine", "Cosine", "Tangent", "Arcsine", "Arccosine", "Arctangent", "Arctan2", "Hyperbolic Sine", "Hyperbolic Cosine", "Hyperbolic Tangent", "To Radians", "To Degrees"],
+			default_value: 0,
+			output: 0
+		},
+		{
+			name: _tr("use_clamp"),
+			type: "BOOL",
+			default_value: false,
+			output: 0
+		}
+	]
+};

+ 15 - 0
base/Sources/nodes/null_node.ts

@@ -0,0 +1,15 @@
+
+type null_node_t = {
+	base?: logic_node_t;
+};
+
+function null_node_create(): null_node_t {
+	let n: null_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = float_node_get;
+	return n;
+}
+
+function null_node_get(self: null_node_t, from: i32, done: (a: any)=>void) {
+	done(null);
+}

+ 96 - 0
base/Sources/nodes/random_node.ts

@@ -0,0 +1,96 @@
+
+type random_node_t = {
+	base?: logic_node_t;
+};
+
+let random_node_a: i32;
+let random_node_b: i32;
+let random_node_c: i32;
+let random_node_d: i32 = -1;
+
+function random_node_create(): random_node_t {
+	let n: random_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = random_node_get;
+	return n;
+}
+
+function random_node_get(self: random_node_t, from: i32, done: (a: any)=>void) {
+	logic_node_input_get(self.base.inputs[0], (min: f32) => {
+		logic_node_input_get(self.base.inputs[1], (max: f32) => {
+			done(min + random_node_get_float() * (max - min));
+		});
+	});
+}
+
+function random_node_get_int(): i32 {
+	if (random_node_d == -1) {
+		random_node_set_seed(352124);
+	}
+
+	// Courtesy of https://github.com/Kode/Kha/blob/main/Sources/kha/math/Random.hx
+	let t: i32 = (random_node_a + random_node_b | 0) + random_node_d | 0;
+	random_node_d = random_node_d + 1 | 0;
+	random_node_a = random_node_b ^ random_node_b >>> 9;
+	random_node_b = random_node_c + (random_node_c << 3) | 0;
+	random_node_c = random_node_c << 21 | random_node_c >>> 11;
+	random_node_c = random_node_c + t | 0;
+	return t & 0x7fffffff;
+}
+
+function random_node_set_seed(seed: i32) {
+	random_node_d = seed;
+	random_node_a = 0x36aef51a;
+	random_node_b = 0x21d4b3eb;
+	random_node_c = 0xf2517abf;
+	// Immediately skip a few possibly poor results the easy way
+	for (let i: i32 = 0; i < 15; ++i) {
+		random_node_get_int();
+	}
+}
+
+function random_node_get_seed(): i32 {
+	return random_node_d;
+}
+
+function random_node_get_float(): f32 {
+	return random_node_get_int() / 0x7fffffff;
+}
+
+let random_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Random"),
+	type: "random_node",
+	x: 0,
+	y: 0,
+	color: 0xffb34f5a,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Min"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Max"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 1.0
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Value"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.5
+		}
+	],
+	buttons: []
+};

+ 65 - 0
base/Sources/nodes/separate_vector_node.ts

@@ -0,0 +1,65 @@
+
+type separate_vector_node_t = {
+	base?: logic_node_t;
+};
+
+function separate_vector_node_create(): separate_vector_node_t {
+	let n: separate_vector_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = separate_vector_node_get;
+	return n;
+}
+
+function separate_vector_node_get(self: separate_vector_node_t, from: i32, done: (a: any)=>void) {
+	logic_node_input_get(self.base.inputs[0], (vector: vec4_t) => {
+		if (from == 0) done(vector.x);
+		else if (from == 1) done(vector.y);
+		else done(vector.z);
+	});
+}
+
+let separate_vector_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Separate Vector"),
+	type: "separate_vector_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Vector"),
+			type: "VECTOR",
+			color: 0xff6363c7,
+			default_value: new Float32Array([0.0, 0.0, 0.0])
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("X"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Y"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Z"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		}
+	],
+	buttons: []
+};

+ 24 - 0
base/Sources/nodes/string_node.ts

@@ -0,0 +1,24 @@
+
+type string_node_t = {
+	base?: logic_node_t;
+	value?: string;
+};
+
+function string_node_create(value: string = ""): string_node_t {
+	let n: string_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = string_node_get;
+	n.base.set = string_node_set;
+	n.value = value;
+	return n;
+}
+
+function string_node_get(self: string_node_t, from: i32, done: (a: any)=>void) {
+	if (self.base.inputs.length > 0) logic_node_input_get(self.base.inputs[0], done);
+	else done(self.value);
+}
+
+function string_node_set(self: string_node_t, value: any) {
+	if (self.base.inputs.length > 0) logic_node_input_set(self.base.inputs[0], value);
+	else self.value = value;
+}

+ 54 - 0
base/Sources/nodes/time_node.ts

@@ -0,0 +1,54 @@
+
+type time_node_t = {
+	base?: logic_node_t;
+};
+
+function time_node_create(): time_node_t {
+	let n: time_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = time_node_get;
+	return n;
+}
+
+function time_node_get(self: time_node_t, from: i32, done: (a: any)=>void) {
+	if (from == 0) done(time_time());
+	else if (from == 1) done(time_delta());
+	else done(context_raw.brush_time);
+}
+
+let time_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Time"),
+	type: "time_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Time"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Delta"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Brush"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		}
+	],
+	buttons: []
+};

+ 193 - 0
base/Sources/nodes/vector_math_node.ts

@@ -0,0 +1,193 @@
+
+type vector_math_node_t = {
+	base?: logic_node_t;
+	operation?: string;
+	v?: vec4_t;
+};
+
+function vector_math_node_create(): vector_math_node_t {
+	let n: vector_math_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = vector_math_node_get;
+	n.v = vec4_create();
+	return n;
+}
+
+function vector_math_node_get(self: vector_math_node_t, from: i32, done: (a: any)=>void) {
+	logic_node_input_get(self.base.inputs[0], (v1: vec4_t) => {
+		logic_node_input_get(self.base.inputs[1], (v2: vec4_t) => {
+			vec4_set_from(self.v, v1);
+			let f: f32 = 0.0;
+
+			switch (self.operation) {
+				case "Add":
+					vec4_add(self.v, v2);
+					break;
+				case "Subtract":
+					vec4_sub(self.v, v2);
+					break;
+				case "Average":
+					vec4_add(self.v, v2);
+					self.v.x *= 0.5;
+					self.v.y *= 0.5;
+					self.v.z *= 0.5;
+					break;
+				case "Dot Product":
+					f = vec4_dot(self.v, v2);
+					vec4_set(self.v, f, f, f);
+					break;
+				case "Cross Product":
+					vec4_cross(self.v, v2);
+					break;
+				case "Normalize":
+					vec4_normalize(self.v, );
+					break;
+				case "Multiply":
+					self.v.x *= v2.x;
+					self.v.y *= v2.y;
+					self.v.z *= v2.z;
+					break;
+				case "Divide":
+					self.v.x /= v2.x == 0.0 ? 0.000001 : v2.x;
+					self.v.y /= v2.y == 0.0 ? 0.000001 : v2.y;
+					self.v.z /= v2.z == 0.0 ? 0.000001 : v2.z;
+					break;
+				case "Length":
+					f = vec4_len(self.v);
+					vec4_set(self.v, f, f, f);
+					break;
+				case "Distance":
+					f = vec4_dist_to(self.v, v2);
+					vec4_set(self.v, f, f, f);
+					break;
+				case "Project":
+					vec4_set_from(self.v, v2);
+					vec4_mult(self.v, vec4_dot(v1, v2) / vec4_dot(v2, v2));
+					break;
+				case "Reflect":
+					let tmp: vec4_t = vec4_create();
+					vec4_set_from(tmp, v2);
+					vec4_normalize(tmp);
+					vec4_reflect(self.v, tmp);
+					break;
+				case "Scale":
+					self.v.x *= v2.x;
+					self.v.y *= v2.x;
+					self.v.z *= v2.x;
+					break;
+				case "Absolute":
+					self.v.x = math_abs(self.v.x);
+					self.v.y = math_abs(self.v.y);
+					self.v.z = math_abs(self.v.z);
+					break;
+				case "Minimum":
+					self.v.x = math_min(v1.x, v2.x);
+					self.v.y = math_min(v1.y, v2.y);
+					self.v.z = math_min(v1.z, v2.z);
+					break;
+				case "Maximum":
+					self.v.x = math_max(v1.x, v2.x);
+					self.v.y = math_max(v1.y, v2.y);
+					self.v.z = math_max(v1.z, v2.z);
+					break;
+				case "Floor":
+					self.v.x = math_floor(v1.x);
+					self.v.y = math_floor(v1.y);
+					self.v.z = math_floor(v1.z);
+					break;
+				case "Ceil":
+					self.v.x = math_ceil(v1.x);
+					self.v.y = math_ceil(v1.y);
+					self.v.z = math_ceil(v1.z);
+					break;
+				case "Fraction":
+					self.v.x = v1.x - math_floor(v1.x);
+					self.v.y = v1.y - math_floor(v1.y);
+					self.v.z = v1.z - math_floor(v1.z);
+					break;
+				case "Modulo":
+					self.v.x = v1.x % v2.x;
+					self.v.y = v1.y % v2.y;
+					self.v.z = v1.z % v2.z;
+					break;
+				case "Snap":
+					self.v.x = math_floor(v1.x / v2.x) * v2.x;
+					self.v.y = math_floor(v1.y / v2.y) * v2.y;
+					self.v.z = math_floor(v1.z / v2.z) * v2.z;
+					break;
+				case "Sine":
+					self.v.x = math_sin(v1.x);
+					self.v.y = math_sin(v1.y);
+					self.v.z = math_sin(v1.z);
+					break;
+				case "Cosine":
+					self.v.x = math_cos(v1.x);
+					self.v.y = math_cos(v1.y);
+					self.v.z = math_cos(v1.z);
+					break;
+				case "Tangent":
+					self.v.x = math_tan(v1.x);
+					self.v.y = math_tan(v1.y);
+					self.v.z = math_tan(v1.z);
+					break;
+			}
+
+			if (from == 0) done(self.v);
+			else done(f);
+		});
+	});
+}
+
+let vector_math_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Vector Math"),
+	type: "vector_math_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Vector"),
+			type: "VECTOR",
+			color: 0xff6363c7,
+			default_value: new Float32Array([0.0, 0.0, 0.0])
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Vector"),
+			type: "VECTOR",
+			color: 0xff6363c7,
+			default_value: new Float32Array([0.0, 0.0, 0.0])
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Vector"),
+			type: "VECTOR",
+			color: 0xff6363c7,
+			default_value: new Float32Array([0.0, 0.0, 0.0])
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Value"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		}
+	],
+	buttons: [
+		{
+			name: _tr("operation"),
+			type: "ENUM",
+			data: ["Add", "Subtract", "Multiply", "Divide", "Average", "Cross Product", "Project", "Reflect", "Dot Product", "Distance", "Length", "Scale", "Normalize", "Absolute", "Minimum", "Maximum", "Floor", "Ceil", "Fraction", "Modulo", "Snap", "Sine", "Cosine", "Tangent"],
+			default_value: 0,
+			output: 0
+		}
+	]
+};

+ 106 - 0
base/Sources/nodes/vector_node.ts

@@ -0,0 +1,106 @@
+
+type vector_node_t = {
+	base?: logic_node_t;
+	value?: vec4_t;
+	image?: image_t;
+};
+
+function vector_node_create(x: Null<f32> = null, y: Null<f32> = null, z: Null<f32> = null): vector_node_t {
+	let n: vector_node_t = {};
+	n.base = logic_node_create();
+	n.base.get = vector_node_get;
+	n.base.get_as_image = vector_node_get_as_image;
+	n.base.set = vector_node_set;
+	n.value = vec4_create();
+
+	if (x != null) {
+		logic_node_add_input(n.base, float_node_create(x).base, 0);
+		logic_node_add_input(n.base, float_node_create(y).base, 0);
+		logic_node_add_input(n.base, float_node_create(z).base, 0);
+	}
+
+	return n;
+}
+
+function vector_node_get(self: vector_node_t, from: i32, done: (a: any)=>void) {
+	logic_node_input_get(self.base.inputs[0], (x: f32) => {
+		logic_node_input_get(self.base.inputs[1], (y: f32) => {
+			logic_node_input_get(self.base.inputs[2], (z: f32) => {
+				self.value.x = x;
+				self.value.y = y;
+				self.value.z = z;
+				done(self.value);
+			});
+		});
+	});
+}
+
+function vector_node_get_as_image(self: vector_node_t, from: i32, done: (img: image_t)=>void) {
+	logic_node_input_get(self.base.inputs[0], (x: f32) => {
+		logic_node_input_get(self.base.inputs[1], (y: f32) => {
+			logic_node_input_get(self.base.inputs[2], (z: f32) => {
+				if (self.image != null) image_unload(self.image);
+				let b: ArrayBuffer = new ArrayBuffer(16);
+				let v: DataView = new DataView(b);
+				v.setFloat32(0, (self.base.inputs[0].node as any).value, true);
+				v.setFloat32(4, (self.base.inputs[1].node as any).value, true);
+				v.setFloat32(8, (self.base.inputs[2].node as any).value, true);
+				v.setFloat32(12, 1.0, true);
+				self.image = image_from_bytes(b, 1, 1, tex_format_t.RGBA128);
+				done(self.image);
+			});
+		});
+	});
+}
+
+function vector_node_set(self: vector_node_t, value: any) {
+	logic_node_input_set(self.base.inputs[0], value.x);
+	logic_node_input_set(self.base.inputs[1], value.y);
+	logic_node_input_set(self.base.inputs[2], value.z);
+}
+
+let vector_node_def: zui_node_t = {
+	id: 0,
+	name: _tr("Vector"),
+	type: "vector_node",
+	x: 0,
+	y: 0,
+	color: 0xff4982a0,
+	inputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("X"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Y"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		},
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Z"),
+			type: "VALUE",
+			color: 0xffa1a1a1,
+			default_value: 0.0
+		}
+	],
+	outputs: [
+		{
+			id: 0,
+			node_id: 0,
+			name: _tr("Vector"),
+			type: "VECTOR",
+			color: 0xff6363c7,
+			default_value: new Float32Array([0.0, 0.0, 0.0])
+		}
+	],
+	buttons: []
+};

+ 1 - 1
base/Sources/nodes_material.ts

@@ -2859,7 +2859,7 @@ function nodes_material_color_ramp_button(ui: zui_t, nodes: zui_nodes_t, node: z
 	if (zui_text("", zui_align_t.RIGHT, chandle.color) == zui_state_t.STARTED) {
 		let rx: f32 = nx + ui._w - zui_nodes_p(37);
 		let ry: f32 = ny - zui_nodes_p(5);
-		nodes._inputStarted = ui.input_started = false;
+		nodes._input_started = ui.input_started = false;
 		zui_nodes_rgba_popup(ui, chandle, val, math_floor(rx), math_floor(ry + zui_ELEMENT_H(ui)));
 	}
 	val[0] = color_get_rb(chandle.color) / 255;

+ 34 - 26
base/Sources/parser_logic.ts

@@ -5,14 +5,14 @@ let parser_logic_links: zui_node_link_t[];
 
 let parser_logic_parsed_nodes: string[] = null;
 let parser_logic_parsed_labels: map_t<string, string> = null;
-let parser_logic_node_map: map_t<string, LogicNode>;
-let parser_logic_raw_map: map_t<LogicNode, zui_node_t>;
+let parser_logic_node_map: map_t<string, logic_node_t>;
+let parser_logic_raw_map: map_t<logic_node_t, zui_node_t>;
 
-function parser_logic_get_logic_node(node: zui_node_t): LogicNode {
+function parser_logic_get_logic_node(node: zui_node_t): logic_node_t {
 	return parser_logic_node_map.get(parser_logic_node_name(node));
 }
 
-function parser_logic_get_raw_node(node: LogicNode): zui_node_t {
+function parser_logic_get_raw_node(node: logic_node_t): zui_node_t {
 	return parser_logic_raw_map.get(node);
 }
 
@@ -83,14 +83,14 @@ function parser_logic_build_node(node: zui_node_t): string {
 	parser_logic_parsed_nodes.push(name);
 
 	// Create node
-	let v: any = parser_logic_create_class_instance(node.type, []);
+	let v: any = parser_logic_create_node_instance(node.type, []);
 	parser_logic_node_map.set(name, v);
 	parser_logic_raw_map.set(v, node);
 
 	// Expose button values in node class
 	for (let b of node.buttons) {
 		if (b.type == "ENUM") {
-			// let arrayData: bool = Array.isArray(b.data);
+			// let array_data: bool = Array.isArray(b.data);
 			let array_data: bool = b.data.length > 1;
 			let texts: string[] = array_data ? b.data : zui_enum_texts_js(node.type);
 			v[b.name] = texts[b.default_value];
@@ -101,7 +101,7 @@ function parser_logic_build_node(node: zui_node_t): string {
 	}
 
 	// Create inputs
-	let inp_node: LogicNode = null;
+	let inp_node: logic_node_t = null;
 	let inp_from: i32 = 0;
 	for (let i: i32 = 0; i < node.inputs.length; ++i) {
 		let inp: zui_node_socket_t = node.inputs[i];
@@ -117,12 +117,12 @@ function parser_logic_build_node(node: zui_node_t): string {
 			inp_from = 0;
 		}
 		// Add input
-		v.add_input(inp_node, inp_from);
+		logic_node_add_input(v.base, inp_node, inp_from);
 	}
 
 	// Create outputss
 	for (let out of node.outputs) {
-		let out_nodes: LogicNode[] = [];
+		let out_nodes: logic_node_t[] = [];
 		let ls: zui_node_link_t[] = parser_logic_get_output_links(out);
 		if (ls != null && ls.length > 0) {
 			for (let l of ls) {
@@ -136,7 +136,7 @@ function parser_logic_build_node(node: zui_node_t): string {
 			out_nodes.push(parser_logic_build_default_node(out));
 		}
 		// Add outputs
-		v.add_outputs(out_nodes);
+		logic_node_add_outputs(v.base, out_nodes);
 	}
 
 	return name;
@@ -160,45 +160,53 @@ function parser_logic_get_root_nodes(node_group: zui_node_canvas_t): zui_node_t[
 	return roots;
 }
 
-function parser_logic_build_default_node(inp: zui_node_socket_t): LogicNode {
-	let v: LogicNode = null;
+function parser_logic_build_default_node(inp: zui_node_socket_t): logic_node_t {
+	let v: logic_node_t = null;
 
 	if (inp.type == "VECTOR") {
 		if (inp.default_value == null) inp.default_value = [0, 0, 0]; // TODO
-		v = parser_logic_create_class_instance("VectorNode", [inp.default_value[0], inp.default_value[1], inp.default_value[2]]);
+		v = parser_logic_create_node_instance("vector_node", [inp.default_value[0], inp.default_value[1], inp.default_value[2]]);
 	}
 	else if (inp.type == "RGBA") {
 		if (inp.default_value == null) inp.default_value = [0, 0, 0, 0]; // TODO
-		v = parser_logic_create_class_instance("ColorNode", [inp.default_value[0], inp.default_value[1], inp.default_value[2], inp.default_value[3]]);
+		v = parser_logic_create_node_instance("color_node", [inp.default_value[0], inp.default_value[1], inp.default_value[2], inp.default_value[3]]);
 	}
 	else if (inp.type == "RGB") {
 		if (inp.default_value == null) inp.default_value = [0, 0, 0, 0]; // TODO
-		v = parser_logic_create_class_instance("ColorNode", [inp.default_value[0], inp.default_value[1], inp.default_value[2], inp.default_value[3]]);
+		v = parser_logic_create_node_instance("color_node", [inp.default_value[0], inp.default_value[1], inp.default_value[2], inp.default_value[3]]);
 	}
 	else if (inp.type == "VALUE") {
-		v = parser_logic_create_class_instance("FloatNode", [inp.default_value]);
+		v = parser_logic_create_node_instance("float_node", [inp.default_value]);
 	}
 	else if (inp.type == "INT") {
-		v = parser_logic_create_class_instance("IntegerNode", [inp.default_value]);
+		v = parser_logic_create_node_instance("integer_node", [inp.default_value]);
 	}
 	else if (inp.type == "BOOLEAN") {
-		v = parser_logic_create_class_instance("BooleanNode", [inp.default_value]);
+		v = parser_logic_create_node_instance("boolean_node", [inp.default_value]);
 	}
 	else if (inp.type == "STRING") {
-		v = parser_logic_create_class_instance("StringNode", [inp.default_value]);
+		v = parser_logic_create_node_instance("string_node", [inp.default_value]);
 	}
 	else {
-		v = parser_logic_create_class_instance("NullNode", []);
+		v = parser_logic_create_node_instance("null_node", []);
 	}
 	return v;
 }
 
-function parser_logic_create_class_instance(className: string, args: any[]): any {
-	if (parser_logic_custom_nodes.get(className) != null) {
-		let node: LogicNode = new LogicNode();
-		node.get = (from: i32) => { return parser_logic_custom_nodes.get(className)(node, from); }
+function parser_logic_create_node_instance(node_type: string, args: any[]): any {
+	if (parser_logic_custom_nodes.get(node_type) != null) {
+		let node: logic_node_t = logic_node_create();
+		node.get = (from: i32) => { return parser_logic_custom_nodes.get(node_type)(node, from); }
 		return node;
 	}
-	let dynamic_class: any = eval(`${className}`);
-	return new dynamic_class(args);
+
+	let eval_args: string = "";
+	for (let arg of args) {
+		if (eval_args != "") {
+			eval_args += ",";
+		}
+		eval_args += arg + "";
+	}
+
+	return eval(node_type + "_create(" + eval_args + ")");
 }

+ 0 - 0
base/Sources/PhysicsBullet.ts → base/Sources/physics_bullet.ts


+ 1 - 1
base/Sources/tab_meshes.ts

@@ -71,7 +71,7 @@ function tab_meshes_draw(htab: zui_handle_t) {
 			///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);
+			util_mesh_apply_displacement(brush_output_node_inst.texpaint_pack, 0.05 * displace_strength, uv_scale);
 			///end
 
 			util_mesh_calc_normals();

+ 2 - 2
base/Sources/ui_base.ts

@@ -1006,12 +1006,12 @@ function ui_base_update_ui() {
 
 				///if (is_paint || is_sculpt)
 				if (context_raw.run_brush != null) {
-					context_raw.run_brush(0);
+					context_raw.run_brush(context_raw.brush_output_node_inst, 0);
 				}
 				///end
 				///if is_lab
 				if (context_run_brush != null) {
-					context_run_brush(0);
+					context_run_brush(context_raw.brush_output_node_inst, 0);
 				}
 				///end
 			}

+ 6 - 6
base/Sources/ui_box.ts

@@ -159,15 +159,15 @@ function ui_box_hide_internal() {
 
 ///if (krom_android || krom_ios)
 function ui_box_tween_in() {
-	tween_reset();
-	tween_to({target: UIBox, props: { ui_box_tween_alpha: 0.5 }, duration: 0.2, ease: ease_t.EXPO_OUT});
-	ui_box_hwnd.drag_y = math_floor(sys_height() / 2);
-	tween_to({target: ui_box_hwnd, props: { dragY: 0 }, duration: 0.2, ease: ease_t.EXPO_OUT, tick: () => { base_redraw_ui(); }});
+	// tween_reset();
+	// tween_to({target: UIBox, props: { ui_box_tween_alpha: 0.5 }, duration: 0.2, ease: ease_t.EXPO_OUT});
+	// ui_box_hwnd.drag_y = math_floor(sys_height() / 2);
+	// tween_to({target: ui_box_hwnd, props: { dragY: 0 }, duration: 0.2, ease: ease_t.EXPO_OUT, tick: () => { base_redraw_ui(); }});
 }
 
 function ui_box_tween_out() {
-	tween_to({target: UIBox, props: { ui_box_tween_alpha: 0.0 }, duration: 0.2, ease: ease_t.EXPO_IN, done: ui_box_hide_internal});
-	tween_to({target: ui_box_hwnd, props: { dragY: sys_height() / 2 }, duration: 0.2, ease: ease_t.EXPO_IN});
+	// tween_to({target: UIBox, props: { ui_box_tween_alpha: 0.0 }, duration: 0.2, ease: ease_t.EXPO_IN, done: ui_box_hide_internal});
+	// tween_to({target: ui_box_hwnd, props: { dragY: sys_height() / 2 }, duration: 0.2, ease: ease_t.EXPO_IN});
 }
 ///end
 

+ 16 - 15
base/Sources/ui_nodes.ts

@@ -234,7 +234,7 @@ function ui_nodes_on_canvas_released() {
 								///end
 								selected.type == "GROUP_INPUT" ||
 								selected.type == "GROUP_OUTPUT" ||
-								selected.type == "BrushOutputNode";
+								selected.type == "brush_output_node";
 				ui_menu.enabled = !is_protected;
 				if (ui_menu_button(ui_menu, tr("Cut"), "ctrl+x")) {
 					base_notify_on_next_frame(function () {
@@ -460,8 +460,8 @@ function ui_nodes_update() {
 	}
 
 	if (operator_shortcut(config_keymap.view_reset)) {
-		nodes.panX = 0.0;
-		nodes.panY = 0.0;
+		nodes.pan_x = 0.0;
+		nodes.pan_y = 0.0;
 		nodes.zoom = 1.0;
 	}
 }
@@ -515,7 +515,7 @@ function ui_nodes_node_search(x: i32 = -1, y: i32 = -1, done: ()=>void = null) {
 						ui_nodes_node_search_spawn = ui_nodes_make_node(n, nodes, canvas); // Spawn selected node
 						canvas.nodes.push(ui_nodes_node_search_spawn);
 						nodes.nodes_selected_id = [ui_nodes_node_search_spawn.id];
-						nodes.nodesDrag = true;
+						nodes.nodes_drag = true;
 
 						///if is_lab
 						parser_logic_parse(canvas);
@@ -650,9 +650,9 @@ function ui_nodes_render() {
 
 	// Remove dragged link when mouse is released out of the node viewport
 	let c: zui_node_canvas_t = ui_nodes_get_canvas(true);
-	if (ui_nodes_release_link && nodes.linkDragId != -1) {
-		array_remove(c.links, zui_get_link(c.links, nodes.linkDragId));
-		nodes.linkDragId = -1;
+	if (ui_nodes_release_link && nodes.link_drag_id != -1) {
+		array_remove(c.links, zui_get_link(c.links, nodes.link_drag_id));
+		nodes.link_drag_id = -1;
 	}
 	ui_nodes_release_link = ui_nodes_ui.input_released;
 
@@ -712,7 +712,7 @@ function ui_nodes_render() {
 		// Grid
 		g2_set_color(0xffffffff);
 		let step: f32 = 100 * zui_SCALE(ui_nodes_ui);
-		g2_draw_image(ui_nodes_grid, (nodes.panX * zui_nodes_SCALE()) % step - step, (nodes.panY * zui_nodes_SCALE()) % step - step);
+		g2_draw_image(ui_nodes_grid, (nodes.pan_x * zui_nodes_SCALE()) % step - step, (nodes.pan_y * zui_nodes_SCALE()) % step - step);
 
 		// Undo
 		if (ui_nodes_ui.input_started || ui_nodes_ui.is_key_pressed) {
@@ -727,13 +727,14 @@ function ui_nodes_render() {
 		///end
 		ui_nodes_ui.window_border_top = ui_header_h * 2;
 		ui_nodes_ui.window_border_bottom = config_raw.layout[layout_size_t.STATUS_H];
+
 		zui_node_canvas(nodes, ui_nodes_ui, c);
 		ui_nodes_ui.input_enabled = _input_enabled;
 
-		if (nodes.colorPickerCallback != null) {
+		if (nodes.color_picker_callback != null) {
 			context_raw.color_picker_previous_tool = context_raw.tool;
 			context_select_tool(workspace_tool_t.PICKER);
-			let tmp: (col: i32)=>void = nodes.colorPickerCallback;
+			let tmp: (col: i32)=>void = nodes.color_picker_callback;
 			context_raw.color_picker_callback = function (color: swatch_color_t) {
 				tmp(color.base);
 				ui_nodes_hwnd.redraws = 2;
@@ -749,11 +750,11 @@ function ui_nodes_render() {
 					ui_nodes_canvas_changed();
 				}
 			};
-			nodes.colorPickerCallback = null;
+			nodes.color_picker_callback = null;
 		}
 
 		// Remove nodes with unknown id for this canvas type
-		if (zui_is_paste) {
+		if (zui_is_paste()) {
 			///if (is_paint || is_sculpt)
 			let node_list: zui_node_t[][] = ui_nodes_canvas_type == canvas_type_t.MATERIAL ? nodes_material_list : nodes_brush_list;
 			///end
@@ -835,7 +836,7 @@ function ui_nodes_render() {
 			else if (sel.type == "OUTPUT_MATERIAL_PBR") {
 				img = context_raw.material.image;
 			}
-			else if (sel.type == "BrushOutputNode") {
+			else if (sel.type == "brush_output_node") {
 				img = context_raw.brush.image;
 			}
 			else if (ui_nodes_canvas_type == canvas_type_t.MATERIAL) {
@@ -1039,7 +1040,7 @@ function ui_nodes_render() {
 				let node: zui_node_t = ui_nodes_make_node(n, nodes, canvas);
 				canvas.nodes.push(node);
 				nodes.nodes_selected_id = [node.id];
-				nodes.nodesDrag = true;
+				nodes.nodes_drag = true;
 				///if is_lab
 				parser_logic_parse(canvas);
 				///end
@@ -1064,7 +1065,7 @@ function ui_nodes_render() {
 					let node: zui_node_t = ui_nodes_make_group_node(g.canvas, nodes, canvas);
 					canvas.nodes.push(node);
 					nodes.nodes_selected_id = [node.id];
-					nodes.nodesDrag = true;
+					nodes.nodes_drag = true;
 				}
 
 				///if (is_paint || is_sculpt)

+ 4 - 4
base/Sources/uniforms_ext.ts

@@ -436,7 +436,7 @@ function uniforms_ext_tex_link(object: object_t, mat: material_data_t, link: str
 		///end
 
 		///if is_lab
-		return BrushOutputNode.inst.texpaint;
+		return brush_output_node_inst.texpaint;
 		///end
 	}
 	if (link.startsWith("_texpaint_nor")) {
@@ -446,7 +446,7 @@ function uniforms_ext_tex_link(object: object_t, mat: material_data_t, link: str
 		///end
 
 		///if is_lab
-		return BrushOutputNode.inst.texpaint_nor;
+		return brush_output_node_inst.texpaint_nor;
 		///end
 	}
 	if (link.startsWith("_texpaint_pack")) {
@@ -456,7 +456,7 @@ function uniforms_ext_tex_link(object: object_t, mat: material_data_t, link: str
 		///end
 
 		///if is_lab
-		return BrushOutputNode.inst.texpaint_pack;
+		return brush_output_node_inst.texpaint_pack;
 		///end
 	}
 	if (link.startsWith("_texpaint")) {
@@ -466,7 +466,7 @@ function uniforms_ext_tex_link(object: object_t, mat: material_data_t, link: str
 		///end
 
 		///if is_lab
-		return BrushOutputNode.inst.texpaint;
+		return brush_output_node_inst.texpaint;
 		///end
 	}
 

Vissa filer visades inte eftersom för många filer har ändrats