@holoscript/core 6.0.4 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -1
  3. package/dist/compiler/index.d.ts +82 -0
  4. package/dist/constants.d.ts +3 -2
  5. package/dist/coordinators/index.d.ts +189 -0
  6. package/dist/index.d.ts +501 -19
  7. package/dist/paper-0c-spike/index.d.ts +29 -0
  8. package/dist/reconstruction/index.d.ts +78 -0
  9. package/dist/tools/index.d.ts +22 -1
  10. package/dist/traitDocs/traitDocs.d.ts +69 -0
  11. package/dist/traits/index.d.ts +61 -0
  12. package/package.json +105 -57
  13. package/dist/GLTFPipeline-DEN36VNK.js +0 -12
  14. package/dist/GLTFPipeline-YPCAK236.cjs +0 -33
  15. package/dist/HoloScriptPlusParser-BN6KVRCM.js +0 -692
  16. package/dist/HoloScriptPlusParser-NC5FE6AL.cjs +0 -697
  17. package/dist/USDZExporter-NTMBNTDB.js +0 -707
  18. package/dist/USDZExporter-UWGWNMMD.cjs +0 -709
  19. package/dist/chunk-27LWW5FU.cjs +0 -410
  20. package/dist/chunk-2CWOLHTE.js +0 -69
  21. package/dist/chunk-33LSUQDD.cjs +0 -835
  22. package/dist/chunk-3FJCK5XA.js +0 -2232
  23. package/dist/chunk-3FXV5S25.cjs +0 -633
  24. package/dist/chunk-3FYZOR7S.cjs +0 -69
  25. package/dist/chunk-3SAK5KFL.cjs +0 -83
  26. package/dist/chunk-4XSNJCLC.cjs +0 -1859
  27. package/dist/chunk-54FHG2MX.js +0 -655
  28. package/dist/chunk-56IF4NTA.js +0 -56
  29. package/dist/chunk-5BGSECRJ.cjs +0 -2237
  30. package/dist/chunk-5GLCTO44.js +0 -161
  31. package/dist/chunk-5O6TQC6M.cjs +0 -1871
  32. package/dist/chunk-5OEOHAAW.cjs +0 -682
  33. package/dist/chunk-5TOFIR6L.js +0 -407
  34. package/dist/chunk-5WUNN6ZN.cjs +0 -428
  35. package/dist/chunk-6TZPZCOJ.cjs +0 -1404
  36. package/dist/chunk-6UEHYZ7V.cjs +0 -773
  37. package/dist/chunk-6XSERL6L.js +0 -5678
  38. package/dist/chunk-72EQECGT.cjs +0 -908
  39. package/dist/chunk-7AGYRWKW.cjs +0 -507
  40. package/dist/chunk-7LCYLPW2.cjs +0 -663
  41. package/dist/chunk-A7PIMZGU.js +0 -263
  42. package/dist/chunk-ATSKSYVE.cjs +0 -51683
  43. package/dist/chunk-B5TCIAPQ.cjs +0 -117
  44. package/dist/chunk-BF2235SI.cjs +0 -5714
  45. package/dist/chunk-BKIGPPVM.js +0 -629
  46. package/dist/chunk-BND3ST2K.cjs +0 -662
  47. package/dist/chunk-BU7VWW2K.js +0 -2522
  48. package/dist/chunk-BWFJEM5A.js +0 -5419
  49. package/dist/chunk-CAUCGUEA.cjs +0 -130
  50. package/dist/chunk-CBJSLA2E.cjs +0 -5423
  51. package/dist/chunk-CDPGV4DN.js +0 -114
  52. package/dist/chunk-CLYEIWA5.cjs +0 -408
  53. package/dist/chunk-CS2EMTGF.js +0 -1966
  54. package/dist/chunk-DMG7C4H2.js +0 -975
  55. package/dist/chunk-DXJQDCFL.js +0 -468
  56. package/dist/chunk-E3FWRT3Z.js +0 -608
  57. package/dist/chunk-E3UALNFA.js +0 -421
  58. package/dist/chunk-EONILVMF.js +0 -762
  59. package/dist/chunk-EQV4R3EV.cjs +0 -769
  60. package/dist/chunk-F3HH56EB.cjs +0 -5538
  61. package/dist/chunk-F4IULT7N.js +0 -1488
  62. package/dist/chunk-FF3Q7ADR.js +0 -380
  63. package/dist/chunk-FFKU7BTK.cjs +0 -1046
  64. package/dist/chunk-FHQVYCH2.js +0 -70
  65. package/dist/chunk-FSF7EQVA.cjs +0 -1974
  66. package/dist/chunk-G4VZTCIB.js +0 -659
  67. package/dist/chunk-G7CT3EMP.cjs +0 -1002
  68. package/dist/chunk-GGHSW2NN.js +0 -51334
  69. package/dist/chunk-GVUR45UF.cjs +0 -777
  70. package/dist/chunk-H5Z7NASB.js +0 -9662
  71. package/dist/chunk-HE7ULERA.cjs +0 -697
  72. package/dist/chunk-HJJLD366.js +0 -1169
  73. package/dist/chunk-HPS76H7Q.js +0 -309
  74. package/dist/chunk-HQUEA5R6.cjs +0 -424
  75. package/dist/chunk-HYNTS5CP.js +0 -422
  76. package/dist/chunk-I6XZ5DZF.cjs +0 -488
  77. package/dist/chunk-IBDG74G2.cjs +0 -1219
  78. package/dist/chunk-IFNQFCTM.js +0 -3098
  79. package/dist/chunk-IHOUYQIA.js +0 -406
  80. package/dist/chunk-ILRBGFVD.cjs +0 -72
  81. package/dist/chunk-IWI6J7FX.js +0 -421
  82. package/dist/chunk-K3JDKWGI.cjs +0 -270
  83. package/dist/chunk-K6EKD26I.js +0 -630
  84. package/dist/chunk-KCQ5NDYC.cjs +0 -3172
  85. package/dist/chunk-KED34ILP.js +0 -832
  86. package/dist/chunk-KSGXLT74.cjs +0 -457
  87. package/dist/chunk-L4G4RLXX.js +0 -767
  88. package/dist/chunk-LDP5OK6G.js +0 -1216
  89. package/dist/chunk-LGGRPKTV.cjs +0 -735
  90. package/dist/chunk-LK5IAVH6.cjs +0 -1173
  91. package/dist/chunk-LSW7P7ML.js +0 -3675
  92. package/dist/chunk-LXJ4667L.cjs +0 -3874
  93. package/dist/chunk-LZ266PCM.cjs +0 -4499
  94. package/dist/chunk-MD6KDHVE.cjs +0 -237
  95. package/dist/chunk-MEASWI2Y.js +0 -126
  96. package/dist/chunk-MPXBVFHQ.js +0 -768
  97. package/dist/chunk-N5FTS757.cjs +0 -415
  98. package/dist/chunk-NCUKG4SK.cjs +0 -3075
  99. package/dist/chunk-NDQNIJEH.js +0 -1715
  100. package/dist/chunk-NOZZ46QU.js +0 -168
  101. package/dist/chunk-OFKWH2IP.js +0 -449
  102. package/dist/chunk-PHENW4MQ.js +0 -65
  103. package/dist/chunk-PMGODDJA.cjs +0 -319
  104. package/dist/chunk-Q4QX3AET.cjs +0 -1505
  105. package/dist/chunk-Q56TT7IL.js +0 -483
  106. package/dist/chunk-Q5JT3YML.cjs +0 -129
  107. package/dist/chunk-Q5SJOGHQ.cjs +0 -4256
  108. package/dist/chunk-QGKNANZ3.js +0 -3071
  109. package/dist/chunk-QLEAOI3Q.cjs +0 -424
  110. package/dist/chunk-R7MA7QWV.js +0 -732
  111. package/dist/chunk-RVYPEYPX.js +0 -678
  112. package/dist/chunk-S2N5TNMA.js +0 -1855
  113. package/dist/chunk-S565K7NK.js +0 -752
  114. package/dist/chunk-SDAMZIRE.js +0 -765
  115. package/dist/chunk-T36DDXSF.js +0 -4250
  116. package/dist/chunk-T4NKMBDL.js +0 -5474
  117. package/dist/chunk-T7LEXZUZ.cjs +0 -2526
  118. package/dist/chunk-TEWCN33Y.js +0 -694
  119. package/dist/chunk-TQSZUOW5.cjs +0 -793
  120. package/dist/chunk-TSH3MSQU.cjs +0 -129
  121. package/dist/chunk-UERJ3YJV.js +0 -504
  122. package/dist/chunk-UPJBDCVF.js +0 -1395
  123. package/dist/chunk-V3NN3HPP.js +0 -4462
  124. package/dist/chunk-VJ3L7CHT.cjs +0 -169
  125. package/dist/chunk-WB4Z63GG.js +0 -229
  126. package/dist/chunk-WDW67DZB.cjs +0 -9728
  127. package/dist/chunk-WGMEAYBG.js +0 -126
  128. package/dist/chunk-X4ZF6CNE.cjs +0 -473
  129. package/dist/chunk-X6SEAXAX.cjs +0 -3681
  130. package/dist/chunk-XCWFGZAA.js +0 -1043
  131. package/dist/chunk-XD6IYDUX.js +0 -903
  132. package/dist/chunk-XG23EUQX.cjs +0 -1717
  133. package/dist/chunk-XRKWN5PL.cjs +0 -173
  134. package/dist/chunk-XUILI4KB.cjs +0 -430
  135. package/dist/chunk-XUTX5CBW.js +0 -126
  136. package/dist/chunk-XWPNTWDN.cjs +0 -755
  137. package/dist/chunk-XXINBTZK.cjs +0 -633
  138. package/dist/chunk-YCXFCXDS.js +0 -1868
  139. package/dist/chunk-YQK5JNFK.js +0 -3725
  140. package/dist/chunk-YYXH45E2.js +0 -426
  141. package/dist/chunk-Z34RLXRA.cjs +0 -612
  142. package/dist/chunk-ZP4D3CSX.cjs +0 -72
  143. package/dist/cli/holoscript-runner.cjs +0 -2259
  144. package/dist/cli/holoscript-runner.js +0 -2234
  145. package/dist/codebase/index.cjs +0 -336
  146. package/dist/codebase/index.js +0 -331
  147. package/dist/compiler/agent-inference.cjs +0 -28
  148. package/dist/compiler/agent-inference.js +0 -5
  149. package/dist/compiler/android-xr.cjs +0 -19
  150. package/dist/compiler/android-xr.js +0 -9
  151. package/dist/compiler/android.cjs +0 -20
  152. package/dist/compiler/android.js +0 -10
  153. package/dist/compiler/ar.cjs +0 -20
  154. package/dist/compiler/ar.js +0 -8
  155. package/dist/compiler/babylon.cjs +0 -17
  156. package/dist/compiler/babylon.js +0 -11
  157. package/dist/compiler/coco.cjs +0 -27
  158. package/dist/compiler/coco.js +0 -4
  159. package/dist/compiler/domain-block-utils.cjs +0 -602
  160. package/dist/compiler/domain-block-utils.js +0 -8
  161. package/dist/compiler/dtdl.cjs +0 -24
  162. package/dist/compiler/dtdl.js +0 -8
  163. package/dist/compiler/gltf-pipeline.cjs +0 -39
  164. package/dist/compiler/gltf-pipeline.js +0 -4
  165. package/dist/compiler/godot.cjs +0 -15
  166. package/dist/compiler/godot.js +0 -9
  167. package/dist/compiler/incremental.cjs +0 -29
  168. package/dist/compiler/incremental.js +0 -4
  169. package/dist/compiler/index.cjs +0 -2578
  170. package/dist/compiler/index.js +0 -2383
  171. package/dist/compiler/ios.cjs +0 -20
  172. package/dist/compiler/ios.js +0 -10
  173. package/dist/compiler/multi-layer.cjs +0 -20
  174. package/dist/compiler/multi-layer.js +0 -11
  175. package/dist/compiler/nodetoy.cjs +0 -23
  176. package/dist/compiler/nodetoy.js +0 -4
  177. package/dist/compiler/openxr.cjs +0 -15
  178. package/dist/compiler/openxr.js +0 -9
  179. package/dist/compiler/playcanvas.cjs +0 -16
  180. package/dist/compiler/playcanvas.js +0 -10
  181. package/dist/compiler/r3f.cjs +0 -34
  182. package/dist/compiler/r3f.js +0 -12
  183. package/dist/compiler/remotion.cjs +0 -23
  184. package/dist/compiler/remotion.js +0 -4
  185. package/dist/compiler/reproducibility.cjs +0 -39
  186. package/dist/compiler/reproducibility.js +0 -4
  187. package/dist/compiler/sdf.cjs +0 -21
  188. package/dist/compiler/sdf.js +0 -9
  189. package/dist/compiler/semantic-scene.cjs +0 -31
  190. package/dist/compiler/semantic-scene.js +0 -4
  191. package/dist/compiler/state.cjs +0 -17
  192. package/dist/compiler/state.js +0 -7
  193. package/dist/compiler/trait-composition.cjs +0 -26
  194. package/dist/compiler/trait-composition.js +0 -5
  195. package/dist/compiler/unity.cjs +0 -15
  196. package/dist/compiler/unity.js +0 -9
  197. package/dist/compiler/unreal.cjs +0 -19
  198. package/dist/compiler/unreal.js +0 -9
  199. package/dist/compiler/urdf.cjs +0 -45
  200. package/dist/compiler/urdf.js +0 -9
  201. package/dist/compiler/usd-physics.cjs +0 -23
  202. package/dist/compiler/usd-physics.js +0 -6
  203. package/dist/compiler/visionos.cjs +0 -15
  204. package/dist/compiler/visionos.js +0 -9
  205. package/dist/compiler/vrchat.cjs +0 -19
  206. package/dist/compiler/vrchat.js +0 -9
  207. package/dist/compiler/vrr.cjs +0 -21
  208. package/dist/compiler/vrr.js +0 -9
  209. package/dist/compiler/wasm.cjs +0 -33
  210. package/dist/compiler/wasm.js +0 -9
  211. package/dist/compiler/webgpu.cjs +0 -15
  212. package/dist/compiler/webgpu.js +0 -9
  213. package/dist/debugger.cjs +0 -21
  214. package/dist/debugger.js +0 -8
  215. package/dist/entries/interop.cjs +0 -62
  216. package/dist/entries/interop.js +0 -5
  217. package/dist/entries/scripting.cjs +0 -68
  218. package/dist/entries/scripting.js +0 -11
  219. package/dist/index.cjs +0 -40921
  220. package/dist/index.js +0 -37763
  221. package/dist/math/vec3.cjs +0 -57
  222. package/dist/math/vec3.js +0 -4
  223. package/dist/ml-dsa-PHCWUBPX.cjs +0 -1178
  224. package/dist/ml-dsa-WYLJHJO6.js +0 -1178
  225. package/dist/parser.cjs +0 -27
  226. package/dist/parser.js +0 -10
  227. package/dist/playwright-5OSAPTNH.js +0 -98660
  228. package/dist/playwright-RL6GY4F7.cjs +0 -98673
  229. package/dist/runtime.cjs +0 -16
  230. package/dist/runtime.js +0 -7
  231. package/dist/storage/index.cjs +0 -37
  232. package/dist/storage/index.js +0 -4
  233. package/dist/traits/index.cjs +0 -14649
  234. package/dist/traits/index.js +0 -12243
  235. package/dist/type-checker.cjs +0 -18
  236. package/dist/type-checker.js +0 -5
@@ -1,1859 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkLXJ4667L_cjs = require('./chunk-LXJ4667L.cjs');
4
- var chunkBF2235SI_cjs = require('./chunk-BF2235SI.cjs');
5
- var chunk3SAK5KFL_cjs = require('./chunk-3SAK5KFL.cjs');
6
- var platform = require('@holoscript/platform');
7
-
8
- // src/compiler/AndroidXRCompiler.ts
9
- var AndroidXRCompiler_exports = {};
10
- chunk3SAK5KFL_cjs.__export(AndroidXRCompiler_exports, {
11
- AndroidXRCompiler: () => exports.AndroidXRCompiler,
12
- compileToAndroidXR: () => compileToAndroidXR
13
- });
14
- function compileToAndroidXR(composition, options) {
15
- const compiler = new exports.AndroidXRCompiler(options);
16
- return compiler.compile(composition, "");
17
- }
18
- exports.AndroidXRCompiler = void 0;
19
- var init_AndroidXRCompiler = chunk3SAK5KFL_cjs.__esm({
20
- "src/compiler/AndroidXRCompiler.ts"() {
21
- chunkBF2235SI_cjs.init_CompilerBase();
22
- chunkLXJ4667L_cjs.init_DomainBlockCompilerMixin();
23
- exports.AndroidXRCompiler = class extends chunkBF2235SI_cjs.CompilerBase {
24
- constructor(options = {}) {
25
- super();
26
- this.compilerName = "AndroidXRCompiler";
27
- this.lines = [];
28
- this.indentLevel = 0;
29
- this.options = {
30
- packageName: options.packageName || "com.holoscript.generated",
31
- activityName: options.activityName || "GeneratedXRActivity",
32
- useFilament: options.useFilament ?? true,
33
- useARCore: options.useARCore ?? true,
34
- indent: options.indent || " ",
35
- minSdk: options.minSdk || 30,
36
- targetSdk: options.targetSdk || 35,
37
- formFactor: options.formFactor || "headset"
38
- };
39
- }
40
- getRequiredCapability() {
41
- return platform.ANSCapabilityPath.ANDROID_XR;
42
- }
43
- /** Returns true when targeting AI glasses form factor. */
44
- get isGlassesMode() {
45
- return this.options.formFactor === "glasses";
46
- }
47
- compile(composition, agentToken, outputPath) {
48
- this.validateCompilerAccess(agentToken, outputPath);
49
- if (this.isGlassesMode) {
50
- return {
51
- activityFile: this.generateGlassesActivityFile(composition),
52
- stateFile: this.generateStateFile(composition),
53
- nodeFactoryFile: this.generateNodeFactoryFile(composition),
54
- manifestFile: this.generateGlassesManifestFile(composition),
55
- buildGradle: this.generateGlassesBuildGradle(composition),
56
- glimmerComponentsFile: this.generateGlimmerComponentsFile(composition)
57
- };
58
- }
59
- return {
60
- activityFile: this.generateActivityFile(composition),
61
- stateFile: this.generateStateFile(composition),
62
- nodeFactoryFile: this.generateNodeFactoryFile(composition),
63
- manifestFile: this.generateManifestFile(composition),
64
- buildGradle: this.generateBuildGradle(composition)
65
- };
66
- }
67
- generateActivityFile(composition) {
68
- this.lines = [];
69
- this.indentLevel = 0;
70
- this.emit("// Auto-generated by HoloScript AndroidXRCompiler");
71
- this.emit(
72
- `// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`
73
- );
74
- this.emit("// Do not edit manually -- regenerate from .holo source");
75
- this.emit("");
76
- this.emit(`package ${this.options.packageName}`);
77
- this.emit("");
78
- this.emitImports(composition);
79
- this.emit("");
80
- this.emitActivityClass(composition);
81
- this.compileAndroidXRDomainBlocks(composition);
82
- return this.lines.join("\n");
83
- }
84
- // ─── State File ─────────────────────────────────────────────────────
85
- generateStateFile(composition) {
86
- this.lines = [];
87
- this.indentLevel = 0;
88
- const pkg = this.options.packageName;
89
- this.emit("// Auto-generated by HoloScript AndroidXRCompiler");
90
- this.emit(`// State: ${this.escapeStringValue(composition.name, "Kotlin")}`);
91
- this.emit("");
92
- this.emit(`package ${pkg}`);
93
- this.emit("");
94
- this.emit("import androidx.compose.runtime.mutableStateOf");
95
- this.emit("import androidx.compose.runtime.getValue");
96
- this.emit("import androidx.compose.runtime.setValue");
97
- this.emit("import androidx.lifecycle.ViewModel");
98
- this.emit("import androidx.lifecycle.MutableLiveData");
99
- this.emit("import androidx.lifecycle.LiveData");
100
- this.emit("import androidx.xr.runtime.math.Vector3");
101
- this.emit("");
102
- this.emit("class XRSceneState : ViewModel() {");
103
- this.indent();
104
- if (composition.state) {
105
- this.emit("// === State Properties ===");
106
- for (const prop of composition.state.properties) {
107
- const kotlinType = this.toKotlinType(prop.value);
108
- const kotlinValue = this.toKotlinValue(prop.value);
109
- this.emit(
110
- `private val _${this.escapeStringValue(prop.key, "Kotlin")} = MutableLiveData(${kotlinValue})`
111
- );
112
- this.emit(
113
- `val ${this.escapeStringValue(prop.key, "Kotlin")}: LiveData<${kotlinType}> get() = _${this.escapeStringValue(prop.key, "Kotlin")}`
114
- );
115
- this.emit("");
116
- }
117
- }
118
- this.emit("// === XR State ===");
119
- this.emit("private val _spatialCapabilities = MutableLiveData<Set<String>>(emptySet())");
120
- this.emit("val spatialCapabilities: LiveData<Set<String>> get() = _spatialCapabilities");
121
- this.emit("");
122
- this.emit(
123
- "private val _activeEntities = MutableLiveData<MutableMap<String, Any>>(mutableMapOf())"
124
- );
125
- this.emit("val activeEntities: LiveData<MutableMap<String, Any>> get() = _activeEntities");
126
- this.emit("");
127
- this.emit("private val _selectedEntity = MutableLiveData<String?>()");
128
- this.emit("val selectedEntity: LiveData<String?> get() = _selectedEntity");
129
- this.emit("");
130
- this.emit("fun onEntitySelected(entityId: String) {");
131
- this.indent();
132
- this.emit("_selectedEntity.value = entityId");
133
- this.emit('android.util.Log.d("HoloScript", "Entity selected: $entityId")');
134
- this.dedent();
135
- this.emit("}");
136
- this.emit("");
137
- this.emit("fun updateSpatialCapabilities(capabilities: Set<String>) {");
138
- this.indent();
139
- this.emit("_spatialCapabilities.value = capabilities");
140
- this.dedent();
141
- this.emit("}");
142
- this.emit("");
143
- this.emit("fun reset() {");
144
- this.indent();
145
- this.emit("_spatialCapabilities.value = emptySet()");
146
- this.emit("_activeEntities.value = mutableMapOf()");
147
- this.emit("_selectedEntity.value = null");
148
- if (composition.state) {
149
- for (const prop of composition.state.properties) {
150
- const kotlinValue = this.toKotlinValue(prop.value);
151
- this.emit(
152
- `_${this.escapeStringValue(prop.key, "Kotlin")}.value = ${kotlinValue}`
153
- );
154
- }
155
- }
156
- this.emit('android.util.Log.d("HoloScript", "XR State reset")');
157
- this.dedent();
158
- this.emit("}");
159
- if (composition.logic?.actions) {
160
- this.emit("");
161
- this.emit("// === Actions ===");
162
- for (const action of composition.logic.actions) {
163
- this.compileAction(action);
164
- }
165
- }
166
- this.dedent();
167
- this.emit("}");
168
- return this.lines.join("\n");
169
- }
170
- compileAction(action) {
171
- const rawName = this.sanitizeName(action.name);
172
- const name = rawName.charAt(0).toLowerCase() + rawName.slice(1);
173
- this.emit(`fun ${name}() {`);
174
- this.indent();
175
- this.emit(
176
- `android.util.Log.d("HoloScript", "Action: ${this.escapeStringValue(action.name, "Kotlin")}")`
177
- );
178
- this.emit("// Action implementation");
179
- this.dedent();
180
- this.emit("}");
181
- this.emit("");
182
- }
183
- // ─── Node Factory File ──────────────────────────────────────────────
184
- generateNodeFactoryFile(composition) {
185
- this.lines = [];
186
- this.indentLevel = 0;
187
- const pkg = this.options.packageName;
188
- this.emit("// Auto-generated by HoloScript AndroidXRCompiler");
189
- this.emit(`// Node Factory: ${this.escapeStringValue(composition.name, "Kotlin")}`);
190
- this.emit("");
191
- this.emit(`package ${pkg}`);
192
- this.emit("");
193
- this.emit("import android.net.Uri");
194
- this.emit("import androidx.xr.scenecore.Session as XRSession");
195
- this.emit("import androidx.xr.scenecore.Entity");
196
- this.emit("import androidx.xr.scenecore.GltfModel");
197
- this.emit("import androidx.xr.scenecore.GltfModelEntity");
198
- this.emit("import androidx.xr.runtime.math.Pose");
199
- this.emit("import androidx.xr.runtime.math.Vector3");
200
- this.emit("import androidx.xr.runtime.math.Quaternion");
201
- this.emit("");
202
- this.emit("object XRNodeFactory {");
203
- this.indent();
204
- this.emit("/**");
205
- this.emit(" * Create a default entity in the XR scene.");
206
- this.emit(" */");
207
- this.emit("fun createDefaultEntity(session: XRSession): Entity {");
208
- this.indent();
209
- if (composition.objects?.length) {
210
- const firstObj = composition.objects[0];
211
- const modelSrc = this.findProp(firstObj, "model") ?? this.findProp(firstObj, "src");
212
- if (modelSrc) {
213
- this.emit(`val gltfModel = GltfModel.create(session, Uri.parse("${modelSrc}"))`);
214
- this.emit("return GltfModelEntity.create(session, gltfModel).apply {");
215
- this.indent();
216
- this.emit("parent = session.scene.activitySpace");
217
- const pos = this.findProp(firstObj, "position");
218
- if (pos && Array.isArray(pos)) {
219
- this.emit(
220
- `setPose(Pose(${this.toKotlinFloat3(pos)}, Quaternion.identity()))`
221
- );
222
- }
223
- this.dedent();
224
- this.emit("}");
225
- } else {
226
- this.emit(
227
- `val entity = session.scene.createEntity("${this.escapeStringValue(firstObj.name, "Kotlin")}")`
228
- );
229
- this.emit("entity.parent = session.scene.activitySpace");
230
- const pos = this.findProp(firstObj, "position");
231
- if (pos && Array.isArray(pos)) {
232
- this.emit(
233
- `entity.setPose(Pose(${this.toKotlinFloat3(pos)}, Quaternion.identity()))`
234
- );
235
- }
236
- this.emit("return entity");
237
- }
238
- } else {
239
- this.emit('val entity = session.scene.createEntity("default")');
240
- this.emit("entity.parent = session.scene.activitySpace");
241
- this.emit("return entity");
242
- }
243
- this.dedent();
244
- this.emit("}");
245
- this.emit("");
246
- this.emit("/**");
247
- this.emit(" * Load a glTF model and create a GltfModelEntity.");
248
- this.emit(" */");
249
- this.emit("fun loadGltfModel(");
250
- this.indent();
251
- this.emit("session: XRSession,");
252
- this.emit("modelUri: String,");
253
- this.emit("position: Vector3 = Vector3(0f, 0f, 0f),");
254
- this.emit("scale: Float = 1f");
255
- this.dedent();
256
- this.emit("): GltfModelEntity {");
257
- this.indent();
258
- this.emit("val gltfModel = GltfModel.create(session, Uri.parse(modelUri))");
259
- this.emit("return GltfModelEntity.create(session, gltfModel).apply {");
260
- this.indent();
261
- this.emit("parent = session.scene.activitySpace");
262
- this.emit("setPose(Pose(position, Quaternion.identity()))");
263
- this.emit("setScale(com.google.android.filament.utils.Float3(scale, scale, scale))");
264
- this.dedent();
265
- this.emit("}");
266
- this.dedent();
267
- this.emit("}");
268
- this.emit("");
269
- for (const obj of composition.objects || []) {
270
- this.compileObjectFactory(obj);
271
- }
272
- this.dedent();
273
- this.emit("}");
274
- return this.lines.join("\n");
275
- }
276
- compileObjectFactory(obj) {
277
- const methodName = `create${this.sanitizeName(obj.name).charAt(0).toUpperCase() + this.sanitizeName(obj.name).slice(1)}`;
278
- const modelSrc = this.findProp(obj, "model") ?? this.findProp(obj, "src");
279
- this.emit(`fun ${methodName}(session: XRSession): Entity {`);
280
- this.indent();
281
- if (modelSrc) {
282
- this.emit(`val gltfModel = GltfModel.create(session, Uri.parse("${modelSrc}"))`);
283
- this.emit("return GltfModelEntity.create(session, gltfModel).apply {");
284
- this.indent();
285
- this.emit("parent = session.scene.activitySpace");
286
- const pos = this.findProp(obj, "position");
287
- if (pos && Array.isArray(pos)) {
288
- this.emit(`setPose(Pose(${this.toKotlinFloat3(pos)}, Quaternion.identity()))`);
289
- }
290
- const scale = this.findProp(obj, "scale");
291
- if (scale && Array.isArray(scale)) {
292
- this.emit(`setScale(${this.toKotlinFloat3(scale)})`);
293
- } else if (typeof scale === "number") {
294
- this.emit(
295
- `setScale(com.google.android.filament.utils.Float3(${scale}f, ${scale}f, ${scale}f))`
296
- );
297
- }
298
- this.dedent();
299
- this.emit("}");
300
- } else {
301
- this.emit(
302
- `val entity = session.scene.createEntity("${this.escapeStringValue(obj.name, "Kotlin")}")`
303
- );
304
- this.emit("entity.parent = session.scene.activitySpace");
305
- const pos = this.findProp(obj, "position");
306
- if (pos && Array.isArray(pos)) {
307
- this.emit(
308
- `entity.setPose(Pose(${this.toKotlinFloat3(pos)}, Quaternion.identity()))`
309
- );
310
- }
311
- const scale = this.findProp(obj, "scale");
312
- if (scale && Array.isArray(scale)) {
313
- this.emit(`entity.setScale(${this.toKotlinFloat3(scale)})`);
314
- } else if (typeof scale === "number") {
315
- this.emit(
316
- `entity.setScale(com.google.android.filament.utils.Float3(${scale}f, ${scale}f, ${scale}f))`
317
- );
318
- }
319
- this.emit("return entity");
320
- }
321
- this.dedent();
322
- this.emit("}");
323
- this.emit("");
324
- }
325
- // ─── Manifest File ──────────────────────────────────────────────────
326
- generateManifestFile(composition) {
327
- const pkg = this.options.packageName;
328
- const activityName = this.options.activityName;
329
- const hasHandTracking = composition.objects?.some(
330
- (o) => o.traits?.some((t) => t.name === "hand_tracking")
331
- );
332
- const hasFaceTracking = composition.objects?.some(
333
- (o) => o.traits?.some((t) => t.name === "face_tracking")
334
- );
335
- const hasHeadFollow = composition.objects?.some(
336
- (o) => o.traits?.some((t) => t.name === "follows_head" || t.name === "head_follow")
337
- );
338
- const hasEyeTracking = composition.objects?.some(
339
- (o) => o.traits?.some((t) => t.name === "eye_tracking" || t.name === "eye_tracked")
340
- );
341
- const hasPlaneDetection = composition.objects?.some(
342
- (o) => o.traits?.some((t) => t.name === "plane_detection" || t.name === "anchor")
343
- );
344
- const xrPermissions = [];
345
- xrPermissions.push(' <uses-permission android:name="android.permission.CAMERA" />');
346
- if (hasHandTracking) {
347
- xrPermissions.push(' <uses-permission android:name="android.permission.HAND_TRACKING" />');
348
- }
349
- if (hasFaceTracking) {
350
- xrPermissions.push(' <uses-permission android:name="android.permission.FACE_TRACKING" />');
351
- }
352
- if (hasHeadFollow) {
353
- xrPermissions.push(' <uses-permission android:name="android.permission.HEAD_TRACKING" />');
354
- }
355
- if (hasEyeTracking) {
356
- xrPermissions.push(' <uses-permission android:name="android.permission.EYE_TRACKING" />');
357
- }
358
- if (hasPlaneDetection) {
359
- xrPermissions.push(
360
- ' <uses-permission android:name="android.permission.SCENE_UNDERSTANDING_COARSE" />'
361
- );
362
- }
363
- return `<?xml version="1.0" encoding="utf-8"?>
364
- <!-- Auto-generated by HoloScript AndroidXRCompiler -->
365
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
366
- package="${pkg}">
367
-
368
- <!-- XR Permissions -->
369
- ${xrPermissions.join("\n")}
370
-
371
- <!-- XR Required Features -->
372
- <uses-feature android:name="android.hardware.xr.headtracking" android:required="true" />
373
- <uses-feature android:glEsVersion="0x00030002" android:required="true" />
374
-
375
- <application
376
- android:allowBackup="true"
377
- android:label="${this.escapeStringValue(composition.name, "XML")}"
378
- android:supportsRtl="true"
379
- android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
380
-
381
- <!-- Android XR metadata -->
382
- <meta-data android:name="com.google.ar.core" android:value="required" />
383
-
384
- <activity
385
- android:name=".${activityName}"
386
- android:exported="true"
387
- android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|uiMode"
388
- android:screenOrientation="landscape"
389
- android:enableOnBackInvokedCallback="true">
390
- <intent-filter>
391
- <action android:name="android.intent.action.MAIN" />
392
- <category android:name="android.intent.category.LAUNCHER" />
393
- <category android:name="com.google.intent.category.XR" />
394
- </intent-filter>
395
- </activity>
396
- </application>
397
- </manifest>`;
398
- }
399
- // ─── Build Gradle File ──────────────────────────────────────────────
400
- generateBuildGradle(composition) {
401
- const hasDrmVideo = composition.objects?.some(
402
- (o) => o.traits?.some((t) => t.name === "drm_video" || t.name === "protected_video")
403
- );
404
- return `// Auto-generated by HoloScript AndroidXRCompiler
405
- // Source: ${this.escapeStringValue(composition.name, "Kotlin")}
406
-
407
- plugins {
408
- id("com.android.application")
409
- id("org.jetbrains.kotlin.android")
410
- id("org.jetbrains.kotlin.plugin.compose")
411
- }
412
-
413
- android {
414
- namespace = "${this.options.packageName}"
415
- compileSdk = ${this.options.targetSdk}
416
-
417
- defaultConfig {
418
- applicationId = "${this.options.packageName}"
419
- minSdk = ${this.options.minSdk}
420
- targetSdk = ${this.options.targetSdk}
421
- versionCode = 1
422
- versionName = "1.0"
423
- }
424
-
425
- buildTypes {
426
- release {
427
- isMinifyEnabled = false
428
- proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
429
- }
430
- }
431
-
432
- compileOptions {
433
- sourceCompatibility = JavaVersion.VERSION_17
434
- targetCompatibility = JavaVersion.VERSION_17
435
- }
436
-
437
- kotlinOptions {
438
- jvmTarget = "17"
439
- }
440
-
441
- buildFeatures {
442
- compose = true
443
- }
444
- }
445
-
446
- dependencies {
447
- // Core
448
- implementation("androidx.core:core-ktx:1.13.1")
449
- implementation("androidx.appcompat:appcompat:1.7.0")
450
- implementation("com.google.android.material:material:1.12.0")
451
- implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
452
- implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.0")
453
- implementation("androidx.activity:activity-compose:1.9.0")
454
-
455
- // Jetpack Compose
456
- implementation(platform("androidx.compose:compose-bom:2024.06.00"))
457
- implementation("androidx.compose.ui:ui")
458
- implementation("androidx.compose.material3:material3")
459
- implementation("androidx.compose.runtime:runtime")
460
-
461
- // Android XR - Jetpack XR SceneCore
462
- implementation("androidx.xr.scenecore:scenecore:1.0.0-alpha01")
463
- implementation("androidx.xr.compose:compose:1.0.0-alpha01")
464
-
465
- // Android XR - ARCore for Jetpack XR
466
- implementation("androidx.xr.arcore:arcore:1.0.0-alpha01")
467
-
468
- // Android XR - Runtime
469
- implementation("androidx.xr:xr:1.0.0-alpha01")
470
-
471
- // Filament (PBR Rendering)
472
- implementation("com.google.android.filament:filament-android:1.51.0")
473
- implementation("com.google.android.filament:filament-utils-android:1.51.0")
474
- implementation("com.google.android.filament:gltfio-android:1.51.0")
475
- ${hasDrmVideo ? `
476
- // Media3 ExoPlayer (DRM Video Playback)
477
- implementation("androidx.media3:media3-exoplayer:1.3.1")
478
- implementation("androidx.media3:media3-common:1.3.1")
479
- implementation("androidx.media3:media3-exoplayer-dash:1.3.1")
480
- ` : ""}
481
- // ARCore
482
- implementation("com.google.ar:core:1.43.0")
483
- }`;
484
- }
485
- // ─── Imports ─────────────────────────────────────────────────────────
486
- emitImports(composition) {
487
- this.emit("import android.os.Bundle");
488
- this.emit("import android.net.Uri");
489
- this.emit("import android.animation.ObjectAnimator");
490
- this.emit("import android.animation.AnimatorSet");
491
- this.emit("");
492
- this.emit("import androidx.activity.ComponentActivity");
493
- this.emit("import androidx.activity.compose.setContent");
494
- this.emit("import androidx.compose.runtime.*");
495
- this.emit("import androidx.compose.foundation.layout.*");
496
- this.emit("import androidx.compose.material3.*");
497
- this.emit("import androidx.compose.ui.Modifier");
498
- this.emit("import androidx.compose.ui.graphics.Color");
499
- this.emit("");
500
- this.emit("import androidx.xr.compose.spatial.*");
501
- this.emit("import androidx.xr.compose.subspace.layout.*");
502
- this.emit("");
503
- this.emit("import androidx.xr.scenecore.Entity");
504
- this.emit("import androidx.xr.scenecore.Session as XRSession");
505
- this.emit("import androidx.xr.scenecore.GltfModel");
506
- this.emit("import androidx.xr.scenecore.GltfModelEntity");
507
- this.emit("import androidx.xr.scenecore.SurfaceEntity");
508
- this.emit("import androidx.xr.scenecore.SpatialCapability");
509
- this.emit("import androidx.xr.runtime.math.Pose");
510
- this.emit("import androidx.xr.runtime.math.Vector3");
511
- this.emit("import androidx.xr.runtime.math.Quaternion");
512
- this.emit("");
513
- if (this.options.useARCore) {
514
- this.emit("import com.google.ar.core.Config");
515
- this.emit("import com.google.ar.core.Plane");
516
- this.emit("import com.google.ar.core.Anchor");
517
- }
518
- if (this.options.useFilament) {
519
- this.emit("import com.google.android.filament.LightManager");
520
- this.emit("import com.google.android.filament.utils.Float3");
521
- }
522
- const hasHandTracking = composition.objects?.some(
523
- (o) => o.traits?.some((t) => t.name === "hand_tracking")
524
- );
525
- if (hasHandTracking) {
526
- this.emit("import androidx.xr.scenecore.InputEvent");
527
- }
528
- const hasFaceTracking = composition.objects?.some(
529
- (o) => o.traits?.some((t) => t.name === "face_tracking")
530
- );
531
- if (hasFaceTracking) {
532
- this.emit("import androidx.xr.arcore.Face");
533
- this.emit("import androidx.xr.arcore.FaceBlendShapeType");
534
- this.emit("import androidx.xr.arcore.FaceConfidenceRegion");
535
- this.emit("import androidx.xr.runtime.FaceTrackingMode");
536
- }
537
- const hasHeadFollow = composition.objects?.some(
538
- (o) => o.traits?.some((t) => t.name === "follows_head" || t.name === "head_follow")
539
- );
540
- if (hasHeadFollow) {
541
- this.emit("import androidx.xr.compose.spatial.UserSubspace");
542
- }
543
- const hasDrmVideo = composition.objects?.some(
544
- (o) => o.traits?.some((t) => t.name === "drm_video" || t.name === "protected_video")
545
- );
546
- if (hasDrmVideo) {
547
- this.emit("import androidx.media3.exoplayer.ExoPlayer");
548
- this.emit("import androidx.media3.common.MediaItem");
549
- this.emit("import androidx.media3.common.C");
550
- }
551
- if (composition.audio?.length) {
552
- this.emit("import android.media.AudioAttributes");
553
- this.emit("import android.media.SoundPool");
554
- this.emit("import androidx.xr.scenecore.SpatialAudioTrack");
555
- }
556
- }
557
- // ─── Activity Class ──────────────────────────────────────────────────
558
- emitActivityClass(composition) {
559
- this.emit(`class ${this.options.activityName} : ComponentActivity() {`);
560
- this.indent();
561
- this.emit("private lateinit var xrSession: XRSession");
562
- if (composition.audio?.length) this.emit("private lateinit var soundPool: SoundPool");
563
- this.emit("");
564
- this.emit("override fun onCreate(savedInstanceState: Bundle?) {");
565
- this.indent();
566
- this.emit("super.onCreate(savedInstanceState)");
567
- this.emit("xrSession = XRSession.create(this)");
568
- if (this.options.useARCore) {
569
- this.emit("");
570
- this.emitARSession();
571
- }
572
- if (composition.audio?.length) {
573
- this.emit("");
574
- this.emit("val audioAttrs = AudioAttributes.Builder()");
575
- this.indent();
576
- this.emit(".setUsage(AudioAttributes.USAGE_GAME)");
577
- this.emit(".setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build()");
578
- this.dedent();
579
- this.emit(
580
- "soundPool = SoundPool.Builder().setMaxStreams(8).setAudioAttributes(audioAttrs).build()"
581
- );
582
- }
583
- this.emit("");
584
- this.emit("setContent {");
585
- this.indent();
586
- this.emit(`${this.sanitizeName(composition.name)}Scene()`);
587
- this.dedent();
588
- this.emit("}");
589
- this.dedent();
590
- this.emit("}");
591
- this.emit("");
592
- this.emitSceneComposable(composition);
593
- for (const tl of composition.timelines ?? []) this.emitTimeline(tl);
594
- for (const tr of composition.transitions ?? []) this.emitTransition(tr);
595
- this.dedent();
596
- this.emit("}");
597
- }
598
- // ─── AR Session ──────────────────────────────────────────────────────
599
- emitARSession() {
600
- this.emit("xrSession.scene.configure { config ->");
601
- this.indent();
602
- this.emit("config.planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_AND_VERTICAL");
603
- this.emit("config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR");
604
- this.emit("config.depthMode = Config.DepthMode.AUTOMATIC");
605
- this.emit("config.updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE");
606
- this.dedent();
607
- this.emit("}");
608
- }
609
- // ─── Scene Composable ────────────────────────────────────────────────
610
- emitSceneComposable(composition) {
611
- const name = this.sanitizeName(composition.name);
612
- this.emit("@Composable");
613
- this.emit(`private fun ${name}Scene() {`);
614
- this.indent();
615
- if (composition.state) {
616
- for (const p of composition.state.properties) {
617
- this.emit(
618
- `var ${this.escapeStringValue(p.key, "Kotlin")} by remember { mutableStateOf(${this.toKotlinValue(p.value)}) }`
619
- );
620
- }
621
- this.emit("");
622
- }
623
- this.emit("Subspace {");
624
- this.indent();
625
- if (composition.environment) this.emitEnvironment(composition.environment);
626
- if (composition.camera) this.emitCamera(composition.camera);
627
- for (const l of composition.lights ?? []) this.emitLight(l);
628
- for (const o of composition.objects ?? []) {
629
- const hasHeadFollow = o.traits?.some(
630
- (t) => t.name === "follows_head" || t.name === "head_follow"
631
- );
632
- if (hasHeadFollow) {
633
- this.emit("");
634
- this.emit("// DP3: UserSubspace \u2014 head-following content");
635
- this.emit("UserSubspace {");
636
- this.indent();
637
- this.emitObject(o);
638
- this.dedent();
639
- this.emit("}");
640
- } else {
641
- this.emitObject(o);
642
- }
643
- }
644
- for (const g of composition.spatialGroups ?? []) this.emitGroup(g);
645
- for (const a of composition.audio ?? []) this.emitAudio(a);
646
- for (const z of composition.zones ?? []) this.emitZone(z);
647
- if (composition.ui) this.emitUI(composition.ui);
648
- if (composition.effects) this.emitEffects(composition.effects);
649
- this.dedent();
650
- this.emit("}");
651
- this.dedent();
652
- this.emit("}");
653
- this.emit("");
654
- }
655
- // ─── Environment ─────────────────────────────────────────────────────
656
- emitEnvironment(env) {
657
- this.emit("// Environment");
658
- for (const prop of env.properties) {
659
- if (prop.key === "preset" || prop.key === "skybox") {
660
- this.emit(`// Preset: "${prop.value}"`);
661
- this.emit("xrSession.scene.configure { config ->");
662
- this.indent();
663
- this.emit("config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR");
664
- this.dedent();
665
- this.emit("}");
666
- } else if (prop.key === "passthrough") {
667
- if (prop.value === true || prop.value === "true") {
668
- this.emit("xrSession.scene.configure { it.focusMode = Config.FocusMode.AUTO }");
669
- }
670
- } else if (prop.key === "fog" && typeof prop.value === "object") {
671
- this.emit(`// Fog: ${JSON.stringify(prop.value)} -- apply via Filament renderer`);
672
- } else if (prop.key === "ambient_light") {
673
- this.emit(`// Ambient light: ${prop.value}`);
674
- }
675
- }
676
- }
677
- // ─── Object ──────────────────────────────────────────────────────────
678
- emitObject(obj) {
679
- const v = this.sanitizeName(obj.name);
680
- const meshType = this.findProp(obj, "mesh") ?? this.findProp(obj, "type") ?? "cube";
681
- const modelSrc = this.findProp(obj, "model") ?? this.findProp(obj, "src");
682
- this.emit("");
683
- this.emit(`// Object: ${this.escapeStringValue(obj.name, "Kotlin")}`);
684
- if (obj.traits?.some((t) => t.name === "plane_detection")) {
685
- this.emit(
686
- `val ${v}Plane = xrSession.scene.createEntity("${this.escapeStringValue(obj.name, "Kotlin")}_plane")`
687
- );
688
- this.emit(`${v}Plane.setPose(Pose(Vector3(0f, 0f, 0f), Quaternion.identity()))`);
689
- }
690
- const anchor = obj.traits?.find((t) => t.name === "anchor");
691
- if (anchor) {
692
- this.emit(
693
- `val ${v}Anchor = xrSession.scene.createAnchorEntity() // type: ${anchor.config?.type ?? "plane"}`
694
- );
695
- }
696
- if (obj.traits?.some((t) => t.name === "hand_tracking")) {
697
- this.emit(`val ${v}Hand = HandNode(xrSession)`);
698
- this.emit(`${v}Hand.setOnInputEventListener { event: InputEvent -> /* hand input */ }`);
699
- }
700
- if (modelSrc) {
701
- this.emit(`val ${v}GltfModel = GltfModel.create(xrSession, Uri.parse("${modelSrc}"))`);
702
- this.emit(`val ${v} = GltfModelEntity.create(xrSession, ${v}GltfModel).apply {`);
703
- this.indent();
704
- this.emit(`parent = xrSession.scene.activitySpace`);
705
- const modelPos = this.findProp(obj, "position");
706
- if (modelPos && Array.isArray(modelPos)) {
707
- this.emit(
708
- `setPose(Pose(${this.toKotlinFloat3(modelPos)}, Quaternion.identity()))`
709
- );
710
- }
711
- this.dedent();
712
- this.emit("}");
713
- } else if (meshType === "text") {
714
- const text = this.findProp(obj, "text") ?? obj.name;
715
- const color = this.findProp(obj, "color");
716
- this.emit(`SpatialPanel(SubspaceModifier.width(200f).height(60f)) {`);
717
- this.indent();
718
- this.emit(
719
- `Text("${text}", color = ${color ? this.toKotlinColor(color) : "Color.White"})`
720
- );
721
- this.dedent();
722
- this.emit("}");
723
- } else {
724
- this.emit(
725
- `val ${v} = xrSession.scene.createEntity("${this.escapeStringValue(obj.name, "Kotlin")}")`
726
- );
727
- const pos = this.findProp(obj, "position");
728
- if (pos && Array.isArray(pos)) {
729
- this.emit(
730
- `${v}.setPose(Pose(${this.toKotlinFloat3(pos)}, Quaternion.identity()))`
731
- );
732
- }
733
- const scale = this.findProp(obj, "scale");
734
- if (scale && Array.isArray(scale)) {
735
- this.emit(`${v}.setScale(${this.toKotlinFloat3(scale)})`);
736
- } else if (typeof scale === "number") {
737
- this.emit(`${v}.setScale(Float3(${scale}f, ${scale}f, ${scale}f))`);
738
- }
739
- const mat = this.findProp(obj, "material");
740
- if (mat && typeof mat === "object") {
741
- const m = mat;
742
- if (m.color) this.emit(`val ${v}Color = ${this.toKotlinColor(m.color)}`);
743
- if (m.roughness !== void 0) this.emit(`// roughness = ${m.roughness}f`);
744
- if (m.metalness !== void 0) this.emit(`// metalness = ${m.metalness}f`);
745
- }
746
- this.emit(`// Geometry: ${this.mapShapeToFilament(meshType)}`);
747
- }
748
- const rot = this.findProp(obj, "rotation");
749
- if (rot && Array.isArray(rot) && meshType !== "text") {
750
- this.emit(`// Rotation: ${rot.join(", ")} deg`);
751
- }
752
- for (const t of obj.traits ?? []) {
753
- if (t.name === "collidable") this.emit(`// Collision enabled for ${v}`);
754
- else if (t.name === "physics") this.emit(`// Physics: mass=${t.config?.mass ?? 1}`);
755
- else if (t.name === "grabbable") {
756
- this.emit(`${v}.setOnInputEventListener { _: InputEvent -> /* grab */ }`);
757
- } else if (t.name === "face_tracking") {
758
- this.emitFaceTracking(v, t.config ?? {});
759
- } else if (t.name === "follows_head" || t.name === "head_follow") {
760
- this.emitHeadFollowUI(v, obj, t.config ?? {});
761
- } else if (t.name === "drm_video" || t.name === "protected_video") {
762
- this.emitDrmVideo(v, t.config ?? {});
763
- }
764
- }
765
- for (const child of obj.children ?? []) this.emitObject(child);
766
- }
767
- // ─── Spatial Group ───────────────────────────────────────────────────
768
- emitGroup(group) {
769
- const v = this.sanitizeName(group.name);
770
- this.emit("");
771
- this.emit(`// Group: ${this.escapeStringValue(group.name, "Kotlin")}`);
772
- this.emit(
773
- `val ${v} = xrSession.scene.createEntity("${this.escapeStringValue(group.name, "Kotlin")}")`
774
- );
775
- for (const p of group.properties) {
776
- if (p.key === "position" && Array.isArray(p.value))
777
- this.emit(
778
- `${v}.setPose(Pose(${this.toKotlinFloat3(p.value)}, Quaternion.identity()))`
779
- );
780
- else if (p.key === "scale" && Array.isArray(p.value))
781
- this.emit(`${v}.setScale(${this.toKotlinFloat3(p.value)})`);
782
- else if (p.key === "scale" && typeof p.value === "number")
783
- this.emit(`${v}.setScale(Float3(${p.value}f, ${p.value}f, ${p.value}f))`);
784
- }
785
- for (const o of group.objects) {
786
- this.emitObject(o);
787
- this.emit(`${v}.addChild(${this.sanitizeName(o.name)})`);
788
- }
789
- for (const sub of group.groups ?? []) {
790
- this.emitGroup(sub);
791
- this.emit(`${v}.addChild(${this.sanitizeName(sub.name)})`);
792
- }
793
- }
794
- // ─── Light ───────────────────────────────────────────────────────────
795
- emitLight(light) {
796
- const v = this.sanitizeName(light.name);
797
- const typeMap = {
798
- directional: "DIRECTIONAL",
799
- point: "POINT",
800
- spot: "SPOT",
801
- ambient: "DIRECTIONAL",
802
- hemisphere: "DIRECTIONAL",
803
- area: "POINT"
804
- };
805
- this.emit("");
806
- this.emit(
807
- `val ${v} = xrSession.scene.createEntity("${this.escapeStringValue(light.name, "Kotlin")}")`
808
- );
809
- this.emit(`// Filament type: LightManager.Type.${typeMap[light.lightType] ?? "POINT"}`);
810
- const color = light.properties.find((p) => p.key === "color");
811
- const intensity = light.properties.find((p) => p.key === "intensity");
812
- const pos = light.properties.find((p) => p.key === "position");
813
- if (color) this.emit(`// Color: ${color.value}`);
814
- if (intensity) this.emit(`// Intensity: ${intensity.value * 1e5}f lux`);
815
- if (pos && Array.isArray(pos.value))
816
- this.emit(
817
- `${v}.setPose(Pose(${this.toKotlinFloat3(pos.value)}, Quaternion.identity()))`
818
- );
819
- }
820
- // ─── Camera ──────────────────────────────────────────────────────────
821
- emitCamera(cam) {
822
- this.emit(`// Camera: ${cam.cameraType}`);
823
- for (const p of cam.properties) {
824
- if (p.key === "passthrough" && (p.value === true || p.value === "true"))
825
- this.emit("xrSession.scene.configure { it.focusMode = Config.FocusMode.AUTO }");
826
- else if (p.key === "fov") this.emit(`// FOV: ${p.value} -- managed by XR headset`);
827
- }
828
- }
829
- // ─── Timeline ────────────────────────────────────────────────────────
830
- emitTimeline(tl) {
831
- const fn = this.sanitizeName(tl.name);
832
- this.emit("");
833
- this.emit(`private fun playTimeline_${fn}() {`);
834
- this.indent();
835
- this.emit("val set = AnimatorSet()");
836
- this.emit("val anims = mutableListOf<ObjectAnimator>()");
837
- for (const e of tl.entries) {
838
- const ms = Math.round(e.time * 1e3);
839
- if (e.action.kind === "animate") {
840
- for (const [k, val] of Object.entries(e.action.properties)) {
841
- if (typeof val === "number") {
842
- this.emit(
843
- `anims.add(ObjectAnimator.ofFloat(null, "${k}", ${val}f).apply { startDelay = ${ms}L; duration = 300L })`
844
- );
845
- }
846
- }
847
- } else if (e.action.kind === "emit") {
848
- this.emit(`// @${e.time}s emit "${e.action.event}"`);
849
- } else if (e.action.kind === "call") {
850
- this.emit(`// @${e.time}s call ${e.action.method}()`);
851
- }
852
- }
853
- this.emit("set.playTogether(anims as List<android.animation.Animator>)");
854
- if (tl.loop) {
855
- this.emit("set.addListener(object : android.animation.AnimatorListenerAdapter() {");
856
- this.indent();
857
- this.emit(
858
- `override fun onAnimationEnd(a: android.animation.Animator) { playTimeline_${fn}() }`
859
- );
860
- this.dedent();
861
- this.emit("})");
862
- }
863
- this.emit("set.start()");
864
- this.dedent();
865
- this.emit("}");
866
- }
867
- // ─── Audio ───────────────────────────────────────────────────────────
868
- emitAudio(audio) {
869
- const v = this.sanitizeName(audio.name);
870
- const src = audio.properties.find((p) => p.key === "src" || p.key === "source")?.value;
871
- const spatial = audio.properties.find((p) => p.key === "spatial")?.value;
872
- const volume = audio.properties.find((p) => p.key === "volume")?.value ?? 1;
873
- const loop = audio.properties.find((p) => p.key === "loop")?.value;
874
- const pos = audio.properties.find((p) => p.key === "position")?.value;
875
- this.emit("");
876
- this.emit(`// Audio: ${this.escapeStringValue(audio.name, "Kotlin")}`);
877
- if (src) {
878
- this.emit(`val ${v}Id = soundPool.load(this, R.raw.${this.sanitizeName(src)}, 1)`);
879
- }
880
- if (spatial && pos && Array.isArray(pos)) {
881
- this.emit(
882
- `val ${v}Entity = xrSession.scene.createEntity("${this.escapeStringValue(audio.name, "Kotlin")}")`
883
- );
884
- this.emit(
885
- `${v}Entity.setPose(Pose(${this.toKotlinFloat3(pos)}, Quaternion.identity()))`
886
- );
887
- this.emit(`val ${v}Track = SpatialAudioTrack(xrSession, ${v}Entity)`);
888
- }
889
- if (src) {
890
- this.emit(`soundPool.setOnLoadCompleteListener { pool, id, _ ->`);
891
- this.indent();
892
- this.emit(
893
- `if (id == ${v}Id) pool.play(id, ${volume}f, ${volume}f, 1, ${loop ? 1 : 0}, 1.0f)`
894
- );
895
- this.dedent();
896
- this.emit("}");
897
- }
898
- }
899
- // ─── UI ──────────────────────────────────────────────────────────────
900
- emitUI(ui) {
901
- this.emit("");
902
- this.emit("// UI Layer");
903
- for (const el of ui.elements) {
904
- const vn = this.sanitizeName(el.name);
905
- const w = el.properties.find((p) => p.key === "width")?.value ?? 400;
906
- const h = el.properties.find((p) => p.key === "height")?.value ?? 300;
907
- const text = el.properties.find((p) => p.key === "text")?.value;
908
- const label = el.properties.find((p) => p.key === "label")?.value;
909
- const elType = el.properties.find((p) => p.key === "type")?.value;
910
- this.emit(`SpatialPanel(SubspaceModifier.width(${w}f).height(${h}f)) {`);
911
- this.indent();
912
- if (elType === "button" && label) {
913
- this.emit(`Button(onClick = { /* ${vn} */ }) { Text("${label}") }`);
914
- } else if (text) {
915
- const color = el.properties.find((p) => p.key === "color")?.value;
916
- this.emit(
917
- `Text("${text}", color = ${color ? this.toKotlinColor(color) : "Color.White"})`
918
- );
919
- } else {
920
- this.emit(
921
- `Column(modifier = Modifier.fillMaxSize()) { Text("${this.escapeStringValue(el.name, "Kotlin")}") }`
922
- );
923
- }
924
- this.dedent();
925
- this.emit("}");
926
- }
927
- }
928
- // ─── Zones ───────────────────────────────────────────────────────────
929
- emitZone(zone) {
930
- const v = this.sanitizeName(zone.name);
931
- this.emit(
932
- `val ${v} = xrSession.scene.createEntity("${this.escapeStringValue(zone.name, "Kotlin")}")`
933
- );
934
- const pos = zone.properties.find((p) => p.key === "position")?.value;
935
- if (pos && Array.isArray(pos))
936
- this.emit(
937
- `${v}.setPose(Pose(${this.toKotlinFloat3(pos)}, Quaternion.identity()))`
938
- );
939
- const shape = zone.properties.find((p) => p.key === "shape")?.value;
940
- const size = zone.properties.find((p) => p.key === "size")?.value;
941
- const radius = zone.properties.find((p) => p.key === "radius")?.value;
942
- if (shape === "box" && size && Array.isArray(size))
943
- this.emit(`// Trigger volume: box ${size.join("x")}`);
944
- else if (shape === "sphere" && radius) this.emit(`// Trigger volume: sphere r=${radius}`);
945
- if (zone.handlers?.length)
946
- this.emit(`// Handlers: ${zone.handlers.map((h) => h.event).join(", ")}`);
947
- }
948
- // ─── Effects ─────────────────────────────────────────────────────────
949
- emitEffects(effects) {
950
- this.emit("// Post-processing (Filament Renderer)");
951
- for (const fx of effects.effects)
952
- this.emit(`// ${fx.effectType}: ${JSON.stringify(fx.properties)}`);
953
- }
954
- // ─── DP3: Face Tracking with 68 Blendshapes ─────────────────────────
955
- emitFaceTracking(varName, config) {
956
- const blendshapes = config.blendshapes ?? [];
957
- this.emit("");
958
- this.emit(`// DP3: Face tracking \u2014 68 blendshapes via ARCore for Jetpack XR`);
959
- this.emit(`// android.permission.FACE_TRACKING required`);
960
- this.emit(`val ${varName}FaceConfig = xrSession.config.copy(`);
961
- this.indent();
962
- this.emit("faceTracking = FaceTrackingMode.BLEND_SHAPES");
963
- this.dedent();
964
- this.emit(")");
965
- this.emit(`xrSession.configure(${varName}FaceConfig)`);
966
- this.emit("");
967
- this.emit(`val ${varName}Face = Face.getUserFace(xrSession)`);
968
- this.emit(`${varName}Face?.state?.collect { faceState ->`);
969
- this.indent();
970
- this.emit(`if (faceState.trackingState != TrackingState.TRACKING) return@collect`);
971
- if (blendshapes.length > 0) {
972
- for (const bs of blendshapes) {
973
- const bsConst = `FaceBlendShapeType.FACE_BLEND_SHAPE_TYPE_${bs.toUpperCase()}`;
974
- this.emit(`val ${varName}_${this.sanitizeName(bs)} = faceState.blendShapes[${bsConst}]`);
975
- }
976
- } else {
977
- this.emit(
978
- "// Access blendshapes: faceState.blendShapes[FaceBlendShapeType.FACE_BLEND_SHAPE_TYPE_*]"
979
- );
980
- this.emit(
981
- "// 68 blendshapes available: BROW_LOWERER_L/R, EYES_CLOSED_L/R, JAW_DROP, LIPS_TOWARD, etc."
982
- );
983
- }
984
- this.emit(
985
- `val ${varName}Confidence = faceState.getConfidence(FaceConfidenceRegion.FACE_CONFIDENCE_REGION_LOWER)`
986
- );
987
- this.dedent();
988
- this.emit("}");
989
- }
990
- // ─── DP3: Head-Following UI via UserSubspace ──────────────────────
991
- emitHeadFollowUI(varName, obj, config) {
992
- const distance = config.distance ?? config.follow_distance ?? 1.5;
993
- const w = this.findProp(obj, "width") ?? config.width ?? 400;
994
- const h = this.findProp(obj, "height") ?? config.height ?? 300;
995
- this.emit("");
996
- this.emit(`// DP3: Head-following UI panel via UserSubspace`);
997
- this.emit(`// android.permission.HEAD_TRACKING required`);
998
- this.emit(`// Content follows user's head at ${distance}m distance (soft-locking)`);
999
- this.emit(`SpatialPanel(SubspaceModifier.width(${w}f).height(${h}f)) {`);
1000
- this.indent();
1001
- const text = this.findProp(obj, "text");
1002
- const label = this.findProp(obj, "label");
1003
- if (text) {
1004
- this.emit(`Text("${text}")`);
1005
- } else if (label) {
1006
- this.emit(`Text("${label}")`);
1007
- } else {
1008
- this.emit(
1009
- `Column(modifier = Modifier.fillMaxSize()) { Text("${this.escapeStringValue(obj.name, "Kotlin")}") }`
1010
- );
1011
- }
1012
- this.dedent();
1013
- this.emit("}");
1014
- }
1015
- // ─── DP3: DRM-Protected Video via SurfaceEntity ───────────────────
1016
- emitDrmVideo(varName, config) {
1017
- const videoUri = config.uri ?? config.src ?? config.source ?? "";
1018
- const licenseUri = config.license_uri ?? config.drm_license ?? "";
1019
- const stereoMode = config.stereo_mode ?? "SIDE_BY_SIDE";
1020
- const shape = config.shape ?? "quad";
1021
- const width = config.width ?? 1;
1022
- const height = config.height ?? 1;
1023
- const radius = config.radius ?? 5;
1024
- this.emit("");
1025
- this.emit(`// DP3: DRM-protected video via SurfaceEntity + Widevine`);
1026
- let shapeCode;
1027
- if (shape === "sphere") {
1028
- shapeCode = `SurfaceEntity.Shape.Sphere(${radius}f)`;
1029
- } else if (shape === "hemisphere") {
1030
- shapeCode = `SurfaceEntity.Shape.Hemisphere(${radius}f)`;
1031
- } else {
1032
- shapeCode = `SurfaceEntity.Shape.Quad(FloatSize2d(${width}f, ${height}f))`;
1033
- }
1034
- const stereoModeMap = {
1035
- SIDE_BY_SIDE: "SurfaceEntity.StereoMode.SIDE_BY_SIDE",
1036
- TOP_BOTTOM: "SurfaceEntity.StereoMode.TOP_BOTTOM",
1037
- MONO: "SurfaceEntity.StereoMode.MONO",
1038
- MULTIVIEW_LEFT: "SurfaceEntity.StereoMode.MULTIVIEW_LEFT_PRIMARY",
1039
- MULTIVIEW_RIGHT: "SurfaceEntity.StereoMode.MULTIVIEW_RIGHT_PRIMARY"
1040
- };
1041
- const stereoModeKotlin = stereoModeMap[stereoMode] ?? "SurfaceEntity.StereoMode.SIDE_BY_SIDE";
1042
- this.emit(`val ${varName}Surface = SurfaceEntity.create(`);
1043
- this.indent();
1044
- this.emit("session = xrSession,");
1045
- this.emit(`stereoMode = ${stereoModeKotlin},`);
1046
- this.emit("pose = Pose(Vector3(0f, 0f, -1.5f), Quaternion.identity()),");
1047
- this.emit(`shape = ${shapeCode},`);
1048
- this.emit("surfaceProtection = SurfaceEntity.SurfaceProtection.PROTECTED");
1049
- this.dedent();
1050
- this.emit(")");
1051
- if (videoUri) {
1052
- this.emit("");
1053
- this.emit(`val ${varName}MediaItem = MediaItem.Builder()`);
1054
- this.indent();
1055
- this.emit(`.setUri("${videoUri}")`);
1056
- if (licenseUri) {
1057
- this.emit(".setDrmConfiguration(");
1058
- this.indent();
1059
- this.emit("MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)");
1060
- this.indent();
1061
- this.emit(`.setLicenseUri("${licenseUri}")`);
1062
- this.emit(".build()");
1063
- this.dedent();
1064
- this.dedent();
1065
- this.emit(")");
1066
- }
1067
- this.emit(".build()");
1068
- this.dedent();
1069
- this.emit("");
1070
- this.emit(`val ${varName}Player = ExoPlayer.Builder(this).build()`);
1071
- this.emit(`${varName}Player.setVideoSurface(${varName}Surface.getSurface())`);
1072
- this.emit(`${varName}Player.setMediaItem(${varName}MediaItem)`);
1073
- this.emit(`${varName}Player.prepare()`);
1074
- this.emit(`${varName}Player.play()`);
1075
- }
1076
- }
1077
- // ─── Domain Blocks (v4.2) ──────────────────────────────────────────
1078
- compileAndroidXRDomainBlocks(composition) {
1079
- const domainBlocks = composition.domainBlocks ?? [];
1080
- if (domainBlocks.length === 0) return;
1081
- this.emit("");
1082
- this.emit("// === v4.2 Domain Blocks ===");
1083
- let blockIdx = 0;
1084
- const compiled = chunkLXJ4667L_cjs.compileDomainBlocks(
1085
- // @ts-expect-error During migration
1086
- domainBlocks,
1087
- {
1088
- material: (block) => {
1089
- const mat = chunkLXJ4667L_cjs.compileMaterialBlock(block);
1090
- return chunkLXJ4667L_cjs.materialToAndroidXR(mat, `db${blockIdx++}`);
1091
- },
1092
- physics: (block) => {
1093
- const phys = chunkLXJ4667L_cjs.compilePhysicsBlock(block);
1094
- return chunkLXJ4667L_cjs.physicsToAndroidXR(phys, `db${blockIdx++}`);
1095
- },
1096
- vfx: (block) => {
1097
- const ps = chunkLXJ4667L_cjs.compileParticleBlock(block);
1098
- return chunkLXJ4667L_cjs.particlesToAndroidXR(ps, `db${blockIdx++}`);
1099
- },
1100
- postfx: (block) => {
1101
- const pp = chunkLXJ4667L_cjs.compilePostProcessingBlock(block);
1102
- const postfx = pp;
1103
- const effects = postfx.effects.map((e) => `// Effect: ${e.type} \u2014 ${JSON.stringify(e.properties)}`).join("\n");
1104
- return `// Post-Processing: ${postfx.keyword} \u2014 configure via Filament Renderer post-processing
1105
- ${effects}`;
1106
- },
1107
- audio: (block) => {
1108
- const audio = chunkLXJ4667L_cjs.compileAudioSourceBlock(block);
1109
- return chunkLXJ4667L_cjs.audioSourceToAndroidXR(audio, `db${blockIdx++}`);
1110
- },
1111
- weather: (block) => {
1112
- const weather = chunkLXJ4667L_cjs.compileWeatherBlock(block);
1113
- return chunkLXJ4667L_cjs.weatherToAndroidXR(weather);
1114
- }
1115
- },
1116
- (block) => `// Domain block: ${block.domain}/${block.keyword} "${this.escapeStringValue(block.name, "Kotlin")}"`
1117
- );
1118
- for (const line of compiled) {
1119
- for (const l of line.split("\n")) {
1120
- this.emit(l);
1121
- }
1122
- }
1123
- }
1124
- // ─── Transitions ─────────────────────────────────────────────────────
1125
- emitTransition(tr) {
1126
- const fn = this.sanitizeName(tr.name);
1127
- const target = tr.properties.find((p) => p.key === "target")?.value;
1128
- const dur = tr.properties.find((p) => p.key === "duration")?.value ?? 0.5;
1129
- this.emit("");
1130
- this.emit(`private fun transition_${fn}() {`);
1131
- this.indent();
1132
- this.emit(`// -> "${target}"`);
1133
- this.emit(
1134
- `ObjectAnimator.ofFloat(null, "alpha", 1f, 0f).apply { duration = ${Math.round(dur * 1e3)}L }.start()`
1135
- );
1136
- this.dedent();
1137
- this.emit("}");
1138
- }
1139
- // ─── Value Conversion Helpers ────────────────────────────────────────
1140
- /** Converts [x, y, z] to `Float3(xf, yf, zf)`. */
1141
- toKotlinFloat3(arr) {
1142
- if (Array.isArray(arr) && arr.length >= 3) return `Float3(${arr[0]}f, ${arr[1]}f, ${arr[2]}f)`;
1143
- if (Array.isArray(arr) && arr.length >= 1) return `Float3(${arr[0]}f, ${arr[0]}f, ${arr[0]}f)`;
1144
- const v = typeof arr === "number" ? arr : 0;
1145
- return `Float3(${v}f, ${v}f, ${v}f)`;
1146
- }
1147
- /** Converts `#RRGGBB` or `#RRGGBBAA` to `Color(0xAARRGGBB)`. */
1148
- toKotlinColor(hex) {
1149
- if (typeof hex === "string" && hex.startsWith("#")) {
1150
- const raw = hex.slice(1);
1151
- if (raw.length === 6) return `Color(0xFF${raw.toUpperCase()})`;
1152
- if (raw.length === 8) {
1153
- const [rr, gg, bb, aa] = [
1154
- raw.substring(0, 2),
1155
- raw.substring(2, 4),
1156
- raw.substring(4, 6),
1157
- raw.substring(6, 8)
1158
- ];
1159
- return `Color(0x${aa.toUpperCase()}${rr.toUpperCase()}${gg.toUpperCase()}${bb.toUpperCase()})`;
1160
- }
1161
- }
1162
- return "Color.White";
1163
- }
1164
- /** Maps HoloScript shape names to Filament geometric primitive descriptions. */
1165
- mapShapeToFilament(type) {
1166
- const map = {
1167
- cube: "BoxShape",
1168
- box: "BoxShape",
1169
- sphere: "IcoSphereShape",
1170
- cylinder: "CylinderShape",
1171
- cone: "ConeShape",
1172
- plane: "QuadShape",
1173
- torus: "CustomRingGeometry",
1174
- capsule: "CapsuleShape"
1175
- };
1176
- return map[type] ?? `Unknown("${type}")`;
1177
- }
1178
- // ─── Generic Helpers ─────────────────────────────────────────────────
1179
- toKotlinType(value) {
1180
- if (value === null) return "Any?";
1181
- if (typeof value === "boolean") return "Boolean";
1182
- if (typeof value === "number") return Number.isInteger(value) ? "Int" : "Float";
1183
- if (typeof value === "string") return "String";
1184
- if (Array.isArray(value)) {
1185
- if (value.length === 3 && value.every((v) => typeof v === "number")) return "Vector3";
1186
- return "List<Any>";
1187
- }
1188
- return "Any";
1189
- }
1190
- toKotlinValue(value) {
1191
- if (typeof value === "number") return Number.isInteger(value) ? `${value}` : `${value}f`;
1192
- if (typeof value === "boolean") return value ? "true" : "false";
1193
- if (typeof value === "string") return `"${this.escapeStringValue(value, "Kotlin")}"`;
1194
- if (value === null) return "null";
1195
- if (Array.isArray(value))
1196
- return `listOf(${value.map((v) => this.toKotlinValue(v)).join(", ")})`;
1197
- return "null";
1198
- }
1199
- findProp(obj, key) {
1200
- return obj.properties?.find((p) => p.key === key)?.value;
1201
- }
1202
- sanitizeName(name) {
1203
- return name.replace(/[^a-zA-Z0-9_]/g, "_");
1204
- }
1205
- emit(line) {
1206
- this.lines.push(this.options.indent.repeat(this.indentLevel) + line);
1207
- }
1208
- indent() {
1209
- this.indentLevel++;
1210
- }
1211
- dedent() {
1212
- if (this.indentLevel > 0) this.indentLevel--;
1213
- }
1214
- // ═══════════════════════════════════════════════════════════════════════════
1215
- // AI GLASSES MODE — Jetpack Compose Glimmer + Jetpack Projected
1216
- // ═══════════════════════════════════════════════════════════════════════════
1217
- /**
1218
- * Generates the main Activity file for AI glasses form factor.
1219
- *
1220
- * Uses Jetpack Compose Glimmer (GlimmerTheme, Card, Button, Text) instead of
1221
- * Material3 + SceneCore, and integrates ProjectedContext /
1222
- * ProjectedDeviceController for hardware access.
1223
- */
1224
- generateGlassesActivityFile(composition) {
1225
- this.lines = [];
1226
- this.indentLevel = 0;
1227
- this.emit("// Auto-generated by HoloScript AndroidXRCompiler (AI Glasses mode)");
1228
- this.emit(
1229
- `// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`
1230
- );
1231
- this.emit("// Form factor: AI Glasses (Jetpack Compose Glimmer + Jetpack Projected)");
1232
- this.emit("// Do not edit manually -- regenerate from .holo source");
1233
- this.emit("");
1234
- this.emit(`package ${this.options.packageName}`);
1235
- this.emit("");
1236
- this.emitGlassesImports(composition);
1237
- this.emit("");
1238
- this.emitGlassesActivityClass(composition);
1239
- return this.lines.join("\n");
1240
- }
1241
- /**
1242
- * Emits Glimmer-specific imports for glasses mode.
1243
- */
1244
- emitGlassesImports(composition) {
1245
- this.emit("import android.os.Bundle");
1246
- this.emit("");
1247
- this.emit("import androidx.activity.ComponentActivity");
1248
- this.emit("import androidx.activity.compose.setContent");
1249
- this.emit("import androidx.compose.runtime.*");
1250
- this.emit("import androidx.compose.foundation.layout.*");
1251
- this.emit("import androidx.compose.ui.Alignment");
1252
- this.emit("import androidx.compose.ui.Modifier");
1253
- this.emit("import androidx.compose.ui.graphics.Color");
1254
- this.emit("import androidx.lifecycle.lifecycleScope");
1255
- this.emit("");
1256
- this.emit("import androidx.xr.glimmer.GlimmerTheme");
1257
- this.emit("import androidx.xr.glimmer.Card");
1258
- this.emit("import androidx.xr.glimmer.Button");
1259
- this.emit("import androidx.xr.glimmer.Text");
1260
- this.emit("import androidx.xr.glimmer.ListItem");
1261
- this.emit("import androidx.xr.glimmer.TitleChip");
1262
- this.emit("import androidx.xr.glimmer.surface");
1263
- this.emit("");
1264
- this.emit("import androidx.xr.projected.ProjectedContext");
1265
- this.emit("import androidx.xr.projected.ProjectedDeviceController");
1266
- this.emit(
1267
- "import androidx.xr.projected.ProjectedDeviceController.Companion.CAPABILITY_VISUAL_UI"
1268
- );
1269
- this.emit("import androidx.xr.projected.ProjectedDisplayController");
1270
- this.emit("");
1271
- this.emit("import kotlinx.coroutines.launch");
1272
- this.emit("");
1273
- if (this.options.useARCore) {
1274
- this.emit("// ARCore for Jetpack XR (motion tracking + geospatial on glasses)");
1275
- this.emit("import com.google.ar.core.Config");
1276
- }
1277
- if (composition.audio?.length) {
1278
- this.emit("import android.media.AudioAttributes");
1279
- this.emit("import android.media.SoundPool");
1280
- }
1281
- }
1282
- /**
1283
- * Emits the AI glasses Activity class using GlimmerTheme and Projected API.
1284
- */
1285
- emitGlassesActivityClass(composition) {
1286
- this.emit("@OptIn(ExperimentalProjectedApi::class)");
1287
- this.emit(`class ${this.options.activityName} : ComponentActivity() {`);
1288
- this.indent();
1289
- this.emit("private var displayController: ProjectedDisplayController? = null");
1290
- this.emit("private var isVisualUiSupported by mutableStateOf(false)");
1291
- this.emit("private var areVisualsOn by mutableStateOf(true)");
1292
- if (composition.audio?.length) {
1293
- this.emit("private lateinit var soundPool: SoundPool");
1294
- }
1295
- this.emit("");
1296
- this.emit("override fun onCreate(savedInstanceState: Bundle?) {");
1297
- this.indent();
1298
- this.emit("super.onCreate(savedInstanceState)");
1299
- this.emit("");
1300
- this.emit("// Detect glasses display capability via Projected API");
1301
- this.emit("lifecycleScope.launch {");
1302
- this.indent();
1303
- this.emit(
1304
- "val projectedDeviceController = ProjectedDeviceController.create(this@" + this.options.activityName + ")"
1305
- );
1306
- this.emit(
1307
- "isVisualUiSupported = projectedDeviceController.capabilities.contains(CAPABILITY_VISUAL_UI)"
1308
- );
1309
- this.dedent();
1310
- this.emit("}");
1311
- this.emit("");
1312
- if (composition.audio?.length) {
1313
- this.emit("val audioAttrs = AudioAttributes.Builder()");
1314
- this.indent();
1315
- this.emit(".setUsage(AudioAttributes.USAGE_GAME)");
1316
- this.emit(".setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build()");
1317
- this.dedent();
1318
- this.emit(
1319
- "soundPool = SoundPool.Builder().setMaxStreams(8).setAudioAttributes(audioAttrs).build()"
1320
- );
1321
- this.emit("");
1322
- }
1323
- this.emit("setContent {");
1324
- this.indent();
1325
- this.emit("GlimmerTheme {");
1326
- this.indent();
1327
- this.emit(`${this.sanitizeName(composition.name)}GlassesScreen(`);
1328
- this.indent();
1329
- this.emit("areVisualsOn = areVisualsOn,");
1330
- this.emit("isVisualUiSupported = isVisualUiSupported,");
1331
- this.emit(`onClose = { finish() }`);
1332
- this.dedent();
1333
- this.emit(")");
1334
- this.dedent();
1335
- this.emit("}");
1336
- this.dedent();
1337
- this.emit("}");
1338
- this.dedent();
1339
- this.emit("}");
1340
- this.emit("");
1341
- this.emitGlassesSceneComposable(composition);
1342
- this.dedent();
1343
- this.emit("}");
1344
- }
1345
- /**
1346
- * Emits the main Glimmer composable screen for AI glasses.
1347
- *
1348
- * Generates lightweight AR overlay UI using Glimmer components (Card, Text, Button,
1349
- * ListItem, TitleChip) instead of the immersive SceneCore entity hierarchy used
1350
- * in headset mode.
1351
- */
1352
- emitGlassesSceneComposable(composition) {
1353
- const name = this.sanitizeName(composition.name);
1354
- this.emit("@Composable");
1355
- this.emit(`private fun ${name}GlassesScreen(`);
1356
- this.indent();
1357
- this.emit("areVisualsOn: Boolean,");
1358
- this.emit("isVisualUiSupported: Boolean,");
1359
- this.emit("onClose: () -> Unit,");
1360
- this.emit("modifier: Modifier = Modifier");
1361
- this.dedent();
1362
- this.emit(") {");
1363
- this.indent();
1364
- if (composition.state) {
1365
- for (const p of composition.state.properties) {
1366
- this.emit(
1367
- `var ${this.escapeStringValue(p.key, "Kotlin")} by remember { mutableStateOf(${this.toKotlinValue(p.value)}) }`
1368
- );
1369
- }
1370
- this.emit("");
1371
- }
1372
- this.emit("Box(");
1373
- this.indent();
1374
- this.emit("modifier = modifier");
1375
- this.indent();
1376
- this.emit(".surface(focusable = false)");
1377
- this.emit(".fillMaxSize(),");
1378
- this.dedent();
1379
- this.emit("contentAlignment = Alignment.Center");
1380
- this.dedent();
1381
- this.emit(") {");
1382
- this.indent();
1383
- this.emit("if (isVisualUiSupported && areVisualsOn) {");
1384
- this.indent();
1385
- this.emitGlimmerContentTree(composition);
1386
- this.dedent();
1387
- this.emit("} else {");
1388
- this.indent();
1389
- this.emit("// Audio-only fallback for glasses without display");
1390
- this.emit('Text("Audio Guidance Mode Active")');
1391
- this.dedent();
1392
- this.emit("}");
1393
- this.dedent();
1394
- this.emit("}");
1395
- this.dedent();
1396
- this.emit("}");
1397
- this.emit("");
1398
- }
1399
- /**
1400
- * Emits Glimmer-based content tree: converts HoloScript objects/UI into
1401
- * Card, TitleChip, ListItem, Button, and Text composables.
1402
- */
1403
- emitGlimmerContentTree(composition) {
1404
- this.emit("Column(");
1405
- this.indent();
1406
- this.emit("modifier = Modifier.fillMaxSize(),");
1407
- this.emit("verticalArrangement = Arrangement.spacedBy(8.dp)");
1408
- this.dedent();
1409
- this.emit(") {");
1410
- this.indent();
1411
- this.emit(
1412
- `TitleChip(title = "${this.escapeStringValue(composition.name, "Kotlin")}")`
1413
- );
1414
- this.emit("");
1415
- for (const obj of composition.objects ?? []) {
1416
- this.emitGlimmerObject(obj);
1417
- }
1418
- if (composition.ui) {
1419
- this.emit("");
1420
- this.emit("// UI Elements");
1421
- for (const el of composition.ui.elements) {
1422
- this.emitGlimmerUIElement(el);
1423
- }
1424
- }
1425
- this.emit("");
1426
- this.emit("Button(onClick = onClose) {");
1427
- this.indent();
1428
- this.emit('Text("Close")');
1429
- this.dedent();
1430
- this.emit("}");
1431
- this.dedent();
1432
- this.emit("}");
1433
- }
1434
- /**
1435
- * Converts a HoloObject into a Glimmer Card composable.
1436
- *
1437
- * For glasses mode, 3D objects are represented as information cards showing
1438
- * the object's name, type, and properties — the glasses display provides
1439
- * AR overlay information rather than full 3D rendering.
1440
- */
1441
- emitGlimmerObject(obj) {
1442
- const v = this.sanitizeName(obj.name);
1443
- const meshType = this.findProp(obj, "mesh") ?? this.findProp(obj, "type") ?? "object";
1444
- const modelSrc = this.findProp(obj, "model") ?? this.findProp(obj, "src");
1445
- const text = this.findProp(obj, "text");
1446
- this.emit(
1447
- `// Object: ${this.escapeStringValue(obj.name, "Kotlin")} (glasses overlay)`
1448
- );
1449
- if (text) {
1450
- const color = this.findProp(obj, "color");
1451
- this.emit(
1452
- `Text("${text}"${color ? `, color = ${this.toKotlinColor(color)}` : ""})`
1453
- );
1454
- } else if (modelSrc) {
1455
- this.emit(`Card(`);
1456
- this.indent();
1457
- this.emit(`title = { Text("${this.escapeStringValue(obj.name, "Kotlin")}") },`);
1458
- this.emit(`action = {`);
1459
- this.indent();
1460
- this.emit(`Button(onClick = { /* interact with ${v} */ }) {`);
1461
- this.indent();
1462
- this.emit('Text("View")');
1463
- this.dedent();
1464
- this.emit("}");
1465
- this.dedent();
1466
- this.emit("}");
1467
- this.dedent();
1468
- this.emit(") {");
1469
- this.indent();
1470
- this.emit(`Text("3D Model: ${modelSrc}")`);
1471
- const pos = this.findProp(obj, "position");
1472
- if (pos && Array.isArray(pos)) {
1473
- this.emit(`Text("Position: ${pos.join(", ")}")`);
1474
- }
1475
- this.dedent();
1476
- this.emit("}");
1477
- } else {
1478
- this.emit(`ListItem(`);
1479
- this.indent();
1480
- this.emit(
1481
- `headlineContent = { Text("${this.escapeStringValue(obj.name, "Kotlin")}") },`
1482
- );
1483
- this.emit(`supportingContent = { Text("Type: ${meshType}") }`);
1484
- this.dedent();
1485
- this.emit(")");
1486
- }
1487
- this.emit("");
1488
- for (const child of obj.children ?? []) {
1489
- this.emitGlimmerObject(child);
1490
- }
1491
- }
1492
- /**
1493
- * Converts a HoloUI element into appropriate Glimmer composable.
1494
- */
1495
- emitGlimmerUIElement(el) {
1496
- const vn = this.sanitizeName(el.name);
1497
- const text = el.properties.find((p) => p.key === "text")?.value;
1498
- const label = el.properties.find((p) => p.key === "label")?.value;
1499
- const elType = el.properties.find((p) => p.key === "type")?.value;
1500
- if (elType === "button" && label) {
1501
- this.emit(`Button(onClick = { /* ${vn} */ }) { Text("${label}") }`);
1502
- } else if (text) {
1503
- const color = el.properties.find((p) => p.key === "color")?.value;
1504
- this.emit(
1505
- `Text("${text}"${color ? `, color = ${this.toKotlinColor(color)}` : ""})`
1506
- );
1507
- } else {
1508
- this.emit(
1509
- `Card(title = { Text("${this.escapeStringValue(el.name, "Kotlin")}") }) {`
1510
- );
1511
- this.indent();
1512
- this.emit(`Text("${this.escapeStringValue(el.name, "Kotlin")}")`);
1513
- this.dedent();
1514
- this.emit("}");
1515
- }
1516
- }
1517
- // ─── Glasses Manifest ──────────────────────────────────────────────
1518
- /**
1519
- * Generates AndroidManifest.xml for AI glasses form factor.
1520
- *
1521
- * Key differences from headset:
1522
- * - requiredDisplayCategory="xr_projected" on the activity
1523
- * - No XR headtracking hardware requirement
1524
- * - Camera permission for glasses camera via ProjectedContext
1525
- */
1526
- generateGlassesManifestFile(composition) {
1527
- const pkg = this.options.packageName;
1528
- const activityName = this.options.activityName;
1529
- const permissions = [];
1530
- permissions.push(' <uses-permission android:name="android.permission.CAMERA" />');
1531
- permissions.push(' <uses-permission android:name="android.permission.RECORD_AUDIO" />');
1532
- permissions.push(' <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />');
1533
- const hasHandTracking = composition.objects?.some(
1534
- (o) => o.traits?.some((t) => t.name === "hand_tracking")
1535
- );
1536
- if (hasHandTracking) {
1537
- permissions.push(' <uses-permission android:name="android.permission.HAND_TRACKING" />');
1538
- }
1539
- return `<?xml version="1.0" encoding="utf-8"?>
1540
- <!-- Auto-generated by HoloScript AndroidXRCompiler (AI Glasses mode) -->
1541
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
1542
- package="${pkg}">
1543
-
1544
- <!-- Glasses Permissions -->
1545
- ${permissions.join("\n")}
1546
-
1547
- <application
1548
- android:allowBackup="true"
1549
- android:label="${this.escapeStringValue(composition.name, "XML")}"
1550
- android:supportsRtl="true"
1551
- android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
1552
-
1553
- <!-- Android XR Glasses metadata -->
1554
- <meta-data android:name="com.google.ar.core" android:value="optional" />
1555
-
1556
- <activity
1557
- android:name=".${activityName}"
1558
- android:exported="true"
1559
- android:requiredDisplayCategory="xr_projected"
1560
- android:label="${this.escapeStringValue(composition.name, "XML")} AI Glasses">
1561
- <intent-filter>
1562
- <action android:name="android.intent.action.MAIN" />
1563
- </intent-filter>
1564
- </activity>
1565
- </application>
1566
- </manifest>`;
1567
- }
1568
- // ─── Glasses Build Gradle ──────────────────────────────────────────
1569
- /**
1570
- * Generates build.gradle.kts for AI glasses form factor.
1571
- *
1572
- * Includes Jetpack Compose Glimmer and Jetpack Projected dependencies
1573
- * instead of the full SceneCore/Filament stack used in headset mode.
1574
- */
1575
- generateGlassesBuildGradle(composition) {
1576
- return `// Auto-generated by HoloScript AndroidXRCompiler (AI Glasses mode)
1577
- // Source: ${this.escapeStringValue(composition.name, "Kotlin")}
1578
-
1579
- plugins {
1580
- id("com.android.application")
1581
- id("org.jetbrains.kotlin.android")
1582
- id("org.jetbrains.kotlin.plugin.compose")
1583
- }
1584
-
1585
- android {
1586
- namespace = "${this.options.packageName}"
1587
- compileSdk = ${this.options.targetSdk}
1588
-
1589
- defaultConfig {
1590
- applicationId = "${this.options.packageName}"
1591
- minSdk = ${this.options.minSdk}
1592
- targetSdk = ${this.options.targetSdk}
1593
- versionCode = 1
1594
- versionName = "1.0"
1595
- }
1596
-
1597
- buildTypes {
1598
- release {
1599
- isMinifyEnabled = false
1600
- proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
1601
- }
1602
- }
1603
-
1604
- compileOptions {
1605
- sourceCompatibility = JavaVersion.VERSION_17
1606
- targetCompatibility = JavaVersion.VERSION_17
1607
- }
1608
-
1609
- kotlinOptions {
1610
- jvmTarget = "17"
1611
- }
1612
-
1613
- buildFeatures {
1614
- compose = true
1615
- }
1616
- }
1617
-
1618
- dependencies {
1619
- // Core
1620
- implementation("androidx.core:core-ktx:1.13.1")
1621
- implementation("androidx.appcompat:appcompat:1.7.0")
1622
- implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
1623
- implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.0")
1624
- implementation("androidx.activity:activity-compose:1.9.0")
1625
-
1626
- // Jetpack Compose
1627
- implementation(platform("androidx.compose:compose-bom:2024.06.00"))
1628
- implementation("androidx.compose.ui:ui")
1629
- implementation("androidx.compose.runtime:runtime")
1630
-
1631
- // Jetpack Compose Glimmer \u2014 AI Glasses UI toolkit
1632
- implementation("androidx.xr.glimmer:glimmer:1.0.0-alpha06")
1633
-
1634
- // Jetpack Projected \u2014 AI Glasses hardware access
1635
- implementation("androidx.xr.projected:projected:1.0.0-alpha04")
1636
-
1637
- // Android XR Runtime
1638
- implementation("androidx.xr.runtime:runtime:1.0.0-alpha11")
1639
- implementation("androidx.xr:xr:1.0.0-alpha01")
1640
-
1641
- // ARCore for Jetpack XR (motion tracking + geospatial on glasses)
1642
- implementation("androidx.xr.arcore:arcore:1.0.0-alpha10")
1643
-
1644
- // CameraX (for glasses camera via ProjectedContext)
1645
- implementation("androidx.camera:camera-core:1.4.0")
1646
- implementation("androidx.camera:camera-camera2:1.4.0")
1647
- implementation("androidx.camera:camera-lifecycle:1.4.0")
1648
-
1649
- // Coroutines
1650
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
1651
- }`;
1652
- }
1653
- // ─── Glimmer Components File ──────────────────────────────────────
1654
- /**
1655
- * Generates a dedicated Glimmer components file with reusable composables
1656
- * for the AI glasses experience.
1657
- *
1658
- * This file contains:
1659
- * - GlimmerOverlay: Root overlay composable for AR content
1660
- * - GlimmerInfoCard: Object information card
1661
- * - GlimmerActionButton: Touchpad-friendly action button
1662
- * - ProjectedCameraPreview: Camera integration via ProjectedContext
1663
- */
1664
- generateGlimmerComponentsFile(composition) {
1665
- this.lines = [];
1666
- this.indentLevel = 0;
1667
- const pkg = this.options.packageName;
1668
- const name = this.sanitizeName(composition.name);
1669
- this.emit("// Auto-generated by HoloScript AndroidXRCompiler (AI Glasses mode)");
1670
- this.emit(
1671
- `// Glimmer Components: ${this.escapeStringValue(composition.name, "Kotlin")}`
1672
- );
1673
- this.emit("");
1674
- this.emit(`package ${pkg}`);
1675
- this.emit("");
1676
- this.emit("import androidx.compose.runtime.*");
1677
- this.emit("import androidx.compose.foundation.layout.*");
1678
- this.emit("import androidx.compose.ui.Alignment");
1679
- this.emit("import androidx.compose.ui.Modifier");
1680
- this.emit("import androidx.compose.ui.unit.dp");
1681
- this.emit("");
1682
- this.emit("import androidx.xr.glimmer.GlimmerTheme");
1683
- this.emit("import androidx.xr.glimmer.Card");
1684
- this.emit("import androidx.xr.glimmer.Button");
1685
- this.emit("import androidx.xr.glimmer.Text");
1686
- this.emit("import androidx.xr.glimmer.ListItem");
1687
- this.emit("import androidx.xr.glimmer.TitleChip");
1688
- this.emit("import androidx.xr.glimmer.surface");
1689
- this.emit("");
1690
- this.emit("import androidx.xr.projected.ProjectedContext");
1691
- this.emit("");
1692
- this.emit("/**");
1693
- this.emit(" * Root overlay composable for the AI glasses AR experience.");
1694
- this.emit(" * Wraps content in GlimmerTheme with glasses-optimized styling.");
1695
- this.emit(" */");
1696
- this.emit("@Composable");
1697
- this.emit(`fun ${name}GlimmerOverlay(`);
1698
- this.indent();
1699
- this.emit("isVisualUiSupported: Boolean,");
1700
- this.emit("content: @Composable () -> Unit");
1701
- this.dedent();
1702
- this.emit(") {");
1703
- this.indent();
1704
- this.emit("GlimmerTheme {");
1705
- this.indent();
1706
- this.emit("Box(");
1707
- this.indent();
1708
- this.emit("modifier = Modifier");
1709
- this.indent();
1710
- this.emit(".surface(focusable = false)");
1711
- this.emit(".fillMaxSize(),");
1712
- this.dedent();
1713
- this.emit("contentAlignment = Alignment.Center");
1714
- this.dedent();
1715
- this.emit(") {");
1716
- this.indent();
1717
- this.emit("if (isVisualUiSupported) {");
1718
- this.indent();
1719
- this.emit("content()");
1720
- this.dedent();
1721
- this.emit("} else {");
1722
- this.indent();
1723
- this.emit('Text("Audio Guidance Mode Active")');
1724
- this.dedent();
1725
- this.emit("}");
1726
- this.dedent();
1727
- this.emit("}");
1728
- this.dedent();
1729
- this.emit("}");
1730
- this.dedent();
1731
- this.emit("}");
1732
- this.emit("");
1733
- this.emit("/**");
1734
- this.emit(" * Glimmer info card for displaying object details in AR overlay.");
1735
- this.emit(" * Optimized for optical see-through display with high contrast.");
1736
- this.emit(" */");
1737
- this.emit("@Composable");
1738
- this.emit("fun GlimmerInfoCard(");
1739
- this.indent();
1740
- this.emit("title: String,");
1741
- this.emit('subtitle: String = "",');
1742
- this.emit("onAction: (() -> Unit)? = null,");
1743
- this.emit('actionLabel: String = "View"');
1744
- this.dedent();
1745
- this.emit(") {");
1746
- this.indent();
1747
- this.emit("Card(");
1748
- this.indent();
1749
- this.emit("title = { Text(title) },");
1750
- this.emit("action = onAction?.let { action ->");
1751
- this.indent();
1752
- this.emit("{");
1753
- this.indent();
1754
- this.emit("Button(onClick = action) {");
1755
- this.indent();
1756
- this.emit("Text(actionLabel)");
1757
- this.dedent();
1758
- this.emit("}");
1759
- this.dedent();
1760
- this.emit("}");
1761
- this.dedent();
1762
- this.emit("}");
1763
- this.dedent();
1764
- this.emit(") {");
1765
- this.indent();
1766
- this.emit("if (subtitle.isNotEmpty()) {");
1767
- this.indent();
1768
- this.emit("Text(subtitle)");
1769
- this.dedent();
1770
- this.emit("}");
1771
- this.dedent();
1772
- this.emit("}");
1773
- this.dedent();
1774
- this.emit("}");
1775
- this.emit("");
1776
- this.emit("/**");
1777
- this.emit(" * Touchpad-friendly action button for AI glasses input.");
1778
- this.emit(" * Pre-sized for comfortable touchpad tap targeting.");
1779
- this.emit(" */");
1780
- this.emit("@Composable");
1781
- this.emit("fun GlimmerActionButton(");
1782
- this.indent();
1783
- this.emit("label: String,");
1784
- this.emit("onClick: () -> Unit,");
1785
- this.emit("modifier: Modifier = Modifier");
1786
- this.dedent();
1787
- this.emit(") {");
1788
- this.indent();
1789
- this.emit("Button(");
1790
- this.indent();
1791
- this.emit("onClick = onClick,");
1792
- this.emit("modifier = modifier.fillMaxWidth()");
1793
- this.dedent();
1794
- this.emit(") {");
1795
- this.indent();
1796
- this.emit("Text(label)");
1797
- this.dedent();
1798
- this.emit("}");
1799
- this.dedent();
1800
- this.emit("}");
1801
- this.emit("");
1802
- this.emit("/**");
1803
- this.emit(" * Accesses the AI glasses camera via ProjectedContext.");
1804
- this.emit(
1805
- " * Uses CameraX with projected device context for the glasses' outward-facing camera."
1806
- );
1807
- this.emit(" */");
1808
- this.emit(
1809
- "fun getGlassesCameraContext(hostContext: android.content.Context): android.content.Context? {"
1810
- );
1811
- this.indent();
1812
- this.emit("return try {");
1813
- this.indent();
1814
- this.emit("ProjectedContext.createProjectedDeviceContext(hostContext)");
1815
- this.dedent();
1816
- this.emit("} catch (e: IllegalStateException) {");
1817
- this.indent();
1818
- this.emit('android.util.Log.w("HoloScript", "AI Glasses not connected: ${e.message}")');
1819
- this.emit("null");
1820
- this.dedent();
1821
- this.emit("}");
1822
- this.dedent();
1823
- this.emit("}");
1824
- this.emit("");
1825
- this.emit("/**");
1826
- this.emit(" * Monitors AI glasses connection state via Jetpack Projected.");
1827
- this.emit(" * Returns a Flow<Boolean> tracking whether glasses are connected.");
1828
- this.emit(" */");
1829
- this.emit("@Composable");
1830
- this.emit(
1831
- "fun rememberGlassesConnectionState(context: android.content.Context): State<Boolean> {"
1832
- );
1833
- this.indent();
1834
- this.emit("val isConnected = remember { mutableStateOf(false) }");
1835
- this.emit("LaunchedEffect(Unit) {");
1836
- this.indent();
1837
- this.emit(
1838
- "ProjectedContext.isProjectedDeviceConnected(context, coroutineContext).collect { connected ->"
1839
- );
1840
- this.indent();
1841
- this.emit("isConnected.value = connected");
1842
- this.dedent();
1843
- this.emit("}");
1844
- this.dedent();
1845
- this.emit("}");
1846
- this.emit("return isConnected");
1847
- this.dedent();
1848
- this.emit("}");
1849
- return this.lines.join("\n");
1850
- }
1851
- };
1852
- }
1853
- });
1854
-
1855
- exports.AndroidXRCompiler_exports = AndroidXRCompiler_exports;
1856
- exports.compileToAndroidXR = compileToAndroidXR;
1857
- exports.init_AndroidXRCompiler = init_AndroidXRCompiler;
1858
- //# sourceMappingURL=chunk-4XSNJCLC.cjs.map
1859
- //# sourceMappingURL=chunk-4XSNJCLC.cjs.map