소스 검색

added h3d.scene.World

ncannasse 10 년 전
부모
커밋
0ff1d6c4bf

+ 239 - 0
h3d/mat/BigTexture.hx

@@ -0,0 +1,239 @@
+package h3d.mat;
+
+class BigTextureElement {
+	public var t : BigTexture;
+	public var du : Float;
+	public var dv : Float;
+	public var su : Float;
+	public var sv : Float;
+	public var blend : BlendMode;
+	public function new(t, du, dv, su, sv, blend) {
+		this.t = t;
+		this.du = du;
+		this.dv = dv;
+		this.su = su;
+		this.sv = sv;
+		this.blend = blend;
+	}
+}
+
+private class QuadTree {
+	public var x : Int;
+	public var y : Int;
+	public var width : Int;
+	public var height : Int;
+	public var used : Bool;
+	public var texture : hxd.res.Image;
+	public var tr : QuadTree;
+	public var tl : QuadTree;
+	public var br : QuadTree;
+	public var bl : QuadTree;
+	public function new(x, y, w, h) {
+		this.x = x;
+		this.y = y;
+		this.width = w;
+		this.height = h;
+	}
+}
+
+class BigTexture {
+
+	public var id : Int;
+	public var tex : h3d.mat.Texture;
+
+	var loadCount : Int;
+	var size : Int;
+	var space : QuadTree;
+	var allPixels : hxd.Pixels;
+	var modified : Bool = true;
+	var isDone : Bool;
+	var isUploaded : Bool;
+	var pending : Array<{ t : hxd.res.Image, x : Int, y : Int, w : Int, h : Int, skip : Bool }>;
+	var waitTimer : haxe.Timer;
+	var lastEvent : Float;
+
+	public function new(id, size, bgColor = 0xFF8080FF) {
+		this.id = id;
+		this.size = size;
+		space = new QuadTree(0,0,size,size);
+		tex = new h3d.mat.Texture(1, 1);
+		tex.clear(bgColor);
+		pending = [];
+	}
+
+	function initPixels() {
+		if( allPixels == null )
+			allPixels = hxd.Pixels.alloc(size, size, BGRA);
+	}
+
+	function findBest( q : QuadTree, w : Int, h : Int ) {
+		if( q == null || q.width < w || q.height < h ) return null;
+		if( !q.used ) return q;
+		var b = findBest(q.tr, w, h);
+		var b2 = findBest(q.tl, w, h);
+		if( b == null || (b2 != null && b2.width * b2.height < b.width * b.height) ) b = b2;
+		var b2 = findBest(q.bl, w, h);
+		if( b == null || (b2 != null && b2.width * b2.height < b.width * b.height) ) b = b2;
+		var b2 = findBest(q.br, w, h);
+		if( b == null || (b2 != null && b2.width * b2.height < b.width * b.height) ) b = b2;
+		return b;
+	}
+
+	function split( q : QuadTree, sw : Int, sh : Int, rw : Int, rh : Int ) {
+		if( q.width < sw || q.height < sh ) {
+			q.used = true;
+			if( q.width == rw && q.height == rh )
+				return q;
+			q.tl = new QuadTree(q.x, q.y, rw, rh);
+			q.tl.used = true;
+			q.tr = new QuadTree(q.x + rw, q.y, q.width - rw, rh);
+			q.bl = new QuadTree(q.x, q.y + rh, rw, q.height - rh);
+			q.br = new QuadTree(q.x + rw, q.y + rh, q.width - rw, q.height - rh);
+			return q.tl;
+		}
+		q.used = true;
+		var qw = q.width >> 1;
+		var qh = q.height >> 1;
+		q.tl = new QuadTree(q.x, q.y, qw, qh);
+		q.tr = new QuadTree(q.x + qw, q.y, qw, qh);
+		q.bl = new QuadTree(q.x, q.y + qh, qw, qh);
+		q.br = new QuadTree(q.x + qw, q.y + qh, qw, qh);
+		return split(q.tl, sw, sh, rw, rh);
+	}
+
+	function allocPos( t : hxd.res.Image, w : Int, h : Int ) {
+		var q = findBest(space, w, h);
+		if( q == null )
+			return null;
+		var w2 = 1, h2 = 1;
+		while( w > w2 ) w2 <<= 1;
+		while( h > h2 ) h2 <<= 1;
+		return split(q, w2<<1, h2<<1, w, h);
+	}
+
+	public function set( t : hxd.res.Image, et : BigTextureElement ) {
+		var tsize = t.getSize();
+		upload(t, Math.round(et.du * size), Math.round(et.dv * size), tsize.width, tsize.height);
+		// todo : update q.texture & setup watch
+	}
+
+	function rebuild() {
+		function rebuildRec( q : QuadTree ) {
+			if( q == null ) return;
+			if( q.texture != null ) {
+				var tsize = q.texture.getSize();
+				upload(q.texture, q.x, q.y, tsize.width, tsize.height);
+			}
+			rebuildRec(q.tl);
+			rebuildRec(q.tr);
+			rebuildRec(q.bl);
+			rebuildRec(q.br);
+		}
+		rebuildRec(space);
+		flush();
+	}
+
+	public function add( t : hxd.res.Image,  blend ) {
+		var tsize = t.getSize();
+		var q = allocPos(t,tsize.width,tsize.height);
+		if( q == null )
+			return null;
+		var x = q.x, y = q.y;
+		upload(t, x, y, tsize.width, tsize.height);
+		if( isUploaded ) {
+			isUploaded = false;
+			rebuild();
+		}
+		q.texture = t;
+		t.watch(rebuild);
+		return new BigTextureElement(this,x / size, y / size, tsize.width / size, tsize.height / size, blend);
+	}
+
+	function upload( t : hxd.res.Image, x : Int, y : Int, width : Int, height : Int ) {
+		switch( t.getFormat() ) {
+		case Png, Gif:
+			initPixels();
+			var pixels = t.getPixels();
+			pixels.convert(allPixels.format);
+			for( dy in 0...height )
+				allPixels.bytes.blit((x + (y + dy) * size) * 4, pixels.bytes, dy * width * 4, width * 4);
+			pixels.dispose();
+			modified = true;
+		case Jpg:
+			loadCount++;
+			var o = { t : t, x : x, y : y, w : width, h : height, skip : false };
+			pending.push(o);
+			t.entry.loadBitmap(function(bmp) {
+				if( o.skip ) return;
+				lastEvent = haxe.Timer.stamp();
+				pending.remove(o);
+				initPixels();
+				#if heaps
+				var bmp = bmp.toBitmap();
+				var pixels = bmp.getPixels();
+				bmp.dispose();
+				#else
+				var pixels = bmp.getPixels();
+				#end
+				pixels.convert(allPixels.format);
+				for( dy in 0...height )
+					allPixels.bytes.blit((x + (y + dy) * size) * 4, pixels.bytes, dy * width * 4, width * 4);
+				modified = true;
+				pixels.dispose();
+				loadCount--;
+				if( isDone )
+					done();
+				else
+					flush();
+			});
+		}
+	}
+
+	function retry() {
+		if( isUploaded ) {
+			waitTimer.stop();
+			waitTimer = null;
+			return;
+		}
+		if( haxe.Timer.stamp() - lastEvent < 4 )
+			return;
+		lastEvent = haxe.Timer.stamp();
+		var old = pending;
+		loadCount -= pending.length;
+		pending = [];
+		for( o in old ) {
+			o.skip = true;
+			upload(o.t, o.x, o.y, o.w, o.h);
+		}
+	}
+
+	public function flush() {
+		if( !modified || allPixels == null || loadCount > 0 )
+			return;
+		if( tex.width != size ) tex.resize(size, size);
+		tex.uploadPixels(allPixels);
+		modified = false;
+		isUploaded = true;
+	}
+
+	public function done() {
+		isDone = true;
+		if( loadCount > 0 ) {
+			#if flash
+			// flash seems to sometime fail to load texture
+			if( waitTimer == null ) {
+				lastEvent = haxe.Timer.stamp();
+				waitTimer = new haxe.Timer(1000);
+				waitTimer.run = retry;
+			}
+			#end
+			return;
+		}
+		flush();
+		if( allPixels != null ) {
+			allPixels.dispose();
+			allPixels = null;
+		}
+	}
+
+}

+ 288 - 0
h3d/scene/World.hx

@@ -0,0 +1,288 @@
+package h3d.scene;
+
+class WorldChunk {
+
+	public var cx : Int;
+	public var cy : Int;
+	public var x : Float;
+	public var y : Float;
+
+	public var root : h3d.scene.Object;
+	public var buffers : Map<Int, h3d.scene.Mesh>;
+	public var bounds : h3d.col.Bounds;
+
+	public function new(cx, cy) {
+		this.cx = cx;
+		this.cy = cy;
+		root = new h3d.scene.Object();
+		buffers = new Map();
+		bounds = new h3d.col.Bounds();
+	}
+
+	public function dispose() {
+		root.remove();
+		root.dispose();
+	}
+
+}
+
+class WorldModelMaterial {
+	public var t : h3d.mat.BigTexture.BigTextureElement;
+	public var m : hxd.fmt.hmd.Data.Material;
+	public var bits : Int;
+	public var startVertex : Int;
+	public var startIndex : Int;
+	public var vertexCount : Int;
+	public var indexCount : Int;
+	public function new(m, t) {
+		this.m = m;
+		this.t = t;
+		this.bits = t.blend.getIndex() | (t.t.id << 3);
+	}
+}
+
+class WorldModel {
+	public var r : hxd.res.FbxModel;
+	public var stride : Int;
+	public var buf : hxd.FloatBuffer;
+	public var idx : hxd.IndexBuffer;
+	public var materials : Array<WorldModelMaterial>;
+	public var bounds : h3d.col.Bounds;
+	public function new(r) {
+		this.r = r;
+		this.buf = new hxd.FloatBuffer();
+		this.idx = new hxd.IndexBuffer();
+		this.materials = [];
+		bounds = new h3d.col.Bounds();
+	}
+}
+
+class World extends Object {
+
+	var chunkBits : Int;
+	var chunkSize : Int;
+	var worldSize : Int;
+	var worldStride : Int;
+	var bigTextureSize = 2048;
+	var bigTextureBG = 0xFF8080FF;
+	var soilColor = 0x408020;
+	var chunks : Array<WorldChunk>;
+	var allChunks : Array<WorldChunk>;
+	var bigTextures : Array<h3d.mat.BigTexture>;
+	var textures : Map<String, h3d.mat.BigTexture.BigTextureElement>;
+
+	public function new( chunkSize : Int, worldSize : Int, ?parent ) {
+		super(parent);
+		chunks = [];
+		bigTextures = [];
+		allChunks = [];
+		textures = new Map();
+		this.chunkBits = 1;
+		while( chunkSize > (1 << chunkBits) )
+			chunkBits++;
+		this.chunkSize = 1 << chunkBits;
+		this.worldSize = worldSize;
+		this.worldStride = Math.ceil(worldSize / chunkSize);
+	}
+
+	function buildFormat() {
+		return {
+			fmt : [
+				new hxd.fmt.hmd.Data.GeometryFormat("position", DVec3),
+				new hxd.fmt.hmd.Data.GeometryFormat("normal", DVec3),
+				new hxd.fmt.hmd.Data.GeometryFormat("uv", DVec2),
+			],
+			defaults : [],
+		};
+	}
+
+	function getBlend( r : hxd.res.Image ) : h3d.mat.BlendMode {
+		if( r.entry.extension == "jpg" )
+			return None;
+		return Alpha;
+	}
+
+	function loadMaterialTexture( r : hxd.res.FbxModel, mat : hxd.fmt.hmd.Data.Material ) {
+		var texturePath = r.entry.directory + mat.diffuseTexture.split("/").pop();
+		var t = textures.get(texturePath);
+		if( t != null )
+			return t;
+		var rt = hxd.res.Loader.currentInstance.load(texturePath).toImage();
+		var blend = getBlend(rt);
+		for( b in bigTextures ) {
+			t = b.add(rt, blend);
+			if( t != null ) break;
+		}
+		if( t == null ) {
+			var b = new h3d.mat.BigTexture(bigTextures.length, bigTextureSize, bigTextureBG);
+			bigTextures.unshift(b);
+			t = b.add(rt, blend);
+			if( t == null ) throw "Texture " + texturePath + " is too big";
+		}
+		return t;
+	}
+
+	public function done() {
+		for( b in bigTextures )
+			b.done();
+	}
+
+	public function loadModel( r : hxd.res.FbxModel ) : WorldModel {
+		var lib = r.toHmd();
+		var models = lib.header.models;
+		var format = buildFormat();
+
+		var model = new WorldModel(r);
+		model.stride = 0;
+		for( f in format.fmt )
+			model.stride += f.format.getSize();
+
+		var startVertex = 0, startIndex = 0;
+		for( m in models ) {
+			var geom = lib.header.geometries[m.geometry];
+			if( geom == null ) continue;
+			var pos = m.position.toMatrix();
+			for( mid in 0...m.materials.length ) {
+				var mat = lib.header.materials[m.materials[mid]];
+				var tex = loadMaterialTexture(r, mat);
+				if( tex == null ) continue;
+				var data = lib.getBuffers(geom, format.fmt, format.defaults, mid);
+
+				var m = new WorldModelMaterial(mat, tex);
+				m.vertexCount = Std.int(data.vertexes.length / model.stride);
+				m.indexCount = data.indexes.length;
+				m.startVertex = startVertex;
+				m.startIndex = startIndex;
+				model.materials.push(m);
+
+				var vl = data.vertexes;
+				var p = 0;
+				var extra = model.stride - 8;
+				for( i in 0...m.vertexCount ) {
+					var x = vl[p++];
+					var y = vl[p++];
+					var z = vl[p++];
+					var nx = vl[p++];
+					var ny = vl[p++];
+					var nz = vl[p++];
+					var u = vl[p++];
+					var v = vl[p++];
+
+					// position
+					var pt = new h3d.Vector(x,y,z);
+					pt.transform3x4(pos);
+					model.buf.push(pt.x);
+					model.buf.push(pt.y);
+					model.buf.push(pt.z);
+					model.bounds.addPos(pt.x, pt.y, pt.z);
+
+					// normal
+					var n = new h3d.Vector(nx, ny, nz);
+					n.transform3x3(pos);
+					var len = hxd.Math.invSqrt(n.lengthSq());
+					model.buf.push(n.x * len);
+					model.buf.push(n.y * len);
+					model.buf.push(n.z * len);
+
+					// uv
+					model.buf.push(u * tex.su + tex.du);
+					model.buf.push(v * tex.sv + tex.dv);
+
+					// extra
+					for( k in 0...extra )
+						model.buf.push(vl[p++]);
+				}
+
+				for( i in 0...m.indexCount )
+					model.idx.push(data.indexes[i] + startIndex);
+
+				startVertex += m.vertexCount;
+				startIndex += m.indexCount;
+			}
+		}
+		return model;
+	}
+
+	function getChunk( x : Float, y : Float, create = false ) {
+		var ix = Std.int(x) >> chunkBits;
+		var iy = Std.int(y) >> chunkBits;
+		if( ix < 0 ) ix = 0;
+		if( iy < 0 ) iy = 0;
+		var cid = ix + iy * worldStride;
+		var c = chunks[cid];
+		if( c == null && create ) {
+			c = new WorldChunk(ix, iy);
+			c.x = ix * chunkSize;
+			c.y = iy * chunkSize;
+			addChild(c.root);
+			chunks[cid] = c;
+			allChunks.push(c);
+			initSoil(c);
+		}
+		return c;
+	}
+
+	function initSoil( c : WorldChunk ) {
+		var cube = new h3d.prim.Cube(chunkSize, chunkSize, 0);
+		cube.addNormals();
+		cube.addUVs();
+		var soil = new h3d.scene.Mesh(cube, c.root);
+		soil.x = c.x;
+		soil.y = c.y;
+		soil.material.texture = h3d.mat.Texture.fromColor(soilColor);
+	}
+
+	function initMaterial( mesh : h3d.scene.Mesh, mat : WorldModelMaterial ) {
+		mesh.material.blendMode = mat.t.blend;
+		mesh.material.texture = mat.t.t.tex;
+	}
+
+	override function dispose() {
+		super.dispose();
+		for( c in allChunks )
+			c.dispose();
+		allChunks = [];
+		chunks = [];
+	}
+
+	public function add( model : WorldModel, x : Float, y : Float, z : Float, scale = 1., rotation = 0. ) {
+		var c = getChunk(x, y, true);
+
+		for( mat in model.materials ) {
+			var b = c.buffers.get(mat.bits);
+			if( b == null ) {
+				b = new h3d.scene.Mesh(new h3d.prim.BigPrimitive(model.stride, true), c.root);
+				initMaterial(b, mat);
+			}
+			var p = Std.instance(b.primitive, h3d.prim.BigPrimitive);
+			p.addSub(model.buf, model.idx, mat.startVertex, Std.int(mat.startIndex / 3), mat.vertexCount, Std.int(mat.indexCount / 3), x, y, z, rotation, scale);
+		}
+
+
+		// update bounds
+		var cosR = Math.cos(rotation);
+		var sinR = Math.sin(rotation);
+
+		inline function addPoint(dx:Float, dy:Float, dz:Float) {
+			var tx = dx * cosR - dy * sinR;
+			var ty = dx * sinR + dy * cosR;
+			c.bounds.addPos(tx * scale + x, ty * scale + y, dz * scale + z);
+		}
+
+		addPoint(model.bounds.xMin, model.bounds.yMin, model.bounds.zMin);
+		addPoint(model.bounds.xMin, model.bounds.yMin, model.bounds.zMax);
+		addPoint(model.bounds.xMin, model.bounds.yMax, model.bounds.zMin);
+		addPoint(model.bounds.xMin, model.bounds.yMax, model.bounds.zMax);
+		addPoint(model.bounds.xMax, model.bounds.yMin, model.bounds.zMin);
+		addPoint(model.bounds.xMax, model.bounds.yMin, model.bounds.zMax);
+		addPoint(model.bounds.xMax, model.bounds.yMax, model.bounds.zMin);
+		addPoint(model.bounds.xMax, model.bounds.yMax, model.bounds.zMax);
+	}
+
+	override function sync(ctx:RenderContext) {
+		super.sync(ctx);
+		for( c in allChunks )
+			c.root.visible = c.bounds.inFrustum(ctx.camera.m);
+	}
+
+}

+ 24 - 0
samples/world/Main.hx

@@ -0,0 +1,24 @@
+class Main extends hxd.App {
+
+	var world : h3d.scene.World;
+
+	override function init() {
+		world = new h3d.scene.World(64, 128, s3d);
+		var t = world.loadModel(hxd.Res.tree);
+		var r = world.loadModel(hxd.Res.rock);
+
+		for( i in 0...1000 )
+			world.add(Std.random(2) == 0 ? t : r, Math.random() * 100, Math.random() * 100, 0);
+
+		s3d.camera.pos.set(0, 0, 120);
+		s3d.camera.target.set(64, 64, 0);
+
+		world.done();
+	}
+
+	static function main() {
+		hxd.Res.initEmbed();
+		new Main();
+	}
+
+}

BIN
samples/world/res/rock.hmd


BIN
samples/world/res/rockTexture.jpg


BIN
samples/world/res/tree.hmd


BIN
samples/world/res/treeTexture.png


+ 6 - 0
samples/world/world.hxml

@@ -0,0 +1,6 @@
+-swf world.swf
+-swf-header 800:600:60:000000
+-swf-version 15.0
+-main Main
+-lib heaps
+-dce full

+ 60 - 0
samples/world/world.hxproj

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project version="2">
+  <!-- Output SWF options -->
+  <output>
+    <movie outputType="Application" />
+    <movie input="" />
+    <movie path="world.swf" />
+    <movie fps="60" />
+    <movie width="800" />
+    <movie height="600" />
+    <movie version="15" />
+    <movie minorVersion="0" />
+    <movie platform="Flash Player" />
+    <movie background="#000000" />
+  </output>
+  <!-- Other classes to be compiled into your SWF -->
+  <classpaths>
+    <!-- example: <class path="..." /> -->
+  </classpaths>
+  <!-- Build options -->
+  <build>
+    <option directives="" />
+    <option flashStrict="False" />
+    <option noInlineOnDebug="False" />
+    <option mainClass="Main" />
+    <option enabledebug="False" />
+    <option additional="-lib heaps&#xA;-dce full" />
+  </build>
+  <!-- haxelib libraries -->
+  <haxelib>
+    <!-- example: <library name="..." /> -->
+  </haxelib>
+  <!-- Class files to compile (other referenced classes will automatically be included) -->
+  <compileTargets>
+    <!-- example: <compile path="..." /> -->
+  </compileTargets>
+  <!-- Assets to embed into the output SWF -->
+  <library>
+    <!-- example: <asset path="..." id="..." update="..." glyphs="..." mode="..." place="..." sharepoint="..." /> -->
+  </library>
+  <!-- Paths to exclude from the Project Explorer tree -->
+  <hiddenPaths>
+    <hidden path="engine.hxml" />
+    <hidden path="obj" />
+    <hidden path="main.js" />
+    <hidden path="main.js.map" />
+  </hiddenPaths>
+  <!-- Executed before build -->
+  <preBuildCommand />
+  <!-- Executed after build -->
+  <postBuildCommand alwaysRun="False" />
+  <!-- Other project options -->
+  <options>
+    <option showHiddenPaths="False" />
+    <option testMovie="OpenDocument" />
+    <option testMovieCommand="world.swf" />
+  </options>
+  <!-- Plugin storage -->
+  <storage />
+</project>