2
0
lviguier 1 жил өмнө
parent
commit
603de8e6c5

+ 152 - 0
h3d/prim/HMDModel.hx

@@ -10,11 +10,15 @@ class HMDModel extends MeshPrimitive {
 	var curMaterial : Int;
 	var collider : h3d.col.Collider;
 	var normalsRecomputed : String;
+	var blendshape : Blendshape;
 
 	public function new(data, dataPos, lib) {
 		this.data = data;
 		this.dataPosition = dataPos;
 		this.lib = lib;
+
+		if (lib.header.shapes != null && lib.header.shapes.length > 0)
+			this.blendshape = new Blendshape(this);
 	}
 
 	override function hasInput( name : String ) {
@@ -224,5 +228,153 @@ class HMDModel extends MeshPrimitive {
 		initCollider(poly);
 		return collider;
 	}
+}
+
+@:access(h3d.prim.HMDModel)
+class Blendshape {
+
+	var hmdModel : HMDModel;
+	var weights : Array<Float> = [];
+	var index : Int = 0;
+	var amount : Float = 0;
+	var inputMapping : Array<Map<String, Int>> = [];
+	var shapesBytes = [];
+
+	public function new(hmdModel) {
+		this.hmdModel = hmdModel;
+
+		if ( hmdModel.data.vertexFormat.hasLowPrecision )
+			throw "Blend shape doesn't support low precision";
+
+		// Cache data for blendshapes
+		var is32 = hmdModel.data.vertexCount > 0x10000;
+		var vertexFormat = hmdModel.data.vertexFormat;
+		var size = hmdModel.data.vertexCount * vertexFormat.strideBytes;
+		var shapes = hmdModel.lib.header.shapes;
+
+		for ( s in 0...shapes.length ) {
+			var s = shapes[s];
+			var size = s.vertexCount * s.vertexFormat.strideBytes;
+
+			var vertexBytes = haxe.io.Bytes.alloc(size);
+			hmdModel.lib.resource.entry.readBytes(vertexBytes, 0, hmdModel.dataPosition + s.vertexPosition, size);
+			size = s.vertexCount << (is32 ? 2 : 1);
+
+			var indexBytes = haxe.io.Bytes.alloc(size);
+			hmdModel.lib.resource.entry.readBytes(indexBytes, 0, hmdModel.dataPosition + s.indexPosition, size);
+			size = hmdModel.data.vertexCount << 2;
+
+			var remapBytes = haxe.io.Bytes.alloc(size);
+			hmdModel.lib.resource.entry.readBytes(remapBytes, 0, hmdModel.dataPosition + s.remapPosition, size);
+			shapesBytes.push({ vertexBytes : vertexBytes, indexBytes : indexBytes, remapBytes : remapBytes});
+
+			inputMapping.push(new Map());
+		}
+
+		// We want to remap inputs since inputs can be not exactly in the same
+		for ( input in vertexFormat.getInputs() ) {
+			for ( s in 0...shapes.length ) {
+				var offset = 0;
+				for ( i in shapes[s].vertexFormat.getInputs() ) {
+					if ( i.name == input.name )
+						inputMapping[s].set(i.name, offset);
+					offset += i.type.getSize();
+				}
+			}
+		}
+	}
+
+	public function setBlendshapeAmount(blendshapeIdx: Int, amount: Float) {
+		this.index = blendshapeIdx;
+		this.amount = amount;
+
+		uploadBlendshapeBytes();
+	}
+
+	public function getBlendshapeCount() {
+		if (hmdModel.lib.header.shapes == null)
+			return 0;
+
+		return hmdModel.lib.header.shapes.length;
+	}
 
+	public function uploadBlendshapeBytes() {
+		var is32 = hmdModel.data.vertexCount > 0x10000;
+		var vertexFormat = hmdModel.data.vertexFormat;
+		hmdModel.buffer = new h3d.Buffer(hmdModel.data.vertexCount, vertexFormat);
+
+		var size = hmdModel.data.vertexCount * vertexFormat.strideBytes;
+		var originalBytes = haxe.io.Bytes.alloc(size);
+		hmdModel.lib.resource.entry.readBytes(originalBytes, 0, hmdModel.dataPosition + hmdModel.data.vertexPosition, size);
+
+		var shapes = hmdModel.lib.header.shapes;
+		weights = [];
+
+		for ( s in 0...shapes.length )
+			weights[s] = s == index ? amount : 0.0;
+
+		var flagOffset = 31;
+		var bytes = haxe.io.Bytes.alloc(originalBytes.length);
+		bytes.blit(0, originalBytes, 0, originalBytes.length);
+
+		// Apply blendshapes offsets to original vertex
+		for (sIdx in 0...shapes.length) {
+			if (sIdx != index)
+				continue;
+
+			var sp = shapesBytes[sIdx];
+			var offsetIdx = 0;
+			var idx = 0;
+
+			while (offsetIdx < shapes[sIdx].indexCount) {
+				var affectedVId = sp.remapBytes.getInt32(idx << 2);
+
+				var reachEnd = false;
+				while (!reachEnd) {
+					reachEnd = affectedVId >> flagOffset != 0;
+					if (reachEnd)
+						affectedVId = affectedVId ^ (1 << flagOffset);
+
+					var inputIdx = 0;
+					var offsetInput = 0;
+					for (input in shapes[sIdx].vertexFormat.getInputs()) {
+						for (sizeIdx in 0...input.type.getSize()) {
+							// if (input.name == "normal")
+							// 	continue;
+
+							var original = originalBytes.getFloat(affectedVId * vertexFormat.stride + inputMapping[sIdx][input.name] + sizeIdx << 2);
+							var offset = sp.vertexBytes.getFloat(offsetIdx * shapes[sIdx].vertexFormat.stride + offsetInput + sizeIdx << 2);
+
+							var res = hxd.Math.lerp(original, original + offset, weights[sIdx]);
+							bytes.setFloat(affectedVId * vertexFormat.stride + inputMapping[sIdx][input.name] + sizeIdx << 2, res);
+						}
+
+						offsetInput += input.type.getSize();
+						inputIdx++;
+					}
+
+					idx++;
+
+					if (idx < hmdModel.data.vertexCount)
+						affectedVId = sp.remapBytes.getInt32(idx << 2);
+				}
+
+				offsetIdx++;
+			}
+		}
+
+		// Send bytes to buffer for rendering
+		hmdModel.buffer.uploadBytes(bytes, 0, hmdModel.data.vertexCount);
+		hmdModel.indexCount = 0;
+		hmdModel.indexesTriPos = [];
+		for( n in hmdModel.data.indexCounts ) {
+			hmdModel.indexesTriPos.push(Std.int(hmdModel.indexCount/3));
+			hmdModel.indexCount += n;
+		}
+
+		hmdModel.indexes = new h3d.Indexes(hmdModel.indexCount, is32);
+		var size = (is32 ? 4 : 2) * hmdModel.indexCount;
+		var bytes = hmdModel.lib.resource.entry.fetchBytes(hmdModel.dataPosition + hmdModel.data.indexPosition, size);
+		hmdModel.indexes.uploadBytes(bytes, 0, hmdModel.indexCount);
+	}
 }

+ 11 - 1
hxd/fmt/fbx/BaseLibrary.hx

@@ -271,7 +271,7 @@ class BaseLibrary {
 
 		// scale on geometry
 		if( geometryScaleFactor != 1 ) {
-			for( g in this.root.getAll("Objects.Geometry.Vertices") ) {
+			for( g in this.root.getAll("Objects.Geometry.Vertices").concat(this.root.getAll("Objects.Geometry.Shape.Vertices")) ) {
 				var v = toFloats(g);
 				for( i in 0...v.length )
 					v[i] = v[i] / geometryScaleFactor;
@@ -412,6 +412,16 @@ class BaseLibrary {
 			for( v in g.getAll("LayerElementBinormal.Binormals") )
 				convertPoints(v.getFloats());
 		}
+		for ( s in root.getAll("Objects.Geometry.Shape") ) {
+			for ( v in s.getAll("Vertices") )
+				convertPoints(v.getFloats());
+			for ( v in s.getAll("Normals") )
+				convertPoints(v.getFloats());
+			for ( v in s.getAll("Tangents") )
+				convertPoints(v.getFloats());
+			for ( v in s.getAll("Binormals") )
+				convertPoints(v.getFloats());
+		}
 	}
 
 	function init( n : FbxNode ) {

+ 152 - 10
hxd/fmt/fbx/HMDOut.hx

@@ -220,6 +220,7 @@ class HMDOut extends BaseLibrary {
 		var uvs = geom.getUVs();
 		var colors = geom.getColors();
 		var mats = geom.getMaterials();
+		var index = geom.getPolygons();
 
 		// remove empty color data
 		if( colors != null ) {
@@ -284,11 +285,20 @@ class HMDOut extends BaseLibrary {
 				ibufs.push([]);
 		}
 
+		var shapes = geom.getRoot().getAll("Shape");
+		var shapeIndexes = []; // Indexes of vertex used in blendshapes
+		var remappedShapes = [];
+		for ( sIdx => s in shapes ) {
+			shapeIndexes.push(s.get("Indexes").getInts());
+			remappedShapes.push([]);
+			for (i in 0...shapeIndexes[sIdx].length)
+				remappedShapes[remappedShapes.length - 1].push([]);
+		}
+
 		g.bounds = new h3d.col.Bounds();
 		var stride = g.vertexFormat.stride;
 		var tmpBuf = new hxd.impl.TypedArray.Float32Array(stride);
 		var vertexRemap = new Array<Int>();
-		var index = geom.getPolygons();
 		var count = 0, matPos = 0, stri = 0;
 		var lookup = new Map();
 		var tmp = new h3d.col.Point();
@@ -381,17 +391,26 @@ class HMDOut extends BaseLibrary {
 					vids = [];
 					lookup.set(itotal, vids);
 				}
-				for( vid in vids ) {
-					var same = true;
-					var p = vid * stride;
-					for( i in 0...stride )
-						if( vbuf[p++] != tmpBuf[i] ) {
-							same = false;
+				var inBlendShape = false;
+				for ( s in shapeIndexes ) {
+					if ( s.contains(vidx) ) {
+						inBlendShape = true;
+						break;
+					}
+				}
+				if ( !inBlendShape ) { // vertices referenced by blend shapes can't be merged
+					for( vid in vids ) {
+						var same = true;
+						var p = vid * stride;
+						for( i in 0...stride )
+							if( vbuf[p++] != tmpBuf[i] ) {
+								same = false;
+								break;
+							}
+						if( same ) {
+							found = vid;
 							break;
 						}
-					if( same ) {
-						found = vid;
-						break;
 					}
 				}
 				if( found == null ) {
@@ -401,7 +420,16 @@ class HMDOut extends BaseLibrary {
 						vbuf.push(tmpBuf[i]);
 					vids.push(found);
 				}
+
 				vertexRemap.push(found);
+
+				for ( s in 0...shapeIndexes.length ) {
+					for (idx in 0...shapeIndexes[s].length) {
+						if (shapeIndexes[s][idx] == vidx) {
+							remappedShapes[s][idx].push(found);
+						}
+					}
+				}
 			}
 
 			// by-skin-group index
@@ -518,6 +546,117 @@ class HMDOut extends BaseLibrary {
 		if( skin != null && skin.isSplit() )
 			matMap = null;
 
+		for ( i in 0...shapes.length ) {
+			var remapped = remappedShapes[i];
+			var s = shapes[i];
+			var shape = new BlendShape();
+			shape.name = s.name;
+			var indexes = s.get("Indexes").getFloats();//shapeIndexes[i];
+			var verts = s.get("Vertices").getFloats();
+			var normals = s.get("Normals").getFloats();
+			var uvs = s.get("UVs", true)?.getFloats();
+			var colors = s.get("Colors", true)?.getFloats();
+			format = [];
+			addFormat("position", DVec3, ppos);
+			if( normals != null )
+				addFormat("normal", DVec3, pnormal);
+			if( tangents != null )
+				addFormat("tangent", DVec3, pnormal);
+			if( uvs != null )
+				addFormat("uv", DVec2, puv);
+			if( colors != null )
+				addFormat("color", DVec3, pcolor);
+			shape.indexCount = remapped.length;
+			shape.vertexCount = indexes.length;
+			shape.vertexFormat = hxd.BufferFormat.make(format);
+			shape.vertexPosition = dataOut.length;
+
+			vbuf = new hxd.FloatBuffer();
+			for ( i in 0...shape.vertexCount ) {
+				vbuf.push(verts[i * 3]);
+				vbuf.push(verts[i * 3 + 1]);
+				vbuf.push(verts[i * 3 + 2]);
+				if ( normals != null ) {
+					vbuf.push(normals[i * 3]);
+					vbuf.push(normals[i * 3 + 1]);
+					vbuf.push(normals[i * 3 + 2]);
+				}
+				if ( uvs != null ) {
+					vbuf.push(uvs[i * 2]);
+					vbuf.push(uvs[i * 2 + 1]);
+				}
+				if ( colors != null ) {
+					vbuf.push(colors[i * 3]);
+					vbuf.push(colors[i * 3 + 1]);
+					vbuf.push(colors[i * 3 + 1]);
+				}
+			}
+			if( lowPrecConfig == null ) {
+				for( i in 0...vbuf.length )
+					writeFloat(vbuf[i]);
+			} else {
+				for( index in 0...Std.int(vbuf.length / stride) ) {
+					var i = index * stride;
+					writePrec(vbuf[i++], ppos);
+					writePrec(vbuf[i++], ppos);
+					writePrec(vbuf[i++], ppos);
+					flushPrec(ppos,3);
+					if( normals != null ) {
+						writePrec(vbuf[i++], pnormal);
+						writePrec(vbuf[i++], pnormal);
+						writePrec(vbuf[i++], pnormal);
+						flushPrec(pnormal,3);
+					}
+					if( tangents != null ) {
+						writePrec(vbuf[i++], pnormal);
+						writePrec(vbuf[i++], pnormal);
+						writePrec(vbuf[i++], pnormal);
+						flushPrec(pnormal,3);
+					}
+					for( k in 0...uvs.length ) {
+						writePrec(vbuf[i++], puv);
+						writePrec(vbuf[i++], puv);
+						flushPrec(puv,2);
+					}
+					if( colors != null ) {
+						writePrec(vbuf[i++], pcolor);
+						writePrec(vbuf[i++], pcolor);
+						writePrec(vbuf[i++], pcolor);
+						flushPrec(pcolor,3);
+					}
+					if( i != (index + 1) * stride )
+						throw "assert";
+				}
+			}
+
+			shape.indexPosition = dataOut.length;
+			if( is32 ) {
+				for( index in shapeIndexes[i] )
+					dataOut.writeInt32(index);
+			} else {
+				for( index in shapeIndexes[i] )
+					dataOut.writeUInt16(index);
+			}
+
+			shape.remapPosition = dataOut.length;
+			for ( i in 0...remapped.length ) {
+				for (j in 0...remapped[i].length) {
+					var toWrite = remapped[i][j];
+
+					// We don't support models vertex count > 2^32 - 1 because we use
+					// the 32th bit for a flag to indicate that it is the last index
+					// affected by this offset
+					if (toWrite > Math.pow(2, 32) - 1)
+						throw ("Not supported, too much vertex");
+
+					if (j == remapped[i].length -1)
+						toWrite = toWrite | (1 << 31);
+
+					dataOut.writeInt32(toWrite);
+				}
+			}
+			d.shapes.push(shape);
+		}
 		return { g : g, materials : matMap };
 	}
 
@@ -778,6 +917,8 @@ class HMDOut extends BaseLibrary {
 				gdata = { gid : d.geometries.length, materials : geom.materials };
 				d.geometries.push(geom.g);
 				hgeom.set(g.getId(), gdata);
+				for ( s in d.shapes )
+					s.geom = gdata.gid;
 			}
 			model.geometry = gdata.gid;
 
@@ -1029,6 +1170,7 @@ class HMDOut extends BaseLibrary {
 		d.materials = [];
 		d.models = [];
 		d.animations = [];
+		d.shapes = [];
 
 		dataOut = new haxe.io.BytesOutput();
 

+ 15 - 1
hxd/fmt/hmd/Data.hx

@@ -78,6 +78,19 @@ class Geometry {
 	}
 }
 
+class BlendShape {
+	public var name : String;
+	public var geom : Index<Geometry>;
+	public var vertexCount : Int;
+	public var vertexFormat : hxd.BufferFormat;
+	public var vertexPosition : DataPosition;
+	public var indexCount : DataPosition;
+	public var indexPosition : DataPosition;
+	public var remapPosition : DataPosition;
+	public function new() {
+	}
+}
+
 class Material {
 
 	public var name : String;
@@ -183,7 +196,7 @@ class Animation {
 
 class Data {
 
-	public static inline var CURRENT_VERSION = 3;
+	public static inline var CURRENT_VERSION = 4;
 
 	public var version : Int;
 	public var props : Properties;
@@ -191,6 +204,7 @@ class Data {
 	public var materials : Array<Material>;
 	public var models : Array<Model>;
 	public var animations : Array<Animation>;
+	public var shapes : Array<BlendShape>;
 	public var dataPosition : Int;
 	public var data : haxe.io.Bytes;
 

+ 29 - 6
hxd/fmt/hmd/Reader.hx

@@ -142,17 +142,21 @@ class Reader {
 			i = new haxe.io.BytesInput(i.read(d.dataPosition-12));
 		d.props = readProps();
 
-		for( k in 0...i.readInt32() ) {
-			var g = new Geometry();
-			g.props = readProps();
-			g.vertexCount = i.readInt32();
+		inline function makeFormat() {
 			var stride = i.readByte();
-			g.vertexFormat = hxd.BufferFormat.make([for( k in 0...i.readByte() ) {
+			var format = hxd.BufferFormat.make([for( k in 0...i.readByte() ) {
 				var name = readCachedName();
 				var type = i.readByte();
 				new GeometryFormat(name, @:privateAccess GeometryDataFormat.fromInt(type&15), @:privateAccess hxd.BufferFormat.Precision.fromInt(type>>4));
 			}]);
-			if( stride != g.vertexFormat.stride ) throw "assert";
+			if ( stride != format.stride ) throw "assert";
+			return format;
+		}
+		for( k in 0...i.readInt32() ) {
+			var g = new Geometry();
+			g.props = readProps();
+			g.vertexCount = i.readInt32();
+			g.vertexFormat = makeFormat();
 			g.vertexPosition = i.readInt32();
 			var subCount = i.readByte();
 			if( subCount == 0xFF ) subCount = i.readInt32();
@@ -229,6 +233,25 @@ class Reader {
 			d.animations.push(a);
 		}
 
+		if ( d.version >= 4 ) {
+			var shapeLength : Int;
+			shapeLength = i.readInt32();
+			d.shapes = [];
+			for ( k in 0...shapeLength ) {
+				var s = new BlendShape();
+				s.name = readName();
+				s.geom = i.readInt32() - 1;
+				s.vertexCount = i.readInt32();
+				s.vertexFormat = makeFormat();
+				s.vertexPosition = i.readInt32();
+				s.indexCount = i.readInt32();
+				s.indexPosition = i.readInt32();
+				s.remapPosition = i.readInt32();
+				d.shapes.push(s);
+			}
+		}
+
+
 		return d;
 	}
 

+ 21 - 6
hxd/fmt/hmd/Writer.hx

@@ -110,16 +110,19 @@ class Writer {
 
 		writeProps(d.props);
 
+		function writeFormat(format : hxd.BufferFormat) {
+			out.writeByte(format.stride);
+			out.writeByte(@:privateAccess format.inputs.length);
+			for( f in format.getInputs() ) {
+				writeName(f.name);
+				out.writeByte(f.type.toInt() | (f.precision.toInt() << 4));
+			}
+		}
 		out.writeInt32(d.geometries.length);
 		for( g in d.geometries ) {
 			writeProps(g.props);
 			out.writeInt32(g.vertexCount);
-			out.writeByte(g.vertexFormat.stride);
-			out.writeByte(@:privateAccess g.vertexFormat.inputs.length);
-			for( f in g.vertexFormat.getInputs() ) {
-				writeName(f.name);
-				out.writeByte(f.type.toInt() | (f.precision.toInt() << 4));
-			}
+			writeFormat(g.vertexFormat);
 			out.writeInt32(g.vertexPosition);
 			if( g.indexCounts.length >= 0xFF ) {
 				out.writeByte(0xFF);
@@ -196,6 +199,18 @@ class Writer {
 			}
 		}
 
+		out.writeInt32(d.shapes.length);
+		for ( s in d.shapes ) {
+			writeName(s.name);
+			out.writeInt32(s.geom + 1);
+			out.writeInt32(s.vertexCount);
+			writeFormat(s.vertexFormat);
+			out.writeInt32(s.vertexPosition);
+			out.writeInt32(s.indexCount);
+			out.writeInt32(s.indexPosition);
+			out.writeInt32(s.remapPosition);
+		}
+
 		var bytes = header.getBytes();
 		out = old;