SpineAtlasResource.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated April 5, 2025. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2025, Esoteric Software LLC
  6. *
  7. * Integration of the Spine Runtimes into software or otherwise creating
  8. * derivative works of the Spine Runtimes is permitted under the terms and
  9. * conditions of Section 2 of the Spine Editor License Agreement:
  10. * http://esotericsoftware.com/spine-editor-license
  11. *
  12. * Otherwise, it is permitted to integrate the Spine Runtimes into software
  13. * or otherwise create derivative works of the Spine Runtimes (collectively,
  14. * "Products"), provided that each user of the Products must obtain their own
  15. * Spine Editor license and redistribution of the Products in any form must
  16. * include this license and copyright notice.
  17. *
  18. * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  27. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. #include "SpineAtlasResource.h"
  30. #include "SpineRendererObject.h"
  31. #ifdef SPINE_GODOT_EXTENSION
  32. #include <godot_cpp/classes/json.hpp>
  33. #include <godot_cpp/classes/texture.hpp>
  34. #include <godot_cpp/classes/file_access.hpp>
  35. #include <godot_cpp/classes/image.hpp>
  36. #include <godot_cpp/classes/image_texture.hpp>
  37. #else
  38. #include "core/io/json.h"
  39. #include "scene/resources/texture.h"
  40. #if VERSION_MAJOR > 3
  41. #include "core/io/image.h"
  42. #include "scene/resources/image_texture.h"
  43. #else
  44. #include "core/image.h"
  45. #endif
  46. #endif
  47. #ifdef TOOLS_ENABLED
  48. #ifdef SPINE_GODOT_EXTENSION
  49. #include <godot_cpp/classes/editor_file_system.hpp>
  50. #else
  51. #include "editor/editor_file_system.h"
  52. #endif
  53. #endif
  54. #include <spine/TextureLoader.h>
  55. class GodotSpineTextureLoader : public spine::TextureLoader {
  56. Array *textures;
  57. Array *normal_maps;
  58. Array *specular_maps;
  59. String normal_map_prefix;
  60. String specular_map_prefix;
  61. public:
  62. GodotSpineTextureLoader(Array *_textures, Array *_normal_maps, Array *_specular_maps, const String &normal_map_prefix, const String &specular_map_prefix, bool is_importing) : textures(_textures), normal_maps(_normal_maps), specular_maps(_specular_maps), normal_map_prefix(normal_map_prefix), specular_map_prefix(specular_map_prefix) {
  63. }
  64. static bool fix_path(String &path) {
  65. const String prefix = "res:/";
  66. auto i = path.find(prefix);
  67. if (i == -1) {
  68. return false;
  69. }
  70. auto sub_str_pos = i + SSIZE(prefix) - 1;
  71. auto res = path.substr(sub_str_pos);
  72. if (!EMPTY(res)) {
  73. if (res[0] != '/') {
  74. path = prefix + String("/") + res;
  75. } else {
  76. path = prefix + res;
  77. }
  78. }
  79. return true;
  80. }
  81. #if VERSION_MAJOR > 3
  82. Ref<Texture2D> get_texture_from_image(const String &path, bool is_resource) {
  83. Error error = OK;
  84. if (is_resource) {
  85. #ifdef SPINE_GODOT_EXTENSION
  86. return ResourceLoader::get_singleton()->load(path, "", ResourceLoader::CACHE_MODE_REUSE);
  87. #else
  88. return ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &error);
  89. #endif
  90. } else {
  91. Ref<Image> img;
  92. img.instantiate();
  93. img = img->load_from_file(path);
  94. return ImageTexture::create_from_image(img);
  95. }
  96. }
  97. #else
  98. Ref<Texture> get_texture_from_image(const String &path, bool is_resource) {
  99. Error error = OK;
  100. if (is_resource) {
  101. return ResourceLoader::load(path, "", false, &error);
  102. } else {
  103. Vector<uint8_t> buf = FileAccess::get_file_as_array(path, &error);
  104. if (error == OK) {
  105. Ref<Image> img;
  106. INSTANTIATE(img);
  107. img->load(path);
  108. Ref<ImageTexture> texture;
  109. INSTANTIATE(texture);
  110. texture->create_from_image(img);
  111. return texture;
  112. }
  113. return Ref<Texture>();
  114. }
  115. }
  116. #endif
  117. void import_image_resource(const String &path) {
  118. #if VERSION_MAJOR > 4
  119. #ifdef TOOLS_ENABLED
  120. // Required when importing into editor by e.g. drag & drop. The .png files
  121. // of the atlas might not have been imported yet.
  122. // See https://github.com/EsotericSoftware/spine-runtimes/issues/2385
  123. if (is_importing) {
  124. HashMap<StringName, Variant> custom_options;
  125. Dictionary generator_parameters;
  126. EditorFileSystem::get_singleton()->reimport_append(path, custom_options, "", generator_parameters);
  127. }
  128. #endif
  129. #endif
  130. }
  131. void load(spine::AtlasPage &page, const spine::String &path) override {
  132. String fixed_path;
  133. fixed_path.parse_utf8(path.buffer());
  134. bool is_resource = fix_path(fixed_path);
  135. import_image_resource(fixed_path);
  136. #if VERSION_MAJOR > 3
  137. Ref<Texture2D> texture = get_texture_from_image(fixed_path, is_resource);
  138. #else
  139. Ref<Texture> texture = get_texture_from_image(fixed_path, is_resource);
  140. #endif
  141. if (!texture.is_valid()) {
  142. ERR_PRINT(vformat("Can't load texture: \"%s\"", fixed_path));
  143. auto renderer_object = memnew(SpineRendererObject);
  144. renderer_object->texture = Ref<Texture>(nullptr);
  145. renderer_object->normal_map = Ref<Texture>(nullptr);
  146. renderer_object->specular_map = Ref<Texture>(nullptr);
  147. page.texture = (void *) renderer_object;
  148. return;
  149. }
  150. textures->append(texture);
  151. auto renderer_object = memnew(SpineRendererObject);
  152. renderer_object->texture = texture;
  153. renderer_object->normal_map = Ref<Texture>(nullptr);
  154. renderer_object->specular_map = Ref<Texture>(nullptr);
  155. String normal_map_path = vformat("%s/%s_%s", fixed_path.get_base_dir(), normal_map_prefix, fixed_path.get_file());
  156. String specular_map_path = vformat("%s/%s_%s", fixed_path.get_base_dir(), specular_map_prefix, fixed_path.get_file());
  157. is_resource = fix_path(normal_map_path);
  158. is_resource = fix_path(specular_map_path);
  159. #if SPINE_GODOT_EXTENSION
  160. if (ResourceLoader::get_singleton()->exists(normal_map_path)) {
  161. import_image_resource(normal_map_path);
  162. Ref<Texture> normal_map = get_texture_from_image(normal_map_path, is_resource);
  163. normal_maps->append(normal_map);
  164. renderer_object->normal_map = normal_map;
  165. }
  166. if (ResourceLoader::get_singleton()->exists(specular_map_path)) {
  167. import_image_resource(specular_map_path);
  168. Ref<Texture> specular_map = get_texture_from_image(specular_map_path, is_resource);
  169. specular_maps->append(specular_map);
  170. renderer_object->specular_map = specular_map;
  171. }
  172. #else
  173. if (ResourceLoader::exists(normal_map_path)) {
  174. import_image_resource(normal_map_path);
  175. Ref<Texture> normal_map = get_texture_from_image(normal_map_path, is_resource);
  176. normal_maps->append(normal_map);
  177. renderer_object->normal_map = normal_map;
  178. }
  179. if (ResourceLoader::exists(specular_map_path)) {
  180. import_image_resource(specular_map_path);
  181. Ref<Texture> specular_map = get_texture_from_image(specular_map_path, is_resource);
  182. specular_maps->append(specular_map);
  183. renderer_object->specular_map = specular_map;
  184. }
  185. #endif
  186. #if VERSION_MAJOR > 3
  187. renderer_object->canvas_texture.instantiate();
  188. renderer_object->canvas_texture->set_diffuse_texture(renderer_object->texture);
  189. renderer_object->canvas_texture->set_normal_texture(renderer_object->normal_map);
  190. renderer_object->canvas_texture->set_specular_texture(renderer_object->specular_map);
  191. #endif
  192. page.texture = (void *) renderer_object;
  193. page.width = texture->get_width();
  194. page.height = texture->get_height();
  195. }
  196. void unload(void *data) override {
  197. auto renderer_object = (SpineRendererObject *) data;
  198. if (renderer_object->texture.is_valid()) renderer_object->texture.unref();
  199. if (renderer_object->normal_map.is_valid()) renderer_object->normal_map.unref();
  200. if (renderer_object->specular_map.is_valid()) renderer_object->specular_map.unref();
  201. #if VERSION_MAJOR > 3
  202. if (renderer_object->canvas_texture.is_valid()) renderer_object->canvas_texture.unref();
  203. #endif
  204. memdelete(renderer_object);
  205. }
  206. };
  207. void SpineAtlasResource::_bind_methods() {
  208. ClassDB::bind_method(D_METHOD("load_from_atlas_file", "path"), &SpineAtlasResource::load_from_atlas_file);
  209. ClassDB::bind_method(D_METHOD("get_source_path"), &SpineAtlasResource::get_source_path);
  210. ClassDB::bind_method(D_METHOD("get_textures"), &SpineAtlasResource::get_textures);
  211. ClassDB::bind_method(D_METHOD("get_normal_maps"), &SpineAtlasResource::get_normal_maps);
  212. ClassDB::bind_method(D_METHOD("get_specular_maps"), &SpineAtlasResource::get_specular_maps);
  213. ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_path"), "", "get_source_path");
  214. ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures"), "", "get_textures");
  215. ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "normal_maps"), "", "get_normal_maps");
  216. ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "specular_maps"), "", "get_specular_maps");
  217. }
  218. SpineAtlasResource::SpineAtlasResource() : atlas(nullptr), texture_loader(nullptr), normal_map_prefix("n"), specular_map_prefix("s") {
  219. }
  220. SpineAtlasResource::~SpineAtlasResource() {
  221. delete atlas;
  222. delete texture_loader;
  223. }
  224. void SpineAtlasResource::clear() {
  225. delete atlas;
  226. atlas = nullptr;
  227. delete texture_loader;
  228. texture_loader = nullptr;
  229. textures.clear();
  230. normal_maps.clear();
  231. specular_maps.clear();
  232. }
  233. Array SpineAtlasResource::get_textures() {
  234. return textures;
  235. }
  236. Array SpineAtlasResource::get_normal_maps() {
  237. return normal_maps;
  238. }
  239. Array SpineAtlasResource::get_specular_maps() {
  240. return specular_maps;
  241. }
  242. String SpineAtlasResource::get_source_path() {
  243. return source_path;
  244. }
  245. Error SpineAtlasResource::load_from_atlas_file(const String &path) {
  246. return load_from_atlas_file_internal(path, false);
  247. }
  248. Error SpineAtlasResource::load_from_atlas_file_internal(const String &path, bool is_importing) {
  249. source_path = path;
  250. #ifdef SPINE_GODOT_EXTENSION
  251. atlas_data = FileAccess::get_file_as_string(path);
  252. if (SSIZE(atlas_data) == 0) return ERR_FILE_UNRECOGNIZED;
  253. #else
  254. Error err;
  255. atlas_data = FileAccess::get_file_as_string(path, &err);
  256. if (err != OK) return err;
  257. #endif
  258. clear();
  259. texture_loader = new GodotSpineTextureLoader(&textures, &normal_maps, &specular_maps, normal_map_prefix, specular_map_prefix, is_importing);
  260. auto atlas_utf8 = atlas_data.utf8();
  261. atlas = new spine::Atlas(atlas_utf8, atlas_utf8.length(), source_path.get_base_dir().utf8(), texture_loader);
  262. if (atlas) return OK;
  263. clear();
  264. return ERR_FILE_UNRECOGNIZED;
  265. }
  266. Error SpineAtlasResource::load_from_file(const String &path) {
  267. Error error;
  268. #ifdef SPINE_GODOT_EXTENSION
  269. String json_string = FileAccess::get_file_as_string(path);
  270. if (SSIZE(json_string) == 0) return ERR_FILE_UNRECOGNIZED;
  271. #else
  272. String json_string = FileAccess::get_file_as_string(path, &error);
  273. if (error != OK) return error;
  274. #endif
  275. #if VERSION_MAJOR > 3
  276. JSON *json = memnew(JSON);
  277. error = json->parse(json_string);
  278. if (error != OK) {
  279. memdelete(json);
  280. return error;
  281. }
  282. Variant result = json->get_data();
  283. memdelete(json);
  284. #else
  285. String error_string;
  286. int error_line;
  287. Variant result;
  288. error = JSON::parse(json_string, result, error_string, error_line);
  289. if (error != OK) return error;
  290. #endif
  291. Dictionary content = Dictionary(result);
  292. source_path = content["source_path"];
  293. atlas_data = content["atlas_data"];
  294. normal_map_prefix = content["normal_texture_prefix"];
  295. specular_map_prefix = content["specular_texture_prefix"];
  296. clear();
  297. texture_loader = new GodotSpineTextureLoader(&textures, &normal_maps, &specular_maps, normal_map_prefix, specular_map_prefix, false);
  298. auto utf8 = atlas_data.utf8();
  299. atlas = new spine::Atlas(utf8.ptr(), utf8.size(), source_path.get_base_dir().utf8(), texture_loader);
  300. if (atlas) return OK;
  301. clear();
  302. return ERR_FILE_UNRECOGNIZED;
  303. }
  304. Error SpineAtlasResource::save_to_file(const String &path) {
  305. Error err;
  306. #if VERSION_MAJOR > 3
  307. #if SPINE_GODOT_EXTENSION
  308. Ref<FileAccess> file = FileAccess::open(path, FileAccess::WRITE);
  309. if (file.is_null()) return ERR_FILE_UNRECOGNIZED;
  310. #else
  311. Ref<FileAccess> file = FileAccess::open(path, FileAccess::WRITE, &err);
  312. if (err != OK) return err;
  313. #endif
  314. #else
  315. FileAccess *file = FileAccess::open(path, FileAccess::WRITE, &err);
  316. if (err != OK) {
  317. if (file) file->close();
  318. return err;
  319. }
  320. #endif
  321. Dictionary content;
  322. content["source_path"] = source_path;
  323. content["atlas_data"] = atlas_data;
  324. content["normal_texture_prefix"] = normal_map_prefix;
  325. content["specular_texture_prefix"] = specular_map_prefix;
  326. #if VERSION_MAJOR > 3
  327. JSON *json = memnew(JSON);
  328. file->store_string(json->stringify(content));
  329. file->flush();
  330. memdelete(json);
  331. #else
  332. file->store_string(JSON::print(content));
  333. file->close();
  334. #endif
  335. return OK;
  336. }
  337. #ifndef SPINE_GODOT_EXTENSION
  338. #if VERSION_MAJOR > 3
  339. Error SpineAtlasResource::copy_from(const Ref<Resource> &p_resource) {
  340. auto error = Resource::copy_from(p_resource);
  341. if (error != OK) return error;
  342. const Ref<SpineAtlasResource> &spineAtlas = static_cast<const Ref<SpineAtlasResource> &>(p_resource);
  343. this->clear();
  344. this->atlas = spineAtlas->atlas;
  345. this->texture_loader = spineAtlas->texture_loader;
  346. spineAtlas->clear_native_data();
  347. this->source_path = spineAtlas->source_path;
  348. this->atlas_data = spineAtlas->atlas_data;
  349. this->normal_map_prefix = spineAtlas->normal_map_prefix;
  350. this->specular_map_prefix = spineAtlas->specular_map_prefix;
  351. this->textures = spineAtlas->textures;
  352. this->normal_maps = spineAtlas->normal_maps;
  353. this->specular_maps = spineAtlas->specular_maps;
  354. emit_signal(SNAME("skeleton_file_changed"));
  355. return OK;
  356. }
  357. #endif
  358. #endif
  359. #ifdef SPINE_GODOT_EXTENSION
  360. Variant SpineAtlasResourceFormatLoader::_load(const String &path, const String &original_path, bool use_sub_threads, int32_t cache_mode) {
  361. #else
  362. #if VERSION_MAJOR > 3
  363. RES SpineAtlasResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode) {
  364. #else
  365. #if VERSION_MINOR > 5
  366. RES SpineAtlasResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool p_no_subresource_cache) {
  367. #else
  368. RES SpineAtlasResourceFormatLoader::load(const String &path, const String &original_path, Error *error) {
  369. #endif
  370. #endif
  371. #endif
  372. Ref<SpineAtlasResource> atlas = memnew(SpineAtlasResource);
  373. atlas->load_from_file(path);
  374. #ifndef SPINE_GODOT_EXTENSION
  375. if (error) *error = OK;
  376. #endif
  377. return atlas;
  378. }
  379. #ifdef SPINE_GODOT_EXTENSION
  380. PackedStringArray SpineAtlasResourceFormatLoader::_get_recognized_extensions() {
  381. PackedStringArray extensions;
  382. extensions.push_back("spatlas");
  383. return extensions;
  384. }
  385. #else
  386. void SpineAtlasResourceFormatLoader::get_recognized_extensions(List<String> *extensions) const {
  387. const char atlas_ext[] = "spatlas";
  388. if (!extensions->find(atlas_ext))
  389. extensions->push_back(atlas_ext);
  390. }
  391. #endif
  392. #ifdef SPINE_GODOT_EXTENSION
  393. String SpineAtlasResourceFormatLoader::_get_resource_type(const String &path) {
  394. #else
  395. String SpineAtlasResourceFormatLoader::get_resource_type(const String &path) const {
  396. #endif
  397. return path.ends_with("spatlas") || path.ends_with(".atlas") ? "SpineAtlasResource" : "";
  398. }
  399. #ifdef SPINE_GODOT_EXTENSION
  400. bool SpineAtlasResourceFormatLoader::_handles_type(const StringName &type) {
  401. #else
  402. bool SpineAtlasResourceFormatLoader::handles_type(const String &type) const {
  403. #endif
  404. return type == StringName("SpineAtlasResource") || ClassDB::is_parent_class(type, "SpineAtlasResource");
  405. }
  406. #ifdef SPINE_GODOT_EXTENSION
  407. Error SpineAtlasResourceFormatSaver::_save(const Ref<Resource> &resource, const String &path, uint32_t flags) {
  408. #else
  409. #if VERSION_MAJOR > 3
  410. Error SpineAtlasResourceFormatSaver::save(const RES &resource, const String &path, uint32_t flags) {
  411. #else
  412. Error SpineAtlasResourceFormatSaver::save(const String &path, const RES &resource, uint32_t flags) {
  413. #endif
  414. #endif
  415. Ref<SpineAtlasResource> res = resource;
  416. return res->save_to_file(path);
  417. }
  418. #ifdef SPINE_GODOT_EXTENSION
  419. PackedStringArray SpineAtlasResourceFormatSaver::_get_recognized_extensions(const Ref<Resource> &resource) {
  420. PackedStringArray extensions;
  421. if (Object::cast_to<SpineAtlasResource>(*resource)) {
  422. extensions.push_back("spatlas");
  423. }
  424. return extensions;
  425. }
  426. #else
  427. void SpineAtlasResourceFormatSaver::get_recognized_extensions(const RES &resource, List<String> *extensions) const {
  428. if (Object::cast_to<SpineAtlasResource>(*resource))
  429. extensions->push_back("spatlas");
  430. }
  431. #endif
  432. #ifdef SPINE_GODOT_EXTENSION
  433. bool SpineAtlasResourceFormatSaver::_recognize(const RES &resource) {
  434. #else
  435. bool SpineAtlasResourceFormatSaver::recognize(const RES &resource) const {
  436. #endif
  437. return Object::cast_to<SpineAtlasResource>(*resource) != nullptr;
  438. }