Merge pull request 'main' (#135) from Onek8/LNXSDK:main into main

Reviewed-on: LeenkxTeam/LNXSDK#135
This commit is contained in:
2026-09-04 17:49:34 +00:00
482 changed files with 27797 additions and 3226 deletions

View File

@ -0,0 +1,17 @@
{
"contexts": [
{
"name": "add_pass",
"depth_write": false,
"compare_mode": "always",
"cull_mode": "none",
"blend_source": "source_alpha",
"blend_destination": "inverse_source_alpha",
"blend_operation": "add",
"links": [],
"texture_params": [],
"vertex_shader": "../include/pass.vert.glsl",
"fragment_shader": "../include/pass_copy.frag.glsl"
}
]
}

View File

@ -11,6 +11,7 @@
#endif
uniform sampler2D tex;
#ifdef _CDepth
uniform sampler2D gbufferD;
#endif
@ -67,6 +68,7 @@ uniform vec3 PPComp14;
uniform vec4 PPComp15;
uniform vec4 PPComp16;
uniform vec4 PPComp18;
uniform vec4 PPComp19;
#endif
// #ifdef _CPos
@ -230,6 +232,45 @@ vec3 lensflare(vec2 uv, vec2 pos) {
}
#endif
#ifdef _CDistort
float distortHash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
float distortValueNoise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
float a = distortHash(i);
float b = distortHash(i + vec2(1.0, 0.0));
float c = distortHash(i + vec2(0.0, 1.0));
float d = distortHash(i + vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
vec2 distortSmoothNoise(vec2 p) {
return vec2(
distortValueNoise(p),
distortValueNoise(p + vec2(5.2, 1.3))
);
}
vec2 distortUV(vec2 uv, vec2 nUV, float t, float strength) {
float intensity = 0.01 * strength;
float scale = 4.0;
float speed = 0.25;
nUV.x += t * speed;
nUV.y += t * speed;
vec2 noise = distortSmoothNoise(nUV * scale);
uv += (-1.0 + noise * 2.0) * intensity;
return uv;
}
#endif
void main() {
vec2 texCo = texCoord;
#ifdef _DynRes
@ -252,14 +293,16 @@ void main() {
#ifdef _CFishEye
#ifdef _CPostprocess
const float fishEyeStrength = -(PPComp2.y);
float fishEyeStrength = PPComp2.y;
#else
const float fishEyeStrength = -0.01;
float fishEyeStrength = compoFisheyeStrength;
#endif
if (abs(fishEyeStrength) > 0.0001) {
const vec2 m = vec2(0.5, 0.5);
vec2 d = texCo - m;
float r = sqrt(dot(d, d));
float power = (2.0 * PI / (2.0 * sqrt(dot(m, m)))) * fishEyeStrength;
float power = - (2.0 * PI / (2.0 * sqrt(dot(m, m)))) * fishEyeStrength;
float bind;
if (power > 0.0) { bind = sqrt(dot(m, m)); }
else { bind = m.x; }
@ -269,6 +312,7 @@ void main() {
else {
texCo = m + normalize(d) * atan(r * -power * 10.0) * bind / atan(-power * bind * 10.0);
}
}
#endif
#ifdef _CDistort
@ -277,9 +321,28 @@ void main() {
#else
float strengthDistort = compoDistortStrength;
#endif
float uX = time * strengthDistort;
texCo.y = texCo.y + (sin(texCo.x*4.0+uX*2.0)*0.01);
texCo.x = texCo.x + (cos(texCo.y*4.0+uX*2.0)*0.01);
vec2 nUV = texCo;
texCo = distortUV(texCo, nUV, time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.1, nUV.y + 0.1), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.2, nUV.y + 0.2), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.3, nUV.y + 0.3), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.4, nUV.y + 0.4), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.5, nUV.y + 0.5), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.6, nUV.y + 0.6), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.7, nUV.y + 0.7), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.8, nUV.y + 0.8), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.9, nUV.y + 0.9), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.15, nUV.y + 0.15), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.25, nUV.y + 0.25), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.35, nUV.y + 0.35), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.45, nUV.y + 0.45), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.55, nUV.y + 0.55), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.65, nUV.y + 0.65), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.75, nUV.y + 0.75), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.85, nUV.y + 0.85), time, strengthDistort);
texCo = distortUV(texCo, vec2(nUV.x + 0.95, nUV.y + 0.95), time, strengthDistort);
#endif
#ifdef _CDepth
@ -343,6 +406,7 @@ void main() {
float compoDistance = PPComp3.x;
float compoLength = PPComp3.y;
float compoStop = PPComp3.z;
vec2 focus = vec2(PPComp19.x, PPComp19.y);
if (PPComp2.z == 1){
compoAutoFocus = true;
@ -350,9 +414,9 @@ void main() {
compoAutoFocus = false;
}
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, compoAutoFocus, compoDistance, compoLength, compoStop);
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, compoAutoFocus, compoDistance, compoLength, compoStop, focus, PPComp19.z);
#else
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, true, compoDOFDistance, compoDOFLength, compoDOFFstop);
fragColor.rgb = dof(texCo, depth, tex, gbufferD, texStep, cameraProj, true, compoDOFDistance, compoDOFLength, compoDOFFstop, vec2(0.5, 0.5), 1.0);
#endif
#else
fragColor = textureLod(tex, texCo, 0.0);
@ -383,7 +447,9 @@ void main() {
vec3 colavg = (col1 + col2 + col3 + col4) * 0.25;
float edgeMagnitude = length(fragColor.rgb - colavg);
fragColor.rgb = mix(fragColor.rgb, SharpenColor, min(edgeMagnitude * strengthSharpen * 2.0, 1.0));
float luma = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));
float sharpenMask = 1.0 - smoothstep(0.5, 0.8, luma);
fragColor.rgb = mix(fragColor.rgb, SharpenColor, min(edgeMagnitude * strengthSharpen * 2.0, 1.0) * sharpenMask);
#endif
#ifdef _CFog

View File

@ -245,6 +245,11 @@
"name": "PPComp18",
"link": "_PPComp18",
"ifdef": ["_CPostprocess"]
},
{
"name": "PPComp19",
"link": "_PPComp19",
"ifdef": ["_CPostprocess"]
}
],
"texture_params": [],

View File

@ -14,6 +14,6 @@ void main() {
#endif
#ifdef _EmissionShaded
fragColor[GBUF_IDX_EMISSION] = vec4(0.0);
fragColor[GBUF_IDX_EMISSION] = vec4(color, 1.0);
#endif
}

View File

@ -0,0 +1,8 @@
#version 450
in vec4 color;
out vec4 fragColor;
void main() {
fragColor = vec4(color);
}

View File

@ -0,0 +1,12 @@
#version 450
in vec3 pos;
in vec4 col;
uniform mat4 ViewProjection;
out vec4 color;
void main() {
color = col;
gl_Position = ViewProjection * vec4(pos, 1.0);
}

View File

@ -0,0 +1,15 @@
#version 450
#include "compiled.inc"
in vec4 color;
out vec4 fragColor[GBUF_SIZE];
void main() {
fragColor[GBUF_IDX_0] = vec4(1.0, 1.0, 0.0, 1.0);
fragColor[GBUF_IDX_1] = vec4(color);
#ifdef _EmissionShaded
fragColor[GBUF_IDX_EMISSION] = vec4(color);
#endif
}

View File

@ -10,9 +10,9 @@
const int samples = 8; // Samples on the first ring
const int rings = 6; // Ring count
const vec2 focus = vec2(0.5, 0.5);
//const vec2 focus = vec2(0.5, 0.5);
const float coc = 0.03; // Circle of confusion size in mm (35mm film = 0.03mm)
const float maxblur = 1.0;
//const float maxblur = 1.0;
const float threshold = 0.5; // Highlight threshold
const float gain = 2.0; // Highlight gain
const float bias = 0.5; // Bokeh edge bias
@ -41,7 +41,9 @@ vec3 dof(
const bool autoFocus,
const float DOFDistance,
const float DOFLength,
const float DOFFStop) {
const float DOFFStop,
const vec2 focus,
const float maxblur) {
float depth = linearize(gdepth, cameraProj);
float fDepth = 0.0;
@ -85,7 +87,6 @@ vec3 dof(
float pw = (cos(float(j) * step) * float(i));
float ph = (sin(float(j) * step) * float(i));
float p = 1.0;
// if (pentagon) p = penta(vec2(pw, ph));
blurredCol += color(texCoord + vec2(pw * w, ph * h), blur, tex, texStep) * mix(1.0, (float(i)) / (float(rings)), bias) * p;
s += 1.0 * mix(1.0, (float(i)) / (float(rings)), bias) * p;
}

View File

@ -28,15 +28,14 @@
#ifdef _ShadowMap
#ifdef _SinglePoint
#ifdef _Spot
#ifndef _LTC
#if defined(_Spot) || defined(_LTC)
uniform sampler2DShadow shadowMapSpot[1];
#ifdef _ShadowMapTransparent
uniform sampler2D shadowMapSpotTransparent[1];
#endif
uniform mat4 LWVPSpotArray[1];
#endif
#else
#ifndef _Spot
uniform samplerCubeShadow shadowMapPoint[1];
#ifdef _ShadowMapTransparent
uniform samplerCube shadowMapPointTransparent[1];
@ -45,12 +44,6 @@
#endif
#endif
#ifdef _Clusters
#ifdef _SingleAtlas
//!uniform sampler2DShadow shadowMapAtlas;
#ifdef _ShadowMapTransparent
//!uniform sampler2D shadowMapAtlasTransparent;
#endif
#endif
#ifndef _SinglePoint
uniform vec2 lightProj;
#endif
@ -67,7 +60,7 @@
uniform samplerCube shadowMapPointTransparent[4];
#endif
#endif
#ifdef _Spot
#if defined(_Spot) || defined(_LTC)
#ifdef _ShadowMapAtlas
#ifndef _SingleAtlas
uniform sampler2DShadow shadowMapAtlasSpot;
@ -93,15 +86,6 @@ uniform vec3 lightArea2;
uniform vec3 lightArea3;
uniform sampler2D sltcMat;
uniform sampler2D sltcMag;
#ifdef _ShadowMap
#ifdef _SinglePoint
uniform sampler2DShadow shadowMapSpot[1];
#ifdef _ShadowMapTransparent
uniform sampler2D shadowMapSpotTransparent[1];
#endif
uniform mat4 LWVPSpotArray[1];
#endif
#endif
#endif
vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol,
@ -143,37 +127,41 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV
#ifdef _VoxelPass
vec3 direct = vec3(dotNL);
#else
#ifdef _LTC
float theta = acos(dotNV);
vec2 tuv = vec2(rough, theta / (0.5 * PI));
tuv = tuv * LUT_SCALE + LUT_BIAS;
vec4 t = textureLod(sltcMat, tuv, 0.0);
mat3 invM = mat3(
vec3(1.0, 0.0, t.y),
vec3(0.0, t.z, 0.0),
vec3(t.w, 0.0, t.x));
float ltcspec = ltcEvaluate(n, v, dotNV, p, invM, lightArea0, lightArea1, lightArea2, lightArea3);
ltcspec *= textureLod(sltcMag, tuv, 0.0).a;
float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3);
vec3 direct = albedo * ltcdiff + ltcspec * spec * 0.05;
#else
vec3 standard;
#ifdef _Anisotropy
vec3 direct;
if (abs(anisotropy) > 0.001 && dot(tangent, tangent) > 0.001) {
vec3 bitangent = normalize(cross(n, tangent));
direct = lambertDiffuseBRDF(albedo, dotNL) +
standard = lambertDiffuseBRDF(albedo, dotNL) +
anisotropicBRDF(f0, rough, anisotropy, anisoRot,
tangent, bitangent, n, l, v, dotNL, dotNV) * spec;
} else {
direct = lambertDiffuseBRDF(albedo, dotNL) +
standard = lambertDiffuseBRDF(albedo, dotNL) +
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
}
#else
vec3 direct = lambertDiffuseBRDF(albedo, dotNL) +
standard = lambertDiffuseBRDF(albedo, dotNL) +
specularBRDF(f0, rough, dotNL, dotNH, dotNV, dotVH) * spec;
#endif
#ifdef _Spot
if (isSpot) {
standard *= spotlightMask(l, spotDir, right, scale, spotSize, spotBlend);
}
#endif
vec3 area = vec3(0.0);
#ifdef _LTC
float theta = acos(dotNV);
vec2 tuv = vec2(rough, theta / (0.5 * PI)) * LUT_SCALE + LUT_BIAS;
vec4 t = textureLod(sltcMat, tuv, 0.0);
mat3 invM = mat3(vec3(1.0, 0.0, t.y), vec3(0.0, t.z, 0.0), vec3(t.w, 0.0, t.x));
float ltcspec = ltcEvaluate(n, v, dotNV, p, invM, lightArea0, lightArea1, lightArea2, lightArea3);
ltcspec *= textureLod(sltcMag, tuv, 0.0).a;
float ltcdiff = ltcEvaluate(n, v, dotNV, p, mat3(1.0), lightArea0, lightArea1, lightArea2, lightArea3);
area = albedo * ltcdiff + ltcspec * spec * 0.05;
#endif
vec3 direct = standard + area;
// before attenuate/shadow so everything is properly shadowed in one pass
#ifdef _ExtBRDF
float layerWeight;
@ -195,185 +183,91 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV
direct *= attenuate(dist);
direct *= min(lightCol, vec3(100.0));
#ifdef _LTC
#ifdef _ShadowMap
if (receiveShadow) {
#ifdef _SinglePoint
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif
#ifdef _Clusters
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
#ifdef _ShadowMapAtlas
tileBounds = tileBoundsSpotArray[index];
direct *= shadowTest(
#ifdef _ShadowMapTransparent
#ifndef _SingleAtlas
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
#else
#ifndef _SingleAtlas
shadowMapAtlasSpot
#else
shadowMapAtlas
#endif
#endif
, lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#else
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[1],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[2],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[3],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif
#endif
}
#endif
l_out = l;
return direct;
#endif
#ifdef _Spot
if (isSpot) {
direct *= spotlightMask(l, spotDir, right, scale, spotSize, spotBlend);
#ifdef _ShadowMap
if (receiveShadow) {
#ifdef _SinglePoint
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10, 1.0);
direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif
#ifdef _Clusters
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10, 1.0);
#ifdef _ShadowMapAtlas
tileBounds = tileBoundsSpotArray[index];
direct *= shadowTest(
#ifdef _ShadowMapTransparent
#ifndef _SingleAtlas
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
#else
#ifndef _SingleAtlas
shadowMapAtlasSpot
#else
shadowMapAtlas
#endif
#endif
, lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#else
if (index == 0) direct *= shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 1) direct *= shadowTest(shadowMapSpot[1],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[1],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 2) direct *= shadowTest(shadowMapSpot[2],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[2],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 3) direct *= shadowTest(shadowMapSpot[3],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[3],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif
#endif
}
#endif
l_out = l;
return direct;
}
#endif
#ifdef _LightIES
direct *= iesAttenuation(-l);
#endif
vec3 visibility = vec3(1.0);
#ifdef _ShadowMap
if (receiveShadow) {
#if defined(_Spot) || defined(_LTC)
#ifdef _SinglePoint
#ifndef _Spot
direct *= PCFCube(shadowMapPoint[0],
vec4 lPos = LWVPSpotArray[0] * vec4(p + n * bias * 10.0, 1.0);
visibility = shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif
#ifdef _Clusters
vec4 lPos = LWVPSpotArray[index] * vec4(p + n * bias * 10.0, 1.0);
#ifdef _ShadowMapAtlas
tileBounds = tileBoundsSpotArray[index];
visibility = shadowTest(
#ifdef _ShadowMapTransparent
#ifndef _SingleAtlas
shadowMapAtlasSpot, shadowMapAtlasSpotTransparent
#else
shadowMapAtlas, shadowMapAtlasTransparent
#endif
#else
#ifndef _SingleAtlas
shadowMapAtlasSpot
#else
shadowMapAtlas
#endif
#endif
, lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#else
if (index == 0) visibility = shadowTest(shadowMapSpot[0],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[0],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 1) visibility = shadowTest(shadowMapSpot[1],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[1],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 2) visibility = shadowTest(shadowMapSpot[2],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[2],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
else if (index == 3) visibility = shadowTest(shadowMapSpot[3],
#ifdef _ShadowMapTransparent
shadowMapSpotTransparent[3],
#endif
lPos.xyz / lPos.w, bias
#ifdef _ShadowMapTransparent
, transparent
#endif
);
#endif
#endif
#else
#ifdef _SinglePoint
visibility = PCFCube(shadowMapPoint[0],
#ifdef _ShadowMapTransparent
shadowMapPointTransparent[0],
#endif
@ -383,10 +277,9 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV
#endif
);
#endif
#endif
#ifdef _Clusters
#ifdef _ShadowMapAtlas
direct *= PCFFakeCube(
visibility = PCFFakeCube(
#ifdef _ShadowMapTransparent
#ifndef _SingleAtlas
shadowMapAtlasPoint, shadowMapAtlasPointTransparent
@ -406,7 +299,7 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV
#endif
);
#else
if (index == 0) direct *= PCFCube(shadowMapPoint[0],
if (index == 0) visibility = PCFCube(shadowMapPoint[0],
#ifdef _ShadowMapTransparent
shadowMapPointTransparent[0],
#endif
@ -415,7 +308,7 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV
, transparent
#endif
);
else if (index == 1) direct *= PCFCube(shadowMapPoint[1],
else if (index == 1) visibility = PCFCube(shadowMapPoint[1],
#ifdef _ShadowMapTransparent
shadowMapPointTransparent[1],
#endif
@ -424,7 +317,7 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV
, transparent
#endif
);
else if (index == 2) direct *= PCFCube(shadowMapPoint[2],
else if (index == 2) visibility = PCFCube(shadowMapPoint[2],
#ifdef _ShadowMapTransparent
shadowMapPointTransparent[2],
#endif
@ -433,7 +326,7 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV
, transparent
#endif
);
else if (index == 3) direct *= PCFCube(shadowMapPoint[3],
else if (index == 3) visibility = PCFCube(shadowMapPoint[3],
#ifdef _ShadowMapTransparent
shadowMapPointTransparent[3],
#endif
@ -444,11 +337,12 @@ vec3 sampleLightCore(const vec3 p, const vec3 n, const vec3 v, const float dotNV
);
#endif
#endif
#endif
}
#endif
l_out = l;
return direct;
return direct * visibility;
}
vec3 sampleLight(const vec3 p, const vec3 n, const vec3 v, const float dotNV, const vec3 lp, const vec3 lightCol,

View File

@ -1,41 +1,81 @@
/*
https://github.com/JonasFolletete/glsl-triplanar-mapping
vec4 boxProjection(sampler2D image, vec3 normal, vec3 coord, float blend) {
vec3 n = normalize(normal);
vec3 N = abs(n);
vec4 color1, color2, color3;
MIT License
vec2 uv = coord.yz;
if (n.x < 0.0) {
uv.x = 1.0 - uv.x;
}
color1 = texture(image, uv);
Copyright (c) 2018 Jonas Folletête
uv = coord.xz;
if (n.y > 0.0) {
uv.x = 1.0 - uv.x;
}
color2 = texture(image, uv);
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
uv = vec2(coord.y, 1.0 - coord.x);
if (n.z > 0.0) {
uv.x = 1.0 - uv.x;
}
color3 = texture(image, uv);
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
N /= max(dot(N, vec3(1.0)), 1e-8);
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
float limit = 0.5 + 0.5 * clamp(blend, 0.0, 1.0);
vec3 weight;
weight = N.xyz / (N.xyx + N.yzz);
weight = clamp((weight - 0.5 * (1.0 - clamp(blend, 0.0, 1.0))) / max(1e-8, clamp(blend, 0.0, 1.0)), 0.0, 1.0);
vec3 blendNormal(vec3 normal) {
vec3 blending = abs(normal);
blending = normalize(max(blending, 0.00001));
blending /= vec3(blending.x + blending.y + blending.z);
return blending;
if (N.z < (1.0 - limit) * (N.y + N.x)) {
weight.z = 0.0;
weight.y = 1.0 - weight.x;
}
else if (N.x < (1.0 - limit) * (N.y + N.z)) {
weight.x = 0.0;
weight.z = 1.0 - weight.y;
}
else if (N.y < (1.0 - limit) * (N.x + N.z)) {
weight.y = 0.0;
weight.x = 1.0 - weight.z;
}
else {
weight = ((2.0 - limit) * N + (limit - 1.0)) / max(1e-8, clamp(blend, 0.0, 1.0));
}
vec3 triplanarMapping (sampler2D ImageTexture, vec3 normal, vec3 position) {
vec3 normalBlend = blendNormal(normal);
vec3 xColor = texture(ImageTexture, position.yz).rgb;
vec3 yColor = texture(ImageTexture, position.xz).rgb;
vec3 zColor = texture(ImageTexture, position.xy).rgb;
return (xColor * normalBlend.x + yColor * normalBlend.y + zColor * normalBlend.z);
return color1 * weight.x + color2 * weight.y + color3 * weight.z;
}
vec2 sphericalMapping(vec3 coord) {
vec3 vin = coord * 2.0 - vec3(1.0);
float len = length(vin);
float v, u;
if (len > 0.0) {
if (vin.x == 0.0 && vin.y == 0.0) {
u = 0.0;
}
else {
u = (1.0 - atan(vin.x, vin.y) / PI) * 0.5;
}
v = acos(clamp(vin.z / len, -1.0, 1.0)) / PI;
}
else {
v = u = 0.0;
}
return vec2(u, v);
}
vec2 tubeMapping(vec3 coord) {
vec3 vin = coord * 2.0 - vec3(1.0);
float u, v;
v = - (vin.z + 1.0) * 0.5;
float len = sqrt(vin.x * vin.x + vin.y * vin.y);
if (len > 0.0) {
u = (1.0 - (atan(vin.x / len, vin.y / len) / PI)) * 0.5;
}
else {
v = u = 0.0;
}
return vec2(u, v);
}

View File

@ -19,6 +19,14 @@ vec2 envMapEquirect(const vec3 normal) {
return vec2(theta / PI2, phi / PI);
}
vec2 envMapMirror(const vec3 co) {
vec3 nco = normalize(co);
nco.y -= 1.0;
float div = 2.0 * sqrt(max(-0.5 * nco.y, 0.0));
nco /= max(1e-8, div);
return 0.5 * nco.xz + 0.5;
}
float rand(const vec2 co) { // Unreliable
return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
}

View File

@ -4,6 +4,36 @@ uniform vec2 morphScaleOffset;
uniform vec2 morphDataDim;
uniform vec4 morphWeights[8];
void getMorphedVertex(vec2 uvCoord, inout vec3 A, vec4 imorph) {
vec3 morph = texture(morphDataPos, uvCoord).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += imorph.x * morph;
morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += imorph.y * morph;
morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - 2.0 * morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += imorph.z * morph;
morph = texture(morphDataPos, vec2(uvCoord.x, uvCoord.y - 3.0 * morphDataDim.y)).rgb * morphScaleOffset.x + morphScaleOffset.y;
A += imorph.w * morph;
}
void getMorphedNormal(vec2 uvCoord, vec3 oldNor, inout vec3 morphNor, vec4 imorph) {
vec3 norm = oldNor + imorph.x * (texture(morphDataNor, uvCoord).rgb * 2.0 - 1.0);
morphNor += norm;
norm = oldNor + imorph.y * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - morphDataDim.y)).rgb * 2.0 - 1.0);
morphNor += norm;
norm = oldNor + imorph.z * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - 2.0 * morphDataDim.y)).rgb * 2.0 - 1.0);
morphNor += norm;
norm = oldNor + imorph.w * (texture(morphDataNor, vec2(uvCoord.x, uvCoord.y - 3.0 * morphDataDim.y)).rgb * 2.0 - 1.0);
morphNor += norm;
morphNor = normalize(morphNor);
}
void getMorphedVertex(vec2 uvCoord, inout vec3 A){
vec3 totalDelta = vec3(0.0);
for(int i = 0; i<8; i++ )

View File

@ -237,8 +237,24 @@ class App {
traitRenders.remove(f);
}
public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
public static function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) {
if (index < 0 || index >= traitRenders2D.length) {
traitRenders2D.push(f);
} else {
traitRenders2D.insert(index, f);
}
}
public static function moveRender2D(f: kha.graphics2.Graphics->Void, newIndex: Int) {
var oldIndex = traitRenders2D.indexOf(f);
if (oldIndex != -1) {
traitRenders2D.splice(oldIndex, 1);
if (newIndex >= traitRenders2D.length) {
traitRenders2D.push(f);
} else {
traitRenders2D.insert(newIndex, f);
}
}
}
public static function removeRender2D(f: kha.graphics2.Graphics->Void) {

View File

@ -531,13 +531,11 @@ class RenderPath {
if (!drawn) submitDraw(context);
#if lnx_debug
// Callbacks to specific context
if (contextEvents != null) {
var ar = contextEvents.get(context);
if (ar != null) for (i in 0...ar.length) ar[i](currentG, i, ar.length);
}
#end
end();
}
@ -597,7 +595,6 @@ class RenderPath {
}
}
#if lnx_debug
static var contextEvents: Map<String, Array<Graphics->Int->Int->Void>> = null;
public static function notifyOnContext(name: String, onContext: Graphics->Int->Int->Void) {
if (contextEvents == null) contextEvents = new Map();
@ -608,7 +605,13 @@ class RenderPath {
}
ar.push(onContext);
}
#end
public static function removeNotifyOnContext(name: String, onContext: Graphics->Int->Int->Void) {
if (contextEvents != null) {
var ar = contextEvents.get(name);
if (ar != null) ar.remove(onContext);
}
}
#if rp_decals
public function drawDecals(context: String) {

View File

@ -14,6 +14,7 @@ import iron.object.SpeakerObject;
import iron.object.DecalObject;
import iron.object.ProbeObject;
import iron.object.Tilesheet;
import iron.object.CurveObject;
import iron.data.CameraData;
import iron.data.MeshData;
import iron.data.LightData;
@ -64,6 +65,7 @@ class Scene {
#end
public var empties: Array<Object>;
public var animations: Array<Animation>;
public var tilesheets: Array<Tilesheet>;
#if lnx_skin
public var armatures: Array<Armature>;
#end
@ -110,6 +112,7 @@ class Scene {
#end
empties = [];
animations = [];
tilesheets = [];
#if lnx_skin
armatures = [];
#end
@ -135,6 +138,14 @@ class Scene {
// Startup scene
active.addScene(format.name, null, function(sceneObject: Object) {
if (format.properties != null) {
sceneObject.properties = new Map();
for (p in format.properties) {
sceneObject.properties.set(p.name, cleanValue(p.value));
}
}
// Create traits bottom-up (children first, then parents)
createTraitsBottomUp(sceneObject);
@ -342,6 +353,7 @@ class Scene {
if (terrainStream != null) terrainStream.update(active.camera);
#end
for (anim in animations) anim.update(Time.delta);
for (tilesheet in tilesheets) tilesheet.update();
for (e in empties) if (e != null && e.parent != null) e.transform.update();
}
@ -441,6 +453,11 @@ class Scene {
return g;
}
public function removeFromGroups(object: Object) {
if (groups == null) return;
for (name in groups.keys()) getGroup(name).remove(object);
}
public function addMeshObject(data: MeshData, materials: Vector<MaterialData>, parent: Object = null): MeshObject {
var object = new MeshObject(data, materials);
parent != null ? object.setParent(parent) : object.setParent(root);
@ -450,6 +467,12 @@ class Scene {
return object;
}
public function addCurveObject(data: TCurveData, parent: Object = null): CurveObject {
var object = new CurveObject(data);
parent != null ? object.setParent(parent) : object.setParent(root);
return object;
}
public function addLightObject(data: LightData, parent: Object = null): LightObject {
var object = new LightObject(data);
parent != null ? object.setParent(parent) : object.setParent(root);
@ -709,6 +732,10 @@ class Scene {
else done(ro);
});
}
else if (o.type == "curve_object") {
var object = addCurveObject(Data.getCurveRawByName(format.curve_datas, o.data_ref), parent);
returnObject(object, o, done);
}
else done(null);
}
@ -889,11 +916,13 @@ class Scene {
}
else { #end // lnx_skin
#if lnx_stream
streamMeshObject(
if ((o.particle_refs == null || o.particle_refs.length == 0) && o.is_particle == null && parent != null)
streamMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
else
returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
#else
returnMeshObject(
returnMeshObject(object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
#end
object_file, data_ref, sceneName, null, materials, parent, parentObject, o, done);
#if lnx_skin
}
#end
@ -968,20 +997,24 @@ class Scene {
#end
if (o.properties != null) {
object.properties = new Map();
for (p in o.properties) object.properties.set(p.name, p.value);
for (p in o.properties) {
object.properties.set(p.name, cleanValue(p.value));
}
}
if (o.vertex_groups != null) {
object.vertex_groups = new Map();
cast(object, MeshObject).vertexGroups = new Map();
for (p in o.vertex_groups) {
var verts = [];
for(i in 0...Std.int(p.value.length/3)){
var x = Std.parseFloat(p.value[i*3]);
var y = Std.parseFloat(p.value[i*3+1]);
var z = Std.parseFloat(p.value[i*3+2]);
verts.push(new iron.math.Vec4(x, y, z, 1));
var verts:Array<iron.math.Vec4> = [];
var data:kha.arrays.Float32Array = cast p.value;
if (data != null) {
for (i in 0...Std.int(data.length / 3)) {
verts.push(new iron.math.Vec4(data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 1.0));
}
object.vertex_groups.set(p.name, verts);
}
cast(object, MeshObject).vertexGroups.set(p.name, verts);
}
}
@ -1145,4 +1178,16 @@ class Scene {
public function notifyOnRemove(f: Void->Void) {
traitRemoves.push(f);
}
static function cleanValue(val: Dynamic): Dynamic {
if (val == null) return null;
if (untyped val.buffer != null) {
var data: kha.arrays.Float32Array = cast val;
return [for (i in 0...data.length) data[i]];
}
if (Std.isOfType(val, Array)) {
return [for (item in (cast val: Array<Dynamic>)) cleanValue(item)];
}
return val;
}
}

View File

@ -125,10 +125,10 @@ class Trait {
/**
Add 2D render handler.
**/
public function notifyOnRender2D(f: kha.graphics2.Graphics->Void) {
public function notifyOnRender2D(f: kha.graphics2.Graphics->Void, index: Int = -1) {
if (_render2D == null) _render2D = [];
_render2D.push(f);
App.notifyOnRender2D(f);
App.notifyOnRender2D(f, index);
}
/**

View File

@ -311,7 +311,7 @@ class Data {
loadingSceneRaws.set(file, [done]);
// If no extension specified, set to .arm
// If no extension specified, set to .lnx
var compressed = file.endsWith(".lz4");
var isJson = file.endsWith(".json");
var ext = (compressed || isJson || file.endsWith(".lnx")) ? "" : ".lnx";
@ -405,6 +405,13 @@ class Data {
}
#end
public static function getCurveRawByName(datas: Array<TCurveData>, name: String): TCurveData {
if (datas == null || datas.length == 0) return null;
if (name == "") return datas[0];
for (dat in datas) if (dat.name == name) return dat;
return null;
}
// Raw assets
public static function getBlob(file: String, done: kha.Blob->Void) {
var cached = cachedBlobs.get(file); // Is already cached

View File

@ -40,6 +40,8 @@ class Geometry {
public var instancedVB: VertexBuffer = null;
public var instanced = false;
public var instanceCount = 0;
public var instanceElements: Array<{name: String, data: String}> = [];
public var instanceStride: Int = 0;
public var positions: TVertexArray;
public var normals: TVertexArray;
@ -130,11 +132,39 @@ class Geometry {
structure.add("iscl", kha.graphics4.VertexData.Float3);
}
if (instanceElements != null && instanceElements.length > 0) {
for (elem in instanceElements) {
if (StringTools.startsWith(elem.name, "i") && elem.name != "ipos" && elem.name != "irot" && elem.name != "iscl") {
var vdata = VertexData.Float1;
var dataStr: String = Reflect.field(elem, "data");
switch (dataStr) {
case "float1": vdata = VertexData.Float1;
case "float2": vdata = VertexData.Float2;
case "float3": vdata = VertexData.Float3;
case "float4": vdata = VertexData.Float4;
}
structure.add(elem.name, vdata);
}
}
}
this.instanceStride = Std.int(structure.byteSize() / 4);
instanceCount = Std.int(data.length / Std.int(structure.byteSize() / 4));
instancedVB = new VertexBuffer(instanceCount, structure, usage, 1);
var vertices = instancedVB.lock();
for (i in 0...Std.int(vertices.byteLength / 4)) vertices.setFloat32(i * 4, data[i]);
instancedVB.unlock();
}
public function updateInstanced(data: Float32Array) {
if (instancedVB == null) return;
var vertices = instancedVB.lock();
for (i in 0...Std.int(vertices.byteLength / 4)) {
vertices.setFloat32(i * 4, data[i]);
}
instancedVB.unlock();
}
public function copyVertices(vertices: ByteArray, offset = 0, fakeUVs = false) {

View File

@ -40,6 +40,8 @@ typedef TSceneFormat = {
@:optional public var irradiance: Float32Array; // Blob with spherical harmonics, bands 0,1,2
@:optional public var terrain_datas: Array<TTerrainData>;
@:optional public var terrain_ref: String;
@:optional public var properties: Array<TProperty>;
@:optional public var curve_datas: Array<TCurveData>;
}
#if js
@ -432,6 +434,7 @@ typedef TParticleData = {
// Velocity
public var object_align_factor: Float32Array;
public var factor_random: FastFloat;
public var normal_factor: FastFloat;
// Rotation
public var use_rotations: Bool;
public var rotation_mode: Int; // 0 - None, 1 - Normal, 2 - Normal-Tangent, 3 - Velocity/Hair, 4 - Global X, 5 - Global Y, 6 - Global Z, 7 - Object X, 8 - Object Y, 9 - Object Z
@ -525,7 +528,7 @@ typedef TVertex_groups = {
@:structInit class TVertex_groups {
#end
public var name: String;
public var value: Dynamic;
public var value: Float32Array;
}
#if js
@ -564,6 +567,21 @@ typedef TConstraint = {
@:optional public var invert_z: Null<Bool>;
@:optional public var use_offset: Null<Bool>;
@:optional public var influence: Null<FastFloat>;
@:optional public var use_min_x: Null<Bool>;
@:optional public var use_max_x: Null<Bool>;
@:optional public var use_min_y: Null<Bool>;
@:optional public var use_max_y: Null<Bool>;
@:optional public var use_min_z: Null<Bool>;
@:optional public var use_max_z: Null<Bool>;
@:optional public var use_limit_x: Null<Bool>;
@:optional public var use_limit_y: Null<Bool>;
@:optional public var use_limit_z: Null<Bool>;
@:optional public var min_x: Null<FastFloat>;
@:optional public var max_x: Null<FastFloat>;
@:optional public var min_y: Null<FastFloat>;
@:optional public var max_y: Null<FastFloat>;
@:optional public var min_z: Null<FastFloat>;
@:optional public var max_z: Null<FastFloat>;
}
#if js
@ -622,3 +640,48 @@ typedef TTrack = {
public var values: Float32Array; // sampled - full matrix transforms, non-sampled - values
@:optional public var ref_values: Array<Array<String>>; // ref values
}
#if js
typedef TBezierPoint = {
#else
@:structInit class TBezierPoint {
#end
public var co: Float32Array;
public var handle_left: Float32Array;
public var handle_right: Float32Array;
}
#if js
typedef TSpline = {
#else
@:structInit class TSpline {
#end
public var closed: Bool;
public var resolution: Int;
public var points: Array<TBezierPoint>;
public var material_index: Int;
}
#if js
typedef TShapeKey = {
#else
@:structInit class TShapeKey {
#end
public var name: String;
public var value: Float;
public var points: Array<TBezierPoint>;
}
#if js
typedef TCurveData = {
#else
@:structInit class TCurveData {
#end
public var name: String;
public var object: String;
public var splines: Array<TSpline>;
public var strength: Float;
public var color: Float32Array;
@:optional public var material_refs: Array<String>;
@:optional public var shape_keys: Array<TShapeKey>;
}

View File

@ -79,6 +79,8 @@ class ShaderContext {
var structure: VertexStructure;
var instancingType = 0;
var instanceElements: Array<{name: String, data: String}> = [];
var instanceStride: Int = 0;
public function new(raw: TShaderContext, done: ShaderContext->Void, overrideContext: TShaderOverride = null) {
this.raw = raw;
@ -108,6 +110,11 @@ class ShaderContext {
if (instancingType == 3 || instancingType == 4) {
instStruct.add("iscl", VertexData.Float3);
}
for (e in instanceElements)
instStruct.add(e.name, parseData(e.data));
this.instanceStride = Std.int(instStruct.byteSize() / 4);
instStruct.instanced = true;
pipeState.inputLayout = [structure, instStruct];
}
@ -268,10 +275,12 @@ class ShaderContext {
if (Reflect.field(elem, "name") == "ipos") { ipos = true; continue; }
if (Reflect.field(elem, "name") == "irot") { irot = true; continue; }
if (Reflect.field(elem, "name") == "iscl") { iscl = true; continue; }
if (Reflect.field(elem, "name").startsWith("i")) { instanceElements.push(elem); continue; }
#else
if (elem.name == "ipos") { ipos = true; continue; }
if (elem.name == "irot") { irot = true; continue; }
if (elem.name == "iscl") { iscl = true; continue; }
if (elem.name.startsWith("i")) { instanceElements.push(elem); continue; }
#end
structure.add(elem.name, parseData(elem.data));
}

View File

@ -0,0 +1,357 @@
package iron.format.gif;
import haxe.io.Bytes;
/**
* Gif data.
*/
typedef Data =
{
/**
* Gif version. There is only 2 Gif version exists. 87a and 89a.
* 87a have less features and does not support any extensions.
* Unknown version is adviced to be interpreted as newest (89a) official version.
*/
var version:Version;
/**
* Information about logical screen of Gif that provides basic information about Gif.
*/
var logicalScreenDescriptor:LogicalScreenDescriptor;
/**
* Global color table used for Gif. Present only if Logical Screen Descriptor contained global color table flag.
* Note that this color table not always present since frames can contain local color tables that overrides global color table.
*/
@:optional var globalColorTable:Null<ColorTable>;
/**
* List of Gif data blocks.
*/
var blocks:List<Block>;
}
/**
* Gif data block. Custom blocks are not supported.
*/
enum Block
{
/**
* Gif frame block.
* Note that this block does not contain link to graphic control extension of Frame even if it is present. GraphicControl extension Block commonly present right before frame Block.
*/
BFrame(frame:Frame);
/**
* Additional extension block. This Block does not supported in 87a Gif specification version.
*/
BExtension(extension:Extension);
/**
* End of File block. Represents end of Gif data.
*/
BEOF;
}
/**
* Extension block contains additional data about Gif image. This block does not supported by 87a version.
*/
enum Extension
{
/**
* Graphic Control extension gives additional control over next frame, like frame delay, disposal method, alpha channel and other information.
*/
EGraphicControl(gce:GraphicControlExtension);
/**
* Commentary extension. Not show up as any visual, just a text in file.
*/
EComment(text:String);
/**
* Text extension. Must work as text rendering on the image, but ignored by all major Gif decoders.
*/
EText(pte:PlainTextExtension);
/**
* Application extension allow to insert additional application data into Gif. Mostly used app extension is NETSCAPE2.0 looping extension, used to set up amount of loops in frame.
*/
EApplicationExtension(ext:ApplicationExtension);
/**
* Unknown extension.
*/
EUnknown(id:Int, data:Bytes);
}
/**
* Application extension. Mostly used only for one reason - setting up loops count. There is exist other app extensions but they are really rare.
*/
enum ApplicationExtension
{
/**
* NETSCAPE2.0 looping extension. Contains only amount of animation repeats.
* Note that there is two NETSCAPE2.0 app extensions for Gif format and the type of extension is stored in first byte of data. Looping extension have ID 1.
*/
AENetscapeLooping(loops:Int);
/**
* Unknown or unsupported app extension.
*/
AEUnknown(name:String, version:String, data:Bytes);
}
/**
* Typical color table for Gif image.
* Can contain 2, 4, 8, 16, 32, 64, 128 or 256 colors.
* Data stored in RGB format. Information about alpha channel provided by Graohic Control Extension.
*/
typedef ColorTable = Bytes;
/**
* Single frame of the image.
* Actually it's a merge of 3 consequent blocks:
* 1. Image Descriptor.
* Contains frame informations like position, size, existing of local color table and interlaced flag.
* 2. [Local color table].
* Only present if Image Descriptor contains local color table flag. Overrides global color table.
* 3. Pixel data blocks.
* LZW compressed pixel data.
*/
typedef Frame =
{
/**
* X position of image on the Logical Screen
*/
var x:Int;
/**
* Y position of image on the Logical Screen
*/
var y:Int;
/**
* Width of image in pixels
*/
var width:Int;
/**
* Height of image in pixels
*/
var height:Int;
/**
* Is this image uses local color table?
*/
var localColorTable:Bool;
/**
* Is this image written in interlace mode?
* Note: The pixel data already deinterlaced and this flag presented only for information purpose (and for Writer when there is one).
*/
var interlaced:Bool;
/**
* Is local color table sorted in order of decreasing priority?
*/
var sorted:Bool;
/**
* Size of local color table
*/
var localColorTableSize:Int;
/**
* Pixel data of frame. Stored as Indexed colors, 1 byte per pixel.
*/
var pixels:Bytes;
/**
* Local color table used by frame. Stored as 3-byte RGB colors. If value is null, must be used global color table.
*/
var colorTable:ColorTable;
}
/**
* Graphic Control Extension block, used for setting up disposal method, transparency, delay and user input.
*/
typedef GraphicControlExtension =
{
/**
* Disposal method of frame.
*/
var disposalMethod:DisposalMethod;
/**
* Is image must wait for user input, before dispose?
* This flag may be used by user-defined program but absolutely ignored by any Gif players.
*/
var userInput:Bool;
/**
* Is image have transparency?
*/
var hasTransparentColor:Bool;
/**
* Delay, before next image appears. Delay is in centiseconds (1 centisecond = 1/100 seconds).
* Note: Some players (like FastStone) cut fraction of elapsed time when progressing to next frame which results in small timing error.
* Recommended to use `time -= delay` instead of `time = 0`.
*/
var delay:Int;
/**
* Index in color table that used as transparent.
*/
var transparentIndex:Int;
}
/**
* Extension for rendering text on Gif logical screen. It does not supported by major Gif decoders.
* Font and text size decision is left to decoder. (recommended to decide based on grid/cell size)
* Text must be rendered with one character at cell.
* It's recommended to replace any characters less than 0x20 and greater than 0xf7 to be rendered as Space (0x20)
*/
typedef PlainTextExtension =
{
/**
* X position of text grid on Logical Screen.
*/
var textGridX:Int;
/**
* Y position of text grid on Logical Screen.
*/
var textGridY:Int;
/**
* Width of text grid in pixels.
*/
var textGridWidth:Int;
/**
* Height of text grid in pixels.
*/
var textGridHeight:Int;
/**
* Width of character cell in text grid.
*/
var charCellWidth:Int;
/**
* Height of character cell in text grid.
*/
var charCellHeight:Int;
/**
* Foreground/character color index.
*/
var textForegroundColorIndex:Int;
/**
* Background color index.
*/
var textBackgroundColorIndex:Int;
/**
* Text to render.
*/
var text:String;
}
/**
* Logical screen descriptor of GIF file.
* Contains very basic information about Gif.
*/
typedef LogicalScreenDescriptor =
{
/**
* Width of GIF image in pixels
*/
var width:Int;
/**
* Height of GIF image in pixels
*/
var height:Int;
/**
* Is this file uses global color table?
*/
var hasGlobalColorTable:Bool;
/**
* Specification:
* Number of bits per primary color available
to the original image, minus 1. This value represents the size of
the entire palette from which the colors in the graphic were
selected, not the number of colors actually used in the graphic.
For example, if the value in this field is 3, then the palette of
the original image had 4 bits per primary color available to create
the image. This value should be set to indicate the richness of
the original palette, even if not every color from the whole
palette is available on the source machine.
*/
var colorResolution:Int;
/**
* Specification:
* Indicates whether the Global Color Table is sorted.
If the flag is set, the Global Color Table is sorted, in order of
decreasing importance. Typically, the order would be decreasing
frequency, with most frequent color first. This assists a decoder,
with fewer available colors, in choosing the best subset of colors;
the decoder may use an initial segment of the table to render the
graphic.
*/
var sorted:Bool;
/**
* Size of global color table.
*/
var globalColorTableSize:Int;
/**
* Background color index in global color table
*/
var backgroundColorIndex:Int;
/**
* Factor used to compute an approximation of the aspect ratio of the pixel in the original image.
*/
var pixelAspectRatio:Float;
}
/**
* Version of Gif file.
* The only 2 official versions is GIF87a and GIF89a.
*/
enum Version
{
/**
* First version of Gif file format from May 1987.
*
* Note: The checking of unsupported blocks disabled by default to save some time. To enable supported blocks check set `yagp_strict_version_check` debug variable.
*/
GIF87a;
/**
* Second and actual version of Gif file format from July 1989.
*/
GIF89a;
/**
* Unknown version of Gif file.
*/
Unknown(version:String);
}
/**
* Disposal method of GIF frame.
*/
enum DisposalMethod
{
/**
* The disposal method is unspecified. Action on demand of viewer.
*
* Mostly interpreted as NO_ACTION.
*/
UNSPECIFIED;
/**
* No action required.
*/
NO_ACTION;
/**
* Fill frame rectangle with background color.
*
* Usage note:
* Most renderers clears to transparency instead of filling background color, when frame's transparent color index not equals to background color index.
*/
FILL_BACKGROUND;
/**
* Render previous state of gif as it before rendering disposing frame.
*/
RENDER_PREVIOUS;
/**
* Reserved disposal methods.
*/
UNDEFINED(index:Int);
}

View File

@ -0,0 +1,359 @@
package iron.format.gif;
/*
* No copyright asserted on the source code of this class. May be used
* for any purpose.
*
* Original code by Kevin Weiner, FM Software.
* Adapted by Thomas Hourdel (https://github.com/Chman/Moments)
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
*/
import haxe.io.UInt8Array;
import haxe.io.BytesOutput;
@:enum abstract GifRepeat(Int)
from Int to Int {
var None = 0;
var Infinite = -1;
}
@:enum abstract GifQuality(Int)
from Int to Int {
var Best = 1;
var VeryHigh = 10;
var QuiteHigh = 20;
var High = 35;
var Mid = 50;
var Low = 65;
var QuiteLow = 80;
var VeryLow = 90;
var Worst = 100;
}
class GifEncoder {
var width: Int;
var height: Int;
var framerate: Float = 24; // used if frame.delay < 0
var repeat: Int = -1; // -1: infinite, 0: none, >0: repeat count
var colorDepth: Int = 8; // Number of bit planes
var paletteSize: Int = 7; // Color table size (bits-1)
var sampleInterval: Int = 10; // Default sample interval for quantizer
//caches
var pixels: UInt8Array;
var indexedPixels: UInt8Array; // Converted frame indexed to palette
var colorTab: UInt8Array; // RGB palette
var usedEntry: Array<Bool>; // Active palette entries
//
var nq: NeuQuant;
var lzwEncoder: LzwEncoder;
//internal
var started: Bool = false;
var first_frame: Bool = true;
//:todo: error handling could be better - but throw inside of another thread on cpp is too quiet
/** Allows a custom print handler for error messages.
Defaults to Sys.println on sys targets, and trace otherwise. */
public var print: Dynamic->Void;
// Public API
/** Construct a gif encoder with options:
frame width/height:
Default is 0, required
framerate:
This is used if an added frame has a delay that is negative.
repeat:
Default is 0 (no repeat); -1 means play indefinitely.
Use GifRepeat for clarity
quality:
Sets quality of color quantization (conversion of images to
the maximum 256 colors allowed by the GIF specification). Lower values (minimum = 1)
produce better colors, but slow processing significantly. Higher values will speed
up the quantization pass at the cost of lower image quality (maximum = 100). */
public function new(
_frame_width:Int,
_frame_height:Int,
_framerate:Float,
_repeat:Int = GifRepeat.Infinite,
_quality:Int = 10
) {
#if sys
print = Sys.println;
#else
print = function(v) { trace(v); }
#end
width = _frame_width;
height = _frame_height;
framerate = _framerate;
repeat = _repeat;
sampleInterval = Std.int(clamp(_quality, 1, 100));
usedEntry = [for (i in 0...256) false];
pixels = new UInt8Array(width * height * 3);
indexedPixels = new UInt8Array(width * height);
nq = new NeuQuant();
lzwEncoder = new LzwEncoder();
} //new
public function start(output:BytesOutput) : Void {
if(output == null) {
print("gif: start() output must not be null.");
return;
}
output.writeString("GIF89a");
write_LSD(output);
started = true;
} //start
public function add(output:BytesOutput, frame:GifFrame) : Void {
if(output == null) {
print("gif: add() output must not be null.");
return;
}
if(!started) {
print("gif: add() requires start to be called before adding frames.");
return;
}
var pixels = get_pixels(frame);
analyze(pixels);
if(first_frame) {
write_palette(output);
if(repeat != GifRepeat.None) {
write_NetscapeExt(output);
}
first_frame = false;
} //first_frame
var delay = if(frame.delay < 0) {
1.0/framerate;
} else {
frame.delay;
}
write_GraphicControlExt(output, delay);
write_image_desc(output, first_frame);
if(!first_frame) {
write_palette(output);
}
write_pixels(output);
} //add
public function commit(output:BytesOutput) : Void {
if(output == null) {
print("gif: commit() output must be not null.");
return;
}
if(!started) {
print("gif: commit() called without start() being called first.");
return;
}
output.writeByte(0x3b); // Gif trailer
output.flush();
output.close();
started = false;
first_frame = true;
} //commit
//helpers
function get_pixels(frame:GifFrame):UInt8Array {
//if not flipped we can use the data as is
if (!frame.flippedY) return frame.data;
//otherwise flip it, and return the cached array
var stride = width * 3;
for(y in 0...height) {
var begin = (height - 1 - y) * stride;
pixels.view.buffer.blit(y * stride, frame.data.view.buffer, begin, stride);
}
return pixels;
} //get_pixels
function analyze(pixels:UInt8Array) {
// Create reduced palette
nq.reset(pixels, pixels.length, sampleInterval);
colorTab = nq.process();
// Map image pixels to new palette
var k:Int = 0;
for (i in 0...(width * height)) {
var r = pixels[k++] & 0xff;
var g = pixels[k++] & 0xff;
var b = pixels[k++] & 0xff;
var index = nq.map(r, g,b);
usedEntry[index] = true;
indexedPixels[i] = index;
}
} //analyze
//writers
//
/** Writes Logical Screen Descriptor. */
function write_LSD(output:BytesOutput) {
//
// Logical screen size
output.writeInt16(width);
output.writeInt16(height);
// Packed fields
output.writeByte(0x80 | // 1 : global color table flag = 1 (gct used)
0x70 | // 2-4 : color resolution = 7
0x00 | // 5 : gct sort flag = 0
paletteSize); // 6-8 : gct size
output.writeByte(0); // Background color index
output.writeByte(0); // Pixel aspect ratio - assume 1:1
} //write_LSD
/** Writes Netscape application extension to define repeat count. */
function write_NetscapeExt(output:BytesOutput):Void {
var repeats = repeat;
if(repeats == GifRepeat.Infinite || repeats < 0) repeats = 0;
if(repeats == GifRepeat.None) repeats = -1;
output.writeByte(0x21); // Extension introducer
output.writeByte(0xff); // App extension label
output.writeByte(11); // Block size
output.writeString("NETSCAPE" + "2.0"); // App id + auth code
output.writeByte(3); // Sub-block size
output.writeByte(1); // Loop sub-block id
output.writeInt16(repeats); // Loop count (extra iterations, 0=repeat forever)
output.writeByte(0); // Block terminator
} //write_NetscapeExt
/** Write color table. */
function write_palette(output:BytesOutput):Void {
output.write(colorTab.view.buffer);
var n:Int = (3 * 256) - colorTab.length;
for (i in 0...n) {
output.writeByte(0);
}
} //write_palette
/** Encodes and writes pixel data. */
function write_pixels(output:BytesOutput):Void {
lzwEncoder.reset(indexedPixels, colorDepth);
lzwEncoder.encode(output);
} //write_pixels
/** Writes Image Descriptor. */
function write_image_desc(output:BytesOutput, first:Bool):Void {
output.writeByte(0x2c); // Image separator
output.writeInt16(0); // Image position x = 0
output.writeInt16(0); // Image position y = 0
output.writeInt16(width); // Image width
output.writeInt16(height); // Image height
//Write LCT, or GCT
if(first) {
output.writeByte(0); // No LCT - GCT is used for first (or only) frame
} else {
output.writeByte(0x80 | // 1 local color table 1=yes
0 | // 2 interlace - 0=no
0 | // 3 sorted - 0=no
0 | // 4-5 reserved
paletteSize); // 6-8 size of color table
} //else
} //write_image_desc
/** Writes Graphic Control Extension. Delay is in seconds, floored and converted to 1/100 of a second */
function write_GraphicControlExt(output:BytesOutput, delay:Float):Void {
output.writeByte(0x21); // Extension introducer
output.writeByte(0xf9); // GCE label
output.writeByte(4); // data block size
// Packed fields
output.writeByte(0 | // 1:3 reserved
0 | // 4:6 disposal
0 | // 7 user input - 0 = none
0 ); // 8 transparency flag
//convert to 1/100 sec
var delay_val = Math.floor(delay * 100);
output.writeInt16(delay_val); // Delay x 1/100 sec
output.writeByte(0); // Transparent color index
output.writeByte(0); // Block terminator
} //write_GraphicControlExt
/** Clamp a value between a and b and return the clamped version */
static inline public function clamp(value:Float, a:Float, b:Float):Float
{
return ( value < a ) ? a : ( ( value > b ) ? b : value );
}
} //GifEncoder
typedef GifFrame = {
/** Delay of the frame in seconds. This value gets floored
when encoded due to gif format requirements. If this value is negative,
the default encoder frame rate will be used. */
var delay: Float;
/** Whether or not this frame should be flipped on the Y axis */
var flippedY: Bool;
/** Pixels data in unsigned bytes, rgb format */
var data: UInt8Array;
}

View File

@ -0,0 +1,350 @@
 package iron.format.gif;
/*
* No copyright asserted on the source code of this class. May be used
* for any purpose, however, refer to the Unisys LZW patent for restrictions
* on use of the associated LZWEncoder class :
*
* The Unisys patent expired on 20 June 2003 in the USA, in Europe it expired
* on 18 June 2004, in Japan the patent expired on 20 June 2004 and in Canada
* it expired on 7 July 2004. The U.S. IBM patent expired 11 August 2006, The
* Software Freedom Law Center says that after 1 October 2006, there will be
* no significant patent claims interfering with employment of the GIF format.
*
* Original code by Kevin Weiner, FM Software.
* Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
*
*/
import haxe.io.Int32Array;
import haxe.io.UInt8Array;
class LzwEncoder {
static var EOF(default, never):Int = -1;
var pixAry:UInt8Array;
var initCodeSize:Int;
var curPixel:Int;
// GIFCOMPR.C - GIF Image compression routines
//
// Lempel-Ziv compression based on 'compress'. GIF modifications by
// David Rowley (mgardi@watdcsu.waterloo.edu)
// General DEFINEs
static var BITS(default, never):Int = 12;
static var HSIZE(default, never):Int = 5003; // 80% occupancy
// GIF Image compression - modified 'compress'
//
// Based on: compress.c - File compression ala IEEE Computer, June 1984.
//
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
// Jim McKie (decvax!mcvax!jim)
// Steve Davies (decvax!vax135!petsd!peora!srd)
// Ken Turkowski (decvax!decwrl!turtlevax!ken)
// James A. Woods (decvax!ihnp4!ames!jaw)
// Joe Orost (decvax!vax135!petsd!joe)
var n_bits:Int; // number of bits/code
var maxbits:Int = BITS; // user settable max # bits/code
var maxcode:Int; // maximum code, given n_bits
var maxmaxcode:Int = 1 << BITS; // should NEVER generate this code
var htab:Int32Array;
var codetab:Int32Array;
var hsize:Int = HSIZE; // for dynamic table sizing
var free_ent:Int = 0; // first unused entry
// block compression parameters -- after all codes are used up,
// and compression rate changes, start over.
var clear_flg:Bool = false;
// Algorithm: use open addressing double hashing (no chaining) on the
// prefix code / next character combination. We do a variant of Knuth's
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
// secondary probe. Here, the modular division first probe is gives way
// to a faster exclusive-or manipulation. Also do block compression with
// an adaptive reset, whereby the code table is cleared when the compression
// ratio decreases, but after the table fills. The variable-length output
// codes are re-sized at this point, and a special CLEAR code is generated
// for the decompressor. Late addition: construct the table according to
// file size for noticeable speed improvement on small files. Please direct
// questions about this implementation to ames!jaw.
var g_init_bits:Int;
var ClearCode:Int;
var EOFCode:Int;
// output
//
// output the given code.
// Inputs:
// code: A n_bits-bit integer. If == -1, then EOF. This assumes
// that n_bits =< wordsize - 1.
// outputs:
// outputs code to the file.
// Assumptions:
// Chars are 8 bits long.
// Algorithm:
// Maintain a BITS character long buffer (so that 8 codes will
// fit in it exactly). Use the VAX insv instruction to insert each
// code in turn. When the buffer fills up empty it and start over.
var cur_accum:Int = 0;
var cur_bits:Int = 0;
var masks:Array<Int> =
[
0x0000,
0x0001,
0x0003,
0x0007,
0x000F,
0x001F,
0x003F,
0x007F,
0x00FF,
0x01FF,
0x03FF,
0x07FF,
0x0FFF,
0x1FFF,
0x3FFF,
0x7FFF,
0xFFFF ];
// Number of characters so far in this 'packet'
var a_count:Int;
// Define the storage for the packet accumulator
var accum:UInt8Array;
//----------------------------------------------------------------------------
public function new()
{
htab = new Int32Array(HSIZE);
codetab = new Int32Array(HSIZE);
accum = new UInt8Array(256);
}
//Reset the encoder to new pixel data and default values
public function reset(pixels:UInt8Array, color_depth:Int) { //width and height used to be passed in though they were never used
pixAry = pixels;
initCodeSize = Std.int(Math.max(2, color_depth));
maxbits = BITS;
maxmaxcode = 1 << BITS;
hsize = HSIZE;
free_ent = 0;
clear_flg = false;
cur_accum = 0;
cur_bits = 0;
}
// add a character to the end of the current packet, and if it is 254
// characters, flush the packet to disk.
function add(c:UInt, out:haxe.io.Output):Void
{
accum[a_count++] = c;
if (a_count >= 254)
flush(out);
}
// Clear out the hash table
// table clear for block compress
function clearTable(out:haxe.io.Output):Void
{
resetCodeTable(hsize);
free_ent = ClearCode + 2;
clear_flg = true;
output(ClearCode, out);
}
// reset code table
function resetCodeTable(hsize:Int):Void
{
for (i in 0...hsize)
htab[i] = -1;
}
function compress(init_bits:Int, out:haxe.io.Output):Void
{
var fcode:Int;
var i:Int /* = 0 */;
var c:Int;
var ent:Int;
var disp:Int;
var hsize_reg:Int;
var hshift:Int;
// Set up the globals: g_init_bits - initial number of bits
g_init_bits = init_bits;
// Set up the necessary values
clear_flg = false;
n_bits = g_init_bits;
maxcode = maxCode(n_bits);
ClearCode = 1 << (init_bits - 1);
EOFCode = ClearCode + 1;
free_ent = ClearCode + 2;
a_count = 0; // clear packet
ent = nextPixel();
hshift = 0;
fcode = hsize;
while (fcode < 65536) {
++hshift;
fcode *= 2;
}
hshift = 8 - hshift; // set hash code range bound
hsize_reg = hsize;
resetCodeTable(hsize_reg); // clear hash table
output(ClearCode, out);
while ((c = nextPixel()) != EOF)
{
fcode = (c << maxbits) + ent;
i = (c << hshift) ^ ent; // xor hashing
if (htab[i] == fcode)
{
ent = codetab[i];
continue;
}
else if (htab[i] >= 0) // non-empty slot
{
disp = hsize_reg - i; // secondary hash (after G. Knott)
if (i == 0)
disp = 1;
do
{
if ((i -= disp) < 0)
i += hsize_reg;
if (htab[i] == fcode)
{
ent = codetab[i];
break;
}
} while (htab[i] >= 0);
if (htab[i] == fcode) continue;
}
output(ent, out);
ent = c;
if (free_ent < maxmaxcode)
{
codetab[i] = free_ent++; // code -> hashtable
htab[i] = fcode;
}
else
clearTable(out);
}
// Put out the final code.
output(ent, out);
output(EOFCode, out);
}
//----------------------------------------------------------------------------
public function encode(os:haxe.io.Output):Void
{
os.writeByte( initCodeSize ); // write "initial code size" byte
curPixel = 0;
compress(initCodeSize + 1, os); // compress and write the pixel data
os.writeByte(0); // write block terminator
}
// flush the packet to disk, and reset the accumulator
function flush(out:haxe.io.Output):Void
{
if (a_count > 0)
{
out.writeByte(a_count);
out.writeBytes(accum.view.buffer, 0, a_count);
a_count = 0;
}
}
inline function maxCode(n_bits:Int):Int
{
return (1 << n_bits) - 1;
}
//----------------------------------------------------------------------------
// Return the next pixel from the image
//----------------------------------------------------------------------------
function nextPixel():Int
{
if (curPixel == pixAry.length)
return EOF;
curPixel++;
return pixAry[curPixel - 1] & 0xff;
}
function output(code:Int, out:haxe.io.Output):Void
{
cur_accum &= masks[cur_bits];
if (cur_bits > 0)
cur_accum |= (code << cur_bits);
else
cur_accum = code;
cur_bits += n_bits;
while (cur_bits >= 8)
{
add(cur_accum & 0xff, out);
cur_accum >>= 8;
cur_bits -= 8;
}
// If the next entry is going to be too big for the code size,
// then increase it, if possible.
if (free_ent > maxcode || clear_flg)
{
if (clear_flg)
{
maxcode = maxCode(n_bits = g_init_bits);
clear_flg = false;
}
else
{
++n_bits;
if (n_bits == maxbits)
maxcode = maxmaxcode;
else
maxcode = maxCode(n_bits);
}
}
if (code == EOFCode)
{
// At EOF, write the rest of the buffer.
while (cur_bits > 0)
{
add(cur_accum & 0xff, out);
cur_accum >>= 8;
cur_bits -= 8;
}
flush(out);
}
}
}

View File

@ -0,0 +1,541 @@
package iron.format.gif;
/*
* Copyright (c) 1994 Anthony Dekker
* Ported to Java by Kevin Weiner, FM Software
* Ported to Haxe by Tilman Schmidt and Sven Bergstr├╢m
*
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
* See "Kohonen neural networks for optimal colour quantization"
* in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
* for a discussion of the algorithm.
*
* Any party obtaining a copy of these files from the author, directly or
* indirectly, is granted, free of charge, a full and unrestricted irrevocable,
* world-wide, paid up, royalty-free, nonexclusive right and license to deal
* in this software and documentation files (the "Software"), including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons who receive
* copies from any such party to do so, with the only requirement being
* that this copyright notice remain intact.
*
*/
import haxe.io.Int32Array;
import haxe.io.UInt8Array;
class NeuQuant {
inline static var netsize : Int = 256; // Number of colours used
// Four primes near 500 - assume no image has a length so large that it is divisible by all four primes
inline static var prime1 : Int = 499;
inline static var prime2 : Int = 491;
inline static var prime3 : Int = 487;
inline static var prime4 : Int = 503;
inline static var minpicturebytes : Int = (3 * prime4); // Minimum size for input image
// Network Definitions
inline static var netbiasshift : Int = 4; // Bias for colour values
inline static var ncycles : Int = 100; // No. of learning cycles
// Defs for freq and bias
inline static var intbiasshift : Int = 16; // Bias for fractions
inline static var intbias : Int = (1 << intbiasshift);
inline static var gammashift : Int = 10; // Gamma = 1024
inline static var gamma : Int = (1 << gammashift);
inline static var betashift : Int = 10;
inline static var beta : Int = (intbias >> betashift); // Beta = 1/1024
inline static var betagamma : Int = (intbias << (gammashift - betashift));
// Defs for decreasing radius factor
inline static var initrad : Int = (netsize >> 3); // For 256 cols, radius starts
inline static var radiusbiasshift : Int = 6; // At 32.0 biased by 6 bits
inline static var radiusbias : Int = (1 << radiusbiasshift);
inline static var initradius : Int = (initrad * radiusbias); // And decreases by a
inline static var radiusdec : Int = 30; // Factor of 1/30 each cycle
// Defs for decreasing alpha factor
inline static var alphabiasshift : Int = 10; /* alpha starts at 1.0 */
inline static var initalpha : Int = (1 << alphabiasshift);
// Radbias and alpharadbias used for radpower calculation
inline static var radbiasshift : Int = 8;
inline static var radbias : Int = (1 << radbiasshift);
inline static var alpharadbshift : Int = (alphabiasshift + radbiasshift);
inline static var alpharadbias : Int = (1 << alpharadbshift);
var alphadec:Int; // Biased by 10 bits
// Types and Global Variables
var thepicture: UInt8Array; // The input image itself
var lengthcount: Int; // Lengthcount = H*W*3
var samplefac: Int; // Sampling factor 1..30
var network: Int32Array; // The network itself - [netsize][4]
var netindex: Int32Array; // For network lookup - really 256
var bias: Int32Array; // Bias array for learning
var freq: Int32Array; // Frequency array for learning
var radpower: Int32Array; // Radpower for precomputation
var colormap_map: UInt8Array; // Cached color map array
var colormap_index: Int32Array; // Cached color map index
public function new()
{
netindex = new Int32Array(256);
bias = new Int32Array(netsize);
freq = new Int32Array(netsize);
radpower = new Int32Array(initrad);
network = new Int32Array(netsize * 4);
colormap_map = new UInt8Array(3 * netsize);
colormap_index = new Int32Array(netsize);
}
// Reset network in range (0,0,0) to (255,255,255) and set parameters
public function reset(thepic:UInt8Array, len:Int, sample:Int):Void {
thepicture = thepic;
lengthcount = len;
samplefac = sample;
for (i in 0...netsize) {
network[i*4 + 0] = network[i*4 + 1] = network[i*4 + 2] = Std.int((i << (netbiasshift + 8)) / netsize);
freq[i] = Std.int(intbias / netsize); // 1 / netsize
bias[i] = 0; // allocated to zero?
}
}
public function colormap():UInt8Array
{
for(i in 0...netsize) {
colormap_index[network[i * 4 + 3]] = i;
}
var k:Int = 0;
for (i in 0...netsize)
{
var j = colormap_index[i];
colormap_map[k++] = network[j * 4];
colormap_map[k++] = network[j * 4 + 1];
colormap_map[k++] = network[j * 4 + 2];
}
return colormap_map;
}
// Insertion sort of network and building of netindex[0..255] (to do after unbias)
public function inxbuild():Void
{
var i:Int;
var j:Int;
var smallpos:Int;
var smallval:Int;
var previouscol:Int;
var startpos:Int;
previouscol = 0;
startpos = 0;
for (i in 0...netsize)
{
smallpos = i;
smallval = network[i*4 + 1]; // Index on g
// Find smallest in i..netsize-1
for (j in (i + 1)...netsize)
{
if (network[j*4 + 1] < smallval)
{
smallpos = j;
smallval = network[j*4 + 1]; // Index on g
}
}
// Swap p (i) and q (smallpos) entries
if (i != smallpos)
{
j = network[smallpos*4 + 0];
network[smallpos*4 + 0] = network[i*4 + 0];
network[i*4 + 0] = j;
j = network[smallpos*4 + 1];
network[smallpos*4 + 1] = network[i*4 + 1];
network[i*4 + 1] = j;
j = network[smallpos*4 + 2];
network[smallpos*4 + 2] = network[i*4 + 2];
network[i*4 + 2] = j;
j = network[smallpos*4 + 3];
network[smallpos*4 + 3] = network[i*4 + 3];
network[i*4 + 3] = j;
}
// Smallval entry is now in position i
if (smallval != previouscol)
{
netindex[previouscol] = (startpos + i) >> 1;
for (j in (previouscol + 1)...smallval)
netindex[j] = i;
previouscol = smallval;
startpos = i;
}
}
var maxnetpos = netsize - 1;
netindex[previouscol] = (startpos + maxnetpos) >> 1;
for (j in (previouscol + 1)...256)
netindex[j] = maxnetpos;
}
// Main learning Loop
public function learn():Void
{
var i:Int;
var j:Int;
var b:Int;
var g:Int;
var r:Int;
var radius:Int;
var rad:Int;
var alpha:Int;
var step:Int;
var delta:Int;
var samplepixels:Int;
var p:UInt8Array;
var pix:Int;
var lim:Int;
if (lengthcount < minpicturebytes)
samplefac = 1;
alphadec = 30 + Std.int((samplefac - 1) / 3);
p = thepicture;
pix = 0;
lim = lengthcount;
samplepixels = Std.int(lengthcount / (3 * samplefac));
delta = Std.int(samplepixels / ncycles);
alpha = initalpha;
radius = initradius;
rad = radius >> radiusbiasshift;
if (rad <= 1)
rad = 0;
for (i in 0...rad)
radpower[i] = Std.int(alpha * (((rad * rad - i * i) * radbias) / (rad * rad)));
if (lengthcount < minpicturebytes)
{
step = 3;
}
else if ((lengthcount % prime1) != 0)
{
step = 3 * prime1;
}
else
{
if ((lengthcount % prime2) != 0)
{
step = 3 * prime2;
}
else
{
if ((lengthcount % prime3) != 0)
step = 3 * prime3;
else
step = 3 * prime4;
}
}
i = 0;
while (i < samplepixels)
{
b = (p[pix + 0] & 0xff) << netbiasshift;
g = (p[pix + 1] & 0xff) << netbiasshift;
r = (p[pix + 2] & 0xff) << netbiasshift;
j = contest(b, g, r);
altersingle(alpha, j, b, g, r);
if (rad != 0)
alterneigh(rad, j, b, g, r); // Alter neighbours
pix += step;
if (pix >= lim)
pix -= lengthcount;
i++;
if (delta == 0)
delta = 1;
if (i % delta == 0)
{
alpha -= Std.int(alpha / alphadec);
radius -= Std.int(radius / radiusdec);
rad = radius >> radiusbiasshift;
if (rad <= 1)
rad = 0;
for (j in 0...rad)
radpower[j] = Std.int(alpha * (((rad * rad - j * j) * radbias) / (rad * rad)));
}
}
}
// Search for BGR values 0..255 (after net is unbiased) and return colour index
public function map(b:Int, g:Int, r:Int):Int
{
var i:Int;
var j:Int;
var dist:Int;
var a:Int;
var bestd:Int;
var best:Int;
bestd = 1000; // Biggest possible dist is 256*3
best = -1;
i = netindex[g]; // Index on g
j = i - 1; // Start at netindex[g] and work outwards
while ((i < netsize) || (j >= 0))
{
if (i < netsize)
{
dist = network[i*4 + 1] - g; // Inx key
if (dist >= bestd)
{
i = netsize; // Stop iter
}
else
{
if (dist < 0)
dist = -dist;
a = network[i*4 + 0] - b;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
a = network[i*4 + 2] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
bestd = dist;
best = network[i*4 + 3];
}
}
i++;
}
}
if (j >= 0)
{
dist = g - network[j*4 + 1]; // Inx key - reverse dif
if (dist >= bestd)
{
j = -1; // Stop iter
}
else
{
if (dist < 0)
dist = -dist;
a = network[j*4 + 0] - b;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
a = network[j*4 + 2] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd)
{
bestd = dist;
best = network[j*4 + 3];
}
}
j--;
}
}
}
return best;
}
public function process():UInt8Array
{
learn();
unbiasnet();
inxbuild();
return colormap();
}
// Unbias network to give byte values 0..255 and record position i to prepare for sort
public function unbiasnet():Void
{
for (i in 0...netsize)
{
network[i*4] >>= netbiasshift;
network[i*4 + 1] >>= netbiasshift;
network[i*4 + 2] >>= netbiasshift;
network[i*4 + 3] = i; // Record colour no
}
}
// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
function alterneigh(rad:Int, i:Int, b:Int, g:Int, r:Int):Void
{
var j:Int;
var k:Int;
var lo:Int;
var hi:Int;
var a:Int;
var m:Int;
lo = i - rad;
if (lo < -1)
lo = -1;
hi = i + rad;
if (hi > netsize)
hi = netsize;
j = i + 1;
k = i - 1;
m = 1;
while ((j < hi) || (k > lo))
{
a = radpower[m++];
if (j < hi)
{
network[j * 4 + 0] -= Std.int((a * (network[j * 4 + 0] - b)) / alpharadbias);
network[j * 4 + 1] -= Std.int((a * (network[j * 4 + 1] - g)) / alpharadbias);
network[j * 4 + 2] -= Std.int((a * (network[j * 4 + 2] - r)) / alpharadbias);
j++;
}
if (k > lo)
{
network[k * 4 + 0] -= Std.int((a * (network[k * 4 + 0] - b)) / alpharadbias);
network[k * 4 + 1] -= Std.int((a * (network[k * 4 + 1] - g)) / alpharadbias);
network[k * 4 + 2] -= Std.int((a * (network[k * 4 + 2] - r)) / alpharadbias);
k--;
}
}
}
// Move neuron i towards biased (b,g,r) by factor alpha
function altersingle(alpha:Int, i:Int, b:Int, g:Int, r:Int):Void
{
/* Alter hit neuron */
network[i*4 + 0] -= Std.int((alpha * (network[i*4 + 0] - b)) / initalpha);
network[i*4 + 1] -= Std.int((alpha * (network[i*4 + 1] - g)) / initalpha);
network[i*4 + 2] -= Std.int((alpha * (network[i*4 + 2] - r)) / initalpha);
}
inline function make_abs(value:Int) : Int {
var tmp = value >> 31;
value ^= tmp;
value += tmp & 1;
return value;
}
// Search for biased BGR values
static inline var bestd_init = ~(1 << 31);
function contest(b:Int, g:Int, r:Int):Int
{
// Finds closest neuron (min dist) and updates freq
// Finds best neuron (min dist-bias) and returns position
// For frequently chosen neurons, freq[i] is high and bias[i] is negative
// bias[i] = gamma*((1/netsize)-freq[i])
var i:Int;
var dist:Int;
var a:Int;
var biasdist:Int;
var betafreq:Int;
var bestpos:Int;
var bestbiaspos:Int;
var bestd:Int;
var bestbiasd:Int;
bestd = bestd_init;
bestbiasd = bestd;
bestpos = -1;
bestbiaspos = bestpos;
for (i in 0...netsize)
{
var i_n = i * 4;
var b_i = i_n + 0;
var g_i = i_n + 1;
var r_i = i_n + 2;
var b_a = network[b_i];
var g_a = network[g_i];
var r_a = network[r_i];
b_a = make_abs(b_a - b);
g_a = make_abs(g_a - g);
r_a = make_abs(r_a - r);
dist = b_a + g_a + r_a;
if (dist < bestd)
{
bestd = dist;
bestpos = i;
}
biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
if (biasdist < bestbiasd)
{
bestbiasd = biasdist;
bestbiaspos = i;
}
betafreq = (freq[i] >> betashift);
freq[i] -= betafreq;
bias[i] += (betafreq << gammashift);
}
freq[bestpos] += beta;
bias[bestpos] -= betagamma;
return bestbiaspos;
}
}

View File

@ -0,0 +1,346 @@
package iron.format.gif;
import iron.format.gif.Data;
import haxe.io.Bytes;
import haxe.io.BytesOutput;
import haxe.io.Input;
/**
* ...
* @author Yanrishatum
*/
class Reader
{
private var i:Input;
public function new(i:Input)
{
this.i = i;
i.bigEndian = false;
}
public function read():Data
{
for (b in [71, 73, 70])
{
if (i.readByte() != b) throw "Invalid header";
}
var gifVer:String = i.readString(3);
var version:Version = Version.GIF89a;
switch(gifVer)
{
case "87a": version = Version.GIF87a;
case "89a": version = Version.GIF89a;
default: version = Version.Unknown(gifVer);
}
// Logical screen descriptor.
var width:Int = i.readUInt16();
var height:Int = i.readUInt16();
var packedField:Int = i.readByte();
var bgIndex:Int = i.readByte();
var pixelAspectRatio:Float = i.readByte();
if (pixelAspectRatio != 0) pixelAspectRatio = (pixelAspectRatio + 15) / 64;
else pixelAspectRatio = 1;
var lsd:LogicalScreenDescriptor =
{
width: width,
height: height,
hasGlobalColorTable: (packedField & 128) == 128,
colorResolution: (packedField & 112) >>> 4,
sorted: (packedField & 8) == 8,
globalColorTableSize: 2 << (packedField & 7),
backgroundColorIndex: bgIndex,
pixelAspectRatio: pixelAspectRatio
}
var gct:ColorTable = null;
if (lsd.hasGlobalColorTable) gct = readColorTable(lsd.globalColorTableSize);
var blocks:List<Block> = new List();
while (true)
{
var b:Block = readBlock();
blocks.add(b);
if (b == Block.BEOF) break;
}
return
{
version: version,
logicalScreenDescriptor: lsd,
globalColorTable: gct,
blocks: blocks
}
}
private function readBlock():Block
{
var blockID:Int = i.readByte();
switch(blockID)
{
case 0x2C:
// Image
return readImage();
case 0x21:
// Extension
return readExtension();
case 0x3B:
return Block.BEOF;
}
// The behaviour of taking unknown block ID is unspecified.
return Block.BEOF;
}
private function readImage():Block
{
var x:Int = i.readUInt16();
var y:Int = i.readUInt16();
var width:Int = i.readUInt16();
var height:Int = i.readUInt16();
var packed:Int = i.readByte();
var localColorTable:Bool = (packed & 128) == 128;
var interlaced:Bool = (packed & 64) == 64;
var sorted:Bool = (packed & 32) == 32;
var localColorTableSize:Int = 2 << (packed & 7);
var lct:ColorTable = null;
if (localColorTable) lct = readColorTable(localColorTableSize);
return Block.BFrame(
{
x: x,
y: y,
width: width,
height: height,
localColorTable: localColorTable,
interlaced:interlaced,
sorted:sorted,
localColorTableSize:localColorTableSize,
pixels:readPixels(width, height, interlaced),
colorTable:lct
});
}
private function readPixels(width:Int, height:Int, interlaced:Bool):Bytes
{
var input:Input = this.i;
var pixelsCount:Int = width * height;
var pixels:Bytes = Bytes.alloc(pixelsCount);
var minCodeSize:Int = input.readByte();
var blockSize:Int = input.readByte() - 1;
var bits:Int = input.readByte();
var bitsCount:Int = 8;
var clearCode:Int = 1 << minCodeSize;
var eoiCode:Int = clearCode + 1;
var codeSize:Int = minCodeSize + 1;
var codeSizeLimit:Int = 1 << codeSize;
var codeMask = codeSizeLimit - 1;
var baseDict:Array<Array<Int>> = new Array();
for (i in 0...clearCode) baseDict[i] = [i];
var dict:Array<Array<Int>> = new Array();
var dictLen:Int = clearCode + 2;
var newRecord:Array<Int>;
var i:Int = 0;
var code:Int = 0;
var last:Int;
while (i < pixelsCount)
{
last = code;
while (bitsCount < codeSize)
{
if (blockSize == 0) break;
bits |= input.readByte() << bitsCount;
bitsCount += 8;
blockSize--;
if (blockSize == 0) blockSize = input.readByte();
}
code = bits & codeMask;
bits >>= codeSize;
bitsCount -= codeSize;
if (code == clearCode)
{
dict = baseDict.copy();
dictLen = clearCode + 2;
codeSize = minCodeSize + 1;
codeSizeLimit = (1 << codeSize);
codeMask = codeSizeLimit - 1;
continue;
}
if (code == eoiCode) break;
if (code < dictLen)
{
if (last != clearCode)
{
newRecord = dict[last].copy();
newRecord.push(dict[code][0]);
dict[dictLen++] = newRecord;
}
}
else
{
if (code != dictLen) throw 'Invalid LZW code. Excepted: $dictLen, got: $code';
newRecord = dict[last].copy();
newRecord.push(newRecord[0]);
dict[dictLen++] = newRecord;
}
newRecord = dict[code];
for (item in newRecord) pixels.set(i++, item);
if (dictLen == codeSizeLimit && codeSize < 12)
{
codeSize++;
codeSizeLimit = (1 << codeSize);
codeMask = codeSizeLimit - 1;
}
}
// Just in case
while (blockSize > 0)
{
input.readByte();
blockSize--;
if (blockSize == 0) blockSize = input.readByte();
}
while (i < pixelsCount) pixels.set(i++, 0);
if (interlaced)
{
var buffer:Bytes = Bytes.alloc(pixelsCount);
var offset:Int = deinterlace(pixels, buffer, 8, 0, 0 , width, height); // Every 8 line with start at 0
offset = deinterlace(pixels, buffer, 8, 4, offset, width, height); // Every 8 line with start at 4
offset = deinterlace(pixels, buffer, 4, 2, offset, width, height); // Every 4 line with start at 2
deinterlace(pixels, buffer, 2, 1, offset, width, height); // Every 2 line with start at 1
pixels = buffer;
}
return pixels;
}
private function deinterlace(input:Bytes, output:Bytes, step:Int, y:Int, offset:Int, width:Int, height:Int):Int
{
while (y < height)
{
output.blit(y * width, input, offset, width);
offset += width;
y += step;
}
return offset;
}
private function readExtension():Block
{
var subId:Int = i.readByte();
switch(subId)
{
case 0xF9:
// Graphics Control Extension
if (i.readByte() != 4) throw "Incorrect Graphic Control Extension block size!";
var packed:Int = i.readByte();
var disposalMethod:DisposalMethod = switch ( (packed & 28) >> 2)
{
case 0: DisposalMethod.UNSPECIFIED;
case 1: DisposalMethod.NO_ACTION;
case 2: DisposalMethod.FILL_BACKGROUND;
case 3: DisposalMethod.RENDER_PREVIOUS;
default: DisposalMethod.UNDEFINED((packed & 28) >> 2);
};
var b:Block = Block.BExtension(Extension.EGraphicControl(
{
disposalMethod:disposalMethod,
userInput: (packed & 2) == 2,
hasTransparentColor: (packed & 1) == 1,
delay: i.readUInt16(),
transparentIndex: i.readByte()
}));
i.readByte(); // Terminator
return b;
case 0x01:
// Text block
// Exists only on paper, nobody ever used it.
if (i.readByte() != 12) throw "Incorrect size of Plain Text Extension introducer block.";
return Block.BExtension(Extension.EText(
{
textGridX: i.readUInt16(),
textGridY: i.readUInt16(),
textGridWidth: i.readUInt16(),
textGridHeight: i.readUInt16(),
charCellWidth: i.readByte(),
charCellHeight: i.readByte(),
textForegroundColorIndex: i.readByte(),
textBackgroundColorIndex: i.readByte(),
text: readBlocks().toString()
}));
case 0xFE:
// Commentary
return Block.BExtension(Extension.EComment(readBlocks().toString()));
case 0xFF:
// Application extension
return readApplicationExtension();
default:
return Block.BExtension(Extension.EUnknown(subId, readBlocks()));
}
}
private function readApplicationExtension():Block
{
if (i.readByte() != 11) throw "Incorrect size of Application Extension introducer block.";
var name:String = i.readString(8);
var version:String = i.readString(3);
var data:Bytes = readBlocks();
if (name == "NETSCAPE" && version == "2.0" && data.get(0) == 1)
{
return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(data.get(1) | (data.get(2) << 8))));
}
return Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AEUnknown(name, version, data)));
}
private inline function readBlocks():Bytes
{
var buffer:BytesOutput = new BytesOutput();
var bytes:Bytes = Bytes.alloc(255);
var len:Int = i.readByte();
while (len != 0)
{
i.readBytes(bytes, 0, len);
buffer.writeBytes(bytes, 0, len);
len = i.readByte();
}
buffer.flush();
bytes = buffer.getBytes();
buffer.close();
return bytes;
}
private function readColorTable(size:Int):ColorTable
{
size *= 3;
var output:ColorTable = ColorTable.alloc(size);
var c:Int = 0;
while (c < size)
{
output.set(c , i.readByte()); // R
output.set(c + 1, i.readByte()); // G
output.set(c + 2, i.readByte()); // B
c += 3;
}
return output;
}
}

View File

@ -0,0 +1,390 @@
package iron.format.gif;
import iron.format.gif.Data;
import haxe.io.Bytes;
import haxe.io.BytesData;
/**
* Tools for gif data.
* @author Yanrishatum
*/
class Tools
{
/**
* Returns amount of frames in Gif data.
*/
public static function framesCount(data:Data):Int
{
var frames:Int = 0;
for (block in data.blocks)
{
switch(block)
{
case Block.BFrame(_):
frames++;
default :
}
}
return frames;
}
/**
* Returns frame at given index.
* @param data Gif data.
* @param frameIndex Index of frame.
* @return Frame at given index or null, if there is no frame at that index.
*/
public static function frame(data:Data, frameIndex:Int):Frame
{
var counter:Int = 0;
for (block in data.blocks)
{
switch (block)
{
case Block.BFrame(frame):
if (counter == frameIndex) return frame;
counter++;
default :
}
}
return null;
}
/**
* Returns Graphic Control extension for frame at given index.
* @param data Gif data.
* @param frameIndex Index of frame.
* @return GCE extension if it is exists for given frame, null otherwise.
*/
public static function graphicControl(data:Data, frameIndex:Int):GraphicControlExtension
{
var counter:Int = 0;
var gce:GraphicControlExtension = null;
for (block in data.blocks)
{
switch (block)
{
case Block.BFrame(frame):
if (counter == frameIndex) return gce;
gce = null;
counter++;
case Block.BExtension(Extension.EGraphicControl(g)):
gce = g;
default :
}
}
return null;
}
//==========================================================
// Extracting.
//==========================================================
/**
* Extracts frame pixel data in Blue-Green-Red-Alpha pixel format.
* This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size.
* @param data Gif data.
* @param frameIndex Frame index.
* @return BGRA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null.
*/
public static function extractBGRA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
if (frameCaret == frameIndex)
{
var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4);
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var writeCaret:Int = 0;
for (i in 0...frame.pixels.length)
{
var index:Int = frame.pixels.get(i) * 3;
bytes.set(writeCaret , ct.get(index + 2)); // B
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index )); // R
if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0
else bytes.set(writeCaret + 3, 0xFF); // A = FF
writeCaret += 4;
}
return bytes;
}
frameCaret++;
gce = null;
default:
}
}
return null;
}
/**
* Extracts frame pixel data in Red-Green-Blue-Alpha pixel format.
* This function extracts only exact frame and does put previous frame pixel data into resulting Bytes. Note that frame size may not equal to Gif logical screen size.
* @param data Gif data.
* @param frameIndex Frame index.
* @return RGBA pixel data with dimensions equals to specified Frame size. If frame does not present in Gif data returns null.
*/
public static function extractRGBA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
if (frameCaret == frameIndex)
{
var bytes:Bytes = Bytes.alloc(frame.width * frame.height * 4);
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var writeCaret:Int = 0;
for (i in 0...frame.pixels.length)
{
var index:Int = frame.pixels.get(i) * 3;
bytes.set(writeCaret , ct.get(index )); // R
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index + 2)); // B
if (transparentIndex == index) bytes.set(writeCaret + 3, 0); // A = 0
else bytes.set(writeCaret + 3, 0xFF); // A = FF
writeCaret += 4;
}
return bytes;
}
frameCaret++;
gce = null;
default:
}
}
return null;
}
/**
* Extracts full Gif pixel data to specified frame in Blue-Green-Red-Alpha pixel format.
* This functions returns full representation of frame including rendering of all other frames before.
* @param data Gif data.
* @param frameIndex Frame index.
* @return BGRA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame.
*/
public static function extractFullBGRA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4);
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var pixels:Bytes = frame.pixels;
var x:Int = 0;
var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4;
var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4;
var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION;
switch (disposalMethod)
{
case DisposalMethod.RENDER_PREVIOUS:
// Do not render frame at all
case DisposalMethod.FILL_BACKGROUND:
for (i in 0...pixels.length)
{
bytes.set(writeCaret , 0); // B
bytes.set(writeCaret + 1, 0); // G
bytes.set(writeCaret + 2, 0); // R
bytes.set(writeCaret + 3, 0); // A
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
default:
for (i in 0...pixels.length)
{
var index:Int = pixels.get(i) * 3;
if (transparentIndex != index) // Render only if pixel non-transparent
{
bytes.set(writeCaret , ct.get(index + 2)); // B
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index )); // R
bytes.set(writeCaret + 3, 0xFF); // A
}
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
}
if (frameCaret == frameIndex) return bytes;
frameCaret++;
gce = null;
default:
}
}
return bytes;
}
/**
* Extracts full Gif pixel data to specified frame in Red-Green-Blue-Alpha pixel format.
* This functions returns full representation of frame including rendering of all other frames before.
* @param data Gif data.
* @param frameIndex Frame index.
* @return RGBA pixel data with dimensions equals to Gif logical screen with full pixel data of Gif image at specified frame.
*/
public static function extractFullRGBA(data:Data, frameIndex:Int):Bytes
{
var gce:GraphicControlExtension = null;
var frameCaret:Int = 0;
var bytes:Bytes = Bytes.alloc(data.logicalScreenDescriptor.width* data.logicalScreenDescriptor.height * 4);
for (block in data.blocks)
{
switch (block)
{
case Block.BExtension(ext):
switch(ext)
{
case Extension.EGraphicControl(g):
gce = g;
default:
}
case Block.BFrame(frame):
var ct:Bytes = frame.localColorTable ? frame.colorTable : data.globalColorTable;
if (ct == null) throw "Frame does not have a color table!";
var transparentIndex:Int = gce != null && gce.hasTransparentColor ? gce.transparentIndex * 3 : -1;
var pixels:Bytes = frame.pixels;
var x:Int = 0;
var writeCaret:Int = (frame.y * data.logicalScreenDescriptor.width + frame.x) * 4;
var lineSkip:Int = (data.logicalScreenDescriptor.width - frame.width) * 4 + 4;
var disposalMethod:DisposalMethod = frameCaret != frameIndex && gce != null ? gce.disposalMethod : DisposalMethod.NO_ACTION;
switch (disposalMethod)
{
case DisposalMethod.RENDER_PREVIOUS:
// Do not render frame at all
case DisposalMethod.FILL_BACKGROUND:
for (i in 0...pixels.length)
{
bytes.set(writeCaret , 0); // R
bytes.set(writeCaret + 1, 0); // G
bytes.set(writeCaret + 2, 0); // B
bytes.set(writeCaret + 3, 0); // A
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
default:
for (i in 0...pixels.length)
{
var index:Int = pixels.get(i) * 3;
if (transparentIndex != index) // Render only if pixel non-transparent
{
bytes.set(writeCaret , ct.get(index )); // R
bytes.set(writeCaret + 1, ct.get(index + 1)); // G
bytes.set(writeCaret + 2, ct.get(index + 2)); // B
bytes.set(writeCaret + 3, 0xFF); // A
}
if (++x == frame.width)
{
x = 0;
writeCaret += lineSkip;
}
else writeCaret += 4;
}
}
if (frameCaret == frameIndex) return bytes;
frameCaret++;
gce = null;
default:
}
}
return bytes;
}
/**
* Returns amount of animation repeats stored in Gif data.
* This is link to Netscape Looping application extension. If this extension does not present amount of loops equals to 1.
* @param data Gif data.
* @return Amount of animation repeats. Zero equals to infinite amount of repeats.
*/
public static function loopCount(data:Data):Int
{
for (block in data.blocks)
{
switch(block)
{
case Block.BExtension(Extension.EApplicationExtension(ApplicationExtension.AENetscapeLooping(loops))): return loops;
default :
}
}
return 1;
}
//==========================================================
// In-Dev writer tools.
//==========================================================
//public static function buildFrameFromTrueColor(pixels:Bytes, width:Int, height:Int):Void
//{
//
//}
private static var LN2:Float = Math.log(2);
@:noCompletion public static inline function log2(val:Float):Float
{
return Math.log(val) / LN2;
}
}

View File

@ -0,0 +1,525 @@
package format.gif;
import format.gif.Data;
import haxe.ds.Vector;
import haxe.io.Bytes;
import haxe.io.Output;
import haxe.io.UInt8Array;
/**
* ...
* @author Yanrishatum
*/
class Writer
{
private var o:Output;
private var lzw:LZWEncoder;
private var gctSize:Int;
public function new(o:Output)
{
this.o = o;
this.lzw = new LZWEncoder();
o.bigEndian = false;
}
/**
* Write entire Data at once.
* @param data Input gif file data
*/
public function write(data:Data):Void
{
// Header
writeHeader(data.version);
// Logical screen descriptor.
writeLogicalScreenDescriptor(data.logicalScreenDescriptor, data.globalColorTable);
for (block in data.blocks)
{
switch (block)
{
case Block.BEOF:
writeEOF();
return;
case Block.BExtension(ext):
switch (ext)
{
case Extension.EUnknown(id, bytes):
writeUnknownExtension(id, bytes);
case Extension.EComment(text):
writeComment(text);
case Extension.EText(textExt):
writeText(textExt);
case Extension.EGraphicControl(gce):
writeGraphicControl(gce);
case Extension.EApplicationExtension(appExt):
writeAppExtension(appExt);
}
case Block.BFrame(frame):
writeFrame(frame);
}
}
writeEOF(); // If we doesn't encountered EOF block - write it.
}
/**
* Writes header of Gif file. Must be first.
* @param version
*/
public function writeHeader(version:Version):Void
{
o.writeString("GIF");
switch(version)
{
case Version.GIF87a: o.writeString("87a");
case Version.GIF89a: o.writeString("89a");
case Version.Unknown(v):
if (v.length == 3) o.writeString(v);
else if (v.length > 3) o.writeString(v.substr(0, 3));
else
{
while (v.length < 3) v += "-";
o.writeString(v);
}
}
}
/**
* Writes Logical Screen Descriptor block. Must go right after header.
* @param lsd Logical Screen Descriptor object.
* @param globalColorTable Global color table. Required only if LSD contains hasGlobalColorTable flag.
* Color table must be a RGB-aligned Bytes with 3 bytes per color.
*/
public function writeLogicalScreenDescriptor(lsd:LogicalScreenDescriptor, globalColorTable:Bytes = null):Void
{
o.writeUInt16(lsd.width);
o.writeUInt16(lsd.height);
var packed:Int = 0;
if (lsd.hasGlobalColorTable) packed |= 128;
packed |= (lsd.colorResolution << 4) & 112;
if (lsd.sorted) packed |= 8;
packed |= Math.round(Tools.log2(lsd.globalColorTableSize) - 1) & 7;
o.writeByte(packed);
o.writeByte(lsd.backgroundColorIndex);
if (lsd.pixelAspectRatio == 1) o.writeByte(0);
else o.writeByte(Std.int(lsd.pixelAspectRatio) * 64 - 15);
if (lsd.hasGlobalColorTable)
{
if (globalColorTable != null)
{
o.writeBytes(globalColorTable, 0, globalColorTable.length);
gctSize = lsd.globalColorTableSize;
}
else throw "hasGlobalColorTable flag present, but there is no global color table!";
}
}
public function writeComment(text:String):Void
{
o.writeByte(0x21);
o.writeByte(0xFE);
writeStringBlocks(text);
}
public function writeText(textExt:PlainTextExtension):Void
{
o.writeByte(0x21);
o.writeByte(0x01);
o.writeByte(12);
o.writeUInt16(textExt.textGridX);
o.writeUInt16(textExt.textGridY);
o.writeUInt16(textExt.textGridWidth);
o.writeUInt16(textExt.textGridHeight);
o.writeByte(textExt.charCellWidth);
o.writeByte(textExt.charCellHeight);
o.writeByte(textExt.textForegroundColorIndex);
o.writeByte(textExt.textForegroundColorIndex);
writeStringBlocks(textExt.text);
}
public function writeGraphicControl(gce:GraphicControlExtension):Void
{
o.writeByte(0x21);
o.writeByte(0xF9);
o.writeByte(4);
var packed:Int = 0;
switch (gce.disposalMethod)
{
case DisposalMethod.UNSPECIFIED: // 0
case DisposalMethod.NO_ACTION: packed |= 4;
case DisposalMethod.FILL_BACKGROUND: packed |= 8;
case DisposalMethod.RENDER_PREVIOUS: packed |= 12;
case DisposalMethod.UNDEFINED(idx): packed |= (idx & 7) << 2;
}
if (gce.userInput) packed |= 2;
if (gce.hasTransparentColor) packed |= 1;
o.writeByte(packed);
o.writeUInt16(gce.delay);
o.writeByte(gce.transparentIndex);
o.writeByte(0); // Terminator
}
public function writeAppExtension(appExt:ApplicationExtension):Void
{
o.writeByte(0x21);
o.writeByte(0xFF);
o.writeByte(11);
switch (appExt)
{
case ApplicationExtension.AENetscapeLooping(loops):
o.writeString("NETSCAPE2.0");
o.writeByte(3);
o.writeByte(1); // Looping
o.writeUInt16(loops);
o.writeByte(0);
case ApplicationExtension.AEUnknown(name, version, bytes):
o.writeString(name);
o.writeString(version);
writeBlocks(bytes);
}
}
public function writeUnknownExtension(id:Int, bytes:Bytes):Void
{
o.writeByte(0x21);
o.writeByte(id);
writeBlocks(bytes);
}
public function writeFrame(frame:Frame):Void
{
o.writeByte(0x2C);
o.writeUInt16(frame.x);
o.writeUInt16(frame.y);
o.writeUInt16(frame.width);
o.writeUInt16(frame.height);
var packed:Int = 0;
if (frame.localColorTable) packed |= 128;
if (frame.interlaced) packed |= 64;
if (frame.sorted) packed |= 32;
packed |= Math.round(Tools.log2(frame.localColorTableSize) - 1) & 7;
o.writeByte(packed);
if (frame.localColorTable)
{
if (frame.colorTable != null) o.writeBytes(frame.colorTable, 0, frame.colorTable.length);
else throw "localColorTable flag is set, but there is no local color table!";
}
lzw.encode(frame.width, frame.height, frame.pixels, frame.localColorTable ? frame.localColorTableSize : gctSize, o, frame.interlaced);
}
/**
* Writes EndOfFile block.
*/
public function writeEOF():Void
{
o.writeByte(0x3B);
}
private function writeStringBlocks(text:String):Void
{
var len:Int;
var caret:Int = 0;
while (caret < text.length)
{
len = text.length - caret;
if (len > 0xFF) len = 0xFF;
o.writeByte(len);
for (i in 0...len) o.writeByte(text.charCodeAt(i + caret));
caret += len;
}
o.writeByte(0);
}
private function writeBlocks(bytes:Bytes):Void
{
var len:Int;
var caret:Int = 0;
while (caret < bytes.length)
{
len = bytes.length - caret;
if (len > 0xFF) len = 0xFF;
o.writeByte(len);
o.writeBytes(bytes, caret, len);
caret += 0xFF;
}
o.writeByte(0); // Terminator
}
}
class LZWEncoder
{
private var EOF:Int = -1;
private static inline var BITS:Int = 12;
private static inline var HSIZE:Int = 5003;
private var masks:Array<Int> = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF];
private var out:Output;
private var bits:Int;
private var bitsCount:Int;
private var minCodeSize:Int;
private var codeSize:Int;
private var codeSizeLimit:Int;
private var clearFlag:Bool;
private var clearCode:Int;
private var eofCode:Int;
// Dict
private var htab:Vector<Int>;
private var codetab:Vector<Int>;
private var freeEnt:Int;
// Block buffer
private var blockBuffer:Bytes;
private var blockBufferCaret:Int;
// Input data
private var pixels:Bytes;
private var width:Int;
private var height:Int;
private var remaining:Int;
// Non-interlaced
private var pixelsCaret:Int;
// Interlaced
private var interlaced:Bool;
private var pixelsX:Int;
private var pixelsY:Int;
private var interlacingStage:Int;
private var interlacingStep:Int;
public function new()
{
blockBuffer = Bytes.alloc(256);
}
public function encode(width:Int, height:Int, pixels:Bytes, colorsCount:Int, out:Output, interlaced:Bool):Void
{
minCodeSize = Math.round(Tools.log2(colorsCount));
this.pixels = pixels;
this.width = width;
this.height = height;
this.out = out;
htab = new Vector(HSIZE);
codetab = new Vector(HSIZE);
blockBufferCaret = 0;
bits = 0;
bitsCount = 0;
clearCode = 1 << minCodeSize;
eofCode = clearCode + 1;
freeEnt = clearCode + 2;
out.writeByte(minCodeSize);
remaining = width * height;
this.interlaced = interlaced;
if (interlaced)
{
pixelsX = 0;
pixelsY = 0;
interlacingStage = 0;
interlacingStep = 8;
}
else pixelsCaret = 0;
compress();
out.writeByte(0);
}
private function char_out(c:Int):Void
{
blockBuffer.set(blockBufferCaret++, c);
if (blockBufferCaret >= 254) flush_char();
}
private function cl_block():Void
{
cl_hash(HSIZE);
freeEnt = clearCode + 2;
clearFlag = true;
output(clearCode);
}
private function cl_hash(hsize:Int):Void
{
for (i in 0...hsize) htab[i] = -1;
}
private function compress():Void
{
var disp:Int;
var i:Int;
clearFlag = false;
codeSize = minCodeSize + 1;
codeSizeLimit = MAXCODE(codeSize);
var ent:Int = nextPixel();
var hshift:Int = 0;
var fcode:Int = HSIZE;
while (fcode < 65536)
{
++hshift;
fcode *= 2;
}
hshift = 8 - hshift;
var hsize_reg:Int = HSIZE;
cl_hash(hsize_reg);
output(clearCode);
var c:Int;
while ((c = nextPixel()) != EOF)
{
fcode = (c << BITS) + ent;
i = (c << hshift) ^ ent;
if (htab[i] == fcode)
{
ent = codetab[i];
continue;
}
else if (htab[i] >= 0)
{
disp = hsize_reg - i;
if (i == 0) disp = 1;
var skip:Bool = false;
do
{
if ((i -= disp) < 0) i += hsize_reg;
if (htab[i] == fcode)
{
ent = codetab[i];
skip = true;
break;
}
}
while (htab[i] >= 0);
if (skip) continue;
}
output(ent);
ent = c;
if (freeEnt < (1 << BITS))
{
codetab[i] = freeEnt++;
htab[i] = fcode;
}
else
{
cl_block();
}
}
output(ent);
output(eofCode);
}
private function flush_char():Void
{
if (blockBufferCaret > 0)
{
out.writeByte(blockBufferCaret);
out.writeBytes(blockBuffer, 0, blockBufferCaret);
blockBufferCaret = 0;
}
}
private inline function MAXCODE(n_bits:Int):Int
{
return (1 << n_bits) - 1;
}
private function nextPixel():Int
{
if (remaining == 0) return EOF;
remaining--;
if (interlaced)
{
if (++pixelsX == width)
{
pixelsX = 0;
pixelsY += interlacingStep;
if (pixelsY >= height)
{
switch (interlacingStage)
{
// first: Every 8 line with start at 0
case 0: pixelsY = 4; // Every 8 line with start at 4
case 1: pixelsY = 2; interlacingStep = 4; // Every 4 line with start at 2
case 2: pixelsY = 1; interlacingStep = 2; // Every 2 line with start at 1
default: return -1; // EOF
}
interlacingStage++;
}
}
return pixels.get(pixelsY * width + pixelsX);
}
else
{
return pixels.get(pixelsCaret++);
}
}
private function output(code:Int):Void
{
bits &= masks[bitsCount];
if (bitsCount > 0) bits |= (code << bitsCount);
else bits = code;
bitsCount += codeSize;
while (bitsCount >= 8)
{
char_out(bits & 0xFF);
bits >>= 8;
bitsCount -= 8;
}
if (freeEnt > codeSizeLimit || clearFlag)
{
if (clearFlag)
{
codeSizeLimit = MAXCODE(codeSize = minCodeSize + 1);
clearFlag = false;
}
else
{
codeSize++;
if (codeSize == BITS) codeSizeLimit = 1 << BITS;
else codeSizeLimit = MAXCODE(codeSize);
}
}
if (code == eofCode)
{
while (bitsCount > 0)
{
char_out(bits & 0xFF);
bits >>= 8;
bitsCount -= 8;
}
flush_char();
}
}
}

View File

@ -0,0 +1,37 @@
/*
* format - Haxe File Formats
*
* JPG 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.jpg;
typedef Data = {
var width : Int;
var height : Int;
var quality : Float;
var pixels : haxe.io.Bytes;
}

View File

@ -0,0 +1,657 @@
package iron.format.jpg;
class Writer {
var ZigZag: Array<Int>;
// Static table initialization
function initZigZag() {
ZigZag = [
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
];
}
var YTable: Array<Int>;
var UVTable: Array<Int>;
var fdtbl_Y: Array<Float>;
var fdtbl_UV: Array<Float>;
function initQuantTables(sf: Int) {
var YQT: Array<Int> = [
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99
];
for (i in 0...64) {
var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 );
if( t < 1 ) t = 1;
else if( t > 255 ) t = 255;
YTable[ ZigZag[i] ] = t;
}
var UVQT: Array<Int> = [
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
];
for( j in 0...64 ) {
var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 );
if( u < 1 ) u = 1;
else if( u > 255 ) u = 255;
UVTable[ ZigZag[j] ] = u;
}
var aasf: Array<Float> = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
];
var k = 0;
for( row in 0...8 ) {
for( col in 0...8 ) {
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
k++;
}
}
}
var std_dc_luminance_nrcodes: Array<Int>;
var std_dc_luminance_values: haxe.io.Bytes;
var std_ac_luminance_nrcodes: Array<Int>;
var std_ac_luminance_values: haxe.io.Bytes;
function initLuminance() {
std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
std_ac_luminance_values = strIntsToBytes(
'0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' +
'0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' +
'0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' +
'0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' +
'0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' +
'0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' +
'0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' +
'0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' +
'0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' +
'0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' +
'0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' +
'0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' +
'0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' +
'0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' +
'0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' +
'0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' +
'0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' +
'0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' +
'0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' +
'0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
'0xf9,0xfa'
);
}
function strIntsToBytes( s: String ) {
var len = s.length;
var b = new haxe.io.BytesBuffer();
var val = 0;
var i = 0;
for( j in 0...len ) {
if( s.charAt( j ) == ',' ) {
val = Std.parseInt( s.substr(i, j - i) );
b.addByte( val );
i = j + 1;
}
}
if( i < len ) {
val = Std.parseInt( s.substr(i) );
b.addByte( val );
}
return b.getBytes();
}
var std_dc_chrominance_nrcodes: Array<Int>;
var std_dc_chrominance_values: haxe.io.Bytes;
var std_ac_chrominance_nrcodes: Array<Int>;
var std_ac_chrominance_values: haxe.io.Bytes;
function initChrominance() {
std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
std_ac_chrominance_values = strIntsToBytes(
'0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' +
'0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' +
'0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' +
'0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' +
'0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' +
'0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' +
'0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' +
'0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' +
'0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' +
'0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' +
'0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' +
'0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' +
'0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' +
'0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' +
'0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' +
'0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' +
'0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' +
'0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' +
'0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' +
'0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
'0xf9,0xfa'
);
}
var YDC_HT: Map<Int,BitString>;
var UVDC_HT: Map<Int,BitString>;
var YAC_HT: Map<Int,BitString>;
var UVAC_HT: Map<Int,BitString>;
// Función para crear la tabla Huffman (Helper)
function computeHuffmanTbl(nrcodes: Array<Int>, std_table: haxe.io.Bytes): Map<Int,BitString> {
var codevalue = 0;
var pos_in_table = 0;
var HT: Map<Int,BitString> = new Map();
for( k in 1...17 ) {
var end = nrcodes[k];
for( j in 0...end ) {
var idx: Int = std_table.get( pos_in_table );
HT.set( idx, new BitString( k, codevalue ) );
pos_in_table++;
codevalue++;
}
codevalue *= 2;
}
return HT;
}
function initHuffmanTbl() {
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values);
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values);
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values);
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values);
// CORRECCIÓN DE TABLAS: Asegurar la existencia de EOB (0x00) y ZRL (0xF0)
// Esto es necesario para evitar fallos si el 'computeHuffmanTbl' no incluye estos valores por algún motivo.
if (YAC_HT.get(0x00) == null) YAC_HT.set(0x00, new BitString(4, 0x00));
if (UVAC_HT.get(0x00) == null) UVAC_HT.set(0x00, new BitString(4, 0x00));
if (YAC_HT.get(0xF0) == null) YAC_HT.set(0xF0, new BitString(11, 0x1E));
if (UVAC_HT.get(0xF0) == null) UVAC_HT.set(0xF0, new BitString(11, 0x1E));
}
var bitcode: Map<Int,BitString>;
var category: Map<Int,Int>;
function initCategoryNumber() {
var nrlower = 1;
var nrupper = 2;
var idx: Int;
for (cat in 1...16) {
//Positive numbers
for( nr in nrlower...nrupper ) {
idx = 32767 + nr;
category.set( idx, cat );
bitcode.set( idx, new BitString( cat, nr ) );
}
//Negative numbers
var nrneg: Int = -(nrupper - 1);
while( nrneg <= -nrlower ) {
idx = 32767 + nrneg;
category.set( idx, cat );
bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) );
nrneg++;
}
nrlower <<= 1;
nrupper <<= 1;
}
}
// IO functions
var byteout: haxe.io.Output;
var bytenew: Int;
var bytepos: Int;
function writeBits(bs: BitString) {
// Se confía en que bs no es nulo gracias al clamping y las correcciones de tablas.
var value: Int = bs.val;
var posval: Int = bs.len - 1;
while( posval >= 0 ) {
if( (value & (1 << posval)) != 0 ) {
bytenew |= (1 << bytepos);
}
posval--;
bytepos--;
if( bytepos < 0 ) {
if( bytenew == 0xFF ) {
b(0xFF);
b(0);
}
else {
b(bytenew);
}
bytepos = 7;
bytenew = 0;
}
}
}
function writeWord( val: Int ) {
b( (val >> 8) & 0xFF );
b( val & 0xFF );
}
// DCT & quantization core
function fDCTQuant(data: Array<Float>, fdtbl: Array<Float>): Array<Float> {
/* Pass 1: process rows. */
var dataOff = 0;
for (i in 0...8) {
var tmp0: Float = data[dataOff + 0] + data[dataOff + 7];
var tmp7: Float = data[dataOff + 0] - data[dataOff + 7];
var tmp1: Float = data[dataOff + 1] + data[dataOff + 6];
var tmp6: Float = data[dataOff + 1] - data[dataOff + 6];
var tmp2: Float = data[dataOff + 2] + data[dataOff + 5];
var tmp5: Float = data[dataOff + 2] - data[dataOff + 5];
var tmp3: Float = data[dataOff + 3] + data[dataOff + 4];
var tmp4: Float = data[dataOff + 3] - data[dataOff + 4];
/* Even part */
var tmp10: Float = tmp0 + tmp3; /* phase 2 */
var tmp13: Float = tmp0 - tmp3;
var tmp11: Float = tmp1 + tmp2;
var tmp12: Float = tmp1 - tmp2;
data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */
data[dataOff + 4] = tmp10 - tmp11;
var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */
data[dataOff + 2] = tmp13 + z1; /* phase 5 */
data[dataOff + 6] = tmp13 - z1;
/* Odd part */
tmp10 = tmp4 + tmp5; /* phase 2 */
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */
var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */
var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */
var z3: Float = tmp11 * 0.707106781; /* c4 */
var z11: Float = tmp7 + z3; /* phase 5 */
var z13: Float = tmp7 - z3;
data[dataOff + 5] = z13 + z2; /* phase 6 */
data[dataOff + 3] = z13 - z2;
data[dataOff + 1] = z11 + z4;
data[dataOff + 7] = z11 - z4;
dataOff += 8; /* advance pointer to next row */
}
/* Pass 2: process columns. */
dataOff = 0;
for (j in 0...8) {
var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56];
var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56];
var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48];
var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48];
var tmp2p2: Float = data[dataOff+16] + data[dataOff+40];
var tmp5p2: Float = data[dataOff+16] - data[dataOff+40];
var tmp3p2: Float = data[dataOff+24] + data[dataOff+32];
var tmp4p2: Float = data[dataOff+24] - data[dataOff+32];
/* Even part */
var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */
var tmp13p2: Float = tmp0p2 - tmp3p2;
var tmp11p2: Float = tmp1p2 + tmp2p2;
var tmp12p2: Float = tmp1p2 - tmp2p2;
data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */
data[dataOff+32] = tmp10p2 - tmp11p2;
var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
data[dataOff+48] = tmp13p2 - z1p2;
/* Odd part */
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
tmp11p2 = tmp5p2 + tmp6p2;
tmp12p2 = tmp6p2 + tmp7p2;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */
var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */
var z13p2: Float = tmp7p2 - z3p2;
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
data[dataOff+24] = z13p2 - z2p2;
data[dataOff+ 8] = z11p2 + z4p2;
data[dataOff+56] = z11p2 - z4p2;
dataOff++; /* advance pointer to next column */
}
// Quantize/descale the coefficients
for (k in 0...64) {
// Apply the quantization and scaling factor & Round to nearest integer
data[k] = Math.round(data[k] * fdtbl[k]);
}
return data;
}
// Chunk writing
inline function b(v) {
byteout.writeByte(v);
}
function writeAPP0() {
b(0xFF); b(0xE0); //<- marker 0xFFE0
b(0); b(16); //<- length
b("J".code); // J
b("F".code);
b("I".code);
b("F".code);
b(0);
b(1); // versionhi
b(1); // versionlo
b(0); // xyunits
b(0); b(1); // xdensity
b(0); b(1); // ydensity
b(0); // thumbnwidth
b(0); // thumbnheight
}
function writeDQT() {
b(0xFF); b(0xDB); //<- marker 0xFFDB
b(0); b(132); //<- length
b(0);
for( j in 0...64 )
b(YTable[j]);
b(1);
for( j in 0...64 )
b(UVTable[j]);
}
function writeSOF0(width: Int, height: Int) {
b(0xFF); b(0xC0); //<- marker 0xFFC0
b(0); b(17); //<- length, truecolor YUV JPG
b(8); // precision
b( (height>>8) & 0xFF );
b( height & 0xFF );
b( (width>>8) & 0xFF );
b( width & 0xFF );
b(3); // nrofcomponents
b(1); // IdY
b(0x11); // HVY
b(0); // QTY
b(2); // IdU
b(0x11); // HVU
b(1); // QTU
b(3); // IdV
b(0x11); // HVV
b(1); // QTV
}
function writeDHT() {
b(0xFF); b(0xC4); //<- marker 0xFFC4
b(0x01); b(0xA2); //<- length
b(0); // HTYDCinfo
for( j in 1...17 )
b(std_dc_luminance_nrcodes[j]);
byteout.write(std_dc_luminance_values);
b(0x10); // HTYACinfo
for( j in 1...17 )
b(std_ac_luminance_nrcodes[j]);
byteout.write(std_ac_luminance_values);
b(1); // HTUDCinfo
for( j in 1...17 )
b(std_dc_chrominance_nrcodes[j]);
byteout.write(std_dc_chrominance_values);
b(0x11); // HTUACinfo
for( j in 1...17 )
b(std_ac_chrominance_nrcodes[j]);
byteout.write(std_ac_chrominance_values);
}
function writeSOS() {
b(0xFF); b(0xDA); //<- marker 0xFFDA
b(0); b(12); //<- length
b(3); // nrofcomponents
b(1); // IdY
b(0); // HTY
b(2); // IdU
b(0x11); // HTU
b(3); // IdV
b(0x11); // HTV
b(0); // Ss
b(0x3F); // Se
b(0); // Bf
}
// Core processing
var DU: Array<Float>;
function processDU(CDU: Array<Float>, fdtbl: Array<Float>, DC: Float, HTDC: Map<Int,BitString>, HTAC: Map<Int,BitString>): Float {
var EOB: BitString = HTAC.get( 0x00 );
var M16zeroes: BitString = HTAC.get( 0xF0 );
var DU_DCT: Array<Float> = fDCTQuant(CDU, fdtbl);
//ZigZag reorder
for (i in 0...64) {
DU[ ZigZag[i] ] = DU_DCT[i];
}
var idx: Int;
var Diff = Std.int( DU[0] - DC );
DC = DU[0];
// CORRECCIÓN DE RANGO: Clamping de la diferencia DC (previene accesos a `category` fuera de rango)
if (Diff > 16383) Diff = 16383;
if (Diff < -16383) Diff = -16383;
//Encode DC
if( Diff == 0 ) {
writeBits( HTDC.get(0) );
} else {
idx = 32767 + Diff;
writeBits(HTDC.get( category.get( idx ) ));
writeBits( bitcode.get( idx ) );
}
//Encode ACs
var end0pos = 63;
while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--;
//end0pos = first element in reverse order !=0
if ( end0pos == 0 ) {
writeBits(EOB);
return DC;
}
var i = 1;
while ( i <= end0pos ) {
var startpos = i;
while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++;
// Chequeo de seguridad si 'i' saltó más allá
if (i > end0pos) break;
var nrzeroes: Int = i - startpos;
if ( nrzeroes >= 16 ) {
for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes);
nrzeroes &= 0xF;
}
// CORRECCIÓN DE RANGO: Clamping del coeficiente AC
var du_val = Std.int( DU[i] );
if (du_val > 16383) du_val = 16383;
if (du_val < -16383) du_val = -16383;
// LÓGICA DE SALTO: Si el clamping forzó el valor a 0, saltamos.
if (du_val == 0) {
i++;
continue;
}
idx = 32767 + du_val;
var cat = category.get( idx );
// Si 'cat' es nulo, significa que el valor de 'du_val' está fuera del rango -16383..16383, lo cual el clamping debería haber prevenido.
var index_ac = nrzeroes * 16 + cat;
writeBits( HTAC.get( index_ac ) );
writeBits( bitcode.get( idx ) );
i++;
}
if( end0pos != 63 ) writeBits(EOB);
return DC;
}
var YDU: Array<Float>;
var UDU: Array<Float>;
var VDU: Array<Float>;
function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) {
var pos = 0;
for( y in 0...8 ) {
var offset = ((y + ypos) * width + xpos) << 2;
for( x in 0...8 ) {
offset++; // skip alpha
var R = img.get(offset++);
var G = img.get(offset++);
var B = img.get(offset++);
YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128;
UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B));
VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B));
pos++;
}
}
}
public function new( out : haxe.io.Output ) {
//begin : lines added to initialize variables
YTable = new Array<Int>();
UVTable = new Array<Int>();
fdtbl_Y = new Array<Float>();
fdtbl_UV = new Array<Float>();
for (i in 0...64) {
YTable.push(0); UVTable.push(0);
fdtbl_Y.push(0.0); fdtbl_UV.push(0.0);
}
bitcode = new Map();
category = new Map();
byteout = out;
bytenew = 0;
bytepos = 7;
YDC_HT = new Map();
UVDC_HT = new Map();
YAC_HT = new Map();
UVAC_HT = new Map();
YDU = new Array<Float>();
UDU = new Array<Float>();
VDU = new Array<Float>();
DU = new Array<Float>();
for (i in 0...64) {
YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0);
}
initZigZag();
initLuminance();
initChrominance();
//end : lines added to initialize variables
// Create tables
initHuffmanTbl();
initCategoryNumber();
}
public function write( image : Data ) {
// init quality table
var quality = image.quality;
if( quality <= 0 ) quality = 1;
if( quality > 100 ) quality = 100;
var sf =
if( quality < 50 ) Std.int( 5000 / quality )
else Std.int( 200 - quality * 2 );
initQuantTables(sf);
// Initialize bit writer
bytenew = 0;
bytepos = 7;
var width = image.width;
var height = image.height;
// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0( width, height );
writeDHT();
writeSOS();
// Encode 8x8 macroblocks
var DCY = 0.0;
var DCU = 0.0;
var DCV = 0.0;
bytenew = 0;
bytepos = 7;
var ypos = 0;
while( ypos < height ) {
var xpos = 0;
while( xpos < width ) {
// CORRECCIÓN CRÍTICA DE ESTADO: Limpieza de arreglos para evitar arrastre de valores (lo que el 'trace' estaba enmascarando)
// Se asegura que los buffers sean cero antes de llenarlos con RGB2YUV si no se llenan completamente.
for (k in 0...64) { YDU[k] = 0.0; UDU[k] = 0.0; VDU[k] = 0.0; }
RGB2YUV(image.pixels, width, xpos, ypos);
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
xpos += 8;
}
ypos += 8;
}
// Do the bit alignment of the EOI marker
if( bytepos >= 0 ) {
var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 );
writeBits(fillbits);
}
writeWord(0xFFD9); //EOI
}
}
private class BitString {
public var len: Int;
public var val: Int;
public function new( l: Int, v: Int ) {
len = l;
val = v;
}
}

View File

@ -0,0 +1,651 @@
/*
* format - Haxe File Formats
*
* JPG File Format
* Copyright (C) 2007-2009 Thibault Imbert, AS3-to-Haxe by Michel Oster
*
* 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.jpg;
class Writer {
var ZigZag: Array<Int>;
// Static table initialization
function initZigZag() {
ZigZag = [
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
];
}
var YTable: Array<Int>; // = new Array(64);
var UVTable: Array<Int>; // = new Array(64);
var fdtbl_Y: Array<Float>; // = new Array(64);
var fdtbl_UV: Array<Float>; // = new Array(64);
function initQuantTables(sf: Int) {
var YQT: Array<Int> = [
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99
];
for (i in 0...64) {
var t: Int = Math.floor( (YQT[i] * sf + 50) / 100 );
if( t < 1 ) t = 1;
else if( t > 255 ) t = 255;
YTable[ ZigZag[i] ] = t;
}
var UVQT: Array<Int> = [
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
];
for( j in 0...64 ) {
var u: Int = Math.floor( (UVQT[j] * sf + 50) / 100 );
if( u < 1 ) u = 1;
else if( u > 255 ) u = 255;
UVTable[ ZigZag[j] ] = u;
}
var aasf: Array<Float> = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
];
var k = 0;
for( row in 0...8 ) {
for( col in 0...8 ) {
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
k++;
}
}
}
var std_dc_luminance_nrcodes: Array<Int>;
var std_dc_luminance_values: haxe.io.Bytes;
var std_ac_luminance_nrcodes: Array<Int>;
var std_ac_luminance_values: haxe.io.Bytes;
function initLuminance() {
std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
std_dc_luminance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
std_ac_luminance_values = strIntsToBytes(
'0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,' +
'0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,' +
'0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,' +
'0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,' +
'0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,' +
'0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,' +
'0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,' +
'0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,' +
'0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,' +
'0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,' +
'0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,' +
'0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,' +
'0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,' +
'0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,' +
'0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,' +
'0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,' +
'0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,' +
'0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,' +
'0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,' +
'0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
'0xf9,0xfa'
);
}
function strIntsToBytes( s: String ) {
var len = s.length;
var b = new haxe.io.BytesBuffer();
var val = 0;
var i = 0;
for( j in 0...len ) {
if( s.charAt( j ) == ',' ) {
val = Std.parseInt( s.substr(i, j - i) );
b.addByte( val );
i = j + 1;
}
}
if( i < len ) {
val = Std.parseInt( s.substr(i) );
b.addByte( val );
}
return b.getBytes();
}
var std_dc_chrominance_nrcodes: Array<Int>;
var std_dc_chrominance_values: haxe.io.Bytes;
var std_ac_chrominance_nrcodes: Array<Int>;
var std_ac_chrominance_values: haxe.io.Bytes;
function initChrominance() {
std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
std_dc_chrominance_values = strIntsToBytes( '0,1,2,3,4,5,6,7,8,9,10,11' );
std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
std_ac_chrominance_values = strIntsToBytes(
'0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,' +
'0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,' +
'0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,' +
'0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,' +
'0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,' +
'0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,' +
'0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,' +
'0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,' +
'0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,' +
'0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,' +
'0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,' +
'0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,' +
'0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,' +
'0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,' +
'0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,' +
'0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,' +
'0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,' +
'0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,' +
'0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,' +
'0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,' +
'0xf9,0xfa'
);
}
var YDC_HT: Map<Int,BitString>;
var UVDC_HT: Map<Int,BitString>;
var YAC_HT: Map<Int,BitString>;
var UVAC_HT: Map<Int,BitString>;
function initHuffmanTbl() {
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values);
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values);
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values);
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values);
}
function computeHuffmanTbl(nrcodes: Array<Int>, std_table: haxe.io.Bytes): Map<Int,BitString> {
var codevalue = 0;
var pos_in_table = 0;
var HT: Map<Int,BitString> = new Map();
for( k in 1...17 ) {
var end = nrcodes[k];
for( j in 0...end ) {
var idx: Int = std_table.get( pos_in_table );
HT.set( idx, new BitString( k, codevalue ) );
pos_in_table++;
codevalue++;
}
codevalue *= 2;
}
return HT;
}
var bitcode: Map<Int,BitString>;
var category: Map<Int,Int>;
function initCategoryNumber() {
var nrlower = 1;
var nrupper = 2;
var idx: Int;
for (cat in 1...16) {
//Positive numbers
for( nr in nrlower...nrupper ) {
idx = 32767 + nr;
category.set( idx, cat );
bitcode.set( idx, new BitString( cat, nr ) );
}
//Negative numbers
var nrneg: Int = -(nrupper - 1);
while( nrneg <= -nrlower ) {
idx = 32767 + nrneg;
category.set( idx, cat );
bitcode.set( idx, new BitString( cat, nrupper - 1 + nrneg ) );
nrneg++;
}
nrlower <<= 1;
nrupper <<= 1;
}
}
// IO functions
var byteout: haxe.io.Output;
var bytenew: Int;
var bytepos: Int;
function writeBits(bs: BitString) {
var value: Int = bs.val;
var posval: Int = bs.len - 1;
while( posval >= 0 ) {
//if (value & uint(1 << posval) ) {
if( (value & (1 << posval)) != 0 ) { //<- CORRECT ?
//bytenew |= uint(1 << bytepos);
bytenew |= (1 << bytepos);
}
posval--;
bytepos--;
if( bytepos < 0 ) {
if( bytenew == 0xFF ) {
b(0xFF);
b(0);
}
else {
b(bytenew);
}
bytepos = 7;
bytenew = 0;
}
}
}
function writeWord( val: Int ) {
b( (val >> 8) & 0xFF );
b( val & 0xFF );
}
// DCT & quantization core
function fDCTQuant(data: Array<Float>, fdtbl: Array<Float>): Array<Float> {
/* Pass 1: process rows. */
var dataOff = 0;
for (i in 0...8) {
var tmp0: Float = data[dataOff + 0] + data[dataOff + 7];
var tmp7: Float = data[dataOff + 0] - data[dataOff + 7];
var tmp1: Float = data[dataOff + 1] + data[dataOff + 6];
var tmp6: Float = data[dataOff + 1] - data[dataOff + 6];
var tmp2: Float = data[dataOff + 2] + data[dataOff + 5];
var tmp5: Float = data[dataOff + 2] - data[dataOff + 5];
var tmp3: Float = data[dataOff + 3] + data[dataOff + 4];
var tmp4: Float = data[dataOff + 3] - data[dataOff + 4];
/* Even part */
var tmp10: Float = tmp0 + tmp3; /* phase 2 */
var tmp13: Float = tmp0 - tmp3;
var tmp11: Float = tmp1 + tmp2;
var tmp12: Float = tmp1 - tmp2;
data[dataOff + 0] = tmp10 + tmp11; /* phase 3 */
data[dataOff + 4] = tmp10 - tmp11;
var z1: Float = (tmp12 + tmp13) * 0.707106781; /* c4 */
data[dataOff + 2] = tmp13 + z1; /* phase 5 */
data[dataOff + 6] = tmp13 - z1;
/* Odd part */
tmp10 = tmp4 + tmp5; /* phase 2 */
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5: Float = (tmp10 - tmp12) * 0.382683433; /* c6 */
var z2: Float = 0.541196100 * tmp10 + z5; /* c2-c6 */
var z4: Float = 1.306562965 * tmp12 + z5; /* c2+c6 */
var z3: Float = tmp11 * 0.707106781; /* c4 */
var z11: Float = tmp7 + z3; /* phase 5 */
var z13: Float = tmp7 - z3;
data[dataOff + 5] = z13 + z2; /* phase 6 */
data[dataOff + 3] = z13 - z2;
data[dataOff + 1] = z11 + z4;
data[dataOff + 7] = z11 - z4;
dataOff += 8; /* advance pointer to next row */
}
/* Pass 2: process columns. */
dataOff = 0;
for (j in 0...8) {
var tmp0p2: Float = data[dataOff+ 0] + data[dataOff+56];
var tmp7p2: Float = data[dataOff+ 0] - data[dataOff+56];
var tmp1p2: Float = data[dataOff+ 8] + data[dataOff+48];
var tmp6p2: Float = data[dataOff+ 8] - data[dataOff+48];
var tmp2p2: Float = data[dataOff+16] + data[dataOff+40];
var tmp5p2: Float = data[dataOff+16] - data[dataOff+40];
var tmp3p2: Float = data[dataOff+24] + data[dataOff+32];
var tmp4p2: Float = data[dataOff+24] - data[dataOff+32];
/* Even part */
var tmp10p2: Float = tmp0p2 + tmp3p2; /* phase 2 */
var tmp13p2: Float = tmp0p2 - tmp3p2;
var tmp11p2: Float = tmp1p2 + tmp2p2;
var tmp12p2: Float = tmp1p2 - tmp2p2;
data[dataOff+ 0] = tmp10p2 + tmp11p2; /* phase 3 */
data[dataOff+32] = tmp10p2 - tmp11p2;
var z1p2: Float = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
data[dataOff+48] = tmp13p2 - z1p2;
/* Odd part */
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
tmp11p2 = tmp5p2 + tmp6p2;
tmp12p2 = tmp6p2 + tmp7p2;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5p2: Float = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
var z2p2: Float = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
var z4p2: Float = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
var z3p2: Float= tmp11p2 * 0.707106781; /* c4 */
var z11p2: Float = tmp7p2 + z3p2; /* phase 5 */
var z13p2: Float = tmp7p2 - z3p2;
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
data[dataOff+24] = z13p2 - z2p2;
data[dataOff+ 8] = z11p2 + z4p2;
data[dataOff+56] = z11p2 - z4p2;
dataOff++; /* advance pointer to next column */
}
// Quantize/descale the coefficients
for (k in 0...64) {
// Apply the quantization and scaling factor & Round to nearest integer
data[k] = Math.round(data[k] * fdtbl[k]);
}
return data;
}
// Chunk writing
inline function b(v) {
byteout.writeByte(v);
}
function writeAPP0() {
b(0xFF); b(0xE0); //<- marker 0xFFE0
b(0); b(16); //<- length
b("J".code); // J
b("F".code);
b("I".code);
b("F".code);
b(0);
b(1); // versionhi
b(1); // versionlo
b(0); // xyunits
b(0); b(1); // xdensity
b(0); b(1); // ydensity
b(0); // thumbnwidth
b(0); // thumbnheight
}
function writeDQT() {
b(0xFF); b(0xDB); //<- marker 0xFFDB
b(0); b(132); //<- length
b(0);
for( j in 0...64 )
b(YTable[j]);
b(1);
for( j in 0...64 )
b(UVTable[j]);
}
function writeSOF0(width: Int, height: Int) {
b(0xFF); b(0xC0); //<- marker 0xFFC0
b(0); b(17); //<- length, truecolor YUV JPG
b(8); // precision
b( (height>>8) & 0xFF );
b( height & 0xFF );
b( (width>>8) & 0xFF );
b( width & 0xFF );
b(3); // nrofcomponents
b(1); // IdY
b(0x11); // HVY
b(0); // QTY
b(2); // IdU
b(0x11); // HVU
b(1); // QTU
b(3); // IdV
b(0x11); // HVV
b(1); // QTV
}
function writeDHT() {
b(0xFF); b(0xC4); //<- marker 0xFFC4
b(0x01); b(0xA2); //<- length
b(0); // HTYDCinfo
for( j in 1...17 )
b(std_dc_luminance_nrcodes[j]);
byteout.write(std_dc_luminance_values);
b(0x10); // HTYACinfo
for( j in 1...17 )
b(std_ac_luminance_nrcodes[j]);
byteout.write(std_ac_luminance_values);
b(1); // HTUDCinfo
for( j in 1...17 )
b(std_dc_chrominance_nrcodes[j]);
byteout.write(std_dc_chrominance_values);
b(0x11); // HTUACinfo
for( j in 1...17 )
b(std_ac_chrominance_nrcodes[j]);
byteout.write(std_ac_chrominance_values);
}
function writeSOS() {
b(0xFF); b(0xDA); //<- marker 0xFFDA
b(0); b(12); //<- length
b(3); // nrofcomponents
b(1); // IdY
b(0); // HTY
b(2); // IdU
b(0x11); // HTU
b(3); // IdV
b(0x11); // HTV
b(0); // Ss
b(0x3F); // Se
b(0); // Bf
}
// Core processing
var DU: Array<Float>; //<- initialized in function new JPEGEncoder()
function processDU(CDU: Array<Float>, fdtbl: Array<Float>, DC: Float, HTDC: Map<Int,BitString>, HTAC: Map<Int,BitString>): Float {
var EOB: BitString = HTAC.get( 0x00 );
var M16zeroes: BitString = HTAC.get( 0xF0 );
var DU_DCT: Array<Float> = fDCTQuant(CDU, fdtbl);
//ZigZag reorder
for (i in 0...64) {
DU[ ZigZag[i] ] = DU_DCT[i];
}
var idx: Int;
var Diff = Std.int( DU[0] - DC );
DC = DU[0];
//Encode DC
if( Diff == 0 ) {
writeBits( HTDC.get(0) ); // Diff might be 0
} else {
idx = 32767 + Diff;
writeBits(HTDC.get( category.get( idx ) ));
writeBits( bitcode.get( idx ) );
}
//Encode ACs
var end0pos = 63;
//for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) { };
while( (end0pos > 0) && ( DU[end0pos] == 0.0 ) ) end0pos--;
//end0pos = first element in reverse order !=0
if ( end0pos == 0 ) {
writeBits(EOB);
return DC;
}
var i = 1;
while ( i <= end0pos ) {
var startpos = i;
//for (; (DU[i]==0) && (i<=end0pos); i++) { }; <- it's a 'while' loop
while( ( DU[i] == 0.0 ) && ( i <= end0pos ) ) i++;
var nrzeroes: Int = i - startpos;
if ( nrzeroes >= 16 ) {
//for (var nrmarker: Int=1; nrmarker <= nrzeroes/16; nrmarker++) {
for( nrmarker in 0...(nrzeroes >> 4) ) writeBits(M16zeroes);
nrzeroes &= 0xF;
}
idx = 32767 + Std.int( DU[i] ); //<- line added
writeBits( HTAC.get( nrzeroes * 16 + category.get( idx ) ) );
writeBits( bitcode.get( idx ) );
i++;
}
if( end0pos != 63 ) writeBits(EOB);
return DC;
}
var YDU: Array<Float>;
var UDU: Array<Float>;
var VDU: Array<Float>;
function RGB2YUV(img: haxe.io.Bytes, width : Int, xpos: Int, ypos: Int) {
var pos = 0;
for( y in 0...8 ) {
var offset = ((y + ypos) * width + xpos) << 2;
for( x in 0...8 ) {
offset++; // skip alpha
var R = img.get(offset++);
var G = img.get(offset++);
var B = img.get(offset++);
YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) -128;
UDU[pos] = (((-0.16874) * R + (-0.33126) * G + ( 0.50000) * B));
VDU[pos] = ((( 0.50000) * R + (-0.41869) * G + (-0.08131) * B));
pos++;
}
}
}
public function new( out : haxe.io.Output ) {
//begin : lines added to initialize variables
YTable = new Array<Int>();
UVTable = new Array<Int>();
fdtbl_Y = new Array<Float>();
fdtbl_UV = new Array<Float>();
for (i in 0...64) {
YTable.push(0); UVTable.push(0);
fdtbl_Y.push(0.0); fdtbl_UV.push(0.0);
}
bitcode = new Map(); //<- 65535 elements <BitString>
category = new Map(); //<- 65535 elements <Int>
byteout = out;
bytenew = 0;
bytepos = 7;
YDC_HT = new Map();
UVDC_HT = new Map();
YAC_HT = new Map();
UVAC_HT = new Map();
YDU = new Array<Float>(); //<- 64 elements
UDU = new Array<Float>();
VDU = new Array<Float>();
DU = new Array<Float>();
for (i in 0...64) {
YDU.push(0.0); UDU.push(0.0); VDU.push(0.0); DU.push(0.0);
}
initZigZag();
initLuminance();
initChrominance();
//end : lines added to initialize variables
// Create tables
initHuffmanTbl();
initCategoryNumber();
}
public function write( image : Data ) {
// init quality table
var quality = image.quality;
if( quality <= 0 ) quality = 1;
if( quality > 100 ) quality = 100;
var sf =
if( quality < 50 ) Std.int( 5000 / quality )
else Std.int( 200 - quality * 2 );
initQuantTables(sf);
// Initialize bit writer
bytenew = 0;
bytepos = 7;
var width = image.width;
var height = image.height;
// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0( width, height );
writeDHT();
writeSOS();
// Encode 8x8 macroblocks
var DCY = 0.0;
var DCU = 0.0;
var DCV = 0.0;
bytenew = 0;
bytepos = 7;
var ypos = 0;
while( ypos < height ) {
var xpos = 0;
while( xpos < width ) {
RGB2YUV(image.pixels, width, xpos, ypos);
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
xpos += 8;
}
ypos += 8;
}
// Do the bit alignment of the EOI marker
if( bytepos >= 0 ) {
var fillbits = new BitString( bytepos + 1, ( 1 << (bytepos + 1) ) - 1 );
writeBits(fillbits);
}
writeWord(0xFFD9); //EOI
}
}
private class BitString {
public var len: Int;
public var val: Int;
public function new( l: Int, v: Int ) {
len = l;
val = v;
}
}

View File

@ -0,0 +1,56 @@
/*
* format - Haxe File Formats
*
* WAVE File Format
* Copyright (C) 2009 Robin Palotai
*
* 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.wav;
typedef WAVE = {
header : WAVEHeader,
data : haxe.io.Bytes,
cuePoints : Array<CuePoint>
}
typedef WAVEHeader = {
format : WAVEFormat,
channels : Int,
samplingRate : Int,
byteRate : Int, // samplingRate * channels * bitsPerSample / 8
blockAlign : Int, // channels * bitsPerSample / 8
bitsPerSample : Int
}
typedef CuePoint = {
id : Int,
sampleOffset : Int
}
enum WAVEFormat {
WF_PCM;
}

View File

@ -0,0 +1,155 @@
/*
* format - Haxe File Formats
*
* WAVE File Format
* Copyright (C) 2009 Robin Palotai
*
* 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.wav;
import iron.format.wav.Data;
class Reader {
var i : haxe.io.Input;
var version : Int;
public function new(i) {
this.i = i;
i.bigEndian = false;
}
inline function readInt() {
#if haxe3
return i.readInt32();
#else
return i.readUInt30();
#end
}
public function read() : WAVE {
if (i.readString(4) != "RIFF")
throw "RIFF header expected";
var len = readInt();
if (i.readString(4) != "WAVE")
throw "WAVE signature not found";
var fmt = i.readString(4);
while(fmt != "fmt ") {
switch( fmt ) {
case "JUNK": //protool
var junkLen = i.readInt32();
i.read(junkLen);
fmt = i.readString(4);
case "bext":
var bextLen = i.readInt32();
i.read(bextLen);
fmt = i.readString(4);
default:
break;
}
}
if ( fmt != "fmt " )
throw "unsupported wave chunk "+fmt;
var fmtlen = readInt();
var format = switch (i.readUInt16()) {
case 1,3: WF_PCM;
default: throw "only PCM (uncompressed) WAV files are supported";
}
var channels = i.readUInt16();
var samplingRate = readInt();
var byteRate = readInt();
var blockAlign = i.readUInt16();
var bitsPerSample = i.readUInt16();
if (fmtlen > 16)
i.read(fmtlen - 16);
var nextChunk = i.readString (4);
while (nextChunk != "data") {
// read past other subchunks
i.read(readInt());
nextChunk = i.readString (4);
}
// data
if (nextChunk != "data")
throw "expected data subchunk";
var datalen = readInt();
var data : haxe.io.Bytes;
try {
data = i.read(datalen);
} catch (e : haxe.io.Eof) {
throw "Invalid chunk data length";
}
var cuePoints = new Array<CuePoint>();
try {
while (true) {
var nextChunk = i.readString (4);
switch (nextChunk) {
case "cue ":
readInt();
var nbCuePoints = readInt();
for (_ in 0...nbCuePoints) {
var cueId = readInt();
readInt();
i.readString(4);
readInt();
readInt();
var cueSampleOffset = readInt();
cuePoints.push({ id : cueId, sampleOffset: cueSampleOffset });
}
default:
var n = readInt();
if( n < 0 ) break;
i.read(n);
}
}
} catch (e : haxe.io.Eof) { }
return {
header: {
format: format,
channels: channels,
samplingRate: samplingRate,
byteRate: byteRate,
blockAlign: blockAlign,
bitsPerSample: bitsPerSample
},
data: data,
cuePoints: cuePoints
}
}
}

View File

@ -0,0 +1,72 @@
/*
* format - Haxe File Formats
*
* WAVE File Format
* Copyright (C) 2009 Robin Palotai
*
* 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.wav;
import iron.format.wav.Data;
class Writer {
var o : haxe.io.Output;
public function new(output : haxe.io.Output) {
o = output;
o.bigEndian = false;
}
public function write(wav : WAVE) {
var hdr = wav.header;
o.writeString("RIFF");
writeInt(36 + wav.data.length);
o.writeString("WAVE");
o.writeString("fmt ");
writeInt(16);
o.writeUInt16(1);
o.writeUInt16(hdr.channels);
writeInt(hdr.samplingRate);
writeInt(hdr.byteRate);
o.writeUInt16(hdr.blockAlign);
o.writeUInt16(hdr.bitsPerSample);
o.writeString("data");
writeInt(wav.data.length);
o.write(wav.data);
}
inline function writeInt( v : Int ) {
#if haxe3
o.writeInt32(v);
#else
o.writeUInt30(v);
#end
}
}

View File

@ -1,31 +1,119 @@
package iron.object;
import iron.data.SceneFormat;
import iron.math.Vec4;
import iron.math.Quat;
class Constraint {
var raw: TConstraint;
var target: Transform = null;
public function new(constr: TConstraint) {
raw = constr;
public function new(constraint: TConstraint) {
raw = constraint;
}
public function apply(transform: Transform) {
if (target == null && raw.target != null) target = Scene.active.getChild(raw.target).transform;
if (target == null && raw.type != "LIMIT_LOCATION" && raw.type != "LIMIT_ROTATION" && raw.type != "LIMIT_SCALE") return;
if (raw.type == "COPY_LOCATION") {
if (raw.use_x) {
transform.world._30 = target.loc.x;
if (raw.use_offset) transform.world._30 += transform.loc.x;
if (raw.use_offset) {
if (raw.use_x) transform.world._30 += target.world._30;
if (raw.use_y) transform.world._31 += target.world._31;
if (raw.use_z) transform.world._32 += target.world._32;
}
if (raw.use_y) {
transform.world._31 = target.loc.y;
if (raw.use_offset) transform.world._31 += transform.loc.y;
}
if (raw.use_z) {
transform.world._32 = target.loc.z;
if (raw.use_offset) transform.world._32 += transform.loc.z;
else {
if (raw.use_x) transform.world._30 = target.world._30;
if (raw.use_y) transform.world._31 = target.world._31;
if (raw.use_z) transform.world._32 = target.world._32;
}
}
else if (raw.type == "COPY_ROTATION") {
var tq = target.rot;
var mq = transform.rot;
if (raw.use_offset) {
mq.mult(tq);
}
else {
if (raw.use_x) mq.x = tq.x;
if (raw.use_y) mq.y = tq.y;
if (raw.use_z) mq.z = tq.z;
mq.w = tq.w;
}
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
var scale = transform.scale;
transform.world.compose(loc, mq, scale);
}
else if (raw.type == "COPY_SCALE") {
var ts = target.scale;
if (raw.use_offset) {
if (raw.use_x) transform.scale.x *= ts.x;
if (raw.use_y) transform.scale.y *= ts.y;
if (raw.use_z) transform.scale.z *= ts.z;
}
else {
if (raw.use_x) transform.scale.x = ts.x;
if (raw.use_y) transform.scale.y = ts.y;
if (raw.use_z) transform.scale.z = ts.z;
}
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
transform.world.compose(loc, transform.rot, transform.scale);
}
else if (raw.type == "COPY_TRANSFORMS") {
transform.world.setFrom(target.world);
}
else if (raw.type == "LIMIT_LOCATION") {
if (raw.use_min_x && transform.world._30 < raw.min_x) transform.world._30 = raw.min_x;
if (raw.use_max_x && transform.world._30 > raw.max_x) transform.world._30 = raw.max_x;
if (raw.use_min_y && transform.world._31 < raw.min_y) transform.world._31 = raw.min_y;
if (raw.use_max_y && transform.world._31 > raw.max_y) transform.world._31 = raw.max_y;
if (raw.use_min_z && transform.world._32 < raw.min_z) transform.world._32 = raw.min_z;
if (raw.use_max_z && transform.world._32 > raw.max_z) transform.world._32 = raw.max_z;
}
else if (raw.type == "LIMIT_ROTATION") {
var euler = transform.rot.getEuler();
var changed = false;
if (raw.use_limit_x) {
if (euler.x < raw.min_x) { euler.x = raw.min_x; changed = true; }
if (euler.x > raw.max_x) { euler.x = raw.max_x; changed = true; }
}
if (raw.use_limit_y) {
if (euler.y < raw.min_y) { euler.y = raw.min_y; changed = true; }
if (euler.y > raw.max_y) { euler.y = raw.max_y; changed = true; }
}
if (raw.use_limit_z) {
if (euler.z < raw.min_z) { euler.z = raw.min_z; changed = true; }
if (euler.z > raw.max_z) { euler.z = raw.max_z; changed = true; }
}
if (changed) {
transform.rot.fromEuler(euler.x, euler.y, euler.z);
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
transform.world.compose(loc, transform.rot, transform.scale);
}
}
else if (raw.type == "LIMIT_SCALE") {
if (raw.use_min_x && transform.scale.x < raw.min_x) transform.scale.x = raw.min_x;
if (raw.use_max_x && transform.scale.x > raw.max_x) transform.scale.x = raw.max_x;
if (raw.use_min_y && transform.scale.y < raw.min_y) transform.scale.y = raw.min_y;
if (raw.use_max_y && transform.scale.y > raw.max_y) transform.scale.y = raw.max_y;
if (raw.use_min_z && transform.scale.z < raw.min_z) transform.scale.z = raw.min_z;
if (raw.use_max_z && transform.scale.z > raw.max_z) transform.scale.z = raw.max_z;
var loc = new Vec4(transform.world._30, transform.world._31, transform.world._32);
transform.world.compose(loc, transform.rot, transform.scale);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,12 @@ import iron.object.CameraObject;
class LightObject extends Object {
public var data: LightData;
public var color: Vec4;
public var strength: Float;
#if lnx_spot
public var size: Float;
public var blend: Float;
#end
#if rp_shadowmap
#if lnx_shadowmap_atlas
@ -79,6 +85,15 @@ class LightObject extends Object {
super();
this.data = data;
this.color = new Vec4(data.raw.color[0], data.raw.color[1], data.raw.color[2]);
this.strength = data.raw.strength;
#if lnx_spot
if (data.raw.type == "spot"){
this.size = data.raw.spot_size;
this.blend = data.raw.spot_blend;
}
#end
var type = data.raw.type;
var fov = data.raw.fov;
@ -374,7 +389,7 @@ class LightObject extends Object {
// Centralize discarding conditions when iterating over lights
// Important to avoid issues later with "misaligned" data in uniforms (lightsArray, clusterData, LWVPSpotArray)
public inline static function discardLight(light: LightObject) {
return !light.visible || light.data.raw.strength == 0.0 || light.data.raw.type == "sun";
return !light.visible || light.strength == 0.0 || light.data.raw.type == "sun";
}
// Discarding conditions but with culling included
public inline static function discardLightCulled(light: LightObject) {
@ -457,7 +472,7 @@ class LightObject extends Object {
lpos.set(l.transform.worldx(), l.transform.worldy(), l.transform.worldz());
lpos.applymat4(camera.V);
lpos.z *= -1.0;
var radius = getRadius(l.data.raw.strength);
var radius = getRadius(l.strength);
var minX = 0;
var minY = 0;
var minZ = 0;
@ -552,10 +567,10 @@ class LightObject extends Object {
lightsArray[i * 12 + 3] = 0.0; // padding or spot scale x
// light color
var f = l.data.raw.strength;
lightsArray[i * 12 + 4] = l.data.raw.color[0] * f;
lightsArray[i * 12 + 5] = l.data.raw.color[1] * f;
lightsArray[i * 12 + 6] = l.data.raw.color[2] * f;
var f = l.strength;
lightsArray[i * 12 + 4] = l.color.x * f;
lightsArray[i * 12 + 5] = l.color.y * f;
lightsArray[i * 12 + 6] = l.color.z * f;
lightsArray[i * 12 + 7] = 0.0; // padding or spot scale y
// other data
@ -566,13 +581,13 @@ class LightObject extends Object {
#if lnx_spot
if (l.data.raw.type == "spot") {
lightsArray[i * 12 + 9] = l.data.raw.spot_size;
lightsArray[i * 12 + 9] = l.size;
var dir = l.look().normalize();
lightsArraySpot[i * 8 ] = dir.x;
lightsArraySpot[i * 8 + 1] = dir.y;
lightsArraySpot[i * 8 + 2] = dir.z;
lightsArraySpot[i * 8 + 3] = l.data.raw.spot_blend;
lightsArraySpot[i * 8 + 3] = l.blend;
// Premultiply scale with z component
var scale = l.transform.scale;

View File

@ -28,13 +28,15 @@ class MeshObject extends Object {
public var cameraList: Array<String> = null;
public var screenSize = 0.0;
public var frustumCulling = true;
public var tilesheet: Tilesheet = null;
public var activeTilesheet: Tilesheet = null;
public var tilesheets: Array<Tilesheet> = null;
public var skip_context: String = null; // Do not draw this context
public var force_context: String = null; // Draw only this context
static var lastPipeline: PipelineState = null;
#if lnx_morph_target
public var morphTarget: MorphTarget = null;
#end
public var vertexGroups: Map<String, Array<Vec4>> = null;
#if lnx_veloc
public var prevMatrix = Mat4.identity();
@ -50,6 +52,10 @@ class MeshObject extends Object {
public function setData(data: MeshData) {
this.data = data;
if (this.materials != null && this.materials.length > 0)
data.geom.instanceElements = @:privateAccess this.materials[0].shader.contexts[0].instanceElements;
data.refcount++;
#if (!lnx_batch)
@ -87,7 +93,8 @@ class MeshObject extends Object {
particleSystems = null;
}
#end
if (tilesheet != null) tilesheet.remove();
if (activeTilesheet != null) activeTilesheet.remove();
if (tilesheets != null) { for (ts in tilesheets) { ts.remove(); } tilesheets = null; }
if (Scene.active != null) Scene.active.meshes.remove(this);
#if (rp_renderer == "Deferred")
if (Scene.active != null) Scene.active.markMaterialParamsDirty();
@ -126,12 +133,34 @@ class MeshObject extends Object {
#end
public function setupTilesheet(tilesheetData: iron.data.SceneFormat.TTilesheetData) {
tilesheet = new Tilesheet(tilesheetData, this);
activeTilesheet = new Tilesheet(tilesheetData, this);
if (tilesheets == null) tilesheets = new Array<Tilesheet>();
tilesheets.push(activeTilesheet);
}
public function setActiveTilesheet(tilesheetData: iron.data.SceneFormat.TTilesheetData, tilesheetActionRef: String = null) {
var set = false;
if (tilesheets != null) {
for (ts in tilesheets) {
if (ts.raw == tilesheetData) {
if (activeTilesheet != null) activeTilesheet.pause();
activeTilesheet = ts;
if (tilesheetActionRef != null) activeTilesheet.play(tilesheetActionRef);
set = true;
break;
}
}
}
if (!set) {
if (activeTilesheet != null) activeTilesheet.pause();
setupTilesheet(tilesheetData);
if (tilesheetActionRef != null) activeTilesheet.play(tilesheetActionRef);
}
}
public function setTilesheetAction(actionRef: String) {
if (tilesheet != null) {
tilesheet.play(actionRef);
if (activeTilesheet != null) {
activeTilesheet.play(actionRef);
}
}
@ -164,6 +193,7 @@ class MeshObject extends Object {
}
function cullMesh(context: String, camera: CameraObject, light: LightObject): Bool {
var isShadow = context == "shadowmap";
if (camera == null) return false;
if (camera.data.raw.frustum_culling && frustumCulling) {
@ -172,11 +202,12 @@ class MeshObject extends Object {
var radiusScale = data.isSkinned ? 2.0 : 1.0;
#if lnx_gpu_particles
// particleSystems for update, particleOwner for render
if (particleSystems != null || particleOwner != null) radiusScale *= 1000;
if (particleSystems != null && particleSystems.length > 0) return setCulled(isShadow, false);
#end
/*
if (context == "voxel") radiusScale *= 100;
if (data.geom.instanced) radiusScale *= 100;
var isShadow = context == "shadowmap";
if (data.geom.instanced) radiusScale *= 100;*/
if (data.geom.instanced) return setCulled(isShadow, false);
var frustumPlanes = isShadow ? light.frustumPlanes : camera.frustumPlanes;
if (isShadow && light.data.raw.type != "sun") { // Non-sun light bounds intersect camera frustum
@ -191,8 +222,9 @@ class MeshObject extends Object {
}
}
culled = false;
return culled;
//culled = false;
//return culled;
return setCulled(isShadow, false);
}
function skipContext(context: String, mat: MaterialData): Bool {
@ -227,11 +259,6 @@ class MeshObject extends Object {
if (cullMesh(context, Scene.active.camera, RenderPath.active.light)) return;
var meshContext = raw != null ? context == "mesh" : false;
// Update tilesheet
if (tilesheet != null && meshContext) {
tilesheet.update();
}
if (cameraList != null && cameraList.indexOf(Scene.active.camera.name) < 0) return;
#if lnx_gpu_particles

View File

@ -61,6 +61,25 @@ class MorphTarget {
public inline function setMorphValueDirect(index: Int, value: Float) {
morphWeights.set(index, value);
}
public function getMorphValue(name: String): Float {
var i = morphMap.get(name);
return (i != null) ? morphWeights.get(i) : 0.0;
}
public function hasMorph(name: String): Bool {
return morphMap.exists(name);
}
public function resetWeights() {
for (i in 0...morphWeights.length) {
morphWeights.set(i, 0.0);
}
}
public function getMorphNames(): Array<String> {
return [for (key in morphMap.keys()) key];
}
}
#end

View File

@ -27,7 +27,6 @@ class Object {
public var culled = false; // Object was culled last frame
public var culledMesh = false;
public var culledShadow = false;
public var vertex_groups: Map<String, Array<Vec4>> = null;
public var properties: Map<String, Dynamic> = null;
var isEmpty = false;
@ -95,6 +94,7 @@ class Object {
Removes the game object from the scene.
**/
public function remove() {
Scene.active.removeFromGroups(this);
if (isEmpty && Scene.active != null) Scene.active.empties.remove(this);
if (animation != null) animation.remove();
while (children.length > 0) children[0].remove();
@ -111,16 +111,12 @@ class Object {
@return Object or null
**/
public function getChild(name: String): Object {
if (this.name == name) return this;
else if (this.filename != "") {
if (this.name == name + "_" + this.filename) return this;
}
for (c in children) {
if (c.name == name) return c;
if (c.filename != "" && c.name == name + "_" + c.filename) return c;
var r = c.getChild(name);
if (r != null) return r;
}
return null;
}
@ -145,13 +141,11 @@ class Object {
}
public function getChildOfType<T: Object>(type: Class<T>): T {
if (Std.isOfType(this, type)) return cast this;
else {
for (c in children) {
if (Std.isOfType(c, type)) return cast c;
var r = c.getChildOfType(type);
if (r != null) return r;
}
}
return null;
}

View File

@ -8,6 +8,7 @@ import iron.data.SceneFormat;
import iron.math.Quat;
import iron.math.Vec3;
import iron.math.Vec4;
import iron.object.CurveObject;
import iron.object.MeshObject;
import iron.object.Object;
import iron.system.Time;
@ -19,6 +20,10 @@ import kha.arrays.Uint32Array;
class ParticleSystemCPU {
public var data: ParticleData;
public var speed: FastFloat = 1.0; // Not used yet. Added to go in hand with `ParticleSystemGPU`
public var curveGuides: Array<CurveObject> = [];
public var curveGuideStrength: FastFloat = 1.0;
public var curveGuideSpeed: FastFloat = 1.0;
var paused: Bool = false;
var r: TParticleData;
// Format
@ -37,6 +42,7 @@ class ParticleSystemCPU {
// Velocity
var velocity: Vec3 = new Vec3(0.0, 0.0, 1.0); // object_align_factor: Float32Array
var velocityRandom: FastFloat = 0.0; // factor_random
var normalFactor: FastFloat = 0.0;
// Rotation
var rotation: Bool = false; // use_rotations
@ -114,11 +120,12 @@ class ParticleSystemCPU {
scale = r.particle_size;
scaleRandom = r.size_random;
velocity = new Vec3(r.object_align_factor[0], r.object_align_factor[1], r.object_align_factor[2]).mult(frameRate / baseFrameRate).mult(1 / scale);
velocity = new Vec3(r.object_align_factor[0], r.object_align_factor[1], r.object_align_factor[2]).mult(frameRate / baseFrameRate);
velocityRandom = r.factor_random * (frameRate / baseFrameRate);
normalFactor = r.normal_factor * (frameRate / baseFrameRate);
if (Scene.active.raw.gravity != null) {
gravity = new Vec3(Scene.active.raw.gravity[0], Scene.active.raw.gravity[1], Scene.active.raw.gravity[2]).mult(frameRate / baseFrameRate).mult(1 / scale);
gravity = new Vec3(Scene.active.raw.gravity[0], Scene.active.raw.gravity[1], Scene.active.raw.gravity[2]).mult(frameRate / baseFrameRate);
}
gravityFactor = r.weight_gravity * (frameRate / baseFrameRate);
textureFactor = r.weight_texture;
@ -139,11 +146,15 @@ class ParticleSystemCPU {
scaleElementsCount = getRampElementsLength();
scaleRampSizeFactor = getRampSizeFactor();
Scene.active.notifyOnInit(function () {
for (i in 0...count) addToPool();
switch (type) {
case 0: // Emission
loopAnim = {
tick: function () {
spawnTime += Time.delta * Time.scale;
if (paused) return;
spawnTime += Time.delta;
var expected: Int = Math.floor(spawnTime / spawnRate);
while (spawnedParticles < expected && spawnedParticles < count) {
spawnParticle();
@ -158,20 +169,13 @@ class ParticleSystemCPU {
if (loop) start();
}
}
Scene.active.notifyOnInit(function () {
if (autoStart) start();
});
case 1: // Hair
Scene.active.notifyOnInit(function () {
for (i in 0...count) spawnParticle();
});
default:
}
Scene.active.notifyOnInit(function () {
for (i in 0...count) addToPool();
});
});
}
@ -182,14 +186,12 @@ class ParticleSystemCPU {
Tween.to(loopAnim);
}
// TODO
public function pause() {
paused = true;
}
// TODO
public function resume() {
paused = false;
}
public function stop() {
@ -246,6 +248,8 @@ class ParticleSystemCPU {
var scalePos: FastFloat = owner.data.scalePos;
var scalePosParticle: FastFloat = cast(o, MeshObject).data.scalePos;
var normDir: Vec3 = new Vec3();
// TODO: add all properties from Blender's UI
switch (emitFrom) {
case 0: // Vertices
@ -253,6 +257,8 @@ class ParticleSystemCPU {
var i: Int = Std.int(Math.random() * (pa.values.length / pa.size));
var loc: Vec4 = new Vec4(pa.values[i * pa.size] * normFactor, pa.values[i * pa.size + 1] * normFactor, pa.values[i * pa.size + 2] * normFactor, 1);
if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize();
if (!localCoords) {
loc.applyQuat(objectRot);
loc.add(objectPos);
@ -274,6 +280,9 @@ class ParticleSystemCPU {
var pos: Vec3 = randomPointInTriangle(v0, v1, v2);
var loc: Vec4 = new Vec4(pos.x, pos.y, pos.z, 1).mult(normFactor);
if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize();
if (!localCoords) {
loc.applyQuat(objectRot);
loc.add(objectPos);
@ -285,6 +294,8 @@ class ParticleSystemCPU {
scaleFactorVolume.mult(0.5);
var loc: Vec4 = new Vec4((Math.random() * 2.0 - 1.0) * scaleFactorVolume.x, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.y, (Math.random() * 2.0 - 1.0) * scaleFactorVolume.z, 1);
if (normalFactor != 0.0) normDir = new Vec3(loc.x, loc.y, loc.z).normalize();
if (!localCoords) {
loc.applyQuat(objectRot);
loc.add(objectPos);
@ -310,7 +321,9 @@ class ParticleSystemCPU {
var randomZ: FastFloat = (Math.random() * 2 / (scale * particleScale) - 1 / (scale * particleScale)) * velocityRandom;
var g: Vec3 = new Vec3();
var rotatedVelocity: Vec4 = new Vec4(velocity.x + randomX, velocity.y + randomY, velocity.z + randomZ, 1);
if (normalFactor != 0.0) normDir = normDir.mult(normalFactor);
var rotatedVelocity: Vec4 = new Vec4(velocity.x + randomX + normDir.x, velocity.y + randomY + normDir.y, velocity.z + randomZ + normDir.z, 1);
if (!localCoords) rotatedVelocity.applyQuat(objectRot);
if (rotation) {
@ -371,7 +384,7 @@ class ParticleSystemCPU {
function updateParticles() {
for (particle => physics in particlePhysics) {
physics.age += Time.delta * Time.scale;
physics.age += Time.delta;
if (physics.age >= physics.lifetime) {
particlePhysics.remove(particle);
@ -379,14 +392,63 @@ class ParticleSystemCPU {
continue;
}
physics.velocity.x += physics.gravity.x * Time.delta * Time.scale;
physics.velocity.y += physics.gravity.y * Time.delta * Time.scale;
physics.velocity.z += physics.gravity.z * Time.delta * Time.scale;
physics.velocity.x += physics.gravity.x * Time.delta;
physics.velocity.y += physics.gravity.y * Time.delta;
physics.velocity.z += physics.gravity.z * Time.delta;
if (curveGuides != null && curveGuides.length > 0) {
var curveVelX: FastFloat = 0.0;
var curveVelY: FastFloat = 0.0;
var curveVelZ: FastFloat = 0.0;
var validCurves: Int = 0;
for (curve in curveGuides) {
if (curve != null && curve.data != null && curve.data.splines != null && curve.splinesLength > 0) {
var t = physics.age / physics.lifetime;
var tangent = curve.getTangent(t, 0);
tangent.w = 0.0;
tangent.applymat4(curve.transform.world);
tangent.normalize();
var curveLen = curve.getLength(0);
var speed = (curveLen / physics.lifetime) * curveGuideSpeed;
var tgtX = tangent.x * speed;
var tgtY = tangent.y * speed;
var tgtZ = tangent.z * speed;
if (localCoords) {
var targetVel = new Vec4(tgtX, tgtY, tgtZ, 0.0);
var invOwnerRot = new Quat(-owner.transform.rot.x, -owner.transform.rot.y, -owner.transform.rot.z, owner.transform.rot.w);
targetVel.applyQuat(invOwnerRot);
tgtX = targetVel.x;
tgtY = targetVel.y;
tgtZ = targetVel.z;
}
curveVelX += tgtX;
curveVelY += tgtY;
curveVelZ += tgtZ;
validCurves++;
}
}
if (validCurves > 0) {
curveVelX /= validCurves;
curveVelY /= validCurves;
curveVelZ /= validCurves;
physics.velocity.x += (curveVelX - physics.velocity.x) * curveGuideStrength;
physics.velocity.y += (curveVelY - physics.velocity.y) * curveGuideStrength;
physics.velocity.z += (curveVelZ - physics.velocity.z) * curveGuideStrength;
}
}
particle.transform.translate(
physics.velocity.x * Time.delta * Time.scale,
physics.velocity.y * Time.delta * Time.scale,
physics.velocity.z * Time.delta * Time.scale
physics.velocity.x * Time.delta,
physics.velocity.y * Time.delta,
physics.velocity.z * Time.delta
);
if (rotation && dynamicRotation && orientationAxis == 3) setVelocityHair(particle, physics.velocity, randQuat, phaseQuat);

View File

@ -141,10 +141,10 @@ class ParticleSystemGPU {
dimx = object.transform.dim.x;
dimy = object.transform.dim.y;
if (object.tilesheet != null) {
tilesx = object.tilesheet.getTilesX();
tilesy = object.tilesheet.getTilesY();
tilesFramerate = object.tilesheet.action.framerate;
if (object.activeTilesheet != null) {
tilesx = object.activeTilesheet.getTilesX();
tilesy = object.activeTilesheet.getTilesY();
tilesFramerate = object.activeTilesheet.action.framerate;
}
// Animate

View File

@ -1,6 +1,7 @@
package iron.object;
import kha.FastFloat;
import kha.Sound;
import kha.audio1.AudioChannel;
import iron.data.Data;
import iron.data.SceneFormat;
@ -13,9 +14,10 @@ class SpeakerObject extends Object {
public var data: TSpeakerData;
public var paused(default, null) = false;
public var sound(default, null): kha.Sound = null;
public var sound(default, null): Sound = null;
public var channels(default, null): Array<AudioChannel> = [];
public var volume(default, null) : FastFloat;
public var sampleRate(default, null) : Int;
public function new(data: TSpeakerData) {
super();
@ -26,28 +28,32 @@ class SpeakerObject extends Object {
if (data.sound == "") return;
Data.getSound(data.sound, function(sound: kha.Sound) {
this.sound = sound;
Data.getSound(data.sound, function(sound: Sound) {
this.sound = cloneSound(sound);
App.notifyOnInit(init);
});
}
function init() {
sampleRate = sound.sampleRate;
if (data.pitch != 1.0)
sound.sampleRate = Std.int(sampleRate * data.pitch);
if (visible && data.play_on_start) play();
}
public function play() {
if (sound == null || data.muted) return;
public function play(): AudioChannel {
if (sound == null || data.muted) return null;
if (paused) {
for (c in channels) c.play();
paused = false;
return;
return null;
}
var channel = Audio.play(sound, data.loop, data.stream);
if (channel != null) {
channels.push(channel);
if (data.attenuation > 0 && channels.length == 1) App.notifyOnUpdate(update);
}
return channel;
}
public function pause() {
@ -65,14 +71,24 @@ class SpeakerObject extends Object {
data.sound = sound;
Data.getSound(sound, function(sound: kha.Sound) {
this.sound = sound;
Data.getSound(sound, function(sound: Sound) {
this.sound = cloneSound(sound);
});
sampleRate = this.sound.sampleRate;
if (data.pitch != 1.0)
this.sound.sampleRate = Std.int(sampleRate * data.pitch);
}
public function setVolume(volume: FastFloat) {
data.volume = volume;
}
public function setPosition(position: Float) {
for (c in channels)
if (position < c.length) c.position = position;
}
function update() {
if (paused) return;
for (c in channels) if (c.finished) channels.remove(c);
@ -103,6 +119,17 @@ class SpeakerObject extends Object {
super.remove();
}
function cloneSound(sound: Sound): Sound {
if (sound == null) return null;
var s = Type.createEmptyInstance(Sound);
s.compressedData = sound.compressedData;
s.uncompressedData = sound.uncompressedData;
s.sampleRate = sound.sampleRate;
s.length = sound.length;
s.channels = sound.channels;
return s;
}
#end
}

View File

@ -1,11 +1,12 @@
package iron.object;
import iron.App;
import iron.Scene;
import iron.data.SceneFormat;
import iron.system.Time;
import haxe.ds.Map;
@:allow(iron.Scene)
class Tilesheet {
public var tileX: Float = 0.0;
@ -14,7 +15,8 @@ class Tilesheet {
public var flipY: Bool = false;
public var paused: Bool = false;
public var frame: Int = 0;
public var actions: Array<TTilesheetAction>;
public var raw: TTilesheetData = null;
public var actions: Array<TTilesheetAction> = null;
public var action: TTilesheetAction = null;
public var ready: Bool = false;
@ -31,8 +33,11 @@ class Tilesheet {
public function new(tilesheetData: TTilesheetData, ownerObject: MeshObject = null) {
owner = ownerObject;
raw = tilesheetData;
actions = tilesheetData.actions;
Scene.active.tilesheets.push(this);
pendingAction = tilesheetData.start_action;
if ((pendingAction == null || pendingAction == "") && actions.length > 0) {
pendingAction = actions[0].name;
@ -262,8 +267,10 @@ class Tilesheet {
}
public function remove() {
Scene.active.tilesheets.remove(this);
ready = false;
action = null;
raw = null;
actions = null;
owner = null;
currentMesh = null;

View File

@ -106,6 +106,7 @@ class Transform {
Rebuild the matrices, if needed.
**/
public function update() {
if (object.constraints != null && object.constraints.length > 0) dirty = true;
if (dirty) buildMatrix();
}
@ -150,12 +151,19 @@ class Transform {
if (boneParent != null) local.multmats(boneParent, local);
if (object.parent != null && !localOnly) {
world.multmats3x4(local, object.parent.transform.world);
// Swap multiplication order for linked objects to keep local transform intact
var swapMult: Bool = object.raw != null && object.parent.raw != null && object.parent.raw.group_ref != null && object.parent.raw.group_ref != "";
var a: Mat4 = swapMult ? object.parent.transform.world : local;
var b: Mat4 = swapMult ? local : object.parent.transform.world;
world.multmats3x4(a, b);
}
else {
world.setFrom(local);
}
// Constraints
if (object.constraints != null) for (c in object.constraints) c.apply(this);
worldUnpack.setFrom(world);
if (scaleWorld != 1.0) {
worldUnpack._00 *= scaleWorld;
@ -172,9 +180,6 @@ class Transform {
worldUnpack._23 *= scaleWorld;
}
// Constraints
if (object.constraints != null) for (c in object.constraints) c.apply(this);
computeDim();
// Update children
@ -265,7 +270,7 @@ class Transform {
}
function computeRadius() {
radius = Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z);
radius = 0.5 * Math.sqrt(dim.x * dim.x + dim.y * dim.y + dim.z * dim.z);
}
function computeDim() {

View File

@ -428,12 +428,10 @@ class Uniforms {
var v: Vec4 = null;
helpVec.set(0, 0, 0, 0);
switch (c.link) {
#if lnx_debug
case "_input": {
helpVec.set(Input.getMouse().x / iron.App.w(), Input.getMouse().y / iron.App.h(), Input.getMouse().down() ? 1.0 : 0.0, 0.0);
v = helpVec;
}
#end
default:
return false;
}
@ -690,7 +688,7 @@ class Uniforms {
}
case "_hosekSunDirection": {
var w = Scene.active.world;
if (w != null) {
if (w != null && w.raw.sun_direction != null) {
// Clamp Z for night cycle
helpVec.set(w.raw.sun_direction[0],
w.raw.sun_direction[1],
@ -956,6 +954,22 @@ class Uniforms {
}
static function setObjectConstant(g: Graphics, object: Object, location: ConstantLocation, c: TShaderConstant) {
#if lnx_spot
if (c.name == "LWVPSpot") {
var light = getSpot(0);
if (light != null) {
if (object == null) helpMat.setIdentity();
else helpMat.setFrom(object.transform.worldUnpack);
helpMat.multmat(light.VP);
helpMat.multmat(biasMat);
g.setMatrix(location, helpMat.self);
return;
}
}
#end
if (c.link == null) return;
var camera = Scene.active.camera;
@ -1263,17 +1277,17 @@ class Uniforms {
var vy: Null<kha.FastFloat> = null;
switch (c.link) {
case "_tilesheetOffset": {
var ts = cast(object, MeshObject).tilesheet;
var ts = cast(object, MeshObject).activeTilesheet;
vx = ts.tileX;
vy = ts.tileY;
}
case "_tilesheetFlip": {
var ts = cast(object, MeshObject).tilesheet;
var ts = cast(object, MeshObject).activeTilesheet;
vx = ts.flipX ? 1.0 : 0.0;
vy = ts.flipY ? 1.0 : 0.0;
}
case "_tilesheetTiles": {
var ts = cast(object, MeshObject).tilesheet;
var ts = cast(object, MeshObject).activeTilesheet;
vx = ts.getTilesX();
vy = ts.getTilesY();
}

View File

@ -0,0 +1,12 @@
package leenkx.logicnode;
class ActiveSceneObjectNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
return iron.Scene.active.root.getChild(iron.Scene.active.raw.name);
}
}

View File

@ -3,6 +3,8 @@ package leenkx.logicnode;
import iron.data.SceneFormat.TSceneFormat;
import iron.data.Data;
import iron.object.Object;
import iron.object.MeshObject;
import iron.Scene;
class AddParticleToObjectNode extends LogicNode {
@ -22,22 +24,22 @@ class AddParticleToObjectNode extends LogicNode {
if (objFrom == null || objTo == null) return;
var mobjFrom = cast(objFrom, iron.object.MeshObject);
var mobjFrom = cast(objFrom, 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);
var mobjTo = cast(objTo, MeshObject);
mobjTo.setupParticleSystem(iron.Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: @:privateAccess psys.r.name});
mobjTo.setupParticleSystem(Scene.active.raw.name, {name: 'LnxPS', seed: 0, particle: @:privateAccess psys.r.name});
mobjTo.render_emitter = inputs[4].get();
iron.Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
Scene.active.spawnObject(psys.data.raw.instance_object, null, function(o: Object) {
if (o != null) {
var c: iron.object.MeshObject = cast o;
var c: MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo;
@ -55,7 +57,7 @@ class AddParticleToObjectNode extends LogicNode {
var slot: Int = inputs[3].get();
var mobjTo: Object = inputs[4].get();
var mobjTo = cast(mobjTo, iron.object.MeshObject);
var mobjTo = cast(mobjTo, MeshObject);
#if lnx_json
sceneName += ".json";
@ -70,9 +72,15 @@ class AddParticleToObjectNode extends LogicNode {
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) {
for (i => ps in rawScene.particle_datas)
if (obj.particle_refs[slot].particle == ps.name){
slot = i;
break;
}
Scene.active.spawnObject(rawScene.particle_datas[slot].instance_object, null, function(o: Object) {
if (o != null) {
var c: iron.object.MeshObject = cast o;
var c: MeshObject = cast o;
if (mobjTo.particleChildren == null) mobjTo.particleChildren = [];
mobjTo.particleChildren.push(c);
c.particleOwner = mobjTo;

View File

@ -111,6 +111,8 @@ class AddPhysicsConstraintNode extends LogicNode {
}
}
}
con.name = property0;
pivotObject.addTrait(con);
}
#end

View File

@ -0,0 +1,46 @@
package leenkx.logicnode;
import iron.object.Object;
#if lnx_bullet
import leenkx.trait.physics.bullet.PhysicsHook;
#end
class AddPhysicsHookNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var obj: Object = inputs[1].get();
var target: Object = inputs[2].get();
var inputVerts: Dynamic = inputs[3].get();
var flattenedVerts: Array<Float> = [];
if (Std.isOfType(inputVerts, Array)) {
var vArray: Array<Dynamic> = cast inputVerts;
for (v in vArray) {
if (v.x != null) {
flattenedVerts.push(v.x);
flattenedVerts.push(v.y);
flattenedVerts.push(v.z);
}
else if (v[0] != null) {
flattenedVerts.push(v[0]);
flattenedVerts.push(v[1]);
flattenedVerts.push(v[2]);
}
}
}
#if lnx_bullet
var hook = obj.getTrait(PhysicsHook);
if (hook == null) {
hook = new PhysicsHook(target.name, flattenedVerts);
obj.addTrait(hook);
}
#end
runOutput(0);
}
}

View File

@ -20,7 +20,6 @@ class AddRigidBodyNode extends LogicNode {
override function run(from: Int) {
object = inputs[1].get();
if (object == null) return;
#if lnx_physics
@ -70,6 +69,7 @@ class AddRigidBodyNode extends LogicNode {
case "Cylinder": shape = Cylinder;
case "Convex Hull": shape = ConvexHull;
case "Mesh": shape = Mesh;
case "Compound Parent": shape = Compound;
}
rb = new RigidBody(shape, mass, friction, bounciness, group, mask);
@ -77,6 +77,48 @@ class AddRigidBodyNode extends LogicNode {
rb.staticObj = !active;
rb.isTriggerObject(trigger);
if (property0 == "Compound Parent") {
var compoundChildren = [];
for (child in object.children) {
var childRb: RigidBody = child.getTrait(RigidBody);
if (childRb != null) {
var childShape = 0;
switch (@:privateAccess childRb.shape) {
case Box: childShape = 0;
case Sphere: childShape = 1;
case ConvexHull: childShape = 2;
case Mesh: childShape = 3;
case Cone: childShape = 4;
case Cylinder: childShape = 5;
case Capsule: childShape = 6;
default: childShape = 0;
}
childRb.remove();
var m = object.transform.world.clone();
m.getInverse(object.transform.world);
m.multmat(child.transform.world);
var loc = new iron.math.Vec4();
var rot = new iron.math.Quat();
var scl = new iron.math.Vec4();
m.decompose(loc, rot, scl);
compoundChildren.push({
shape: childShape,
posX: loc.x,
posY: loc.y,
posZ: loc.z,
rotX: rot.x,
rotY: rot.y,
rotZ: rot.z,
rotW: rot.w,
dimX: child.transform.dim.x,
dimY: child.transform.dim.y,
dimZ: child.transform.dim.z
});
}
}
@:privateAccess rb.compoundChildren = compoundChildren;
}
if (property1) {
rb.linearDamping = linDamp;
rb.angularDamping = angDamp;

View File

@ -0,0 +1,38 @@
package leenkx.logicnode;
import iron.object.Object;
#if lnx_physics_soft
import leenkx.trait.physics.bullet.SoftBody;
#end
class AddSoftBodyNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var obj: Object = inputs[1].get();
if (obj == null) return;
var shape: Int = inputs[2].get(); // 0: Cloth, 1: Volume
var bend: Float = inputs[3].get();
var mass: Float = inputs[4].get();
var margin: Float = inputs[5].get();
var friction: Float = inputs[6].get();
var damping: Float = inputs[7].get();
var pressure: Float = inputs[8].get();
var lStiff: Float = inputs[9].get();
var aStiff: Float = inputs[10].get();
#if lnx_physics_soft
var sb: SoftBody = obj.getTrait(SoftBody);
if (sb == null) {
sb = new SoftBody(shape, bend, mass, margin, friction, damping, lStiff, aStiff, pressure);
obj.addTrait(sb);
}
#end
runOutput(0);
}
}

View File

@ -4,15 +4,20 @@ import iron.object.Object;
class AddTraitNode extends LogicNode {
public var property0: String;
var trait: Dynamic;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var object: Object = inputs[1].get();
assert(Error, object != null, "Object should not be null");
if (property0 == 'TraitName'){
var traitName: String = inputs[2].get();
assert(Error, object != null, "Object should not be null");
assert(Error, traitName != null, "Trait name should not be null");
var cname = Type.resolveClass(Main.projectPackage + "." + traitName);
@ -20,9 +25,17 @@ class AddTraitNode extends LogicNode {
assert(Error, cname != null, 'No trait with the name "$traitName" found, make sure that the trait is exported!');
assert(Warning, object.getTrait(cname) == null, 'Object already has the trait "$traitName" applied');
var trait = Type.createInstance(cname, []);
trait = Type.createInstance(cname, []);
} else
trait = inputs[2].get();
object.addTrait(trait);
runOutput(0);
}
override function get(from: Int): Dynamic {
return trait;
}
}

View File

@ -0,0 +1,23 @@
package leenkx.logicnode;
import iron.object.Object;
import iron.object.CurveObject;
class AlongCurveNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var object: Object = inputs[1].get();
var curve: CurveObject = inputs[2].get();
var splineIdx: Int = inputs[3].get();
var forwardAxis: String = inputs[4].get();
var position: Float = inputs[5].get();
curve.follow(object, position, splineIdx, forwardAxis);
runOutput(0);
}
}

View File

@ -0,0 +1,35 @@
package leenkx.logicnode;
import iron.object.Object;
import iron.object.Animation;
import iron.object.ObjectAnimation;
class AnimationNode 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 from == 0 ? null : 0;
var animation: Animation = object.animation;
if (animation == null) animation = object.getParentArmature(object.name);
var actions: Array<String> = [];
if (animation.isSkinned)
for(a in animation.armature.actions)
actions.push(a.name);
else
for (a in cast(animation, ObjectAnimation).oactions)
if (a != null)
actions.push(a.objects[0].name);
return from == 0 ? actions : actions.length;
}
}

View File

@ -0,0 +1,29 @@
package leenkx.logicnode;
#if lnx_navigation
import leenkx.trait.navigation.Navigation;
import leenkx.trait.NavMesh;
#end
import iron.object.Object;
import iron.math.Vec4;
class AroundNavigableLocationNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
#if lnx_navigation
var activeNavMesh: NavMesh = Navigation.active.navMeshes.get(inputs[0].get());
var position: Vec4 = inputs[1].get();
var radius: Float = inputs[2].get();
assert(Error, activeNavMesh != null, "No Navigation Mesh Present");
return activeNavMesh.getRandomPointAround(position, radius);
#end
return null;
}
}

View File

@ -12,7 +12,7 @@ class ArrayAddNode extends LogicNode {
override function run(from: Int) {
ar = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
// "Modify Original" == `false` -> Copy the input array
if (!inputs[2].get()) {

View File

@ -2,7 +2,7 @@ package leenkx.logicnode;
class ArrayGetNextNode extends LogicNode {
var i = 0;
var i = -1;
public function new(tree: LogicTree) {
super(tree);
@ -13,13 +13,13 @@ class ArrayGetNextNode extends LogicNode {
if (ar == null) return null;
var value = ar[i];
if (i < ar.length - 1)
i++;
else
i = 0;
var value = ar[i];
return value;
}

View File

@ -2,7 +2,7 @@ package leenkx.logicnode;
class ArrayGetPreviousNextNode extends LogicNode {
var i = 0;
var i = -1;
public function new(tree: LogicTree) {
super(tree);

View File

@ -13,7 +13,7 @@ class ArrayInsertNode extends LogicNode {
var index: Int = inputs[2].get();
var value: Dynamic = inputs[3].get();
if (ar == null || value == null) return;
assert(Error, ar != null && value != null, 'Array or Value should not be null');
ar.insert(index, value);

View File

@ -11,7 +11,7 @@ class ArrayLoopNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
index = -1;
for (val in ar) {

View File

@ -10,7 +10,7 @@ class ArrayRemoveNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var i: Int = inputs[2].get();
if (i < 0) i = ar.length + i;

View File

@ -10,7 +10,7 @@ class ArrayRemoveValueNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var val: Dynamic = inputs[2].get();

View File

@ -8,7 +8,7 @@ class ArrayResizeNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var len = inputs[2].get();

View File

@ -8,7 +8,7 @@ class ArraySetNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var i: Int = inputs[2].get();
var value: Dynamic = inputs[3].get();

View File

@ -10,7 +10,7 @@ class ArraySpliceNode extends LogicNode {
override function run(from: Int) {
var ar: Array<Dynamic> = inputs[1].get();
if (ar == null) return;
assert(Error, ar != null, 'Array should not be null');
var i = inputs[2].get();
var len = inputs[3].get();

View File

@ -0,0 +1,62 @@
package leenkx.logicnode;
class AsyncArrayLoopNode extends LogicNode {
var array:Array<Dynamic>;
var index:Int = 0;
var running:Bool = false;
var itemsPerFrame:Int = 1;
public function new(tree:LogicTree) {
super(tree);
}
override function run(from: Int) {
array = inputs[1].get();
itemsPerFrame = inputs[2].get();
index = 0;
if (array == null || array.length == 0) {
runOutput(3);
return;
}
running = true;
tree.notifyOnUpdate(update);
}
function update() {
if (!running) return;
var processed = 0;
while (processed < itemsPerFrame && index < array.length) {
index++;
processed++;
runOutput(0);
if (tree.loopBreak) {
tree.loopBreak = false;
running = false;
tree.removeUpdate(update);
runOutput(2);
return;
}
if (tree.loopContinue) {
tree.loopContinue = false;
continue;
}
}
if (index >= array.length) {
running = false;
tree.removeUpdate(update);
runOutput(3);
}
}
override function get(from: Int): Dynamic {
if (from == 1)
return array[index - 1];
return index - 1;
}
}

View File

@ -0,0 +1,63 @@
package leenkx.logicnode;
class AsyncLoopNode extends LogicNode {
var from:Int;
var to:Int;
var index:Int;
var running:Bool = false;
var itemsPerFrame:Int = 1;
public function new(tree:LogicTree) {
super(tree);
}
override function run(from: Int) {
this.from = inputs[1].get();
this.to = inputs[2].get();
this.itemsPerFrame = inputs[3].get();
index = this.from;
if (this.from >= this.to) {
runOutput(2);
return;
}
running = true;
tree.notifyOnUpdate(update);
}
function update() {
if (!running) return;
var processed = 0;
while (processed < itemsPerFrame && index < to) {
runOutput(0);
index++;
processed++;
if (tree.loopBreak) {
tree.loopBreak = false;
running = false;
tree.removeUpdate(update);
runOutput(2);
return;
}
if (tree.loopContinue) {
tree.loopContinue = false;
continue;
}
}
if (index >= to) {
running = false;
tree.removeUpdate(update);
runOutput(2);
}
}
override function get(from: Int): Dynamic {
return index - 1;
}
}

View File

@ -1,7 +1,5 @@
package leenkx.logicnode;
import iron.object.Object;
class CallFunctionNode extends LogicNode {
var result: Dynamic;
@ -11,8 +9,8 @@ class CallFunctionNode extends LogicNode {
}
override function run(from: Int) {
var object: Dynamic = inputs[1].get();
if (object == null) return;
var trait: Dynamic = inputs[1].get();
if (trait == null){ runOutput(0); return; }
var funName: String = inputs[2].get();
var args: Array<Dynamic> = [];
@ -21,9 +19,9 @@ class CallFunctionNode extends LogicNode {
args.push(inputs[i].get());
}
var func = Reflect.field(object, funName);
var func = Reflect.field(trait, funName);
if (func != null) {
result = Reflect.callMethod(object, func, args);
result = Reflect.callMethod(trait, func, args);
}
runOutput(0);

View File

@ -14,14 +14,16 @@ class CameraGetNode extends LogicNode {
case 3: leenkx.renderpath.Postprocess.camera_uniforms[3];//Camera: Exposure Compensation
case 4: leenkx.renderpath.Postprocess.camera_uniforms[4];//Fisheye Distortion
case 5: leenkx.renderpath.Postprocess.camera_uniforms[5];//DoF AutoFocus §§ If true, it ignores the DoF Distance setting
case 6: leenkx.renderpath.Postprocess.camera_uniforms[6];//DoF Distance
case 7: leenkx.renderpath.Postprocess.camera_uniforms[7];//DoF Focal Length mm
case 8: leenkx.renderpath.Postprocess.camera_uniforms[8];//DoF F-Stop
case 9: leenkx.renderpath.Postprocess.camera_uniforms[9];//Tonemapping Method
case 10: leenkx.renderpath.Postprocess.camera_uniforms[10];//Distort
case 11: leenkx.renderpath.Postprocess.camera_uniforms[11];//Film Grain
case 12: leenkx.renderpath.Postprocess.camera_uniforms[12];//Sharpen
case 13: leenkx.renderpath.Postprocess.camera_uniforms[13];//Vignette
case 6: new iron.math.Vec4(leenkx.renderpath.Postprocess.auto_focus[0], leenkx.renderpath.Postprocess.auto_focus[1], 0, 1); //Auto Focus Value
case 7: leenkx.renderpath.Postprocess.auto_focus[2]; //max blur
case 8: leenkx.renderpath.Postprocess.camera_uniforms[6];//DoF Distance
case 9: leenkx.renderpath.Postprocess.camera_uniforms[7];//DoF Focal Length mm
case 10: leenkx.renderpath.Postprocess.camera_uniforms[8];//DoF F-Stop
case 11: leenkx.renderpath.Postprocess.camera_uniforms[9];//Tonemapping Method
case 12: leenkx.renderpath.Postprocess.camera_uniforms[10];//Distort
case 13: leenkx.renderpath.Postprocess.camera_uniforms[11];//Film Grain
case 14: leenkx.renderpath.Postprocess.camera_uniforms[12];//Sharpen
case 15: leenkx.renderpath.Postprocess.camera_uniforms[13];//Vignette
default: 0.0;
}
}

View File

@ -23,6 +23,11 @@ class CameraSetNode extends LogicNode {
leenkx.renderpath.Postprocess.camera_uniforms[4] = inputs[1].get();//Fisheye Distortion
case 'Auto Focus':
leenkx.renderpath.Postprocess.camera_uniforms[5] = inputs[1].get();//DoF AutoFocus §§ If true, it ignores the DoF Distance setting
case 'Auto Focus Value':
leenkx.renderpath.Postprocess.auto_focus[0] = inputs[1].get().x;
leenkx.renderpath.Postprocess.auto_focus[1] = inputs[1].get().y;
case 'DoF Max Blur':
leenkx.renderpath.Postprocess.auto_focus[2] = inputs[1].get(); // DoF Max Blur
case 'DoF Distance':
leenkx.renderpath.Postprocess.camera_uniforms[6] = inputs[1].get();//DoF Distance
case 'DoF Length':

View File

@ -14,6 +14,7 @@ class CanvasSetSliderNode extends LogicNode {
}
#if lnx_ui
/*
function update() {
if (!canvas.ready) return;
@ -26,7 +27,7 @@ class CanvasSetSliderNode extends LogicNode {
catch (e: Dynamic) {}
runOutput(0);
}
}*/
override function run(from: Int) {
element = inputs[1].get();
@ -34,9 +35,15 @@ class CanvasSetSliderNode extends LogicNode {
canvas = CanvasScript.getActiveCanvas();
canvas.notifyOnReady(() -> {
canvas.getHandle(element).value = value;
runOutput(0);
});
// Ensure canvas is ready
tree.notifyOnUpdate(update);
update();
//tree.notifyOnUpdate(update);
//update();
}
#end
}

View File

@ -4,148 +4,15 @@ import iron.math.Vec4;
class ColorMixNode extends LogicNode {
var SIZE:Int = 38;
var GAMMA:Float = 2.4;
var EPSILON:Float = 0.00000001;
var SPD_C:Array<Float> = [0.96853629, 0.96855103, 0.96859338, 0.96877345, 0.96942204, 0.97143709, 0.97541862, 0.98074186, 0.98580992, 0.98971194, 0.99238027, 0.99409844, 0.995172, 0.99576545, 0.99593552, 0.99564041, 0.99464769, 0.99229579, 0.98638762, 0.96829712, 0.89228016, 0.53740239, 0.15360445, 0.05705719, 0.03126539, 0.02205445, 0.01802271, 0.0161346, 0.01520947, 0.01475977, 0.01454263, 0.01444459, 0.01439897, 0.0143762, 0.01436343, 0.01435687, 0.0143537, 0.01435408];
var SPD_M:Array<Float> = [0.51567122, 0.5401552, 0.62645502, 0.75595012, 0.92826996, 0.97223624, 0.98616174, 0.98955255, 0.98676237, 0.97312575, 0.91944277, 0.32564851, 0.13820628, 0.05015143, 0.02912336, 0.02421691, 0.02660696, 0.03407586, 0.04835936, 0.0001172, 0.00008554, 0.85267882, 0.93188793, 0.94810268, 0.94200977, 0.91478045, 0.87065445, 0.78827548, 0.65738359, 0.59909403, 0.56817268, 0.54031997, 0.52110241, 0.51041094, 0.50526577, 0.5025508, 0.50126452, 0.50083021];
var SPD_Y:Array<Float> = [0.02055257, 0.02059936, 0.02062723, 0.02073387, 0.02114202, 0.02233154, 0.02556857, 0.03330189, 0.05185294, 0.10087639, 0.24000413, 0.53589066, 0.79874659, 0.91186529, 0.95399623, 0.97137099, 0.97939505, 0.98345207, 0.98553736, 0.98648905, 0.98674535, 0.98657555, 0.98611877, 0.98559942, 0.98507063, 0.98460039, 0.98425301, 0.98403909, 0.98388535, 0.98376116, 0.98368246, 0.98365023, 0.98361309, 0.98357259, 0.98353856, 0.98351247, 0.98350101, 0.98350852];
var SPD_R:Array<Float> = [0.03147571, 0.03146636, 0.03140624, 0.03119611, 0.03053888, 0.02856855, 0.02459485, 0.0192952, 0.01423112, 0.01033111, 0.00765876, 0.00593693, 0.00485616, 0.00426186, 0.00409039, 0.00438375, 0.00537525, 0.00772962, 0.0136612, 0.03181352, 0.10791525, 0.46249516, 0.84604333, 0.94275572, 0.96860996, 0.97783966, 0.98187757, 0.98377315, 0.98470202, 0.98515481, 0.98537114, 0.98546685, 0.98550011, 0.98551031, 0.98550741, 0.98551323, 0.98551563, 0.98551547];
var SPD_G:Array<Float> = [0.49108579, 0.46944057, 0.4016578, 0.2449042, 0.0682688, 0.02732883, 0.013606, 0.01000187, 0.01284127, 0.02636635, 0.07058713, 0.70421692, 0.85473994, 0.95081565, 0.9717037, 0.97651888, 0.97429245, 0.97012917, 0.9425863, 0.99989207, 0.99989891, 0.13823139, 0.06968113, 0.05628787, 0.06111561, 0.08987709, 0.13656016, 0.22169624, 0.32176956, 0.36157329, 0.4836192, 0.46488579, 0.47440306, 0.4857699, 0.49267971, 0.49625685, 0.49807754, 0.49889859];
var SPD_B:Array<Float> = [0.97901834, 0.97901649, 0.97901118, 0.97892146, 0.97858555, 0.97743705, 0.97428075, 0.96663223, 0.94822893, 0.89937713, 0.76070164, 0.4642044, 0.20123039, 0.08808402, 0.04592894, 0.02860373, 0.02060067, 0.01656701, 0.01451549, 0.01357964, 0.01331243, 0.01347661, 0.01387181, 0.01435472, 0.01479836, 0.0151525, 0.01540513, 0.01557233, 0.0156571, 0.01571025, 0.01571916, 0.01572133, 0.01572502, 0.01571717, 0.01571905, 0.01571059, 0.01569728, 0.0157002];
var CIE_CMF_X:Array<Float> = [0.00006469, 0.00021941, 0.00112057, 0.00376661, 0.01188055, 0.02328644, 0.03455942, 0.03722379, 0.03241838, 0.02123321, 0.01049099, 0.00329584, 0.00050704, 0.00094867, 0.00627372, 0.01686462, 0.02868965, 0.04267481, 0.05625475, 0.0694704, 0.08305315, 0.0861261, 0.09046614, 0.08500387, 0.07090667, 0.05062889, 0.03547396, 0.02146821, 0.01251646, 0.00680458, 0.00346457, 0.00149761, 0.0007697, 0.00040737, 0.00016901, 0.00009522, 0.00004903, 0.00002];
var CIE_CMF_Y:Array<Float> = [0.00000184, 0.00000621, 0.00003101, 0.00010475, 0.00035364, 0.00095147, 0.00228226, 0.00420733, 0.0066888, 0.0098884, 0.01524945, 0.02141831, 0.03342293, 0.05131001, 0.07040208, 0.08783871, 0.09424905, 0.09795667, 0.09415219, 0.08678102, 0.07885653, 0.0635267, 0.05374142, 0.04264606, 0.03161735, 0.02088521, 0.01386011, 0.00810264, 0.0046301, 0.00249138, 0.0012593, 0.00054165, 0.00027795, 0.00014711, 0.00006103, 0.00003439, 0.00001771, 0.00000722];
var CIE_CMF_Z:Array<Float> = [0.00030502, 0.00103681, 0.00531314, 0.01795439, 0.05707758, 0.11365162, 0.17335873, 0.19620658, 0.18608237, 0.13995048, 0.08917453, 0.04789621, 0.02814563, 0.01613766, 0.0077591, 0.00429615, 0.00200551, 0.00086147, 0.00036904, 0.00019143, 0.00014956, 0.00009231, 0.00006813, 0.00002883, 0.00001577, 0.00000394, 0.00000158, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
var XYZ_RGB:Array<Array<Float>> = [[3.24306333, -1.53837619, -0.49893282], [-0.96896309, 1.87542451, 0.04154303], [0.05568392, -0.20417438, 1.05799454]];
public function linearToConcentration(l1:Float, l2:Float, t:Float):Float {
var t1 = l1 * Math.pow(1 - t, 2);
var t2 = l2 * Math.pow(t, 2);
return t2 / (t1 + t2);
}
public function spectralMix(color1:Array<Int>, color2:Array<Int>, t:Float):Array<Int> {
var lrgb1 = srgbToLinear(color1);
var lrgb2 = srgbToLinear(color2);
var R1 = linearToReflectance(lrgb1);
var R2 = linearToReflectance(lrgb2);
var l1 = dotProduct(R1, CIE_CMF_Y);
var l2 = dotProduct(R2, CIE_CMF_Y);
t = linearToConcentration(l1, l2, t);
var R:Array<Float> = new Array<Float>();
for (i in 0...SIZE) {
var KS = (1 - t) * Math.pow((1 - R1[i]), 2) / (2 * R1[i]) + t * Math.pow((1 - R2[i]), 2) / (2 * R2[i]);
var KM = 1 + KS - Math.sqrt(KS * KS + 2 * KS);
R.push(KM);
}
var xyz = reflectanceToXYZ(R);
return xyzToSrgb(xyz);
}
public function uncompand(x:Float):Float {
return x < 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, GAMMA);
}
public function compand(x:Float):Float {
return x < 0.0031308 ? x * 12.92 : 1.055 * Math.pow(x, 1.0 / GAMMA) - 0.055;
}
public function srgbToLinear(srgb:Array<Int>):Array<Float> {
var r = uncompand(srgb[0] / 255);
var g = uncompand(srgb[1] / 255);
var b = uncompand(srgb[2] / 255);
return [r, g, b];
}
public function linearToSrgb(lrgb:Array<Float>):Array<Int> {
var r = compand(lrgb[0]);
var g = compand(lrgb[1]);
var b = compand(lrgb[2]);
return [Math.round(clamp(r, 0, 1) * 255), Math.round(clamp(g, 0, 1) * 255), Math.round(clamp(b, 0, 1) * 255)];
}
public function reflectanceToXYZ(R:Array<Float>):Array<Float> {
var x = dotProduct(R, CIE_CMF_X);
var y = dotProduct(R, CIE_CMF_Y);
var z = dotProduct(R, CIE_CMF_Z);
return [x, y, z];
}
public function xyzToSrgb(xyz:Array<Float>):Array<Int> {
var r = dotProduct(XYZ_RGB[0], xyz);
var g = dotProduct(XYZ_RGB[1], xyz);
var b = dotProduct(XYZ_RGB[2], xyz);
return linearToSrgb([r, g, b]);
}
public function spectralUpsampling(lrgb:Array<Float>):Array<Float> {
var w = Math.min(Math.min(lrgb[0], lrgb[1]), lrgb[2]);
var lrgbNew = [lrgb[0] - w, lrgb[1] - w, lrgb[2] - w];
var c = Math.min(lrgbNew[1], lrgbNew[2]);
var m = Math.min(lrgbNew[0], lrgbNew[2]);
var y = Math.min(lrgbNew[0], lrgbNew[1]);
var r = Math.max(0, Math.min(lrgbNew[0] - lrgbNew[2], lrgbNew[0] - lrgbNew[1]));
var g = Math.max(0, Math.min(lrgbNew[1] - lrgbNew[2], lrgbNew[1] - lrgbNew[0]));
var b = Math.max(0, Math.min(lrgbNew[2] - lrgbNew[1], lrgbNew[2] - lrgbNew[0]));
return [w, c, m, y, r, g, b];
}
public function linearToReflectance(lrgb:Array<Float>):Array<Float> {
var weights = spectralUpsampling(lrgb);
var R:Array<Float> = new Array<Float>();
for (i in 0...SIZE) {
R[i] = Math.max(EPSILON,
weights[0]
+ weights[1] * SPD_C[i]
+ weights[2] * SPD_M[i]
+ weights[3] * SPD_Y[i]
+ weights[4] * SPD_R[i]
+ weights[5] * SPD_G[i]
+ weights[6] * SPD_B[i]
);
}
return R;
}
public function dotProduct(a:Array<Float>, b:Array<Float>):Float {
var sum:Float = 0;
for (i in 0...a.length) {
sum += a[i] * b[i];
}
return sum;
}
public function clamp(value:Float, minValue:Float, maxValue:Float):Float {
return Math.min(Math.max(value, minValue), maxValue);
}
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var Color1:Vec4 = inputs[0].get();
var Color2:Vec4 = inputs[1].get();
var mix:Float = inputs[2].get();
var c1: Vec4 = inputs[0].get();
var c2: Vec4 = inputs[1].get();
var factor: Float = inputs[2].get();
mix = Math.min(Math.max(mix, 0), 1);
var col1 = [Std.int(Color1.x*255), Std.int(Color1.y*255), Std.int(Color1.z*255)];
var col2 = [Std.int(Color2.x*255), Std.int(Color2.y*255), Std.int(Color2.z*255)];
var ColorMix = spectralMix(col1, col2, mix);
return new Vec4(ColorMix[0]/255, ColorMix[1]/255, ColorMix[2]/255, (Color1.w+Color2.w)/2);
return leenkx.trait.internal.Spectral.mix([c1, c2], [1.0 - factor, factor]);
}
}

View File

@ -0,0 +1,26 @@
package leenkx.logicnode;
import iron.math.Vec4;
import leenkx.trait.internal.Spectral;
class ColorsMixNode extends LogicNode {
var result = new Vec4();
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var colors: Array<Vec4> = inputs[0].get();
var factors: Array<Float> = inputs[1].get();
if (colors == null || factors == null || colors.length == 0 || factors.length == 0) {
return result;
}
result.setFrom(Spectral.mix(colors, factors));
return result;
}
}

View File

@ -0,0 +1,73 @@
package leenkx.logicnode;
import iron.object.Object;
import iron.object.MeshObject;
import leenkx.object.BreakerExtension;
import iron.math.Vec4;
class ConvexBreakNode extends LogicNode {
public var property0: String;
var objects: Array<Object>;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
objects = [];
var object: Object = inputs[1].get();
var breaker: ConvexBreaker = new ConvexBreaker(0.1);
@:privateAccess breaker.scaleUV = inputs[3].get();
@:privateAccess breaker.flatShading = inputs[4].get();
breaker.initBreakableObject(cast object, 0, 0, new Vec4(), new Vec4(), true);
var debris: Array<MeshObject> = [];
if (property0 == 'Plane')
debris = breaker.subdivideByPlane(cast object, inputs[3].get(), inputs[2].get());
else
debris = breaker.subdivideByImpact(cast object, inputs[2].get(), inputs[3].get(), 1, 1);
for (o in debris) {
var obj: Object = cast o;
obj.name = o.data.raw.name;
var dims = new kha.arrays.Float32Array(3);
dims[0] = o.data.geom.aabb.x;
dims[1] = o.data.geom.aabb.y;
dims[2] = o.data.geom.aabb.z;
obj.raw = cast {
type: "mesh_object",
name: obj.name,
data_ref: obj.name,
dimensions: dims
};
obj.addTrait(new leenkx.trait.internal.UniformsManager());
var ud = breaker.userDataMap.get(cast o);
if (ud == null) continue;
objects.push(obj);
}
if (objects.length > 1){
for (obj in objects)
obj.setParent(iron.Scene.active.root);
object.remove();
runOutput(1);
}
else
runOutput(2);
runOutput(0);
}
override function get(from: Int): Dynamic {
return objects;
}
}

View File

@ -1,7 +1,6 @@
package leenkx.logicnode;
import iron.object.Object;
import iron.math.Vec4;
import leenkx.system.Event;
class CreateMapNode extends LogicNode {

View File

@ -2,6 +2,8 @@ package leenkx.logicnode;
#if lnx_navigation
import leenkx.trait.navigation.Navigation;
import leenkx.trait.NavMesh;
import leenkx.trait.NavCrowd;
#end
import iron.object.Object;
@ -9,24 +11,30 @@ import iron.math.Vec4;
class CrowdGoToLocationNode extends LogicNode {
var object: Object;
var location: Vec4;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
object = inputs[1].get();
location = inputs[2].get();
var navMeshId: String = inputs[1].get();
var object: Object = inputs[2].get();
var location: Vec4 = inputs[3].get();
var maxSpeed: Float = inputs[4].get();
var maxAcceleration: Float = inputs[5].get();
var turnSpeed: Float = inputs[6].get();
assert(Error, object != null, "The object input not be null");
assert(Error, location != null, "The location to navigate to must not be null");
#if lnx_navigation
assert(Error, Navigation.active.navMeshes.length > 0, "No Navigation Mesh Present");
var crowdAgent: leenkx.trait.NavCrowd = object.getTrait(leenkx.trait.NavCrowd);
var activeNavMesh: NavMesh = Navigation.active.navMeshes.get(navMeshId);
assert(Error, activeNavMesh != null, "No Navigation Mesh Present");
var crowdAgent: NavCrowd = object.getTrait(NavCrowd);
assert(Error, crowdAgent != null, "Object does not have a NavCrowd trait");
crowdAgent.crowdAgentSetMaxSpeed(maxSpeed);
crowdAgent.crowdAgentSetMaxAcceleration(maxAcceleration);
crowdAgent.turnSpeed = turnSpeed;
crowdAgent.crowdAgentGoto(location);
#end
runOutput(0);

View File

@ -0,0 +1,33 @@
package leenkx.logicnode;
#if lnx_navigation
import leenkx.trait.navigation.Navigation;
import leenkx.trait.NavMesh;
import leenkx.trait.NavCrowd;
#end
import iron.object.Object;
import iron.math.Vec4;
class CrowdSetLocationNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var navMeshId: String = inputs[1].get();
var object: Object = inputs[2].get();
var location: Vec4 = inputs[3].get();
#if lnx_navigation
var activeNavMesh: NavMesh = Navigation.active.navMeshes.get(navMeshId);
assert(Error, activeNavMesh != null, "No Navigation Mesh Present");
var crowdAgent: NavCrowd = object.getTrait(NavCrowd);
assert(Error, crowdAgent != null, "Object does not have a NavCrowd trait");
crowdAgent.crowdAgentTeleport(location);
#end
runOutput(0);
}
}

View File

@ -0,0 +1,33 @@
package leenkx.logicnode;
import iron.object.Object;
import iron.object.MeshObject;
class CurveGuideNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if lnx_cpu_particles
var object: Object = inputs[1].get();
var slot: Int = inputs[2].get();
if (object == null){ runOutput(0); return; }
var mo: MeshObject = cast object;
var psys = mo.particleSystems != null ? mo.particleSystems[slot] : null;
if (psys == null){ runOutput(0); return; }
psys.curveGuides = inputs[3].get();
psys.curveGuideStrength = inputs[4].get();
psys.curveGuideSpeed = inputs[5].get();
#end
runOutput(0);
}
}

View File

@ -0,0 +1,27 @@
package leenkx.logicnode;
import iron.object.CurveObject;
import iron.object.MeshObject;
import iron.data.MeshData;
class DeformCurveNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var mo: MeshObject = inputs[1].get();
var curve: CurveObject = inputs[2].get();
var mData: MeshData = curve.generateDeformedMesh(mo.data, inputs[3].get(), inputs[4].get(), 1, inputs[5].get(), inputs[6].get());
if (mData != null){
mo.setData(mData);
mo.transform.scale.set(1, 1, 1);
mo.transform.buildMatrix();
}
runOutput(0);
}
}

View File

@ -8,11 +8,9 @@ import iron.object.CameraObject;
import leenkx.renderpath.RenderPathCreator;
class DrawCameraNode extends LogicNode {
static inline var numStaticInputs = 2;
var cameras: Array<CameraObject>;
var renderTargets: Array<kha.Image>;
var positions: Array<Vec2>;
var camera: CameraObject;
var renderTarget: kha.Image;
var position: Vec2 = new Vec2();
public function new(tree: LogicTree) {
super(tree);
@ -21,39 +19,18 @@ class DrawCameraNode extends LogicNode {
override function run(from: Int) {
switch (from) {
case 0: // Start
if (cameras == null) {
final numDynamicInputs = inputs.length - numStaticInputs;
final numCams = Std.int(numDynamicInputs / 5);
// Preallocate
cameras = [];
cameras.resize(numCams);
positions = [];
positions.resize(numCams);
for (i in 0...positions.length) {
positions[i] = new Vec2();
}
renderTargets = [];
renderTargets.resize(numCams);
}
for (i in 0...cameras.length) {
cameras[i] = inputs[numStaticInputs + i * 5].get();
positions[i].set(
inputs[numStaticInputs + i * 5 + 1].get(),
inputs[numStaticInputs + i * 5 + 2].get()
camera = inputs[2].get();
position.set(
inputs[3].get(),
inputs[4].get()
);
// TODO: implement proper rendertarget cache/pool
renderTargets[i] = kha.Image.createRenderTarget(
inputs[numStaticInputs + i * 5 + 3].get(), // w
inputs[numStaticInputs + i * 5 + 4].get(), // h
renderTarget = kha.Image.createRenderTarget(
inputs[5].get(), // w
inputs[6].get(), // h
kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil
);
}
tree.notifyOnRender(render);
tree.notifyOnRender2D(render2D);
@ -67,38 +44,37 @@ class DrawCameraNode extends LogicNode {
}
function render(g:kha.graphics4.Graphics) {
if (inputs[7].get()) return;
final rpPaused = RenderPath.active.paused;
RenderPath.active.paused = false;
final sceneCam = iron.Scene.active.camera;
for (i in 0...cameras.length) {
final cam = cameras[i];
final cam = camera;
final oldRT = cam.renderTarget;
cam.renderTarget = renderTargets[i];
cam.renderTarget = renderTarget;
iron.Scene.active.camera = cam;
cam.renderFrame(g);
cam.renderTarget = oldRT;
}
iron.Scene.active.camera = sceneCam;
RenderPath.active.paused = rpPaused;
}
function render2D(g: kha.graphics2.Graphics) {
for(i in 0...cameras.length) {
final rt = renderTargets[i];
if (inputs[7].get()) return;
final rt = renderTarget;
positions[i].set(
inputs[numStaticInputs + i * 5 + 1].get(),
inputs[numStaticInputs + i * 5 + 2].get()
position.set(
inputs[3].get(),
inputs[4].get()
);
final posX = positions[i].x;
final posY = positions[i].y;
final posX = position.x;
final posY = position.y;
g.color = 0xff000000;
g.fillRect(posX, posY, rt.width, rt.height);
@ -110,4 +86,3 @@ class DrawCameraNode extends LogicNode {
g.drawScaledImage(rt, posX, posY, rt.width, rt.height);
}
}
}

View File

@ -0,0 +1,129 @@
package leenkx.logicnode;
import iron.math.Vec4;
import kha.Image;
import kha.Color;
import leenkx.renderpath.RenderToTexture;
import iron.format.gif.Reader;
import iron.format.gif.Data;
import iron.format.gif.Tools;
import haxe.io.Bytes;
import haxe.io.BytesInput;
import leenkx.system.Event;
class DrawGifNode extends LogicNode {
var data:Data = null;
var frames:Int = 0;
var img: Array<Image> = [];
var lastImgName = "";
var duration = 0.0;
var index = 0;
var i = 0;
public function new(tree: LogicTree) {
super(tree);
Event.add('load', function() {
var extractedBytes:Bytes = Tools.extractFullRGBA(data, i);
img.push(kha.Image.fromBytes(extractedBytes, data.logicalScreenDescriptor.width, data.logicalScreenDescriptor.height, kha.graphics4.TextureFormat.RGBA32));
});
tree.notifyOnRemove(onRemove);
}
function onRemove() {
Event.remove('load');
}
override function run(from: Int) {
if (from == 0){
if (data == null){
runOutput(0);
return;
}
RenderToTexture.ensure2DContext("DrawGifNode");
final colorVec: Vec4 = inputs[3].get();
final anchorH: Int = inputs[4].get();
final anchorV: Int = inputs[5].get();
final x: Float = inputs[6].get();
final y: Float = inputs[7].get();
final width: Float = inputs[8].get();
final height: Float = inputs[9].get();
final angle: Float = inputs[10].get();
var sindex: Int = inputs[11].get();
var eindex: Int = inputs[12].get();
final fdur: Float = inputs[13].get();
final loop: Bool = inputs[14].get();
final drawx = x - 0.5 * width * anchorH;
final drawy = y - 0.5 * height * anchorV;
if(eindex == -1 || eindex > frames)
eindex = frames;
if(sindex < 0 || sindex > frames)
sindex = 0;
if (index < sindex || index > eindex)
index = sindex;
duration += iron.system.Time.delta;
if (duration >= fdur){
if (index < eindex)
index += 1;
else
if (loop) index = sindex;
duration = 0;
if (i < eindex){
++i;
Event.send('load');
}
}
if (img.length > (index - sindex)){
RenderToTexture.g.rotate(angle, x, y);
RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w);
RenderToTexture.g.drawScaledImage(img[index-sindex], drawx, drawy, width, height);
RenderToTexture.g.rotate(-angle, x, y);
}
runOutput(0);
}
else{
final imgName: String = inputs[2].get();
if (imgName != lastImgName) {
lastImgName = imgName;
img = [];
i = inputs[11].get();
index = i;
iron.data.Data.getBlob(imgName, (blob: kha.Blob) -> {
var bytes: Bytes = blob.toBytes();
var input: BytesInput = new BytesInput(bytes);
data = new Reader(input).read();
frames = Tools.framesCount(data);
if (i > frames) return;
var extractedBytes:Bytes = Tools.extractFullRGBA(data, i);
img.push(kha.Image.fromBytes(extractedBytes, data.logicalScreenDescriptor.width, data.logicalScreenDescriptor.height, kha.graphics4.TextureFormat.RGBA32));
++i;
Event.send('load');
});
}
}
}
override function get(from: Int): Dynamic {
if (from == 1)
return frames;
else
return index;
}
}

View File

@ -1,12 +1,15 @@
package leenkx.logicnode;
import iron.math.Vec4;
import iron.RenderPath;
import kha.Image;
import kha.Color;
import leenkx.renderpath.RenderToTexture;
class DrawImageRenderNode extends LogicNode {
var img: Image;
var img2D: Image;
var initialized: Bool = false;
public function new(tree: LogicTree) {
super(tree);
@ -14,8 +17,21 @@ class DrawImageRenderNode extends LogicNode {
override function run(from: Int) {
if (from == 1)
if (from == 1){
if (inputs[16].get()){
if (initialized) return;
initialized = true;
}
img = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(),
kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
if (inputs[15].get() || kha.Image.renderTargetsInvertedY())
img2D = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(),
kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
tree.notifyOnRender(render);
}
else {
RenderToTexture.ensure2DContext("DrawImageRenderNode");
@ -38,12 +54,14 @@ class DrawImageRenderNode extends LogicNode {
RenderToTexture.g.rotate(angle, x, y);
if (img != null){
RenderToTexture.g.color = 0xff000000;
RenderToTexture.g.fillRect(drawx, drawy, width, height);
RenderToTexture.g.color = RenderToTexture.g.color = Color.fromFloats(colorVec.x, colorVec.y, colorVec.z, colorVec.w);
if ((inputs[15].get() || kha.Image.renderTargetsInvertedY()) && img2D != null)
RenderToTexture.g.drawScaledSubImage(img2D, sx, sy, swidth, sheight, drawx, drawy, width, height);
else if(img != null)
RenderToTexture.g.drawScaledSubImage(img, sx, sy, swidth, sheight, drawx, drawy, width, height);
}
RenderToTexture.g.rotate(-angle, x, y);
@ -54,13 +72,11 @@ class DrawImageRenderNode extends LogicNode {
}
function render(g: kha.graphics4.Graphics) {
final rpPaused = RenderPath.active.paused;
RenderPath.active.paused = false;
var camera = inputs[2].get();
img = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(),
kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
final sceneCam = iron.Scene.active.camera;
final oldRT = camera.renderTarget;
@ -69,37 +85,29 @@ class DrawImageRenderNode extends LogicNode {
camera.renderFrame(g);
img = camera.renderTarget;
if (inputs[15].get() || kha.Image.renderTargetsInvertedY()) {
img = kha.Image.createRenderTarget(iron.App.w(), iron.App.h(),
kha.graphics4.TextureFormat.RGBA32,
kha.graphics4.DepthStencilFormat.NoDepthAndStencil);
img2D.g2.begin(true, Color.Transparent);
img2D.g2.color = Color.White;
img.g2.begin(true, Color.Transparent);
if (kha.Image.renderTargetsInvertedY())
img2D.g2.drawScaledImage(camera.renderTarget, 0, iron.App.h(), iron.App.w(), -iron.App.h());
else
img2D.g2.drawImage(camera.renderTarget, 0, 0);
img.g2.color = Color.White;
if (kha.Image.renderTargetsInvertedY()) {
img.g2.drawScaledImage(camera.renderTarget, 0, iron.App.h(), iron.App.w(), -iron.App.h());
} else {
img.g2.drawImage(camera.renderTarget, 0, 0);
}
if (inputs[15].get()) {
for (f in @:privateAccess iron.App.traitRenders2D) {
f(img.g2);
}
}
img.g2.end();
if (inputs[15].get())
for (f in @:privateAccess iron.App.traitRenders2D)
f(img2D.g2);
img2D.g2.end();
}
camera.renderTarget = oldRT;
iron.Scene.active.camera = sceneCam;
RenderPath.active.paused = rpPaused;
if (!inputs[16].get())
tree.removeRender(render);
}

View File

@ -16,8 +16,8 @@ class DrawRoundedRectNode extends LogicNode {
super(tree);
}
override function run(from: Int) {
#if lnx_ui
override function run(from: Int) {
RenderToTexture.ensure2DContext("DrawPolygonNode");
final anchorH: Int = inputs[4].get();
@ -61,8 +61,6 @@ class DrawRoundedRectNode extends LogicNode {
} else {
RenderToTexture.g.drawPolygon(0, 0, vertices, inputs[3].get());
}
#end
RenderToTexture.g.rotate(-angle, x, y);
runOutput(0);
}
@ -115,4 +113,5 @@ class DrawRoundedRectNode extends LogicNode {
return vertices;
}
#end
}

View File

@ -3,9 +3,13 @@ package leenkx.logicnode;
import kha.Font;
import kha.Color;
import leenkx.renderpath.RenderToTexture;
import kha.graphics2.VerTextAlignment;
import kha.graphics2.HorTextAlignment;
#if lnx_ui
import leenkx.ui.Canvas;
using zui.GraphicsExtension;
#end
class DrawStringNode extends LogicNode {
@ -13,13 +17,20 @@ class DrawStringNode extends LogicNode {
var lastFontName = "";
var string:String;
public var property1: String;
public var property2: String;
public function new(tree: LogicTree) {
super(tree);
}
#if lnx_ui
override function run(from: Int) {
RenderToTexture.ensure2DContext("DrawStringNode");
var horA = TextLeft;
var verA = TextTop;
string = Std.string(inputs[1].get());
var angle: Float = inputs[7].get();
@ -45,6 +56,18 @@ class DrawStringNode extends LogicNode {
return;
}
switch(property1){
case 'TextLeft': horA = TextLeft;
case 'TextCenter': horA = TextCenter;
case 'TextRight': horA = TextRight;
}
switch(property2){
case 'TextTop': verA = TextTop;
case 'TextMiddle': verA = TextMiddle;
case 'TextBottom': verA = TextBottom;
}
RenderToTexture.g.rotate(angle, inputs[5].get(), inputs[6].get());
final colorVec = inputs[4].get();
@ -53,7 +76,7 @@ class DrawStringNode extends LogicNode {
RenderToTexture.g.fontSize = inputs[3].get();
RenderToTexture.g.font = font;
RenderToTexture.g.drawString(string, inputs[5].get(), inputs[6].get());
RenderToTexture.g.drawAlignedString(string, inputs[5].get(), inputs[6].get(), horA, verA);
RenderToTexture.g.rotate(-angle, inputs[5].get(), inputs[6].get());
@ -65,4 +88,5 @@ class DrawStringNode extends LogicNode {
return from == 1 ? RenderToTexture.g.font.width(RenderToTexture.g.fontSize, string) : RenderToTexture.g.font.height(RenderToTexture.g.fontSize);
}
#end
}

View File

@ -0,0 +1,47 @@
package leenkx.logicnode;
import iron.object.Object;
import iron.object.CurveObject;
import iron.system.Time;
class FollowCurveNode extends LogicNode {
var progress: Float = -1;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var cycle: Bool = false;
var object: Object = inputs[1].get();
var curve: CurveObject = inputs[2].get();
var splineIdx: Int = inputs[3].get();
if (progress == -1) progress = inputs[8].get();
var len = curve.getLength(splineIdx);
var speed = inputs[5].get();
var currentDist = progress * len;
currentDist += (speed * Time.delta * (inputs[6].get() ? 1.0 : -1.0));
if (inputs[7].get()){
if (currentDist > len){ currentDist -= len; cycle = true; }
else if (currentDist < 0){ currentDist += len; cycle = true; }
}
progress = (len > 0) ? currentDist / len : 0.0;
if (cycle)
runOutput(1);
else{
curve.follow(object, progress, splineIdx, inputs[4].get());
runOutput(0);
}
}
override function get(from: Int): Dynamic {
return progress;
}
}

View File

@ -0,0 +1,57 @@
package leenkx.logicnode;
class FormatNumberNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var number: Float = inputs[0].get();
var thousandsSeparator: String = inputs[1].get();
var decimalSeparator: String = inputs[2].get();
var includeSymbol: Bool = inputs[3].get();
var s = Std.string(number);
var isNegative = s.charAt(0) == "-";
if (isNegative) s = s.substring(1);
var parts = s.split(".");
var integerPart = parts[0];
var decimalPart = (parts.length > 1) ? parts[1] : "";
var len = integerPart.length;
var formattedInteger = "";
if (len <= 3) {
formattedInteger = integerPart;
} else {
var firstGroupLen = len % 3;
if (firstGroupLen == 0) firstGroupLen = 3;
formattedInteger += integerPart.substring(0, firstGroupLen);
var i = firstGroupLen;
while (i < len) {
formattedInteger += thousandsSeparator + integerPart.substring(i, i + 3);
i += 3;
}
}
var finalNumber = formattedInteger;
if (decimalPart != "") {
finalNumber += decimalSeparator + decimalPart;
}
var result = "";
var sign = isNegative ? "-" : "";
if (includeSymbol) {
result = sign + "$" + finalNumber;
} else {
result = sign + finalNumber;
}
return result;
}
}

View File

@ -0,0 +1,32 @@
package leenkx.logicnode;
class FormatTimeNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var time: Float = inputs[0].get();
var format: String = inputs[1].get();
var totalSeconds = Math.floor(time);
var milliseconds = Math.floor((time - totalSeconds) * 1000);
var centiseconds = Math.floor(milliseconds / 10);
var h = Math.floor(totalSeconds / 3600);
var m = Math.floor((totalSeconds % 3600) / 60);
var s = totalSeconds % 60;
var result = format;
result = StringTools.replace(result, "HH", (h < 10 ? "0" : "") + h);
result = StringTools.replace(result, "MM", (m < 10 ? "0" : "") + m);
result = StringTools.replace(result, "SS", (s < 10 ? "0" : "") + s);
result = StringTools.replace(result, "MS", (centiseconds < 10 ? "0" : "") + centiseconds);
result = StringTools.replace(result, "H", "" + h);
result = StringTools.replace(result, "M", "" + m);
result = StringTools.replace(result, "S", "" + s);
return result;
}
}

View File

@ -2,22 +2,30 @@ package leenkx.logicnode;
import iron.object.Object;
#if lnx_navigation
import leenkx.trait.navigation.Navigation;
import leenkx.trait.NavAgent;
#end
class GetAgentDataNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Float {
override function get(from: Int): Dynamic {
var object: Object = inputs[0].get();
assert(Error, object != null, "The object to naviagte should not be null");
#if lnx_navigation
var agent: leenkx.trait.NavAgent = object.getTrait(leenkx.trait.NavAgent);
assert(Error, agent != null, "The object does not have NavAgent Trait");
if(from == 0) return agent.speed;
else return agent.turnDuration;
var agent: NavAgent = object.getTrait(NavAgent);
if (agent == null) return null;
return switch(from){
case 0: agent.navMeshId;
case 1: agent.speed;
case 2: agent.turnDuration;
case 3: @:privateAccess agent.path;
default: null;
}
#else
return null;
#end

View File

@ -0,0 +1,27 @@
package leenkx.logicnode;
import iron.object.LightObject;
class GetAreaLightDataNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var light: LightObject = inputs[0].get();
if (light == null) return null;
#if lnx_ltc
if (light.data.raw.type == "area")
if (from == 0)
return light.data.raw.size;
else
return light.data.raw.size_y;
#end
return null;
}
}

View File

@ -0,0 +1,12 @@
package leenkx.logicnode;
class GetAssetsNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
return leenkx.system.Starter.assets;
}
}

View File

@ -1,10 +1,18 @@
package leenkx.logicnode;
import iron.object.Object;
import iron.object.MeshObject;
import iron.object.CameraObject;
import iron.object.LightObject;
import iron.object.SpeakerObject;
import iron.object.DecalObject;
import iron.object.ProbeObject;
import iron.object.CurveObject;
class GetChildNode extends LogicNode {
public var property0: String;
public var property1: String;
public function new(tree: LogicTree) {
super(tree);
@ -12,22 +20,31 @@ class GetChildNode extends LogicNode {
override function get(from: Int): Dynamic {
var object: Object = inputs[0].get();
if (object == null) return null;
if (property0 != "By Type") {
var childName: String = inputs[1].get();
if (childName == null) return null;
if (object == null || childName == null) return null;
switch (property0) {
case "By Name":
return object.getChild(childName);
case "Contains":
return contains(object, childName);
case "Starts With":
return startsWith(object, childName);
case "Ends With":
return endsWith(object, childName);
return switch (property0) {
case "By Name": object.getChild(childName);
case "Contains": contains(object, childName);
case "Starts With": startsWith(object, childName);
case "Ends With": endsWith(object, childName);
default: null;
}
}
return null;
return switch (property1) {
case "MeshObject": object.getChildOfType(MeshObject);
case "CameraObject": object.getChildOfType(CameraObject);
case "LightObject": object.getChildOfType(LightObject);
case "SpeakerObject": object.getChildOfType(SpeakerObject);
case "DecalObject": object.getChildOfType(DecalObject);
case "ProbeObject": object.getChildOfType(ProbeObject);
case "CurveObject": object.getChildOfType(CurveObject);
default: null;
}
}
function contains(o: Object, name: String): Object {

View File

@ -13,6 +13,6 @@ class GetChildrenNode extends LogicNode {
if (object == null) return null;
return object.children;
return from == 0 ? object.children : object.children.length;
}
}

View File

@ -16,7 +16,9 @@ class GetContactsNode extends LogicNode {
#if lnx_physics
var physics = leenkx.trait.physics.PhysicsWorld.active;
var rbs = physics.getContacts(object.getTrait(RigidBody));
var rb = object.getTrait(RigidBody);
if (rb == null) return null;
var rbs = physics.getContacts(rb);
var obs = [];
if (rbs != null) for (rb in rbs) if (rb != null) obs.push(rb.object);

View File

@ -0,0 +1,39 @@
package leenkx.logicnode;
import iron.object.Object;
#if lnx_navigation
import leenkx.trait.navigation.Navigation;
import leenkx.trait.NavCrowd;
#end
class GetCrowdDataNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var object: Object = inputs[0].get();
#if lnx_navigation
var crowdAgent: NavCrowd = object.getTrait(NavCrowd);
if (crowdAgent == null) return null;
return switch(from){
case 0: crowdAgent.navMeshId;
case 1: crowdAgent.crowdAgentVelocity();
case 2: crowdAgent.crowdAgentMaxSpeed();
case 3: crowdAgent.crowdAgentMaxAcceleration();
case 4: crowdAgent.turnSpeed;
case 5: crowdAgent.crowdAgentPosition();
case 6: crowdAgent.crowdAgentNextPath();
case 7: @:privateAccess crowdAgent.agentID;
case 8: crowdAgent.crowdAgentPath();
default: null;
}
#else
return null;
#end
}
}

View File

@ -0,0 +1,33 @@
package leenkx.logicnode;
import iron.object.CurveObject;
import iron.math.Vec4;
class GetCurveDataNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var curve: CurveObject = inputs[0].get();
if (curve == null) return null;
return
switch (from) {
case 0:
curve.splinesLength;
case 1:
curve.equidistantSamples;
case 2:
curve.visible;
case 3:
curve.data.strength;
case 4:
new Vec4(curve.data.color[0], curve.data.color[1], curve.data.color[2], curve.data.color[3]);
case 5:
curve.curveMesh;
default:
null;
}
}
}

View File

@ -0,0 +1,36 @@
package leenkx.logicnode;
import iron.object.CurveObject;
import iron.math.Vec4;
class GetCurveSplineNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var curve: CurveObject = inputs[0].get();
var index: Int = inputs[1].get();
if (index > curve.splinesLength) return null;
switch(from){
case 0:
var points: Array<Vec4> = [];
var worldMat = curve.transform.world;
for (point in curve.data.splines[index].points){
var p = new Vec4(point.co[0], point.co[1], point.co[2]);
p.applymat(worldMat);
points.push(p);
}
return points;
case 1:
return curve.data.splines[index].closed;
case 2:
return curve.data.splines[index].resolution;
}
return null;
}
}

View File

@ -15,7 +15,10 @@ class GetFirstContactNode extends LogicNode {
#if lnx_physics
var physics = leenkx.trait.physics.PhysicsWorld.active;
var rbs = physics.getContacts(object.getTrait(RigidBody));
var rb = object.getTrait(RigidBody);
if (rb == null) return null;
var rbs = physics.getContacts(rb);
if (rbs != null && rbs.length > 0) return rbs[0].object;
#end

View File

@ -8,6 +8,6 @@ class GetGroupNode extends LogicNode {
override function get(from: Int): Dynamic {
var groupName: String = inputs[0].get();
return iron.Scene.active.getGroup(groupName);
return from == 0 ? iron.Scene.active.getGroup(groupName) : iron.Scene.active.getGroup(groupName).length;
}
}

View File

@ -0,0 +1,52 @@
package leenkx.logicnode;
import iron.system.Input;
import iron.system.Time;
class GetKeyboardNode extends LogicNode {
public var property0: String;
var activeKey: String = "";
var lastTime: Float = -1.0;
public function new(tree: LogicTree) {
super(tree);
tree.notifyOnUpdate(update);
}
function update() {
var keyboard = Input.getKeyboard();
var found = false;
for (k in iron.system.Keyboard.keys) {
var b = false;
switch (property0) {
case "started":
b = keyboard.started(k);
case "down":
b = keyboard.down(k);
case "released":
b = keyboard.released(k);
}
if (b) {
if (property0 == "started" || property0 == "released") {
var currentTime = Time.time();
if (currentTime == lastTime && Time.delta != 0) continue;
lastTime = currentTime;
}
activeKey = k;
found = true;
break;
}
}
if (found) {
runOutput(0);
}
}
override function get(from: Int): Dynamic {
return activeKey;
}
}

View File

@ -0,0 +1,37 @@
package leenkx.logicnode;
import iron.object.LightObject;
class GetLightDataNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var light: LightObject = inputs[0].get();
if (light.data == null) return null;
return switch (from) {
case 0:
return iron.data.LightData.typeToInt(light.data.raw.type);
#if lnx_single_point
case 1:
return light.data.raw.type == "sun" ? light.data.raw.strength / 0.325 : light.data.raw.strength / 0.01;
case 2:
return new iron.math.Vec4(light.data.raw.color[0], light.data.raw.color[1], light.data.raw.color[2]);
#else
case 1:
return light.data.raw.type == "sun" ? light.data.raw.strength / 0.325 : light.strength / 0.01;
case 2:
return light.data.raw.type == "sun" ? new iron.math.Vec4(light.data.raw.color[0], light.data.raw.color[1], light.data.raw.color[2]) : light.color;
#end
case 3:
light.data.raw.cast_shadow;
default:
null;
}
}
}

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