/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

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