Merge pull request 't3du [ Repe ] - New features + Fixes' (#56) from Onek8/LNXSDK:main into main

Reviewed-on: LeenkxTeam/LNXSDK#56
This commit is contained in:
LeenkxTeam 2025-05-28 23:33:34 +00:00
commit 28d60a652b
19 changed files with 922 additions and 24 deletions

View 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
}

View 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
}
}
}

View 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
}
}
}

View 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);
}
}

View File

@ -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;
}

View File

@ -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());
}
}

View 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);
}
}

View File

@ -18,7 +18,6 @@ class ProbabilisticOutputNode extends LogicNode {
}
if (sum > 1){
trace(sum);
for (p in 0...probs.length)
probs[p] /= sum;
}

View 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);
}
}
}

View File

@ -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)

View 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')

View File

@ -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.

View 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')

View File

@ -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

View File

@ -233,7 +233,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'

View File

@ -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;

View File

@ -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

View File

@ -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)

View File

@ -781,9 +781,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: