Merge branch 'master' into live-patch

This commit is contained in:
Moritz Brückner 2021-07-25 16:47:32 +02:00 committed by GitHub
commit cca82a69bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
82 changed files with 2046 additions and 689 deletions

View file

@ -21,7 +21,9 @@
uniform sampler2D gbufferD;
uniform sampler2D gbuffer0;
uniform sampler2D gbuffer1;
#ifdef _gbuffer2
uniform sampler2D gbuffer2;
#endif
#ifdef _VoxelAOvar
uniform sampler3D voxels;
@ -206,7 +208,9 @@ void main() {
vec3 v = normalize(eye - p);
float dotNV = max(dot(n, v), 0.0);
#ifdef _gbuffer2
vec4 g2 = textureLod(gbuffer2, texCoord, 0.0);
#endif
#ifdef _MicroShadowing
occspec.x = mix(1.0, occspec.x, dotNV); // AO Fresnel
@ -221,14 +225,16 @@ void main() {
vec3 envl = shIrradiance(n, shirr);
if (g2.b < 0.5) {
envl = envl;
} else {
envl = vec3(1.0);
}
#ifdef _gbuffer2
if (g2.b < 0.5) {
envl = envl;
} else {
envl = vec3(1.0);
}
#endif
#ifdef _EnvTex
envl /= PI;
envl /= PI;
#endif
#else
vec3 envl = vec3(1.0);

View file

@ -60,7 +60,7 @@ float random(vec2 coords) {
vec3 nishita_lookupLUT(const float height, const float sunTheta) {
vec2 coords = vec2(
sqrt(height * (1 / nishita_atmo_radius)),
sqrt(height * (1 / nishita_atmo_radius)),
0.5 + 0.5 * sign(sunTheta - HALF_PI) * sqrt(abs(sunTheta * (1 / HALF_PI) - 1))
);
return textureLod(nishitaLUT, coords, 0.0).rgb;
@ -125,7 +125,7 @@ vec3 nishita_atmosphere(const vec3 r, const vec3 r0, const vec3 pSun, const floa
// Idea behind this: "Rotate" everything by iPos (-> iPos is the new zenith) and then all calculations for the
// inner integral only depend on the sample height (iHeight) and sunTheta (angle between sun and new zenith).
float sunTheta = acos(dot(normalize(iPos), normalize(pSun)));
vec3 jODepth = nishita_lookupLUT(iHeight, sunTheta);// * vec3(14000000 / 255, 14000000 / 255, 2000000 / 255);
vec3 jODepth = nishita_lookupLUT(iHeight, sunTheta);
// Apply dithering to reduce visible banding
jODepth += mix(-1000, 1000, random(r.xy));

View file

@ -1,5 +1,7 @@
package armory.logicnode;
import iron.math.Quat;
import iron.math.Vec4;
import iron.object.Object;
import iron.object.BoneAnimation;
import iron.math.Mat4;
@ -26,19 +28,28 @@ class BoneFKNode extends LogicNode {
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
if (anim == null) anim = object.getParentArmature(object.name);
// Manipulating bone in world space
// Get bone in armature
var bone = anim.getBone(boneName);
m = anim.getBoneMat(bone);
w = anim.getAbsMat(bone);
function moveBone() {
m.setFrom(w);
m.multmat(transform);
iw.getInverse(w);
m.multmat(iw);
var t2 = Mat4.identity();
var loc= new Vec4();
var rot = new Quat();
var scl = new Vec4();
// anim.removeUpdate(moveBone);
// notified = false;
//Set scale to Armature scale. Bone scaling not yet implemented
t2.setFrom(transform);
t2.decompose(loc, rot, scl);
scl = object.transform.world.getScale();
t2.compose(loc, rot, scl);
//Set the bone local transform from world transform
anim.setBoneMatFromWorldMat(t2, bone);
//Remove this method from animation loop after FK
anim.removeUpdate(moveBone);
notified = false;
}
if (!notified) {

View file

@ -7,6 +7,13 @@ import iron.math.Vec4;
class BoneIKNode extends LogicNode {
var goal: Vec4;
var pole: Vec4;
var poleEnabled: Bool;
var chainLength: Int;
var maxIterartions: Int;
var precision: Float;
var rollAngle: Float;
var notified = false;
public function new(tree: LogicTree) {
@ -19,6 +26,12 @@ class BoneIKNode extends LogicNode {
var object: Object = inputs[1].get();
var boneName: String = inputs[2].get();
goal = inputs[3].get();
poleEnabled = inputs[4].get();
pole = inputs[5].get();
chainLength = inputs[6].get();
maxIterartions = inputs[7].get();
precision = inputs[8].get();
rollAngle = inputs[9].get();
if (object == null || goal == null) return;
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
@ -26,11 +39,15 @@ class BoneIKNode extends LogicNode {
var bone = anim.getBone(boneName);
function solveBone() {
anim.solveIK(bone, goal);
if(! poleEnabled) pole = null;
// anim.removeUpdate(solveBone);
// notified = false;
function solveBone() {
//Solve IK
anim.solveIK(bone, goal, precision, maxIterartions, chainLength, pole, rollAngle);
//Remove this method from animation loop after IK
anim.removeUpdate(solveBone);
notified = false;
}
if (!notified) {

View file

@ -19,9 +19,9 @@ class CompareNode extends LogicNode {
switch (property0) {
case "Equal":
cond = Std.is(v1, Vec4) ? v1.equals(v2) : v1 == v2;
cond = Std.isOfType(v1, Vec4) ? v1.equals(v2) : v1 == v2;
case "Almost Equal":
cond = Std.is(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1;
cond = Std.isOfType(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1;
case "Greater":
cond = v1 > v2;
case "Greater Equal":

View file

@ -18,9 +18,9 @@ class GateNode extends LogicNode {
switch (property0) {
case "Equal":
cond = Std.is(v1, Vec4) ? v1.equals(v2) : v1 == v2;
cond = Std.isOfType(v1, Vec4) ? v1.equals(v2) : v1 == v2;
case "Almost Equal":
cond = Std.is(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1;
cond = Std.isOfType(v1, Vec4) ? v1.almostEquals(v2, property1) : Math.abs(v1 - v2) < property1;
case "Greater":
cond = v1 > v2;
case "Greater Equal":

View file

@ -0,0 +1,31 @@
package armory.logicnode;
import iron.object.Object;
import iron.object.BoneAnimation;
class GetBoneFkIkOnlyNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Bool {
#if arm_skin
var object: Object = inputs[0].get();
var boneName: String = inputs[1].get();
if (object == null) return null;
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
if (anim == null) anim = object.getParentArmature(object.name);
// Get bone in armature
var bone = anim.getBone(boneName);
//Get bone transform in world coordinates
return bone.is_ik_fk_only;
#end
}
}

View file

@ -0,0 +1,30 @@
package armory.logicnode;
import iron.object.Object;
import iron.object.BoneAnimation;
import iron.math.Mat4;
class GetBoneTransformNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Mat4 {
#if arm_skin
var object: Object = inputs[0].get();
var boneName: String = inputs[1].get();
if (object == null) return null;
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
if (anim == null) anim = object.getParentArmature(object.name);
// Get bone in armature
var bone = anim.getBone(boneName);
return anim.getAbsWorldMat(bone);
#end
}
}

View file

@ -0,0 +1,33 @@
package armory.logicnode;
import iron.system.Input;
class GetGamepadStartedNode extends LogicNode {
var buttonStarted: Null<String>;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var g = Input.getGamepad(inputs[0].get());
buttonStarted = null;
for (b in Gamepad.buttons) {
if (g.started(b)) {
buttonStarted = b;
break;
}
}
if (buttonStarted != null) {
runOutput(0);
}
}
override function get(from: Int) {
return buttonStarted;
}
}

View file

@ -0,0 +1,24 @@
package armory.logicnode;
import armory.system.InputMap;
class GetInputMapKeyNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
var inputMap = inputs[0].get();
var key = inputs[1].get();
var k = InputMap.getInputMapKey(inputMap, key);
if (k != null) {
if (from == 0) return k.scale;
else if (from == 1) return k.deadzone;
}
return null;
}
}

View file

@ -0,0 +1,32 @@
package armory.logicnode;
import iron.system.Input;
class GetKeyboardStartedNode extends LogicNode {
var kb = Input.getKeyboard();
var keyStarted: Null<String>;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
keyStarted = null;
for (k in Keyboard.keys) {
if (kb.started(k)) {
keyStarted = k;
break;
}
}
if (keyStarted != null) {
runOutput(0);
}
}
override function get(from: Int) {
return keyStarted;
}
}

View file

@ -10,9 +10,22 @@ class GetLocationNode extends LogicNode {
override function get(from: Int): Dynamic {
var object: Object = inputs[0].get();
var relative: Bool = inputs[1].get();
if (object == null) return null;
return object.transform.world.getLoc();
var loc = object.transform.world.getLoc();
if (relative && object.parent != null) {
loc.sub(object.parent.transform.world.getLoc()); // Add parent location influence
// Convert loc to parent local space
var dotX = loc.dot(object.parent.transform.right());
var dotY = loc.dot(object.parent.transform.look());
var dotZ = loc.dot(object.parent.transform.up());
loc.set(dotX, dotY, dotZ);
}
return loc;
}
}

View file

@ -0,0 +1,32 @@
package armory.logicnode;
import iron.system.Input;
class GetMouseStartedNode extends LogicNode {
var m = Input.getMouse();
var buttonStarted: Null<String>;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
buttonStarted = null;
for (b in Mouse.buttons) {
if (m.started(b)) {
buttonStarted = b;
break;
}
}
if (buttonStarted != null) {
runOutput(0);
}
}
override function get(from: Int) {
return buttonStarted;
}
}

View file

@ -16,12 +16,12 @@ class GetTraitNameNode extends LogicNode {
case 0: {
// Check CanvasScript
var cname = cast Type.resolveClass("armory.trait.internal.CanvasScript");
if (Std.is(trait, cname)) {
if (Std.isOfType(trait, cname)) {
return trait.cnvName;
}
// Check WasmScript
var cname = cast Type.resolveClass("armory.trait.internal.WasmScript");
if (Std.is(trait, cname)) {
if (Std.isOfType(trait, cname)) {
return trait.wasmName;
}
// Other

View file

@ -2,11 +2,31 @@ package armory.logicnode;
class MergeNode extends LogicNode {
/** Execution mode. **/
public var property0: String;
var lastInputIndex = -1;
public function new(tree: LogicTree) {
super(tree);
tree.notifyOnLateUpdate(lateUpdate);
}
override function run(from: Int) {
// Check if there already were executions on the same frame
if (lastInputIndex != -1 && property0 == "once_per_frame") {
return;
}
lastInputIndex = from;
runOutput(0);
}
override function get(from: Int): Dynamic {
return lastInputIndex;
}
function lateUpdate() {
lastInputIndex = -1;
}
}

View file

@ -4,7 +4,7 @@ import armory.trait.internal.CanvasScript;
import iron.Scene;
#if arm_ui
import zui.Canvas.Anchor;
import armory.ui.Canvas.Anchor;
#end
class OnCanvasElementNode extends LogicNode {

View file

@ -0,0 +1,35 @@
package armory.logicnode;
import armory.system.InputMap;
class OnInputMapNode extends LogicNode {
var inputMap: Null<InputMap>;
public function new(tree: LogicTree) {
super(tree);
tree.notifyOnUpdate(update);
}
function update() {
var i = inputs[0].get();
inputMap = InputMap.getInputMap(i);
if (inputMap != null) {
if (inputMap.started()) {
runOutput(0);
}
if (inputMap.released()) {
runOutput(1);
}
}
}
override function get(from: Int): Dynamic {
if (from == 2) return inputMap.value();
else return inputMap.lastKeyPressed;
}
}

View file

@ -0,0 +1,22 @@
package armory.logicnode;
class OncePerFrameNode extends LogicNode {
var c = false;
public function new(tree: LogicTree) {
super(tree);
tree.notifyOnUpdate(update);
}
override function run(from: Int) {
if(c) {
c = false;
runOutput(0);
}
}
function update() {
c = true;
}
}

View file

@ -8,7 +8,7 @@ class PauseTraitNode extends LogicNode {
override function run(from: Int) {
var trait: Dynamic = inputs[1].get();
if (trait == null || !Std.is(trait, LogicTree)) return;
if (trait == null || !Std.isOfType(trait, LogicTree)) return;
cast(trait, LogicTree).pause();

View file

@ -0,0 +1,19 @@
package armory.logicnode;
import armory.system.InputMap;
class RemoveInputMapKeyNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var inputMap = inputs[1].get();
var key = inputs[2].get();
if (InputMap.removeInputMapKey(inputMap, key)) {
runOutput(0);
}
}
}

View file

@ -8,7 +8,7 @@ class ResumeTraitNode extends LogicNode {
override function run(from: Int) {
var trait: Dynamic = inputs[1].get();
if (trait == null || !Std.is(trait, LogicTree)) return;
if (trait == null || !Std.isOfType(trait, LogicTree)) return;
cast(trait, LogicTree).resume();

View file

@ -0,0 +1,37 @@
package armory.logicnode;
class SelectNode extends LogicNode {
/** Execution mode. **/
public var property0: String;
var value: Dynamic = null;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
// Get value according to the activated input (run() can only be called
// if the execution mode is from_input).
value = inputs[from + Std.int(inputs.length / 2)].get();
runOutput(0);
}
override function get(from: Int): Dynamic {
if (property0 == "from_index") {
var index = inputs[0].get() + 2;
// Return default value for invalid index
if (index < 2 || index >= inputs.length) {
return inputs[1].get();
}
return inputs[index].get();
}
// from_input
return value;
}
}

View file

@ -0,0 +1,35 @@
package armory.logicnode;
import iron.math.Quat;
import iron.math.Vec4;
import iron.object.Object;
import iron.object.BoneAnimation;
class SetBoneFkIkOnlyNode extends LogicNode {
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
#if arm_skin
var object: Object = inputs[1].get();
var boneName: String = inputs[2].get();
var fk_ik_only: Bool = inputs[3].get();
if (object == null) return;
var anim = object.animation != null ? cast(object.animation, BoneAnimation) : null;
if (anim == null) anim = object.getParentArmature(object.name);
// Get bone in armature
var bone = anim.getBone(boneName);
//Set bone animated by FK or IK only
bone.is_ik_fk_only = fk_ik_only;
runOutput(0);
#end
}
}

View file

@ -0,0 +1,46 @@
package armory.logicnode;
import armory.system.InputMap;
class SetInputMapKeyNode extends LogicNode {
public var property0: String;
public function new(tree: LogicTree) {
super(tree);
}
override function run(from: Int) {
var inputMap = inputs[1].get();
var key = inputs[2].get();
var scale = inputs[3].get();
var deadzone = inputs[4].get();
var index = inputs[5].get();
var i = InputMap.getInputMap(inputMap);
if (i == null) {
i = InputMap.addInputMap(inputMap);
}
var k = InputMap.getInputMapKey(inputMap, key);
if (k == null) {
switch(property0) {
case "keyboard": k = i.addKeyboard(key, scale);
case "mouse": k = i.addMouse(key, scale, deadzone);
case "gamepad": {
k = i.addGamepad(key, scale, deadzone);
k.setIndex(index);
}
}
} else {
k.scale = scale;
k.deadzone = deadzone;
k.setIndex(index);
}
runOutput(0);
}
}

View file

@ -13,9 +13,21 @@ class SetLocationNode extends LogicNode {
override function run(from: Int) {
var object: Object = inputs[1].get();
var vec: Vec4 = inputs[2].get();
var relative: Bool = inputs[3].get();
if (object == null || vec == null) return;
if (!relative && object.parent != null) {
var loc = vec.clone();
loc.sub(object.parent.transform.world.getLoc()); // Remove parent location influence
// Convert vec to parent local space
var dotX = loc.dot(object.parent.transform.right());
var dotY = loc.dot(object.parent.transform.look());
var dotZ = loc.dot(object.parent.transform.up());
vec.set(dotX, dotY, dotZ);
}
object.transform.loc.setFrom(vec);
object.transform.buildMatrix();
@ -26,4 +38,4 @@ class SetLocationNode extends LogicNode {
runOutput(0);
}
}
}

View file

@ -1,40 +1,40 @@
package armory.logicnode;
import iron.Scene;
import iron.data.MaterialData;
import iron.object.Object;
import armory.trait.internal.UniformsManager;
class SetMaterialImageParamNode extends LogicNode {
static var registered = false;
static var map = new Map<MaterialData, Map<String, kha.Image>>();
public function new(tree: LogicTree) {
super(tree);
if (!registered) {
registered = true;
iron.object.Uniforms.externalTextureLinks.push(textureLink);
}
}
override function run(from: Int) {
var mat = inputs[1].get();
if (mat == null) return;
var entry = map.get(mat);
if (entry == null) {
entry = new Map();
map.set(mat, entry);
var perObject: Null<Bool>;
var object = inputs[1].get();
if(object == null) return;
perObject = inputs[2].get();
if(perObject == null) perObject = false;
var mat = inputs[3].get();
if(mat == null) return;
if(! perObject){
UniformsManager.removeObjectFromMap(object, Texture);
object = Scene.active.root;
}
iron.data.Data.getImage(inputs[3].get(), function(image: kha.Image) {
entry.set(inputs[2].get(), image); // Node name, value
var img = inputs[5].get();
if(img == null) return;
iron.data.Data.getImage(img, function(image: kha.Image) {
UniformsManager.setTextureValue(mat, object, inputs[4].get(), image);
});
runOutput(0);
}
static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
if (mat == null) return null;
var entry = map.get(mat);
if (entry == null) return null;
return entry.get(link);
}
}

View file

@ -1,38 +1,35 @@
package armory.logicnode;
import iron.Scene;
import iron.math.Vec4;
import iron.data.MaterialData;
import iron.object.Object;
import armory.trait.internal.UniformsManager;
class SetMaterialRgbParamNode extends LogicNode {
static var registered = false;
static var map = new Map<MaterialData, Map<String, Vec4>>();
public function new(tree: LogicTree) {
super(tree);
if (!registered) {
registered = true;
iron.object.Uniforms.externalVec3Links.push(vec3Link);
}
}
override function run(from: Int) {
var mat = inputs[1].get();
if (mat == null) return;
var entry = map.get(mat);
if (entry == null) {
entry = new Map();
map.set(mat, entry);
var perObject: Null<Bool>;
var object = inputs[1].get();
if(object == null) return;
perObject = inputs[2].get();
if(perObject == null) perObject = false;
var mat = inputs[3].get();
if(mat == null) return;
if(! perObject){
UniformsManager.removeObjectFromMap(object, Vector);
object = Scene.active.root;
}
entry.set(inputs[2].get(), inputs[3].get()); // Node name, value
UniformsManager.setVec3Value(mat, object, inputs[4].get(), inputs[5].get());
runOutput(0);
}
static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
if (mat == null) return null;
var entry = map.get(mat);
if (entry == null) return null;
return entry.get(link);
}
}

View file

@ -1,37 +1,35 @@
package armory.logicnode;
import iron.Scene;
import iron.data.MaterialData;
import iron.object.Object;
import armory.trait.internal.UniformsManager;
class SetMaterialValueParamNode extends LogicNode {
static var registered = false;
static var map = new Map<MaterialData, Map<String, Null<kha.FastFloat>>>();
public function new(tree: LogicTree) {
super(tree);
if (!registered) {
registered = true;
iron.object.Uniforms.externalFloatLinks.push(floatLink);
}
}
override function run(from: Int) {
var mat = inputs[1].get();
if (mat == null) return;
var entry = map.get(mat);
if (entry == null) {
entry = new Map();
map.set(mat, entry);
var perObject: Null<Bool>;
var object = inputs[1].get();
if(object == null) return;
perObject = inputs[2].get();
if(perObject == null) perObject = false;
var mat = inputs[3].get();
if(mat == null) return;
if(! perObject){
UniformsManager.removeObjectFromMap(object, Float);
object = Scene.active.root;
}
entry.set(inputs[2].get(), inputs[3].get()); // Node name, value
UniformsManager.setFloatValue(mat, object, inputs[4].get(), inputs[5].get());
runOutput(0);
}
static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
if (mat == null) return null;
var entry = map.get(mat);
if (entry == null) return null;
return entry.get(link);
}
}

View file

@ -14,7 +14,8 @@ class SetParentNode extends LogicNode {
var parent: Object;
var isUnparent = false;
if (Std.is(inputs[2].fromNode, ObjectNode)) {
if (Std.isOfType(inputs[2].fromNode, ObjectNode)) {
var parentNode = cast(inputs[2].fromNode, ObjectNode);
isUnparent = parentNode.objectName == "";
}

View file

@ -10,7 +10,7 @@ class SetTraitPausedNode extends LogicNode {
var trait: Dynamic = inputs[1].get();
var paused: Bool = inputs[2].get();
if (trait == null || !Std.is(trait, LogicTree)) return;
if (trait == null || !Std.isOfType(trait, LogicTree)) return;
paused ? cast(trait, LogicTree).pause() : cast(trait, LogicTree).resume();

View file

@ -3,8 +3,6 @@ package armory.logicnode;
import iron.object.Object;
import iron.math.Vec4;
using armory.object.TransformExtension;
class VectorToObjectOrientationNode extends LogicNode {
public function new(tree: LogicTree) {
@ -18,7 +16,7 @@ class VectorToObjectOrientationNode extends LogicNode {
if (object == null || vec == null) return null;
return object.transform.worldVecToOrientation(vec);
return vec.applyQuat(object.transform.rot);
}
}

View file

@ -3,8 +3,6 @@ package armory.logicnode;
import iron.math.Vec4;
import iron.object.Object;
using armory.object.TransformExtension;
class WorldVectorToLocalSpaceNode extends LogicNode {
public function new(tree: LogicTree) {
@ -17,7 +15,8 @@ class WorldVectorToLocalSpaceNode extends LogicNode {
if (object == null || worldVec == null) return null;
var localVec: Vec4 = new Vec4();
var localVec = new Vec4();
localVec.sub(object.transform.world.getLoc());
localVec.x = worldVec.dot(object.transform.right());
localVec.y = worldVec.dot(object.transform.look());

View file

@ -715,7 +715,9 @@ class ShadowMapAtlas {
public static inline function getMaxAtlasSize(type: String): Int {
#if arm_shadowmap_atlas_single_map
#if (rp_shadowmap_atlas_max_size == 1024)
#if (rp_shadowmap_atlas_max_size == 512)
return 512;
#elseif (rp_shadowmap_atlas_max_size == 1024)
return 1024;
#elseif (rp_shadowmap_atlas_max_size == 2048)
return 2048;
@ -742,7 +744,9 @@ class ShadowMapAtlas {
#end
}
case "spot": {
#if (rp_shadowmap_atlas_max_size_spot == 1024)
#if (rp_shadowmap_atlas_max_size_spot == 512)
return 512;
#elseif (rp_shadowmap_atlas_max_size_spot == 1024)
return 1024;
#elseif (rp_shadowmap_atlas_max_size_spot == 2048)
return 2048;
@ -755,7 +759,9 @@ class ShadowMapAtlas {
#end
}
case "sun": {
#if (rp_shadowmap_atlas_max_size_sun == 1024)
#if (rp_shadowmap_atlas_max_size_sun == 512)
return 512;
#elseif (rp_shadowmap_atlas_max_size_sun == 1024)
return 1024;
#elseif (rp_shadowmap_atlas_max_size_sun == 2048)
return 2048;
@ -768,7 +774,9 @@ class ShadowMapAtlas {
#end
}
default: {
#if (rp_shadowmap_atlas_max_size == 1024)
#if (rp_shadowmap_atlas_max_size == 512)
return 512;
#elseif (rp_shadowmap_atlas_max_size == 1024)
return 1024;
#elseif (rp_shadowmap_atlas_max_size == 2048)
return 2048;

View file

@ -4,347 +4,223 @@ import kha.FastFloat;
import iron.system.Input;
class InputMap {
var commands = new Map<String, Null<Array<InputCommand>>>();
static var inputMaps = new Map<String, InputMap>();
public var keys(default, null) = new Array<InputMapKey>();
public var lastKeyPressed(default, null) = "";
public function new() {}
public function addKeyboard(config: String) {
var command = new KeyboardCommand();
return addCustomCommand(command, config);
public static function getInputMap(inputMap: String): Null<InputMap> {
if (inputMaps.exists(inputMap)) {
return inputMaps[inputMap];
}
return null;
}
public function addGamepad(config: String) {
var command = new GamepadCommand();
return addCustomCommand(command, config);
public static function addInputMap(inputMap: String): InputMap {
return inputMaps[inputMap] = new InputMap();
}
public function addCustomCommand(command: InputCommand, config: String) {
if (commands[config] == null) commands[config] = new Array<InputCommand>();
commands[config].push(command);
return command;
}
}
class ActionMap extends InputMap {
public inline function started(config: String) {
var started = false;
for (c in commands[config]) {
if (c.started()) {
started = true;
break;
public static function getInputMapKey(inputMap: String, key: String): Null<InputMapKey> {
if (inputMaps.exists(inputMap)) {
for (k in inputMaps[inputMap].keys) {
if (k.key == key) {
return k;
}
}
}
return started;
return null;
}
public inline function released(config: String) {
var released = false;
public static function removeInputMapKey(inputMap: String, key: String): Bool {
if (inputMaps.exists(inputMap)) {
var i = inputMaps[inputMap];
for (c in commands[config]) {
if (c.released()) {
released = true;
break;
for (k in i.keys) {
if (k.key == key) {
return i.removeKey(k);
}
}
}
return released;
return false;
}
}
class AxisMap extends InputMap {
var scale: FastFloat = 1.0;
public function addKeyboard(key: String, scale: FastFloat = 1.0): InputMapKey {
return addKey(new KeyboardKey(key, scale));
}
public inline function getAxis(config: String) {
var axis = 0.0;
public function addMouse(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey {
return addKey(new MouseKey(key, scale, deadzone));
}
for (c in commands[config]) {
var tempAxis = c.getAxis();
public function addGamepad(key: String, scale: FastFloat = 1.0, deadzone: FastFloat = 0.0): InputMapKey {
return addKey(new GamepadKey(key, scale, deadzone));
}
if (tempAxis != 0.0 && tempAxis != axis) {
axis += tempAxis;
scale = c.getScale();
}
public function addKey(key: InputMapKey): InputMapKey {
keys.push(key);
return key;
}
public function removeKey(key: InputMapKey): Bool {
return keys.remove(key);
}
public function value(): FastFloat {
var v = 0.0;
for (k in keys) {
v += k.value();
}
return axis;
}
public inline function getScale() {
return scale;
}
}
class InputCommand {
var keys = new Array<String>();
var modifiers = new Array<String>();
var displacementKeys = new Array<String>();
var displacementModifiers = new Array<String>();
var deadzone: FastFloat = 0.0;
var scale: FastFloat = 1.0;
public function new() {}
public function setKeys(keys: Array<String>) {
return this.keys = keys;
}
public function setMods(modifiers: Array<String>) {
return this.modifiers = modifiers;
}
public function setDisplacementKeys(keys: Array<String>) {
return displacementKeys = keys;
}
public function setDisplacementMods(modifiers: Array<String>) {
return displacementModifiers = modifiers;
}
public function setDeadzone(deadzone: FastFloat) {
return this.deadzone = deadzone;
}
public function setScale(scale: FastFloat) {
return this.scale = scale;
}
public function getScale() {
return scale;
return v;
}
public function started() {
for (k in keys) {
if (k.started()) {
lastKeyPressed = k.key;
return true;
}
}
return false;
}
public function released() {
for (k in keys) {
if (k.released()) {
lastKeyPressed = k.key;
return true;
}
}
return false;
}
}
class InputMapKey {
public var key: String;
public var scale: FastFloat;
public var deadzone: FastFloat;
public function new(key: String, scale = 1.0, deadzone = 0.0) {
this.key = key.toLowerCase();
this.scale = scale;
this.deadzone = deadzone;
}
public function started(): Bool {
return false;
}
public function getAxis(): FastFloat {
public function released(): Bool {
return false;
}
public function value(): FastFloat {
return 0.0;
}
}
class KeyboardCommand extends InputCommand {
var keyboard = Input.getKeyboard();
var mouse = Input.getMouse();
public function setIndex(index: Int) {}
public inline override function started() {
for (k in keys) {
if (keyboard.started(k)) {
for (m in modifiers) {
if (!keyboard.down(m)) return false;
}
function evalDeadzone(value: FastFloat): FastFloat {
var v = 0.0;
for (m in displacementModifiers) {
if (!mouse.down(m)) return false;
}
if (value > deadzone) {
v = value - deadzone;
return true;
}
} else if (value < -deadzone) {
v = value + deadzone;
}
for (k in displacementKeys) {
if (mouse.started(k)) {
for (m in modifiers) {
if (!keyboard.down(m)) return false;
}
for (m in displacementModifiers) {
if (!mouse.down(m)) return false;
}
return true;
}
}
return false;
return v * scale;
}
public inline override function released() {
for (k in keys) {
if (keyboard.released(k)) {
for (m in modifiers) {
if (!keyboard.down(m)) return false;
}
function evalPressure(value: FastFloat): FastFloat {
var v = value - deadzone;
for (m in displacementModifiers) {
if (!mouse.down(m)) return false;
}
if (v > 0.0) {
v /= (1.0 - deadzone);
return true;
}
} else {
v = 0.0;
}
for (k in displacementKeys) {
if (mouse.released(k)) {
for (m in modifiers) {
if (!keyboard.down(m)) return false;
}
for (m in displacementModifiers) {
if (!mouse.down(m)) return false;
}
return true;
}
}
return false;
}
public inline override function getAxis() {
var axis = 0.0;
var movementX = mouse.movementX;
var movementY = mouse.movementY;
var wheelDelta = mouse.wheelDelta;
for (k in keys) {
if (keyboard.down(k)) {
axis++;
break;
}
}
for (m in modifiers) {
if (keyboard.down(m)) {
axis --;
break;
}
}
for (k in displacementKeys) {
switch (k) {
case "moved x": if (movementX > deadzone) axis++;
case "moved y": if (movementY > deadzone) axis--;
case "wheel": if (wheelDelta < -deadzone) axis++;
case "movement x": if (movementX > deadzone) return movementX - deadzone;
case "movement y": if (movementY > deadzone) return movementY - deadzone;
default: {
if (mouse.down(k)) {
axis ++;
break;
}
}
}
}
for (m in displacementModifiers) {
switch (m) {
case "moved x": if (movementX < -deadzone) axis--;
case "moved y": if (movementY < -deadzone) axis++;
case "wheel": if (wheelDelta > deadzone) axis--;
case "movement x": if (movementX < -deadzone) return movementX + deadzone;
case "movement y": if (movementY < -deadzone) return movementY + deadzone;
default: {
if (mouse.down(m)) {
axis --;
break;
}
}
}
}
return axis > 1 ? 1 : axis < -1 ? -1 : axis;
return v;
}
}
class GamepadCommand extends InputCommand {
var gamepad = Input.getGamepad(0);
class KeyboardKey extends InputMapKey {
var kb = Input.getKeyboard();
public inline override function started() {
for (k in keys) {
if (gamepad.started(k)) {
for (m in modifiers) {
if (gamepad.down(m) < deadzone) return false;
}
return true;
}
}
return false;
return kb.started(key);
}
public inline override function released() {
for (k in keys) {
if (gamepad.released(k)) {
for (m in modifiers) {
if (gamepad.down(m) < deadzone) return false;
}
return true;
}
}
return false;
return kb.released(key);
}
public inline override function getAxis() {
var axis = 0.0;
var rsMovementX = gamepad.rightStick.movementX;
var rsMovementY = gamepad.rightStick.movementY;
var lsMovementX = gamepad.leftStick.movementX;
var lsMovementY = gamepad.leftStick.movementY;
var rtPressure = gamepad.down("r2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0;
var ltPressure = gamepad.down("l2") > 0.0 ? (gamepad.down("r2") - deadzone) / (1 - deadzone) : 0.0;
for (k in keys) {
switch(k) {
case "rtPressure": axis += rtPressure;
case "ltPressure": axis += ltPressure;
default: {
if (gamepad.down(k) > deadzone) {
axis++;
break;
}
}
}
}
for (m in modifiers) {
switch (m) {
case "rtPressure": axis -= rtPressure;
case "ltPressure": axis -= ltPressure;
default: {
if (gamepad.down(m) > deadzone) {
axis--;
break;
}
}
}
}
for (k in displacementKeys) {
switch(k) {
case "rs moved x": if (rsMovementX > deadzone) axis++;
case "rs moved y": if (rsMovementY > deadzone) axis++;
case "ls moved x": if (lsMovementX > deadzone) axis++;
case "ls moved y": if (lsMovementY > deadzone) axis++;
case "rs movement x": if (rsMovementX > deadzone) return rsMovementX - deadzone;
case "rs movement y": if (rsMovementY > deadzone) return rsMovementY - deadzone;
case "ls movement x": if (lsMovementX > deadzone) return lsMovementX - deadzone;
case "ls movement y": if (lsMovementY > deadzone) return lsMovementY - deadzone;
}
}
for (m in displacementModifiers) {
switch (m) {
case "rs moved x": if (rsMovementX < -deadzone) axis--;
case "rs moved y": if (rsMovementY < -deadzone) axis--;
case "ls moved x": if (lsMovementX < -deadzone) axis--;
case "ls moved y": if (lsMovementY < -deadzone) axis--;
case "rs movement x": if (rsMovementX < -deadzone) return rsMovementX + deadzone;
case "rs movement y": if (rsMovementY < -deadzone) return rsMovementY + deadzone;
case "ls movement x": if (lsMovementX < -deadzone) return lsMovementX + deadzone;
case "ls movement y": if (lsMovementY < -deadzone) return lsMovementY + deadzone;
}
}
return axis > 1 ? 1 : axis < -1 ? -1 : axis;
public inline override function value(): FastFloat {
return kb.down(key) ? scale : 0.0;
}
}
}
class MouseKey extends InputMapKey {
var m = Input.getMouse();
public inline override function started() {
return m.started(key);
}
public inline override function released() {
return m.released(key);
}
public override function value(): FastFloat {
return switch (key) {
case "movement x": evalDeadzone(m.movementX);
case "movement y": evalDeadzone(m.movementY);
case "wheel": evalDeadzone(m.wheelDelta);
default: m.down(key) ? scale : 0.0;
}
}
}
class GamepadKey extends InputMapKey {
var g = Input.getGamepad();
public inline override function started() {
return g.started(key);
}
public inline override function released() {
return g.released(key);
}
public override function value(): FastFloat {
return switch(key) {
case "ls movement x": evalDeadzone(g.leftStick.movementX);
case "ls movement y": evalDeadzone(g.leftStick.movementY);
case "rs movement x": evalDeadzone(g.rightStick.movementX);
case "rs movement y": evalDeadzone(g.rightStick.movementY);
case "lt pressure": evalDeadzone(evalPressure(g.down("l2")));
case "rt pressure": evalDeadzone(evalPressure(g.down("r2")));
default: evalDeadzone(g.down(key));
}
}
public override function setIndex(index: Int) {
g = Input.getGamepad(index);
}
}

View file

@ -33,7 +33,7 @@ class FollowCamera extends iron.Trait {
trace("FollowCamera error, unable to set target object");
}
if (Std.is(object, iron.object.CameraObject)) {
if (Std.isOfType(object, iron.object.CameraObject)) {
disabled = true;
trace("FollowCamera error, this trait should not be placed directly on a camera objet. It should be placed on another object such as an Empty. The camera should be placed as a child to the Empty object with offset, creating a camera boom.");
}

View file

@ -594,7 +594,7 @@ class DebugConsole extends Trait {
}
function drawTiles(tile: ShadowMapTile, atlas: ShadowMapAtlas, atlasVisualSize: Float) {
var color = kha.Color.fromFloats(0.1, 0.1, 0.1);
var color: Null<kha.Color> = kha.Color.fromFloats(0.1, 0.1, 0.1);
var borderColor = color;
var tileScale = (tile.size / atlas.sizew) * atlasVisualSize; //* 0.95;
var x = (tile.coordsX / atlas.sizew) * atlasVisualSize;

View file

@ -2,14 +2,29 @@ package armory.trait.internal;
import kha.Image;
import kha.Video;
import iron.Trait;
import iron.object.MeshObject;
/**
Replaces the diffuse texture of the first material of the trait's object
with a video texture.
@see https://github.com/armory3d/armory_examples/tree/master/material_movie
**/
class MovieTexture extends Trait {
/**
Caches all render targets used by this trait for re-use when having
multiple videos of the same size. The lookup only takes place on trait
initialization.
Map layout: `[width => [height => image]]`
**/
static var imageCache: Map<Int, Map<Int, Image>> = new Map();
var video: Video;
public static var image: Image;
public static var created = false;
var image: Image;
var videoName: String;
@ -33,10 +48,7 @@ class MovieTexture extends Trait {
this.videoName = videoName;
if (!created) {
created = true;
notifyOnInit(init);
}
notifyOnInit(init);
}
function init() {
@ -44,9 +56,21 @@ class MovieTexture extends Trait {
video = vid;
video.play(true);
image = Image.createRenderTarget(getPower2(video.width()), getPower2(video.height()));
var w = getPower2(video.width());
var h = getPower2(video.height());
var o = cast(object, iron.object.MeshObject);
// Lazily fill the outer map
var hMap: Map<Int, Image> = imageCache[w];
if (hMap == null) {
imageCache[w] = new Map<Int, Image>();
}
image = imageCache[w][h];
if (image == null) {
imageCache[w][h] = image = Image.createRenderTarget(w, h);
}
var o = cast(object, MeshObject);
o.materials[0].contexts[0].textures[0] = image; // Override diffuse texture
notifyOnRender2D(render);
});

View file

@ -0,0 +1,313 @@
package armory.trait.internal;
import iron.object.MeshObject;
import iron.Trait;
import kha.Image;
import iron.math.Vec4;
import iron.data.MaterialData;
import iron.Scene;
import iron.object.Object;
import iron.object.Uniforms;
class UniformsManager extends Trait{
static var floatsRegistered = false;
static var floatsMap = new Map<Object, Map<MaterialData, Map<String, Null<kha.FastFloat>>>>();
static var vectorsRegistered = false;
static var vectorsMap = new Map<Object, Map<MaterialData, Map<String, Vec4>>>();
static var texturesRegistered = false;
static var texturesMap = new Map<Object, Map<MaterialData, Map<String, kha.Image>>>();
static var sceneRemoveInitalized = false;
public var uniformExists = false;
public function new(){
super();
notifyOnInit(init);
notifyOnRemove(removeObject);
if(! sceneRemoveInitalized){
Scene.active.notifyOnRemove(removeScene);
}
}
function init() {
var materials = cast(object, MeshObject).materials;
for (material in materials){
var exists = registerShaderUniforms(material);
if(exists) {
uniformExists = true;
}
}
}
static function removeScene() {
removeObjectFromAllMaps(Scene.active.root);
}
function removeObject() {
removeObjectFromAllMaps(object);
}
// Helper method to register float, vec3 and texture getter functions
static function register(type: UniformType){
switch (type){
case Float:{
if(! floatsRegistered){
floatsRegistered = true;
Uniforms.externalFloatLinks.push(floatLink);
}
}
case Vector:{
if(! vectorsRegistered){
vectorsRegistered = true;
Uniforms.externalVec3Links.push(vec3Link);
}
}
case Texture:{
if(! texturesRegistered){
texturesRegistered = true;
Uniforms.externalTextureLinks.push(textureLink);
}
}
}
}
// Register and map shader uniforms if it is an armory shader parameter
public static function registerShaderUniforms(material: MaterialData) : Bool {
var uniformExist = false;
if(! floatsMap.exists(Scene.active.root)) floatsMap.set(Scene.active.root, null);
if(! vectorsMap.exists(Scene.active.root)) vectorsMap.set(Scene.active.root, null);
if(! texturesMap.exists(Scene.active.root)) texturesMap.set(Scene.active.root, null);
for(context in material.shader.raw.contexts){ // For each context in shader
for (constant in context.constants){ // For each constant in the context
if(constant.is_arm_parameter){ // Check if armory parameter
uniformExist = true;
var object = Scene.active.root; // Map default uniforms to scene root
switch (constant.type){
case "float":{
var link = constant.link;
var value = constant.float;
setFloatValue(material, object, link, value);
register(Float);
}
case "vec3":{
var vec = new Vec4();
vec.x = constant.vec3.get(0);
vec.y = constant.vec3.get(1);
vec.z = constant.vec3.get(2);
setVec3Value(material, object, constant.link, vec);
register(Vector);
}
}
}
}
for (texture in context.texture_units){
if(texture.is_arm_parameter){ // Check if armory parameter
uniformExist = true;
var object = Scene.active.root; // Map default texture to scene root
iron.data.Data.getImage(texture.default_image_file, function(image: kha.Image) {
setTextureValue(material, object, texture.link, image);
});
register(Texture);
}
}
}
return uniformExist;
}
// Method to set map Object -> Material -> Link -> FLoat
public static function setFloatValue(material: MaterialData, object: Object, link: String, value: Null<kha.FastFloat>){
if(object == null || material == null || link == null) return;
var map = floatsMap;
var matMap = map.get(object);
if (matMap == null) {
matMap = new Map();
map.set(object, matMap);
}
var entry = matMap.get(material);
if (entry == null) {
entry = new Map();
matMap.set(material, entry);
}
entry.set(link, value); // parameter name, value
}
// Method to set map Object -> Material -> Link -> Vec3
public static function setVec3Value(material: MaterialData, object: Object, link: String, value: Vec4){
if(object == null || material == null || link == null) return;
var map = vectorsMap;
var matMap = map.get(object);
if (matMap == null) {
matMap = new Map();
map.set(object, matMap);
}
var entry = matMap.get(material);
if (entry == null) {
entry = new Map();
matMap.set(material, entry);
}
entry.set(link, value); // parameter name, value
}
// Method to set map Object -> Material -> Link -> Texture
public static function setTextureValue(material: MaterialData, object: Object, link: String, value: kha.Image){
if(object == null || material == null || link == null) return;
var map = texturesMap;
var matMap = map.get(object);
if (matMap == null) {
matMap = new Map();
map.set(object, matMap);
}
var entry = matMap.get(material);
if (entry == null) {
entry = new Map();
matMap.set(material, entry);
}
entry.set(link, value); // parameter name, value
}
// Mehtod to get object specific material parameter float value
static function floatLink(object: Object, mat: MaterialData, link: String): Null<kha.FastFloat> {
if(object == null || mat == null) return null;
if(! floatsMap.exists(object)){
object = Scene.active.root;
}
var material = floatsMap.get(object);
if (material == null) return null;
var entry = material.get(mat);
if (entry == null) return null;
return entry.get(link);
}
// Mehtod to get object specific material parameter vec3 value
static function vec3Link(object: Object, mat: MaterialData, link: String): iron.math.Vec4 {
if(object == null || mat == null) return null;
if(! vectorsMap.exists(object)){
object = Scene.active.root;
}
var material = vectorsMap.get(object);
if (material == null) return null;
var entry = material.get(mat);
if (entry == null) return null;
return entry.get(link);
}
// Mehtod to get object specific material parameter texture value
static function textureLink(object: Object, mat: MaterialData, link: String): kha.Image {
if(object == null || mat == null) return null;
if(! texturesMap.exists(object)){
object = Scene.active.root;
}
var material = texturesMap.get(object);
if (material == null) return null;
var entry = material.get(mat);
if (entry == null) return null;
return entry.get(link);
}
// Returns complete map of float value material paramets
public static function getFloatsMap():Map<Object, Map<MaterialData, Map<String, Null<kha.FastFloat>>>>{
return floatsMap;
}
// Returns complete map of vec3 value material paramets
public static function getVectorsMap():Map<Object, Map<MaterialData, Map<String, Vec4>>>{
return vectorsMap;
}
// Returns complete map of texture value material paramets
public static function getTexturesMap():Map<Object, Map<MaterialData, Map<String, kha.Image>>>{
return texturesMap;
}
// Remove all object specific material paramenter keys
public static function removeObjectFromAllMaps(object: Object) {
floatsMap.remove(object);
vectorsMap.remove(object);
texturesMap.remove(object);
}
// Remove object specific material paramenter keys
public static function removeObjectFromMap(object: Object, type: UniformType) {
switch (type){
case Float: floatsMap.remove(object);
case Vector: vectorsMap.remove(object);
case Texture: texturesMap.remove(object);
}
}
}
@:enum abstract UniformType(Int) from Int to Int {
var Float = 0;
var Vector = 1;
var Texture = 2;
}

View file

@ -18,8 +18,9 @@ class PhysicsConstraintExportHelper extends iron.Trait {
var breakingThreshold: Float;
var limits: Array<Float>;
var constraintAdded: Bool = false;
var relativeConstraint: Bool = false;
public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, limits: Array<Float> = null) {
public function new(body1: String, body2: String, type: Int, disableCollisions: Bool, breakingThreshold: Float, relatieConstraint: Bool = false, limits: Array<Float> = null) {
super();
this.body1 = body1;
@ -27,14 +28,26 @@ class PhysicsConstraintExportHelper extends iron.Trait {
this.type = type;
this.disableCollisions = disableCollisions;
this.breakingThreshold = breakingThreshold;
this.relativeConstraint = relatieConstraint;
this.limits = limits;
notifyOnInit(init);
notifyOnUpdate(update);
}
function init() {
var target1 = Scene.active.getChild(body1);
var target2 = Scene.active.getChild(body2);
var target1;
var target2;
if(relativeConstraint) {
target1 = object.parent.getChild(body1);
target2 = object.parent.getChild(body2);
}
else {
target1 = Scene.active.getChild(body1);
target2 = Scene.active.getChild(body2);
}
object.addTrait(new PhysicsConstraint(target1, target2, type, disableCollisions, breakingThreshold, limits));
constraintAdded = true;
}

View file

@ -149,7 +149,7 @@ class RigidBody extends iron.Trait {
if (ready) return;
ready = true;
if (!Std.is(object, MeshObject)) return; // No mesh data
if (!Std.isOfType(object, MeshObject)) return; // No mesh data
transform = object.transform;
physics = armory.trait.physics.PhysicsWorld.active;

View file

@ -58,18 +58,18 @@ class Canvas {
var rotated = element.rotation != null && element.rotation != 0;
if (rotated) ui.g.pushRotation(element.rotation, ui._x + scaled(element.width) / 2, ui._y + scaled(element.height) / 2);
var font = ui.ops.font;
var fontAsset = isFontAsset(element.asset);
if (fontAsset) ui.ops.font = getAsset(canvas, element.asset);
switch (element.type) {
case Text:
var font = ui.ops.font;
var size = ui.fontSize;
var fontAsset = element.asset != null && StringTools.endsWith(element.asset, ".ttf");
if (fontAsset) ui.ops.font = getAsset(canvas, element.asset);
ui.fontSize = scaled(element.height);
ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL);
ui.text(getText(canvas, element), element.alignment);
ui.ops.font = font;
ui.fontSize = size;
case Button:
@ -90,7 +90,6 @@ class Canvas {
case Image:
var image = getAsset(canvas, element.asset);
var fontAsset = element.asset != null && StringTools.endsWith(element.asset, ".ttf");
if (image != null && !fontAsset) {
ui.imageScrollAlign = false;
var tint = element.color != null ? element.color : 0xffffffff;
@ -218,6 +217,8 @@ class Canvas {
case Empty:
}
ui.ops.font = font;
if (element.children != null) {
for (id in element.children) {
drawElement(ui, canvas, elemById(canvas, id), scaled(element.x) + px, scaled(element.y) + py);
@ -257,6 +258,10 @@ class Canvas {
return Std.int(f * _ui.SCALE());
}
static inline function isFontAsset(assetName: Null<String>): Bool {
return assetName != null && StringTools.endsWith(assetName.toLowerCase(), ".ttf");
}
public static inline function getColor(color: Null<Int>, defaultColor: Int): Int {
return color != null ? color : defaultColor;
}

View file

@ -172,160 +172,160 @@ class Ext {
Keycodes can be found here: http://api.kha.tech/kha/input/KeyCode.html
**/
static function keycodeToString(keycode: Int): String {
switch (keycode) {
case -1: return "None";
case KeyCode.Unknown: return "Unknown";
case KeyCode.Back: return "Back";
case KeyCode.Cancel: return "Cancel";
case KeyCode.Help: return "Help";
case KeyCode.Backspace: return "Backspace";
case KeyCode.Tab: return "Tab";
case KeyCode.Clear: return "Clear";
case KeyCode.Return: return "Return";
case KeyCode.Shift: return "Shift";
case KeyCode.Control: return "Ctrl";
case KeyCode.Alt: return "Alt";
case KeyCode.Pause: return "Pause";
case KeyCode.CapsLock: return "CapsLock";
case KeyCode.Kana: return "Kana";
// case KeyCode.Hangul: return "Hangul"; // Hangul == Kana
case KeyCode.Eisu: return "Eisu";
case KeyCode.Junja: return "Junja";
case KeyCode.Final: return "Final";
case KeyCode.Hanja: return "Hanja";
// case KeyCode.Kanji: return "Kanji"; // Kanji == Hanja
case KeyCode.Escape: return "Esc";
case KeyCode.Convert: return "Convert";
case KeyCode.NonConvert: return "NonConvert";
case KeyCode.Accept: return "Accept";
case KeyCode.ModeChange: return "ModeChange";
case KeyCode.Space: return "Space";
case KeyCode.PageUp: return "PageUp";
case KeyCode.PageDown: return "PageDown";
case KeyCode.End: return "End";
case KeyCode.Home: return "Home";
case KeyCode.Left: return "Left";
case KeyCode.Up: return "Up";
case KeyCode.Right: return "Right";
case KeyCode.Down: return "Down";
case KeyCode.Select: return "Select";
case KeyCode.Print: return "Print";
case KeyCode.Execute: return "Execute";
case KeyCode.PrintScreen: return "PrintScreen";
case KeyCode.Insert: return "Insert";
case KeyCode.Delete: return "Delete";
case KeyCode.Colon: return "Colon";
case KeyCode.Semicolon: return "Semicolon";
case KeyCode.LessThan: return "LessThan";
case KeyCode.Equals: return "Equals";
case KeyCode.GreaterThan: return "GreaterThan";
case KeyCode.QuestionMark: return "QuestionMark";
case KeyCode.At: return "At";
case KeyCode.Win: return "Win";
case KeyCode.ContextMenu: return "ContextMenu";
case KeyCode.Sleep: return "Sleep";
case KeyCode.Numpad0: return "Numpad0";
case KeyCode.Numpad1: return "Numpad1";
case KeyCode.Numpad2: return "Numpad2";
case KeyCode.Numpad3: return "Numpad3";
case KeyCode.Numpad4: return "Numpad4";
case KeyCode.Numpad5: return "Numpad5";
case KeyCode.Numpad6: return "Numpad6";
case KeyCode.Numpad7: return "Numpad7";
case KeyCode.Numpad8: return "Numpad8";
case KeyCode.Numpad9: return "Numpad9";
case KeyCode.Multiply: return "Multiply";
case KeyCode.Add: return "Add";
case KeyCode.Separator: return "Separator";
case KeyCode.Subtract: return "Subtract";
case KeyCode.Decimal: return "Decimal";
case KeyCode.Divide: return "Divide";
case KeyCode.F1: return "F1";
case KeyCode.F2: return "F2";
case KeyCode.F3: return "F3";
case KeyCode.F4: return "F4";
case KeyCode.F5: return "F5";
case KeyCode.F6: return "F6";
case KeyCode.F7: return "F7";
case KeyCode.F8: return "F8";
case KeyCode.F9: return "F9";
case KeyCode.F10: return "F10";
case KeyCode.F11: return "F11";
case KeyCode.F12: return "F12";
case KeyCode.F13: return "F13";
case KeyCode.F14: return "F14";
case KeyCode.F15: return "F15";
case KeyCode.F16: return "F16";
case KeyCode.F17: return "F17";
case KeyCode.F18: return "F18";
case KeyCode.F19: return "F19";
case KeyCode.F20: return "F20";
case KeyCode.F21: return "F21";
case KeyCode.F22: return "F22";
case KeyCode.F23: return "F23";
case KeyCode.F24: return "F24";
case KeyCode.NumLock: return "NumLock";
case KeyCode.ScrollLock: return "ScrollLock";
case KeyCode.WinOemFjJisho: return "WinOemFjJisho";
case KeyCode.WinOemFjMasshou: return "WinOemFjMasshou";
case KeyCode.WinOemFjTouroku: return "WinOemFjTouroku";
case KeyCode.WinOemFjLoya: return "WinOemFjLoya";
case KeyCode.WinOemFjRoya: return "WinOemFjRoya";
case KeyCode.Circumflex: return "Circumflex";
case KeyCode.Exclamation: return "Exclamation";
case KeyCode.DoubleQuote: return "DoubleQuote";
case KeyCode.Hash: return "Hash";
case KeyCode.Dollar: return "Dollar";
case KeyCode.Percent: return "Percent";
case KeyCode.Ampersand: return "Ampersand";
case KeyCode.Underscore: return "Underscore";
case KeyCode.OpenParen: return "OpenParen";
case KeyCode.CloseParen: return "CloseParen";
case KeyCode.Asterisk: return "Asterisk";
case KeyCode.Plus: return "Plus";
case KeyCode.Pipe: return "Pipe";
case KeyCode.HyphenMinus: return "HyphenMinus";
case KeyCode.OpenCurlyBracket: return "OpenCurlyBracket";
case KeyCode.CloseCurlyBracket: return "CloseCurlyBracket";
case KeyCode.Tilde: return "Tilde";
case KeyCode.VolumeMute: return "VolumeMute";
case KeyCode.VolumeDown: return "VolumeDown";
case KeyCode.VolumeUp: return "VolumeUp";
case KeyCode.Comma: return "Comma";
case KeyCode.Period: return "Period";
case KeyCode.Slash: return "Slash";
case KeyCode.BackQuote: return "BackQuote";
case KeyCode.OpenBracket: return "OpenBracket";
case KeyCode.BackSlash: return "BackSlash";
case KeyCode.CloseBracket: return "CloseBracket";
case KeyCode.Quote: return "Quote";
case KeyCode.Meta: return "Meta";
case KeyCode.AltGr: return "AltGr";
case KeyCode.WinIcoHelp: return "WinIcoHelp";
case KeyCode.WinIco00: return "WinIco00";
case KeyCode.WinIcoClear: return "WinIcoClear";
case KeyCode.WinOemReset: return "WinOemReset";
case KeyCode.WinOemJump: return "WinOemJump";
case KeyCode.WinOemPA1: return "WinOemPA1";
case KeyCode.WinOemPA2: return "WinOemPA2";
case KeyCode.WinOemPA3: return "WinOemPA3";
case KeyCode.WinOemWSCTRL: return "WinOemWSCTRL";
case KeyCode.WinOemCUSEL: return "WinOemCUSEL";
case KeyCode.WinOemATTN: return "WinOemATTN";
case KeyCode.WinOemFinish: return "WinOemFinish";
case KeyCode.WinOemCopy: return "WinOemCopy";
case KeyCode.WinOemAuto: return "WinOemAuto";
case KeyCode.WinOemENLW: return "WinOemENLW";
case KeyCode.WinOemBackTab: return "WinOemBackTab";
case KeyCode.ATTN: return "ATTN";
case KeyCode.CRSEL: return "CRSEL";
case KeyCode.EXSEL: return "EXSEL";
case KeyCode.EREOF: return "EREOF";
case KeyCode.Play: return "Play";
case KeyCode.Zoom: return "Zoom";
case KeyCode.PA1: return "PA1";
case KeyCode.WinOemClear: return "WinOemClear";
return switch (keycode) {
default: String.fromCharCode(keycode);
case -1: "None";
case KeyCode.Unknown: "Unknown";
case KeyCode.Back: "Back";
case KeyCode.Cancel: "Cancel";
case KeyCode.Help: "Help";
case KeyCode.Backspace: "Backspace";
case KeyCode.Tab: "Tab";
case KeyCode.Clear: "Clear";
case KeyCode.Return: "Return";
case KeyCode.Shift: "Shift";
case KeyCode.Control: "Ctrl";
case KeyCode.Alt: "Alt";
case KeyCode.Pause: "Pause";
case KeyCode.CapsLock: "CapsLock";
case KeyCode.Kana: "Kana";
// case KeyCode.Hangul: "Hangul"; // Hangul == Kana
case KeyCode.Eisu: "Eisu";
case KeyCode.Junja: "Junja";
case KeyCode.Final: "Final";
case KeyCode.Hanja: "Hanja";
// case KeyCode.Kanji: "Kanji"; // Kanji == Hanja
case KeyCode.Escape: "Esc";
case KeyCode.Convert: "Convert";
case KeyCode.NonConvert: "NonConvert";
case KeyCode.Accept: "Accept";
case KeyCode.ModeChange: "ModeChange";
case KeyCode.Space: "Space";
case KeyCode.PageUp: "PageUp";
case KeyCode.PageDown: "PageDown";
case KeyCode.End: "End";
case KeyCode.Home: "Home";
case KeyCode.Left: "Left";
case KeyCode.Up: "Up";
case KeyCode.Right: "Right";
case KeyCode.Down: "Down";
case KeyCode.Select: "Select";
case KeyCode.Print: "Print";
case KeyCode.Execute: "Execute";
case KeyCode.PrintScreen: "PrintScreen";
case KeyCode.Insert: "Insert";
case KeyCode.Delete: "Delete";
case KeyCode.Colon: "Colon";
case KeyCode.Semicolon: "Semicolon";
case KeyCode.LessThan: "LessThan";
case KeyCode.Equals: "Equals";
case KeyCode.GreaterThan: "GreaterThan";
case KeyCode.QuestionMark: "QuestionMark";
case KeyCode.At: "At";
case KeyCode.Win: "Win";
case KeyCode.ContextMenu: "ContextMenu";
case KeyCode.Sleep: "Sleep";
case KeyCode.Numpad0: "Numpad0";
case KeyCode.Numpad1: "Numpad1";
case KeyCode.Numpad2: "Numpad2";
case KeyCode.Numpad3: "Numpad3";
case KeyCode.Numpad4: "Numpad4";
case KeyCode.Numpad5: "Numpad5";
case KeyCode.Numpad6: "Numpad6";
case KeyCode.Numpad7: "Numpad7";
case KeyCode.Numpad8: "Numpad8";
case KeyCode.Numpad9: "Numpad9";
case KeyCode.Multiply: "Multiply";
case KeyCode.Add: "Add";
case KeyCode.Separator: "Separator";
case KeyCode.Subtract: "Subtract";
case KeyCode.Decimal: "Decimal";
case KeyCode.Divide: "Divide";
case KeyCode.F1: "F1";
case KeyCode.F2: "F2";
case KeyCode.F3: "F3";
case KeyCode.F4: "F4";
case KeyCode.F5: "F5";
case KeyCode.F6: "F6";
case KeyCode.F7: "F7";
case KeyCode.F8: "F8";
case KeyCode.F9: "F9";
case KeyCode.F10: "F10";
case KeyCode.F11: "F11";
case KeyCode.F12: "F12";
case KeyCode.F13: "F13";
case KeyCode.F14: "F14";
case KeyCode.F15: "F15";
case KeyCode.F16: "F16";
case KeyCode.F17: "F17";
case KeyCode.F18: "F18";
case KeyCode.F19: "F19";
case KeyCode.F20: "F20";
case KeyCode.F21: "F21";
case KeyCode.F22: "F22";
case KeyCode.F23: "F23";
case KeyCode.F24: "F24";
case KeyCode.NumLock: "NumLock";
case KeyCode.ScrollLock: "ScrollLock";
case KeyCode.WinOemFjJisho: "WinOemFjJisho";
case KeyCode.WinOemFjMasshou: "WinOemFjMasshou";
case KeyCode.WinOemFjTouroku: "WinOemFjTouroku";
case KeyCode.WinOemFjLoya: "WinOemFjLoya";
case KeyCode.WinOemFjRoya: "WinOemFjRoya";
case KeyCode.Circumflex: "Circumflex";
case KeyCode.Exclamation: "Exclamation";
case KeyCode.DoubleQuote: "DoubleQuote";
case KeyCode.Hash: "Hash";
case KeyCode.Dollar: "Dollar";
case KeyCode.Percent: "Percent";
case KeyCode.Ampersand: "Ampersand";
case KeyCode.Underscore: "Underscore";
case KeyCode.OpenParen: "OpenParen";
case KeyCode.CloseParen: "CloseParen";
case KeyCode.Asterisk: "Asterisk";
case KeyCode.Plus: "Plus";
case KeyCode.Pipe: "Pipe";
case KeyCode.HyphenMinus: "HyphenMinus";
case KeyCode.OpenCurlyBracket: "OpenCurlyBracket";
case KeyCode.CloseCurlyBracket: "CloseCurlyBracket";
case KeyCode.Tilde: "Tilde";
case KeyCode.VolumeMute: "VolumeMute";
case KeyCode.VolumeDown: "VolumeDown";
case KeyCode.VolumeUp: "VolumeUp";
case KeyCode.Comma: "Comma";
case KeyCode.Period: "Period";
case KeyCode.Slash: "Slash";
case KeyCode.BackQuote: "BackQuote";
case KeyCode.OpenBracket: "OpenBracket";
case KeyCode.BackSlash: "BackSlash";
case KeyCode.CloseBracket: "CloseBracket";
case KeyCode.Quote: "Quote";
case KeyCode.Meta: "Meta";
case KeyCode.AltGr: "AltGr";
case KeyCode.WinIcoHelp: "WinIcoHelp";
case KeyCode.WinIco00: "WinIco00";
case KeyCode.WinIcoClear: "WinIcoClear";
case KeyCode.WinOemReset: "WinOemReset";
case KeyCode.WinOemJump: "WinOemJump";
case KeyCode.WinOemPA1: "WinOemPA1";
case KeyCode.WinOemPA2: "WinOemPA2";
case KeyCode.WinOemPA3: "WinOemPA3";
case KeyCode.WinOemWSCTRL: "WinOemWSCTRL";
case KeyCode.WinOemCUSEL: "WinOemCUSEL";
case KeyCode.WinOemATTN: "WinOemATTN";
case KeyCode.WinOemFinish: "WinOemFinish";
case KeyCode.WinOemCopy: "WinOemCopy";
case KeyCode.WinOemAuto: "WinOemAuto";
case KeyCode.WinOemENLW: "WinOemENLW";
case KeyCode.WinOemBackTab: "WinOemBackTab";
case KeyCode.ATTN: "ATTN";
case KeyCode.CRSEL: "CRSEL";
case KeyCode.EXSEL: "EXSEL";
case KeyCode.EREOF: "EREOF";
case KeyCode.Play: "Play";
case KeyCode.Zoom: "Zoom";
case KeyCode.PA1: "PA1";
case KeyCode.WinOemClear: "WinOemClear";
}
return String.fromCharCode(keycode);
}
}

View file

@ -2434,7 +2434,7 @@ Make sure the mesh only has tris/quads.""")
# Rigid body constraint
rbc = bobject.rigid_body_constraint
if rbc is not None and rbc.enabled:
self.add_rigidbody_constraint(o, rbc)
self.add_rigidbody_constraint(o, bobject, rbc)
# Camera traits
if type is NodeType.CAMERA:
@ -2454,6 +2454,13 @@ Make sure the mesh only has tris/quads.""")
else:
self.material_to_object_dict[mat] = [bobject]
self.material_to_arm_object_dict[mat] = [o]
# Add UniformsManager trait
if type is NodeType.MESH:
uniformManager = {}
uniformManager['type'] = 'Script'
uniformManager['class_name'] = 'armory.trait.internal.UniformsManager'
o['traits'].append(uniformManager)
# Export constraints
if len(bobject.constraints) > 0:
@ -2741,7 +2748,7 @@ Make sure the mesh only has tris/quads.""")
o['traits'].append(out_trait)
@staticmethod
def add_rigidbody_constraint(o, rbc):
def add_rigidbody_constraint(o, bobject, rbc):
rb1 = rbc.object1
rb2 = rbc.object2
if rb1 is None or rb2 is None:
@ -2760,7 +2767,8 @@ Make sure the mesh only has tris/quads.""")
"'" + rb1.name + "'",
"'" + rb2.name + "'",
str(rbc.disable_collisions).lower(),
str(breaking_threshold)
str(breaking_threshold),
str(bobject.arm_relative_physics_constraint).lower()
]
}
if rbc.type == "FIXED":

View file

@ -1,5 +1,6 @@
import importlib
import os
import queue
import sys
import bpy
@ -70,20 +71,56 @@ def on_operator_post(operator_id: str) -> None:
target_obj.arm_rb_collision_filter_mask = source_obj.arm_rb_collision_filter_mask
def always():
def send_operator(op):
if hasattr(bpy.context, 'object') and bpy.context.object != None:
obj = bpy.context.object.name
if op.name == 'Move':
vec = bpy.context.object.location
js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.loc.set(' + str(vec[0]) + ', ' + str(vec[1]) + ', ' + str(vec[2]) + '); o.transform.dirty = true;'
make.write_patch(js)
elif op.name == 'Resize':
vec = bpy.context.object.scale
js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.scale.set(' + str(vec[0]) + ', ' + str(vec[1]) + ', ' + str(vec[2]) + '); o.transform.dirty = true;'
make.write_patch(js)
elif op.name == 'Rotate':
vec = bpy.context.object.rotation_euler.to_quaternion()
js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.rot.set(' + str(vec[1]) + ', ' + str(vec[2]) + ', ' + str(vec[3]) + ' ,' + str(vec[0]) + '); o.transform.dirty = true;'
make.write_patch(js)
else: # Rebuild
make.patch()
def always() -> float:
# Force ui redraw
if state.redraw_ui and context_screen != None:
if state.redraw_ui and context_screen is not None:
for area in context_screen.areas:
if area.type == 'VIEW_3D' or area.type == 'PROPERTIES':
area.tag_redraw()
state.redraw_ui = False
# TODO: depsgraph.updates only triggers material trees
space = arm.utils.logic_editor_space(context_screen)
if space != None:
if space is not None:
space.node_tree.arm_cached = False
return 0.5
def poll_threads() -> float:
"""Polls the thread callback queue and if a thread has finished, it
is joined with the main thread and the corresponding callback is
executed in the main thread.
"""
try:
thread, callback = make.thread_callback_queue.get(block=False)
except queue.Empty:
return 0.25
thread.join()
callback()
# Quickly check if another thread has finished
return 0.01
appended_py_paths = []
context_screen = None
@ -164,7 +201,9 @@ def register():
bpy.app.handlers.load_post.append(on_load_post)
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post)
# bpy.app.handlers.undo_post.append(on_undo_post)
bpy.app.timers.register(always, persistent=True)
bpy.app.timers.register(poll_threads, persistent=True)
if arm.utils.get_fp() != '':
appended_py_paths = []
@ -181,6 +220,9 @@ def register():
def unregister():
bpy.app.timers.unregister(poll_threads)
bpy.app.timers.unregister(always)
bpy.app.handlers.load_post.remove(on_load_post)
bpy.app.handlers.depsgraph_update_post.remove(on_depsgraph_update_post)
# bpy.app.handlers.undo_post.remove(on_undo_post)

View file

@ -1,16 +1,50 @@
from arm.logicnode.arm_nodes import *
class BoneIKNode(ArmLogicTreeNode):
"""Applies inverse kinematics in the given object bone."""
"""Performs inverse kinematics on the selected armature with specified bone.
@input Object: Armature on which IK should be performed.
@input Bone: Effector or tip bone for the inverse kinematics
@input Goal Position: Position in world coordinates the effector bone will track to
@input Enable Pole: Bend IK solution towards pole location
@input Pole Position: Location of the pole in world coordinates
@input Chain Length: Number of bones to include in the IK solver including the effector. If set to 0, all bones from effector to the root bone of the armature will be considered.
@input Max Iterations: Maximum allowed FABRIK iterations to solve for IK. For longer chains, more iterations are needed.
@input Precision: Presition of IK to stop at. It is described as a tolerence in length. Typically 0.01 is a good value.
@input Roll Angle: Roll the bones along their local axis with specified radians. set 0 for no extra roll.
"""
bl_idname = 'LNBoneIKNode'
bl_label = 'Bone IK'
arm_version = 1
arm_version = 2
arm_section = 'armature'
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmStringSocket', 'Bone')
self.add_input('ArmVectorSocket', 'Goal')
self.add_input('ArmVectorSocket', 'Goal Position')
self.add_input('ArmBoolSocket', 'Enable Pole')
self.add_input('ArmVectorSocket', 'Pole Position')
self.add_input('ArmIntSocket', 'Chain Length')
self.add_input('ArmIntSocket', 'Max Iterations', 10)
self.add_input('ArmFloatSocket', 'Precision', 0.01)
self.add_input('ArmFloatSocket', 'Roll Angle')
self.add_output('ArmNodeSocketAction', 'Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
return NodeReplacement(
'LNBoneIKNode', self.arm_version, 'LNBoneIKNode', 2,
in_socket_mapping={0:0, 1:1, 2:2, 3:3}, out_socket_mapping={0:0}
)

View file

@ -0,0 +1,14 @@
from arm.logicnode.arm_nodes import *
class GetBoneFkIkOnlyNode(ArmLogicTreeNode):
"""Get if a particular bone is animated by Forward kinematics or Inverse kinematics only."""
bl_idname = 'LNGetBoneFkIkOnlyNode'
bl_label = 'Get Bone FK IK Only'
arm_version = 1
arm_section = 'armature'
def init(self, context):
super(GetBoneFkIkOnlyNode, self).init(context)
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Bone')
self.add_output('NodeSocketBool', 'FK or IK only')

View file

@ -0,0 +1,14 @@
from arm.logicnode.arm_nodes import *
class GetBoneTransformNode(ArmLogicTreeNode):
"""Returns bone transform in world space."""
bl_idname = 'LNGetBoneTransformNode'
bl_label = 'Get Bone Transform'
arm_version = 1
arm_section = 'armature'
def init(self, context):
super(GetBoneTransformNode, self).init(context)
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Bone')
self.add_output('NodeSocketShader', 'Transform')

View file

@ -0,0 +1,17 @@
from arm.logicnode.arm_nodes import *
class SetBoneFkIkOnlyNode(ArmLogicTreeNode):
"""Set particular bone to be animated by Forward kinematics or Inverse kinematics only. All other animations will be ignored"""
bl_idname = 'LNSetBoneFkIkOnlyNode'
bl_label = 'Set Bone FK IK Only'
arm_version = 1
arm_section = 'armature'
def init(self, context):
super(SetBoneFkIkOnlyNode, self).init(context)
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketString', 'Bone')
self.add_input('NodeSocketBool', 'FK or IK only')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -347,6 +347,30 @@ class ArmNodeRemoveInputOutputButton(bpy.types.Operator):
return{'FINISHED'}
class ArmNodeCallFuncButton(bpy.types.Operator):
"""Operator that calls a function on a specified
node (used for dynamic callbacks)."""
bl_idname = 'arm.node_call_func'
bl_label = 'Execute'
bl_options = {'UNDO', 'INTERNAL'}
node_index: StringProperty(name='Node Index', default='')
callback_name: StringProperty(name='Callback Name', default='')
def execute(self, context):
node = array_nodes[self.node_index]
if hasattr(node, self.callback_name):
getattr(node, self.callback_name)()
else:
return {'CANCELLED'}
# Reset to default again for subsequent calls of this operator
self.node_index = ''
self.callback_name = ''
return {'FINISHED'}
class ArmNodeSearch(bpy.types.Operator):
bl_idname = "arm.node_search"
bl_label = "Search..."
@ -537,12 +561,16 @@ def reset_globals():
category_items = OrderedDict()
bpy.utils.register_class(ArmNodeSearch)
bpy.utils.register_class(ArmNodeAddInputButton)
bpy.utils.register_class(ArmNodeAddInputValueButton)
bpy.utils.register_class(ArmNodeRemoveInputButton)
bpy.utils.register_class(ArmNodeRemoveInputValueButton)
bpy.utils.register_class(ArmNodeAddOutputButton)
bpy.utils.register_class(ArmNodeRemoveOutputButton)
bpy.utils.register_class(ArmNodeAddInputOutputButton)
bpy.utils.register_class(ArmNodeRemoveInputOutputButton)
REG_CLASSES = (
ArmNodeSearch,
ArmNodeAddInputButton,
ArmNodeAddInputValueButton,
ArmNodeRemoveInputButton,
ArmNodeRemoveInputValueButton,
ArmNodeAddOutputButton,
ArmNodeRemoveOutputButton,
ArmNodeAddInputOutputButton,
ArmNodeRemoveInputOutputButton,
ArmNodeCallFuncButton
)
register, unregister = bpy.utils.register_classes_factory(REG_CLASSES)

View file

@ -0,0 +1,15 @@
from arm.logicnode.arm_nodes import *
class GetGamepadStartedNode(ArmLogicTreeNode):
"""."""
bl_idname = 'LNGetGamepadStartedNode'
bl_label = 'Get Gamepad Started'
arm_version = 1
def init(self, context):
super(GetGamepadStartedNode, self).init(context)
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketInt', 'Index')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketString', 'Button')

View file

@ -0,0 +1,15 @@
from arm.logicnode.arm_nodes import *
class GetInputMapKeyNode(ArmLogicTreeNode):
"""Get key data if it exists in the input map."""
bl_idname = 'LNGetInputMapKeyNode'
bl_label = 'Get Input Map Key'
arm_version = 1
def init(self, context):
super(GetInputMapKeyNode, self).init(context)
self.add_input('NodeSocketString', 'Input Map')
self.add_input('NodeSocketString', 'Key')
self.add_output('NodeSocketFloat', 'Scale', default_value = 1.0)
self.add_output('NodeSocketFloat', 'Deadzone')

View file

@ -0,0 +1,14 @@
from arm.logicnode.arm_nodes import *
class GetKeyboardStartedNode(ArmLogicTreeNode):
"""."""
bl_idname = 'LNGetKeyboardStartedNode'
bl_label = 'Get Keyboard Started'
arm_version = 1
def init(self, context):
super(GetKeyboardStartedNode, self).init(context)
self.add_input('ArmNodeSocketAction', 'In')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketString', 'Key')

View file

@ -0,0 +1,14 @@
from arm.logicnode.arm_nodes import *
class GetMouseStartedNode(ArmLogicTreeNode):
"""."""
bl_idname = 'LNGetMouseStartedNode'
bl_label = 'Get Mouse Started'
arm_version = 1
def init(self, context):
super(GetMouseStartedNode, self).init(context)
self.add_input('ArmNodeSocketAction', 'In')
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketString', 'Button')

View file

@ -0,0 +1,16 @@
from arm.logicnode.arm_nodes import *
class OnInputMapNode(ArmLogicTreeNode):
"""Send a signal if any input map key is started or released."""
bl_idname = 'LNOnInputMapNode'
bl_label = 'On Input Map'
arm_version = 1
def init(self, context):
super(OnInputMapNode, self).init(context)
self.add_input('NodeSocketString', 'Input Map')
self.add_output('ArmNodeSocketAction', 'Started')
self.add_output('ArmNodeSocketAction', 'Released')
self.add_output('NodeSocketFloat', 'Value')
self.add_output('NodeSocketString', 'Key Pressed')

View file

@ -0,0 +1,15 @@
from arm.logicnode.arm_nodes import *
class RemoveInputMapKeyNode(ArmLogicTreeNode):
"""Remove input map key."""
bl_idname = 'LNRemoveInputMapKeyNode'
bl_label = 'Remove Input Map Key'
arm_version = 1
def init(self, context):
super(RemoveInputMapKeyNode, self).init(context)
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Input Map')
self.add_input('NodeSocketString', 'Key')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -0,0 +1,28 @@
from arm.logicnode.arm_nodes import *
class SetInputMapKeyNode(ArmLogicTreeNode):
"""Set input map key."""
bl_idname = 'LNSetInputMapKeyNode'
bl_label = 'Set Input Map Key'
arm_version = 1
property0: EnumProperty(
items = [('keyboard', 'Keyboard', 'Keyboard input'),
('mouse', 'Mouse', 'Mouse input'),
('gamepad', 'Gamepad', 'Gamepad input')],
name='', default='keyboard')
def init(self, context):
super(SetInputMapKeyNode, self).init(context)
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('NodeSocketString', 'Input Map')
self.add_input('NodeSocketString', 'Key')
self.add_input('NodeSocketFloat', 'Scale', default_value=1.0)
self.add_input('NodeSocketFloat', 'Deadzone')
self.add_input('NodeSocketInt', 'Index')
self.add_output('ArmNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')

View file

@ -1,14 +1,45 @@
from arm.logicnode.arm_nodes import *
class MergeNode(ArmLogicTreeNode):
"""Activates the output when any connected input is activated.
"""Activates the output when at least one connected input is activated.
If multiple inputs are active, the behaviour is specified by the
`Execution Mode` option.
@output Active Input Index: [*Available if Execution Mode is set to
Once Per Input*] The index of the last input that activated the output,
-1 if there was no execution yet on the current frame.
@option Execution Mode: The node's behaviour if multiple inputs are
active on the same frame.
- `Once Per Input`: If multiple inputs are active on one frame, activate
the output for each active input individually (simple forwarding).
- `Once Per Frame`: If multiple inputs are active on one frame,
trigger the output only once.
@option New: Add a new input socket.
@option X Button: Remove the lowermost input socket."""
bl_idname = 'LNMergeNode'
bl_label = 'Merge'
arm_section = 'flow'
arm_version = 1
arm_version = 2
def update_exec_mode(self, context):
self.outputs['Active Input Index'].hide = self.property0 == 'once_per_frame'
property0: EnumProperty(
name='Execution Mode',
description='The node\'s behaviour if multiple inputs are active on the same frame',
items=[('once_per_input', 'Once Per Input',
'If multiple inputs are active on one frame, activate the'
' output for each active input individually (simple forwarding)'),
('once_per_frame', 'Once Per Frame',
'If multiple inputs are active on one frame, trigger the output only once')],
default='once_per_input',
update=update_exec_mode,
)
def __init__(self):
super(MergeNode, self).__init__()
@ -16,10 +47,12 @@ class MergeNode(ArmLogicTreeNode):
def arm_init(self, context):
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketInt', 'Active Input Index')
def draw_buttons(self, context, layout):
row = layout.row(align=True)
layout.prop(self, 'property0', text='')
row = layout.row(align=True)
op = row.operator('arm.node_add_input', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.socket_type = 'ArmNodeSocketAction'
@ -31,3 +64,24 @@ class MergeNode(ArmLogicTreeNode):
return self.bl_label
return f'{self.bl_label}: [{len(self.inputs)}]'
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
newnode = node_tree.nodes.new('LNMergeNode')
newnode.property0 = self.property0
# Recreate all original inputs
array_nodes[str(id(newnode))] = newnode
for idx, input in enumerate(self.inputs):
bpy.ops.arm.node_add_input('EXEC_DEFAULT', node_index=str(id(newnode)), socket_type='ArmNodeSocketAction')
for link in input.links:
node_tree.links.new(link.from_socket, newnode.inputs[idx])
# Recreate outputs
for link in self.outputs[0].links:
node_tree.links.new(newnode.outputs[0], link.to_socket)
return newnode

View file

@ -0,0 +1,16 @@
from arm.logicnode.arm_nodes import *
class OncePerFrameNode(ArmLogicTreeNode):
"""Activates the output only once per frame if receives one or more inputs in that frame
If there is no input, there will be no output"""
bl_idname = 'LNOncePerFrameNode'
bl_label = 'Once Per Frame'
arm_section = 'flow'
arm_version = 1
def init(self, context):
super(OncePerFrameNode, self).init(context)
self.add_input('ArmNodeSocketAction', 'In')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -0,0 +1,122 @@
from bpy.types import NodeSocketInterfaceInt
from arm.logicnode.arm_nodes import *
class SelectNode(ArmLogicTreeNode):
"""Selects one of multiple values (of arbitrary types) based on some
input state. The exact behaviour of this node is specified by the
`Execution Mode` option (see below).
@output Out: [*Available if Execution Mode is set to From Input*]
Activated after the node was executed.
@output Value: The last selected value. This value is not reset
until the next execution of this node.
@option Execution Mode: Specifies the condition that determines
what value to choose.
- `From Index`: Select the value at the given index. If there is
no value at that index, the value plugged in to the
`Default` input is used instead (`null` if unconnected).
- `From Input`: This mode uses input pairs of one action socket
and one value socket. Depending on which action socket is
activated, the associated value socket (the value with the
same index as the activated action input) is forwarded to
the `Value` output.
@option New: Add a new value to the list of values.
@option X Button: Remove the value with the highest index."""
bl_idname = 'LNSelectNode'
bl_label = 'Select'
arm_version = 1
min_inputs = 2
def update_exec_mode(self, context):
self.set_mode()
property0: EnumProperty(
name='Execution Mode',
description="The node's behaviour.",
items=[
('from_index', 'From Index', 'Choose the value from the given index'),
('from_input', 'From Input', 'Choose the value with the same position as the active input')],
default='from_index',
update=update_exec_mode,
)
# The number of choices, NOT of individual inputs. This needs to be
# a property in order to be saved with each individual node
num_choices: IntProperty(default=1, min=0)
def __init__(self):
super().__init__()
array_nodes[str(id(self))] = self
def init(self, context):
super().init(context)
self.set_mode()
def set_mode(self):
self.inputs.clear()
self.outputs.clear()
if self.property0 == 'from_index':
self.add_input('NodeSocketInt', 'Index')
self.add_input('NodeSocketShader', 'Default')
self.num_choices = 0
# from_input
else:
# We could also start with index 1 here, but we need to use
# 0 for the "from_index" mode and it makes the code simpler
# if we stick to the same convention for both exec modes
self.add_input('ArmNodeSocketAction', 'Input 0')
self.add_input('NodeSocketShader', 'Value 0')
self.num_choices = 1
self.add_output('ArmNodeSocketAction', 'Out')
self.add_output('NodeSocketShader', 'Value')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0', text='')
row = layout.row(align=True)
op = row.operator('arm.node_call_func', text='New', icon='PLUS', emboss=True)
op.node_index = str(id(self))
op.callback_name = 'add_input_func'
op = row.operator('arm.node_call_func', text='', icon='X', emboss=True)
op.node_index = str(id(self))
op.callback_name = 'remove_input_func'
def add_input_func(self):
if self.property0 == 'from_input':
self.add_input('ArmNodeSocketAction', f'Input {self.num_choices}')
# Move new action input up to the end of all other action inputs
self.inputs.move(from_index=len(self.inputs) - 1, to_index=self.num_choices)
self.add_input('NodeSocketShader', f'Value {self.num_choices}')
self.num_choices += 1
def remove_input_func(self):
if self.property0 == 'from_input':
if len(self.inputs) > self.min_inputs:
self.inputs.remove(self.inputs[self.num_choices - 1])
if len(self.inputs) > self.min_inputs:
self.inputs.remove(self.inputs[-1])
self.num_choices -= 1
def draw_label(self) -> str:
if self.num_choices == 0:
return self.bl_label
return f'{self.bl_label}: [{self.num_choices}]'

View file

@ -1,16 +1,42 @@
from arm.logicnode.arm_nodes import *
class SetMaterialImageParamNode(ArmLogicTreeNode):
"""TO DO."""
"""Set an image value material parameter to the specified object.
@seeNode Get Scene Root
@input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally.
@input Per Object:
- `Enabled`: Set material parameter specific to this object. Global parameter will be ignored.
- `Disabled`: Set parameter globally, including this object.
@input Material: Material whose parameter to be set.
@input Node: Name of the parameter.
@input Image: Name of the image.
"""
bl_idname = 'LNSetMaterialImageParamNode'
bl_label = 'Set Material Image Param'
arm_section = 'params'
arm_version = 1
arm_version = 2
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmBoolSocket', 'Per Object')
self.add_input('ArmDynamicSocket', 'Material')
self.add_input('ArmStringSocket', 'Node')
self.add_input('ArmStringSocket', 'Image')
self.add_output('ArmNodeSocketAction', 'Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
return NodeReplacement(
'LNSetMaterialImageParamNode', self.arm_version, 'LNSetMaterialImageParamNode', 2,
in_socket_mapping={0:0, 1:3, 2:4, 3:5}, out_socket_mapping={0:0}
)

View file

@ -1,16 +1,42 @@
from arm.logicnode.arm_nodes import *
class SetMaterialRgbParamNode(ArmLogicTreeNode):
"""TO DO."""
"""Set a color or vector value material parameter to the specified object.
@seeNode Get Scene Root
@input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally.
@input Per Object:
- `Enabled`: Set material parameter specific to this object. Global parameter will be ignored.
- `Disabled`: Set parameter globally, including this object.
@input Material: Material whose parameter to be set.
@input Node: Name of the parameter.
@input Color: Color or vector input.
"""
bl_idname = 'LNSetMaterialRgbParamNode'
bl_label = 'Set Material RGB Param'
arm_section = 'params'
arm_version = 1
arm_version = 2
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmBoolSocket', 'Per Object')
self.add_input('ArmDynamicSocket', 'Material')
self.add_input('ArmStringSocket', 'Node')
self.add_input('ArmColorSocket', 'Color')
self.add_output('ArmNodeSocketAction', 'Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
return NodeReplacement(
'LNSetMaterialRgbParamNode', self.arm_version, 'LNSetMaterialRgbParamNode', 2,
in_socket_mapping={0:0, 1:3, 2:4, 3:5}, out_socket_mapping={0:0}
)

View file

@ -1,16 +1,43 @@
from arm.logicnode.arm_nodes import *
class SetMaterialValueParamNode(ArmLogicTreeNode):
"""TO DO."""
"""Set a float value material parameter to the specified object.
@seeNode Get Scene Root
@input Object: Object whose material parameter should change. Use `Get Scene Root` node to set parameter globally.
@input Per Object:
- `Enabled`: Set material parameter specific to this object. Global parameter will be ignored.
- `Disabled`: Set parameter globally, including this object.
@input Material: Material whose parameter to be set.
@input Node: Name of the parameter.
@input Float: float value.
"""
bl_idname = 'LNSetMaterialValueParamNode'
bl_label = 'Set Material Value Param'
arm_section = 'params'
arm_version = 1
arm_version = 2
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmBoolSocket', 'Per Object')
self.add_input('ArmDynamicSocket', 'Material')
self.add_input('ArmStringSocket', 'Node')
self.add_input('ArmFloatSocket', 'Float')
self.add_output('ArmNodeSocketAction', 'Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
return NodeReplacement(
'LNSetMaterialValueParamNode', self.arm_version, 'LNSetMaterialValueParamNode', 2,
in_socket_mapping={0:0, 1:3, 2:4, 3:5}, out_socket_mapping={0:0}
)

View file

@ -9,7 +9,7 @@ class GoToLocationNode(ArmLogicTreeNode):
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmDynamicSocket', 'Location')
self.add_input('ArmVectorSocket', 'Location')
self.add_output('ArmNodeSocketAction', 'Out')

View file

@ -249,7 +249,8 @@ def replace_all():
print(f"A node whose class doesn't exist was found in node tree \"{tree_name}\"", file=reportf)
elif error_type == 'update failed':
print(f"A node of type {node_class} in tree \"{tree_name}\" failed to be updated, "
f"because there is no (longer?) an update routine for this version of the node.", file=reportf)
f"because there is no (longer?) an update routine for this version of the node. Original exception:"
"\n" + tb + "\n", file=reportf)
elif error_type == 'future version':
print(f"A node of type {node_class} in tree \"{tree_name}\" seemingly comes from a future version of armory. "
f"Please check whether your version of armory is up to date", file=reportf)

View file

@ -1,13 +1,26 @@
from arm.logicnode.arm_nodes import *
class GetLocationNode(ArmLogicTreeNode):
"""Returns the current location of the given object in world coordinates."""
"""Get the location of the given object in world coordinates.
@input Parent Relative: If enabled, transforms the world coordinates into object parent local coordinates
@seeNode Set Object Location
@seeNode World Vector to Local Space
@seeNode Vector to Object Orientation
"""
bl_idname = 'LNGetLocationNode'
bl_label = 'Get Object Location'
arm_section = 'location'
arm_version = 1
arm_version = 2
def arm_init(self, context):
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('NodeSocketBool', 'Parent Relative')
self.add_output('ArmVectorSocket', 'Location')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
return NodeReplacement.Identity(self)

View file

@ -1,15 +1,28 @@
from arm.logicnode.arm_nodes import *
class SetLocationNode(ArmLogicTreeNode):
"""Sets the location of the given object."""
"""Set the location of the given object in world coordinates.
@input Parent Relative: If enabled, transforms the world coordinates into object parent local coordinates
@seeNode Get Object Location
@seeNode World Vector to Local Space
@seeNode Vector to Object Orientation
"""
bl_idname = 'LNSetLocationNode'
bl_label = 'Set Object Location'
arm_section = 'location'
arm_version = 1
arm_version = 2
def arm_init(self, context):
self.add_input('ArmNodeSocketAction', 'In')
self.add_input('ArmNodeSocketObject', 'Object')
self.add_input('ArmVectorSocket', 'Location')
self.add_input('NodeSocketBool', 'Parent Relative')
self.add_output('ArmNodeSocketAction', 'Out')
def get_replacement_node(self, node_tree: bpy.types.NodeTree):
if self.arm_version not in (0, 1):
raise LookupError()
return NodeReplacement.Identity(self)

View file

@ -1,10 +1,9 @@
from arm.logicnode.arm_nodes import *
class VectorToObjectOrientationNode(ArmLogicTreeNode):
"""Converts the given world vector to a vector oriented by the given object.
The object scale is taken in count.
"""Transform world coordinates into object oriented coordinates (in other words: apply object rotation to it).
@seeNode World Vector To Object Space
@seeNode World Vector to Object Space
@seeNode Get World Orientation
@seeNode Vector From Transform
"""

View file

@ -1,10 +1,9 @@
from arm.logicnode.arm_nodes import *
class WorldVectorToLocalSpaceNode(ArmLogicTreeNode):
"""Converts the given world vector to a object space vector.
The object scale is taken in count.
"""Transform world coordinates into object local coordinates.
@seeNode Vector To Object Orientation
@seeNode Vector to Object Orientation
@seeNode Get World Orientation
@seeNode Vector From Transform
"""

View file

@ -1,15 +1,16 @@
import errno
import glob
import json
import os
from queue import Queue
import shlex
import shutil
import time
import stat
import subprocess
import threading
import time
from typing import Callable
import webbrowser
import shlex
import errno
import math
import bpy
@ -29,15 +30,42 @@ import arm.write_data as write_data
scripts_mtime = 0 # Monitor source changes
profile_time = 0
def run_proc(cmd, done):
def fn(p, done):
p.wait()
if done != None:
# Queue of threads and their done callbacks. Item format: [thread, done]
thread_callback_queue = Queue(maxsize=0)
def run_proc(cmd, done: Callable) -> subprocess.Popen:
"""Creates a subprocess with the given command and returns it.
If Blender is not running in background mode, a thread is spawned
that waits until the subprocess has finished executing to not freeze
the UI, otherwise (in background mode) execution is blocked until
the subprocess has finished.
If `done` is not `None`, it is called afterwards in the main thread.
"""
use_thread = not bpy.app.background
def wait_for_proc(proc: subprocess.Popen):
proc.wait()
if use_thread:
# Put the done callback into the callback queue so that it
# can be received by a polling function in the main thread
thread_callback_queue.put([threading.current_thread(), done], block=True)
else:
done()
p = subprocess.Popen(cmd)
threading.Thread(target=fn, args=(p, done)).start()
if use_thread:
threading.Thread(target=wait_for_proc, args=(p,)).start()
else:
wait_for_proc(p)
return p
def compile_shader_pass(res, raw_shaders_path, shader_name, defs, make_variants):
os.chdir(raw_shaders_path + '/' + shader_name)

View file

@ -204,6 +204,8 @@ def parse_shader(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> Tuple[st
'BSDF_VELVET': nodes_shader.parse_bsdfvelvet,
}
state.reset_outs()
if node.type in node_parser_funcs:
node_parser_funcs[node.type](node, socket, state)
@ -558,7 +560,7 @@ def to_uniform(inp: bpy.types.NodeSocket):
def store_var_name(node: bpy.types.Node):
return node_name(node.name) + '_store'
def texture_store(node, tex, tex_name, to_linear=False, tex_link=None):
def texture_store(node, tex, tex_name, to_linear=False, tex_link=None, default_value=None, is_arm_mat_param=None):
curshader = state.curshader
tex_store = store_var_name(node)
@ -567,7 +569,7 @@ def texture_store(node, tex, tex_name, to_linear=False, tex_link=None):
state.parsed.add(tex_store)
mat_bind_texture(tex)
state.con.add_elem('tex', 'short2norm')
curshader.add_uniform('sampler2D {0}'.format(tex_name), link=tex_link)
curshader.add_uniform('sampler2D {0}'.format(tex_name), link=tex_link, default_value=default_value, is_arm_mat_param=is_arm_mat_param)
triplanar = node.projection == 'BOX'
if node.inputs[0].is_linked:
uv_name = parse_vector_input(node.inputs[0])

View file

@ -92,7 +92,13 @@ def parse_attribute(node: bpy.types.ShaderNodeAttribute, out_socket: bpy.types.N
def parse_rgb(node: bpy.types.ShaderNodeRGB, out_socket: bpy.types.NodeSocket, state: ParserState) -> vec3str:
if node.arm_material_param:
nn = 'param_' + c.node_name(node.name)
state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}')
v = out_socket.default_value
value = []
value.append(float(v[0]))
value.append(float(v[1]))
value.append(float(v[2]))
is_arm_mat_param = True
state.curshader.add_uniform(f'vec3 {nn}', link=f'{node.name}', default_value = value, is_arm_mat_param = is_arm_mat_param)
return nn
else:
return c.to_vec3(out_socket.default_value)
@ -351,7 +357,9 @@ def parse_lightpath(node: bpy.types.ShaderNodeLightPath, out_socket: bpy.types.N
def parse_value(node: bpy.types.ShaderNodeValue, out_socket: bpy.types.NodeSocket, state: ParserState) -> floatstr:
if node.arm_material_param:
nn = 'param_' + c.node_name(node.name)
state.curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name))
value = c.to_vec1(node.outputs[0].default_value)
is_arm_mat_param = True
state.curshader.add_uniform('float {0}'.format(nn), link='{0}'.format(node.name), default_value=value, is_arm_mat_param=is_arm_mat_param)
return nn
else:
return c.to_vec1(node.outputs[0].default_value)

View file

@ -114,15 +114,22 @@ def parse_tex_image(node: bpy.types.ShaderNodeTexImage, out_socket: bpy.types.No
tex_name = c.node_name(node.name)
tex = c.make_texture(node, tex_name)
tex_link = node.name if node.arm_material_param else None
tex_link = None
tex_default_file = None
is_arm_mat_param = None
if node.arm_material_param:
tex_link = node.name
is_arm_mat_param = True
if tex['file'] is not None:
tex_default_file = tex['file']
if tex is not None:
state.curshader.write_textures += 1
if use_color_out:
to_linear = node.image is not None and node.image.colorspace_settings.name == 'sRGB'
res = f'{c.texture_store(node, tex, tex_name, to_linear, tex_link=tex_link)}.rgb'
res = f'{c.texture_store(node, tex, tex_name, to_linear, tex_link=tex_link, default_value=tex_default_file, is_arm_mat_param=is_arm_mat_param)}.rgb'
else:
res = f'{c.texture_store(node, tex, tex_name, tex_link=tex_link)}.a'
res = f'{c.texture_store(node, tex, tex_name, tex_link=tex_link, default_value=tex_default_file, is_arm_mat_param=is_arm_mat_param)}.a'
state.curshader.write_textures -= 1
return res

View file

@ -1,9 +1,12 @@
from typing import Optional
import arm.material.cycles as cycles
import arm.material.mat_state as mat_state
import arm.material.make_skin as make_skin
import arm.material.make_particle as make_particle
import arm.material.make_inst as make_inst
import arm.material.shader as shader
import arm.material.make_tess as make_tess
from arm.material.shader import Shader, ShaderContext
import arm.utils
@ -33,17 +36,45 @@ def write_vertpos(vert):
vert.write('gl_Position = WVP * spos;')
def write_norpos(con_mesh: shader.ShaderContext, vert: shader.Shader, declare=False, write_nor=True):
prep = ''
if declare:
prep = 'vec3 '
def write_norpos(con_mesh: ShaderContext, vert: Shader, declare=False, write_nor=True):
is_bone = con_mesh.is_elem('bone')
if is_bone:
make_skin.skin_pos(vert)
if write_nor:
prep = 'vec3 ' if declare else ''
if is_bone:
make_skin.skin_nor(vert, prep)
else:
vert.write_attrib(prep + 'wnormal = normalize(N * vec3(nor.xy, pos.w));')
if con_mesh.is_elem('ipos'):
make_inst.inst_pos(con_mesh, vert)
def write_tex_coords(con_mesh: ShaderContext, vert: Shader, frag: Shader, tese: Optional[Shader]):
rpdat = arm.utils.get_rp()
if con_mesh.is_elem('tex'):
vert.add_out('vec2 texCoord')
vert.add_uniform('float texUnpack', link='_texUnpack')
if mat_state.material.arm_tilesheet_flag:
if mat_state.material.arm_particle_flag and rpdat.arm_particles == 'On':
make_particle.write_tilesheet(vert)
else:
vert.add_uniform('vec2 tilesheetOffset', '_tilesheetOffset')
vert.write_attrib('texCoord = tex * texUnpack + tilesheetOffset;')
else:
vert.write_attrib('texCoord = tex * texUnpack;')
if tese is not None:
tese.write_pre = True
make_tess.interpolate(tese, 'texCoord', 2, declare_out=frag.contains('texCoord'))
tese.write_pre = False
if con_mesh.is_elem('tex1'):
vert.add_out('vec2 texCoord1')
vert.add_uniform('float texUnpack', link='_texUnpack')
vert.write_attrib('texCoord1 = tex1 * texUnpack;')
if tese is not None:
tese.write_pre = True
make_tess.interpolate(tese, 'texCoord1', 2, declare_out=frag.contains('texCoord1'))
tese.write_pre = False

View file

@ -33,8 +33,6 @@ def make(context_id, rpasses, shadowmap=False):
parse_custom_particle = (cycles.node_by_name(mat_state.nodes, 'ArmCustomParticleNode') is not None)
if parse_opacity:
frag.write('vec3 n;') # Discard at compile time
frag.write('float dotNV;')
frag.write('float opacity;')
if con_depth.is_elem('bone'):

View file

@ -1,7 +1,10 @@
import bpy
import arm.material.make_tess as make_tess
def make(con_mesh):
import arm.material.make_tess as make_tess
from arm.material.shader import ShaderContext
def make(con_mesh: ShaderContext):
vert = con_mesh.vert
frag = con_mesh.frag
geom = con_mesh.geom
@ -13,9 +16,26 @@ def make(con_mesh):
if frag.contains('dotNV') and not frag.contains('float dotNV'):
frag.write_init('float dotNV = max(dot(n, vVec), 0.0);')
# n is not always defined yet (in some shadowmap shaders e.g.)
if not frag.contains('vec3 n'):
vert.add_out('vec3 wnormal')
vert.add_uniform('mat3 N', '_normalMatrix')
vert.write_attrib('wnormal = normalize(N * vec3(nor.xy, pos.w));')
frag.write_attrib('vec3 n = normalize(wnormal);')
# If not yet added, add nor vertex data
vertex_elems = con_mesh.data['vertex_elements']
has_normals = False
for elem in vertex_elems:
if elem['name'] == 'nor':
has_normals = True
break
if not has_normals:
vertex_elems.append({'name': 'nor', 'data': 'short2norm'})
write_wpos = False
if frag.contains('vVec') and not frag.contains('vec3 vVec'):
if tese != None:
if tese is not None:
tese.add_out('vec3 eyeDir')
tese.add_uniform('vec3 eye', '_cameraPosition')
tese.write('eyeDir = eye - wposition;')
@ -31,7 +51,7 @@ def make(con_mesh):
export_wpos = False
if frag.contains('wposition') and not frag.contains('vec3 wposition'):
export_wpos = True
if tese != None:
if tese is not None:
export_wpos = True
if vert.contains('wposition'):
write_wpos = True
@ -50,7 +70,7 @@ def make(con_mesh):
vert.add_uniform('float posUnpack', link='_posUnpack')
vert.write_attrib('mposition = spos.xyz * posUnpack;')
if tese != None:
if tese is not None:
if frag_mpos:
make_tess.interpolate(tese, 'mposition', 3, declare_out=True)
elif tese.contains('mposition') and not tese.contains('vec3 mposition'):
@ -72,7 +92,7 @@ def make(con_mesh):
vert.write_attrib('if (dim.y == 0) bposition.y = 0;')
vert.write_attrib('if (dim.x == 0) bposition.x = 0;')
if tese != None:
if tese is not None:
if frag_bpos:
make_tess.interpolate(tese, 'bposition', 3, declare_out=True)
elif tese.contains('bposition') and not tese.contains('vec3 bposition'):
@ -93,7 +113,7 @@ def make(con_mesh):
vert.write('wtangent = normalize(N * tang.xyz);')
vert.write_pre = False
if tese != None:
if tese is not None:
if frag_wtan:
make_tess.interpolate(tese, 'wtangent', 3, declare_out=True)
elif tese.contains('wtangent') and not tese.contains('vec3 wtangent'):

View file

@ -132,31 +132,7 @@ def make_base(con_mesh, parse_opacity):
if not is_displacement and not vattr_written:
make_attrib.write_vertpos(vert)
if con_mesh.is_elem('tex'):
vert.add_out('vec2 texCoord')
vert.add_uniform('float texUnpack', link='_texUnpack')
if mat_state.material.arm_tilesheet_flag:
if mat_state.material.arm_particle_flag and rpdat.arm_particles == 'On':
make_particle.write_tilesheet(vert)
else:
vert.add_uniform('vec2 tilesheetOffset', '_tilesheetOffset')
vert.write_attrib('texCoord = tex * texUnpack + tilesheetOffset;')
else:
vert.write_attrib('texCoord = tex * texUnpack;')
if tese is not None:
tese.write_pre = True
make_tess.interpolate(tese, 'texCoord', 2, declare_out=frag.contains('texCoord'))
tese.write_pre = False
if con_mesh.is_elem('tex1'):
vert.add_out('vec2 texCoord1')
vert.add_uniform('float texUnpack', link='_texUnpack')
vert.write_attrib('texCoord1 = tex1 * texUnpack;')
if tese is not None:
tese.write_pre = True
make_tess.interpolate(tese, 'texCoord1', 2, declare_out=frag.contains('texCoord1'))
tese.write_pre = False
make_attrib.write_tex_coords(con_mesh, vert, frag, tese)
if con_mesh.is_elem('col'):
vert.add_out('vec3 vcolor')
@ -296,8 +272,6 @@ def make_forward_mobile(con_mesh):
vert.write_attrib('vec4 spos = vec4(pos.xyz, 1.0);')
frag.ins = vert.outs
make_attrib.write_vertpos(vert)
frag.add_include('compiled.inc')
frag.write('vec3 basecol;')
frag.write('float roughness;')
@ -320,14 +294,7 @@ def make_forward_mobile(con_mesh):
opac = mat_state.material.arm_discard_opacity
frag.write('if (opacity < {0}) discard;'.format(opac))
if con_mesh.is_elem('tex'):
vert.add_out('vec2 texCoord')
vert.add_uniform('float texUnpack', link='_texUnpack')
if mat_state.material.arm_tilesheet_flag:
vert.add_uniform('vec2 tilesheetOffset', '_tilesheetOffset')
vert.write('texCoord = tex * texUnpack + tilesheetOffset;')
else:
vert.write('texCoord = tex * texUnpack;')
make_attrib.write_tex_coords(con_mesh, vert, frag, tese)
if con_mesh.is_elem('col'):
vert.add_out('vec3 vcolor')
@ -344,6 +311,8 @@ def make_forward_mobile(con_mesh):
make_attrib.write_norpos(con_mesh, vert)
frag.write_attrib('vec3 n = normalize(wnormal);')
make_attrib.write_vertpos(vert)
frag.add_include('std/math.glsl')
frag.add_include('std/brdf.glsl')
@ -474,8 +443,6 @@ def make_forward_solid(con_mesh):
vert.write_attrib('vec4 spos = vec4(pos.xyz, 1.0);')
frag.ins = vert.outs
make_attrib.write_vertpos(vert)
frag.add_include('compiled.inc')
frag.write('vec3 basecol;')
frag.write('float roughness;')
@ -512,6 +479,7 @@ def make_forward_solid(con_mesh):
vert.write('vcolor = col.rgb;')
make_attrib.write_norpos(con_mesh, vert, write_nor=False)
make_attrib.write_vertpos(vert)
frag.add_out('vec4 fragColor')
if blend and parse_opacity:

View file

@ -64,6 +64,16 @@ class ParserState:
self.out_opacity: floatstr = '1.0'
self.out_emission: floatstr = '0.0'
def reset_outs(self):
"""Reset the shader output values to their default values."""
self.out_basecol = 'vec3(0.8)'
self.out_roughness = '0.0'
self.out_metallic = '0.0'
self.out_occlusion = '1.0'
self.out_specular = '1.0'
self.out_opacity = '1.0'
self.out_emission = '0.0'
def get_outs(self) -> Tuple[vec3str, floatstr, floatstr, floatstr, floatstr, floatstr, floatstr]:
"""Return the shader output values as a tuple."""
return (self.out_basecol, self.out_roughness, self.out_metallic, self.out_occlusion, self.out_specular,

View file

@ -109,19 +109,27 @@ class ShaderContext:
def get(self):
return self.data
def add_constant(self, ctype, name, link=None):
def add_constant(self, ctype, name, link=None, default_value=None, is_arm_mat_param=None):
for c in self.constants:
if c['name'] == name:
return
c = { 'name': name, 'type': ctype }
if link != None:
c = { 'name': name, 'type': ctype}
if link is not None:
c['link'] = link
if default_value is not None:
if ctype == 'float':
c['float'] = default_value
if ctype == 'vec3':
c['vec3'] = default_value
if is_arm_mat_param is not None:
c['is_arm_parameter'] = 'true'
self.constants.append(c)
def add_texture_unit(self, name, link=None, is_image=None,
addr_u=None, addr_v=None,
filter_min=None, filter_mag=None, mipmap_filter=None):
filter_min=None, filter_mag=None, mipmap_filter=None,
default_value=None, is_arm_mat_param=None):
for c in self.tunits:
if c['name'] == name:
return
@ -141,6 +149,10 @@ class ShaderContext:
c['filter_mag'] = filter_mag
if mipmap_filter is not None:
c['mipmap_filter'] = mipmap_filter
if default_value is not None:
c['default_image_file'] = default_value
if is_arm_mat_param is not None:
c['is_arm_parameter'] = 'true'
self.tunits.append(c)
@ -238,7 +250,7 @@ class Shader:
def add_uniform(self, s, link=None, included=False, top=False,
tex_addr_u=None, tex_addr_v=None,
tex_filter_min=None, tex_filter_mag=None,
tex_mipmap_filter=None):
tex_mipmap_filter=None, default_value=None, is_arm_mat_param=None):
ar = s.split(' ')
# layout(RGBA8) image3D voxels
utype = ar[-2]
@ -257,7 +269,8 @@ class Shader:
self.context.add_texture_unit(
uname, link, is_image,
tex_addr_u, tex_addr_v,
tex_filter_min, tex_filter_mag, tex_mipmap_filter)
tex_filter_min, tex_filter_mag, tex_mipmap_filter,
default_value=default_value, is_arm_mat_param=is_arm_mat_param)
else:
# Prefer vec4[] for d3d to avoid padding
if ar[0] == 'float' and '[' in ar[1]:
@ -266,7 +279,7 @@ class Shader:
elif ar[0] == 'vec4' and '[' in ar[1]:
ar[0] = 'floats'
ar[1] = ar[1].split('[', 1)[0]
self.context.add_constant(ar[0], ar[1], link=link)
self.context.add_constant(ar[0], ar[1], link=link, default_value=default_value, is_arm_mat_param=is_arm_mat_param)
if top:
if not included and s not in self.uniforms_top:
self.uniforms_top.append(s)

View file

@ -363,6 +363,7 @@ class ReplaceNodesOperator(bpy.types.Operator):
def register():
arm.logicnode.arm_nodes.register()
arm.logicnode.arm_sockets.register()
bpy.utils.register_class(ArmLogicTree)
@ -403,3 +404,4 @@ def unregister():
bpy.utils.register_class(ARM_MT_NodeAddOverride.overridden_menu)
arm.logicnode.arm_sockets.unregister()
arm.logicnode.arm_nodes.unregister()

View file

@ -11,7 +11,7 @@ import arm.proxy
import arm.utils
# Armory version
arm_version = '2021.5'
arm_version = '2021.7'
arm_commit = '$Id$'
def get_project_html5_copy(self):
@ -286,6 +286,7 @@ def init_properties():
default=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False),
size=20,
subtype='LAYER')
bpy.types.Object.arm_relative_physics_constraint = BoolProperty(name="Relative Physics Constraint", description="Add physics constraint relative to the parent object or collection when spawned", default=False)
bpy.types.Object.arm_animation_enabled = BoolProperty(name="Animation", description="Enable skinning & timeline animation", default=True)
bpy.types.Object.arm_tilesheet = StringProperty(name="Tilesheet", description="Set tilesheet animation", default='')
bpy.types.Object.arm_tilesheet_action = StringProperty(name="Tilesheet Action", description="Set startup action", default='')

View file

@ -211,8 +211,11 @@ class ARM_PT_PhysicsPropsPanel(bpy.types.Panel):
layout.prop(obj, 'arm_rb_trigger')
layout.prop(obj, 'arm_rb_ccd')
if obj.soft_body != None:
if obj.soft_body is not None:
layout.prop(obj, 'arm_soft_body_margin')
if obj.rigid_body_constraint is not None:
layout.prop(obj, 'arm_relative_physics_constraint')
# Menu in data region
class ARM_PT_DataPropsPanel(bpy.types.Panel):
@ -626,7 +629,6 @@ class ARM_PT_ArmoryExporterPanel(bpy.types.Panel):
row = layout.row(align=True)
row.alignment = 'EXPAND'
row.scale_y = 1.3
row.enabled = wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0
row.operator("arm.build_project", icon="MOD_BUILD")
# row.operator("arm.patch_project")
row.operator("arm.publish_project", icon="EXPORT")
@ -1053,10 +1055,15 @@ class ArmoryStopButton(bpy.types.Operator):
return{'FINISHED'}
class ArmoryBuildProjectButton(bpy.types.Operator):
'''Build and compile project'''
"""Build and compile project"""
bl_idname = 'arm.build_project'
bl_label = 'Build'
@classmethod
def poll(cls, context):
wrd = bpy.data.worlds['Arm']
return wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0
def execute(self, context):
# Compare version Blender and Armory (major, minor)
if not arm.utils.compare_version_blender_arm():
@ -1093,10 +1100,15 @@ class ArmoryBuildProjectButton(bpy.types.Operator):
return{'FINISHED'}
class ArmoryPublishProjectButton(bpy.types.Operator):
'''Build project ready for publishing'''
"""Build project ready for publishing."""
bl_idname = 'arm.publish_project'
bl_label = 'Publish'
@classmethod
def poll(cls, context):
wrd = bpy.data.worlds['Arm']
return wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0
def execute(self, context):
# Compare version Blender and Armory (major, minor)
if not arm.utils.compare_version_blender_arm():

View file

@ -300,7 +300,7 @@ script_warnings: Dict[str, List[Tuple[str, str]]] = {} # Script name -> List of
# See https://regex101.com/r/bbrCzN/8
RX_MODIFIERS = r'(?P<modifiers>(?:public\s+|private\s+|static\s+|inline\s+|final\s+)*)?' # Optional modifiers
RX_IDENTIFIER = r'(?P<identifier>[_$a-z]+[_a-z0-9]*)' # Variable name, follow Haxe rules
RX_TYPE = r'(?::\s+(?P<type>[_a-z]+[\._a-z0-9]*))?' # Optional type annotation
RX_TYPE = r'(?:\s*:\s*(?P<type>[_a-z]+[\._a-z0-9]*))?' # Optional type annotation
RX_VALUE = r'(?:\s*=\s*(?P<value>(?:\".*\")|(?:[^;]+)|))?' # Optional default value
PROP_REGEX_RAW = fr'@prop\s+{RX_MODIFIERS}(?P<attr_type>var|final)\s+{RX_IDENTIFIER}{RX_TYPE}{RX_VALUE};'