Ver código fonte

Merge branch 'master' of https://github.com/HeapsIO/hide

trethaller 2 anos atrás
pai
commit
76fb99c716

+ 20 - 2
hide/comp/PropsEditor.hx

@@ -556,8 +556,26 @@ class PropsField extends Component {
 			if( f.is("select") ) {
 				enumValue = Type.getEnum(current);
 				if( enumValue != null && f.find("option").length == 0 ) {
-					for( c in enumValue.getConstructors() )
-						new Element('<option value="$c">$c</option>').appendTo(f);
+					var meta = haxe.rtti.Meta.getFields(enumValue);
+					for( c in enumValue.getConstructors() ) {
+						
+						var name = c;
+						var comment = "";
+						if (Reflect.hasField(meta, c)) {
+							var fieldMeta = Reflect.getProperty(meta, c);
+							if (Reflect.hasField(fieldMeta, "display")) {
+								var displayArr = Reflect.getProperty(fieldMeta, "display");
+								if (displayArr.length > 0) {
+									name = displayArr[0];
+								}
+								if (displayArr.length > 1) {
+									comment = displayArr[1];
+								}
+							}
+						}
+
+						new Element('<option value="$c" title="$comment">$name</option>').appendTo(f);
+					}
 				}
 			}
 

+ 65 - 7
hide/view/FXEditor.hx

@@ -171,7 +171,7 @@ private class FXSceneEditor extends hide.comp.SceneEditor {
 				});
 			}
 		} else {
-			for(name in ["Group", "Polygon", "Model", "Shader", "Emitter"]) {
+			for(name in ["Group", "Polygon", "Model", "Shader", "Emitter", "Trails"]) {
 				var item = allTypes.find(i -> i.label == name);
 				if(item == null) continue;
 				allTypes.remove(item);
@@ -1749,6 +1749,8 @@ class FXEditor extends FileView {
 	}
 
 	var avg_smooth = 0.0;
+	var trailTime_smooth = 0.0;
+	var num_trail_tri_smooth = 0.0;
 
 	public static function floatToStringPrecision(n : Float, ?prec : Int = 2, ?showZeros : Bool = true) {
 		if(n == 0) { // quick return
@@ -1829,14 +1831,55 @@ class FXEditor extends FileView {
 		for(e in emitters)
 			totalParts += @:privateAccess e.numInstances;
 
-		var total_time = 0.0;
+		var emitterTime = 0.0;
 		for (e in emitters) {
-			total_time += e.tickTime;
-		}
+			emitterTime += e.tickTime;
+		}
+
+		var trails = ctx.local3d.findAll(o -> Std.downcast(o, hrt.prefab.l3d.Trails.TrailObj));
+		var trailCount = 0;
+		var trailTime = 0.0;
+		var trailTris = 0.0;
+		var trailMaxTris = 0;
+		var trailMaxLen = 0;
+		var trailCalcMaxLen = 0;
+		var trailRealIndicies = 0;
+		var trailAllocIndicies = 0;
+
+
+		var poolSize = 0;
+		@:privateAccess
+		for (trail in trails) {
+			for (head in trail.trails) {
+				trailCount ++;
+				var p = head.firstPoint;
+				var len = 0;
+				while(p != null) {
+					len ++;
+					p = p.next;
+				}
+				if (len > trailMaxLen) {
+					trailMaxLen = len;
+				}
+			}
+			trailTime += trail.lastUpdateDuration;
+			trailTris += trail.numVerts;
 
-		var smooth_factor = 1/30.0;
-		avg_smooth = avg_smooth * (1.0 - smooth_factor) + total_time * smooth_factor;
+			var p = trail.pool;
+			while(p != null) {
+				poolSize ++;
+				p = p.next;
+			}
+			trailMaxTris += Std.int(trail.vbuf.length/8.0);
+			trailCalcMaxLen = trail.calcMaxTrailPoints();
+			trailRealIndicies += trail.numVertsIndices;
+			trailAllocIndicies += trail.currentAllocatedIndexCount;
+		}
 
+		var smooth_factor = 0.10;
+		avg_smooth = avg_smooth * (1.0 - smooth_factor) + emitterTime * smooth_factor;
+		trailTime_smooth = trailTime_smooth * (1.0 - smooth_factor) + trailTime * smooth_factor;
+		num_trail_tri_smooth = num_trail_tri_smooth * (1.0-smooth_factor) + trailTris * smooth_factor;
 
 		if(statusText != null) {
 			var lines : Array<String> = [
@@ -1844,8 +1887,23 @@ class FXEditor extends FileView {
 				'Scene objects: ${scene.s3d.getObjectsCount()}',
 				'Drawcalls: ${h3d.Engine.getCurrent().drawCalls}',
 				'Particles: $totalParts',
-				'Particles CPU time: ${floatToStringPrecision(avg_smooth * 1000, 3, true)} ms'
+				'Particles CPU time: ${floatToStringPrecision(avg_smooth * 1000, 3, true)} ms',
 			];
+
+			if (trailCount > 0) {
+
+				lines.push('Trails CPU time : ${floatToStringPrecision(trailTime_smooth * 1000, 3, true)} ms');
+
+				/*lines.push("---");
+				lines.push('Num Trails : $trailCount');
+				lines.push('Trails Vertexes : ${floatToStringPrecision(num_trail_tri_smooth, 2, true)}');
+				lines.push('Allocated Trails Vertexes : $trailMaxTris');
+				lines.push('Max Trail Lenght : $trailMaxLen');
+				lines.push('Theorical Max Trail Lenght : $trailCalcMaxLen');
+				lines.push('Trail pool : $poolSize');
+				lines.push('Num Indices : $trailRealIndicies');
+				lines.push('Num Allocated Indices : $trailAllocIndicies');*/
+			}
 			statusText.text = lines.join("\n");
 		}
 

+ 120 - 1
hrt/impl/Macros.hx

@@ -1,10 +1,94 @@
 package hrt.impl;
 import haxe.macro.Context;
 import haxe.macro.Expr;
+import haxe.macro.Type;
 
 class Macros {
 
+	public static function enumOrNullByName<T>(e:Enum<T>, constr:String, ?params:Array<Dynamic>):T {
+		var value = try {
+			haxe.EnumTools.createByName(e, constr, params);
+		} catch (_) {
+			null;
+		};
+
+		if (value == null) {
+			var defaultConstructors = Type.allEnums(e);
+			if (defaultConstructors.length > 0) value = defaultConstructors[0];
+		}
+
+		return value;
+	}
+
 	#if macro
+
+	// Get the field in the specified field path or null if any element of the path is not null
+	static function getOrDefault(path: Array<String>, ?startIndex: Int, ?defaultValue: Expr) : Expr {
+		function recursive(path: Array<String>, index : Int, defaultValue: Expr) : Expr {
+			var pos = Context.currentPos();
+			if (index == path.length - 1) {
+				return macro @:pos(pos) $p{path};
+			}
+			else {
+				var subpath = path.slice(0, index+1);
+				return macro @:pos(pos) $p{subpath} != null ? ${recursive(path, index+1, defaultValue)} : $defaultValue;
+			}
+		}
+
+		return recursive(path, startIndex != null ? startIndex : 0, defaultValue != null ? defaultValue : macro null);
+	}
+
+	static function forEachFieldInType(t: Type, path: Array<String>, pos, func: (t: Type, path : Array<String>, pos: Position) -> Void) : Void {
+		var trueType = Context.follow(t, false);
+			switch(trueType) {
+				case TAnonymous(a):
+					for (f in a.get().fields) {
+						path.push(f.name);
+						forEachFieldInType(f.type, path, pos, func);
+						path.pop();
+					}
+				default:
+					func(t, path, pos);
+			}
+	}
+
+	static function getTypeExpression(t : Type, path : Array<String>) : Expr {
+		var trueType = Context.follow(t, false);
+		var pos = Context.currentPos();
+		switch(trueType) {
+			case TAnonymous(a):
+				return createAnonDecl(a, path, pos);
+			case TEnum(_,_):
+				var objFields : Array<ObjectField> = [];
+
+				return macro @:pos(pos) {
+					var name = haxe.EnumTools.EnumValueTools.getName($p{path});
+					var params = haxe.EnumTools.EnumValueTools.getParameters($p{path});
+					if (params.length == 0) {
+						(name:Dynamic);
+					} else {
+						({
+							"name" : name,
+							"params" : params
+						}:Dynamic);
+					}
+				};
+			default:
+				return macro $p{path};
+		}
+	}
+
+	static function createAnonDecl(anonType: Ref<AnonType>, path: Array<String>, pos) {
+		var objFields : Array<ObjectField> = [];
+		for (f in anonType.get().fields) {
+			path.push(f.name);
+			var e = getTypeExpression(f.type, path);
+			path.pop();
+			objFields.push({field : f.name, expr : e});
+		}
+		return {expr: EObjectDecl(objFields), pos : pos};
+	}
+
 	public static function buildPrefab() {
 		var fields = Context.getBuildFields();
 		var toSerialize = [], toCopy = [];
@@ -39,7 +123,9 @@ class Macros {
 			return changed ? fields : null;
 		var ser = [], unser = [], copy = [];
 		var pos = Context.currentPos();
+
 		for( f in toSerialize ) {
+
 			switch( f.kind ) {
 			case FProp(_, _, t, e), FVar(t,e):
 				var name = f.name;
@@ -50,6 +136,7 @@ class Macros {
 					case null: Context.error("Invalid var decl", f.pos);
 					case TPath({ pack : [], name : "Int"|"Float" }): CInt("0");
 					case TPath({ pack : [], name : "Bool" }): CIdent("false");
+					//case TPath(p): setDef = false; trace(p); CIdent("null");
 					default: setDef = false; CIdent("null");
 					}
 					e = { expr : EConst(c), pos : f.pos };
@@ -66,12 +153,44 @@ class Macros {
 						serCond = macro @:pos(f.pos) this.$name.length != 0;
 				}
 
+
+				var type = null;
+				if (t != null) {
+					type = haxe.macro.ComplexTypeTools.toType(t);
+				}
+				else if (e != null) {
+					type = Context.typeof(e);
+				}
+				if (type == null) throw "assert";
+
+				var expr = getTypeExpression(type, ["this", name]);
+
 				if( serCond == null ) {
 					var defVal = e.expr.match(EConst(_) | EBinop(_) | EUnop(_)) ? e : macro @:pos(f.pos) null;
 					serCond = macro @:pos(pos) this.$name != $defVal;
 				}
 
-				ser.push(macro @:pos(pos) if( $serCond ) obj.$name = this.$name);
+				ser.push(macro @:pos(pos) if( $serCond ) obj.$name = $expr);
+
+				forEachFieldInType(type, ["obj", name], pos, function(t: Type, path: Array<String>, pos: Position) : Void
+				{
+					switch(t) {
+						case TEnum(enumRef,_): {
+							var name = path.copy(); name.push("name");
+							var params = path.copy(); params.push("parameters");
+							var parentPath = path.copy(); parentPath.pop();
+							var expr = macro @:pos(pos) {
+								var objNullCheck = ${getOrDefault(parentPath)};
+								var isString = Std.is($p{path}, String);
+								if (objNullCheck != null)
+									$p{path} = hrt.impl.Macros.enumOrNullByName($i{enumRef.get().name}, isString ? $p{path} : ${getOrDefault(name, parentPath.length)}, isString ? null : ${getOrDefault(params, parentPath.length)});
+							};
+							unser.push(expr);
+						}
+						default: {}
+					}
+				});
+
 				unser.push(macro @:pos(pos) this.$name = obj.$name == null ? $e : obj.$name);
 				copy.push(macro @:pos(pos) this.$name = p.$name);
 			default:

+ 76 - 27
hrt/prefab/fx/Emitter.hx

@@ -86,12 +86,15 @@ private class ParticleInstance {
 	var scaleY : Single;
 	var scaleZ : Single;
 
+	var trail : hrt.prefab.l3d.Trails.TrailHead;
+	var trailGeneration : Int = 0;
+
 	#if (hl_ver >= version("1.13.0"))
 	@:packed var speedAccumulation(default, never) : SVector3;
 	@:packed var qRot(default, never) : SVector4;
 	@:packed var absPos(default, never) : SMatrix4;  // Needed for sortZ
 	@:packed var emitOrientation(default, never) : SMatrix3;
-	#else 
+	#else
 	var speedAccumulation(default, never) = new SVector3();
 	var qRot(default, never) = new SVector4();
 	var absPos(default, never) = new SMatrix4();
@@ -123,7 +126,7 @@ private class ParticleInstance {
 		qRot.load(p.qRot.toVector());
 		absPos.load(p.absPos.toMatrix());
 		emitOrientation.load(p.emitOrientation.toMatrix());
-		
+
 		colorMult = p.colorMult;
 		idx = p.idx;
 		startFrame = p.startFrame;
@@ -134,6 +137,8 @@ private class ParticleInstance {
 		startTime = p.startTime;
 		prev = p.prev;
 		next = p.next;
+		trail = p.trail;
+		trailGeneration = p.trailGeneration;
 	}
 
 	function init(idx: Int, emitter: EmitterObject) {
@@ -152,6 +157,11 @@ private class ParticleInstance {
 		lifeTime = 0;
 		startFrame = 0;
 		random = emitter.random.rand();
+
+		if (emitter.trails != null) {
+			trail = emitter.trails.allocTrail();
+			trailGeneration = trail.generation;
+		}
 	}
 
 	static var tmpRot = new h3d.Vector();
@@ -211,7 +221,7 @@ private class ParticleInstance {
 				qRot.load(emitter.screenQuat);
 			default:
 		}
-		
+
 		var absPos = tmpMat;
 		var localMat = tmpMat2;
 
@@ -404,6 +414,8 @@ class EmitterObject extends h3d.scene.Object {
 	public var particleTemplate : hrt.prefab.Object3D;
 	public var subEmitterTemplate : Emitter;
 	public var subEmitters : Array<EmitterObject>;
+	public var trails : hrt.prefab.l3d.Trails.TrailObj;
+	public var trailsTemplate : hrt.prefab.l3d.Trails;
 	// LIFE
 	public var lifeTime = 2.0;
 	public var lifeTimeRand = 0.0;
@@ -573,6 +585,10 @@ class EmitterObject extends h3d.scene.Object {
 				s.remove();
 			subEmitters = null;
 		}
+
+		if (trails != null) {
+			trails.reset();
+		}
 	}
 
 	inline function checkList() { /*
@@ -620,13 +636,13 @@ class EmitterObject extends h3d.scene.Object {
 
 	function disposeInstance(idx: Int) {
 		checkList();
-
 		--numInstances;
 		if(numInstances < 0)
 			throw "assert";
 
 		// stitch list after remove
 		var o = particles[idx];
+
 		if(o.idx == ParticleInstance.REMOVED_IDX) throw "!";
 		var prev = o.prev;
 		var next = o.next;
@@ -634,7 +650,7 @@ class EmitterObject extends h3d.scene.Object {
 			if(prev.next == next) throw "!";
 			prev.next = next;
 		}
-		else { 
+		else {
 			if(listHead != o) throw "!";
 			if(listHead == next) throw "!";
 			listHead = next;
@@ -656,7 +672,7 @@ class EmitterObject extends h3d.scene.Object {
 			if(listHead == swap)
 				listHead = o;
 		}
-		else 
+		else
 			o.idx = ParticleInstance.REMOVED_IDX;
 
 		checkList();
@@ -683,6 +699,18 @@ class EmitterObject extends h3d.scene.Object {
 
 		var emitterQuat : h3d.Quat = null;
 		if (count > 0) {
+
+			if (trailsTemplate != null && trails == null) {
+				if( tmpCtx == null ) {
+					tmpCtx = new hrt.prefab.Context();
+					tmpCtx.shared = context.shared;
+				}
+				tmpCtx.custom = {numTrails: maxCount};
+				tmpCtx.local3d = this;
+				trails = cast trailsTemplate.make(tmpCtx).local3d;
+				trails.autoTrackPosition = false;
+			}
+
 			for( i in 0...count ) {
 				var part = allocInstance();
 				part.startTime = startTime + curTime;
@@ -789,6 +817,7 @@ class EmitterObject extends h3d.scene.Object {
 				var frameCount = frameCount == 0 ? frameDivisionX * frameDivisionY : frameCount;
 				if(animationLoop)
 					part.startFrame = random.random(frameCount);
+
 			}
 		}
 
@@ -975,6 +1004,8 @@ class EmitterObject extends h3d.scene.Object {
 		batch.begin(hxd.Math.nextPOT(maxCount));
 
 		inline function emit(p: ParticleInstance) {
+			if (p.life > p.lifeTime)
+				return;
 			inline tmpMat.load(p.absPos.toMatrix());
 			batch.worldPosition = tmpMat;
 			for( anim in shaderAnims ) {
@@ -1021,28 +1052,37 @@ class EmitterObject extends h3d.scene.Object {
 
 		var prev : ParticleInstance = null;
 		var camPos = getScene().camera.pos;
+
+		if (trails != null) {
+			trails.numTrails = maxCount;
+		}
+
 		var i = 0;
 		while(i < numInstances) {
 			var p = particles[i];
 			if(p.life > p.lifeTime) {
-				i = disposeInstance(i);
-
-				// SUB EMITTER
-				if( subEmitterTemplate != null ) {
-					if( tmpCtx == null ) {
-						tmpCtx = new hrt.prefab.Context();
+				if (p.trail == null || p.trail.generation != p.trailGeneration) {
+					i = disposeInstance(i);
+					// SUB EMITTER
+					if( subEmitterTemplate != null ) {
+						if( tmpCtx == null ) {
+							tmpCtx = new hrt.prefab.Context();
+							tmpCtx.local3d = this.getScene();
+							tmpCtx.shared = context.shared;
+						}
 						tmpCtx.local3d = this.getScene();
-						tmpCtx.shared = context.shared;
+						var emitter : EmitterObject = cast subEmitterTemplate.makeInstance(tmpCtx).local3d;
+						var pos = p.absPos.getPosition();
+						emitter.setPosition(pos.x, pos.y, pos.z);
+						emitter.isSubEmitter = true;
+						emitter.parentEmitter = this;
+						if(subEmitters == null)
+							subEmitters = [];
+						subEmitters.push(emitter);
 					}
-					tmpCtx.local3d = this.getScene();
-					var emitter : EmitterObject = cast subEmitterTemplate.makeInstance(tmpCtx).local3d;
-					var pos = p.absPos.getPosition();
-					emitter.setPosition(pos.x, pos.y, pos.z);
-					emitter.isSubEmitter = true;
-					emitter.parentEmitter = this;
-					if(subEmitters == null)
-						subEmitters = [];
-					subEmitters.push(emitter);
+				} else {
+					prev = p;
+					++i;
 				}
 			}
 			else {
@@ -1055,6 +1095,10 @@ class EmitterObject extends h3d.scene.Object {
 				p.life += dt;  // After updateAbsPos(), which uses current life
 				prev = p;
 				++i;
+
+				if (trails != null) {
+					trails.addPoint(p.trail, p.absPos._41, p.absPos._42, p.absPos._43, ECamera, 1.0);
+				}
 			}
 		}
 	}
@@ -1437,6 +1481,11 @@ class Emitter extends Object3D {
 		// SUB-EMITTER
 		var subEmitterTemplate : Emitter = cast children.find( p -> p.enabled && Std.downcast(p, Emitter) != null && p.to(Object3D).visible);
 		emitterObj.subEmitterTemplate = subEmitterTemplate;
+
+		// TRAILS
+		var trailsTemplate : hrt.prefab.l3d.Trails = cast children.find(p -> p.enabled && Std.isOfType(p, hrt.prefab.l3d.Trails) && p.to(Object3D).visible);
+		emitterObj.trailsTemplate = trailsTemplate;
+
 		// RANDOM
 		emitterObj.seedGroup 			= 	getParamVal("seedGroup");
 		// LIFE
@@ -1877,7 +1926,7 @@ class SVector4 {
 	inline function toQuat() {
 		return new h3d.Quat(x, y, z, w);
 	}
-	
+
 	inline function load(v: h3d.Vector) {
 		this.x = v.x;
 		this.y = v.y;
@@ -1939,10 +1988,10 @@ class SMatrix4 {
 
 	inline function toMatrix() {
 		var m = new h3d.Matrix();
-		m._11 = _11; m._12 = _12; m._13 = _13; m._14 = _14; 
-		m._21 = _21; m._22 = _22; m._23 = _23; m._24 = _24; 
-		m._31 = _31; m._32 = _32; m._33 = _33; m._34 = _34; 
-		m._41 = _41; m._42 = _42; m._43 = _43; m._44 = _44; 
+		m._11 = _11; m._12 = _12; m._13 = _13; m._14 = _14;
+		m._21 = _21; m._22 = _22; m._23 = _23; m._24 = _24;
+		m._31 = _31; m._32 = _32; m._33 = _33; m._34 = _34;
+		m._41 = _41; m._42 = _42; m._43 = _43; m._44 = _44;
 		return m;
 	}
 

+ 749 - 0
hrt/prefab/l3d/Trails.hx

@@ -0,0 +1,749 @@
+package hrt.prefab.l3d;
+
+@:struct
+class TrailPoint {
+    public var x : Float = 0;
+    public var y : Float = 0;
+    public var z : Float = 0;
+    public var nx : Float = 0;
+    public var ny : Float = 0;
+    public var nz : Float = 0;
+    public var ux : Float = 0;
+    public var uy : Float = 0;
+    public var uz : Float = 0;
+    public var w : Float = 0;
+    public var len : Float = 0;
+    public var lifetime : Float = 0;
+    public var next : TrailPoint = null;
+
+
+    public function new(){};
+}
+
+@:struct
+class TrailHead {
+    public var firstPoint : TrailPoint = null;
+    public var totalLength : Float = 0;
+    public var numPoints : Int = 0;
+    public var generation : Int = 0;
+    public var nextTrail : TrailHead = null;
+
+    public function new(){};
+}
+
+enum TrailOrientation {
+    ECamera;
+    EUp(x : Float, y : Float, z : Float);
+    EBasis(m : h3d.Matrix);
+}
+
+enum UVMode {
+    @display("Stretch", "Stretch the UV between the head (u=0) and the tail (u=1) of the trails")
+    EStretch;
+    @display("Tile Fixed", "Tile the texture through the trail, but the texture stay fixed in world space")
+    ETileFixed;
+    @display("Tile Follow", "Tile the texture throught the tail, and the texture follow the head of the tail (u=0 at the head)")
+    ETileFollow;
+}
+
+enum UVRepeat {
+    @display("Mod", "U value goes from 0 to 1, then back to 0 again. For textures that tile horizontally")
+    EMod;
+    @display("Mirror", "U value goes from 0 to 1 then from 1 to 0, repeating. For textures that don't tile horizontally")
+    EMirror;
+    @display("Clamp", "U value goes from 0 to 1, then stay at 1")
+    EClamp;
+    @display("None", "No repeat for the UV values")
+    ENone;
+}
+
+
+typedef PointsArray = #if (hl_ver >= version("1.13.0")) hl.CArray<TrailPoint> #else Array<TrailPoint> #end;
+typedef TrailsArray = #if (hl_ver >= version("1.13.0")) hl.CArray<TrailHead> #else Array<TrailHead> #end;
+
+
+class TrailObj extends h3d.scene.Mesh {
+
+    var points : PointsArray;
+    var trails : TrailsArray;
+
+    var trailsPool : TrailHead = null;
+    var firstFreePointID = 0;
+
+    var nextTrailID = 0;
+
+    var dprim : h3d.prim.RawPrimitive;
+    var vbuf : hxd.FloatBuffer;
+    var ibuf : hxd.IndexBuffer;
+    var numVertsIndices : Int = 0;
+    var numVerts : Int = 0;
+    var bounds : h3d.col.Bounds;
+    var prefab : Trails;
+
+    // Sets whenever we check the position of this object to automaticaly add points to the 0th trail.
+    // If set to false, trail can be created by manually calling addPoint()
+    public var autoTrackPosition : Bool = true;
+
+    var currentAllocatedVertexCount = 0;
+    var currentAllocatedIndexCount = 0;
+
+
+    public var numTrails(default, set) : Int = -1;
+
+    public function set_numTrails(new_value : Int) : Int {
+        if (numTrails != new_value) {
+            numTrails = new_value;
+            allocBuffers();
+        }
+        return numTrails;
+    }
+
+    // How many frame we wait before adding a new point
+    static final maxFramerate : Float = 30.0;
+
+    var shader : hrt.shader.BaseTrails;
+
+
+    public function calcMaxTrailPoints() : Int {
+        return Std.int(std.Math.ceil(prefab.lifetime * maxFramerate));
+    }
+
+    function calcMaxVertexes() : Int {
+        var pointsPerTrail = calcMaxTrailPoints();
+        var vertsPerTrail = std.Math.ceil(pointsPerTrail * 2);
+        var num = vertsPerTrail * numTrails;
+        if (num > 65534) {
+            num = 65534;
+        }
+        return num;
+    }
+
+    function calcMaxIndexes() : Int {
+        var pointsPerTrail = calcMaxTrailPoints();
+        var indicesPerTrail = (pointsPerTrail-1) * 6;
+        return indicesPerTrail * numTrails;
+    }
+
+    function allocBuffers() {
+        var alloc = hxd.impl.Allocator.get();
+        if (vbuf != null)
+            alloc.disposeFloats(vbuf);
+        currentAllocatedVertexCount = calcMaxVertexes();
+        vbuf = new hxd.FloatBuffer(currentAllocatedVertexCount * 8);
+        if (ibuf != null)
+            alloc.disposeIndexes(ibuf);
+        currentAllocatedIndexCount = calcMaxIndexes();
+        ibuf = new hxd.IndexBuffer(currentAllocatedIndexCount);
+
+
+        pool = null;
+        firstFreePointID = 0;
+
+        maxNumPoints = calcMaxTrailPoints() * numTrails;
+        if (maxNumPoints <= 0) maxNumPoints = 1;
+		points = #if (hl_ver >= version("1.13.0")) hl.CArray.alloc(TrailPoint, maxNumPoints) #else [for(i in 0...maxNumPoints) new TrailPoint()] #end;
+
+        trails = #if (hl_ver >= version("1.13.0")) hl.CArray.alloc(TrailHead, numTrails) #else [for(i in 0...numTrails) new TrailHead()] #end;
+
+        for (i in 0...numTrails-1) {
+            trails[i].nextTrail = trails[i+1];
+        }
+        trailsPool = trails[0];
+
+        if (dprim != null)
+            dprim.alloc(null);
+
+        reset();
+
+    }
+
+    var maxNumPoints : Int = 0;
+
+    var pool : TrailPoint = null;
+
+    #if editor
+    var debugPointViz : h3d.scene.Graphics = null;
+    #end
+
+	public var materialData = {};
+
+    public  function updateParams() {
+        updateShader();
+    }
+
+    function allocPoint() : TrailPoint {
+        var r = null;
+        if (pool != null)
+        {
+            r = pool;
+            pool = r.next;
+        } else {
+            if (firstFreePointID >= maxNumPoints)
+                return null;
+            r = points[firstFreePointID++];
+        }
+
+        r.next = null;
+        r.len = 0.0;
+        return r;
+    }
+
+    public function allocTrail() : TrailHead {
+        if (trailsPool == null) throw "assert";
+        var r = trailsPool;
+        trailsPool = trailsPool.nextTrail;
+        return r;
+    }
+
+    function disposeTrail(t : TrailHead) {
+        t.firstPoint = null;
+        t.totalLength = 0;
+        t.numPoints = 0;
+        t.generation++;
+        t.nextTrail = trailsPool;
+        trailsPool = t;
+    }
+
+    function disposePoint(p : TrailPoint) {
+        if (pool != null)
+            p.next = pool;
+        else
+            p.next = null;
+        pool = p;
+    }
+
+    override function onRemove() {
+        dprim.dispose();
+    }
+
+    public function reset() {
+        for (t in trails) {
+            var p = t.firstPoint;
+            while (p != null) {
+                var n = p.next;
+                disposePoint(p);
+                p = n;
+            }
+            disposeTrail(t);
+        }
+    }
+
+    public function updateShader() {
+        shader.uvRepeat = prefab.uvRepeat.getIndex();
+    }
+
+    static var showDebugLines = false;
+
+    var statusText : h2d.Text;
+
+    public function addPoint(t : TrailHead, x : Float, y : Float, z : Float, orient : TrailOrientation, w : Float) {
+
+        var ux : Float = 0.0;
+        var uy : Float = 0.0;
+        var uz : Float = 0.0;
+        var nx : Float = 0.0;
+        var ny : Float = 0.0;
+        var nz : Float = 0.0;
+
+        switch (orient) {
+            case ECamera: {
+                var cam = getScene().camera.pos;
+                var target = getScene().camera.target;
+
+                var vcamx = cam.x - target.x;
+                var vcamy = cam.y - target.y;
+                var vcamz = cam.z - target.z;
+
+                var len = 1.0/hxd.Math.distance(vcamx, vcamy, vcamz);
+
+                ux = vcamx * len;
+                uy = vcamy * len;
+                uz = vcamz * len;
+            }
+            case EUp(x,y,z): {
+                ux = x;
+                uy = y;
+                uz = z;
+            }
+            case EBasis(m): {
+                var up = m.up();
+                ux = up.x;
+                uy = up.y;
+                uz = up.z;
+
+                var right = m.right();
+                nx = right.x;
+                ny = right.y;
+                nz = right.z;
+            }
+        }
+
+        var head = t;
+
+        var prev = head.firstPoint;
+        var new_pt : TrailPoint = null;
+
+        var added_point = true;
+
+        // If we haven't moved far enought from the previous point, reuse the head instead of creating a new point
+        if (prev != null && prev.next != null) {
+            var len = (x - prev.next.x) * (x - prev.next.x) +
+            (y - prev.next.y) * (y - prev.next.y) +
+            (z - prev.next.z) * (z - prev.next.z);
+            len = Math.sqrt(len);
+
+            if (prev.lifetime < 1.0/maxFramerate-0.001 ||
+                head.numPoints >= calcMaxTrailPoints() // Don't allocate points if we have the max numPoints
+                ) {
+                new_pt = prev;
+                prev = prev.next;
+                added_point = false;
+            } else {
+                if (prefab.uvMode == ETileFixed) {
+                    head.totalLength = prev != null ? prev.len + len : len;
+                } else {
+                    head.totalLength += prev.len;
+                }
+            }
+        }
+
+        if (new_pt == null)
+        {
+            new_pt = allocPoint();
+            if (new_pt == null)
+                return;
+            head.numPoints ++;
+            new_pt.lifetime = 0.0;
+        }
+
+        new_pt.w = w;
+
+        new_pt.x = x;
+        new_pt.y = y;
+        new_pt.z = z;
+
+        var len = 0.0;
+
+        if (prev != null) {
+            var lenSq = (x - prev.x) * (x - prev.x) +
+            (y - prev.y) * (y - prev.y) +
+            (z - prev.z) * (z - prev.z);
+            len = Math.sqrt(lenSq);
+
+            if (prefab.uvMode == ETileFixed) {
+                new_pt.len = head.totalLength + len;
+            }
+            else {
+                new_pt.len = len;
+            }
+
+            var len = 1.0/len;
+
+            if (nx == 0 && ny == 0 && nz == 0) {
+                nx = (prev.x - x) * len;
+                ny = (prev.y - y) * len;
+                nz = (prev.z - z) * len;
+
+                new_pt.nx = ny * uz - nz * uy;
+                new_pt.ny = nz * ux - nx * uz;
+                new_pt.nz = nx * uy - ny * ux;
+
+                var nlen = 1.0/hxd.Math.distance(new_pt.nx, new_pt.ny, new_pt.nz);
+                new_pt.nx *= nlen;
+                new_pt.ny *= nlen;
+                new_pt.nz *= nlen;
+
+                new_pt.ux = new_pt.ny * nz - new_pt.nz * ny;
+                new_pt.uy = new_pt.nz * nx - new_pt.nx * nz;
+                new_pt.uz = new_pt.nx * ny - new_pt.ny * nx;
+
+                if (prev.nx == 0 && prev.ny == 0 && prev.nz == 0) {
+                    prev.nx = new_pt.nx;
+                    prev.ny = new_pt.ny;
+                    prev.nz = new_pt.nz;
+
+                    prev.ux = new_pt.ux;
+                    prev.uy = new_pt.uy;
+                    prev.uz = new_pt.uz;
+                }
+            }
+            else {
+                new_pt.nx = nx;
+                new_pt.ny = ny;
+                new_pt.nz = nz;
+
+                new_pt.ux = ux;
+                new_pt.uy = uy;
+                new_pt.uz = uz;
+            }
+        } else {
+            new_pt.nx = nx;
+            new_pt.ny = ny;
+            new_pt.nz = nz;
+
+            new_pt.ux = ux;
+            new_pt.uy = uy;
+            new_pt.uz = uz;
+
+            new_pt.len = 0;
+        }
+
+        if (prev != null)
+            new_pt.next = prev;
+        head.firstPoint = new_pt;
+    }
+
+    public function getMaterialProps() {
+		var name = h3d.mat.MaterialSetup.current.name;
+		var p = Reflect.field(materialData, name);
+		if( p == null ) {
+			p = h3d.mat.MaterialSetup.current.getDefaults("trail3D");
+			Reflect.setField(materialData, name, p);
+		}
+		return p;
+	}
+
+    function onDprimContextLost() {
+        return {
+            vbuf : vbuf,
+            ibuf : ibuf,
+            stride : 8,
+            quads : false,
+            bounds : bounds,
+        };
+    }
+
+    public function new(parentPrefab: Trails, ?parent : h3d.scene.Object, ?numTrails : Int) {
+        bounds = new h3d.col.Bounds();
+        prefab = parentPrefab;
+        bounds.addPos(0,0,0);
+
+        this.numTrails = numTrails != null ? numTrails : 1;
+
+        dprim = new h3d.prim.RawPrimitive(onDprimContextLost(), true);
+        dprim.onContextLost = onDprimContextLost;
+
+        super(dprim,parent);
+
+        #if editor
+        debugPointViz = new h3d.scene.Graphics(parent);
+        #end
+
+        material.props = getMaterialProps();
+		material.mainPass.dynamicParameters = true;
+
+        shader = new hrt.shader.BaseTrails();
+        material.mainPass.addShader(shader);
+
+        shader.setPriority(-999);
+
+        updateParams();
+    }
+
+    #if editor
+    static var pointA = new h3d.col.Point();
+    static var pointB = new h3d.col.Point();
+    #end
+
+    var prev_x : Float = 0;
+    var prev_y : Float = 0;
+    var prev_z : Float = 0;
+
+    var lastUpdateDuration = 0.0;
+
+    override function sync(ctx) {
+        var t = haxe.Timer.stamp();
+        calcAbsPos();
+
+
+		super.sync(ctx);
+
+        if (autoTrackPosition) {
+            var x = absPos.tx;
+            var y = absPos.ty;
+            var z = absPos.tz;
+
+            var spdSqr =
+                (x - prev_x) * (x - prev_x) +
+                (y - prev_y) * (y - prev_y) +
+                (z - prev_z) * (z - prev_z);
+
+            var shouldAddPoint : Bool = false;
+
+            var minSpd = 0.0;
+            if (spdSqr > minSpd * minSpd) {
+                shouldAddPoint = true;
+            }
+
+            if (shouldAddPoint) {
+                addPoint(trails[0], x,y,z, ECamera, 1);
+                //addPoint(0, x,y,z, EUp(0,0,1), 1);
+                //addPoint(0, x,y,z, EBasis(absPos), 1);
+            }
+        }
+
+        prev_x = x;
+        prev_y = y;
+        prev_z = z;
+
+
+        #if editor
+        debugPointViz.clear();
+        #end
+
+        var buffer = vbuf;
+        var indices = ibuf;
+
+        var count = 0;
+        numVertsIndices = 0;
+        var currentIndex = 0;
+        var num_segments = 0;
+
+        // render
+        for (trail in trails) {
+            var prev = null;
+            var cur = trail.firstPoint;
+            var len = 0.0;
+
+            var totalLen = trail.totalLength + (cur != null ? cur.len : 0.0);
+            while (cur != null) {
+                num_segments += 1;
+                cur.lifetime += ctx.elapsedTime;
+                var t = cur.lifetime / prefab.lifetime;
+                cur.w = hxd.Math.lerp(prefab.startWidth, prefab.endWidth, t);
+                if (cur.lifetime > prefab.lifetime) {
+                    if (prefab.uvMode != ETileFixed)
+                        trail.totalLength -= cur.len;
+                    if (prev != null) {
+                        prev.next = null;
+                    } else {
+                        disposeTrail(trail);
+                    }
+                    var dp = cur;
+                    while(dp != null) {
+                        var next = dp.next;
+                        disposePoint(dp);
+                        dp = next;
+                        trail.numPoints--;
+                    }
+                    break;
+                }
+
+                #if editor
+                if (cur.next != null) {
+                    if (showDebugLines) {
+                        pointA.set(cur.next.x, cur.next.y, cur.next.z);
+                        pointB.set(cur.x, cur.y, cur.z);
+                        debugPointViz.drawLine(pointA, pointB);
+
+                        pointA.set((cur.x+cur.next.x) / 2.0,
+                                    (cur.y+cur.next.y) / 2.0,
+                                    (cur.z+cur.next.z) / 2.0);
+
+                        pointB.set(pointA.x + cur.nx * 2.0,
+                                    pointA.y + cur.ny * 2.0,
+                                    pointA.z + cur.nz * 2.0);
+
+                        debugPointViz.setColor(0xFF0000, 1.0);
+                        debugPointViz.drawLine(pointA, pointB);
+
+                        pointB.set(pointA.x + cur.ux * 2.0,
+                            pointA.y + cur.uy * 2.0,
+                            pointA.z + cur.uz * 2.0);
+                        debugPointViz.setColor(0x0000FF, 1.0);
+                        debugPointViz.drawLine(pointA, pointB);
+
+
+                        debugPointViz.setColor(0xFFFFFF, 1.0);
+                    }
+                }
+                #end
+
+
+                var nx = 0.0;
+                var ny = 0.0;
+                var nz = 0.0;
+
+                if (prev != null) {
+                    nx = (cur.nx + prev.nx) * 0.5;
+                    ny = (cur.ny + prev.ny) * 0.5;
+                    nz = (cur.nz + prev.nz) * 0.5;
+                } else {
+                    nx = cur.nx;
+                    ny = cur.ny;
+                    nz = cur.nz;
+                }
+
+                #if editor
+                if (showDebugLines) {
+                    pointA.set(cur.x, cur.y, cur.z);
+                    pointB.set( cur.x+nx,
+                        cur.y+ny,
+                        cur.z+nz);
+
+                    debugPointViz.drawLine(pointA, pointB);
+
+                    pointA.set(cur.x, cur.y, cur.z);
+                    pointB.set( cur.x-nx,
+                            cur.y-ny,
+                            cur.z-nz);
+
+                    debugPointViz.drawLine(pointA, pointB);
+                }
+                #end
+
+
+                if (count+16 > currentAllocatedVertexCount * 8) {
+                    break;
+                }
+
+                var u = if (prefab.uvMode == ETileFixed) cur.len else len;
+                if (prefab.uvMode == EStretch) u = (totalLen - len) / totalLen;
+                buffer[count++] = cur.x+nx * cur.w;
+                buffer[count++] = cur.y+ny * cur.w;
+                buffer[count++] = cur.z+nz * cur.w;
+                buffer[count++] = cur.ux;
+                buffer[count++] = cur.uy;
+                buffer[count++] = cur.uz;
+                buffer[count++] = u;
+                buffer[count++] = 0;
+
+
+                buffer[count++] = cur.x+ (nx * -cur.w);
+                buffer[count++] = cur.y+ (ny * -cur.w);
+                buffer[count++] = cur.z+ (nz * -cur.w);
+                buffer[count++] = cur.ux;
+                buffer[count++] = cur.uy;
+                buffer[count++] = cur.uz;
+                buffer[count++] = u;
+                buffer[count++] = 1;
+
+                if (prev != null) {
+
+                    if (numVertsIndices + 6 > currentAllocatedIndexCount) break;
+
+                    indices[numVertsIndices] = currentIndex+2;
+                    indices[numVertsIndices+1] = currentIndex+1;
+                    indices[numVertsIndices+2] = currentIndex;
+
+                    numVertsIndices += 3;
+
+                    indices[numVertsIndices] = currentIndex+2;
+                    indices[numVertsIndices+1] = currentIndex+3;
+                    indices[numVertsIndices+2] = currentIndex+1;
+
+                    numVertsIndices += 3;
+                    currentIndex += 2;
+                }
+
+                len += cur.len;
+
+                prev = cur;
+                cur = cur.next;
+
+            }
+
+            if (prev != null ){
+                currentIndex +=2;
+            }
+        }
+
+        numVerts = Std.int(count/8);
+
+        shader.uvStretch = prefab.uvStretch;
+
+        dprim.buffer.uploadVector(vbuf, 0, numVerts, 0);
+        dprim.indexes.upload(ibuf, 0, numVertsIndices);
+
+
+        lastUpdateDuration = haxe.Timer.stamp() - t;
+    }
+
+    override function draw(ctx:h3d.scene.RenderContext) {
+        absPos.identity();
+        posChanged = true;
+        ctx.uploadParams();
+
+        var triToDraw : Int = Std.int(numVertsIndices/3);
+        if (triToDraw < 0) triToDraw = 0;
+        ctx.engine.renderIndexed(dprim.buffer, dprim.indexes, 0, triToDraw);
+
+	}
+}
+
+
+class Trails extends Object3D {
+
+    @:s public var startWidth : Float = 1.0;
+    @:s public var endWidth : Float = 0.0;
+    @:s public var lifetime : Float = 1.0;
+
+    @:s public var uvMode : UVMode = EStretch;
+    @:s public var uvStretch: Float = 1.0;
+    @:s public var uvRepeat : UVRepeat = EMod;
+
+
+    function new(?parent) {
+		super(parent);
+	}
+
+	public function create( ?parent : h3d.scene.Object, ?numTrails : Int ) {
+		var tr = new TrailObj(this, parent, numTrails);
+		applyTransform(tr);
+		tr.name = name;
+        tr.updateShader();
+		return tr;
+	}
+
+	override function makeInstance(ctx:Context):Context {
+		ctx = ctx.clone(this);
+		var tr = create(ctx.local3d, ctx.custom != null ? ctx.custom.numTrails : 1);
+		ctx.local3d = tr;
+		return ctx;
+	}
+
+    #if editor
+
+	override function getHideProps():HideProps {
+		return { icon : "toggle-on", name : "Trails" };
+	}
+
+	override public function edit(ctx:EditContext) {
+		super.edit(ctx);
+
+		var trailContext = ctx.getContext(this);
+		var trail = trailContext == null ? create(null) : Std.downcast(trailContext.local3d, TrailObj);
+		var props = ctx.properties.add(new hide.Element('
+		<div class="group" name="Trail Properties">
+			<dl>
+				<dt>Lifetime</dt><dd><input type="range" field="lifetime" min="0" max="1"/></dd>
+				<dt>Width Start</dt><dd><input type="range" field="startWidth" min="0" max="10"/></dd>
+				<dt>Width End</dt><dd><input type="range" field="endWidth" min="0" max="10"/></dd>
+			</dl>
+		</div>
+
+        <div class="group" name="UV">
+        <dl>
+            <dt>UV Mode</dt><dd><select field="uvMode"></select></dd>
+            <dt>UV Repeat</dt><dd><select field="uvRepeat"></select></dd>
+            <dt>UV Scale</dt><dd><input type="range" field="uvStretch" min="0" max="5"/></dd>
+        </dl>
+    </div>
+		'),this, function(name:String) {
+            if (name == "uvRepeat") {
+                trail.updateShader();
+            }
+            if (name == "uvMode") {
+                trail.reset();
+            }
+            if (name == "maxTriangles") {
+                trail.updateParams();
+            }
+		});
+		//ctx.properties.addMaterial( trail.material, props.find("[name=Material] > .content"), function(_) data = trail.save());
+	}
+
+	#end
+
+	static var _ = Library.register("trails", Trails);
+}

+ 39 - 0
hrt/shader/BaseTrails.hx

@@ -0,0 +1,39 @@
+package hrt.shader;
+
+class BaseTrails extends hxsl.Shader {
+
+	static var SRC = {
+
+        @param var uvStretch : Float;
+        @const @param var uvRepeat : Int = 0;
+
+        @input var input2 : {
+			var uv : Vec2;
+        };
+
+		var calculatedUV : Vec2;
+
+        function __init__() {
+            calculatedUV = input2.uv;
+        }
+
+        function fragment() {
+            calculatedUV = calculatedUV * vec2(uvStretch, 1.0);
+
+            switch(uvRepeat) {
+                case 0: // Modulo
+                    calculatedUV.x = calculatedUV.x % 1.0;
+                case 1: // Mirror
+                    calculatedUV.x = calculatedUV.x % 2.0;
+                    if (calculatedUV.x > 1.0) {
+                        calculatedUV.x = 2.0-calculatedUV.x;
+                    }
+                case 3: // Clamp
+                    calculatedUV.x = saturate(calculatedUV.x);
+                case 4: {};// None
+                default: {};
+            }
+        }
+	};
+
+}

+ 30 - 0
hrt/shader/UVDebug.hx

@@ -0,0 +1,30 @@
+package hrt.shader;
+
+class UVDebug extends hxsl.Shader {
+
+	static var SRC = {
+
+		var pixelColor : Vec4;
+
+        /*@input var input2 : {
+			var uv : Vec2;
+        };*/
+
+		var calculatedUV : Vec2;
+
+        /*function __init__() {
+            calculatedUV = input2.uv;
+        }*/
+
+        function fragment() {
+            pixelColor.rgb = vec3(calculatedUV.x % 1.0, calculatedUV.y, 0.0);
+            if (abs(calculatedUV.x % 1.0) < 0.05)
+                pixelColor.rgb = vec3(1.0,0.0,1.0);
+            if (abs(calculatedUV.x % 1.0) > 0.95)
+                pixelColor.rgb = vec3(0.0,1.0,1.0);
+
+            if (calculatedUV.x < 0)
+                pixelColor.b = 1.0;
+        }
+	};
+}