diff --git a/leenkx/Sources/iron/Scene.hx b/leenkx/Sources/iron/Scene.hx index df640e2..75b1f62 100644 --- a/leenkx/Sources/iron/Scene.hx +++ b/leenkx/Sources/iron/Scene.hx @@ -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 diff --git a/leenkx/Sources/iron/format/bmp/Data.hx b/leenkx/Sources/iron/format/bmp/Data.hx new file mode 100644 index 0000000..db35cdd --- /dev/null +++ b/leenkx/Sources/iron/format/bmp/Data.hx @@ -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; +#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 +} diff --git a/leenkx/Sources/iron/format/bmp/Reader.hx b/leenkx/Sources/iron/format/bmp/Reader.hx new file mode 100644 index 0000000..d46895c --- /dev/null +++ b/leenkx/Sources/iron/format/bmp/Reader.hx @@ -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 + } + } +} \ No newline at end of file diff --git a/leenkx/Sources/iron/format/bmp/Tools.hx b/leenkx/Sources/iron/format/bmp/Tools.hx new file mode 100644 index 0000000..04051e6 --- /dev/null +++ b/leenkx/Sources/iron/format/bmp/Tools.hx @@ -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 = [0, 1, 2, 3]; + static var BGRA_MAP(default, never):Array = [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, 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, 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 + } + } +} \ No newline at end of file diff --git a/leenkx/Sources/iron/format/bmp/Writer.hx b/leenkx/Sources/iron/format/bmp/Writer.hx new file mode 100644 index 0000000..2f56132 --- /dev/null +++ b/leenkx/Sources/iron/format/bmp/Writer.hx @@ -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); + } +} \ No newline at end of file diff --git a/leenkx/Sources/iron/object/MeshObject.hx b/leenkx/Sources/iron/object/MeshObject.hx index 1c42bcd..4711a06 100644 --- a/leenkx/Sources/iron/object/MeshObject.hx +++ b/leenkx/Sources/iron/object/MeshObject.hx @@ -21,6 +21,7 @@ class MeshObject extends Object { public var particleChildren: Array = 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; diff --git a/leenkx/Sources/iron/object/ParticleSystem.hx b/leenkx/Sources/iron/object/ParticleSystem.hx index 6e55a36..5c94f08 100644 --- a/leenkx/Sources/iron/object/ParticleSystem.hx +++ b/leenkx/Sources/iron/object/ParticleSystem.hx @@ -18,29 +18,29 @@ class ParticleSystem { public var speed = 1.0; var particles: Array; 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; diff --git a/leenkx/Sources/leenkx/logicnode/AddParticleToObjectNode.hx b/leenkx/Sources/leenkx/logicnode/AddParticleToObjectNode.hx new file mode 100644 index 0000000..ae2ff19 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/AddParticleToObjectNode.hx @@ -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); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/DrawCameraTextureNode.hx b/leenkx/Sources/leenkx/logicnode/DrawCameraTextureNode.hx index e367709..58eff7c 100644 --- a/leenkx/Sources/leenkx/logicnode/DrawCameraTextureNode.hx +++ b/leenkx/Sources/leenkx/logicnode/DrawCameraTextureNode.hx @@ -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; } diff --git a/leenkx/Sources/leenkx/logicnode/DrawImageSequenceNode.hx b/leenkx/Sources/leenkx/logicnode/DrawImageSequenceNode.hx index dd1d494..230b6db 100644 --- a/leenkx/Sources/leenkx/logicnode/DrawImageSequenceNode.hx +++ b/leenkx/Sources/leenkx/logicnode/DrawImageSequenceNode.hx @@ -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()); } } diff --git a/leenkx/Sources/leenkx/logicnode/DrawSubImageNode.hx b/leenkx/Sources/leenkx/logicnode/DrawSubImageNode.hx new file mode 100644 index 0000000..4318c1c --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/DrawSubImageNode.hx @@ -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); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetParticleDataNode.hx b/leenkx/Sources/leenkx/logicnode/GetParticleDataNode.hx new file mode 100644 index 0000000..2b2b669 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetParticleDataNode.hx @@ -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; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/GetParticleNode.hx b/leenkx/Sources/leenkx/logicnode/GetParticleNode.hx new file mode 100644 index 0000000..1a2df24 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/GetParticleNode.hx @@ -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 = []; + 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; + } +} diff --git a/leenkx/Sources/leenkx/logicnode/ProbabilisticOutputNode.hx b/leenkx/Sources/leenkx/logicnode/ProbabilisticOutputNode.hx index a53ae7d..5c3f2b6 100644 --- a/leenkx/Sources/leenkx/logicnode/ProbabilisticOutputNode.hx +++ b/leenkx/Sources/leenkx/logicnode/ProbabilisticOutputNode.hx @@ -18,7 +18,6 @@ class ProbabilisticOutputNode extends LogicNode { } if (sum > 1){ - trace(sum); for (p in 0...probs.length) probs[p] /= sum; } diff --git a/leenkx/Sources/leenkx/logicnode/RemoveParticleFromObjectNode.hx b/leenkx/Sources/leenkx/logicnode/RemoveParticleFromObjectNode.hx new file mode 100644 index 0000000..17f7ef7 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/RemoveParticleFromObjectNode.hx @@ -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); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetParticleDataNode.hx b/leenkx/Sources/leenkx/logicnode/SetParticleDataNode.hx new file mode 100644 index 0000000..acf6071 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetParticleDataNode.hx @@ -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); + } +} diff --git a/leenkx/Sources/leenkx/logicnode/SetParticleRenderEmitterNode.hx b/leenkx/Sources/leenkx/logicnode/SetParticleRenderEmitterNode.hx new file mode 100644 index 0000000..1125962 --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/SetParticleRenderEmitterNode.hx @@ -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); + } +} \ No newline at end of file diff --git a/leenkx/Sources/leenkx/logicnode/SetParticleSpeedNode.hx b/leenkx/Sources/leenkx/logicnode/SetParticleSpeedNode.hx index fc108d2..5d36d61 100644 --- a/leenkx/Sources/leenkx/logicnode/SetParticleSpeedNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetParticleSpeedNode.hx @@ -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; diff --git a/leenkx/Sources/leenkx/logicnode/WriteImageNode.hx b/leenkx/Sources/leenkx/logicnode/WriteImageNode.hx new file mode 100644 index 0000000..8fc0a8e --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/WriteImageNode.hx @@ -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); + + } + + } + +} diff --git a/leenkx/blender/lnx/lightmapper/utility/cycles/nodes.py b/leenkx/blender/lnx/lightmapper/utility/cycles/nodes.py index da5e28b..fda6133 100644 --- a/leenkx/blender/lnx/lightmapper/utility/cycles/nodes.py +++ b/leenkx/blender/lnx/lightmapper/utility/cycles/nodes.py @@ -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 diff --git a/leenkx/blender/lnx/lightmapper/utility/cycles/prepare.py b/leenkx/blender/lnx/lightmapper/utility/cycles/prepare.py index 4403f41..1615d75 100644 --- a/leenkx/blender/lnx/lightmapper/utility/cycles/prepare.py +++ b/leenkx/blender/lnx/lightmapper/utility/cycles/prepare.py @@ -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: diff --git a/leenkx/blender/lnx/lightmapper/utility/gui/Viewport.py b/leenkx/blender/lnx/lightmapper/utility/gui/Viewport.py index 01ef600..4e1484a 100644 --- a/leenkx/blender/lnx/lightmapper/utility/gui/Viewport.py +++ b/leenkx/blender/lnx/lightmapper/utility/gui/Viewport.py @@ -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', { diff --git a/leenkx/blender/lnx/lightmapper/utility/utility.py b/leenkx/blender/lnx/lightmapper/utility/utility.py index 6b6efa3..c028704 100644 --- a/leenkx/blender/lnx/lightmapper/utility/utility.py +++ b/leenkx/blender/lnx/lightmapper/utility/utility.py @@ -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 diff --git a/leenkx/blender/lnx/logicnode/__init__.py b/leenkx/blender/lnx/logicnode/__init__.py index ce3969e..ab82a33 100644 --- a/leenkx/blender/lnx/logicnode/__init__.py +++ b/leenkx/blender/lnx/logicnode/__init__.py @@ -47,7 +47,8 @@ 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") lnx_nodes.add_category('Database', icon='MESH_CYLINDER', section="values") diff --git a/leenkx/blender/lnx/logicnode/draw/LN_draw_camera_texture.py b/leenkx/blender/lnx/logicnode/draw/LN_draw_camera_texture.py index c2c505d..47fe3ed 100644 --- a/leenkx/blender/lnx/logicnode/draw/LN_draw_camera_texture.py +++ b/leenkx/blender/lnx/logicnode/draw/LN_draw_camera_texture.py @@ -11,14 +11,15 @@ 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. """ 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) \ No newline at end of file diff --git a/leenkx/blender/lnx/logicnode/draw/LN_draw_sub_image.py b/leenkx/blender/lnx/logicnode/draw/LN_draw_sub_image.py new file mode 100644 index 0000000..7559e61 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/draw/LN_draw_sub_image.py @@ -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') \ No newline at end of file diff --git a/leenkx/blender/lnx/logicnode/native/LN_write_file.py b/leenkx/blender/lnx/logicnode/native/LN_write_file.py index c275ee3..7ea1ac6 100644 --- a/leenkx/blender/lnx/logicnode/native/LN_write_file.py +++ b/leenkx/blender/lnx/logicnode/native/LN_write_file.py @@ -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. diff --git a/leenkx/blender/lnx/logicnode/native/LN_write_image.py b/leenkx/blender/lnx/logicnode/native/LN_write_image.py new file mode 100644 index 0000000..87cab80 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/native/LN_write_image.py @@ -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') \ No newline at end of file diff --git a/leenkx/blender/lnx/logicnode/native/LN_write_json.py b/leenkx/blender/lnx/logicnode/native/LN_write_json.py index 779262e..b7a7264 100644 --- a/leenkx/blender/lnx/logicnode/native/LN_write_json.py +++ b/leenkx/blender/lnx/logicnode/native/LN_write_json.py @@ -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 diff --git a/leenkx/blender/lnx/logicnode/particle/LN_add_particle_to_object.py b/leenkx/blender/lnx/logicnode/particle/LN_add_particle_to_object.py new file mode 100644 index 0000000..59431b3 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/particle/LN_add_particle_to_object.py @@ -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') + + diff --git a/leenkx/blender/lnx/logicnode/particle/LN_get_particle.py b/leenkx/blender/lnx/logicnode/particle/LN_get_particle.py new file mode 100644 index 0000000..d0de058 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/particle/LN_get_particle.py @@ -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') \ No newline at end of file diff --git a/leenkx/blender/lnx/logicnode/particle/LN_get_particle_data.py b/leenkx/blender/lnx/logicnode/particle/LN_get_particle_data.py new file mode 100644 index 0000000..01ef6d0 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/particle/LN_get_particle_data.py @@ -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') diff --git a/leenkx/blender/lnx/logicnode/particle/LN_remove_particle_from_object.py b/leenkx/blender/lnx/logicnode/particle/LN_remove_particle_from_object.py new file mode 100644 index 0000000..8bdd0f4 --- /dev/null +++ b/leenkx/blender/lnx/logicnode/particle/LN_remove_particle_from_object.py @@ -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') \ No newline at end of file diff --git a/leenkx/blender/lnx/logicnode/particle/LN_set_particle_data.py b/leenkx/blender/lnx/logicnode/particle/LN_set_particle_data.py new file mode 100644 index 0000000..805ac2b --- /dev/null +++ b/leenkx/blender/lnx/logicnode/particle/LN_set_particle_data.py @@ -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') diff --git a/leenkx/blender/lnx/logicnode/animation/LN_set_particle_speed.py b/leenkx/blender/lnx/logicnode/particle/LN_set_particle_speed.py similarity index 62% rename from leenkx/blender/lnx/logicnode/animation/LN_set_particle_speed.py rename to leenkx/blender/lnx/logicnode/particle/LN_set_particle_speed.py index 54903a9..a99d77e 100644 --- a/leenkx/blender/lnx/logicnode/animation/LN_set_particle_speed.py +++ b/leenkx/blender/lnx/logicnode/particle/LN_set_particle_speed.py @@ -1,14 +1,22 @@ -from lnx.logicnode.lnx_nodes import * - -class SetParticleSpeedNode(LnxLogicTreeNode): - """Sets the speed of the given particle source.""" - bl_idname = 'LNSetParticleSpeedNode' - bl_label = 'Set Particle Speed' - lnx_version = 1 - - def lnx_init(self, context): - self.add_input('LnxNodeSocketAction', 'In') - self.add_input('LnxNodeSocketObject', 'Object') - self.add_input('LnxFloatSocket', 'Speed', default_value=1.0) - - self.add_output('LnxNodeSocketAction', 'Out') +from lnx.logicnode.lnx_nodes import * + +class SetParticleSpeedNode(LnxLogicTreeNode): + """Sets the speed of the given particle source.""" + bl_idname = 'LNSetParticleSpeedNode' + bl_label = 'Set Particle Speed' + 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) diff --git a/leenkx/blender/lnx/logicnode/particle/__init__.py b/leenkx/blender/lnx/logicnode/particle/__init__.py new file mode 100644 index 0000000..a6d363e --- /dev/null +++ b/leenkx/blender/lnx/logicnode/particle/__init__.py @@ -0,0 +1,3 @@ +from lnx.logicnode.lnx_nodes import add_node_section + +add_node_section(name='default', category='Particle') \ No newline at end of file diff --git a/leenkx/blender/lnx/make_renderpath.py b/leenkx/blender/lnx/make_renderpath.py index c456c5a..3d2b8ce 100644 --- a/leenkx/blender/lnx/make_renderpath.py +++ b/leenkx/blender/lnx/make_renderpath.py @@ -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' diff --git a/leenkx/blender/lnx/material/cycles_functions.py b/leenkx/blender/lnx/material/cycles_functions.py index f0a8419..1ba2321 100644 --- a/leenkx/blender/lnx/material/cycles_functions.py +++ b/leenkx/blender/lnx/material/cycles_functions.py @@ -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; diff --git a/leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py b/leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py index 268cbde..e771577 100644 --- a/leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py +++ b/leenkx/blender/lnx/material/cycles_nodes/nodes_texture.py @@ -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 diff --git a/leenkx/blender/lnx/nodes_logic.py b/leenkx/blender/lnx/nodes_logic.py index 1babb94..a591702 100644 --- a/leenkx/blender/lnx/nodes_logic.py +++ b/leenkx/blender/lnx/nodes_logic.py @@ -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) diff --git a/leenkx/blender/lnx/write_data.py b/leenkx/blender/lnx/write_data.py index 8c12d12..4c8b937 100644 --- a/leenkx/blender/lnx/write_data.py +++ b/leenkx/blender/lnx/write_data.py @@ -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: