TransformControls.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170
  1. /**
  2. * @author arodic / https://github.com/arodic
  3. */
  4. (function () {
  5. 'use strict';
  6. var GizmoMaterial = function (parameters) {
  7. THREE.MeshBasicMaterial.call(this);
  8. this.depthTest = false;
  9. this.depthWrite = false;
  10. this.side = THREE.FrontSide;
  11. this.transparent = true;
  12. this.setValues(parameters);
  13. this.oldColor = this.color.clone();
  14. this.oldOpacity = this.opacity;
  15. this.highlight = function (highlighted) {
  16. if (highlighted) {
  17. this.color.setRGB(1, 1, 0);
  18. this.opacity = 1;
  19. } else {
  20. this.color.copy(this.oldColor);
  21. this.opacity = this.oldOpacity;
  22. }
  23. };
  24. };
  25. GizmoMaterial.prototype = Object.create(THREE.MeshBasicMaterial.prototype);
  26. GizmoMaterial.prototype.constructor = GizmoMaterial;
  27. var GizmoLineMaterial = function (parameters) {
  28. THREE.LineBasicMaterial.call(this);
  29. this.depthTest = false;
  30. this.depthWrite = false;
  31. this.transparent = true;
  32. this.linewidth = 1;
  33. this.setValues(parameters);
  34. this.oldColor = this.color.clone();
  35. this.oldOpacity = this.opacity;
  36. this.highlight = function (highlighted) {
  37. if (highlighted) {
  38. this.color.setRGB(1, 1, 0);
  39. this.opacity = 1;
  40. } else {
  41. this.color.copy(this.oldColor);
  42. this.opacity = this.oldOpacity;
  43. }
  44. };
  45. };
  46. GizmoLineMaterial.prototype = Object.create(THREE.LineBasicMaterial.prototype);
  47. GizmoLineMaterial.prototype.constructor = GizmoLineMaterial;
  48. var pickerMaterial = new GizmoMaterial({visible: false, transparent: false});
  49. THREE.TransformGizmo = function () {
  50. var scope = this;
  51. this.init = function () {
  52. THREE.Object3D.call(this);
  53. this.handles = new THREE.Object3D();
  54. this.pickers = new THREE.Object3D();
  55. this.planes = new THREE.Object3D();
  56. this.add(this.handles);
  57. this.add(this.pickers);
  58. this.add(this.planes);
  59. //// PLANES
  60. var planeGeometry = new THREE.PlaneBufferGeometry(50, 50, 2, 2);
  61. var planeMaterial = new THREE.MeshBasicMaterial({visible: false, side: THREE.DoubleSide});
  62. var planes = {
  63. "XY": new THREE.Mesh(planeGeometry, planeMaterial),
  64. "YZ": new THREE.Mesh(planeGeometry, planeMaterial),
  65. "XZ": new THREE.Mesh(planeGeometry, planeMaterial),
  66. "XYZE": new THREE.Mesh(planeGeometry, planeMaterial)
  67. };
  68. this.activePlane = planes[ "XYZE" ];
  69. planes[ "YZ" ].rotation.set(0, Math.PI / 2, 0);
  70. planes[ "XZ" ].rotation.set(-Math.PI / 2, 0, 0);
  71. for (var i in planes) {
  72. planes[ i ].name = i;
  73. this.planes.add(planes[ i ]);
  74. this.planes[ i ] = planes[ i ];
  75. }
  76. //// HANDLES AND PICKERS
  77. var setupGizmos = function (gizmoMap, parent) {
  78. for (var name in gizmoMap) {
  79. for (i = gizmoMap[ name ].length; i--; ) {
  80. var object = gizmoMap[ name ][ i ][ 0 ];
  81. var position = gizmoMap[ name ][ i ][ 1 ];
  82. var rotation = gizmoMap[ name ][ i ][ 2 ];
  83. object.name = name;
  84. if (position)
  85. object.position.set(position[ 0 ], position[ 1 ], position[ 2 ]);
  86. if (rotation)
  87. object.rotation.set(rotation[ 0 ], rotation[ 1 ], rotation[ 2 ]);
  88. parent.add(object);
  89. }
  90. }
  91. };
  92. setupGizmos(this.handleGizmos, this.handles);
  93. setupGizmos(this.pickerGizmos, this.pickers);
  94. // reset Transformations
  95. this.traverse(function (child) {
  96. if (child instanceof THREE.Mesh) {
  97. child.updateMatrix();
  98. var tempGeometry = child.geometry.clone();
  99. tempGeometry.applyMatrix(child.matrix);
  100. child.geometry = tempGeometry;
  101. child.position.set(0, 0, 0);
  102. child.rotation.set(0, 0, 0);
  103. child.scale.set(1, 1, 1);
  104. }
  105. });
  106. };
  107. this.highlight = function (axis) {
  108. this.traverse(function (child) {
  109. if (child.material && child.material.highlight) {
  110. if (child.name === axis) {
  111. child.material.highlight(true);
  112. } else {
  113. child.material.highlight(false);
  114. }
  115. }
  116. });
  117. };
  118. };
  119. THREE.TransformGizmo.prototype = Object.create(THREE.Object3D.prototype);
  120. THREE.TransformGizmo.prototype.constructor = THREE.TransformGizmo;
  121. THREE.TransformGizmo.prototype.update = function (rotation, eye) {
  122. var vec1 = new THREE.Vector3(0, 0, 0);
  123. var vec2 = new THREE.Vector3(0, 1, 0);
  124. var lookAtMatrix = new THREE.Matrix4();
  125. this.traverse(function (child) {
  126. if (child.name.search("E") !== -1) {
  127. child.quaternion.setFromRotationMatrix(lookAtMatrix.lookAt(eye, vec1, vec2));
  128. } else if (child.name.search("X") !== -1 || child.name.search("Y") !== -1 || child.name.search("Z") !== -1) {
  129. child.quaternion.setFromEuler(rotation);
  130. }
  131. });
  132. };
  133. THREE.TransformGizmoTranslate = function () {
  134. THREE.TransformGizmo.call(this);
  135. var arrowGeometry = new THREE.Geometry();
  136. var mesh = new THREE.Mesh(new THREE.CylinderGeometry(0, 0.05, 0.2, 12, 1, false));
  137. mesh.position.y = 0.5;
  138. mesh.updateMatrix();
  139. arrowGeometry.merge(mesh.geometry, mesh.matrix);
  140. var lineXGeometry = new THREE.BufferGeometry();
  141. lineXGeometry.addAttribute('position', new THREE.Float32Attribute([0, 0, 0, 1, 0, 0], 3));
  142. var lineYGeometry = new THREE.BufferGeometry();
  143. lineYGeometry.addAttribute('position', new THREE.Float32Attribute([0, 0, 0, 0, 1, 0], 3));
  144. var lineZGeometry = new THREE.BufferGeometry();
  145. lineZGeometry.addAttribute('position', new THREE.Float32Attribute([0, 0, 0, 0, 0, 1], 3));
  146. this.handleGizmos = {
  147. X: [
  148. [new THREE.Mesh(arrowGeometry, new GizmoMaterial({color: 0xff0000})), [0.5, 0, 0], [0, 0, -Math.PI / 2]],
  149. [new THREE.Line(lineXGeometry, new GizmoLineMaterial({color: 0xff0000}))]
  150. ],
  151. Y: [
  152. [new THREE.Mesh(arrowGeometry, new GizmoMaterial({color: 0x00ff00})), [0, 0.5, 0]],
  153. [new THREE.Line(lineYGeometry, new GizmoLineMaterial({color: 0x00ff00}))]
  154. ],
  155. Z: [
  156. [new THREE.Mesh(arrowGeometry, new GizmoMaterial({color: 0x0000ff})), [0, 0, 0.5], [Math.PI / 2, 0, 0]],
  157. [new THREE.Line(lineZGeometry, new GizmoLineMaterial({color: 0x0000ff}))]
  158. ],
  159. XYZ: [
  160. [new THREE.Mesh(new THREE.OctahedronGeometry(0.1, 0), new GizmoMaterial({color: 0xffffff, opacity: 0.25})), [0, 0, 0], [0, 0, 0]]
  161. ],
  162. XY: [
  163. [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.29, 0.29), new GizmoMaterial({color: 0xffff00, opacity: 0.25})), [0.15, 0.15, 0]]
  164. ],
  165. YZ: [
  166. [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.29, 0.29), new GizmoMaterial({color: 0x00ffff, opacity: 0.25})), [0, 0.15, 0.15], [0, Math.PI / 2, 0]]
  167. ],
  168. XZ: [
  169. [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.29, 0.29), new GizmoMaterial({color: 0xff00ff, opacity: 0.25})), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]]
  170. ]
  171. };
  172. this.pickerGizmos = {
  173. X: [
  174. [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), pickerMaterial), [0.6, 0, 0], [0, 0, -Math.PI / 2]]
  175. ],
  176. Y: [
  177. [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), pickerMaterial), [0, 0.6, 0]]
  178. ],
  179. Z: [
  180. [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), pickerMaterial), [0, 0, 0.6], [Math.PI / 2, 0, 0]]
  181. ],
  182. XYZ: [
  183. [new THREE.Mesh(new THREE.OctahedronGeometry(0.2, 0), pickerMaterial)]
  184. ],
  185. XY: [
  186. [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.4, 0.4), pickerMaterial), [0.2, 0.2, 0]]
  187. ],
  188. YZ: [
  189. [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.4, 0.4), pickerMaterial), [0, 0.2, 0.2], [0, Math.PI / 2, 0]]
  190. ],
  191. XZ: [
  192. [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.4, 0.4), pickerMaterial), [0.2, 0, 0.2], [-Math.PI / 2, 0, 0]]
  193. ]
  194. };
  195. this.setActivePlane = function (axis, eye) {
  196. var tempMatrix = new THREE.Matrix4();
  197. eye.applyMatrix4(tempMatrix.getInverse(tempMatrix.extractRotation(this.planes[ "XY" ].matrixWorld)));
  198. if (axis === "X") {
  199. this.activePlane = this.planes[ "XY" ];
  200. if (Math.abs(eye.y) > Math.abs(eye.z))
  201. this.activePlane = this.planes[ "XZ" ];
  202. }
  203. if (axis === "Y") {
  204. this.activePlane = this.planes[ "XY" ];
  205. if (Math.abs(eye.x) > Math.abs(eye.z))
  206. this.activePlane = this.planes[ "YZ" ];
  207. }
  208. if (axis === "Z") {
  209. this.activePlane = this.planes[ "XZ" ];
  210. if (Math.abs(eye.x) > Math.abs(eye.y))
  211. this.activePlane = this.planes[ "YZ" ];
  212. }
  213. if (axis === "XYZ")
  214. this.activePlane = this.planes[ "XYZE" ];
  215. if (axis === "XY")
  216. this.activePlane = this.planes[ "XY" ];
  217. if (axis === "YZ")
  218. this.activePlane = this.planes[ "YZ" ];
  219. if (axis === "XZ")
  220. this.activePlane = this.planes[ "XZ" ];
  221. };
  222. this.init();
  223. };
  224. THREE.TransformGizmoTranslate.prototype = Object.create(THREE.TransformGizmo.prototype);
  225. THREE.TransformGizmoTranslate.prototype.constructor = THREE.TransformGizmoTranslate;
  226. THREE.TransformGizmoRotate = function () {
  227. THREE.TransformGizmo.call(this);
  228. var CircleGeometry = function (radius, facing, arc) {
  229. var geometry = new THREE.BufferGeometry();
  230. var vertices = [];
  231. arc = arc ? arc : 1;
  232. for (var i = 0; i <= 64 * arc; ++i) {
  233. if (facing === 'x')
  234. vertices.push(0, Math.cos(i / 32 * Math.PI) * radius, Math.sin(i / 32 * Math.PI) * radius);
  235. if (facing === 'y')
  236. vertices.push(Math.cos(i / 32 * Math.PI) * radius, 0, Math.sin(i / 32 * Math.PI) * radius);
  237. if (facing === 'z')
  238. vertices.push(Math.sin(i / 32 * Math.PI) * radius, Math.cos(i / 32 * Math.PI) * radius, 0);
  239. }
  240. geometry.addAttribute('position', new THREE.Float32Attribute(vertices, 3));
  241. return geometry;
  242. };
  243. this.handleGizmos = {
  244. X: [
  245. [new THREE.Line(new CircleGeometry(1, 'x', 0.5), new GizmoLineMaterial({color: 0xff0000}))]
  246. ],
  247. Y: [
  248. [new THREE.Line(new CircleGeometry(1, 'y', 0.5), new GizmoLineMaterial({color: 0x00ff00}))]
  249. ],
  250. Z: [
  251. [new THREE.Line(new CircleGeometry(1, 'z', 0.5), new GizmoLineMaterial({color: 0x0000ff}))]
  252. ],
  253. E: [
  254. [new THREE.Line(new CircleGeometry(1.25, 'z', 1), new GizmoLineMaterial({color: 0xcccc00}))]
  255. ],
  256. XYZE: [
  257. [new THREE.Line(new CircleGeometry(1, 'z', 1), new GizmoLineMaterial({color: 0x787878}))]
  258. ]
  259. };
  260. this.pickerGizmos = {
  261. X: [
  262. [new THREE.Mesh(new THREE.TorusBufferGeometry(1, 0.12, 4, 12, Math.PI), pickerMaterial), [0, 0, 0], [0, -Math.PI / 2, -Math.PI / 2]]
  263. ],
  264. Y: [
  265. [new THREE.Mesh(new THREE.TorusBufferGeometry(1, 0.12, 4, 12, Math.PI), pickerMaterial), [0, 0, 0], [Math.PI / 2, 0, 0]]
  266. ],
  267. Z: [
  268. [new THREE.Mesh(new THREE.TorusBufferGeometry(1, 0.12, 4, 12, Math.PI), pickerMaterial), [0, 0, 0], [0, 0, -Math.PI / 2]]
  269. ],
  270. E: [
  271. [new THREE.Mesh(new THREE.TorusBufferGeometry(1.25, 0.12, 2, 24), pickerMaterial)]
  272. ],
  273. XYZE: [
  274. [new THREE.Mesh(new THREE.Geometry())]// TODO
  275. ]
  276. };
  277. this.setActivePlane = function (axis) {
  278. if (axis === "E")
  279. this.activePlane = this.planes[ "XYZE" ];
  280. if (axis === "X")
  281. this.activePlane = this.planes[ "YZ" ];
  282. if (axis === "Y")
  283. this.activePlane = this.planes[ "XZ" ];
  284. if (axis === "Z")
  285. this.activePlane = this.planes[ "XY" ];
  286. };
  287. this.update = function (rotation, eye2) {
  288. THREE.TransformGizmo.prototype.update.apply(this, arguments);
  289. var group = {
  290. handles: this[ "handles" ],
  291. pickers: this[ "pickers" ],
  292. };
  293. var tempMatrix = new THREE.Matrix4();
  294. var worldRotation = new THREE.Euler(0, 0, 1);
  295. var tempQuaternion = new THREE.Quaternion();
  296. var unitX = new THREE.Vector3(1, 0, 0);
  297. var unitY = new THREE.Vector3(0, 1, 0);
  298. var unitZ = new THREE.Vector3(0, 0, 1);
  299. var quaternionX = new THREE.Quaternion();
  300. var quaternionY = new THREE.Quaternion();
  301. var quaternionZ = new THREE.Quaternion();
  302. var eye = eye2.clone();
  303. worldRotation.copy(this.planes[ "XY" ].rotation);
  304. tempQuaternion.setFromEuler(worldRotation);
  305. tempMatrix.makeRotationFromQuaternion(tempQuaternion).getInverse(tempMatrix);
  306. eye.applyMatrix4(tempMatrix);
  307. this.traverse(function (child) {
  308. tempQuaternion.setFromEuler(worldRotation);
  309. if (child.name === "X") {
  310. quaternionX.setFromAxisAngle(unitX, Math.atan2(-eye.y, eye.z));
  311. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionX);
  312. child.quaternion.copy(tempQuaternion);
  313. }
  314. if (child.name === "Y") {
  315. quaternionY.setFromAxisAngle(unitY, Math.atan2(eye.x, eye.z));
  316. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionY);
  317. child.quaternion.copy(tempQuaternion);
  318. }
  319. if (child.name === "Z") {
  320. quaternionZ.setFromAxisAngle(unitZ, Math.atan2(eye.y, eye.x));
  321. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionZ);
  322. child.quaternion.copy(tempQuaternion);
  323. }
  324. });
  325. };
  326. this.init();
  327. };
  328. THREE.TransformGizmoRotate.prototype = Object.create(THREE.TransformGizmo.prototype);
  329. THREE.TransformGizmoRotate.prototype.constructor = THREE.TransformGizmoRotate;
  330. THREE.TransformGizmoScale = function () {
  331. THREE.TransformGizmo.call(this);
  332. var arrowGeometry = new THREE.Geometry();
  333. var mesh = new THREE.Mesh(new THREE.BoxGeometry(0.125, 0.125, 0.125));
  334. mesh.position.y = 0.5;
  335. mesh.updateMatrix();
  336. arrowGeometry.merge(mesh.geometry, mesh.matrix);
  337. var lineXGeometry = new THREE.BufferGeometry();
  338. lineXGeometry.addAttribute('position', new THREE.Float32Attribute([0, 0, 0, 1, 0, 0], 3));
  339. var lineYGeometry = new THREE.BufferGeometry();
  340. lineYGeometry.addAttribute('position', new THREE.Float32Attribute([0, 0, 0, 0, 1, 0], 3));
  341. var lineZGeometry = new THREE.BufferGeometry();
  342. lineZGeometry.addAttribute('position', new THREE.Float32Attribute([0, 0, 0, 0, 0, 1], 3));
  343. this.handleGizmos = {
  344. X: [
  345. [new THREE.Mesh(arrowGeometry, new GizmoMaterial({color: 0xff0000})), [0.5, 0, 0], [0, 0, -Math.PI / 2]],
  346. [new THREE.Line(lineXGeometry, new GizmoLineMaterial({color: 0xff0000}))]
  347. ],
  348. Y: [
  349. [new THREE.Mesh(arrowGeometry, new GizmoMaterial({color: 0x00ff00})), [0, 0.5, 0]],
  350. [new THREE.Line(lineYGeometry, new GizmoLineMaterial({color: 0x00ff00}))]
  351. ],
  352. Z: [
  353. [new THREE.Mesh(arrowGeometry, new GizmoMaterial({color: 0x0000ff})), [0, 0, 0.5], [Math.PI / 2, 0, 0]],
  354. [new THREE.Line(lineZGeometry, new GizmoLineMaterial({color: 0x0000ff}))]
  355. ],
  356. XYZ: [
  357. [new THREE.Mesh(new THREE.BoxBufferGeometry(0.125, 0.125, 0.125), new GizmoMaterial({color: 0xffffff, opacity: 0.25}))]
  358. ]
  359. };
  360. this.pickerGizmos = {
  361. X: [
  362. [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), pickerMaterial), [0.6, 0, 0], [0, 0, -Math.PI / 2]]
  363. ],
  364. Y: [
  365. [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), pickerMaterial), [0, 0.6, 0]]
  366. ],
  367. Z: [
  368. [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), pickerMaterial), [0, 0, 0.6], [Math.PI / 2, 0, 0]]
  369. ],
  370. XYZ: [
  371. [new THREE.Mesh(new THREE.BoxBufferGeometry(0.4, 0.4, 0.4), pickerMaterial)]
  372. ]
  373. };
  374. this.setActivePlane = function (axis, eye) {
  375. var tempMatrix = new THREE.Matrix4();
  376. eye.applyMatrix4(tempMatrix.getInverse(tempMatrix.extractRotation(this.planes[ "XY" ].matrixWorld)));
  377. if (axis === "X") {
  378. this.activePlane = this.planes[ "XY" ];
  379. if (Math.abs(eye.y) > Math.abs(eye.z))
  380. this.activePlane = this.planes[ "XZ" ];
  381. }
  382. if (axis === "Y") {
  383. this.activePlane = this.planes[ "XY" ];
  384. if (Math.abs(eye.x) > Math.abs(eye.z))
  385. this.activePlane = this.planes[ "YZ" ];
  386. }
  387. if (axis === "Z") {
  388. this.activePlane = this.planes[ "XZ" ];
  389. if (Math.abs(eye.x) > Math.abs(eye.y))
  390. this.activePlane = this.planes[ "YZ" ];
  391. }
  392. if (axis === "XYZ")
  393. this.activePlane = this.planes[ "XYZE" ];
  394. };
  395. this.init();
  396. };
  397. THREE.TransformGizmoScale.prototype = Object.create(THREE.TransformGizmo.prototype);
  398. THREE.TransformGizmoScale.prototype.constructor = THREE.TransformGizmoScale;
  399. THREE.TransformControls = function (camera, domElement) {
  400. // TODO: Make non-uniform scale and rotate play nice in hierarchies
  401. // TODO: ADD RXYZ contol
  402. THREE.Object3D.call(this);
  403. domElement = (domElement !== undefined) ? domElement : document;
  404. this.body = undefined;
  405. this.object = undefined;
  406. this.visible = false;
  407. this.translationSnap = null;
  408. this.rotationSnap = null;
  409. this.space = "world";
  410. this.size = 1;
  411. this.axis = null;
  412. var scope = this;
  413. var _mode = "translate";
  414. var _dragging = false;
  415. var _plane = "XY";
  416. var _gizmo = {
  417. "translate": new THREE.TransformGizmoTranslate(),
  418. "rotate": new THREE.TransformGizmoRotate(),
  419. "scale": new THREE.TransformGizmoScale()
  420. };
  421. for (var type in _gizmo) {
  422. var gizmoObj = _gizmo[ type ];
  423. gizmoObj.visible = (type === _mode);
  424. this.add(gizmoObj);
  425. }
  426. var changeEvent = {type: "change"};
  427. var mouseDownEvent = {type: "mouseDown"};
  428. var mouseUpEvent = {type: "mouseUp", mode: _mode};
  429. var objectChangeEvent = {type: "objectChange"};
  430. var ray = new THREE.Raycaster();
  431. var pointerVector = new THREE.Vector2();
  432. var point = new THREE.Vector3();
  433. var offset = new THREE.Vector3();
  434. var rotation = new THREE.Vector3();
  435. var offsetRotation = new THREE.Vector3();
  436. var scale = 1;
  437. var lookAtMatrix = new THREE.Matrix4();
  438. var eye = new THREE.Vector3();
  439. var tempMatrix = new THREE.Matrix4();
  440. var tempVector = new THREE.Vector3();
  441. var tempQuaternion = new THREE.Quaternion();
  442. var unitX = new THREE.Vector3(1, 0, 0);
  443. var unitY = new THREE.Vector3(0, 1, 0);
  444. var unitZ = new THREE.Vector3(0, 0, 1);
  445. var quaternionXYZ = new THREE.Quaternion();
  446. var quaternionX = new THREE.Quaternion();
  447. var quaternionY = new THREE.Quaternion();
  448. var quaternionZ = new THREE.Quaternion();
  449. var quaternionE = new THREE.Quaternion();
  450. var oldPosition = new THREE.Vector3();
  451. var oldScale = new THREE.Vector3();
  452. var oldRotationMatrix = new THREE.Matrix4();
  453. var parentRotationMatrix = new THREE.Matrix4();
  454. var parentScale = new THREE.Vector3();
  455. var worldPosition = new THREE.Vector3();
  456. var worldRotation = new THREE.Euler();
  457. var worldRotationMatrix = new THREE.Matrix4();
  458. var camPosition = new THREE.Vector3();
  459. var camRotation = new THREE.Euler();
  460. domElement.addEventListener("mousedown", onPointerDown, false);
  461. domElement.addEventListener("touchstart", onPointerDown, false);
  462. domElement.addEventListener("mousemove", onPointerHover, false);
  463. domElement.addEventListener("touchmove", onPointerHover, false);
  464. domElement.addEventListener("mousemove", onPointerMove, false);
  465. domElement.addEventListener("touchmove", onPointerMove, false);
  466. domElement.addEventListener("mouseup", onPointerUp, false);
  467. domElement.addEventListener("mouseout", onPointerUp, false);
  468. domElement.addEventListener("touchend", onPointerUp, false);
  469. domElement.addEventListener("touchcancel", onPointerUp, false);
  470. domElement.addEventListener("touchleave", onPointerUp, false);
  471. this.dispose = function () {
  472. domElement.removeEventListener("mousedown", onPointerDown);
  473. domElement.removeEventListener("touchstart", onPointerDown);
  474. domElement.removeEventListener("mousemove", onPointerHover);
  475. domElement.removeEventListener("touchmove", onPointerHover);
  476. domElement.removeEventListener("mousemove", onPointerMove);
  477. domElement.removeEventListener("touchmove", onPointerMove);
  478. domElement.removeEventListener("mouseup", onPointerUp);
  479. domElement.removeEventListener("mouseout", onPointerUp);
  480. domElement.removeEventListener("touchend", onPointerUp);
  481. domElement.removeEventListener("touchcancel", onPointerUp);
  482. domElement.removeEventListener("touchleave", onPointerUp);
  483. };
  484. this.attach = function (object, body) {
  485. this.body = body;
  486. this.object = object;
  487. this.visible = true;
  488. this.update();
  489. };
  490. this.detach = function () {
  491. this.body = undefined;
  492. this.object = undefined;
  493. this.visible = false;
  494. this.axis = null;
  495. };
  496. this.getMode = function () {
  497. return _mode;
  498. };
  499. this.setMode = function (mode) {
  500. _mode = mode ? mode : _mode;
  501. if (_mode === "scale")
  502. scope.space = "local";
  503. for (var type in _gizmo)
  504. _gizmo[ type ].visible = (type === _mode);
  505. this.update();
  506. scope.dispatchEvent(changeEvent);
  507. };
  508. this.setTranslationSnap = function (translationSnap) {
  509. scope.translationSnap = translationSnap;
  510. };
  511. this.setRotationSnap = function (rotationSnap) {
  512. scope.rotationSnap = rotationSnap;
  513. };
  514. this.setSize = function (size) {
  515. scope.size = size;
  516. this.update();
  517. scope.dispatchEvent(changeEvent);
  518. };
  519. this.setSpace = function (space) {
  520. scope.space = space;
  521. this.update();
  522. scope.dispatchEvent(changeEvent);
  523. };
  524. this.update = function () {
  525. if (scope.object === undefined)
  526. return;
  527. scope.object.updateMatrixWorld();
  528. worldPosition.setFromMatrixPosition(scope.object.matrixWorld);
  529. worldRotation.setFromRotationMatrix(tempMatrix.extractRotation(scope.object.matrixWorld));
  530. camera.updateMatrixWorld();
  531. camPosition.setFromMatrixPosition(camera.matrixWorld);
  532. camRotation.setFromRotationMatrix(tempMatrix.extractRotation(camera.matrixWorld));
  533. scale = worldPosition.distanceTo(camPosition) / 6 * scope.size;
  534. this.position.copy(worldPosition);
  535. this.scale.set(scale, scale, scale);
  536. eye.copy(camPosition).sub(worldPosition).normalize();
  537. if (scope.space === "local") {
  538. _gizmo[ _mode ].update(worldRotation, eye);
  539. } else if (scope.space === "world") {
  540. _gizmo[ _mode ].update(new THREE.Euler(), eye);
  541. }
  542. _gizmo[ _mode ].highlight(scope.axis);
  543. };
  544. function onPointerHover(event) {
  545. if (scope.object === undefined || _dragging === true || (event.button !== undefined && event.button !== 0))
  546. return;
  547. var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
  548. var intersect = intersectObjects(pointer, _gizmo[ _mode ].pickers.children);
  549. var axis = null;
  550. if (intersect) {
  551. axis = intersect.object.name;
  552. event.preventDefault();
  553. }
  554. if (scope.axis !== axis) {
  555. scope.axis = axis;
  556. scope.update();
  557. scope.dispatchEvent(changeEvent);
  558. }
  559. }
  560. function onPointerDown(event) {
  561. if (scope.object === undefined || _dragging === true || (event.button !== undefined && event.button !== 0))
  562. return;
  563. var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
  564. if (pointer.button === 0 || pointer.button === undefined) {
  565. var intersect = intersectObjects(pointer, _gizmo[ _mode ].pickers.children);
  566. if (intersect) {
  567. event.preventDefault();
  568. event.stopPropagation();
  569. scope.dispatchEvent(mouseDownEvent);
  570. scope.axis = intersect.object.name;
  571. scope.update();
  572. eye.copy(camPosition).sub(worldPosition).normalize();
  573. _gizmo[ _mode ].setActivePlane(scope.axis, eye);
  574. var planeIntersect = intersectObjects(pointer, [_gizmo[ _mode ].activePlane]);
  575. if (planeIntersect) {
  576. oldPosition.copy(scope.object.position);
  577. oldScale.copy(scope.object.scale);
  578. oldRotationMatrix.extractRotation(scope.object.matrix);
  579. worldRotationMatrix.extractRotation(scope.object.matrixWorld);
  580. parentRotationMatrix.extractRotation(scope.object.parent.matrixWorld);
  581. parentScale.setFromMatrixScale(tempMatrix.getInverse(scope.object.parent.matrixWorld));
  582. offset.copy(planeIntersect.point);
  583. }
  584. }
  585. }
  586. _dragging = true;
  587. }
  588. function onPointerMove(event) {
  589. if (scope.object === undefined || scope.axis === null || _dragging === false || (event.button !== undefined && event.button !== 0))
  590. return;
  591. var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
  592. var planeIntersect = intersectObjects(pointer, [_gizmo[ _mode ].activePlane]);
  593. if (planeIntersect === false)
  594. return;
  595. event.preventDefault();
  596. event.stopPropagation();
  597. point.copy(planeIntersect.point);
  598. if (_mode === "translate") {
  599. point.sub(offset);
  600. point.multiply(parentScale);
  601. if (scope.space === "local") {
  602. point.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix));
  603. if (scope.axis.search("X") === -1)
  604. point.x = 0;
  605. if (scope.axis.search("Y") === -1)
  606. point.y = 0;
  607. if (scope.axis.search("Z") === -1)
  608. point.z = 0;
  609. point.applyMatrix4(oldRotationMatrix);
  610. scope.object.position.copy(oldPosition);
  611. scope.object.position.add(point);
  612. scope.body.position.copy(scope.object.position);
  613. }
  614. if (scope.space === "world" || scope.axis.search("XYZ") !== -1) {
  615. if (scope.axis.search("X") === -1)
  616. point.x = 0;
  617. if (scope.axis.search("Y") === -1)
  618. point.y = 0;
  619. if (scope.axis.search("Z") === -1)
  620. point.z = 0;
  621. point.applyMatrix4(tempMatrix.getInverse(parentRotationMatrix));
  622. scope.object.position.copy(oldPosition);
  623. scope.object.position.add(point);
  624. scope.body.position.copy(scope.object.position);
  625. }
  626. if (scope.translationSnap !== null) {
  627. if (scope.space === "local") {
  628. scope.object.position.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix));
  629. }
  630. if (scope.axis.search("X") !== -1)
  631. scope.object.position.x = Math.round(scope.object.position.x / scope.translationSnap) * scope.translationSnap;
  632. if (scope.axis.search("Y") !== -1)
  633. scope.object.position.y = Math.round(scope.object.position.y / scope.translationSnap) * scope.translationSnap;
  634. if (scope.axis.search("Z") !== -1)
  635. scope.object.position.z = Math.round(scope.object.position.z / scope.translationSnap) * scope.translationSnap;
  636. if (scope.space === "local") {
  637. scope.object.position.applyMatrix4(worldRotationMatrix);
  638. }
  639. }
  640. } else if (_mode === "scale") {
  641. point.sub(offset);
  642. point.multiply(parentScale);
  643. if (scope.space === "local") {
  644. if (scope.axis === "XYZ") {
  645. scale = 1 + ((point.y) / Math.max(oldScale.x, oldScale.y, oldScale.z));
  646. scope.object.scale.x = oldScale.x * scale;
  647. scope.object.scale.y = oldScale.y * scale;
  648. scope.object.scale.z = oldScale.z * scale;
  649. } else {
  650. point.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix));
  651. if (scope.axis === "X")
  652. scope.object.scale.x = oldScale.x * (1 + point.x / oldScale.x);
  653. if (scope.axis === "Y")
  654. scope.object.scale.y = oldScale.y * (1 + point.y / oldScale.y);
  655. if (scope.axis === "Z")
  656. scope.object.scale.z = oldScale.z * (1 + point.z / oldScale.z);
  657. }
  658. }
  659. } else if (_mode === "rotate") {
  660. point.sub(worldPosition);
  661. point.multiply(parentScale);
  662. tempVector.copy(offset).sub(worldPosition);
  663. tempVector.multiply(parentScale);
  664. if (scope.axis === "E") {
  665. point.applyMatrix4(tempMatrix.getInverse(lookAtMatrix));
  666. tempVector.applyMatrix4(tempMatrix.getInverse(lookAtMatrix));
  667. rotation.set(Math.atan2(point.z, point.y), Math.atan2(point.x, point.z), Math.atan2(point.y, point.x));
  668. offsetRotation.set(Math.atan2(tempVector.z, tempVector.y), Math.atan2(tempVector.x, tempVector.z), Math.atan2(tempVector.y, tempVector.x));
  669. tempQuaternion.setFromRotationMatrix(tempMatrix.getInverse(parentRotationMatrix));
  670. quaternionE.setFromAxisAngle(eye, rotation.z - offsetRotation.z);
  671. quaternionXYZ.setFromRotationMatrix(worldRotationMatrix);
  672. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionE);
  673. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionXYZ);
  674. scope.object.quaternion.copy(tempQuaternion);
  675. scope.body.quaternion.copy(tempQuaternion);
  676. } else if (scope.axis === "XYZE") {
  677. quaternionE.setFromEuler(point.clone().cross(tempVector).normalize()); // rotation axis
  678. tempQuaternion.setFromRotationMatrix(tempMatrix.getInverse(parentRotationMatrix));
  679. quaternionX.setFromAxisAngle(quaternionE, -point.clone().angleTo(tempVector));
  680. quaternionXYZ.setFromRotationMatrix(worldRotationMatrix);
  681. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionX);
  682. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionXYZ);
  683. scope.object.quaternion.copy(tempQuaternion);
  684. scope.body.quaternion.copy(tempQuaternion);
  685. } else if (scope.space === "local") {
  686. point.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix));
  687. tempVector.applyMatrix4(tempMatrix.getInverse(worldRotationMatrix));
  688. rotation.set(Math.atan2(point.z, point.y), Math.atan2(point.x, point.z), Math.atan2(point.y, point.x));
  689. offsetRotation.set(Math.atan2(tempVector.z, tempVector.y), Math.atan2(tempVector.x, tempVector.z), Math.atan2(tempVector.y, tempVector.x));
  690. quaternionXYZ.setFromRotationMatrix(oldRotationMatrix);
  691. if (scope.rotationSnap !== null) {
  692. quaternionX.setFromAxisAngle(unitX, Math.round((rotation.x - offsetRotation.x) / scope.rotationSnap) * scope.rotationSnap);
  693. quaternionY.setFromAxisAngle(unitY, Math.round((rotation.y - offsetRotation.y) / scope.rotationSnap) * scope.rotationSnap);
  694. quaternionZ.setFromAxisAngle(unitZ, Math.round((rotation.z - offsetRotation.z) / scope.rotationSnap) * scope.rotationSnap);
  695. } else {
  696. quaternionX.setFromAxisAngle(unitX, rotation.x - offsetRotation.x);
  697. quaternionY.setFromAxisAngle(unitY, rotation.y - offsetRotation.y);
  698. quaternionZ.setFromAxisAngle(unitZ, rotation.z - offsetRotation.z);
  699. }
  700. if (scope.axis === "X")
  701. quaternionXYZ.multiplyQuaternions(quaternionXYZ, quaternionX);
  702. if (scope.axis === "Y")
  703. quaternionXYZ.multiplyQuaternions(quaternionXYZ, quaternionY);
  704. if (scope.axis === "Z")
  705. quaternionXYZ.multiplyQuaternions(quaternionXYZ, quaternionZ);
  706. scope.object.quaternion.copy(quaternionXYZ);
  707. scope.body.quaternion.copy(quaternionXYZ);
  708. } else if (scope.space === "world") {
  709. rotation.set(Math.atan2(point.z, point.y), Math.atan2(point.x, point.z), Math.atan2(point.y, point.x));
  710. offsetRotation.set(Math.atan2(tempVector.z, tempVector.y), Math.atan2(tempVector.x, tempVector.z), Math.atan2(tempVector.y, tempVector.x));
  711. tempQuaternion.setFromRotationMatrix(tempMatrix.getInverse(parentRotationMatrix));
  712. if (scope.rotationSnap !== null) {
  713. quaternionX.setFromAxisAngle(unitX, Math.round((rotation.x - offsetRotation.x) / scope.rotationSnap) * scope.rotationSnap);
  714. quaternionY.setFromAxisAngle(unitY, Math.round((rotation.y - offsetRotation.y) / scope.rotationSnap) * scope.rotationSnap);
  715. quaternionZ.setFromAxisAngle(unitZ, Math.round((rotation.z - offsetRotation.z) / scope.rotationSnap) * scope.rotationSnap);
  716. } else {
  717. quaternionX.setFromAxisAngle(unitX, rotation.x - offsetRotation.x);
  718. quaternionY.setFromAxisAngle(unitY, rotation.y - offsetRotation.y);
  719. quaternionZ.setFromAxisAngle(unitZ, rotation.z - offsetRotation.z);
  720. }
  721. quaternionXYZ.setFromRotationMatrix(worldRotationMatrix);
  722. if (scope.axis === "X")
  723. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionX);
  724. if (scope.axis === "Y")
  725. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionY);
  726. if (scope.axis === "Z")
  727. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionZ);
  728. tempQuaternion.multiplyQuaternions(tempQuaternion, quaternionXYZ);
  729. scope.object.quaternion.copy(tempQuaternion);
  730. scope.body.quaternion.copy(tempQuaternion);
  731. }
  732. }
  733. scope.update();
  734. scope.dispatchEvent(changeEvent);
  735. scope.dispatchEvent(objectChangeEvent);
  736. }
  737. function onPointerUp(event) {
  738. event.preventDefault(); // Prevent MouseEvent on mobile
  739. if (event.button !== undefined && event.button !== 0)
  740. return;
  741. if (_dragging && (scope.axis !== null)) {
  742. mouseUpEvent.mode = _mode;
  743. scope.dispatchEvent(mouseUpEvent);
  744. }
  745. _dragging = false;
  746. if (event instanceof TouchEvent) {
  747. // Force "rollover"
  748. scope.axis = null;
  749. scope.update();
  750. scope.dispatchEvent(changeEvent);
  751. } else {
  752. onPointerHover(event);
  753. }
  754. }
  755. function intersectObjects(pointer, objects) {
  756. var rect = domElement.getBoundingClientRect();
  757. var x = (pointer.clientX - rect.left) / rect.width;
  758. var y = (pointer.clientY - rect.top) / rect.height;
  759. pointerVector.set((x * 2) - 1, -(y * 2) + 1);
  760. ray.setFromCamera(pointerVector, camera);
  761. var intersections = ray.intersectObjects(objects, true);
  762. return intersections[ 0 ] ? intersections[ 0 ] : false;
  763. }
  764. };
  765. THREE.TransformControls.prototype = Object.create(THREE.Object3D.prototype);
  766. THREE.TransformControls.prototype.constructor = THREE.TransformControls;
  767. }());