index.html 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. <!-----------------------------------------------------------------------------
  2. -- Spine Runtimes Software License
  3. -- Version 2.1
  4. --
  5. -- Copyright (c) 2013, Esoteric Software
  6. -- All rights reserved.
  7. --
  8. -- You are granted a perpetual, non-exclusive, non-sublicensable and
  9. -- non-transferable license to install, execute and perform the Spine Runtimes
  10. -- Software (the "Software") solely for internal use. Without the written
  11. -- permission of Esoteric Software (typically granted by licensing Spine), you
  12. -- may not (a) modify, translate, adapt or otherwise create derivative works,
  13. -- improvements of the Software or develop new applications using the Software
  14. -- or (b) remove, delete, alter or obscure any trademarks or any copyright,
  15. -- trademark, patent or other intellectual property or proprietary rights
  16. -- notices on or in the Software, including any copy thereof. Redistributions
  17. -- in binary or source form must include this license and terms.
  18. --
  19. -- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  20. -- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  21. -- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  22. -- EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  24. -- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  25. -- OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  26. -- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  27. -- OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  28. -- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. ------------------------------------------------------------------------------>
  30. <html>
  31. <head>
  32. <meta charset="UTF-8">
  33. <title>spine-threejs</title>
  34. <script src="../spine-js/spine.js"></script>
  35. <script src="../three.min.js"></script>
  36. <style>body, input { font-family: tahoma; font-size: 11pt }</style>
  37. </head>
  38. <body>
  39. <script>
  40. (function () {
  41. var loadText = function (url, callback) {
  42. var req = new XMLHttpRequest();
  43. req.open("GET", url, true);
  44. req.responseType = 'text';
  45. req.addEventListener('error', function (event) {}, false);
  46. req.addEventListener('abort', function (event) {}, false);
  47. req.addEventListener('load', function (event) { callback(req.response); }, false);
  48. req.send();
  49. return req;
  50. };
  51. var loadImage = function (url, callback) {
  52. var image = new Image();
  53. image.addEventListener('error', function (event) {}, false);
  54. image.addEventListener('abort', function (event) {}, false);
  55. image.addEventListener('load', function (event) { callback (image); }, false);
  56. image.src = url;
  57. return image;
  58. };
  59. SpineAnimation = function (name, path, scale) {
  60. THREE.Object3D.call(this);
  61. this.name = name;
  62. path = path ? (path +
  63. ((path.substr(-1) != '/') ? '/' : '')
  64. ) : '';
  65. var self = this;
  66. loadText(path + name + '.atlas', function (atlasText) {
  67. self.atlas = new spine.Atlas(atlasText, {
  68. load: function (page, image, atlas) {
  69. loadImage(path + image, function (image) {
  70. // calculate UVs in atlas regions
  71. page.width = image.width;
  72. page.height = image.height;
  73. atlas.updateUVs(page);
  74. // propagate new UVs to attachments, if they were already created
  75. if (self.skeleton) {
  76. var skins = self.skeleton.data.skins;
  77. for (var s = 0, n = skins.length; s < n; s++) {
  78. var attachments = skins[s].attachments;
  79. for (var k in attachments) {
  80. var attachment = attachments[k];
  81. if (attachment instanceof spine.RegionAttachment) {
  82. var region = attachment.rendererObject;
  83. attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate);
  84. }
  85. }
  86. }
  87. }
  88. // create basic material for the page
  89. var texture = new THREE.Texture(image);
  90. texture.needsUpdate = true;
  91. page.rendererObject = [
  92. new THREE.MeshBasicMaterial({
  93. //color: 0xff00, wireframe: true,
  94. map : texture, side : THREE.DoubleSide, transparent : true, alphaTest : 0.5
  95. })
  96. ];
  97. });
  98. },
  99. unload: function (materials) {
  100. for (var i = 0, n = materials.length; i < n; i++) {
  101. var material = materials[i];
  102. if (material.meshes) {
  103. for (var name in material.meshes) {
  104. var mesh = material.meshes[name];
  105. if (mesh.parent) mesh.parent.remove(mesh);
  106. mesh.geometry.dispose();
  107. }
  108. }
  109. material.map.dispose();
  110. material.dispose();
  111. }
  112. // will be called multiple times
  113. materials.length = 0;
  114. }
  115. });
  116. loadText(path + name + '.json', function (skeletonText) {
  117. var json = new spine.SkeletonJson(new spine.AtlasAttachmentLoader(self.atlas));
  118. json.scale = scale || 1;
  119. var skeletonData = json.readSkeletonData(JSON.parse(skeletonText));
  120. self.skeleton = new spine.Skeleton(skeletonData);
  121. self.stateData = new spine.AnimationStateData(skeletonData);
  122. self.state = new spine.AnimationState(self.stateData);
  123. self.dispatchEvent({
  124. type : SpineAnimation.SKELETON_DATA_LOADED
  125. });
  126. });
  127. });
  128. var matrix = new THREE.Matrix4();
  129. // if given, dt must be in ms
  130. this.update = function (dt, dz) {
  131. if (!this.state) return;
  132. this.state.update(dt || (1.0 / 60));
  133. this.state.apply(this.skeleton);
  134. this.skeleton.updateWorldTransform();
  135. this.traverse(function (object) {
  136. if (object instanceof THREE.Mesh) {
  137. object.visible = false;
  138. }
  139. });
  140. var Z = 0;
  141. var drawOrder = this.skeleton.drawOrder;
  142. for (var i = 0, n = drawOrder.length; i < n; i++) {
  143. var slot = drawOrder[i];
  144. var attachment = slot.attachment;
  145. if (!(attachment instanceof spine.RegionAttachment)) continue;
  146. var materials = attachment.rendererObject.page.rendererObject;
  147. // texture was not loaded yet
  148. if (!materials) continue;
  149. if (slot.data.additiveBlending && (materials.length == 1)) {
  150. // create separate material for additive blending
  151. materials.push(new THREE.MeshBasicMaterial({
  152. map : materials[0].map,
  153. side : THREE.DoubleSide,
  154. transparent : true,
  155. blending : THREE.AdditiveBlending,
  156. depthWrite : false
  157. }));
  158. }
  159. var material = materials[ slot.data.additiveBlending ? 1 : 0 ];
  160. material.meshes = material.meshes || {};
  161. var mesh = material.meshes[slot.data.name];
  162. var geometry;
  163. if (mesh) {
  164. geometry = mesh.geometry;
  165. mesh.visible = true;
  166. } else {
  167. geometry = new THREE.PlaneGeometry(
  168. attachment.regionOriginalWidth,
  169. attachment.regionOriginalHeight
  170. );
  171. geometry.dynamic = true;
  172. mesh = new THREE.Mesh(geometry, material);
  173. mesh.matrixAutoUpdate = false;
  174. material.meshes[slot.data.name] = mesh;
  175. this.add(mesh);
  176. }
  177. if (mesh.attachmentTime && (slot.getAttachmentTime () > mesh.attachmentTime)) {
  178. // do nothing
  179. } else {
  180. // update UVs
  181. geometry.faceVertexUvs[0][0][0].set(attachment.uvs[6], 1- attachment.uvs[7]);
  182. geometry.faceVertexUvs[0][0][1].set(attachment.uvs[4], 1- attachment.uvs[5]);
  183. geometry.faceVertexUvs[0][0][2].set(attachment.uvs[0], 1- attachment.uvs[1]);
  184. geometry.faceVertexUvs[0][1][0].set(attachment.uvs[4], 1- attachment.uvs[5]);
  185. geometry.faceVertexUvs[0][1][1].set(attachment.uvs[2], 1- attachment.uvs[3]);
  186. geometry.faceVertexUvs[0][1][2].set(attachment.uvs[0], 1- attachment.uvs[1]);
  187. geometry.uvsNeedUpdate = true;
  188. geometry.vertices[1].set(attachment.offset[0], attachment.offset[1], 0);
  189. geometry.vertices[3].set(attachment.offset[2], attachment.offset[3], 0);
  190. geometry.vertices[2].set(attachment.offset[4], attachment.offset[5], 0);
  191. geometry.vertices[0].set(attachment.offset[6], attachment.offset[7], 0);
  192. geometry.verticesNeedUpdate = true;
  193. mesh.attachmentTime = slot.getAttachmentTime();
  194. }
  195. matrix.makeTranslation(
  196. this.skeleton.x + slot.bone.worldX,
  197. this.skeleton.y + slot.bone.worldY,
  198. (dz || 0.1) * Z++
  199. );
  200. matrix.elements[0] = slot.bone.a; matrix.elements[4] = slot.bone.b;
  201. matrix.elements[1] = slot.bone.c; matrix.elements[5] = slot.bone.d;
  202. mesh.matrix.copy(matrix);
  203. /* TODO slot.r,g,b,a ?? turbulenz example code:
  204. batch.add(
  205. attachment.rendererObject.page.rendererObject,
  206. vertices[0], vertices[1],
  207. vertices[6], vertices[7],
  208. vertices[2], vertices[3],
  209. vertices[4], vertices[5],
  210. skeleton.r * slot.r,
  211. skeleton.g * slot.g,
  212. skeleton.b * slot.b,
  213. skeleton.a * slot.a,
  214. attachment.uvs[0], attachment.uvs[1],
  215. attachment.uvs[4], attachment.uvs[5]
  216. );
  217. */
  218. }
  219. };
  220. };
  221. SpineAnimation.SKELETON_DATA_LOADED = 'skeletonDataLoaded';
  222. SpineAnimation.prototype = Object.create(THREE.Object3D.prototype);
  223. SpineAnimation.prototype.dispose = function () {
  224. if (this.parent) this.parent.remove(this); this.atlas.dispose();
  225. };
  226. }) ();
  227. var scene, camera, renderer;
  228. var geometry, material, mesh;
  229. var anim;
  230. function load (name, scale) {
  231. if (anim) anim.dispose();
  232. anim = new SpineAnimation(name, 'data/', scale);
  233. anim.addEventListener(SpineAnimation.SKELETON_DATA_LOADED, function () {
  234. var canvas = renderer.domElement;
  235. switch (anim.name) {
  236. case 'spineboy':
  237. anim.stateData.setMixByName('walk', 'jump', 0.2);
  238. anim.stateData.setMixByName('run', 'jump', 0.2);
  239. anim.stateData.setMixByName('jump', 'run', 0.2);
  240. anim.state.setAnimationByName(0, 'walk', true);
  241. canvas.onmousedown = function () {
  242. anim.state.setAnimationByName(0, 'jump', false);
  243. anim.state.addAnimationByName(0, 'run', true, 0);
  244. }
  245. break;
  246. case 'hero':
  247. anim.stateData.defaultMix = 0.2;
  248. anim.stateData.setMixByName('Walk', 'Attack', 0);
  249. anim.stateData.setMixByName('Attack', 'Run', 0);
  250. anim.stateData.setMixByName('Run', 'Attack', 0);
  251. anim.state.setAnimationByName(0, 'Idle', true);
  252. canvas.onmousedown = function () {
  253. var name = anim.state.getCurrent(0).animation.name;
  254. if (name == 'Idle')
  255. anim.state.setAnimationByName(0, 'Crouch', true);
  256. else if (name == 'Crouch')
  257. anim.state.setAnimationByName(0, 'Walk', true);
  258. else {
  259. anim.state.setAnimationByName(0, 'Attack', false);
  260. anim.state.addAnimationByName(0, 'Run', true, 0);
  261. }
  262. }
  263. break;
  264. case 'goblins':
  265. anim.skeleton.setSkinByName('goblingirl'); // TODO - spine.Skeleton.prototype.setSkin doesn't work?
  266. anim.skeleton.setSlotsToSetupPose();
  267. anim.state.setAnimationByName(0, 'walk', true);
  268. canvas.onmousedown = function () {
  269. anim.skeleton.setSkinByName(anim.skeleton.skin.name == 'goblin' ? 'goblingirl' : 'goblin');
  270. anim.skeleton.setSlotsToSetupPose();
  271. }
  272. break;
  273. case 'skeleton':
  274. anim.state.setAnimationByName(0, 'walk test', true);
  275. canvas.onmousedown = function () {
  276. var name = anim.state.getCurrent(0).animation.name;
  277. if (name == 'walk test')
  278. anim.state.setAnimationByName(0, 'idle test', true);
  279. else
  280. anim.state.setAnimationByName(0, 'walk test', true);
  281. }
  282. break;
  283. }
  284. //anim.skeleton.y = -200;
  285. });
  286. mesh.add(anim);
  287. }
  288. function init() {
  289. scene = new THREE.Scene();
  290. var width = 640, height = 480;
  291. camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000);
  292. camera.position.z = 400;
  293. geometry = new THREE.BoxGeometry(200, 200, 200);
  294. material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });
  295. mesh = new THREE.Mesh(geometry, material);
  296. scene.add(mesh);
  297. load('spineboy', 0.4);
  298. renderer = new THREE.WebGLRenderer();
  299. renderer.setSize(width, height);
  300. document.body.appendChild(renderer.domElement);
  301. }
  302. var lastTime = Date.now();
  303. function animate() {
  304. requestAnimationFrame(animate);
  305. var t = Date.now();
  306. var a = Math.sin(t * 6e-4);
  307. var b = Math.cos(t * 3e-4);
  308. mesh.rotation.x = a * Math.PI * 0.2;
  309. mesh.rotation.y = b * Math.PI * 0.4;
  310. anim.update((t - lastTime) / 1000);
  311. lastTime = t;
  312. renderer.render(scene, camera);
  313. }
  314. init();
  315. animate();
  316. </script>
  317. <br><br>
  318. <input type="button" value="Spineboy" onclick="load('spineboy', 0.6)">
  319. <input type="button" value="Hero" onclick="load('hero', 1)">
  320. <input type="button" value="Goblins" onclick="load('goblins', 1)">
  321. &nbsp; &nbsp; Click to change the animation or skin.
  322. </body>
  323. </html>