PageRenderTime 53ms CodeModel.GetById 2ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

/src/away3d/core/render/HitTestRenderer.as

http://github.com/away3d/away3d-core-fp11
ActionScript | 464 lines | 309 code | 71 blank | 84 comment | 29 complexity | 38ab5b71954bdd1b3bb4a3a1d9882223 MD5 | raw file
  1package away3d.core.render
  2{
  3	import away3d.arcane;
  4	import away3d.cameras.Camera3D;
  5	import away3d.containers.View3D;
  6	import away3d.core.base.IRenderable;
  7	import away3d.core.base.SubGeometry;
  8	import away3d.core.base.SubMesh;
  9	import away3d.core.data.RenderableListItem;
 10	import away3d.core.math.Matrix3DUtils;
 11	import away3d.core.traverse.EntityCollector;
 12	import away3d.entities.Entity;
 13
 14	import com.adobe.utils.AGALMiniAssembler;
 15
 16	import flash.display.BitmapData;
 17	import flash.display3D.Context3DBlendFactor;
 18	import flash.display3D.Context3DClearMask;
 19	import flash.display3D.Context3DCompareMode;
 20	import flash.display3D.Context3DProgramType;
 21	import flash.display3D.Context3DTriangleFace;
 22	import flash.display3D.Context3DVertexBufferFormat;
 23	import flash.display3D.Program3D;
 24	import flash.geom.Matrix3D;
 25	import flash.geom.Point;
 26	import flash.geom.Rectangle;
 27	import flash.geom.Vector3D;
 28
 29	use namespace arcane;
 30
 31	/**
 32	 * HitTestRenderer provides a renderer that can identify objects under a given screen position and can optionally
 33	 * calculate further geometrical information about the object at that point.
 34	 *
 35	 * @see away3d.core.managers.Mouse3DManager
 36	 */
 37	public class HitTestRenderer extends RendererBase
 38	{
 39		private var _objectProgram3D : Program3D;
 40		private var _triangleProgram3D : Program3D;
 41		public var _bitmapData : BitmapData;
 42		private var _viewportData : Vector.<Number>;
 43		private var _boundOffsetScale : Vector.<Number>;
 44		private var _id : Vector.<Number>;
 45
 46		private var _interactives : Vector.<IRenderable>;
 47		private var _interactiveId : uint;
 48		private var _hitColor : uint;
 49		private var _inverse : Matrix3D;
 50		private var _projX : Number;
 51		private var _projY : Number;
 52
 53		private var _hitRenderable : IRenderable;
 54		private var _localHitPosition : Vector3D;
 55		private var _hitUV : Point;
 56
 57		private var _rayPos : Vector3D = new Vector3D();
 58		private var _rayDir : Vector3D = new Vector3D();
 59//		private var _localRayPos : Vector3D = new Vector3D();
 60//		private var _localRayDir : Vector3D = new Vector3D();
 61		private var _potentialFound : Boolean;
 62		private var _view : View3D;
 63		private static const MOUSE_SCISSOR_RECT : Rectangle = new Rectangle(0, 0, 1, 1);
 64
 65		/**
 66		 * Creates a new HitTestRenderer object.
 67		 * @param renderMode The render mode to use.
 68		 */
 69		public function HitTestRenderer(view : View3D)
 70		{
 71			super();
 72			_view = view;
 73			swapBackBuffer = false;
 74
 75			// DO NOT SORT
 76			renderableSorter = null;
 77
 78			init();
 79		}
 80
 81		/**
 82		 * Initializes data.
 83		 */
 84		private function init() : void
 85		{
 86			_id = new Vector.<Number>(4, true);
 87			_viewportData = new Vector.<Number>(4, true);	// first 2 contain scale, last 2 translation
 88			_boundOffsetScale = new Vector.<Number>(8, true);	// first 2 contain scale, last 2 translation
 89			_boundOffsetScale[3] = 0;
 90			_boundOffsetScale[7] = 1;
 91			_localHitPosition = new Vector3D();
 92			_interactives = new Vector.<IRenderable>();
 93			_bitmapData = new BitmapData(1, 1, false, 0);
 94			_inverse = new Matrix3D();
 95		}
 96
 97		/**
 98		 * Updates the object information at the given position for the given visible objects.
 99		 * @param ratioX A ratio between 0 and 1 of the horizontal hit-test position relative to the viewport width.
100		 * @param ratioY A ratio between 0 and 1 of the vertical hit-test position relative to the viewport height.
101		 * @param entityCollector The EntityCollector object containing all potentially visible objects.
102		 */
103		public function update(ratioX : Number, ratioY : Number, entityCollector : EntityCollector) : void
104		{
105			if (!_stage3DProxy) return;
106
107			_viewportData[0] = _view.width;
108			_viewportData[1] = _view.height;
109			_viewportData[2] = -(_projX = ratioX*2 - 1);
110			_viewportData[3] = _projY = ratioY*2 - 1;
111
112			// _potentialFound will be set to true if any object is actually rendered
113			_hitRenderable = null;
114			_hitUV = null;
115			_potentialFound = false;
116
117			draw(entityCollector);
118
119			// clear buffers
120			_stage3DProxy.setSimpleVertexBuffer(0, null);
121
122			if (!_context || !_potentialFound) return;
123			_context.drawToBitmapData(_bitmapData);
124			_hitColor = _bitmapData.getPixel(0, 0);
125
126			if (_hitColor != 0) {
127				_hitRenderable = _interactives[_hitColor-1];
128
129				if (_hitRenderable.mouseDetails)
130					getHitDetails(entityCollector.camera);
131				else {
132					_hitUV = null;
133//					_localHitPosition = null;
134				}
135			}
136//			_context.clear(0, 0, 0, 0, 1, 0);
137			_context.present();
138		}
139
140		/**
141		 * The IRenderable object directly under the hit-test position after a call to update.
142		 */
143		public function get hitRenderable() : IRenderable
144		{
145			return _hitRenderable;
146		}
147
148		/**
149		 * The UV coordinate at the hit position.
150		 */
151		public function get hitUV() : Point
152		{
153			return _hitUV;
154		}
155
156		/**
157		 * The coordinate in object space of the hit position.
158		 */
159		public function get localHitPosition() : Vector3D
160		{
161			return _localHitPosition;
162		}
163
164		/**
165		 * @inheritDoc
166		 */
167		override protected function draw(entityCollector : EntityCollector) : void
168		{
169			var camera : Camera3D = entityCollector.camera;
170
171			_context.clear(0, 0, 0, 1);
172			_stage3DProxy.scissorRect = MOUSE_SCISSOR_RECT;
173
174			_interactives.length = _interactiveId = 0;
175
176			if (!_objectProgram3D) initObjectProgram3D();
177			_context.setBlendFactors(Context3DBlendFactor.ONE, Context3DBlendFactor.ZERO);
178			_context.setDepthTest(true, Context3DCompareMode.LESS);
179			_stage3DProxy.setProgram(_objectProgram3D);
180			_context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, _viewportData, 1);
181			drawRenderables(entityCollector.opaqueRenderableHead, camera);
182			drawRenderables(entityCollector.blendedRenderableHead, camera);
183		}
184
185		/**
186		 * Draw a list of renderables.
187		 * @param renderables The renderables to draw.
188		 * @param camera The camera for which to render.
189		 */
190		private function drawRenderables(item : RenderableListItem, camera : Camera3D) : void
191		{
192			var renderable : IRenderable;
193
194			while (item) {
195				renderable = item.renderable;
196
197				// it's possible that the renderable was already removed from the scene
198				if (!renderable.sourceEntity.scene || !renderable.mouseEnabled) {
199					item = item.next;
200					continue;
201				}
202
203				_potentialFound = true;
204
205				_context.setCulling(renderable.material.bothSides? Context3DTriangleFace.NONE : Context3DTriangleFace.BACK);
206
207				_interactives[_interactiveId++] = renderable;
208				// color code so that reading from bitmapdata will contain the correct value
209				_id[1] = (_interactiveId >> 8)/255;	    // on green channel
210				_id[2] = (_interactiveId & 0xff)/255;  	// on blue channel
211
212				_context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, renderable.modelViewProjection, true);
213				_context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, _id, 1);
214				_stage3DProxy.setSimpleVertexBuffer(0, renderable.getVertexBuffer(_stage3DProxy), Context3DVertexBufferFormat.FLOAT_3);
215				_context.drawTriangles(renderable.getIndexBuffer(_stage3DProxy), 0, renderable.numTriangles);
216
217				item = item.next;
218			}
219		}
220
221		private function updateRay(camera : Camera3D) : void
222		{
223			var p1 : Vector3D = camera.scenePosition;
224			var p2 : Vector3D = camera.unproject(_projX, _projY);
225
226			_rayPos.x = p1.x;
227			_rayPos.y = p1.y;
228			_rayPos.z = p1.z;
229			_rayDir.x = p2.x - p1.x;
230			_rayDir.y = p2.y - p1.y;
231			_rayDir.z = p2.z - p1.z;
232			_rayDir.normalize();
233		}
234
235		/**
236		 * Creates the Program3D that color-codes objects.
237		 */
238		private function initObjectProgram3D() : void
239		{
240			var vertexCode : String;
241			var fragmentCode : String;
242
243			_objectProgram3D = _context.createProgram();
244
245			vertexCode = 	"m44 vt0, va0, vc0			\n" +
246							"mul vt1.xy, vt0.w, vc4.zw	\n" +
247							"add vt0.xy, vt0.xy, vt1.xy	\n" +
248							"mul vt0.xy, vt0.xy, vc4.xy	\n" +
249							"mov op, vt0	\n";
250			fragmentCode =  "mov oc, fc0";		// write identifier
251
252			_objectProgram3D.upload(	new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX, vertexCode),
253										new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT, fragmentCode));
254		}
255
256		/**
257		 * Creates the Program3D that renders positions.
258		 */
259		private function initTriangleProgram3D() : void
260		{
261			var vertexCode : String;
262			var fragmentCode : String;
263
264			_triangleProgram3D = _context.createProgram();
265
266			// todo: add animation code
267			vertexCode = 	"add vt0, va0, vc5 			\n" +
268							"mul vt0, vt0, vc6 			\n" +
269							"mov v0, vt0				\n" +
270							"m44 vt0, va0, vc0			\n" +
271							"mul vt1.xy, vt0.w, vc4.zw	\n" +
272							"add vt0.xy, vt0.xy, vt1.xy	\n" +
273							"mul vt0.xy, vt0.xy, vc4.xy	\n" +
274							"mov op, vt0	\n";
275			fragmentCode =  "mov oc, v0";		// write identifier
276
277			_triangleProgram3D.upload(	new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX, vertexCode),
278										new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT, fragmentCode));
279		}
280
281		/**
282		 * Gets more detailed information about the hir position, if required.
283		 * @param camera The camera used to view the hit object.
284		 */
285		private function getHitDetails(camera : Camera3D) : void
286		{
287			getApproximatePosition(camera);
288			getPreciseDetails(camera);
289		}
290
291		/**
292		 * Finds a first-guess approximate position about the hit position.
293		 * @param camera The camera used to view the hit object.
294		 */
295		private function getApproximatePosition(camera : Camera3D) : void
296		{
297			var entity : Entity = _hitRenderable.sourceEntity;
298			var col : uint;
299			var scX : Number, scY : Number, scZ : Number;
300			var offsX : Number, offsY : Number, offsZ : Number;
301			var localViewProjection : Matrix3D = _hitRenderable.modelViewProjection;
302
303			if (!_triangleProgram3D) initTriangleProgram3D();
304
305			_boundOffsetScale[4] = scX = 1/(entity.maxX-entity.minX);
306			_boundOffsetScale[5] = scY = 1/(entity.maxY-entity.minY);
307			_boundOffsetScale[6] = scZ = 1/(entity.maxZ-entity.minZ);
308			_boundOffsetScale[0] = offsX = -entity.minX;
309			_boundOffsetScale[1] = offsY = -entity.minY;
310			_boundOffsetScale[2] = offsZ = -entity.minZ;
311
312			_stage3DProxy.setProgram(_triangleProgram3D);
313			_context.clear(0, 0, 0, 0, 1, 0, Context3DClearMask.DEPTH);
314			_context.setScissorRectangle(MOUSE_SCISSOR_RECT);
315			_context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, localViewProjection, true);
316			_context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 5, _boundOffsetScale, 2);
317			_stage3DProxy.setSimpleVertexBuffer(0, _hitRenderable.getVertexBuffer(_stage3DProxy), Context3DVertexBufferFormat.FLOAT_3);
318			_context.drawTriangles(_hitRenderable.getIndexBuffer(_stage3DProxy), 0, _hitRenderable.numTriangles);
319			_context.drawToBitmapData(_bitmapData);
320
321			col = _bitmapData.getPixel(0, 0);
322
323			_localHitPosition.x = ((col >> 16) & 0xff)/(scX*255) - offsX;
324			_localHitPosition.y = ((col >> 8) & 0xff)/(scY*255) - offsY;
325			_localHitPosition.z = (col & 0xff)/(scZ*255) - offsZ;
326		}
327
328		/**
329		 * Use the approximate position info to find the face under the mouse position from which we can derive the precise
330		 * ray-face intersection point, then use barycentric coordinates to figure out the uv coordinates, etc.
331		 * @param camera The camera used to view the hit object.
332		 */
333		private function getPreciseDetails(camera : Camera3D) : void
334		{
335			var subGeom : SubGeometry = SubMesh(_hitRenderable).subGeometry;
336			var indices : Vector.<uint> = subGeom.indexData;
337			var vertices : Vector.<Number> = subGeom.vertexData;
338			var len : int = indices.length;
339			var x1 : Number, y1 : Number, z1 : Number;
340			var x2 : Number, y2 : Number, z2 : Number;
341			var x3 : Number, y3 : Number, z3 : Number;
342			var i : uint = 0, j : uint = 1, k : uint = 2;
343			var t1 : uint, t2 : uint, t3 : uint;
344			var v0x : Number, v0y : Number, v0z : Number;
345			var v1x : Number, v1y : Number, v1z : Number;
346			var v2x : Number, v2y : Number, v2z : Number;
347			var dot00 : Number, dot01 : Number, dot02 : Number, dot11 : Number, dot12 : Number;
348			var s : Number, t : Number, invDenom : Number;
349			var uvs : Vector.<Number> = subGeom.UVData;
350			var normals : Vector.<Number> = subGeom.faceNormalsData;
351			var x : Number = _localHitPosition.x, y : Number = _localHitPosition.y, z : Number = _localHitPosition.z;
352			var u : Number, v : Number;
353			var ui1 : uint, ui2 : uint, ui3 : uint;
354
355			updateRay(camera);
356
357			_hitUV = new Point();
358
359			while (i < len) {
360				t1 = indices[i]*3;
361				t2 = indices[j]*3;
362				t3 = indices[k]*3;
363				x1 = vertices[t1];	y1 = vertices[t1+1];	z1 = vertices[t1+2];
364				x2 = vertices[t2];	y2 = vertices[t2+1];	z2 = vertices[t2+2];
365				x3 = vertices[t3];	y3 = vertices[t3+1];	z3 = vertices[t3+2];
366
367				// if within bounds
368				if (!(	(x < x1 && x < x2 && x < x3) ||
369						(y < y1 && y < y2 && y < y3) ||
370						(z < z1 && z < z2 && z < z3) ||
371						(x > x1 && x > x2 && x > x3) ||
372						(y > y1 && y > y2 && y > y3) ||
373						(z > z1 && z > z2 && z > z3))) {
374
375					// calculate barycentric coords for approximated position
376					v0x = x3 - x1; v0y = y3 - y1; v0z = z3 - z1;
377					v1x = x2 - x1; v1y = y2 - y1; v1z = z2 - z1;
378					v2x = x - x1; v2y = y - y1; v2z = z - z1;
379					dot00 = v0x*v0x + v0y*v0y + v0z*v0z;
380					dot01 = v0x*v1x + v0y*v1y + v0z*v1z;
381					dot02 = v0x*v2x + v0y*v2y + v0z*v2z;
382					dot11 = v1x*v1x + v1y*v1y + v1z*v1z;
383					dot12 = v1x*v2x + v1y*v2y + v1z*v2z;
384					invDenom = 1/(dot00*dot11 - dot01*dot01);
385					s = (dot11*dot02 - dot01*dot12)*invDenom;
386					t = (dot00*dot12 - dot01*dot02)*invDenom;
387
388					// if inside the current triangle, fetch details hit information
389					if (s >= 0 && t >= 0 && (s + t) <= 1) {
390
391						// this is def the triangle, now calculate precise coords
392						getPrecisePosition(camera, _hitRenderable.inverseSceneTransform, normals[i], normals[i+1], normals[i+2], x1, y1, z1);
393
394						v2x = _localHitPosition.x - x1;
395						v2y = _localHitPosition.y - y1;
396						v2z = _localHitPosition.z - z1;
397
398						dot02 = v0x*v2x + v0y*v2y + v0z*v2z;
399						dot12 = v1x*v2x + v1y*v2y + v1z*v2z;
400						s = (dot11*dot02 - dot01*dot12)*invDenom;
401						t = (dot00*dot12 - dot01*dot02)*invDenom;
402
403						ui1 = indices[i] << 1;
404						ui2 = indices[j] << 1;
405						ui3 = indices[k] << 1;
406
407						u = uvs[ui1]; v = uvs[ui1+1];
408						_hitUV.x = u + t*(uvs[ui2] - u) + s*(uvs[ui3] - u);
409						_hitUV.y = v + t*(uvs[ui2+1] - v) + s*(uvs[ui3+1] - v);
410
411						return;
412					}
413				}
414
415				i += 3;
416				j += 3;
417				k += 3;
418			}
419		}
420
421		/**
422		 * Finds the precise hit position by unprojecting the screen coordinate back unto the hit face's plane and
423		 * calculating the intersection point.
424		 * @param camera The camera used to render the object.
425		 * @param invSceneTransform The inverse scene transformation of the hit object.
426		 * @param nx The x-coordinate of the face's plane normal.
427		 * @param ny The y-coordinate of the face plane normal.
428		 * @param nz The z-coordinate of the face plane normal.
429		 * @param px The x-coordinate of a point on the face's plane (ie a face vertex)
430		 * @param py The y-coordinate of a point on the face's plane (ie a face vertex)
431		 * @param pz The z-coordinate of a point on the face's plane (ie a face vertex)
432		 */
433		private function getPrecisePosition(camera : Camera3D, invSceneTransform : Matrix3D, nx : Number, ny : Number, nz : Number, px : Number, py : Number, pz : Number) : void
434		{
435			// calculate screen ray and find exact intersection position with triangle
436			var rx : Number, ry : Number, rz : Number;
437			var ox : Number, oy : Number, oz : Number;
438			var t : Number;
439			var raw : Vector.<Number> = Matrix3DUtils.RAW_DATA_CONTAINER;
440			var cx : Number = _rayPos.x, cy : Number = _rayPos.y, cz : Number = _rayPos.z;
441
442			// unproject projection point, gives ray dir in cam space
443			ox = _rayDir.x;
444			oy = _rayDir.y;
445			oz = _rayDir.z;
446
447			// transform ray dir and origin (cam pos) to object space
448			invSceneTransform.copyRawDataTo(raw);
449			rx = raw[0]*ox + raw[4]*oy + raw[8]*oz;
450			ry = raw[1]*ox + raw[5]*oy + raw[9]*oz;
451			rz = raw[2]*ox + raw[6]*oy + raw[10]*oz;
452
453			ox = raw[0]*cx + raw[4]*cy + raw[8]*cz + raw[12];
454			oy = raw[1]*cx + raw[5]*cy + raw[9]*cz + raw[13];
455			oz = raw[2]*cx + raw[6]*cy + raw[10]*cz + raw[14];
456
457			t = ((px - ox)*nx + (py - oy)*ny + (pz - oz)*nz) / (rx*nx + ry*ny + rz*nz);
458
459			_localHitPosition.x = ox + rx*t;
460			_localHitPosition.y = oy + ry*t;
461			_localHitPosition.z = oz + rz*t;
462		}
463	}
464}