cubelets.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  1. /*
  2. CUBELETS
  3. Faces are mapped in a clockwise spiral from Front to Back:
  4. Back
  5. 5
  6. -----------
  7. / Up /|
  8. / 1 / |
  9. ----------- Right
  10. | | 2
  11. Left | Front | .
  12. 4 | 0 | /
  13. | |/
  14. -----------
  15. Down
  16. 3
  17. The faces[] Array is mapped to names for convenience:
  18. this.faces[ 0 ] === this.front
  19. this.faces[ 1 ] === this.up
  20. this.faces[ 2 ] === this.right
  21. this.faces[ 3 ] === this.down
  22. this.faces[ 4 ] === this.left
  23. this.faces[ 5 ] === this.back
  24. Each Cubelet has an Index which is assigned during Cube creation
  25. and an Address which changes as the Cubelet changes location.
  26. Additionally an AddressX, AddressY, and AddressZ are calculated
  27. from the Address and represent the Cubelet's location relative
  28. to the Cube's core with integer values ranging from -1 to +1.
  29. For an overview of the Cubelet's data from the browser's console:
  30. this.inspect()
  31. */
  32. export function Cubelet( cube, id, colors ){
  33. // Our Cube can directly address its Cubelet children,
  34. // only fair the Cubelet can address their parent Cube!
  35. this.cube = cube
  36. // Our Cubelet's ID is its unique number on the Cube.
  37. // Each Cube has Cubletes numbered 0 through 26.
  38. // Even if we're debugging (and not attached to an actual Cube)
  39. // we need an ID number for later below
  40. // when we derive positions and rotations for the Cubelet faces.
  41. this.id = id || 0
  42. // Our Cubelet's address is its current location on the Cube.
  43. // When the Cubelet is initialized its ID and address are the same.
  44. // This method will also set the X, Y, and Z components of the
  45. // Cubelet's address on the Cube.
  46. this.setAddress( this.id )
  47. // We're going to build Cubelets that are 140 pixels square.
  48. // Yup. This size is hardwired in Cube.
  49. // It is also hard-wired into the CSS, but we can't simply
  50. // grab the style.getBoundingClientRect() value because
  51. // that's a 2D measurement -- doesn't account for pos and rot.
  52. this.size = cube.cubeletSize || 140
  53. // Now we can find our Cubelet's X, Y, and Z position in space.
  54. // We only need this momentarily to create our Object3D so
  55. // there's no need to attach these properties to our Cubelet object.
  56. var
  57. x = this.addressX * this.size,
  58. y = this.addressY * this.size,
  59. z = this.addressZ * this.size
  60. // For convenience here are maps for rotating and positioning
  61. // the Cubelet face wall into place.
  62. var
  63. half = this.size / 2,
  64. rotations = [
  65. [ 0, 0, 0 ],// Front
  66. [ -90, 0, 0 ],// Up
  67. [ 0, 90, 0 ],// Right
  68. [ 90, 0, 0 ],// Down
  69. [ 0, -90, 0 ],// Left
  70. [ 0, 180, 0 ] // Back
  71. ],
  72. positions = [
  73. [ 0, 0, half ],// Front
  74. [ 0, -half, 0 ],// Up
  75. [ half, 0, 0 ],// Right
  76. [ 0, half, 0 ],// Down
  77. [ -half, 0, 0 ],// Left
  78. [ 0, 0, -half ] // Back
  79. ]
  80. // Our anchor only achieves rotation during a tween animation.
  81. // It is then immediately reset to rotation( 0, 0, 0 )
  82. // (and its rotation information is applied to the wrapper at that moment)
  83. // and thus can be used as a reliable anchor in space repeatedly.
  84. ///////VWF_VIEW//////
  85. // this.anchor = new THREE.Object3D()
  86. // this.anchor.name = 'anchor-' + this.id
  87. // if( this.cube ) this.cube.threeObject.add( this.anchor )
  88. // else scene.add( this.anchor )
  89. //
  90. //
  91. //
  92. // if( erno.renderMode === 'css' ){
  93. // var domElement = document.createElement( 'div' )
  94. // domElement.classList.add( 'cubelet' )
  95. // domElement.classList.add( 'cubeletId-'+ this.id )
  96. // this.wrapper = new THREE.CSS3DObject( domElement )
  97. // }
  98. // else if( erno.renderMode === 'svg' ){
  99. // this.wrapper = new THREE.Object3D()
  100. // // Create this Cubelet's plastic shell.
  101. // this.plastic = new THREE.Mesh(
  102. // new THREE.CubeGeometry( cube.cubeletSize, cube.cubeletSize, cube.cubeletSize ),
  103. // new THREE.MeshBasicMaterial({ color: 0xFFFFFF, vertexColors: THREE.FaceColors })
  104. // )
  105. // this.plastic.position.set( x, y, z )
  106. // this.wrapper.add( this.plastic )
  107. // // Wireframe!
  108. // this.wireframe = new THREE.Mesh(
  109. // new THREE.CubeGeometry( cube.cubeletSize, cube.cubeletSize, cube.cubeletSize ),
  110. // new THREE.MeshBasicMaterial({ color: 0x00CCFF, wireframe: true })
  111. // )
  112. // this.wireframe.position.set( x, y, z )
  113. // this.wrapper.add( this.wireframe )
  114. // }
  115. // this.wrapper.name = 'wrapper-' + this.id
  116. // this.wrapper.position.set( x, y, z )
  117. // this.anchor.add( this.wrapper )
  118. //////////
  119. //@@@ QUICK HACK TO FORCE A CUBE TO APPEAR FOR HIT TESTING WITH RAYCASTING!!!
  120. /*if( this.plastic === undefined ){
  121. this.plastic = new THREE.Mesh(
  122. new THREE.CubeGeometry( cube.cubeletSize, cube.cubeletSize, cube.cubeletSize ),
  123. new THREE.MeshBasicMaterial({ color: 0xFF00FF, vertexColors: THREE.FaceColors })
  124. )
  125. this.plastic.position.set( x, y, z )
  126. this.wrapper.add( this.plastic )
  127. }*/
  128. // We're about to loop through our colors[] Array
  129. // to build the six faces of our Cubelet.
  130. // Here's our overhead for that:
  131. var extrovertedFaces = 0
  132. if( colors === undefined ) colors = [ W, O, , , G, ]
  133. this.faces = []
  134. // Now let's map one color per side based on colors[].
  135. // Undefined values are allowed (and anticipated).
  136. // We need to loop through the colors[] Array "manually"
  137. // because Array.forEach() would skip the undefined entries.
  138. for( var i = 0; i < 6; i ++ ){
  139. // Before we create our face's THREE object
  140. // we need to know where it should be positioned and rotated.
  141. // (This is based on our above positions and rotations map.)
  142. var
  143. color = colors[ i ] || COLORLESS
  144. // Each face is an object and keeps track of its original ID number
  145. // (which is important because its address will change with each rotation)
  146. // its current color, and so on.
  147. this.faces[ i ] = {}
  148. this.faces[ i ].id = i
  149. this.faces[ i ].color = color
  150. // We're going to keep track of what face was what at the moment of initialization,
  151. // mostly for solving purposes.
  152. // This is particularly useful for Striegel's solver
  153. // which requires an UP normal.
  154. this.faces[ i ].normal = Direction.getNameById( i )
  155. ////////VWF_VIEW//////
  156. // if( erno.renderMode === 'css' ){
  157. // // FACE CONTAINER.
  158. // // This face of our Cubelet needs a DOM element for all the
  159. // // related DOM elements to be attached to.
  160. // var faceElement = document.createElement( 'div' )
  161. // faceElement.classList.add( 'face' )
  162. // faceElement.classList.add( 'face'+ Direction.getNameById( i ).capitalize() )
  163. // this.wrapper.element.appendChild( faceElement )
  164. // // WIREFRAME.
  165. // var wireframeElement = document.createElement( 'div' )
  166. // wireframeElement.classList.add( 'wireframe' )
  167. // faceElement.appendChild( wireframeElement )
  168. // // CUBELET ID.
  169. // // For debugging we want the ability to display this Cubelet's ID number
  170. // // with an underline (to make numbers like 6 and 9 legible upside-down).
  171. // var idElement = document.createElement( 'div' )
  172. // idElement.classList.add( 'id' )
  173. // faceElement.appendChild( idElement )
  174. // var underlineElement = document.createElement( 'span' )
  175. // underlineElement.classList.add( 'underline' )
  176. // underlineElement.innerText = this.id
  177. // idElement.appendChild( underlineElement )
  178. // }
  179. // // INTROVERTED FACES.
  180. // // If this face has no color sticker then it must be interior to the Cube.
  181. // // That means in a normal state (no twisting happening) it is entirely hidden.
  182. // if( color === COLORLESS ){
  183. // if( erno.renderMode === 'css' ) faceElement.classList.add( 'faceIntroverted' )
  184. // else {
  185. // this.plastic.geometry.faces[ i ].color.setHex( 0x000000 )
  186. // this.plastic.geometry.colorsNeedUpdate = true
  187. // }
  188. // }
  189. // // EXTROVERTED FACES.
  190. // // But if this face does have a color then we need to
  191. // // create a sticker with that color
  192. // // and also allow text to be placed on it.
  193. // else {
  194. // // We're going to use the number of exposed sides
  195. // // to determine below what 'type' of Cubelet this is:
  196. // // Core, Center, Edge, or Corner.
  197. // extrovertedFaces ++
  198. // if( erno.renderMode === 'css' ){
  199. // faceElement.classList.add( 'faceExtroverted' )
  200. // // STICKER.
  201. // // You know, the color part that makes the Cube
  202. // // the most frustrating toy ever.
  203. // var stickerElement = document.createElement( 'div' )
  204. // stickerElement.classList.add( 'sticker' )
  205. // stickerElement.style.backgroundColor = color.hex
  206. // faceElement.appendChild( stickerElement )
  207. // // TEXT.
  208. // // One character per face, mostly for our branding.
  209. // var textElement = document.createElement( 'div' )
  210. // textElement.classList.add( 'text' )
  211. // textElement.innerText = i
  212. // this.faces[ i ].text = textElement
  213. // faceElement.appendChild( textElement )
  214. // }
  215. // else {
  216. // /*
  217. // var sticker = new THREE.Mesh(
  218. // new THREE.PlaneGeometry( cube.cubeletSize, cube.cubeletSize ),
  219. // new THREE.MeshBasicMaterial({ color: color })
  220. // )
  221. // sticker.material.opacity = 0.7
  222. // sticker.overdraw = true
  223. // sticker.position.set( faceX, faceY, faceZ )
  224. // sticker.rotation.set( faceXR, faceYR, faceZR )
  225. // this.faces[ i ].sticker = sticker
  226. // this.wrapper.add( this.faces[ i ].sticker )
  227. // */
  228. // //console.log(colorNameToHex( color ))
  229. // //console.log(' ' )
  230. // //console.log(this.plastic.geometry.faces[ i ].color)
  231. // //console.log(colorNameToDecimal( color ))
  232. // //var j = [ 1, 0, 2, 3, 4, 5 ][ i ]
  233. // var j = [ 3,3,3,3,3,3 ][ i ]
  234. // this.plastic.geometry.faces[ j ].color.setHex( colorNameToDecimal( color ))
  235. // //this.plastic.geometry.faces[ i ].color.setHex( 0xFF00FF )
  236. // this.plastic.geometry.colorsNeedUpdate = true
  237. // //console.log(this.plastic.geometry.faces[ i ].color)
  238. // //console.log(' ' )
  239. // }
  240. // }
  241. /////////////
  242. }
  243. // Now that we've run through our colors[] Array
  244. // and counted the number of extroverted sides
  245. // we can determine what 'type' of Cubelet this is.
  246. this.type = [
  247. 'core',
  248. 'center',
  249. 'edge',
  250. 'corner'
  251. ][ extrovertedFaces ]
  252. // Mapping the Cubelet will setup all of our convenience shortcuts
  253. // like "this.front.color" and "this.left.text" for example.
  254. this.map()
  255. // If this happens to be our logo-bearing Cubelet
  256. // we had better attach the logo to it!
  257. if( this.front.color && this.front.color.name === 'white' && this.type === 'center' ){
  258. ////VWF_VIEW////
  259. //if( erno.renderMode === 'css' ) stickerElement.classList.add( 'stickerLogo' )
  260. ////
  261. }
  262. // We need to know if we're "engaged" on an axis
  263. // which at first seems indentical to isTweening,
  264. // until you consider partial rotations.
  265. this.isTweening = true
  266. this.isEngagedX = false
  267. this.isEngagedY = false
  268. this.isEngagedZ = false
  269. // Remember our separation of state code and visual code?
  270. // Well here's some slightly (though not entirely!) redundant
  271. // rotation tracking.
  272. // It's actually this that makes partial rotations possible...
  273. this.x = this.xPrevious = 0
  274. this.y = this.yPrevious = 0
  275. this.z = this.zPrevious = 0
  276. // These will perform their actions, of course,
  277. // but also setup their own boolean toggles.
  278. ////VWF_VIEW
  279. // this.show()
  280. // this.showPlastics()
  281. // this.showIntroverts()
  282. // this.showStickers()
  283. // this.hideIds()
  284. // this.hideTexts()
  285. // this.hideWireframes()
  286. ///////
  287. // During a rotation animation this Cubelet marks itself as
  288. // this.isTweening = true.
  289. // Very useful. Let's try it out.
  290. this.isTweening = false
  291. // Some fun tweenable properties.
  292. this.opacity = 1
  293. this.radius = 0
  294. }
  295. globalThis.setupTasks = globalThis.setupTasks || []
  296. globalThis.setupTasks.push( function(){
  297. // Let's add some functionality to Cubelet's prototype
  298. // via the augment() function from Skip.js.
  299. // By adding to Cubelet's prototype and not the Cubelet constructor
  300. // we're keeping instances of Cubelet super clean and light.
  301. globalThis.augment( Cubelet, {
  302. // Convience accessors for the Cubelet's faces.
  303. // What color is the left face? this.left() !!
  304. map: function(){
  305. this.front = this.faces[ 0 ]
  306. this.up = this.faces[ 1 ]
  307. this.right = this.faces[ 2 ]
  308. this.down = this.faces[ 3 ]
  309. this.left = this.faces[ 4 ]
  310. this.back = this.faces[ 5 ]
  311. this.colors =
  312. ( this.faces[ 0 ].color ? this.faces[ 0 ].color.initial : '-' ) +
  313. ( this.faces[ 1 ].color ? this.faces[ 1 ].color.initial : '-' ) +
  314. ( this.faces[ 2 ].color ? this.faces[ 2 ].color.initial : '-' ) +
  315. ( this.faces[ 3 ].color ? this.faces[ 3 ].color.initial : '-' ) +
  316. ( this.faces[ 4 ].color ? this.faces[ 4 ].color.initial : '-' ) +
  317. ( this.faces[ 5 ].color ? this.faces[ 5 ].color.initial : '-' )
  318. },
  319. // Aside from initialization this function will be called
  320. // by the Cube during remapping.
  321. // The raw address is an integer from 0 through 26
  322. // mapped to the Cube in the same fashion as this.id.
  323. // The X, Y, and Z components each range from -1 through +1
  324. // where (0, 0, 0) is the Cube's core.
  325. setAddress: function( address ){
  326. this.address = address || 0
  327. this.addressX = address.modulo( 3 ).subtract( 1 )
  328. this.addressY = address.modulo( 9 ).divide( 3 ).roundDown().subtract( 1 ) * -1
  329. this.addressZ = address.divide( 9 ).roundDown().subtract( 1 ) * -1
  330. },
  331. // Full inspection of the Cublet's faces
  332. // using the convenience accessors from above.
  333. inspect: function( face ){
  334. if( face !== undefined ){
  335. // Just a particular face's color -- called by Slice's inspector.
  336. return this[ face ].color || '!'
  337. }
  338. else {
  339. // Full on ASCII-art inspection mode -- with console colors!
  340. var
  341. that = this,
  342. id = this.id,
  343. address = this.address,
  344. type = this.type,
  345. color = this.cube.color,
  346. LEFT = 0,
  347. CENTER = 1,
  348. getColorName = function( face, justification, minimumLength ){
  349. var colorName = that[ face ].color.name.toUpperCase()
  350. if( justification !== undefined && minimumLength !== undefined ){
  351. if( justification === CENTER ) colorName = colorName.justifyCenter( minimumLength )
  352. else if( justification === LEFT ) colorName = colorName.justifyLeft( minimumLength )
  353. }
  354. return colorName
  355. }
  356. if( id < 10 ) id = '0' + id
  357. if( address < 10 ) address = '0' + address
  358. console.log(
  359. '\n ID '+ id +
  360. '\n Type '+ type.toUpperCase() +'\n'+
  361. '\n Address '+ address +
  362. '\n Address X '+ this.addressX.toSignedString() +
  363. '\n Address Y '+ this.addressY.toSignedString() +
  364. '\n Address Z '+ this.addressZ.toSignedString() +'\n'+
  365. '\n Engaged X '+ this.isEngagedX +
  366. '\n Engaged Y '+ this.isEngagedY +
  367. '\n Engaged Z '+ this.isEngagedZ +
  368. '\n Tweening '+ this.isTweening +'\n'+
  369. '\n%c 0 Front '+ getColorName( 'front', LEFT, 7 ) +'%c'+
  370. '\n%c 1 Up '+ getColorName( 'up', LEFT, 7 ) +'%c'+
  371. '\n%c 2 Right '+ getColorName( 'right', LEFT, 7 ) +'%c'+
  372. '\n%c 3 Down '+ getColorName( 'down', LEFT, 7 ) +'%c'+
  373. '\n%c 4 Left '+ getColorName( 'left', LEFT, 7 ) +'%c'+
  374. '\n%c 5 Back '+ getColorName( 'back', LEFT, 7 ) +'%c\n' +
  375. '\n ----------- %cback%c'+
  376. '\n / %cup%c /| %c5%c'+
  377. '\n / %c1%c / | %c'+ getColorName( 'back' ) +'%c'+
  378. '\n /%c'+ getColorName( 'up', CENTER, 11 ) +'%c/ |'+
  379. '\n %cleft%c ----------- %cright%c'+
  380. '\n %c4%c | | %c2%c'+
  381. '\n%c'+ getColorName( 'left', CENTER, 8 ) +'%c | %cfront%c | %c'+ getColorName( 'right' ) +'%c'+
  382. '\n | %c0%c | /'+
  383. '\n |%c'+ getColorName( 'front', CENTER, 11 ) +'%c| /'+
  384. '\n | |/'+
  385. '\n -----------'+
  386. '\n %cdown%c'+
  387. '\n %c3%c'+
  388. '\n %c'+ getColorName( 'down', CENTER, 11 ) +'%c\n',
  389. this.front.color.styleB, '',
  390. this.up.color.styleB, '',
  391. this.right.color.styleB, '',
  392. this.down.color.styleB, '',
  393. this.left.color.styleB, '',
  394. this.back.color.styleB, '',
  395. this.back.color.styleF, '',
  396. this.up.color.styleF, '',
  397. this.back.color.styleF, '',
  398. this.up.color.styleF, '',
  399. this.back.color.styleF, '',
  400. this.up.color.styleF, '',
  401. this.left.color.styleF, '',
  402. this.right.color.styleF, '',
  403. this.left.color.styleF, '',
  404. this.right.color.styleF, '',
  405. this.left.color.styleF, '',
  406. this.front.color.styleF, '',
  407. this.right.color.styleF, '',
  408. this.front.color.styleF, '',
  409. this.front.color.styleF, '',
  410. this.down.color.styleF, '',
  411. this.down.color.styleF, '',
  412. this.down.color.styleF, ''
  413. )
  414. }
  415. },
  416. // Does this Cubelet contain a certain color?
  417. // If so, return a String decribing what face that color is on.
  418. // Otherwise return false.
  419. hasColor: function( color ){
  420. var i, face
  421. for( i = 0; i < 6; i ++ ){
  422. if( this.faces[ i ].color === color ){
  423. face = i
  424. break
  425. }
  426. }
  427. if( face !== undefined ){
  428. return [
  429. 'front',
  430. 'up',
  431. 'right',
  432. 'down',
  433. 'left',
  434. 'back'
  435. ][ face ]
  436. }
  437. else return false
  438. },
  439. // Similar to above, but accepts an arbitrary number of colors.
  440. // This function implies AND rather than OR, XOR, etc.
  441. hasColors: function(){
  442. var
  443. cubelet = this,
  444. result = true,
  445. colors = Array.prototype.slice.call( arguments )
  446. colors.forEach( function( color ){
  447. result = result && !!cubelet.hasColor( color )
  448. })
  449. return result
  450. },
  451. // We can rotate this Cublet on the X, Y, and Z axes
  452. // both clockwise and anticlockwise.
  453. rotate: function( rotation, degrees, cubeCallback, local ){
  454. var
  455. cubelet = this,
  456. cube = this.cube,
  457. xTarget = 0,
  458. yTarget = 0,
  459. zTarget = 0,
  460. rotationUpperCase = rotation.toUpperCase(),
  461. threshold = 0.001
  462. // We need to signal to the world that we cannot accept more rotation() commands.
  463. // This will also cause all Groups (and Slices) containing this Cubelet
  464. // to refuse all twist() commands until further notice.
  465. this.isTweening = true
  466. // Logically rotating our Cubelet is a matter of swapping the order
  467. // of the faces in the this.faces Array. The order is interpreted as:
  468. // Front, Up, Right, Down, Left, Back.
  469. if( rotationUpperCase === 'X' ){
  470. cubelet.isEngagedX = true
  471. if( rotation === 'X' ) xTarget = degrees
  472. else xTarget = -degrees
  473. }
  474. else if( rotationUpperCase === 'Y' ){
  475. cubelet.isEngagedY = true
  476. if( rotation === 'Y' ) yTarget = degrees
  477. else yTarget = -degrees
  478. }
  479. else if( rotationUpperCase === 'Z' ){
  480. cubelet.isEngagedZ = true
  481. if( rotation === 'Z' ) zTarget = degrees
  482. else zTarget = -degrees
  483. }
  484. // At every steps let's try to keep our values tidy.
  485. this.x += xTarget.round()
  486. this.y += yTarget.round()
  487. this.z += zTarget.round()
  488. // Our Cube's twistDuration is the amount of time (in miliseconds)
  489. // that it should take to rotate 90 dgrees.
  490. // We're going to scale that to match whatever number of degrees we're actually rotating:
  491. let twistDurationScaled = 0.5
  492. // var
  493. // twistDuration = this.cube !== undefined ? this.cube.twistDuration : SECOND,
  494. // twistDurationScaled = Math.max(degrees.absolute().scale( 0, 90, 0, twistDuration ), 250)
  495. // And now for the rotation tween itself...
  496. // It feels very wrong to me that we're going to invert the coordinate space here
  497. // but that's how the cookie crumbles. Sorry.
  498. if(!local){
  499. this.cube.kernel.callMethod(this.nodeID, "rotateCubelet", [
  500. [
  501. -xTarget,
  502. -yTarget,
  503. -zTarget
  504. ], twistDurationScaled, cubeCallback])
  505. } else {
  506. this.cube.remap(this.id, cubeCallback);
  507. //this.isTweening = false
  508. }
  509. // new TWEEN.Tween( this.anchor.rotation )
  510. // .to({
  511. // x: -xTarget.degreesToRadians(),
  512. // y: -yTarget.degreesToRadians(),
  513. // z: -zTarget.degreesToRadians()
  514. // }, twistDurationScaled )
  515. // .easing( TWEEN.Easing.Quartic.Out )
  516. // .start()
  517. // .onComplete( function(){
  518. // // What dark voodoo is this?
  519. // // Calling window.render() within a tween onComplete()?
  520. // // I noticed that when switching between tabs,
  521. // // or after putting the machine to sleep then waking it,
  522. // // the tweened bits would be totally f'd
  523. // // yet their X, Y, Z rotation values were as expected.
  524. // // Calling window.render() is a dirty way to update
  525. // // all of the *matrices* and keeps the world tidy!
  526. // render()
  527. // // We need to reset our anchor's rotation to ( 0, 0, 0 )
  528. // // so that we never lose our anchor's orientation relative to the cube itself.
  529. // // First thing to do is apply our anchor's matrix
  530. // // to the matrix of our visual wrapper:
  531. // cubelet.wrapper.applyMatrix( cubelet.anchor.matrix )
  532. // // And now that we've retained that rotation information
  533. // // we can safely reset the anchor's rotation:
  534. // cubelet.anchor.rotation.set( 0, 0, 0 )
  535. // // // Here's some complexity.
  536. // // // We need to support partial rotations of arbitrary degrees
  537. // // // yet ensure our internal model is always in a valid state.
  538. // // // This means only remapping the Cubelet when it makes sense
  539. // // // and also remapping the Cube if this Cubelet is allowed to do so.
  540. // // var
  541. // // xRemaps = cubelet.x.divide( 90 ).round()
  542. // // .subtract( cubelet.xPrevious.divide( 90 ).round() )
  543. // // .absolute(),
  544. // // yRemaps = cubelet.y.divide( 90 ).round()
  545. // // .subtract( cubelet.yPrevious.divide( 90 ).round() )
  546. // // .absolute(),
  547. // // zRemaps = cubelet.z.divide( 90 ).round()
  548. // // .subtract( cubelet.zPrevious.divide( 90 ).round() )
  549. // // .absolute()
  550. // // if( erno.verbosity >= 0.9 ){
  551. // // console.log( 'Cublet #'+ ( cubelet.id < 10 ? '0'+ cubelet.id : cubelet.id ),
  552. // // ' | xRemaps:', xRemaps, ' yRemaps:', yRemaps, ' zRemaps:', zRemaps,
  553. // // ' | xPrev:', cubelet.xPrevious, ' x:', cubelet.x,
  554. // // ' | yPrev:', cubelet.yPrevious, ' y:', cubelet.y,
  555. // // ' | zPrev:', cubelet.zPrevious, ' z:', cubelet.z )
  556. // // }
  557. // // if( xRemaps ){
  558. // // while( xRemaps -- ){
  559. // // if( cubelet.x < cubelet.xPrevious ) cubelet.faces = [ cubelet.up, cubelet.back, cubelet.right, cubelet.front, cubelet.left, cubelet.down ]
  560. // // else cubelet.faces = [ cubelet.down, cubelet.front, cubelet.right, cubelet.back, cubelet.left, cubelet.up ]
  561. // // cubelet.map()
  562. // // if( cubeCallback !== undefined ){
  563. // // cubeCallback( cubelet.cube.cubelets.slice())
  564. // // cubelet.cube.map()
  565. // // }
  566. // // }
  567. // // cubelet.xPrevious = cubelet.x
  568. // // }
  569. // // if( cubelet.x.modulo( 90 ).absolute() < threshold ){
  570. // // cubelet.x = 0
  571. // // cubelet.xPrevious = cubelet.x
  572. // // cubelet.isEngagedX = false
  573. // // }
  574. // // if( yRemaps ){
  575. // // while( yRemaps -- ){
  576. // // if( cubelet.y < cubelet.yPrevious ) cubelet.faces = [ cubelet.left, cubelet.up, cubelet.front, cubelet.down, cubelet.back, cubelet.right ]
  577. // // else cubelet.faces = [ cubelet.right, cubelet.up, cubelet.back, cubelet.down, cubelet.front, cubelet.left ]
  578. // // cubelet.map()
  579. // // if( cubeCallback !== undefined ){
  580. // // cubeCallback( cubelet.cube.cubelets.slice())
  581. // // cubelet.cube.map()
  582. // // }
  583. // // }
  584. // // cubelet.yPrevious = cubelet.y
  585. // // }
  586. // // if( cubelet.y.modulo( 90 ).absolute() < threshold ){
  587. // // cubelet.y = 0
  588. // // cubelet.yPrevious = cubelet.y
  589. // // cubelet.isEngagedY = false
  590. // // }
  591. // // if( zRemaps ){
  592. // // while( zRemaps -- ){
  593. // // if( cubelet.z < cubelet.zPrevious ) cubelet.faces = [ cubelet.front, cubelet.right, cubelet.down, cubelet.left, cubelet.up, cubelet.back ]
  594. // // else cubelet.faces = [ cubelet.front, cubelet.left, cubelet.up, cubelet.right, cubelet.down, cubelet.back ]
  595. // // cubelet.map()
  596. // // if( cubeCallback !== undefined ){
  597. // // cubeCallback( cubelet.cube.cubelets.slice())
  598. // // cubelet.cube.map()
  599. // // }
  600. // // }
  601. // // cubelet.zPrevious = cubelet.z
  602. // // }
  603. // // if( cubelet.z.modulo( 90 ).absolute() < threshold ){
  604. // // cubelet.z = 0
  605. // // cubelet.zPrevious = cubelet.z
  606. // // cubelet.isEngagedZ = false
  607. // // }
  608. // // // Phew! Now we can turn off the tweening flag.
  609. // // cubelet.isTweening = false
  610. // })
  611. },
  612. // Visual switches.
  613. show: function(){
  614. $( '.cubeletId-'+ this.id ).show()
  615. this.showing = true
  616. },
  617. hide: function(){
  618. $( '.cubeletId-'+ this.id ).hide()
  619. this.showing = false
  620. },
  621. showPlastics: function(){
  622. if( erno.renderMode === 'css' ) $( '.cubeletId-'+ this.id +' .face' ).removeClass( 'faceTransparent' )
  623. else this.plastic.material.opacity = 1
  624. this.showingPlastics = true
  625. },
  626. hidePlastics: function(){
  627. if( erno.renderMode === 'css' ) $( '.cubeletId-'+ this.id +' .face' ).addClass( 'faceTransparent' )
  628. else this.plastic.material.opacity = 0
  629. this.showingPlastics = false
  630. },
  631. showExtroverts: function(){
  632. $( '.cubeletId-'+ this.id + ' .faceExtroverted' ).show()
  633. this.showingExtroverts = true
  634. },
  635. hideExtroverts: function(){
  636. $( '.cubeletId-'+ this.id + ' .faceExtroverted' ).hide()
  637. this.showingExtroverts = false
  638. },
  639. showIntroverts: function(){
  640. $( '.cubeletId-'+ this.id + ' .faceIntroverted' ).show()
  641. this.showingIntroverts = true
  642. },
  643. hideIntroverts: function(){
  644. $( '.cubeletId-'+ this.id + ' .faceIntroverted' ).hide()
  645. this.showingIntroverts = false
  646. },
  647. showStickers: function(){
  648. if( erno.renderMode === 'css' ) $( '.cubeletId-'+ this.id + ' .sticker' ).show()
  649. else this.faces.forEach( function( face ){
  650. if( face.sticker ) face.sticker.material.opacity = 1
  651. })
  652. this.showingStickers = true
  653. },
  654. hideStickers: function(){
  655. if( erno.renderMode === 'css' ) $( '.cubeletId-'+ this.id + ' .sticker' ).hide()
  656. else this.faces.forEach( function( face ){
  657. if( face.sticker ) face.sticker.material.opacity = 0
  658. })
  659. this.showingStickers = false
  660. },
  661. showWireframes: function(){
  662. if( erno.renderMode === 'css' ) $( '.cubeletId-'+ this.id + ' .wireframe' ).show()
  663. else this.wireframe.material.opacity = 1
  664. this.showingWireframes = true
  665. },
  666. hideWireframes: function(){
  667. if( erno.renderMode === 'css' ) $( '.cubeletId-'+ this.id + ' .wireframe' ).hide()
  668. else this.wireframe.material.opacity = 0
  669. this.showingWireframes = false
  670. },
  671. showIds: function(){
  672. $( '.cubeletId-'+ this.id + ' .id' ).show()
  673. this.showingIds = true
  674. },
  675. hideIds: function(){
  676. $( '.cubeletId-'+ this.id + ' .id' ).hide()
  677. this.showingIds = false
  678. },
  679. showTexts: function(){
  680. $( '.cubeletId-'+ this.id + ' .text' ).show()
  681. this.showingTexts = true
  682. },
  683. hideTexts: function(){
  684. $( '.cubeletId-'+ this.id + ' .text' ).hide()
  685. this.showingTexts = false
  686. },
  687. getOpacity: function(){
  688. return this.opacity
  689. },
  690. setOpacity: function( opacityTarget, onComplete ){
  691. if( this.opacityTween ) this.opacityTween.stop()
  692. if( opacityTarget === undefined ) opacityTarget = 1
  693. if( opacityTarget !== this.opacity ){
  694. var
  695. that = this,
  696. tweenDuration = ( opacityTarget - this.opacity ).absolute().scale( 0, 1, 0, SECOND )
  697. this.opacityTween = new TWEEN.Tween({ opacity: this.opacity })
  698. .to({
  699. opacity: opacityTarget
  700. }, tweenDuration )
  701. .easing( TWEEN.Easing.Quadratic.InOut )
  702. .onUpdate( function(){
  703. $( '.cubeletId-'+ that.id ).css( 'opacity', this.opacity )
  704. that.opacity = this.opacity//opacityTarget
  705. })
  706. .onComplete( function(){
  707. if( onComplete instanceof Function ) onComplete()
  708. })
  709. .start()
  710. }
  711. },
  712. getStickersOpacity: function( value ){
  713. return $( '.cubeletId-'+ this.id + ' .sticker' ).css( 'opacity' )
  714. },
  715. setStickersOpacity: function( value ){
  716. if( value === undefined ) value = 0.2
  717. $( '.cubeletId-'+ this.id + ' .sticker' ).css( 'opacity', value )
  718. },
  719. getRadius: function(){
  720. return this.radius
  721. },
  722. setRadius: function( radius, onComplete ){
  723. // @@
  724. // It's a shame that we can't do this whilst tweening
  725. // but it's because the current implementation is altering the actual X, Y, Z
  726. // rather than the actual radius. Can fix later.
  727. // Current may produce unexpected results while shuffling. For example:
  728. // cube.corners.setRadius( 90 )
  729. // may cause only 4 corners instead of 6 to setRadius()
  730. // because one side is probably engaged in a twist tween.
  731. if( this.isTweening === false ){
  732. radius = radius || 0
  733. if( this.radius === undefined ) this.radius = 0
  734. if( this.radius !== radius ){
  735. // Here's some extra cuteness to make the tween's duration
  736. // proportional to the distance traveled.
  737. var tweenDuration = ( this.radius - radius ).absolute().scale( 0, 100, 0, SECOND )
  738. // We need a "that = this" in order to set this.radius = radius
  739. // from inside the anonymous onComplete() function below.
  740. var that = this
  741. new TWEEN.Tween( this.wrapper.position )
  742. .to({
  743. x: this.addressX.multiply( this.size + radius ),
  744. y: this.addressY.multiply( this.size + radius ),
  745. z: this.addressZ.multiply( this.size + radius )
  746. }, tweenDuration )
  747. .easing( TWEEN.Easing.Quartic.Out )
  748. .onComplete( function(){
  749. that.radius = radius
  750. if( onComplete instanceof Function ) onComplete()
  751. })
  752. .start()
  753. }
  754. }
  755. }
  756. })
  757. })