project.vala 27 KB

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