Merge branch 'master' into live-patch
This commit is contained in:
commit
cca82a69bf
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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":
|
||||
|
|
31
Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx
Normal file
31
Sources/armory/logicnode/GetBoneFkIkOnlyNode.hx
Normal 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
|
||||
}
|
||||
}
|
30
Sources/armory/logicnode/GetBoneTransformNode.hx
Normal file
30
Sources/armory/logicnode/GetBoneTransformNode.hx
Normal 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
|
||||
}
|
||||
}
|
33
Sources/armory/logicnode/GetGamepadStartedNode.hx
Normal file
33
Sources/armory/logicnode/GetGamepadStartedNode.hx
Normal 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;
|
||||
}
|
||||
}
|
24
Sources/armory/logicnode/GetInputMapKeyNode.hx
Normal file
24
Sources/armory/logicnode/GetInputMapKeyNode.hx
Normal 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;
|
||||
}
|
||||
}
|
32
Sources/armory/logicnode/GetKeyboardStartedNode.hx
Normal file
32
Sources/armory/logicnode/GetKeyboardStartedNode.hx
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
32
Sources/armory/logicnode/GetMouseStartedNode.hx
Normal file
32
Sources/armory/logicnode/GetMouseStartedNode.hx
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
35
Sources/armory/logicnode/OnInputMapNode.hx
Normal file
35
Sources/armory/logicnode/OnInputMapNode.hx
Normal 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;
|
||||
}
|
||||
}
|
22
Sources/armory/logicnode/OncePerFrameNode.hx
Normal file
22
Sources/armory/logicnode/OncePerFrameNode.hx
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
19
Sources/armory/logicnode/RemoveInputMapKeyNode.hx
Normal file
19
Sources/armory/logicnode/RemoveInputMapKeyNode.hx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
37
Sources/armory/logicnode/SelectNode.hx
Normal file
37
Sources/armory/logicnode/SelectNode.hx
Normal 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;
|
||||
}
|
||||
}
|
35
Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx
Normal file
35
Sources/armory/logicnode/SetBoneFkIkOnlyNode.hx
Normal 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
|
||||
}
|
||||
}
|
46
Sources/armory/logicnode/SetInputMapKeyNode.hx
Normal file
46
Sources/armory/logicnode/SetInputMapKeyNode.hx
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 == "";
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
313
Sources/armory/trait/internal/UniformsManager.hx
Normal file
313
Sources/armory/trait/internal/UniformsManager.hx
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
|
|
14
blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py
Normal file
14
blender/arm/logicnode/animation/LN_get_bone_fk_ik_only.py
Normal 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')
|
14
blender/arm/logicnode/animation/LN_get_bone_transform.py
Normal file
14
blender/arm/logicnode/animation/LN_get_bone_transform.py
Normal 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')
|
17
blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py
Normal file
17
blender/arm/logicnode/animation/LN_set_bone_fk_ik_only.py
Normal 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')
|
|
@ -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)
|
||||
|
|
15
blender/arm/logicnode/input/LN_get_gamepad_started.py
Normal file
15
blender/arm/logicnode/input/LN_get_gamepad_started.py
Normal 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')
|
15
blender/arm/logicnode/input/LN_get_input_map_key.py
Normal file
15
blender/arm/logicnode/input/LN_get_input_map_key.py
Normal 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')
|
14
blender/arm/logicnode/input/LN_get_keyboard_started.py
Normal file
14
blender/arm/logicnode/input/LN_get_keyboard_started.py
Normal 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')
|
14
blender/arm/logicnode/input/LN_get_mouse_started.py
Normal file
14
blender/arm/logicnode/input/LN_get_mouse_started.py
Normal 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')
|
16
blender/arm/logicnode/input/LN_on_input_map.py
Normal file
16
blender/arm/logicnode/input/LN_on_input_map.py
Normal 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')
|
15
blender/arm/logicnode/input/LN_remove_input_map_key.py
Normal file
15
blender/arm/logicnode/input/LN_remove_input_map_key.py
Normal 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')
|
28
blender/arm/logicnode/input/LN_set_input_map_key.py
Normal file
28
blender/arm/logicnode/input/LN_set_input_map_key.py
Normal 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')
|
|
@ -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
|
||||
|
|
16
blender/arm/logicnode/logic/LN_once_per_frame.py
Normal file
16
blender/arm/logicnode/logic/LN_once_per_frame.py
Normal 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')
|
122
blender/arm/logicnode/logic/LN_select.py
Normal file
122
blender/arm/logicnode/logic/LN_select.py
Normal 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}]'
|
|
@ -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}
|
||||
)
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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='')
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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};'
|
||||
|
|
Loading…
Reference in a new issue