@holoscript/core 6.0.3 → 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 (244) hide show
  1. package/README.md +40 -40
  2. package/dist/codebase/index.d.ts +25 -0
  3. package/dist/compiler/index.d.ts +82 -0
  4. package/dist/constants.d.ts +4 -0
  5. package/dist/coordinators/index.d.ts +189 -0
  6. package/dist/entries/interop.d.ts +9 -0
  7. package/dist/entries/scripting.d.ts +7 -0
  8. package/dist/index.d.ts +520 -19
  9. package/dist/paper-0c-spike/index.d.ts +29 -0
  10. package/dist/reconstruction/index.d.ts +78 -0
  11. package/dist/self-improvement/index.d.ts +0 -48
  12. package/dist/storage/index.d.ts +24 -0
  13. package/dist/tools/index.d.ts +33 -0
  14. package/dist/traitDocs/traitDocs.d.ts +69 -0
  15. package/dist/traits/index.d.ts +61 -0
  16. package/package.json +102 -19
  17. package/dist/GLTFPipeline-3KLWWUQO.cjs +0 -34
  18. package/dist/GLTFPipeline-LYII2ZVQ.js +0 -13
  19. package/dist/HoloScriptPlusParser-BZR5DELQ.js +0 -692
  20. package/dist/HoloScriptPlusParser-SBMYDNXO.cjs +0 -697
  21. package/dist/USDZExporter-MHEHXZB4.cjs +0 -709
  22. package/dist/USDZExporter-YA55ZS35.js +0 -707
  23. package/dist/chunk-26TLYBFD.js +0 -309
  24. package/dist/chunk-27LWW5FU.cjs +0 -410
  25. package/dist/chunk-2UX5LRAP.cjs +0 -833
  26. package/dist/chunk-32TWR3HE.js +0 -70
  27. package/dist/chunk-3FXV5S25.cjs +0 -633
  28. package/dist/chunk-3FYZOR7S.cjs +0 -69
  29. package/dist/chunk-3KEU5QYY.cjs +0 -175
  30. package/dist/chunk-4FCZDTD5.js +0 -610
  31. package/dist/chunk-4KJ2R7VP.cjs +0 -507
  32. package/dist/chunk-4SMUJFHL.js +0 -1213
  33. package/dist/chunk-54FHG2MX.js +0 -655
  34. package/dist/chunk-56IF4NTA.js +0 -56
  35. package/dist/chunk-5GLCTO44.js +0 -161
  36. package/dist/chunk-5TOFIR6L.js +0 -407
  37. package/dist/chunk-65RFOWZI.js +0 -765
  38. package/dist/chunk-6OQBLABR.js +0 -5240
  39. package/dist/chunk-6WNCRE6F.js +0 -126
  40. package/dist/chunk-74YCHHTE.js +0 -1872
  41. package/dist/chunk-7H5UNJZD.cjs +0 -1973
  42. package/dist/chunk-7KPI4EKH.cjs +0 -701
  43. package/dist/chunk-7LCYLPW2.cjs +0 -663
  44. package/dist/chunk-7N3JIJMT.js +0 -1395
  45. package/dist/chunk-7X2IEJIE.cjs +0 -2526
  46. package/dist/chunk-A6GO3DPZ.cjs +0 -1002
  47. package/dist/chunk-AC4BSHFV.js +0 -765
  48. package/dist/chunk-AIUXRS74.cjs +0 -3681
  49. package/dist/chunk-ARNKA274.cjs +0 -130
  50. package/dist/chunk-AW7WAELW.js +0 -975
  51. package/dist/chunk-BKIGPPVM.js +0 -629
  52. package/dist/chunk-BO2OKHIY.js +0 -6055
  53. package/dist/chunk-BU5ZAFMC.js +0 -115
  54. package/dist/chunk-BY3B7ZYV.cjs +0 -457
  55. package/dist/chunk-C2QHVHZF.js +0 -4066
  56. package/dist/chunk-C7BNX4XJ.js +0 -1043
  57. package/dist/chunk-CLYEIWA5.cjs +0 -408
  58. package/dist/chunk-CMYAWUX3.js +0 -4455
  59. package/dist/chunk-CN4NOESF.cjs +0 -416
  60. package/dist/chunk-CNVM7J3M.js +0 -883
  61. package/dist/chunk-CO2VM2DK.js +0 -1965
  62. package/dist/chunk-CQDOF3G7.cjs +0 -1404
  63. package/dist/chunk-DH5G2JUA.cjs +0 -614
  64. package/dist/chunk-DIEDKX5B.cjs +0 -660
  65. package/dist/chunk-DN7UFU63.cjs +0 -172
  66. package/dist/chunk-DXVCEFZB.js +0 -2027
  67. package/dist/chunk-EGIZHYJP.cjs +0 -85
  68. package/dist/chunk-EJA7G2C4.cjs +0 -428
  69. package/dist/chunk-ELLQPFAF.js +0 -830
  70. package/dist/chunk-EMO7HAKJ.cjs +0 -1875
  71. package/dist/chunk-ENV7K6XA.js +0 -282
  72. package/dist/chunk-EPWRXL6S.js +0 -2522
  73. package/dist/chunk-EUFLX2PI.js +0 -56956
  74. package/dist/chunk-F5LVGHNT.js +0 -449
  75. package/dist/chunk-FEFHPUEM.cjs +0 -6122
  76. package/dist/chunk-FF3Q7ADR.js +0 -380
  77. package/dist/chunk-GC3YU46J.js +0 -762
  78. package/dist/chunk-H6WMMLQK.cjs +0 -4492
  79. package/dist/chunk-H7XMORZI.js +0 -2731
  80. package/dist/chunk-HAN4V3PF.cjs +0 -2037
  81. package/dist/chunk-HBZYCASG.js +0 -4208
  82. package/dist/chunk-HF4OFY25.cjs +0 -4097
  83. package/dist/chunk-HHS6FMOU.cjs +0 -3813
  84. package/dist/chunk-HJJLD366.js +0 -1169
  85. package/dist/chunk-HKCVM6OK.cjs +0 -2237
  86. package/dist/chunk-HUFNKFA6.js +0 -698
  87. package/dist/chunk-I3XGTIHM.cjs +0 -1505
  88. package/dist/chunk-I6XZ5DZF.cjs +0 -488
  89. package/dist/chunk-IHOUYQIA.js +0 -406
  90. package/dist/chunk-ILRBGFVD.cjs +0 -72
  91. package/dist/chunk-KDB6BUMB.js +0 -3666
  92. package/dist/chunk-KUJRR4FJ.js +0 -657
  93. package/dist/chunk-KYM4XRFG.js +0 -421
  94. package/dist/chunk-L3Z2HIWJ.cjs +0 -793
  95. package/dist/chunk-LBPEZQAF.js +0 -2232
  96. package/dist/chunk-LER4WXW5.cjs +0 -286
  97. package/dist/chunk-LK5IAVH6.cjs +0 -1173
  98. package/dist/chunk-M6FU6S22.js +0 -2911
  99. package/dist/chunk-MWEFR6YQ.js +0 -1857
  100. package/dist/chunk-N5FTS757.cjs +0 -415
  101. package/dist/chunk-NCUHGRTZ.js +0 -752
  102. package/dist/chunk-NKRKT6V2.js +0 -69
  103. package/dist/chunk-NLPSZT4C.js +0 -630
  104. package/dist/chunk-NRUB55IT.cjs +0 -771
  105. package/dist/chunk-NTMZSDXM.cjs +0 -633
  106. package/dist/chunk-ODGMVILH.js +0 -9662
  107. package/dist/chunk-PBA6NXCT.cjs +0 -1216
  108. package/dist/chunk-PHENW4MQ.js +0 -65
  109. package/dist/chunk-PJMOXFPR.cjs +0 -1861
  110. package/dist/chunk-PLMYCCA4.js +0 -504
  111. package/dist/chunk-PPULB4GG.cjs +0 -9728
  112. package/dist/chunk-PQLGZKMC.cjs +0 -755
  113. package/dist/chunk-Q56TT7IL.js +0 -483
  114. package/dist/chunk-QHVVVN47.cjs +0 -2033
  115. package/dist/chunk-QWKUKVRE.js +0 -2026
  116. package/dist/chunk-RE3OKSYF.cjs +0 -424
  117. package/dist/chunk-RT7LJRSF.cjs +0 -57378
  118. package/dist/chunk-SPDELRRV.cjs +0 -319
  119. package/dist/chunk-SUCBB66F.js +0 -229
  120. package/dist/chunk-TSWTWZ42.cjs +0 -4214
  121. package/dist/chunk-UITWA6DV.cjs +0 -2829
  122. package/dist/chunk-UQW6SLM5.js +0 -172
  123. package/dist/chunk-V2ILLPHK.cjs +0 -237
  124. package/dist/chunk-V42NTCFH.js +0 -3645
  125. package/dist/chunk-VJ3L7CHT.cjs +0 -169
  126. package/dist/chunk-VJVCD5T5.cjs +0 -769
  127. package/dist/chunk-VK5AXKO3.js +0 -746
  128. package/dist/chunk-VSXOIUCF.cjs +0 -473
  129. package/dist/chunk-VZNKJZTT.cjs +0 -749
  130. package/dist/chunk-W76ETJTI.js +0 -678
  131. package/dist/chunk-WFJIDI2N.cjs +0 -424
  132. package/dist/chunk-WN3YF33G.cjs +0 -118
  133. package/dist/chunk-WUXIRGZP.cjs +0 -2915
  134. package/dist/chunk-WYH4GVZ5.js +0 -1488
  135. package/dist/chunk-X4YVN7H3.cjs +0 -888
  136. package/dist/chunk-X67XRI2T.js +0 -410
  137. package/dist/chunk-XDXZM3ZP.cjs +0 -1046
  138. package/dist/chunk-XH7SE4HH.cjs +0 -72
  139. package/dist/chunk-XNMEH2BI.js +0 -126
  140. package/dist/chunk-XSF76QRU.js +0 -468
  141. package/dist/chunk-XSUZMPVQ.cjs +0 -682
  142. package/dist/chunk-XUILI4KB.cjs +0 -430
  143. package/dist/chunk-Y5MUAYTO.js +0 -167
  144. package/dist/chunk-Y7VK5TH3.cjs +0 -129
  145. package/dist/chunk-YCMQQQ5U.cjs +0 -5244
  146. package/dist/chunk-YYXH45E2.js +0 -426
  147. package/dist/chunk-ZGTGVSTZ.js +0 -422
  148. package/dist/chunk-ZL6VJ6SN.js +0 -421
  149. package/dist/cli/holoscript-runner.cjs +0 -2258
  150. package/dist/cli/holoscript-runner.js +0 -2233
  151. package/dist/codebase/index.cjs +0 -336
  152. package/dist/codebase/index.js +0 -331
  153. package/dist/compiler/agent-inference.cjs +0 -29
  154. package/dist/compiler/agent-inference.js +0 -6
  155. package/dist/compiler/android-xr.cjs +0 -20
  156. package/dist/compiler/android-xr.js +0 -10
  157. package/dist/compiler/android.cjs +0 -21
  158. package/dist/compiler/android.js +0 -11
  159. package/dist/compiler/ar.cjs +0 -21
  160. package/dist/compiler/ar.js +0 -9
  161. package/dist/compiler/babylon.cjs +0 -18
  162. package/dist/compiler/babylon.js +0 -12
  163. package/dist/compiler/coco.cjs +0 -27
  164. package/dist/compiler/coco.js +0 -4
  165. package/dist/compiler/domain-block-utils.cjs +0 -595
  166. package/dist/compiler/domain-block-utils.js +0 -9
  167. package/dist/compiler/dtdl.cjs +0 -25
  168. package/dist/compiler/dtdl.js +0 -9
  169. package/dist/compiler/gltf-pipeline.cjs +0 -39
  170. package/dist/compiler/gltf-pipeline.js +0 -4
  171. package/dist/compiler/godot.cjs +0 -16
  172. package/dist/compiler/godot.js +0 -10
  173. package/dist/compiler/incremental.cjs +0 -30
  174. package/dist/compiler/incremental.js +0 -5
  175. package/dist/compiler/index.cjs +0 -1522
  176. package/dist/compiler/index.js +0 -1321
  177. package/dist/compiler/ios.cjs +0 -21
  178. package/dist/compiler/ios.js +0 -11
  179. package/dist/compiler/multi-layer.cjs +0 -27
  180. package/dist/compiler/multi-layer.js +0 -12
  181. package/dist/compiler/nodetoy.cjs +0 -23
  182. package/dist/compiler/nodetoy.js +0 -4
  183. package/dist/compiler/openxr.cjs +0 -16
  184. package/dist/compiler/openxr.js +0 -10
  185. package/dist/compiler/playcanvas.cjs +0 -17
  186. package/dist/compiler/playcanvas.js +0 -11
  187. package/dist/compiler/r3f.cjs +0 -35
  188. package/dist/compiler/r3f.js +0 -13
  189. package/dist/compiler/remotion.cjs +0 -23
  190. package/dist/compiler/remotion.js +0 -4
  191. package/dist/compiler/reproducibility.cjs +0 -39
  192. package/dist/compiler/reproducibility.js +0 -4
  193. package/dist/compiler/sdf.cjs +0 -22
  194. package/dist/compiler/sdf.js +0 -10
  195. package/dist/compiler/semantic-scene.cjs +0 -31
  196. package/dist/compiler/semantic-scene.js +0 -4
  197. package/dist/compiler/state.cjs +0 -18
  198. package/dist/compiler/state.js +0 -8
  199. package/dist/compiler/trait-composition.cjs +0 -27
  200. package/dist/compiler/trait-composition.js +0 -6
  201. package/dist/compiler/unity.cjs +0 -16
  202. package/dist/compiler/unity.js +0 -10
  203. package/dist/compiler/unreal.cjs +0 -20
  204. package/dist/compiler/unreal.js +0 -10
  205. package/dist/compiler/urdf.cjs +0 -46
  206. package/dist/compiler/urdf.js +0 -10
  207. package/dist/compiler/usd-physics.cjs +0 -24
  208. package/dist/compiler/usd-physics.js +0 -7
  209. package/dist/compiler/visionos.cjs +0 -16
  210. package/dist/compiler/visionos.js +0 -10
  211. package/dist/compiler/vrchat.cjs +0 -20
  212. package/dist/compiler/vrchat.js +0 -10
  213. package/dist/compiler/vrr.cjs +0 -22
  214. package/dist/compiler/vrr.js +0 -10
  215. package/dist/compiler/wasm.cjs +0 -34
  216. package/dist/compiler/wasm.js +0 -10
  217. package/dist/compiler/webgpu.cjs +0 -16
  218. package/dist/compiler/webgpu.js +0 -10
  219. package/dist/debugger.cjs +0 -21
  220. package/dist/debugger.js +0 -8
  221. package/dist/entries/interop.cjs +0 -62
  222. package/dist/entries/interop.js +0 -5
  223. package/dist/entries/scripting.cjs +0 -67
  224. package/dist/entries/scripting.js +0 -10
  225. package/dist/index.cjs +0 -61703
  226. package/dist/index.js +0 -58652
  227. package/dist/math/vec3.cjs +0 -57
  228. package/dist/math/vec3.js +0 -4
  229. package/dist/parser.cjs +0 -26
  230. package/dist/parser.js +0 -9
  231. package/dist/playwright-7DTEQCBD.cjs +0 -98673
  232. package/dist/playwright-BIZXMLD2.js +0 -98660
  233. package/dist/post-quantum-JTTAAGO3.cjs +0 -6
  234. package/dist/post-quantum-RVPVDEPI.js +0 -4
  235. package/dist/runtime.cjs +0 -16
  236. package/dist/runtime.js +0 -7
  237. package/dist/storage/index.cjs +0 -37
  238. package/dist/storage/index.js +0 -4
  239. package/dist/traits/index.cjs +0 -14331
  240. package/dist/traits/index.js +0 -12126
  241. package/dist/type-checker.cjs +0 -18
  242. package/dist/type-checker.js +0 -5
  243. package/dist/wot/index.cjs +0 -29
  244. package/dist/wot/index.js +0 -4
@@ -1,1857 +0,0 @@
1
- import { init_DomainBlockCompilerMixin, compileDomainBlocks, compileWeatherBlock, weatherToAndroidXR, compileAudioSourceBlock, audioSourceToAndroidXR, compilePostProcessingBlock, compileParticleBlock, particlesToAndroidXR, compilePhysicsBlock, physicsToAndroidXR, compileMaterialBlock, materialToAndroidXR } from './chunk-KDB6BUMB.js';
2
- import { init_CompilerBase, init_ANSNamespace, CompilerBase, ANSCapabilityPath } from './chunk-QWKUKVRE.js';
3
- import { __export, __esm } from './chunk-32TWR3HE.js';
4
-
5
- // src/compiler/AndroidXRCompiler.ts
6
- var AndroidXRCompiler_exports = {};
7
- __export(AndroidXRCompiler_exports, {
8
- AndroidXRCompiler: () => AndroidXRCompiler,
9
- compileToAndroidXR: () => compileToAndroidXR
10
- });
11
- function compileToAndroidXR(composition, options) {
12
- const compiler = new AndroidXRCompiler(options);
13
- return compiler.compile(composition, "");
14
- }
15
- var AndroidXRCompiler;
16
- var init_AndroidXRCompiler = __esm({
17
- "src/compiler/AndroidXRCompiler.ts"() {
18
- init_CompilerBase();
19
- init_ANSNamespace();
20
- init_DomainBlockCompilerMixin();
21
- AndroidXRCompiler = class extends CompilerBase {
22
- constructor(options = {}) {
23
- super();
24
- this.compilerName = "AndroidXRCompiler";
25
- this.lines = [];
26
- this.indentLevel = 0;
27
- this.options = {
28
- packageName: options.packageName || "com.holoscript.generated",
29
- activityName: options.activityName || "GeneratedXRActivity",
30
- useFilament: options.useFilament ?? true,
31
- useARCore: options.useARCore ?? true,
32
- indent: options.indent || " ",
33
- minSdk: options.minSdk || 30,
34
- targetSdk: options.targetSdk || 35,
35
- formFactor: options.formFactor || "headset"
36
- };
37
- }
38
- getRequiredCapability() {
39
- return ANSCapabilityPath.ANDROID_XR;
40
- }
41
- /** Returns true when targeting AI glasses form factor. */
42
- get isGlassesMode() {
43
- return this.options.formFactor === "glasses";
44
- }
45
- compile(composition, agentToken, outputPath) {
46
- this.validateCompilerAccess(agentToken, outputPath);
47
- if (this.isGlassesMode) {
48
- return {
49
- activityFile: this.generateGlassesActivityFile(composition),
50
- stateFile: this.generateStateFile(composition),
51
- nodeFactoryFile: this.generateNodeFactoryFile(composition),
52
- manifestFile: this.generateGlassesManifestFile(composition),
53
- buildGradle: this.generateGlassesBuildGradle(composition),
54
- glimmerComponentsFile: this.generateGlimmerComponentsFile(composition)
55
- };
56
- }
57
- return {
58
- activityFile: this.generateActivityFile(composition),
59
- stateFile: this.generateStateFile(composition),
60
- nodeFactoryFile: this.generateNodeFactoryFile(composition),
61
- manifestFile: this.generateManifestFile(composition),
62
- buildGradle: this.generateBuildGradle(composition)
63
- };
64
- }
65
- generateActivityFile(composition) {
66
- this.lines = [];
67
- this.indentLevel = 0;
68
- this.emit("// Auto-generated by HoloScript AndroidXRCompiler");
69
- this.emit(
70
- `// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`
71
- );
72
- this.emit("// Do not edit manually -- regenerate from .holo source");
73
- this.emit("");
74
- this.emit(`package ${this.options.packageName}`);
75
- this.emit("");
76
- this.emitImports(composition);
77
- this.emit("");
78
- this.emitActivityClass(composition);
79
- this.compileAndroidXRDomainBlocks(composition);
80
- return this.lines.join("\n");
81
- }
82
- // ─── State File ─────────────────────────────────────────────────────
83
- generateStateFile(composition) {
84
- this.lines = [];
85
- this.indentLevel = 0;
86
- const pkg = this.options.packageName;
87
- this.emit("// Auto-generated by HoloScript AndroidXRCompiler");
88
- this.emit(`// State: ${this.escapeStringValue(composition.name, "Kotlin")}`);
89
- this.emit("");
90
- this.emit(`package ${pkg}`);
91
- this.emit("");
92
- this.emit("import androidx.compose.runtime.mutableStateOf");
93
- this.emit("import androidx.compose.runtime.getValue");
94
- this.emit("import androidx.compose.runtime.setValue");
95
- this.emit("import androidx.lifecycle.ViewModel");
96
- this.emit("import androidx.lifecycle.MutableLiveData");
97
- this.emit("import androidx.lifecycle.LiveData");
98
- this.emit("import androidx.xr.runtime.math.Vector3");
99
- this.emit("");
100
- this.emit("class XRSceneState : ViewModel() {");
101
- this.indent();
102
- if (composition.state) {
103
- this.emit("// === State Properties ===");
104
- for (const prop of composition.state.properties) {
105
- const kotlinType = this.toKotlinType(prop.value);
106
- const kotlinValue = this.toKotlinValue(prop.value);
107
- this.emit(
108
- `private val _${this.escapeStringValue(prop.key, "Kotlin")} = MutableLiveData(${kotlinValue})`
109
- );
110
- this.emit(
111
- `val ${this.escapeStringValue(prop.key, "Kotlin")}: LiveData<${kotlinType}> get() = _${this.escapeStringValue(prop.key, "Kotlin")}`
112
- );
113
- this.emit("");
114
- }
115
- }
116
- this.emit("// === XR State ===");
117
- this.emit("private val _spatialCapabilities = MutableLiveData<Set<String>>(emptySet())");
118
- this.emit("val spatialCapabilities: LiveData<Set<String>> get() = _spatialCapabilities");
119
- this.emit("");
120
- this.emit(
121
- "private val _activeEntities = MutableLiveData<MutableMap<String, Any>>(mutableMapOf())"
122
- );
123
- this.emit("val activeEntities: LiveData<MutableMap<String, Any>> get() = _activeEntities");
124
- this.emit("");
125
- this.emit("private val _selectedEntity = MutableLiveData<String?>()");
126
- this.emit("val selectedEntity: LiveData<String?> get() = _selectedEntity");
127
- this.emit("");
128
- this.emit("fun onEntitySelected(entityId: String) {");
129
- this.indent();
130
- this.emit("_selectedEntity.value = entityId");
131
- this.emit('android.util.Log.d("HoloScript", "Entity selected: $entityId")');
132
- this.dedent();
133
- this.emit("}");
134
- this.emit("");
135
- this.emit("fun updateSpatialCapabilities(capabilities: Set<String>) {");
136
- this.indent();
137
- this.emit("_spatialCapabilities.value = capabilities");
138
- this.dedent();
139
- this.emit("}");
140
- this.emit("");
141
- this.emit("fun reset() {");
142
- this.indent();
143
- this.emit("_spatialCapabilities.value = emptySet()");
144
- this.emit("_activeEntities.value = mutableMapOf()");
145
- this.emit("_selectedEntity.value = null");
146
- if (composition.state) {
147
- for (const prop of composition.state.properties) {
148
- const kotlinValue = this.toKotlinValue(prop.value);
149
- this.emit(
150
- `_${this.escapeStringValue(prop.key, "Kotlin")}.value = ${kotlinValue}`
151
- );
152
- }
153
- }
154
- this.emit('android.util.Log.d("HoloScript", "XR State reset")');
155
- this.dedent();
156
- this.emit("}");
157
- if (composition.logic?.actions) {
158
- this.emit("");
159
- this.emit("// === Actions ===");
160
- for (const action of composition.logic.actions) {
161
- this.compileAction(action);
162
- }
163
- }
164
- this.dedent();
165
- this.emit("}");
166
- return this.lines.join("\n");
167
- }
168
- compileAction(action) {
169
- const rawName = this.sanitizeName(action.name);
170
- const name = rawName.charAt(0).toLowerCase() + rawName.slice(1);
171
- this.emit(`fun ${name}() {`);
172
- this.indent();
173
- this.emit(
174
- `android.util.Log.d("HoloScript", "Action: ${this.escapeStringValue(action.name, "Kotlin")}")`
175
- );
176
- this.emit("// Action implementation");
177
- this.dedent();
178
- this.emit("}");
179
- this.emit("");
180
- }
181
- // ─── Node Factory File ──────────────────────────────────────────────
182
- generateNodeFactoryFile(composition) {
183
- this.lines = [];
184
- this.indentLevel = 0;
185
- const pkg = this.options.packageName;
186
- this.emit("// Auto-generated by HoloScript AndroidXRCompiler");
187
- this.emit(
188
- `// Node Factory: ${this.escapeStringValue(composition.name, "Kotlin")}`
189
- );
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 = compileDomainBlocks(
1085
- // @ts-expect-error During migration
1086
- domainBlocks,
1087
- {
1088
- material: (block) => {
1089
- const mat = compileMaterialBlock(block);
1090
- return materialToAndroidXR(mat, `db${blockIdx++}`);
1091
- },
1092
- physics: (block) => {
1093
- const phys = compilePhysicsBlock(block);
1094
- return physicsToAndroidXR(phys, `db${blockIdx++}`);
1095
- },
1096
- vfx: (block) => {
1097
- const ps = compileParticleBlock(block);
1098
- return particlesToAndroidXR(ps, `db${blockIdx++}`);
1099
- },
1100
- postfx: (block) => {
1101
- const pp = 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 = compileAudioSourceBlock(block);
1109
- return audioSourceToAndroidXR(audio, `db${blockIdx++}`);
1110
- },
1111
- weather: (block) => {
1112
- const weather = compileWeatherBlock(block);
1113
- return 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
- export { AndroidXRCompiler, AndroidXRCompiler_exports, compileToAndroidXR, init_AndroidXRCompiler };
1856
- //# sourceMappingURL=chunk-MWEFR6YQ.js.map
1857
- //# sourceMappingURL=chunk-MWEFR6YQ.js.map