project.vala 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. /*
  2. * Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.
  3. * License: https://github.com/dbartolini/crown/blob/master/LICENSE
  4. */
  5. using Gtk;
  6. using Gee;
  7. namespace Crown
  8. {
  9. public class Project
  10. {
  11. // Data
  12. public File _source_dir;
  13. public File _toolchain_dir;
  14. public File _data_dir;
  15. public File _level_editor_test_level;
  16. public File _level_editor_test_package;
  17. public string _platform;
  18. public Database _files;
  19. public HashMap<string, Guid?> _map;
  20. public DataCompiler _data_compiler;
  21. public signal void file_added(string type, string name);
  22. public signal void file_removed(string type, string name);
  23. public signal void tree_added(string name);
  24. public signal void tree_removed(string name);
  25. public signal void project_reset();
  26. public Project(DataCompiler dc)
  27. {
  28. _source_dir = null;
  29. _toolchain_dir = null;
  30. _data_dir = null;
  31. _level_editor_test_level = null;
  32. _level_editor_test_package = null;
  33. #if CROWN_PLATFORM_LINUX
  34. _platform = "linux";
  35. #elif CROWN_PLATFORM_WINDOWS
  36. _platform = "windows";
  37. #endif // CROWN_PLATFORM_LINUX
  38. _files = new Database();
  39. _map = new HashMap<string, Guid?>();
  40. _data_compiler = dc;
  41. }
  42. public void reset()
  43. {
  44. _files.reset();
  45. _map.clear();
  46. project_reset();
  47. }
  48. public void load(string source_dir, string toolchain_dir)
  49. {
  50. reset();
  51. _source_dir = File.new_for_path(source_dir);
  52. _toolchain_dir = File.new_for_path(toolchain_dir);
  53. _data_dir = File.new_for_path(_source_dir.get_path() + "_" + _platform);
  54. _level_editor_test_level = File.new_for_path(Path.build_filename(_source_dir.get_path(), "_level_editor_test.level"));
  55. _level_editor_test_package = File.new_for_path(Path.build_filename(_source_dir.get_path(), "_level_editor_test.package"));
  56. // Cleanup source directory from previous runs' garbage
  57. delete_garbage();
  58. }
  59. public void create_initial_files()
  60. {
  61. // Write boot.config
  62. {
  63. string text = """// Lua script to launch on boot
  64. boot_script = "core/game/boot"
  65. // Package to load on boot
  66. boot_package = "boot"
  67. window_title = "New Project"
  68. // Linux-only configs
  69. linux = {
  70. renderer = {
  71. resolution = [ 1280 720 ]
  72. }
  73. }
  74. // Windows-only configs
  75. windows = {
  76. renderer = {
  77. resolution = [ 1280 720 ]
  78. }
  79. }
  80. """;
  81. string path = Path.build_filename(_source_dir.get_path(), "boot.config");
  82. FileStream fs = FileStream.open(path, "wb");
  83. if (fs != null)
  84. fs.write(text.data);
  85. }
  86. // Write boot.package
  87. {
  88. string text = """lua = [
  89. "core/game/boot"
  90. "core/game/camera"
  91. "core/game/game"
  92. "core/lua/class"
  93. "main"
  94. ]
  95. shader = [
  96. "core/shaders/common"
  97. "core/shaders/default"
  98. ]
  99. physics_config = [
  100. "global"
  101. ]
  102. unit = [
  103. "core/units/camera"
  104. ]
  105. """;
  106. string path = Path.build_filename(_source_dir.get_path(), "boot.package");
  107. FileStream fs = FileStream.open(path, "wb");
  108. if (fs != null)
  109. fs.write(text.data);
  110. }
  111. // Write global.physics_config
  112. {
  113. string text = """materials = {
  114. default = { friction = 0.8 rolling_friction = 0.5 restitution = 0.81 }
  115. }
  116. collision_filters = {
  117. no_collision = { collides_with = [] }
  118. default = { collides_with = [ "default" ] }
  119. }
  120. actors = {
  121. static = { dynamic = false }
  122. dynamic = { dynamic = true }
  123. keyframed = { dynamic = true kinematic = true disable_gravity = true }
  124. }
  125. """;
  126. string path = Path.build_filename(_source_dir.get_path(), "global.physics_config");
  127. FileStream fs = FileStream.open(path, "wb");
  128. if (fs != null)
  129. fs.write(text.data);
  130. }
  131. // Write main.lua
  132. {
  133. string text = """require "core/game/camera"
  134. Game = Game or {
  135. sg = nil,
  136. pw = nil,
  137. rw = nil,
  138. camera = nil,
  139. }
  140. GameBase.game = Game
  141. GameBase.game_level = nil
  142. function Game.level_loaded()
  143. Device.enable_resource_autoload(true)
  144. Game.sg = World.scene_graph(GameBase.world)
  145. Game.pw = World.physics_world(GameBase.world)
  146. Game.rw = World.render_world(GameBase.world)
  147. -- Spawn camera
  148. local camera_unit = World.spawn_unit(GameBase.world, "core/units/camera")
  149. SceneGraph.set_local_position(Game.sg, camera_unit, Vector3(0, 6.5, -30))
  150. GameBase.game_camera = camera_unit
  151. Game.camera = FPSCamera(GameBase.world, camera_unit)
  152. end
  153. function Game.update(dt)
  154. -- Stop the engine when the 'ESC' key is released
  155. if Keyboard.released(Keyboard.button_id("escape")) then
  156. Device.quit()
  157. end
  158. -- Update camera
  159. local delta = Vector3.zero()
  160. if Mouse.pressed(Mouse.button_id("right")) then move = true end
  161. if Mouse.released(Mouse.button_id("right")) then move = false end
  162. if move then delta = Mouse.axis(Mouse.axis_id("cursor_delta")) end
  163. Game.camera:update(dt, delta.x, delta.y)
  164. end
  165. function Game.render(dt)
  166. end
  167. function Game.shutdown()
  168. end
  169. """;
  170. string path = Path.build_filename(_source_dir.get_path(), "main.lua");
  171. FileStream fs = FileStream.open(path, "wb");
  172. if (fs != null)
  173. fs.write(text.data);
  174. }
  175. }
  176. public string source_dir()
  177. {
  178. return _source_dir.get_path();
  179. }
  180. public string toolchain_dir()
  181. {
  182. return _toolchain_dir.get_path();
  183. }
  184. public string data_dir()
  185. {
  186. return _data_dir.get_path();
  187. }
  188. public string platform()
  189. {
  190. return _platform;
  191. }
  192. public string name()
  193. {
  194. string sd = source_dir();
  195. return sd.substring(sd.last_index_of_char(GLib.Path.DIR_SEPARATOR) + 1);
  196. }
  197. public bool path_is_within_dir(string path, string dir)
  198. {
  199. GLib.File file = GLib.File.new_for_path(path);
  200. return file.has_prefix(_source_dir);
  201. }
  202. public void dump_test_level(Database db)
  203. {
  204. // Save test level to file
  205. db.dump(_level_editor_test_level.get_path());
  206. // Save temporary package to reference test level
  207. ArrayList<Value?> level = new ArrayList<Value?>();
  208. level.add("_level_editor_test");
  209. Hashtable package = new Hashtable();
  210. package["level"] = level;
  211. SJSON.save(package, _level_editor_test_package.get_path());
  212. }
  213. public void delete_garbage()
  214. {
  215. try
  216. {
  217. _level_editor_test_level.delete();
  218. _level_editor_test_package.delete();
  219. }
  220. catch (GLib.Error e)
  221. {
  222. // Ignored
  223. }
  224. }
  225. public string id_to_name(string id)
  226. {
  227. Hashtable index = SJSON.load(Path.build_filename(_data_dir.get_path(), "data_index.sjson"));
  228. Value? name = index[id];
  229. return name != null ? (string)name : id;
  230. }
  231. public Database files()
  232. {
  233. return _files;
  234. }
  235. public void add_file(string path)
  236. {
  237. string name = path.substring(0, path.last_index_of("."));
  238. string type = path.substring(path.last_index_of(".") + 1);
  239. Guid id = Guid.new_guid();
  240. _files.create(id);
  241. _files.set_property_string(id, "path", path);
  242. _files.set_property_string(id, "type", type);
  243. _files.set_property_string(id, "name", name);
  244. _files.add_to_set(GUID_ZERO, "data", id);
  245. _map[path] = id;
  246. file_added(type, name);
  247. }
  248. public void remove_file(string path)
  249. {
  250. Guid id = _map[path];
  251. file_removed(_files.get_property_string(id, "type"), _files.get_property_string(id, "name"));
  252. _files.remove_from_set(GUID_ZERO, "data", id);
  253. _files.destroy(id);
  254. _map.unset(path);
  255. }
  256. public void add_tree(string path)
  257. {
  258. tree_added(path);
  259. }
  260. public void remove_tree(string path)
  261. {
  262. tree_removed(path);
  263. }
  264. /// Converts @a path to the corresponding resource name.
  265. /// On Linux, no transformation is needed. On Windows,
  266. /// backslashes are converted to slashes.
  267. public string resource_path_to_resource_name(string path)
  268. {
  269. return path.replace("\\", "/");
  270. }
  271. public void import_sprites(SList<string> filenames, string destination_dir)
  272. {
  273. Hashtable importer_settings = null;
  274. string importer_settings_path = null;
  275. {
  276. GLib.File file_src = File.new_for_path(filenames.nth_data(0));
  277. GLib.File file_dst = File.new_for_path(Path.build_filename(destination_dir, file_src.get_basename()));
  278. string resource_filename = _source_dir.get_relative_path(file_dst);
  279. string resource_path = resource_filename.substring(0, resource_filename.last_index_of_char('.'));
  280. importer_settings_path = Path.build_filename(_source_dir.get_path(), resource_path) + ".importer_settings";
  281. }
  282. SpriteImportDialog sid = new SpriteImportDialog(filenames.nth_data(0));
  283. if (File.new_for_path(importer_settings_path).query_exists())
  284. {
  285. importer_settings = SJSON.load(importer_settings_path);
  286. sid.load(importer_settings);
  287. }
  288. else
  289. {
  290. importer_settings = new Hashtable();
  291. }
  292. if (sid.run() != Gtk.ResponseType.OK)
  293. {
  294. sid.destroy();
  295. return;
  296. }
  297. sid.save(importer_settings);
  298. int width = (int)sid._pixbuf.width;
  299. int height = (int)sid._pixbuf.height;
  300. int num_h = (int)sid.cells_h.value;
  301. int num_v = (int)sid.cells_v.value;
  302. int cell_w = (int)sid.cell_w.value;
  303. int cell_h = (int)sid.cell_h.value;
  304. int offset_x = (int)sid.offset_x.value;
  305. int offset_y = (int)sid.offset_y.value;
  306. int spacing_x = (int)sid.spacing_x.value;
  307. int spacing_y = (int)sid.spacing_y.value;
  308. double layer = sid.layer.value;
  309. double depth = sid.depth.value;
  310. Vector2 pivot_xy = sprite_cell_pivot_xy(cell_w, cell_h, sid.pivot.active);
  311. bool collision_enabled = sid.collision_enabled.active;
  312. string shape_active_name = (string)sid.shape.visible_child_name;
  313. int circle_collision_center_x = (int)sid.circle_collision_center_x.value;
  314. int circle_collision_center_y = (int)sid.circle_collision_center_y.value;
  315. int circle_collision_radius = (int)sid.circle_collision_radius.value;
  316. int capsule_collision_center_x = (int)sid.capsule_collision_center_x.value;
  317. int capsule_collision_center_y = (int)sid.capsule_collision_center_y.value;
  318. int capsule_collision_radius = (int)sid.capsule_collision_radius.value;
  319. int capsule_collision_height = (int)sid.capsule_collision_height.value;
  320. int collision_x = (int)sid.collision_x.value;
  321. int collision_y = (int)sid.collision_y.value;
  322. int collision_w = (int)sid.collision_w.value;
  323. int collision_h = (int)sid.collision_h.value;
  324. string actor_class = (string)sid.actor_class.value;
  325. bool lock_rotation_y = sid.lock_rotation_y.active;
  326. double mass = (double)sid.mass.value;
  327. sid.destroy();
  328. foreach (unowned string filename_i in filenames)
  329. {
  330. if (!filename_i.has_suffix(".png"))
  331. continue;
  332. GLib.File file_src = File.new_for_path(filename_i);
  333. GLib.File file_dst = File.new_for_path(Path.build_filename(destination_dir, file_src.get_basename()));
  334. string resource_filename = _source_dir.get_relative_path(file_dst);
  335. string resource_path = resource_filename.substring(0, resource_filename.last_index_of_char('.'));
  336. string resource_name = resource_path_to_resource_name(resource_path);
  337. SJSON.save(importer_settings, Path.build_filename(_source_dir.get_path(), resource_path) + ".importer_settings");
  338. Hashtable textures = new Hashtable();
  339. textures["u_albedo"] = resource_name;
  340. Hashtable uniform = new Hashtable();
  341. uniform["type"] = "vector4";
  342. uniform["value"] = Vector4(1.0, 1.0, 1.0, 1.0).to_array();
  343. Hashtable uniforms = new Hashtable();
  344. uniforms["u_color"] = uniform;
  345. Hashtable material = new Hashtable();
  346. material["shader"] = "sprite";
  347. material["textures"] = textures;
  348. material["uniforms"] = uniforms;
  349. SJSON.save(material, Path.build_filename(_source_dir.get_path(), resource_path) + ".material");
  350. try
  351. {
  352. file_src.copy(file_dst, FileCopyFlags.OVERWRITE);
  353. }
  354. catch (Error e)
  355. {
  356. stderr.printf("Error: %s\n", e.message);
  357. }
  358. Hashtable texture = new Hashtable();
  359. texture["source"] = resource_path_to_resource_name(resource_filename);
  360. texture["generate_mips"] = false;
  361. texture["normal_map"] = false;
  362. SJSON.save(texture, Path.build_filename(_source_dir.get_path(), resource_path) + ".texture");
  363. Hashtable sprite = new Hashtable();
  364. sprite["width"] = width;
  365. sprite["height"] = height;
  366. ArrayList<Value?> frames = new ArrayList<Value?>();
  367. for (int r = 0; r < num_v; ++r)
  368. {
  369. for (int c = 0; c < num_h; ++c)
  370. {
  371. Vector2 cell_xy = sprite_cell_xy(r
  372. , c
  373. , offset_x
  374. , offset_y
  375. , cell_w
  376. , cell_h
  377. , spacing_x
  378. , spacing_y
  379. );
  380. // Pivot is relative to the top-left corner of the cell
  381. int x = (int)cell_xy.x;
  382. int y = (int)cell_xy.y;
  383. Hashtable data = new Hashtable();
  384. data["name"] = "sprite_%d".printf(c+num_h*r);
  385. data["region"] = Vector4(x, y, cell_w, cell_h).to_array();
  386. data["pivot"] = Vector2(x+pivot_xy.x, y+pivot_xy.y).to_array();
  387. frames.add(data);
  388. }
  389. }
  390. sprite["frames"] = frames;
  391. SJSON.save(sprite, Path.build_filename(_source_dir.get_path(), resource_path) + ".sprite");
  392. // Generate .unit
  393. Database db = new Database();
  394. // Do not overwrite existing .unit
  395. string unit_name = Path.build_filename(_source_dir.get_path(), resource_path) + ".unit";
  396. if (File.new_for_path(unit_name).query_exists())
  397. db.load(unit_name);
  398. Unit unit = new Unit(db, GUID_ZERO, null);
  399. // Create transform
  400. {
  401. Guid id = Guid.new_guid();
  402. if (!unit.has_component("transform", ref id))
  403. {
  404. db.create(id);
  405. db.set_property_vector3 (id, "data.position", VECTOR3_ZERO);
  406. db.set_property_quaternion(id, "data.rotation", QUATERNION_IDENTITY);
  407. db.set_property_vector3 (id, "data.scale", VECTOR3_ONE);
  408. db.set_property_string (id, "type", "transform");
  409. db.add_to_set(GUID_ZERO, "components", id);
  410. }
  411. else
  412. {
  413. unit.set_component_property_vector3 (id, "data.position", VECTOR3_ZERO);
  414. unit.set_component_property_quaternion(id, "data.rotation", QUATERNION_IDENTITY);
  415. unit.set_component_property_vector3 (id, "data.scale", VECTOR3_ONE);
  416. unit.set_component_property_string (id, "type", "transform");
  417. }
  418. }
  419. // Create sprite_renderer
  420. {
  421. Guid id = Guid.new_guid();
  422. if (!unit.has_component("sprite_renderer", ref id))
  423. {
  424. db.create(id);
  425. db.set_property_string(id, "data.material", resource_name);
  426. db.set_property_string(id, "data.sprite_resource", resource_name);
  427. db.set_property_double(id, "data.layer", layer);
  428. db.set_property_double(id, "data.depth", depth);
  429. db.set_property_bool (id, "data.visible", true);
  430. db.set_property_string(id, "type", "sprite_renderer");
  431. db.add_to_set(GUID_ZERO, "components", id);
  432. }
  433. else
  434. {
  435. unit.set_component_property_string(id, "data.material", resource_name);
  436. unit.set_component_property_string(id, "data.sprite_resource", resource_name);
  437. unit.set_component_property_double(id, "data.layer", layer);
  438. unit.set_component_property_double(id, "data.depth", depth);
  439. unit.set_component_property_bool (id, "data.visible", true);
  440. unit.set_component_property_string(id, "type", "sprite_renderer");
  441. }
  442. }
  443. if (collision_enabled)
  444. {
  445. // Create collider
  446. double PIXELS_PER_METER = 32.0;
  447. {
  448. Guid id = Guid.new_guid();
  449. Quaternion rotation = QUATERNION_IDENTITY;
  450. if (!unit.has_component("collider", ref id))
  451. {
  452. db.create(id);
  453. db.set_property_string(id, "data.source", "inline");
  454. if (shape_active_name == "square_collider")
  455. {
  456. double pos_x = (collision_x + collision_w/2.0 - pivot_xy.x) / PIXELS_PER_METER;
  457. double pos_y = -(collision_y + collision_h/2.0 - pivot_xy.y) / PIXELS_PER_METER;
  458. Vector3 position = Vector3(pos_x, 0, pos_y);
  459. Vector3 half_extents = Vector3(collision_w/2/PIXELS_PER_METER, 0.5/PIXELS_PER_METER, collision_h/2/PIXELS_PER_METER);
  460. db.set_property_vector3 (id, "data.collider_data.position", position);
  461. db.set_property_string (id, "data.shape", "box");
  462. db.set_property_quaternion(id, "data.collider_data.rotation", rotation);
  463. db.set_property_vector3 (id, "data.collider_data.half_extents", half_extents);
  464. }
  465. else if (shape_active_name == "circle_collider")
  466. {
  467. double pos_x = (circle_collision_center_x - pivot_xy.x) / PIXELS_PER_METER;
  468. double pos_y = -(circle_collision_center_y - pivot_xy.y) / PIXELS_PER_METER;
  469. Vector3 position = Vector3(pos_x, 0, pos_y);
  470. double radius = circle_collision_radius / PIXELS_PER_METER;
  471. db.set_property_vector3 (id, "data.collider_data.position", position);
  472. db.set_property_string (id, "data.shape", "sphere");
  473. db.set_property_quaternion(id, "data.collider_data.rotation", rotation);
  474. db.set_property_double (id, "data.collider_data.radius", radius);
  475. }
  476. else if (shape_active_name == "capsule_collider")
  477. {
  478. double pos_x = (capsule_collision_center_x - pivot_xy.x) / PIXELS_PER_METER;
  479. double pos_y = -(capsule_collision_center_y - pivot_xy.y) / PIXELS_PER_METER;
  480. Vector3 position = Vector3(pos_x, 0, pos_y);
  481. double radius = capsule_collision_radius / PIXELS_PER_METER;
  482. double capsule_height = (capsule_collision_height - 2*capsule_collision_radius) / PIXELS_PER_METER;
  483. db.set_property_vector3 (id, "data.collider_data.position", position);
  484. db.set_property_string (id, "data.shape", "capsule");
  485. db.set_property_quaternion(id, "data.collider_data.rotation", Quaternion.from_axis_angle(Vector3(0, 0, 1), (float)Math.PI/2));
  486. db.set_property_double (id, "data.collider_data.radius", radius);
  487. db.set_property_double (id, "data.collider_data.height", capsule_height);
  488. }
  489. db.set_property_string(id, "type", "collider");
  490. db.add_to_set(GUID_ZERO, "components", id);
  491. }
  492. else
  493. {
  494. unit.set_component_property_string(id, "data.source", "inline");
  495. if (shape_active_name == "square_collider")
  496. {
  497. double pos_x = (collision_x + collision_w/2.0 - pivot_xy.x) / PIXELS_PER_METER;
  498. double pos_y = -(collision_y + collision_h/2.0 - pivot_xy.y) / PIXELS_PER_METER;
  499. Vector3 position = Vector3(pos_x, 0, pos_y);
  500. Vector3 half_extents = Vector3(collision_w/2/PIXELS_PER_METER, 0.5/PIXELS_PER_METER, collision_h/2/PIXELS_PER_METER);
  501. unit.set_component_property_vector3 (id, "data.collider_data.position", position);
  502. unit.set_component_property_quaternion(id, "data.collider_data.rotation", rotation);
  503. unit.set_component_property_string (id, "data.shape", "box");
  504. unit.set_component_property_vector3 (id, "data.collider_data.half_extents", half_extents);
  505. }
  506. else if (shape_active_name == "circle_collider")
  507. {
  508. double pos_x = (circle_collision_center_x - pivot_xy.x) / PIXELS_PER_METER;
  509. double pos_y = -(circle_collision_center_y - pivot_xy.y) / PIXELS_PER_METER;
  510. Vector3 position = Vector3(pos_x, 0, pos_y);
  511. double radius = circle_collision_radius / PIXELS_PER_METER;
  512. unit.set_component_property_vector3 (id, "data.collider_data.position", position);
  513. unit.set_component_property_quaternion(id, "data.collider_data.rotation", rotation);
  514. unit.set_component_property_string (id, "data.shape", "sphere");
  515. unit.set_component_property_double (id, "data.collider_data.radius", radius);
  516. }
  517. else if (shape_active_name == "capsule_collider")
  518. {
  519. double pos_x = (capsule_collision_center_x - pivot_xy.x) / PIXELS_PER_METER;
  520. double pos_y = -(capsule_collision_center_y - pivot_xy.y) / PIXELS_PER_METER;
  521. Vector3 position = Vector3(pos_x, 0, pos_y);
  522. double radius = capsule_collision_radius / PIXELS_PER_METER;
  523. double capsule_height = (capsule_collision_height - 2*capsule_collision_radius) / PIXELS_PER_METER;
  524. unit.set_component_property_vector3 (id, "data.collider_data.position", position);
  525. unit.set_component_property_quaternion(id, "data.collider_data.rotation", Quaternion.from_axis_angle(Vector3(0, 0, 1), (float)Math.PI/2));
  526. unit.set_component_property_string (id, "data.shape", "capsule");
  527. unit.set_component_property_double (id, "data.collider_data.radius", radius);
  528. unit.set_component_property_double (id, "data.collider_data.height", capsule_height);
  529. }
  530. unit.set_component_property_string(id, "type", "collider");
  531. }
  532. }
  533. // Create actor
  534. {
  535. Guid id = Guid.new_guid();
  536. if (!unit.has_component("actor", ref id))
  537. {
  538. db.create(id);
  539. db.set_property_string(id, "data.class", actor_class);
  540. db.set_property_string(id, "data.collision_filter", "default");
  541. db.set_property_bool (id, "data.lock_rotation_x", true);
  542. db.set_property_bool (id, "data.lock_rotation_y", lock_rotation_y);
  543. db.set_property_bool (id, "data.lock_rotation_z", true);
  544. db.set_property_bool (id, "data.lock_translation_x", false);
  545. db.set_property_bool (id, "data.lock_translation_y", true);
  546. db.set_property_bool (id, "data.lock_translation_z", false);
  547. db.set_property_double(id, "data.mass", mass);
  548. db.set_property_string(id, "data.material", "default");
  549. db.set_property_string(id, "type", "actor");
  550. db.add_to_set(GUID_ZERO, "components", id);
  551. }
  552. else
  553. {
  554. unit.set_component_property_string(id, "data.class", actor_class);
  555. unit.set_component_property_string(id, "data.collision_filter", "default");
  556. unit.set_component_property_bool (id, "data.lock_rotation_x", true);
  557. unit.set_component_property_bool (id, "data.lock_rotation_y", lock_rotation_y);
  558. unit.set_component_property_bool (id, "data.lock_rotation_z", true);
  559. unit.set_component_property_bool (id, "data.lock_translation_x", false);
  560. unit.set_component_property_bool (id, "data.lock_translation_y", true);
  561. unit.set_component_property_bool (id, "data.lock_translation_z", false);
  562. unit.set_component_property_double(id, "data.mass", mass);
  563. unit.set_component_property_string(id, "data.material", "default");
  564. unit.set_component_property_string(id, "type", "actor");
  565. }
  566. }
  567. }
  568. db.save(unit_name);
  569. }
  570. }
  571. public void import_meshes(SList<string> filenames, string destination_dir)
  572. {
  573. foreach (unowned string filename_i in filenames)
  574. {
  575. if (!filename_i.has_suffix(".mesh"))
  576. continue;
  577. GLib.File file_src = File.new_for_path(filename_i);
  578. GLib.File file_dst = File.new_for_path(Path.build_filename(destination_dir, file_src.get_basename()));
  579. string resource_filename = _source_dir.get_relative_path(file_dst);
  580. string resource_path = resource_filename.substring(0, resource_filename.last_index_of_char('.'));
  581. string resource_name = resource_path_to_resource_name(resource_path);
  582. // Choose material or create new one
  583. FileChooserDialog mtl = new FileChooserDialog("Select material... (Cancel to create a new one)"
  584. , null
  585. , FileChooserAction.OPEN
  586. , "Cancel"
  587. , ResponseType.CANCEL
  588. , "Select"
  589. , ResponseType.ACCEPT
  590. );
  591. mtl.set_current_folder(_source_dir.get_path());
  592. FileFilter fltr = new FileFilter();
  593. fltr.set_filter_name("Material (*.material)");
  594. fltr.add_pattern("*.material");
  595. mtl.add_filter(fltr);
  596. string material_path = resource_path;
  597. if (mtl.run() == (int)ResponseType.ACCEPT)
  598. {
  599. material_path = _source_dir.get_relative_path(File.new_for_path(mtl.get_filename()));
  600. material_path = material_path.substring(0, material_path.last_index_of_char('.'));
  601. }
  602. else
  603. {
  604. Hashtable material = new Hashtable();
  605. material["shader"] = "mesh+DIFFUSE_MAP";
  606. material["textures"] = new Hashtable();
  607. material["uniforms"] = new Hashtable();
  608. SJSON.save(material, Path.build_filename(_source_dir.get_path(), material_path) + ".material");
  609. }
  610. mtl.destroy();
  611. string material_name = resource_path_to_resource_name(material_path);
  612. try
  613. {
  614. file_src.copy(file_dst, FileCopyFlags.OVERWRITE);
  615. }
  616. catch (Error e)
  617. {
  618. stderr.printf("Error: %s\n", e.message);
  619. }
  620. // Generate .unit
  621. Database db = new Database();
  622. // Do not overwrite existing .unit
  623. string unit_name = Path.build_filename(_source_dir.get_path(), resource_name) + ".unit";
  624. if (File.new_for_path(unit_name).query_exists())
  625. db.load(unit_name);
  626. Unit unit = new Unit(db, GUID_ZERO, null);
  627. // Create transform
  628. {
  629. Guid id = Guid.new_guid();
  630. if (!unit.has_component("transform", ref id))
  631. {
  632. db.create(id);
  633. db.set_property_vector3 (id, "data.position", VECTOR3_ZERO);
  634. db.set_property_quaternion(id, "data.rotation", QUATERNION_IDENTITY);
  635. db.set_property_vector3 (id, "data.scale", VECTOR3_ONE);
  636. db.set_property_string (id, "type", "transform");
  637. db.add_to_set(GUID_ZERO, "components", id);
  638. }
  639. else
  640. {
  641. unit.set_component_property_vector3 (id, "data.position", VECTOR3_ZERO);
  642. unit.set_component_property_quaternion(id, "data.rotation", QUATERNION_IDENTITY);
  643. unit.set_component_property_vector3 (id, "data.scale", VECTOR3_ONE);
  644. unit.set_component_property_string (id, "type", "transform");
  645. }
  646. }
  647. // Remove all existing mesh_renderer components
  648. {
  649. Guid id = GUID_ZERO;
  650. while (unit.has_component("mesh_renderer", ref id))
  651. unit.remove_component(id);
  652. }
  653. Hashtable mesh = SJSON.load(filename_i);
  654. Hashtable mesh_nodes = (Hashtable)mesh["nodes"];
  655. // Create mesh_renderer
  656. {
  657. foreach (var entry in mesh_nodes.entries)
  658. {
  659. string node_name = (string)entry.key;
  660. Guid id = Guid.new_guid();
  661. db.create(id);
  662. db.set_property_string(id, "data.geometry_name", node_name);
  663. db.set_property_string(id, "data.material", material_name);
  664. db.set_property_string(id, "data.mesh_resource", resource_name);
  665. db.set_property_bool (id, "data.visible", true);
  666. db.set_property_string(id, "type", "mesh_renderer");
  667. db.add_to_set(GUID_ZERO, "components", id);
  668. }
  669. }
  670. // Create collider
  671. {
  672. Guid id = Guid.new_guid();
  673. if (!unit.has_component("collider", ref id))
  674. {
  675. db.create(id);
  676. db.set_property_string(id, "data.shape", "mesh");
  677. db.set_property_string(id, "data.scene", resource_name);
  678. db.set_property_string(id, "data.name", mesh_nodes.entries.to_array()[0].key);
  679. db.set_property_string(id, "type", "collider");
  680. db.add_to_set(GUID_ZERO, "components", id);
  681. }
  682. else
  683. {
  684. unit.set_component_property_string(id, "data.shape", "mesh");
  685. unit.set_component_property_string(id, "data.scene", resource_name);
  686. unit.set_component_property_string(id, "data.name", mesh_nodes.entries.to_array()[0].key);
  687. unit.set_component_property_string(id, "type", "collider");
  688. }
  689. }
  690. // Create actor
  691. {
  692. Guid id = Guid.new_guid();
  693. if (!unit.has_component("actor", ref id))
  694. {
  695. db.create(id);
  696. db.set_property_string(id, "data.class", "static");
  697. db.set_property_string(id, "data.collision_filter", "default");
  698. db.set_property_double(id, "data.mass", 10);
  699. db.set_property_string(id, "data.material", "default");
  700. db.set_property_string(id, "type", "actor");
  701. db.add_to_set(GUID_ZERO, "components", id);
  702. }
  703. else
  704. {
  705. unit.set_component_property_string(id, "data.class", "static");
  706. unit.set_component_property_string(id, "data.collision_filter", "default");
  707. unit.set_component_property_double(id, "data.mass", 10);
  708. unit.set_component_property_string(id, "data.material", "default");
  709. unit.set_component_property_string(id, "type", "actor");
  710. }
  711. }
  712. db.save(unit_name);
  713. }
  714. }
  715. public void import_sounds(SList<string> filenames, string destination_dir)
  716. {
  717. foreach (unowned string filename_i in filenames)
  718. {
  719. if (!filename_i.has_suffix(".wav"))
  720. continue;
  721. GLib.File file_src = File.new_for_path(filename_i);
  722. GLib.File file_dst = File.new_for_path(Path.build_filename(destination_dir, file_src.get_basename()));
  723. string resource_filename = _source_dir.get_relative_path(file_dst);
  724. string resource_path = resource_filename.substring(0, resource_filename.last_index_of_char('.'));
  725. try
  726. {
  727. file_src.copy(file_dst, FileCopyFlags.OVERWRITE);
  728. }
  729. catch (Error e)
  730. {
  731. stderr.printf("Error: %s\n", e.message);
  732. }
  733. Hashtable sound = new Hashtable();
  734. sound["source"] = resource_path_to_resource_name(resource_filename);
  735. SJSON.save(sound, Path.build_filename(_source_dir.get_path(), resource_path) + ".sound");
  736. }
  737. }
  738. public void import_textures(SList<string> filenames, string destination_dir)
  739. {
  740. foreach (unowned string filename_i in filenames)
  741. {
  742. if (!filename_i.has_suffix(".png"))
  743. continue;
  744. GLib.File file_src = File.new_for_path(filename_i);
  745. GLib.File file_dst = File.new_for_path(Path.build_filename(destination_dir, file_src.get_basename()));
  746. string resource_filename = _source_dir.get_relative_path(file_dst);
  747. string resource_path = resource_filename.substring(0, resource_filename.last_index_of_char('.'));
  748. try
  749. {
  750. file_src.copy(file_dst, FileCopyFlags.OVERWRITE);
  751. }
  752. catch (Error e)
  753. {
  754. stderr.printf("Error: %s\n", e.message);
  755. }
  756. Hashtable texture = new Hashtable();
  757. texture["source"] = resource_path_to_resource_name(resource_filename);
  758. texture["generate_mips"] = true;
  759. texture["normal_map"] = false;
  760. SJSON.save(texture, Path.build_filename(_source_dir.get_path(), resource_path) + ".texture");
  761. }
  762. }
  763. public void import(string? destination_dir, Gtk.Window? parent_window = null)
  764. {
  765. Gtk.FileFilter sprite_filter = new Gtk.FileFilter();
  766. sprite_filter.set_filter_name("Sprite (*.png)");
  767. sprite_filter.add_pattern("*.png");
  768. Gtk.FileFilter mesh_filter = new Gtk.FileFilter();
  769. mesh_filter.set_filter_name("Mesh (*.mesh)");
  770. mesh_filter.add_pattern("*.mesh");
  771. Gtk.FileFilter sound_filter = new Gtk.FileFilter();
  772. sound_filter.set_filter_name("Sound (*.wav)");
  773. sound_filter.add_pattern("*.wav");
  774. Gtk.FileFilter texture_filter = new Gtk.FileFilter();
  775. texture_filter.set_filter_name("Texture (*.png, *.tga, *.dds, *.ktx, *.pvr)");
  776. texture_filter.add_pattern("*.png");
  777. texture_filter.add_pattern("*.tga");
  778. texture_filter.add_pattern("*.dds");
  779. texture_filter.add_pattern("*.ktx");
  780. texture_filter.add_pattern("*.pvr");
  781. Gtk.FileChooserDialog src = new Gtk.FileChooserDialog("Import..."
  782. , parent_window
  783. , FileChooserAction.OPEN
  784. , "Cancel"
  785. , ResponseType.CANCEL
  786. , "Open"
  787. , ResponseType.ACCEPT
  788. );
  789. src.select_multiple = true;
  790. src.add_filter(sprite_filter);
  791. src.add_filter(mesh_filter);
  792. src.add_filter(sound_filter);
  793. src.add_filter(texture_filter);
  794. if (src.run() != (int)ResponseType.ACCEPT)
  795. {
  796. src.destroy();
  797. return;
  798. }
  799. string out_dir = "";
  800. if (destination_dir == null)
  801. {
  802. Gtk.FileChooserDialog dst = new Gtk.FileChooserDialog("Select destination folder..."
  803. , parent_window
  804. , FileChooserAction.SELECT_FOLDER
  805. , "Cancel"
  806. , ResponseType.CANCEL
  807. , "Select"
  808. , ResponseType.ACCEPT
  809. );
  810. dst.set_current_folder(this.source_dir());
  811. if (dst.run() != (int)ResponseType.ACCEPT)
  812. {
  813. dst.destroy();
  814. src.destroy();
  815. return;
  816. }
  817. out_dir = dst.get_filename();
  818. dst.destroy();
  819. }
  820. else
  821. {
  822. out_dir = destination_dir;
  823. }
  824. Gtk.FileFilter? current_filter = src.get_filter();
  825. GLib.SList<string> filenames = src.get_filenames();
  826. if (current_filter != null)
  827. {
  828. if (current_filter == sprite_filter)
  829. this.import_sprites(filenames, out_dir);
  830. else if (current_filter == mesh_filter)
  831. this.import_meshes(filenames, out_dir);
  832. else if (current_filter == sound_filter)
  833. this.import_sounds(filenames, out_dir);
  834. else if (current_filter == texture_filter)
  835. this.import_textures(filenames, out_dir);
  836. }
  837. _data_compiler.compile.begin(this.data_dir(), this.platform(), (obj, res) => {
  838. _data_compiler.compile.end(res);
  839. });
  840. src.destroy();
  841. }
  842. public void delete_tree(GLib.File file) throws Error
  843. {
  844. GLib.FileEnumerator fe = file.enumerate_children("standard::*"
  845. , GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS
  846. );
  847. GLib.FileInfo info = null;
  848. while ((info = fe.next_file()) != null)
  849. {
  850. GLib.File subfile = file.resolve_relative_path(info.get_name());
  851. if (info.get_file_type() == GLib.FileType.DIRECTORY)
  852. delete_tree(subfile);
  853. else
  854. subfile.delete();
  855. }
  856. file.delete();
  857. }
  858. }
  859. }