Primitive navmeshes.

This commit is contained in:
Lubos Lenco 2016-12-07 02:01:42 +01:00
parent 9c3add2075
commit 695d0bf69b
26 changed files with 413 additions and 107 deletions

View file

@ -2,7 +2,7 @@ package armory.logicnode;
class BoolNode extends Node {
public var val:Bool;
public var val:Bool = false;
public function new() {
super();

View file

@ -0,0 +1,16 @@
package armory.logicnode;
class DynamicNode extends Node {
public var val:Dynamic;
public function new() {
super();
}
public static function create(d:Dynamic) {
var n = new DynamicNode();
n.val = d;
return n;
}
}

View file

@ -2,7 +2,7 @@ package armory.logicnode;
class FloatNode extends Node {
public var val:Float;
public var val:Float = 0.0;
public function new() {
super();

View file

@ -0,0 +1,40 @@
package armory.logicnode;
import armory.trait.internal.NodeExecutor;
class GetTransformNode extends TransformNode {
public static inline var _target = 0; // Target
public function new() {
super(false);
}
public override function start(executor:NodeExecutor, parent:Node = null) {
super.start(executor, parent);
executor.notifyOnNodeInit(init);
}
public override function inputChanged() {
var t = inputs[_target].target;
if (t != null && matrix == null) {
var tr = t.transform;
matrix = tr.matrix;
loc = tr.loc;
rot = tr.rot;
scale = tr.scale;
super.inputChanged();
}
}
function init() {
inputChanged();
}
public static function create(target:iron.object.Object):GetTransformNode {
var n = new GetTransformNode();
n.inputs.push(target);
return n;
}
}

View file

@ -0,0 +1,43 @@
package armory.logicnode;
import armory.trait.internal.NodeExecutor;
#if arm_navigation
import armory.trait.internal.Navigation;
#end
class GoToNode extends Node {
public static inline var _trigger = 0; // Bool
public static inline var _target = 1; // Target
public static inline var _transform = 2; // Transform
public function new() {
super();
}
public override function inputChanged() {
if (!inputs[_trigger].val || inputs[_target].target == null || inputs[_transform].matrix == null) return;
#if arm_navigation
// Assume navmesh exists..
var from = inputs[_target].target.transform.loc;
var to = inputs[_transform].loc;
Navigation.active.navMeshes[0].findPath(from, to, function(path:Array<iron.math.Vec4>) {
var agent:armory.trait.NavAgent = inputs[_target].target.getTrait(armory.trait.NavAgent);
agent.setPath(path);
//super.inputChanged();
});
#end
}
public static function create(trigger:Bool, target:iron.object.Object, transform:iron.object.Transform):GoToNode {
var n = new GoToNode();
n.inputs.push(BoolNode.create(trigger));
n.inputs.push(target);
n.inputs.push(transform);
return n;
}
}

View file

@ -0,0 +1,32 @@
package armory.logicnode;
import armory.system.Input;
import armory.trait.internal.NodeExecutor;
class InputStartedNode extends BoolNode {
public function new() {
super();
}
public override function start(executor:NodeExecutor, parent:Node = null) {
super.start(executor, parent);
executor.notifyOnNodeUpdate(update);
}
function update() {
if (Input.started) {
val = Input.started;
inputChanged();
}
else if (val) {
val = false;
inputChanged();
}
}
public static function create(value:Float) {
var n = new InputStartedNode();
return n;
}
}

View file

@ -2,7 +2,7 @@ package armory.logicnode;
class IntNode extends Node {
public var val:Int;
public var val:Int = 0;
public function new() {
super();

View file

@ -4,7 +4,7 @@ import armory.trait.internal.NodeExecutor;
class PickerNode extends Node {
public var target:iron.node.Node;
public var target:iron.object.Object;
public var property0:String;
public function new() {
@ -18,6 +18,7 @@ class PickerNode extends Node {
function init() {
target = armory.Scene.active.getChild(property0);
inputChanged();
}
public static function create(_property0:String):PickerNode {

View file

@ -4,40 +4,25 @@ import armory.trait.internal.NodeExecutor;
class SetTransformNode extends Node {
public static inline var _target = 0; // Target
public static inline var _transform = 1; // Transform
public static inline var _trigger = 0; // Bool
public static inline var _target = 1; // Target
public static inline var _transform = 2; // Transform
public function new() {
super();
}
public override function start(executor:NodeExecutor, parent:Node = null) {
super.start(executor, parent);
executor.notifyOnNodeInit(init);
}
function init() {
var target:iron.object.Object = inputs[_target].target;
if (target != null) {
var matrix:iron.math.Mat4 = inputs[_transform].matrix;
target.transform.prependMatrix(matrix);
}
}
public override function inputChanged() {
if (inputs[_target].target == null) { // Target not attached, check next time
executor.notifyOnNodeInit(init);
}
else {
inputs[_target].target.transform.dirty = true;
}
if (!inputs[_trigger].val || inputs[_target].target == null || inputs[_transform].matrix == null) return;
inputs[_target].target.transform.setMatrix(inputs[_transform].matrix);
super.inputChanged();
}
public static function create(target:iron.object.Object, transform:iron.object.Transform):SetTransformNode {
public static function create(trigger:Bool, target:iron.object.Object, transform:iron.object.Transform):SetTransformNode {
var n = new SetTransformNode();
n.inputs.push(BoolNode.create(trigger));
n.inputs.push(target);
n.inputs.push(transform);
return n;

View file

@ -2,7 +2,7 @@ package armory.logicnode;
class StringNode extends Node {
public var val:String;
public var val:String = '';
public function new() {
super();

View file

@ -7,40 +7,46 @@ import iron.object.Transform;
class TransformNode extends Node {
public static inline var _position = 0; // Vector
public static inline var _location = 0; // Vector
public static inline var _rotation = 1; // Vector
public static inline var _scale = 2; // Vector
public var matrix:Mat4;
public var matrix:Mat4 = null;
var pos:Vec4;
var rot:Quat;
var scale:Vec4;
var loc:Vec4 = null;
var rot:Quat = null;
var scale:Vec4 = null;
public function new() {
var buildMatrix:Bool;
public function new(buildMatrix = true) {
super();
matrix = Mat4.identity();
pos = new Vec4();
rot = new Quat();
scale = new Vec4();
this.buildMatrix = buildMatrix;
if (buildMatrix) {
matrix = Mat4.identity();
loc = new Vec4();
rot = new Quat();
scale = new Vec4();
}
}
public override function inputChanged() {
// Build matrix
pos.set(inputs[_position].inputs[VectorNode._x].val,
inputs[_position].inputs[VectorNode._y].val,
inputs[_position].inputs[VectorNode._z].val);
if (buildMatrix) {
loc.set(inputs[_location].inputs[VectorNode._x].val,
inputs[_location].inputs[VectorNode._y].val,
inputs[_location].inputs[VectorNode._z].val);
rot.fromEuler(inputs[_rotation].inputs[VectorNode._x].val,
inputs[_rotation].inputs[VectorNode._y].val,
inputs[_rotation].inputs[VectorNode._z].val);
rot.fromEuler(inputs[_rotation].inputs[VectorNode._x].val,
inputs[_rotation].inputs[VectorNode._y].val,
inputs[_rotation].inputs[VectorNode._z].val);
scale.set(inputs[_scale].inputs[VectorNode._x].val,
inputs[_scale].inputs[VectorNode._y].val,
inputs[_scale].inputs[VectorNode._z].val);
scale.set(inputs[_scale].inputs[VectorNode._x].val,
inputs[_scale].inputs[VectorNode._y].val,
inputs[_scale].inputs[VectorNode._z].val);
matrix.compose(pos, rot, scale);
matrix.compose(loc, rot, scale);
}
super.inputChanged();
}

View file

@ -34,7 +34,7 @@ class FirstPersonController extends CameraController {
var xVec = Vec4.xAxis();
var zVec = Vec4.zAxis();
function preUpdate() {
if (Input.occupied || !body.bodyReady) return;
if (Input.occupied || !body.ready) return;
if (Input.started && !locked) {
kha.SystemImpl.lockMouse();
@ -58,7 +58,7 @@ class FirstPersonController extends CameraController {
var dir = new Vec4();
function update() {
if (!body.bodyReady) return;
if (!body.ready) return;
if (jump) {
body.applyImpulse(new Vec4(0, 0, 16));

View file

@ -1,31 +1,39 @@
package armory.trait;
import iron.Trait;
import iron.math.Vec4;
import iron.system.Tween;
@:keep
class NavAgent extends Trait {
// nav mesh
// target object
// behaviour
var path:Array<Vec4> = null;
var index = 0;
public function new() {
super();
}
notifyOnInit(init);
public function setPath(path:Array<Vec4>) {
this.path = path;
index = 1;
notifyOnUpdate(update);
notifyOnRemove(removed);
}
function removed() {
go();
}
function init() {
function go() {
if (path == null || index >= path.length) return;
var p = path[index];
var dist = Vec4.distance3d(object.transform.loc, p);
var speed = 0.2;
Tween.to(object.transform.loc, dist * speed, { x: p.x, y: p.y /*, z: p.z*/ }, function() {
index++;
if (index < path.length) go();
}, 0, 0);
}
function update() {
object.transform.dirty = true;
}
}

View file

@ -7,21 +7,5 @@ class NavCrowd extends Trait {
public function new() {
super();
notifyOnInit(init);
notifyOnUpdate(update);
notifyOnRemove(removed);
}
function removed() {
}
function init() {
}
function update() {
}
}

View file

@ -1,27 +1,60 @@
package armory.trait;
#if arm_navigation
import armory.trait.internal.Navigation;
import haxerecast.Recast;
#end
import iron.Trait;
import iron.data.Data;
import iron.math.Vec4;
@:keep
class NavMesh extends Trait {
#if (!arm_navigation)
public function new() { super(); }
#else
var recast:Recast = null;
var ready = false;
public function new() {
super();
notifyOnInit(init);
notifyOnUpdate(update);
notifyOnRemove(removed);
}
function removed() {
}
function init() {
Navigation.active.navMeshes.push(this);
// Load navmesh
var name = "nav_" + cast(object, iron.object.MeshObject).data.name + ".arm";
Data.getBlob(name, function(b:kha.Blob) {
recast = Navigation.active.recast;
recast.OBJDataLoader(b.toString(), function() {
recast.buildSolo();
ready = true;
});
});
}
function update() {
public function findPath(from:Vec4, to:Vec4, done:Array<Vec4>->Void) {
if (!ready) return;
recast.findPath(from.x, from.z, from.y, to.x, to.z, to.y, 1000, untyped recast.cb(function(path:Array<RecastWaypoint>) {
var ar:Array<Vec4> = [];
for (p in path) ar.push(new Vec4(p.x, p.z, -p.y));
done(ar);
}));
}
public function getRandomPoint(done:Vec4->Void) {
if (!ready) return;
recast.getRandomPoint(untyped recast.cb(function(x:Float, y:Float, z:Float) {
done(new Vec4(x, z, -y));
}));
}
#end
}

View file

@ -22,7 +22,7 @@ class SidescrollerController extends CameraController {
var dir = new Vec4();
function update() {
if (!body.bodyReady) return;
if (!body.ready) return;
if (jump) {
body.applyImpulse(new Vec4(0, 0, 20));

View file

@ -29,7 +29,7 @@ class ThirdPersonController extends CameraController {
var xVec = Vec4.xAxis();
var zVec = Vec4.zAxis();
function preUpdate() {
if (Input.occupied || !body.bodyReady) return;
if (Input.occupied || !body.ready) return;
if (Input.down) {
// kha.SystemImpl.lockMouse();
@ -46,7 +46,7 @@ class ThirdPersonController extends CameraController {
var dir = new Vec4();
function update() {
if (!body.bodyReady) return;
if (!body.ready) return;
if (jump) {
body.applyImpulse(new Vec4(0, 0, 20));

View file

@ -0,0 +1,27 @@
package armory.trait.internal;
#if arm_navigation
import haxerecast.Recast;
import armory.trait.NavMesh;
#end
@:keep
class Navigation extends iron.Trait {
#if (!arm_navigation)
public function new() { super(); }
#else
public static var active:Navigation = null;
public var navMeshes:Array<NavMesh> = [];
public var recast:Recast;
public function new() {
super();
active = this;
recast = untyped __js__("(1, eval)('this').recast");
}
#end
}

View file

@ -26,7 +26,7 @@ class RigidBody extends Trait {
public var collisionMargin:Float;
public var body:BtRigidBodyPointer = null;
public var bodyReady = false;
public var ready = false;
public var shapeConvexCreated:Bool;
static var nextId = 0;
@ -61,8 +61,8 @@ class RigidBody extends Trait {
transform = object.transform;
physics = armory.trait.internal.PhysicsWorld.active;
if (bodyReady) return;
bodyReady = true;
if (ready) return;
ready = true;
var _shape:BtCollisionShapePointer = null;
var _shapeConvex:BtConvexHullShapePointer = null;
@ -156,7 +156,7 @@ class RigidBody extends Trait {
}
function lateUpdate() {
if (!bodyReady) return;
if (!ready) return;
if (object.animation != null) {
syncTransform();
}

View file

@ -2330,11 +2330,19 @@ class ArmoryExporter:
# Scene root traits
if bpy.data.worlds['Arm'].arm_physics != 'Disabled':
self.output['traits'] = []
if not 'traits' in self.output:
self.output['traits'] = []
x = {}
x['type'] = 'Script'
x['class_name'] = 'armory.trait.internal.PhysicsWorld'
self.output['traits'].append(x)
if bpy.data.worlds['Arm'].arm_navigation != 'Disabled':
if not 'traits' in self.output:
self.output['traits'] = []
x = {}
x['type'] = 'Script'
x['class_name'] = 'armory.trait.internal.Navigation'
self.output['traits'].append(x)
self.export_objects(self.scene)
@ -2552,6 +2560,26 @@ class ArmoryExporter:
x['type'] = 'Script'
if t.type_prop == 'Bundled Script':
trait_prefix = 'armory.trait.'
# TODO: temporary, export single mesh navmesh as obj
if t.class_name_prop == 'NavMesh' and bobject.type == 'MESH' and bpy.data.worlds['Arm'].arm_navigation != 'Disabled':
nav_path = armutils.get_fp() + '/build/compiled/Assets/navigation'
if not os.path.exists(nav_path):
os.makedirs(nav_path)
nav_filepath = nav_path + '/nav_' + bobject.data.name + '.arm'
assets.add(nav_filepath)
# TODO: Implement cache
#if os.path.isfile(nav_filepath) == False:
with open(nav_filepath, 'w') as f:
for v in bobject.data.vertices:
# f.write("v %.4f %.4f %.4f\n" % v.co[:])
f.write("v %.4f " % (v.co[0] * bobject.scale.x))
f.write("%.4f " % (v.co[2] * bobject.scale.z))
f.write("%.4f\n" % (-v.co[1] * bobject.scale.y))
for p in bobject.data.polygons:
f.write("f")
for i in p.vertices:
f.write(" %d" % (i + 1))
f.write("\n")
else:
trait_prefix = bpy.data.worlds['Arm'].arm_project_package + '.'
x['class_name'] = trait_prefix + t.class_name_prop

View file

@ -101,4 +101,6 @@ def build_default_node(inp):
inpname = 'FloatNode.create(' + str(inp.default_value) + ')'
elif inp.type == 'BOOLEAN':
inpname = 'BoolNode.create(' + str(inp.default_value).lower() + ')'
else:
inpname = 'BoolNode.create(true)' # Unlinked triggers
return inpname

View file

@ -20,7 +20,7 @@ class TransformNode(Node, ArmLogicTreeNode):
bl_icon = 'SOUND'
def init(self, context):
self.inputs.new('NodeSocketVector', "Position")
self.inputs.new('NodeSocketVector', "Location")
self.inputs.new('NodeSocketVector', "Rotation")
self.inputs.new('NodeSocketVector', "Scale")
self.inputs[-1].default_value = [1.0, 1.0, 1.0]
@ -112,9 +112,31 @@ class SetTransformNode(Node, ArmLogicTreeNode):
bl_icon = 'GAME'
def init(self, context):
self.inputs.new('NodeSocketShader', "Trigger")
self.inputs.new('NodeSocketShader', "Target")
self.inputs.new('NodeSocketShader', "Transform")
class GoToNode(Node, ArmLogicTreeNode):
'''Navigate to location'''
bl_idname = 'GoToNodeType'
bl_label = 'Go To'
bl_icon = 'GAME'
def init(self, context):
self.inputs.new('NodeSocketShader', "Trigger")
self.inputs.new('NodeSocketShader', "Target")
self.inputs.new('NodeSocketShader', "Transform")
class GetTransformNode(Node, ArmLogicTreeNode):
'''Get transform node'''
bl_idname = 'GetTransformNodeType'
bl_label = 'Get Transform'
bl_icon = 'GAME'
def init(self, context):
self.inputs.new('NodeSocketShader', "Target")
self.outputs.new('NodeSocketShader', "Transform")
class SetVisibleNode(Node, ArmLogicTreeNode):
'''Set visible node'''
bl_idname = 'SetVisibleNodeType'
@ -134,6 +156,15 @@ class InputDownNode(Node, ArmLogicTreeNode):
def init(self, context):
self.outputs.new('NodeSocketBool', "Bool")
class InputStartedNode(Node, ArmLogicTreeNode):
'''Input started node'''
bl_idname = 'InputStartedNodeType'
bl_label = 'Input Started'
bl_icon = 'GAME'
def init(self, context):
self.outputs.new('NodeSocketBool', "Bool")
class GreaterThanNode(Node, ArmLogicTreeNode):
'''Greater than node'''
bl_idname = 'GreaterThanNodeType'
@ -154,7 +185,7 @@ class ObjectNodeCategory(NodeCategory):
def poll(cls, context):
return context.space_data.tree_type == 'ArmLogicTreeType'
class TypeNodeCategory(NodeCategory):
class ValueNodeCategory(NodeCategory):
@classmethod
def poll(cls, context):
return context.space_data.tree_type == 'ArmLogicTreeType'
@ -174,12 +205,11 @@ node_categories = [
NodeItem("ThisNodeType"),
NodeItem("PickerNodeType"),
]),
TypeNodeCategory("LOGICTYPENODES", "Type", items=[
ValueNodeCategory("LOGICVALUENODES", "Value", items=[
NodeItem("TransformNodeType"),
NodeItem("VectorNodeType"),
]),
MathNodeCategory("LOGICMATHNODES", "Math", items=[
NodeItem("TimeNodeType"),
NodeItem("ScaleValueNodeType"),
NodeItem("SineNodeType"),
]),
@ -188,10 +218,14 @@ node_categories = [
]),
LogicNodeCategory("LOGICOPERATORNODES", "Operator", items=[
NodeItem("SetTransformNodeType"),
NodeItem("GetTransformNodeType"),
NodeItem("GoToNodeType"),
NodeItem("SetVisibleNodeType"),
]),
LogicNodeCategory("LOGICINPUTNODES", "Input", items=[
NodeItem("TimeNodeType"),
NodeItem("InputDownNodeType"),
NodeItem("InputStartedNodeType"),
]),
]

View file

@ -105,7 +105,7 @@ class ArmoryGenerateLodButton(bpy.types.Operator):
def execute(self, context):
obj = context.object
if obj == None:
return
return{'CANCELLED'}
# Clear
mdata = context.object.data

50
blender/props_navigation.py Executable file
View file

@ -0,0 +1,50 @@
import bpy
from bpy.types import Menu, Panel, UIList
from bpy.props import *
class ArmoryGenerateNavmeshButton(bpy.types.Operator):
'''Generate navmesh from selected meshes'''
bl_idname = 'arm.generate_navmesh'
bl_label = 'Generate Navmesh'
def execute(self, context):
obj = context.active_object
if obj == None or obj.type != 'MESH':
return{'CANCELLED'}
# TODO: build tilecache instead
# export_obj()
# For visualization
# bpy.ops.mesh.navmesh_make('EXEC_DEFAULT')
# obj = context.active_object
# obj.hide_render = True
# Navmesh trait
# obj.my_traitlist.add()
# obj.my_traitlist[-1].type_prop = 'Bundled Script'
# obj.my_traitlist[-1].class_name_prop = 'NavMesh'
return{'FINISHED'}
class ScenePropsPanel(bpy.types.Panel):
bl_label = "Armory Navigation"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = bpy.context.scene
if scene == None:
return
layout.operator("arm.generate_navmesh")
def register():
pass
# bpy.utils.register_module(__name__)
def unregister():
bpy.utils.unregister_module(__name__)

View file

@ -6,6 +6,7 @@ import props_traits_clip
import props_traits_params
import props_traits
import props_lod
import props_navigation
import props
import props_ui
import gen_renderpath
@ -27,6 +28,7 @@ def register():
props_traits_params.register()
props_traits.register()
props_lod.register()
props_navigation.register()
space_armory.register()
keymap.register()
@ -38,6 +40,7 @@ def unregister():
props_traits_params.unregister()
props_traits.unregister()
props_lod.unregister()
props_navigation.unregister()
handlers.unregister()
gen_renderpath.unregister()
props_ui.unregister()

View file

@ -37,6 +37,15 @@ project.addSources('Sources');
ammojs_path = ammojs_path.replace('\\', '/')
f.write("project.addAssets('" + ammojs_path + "');\n")
export_navigation = wrd.arm_navigation != 'Disabled'
if export_navigation:
f.write("project.addDefine('arm_navigation');\n")
f.write(add_armory_library(sdk_path + '/lib/', 'haxerecast'))
if state.target == 'krom' or state.target == 'html5':
recastjs_path = sdk_path + '/lib/haxerecast/js/recast/recast.js'
recastjs_path = recastjs_path.replace('\\', '/')
f.write("project.addAssets('" + recastjs_path + "');\n")
if dce_full:
f.write("project.addParameter('-dce full');")
@ -99,20 +108,25 @@ class Main {
static inline var projectHeight = """ + str(resy) + """;
static inline var projectSamplesPerPixel = """ + str(wrd.arm_project_samples_per_pixel) + """;
static inline var projectScene = '""" + scene_name + scene_ext + """';
static var state:Int;
static function loadLib(name:String) {
kha.LoaderImpl.loadBlobFromDescription({ files: [name] }, function(b:kha.Blob) {
untyped __js__("(1, eval)({0})", b.toString());
state--;
start();
});
}
public static function main() {
iron.system.CompileTime.importPackage('armory.trait');
iron.system.CompileTime.importPackage('armory.renderpath');
iron.system.CompileTime.importPackage('""" + wrd.arm_project_package + """');
#if (js && arm_physics)
kha.LoaderImpl.loadBlobFromDescription({ files: ["ammo.js"] }, function(b:kha.Blob) {
untyped __js__("(1, eval)({0})", b.toString());
start();
});
#else
start();
#end
state = 1;
#if (js && arm_physics) state++; loadLib("ammo.js"); #end
#if (js && arm_navigation) state++; loadLib("recast.js"); #end
state--; start();
}
static function start() {
if (state > 0) return;
armory.object.Uniforms.register();
kha.System.init({title: projectName, width: projectWidth, height: projectHeight, samplesPerPixel: projectSamplesPerPixel}, function() {
iron.App.init(function() {