merge upstream
This commit is contained in:
		@ -775,6 +775,7 @@ class Scene {
 | 
			
		||||
			// Attach particle systems
 | 
			
		||||
			#if lnx_particles
 | 
			
		||||
			if (o.particle_refs != null) {
 | 
			
		||||
				cast(object, MeshObject).render_emitter = o.render_emitter;
 | 
			
		||||
				for (ref in o.particle_refs) cast(object, MeshObject).setupParticleSystem(sceneName, ref);
 | 
			
		||||
			}
 | 
			
		||||
			#end
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										50
									
								
								leenkx/Sources/iron/format/bmp/Data.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								leenkx/Sources/iron/format/bmp/Data.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
/*
 | 
			
		||||
 * format - Haxe File Formats
 | 
			
		||||
 *
 | 
			
		||||
 *  BMP File Format
 | 
			
		||||
 *  Copyright (C) 2007-2009 Trevor McCauley, Baluta Cristian (hx port) & Robert Sköld (format conversion)
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2009, The Haxe Project Contributors
 | 
			
		||||
 * All rights reserved.
 | 
			
		||||
 * Redistribution and use in source and binary forms, with or without
 | 
			
		||||
 * modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 *
 | 
			
		||||
 *   - Redistributions of source code must retain the above copyright
 | 
			
		||||
 *     notice, this list of conditions and the following disclaimer.
 | 
			
		||||
 *   - Redistributions in binary form must reproduce the above copyright
 | 
			
		||||
 *     notice, this list of conditions and the following disclaimer in the
 | 
			
		||||
 *     documentation and/or other materials provided with the distribution.
 | 
			
		||||
 *
 | 
			
		||||
 * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 | 
			
		||||
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 | 
			
		||||
 * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
 | 
			
		||||
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
			
		||||
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 | 
			
		||||
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 | 
			
		||||
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
			
		||||
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
			
		||||
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 | 
			
		||||
 * DAMAGE.
 | 
			
		||||
 */
 | 
			
		||||
package iron.format.bmp;
 | 
			
		||||
 | 
			
		||||
typedef Data = {
 | 
			
		||||
    var header : iron.format.bmp.Header;
 | 
			
		||||
    var pixels : haxe.io.Bytes;
 | 
			
		||||
#if (haxe_ver < 4)
 | 
			
		||||
    var colorTable : Null<haxe.io.Bytes>;
 | 
			
		||||
#else
 | 
			
		||||
    var ?colorTable : haxe.io.Bytes;
 | 
			
		||||
#end
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef Header = {
 | 
			
		||||
    var width : Int;          // real width (in pixels)
 | 
			
		||||
    var height : Int;         // real height (in pixels)
 | 
			
		||||
    var paddedStride : Int;   // number of bytes in a stride (including padding)
 | 
			
		||||
    var topToBottom : Bool;   // whether the bitmap is stored top to bottom
 | 
			
		||||
    var bpp : Int;            // bits per pixel
 | 
			
		||||
    var dataLength : Int;     // equal to `paddedStride` * `height`
 | 
			
		||||
    var compression : Int;    // which compression is being used, 0 for no compression
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								leenkx/Sources/iron/format/bmp/Reader.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								leenkx/Sources/iron/format/bmp/Reader.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
			
		||||
/*
 | 
			
		||||
 * format - Haxe File Formats
 | 
			
		||||
 *
 | 
			
		||||
 *  BMP File Format
 | 
			
		||||
 *  Copyright (C) 2007-2009 Robert Sköld
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2009, The Haxe Project Contributors
 | 
			
		||||
 * All rights reserved.
 | 
			
		||||
 * Redistribution and use in source and binary forms, with or without
 | 
			
		||||
 * modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 *
 | 
			
		||||
 *   - Redistributions of source code must retain the above copyright
 | 
			
		||||
 *     notice, this list of conditions and the following disclaimer.
 | 
			
		||||
 *   - Redistributions in binary form must reproduce the above copyright
 | 
			
		||||
 *     notice, this list of conditions and the following disclaimer in the
 | 
			
		||||
 *     documentation and/or other materials provided with the distribution.
 | 
			
		||||
 *
 | 
			
		||||
 * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 | 
			
		||||
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 | 
			
		||||
 * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
 | 
			
		||||
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
			
		||||
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 | 
			
		||||
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 | 
			
		||||
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
			
		||||
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
			
		||||
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 | 
			
		||||
 * DAMAGE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package iron.format.bmp;
 | 
			
		||||
 | 
			
		||||
import iron.format.bmp.Data;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Reader {
 | 
			
		||||
 | 
			
		||||
	var input : haxe.io.Input;
 | 
			
		||||
 | 
			
		||||
	public function new( i ) {
 | 
			
		||||
		input = i;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** 
 | 
			
		||||
	 * Only supports uncompressed 24bpp bitmaps (the most common format).
 | 
			
		||||
	 * 
 | 
			
		||||
	 * The returned bytes in `Data.pixels` will be in BGR order, and with padding (if present).
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @see https://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx
 | 
			
		||||
	 * @see https://en.wikipedia.org/wiki/BMP_file_format#Bitmap_file_header
 | 
			
		||||
	 */
 | 
			
		||||
	public function read() : format.bmp.Data {
 | 
			
		||||
		// Read Header
 | 
			
		||||
		for (b in ["B".code, "M".code]) {
 | 
			
		||||
			if (input.readByte() != b) throw "Invalid header";
 | 
			
		||||
		}
 | 
			
		||||
	
 | 
			
		||||
		var fileSize = input.readInt32();
 | 
			
		||||
		input.readInt32();							// Reserved
 | 
			
		||||
		var offset = input.readInt32();
 | 
			
		||||
 | 
			
		||||
		// Read InfoHeader
 | 
			
		||||
		var infoHeaderSize = input.readInt32();		// InfoHeader size
 | 
			
		||||
		if (infoHeaderSize != 40) {
 | 
			
		||||
			throw 'Info headers with size $infoHeaderSize not supported.';
 | 
			
		||||
		}
 | 
			
		||||
		var width = input.readInt32();				// Image width (actual, not padded)
 | 
			
		||||
		var height = input.readInt32();				// Image height
 | 
			
		||||
		var numPlanes = input.readInt16();			// Number of planes
 | 
			
		||||
		var bits = input.readInt16();				// Bits per pixel
 | 
			
		||||
		var compression = input.readInt32();		// Compression type
 | 
			
		||||
		var dataLength = input.readInt32();			// Image data size (includes padding!)
 | 
			
		||||
		input.readInt32();							// Horizontal resolution
 | 
			
		||||
		input.readInt32();							// Vertical resolution
 | 
			
		||||
		var colorsUsed = input.readInt32();			// Colors used (0 when uncompressed)
 | 
			
		||||
		input.readInt32();							// Important colors (0 when uncompressed)
 | 
			
		||||
 | 
			
		||||
		// If there's no compression, the dataLength may be 0
 | 
			
		||||
		if ( compression == 0 && dataLength == 0 ) dataLength = fileSize - offset;
 | 
			
		||||
 | 
			
		||||
		var bytesRead = 54; // total read above
 | 
			
		||||
		
 | 
			
		||||
		var colorTable : haxe.io.Bytes = null;
 | 
			
		||||
		if ( bits <= 8 ) {
 | 
			
		||||
			if ( colorsUsed == 0 ) {
 | 
			
		||||
				colorsUsed = Tools.getNumColorsForBitDepth(bits);
 | 
			
		||||
			}
 | 
			
		||||
			var colorTableLength = 4 * colorsUsed;
 | 
			
		||||
			colorTable = haxe.io.Bytes.alloc( colorTableLength );
 | 
			
		||||
			input.readFullBytes( colorTable, 0, colorTableLength );
 | 
			
		||||
			bytesRead += colorTableLength;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		input.read( offset - bytesRead );
 | 
			
		||||
		
 | 
			
		||||
		var p = haxe.io.Bytes.alloc( dataLength );	
 | 
			
		||||
		
 | 
			
		||||
		// Read Raster Data
 | 
			
		||||
		var paddedStride = Tools.computePaddedStride(width, bits);
 | 
			
		||||
		var topToBottom = false;
 | 
			
		||||
		if ( height < 0 ) { // if bitmap is stored top to bottom
 | 
			
		||||
			topToBottom = true;
 | 
			
		||||
			height = -height;
 | 
			
		||||
		}
 | 
			
		||||
    
 | 
			
		||||
		input.readFullBytes(p, 0, dataLength);
 | 
			
		||||
			
 | 
			
		||||
		return {
 | 
			
		||||
			header: {
 | 
			
		||||
				width: width,
 | 
			
		||||
				height: height,
 | 
			
		||||
				paddedStride: paddedStride,
 | 
			
		||||
				topToBottom: topToBottom,
 | 
			
		||||
				bpp: bits,
 | 
			
		||||
				dataLength: dataLength,
 | 
			
		||||
				compression: compression
 | 
			
		||||
			},
 | 
			
		||||
			pixels: p,
 | 
			
		||||
			colorTable: colorTable
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										256
									
								
								leenkx/Sources/iron/format/bmp/Tools.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								leenkx/Sources/iron/format/bmp/Tools.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,256 @@
 | 
			
		||||
/*
 | 
			
		||||
 * format - Haxe File Formats
 | 
			
		||||
 *
 | 
			
		||||
 *  BMP File Format
 | 
			
		||||
 *  Copyright (C) 2007-2009 Trevor McCauley, Baluta Cristian (hx port) & Robert Sköld (format conversion)
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2009, The Haxe Project Contributors
 | 
			
		||||
 * All rights reserved.
 | 
			
		||||
 * Redistribution and use in source and binary forms, with or without
 | 
			
		||||
 * modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 *
 | 
			
		||||
 *   - Redistributions of source code must retain the above copyright
 | 
			
		||||
 *     notice, this list of conditions and the following disclaimer.
 | 
			
		||||
 *   - Redistributions in binary form must reproduce the above copyright
 | 
			
		||||
 *     notice, this list of conditions and the following disclaimer in the
 | 
			
		||||
 *     documentation and/or other materials provided with the distribution.
 | 
			
		||||
 *
 | 
			
		||||
 * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 | 
			
		||||
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 | 
			
		||||
 * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
 | 
			
		||||
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
			
		||||
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 | 
			
		||||
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 | 
			
		||||
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
			
		||||
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
			
		||||
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 | 
			
		||||
 * DAMAGE.
 | 
			
		||||
 */
 | 
			
		||||
package iron.format.bmp;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tools {
 | 
			
		||||
 | 
			
		||||
	//												  a  r  g  b
 | 
			
		||||
	static var ARGB_MAP(default, never):Array<Int> = [0, 1, 2, 3];
 | 
			
		||||
	static var BGRA_MAP(default, never):Array<Int> = [3, 2, 1, 0];
 | 
			
		||||
 | 
			
		||||
	static var COLOR_SIZE(default, never):Int = 4;
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
		Extract BMP pixel data (24bpp in BGR format) and expands it to BGRA, removing any padding in the process.
 | 
			
		||||
	**/
 | 
			
		||||
	inline static public function extractBGRA( bmp : iron.format.bmp.Data ) : haxe.io.Bytes {
 | 
			
		||||
		return _extract32(bmp, BGRA_MAP, 0xFF);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
		Extract BMP pixel data (24bpp in BGR format) and converts it to ARGB.
 | 
			
		||||
	**/
 | 
			
		||||
	inline static public function extractARGB( bmp : iron.format.bmp.Data ) : haxe.io.Bytes {
 | 
			
		||||
		return _extract32(bmp, ARGB_MAP, 0xFF);
 | 
			
		||||
	}
 | 
			
		||||
  
 | 
			
		||||
	/**
 | 
			
		||||
		Creates BMP data from bytes in BGRA format for each pixel.
 | 
			
		||||
	**/
 | 
			
		||||
	inline static public function buildFromBGRA( width : Int, height : Int, srcBytes : haxe.io.Bytes, topToBottom : Bool = false ) : Data {
 | 
			
		||||
		return _buildFrom32(width, height, srcBytes, BGRA_MAP, topToBottom);
 | 
			
		||||
	}
 | 
			
		||||
  
 | 
			
		||||
	/**
 | 
			
		||||
		Creates BMP data from bytes in ARGB format for each pixel.
 | 
			
		||||
	**/
 | 
			
		||||
	inline static public function buildFromARGB( width : Int, height : Int, srcBytes : haxe.io.Bytes, topToBottom : Bool = false ) : Data {
 | 
			
		||||
		return _buildFrom32(width, height, srcBytes, ARGB_MAP, topToBottom);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inline static public function computePaddedStride(width:Int, bpp:Int):Int {
 | 
			
		||||
		return ((((width * bpp) + 31) & ~31) >> 3);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets number of colors for indexed palettes
 | 
			
		||||
	 */
 | 
			
		||||
	inline static public function getNumColorsForBitDepth(bpp:Int):Int {
 | 
			
		||||
		return switch (bpp) {
 | 
			
		||||
			case 1: 2;
 | 
			
		||||
			case 4: 16;
 | 
			
		||||
			case 8: 256;
 | 
			
		||||
			case 16: 65536;
 | 
			
		||||
			default: throw 'Unsupported bpp $bpp';
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	// `channelMap` contains indices to map into ARGB (f.e. the mapping for ARGB is [0,1,2,3], while for BGRA is [3,2,1,0])
 | 
			
		||||
	static function _extract32( bmp : iron.format.bmp.Data, channelMap : Array<Int>, alpha : Int = 0xFF) : haxe.io.Bytes {
 | 
			
		||||
		var srcBytes = bmp.pixels;
 | 
			
		||||
		var dstLen = bmp.header.width * bmp.header.height * 4;
 | 
			
		||||
		var dstBytes = haxe.io.Bytes.alloc( dstLen );
 | 
			
		||||
		var srcPaddedStride = bmp.header.paddedStride;
 | 
			
		||||
		
 | 
			
		||||
		var yDir = -1;
 | 
			
		||||
		var dstPos = 0;
 | 
			
		||||
		var srcPos = srcPaddedStride * (bmp.header.height - 1);
 | 
			
		||||
    
 | 
			
		||||
		if ( bmp.header.topToBottom ) {
 | 
			
		||||
			yDir = 1;
 | 
			
		||||
			srcPos = 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ( bmp.header.bpp < 8 || bmp.header.bpp == 16 ) {
 | 
			
		||||
			throw 'bpp ${bmp.header.bpp} not supported';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var colorTable:haxe.io.Bytes = null;
 | 
			
		||||
		if ( bmp.header.bpp <= 8 ) {
 | 
			
		||||
			var colorTableLength = getNumColorsForBitDepth(bmp.header.bpp);
 | 
			
		||||
			colorTable = haxe.io.Bytes.alloc(colorTableLength * COLOR_SIZE);
 | 
			
		||||
			var definedColorTableLength = Std.int( bmp.colorTable.length / COLOR_SIZE );
 | 
			
		||||
			for( i in 0...definedColorTableLength ) {
 | 
			
		||||
				var b = bmp.colorTable.get( i * COLOR_SIZE);
 | 
			
		||||
				var g = bmp.colorTable.get( i * COLOR_SIZE + 1);
 | 
			
		||||
				var r = bmp.colorTable.get( i * COLOR_SIZE + 2);
 | 
			
		||||
 | 
			
		||||
				colorTable.set(i * COLOR_SIZE + channelMap[0], alpha);
 | 
			
		||||
				colorTable.set(i * COLOR_SIZE + channelMap[1], r);
 | 
			
		||||
				colorTable.set(i * COLOR_SIZE + channelMap[2], g);
 | 
			
		||||
				colorTable.set(i * COLOR_SIZE + channelMap[3], b);
 | 
			
		||||
			}
 | 
			
		||||
			// We want to have the table the full length in case indices outside the range are present
 | 
			
		||||
			colorTable.fill(definedColorTableLength, colorTableLength - definedColorTableLength, 0);
 | 
			
		||||
			for( i in definedColorTableLength...colorTableLength ) {
 | 
			
		||||
				colorTable.set(i * COLOR_SIZE + channelMap[0], alpha);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch bmp.header.compression {
 | 
			
		||||
			case 0:
 | 
			
		||||
				while( dstPos < dstLen ) {
 | 
			
		||||
					for( i in 0...bmp.header.width ) {
 | 
			
		||||
						if (bmp.header.bpp == 8) {
 | 
			
		||||
 | 
			
		||||
							var currentSrcPos = srcPos + i;
 | 
			
		||||
							var index = srcBytes.get(currentSrcPos);
 | 
			
		||||
							dstBytes.blit( dstPos, colorTable, index * COLOR_SIZE, COLOR_SIZE );
 | 
			
		||||
 | 
			
		||||
						} else if (bmp.header.bpp == 24) {
 | 
			
		||||
 | 
			
		||||
							var currentSrcPos = srcPos + i * 3;
 | 
			
		||||
							var b = srcBytes.get(currentSrcPos);
 | 
			
		||||
							var g = srcBytes.get(currentSrcPos + 1);
 | 
			
		||||
							var r = srcBytes.get(currentSrcPos + 2);
 | 
			
		||||
							
 | 
			
		||||
							dstBytes.set(dstPos + channelMap[0], alpha);
 | 
			
		||||
							dstBytes.set(dstPos + channelMap[1], r);
 | 
			
		||||
							dstBytes.set(dstPos + channelMap[2], g);
 | 
			
		||||
							dstBytes.set(dstPos + channelMap[3], b);
 | 
			
		||||
 | 
			
		||||
						} else if (bmp.header.bpp == 32) {
 | 
			
		||||
 | 
			
		||||
							var currentSrcPos = srcPos + i * 4;
 | 
			
		||||
							var b = srcBytes.get(currentSrcPos);
 | 
			
		||||
							var g = srcBytes.get(currentSrcPos + 1);
 | 
			
		||||
							var r = srcBytes.get(currentSrcPos + 2);
 | 
			
		||||
							
 | 
			
		||||
							dstBytes.set(dstPos + channelMap[0], alpha);
 | 
			
		||||
							dstBytes.set(dstPos + channelMap[1], r);
 | 
			
		||||
							dstBytes.set(dstPos + channelMap[2], g);
 | 
			
		||||
							dstBytes.set(dstPos + channelMap[3], b);
 | 
			
		||||
 | 
			
		||||
						}
 | 
			
		||||
						dstPos += 4;
 | 
			
		||||
					}
 | 
			
		||||
					srcPos += yDir * srcPaddedStride;
 | 
			
		||||
				}
 | 
			
		||||
			case 1:
 | 
			
		||||
				srcPos = 0;
 | 
			
		||||
				var x = 0;
 | 
			
		||||
				var y = bmp.header.topToBottom ? 0 : bmp.header.height - 1;
 | 
			
		||||
				while( srcPos < bmp.header.dataLength ) {
 | 
			
		||||
					var count = srcBytes.get(srcPos++);
 | 
			
		||||
					var index = srcBytes.get(srcPos++);
 | 
			
		||||
					if ( count == 0 ) {
 | 
			
		||||
						if ( index == 0 ) {
 | 
			
		||||
							x = 0;
 | 
			
		||||
							y += yDir;
 | 
			
		||||
						} else if ( index == 1 ) {
 | 
			
		||||
							break;
 | 
			
		||||
						} else if ( index == 2 ) {
 | 
			
		||||
							x += srcBytes.get(srcPos++);
 | 
			
		||||
							y += srcBytes.get(srcPos++);
 | 
			
		||||
						} else {
 | 
			
		||||
							count = index;
 | 
			
		||||
							for( i in 0...count ) {
 | 
			
		||||
								index = srcBytes.get(srcPos++);
 | 
			
		||||
								dstBytes.blit( COLOR_SIZE * ((x+i) + y * bmp.header.width), colorTable, index * COLOR_SIZE, COLOR_SIZE );
 | 
			
		||||
							}
 | 
			
		||||
							if (srcPos % 2 != 0) srcPos++;
 | 
			
		||||
							x += count;
 | 
			
		||||
						}
 | 
			
		||||
					} else {
 | 
			
		||||
						for( i in 0...count ) {
 | 
			
		||||
							dstBytes.blit( COLOR_SIZE * ((x+i) + y * bmp.header.width), colorTable, index * COLOR_SIZE, COLOR_SIZE );
 | 
			
		||||
						}
 | 
			
		||||
						x += count;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			default:
 | 
			
		||||
				throw 'compression ${bmp.header.compression} not supported';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return dstBytes;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// `channelMap` contains indices to map into ARGB (f.e. the mapping for ARGB is [0,1,2,3], while for BGRA is [3,2,1,0])
 | 
			
		||||
	static function _buildFrom32( width : Int, height : Int, srcBytes : haxe.io.Bytes, channelMap : Array<Int>, topToBottom : Bool = false ) : Data {
 | 
			
		||||
		var bpp = 24;
 | 
			
		||||
		var paddedStride = computePaddedStride(width, bpp);
 | 
			
		||||
		var bytesBGR = haxe.io.Bytes.alloc(paddedStride * height);
 | 
			
		||||
		var topToBottom = topToBottom;
 | 
			
		||||
		var dataLength = bytesBGR.length;
 | 
			
		||||
		
 | 
			
		||||
		var dstStride = width * 3;
 | 
			
		||||
		var srcLen = width * height * 4;
 | 
			
		||||
		var yDir = -1;
 | 
			
		||||
		var dstPos = dataLength - paddedStride;
 | 
			
		||||
		var srcPos = 0;
 | 
			
		||||
		
 | 
			
		||||
		if ( topToBottom ) {
 | 
			
		||||
			yDir = 1;
 | 
			
		||||
			dstPos = 0;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		while( srcPos < srcLen ) {
 | 
			
		||||
			var i = dstPos;
 | 
			
		||||
			while( i < dstPos + dstStride ) {
 | 
			
		||||
				var r = srcBytes.get(srcPos + channelMap[1]);
 | 
			
		||||
				var g = srcBytes.get(srcPos + channelMap[2]);
 | 
			
		||||
				var b = srcBytes.get(srcPos + channelMap[3]);
 | 
			
		||||
				
 | 
			
		||||
				bytesBGR.set(i++, b);
 | 
			
		||||
				bytesBGR.set(i++, g);
 | 
			
		||||
				bytesBGR.set(i++, r);
 | 
			
		||||
				
 | 
			
		||||
				srcPos += 4;
 | 
			
		||||
			}
 | 
			
		||||
			dstPos += yDir * paddedStride;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return {
 | 
			
		||||
			header: {
 | 
			
		||||
				width: width,
 | 
			
		||||
				height: height,
 | 
			
		||||
				paddedStride: paddedStride,
 | 
			
		||||
				topToBottom: topToBottom,
 | 
			
		||||
				bpp: bpp,
 | 
			
		||||
				dataLength: dataLength,
 | 
			
		||||
				compression: 0
 | 
			
		||||
			},
 | 
			
		||||
			pixels: bytesBGR,
 | 
			
		||||
			colorTable: null
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										74
									
								
								leenkx/Sources/iron/format/bmp/Writer.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								leenkx/Sources/iron/format/bmp/Writer.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,74 @@
 | 
			
		||||
/*
 | 
			
		||||
 * format - Haxe File Formats
 | 
			
		||||
 *
 | 
			
		||||
 *  BMP File Format
 | 
			
		||||
 *  Copyright (C) 2007-2009 Robert Sköld
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2009, The Haxe Project Contributors
 | 
			
		||||
 * All rights reserved.
 | 
			
		||||
 * Redistribution and use in source and binary forms, with or without
 | 
			
		||||
 * modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 *
 | 
			
		||||
 *   - Redistributions of source code must retain the above copyright
 | 
			
		||||
 *     notice, this list of conditions and the following disclaimer.
 | 
			
		||||
 *   - Redistributions in binary form must reproduce the above copyright
 | 
			
		||||
 *     notice, this list of conditions and the following disclaimer in the
 | 
			
		||||
 *     documentation and/or other materials provided with the distribution.
 | 
			
		||||
 *
 | 
			
		||||
 * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
 | 
			
		||||
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 | 
			
		||||
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 | 
			
		||||
 * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
 | 
			
		||||
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
			
		||||
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 | 
			
		||||
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 | 
			
		||||
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
			
		||||
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
			
		||||
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 | 
			
		||||
 * DAMAGE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package iron.format.bmp;
 | 
			
		||||
 | 
			
		||||
import iron.format.bmp.Data;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Writer {
 | 
			
		||||
 | 
			
		||||
	static var DATA_OFFSET : Int = 0x36;
 | 
			
		||||
 | 
			
		||||
	var output : haxe.io.Output;
 | 
			
		||||
 | 
			
		||||
	public function new(o) {
 | 
			
		||||
		output = o;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Specs: http://s223767089.online.de/en/file-format-bmp
 | 
			
		||||
	 */
 | 
			
		||||
	public function write( bmp : Data ) {
 | 
			
		||||
		// Write Header (14 bytes)
 | 
			
		||||
		output.writeString( "BM" );								// Signature
 | 
			
		||||
		output.writeInt32(bmp.pixels.length + DATA_OFFSET );	// FileSize
 | 
			
		||||
		output.writeInt32( 0 );									// Reserved
 | 
			
		||||
		output.writeInt32( DATA_OFFSET );						// Offset
 | 
			
		||||
 | 
			
		||||
		// Write InfoHeader (40 bytes)
 | 
			
		||||
		output.writeInt32( 40 );								// InfoHeader size
 | 
			
		||||
		output.writeInt32( bmp.header.width );					// Image width
 | 
			
		||||
		var height = bmp.header.height;
 | 
			
		||||
		if (bmp.header.topToBottom) height = -height; 
 | 
			
		||||
		output.writeInt32( height );							// Image height
 | 
			
		||||
		output.writeInt16( 1 );									// Number of planes
 | 
			
		||||
		output.writeInt16( 24 );								// Bits per pixel (24bit RGB)
 | 
			
		||||
		output.writeInt32( 0 );									// Compression type (no compression)
 | 
			
		||||
		output.writeInt32( bmp.header.dataLength );				// Image data size (0 when uncompressed)
 | 
			
		||||
		output.writeInt32( 0x2e30 );							// Horizontal resolution
 | 
			
		||||
		output.writeInt32( 0x2e30 );							// Vertical resolution
 | 
			
		||||
		output.writeInt32( 0 );									// Colors used (0 when uncompressed)
 | 
			
		||||
		output.writeInt32( 0 );									// Important colors (0 when uncompressed)
 | 
			
		||||
 | 
			
		||||
		// Write Raster Data
 | 
			
		||||
		output.write(bmp.pixels);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -21,6 +21,7 @@ class MeshObject extends Object {
 | 
			
		||||
	public var particleChildren: Array<MeshObject> = null;
 | 
			
		||||
	public var particleOwner: MeshObject = null; // Particle object
 | 
			
		||||
	public var particleIndex = -1;
 | 
			
		||||
	public var render_emitter = true;
 | 
			
		||||
	#end
 | 
			
		||||
	public var cameraDistance: Float;
 | 
			
		||||
	public var screenSize = 0.0;
 | 
			
		||||
@ -255,11 +256,11 @@ class MeshObject extends Object {
 | 
			
		||||
				particleSystems[i].update(particleChildren[i], this);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (particleSystems != null && particleSystems.length > 0 && !raw.render_emitter) return;
 | 
			
		||||
		if (particleSystems != null && particleSystems.length > 0 && !render_emitter) return;
 | 
			
		||||
        if (particleSystems == null && cullMaterial(context)) return;
 | 
			
		||||
        #else
 | 
			
		||||
        if (cullMaterial(context)) return;
 | 
			
		||||
		#end
 | 
			
		||||
 | 
			
		||||
		if (cullMaterial(context)) return;
 | 
			
		||||
 | 
			
		||||
		// Get lod
 | 
			
		||||
		var mats = materials;
 | 
			
		||||
		var lod = this;
 | 
			
		||||
 | 
			
		||||
@ -18,29 +18,29 @@ class ParticleSystem {
 | 
			
		||||
	public var speed = 1.0;
 | 
			
		||||
	var particles: Array<Particle>;
 | 
			
		||||
	var ready: Bool;
 | 
			
		||||
	var frameRate = 24;
 | 
			
		||||
	var lifetime = 0.0;
 | 
			
		||||
	var animtime = 0.0;
 | 
			
		||||
	var time = 0.0;
 | 
			
		||||
	var spawnRate = 0.0;
 | 
			
		||||
	public var frameRate = 24;
 | 
			
		||||
	public var lifetime = 0.0;
 | 
			
		||||
	public var animtime = 0.0;
 | 
			
		||||
	public var time = 0.0;
 | 
			
		||||
	public var spawnRate = 0.0;
 | 
			
		||||
	var seed = 0;
 | 
			
		||||
 | 
			
		||||
	var r: TParticleData;
 | 
			
		||||
	var gx: Float;
 | 
			
		||||
	var gy: Float;
 | 
			
		||||
	var gz: Float;
 | 
			
		||||
	var alignx: Float;
 | 
			
		||||
	var aligny: Float;
 | 
			
		||||
	var alignz: Float;
 | 
			
		||||
	public var r: TParticleData;
 | 
			
		||||
	public var gx: Float;
 | 
			
		||||
	public var gy: Float;
 | 
			
		||||
	public var gz: Float;
 | 
			
		||||
	public var alignx: Float;
 | 
			
		||||
	public var aligny: Float;
 | 
			
		||||
	public var alignz: Float;
 | 
			
		||||
	var dimx: Float;
 | 
			
		||||
	var dimy: Float;
 | 
			
		||||
	var tilesx: Int;
 | 
			
		||||
	var tilesy: Int;
 | 
			
		||||
	var tilesFramerate: Int;
 | 
			
		||||
 | 
			
		||||
	var count = 0;
 | 
			
		||||
	var lap = 0;
 | 
			
		||||
	var lapTime = 0.0;
 | 
			
		||||
	public var count = 0;
 | 
			
		||||
	public var lap = 0;
 | 
			
		||||
	public var lapTime = 0.0;
 | 
			
		||||
	var m = Mat4.identity();
 | 
			
		||||
 | 
			
		||||
	var ownerLoc = new Vec4();
 | 
			
		||||
@ -149,7 +149,7 @@ class ParticleSystem {
 | 
			
		||||
		// GPU particles transform is attached to owner object
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function setupGeomGpu(object: MeshObject, owner: MeshObject) {
 | 
			
		||||
	public function setupGeomGpu(object: MeshObject, owner: MeshObject) {
 | 
			
		||||
		var instancedData = new Float32Array(particles.length * 6);
 | 
			
		||||
		var i = 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										99
									
								
								leenkx/Sources/leenkx/logicnode/AddParticleToObjectNode.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								leenkx/Sources/leenkx/logicnode/AddParticleToObjectNode.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
			
		||||
package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.data.SceneFormat.TSceneFormat;
 | 
			
		||||
import iron.data.Data;
 | 
			
		||||
import iron.object.Object;
 | 
			
		||||
 | 
			
		||||
class AddParticleToObjectNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
	public var property0: String;
 | 
			
		||||
 | 
			
		||||
	public function new(tree: LogicTree) {
 | 
			
		||||
		super(tree);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override function run(from: Int) {
 | 
			
		||||
		#if lnx_particles
 | 
			
		||||
 | 
			
		||||
		if (property0 == 'Scene Active'){		
 | 
			
		||||
			var objFrom: Object = inputs[1].get();
 | 
			
		||||
			var slot: Int = inputs[2].get();
 | 
			
		||||
			var objTo: Object = inputs[3].get();
 | 
			
		||||
	
 | 
			
		||||
			if (objFrom == null || objTo == null) return;
 | 
			
		||||
	
 | 
			
		||||
			var mobjFrom = cast(objFrom, iron.object.MeshObject);
 | 
			
		||||
	
 | 
			
		||||
			var psys = mobjFrom.particleSystems != null ? mobjFrom.particleSystems[slot] : 
 | 
			
		||||
				mobjFrom.particleOwner != null && mobjFrom.particleOwner.particleSystems != null ? mobjFrom.particleOwner.particleSystems[slot] : null;
 | 
			
		||||
 | 
			
		||||
			if (psys == null) return;
 | 
			
		||||
	
 | 
			
		||||
			var mobjTo = cast(objTo, iron.object.MeshObject);
 | 
			
		||||
	
 | 
			
		||||
			mobjTo.setupParticleSystem(iron.Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: psys.r.name});
 | 
			
		||||
			
 | 
			
		||||
			mobjTo.render_emitter = inputs[4].get();
 | 
			
		||||
 | 
			
		||||
			iron.Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
 | 
			
		||||
				if (o != null) {
 | 
			
		||||
					var c: iron.object.MeshObject = cast o;
 | 
			
		||||
					if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
 | 
			
		||||
					mobjTo.particleChildren.push(c);
 | 
			
		||||
					c.particleOwner = mobjTo;
 | 
			
		||||
					c.particleIndex = mobjTo.particleChildren.length - 1;
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
	
 | 
			
		||||
			var oslot: Int = mobjTo.particleSystems.length-1;
 | 
			
		||||
			var opsys = mobjTo.particleSystems[oslot];
 | 
			
		||||
			opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo);
 | 
			
		||||
		
 | 
			
		||||
		} else {
 | 
			
		||||
			var sceneName: String = inputs[1].get();
 | 
			
		||||
			var objectName: String = inputs[2].get();
 | 
			
		||||
			var slot: Int = inputs[3].get();
 | 
			
		||||
 | 
			
		||||
			var mobjTo: Object = inputs[4].get();
 | 
			
		||||
			var mobjTo = cast(mobjTo, iron.object.MeshObject);
 | 
			
		||||
 | 
			
		||||
			#if lnx_json
 | 
			
		||||
				sceneName += ".json";
 | 
			
		||||
			#elseif lnx_compress
 | 
			
		||||
				sceneName += ".lz4";
 | 
			
		||||
			#end
 | 
			
		||||
 | 
			
		||||
			Data.getSceneRaw(sceneName, (rawScene: TSceneFormat) -> {
 | 
			
		||||
 | 
			
		||||
			for (obj in rawScene.objects) {
 | 
			
		||||
				if (obj.name == objectName) {
 | 
			
		||||
					mobjTo.setupParticleSystem(sceneName, obj.particle_refs[slot]);
 | 
			
		||||
					mobjTo.render_emitter = inputs[5].get();					
 | 
			
		||||
 | 
			
		||||
					iron.Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) {
 | 
			
		||||
						if (o != null) {
 | 
			
		||||
							var c: iron.object.MeshObject = cast o;
 | 
			
		||||
							if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
 | 
			
		||||
							mobjTo.particleChildren.push(c);
 | 
			
		||||
							c.particleOwner = mobjTo;
 | 
			
		||||
							c.particleIndex = mobjTo.particleChildren.length - 1;
 | 
			
		||||
						}
 | 
			
		||||
					}, true, rawScene);
 | 
			
		||||
 | 
			
		||||
					var oslot: Int = mobjTo.particleSystems.length-1;
 | 
			
		||||
					var opsys = mobjTo.particleSystems[oslot];
 | 
			
		||||
					opsys.setupGeomGpu(mobjTo.particleChildren[oslot], mobjTo);
 | 
			
		||||
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		#end
 | 
			
		||||
 | 
			
		||||
		runOutput(0);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -2,6 +2,9 @@ package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.Scene;
 | 
			
		||||
import iron.object.CameraObject;
 | 
			
		||||
import iron.math.Vec4;
 | 
			
		||||
import iron.math.Quat;
 | 
			
		||||
import leenkx.math.Helper;
 | 
			
		||||
 | 
			
		||||
import leenkx.renderpath.RenderPathCreator;
 | 
			
		||||
 | 
			
		||||
@ -27,11 +30,19 @@ class DrawCameraTextureNode extends LogicNode {
 | 
			
		||||
				final c = inputs[2].get();
 | 
			
		||||
				assert(Error, Std.isOfType(c, CameraObject), "Camera must be a camera object!");
 | 
			
		||||
				cam = cast(c, CameraObject);
 | 
			
		||||
				rt = kha.Image.createRenderTarget(iron.App.w(), iron.App.h());
 | 
			
		||||
				rt = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(),
 | 
			
		||||
					kha.graphics4.TextureFormat.RGBA32,
 | 
			
		||||
					kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
 | 
			
		||||
 | 
			
		||||
				assert(Error, mo.materials[matSlot].contexts[0].textures != null, 'Object "${mo.name}" has no diffuse texture to render to');
 | 
			
		||||
				mo.materials[matSlot].contexts[0].textures[0] = rt; // Override diffuse texture
 | 
			
		||||
 | 
			
		||||
                final n = inputs[5].get();
 | 
			
		||||
				for (i => node in mo.materials[matSlot].contexts[0].raw.bind_textures){
 | 
			
		||||
					if (node.name == n){
 | 
			
		||||
						mo.materials[matSlot].contexts[0].textures[i] = rt; // Override diffuse texture
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				tree.notifyOnRender(render);
 | 
			
		||||
				runOutput(0);
 | 
			
		||||
 | 
			
		||||
@ -48,8 +59,20 @@ class DrawCameraTextureNode extends LogicNode {
 | 
			
		||||
		iron.Scene.active.camera = cam;
 | 
			
		||||
		cam.renderTarget = rt;
 | 
			
		||||
 | 
			
		||||
		#if kha_html5
 | 
			
		||||
		var q: Quat = new Quat();
 | 
			
		||||
		q.fromAxisAngle(new Vec4(0, 0, 1, 1), Helper.degToRad(180));
 | 
			
		||||
		cam.transform.rot.mult(q);
 | 
			
		||||
		cam.transform.buildMatrix();
 | 
			
		||||
		#end
 | 
			
		||||
 | 
			
		||||
		cam.renderFrame(g);
 | 
			
		||||
 | 
			
		||||
		#if kha_html5
 | 
			
		||||
		cam.transform.rot.mult(q);
 | 
			
		||||
		cam.transform.buildMatrix();
 | 
			
		||||
		#end
 | 
			
		||||
 | 
			
		||||
		cam.renderTarget = oldRT;
 | 
			
		||||
		iron.Scene.active.camera = sceneCam;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -99,8 +99,6 @@ class DrawImageSequenceNode extends LogicNode {
 | 
			
		||||
		final colorVec = inputs[4].get();
 | 
			
		||||
		g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w);
 | 
			
		||||
 | 
			
		||||
		trace(currentImgIdx);
 | 
			
		||||
 | 
			
		||||
		g.drawScaledImage(images[currentImgIdx], inputs[5].get(), inputs[6].get(), inputs[7].get(), inputs[8].get());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										59
									
								
								leenkx/Sources/leenkx/logicnode/DrawSubImageNode.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								leenkx/Sources/leenkx/logicnode/DrawSubImageNode.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.math.Vec4;
 | 
			
		||||
import kha.Image;
 | 
			
		||||
import kha.Color;
 | 
			
		||||
import leenkx.renderpath.RenderToTexture;
 | 
			
		||||
 | 
			
		||||
class DrawSubImageNode extends LogicNode {
 | 
			
		||||
    var img: Image;
 | 
			
		||||
    var lastImgName = "";
 | 
			
		||||
 | 
			
		||||
    public function new(tree: LogicTree) {
 | 
			
		||||
        super(tree);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override function run(from: Int) {
 | 
			
		||||
        RenderToTexture.ensure2DContext("DrawImageNode");
 | 
			
		||||
 | 
			
		||||
        final imgName: String = inputs[1].get();
 | 
			
		||||
        final colorVec: Vec4 = inputs[2].get();
 | 
			
		||||
        final anchorH: Int = inputs[3].get();
 | 
			
		||||
        final anchorV: Int = inputs[4].get();
 | 
			
		||||
        final x: Float = inputs[5].get();
 | 
			
		||||
        final y: Float = inputs[6].get();
 | 
			
		||||
        final width: Float = inputs[7].get();
 | 
			
		||||
        final height: Float = inputs[8].get();
 | 
			
		||||
        final sx: Float = inputs[9].get();
 | 
			
		||||
        final sy: Float = inputs[10].get();
 | 
			
		||||
        final swidth: Float = inputs[11].get();
 | 
			
		||||
        final sheight: Float = inputs[12].get();
 | 
			
		||||
        final angle: Float = inputs[13].get();
 | 
			
		||||
 | 
			
		||||
        final drawx = x - 0.5 * width * anchorH;
 | 
			
		||||
        final drawy = y - 0.5 * height * anchorV;
 | 
			
		||||
        final sdrawx = sx - 0.5 * swidth * anchorH;
 | 
			
		||||
        final sdrawy = sy - 0.5 * sheight * anchorV;
 | 
			
		||||
 | 
			
		||||
        RenderToTexture.g.rotate(angle, x, y);
 | 
			
		||||
 | 
			
		||||
        if (imgName != lastImgName) {
 | 
			
		||||
            // Load new image
 | 
			
		||||
            lastImgName = imgName;
 | 
			
		||||
            iron.data.Data.getImage(imgName, (image: Image) -> {
 | 
			
		||||
                img = image;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (img == null) {
 | 
			
		||||
            runOutput(0);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w);
 | 
			
		||||
        RenderToTexture.g.drawScaledSubImage(img, sdrawx, sdrawy, swidth, sheight, drawx, drawy, width, height);
 | 
			
		||||
        RenderToTexture.g.rotate(-angle, x, y);
 | 
			
		||||
 | 
			
		||||
        runOutput(0);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								leenkx/Sources/leenkx/logicnode/GetParticleDataNode.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								leenkx/Sources/leenkx/logicnode/GetParticleDataNode.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.object.Object;
 | 
			
		||||
 | 
			
		||||
class GetParticleDataNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
	public function new(tree: LogicTree) {
 | 
			
		||||
		super(tree);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override function get(from: Int): Dynamic {
 | 
			
		||||
		var object: Object = inputs[0].get();
 | 
			
		||||
		var slot: Int = inputs[1].get();
 | 
			
		||||
 | 
			
		||||
		if (object == null) return null;
 | 
			
		||||
 | 
			
		||||
	#if lnx_particles
 | 
			
		||||
 | 
			
		||||
		var mo = cast(object, iron.object.MeshObject);
 | 
			
		||||
 | 
			
		||||
		var psys = mo.particleSystems != null ? mo.particleSystems[slot] : 
 | 
			
		||||
			mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null;
 | 
			
		||||
 | 
			
		||||
		if (psys == null) return null;
 | 
			
		||||
 | 
			
		||||
		return switch (from) {
 | 
			
		||||
			case 0:
 | 
			
		||||
				psys.r.name;
 | 
			
		||||
			case 1:
 | 
			
		||||
				psys.r.particle_size;
 | 
			
		||||
			case 2:
 | 
			
		||||
				psys.r.frame_start;
 | 
			
		||||
			case 3:
 | 
			
		||||
				psys.r.frame_end;
 | 
			
		||||
			case 4:
 | 
			
		||||
				psys.lifetime;
 | 
			
		||||
			case 5:
 | 
			
		||||
				psys.r.lifetime;
 | 
			
		||||
			case 6:
 | 
			
		||||
				psys.r.emit_from;
 | 
			
		||||
			case 7:
 | 
			
		||||
				new iron.math.Vec3(psys.alignx*2, psys.aligny*2, psys.alignz*2);
 | 
			
		||||
			case 8:
 | 
			
		||||
				psys.r.factor_random;
 | 
			
		||||
			case 9:
 | 
			
		||||
				new iron.math.Vec3(psys.gx, psys.gy, psys.gz);
 | 
			
		||||
			case 10:
 | 
			
		||||
				psys.r.weight_gravity;
 | 
			
		||||
			case 11:
 | 
			
		||||
				psys.speed;
 | 
			
		||||
			case 12:
 | 
			
		||||
				psys.time;
 | 
			
		||||
			case 13:
 | 
			
		||||
				psys.lap;
 | 
			
		||||
			case 14:
 | 
			
		||||
				psys.lapTime;
 | 
			
		||||
			case 15:
 | 
			
		||||
				psys.count;
 | 
			
		||||
			default: 
 | 
			
		||||
				null;
 | 
			
		||||
		}
 | 
			
		||||
	#end
 | 
			
		||||
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								leenkx/Sources/leenkx/logicnode/GetParticleNode.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								leenkx/Sources/leenkx/logicnode/GetParticleNode.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.object.Object;
 | 
			
		||||
 | 
			
		||||
class GetParticleNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
	public function new(tree: LogicTree) {
 | 
			
		||||
		super(tree);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override function get(from: Int): Dynamic {
 | 
			
		||||
		var object: Object = inputs[0].get();
 | 
			
		||||
 | 
			
		||||
		if (object == null) return null;
 | 
			
		||||
 | 
			
		||||
	#if lnx_particles
 | 
			
		||||
 | 
			
		||||
		var mo = cast(object, iron.object.MeshObject);
 | 
			
		||||
	
 | 
			
		||||
		switch (from) {
 | 
			
		||||
			case 0:
 | 
			
		||||
				var names: Array<String> = [];
 | 
			
		||||
				if (mo.particleSystems != null)
 | 
			
		||||
					for (psys in mo.particleSystems)
 | 
			
		||||
						names.push(psys.r.name);
 | 
			
		||||
				return names;
 | 
			
		||||
			case 1:
 | 
			
		||||
				return mo.particleSystems != null ? mo.particleSystems.length : 0;
 | 
			
		||||
			case 2:
 | 
			
		||||
				return mo.render_emitter;
 | 
			
		||||
			default: 
 | 
			
		||||
				null;
 | 
			
		||||
		}
 | 
			
		||||
	#end
 | 
			
		||||
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -18,7 +18,6 @@ class ProbabilisticOutputNode extends LogicNode {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (sum > 1){
 | 
			
		||||
			trace(sum);
 | 
			
		||||
			for (p in 0...probs.length)
 | 
			
		||||
				probs[p] /= sum;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,64 @@
 | 
			
		||||
package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.object.Object;
 | 
			
		||||
 | 
			
		||||
class RemoveParticleFromObjectNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
	public var property0: String;
 | 
			
		||||
 | 
			
		||||
	public function new(tree: LogicTree) {
 | 
			
		||||
		super(tree);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override function run(from: Int) {
 | 
			
		||||
		#if lnx_particles
 | 
			
		||||
		var object: Object = inputs[1].get();
 | 
			
		||||
 | 
			
		||||
		if (object == null) return;
 | 
			
		||||
 | 
			
		||||
		var mo = cast(object, iron.object.MeshObject);
 | 
			
		||||
 | 
			
		||||
		if (mo.particleSystems == null) return;
 | 
			
		||||
 | 
			
		||||
		if (property0 == 'All'){
 | 
			
		||||
			mo.particleSystems = null;
 | 
			
		||||
			for (c in mo.particleChildren) c.remove();
 | 
			
		||||
			mo.particleChildren = null;
 | 
			
		||||
			mo.particleOwner = null;
 | 
			
		||||
			mo.render_emitter = true;
 | 
			
		||||
		} 
 | 
			
		||||
		else {
 | 
			
		||||
			
 | 
			
		||||
			var slot: Int = -1;
 | 
			
		||||
			if (property0 == 'Name'){
 | 
			
		||||
				var name: String = inputs[2].get();
 | 
			
		||||
				for (i => psys in mo.particleSystems){
 | 
			
		||||
					if (psys.r.name == name){ slot = i; break; }
 | 
			
		||||
				}
 | 
			
		||||
			} 
 | 
			
		||||
			else slot = inputs[2].get();
 | 
			
		||||
				
 | 
			
		||||
			if (mo.particleSystems.length > slot){
 | 
			
		||||
				for (i in slot+1...mo.particleSystems.length){
 | 
			
		||||
					var mi = cast(mo.particleChildren[i], iron.object.MeshObject);
 | 
			
		||||
					mi.particleIndex = mi.particleIndex - 1;
 | 
			
		||||
				}
 | 
			
		||||
				mo.particleSystems.splice(slot, 1);
 | 
			
		||||
				mo.particleChildren[slot].remove();
 | 
			
		||||
				mo.particleChildren.splice(slot, 1);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (slot == 0){
 | 
			
		||||
				mo.particleSystems = null;
 | 
			
		||||
				mo.particleChildren = null;
 | 
			
		||||
				mo.particleOwner = null;
 | 
			
		||||
				mo.render_emitter = true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		#end
 | 
			
		||||
 | 
			
		||||
		runOutput(0);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								leenkx/Sources/leenkx/logicnode/SetParticleDataNode.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								leenkx/Sources/leenkx/logicnode/SetParticleDataNode.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.object.Object;
 | 
			
		||||
 | 
			
		||||
class SetParticleDataNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
	public var property0: String;
 | 
			
		||||
 | 
			
		||||
	public function new(tree: LogicTree) {
 | 
			
		||||
		super(tree);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override function run(from: Int) {
 | 
			
		||||
		#if lnx_particles
 | 
			
		||||
		var object: Object = inputs[1].get();
 | 
			
		||||
		var slot: Int = inputs[2].get();
 | 
			
		||||
 | 
			
		||||
		if (object == null) return;
 | 
			
		||||
 | 
			
		||||
		var mo = cast(object, iron.object.MeshObject);
 | 
			
		||||
 | 
			
		||||
		var psys = mo.particleSystems != null ? mo.particleSystems[slot] : 
 | 
			
		||||
			mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null;		if (psys == null) return;
 | 
			
		||||
 | 
			
		||||
		switch (property0) {
 | 
			
		||||
			case 'Particle Size':
 | 
			
		||||
				psys.r.particle_size = inputs[3].get();
 | 
			
		||||
			case 'Frame Start':
 | 
			
		||||
				psys.r.frame_start = inputs[3].get();
 | 
			
		||||
				psys.animtime = (psys.r.frame_end - psys.r.frame_start) / psys.frameRate;
 | 
			
		||||
				psys.spawnRate = ((psys.r.frame_end - psys.r.frame_start) / psys.count) / psys.frameRate;
 | 
			
		||||
			case 'Frame End':
 | 
			
		||||
				psys.r.frame_end = inputs[3].get();
 | 
			
		||||
				psys.animtime = (psys.r.frame_end - psys.r.frame_start) / psys.frameRate;
 | 
			
		||||
				psys.spawnRate = ((psys.r.frame_end - psys.r.frame_start) / psys.count) / psys.frameRate;
 | 
			
		||||
			case 'Lifetime':
 | 
			
		||||
				psys.lifetime = inputs[3].get() / psys.frameRate;
 | 
			
		||||
			case 'Lifetime Random':
 | 
			
		||||
				psys.r.lifetime_random = inputs[3].get();
 | 
			
		||||
			case 'Emit From':
 | 
			
		||||
				var emit_from: Int = inputs[3].get();
 | 
			
		||||
				if (emit_from == 0 || emit_from == 1 || emit_from == 2) {
 | 
			
		||||
					psys.r.emit_from = emit_from;
 | 
			
		||||
					psys.setupGeomGpu(mo.particleChildren != null ? mo.particleChildren[slot] : cast(iron.Scene.active.getChild(psys.data.raw.instance_object), iron.object.MeshObject), mo);
 | 
			
		||||
				}
 | 
			
		||||
			case 'Velocity':
 | 
			
		||||
				var vel: iron.math.Vec3 = inputs[3].get();
 | 
			
		||||
				psys.alignx = vel.x / 2;
 | 
			
		||||
				psys.aligny = vel.y / 2;
 | 
			
		||||
				psys.alignz = vel.z / 2;
 | 
			
		||||
			case 'Velocity Random':
 | 
			
		||||
				psys.r.factor_random = inputs[3].get();
 | 
			
		||||
			case 'Weight Gravity':
 | 
			
		||||
				psys.r.weight_gravity = inputs[3].get();
 | 
			
		||||
				if (iron.Scene.active.raw.gravity != null) {
 | 
			
		||||
					psys.gx = iron.Scene.active.raw.gravity[0] * psys.r.weight_gravity;
 | 
			
		||||
					psys.gy = iron.Scene.active.raw.gravity[1] * psys.r.weight_gravity;
 | 
			
		||||
					psys.gz = iron.Scene.active.raw.gravity[2] * psys.r.weight_gravity;
 | 
			
		||||
				}
 | 
			
		||||
				else {
 | 
			
		||||
					psys.gx = 0;
 | 
			
		||||
					psys.gy = 0;
 | 
			
		||||
					psys.gz = -9.81 * psys.r.weight_gravity;
 | 
			
		||||
				}
 | 
			
		||||
			case 'Speed':
 | 
			
		||||
				psys.speed = inputs[3].get();
 | 
			
		||||
			default: 
 | 
			
		||||
				null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		#end
 | 
			
		||||
 | 
			
		||||
		runOutput(0);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.object.Object;
 | 
			
		||||
 | 
			
		||||
class SetParticleRenderEmitterNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
	public function new(tree: LogicTree) {
 | 
			
		||||
		super(tree);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override function run(from: Int) {
 | 
			
		||||
		#if lnx_particles
 | 
			
		||||
		var object: Object = inputs[1].get();
 | 
			
		||||
 | 
			
		||||
		if (object == null) return;
 | 
			
		||||
 | 
			
		||||
		cast(object, iron.object.MeshObject).render_emitter = inputs[2].get();
 | 
			
		||||
 | 
			
		||||
		#end
 | 
			
		||||
 | 
			
		||||
		runOutput(0);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -11,13 +11,16 @@ class SetParticleSpeedNode extends LogicNode {
 | 
			
		||||
	override function run(from: Int) {
 | 
			
		||||
		#if lnx_particles
 | 
			
		||||
		var object: Object = inputs[1].get();
 | 
			
		||||
		var speed: Float = inputs[2].get();
 | 
			
		||||
		var slot: Int = inputs[2].get();
 | 
			
		||||
		var speed: Float = inputs[3].get();
 | 
			
		||||
 | 
			
		||||
		if (object == null) return;
 | 
			
		||||
 | 
			
		||||
		var mo = cast(object, iron.object.MeshObject);
 | 
			
		||||
		var psys = mo.particleSystems.length > 0 ? mo.particleSystems[0] : null;
 | 
			
		||||
		if (psys == null) mo.particleOwner.particleSystems[0];
 | 
			
		||||
		var psys = mo.particleSystems != null ? mo.particleSystems[slot] : 
 | 
			
		||||
			mo.particleOwner != null && mo.particleOwner.particleSystems != null ? mo.particleOwner.particleSystems[slot] : null;
 | 
			
		||||
 | 
			
		||||
		if (psys == null) return;
 | 
			
		||||
 | 
			
		||||
		psys.speed = speed;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										105
									
								
								leenkx/Sources/leenkx/logicnode/WriteImageNode.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								leenkx/Sources/leenkx/logicnode/WriteImageNode.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
			
		||||
package leenkx.logicnode;
 | 
			
		||||
 | 
			
		||||
import iron.object.CameraObject;
 | 
			
		||||
 | 
			
		||||
class WriteImageNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
    var file: String;
 | 
			
		||||
    var camera: CameraObject;
 | 
			
		||||
    var renderTarget: kha.Image;
 | 
			
		||||
 | 
			
		||||
    public function new(tree: LogicTree) {
 | 
			
		||||
        super(tree);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override function run(from: Int) {
 | 
			
		||||
        // Relative or absolute path to file
 | 
			
		||||
        file = inputs[1].get();
 | 
			
		||||
 | 
			
		||||
        assert(Error, iron.App.w() % inputs[3].get() == 0 && iron.App.h() % inputs[4].get() == 0, "Aspect ratio must match display resolution ratio");
 | 
			
		||||
 | 
			
		||||
        camera = inputs[2].get();
 | 
			
		||||
        renderTarget = kha.Image.createRenderTarget(inputs[3].get(), inputs[4].get(),
 | 
			
		||||
            kha.graphics4.TextureFormat.RGBA32,
 | 
			
		||||
            kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
 | 
			
		||||
 | 
			
		||||
        tree.notifyOnRender(render);
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function render(g: kha.graphics4.Graphics) {
 | 
			
		||||
 | 
			
		||||
        var ready = false;
 | 
			
		||||
        final sceneCam = iron.Scene.active.camera;
 | 
			
		||||
        final oldRT = camera.renderTarget;
 | 
			
		||||
 | 
			
		||||
        iron.Scene.active.camera = camera;
 | 
			
		||||
        camera.renderTarget = renderTarget;
 | 
			
		||||
 | 
			
		||||
        camera.renderFrame(g);
 | 
			
		||||
 | 
			
		||||
        var tex = camera.renderTarget;
 | 
			
		||||
 | 
			
		||||
        camera.renderTarget = oldRT;
 | 
			
		||||
        iron.Scene.active.camera = sceneCam;
 | 
			
		||||
 | 
			
		||||
        var pixels = tex.getPixels();
 | 
			
		||||
 | 
			
		||||
        for (i in 0...pixels.length){
 | 
			
		||||
            if (pixels.get(i) != 0){ ready = true; break; }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //wait for getPixels ready
 | 
			
		||||
        if (ready) { 
 | 
			
		||||
 | 
			
		||||
            var tx = inputs[5].get();
 | 
			
		||||
            var ty = inputs[6].get();
 | 
			
		||||
            var tw = inputs[7].get();
 | 
			
		||||
            var th = inputs[8].get();
 | 
			
		||||
 | 
			
		||||
            var bo = new haxe.io.BytesOutput();
 | 
			
		||||
            var rgb = haxe.io.Bytes.alloc(tw * th * 4);
 | 
			
		||||
            for (j in ty...ty + th) {
 | 
			
		||||
                for (i in tx...tx + tw) {
 | 
			
		||||
                    var k = j * tex.width + i;
 | 
			
		||||
                    var m =  (j - ty) * tw + i - tx;
 | 
			
		||||
                    
 | 
			
		||||
                    #if kha_krom
 | 
			
		||||
                    var l = k;
 | 
			
		||||
                    #elseif kha_html5
 | 
			
		||||
                    var l = (tex.height - j) * tex.width + i;
 | 
			
		||||
                    #end
 | 
			
		||||
 | 
			
		||||
                    //ARGB 0xff
 | 
			
		||||
                    rgb.set(m * 4 + 0, pixels.get(l * 4 + 3));
 | 
			
		||||
                    rgb.set(m * 4 + 1, pixels.get(l * 4 + 0)); 
 | 
			
		||||
                    rgb.set(m * 4 + 2, pixels.get(l * 4 + 1));
 | 
			
		||||
                    rgb.set(m * 4 + 3, pixels.get(l * 4 + 2));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var imgwriter = new iron.format.bmp.Writer(bo);
 | 
			
		||||
            imgwriter.write(iron.format.bmp.Tools.buildFromARGB(tw, th, rgb));
 | 
			
		||||
 | 
			
		||||
            #if kha_krom
 | 
			
		||||
            Krom.fileSaveBytes(Krom.getFilesLocation() +  "/" + file, bo.getBytes().getData());
 | 
			
		||||
    
 | 
			
		||||
            #elseif kha_html5
 | 
			
		||||
            var blob = new js.html.Blob([bo.getBytes().getData()], {type: "application"});
 | 
			
		||||
            var url = js.html.URL.createObjectURL(blob);
 | 
			
		||||
            var a = cast(js.Browser.document.createElement("a"), js.html.AnchorElement);
 | 
			
		||||
            a.href = url;
 | 
			
		||||
            a.download = file;
 | 
			
		||||
            a.click();
 | 
			
		||||
            js.html.URL.revokeObjectURL(url);
 | 
			
		||||
            #end
 | 
			
		||||
 | 
			
		||||
            runOutput(0);
 | 
			
		||||
 | 
			
		||||
            tree.removeRender(render);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -191,7 +191,7 @@ def apply_materials(load_atlas=0):
 | 
			
		||||
 | 
			
		||||
                                mainNode = outputNode.inputs[0].links[0].from_node.inputs[0].links[0].from_node
 | 
			
		||||
 | 
			
		||||
                            if (mainNode.type == "ShaderNodeMixRGB"):
 | 
			
		||||
                            if (mainNode.type == "ShaderNodeMix"):
 | 
			
		||||
                                if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
 | 
			
		||||
                                    print("Mix RGB shader found")
 | 
			
		||||
                                
 | 
			
		||||
@ -199,9 +199,11 @@ def apply_materials(load_atlas=0):
 | 
			
		||||
 | 
			
		||||
                            #Add all nodes first
 | 
			
		||||
                            #Add lightmap multipliction texture
 | 
			
		||||
                            mixNode = node_tree.nodes.new(type="ShaderNodeMixRGB")
 | 
			
		||||
                            mixNode = node_tree.nodes.new(type="ShaderNodeMix")
 | 
			
		||||
                            mixNode.name = "Lightmap_Multiplication"
 | 
			
		||||
                            mixNode.location = -800, 300
 | 
			
		||||
                            mixNode.data_type = 'RGBA'
 | 
			
		||||
                            mixNode.inputs[0].default_value = 1
 | 
			
		||||
                            if scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO":
 | 
			
		||||
                                mixNode.blend_type = 'MULTIPLY'
 | 
			
		||||
                            else:
 | 
			
		||||
@ -312,8 +314,8 @@ def apply_materials(load_atlas=0):
 | 
			
		||||
                                    else:
 | 
			
		||||
                                        mat.node_tree.links.new(lightmapNode.outputs[1], DecodeNode.inputs[1]) #Connect lightmap node to decodenode
 | 
			
		||||
 | 
			
		||||
                                    mat.node_tree.links.new(DecodeNode.outputs[0], mixNode.inputs[1]) #Connect decode node to mixnode
 | 
			
		||||
                                    mat.node_tree.links.new(ExposureNode.outputs[0], mixNode.inputs[1]) #Connect exposure node to mixnode
 | 
			
		||||
                                    mat.node_tree.links.new(DecodeNode.outputs[0], mixNode.inputs[6]) #Connect decode node to mixnode
 | 
			
		||||
                                    mat.node_tree.links.new(ExposureNode.outputs[0], mixNode.inputs[6]) #Connect exposure node to mixnode
 | 
			
		||||
                                
 | 
			
		||||
                                else:
 | 
			
		||||
                                    
 | 
			
		||||
@ -323,10 +325,10 @@ def apply_materials(load_atlas=0):
 | 
			
		||||
                                    else:
 | 
			
		||||
                                        mat.node_tree.links.new(lightmapNode.outputs[1], DecodeNode.inputs[1]) #Connect lightmap node to decodenode
 | 
			
		||||
 | 
			
		||||
                                    mat.node_tree.links.new(DecodeNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
 | 
			
		||||
                                    mat.node_tree.links.new(DecodeNode.outputs[0], mixNode.inputs[6]) #Connect lightmap node to mixnode
 | 
			
		||||
                                
 | 
			
		||||
                                mat.node_tree.links.new(baseColorNode.outputs[0], mixNode.inputs[2]) #Connect basecolor to pbr node
 | 
			
		||||
                                mat.node_tree.links.new(mixNode.outputs[0], mainNode.inputs[0]) #Connect mixnode to pbr node
 | 
			
		||||
                                mat.node_tree.links.new(baseColorNode.outputs[0], mixNode.inputs[7]) #Connect basecolor to pbr node
 | 
			
		||||
                                mat.node_tree.links.new(mixNode.outputs[2], mainNode.inputs[0]) #Connect mixnode to pbr node
 | 
			
		||||
 | 
			
		||||
                                if not scene.TLM_EngineProperties.tlm_target == "vertex":
 | 
			
		||||
                                    mat.node_tree.links.new(UVLightmap.outputs[0], lightmapNode.inputs[0]) #Connect uvnode to lightmapnode
 | 
			
		||||
@ -338,11 +340,11 @@ def apply_materials(load_atlas=0):
 | 
			
		||||
 | 
			
		||||
                                if(scene.TLM_EngineProperties.tlm_exposure_multiplier > 0):
 | 
			
		||||
                                    mat.node_tree.links.new(lightmapNode.outputs[0], ExposureNode.inputs[0]) #Connect lightmap node to mixnode
 | 
			
		||||
                                    mat.node_tree.links.new(ExposureNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
 | 
			
		||||
                                    mat.node_tree.links.new(ExposureNode.outputs[0], mixNode.inputs[6]) #Connect lightmap node to mixnode
 | 
			
		||||
                                else:
 | 
			
		||||
                                    mat.node_tree.links.new(lightmapNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
 | 
			
		||||
                                mat.node_tree.links.new(baseColorNode.outputs[0], mixNode.inputs[2]) #Connect basecolor to pbr node
 | 
			
		||||
                                mat.node_tree.links.new(mixNode.outputs[0], mainNode.inputs[0]) #Connect mixnode to pbr node
 | 
			
		||||
                                    mat.node_tree.links.new(lightmapNode.outputs[0], mixNode.inputs[6]) #Connect lightmap node to mixnode
 | 
			
		||||
                                mat.node_tree.links.new(baseColorNode.outputs[0], mixNode.inputs[7]) #Connect basecolor to pbr node
 | 
			
		||||
                                mat.node_tree.links.new(mixNode.outputs[0], mainNode.inputs[2]) #Connect mixnode to pbr node
 | 
			
		||||
                                if not scene.TLM_EngineProperties.tlm_target == "vertex":
 | 
			
		||||
                                    mat.node_tree.links.new(UVLightmap.outputs[0], lightmapNode.inputs[0]) #Connect uvnode to lightmapnode
 | 
			
		||||
 | 
			
		||||
@ -491,8 +493,9 @@ def applyAOPass():
 | 
			
		||||
                                AOMap.image = AOImage
 | 
			
		||||
                                AOMap.location = -800, 0
 | 
			
		||||
 | 
			
		||||
                                AOMult = nodes.new(type="ShaderNodeMixRGB")
 | 
			
		||||
                                AOMult = nodes.new(type="ShaderNodeMix")
 | 
			
		||||
                                AOMult.name = "TLM_AOMult"
 | 
			
		||||
                                AOMult.data_type = 'RGBA'
 | 
			
		||||
                                AOMult.blend_type = 'MULTIPLY'
 | 
			
		||||
                                AOMult.inputs[0].default_value = 1.0
 | 
			
		||||
                                AOMult.location = -300, 300
 | 
			
		||||
 | 
			
		||||
@ -518,7 +518,7 @@ def configure_meshes(self):
 | 
			
		||||
                                if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
 | 
			
		||||
                                    print("The material group is not supported!")
 | 
			
		||||
 | 
			
		||||
                        if (mainNode.type == "ShaderNodeMixRGB"):
 | 
			
		||||
                        if (mainNode.type == "ShaderNodeMix"):
 | 
			
		||||
                            if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
 | 
			
		||||
                                print("Mix shader found")
 | 
			
		||||
 | 
			
		||||
@ -811,7 +811,7 @@ def set_settings():
 | 
			
		||||
    
 | 
			
		||||
    print(bpy.app.version)
 | 
			
		||||
 | 
			
		||||
    if bpy.app.version[0] == 3:
 | 
			
		||||
    if bpy.app.version[0] == 3 or byp.app.version[0] == 4:
 | 
			
		||||
        if cycles.device == "GPU":
 | 
			
		||||
            scene.cycles.tile_size = 256
 | 
			
		||||
        else:
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,11 @@ class ViewportDraw:
 | 
			
		||||
        w = 400
 | 
			
		||||
        h = 200
 | 
			
		||||
 | 
			
		||||
        self.shader = gpu.shader.from_builtin('2D_IMAGE')
 | 
			
		||||
        if bpy.app.version[0] == 3:
 | 
			
		||||
            self.shader = gpu.shader.from_builtin('2D_IMAGE')
 | 
			
		||||
        else:
 | 
			
		||||
            self.shader = gpu.shader.from_builtin('IMAGE')
 | 
			
		||||
            
 | 
			
		||||
        self.batch = batch_for_shader(
 | 
			
		||||
            self.shader, 'TRI_FAN',
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ class Shader_Node_Types:
 | 
			
		||||
    normal = "ShaderNodeNormalMap"
 | 
			
		||||
    ao = "ShaderNodeAmbientOcclusion"
 | 
			
		||||
    uv = "ShaderNodeUVMap"
 | 
			
		||||
    mix = "ShaderNodeMixRGB"
 | 
			
		||||
    mix = "ShaderNodeMix"
 | 
			
		||||
 | 
			
		||||
def select_object(self,obj):
 | 
			
		||||
    C = bpy.context
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,7 @@ def init_categories():
 | 
			
		||||
    lnx_nodes.add_category('Navmesh', icon='UV_VERTEXSEL', section="motion")
 | 
			
		||||
    lnx_nodes.add_category('Transform', icon='TRANSFORM_ORIGINS', section="motion")
 | 
			
		||||
    lnx_nodes.add_category('Physics', icon='PHYSICS', section="motion")
 | 
			
		||||
    lnx_nodes.add_category('Particle', icon='PARTICLE_DATA', section="motion")
 | 
			
		||||
    
 | 
			
		||||
    lnx_nodes.add_category('Array', icon='MOD_ARRAY', section="values")
 | 
			
		||||
    lnx_nodes.add_category('Map', icon='SHORTDISPLAY', section="values")
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ class DrawCameraTextureNode(LnxLogicTreeNode):
 | 
			
		||||
    @input Object: Object of which to choose the material in the `Material Slot` input.
 | 
			
		||||
    @input Material Slot: Index of the material slot of which the diffuse
 | 
			
		||||
        texture is replaced with the camera's render target.
 | 
			
		||||
    @input Node: Node name of the Image Texture Node.
 | 
			
		||||
    
 | 
			
		||||
    @output On Start: Activated after the `Start` input has been activated.
 | 
			
		||||
    @output On Stop: Activated after the `Stop` input has been activated.
 | 
			
		||||
@ -18,7 +19,7 @@ class DrawCameraTextureNode(LnxLogicTreeNode):
 | 
			
		||||
    bl_idname = 'LNDrawCameraTextureNode'
 | 
			
		||||
    bl_label = 'Draw Camera to Texture'
 | 
			
		||||
    lnx_section = 'draw'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
    lnx_version = 2
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketAction', 'Start')
 | 
			
		||||
@ -26,6 +27,13 @@ class DrawCameraTextureNode(LnxLogicTreeNode):
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'Camera')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'Object')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'Material Slot')
 | 
			
		||||
        self.add_input('LnxStringSocket', 'Node')
 | 
			
		||||
        
 | 
			
		||||
        self.add_output('LnxNodeSocketAction', 'On Start')
 | 
			
		||||
        self.add_output('LnxNodeSocketAction', 'On Stop')
 | 
			
		||||
 | 
			
		||||
    def get_replacement_node(self, node_tree: bpy.types.NodeTree):
 | 
			
		||||
        if self.lnx_version not in (0, 1):
 | 
			
		||||
            raise LookupError()
 | 
			
		||||
 | 
			
		||||
        return NodeReplacement.Identity(self)
 | 
			
		||||
							
								
								
									
										44
									
								
								leenkx/blender/lnx/logicnode/draw/LN_draw_sub_image.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								leenkx/blender/lnx/logicnode/draw/LN_draw_sub_image.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DrawSubImageNode(LnxLogicTreeNode):
 | 
			
		||||
    """Draws an image.
 | 
			
		||||
    @input Draw: Activate to draw the image on this frame. The input must
 | 
			
		||||
        be (indirectly) called from an `On Render2D` node.
 | 
			
		||||
    @input Image: The filename of the image.
 | 
			
		||||
    @input Color: The color that the image's pixels are multiplied with.
 | 
			
		||||
    @input Left/Center/Right: Horizontal anchor point of the image.
 | 
			
		||||
        0 = Left, 1 = Center, 2 = Right
 | 
			
		||||
    @input Top/Middle/Bottom: Vertical anchor point of the image.
 | 
			
		||||
        0 = Top, 1 = Middle, 2 = Bottom
 | 
			
		||||
    @input X/Y: Position of the anchor point in pixels.
 | 
			
		||||
    @input Width/Height: Size of the sub image in pixels.
 | 
			
		||||
    @input sX/Y: Position of the sub anchor point in pixels.
 | 
			
		||||
    @input sWidth/Height: Size of the image in pixels.
 | 
			
		||||
    @input Angle: Rotation angle in radians. Image will be rotated cloclwiswe
 | 
			
		||||
        at the anchor point.
 | 
			
		||||
    @output Out: Activated after the image has been drawn.
 | 
			
		||||
    @see [`kha.graphics2.Graphics.drawImage()`](http://kha.tech/api/kha/graphics2/Graphics.html#drawImage).
 | 
			
		||||
    """
 | 
			
		||||
    bl_idname = 'LNDrawSubImageNode'
 | 
			
		||||
    bl_label = 'Draw Sub Image'
 | 
			
		||||
    lnx_section = 'draw'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketAction', 'Draw')
 | 
			
		||||
        self.add_input('LnxStringSocket', 'Image File')
 | 
			
		||||
        self.add_input('LnxColorSocket', 'Color', default_value=[1.0, 1.0, 1.0, 1.0])
 | 
			
		||||
        self.add_input('LnxIntSocket', '0/1/2 = Left/Center/Right', default_value=0)
 | 
			
		||||
        self.add_input('LnxIntSocket', '0/1/2 = Top/Middle/Bottom', default_value=0)
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'X')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'Y')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'Width')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'Height')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'sX')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'sY')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'sWidth')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'sHeight')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'Angle')
 | 
			
		||||
 | 
			
		||||
        self.add_output('LnxNodeSocketAction', 'Out') 
 | 
			
		||||
@ -5,8 +5,6 @@ class WriteFileNode(LnxLogicTreeNode):
 | 
			
		||||
    """Writes the given string content to the given file. If the file
 | 
			
		||||
    already exists, the existing content of the file is overwritten.
 | 
			
		||||
 | 
			
		||||
    > **This node is currently only implemented on Krom**
 | 
			
		||||
 | 
			
		||||
    @input File: the name of the file, relative to `Krom.getFilesLocation()`
 | 
			
		||||
    @input Content: the content to write to the file.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								leenkx/blender/lnx/logicnode/native/LN_write_image.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								leenkx/blender/lnx/logicnode/native/LN_write_image.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WriteImageNode(LnxLogicTreeNode):
 | 
			
		||||
    """Writes the given image to the given file. If the image
 | 
			
		||||
    already exists, the existing content of the image is overwritten.
 | 
			
		||||
    Aspect ratio must match display resolution ratio.
 | 
			
		||||
    @input Image File: the name of the image, relative to `Krom.getFilesLocation()`
 | 
			
		||||
    @input Camera: the render target image of the camera to write to the image file.
 | 
			
		||||
    @input Width: width of the image file.
 | 
			
		||||
    @input Height: heigth of the image file.
 | 
			
		||||
    @input sX: sub position of first x pixel of the sub image (0 for start).
 | 
			
		||||
    @input sY: sub position of first y pixel of the sub image (0 for start).
 | 
			
		||||
    @input sWidth: width of the sub image.
 | 
			
		||||
    @input sHeight: height of the sub image.
 | 
			
		||||
    @seeNode Read File
 | 
			
		||||
    """
 | 
			
		||||
    bl_idname = 'LNWriteImageNode'
 | 
			
		||||
    bl_label = 'Write Image'
 | 
			
		||||
    lnx_section = 'file'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketAction', 'In')
 | 
			
		||||
        self.add_input('LnxStringSocket', 'Image File')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'Camera')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'Width')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'Height')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'sX')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'sY')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'sWidth')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'sHeight')
 | 
			
		||||
 | 
			
		||||
        self.add_output('LnxNodeSocketAction', 'Out')
 | 
			
		||||
@ -5,8 +5,6 @@ class WriteJsonNode(LnxLogicTreeNode):
 | 
			
		||||
    """Writes the given content to the given JSON file. If the file
 | 
			
		||||
    already exists, the existing content of the file is overwritten.
 | 
			
		||||
 | 
			
		||||
    > **This node is currently only implemented on Krom**
 | 
			
		||||
 | 
			
		||||
    @input File: the name of the file, relative to `Krom.getFilesLocation()`,
 | 
			
		||||
        including the file extension.
 | 
			
		||||
    @input Dynamic: the content to write to the file. Can be any type that can
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,41 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
class AddParticleToObjectNode(LnxLogicTreeNode):
 | 
			
		||||
    """Sets the speed of the given particle source."""
 | 
			
		||||
    bl_idname = 'LNAddParticleToObjectNode'
 | 
			
		||||
    bl_label = 'Add Particle To Object'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
 | 
			
		||||
    def remove_extra_inputs(self, context):
 | 
			
		||||
        while len(self.inputs) > 1:
 | 
			
		||||
            self.inputs.remove(self.inputs[-1])
 | 
			
		||||
        if self.property0 == 'Scene':
 | 
			
		||||
            self.add_input('LnxStringSocket', 'Scene From Name')
 | 
			
		||||
            self.add_input('LnxStringSocket', 'Object From Name')
 | 
			
		||||
        else:
 | 
			
		||||
            self.add_input('LnxNodeSocketObject', 'Object From')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'Slot')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'Object To')
 | 
			
		||||
        self.add_input('LnxBoolSocket', 'Render Emitter', default_value = True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    property0: HaxeEnumProperty(
 | 
			
		||||
    'property0',
 | 
			
		||||
    items = [('Scene Active', 'Scene Active', 'Scene Active'),
 | 
			
		||||
             ('Scene', 'Scene', 'Scene')],
 | 
			
		||||
    name='', default='Scene Active', update=remove_extra_inputs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketAction', 'In')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'Object From')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'Slot')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'Object To')
 | 
			
		||||
        self.add_input('LnxBoolSocket', 'Render Emitter', default_value = True)
 | 
			
		||||
 | 
			
		||||
        self.add_output('LnxNodeSocketAction', 'Out')
 | 
			
		||||
 | 
			
		||||
    def draw_buttons(self, context, layout):
 | 
			
		||||
        layout.prop(self, 'property0')
 | 
			
		||||
       
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								leenkx/blender/lnx/logicnode/particle/LN_get_particle.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								leenkx/blender/lnx/logicnode/particle/LN_get_particle.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
class GetParticleNode(LnxLogicTreeNode):
 | 
			
		||||
    """Returns the Particle Systems of an object."""
 | 
			
		||||
    bl_idname = 'LNGetParticleNode'
 | 
			
		||||
    bl_label = 'Get Particle'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.inputs.new('LnxNodeSocketObject', 'Object')
 | 
			
		||||
 | 
			
		||||
        self.outputs.new('LnxNodeSocketArray', 'Names')
 | 
			
		||||
        self.outputs.new('LnxIntSocket', 'Length')
 | 
			
		||||
        self.outputs.new('LnxBoolSocket', 'Render Emitter')
 | 
			
		||||
@ -0,0 +1,31 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
class GetParticleDataNode(LnxLogicTreeNode):
 | 
			
		||||
    """Returns the data of the given Particle System."""
 | 
			
		||||
    bl_idname = 'LNGetParticleDataNode'
 | 
			
		||||
    bl_label = 'Get Particle Data'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.inputs.new('LnxNodeSocketObject', 'Object')
 | 
			
		||||
        self.inputs.new('LnxIntSocket', 'Slot')
 | 
			
		||||
 | 
			
		||||
        self.outputs.new('LnxStringSocket', 'Name')
 | 
			
		||||
        self.outputs.new('LnxFloatSocket', 'Particle Size')
 | 
			
		||||
        self.outputs.new('LnxIntSocket', 'Frame Start')
 | 
			
		||||
        self.outputs.new('LnxIntSocket', 'Frame End')
 | 
			
		||||
        self.outputs.new('LnxIntSocket', 'Lifetime')
 | 
			
		||||
        self.outputs.new('LnxFloatSocket', 'Lifetime Random')
 | 
			
		||||
        self.outputs.new('LnxIntSocket', 'Emit From')
 | 
			
		||||
 | 
			
		||||
        self.outputs.new('LnxVectorSocket', 'Velocity')
 | 
			
		||||
        self.outputs.new('LnxFloatSocket', 'Velocity Random')
 | 
			
		||||
        self.outputs.new('LnxVectorSocket', 'Gravity')
 | 
			
		||||
        self.outputs.new('LnxFloatSocket', 'Weight Gravity')
 | 
			
		||||
 | 
			
		||||
        self.outputs.new('LnxFloatSocket', 'Speed')
 | 
			
		||||
    
 | 
			
		||||
        self.outputs.new('LnxFloatSocket', 'Time')
 | 
			
		||||
        self.outputs.new('LnxFloatSocket', 'Lap')
 | 
			
		||||
        self.outputs.new('LnxFloatSocket', 'Lap Time')
 | 
			
		||||
        self.outputs.new('LnxIntSocket', 'Count')
 | 
			
		||||
@ -0,0 +1,33 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
class RemoveParticleFromObjectNode(LnxLogicTreeNode):
 | 
			
		||||
    """Remove Particle From Object."""
 | 
			
		||||
    bl_idname = 'LNRemoveParticleFromObjectNode'
 | 
			
		||||
    bl_label = 'Remove Particle From Object'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
 | 
			
		||||
    def remove_extra_inputs(self, context):
 | 
			
		||||
        while len(self.inputs) > 2:
 | 
			
		||||
                self.inputs.remove(self.inputs[-1])
 | 
			
		||||
        if self.property0 == 'Slot':
 | 
			
		||||
            self.add_input('LnxIntSocket', 'Slot')
 | 
			
		||||
        if self.property0 == 'Name':
 | 
			
		||||
            self.add_input('LnxStringSocket', 'Name')
 | 
			
		||||
 | 
			
		||||
    property0: HaxeEnumProperty(
 | 
			
		||||
    'property0',
 | 
			
		||||
    items = [('Slot', 'Slot', 'Slot'),
 | 
			
		||||
             ('Name', 'Name', 'Name'),
 | 
			
		||||
             ('All', 'All', 'All')],
 | 
			
		||||
    name='', default='Slot', update=remove_extra_inputs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketAction', 'In')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'Object')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'Slot')
 | 
			
		||||
 | 
			
		||||
        self.add_output('LnxNodeSocketAction', 'Out')
 | 
			
		||||
 | 
			
		||||
    def draw_buttons(self, context, layout):
 | 
			
		||||
        layout.prop(self, 'property0')
 | 
			
		||||
@ -0,0 +1,58 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
class SetParticleDataNode(LnxLogicTreeNode):
 | 
			
		||||
    """Sets the parameters of the given particle system."""
 | 
			
		||||
    bl_idname = 'LNSetParticleDataNode'
 | 
			
		||||
    bl_label = 'Set Particle Data'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
 | 
			
		||||
    def remove_extra_inputs(self, context):
 | 
			
		||||
        while len(self.inputs) > 3:
 | 
			
		||||
                self.inputs.remove(self.inputs[-1])
 | 
			
		||||
        if self.property0 == 'Particle Size':
 | 
			
		||||
            self.add_input('LnxFloatSocket', 'Particle Size')
 | 
			
		||||
        if self.property0 == 'Frame End':
 | 
			
		||||
            self.add_input('LnxIntSocket', 'Frame End')
 | 
			
		||||
        if self.property0 == 'Frame Start':
 | 
			
		||||
            self.add_input('LnxIntSocket', 'Frame Start')
 | 
			
		||||
        if self.property0 == 'Lifetime': 
 | 
			
		||||
            self.add_input('LnxIntSocket', 'Lifetime')
 | 
			
		||||
        if self.property0 == 'Lifetime Random':
 | 
			
		||||
            self.add_input('LnxFloatSocket', 'Lifetime Random')
 | 
			
		||||
        if self.property0 == 'Emit From':
 | 
			
		||||
            self.add_input('LnxIntSocket', 'Emit From')
 | 
			
		||||
        if self.property0 == 'Velocity':
 | 
			
		||||
            self.add_input('LnxVectorSocket', 'Velocity')
 | 
			
		||||
        if self.property0 == 'Velocity Random':
 | 
			
		||||
            self.add_input('LnxFloatSocket', 'Velocity Random')
 | 
			
		||||
        if self.property0 == 'Weight Gravity':
 | 
			
		||||
            self.add_input('LnxFloatSocket', 'Weight Gravity')
 | 
			
		||||
        if self.property0 == 'Speed':
 | 
			
		||||
            self.add_input('LnxFloatSocket', 'Speed')
 | 
			
		||||
 | 
			
		||||
       
 | 
			
		||||
    property0: HaxeEnumProperty(
 | 
			
		||||
    'property0',
 | 
			
		||||
    items = [('Particle Size', 'Particle Size', 'for the system'),
 | 
			
		||||
             ('Frame Start', 'Frame Start', 'for the system'),
 | 
			
		||||
             ('Frame End', 'Frame End', 'for the system'),
 | 
			
		||||
             ('Lifetime', 'Lifetime', 'for the instance'),
 | 
			
		||||
             ('Lifetime Random', 'Lifetime Random', 'for the system'),
 | 
			
		||||
             ('Emit From', 'Emit From', 'for the system (Vertices:0 Faces:1 Volume: 2)'),
 | 
			
		||||
             ('Velocity', 'Velocity', 'for the instance'),
 | 
			
		||||
             ('Velocity Random', 'Velocity Random', 'for the system'),
 | 
			
		||||
             ('Weight Gravity', 'Weight Gravity', 'for the instance'),
 | 
			
		||||
             ('Speed', 'Speed', 'for the instance')],
 | 
			
		||||
    name='', default='Speed', update=remove_extra_inputs)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketAction', 'In')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'Object')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'Slot')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'Speed', default_value=1.0)
 | 
			
		||||
 | 
			
		||||
        self.add_output('LnxNodeSocketAction', 'Out')
 | 
			
		||||
        
 | 
			
		||||
    def draw_buttons(self, context, layout):
 | 
			
		||||
        layout.prop(self, 'property0')
 | 
			
		||||
@ -4,11 +4,19 @@ class SetParticleSpeedNode(LnxLogicTreeNode):
 | 
			
		||||
    """Sets the speed of the given particle source."""
 | 
			
		||||
    bl_idname = 'LNSetParticleSpeedNode'
 | 
			
		||||
    bl_label = 'Set Particle Speed'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
    lnx_version = 2
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketAction', 'In')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'Object')
 | 
			
		||||
        self.add_input('LnxIntSocket', 'Slot')
 | 
			
		||||
        self.add_input('LnxFloatSocket', 'Speed', default_value=1.0)
 | 
			
		||||
 | 
			
		||||
        self.add_output('LnxNodeSocketAction', 'Out')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def get_replacement_node(self, node_tree: bpy.types.NodeTree):
 | 
			
		||||
        if self.lnx_version not in (0, 1):
 | 
			
		||||
            raise LookupError()
 | 
			
		||||
            
 | 
			
		||||
        return NodeReplacement.Identity(self)
 | 
			
		||||
							
								
								
									
										3
									
								
								leenkx/blender/lnx/logicnode/particle/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								leenkx/blender/lnx/logicnode/particle/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import add_node_section
 | 
			
		||||
 | 
			
		||||
add_node_section(name='default', category='Particle')
 | 
			
		||||
@ -234,7 +234,7 @@ def build():
 | 
			
		||||
                wrd.compo_defs += '_CGrain'
 | 
			
		||||
            if rpdat.lnx_sharpen:
 | 
			
		||||
                wrd.compo_defs += '_CSharpen'
 | 
			
		||||
            if bpy.data.scenes[0].view_settings.exposure != 0.0:
 | 
			
		||||
            if bpy.utils.get_active_scene().view_settings.exposure != 0.0:
 | 
			
		||||
                wrd.compo_defs += '_CExposure'
 | 
			
		||||
            if rpdat.lnx_fog:
 | 
			
		||||
                wrd.compo_defs += '_CFog'
 | 
			
		||||
 | 
			
		||||
@ -298,6 +298,125 @@ float tex_brick_f(vec3 p) {
 | 
			
		||||
}
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#https://github.com/blender/blender/blob/main/source/blender/gpu/shaders/material/gpu_shader_material_tex_brick.glsl
 | 
			
		||||
str_tex_brick_blender = """
 | 
			
		||||
float integer_noise(int n)
 | 
			
		||||
{
 | 
			
		||||
  /* Integer bit-shifts for these calculations can cause precision problems on macOS.
 | 
			
		||||
   * Using uint resolves these issues. */
 | 
			
		||||
  uint nn;
 | 
			
		||||
  nn = (uint(n) + 1013u) & 0x7fffffffu;
 | 
			
		||||
  nn = (nn >> 13u) ^ nn;
 | 
			
		||||
  nn = (uint(nn * (nn * nn * 60493u + 19990303u)) + 1376312589u) & 0x7fffffffu;
 | 
			
		||||
  return 0.5f * (float(nn) / 1073741824.0f);
 | 
			
		||||
}
 | 
			
		||||
vec2 calc_brick_texture(vec3 p,
 | 
			
		||||
                          float mortar_size,
 | 
			
		||||
                          float mortar_smooth,
 | 
			
		||||
                          float bias,
 | 
			
		||||
                          float brick_width,
 | 
			
		||||
                          float row_height,
 | 
			
		||||
                          float offset_amount,
 | 
			
		||||
                          int offset_frequency,
 | 
			
		||||
                          float squash_amount,
 | 
			
		||||
                          int squash_frequency)
 | 
			
		||||
{
 | 
			
		||||
  int bricknum, rownum;
 | 
			
		||||
  float offset = 0.0f;
 | 
			
		||||
  float x, y;
 | 
			
		||||
  rownum = int(floor(p.y / row_height));
 | 
			
		||||
  if (offset_frequency != 0 && squash_frequency != 0) {
 | 
			
		||||
    brick_width *= (rownum % squash_frequency != 0) ? 1.0f : squash_amount;           /* squash */
 | 
			
		||||
    offset = (rownum % offset_frequency != 0) ? 0.0f : (brick_width * offset_amount); /* offset */
 | 
			
		||||
  }
 | 
			
		||||
  bricknum = int(floor((p.x + offset) / brick_width));
 | 
			
		||||
  x = (p.x + offset) - brick_width * bricknum;
 | 
			
		||||
  y = p.y - row_height * rownum;
 | 
			
		||||
  float tint = clamp((integer_noise((rownum << 16) + (bricknum & 0xFFFF)) + bias), 0.0f, 1.0f);
 | 
			
		||||
  float min_dist = min(min(x, y), min(brick_width - x, row_height - y));
 | 
			
		||||
  if (min_dist >= mortar_size) {
 | 
			
		||||
    return vec2(tint, 0.0f);
 | 
			
		||||
  }
 | 
			
		||||
  else if (mortar_smooth == 0.0f) {
 | 
			
		||||
    return vec2(tint, 1.0f);
 | 
			
		||||
  }
 | 
			
		||||
  else {
 | 
			
		||||
    min_dist = 1.0f - min_dist / mortar_size;
 | 
			
		||||
    return vec2(tint, smoothstep(0.0f, mortar_smooth, min_dist));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
vec3 tex_brick_blender(vec3 co,
 | 
			
		||||
                    vec3 color1,
 | 
			
		||||
                    vec3 color2,
 | 
			
		||||
                    vec3 mortar,
 | 
			
		||||
                    float scale,
 | 
			
		||||
                    float mortar_size,
 | 
			
		||||
                    float mortar_smooth,
 | 
			
		||||
                    float bias,
 | 
			
		||||
                    float brick_width,
 | 
			
		||||
                    float row_height,
 | 
			
		||||
                    float offset_amount,
 | 
			
		||||
                    float offset_frequency,
 | 
			
		||||
                    float squash_amount,
 | 
			
		||||
                    float squash_frequency)
 | 
			
		||||
{
 | 
			
		||||
  vec2 f2 = calc_brick_texture(co * scale,
 | 
			
		||||
                                 mortar_size,
 | 
			
		||||
                                 mortar_smooth,
 | 
			
		||||
                                 bias,
 | 
			
		||||
                                 brick_width,
 | 
			
		||||
                                 row_height,
 | 
			
		||||
                                 offset_amount,
 | 
			
		||||
                                 int(offset_frequency),
 | 
			
		||||
                                 squash_amount,
 | 
			
		||||
                                 int(squash_frequency));
 | 
			
		||||
  float tint = f2.x;
 | 
			
		||||
  float f = f2.y;
 | 
			
		||||
  if (f != 1.0f) {
 | 
			
		||||
    float facm = 1.0f - tint;
 | 
			
		||||
    color1 = facm * color1 + tint * color2;
 | 
			
		||||
  }
 | 
			
		||||
    return mix(color1, mortar, f);
 | 
			
		||||
}
 | 
			
		||||
float tex_brick_blender_f(vec3 co,
 | 
			
		||||
                    vec3 color1,
 | 
			
		||||
                    vec3 color2,
 | 
			
		||||
                    vec3 mortar,
 | 
			
		||||
                    float scale,
 | 
			
		||||
                    float mortar_size,
 | 
			
		||||
                    float mortar_smooth,
 | 
			
		||||
                    float bias,
 | 
			
		||||
                    float brick_width,
 | 
			
		||||
                    float row_height,
 | 
			
		||||
                    float offset_amount,
 | 
			
		||||
                    float offset_frequency,
 | 
			
		||||
                    float squash_amount,
 | 
			
		||||
                    float squash_frequency)
 | 
			
		||||
{
 | 
			
		||||
  vec2 f2 = calc_brick_texture(co * scale,
 | 
			
		||||
                                 mortar_size,
 | 
			
		||||
                                 mortar_smooth,
 | 
			
		||||
                                 bias,
 | 
			
		||||
                                 brick_width,
 | 
			
		||||
                                 row_height,
 | 
			
		||||
                                 offset_amount,
 | 
			
		||||
                                 int(offset_frequency),
 | 
			
		||||
                                 squash_amount,
 | 
			
		||||
                                 int(squash_frequency));
 | 
			
		||||
  float tint = f2.x;
 | 
			
		||||
  float f = f2.y;
 | 
			
		||||
  if (f != 1.0f) {
 | 
			
		||||
    float facm = 1.0f - tint;
 | 
			
		||||
    color1 = facm * color1 + tint * color2;
 | 
			
		||||
  }
 | 
			
		||||
    return f;
 | 
			
		||||
}
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
str_tex_wave = """
 | 
			
		||||
float tex_wave_f(const vec3 p, const int type, const int profile, const float dist, const float detail, const float detail_scale) {
 | 
			
		||||
    float n;
 | 
			
		||||
 | 
			
		||||
@ -29,24 +29,35 @@ else:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_tex_brick(node: bpy.types.ShaderNodeTexBrick, out_socket: bpy.types.NodeSocket, state: ParserState) -> Union[floatstr, vec3str]:
 | 
			
		||||
    state.curshader.add_function(c_functions.str_tex_brick)
 | 
			
		||||
    state.curshader.add_function(c_functions.str_tex_brick_blender)
 | 
			
		||||
 | 
			
		||||
    if node.inputs[0].is_linked:
 | 
			
		||||
        co = c.parse_vector_input(node.inputs[0])
 | 
			
		||||
    else:
 | 
			
		||||
        co = 'bposition'
 | 
			
		||||
 | 
			
		||||
    offset_amount = node.offset
 | 
			
		||||
    offset_frequency = node.offset_frequency
 | 
			
		||||
    squash_amount = node.squash
 | 
			
		||||
    squash_frequency = node.squash_frequency        
 | 
			
		||||
 | 
			
		||||
    col1 = c.parse_vector_input(node.inputs[1])
 | 
			
		||||
    col2 = c.parse_vector_input(node.inputs[2])
 | 
			
		||||
    col3 = c.parse_vector_input(node.inputs[3])
 | 
			
		||||
    scale = c.parse_value_input(node.inputs[4])
 | 
			
		||||
    mortar_size = c.parse_value_input(node.inputs[5])
 | 
			
		||||
    mortar_smooth = c.parse_value_input(node.inputs[6])
 | 
			
		||||
    bias = c.parse_value_input(node.inputs[7])
 | 
			
		||||
    brick_width = c.parse_value_input(node.inputs[8])
 | 
			
		||||
    row_height = c.parse_value_input(node.inputs[9])
 | 
			
		||||
    #res = f'tex_brick({co} * {scale}, {col1}, {col2}, {col3})'
 | 
			
		||||
 | 
			
		||||
    # Color
 | 
			
		||||
    if out_socket == node.outputs[0]:
 | 
			
		||||
        col1 = c.parse_vector_input(node.inputs[1])
 | 
			
		||||
        col2 = c.parse_vector_input(node.inputs[2])
 | 
			
		||||
        col3 = c.parse_vector_input(node.inputs[3])
 | 
			
		||||
        scale = c.parse_value_input(node.inputs[4])
 | 
			
		||||
        res = f'tex_brick({co} * {scale}, {col1}, {col2}, {col3})'
 | 
			
		||||
        res = f'tex_brick_blender({co}, {col1}, {col2}, {col3}, {scale}, {mortar_size}, {mortar_smooth}, {bias}, {brick_width}, {row_height}, {offset_amount}, {offset_frequency}, {squash_amount}, {squash_frequency})'
 | 
			
		||||
    # Fac
 | 
			
		||||
    else:
 | 
			
		||||
        scale = c.parse_value_input(node.inputs[4])
 | 
			
		||||
        res = 'tex_brick_f({0} * {1})'.format(co, scale)
 | 
			
		||||
        res = f'tex_brick_blender_f({co}, {col1}, {col2}, {col3}, {scale}, {mortar_size}, {mortar_smooth}, {bias}, {brick_width}, {row_height}, {offset_amount}, {offset_frequency}, {squash_amount}, {squash_frequency})'
 | 
			
		||||
 | 
			
		||||
    return res
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -77,7 +77,7 @@ class LNX_MT_NodeAddOverride(bpy.types.Menu):
 | 
			
		||||
                layout.separator()
 | 
			
		||||
                layout.menu(f'LNX_MT_{INTERNAL_GROUPS_MENU_ID}_menu', text=internal_groups_menu_class.bl_label, icon='OUTLINER_OB_GROUP_INSTANCE')
 | 
			
		||||
 | 
			
		||||
        elif context.space_data.tree_type == 'ShaderNodeTree':
 | 
			
		||||
        elif context.space_data.tree_type == 'ShaderNodeTree' and bpy.app.version > (4, 0, 0):
 | 
			
		||||
            # TO DO - Recursively gather nodes and draw them to menu
 | 
			
		||||
 | 
			
		||||
            LNX_MT_NodeAddOverride.overridden_draw(self, context)
 | 
			
		||||
 | 
			
		||||
@ -769,9 +769,9 @@ const vec3 compoLetterboxColor = vec3(""" + str(round(rpdat.lnx_letterbox_color[
 | 
			
		||||
"""const float compoSharpenStrength = """ + str(round(rpdat.lnx_sharpen_strength * 100) / 100) + """;
 | 
			
		||||
""")
 | 
			
		||||
 | 
			
		||||
        if bpy.data.scenes[0].view_settings.exposure != 0.0:
 | 
			
		||||
        if bpy.utils.get_active_scene().view_settings.exposure != 0.0:
 | 
			
		||||
            f.write(
 | 
			
		||||
"""const float compoExposureStrength = """ + str(round(bpy.data.scenes[0].view_settings.exposure * 100) / 100) + """;
 | 
			
		||||
"""const float compoExposureStrength = """ + str(round(bpy.utils.get_active_scene().view_settings.exposure * 100) / 100) + """;
 | 
			
		||||
""")
 | 
			
		||||
 | 
			
		||||
        if rpdat.lnx_fog:
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user