physics_resource.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. /*
  2. * Copyright (c) 2012-2024 Daniele Bartolini et al.
  3. * SPDX-License-Identifier: MIT
  4. */
  5. #include "config.h"
  6. #include "core/containers/array.inl"
  7. #include "core/containers/hash_map.inl"
  8. #include "core/filesystem/file.h"
  9. #include "core/filesystem/file_buffer.inl"
  10. #include "core/filesystem/filesystem.h"
  11. #include "core/json/json_object.inl"
  12. #include "core/json/sjson.h"
  13. #include "core/math/aabb.inl"
  14. #include "core/math/constants.h"
  15. #include "core/math/quaternion.inl"
  16. #include "core/math/sphere.inl"
  17. #include "core/memory/temp_allocator.inl"
  18. #include "core/strings/dynamic_string.inl"
  19. #include "core/strings/string.inl"
  20. #include "core/strings/string_id.inl"
  21. #include "resource/compile_options.inl"
  22. #include "resource/mesh.h"
  23. #include "resource/mesh_resource.h"
  24. #include "resource/physics_resource.h"
  25. #include "world/types.h"
  26. namespace crown
  27. {
  28. namespace physics_config_resource
  29. {
  30. const PhysicsMaterial *material(const PhysicsConfigResource *pcr, StringId32 name)
  31. {
  32. const PhysicsMaterial *begin = (PhysicsMaterial *)((const char *)pcr + pcr->materials_offset);
  33. for (u32 i = 0; i < pcr->num_materials; ++i) {
  34. if (begin[i].name == name)
  35. return &begin[i];
  36. }
  37. CE_FATAL("Material not found");
  38. return NULL;
  39. }
  40. const PhysicsActor *actor(const PhysicsConfigResource *pcr, StringId32 name)
  41. {
  42. const PhysicsActor *begin = (PhysicsActor *)((const char *)pcr + pcr->actors_offset);
  43. for (u32 i = 0; i < pcr->num_actors; ++i) {
  44. if (begin[i].name == name)
  45. return &begin[i];
  46. }
  47. CE_FATAL("Actor not found");
  48. return NULL;
  49. }
  50. const PhysicsCollisionFilter *filter(const PhysicsConfigResource *pcr, StringId32 name)
  51. {
  52. const PhysicsCollisionFilter *begin = (PhysicsCollisionFilter *)((const char *)pcr + pcr->filters_offset);
  53. for (u32 i = 0; i < pcr->num_filters; ++i) {
  54. if (begin[i].name == name)
  55. return &begin[i];
  56. }
  57. CE_FATAL("Filter not found");
  58. return NULL;
  59. }
  60. } // namespace physics_config_resource
  61. #if CROWN_CAN_COMPILE
  62. namespace physics_resource_internal
  63. {
  64. struct ColliderInfo
  65. {
  66. const char *name;
  67. ColliderType::Enum type;
  68. };
  69. static const ColliderInfo s_collider[] =
  70. {
  71. { "sphere", ColliderType::SPHERE },
  72. { "capsule", ColliderType::CAPSULE },
  73. { "box", ColliderType::BOX },
  74. { "convex_hull", ColliderType::CONVEX_HULL },
  75. { "mesh", ColliderType::MESH },
  76. { "heightfield", ColliderType::HEIGHTFIELD }
  77. };
  78. CE_STATIC_ASSERT(countof(s_collider) == ColliderType::COUNT);
  79. struct JointInfo
  80. {
  81. const char *name;
  82. JointType::Enum type;
  83. };
  84. static const JointInfo s_joint[] =
  85. {
  86. { "fixed", JointType::FIXED },
  87. { "hinge", JointType::HINGE },
  88. { "spring", JointType::SPRING }
  89. };
  90. CE_STATIC_ASSERT(countof(s_joint) == JointType::COUNT);
  91. static ColliderType::Enum shape_type_to_enum(const char *type)
  92. {
  93. for (u32 i = 0; i < countof(s_collider); ++i) {
  94. if (strcmp(type, s_collider[i].name) == 0)
  95. return s_collider[i].type;
  96. }
  97. return ColliderType::COUNT;
  98. }
  99. static JointType::Enum joint_type_to_enum(const char *type)
  100. {
  101. for (u32 i = 0; i < countof(s_joint); ++i) {
  102. if (strcmp(type, s_joint[i].name) == 0)
  103. return s_joint[i].type;
  104. }
  105. return JointType::COUNT;
  106. }
  107. void compile_sphere(ColliderDesc &sd, const Array<Vector3> &points)
  108. {
  109. AABB aabb;
  110. aabb::from_points(aabb, array::size(points), array::begin(points));
  111. const Vector3 origin = aabb::center(aabb);
  112. sd.local_tm.t = vector4(origin.x, origin.y, origin.z, 1.0f);
  113. sd.sphere.radius = max(0.0f, aabb.max.x - aabb.min.x);
  114. sd.sphere.radius = max(sd.sphere.radius, aabb.max.y - aabb.min.y);
  115. sd.sphere.radius = max(sd.sphere.radius, aabb.max.z - aabb.min.z);
  116. sd.sphere.radius *= 0.5f;
  117. }
  118. void compile_capsule(ColliderDesc &sd, const Array<Vector3> &points)
  119. {
  120. AABB aabb;
  121. aabb::from_points(aabb, array::size(points), array::begin(points));
  122. const Vector3 origin = aabb::center(aabb);
  123. sd.local_tm.t = vector4(origin.x, origin.y, origin.z, 1.0f);
  124. sd.capsule.radius = aabb::radius(aabb) / 2.0f;
  125. sd.capsule.height = (aabb.max.y - aabb.min.y) / 2.0f;
  126. }
  127. void compile_box(ColliderDesc &sd, const Array<Vector3> &points)
  128. {
  129. AABB aabb;
  130. aabb::from_points(aabb, array::size(points), array::begin(points));
  131. const Vector3 origin = aabb::center(aabb);
  132. sd.local_tm.t = vector4(origin.x, origin.y, origin.z, 1.0f);
  133. sd.box.half_size = (aabb.max - aabb.min) * 0.5f;
  134. }
  135. s32 compile_collider(Buffer &output, const char *json, CompileOptions &opts)
  136. {
  137. TempAllocator4096 ta;
  138. JsonObject obj(ta);
  139. sjson::parse(obj, json);
  140. DynamicString type(ta);
  141. sjson::parse_string(type, obj["shape"]);
  142. ColliderType::Enum st = shape_type_to_enum(type.c_str());
  143. DATA_COMPILER_ASSERT(st != ColliderType::COUNT
  144. , opts
  145. , "Unknown shape type: '%s'"
  146. , type.c_str()
  147. );
  148. ColliderDesc cd;
  149. memset((void *)&cd, 0, sizeof(cd));
  150. cd.type = st;
  151. cd.local_tm = MATRIX4X4_IDENTITY;
  152. cd.size = 0;
  153. Array<Vector3> points(default_allocator());
  154. Array<u16> point_indices(default_allocator());
  155. DynamicString source(ta);
  156. if (json_object::has(obj, "source"))
  157. sjson::parse_string(source, obj["source"]);
  158. bool explicit_collider = source == "mesh"
  159. || (source != "inline" && json_object::has(obj, "scene"));
  160. if (explicit_collider) {
  161. DynamicString scene(ta);
  162. DynamicString name(ta);
  163. sjson::parse_string(scene, obj["scene"]);
  164. sjson::parse_string(name, obj["name"]);
  165. // Parse mesh resource.
  166. Mesh mesh(default_allocator());
  167. s32 err = mesh::parse(mesh, opts, scene.c_str());
  168. DATA_COMPILER_ENSURE(err == 0, opts);
  169. Node deffault_node(default_allocator());
  170. Node &node = hash_map::get(mesh._nodes, name, deffault_node);
  171. DATA_COMPILER_ASSERT(&node != &deffault_node
  172. , opts
  173. , "Node '%s' does not exist"
  174. , name.c_str()
  175. );
  176. Geometry deffault_geometry(default_allocator());
  177. Geometry &geometry = hash_map::get(mesh._geometries, node._geometry, deffault_geometry);
  178. DATA_COMPILER_ASSERT(&geometry != &deffault_geometry
  179. , opts
  180. , "Geometry '%s' does not exist"
  181. , node._geometry.c_str()
  182. );
  183. for (u32 i = 0; i < array::size(geometry._positions); i += 3) {
  184. Vector3 p;
  185. p.x = geometry._positions[i + 0];
  186. p.y = geometry._positions[i + 1];
  187. p.z = geometry._positions[i + 2];
  188. array::push_back(points, p);
  189. }
  190. point_indices = geometry._position_indices;
  191. switch (cd.type) {
  192. case ColliderType::SPHERE: compile_sphere(cd, points); break;
  193. case ColliderType::CAPSULE: compile_capsule(cd, points); break;
  194. case ColliderType::BOX: compile_box(cd, points); break;
  195. case ColliderType::CONVEX_HULL: break;
  196. case ColliderType::MESH: break;
  197. case ColliderType::HEIGHTFIELD:
  198. DATA_COMPILER_ASSERT(false, opts, "Not implemented yet");
  199. break;
  200. default:
  201. DATA_COMPILER_ASSERT(false, opts, "Invalid collider type");
  202. break;
  203. }
  204. } else {
  205. JsonObject collider_data(ta);
  206. JsonArray org(ta);
  207. DATA_COMPILER_ASSERT(json_object::has(obj, "collider_data")
  208. , opts
  209. , "No collider_data found"
  210. );
  211. sjson::parse_object(collider_data, obj["collider_data"]);
  212. Quaternion rotation = sjson::parse_quaternion(collider_data["rotation"]);
  213. Vector3 position = sjson::parse_vector3(collider_data["position"]);
  214. Matrix4x4 matrix_local = from_quaternion_translation(rotation, position);
  215. cd.local_tm = matrix_local;
  216. if (cd.type == ColliderType::SPHERE) {
  217. cd.sphere.radius = sjson::parse_float(collider_data["radius"]);
  218. } else if (cd.type == ColliderType::BOX) {
  219. cd.box.half_size = sjson::parse_vector3(collider_data["half_extents"]);
  220. } else if (cd.type == ColliderType::CAPSULE) {
  221. cd.capsule.radius = sjson::parse_float(collider_data["radius"]);
  222. cd.capsule.height = sjson::parse_float(collider_data["height"]);
  223. } else {
  224. DATA_COMPILER_ASSERT(false, opts, "Invalid collider type");
  225. }
  226. }
  227. const bool needs_points = cd.type == ColliderType::CONVEX_HULL
  228. || cd.type == ColliderType::MESH;
  229. if (needs_points) {
  230. cd.size += sizeof(u32) + sizeof(Vector3)*array::size(points);
  231. if (cd.type == ColliderType::MESH)
  232. cd.size += sizeof(u32) + sizeof(u16)*array::size(point_indices);
  233. }
  234. FileBuffer fb(output);
  235. BinaryWriter bw(fb);
  236. bw.write(cd.type);
  237. bw.write(cd.local_tm);
  238. bw.write(cd.sphere.radius);
  239. bw.write(cd.capsule.radius);
  240. bw.write(cd.capsule.height);
  241. bw.write(cd.box.half_size);
  242. bw.write(cd.heightfield.width);
  243. bw.write(cd.heightfield.length);
  244. bw.write(cd.heightfield.height_scale);
  245. bw.write(cd.heightfield.height_min);
  246. bw.write(cd.heightfield.height_max);
  247. bw.write(cd.size);
  248. if (needs_points) {
  249. bw.write(array::size(points));
  250. for (u32 ii = 0; ii < array::size(points); ++ii)
  251. bw.write(points[ii]);
  252. if (cd.type == ColliderType::MESH) {
  253. bw.write(array::size(point_indices));
  254. for (u32 ii = 0; ii < array::size(point_indices); ++ii)
  255. bw.write(point_indices[ii]);
  256. }
  257. }
  258. return 0;
  259. }
  260. s32 compile_actor(Buffer &output, const char *json, CompileOptions & /*opts*/)
  261. {
  262. TempAllocator4096 ta;
  263. JsonObject obj(ta);
  264. sjson::parse(obj, json);
  265. u32 flags = 0;
  266. if (json_object::has(obj, "lock_translation_x") && sjson::parse_bool(obj["lock_translation_x"]))
  267. flags |= ActorFlags::LOCK_TRANSLATION_X;
  268. if (json_object::has(obj, "lock_translation_y") && sjson::parse_bool(obj["lock_translation_y"]))
  269. flags |= ActorFlags::LOCK_TRANSLATION_Y;
  270. if (json_object::has(obj, "lock_translation_z") && sjson::parse_bool(obj["lock_translation_z"]))
  271. flags |= ActorFlags::LOCK_TRANSLATION_Z;
  272. if (json_object::has(obj, "lock_rotation_x") && sjson::parse_bool(obj["lock_rotation_x"]))
  273. flags |= ActorFlags::LOCK_ROTATION_X;
  274. if (json_object::has(obj, "lock_rotation_y") && sjson::parse_bool(obj["lock_rotation_y"]))
  275. flags |= ActorFlags::LOCK_ROTATION_Y;
  276. if (json_object::has(obj, "lock_rotation_z") && sjson::parse_bool(obj["lock_rotation_z"]))
  277. flags |= ActorFlags::LOCK_ROTATION_Z;
  278. ActorResource ar;
  279. ar.actor_class = sjson::parse_string_id(obj["class"]);
  280. ar.mass = sjson::parse_float (obj["mass"]);
  281. ar.flags = flags;
  282. ar.collision_filter = sjson::parse_string_id(obj["collision_filter"]);
  283. ar.material = sjson::parse_string_id(obj["material"]);
  284. FileBuffer fb(output);
  285. BinaryWriter bw(fb);
  286. bw.write(ar.actor_class);
  287. bw.write(ar.mass);
  288. bw.write(ar.flags);
  289. bw.write(ar.collision_filter);
  290. bw.write(ar.material);
  291. return 0;
  292. }
  293. s32 compile_joint(Buffer &output, const char *json, CompileOptions &opts)
  294. {
  295. TempAllocator4096 ta;
  296. JsonObject obj(ta);
  297. sjson::parse(obj, json);
  298. DynamicString type(ta);
  299. sjson::parse_string(type, obj["type"]);
  300. JointType::Enum jt = joint_type_to_enum(type.c_str());
  301. DATA_COMPILER_ASSERT(jt != JointType::COUNT
  302. , opts
  303. , "Unknown joint type: '%s'"
  304. , type.c_str()
  305. );
  306. JointDesc jd;
  307. jd.type = jt;
  308. jd.anchor_0 = sjson::parse_vector3(obj["anchor_0"]);
  309. jd.anchor_1 = sjson::parse_vector3(obj["anchor_1"]);
  310. switch (jd.type) {
  311. case JointType::HINGE:
  312. jd.hinge.use_motor = sjson::parse_bool (obj["use_motor"]);
  313. jd.hinge.target_velocity = sjson::parse_float(obj["target_velocity"]);
  314. jd.hinge.max_motor_impulse = sjson::parse_float(obj["max_motor_impulse"]);
  315. jd.hinge.lower_limit = sjson::parse_float(obj["lower_limit"]);
  316. jd.hinge.upper_limit = sjson::parse_float(obj["upper_limit"]);
  317. jd.hinge.bounciness = sjson::parse_float(obj["bounciness"]);
  318. break;
  319. }
  320. FileBuffer fb(output);
  321. BinaryWriter bw(fb);
  322. bw.write(jd.type);
  323. bw.write(jd.anchor_0);
  324. bw.write(jd.anchor_1);
  325. bw.write(jd.breakable);
  326. bw.write(jd._pad[0]);
  327. bw.write(jd._pad[1]);
  328. bw.write(jd._pad[2]);
  329. bw.write(jd.break_force);
  330. bw.write(jd.hinge);
  331. bw.write(jd.hinge.axis);
  332. bw.write(jd.hinge.use_motor);
  333. bw.write(jd.hinge.target_velocity);
  334. bw.write(jd.hinge.max_motor_impulse);
  335. bw.write(jd.hinge.use_limits);
  336. bw.write(jd.hinge.lower_limit);
  337. bw.write(jd.hinge.upper_limit);
  338. bw.write(jd.hinge.bounciness);
  339. return 0;
  340. }
  341. } // namespace physics_resource_internal
  342. namespace physics_config_resource_internal
  343. {
  344. void parse_materials(const char *json, Array<PhysicsMaterial> &objects)
  345. {
  346. TempAllocator4096 ta;
  347. JsonObject obj(ta);
  348. sjson::parse(obj, json);
  349. auto cur = json_object::begin(obj);
  350. auto end = json_object::end(obj);
  351. for (; cur != end; ++cur) {
  352. JSON_OBJECT_SKIP_HOLE(obj, cur);
  353. const StringView key = cur->first;
  354. const char *value = cur->second;
  355. JsonObject material(ta);
  356. sjson::parse_object(material, value);
  357. PhysicsMaterial mat;
  358. mat.name = StringId32(key.data(), key.length());
  359. mat.friction = sjson::parse_float(material["friction"]);
  360. mat.rolling_friction = sjson::parse_float(material["rolling_friction"]);
  361. mat.restitution = sjson::parse_float(material["restitution"]);
  362. array::push_back(objects, mat);
  363. }
  364. }
  365. void parse_actors(const char *json, Array<PhysicsActor> &objects)
  366. {
  367. TempAllocator4096 ta;
  368. JsonObject obj(ta);
  369. sjson::parse(obj, json);
  370. auto cur = json_object::begin(obj);
  371. auto end = json_object::end(obj);
  372. for (; cur != end; ++cur) {
  373. JSON_OBJECT_SKIP_HOLE(obj, cur);
  374. const StringView key = cur->first;
  375. const char *value = cur->second;
  376. JsonObject actor(ta);
  377. sjson::parse_object(actor, value);
  378. PhysicsActor pa;
  379. pa.name = StringId32(key.data(), key.length());
  380. pa.linear_damping = 0.0f;
  381. pa.angular_damping = 0.0f;
  382. if (json_object::has(actor, "linear_damping"))
  383. pa.linear_damping = sjson::parse_float(actor["linear_damping"]);
  384. if (json_object::has(actor, "angular_damping"))
  385. pa.angular_damping = sjson::parse_float(actor["angular_damping"]);
  386. pa.flags = 0;
  387. if (json_object::has(actor, "dynamic") && sjson::parse_bool(actor["dynamic"]))
  388. pa.flags |= CROWN_PHYSICS_ACTOR_DYNAMIC;
  389. if (json_object::has(actor, "kinematic") && sjson::parse_bool(actor["kinematic"]))
  390. pa.flags |= CROWN_PHYSICS_ACTOR_KINEMATIC;
  391. if (json_object::has(actor, "disable_gravity") && sjson::parse_bool(actor["disable_gravity"]))
  392. pa.flags |= CROWN_PHYSICS_ACTOR_DISABLE_GRAVITY;
  393. if (json_object::has(actor, "trigger") && sjson::parse_bool(actor["trigger"]))
  394. pa.flags |= CROWN_PHYSICS_ACTOR_TRIGGER;
  395. array::push_back(objects, pa);
  396. }
  397. }
  398. struct CollisionFilterCompiler
  399. {
  400. CompileOptions &_opts;
  401. HashMap<StringId32, u32> _filter_map;
  402. Array<PhysicsCollisionFilter> _filters;
  403. u32 _filter;
  404. explicit CollisionFilterCompiler(CompileOptions &opts)
  405. : _opts(opts)
  406. , _filter_map(default_allocator())
  407. , _filters(default_allocator())
  408. , _filter(1)
  409. {
  410. }
  411. void parse(const char *json)
  412. {
  413. TempAllocator4096 ta;
  414. JsonObject obj(ta);
  415. sjson::parse(obj, json);
  416. auto cur = json_object::begin(obj);
  417. auto end = json_object::end(obj);
  418. for (; cur != end; ++cur) {
  419. JSON_OBJECT_SKIP_HOLE(obj, cur);
  420. const StringView key = cur->first;
  421. const StringId32 id = StringId32(key.data(), key.length());
  422. hash_map::set(_filter_map, id, new_filter_mask());
  423. }
  424. cur = json_object::begin(obj);
  425. end = json_object::end(obj);
  426. for (; cur != end; ++cur) {
  427. JSON_OBJECT_SKIP_HOLE(obj, cur);
  428. const StringView key = cur->first;
  429. const char *value = cur->second;
  430. const StringId32 id = StringId32(key.data(), key.length());
  431. TempAllocator4096 ta;
  432. JsonObject filter(ta);
  433. sjson::parse_object(filter, value);
  434. JsonArray collides_with(ta);
  435. sjson::parse_array(collides_with, filter["collides_with"]);
  436. u32 mask = 0;
  437. for (u32 i = 0; i < array::size(collides_with); ++i) {
  438. const StringId32 fi = sjson::parse_string_id(collides_with[i]);
  439. mask |= filter_to_mask(fi);
  440. }
  441. // Build mask
  442. PhysicsCollisionFilter pcf;
  443. pcf.name = id;
  444. pcf.me = filter_to_mask(id);
  445. pcf.mask = mask;
  446. array::push_back(_filters, pcf);
  447. }
  448. }
  449. u32 new_filter_mask()
  450. {
  451. DATA_COMPILER_ASSERT(_filter != 0x80000000u
  452. , _opts
  453. , "Too many collision filters"
  454. );
  455. const u32 f = _filter;
  456. _filter = _filter << 1;
  457. return f;
  458. }
  459. u32 filter_to_mask(StringId32 filter)
  460. {
  461. DATA_COMPILER_ASSERT(hash_map::has(_filter_map, filter)
  462. , _opts
  463. , "Filter not found"
  464. );
  465. return hash_map::get(_filter_map, filter, 0u);
  466. }
  467. };
  468. s32 compile(CompileOptions &opts)
  469. {
  470. Buffer buf = opts.read();
  471. TempAllocator4096 ta;
  472. JsonObject obj(ta);
  473. sjson::parse(obj, buf);
  474. Array<PhysicsMaterial> materials(default_allocator());
  475. Array<PhysicsActor> actors(default_allocator());
  476. CollisionFilterCompiler cfc(opts);
  477. // Parse materials
  478. if (json_object::has(obj, "collision_filters"))
  479. cfc.parse(obj["collision_filters"]);
  480. if (json_object::has(obj, "materials"))
  481. parse_materials(obj["materials"], materials);
  482. if (json_object::has(obj, "actors"))
  483. parse_actors(obj["actors"], actors);
  484. // Setup struct for writing
  485. PhysicsConfigResource pcr;
  486. pcr.version = RESOURCE_HEADER(RESOURCE_VERSION_PHYSICS_CONFIG);
  487. pcr.num_materials = array::size(materials);
  488. pcr.num_actors = array::size(actors);
  489. pcr.num_filters = array::size(cfc._filters);
  490. u32 offt = sizeof(PhysicsConfigResource);
  491. pcr.materials_offset = offt;
  492. offt += sizeof(PhysicsMaterial) * pcr.num_materials;
  493. pcr.actors_offset = offt;
  494. offt += sizeof(PhysicsActor) * pcr.num_actors;
  495. pcr.filters_offset = offt;
  496. // Write all
  497. opts.write(pcr.version);
  498. opts.write(pcr.num_materials);
  499. opts.write(pcr.materials_offset);
  500. opts.write(pcr.num_actors);
  501. opts.write(pcr.actors_offset);
  502. opts.write(pcr.num_filters);
  503. opts.write(pcr.filters_offset);
  504. // Write materials
  505. for (u32 i = 0; i < pcr.num_materials; ++i) {
  506. opts.write(materials[i].name._id);
  507. opts.write(materials[i].friction);
  508. opts.write(materials[i].rolling_friction);
  509. opts.write(materials[i].restitution);
  510. }
  511. // Write actors
  512. for (u32 i = 0; i < pcr.num_actors; ++i) {
  513. opts.write(actors[i].name._id);
  514. opts.write(actors[i].linear_damping);
  515. opts.write(actors[i].angular_damping);
  516. opts.write(actors[i].flags);
  517. }
  518. // Write collision filters
  519. for (u32 i = 0; i < array::size(cfc._filters); ++i) {
  520. opts.write(cfc._filters[i].name._id);
  521. opts.write(cfc._filters[i].me);
  522. opts.write(cfc._filters[i].mask);
  523. }
  524. return 0;
  525. }
  526. } // namespace physics_config_resource_internal
  527. #endif // if CROWN_CAN_COMPILE
  528. } // namespace crown