scene-to-spec.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import type { Scene, SceneObject } from "./types";
  2. interface SpecElement {
  3. type: string;
  4. props: Record<string, unknown>;
  5. children: string[];
  6. }
  7. interface Spec {
  8. root: string;
  9. elements: Record<string, SpecElement>;
  10. }
  11. const primitiveTypeMap: Record<string, string> = {
  12. box: "GameBox",
  13. sphere: "GameSphere",
  14. cylinder: "GameCylinder",
  15. cone: "GameCone",
  16. torus: "GameTorus",
  17. plane: "GamePlane",
  18. capsule: "GameCapsule",
  19. knot: "GameKnot",
  20. tetrahedron: "GameTetrahedron",
  21. octahedron: "GameOctahedron",
  22. dodecahedron: "GameDodecahedron",
  23. icosahedron: "GameIcosahedron",
  24. extrude: "GameExtrude",
  25. tube: "GameTube",
  26. shape: "GameShape",
  27. mesh: "GameMesh",
  28. };
  29. function objectToSpecType(obj: SceneObject): string {
  30. if (obj.type === "light") return "GameLight";
  31. if (obj.type === "player") return "Player";
  32. if (obj.type === "character") return "GameCharacter";
  33. if (obj.type === "model") return "GameModel";
  34. if (obj.type === "sound") return "SoundEmitter";
  35. if (obj.type === "image" || obj.type === "video") return "MediaPlane";
  36. return primitiveTypeMap[obj.type] || "GameBox";
  37. }
  38. function objectToProps(obj: SceneObject): Record<string, unknown> {
  39. const props: Record<string, unknown> = {
  40. position: obj.position,
  41. rotation: obj.rotation,
  42. scale: obj.scale,
  43. objectId: obj.id,
  44. };
  45. const specType = objectToSpecType(obj);
  46. if (specType === "GameLight") {
  47. props.lightType = obj.lightType || "point";
  48. props.color = obj.material.color;
  49. props.intensity = obj.intensity ?? 1;
  50. if (obj.distance) props.distance = obj.distance;
  51. if (obj.decay) props.decay = obj.decay;
  52. if (obj.angle) props.angle = obj.angle;
  53. if (obj.penumbra) props.penumbra = obj.penumbra;
  54. props.castShadow =
  55. obj.lightType === "directional" || obj.lightType === "spot";
  56. return props;
  57. }
  58. if (specType === "Player") {
  59. props.isPlayer = true;
  60. return props;
  61. }
  62. if (specType === "GameCharacter") {
  63. props.modelUrl = obj.modelUrl || "";
  64. props.role = obj.character?.role || "villager";
  65. if (obj.physics.colliderType !== "none") {
  66. props.physics = obj.physics;
  67. }
  68. props.castShadow = true;
  69. props.receiveShadow = true;
  70. return props;
  71. }
  72. if (specType === "GameModel") {
  73. props.url = obj.modelUrl || "";
  74. if (obj.physics.colliderType !== "none") {
  75. props.physics = obj.physics;
  76. }
  77. if (obj.damage?.enabled) {
  78. props.damage = obj.damage;
  79. }
  80. props.castShadow = true;
  81. props.receiveShadow = true;
  82. return props;
  83. }
  84. if (specType === "SoundEmitter") {
  85. props.url = obj.sound?.url || "";
  86. props.loop = obj.sound?.loop ?? false;
  87. props.volume = obj.sound?.volume ?? 1;
  88. props.positional = obj.sound?.positional ?? true;
  89. props.distance = obj.sound?.distance ?? 10;
  90. return props;
  91. }
  92. if (specType === "MediaPlane") {
  93. props.url = obj.media?.url || "";
  94. props.mediaType = obj.type === "video" ? "video" : "image";
  95. props.loop = obj.media?.loop;
  96. props.autoplay = obj.media?.autoplay;
  97. props.muted = obj.media?.muted;
  98. return props;
  99. }
  100. // Game primitives
  101. props.material = obj.material;
  102. if (obj.physics.colliderType !== "none") {
  103. props.physics = obj.physics;
  104. }
  105. if (obj.damage?.enabled) {
  106. props.damage = obj.damage;
  107. }
  108. if (obj.shapeData) {
  109. props.shapeData = obj.shapeData;
  110. }
  111. if (obj.meshData) {
  112. props.meshData = obj.meshData;
  113. }
  114. // Geometry props
  115. if (obj.width != null) props.width = obj.width;
  116. if (obj.height != null) props.height = obj.height;
  117. if (obj.depth != null) props.depth = obj.depth;
  118. if (obj.radius != null) props.radius = obj.radius;
  119. if (obj.radiusTop != null) props.radiusTop = obj.radiusTop;
  120. if (obj.radiusBottom != null) props.radiusBottom = obj.radiusBottom;
  121. if (obj.radialSegments != null) props.radialSegments = obj.radialSegments;
  122. if (obj.tube != null) props.tube = obj.tube;
  123. if (obj.tubularSegments != null) props.tubularSegments = obj.tubularSegments;
  124. if (obj.widthSegments != null) props.widthSegments = obj.widthSegments;
  125. if (obj.heightSegments != null) props.heightSegments = obj.heightSegments;
  126. if (obj.length != null) props.length = obj.length;
  127. if (obj.p != null) props.p = obj.p;
  128. if (obj.q != null) props.q = obj.q;
  129. props.castShadow = true;
  130. props.receiveShadow = true;
  131. return props;
  132. }
  133. export function sceneToSpec(scene: Scene): Spec {
  134. const elements: Record<string, SpecElement> = {};
  135. const childIds: string[] = [];
  136. // Add environment
  137. elements["env"] = {
  138. type: "Environment",
  139. props: {
  140. preset: scene.environmentSettings.preset,
  141. background: true,
  142. blur: scene.environmentSettings.blur,
  143. intensity: scene.environmentSettings.intensity,
  144. },
  145. children: [],
  146. };
  147. childIds.push("env");
  148. // Add fog if enabled
  149. if (scene.sceneSettings.fog.enabled) {
  150. elements["fog"] = {
  151. type: "Fog",
  152. props: {
  153. color: scene.sceneSettings.fog.color,
  154. near: scene.sceneSettings.fog.near,
  155. far: scene.sceneSettings.fog.far,
  156. },
  157. children: [],
  158. };
  159. childIds.push("fog");
  160. }
  161. // Add grid if visible
  162. if (scene.sceneSettings.grid.visible) {
  163. elements["grid"] = {
  164. type: "GridHelper",
  165. props: {
  166. position: [0, 0.01, 0],
  167. size:
  168. scene.sceneSettings.grid.size * scene.sceneSettings.grid.divisions,
  169. divisions: scene.sceneSettings.grid.divisions,
  170. color: scene.sceneSettings.grid.color,
  171. secondaryColor: scene.sceneSettings.grid.secondaryColor,
  172. fadeDistance: scene.sceneSettings.grid.fadeDistance,
  173. fadeStrength: scene.sceneSettings.grid.fadeStrength,
  174. },
  175. children: [],
  176. };
  177. childIds.push("grid");
  178. }
  179. // Add scene objects
  180. for (const obj of scene.objects) {
  181. if (!obj.visible) continue;
  182. const key = `obj-${obj.id}`;
  183. elements[key] = {
  184. type: objectToSpecType(obj),
  185. props: objectToProps(obj),
  186. children: [],
  187. };
  188. childIds.push(key);
  189. }
  190. // Root scene group
  191. elements["scene"] = {
  192. type: "Group",
  193. props: { position: null, rotation: null, scale: null },
  194. children: childIds,
  195. };
  196. return {
  197. root: "scene",
  198. elements,
  199. };
  200. }