@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,2911 +0,0 @@
1
- import { init_CompilerBase, init_ANSNamespace, CompilerBase, ANSCapabilityPath } from './chunk-QWKUKVRE.js';
2
- import { init_geospatial, init_depth_scanner, init_spatial_authoring, init_haptic_feedback, init_nearby_connections, init_foldable_display, init_samsung_dex, init_google_lens, init_webxr, GEOSPATIAL_DEFAULTS, DEPTH_SCANNER_TRAITS, DEPTH_SCANNER_DEFAULTS, SPATIAL_AUTHORING_TRAITS, SPATIAL_AUTHORING_DEFAULTS, HAPTIC_FEEDBACK_TRAITS, NEARBY_CONNECTIONS_TRAITS, FOLDABLE_DISPLAY_TRAITS, SAMSUNG_DEX_TRAITS, GOOGLE_LENS_TRAITS, WEBXR_TRAITS } from './chunk-26TLYBFD.js';
3
- import { init_portal_ar, init_camera_hand_tracking, init_npu_scene, NPU_SCENE_TRAITS, NPU_SCENE_DEFAULTS, PORTAL_AR_TRAITS, CAMERA_HAND_TRACKING_TRAITS } from './chunk-XNMEH2BI.js';
4
- import { __export, __esm } from './chunk-32TWR3HE.js';
5
-
6
- // src/compiler/AndroidCompiler.ts
7
- var AndroidCompiler_exports = {};
8
- __export(AndroidCompiler_exports, {
9
- AndroidCompiler: () => AndroidCompiler,
10
- compileToAndroid: () => compileToAndroid
11
- });
12
- function compileToAndroid(composition, options) {
13
- const compiler = new AndroidCompiler(options);
14
- return Promise.resolve(compiler.compile(composition, "test-token", void 0));
15
- }
16
- var AndroidCompiler;
17
- var init_AndroidCompiler = __esm({
18
- "src/compiler/AndroidCompiler.ts"() {
19
- init_CompilerBase();
20
- init_ANSNamespace();
21
- init_geospatial();
22
- init_depth_scanner();
23
- init_portal_ar();
24
- init_camera_hand_tracking();
25
- init_npu_scene();
26
- init_spatial_authoring();
27
- init_haptic_feedback();
28
- init_nearby_connections();
29
- init_foldable_display();
30
- init_samsung_dex();
31
- init_google_lens();
32
- init_webxr();
33
- AndroidCompiler = class extends CompilerBase {
34
- constructor(options = {}) {
35
- super();
36
- this.compilerName = "AndroidCompiler";
37
- this.lines = [];
38
- this.indentLevel = 0;
39
- this.options = {
40
- packageName: options.packageName || "com.holoscript.generated",
41
- className: options.className || "GeneratedARScene",
42
- indent: options.indent || " ",
43
- minSdk: options.minSdk || 26,
44
- targetSdk: options.targetSdk || 34,
45
- useJetpackCompose: options.useJetpackCompose ?? true,
46
- useSceneform: options.useSceneform ?? true,
47
- useFilament: options.useFilament ?? false
48
- };
49
- }
50
- getRequiredCapability() {
51
- return ANSCapabilityPath.ANDROID;
52
- }
53
- compile(composition, agentToken, outputPath) {
54
- this.validateCompilerAccess(agentToken, outputPath);
55
- const result = {
56
- activityFile: this.generateActivityFile(composition),
57
- stateFile: this.generateStateFile(composition),
58
- nodeFactoryFile: this.generateNodeFactoryFile(composition),
59
- manifestFile: this.generateManifestFile(composition),
60
- buildGradle: this.generateBuildGradle(composition)
61
- };
62
- if (this.hasNPUSceneTraits(composition)) {
63
- result.npuSceneSetup = this.emitNPUSceneSetup(composition);
64
- }
65
- if (this.hasAuthoringTraits(composition)) {
66
- result.authoringSetup = this.emitAuthoringSetup(composition);
67
- }
68
- if (this.hasHapticTraits(composition)) {
69
- result.hapticSetup = this.emitHapticSetup(composition);
70
- }
71
- if (this.hasNearbyTraits(composition)) {
72
- result.nearbySetup = this.emitNearbySetup(composition);
73
- }
74
- if (this.hasFoldableTraits(composition)) {
75
- result.foldableSetup = this.emitFoldableSetup(composition);
76
- }
77
- if (this.hasDexTraits(composition)) {
78
- result.dexSetup = this.emitDexSetup(composition);
79
- }
80
- if (this.hasLensTraits(composition)) {
81
- result.lensSetup = this.emitLensSetup(composition);
82
- }
83
- if (this.hasWebXRTraits(composition)) {
84
- result.webxrSetup = this.emitWebXRSetup(composition);
85
- }
86
- return result;
87
- }
88
- generateActivityFile(composition) {
89
- this.lines = [];
90
- this.indentLevel = 0;
91
- const pkg = this.options.packageName;
92
- const cls = this.options.className;
93
- this.emit("// Auto-generated by HoloScript AndroidCompiler");
94
- this.emit(
95
- `// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`
96
- );
97
- this.emit("// Do not edit manually \u2014 regenerate from .holo source");
98
- this.emit("");
99
- this.emit(`package ${pkg}`);
100
- this.emit("");
101
- this.emit("import android.os.Bundle");
102
- this.emit("import android.view.MotionEvent");
103
- this.emit("import android.view.View");
104
- this.emit("import android.widget.Toast");
105
- this.emit("import androidx.appcompat.app.AppCompatActivity");
106
- this.emit("import androidx.lifecycle.ViewModelProvider");
107
- this.emit("import com.google.ar.core.*");
108
- this.emit("import com.google.ar.core.exceptions.*");
109
- this.emit("import com.google.ar.sceneform.*");
110
- this.emit("import com.google.ar.sceneform.math.Vector3");
111
- this.emit("import com.google.ar.sceneform.rendering.*");
112
- this.emit("import com.google.ar.sceneform.ux.*");
113
- this.emit("import java.util.concurrent.CompletableFuture");
114
- this.emit("");
115
- this.emit(`class ${cls}Activity : AppCompatActivity() {`);
116
- this.indentLevel++;
117
- this.emit("private lateinit var arFragment: ArFragment");
118
- this.emit("private lateinit var sceneState: SceneState");
119
- this.emit("private val placedNodes = mutableMapOf<String, TransformableNode>()");
120
- this.emit("");
121
- this.emit("override fun onCreate(savedInstanceState: Bundle?) {");
122
- this.indentLevel++;
123
- this.emit("super.onCreate(savedInstanceState)");
124
- this.emit(`setContentView(R.layout.activity_ar_scene)`);
125
- this.emit("");
126
- this.emit("sceneState = ViewModelProvider(this)[SceneState::class.java]");
127
- this.emit(
128
- "arFragment = supportFragmentManager.findFragmentById(R.id.ar_fragment) as ArFragment"
129
- );
130
- this.emit("");
131
- this.emit("setupARSession()");
132
- this.emit("setupTapListener()");
133
- this.emit("setupUI()");
134
- if (this.hasGeoTraits(composition)) {
135
- this.emit("setupGeoAnchors()");
136
- }
137
- if (this.hasGeospatialVPSTraits(composition)) {
138
- this.emit("setupGeospatialVPS()");
139
- }
140
- if (this.hasDepthScanTraits(composition)) {
141
- this.emit("setupDepthScanner()");
142
- }
143
- if (this.hasPortalARTraits(composition)) {
144
- this.emit("setupPortalAR()");
145
- }
146
- if (this.hasHandTrackingTraits(composition)) {
147
- this.emit("setupHandTracking()");
148
- }
149
- if (this.hasAuthoringTraits(composition)) {
150
- this.emit("setupSpatialAuthoring()");
151
- }
152
- this.indentLevel--;
153
- this.emit("}");
154
- this.emit("");
155
- this.emit("private fun setupARSession() {");
156
- this.indentLevel++;
157
- this.emit("arFragment.arSceneView.scene.addOnUpdateListener { frameTime ->");
158
- this.indentLevel++;
159
- this.emit("val frame = arFragment.arSceneView.arFrame ?: return@addOnUpdateListener");
160
- this.emit("");
161
- this.emit("// Track planes");
162
- this.emit("for (plane in frame.getUpdatedTrackables(Plane::class.java)) {");
163
- this.indentLevel++;
164
- this.emit("if (plane.trackingState == TrackingState.TRACKING) {");
165
- this.indentLevel++;
166
- this.emit("sceneState.onPlaneDetected(plane)");
167
- this.indentLevel--;
168
- this.emit("}");
169
- this.indentLevel--;
170
- this.emit("}");
171
- this.indentLevel--;
172
- this.emit("}");
173
- this.indentLevel--;
174
- this.emit("}");
175
- this.emit("");
176
- this.emit("private fun setupTapListener() {");
177
- this.indentLevel++;
178
- this.emit("arFragment.setOnTapArPlaneListener { hitResult, plane, motionEvent ->");
179
- this.indentLevel++;
180
- this.emit(
181
- "if (plane.type != Plane.Type.HORIZONTAL_UPWARD_FACING) return@setOnTapArPlaneListener"
182
- );
183
- this.emit("");
184
- this.emit("placeObject(hitResult)");
185
- this.indentLevel--;
186
- this.emit("}");
187
- this.indentLevel--;
188
- this.emit("}");
189
- this.emit("");
190
- this.emit("private fun placeObject(hitResult: HitResult) {");
191
- this.indentLevel++;
192
- this.emit("val anchor = hitResult.createAnchor()");
193
- this.emit("val anchorNode = AnchorNode(anchor)");
194
- this.emit("anchorNode.setParent(arFragment.arSceneView.scene)");
195
- this.emit("");
196
- this.emit("// Create node from factory");
197
- this.emit("NodeFactory.createDefaultNode(this) { renderable ->");
198
- this.indentLevel++;
199
- this.emit("val transformableNode = TransformableNode(arFragment.transformationSystem)");
200
- this.emit("transformableNode.setParent(anchorNode)");
201
- this.emit("transformableNode.renderable = renderable");
202
- this.emit("transformableNode.select()");
203
- this.emit("");
204
- this.emit("val id = java.util.UUID.randomUUID().toString()");
205
- this.emit("placedNodes[id] = transformableNode");
206
- this.emit("");
207
- this.emit("// Setup interaction");
208
- this.emit("transformableNode.setOnTapListener { _, _ ->");
209
- this.indentLevel++;
210
- this.emit("sceneState.onNodeTapped(id)");
211
- this.emit("animateNodeTap(transformableNode)");
212
- this.indentLevel--;
213
- this.emit("}");
214
- this.emit("");
215
- this.emit('android.util.Log.d("HoloScript", "Placed object: $id")');
216
- this.indentLevel--;
217
- this.emit("}");
218
- this.indentLevel--;
219
- this.emit("}");
220
- this.emit("");
221
- this.emit("private fun animateNodeTap(node: TransformableNode) {");
222
- this.indentLevel++;
223
- this.emit("val originalScale = node.localScale");
224
- this.emit(
225
- "val scaledUp = Vector3(originalScale.x * 1.2f, originalScale.y * 1.2f, originalScale.z * 1.2f)"
226
- );
227
- this.emit("");
228
- this.emit("android.animation.ObjectAnimator.ofObject(");
229
- this.indentLevel++;
230
- this.emit('node, "localScale",');
231
- this.emit("com.google.ar.sceneform.math.Vector3Evaluator(),");
232
- this.emit("originalScale, scaledUp, originalScale");
233
- this.indentLevel--;
234
- this.emit(").apply {");
235
- this.indentLevel++;
236
- this.emit("duration = 200");
237
- this.emit("start()");
238
- this.indentLevel--;
239
- this.emit("}");
240
- this.indentLevel--;
241
- this.emit("}");
242
- this.emit("");
243
- this.emit("private fun setupUI() {");
244
- this.indentLevel++;
245
- this.emit("findViewById<View>(R.id.reset_button)?.setOnClickListener {");
246
- this.indentLevel++;
247
- this.emit("resetScene()");
248
- this.indentLevel--;
249
- this.emit("}");
250
- this.indentLevel--;
251
- this.emit("}");
252
- this.emit("");
253
- this.emit("private fun resetScene() {");
254
- this.indentLevel++;
255
- this.emit("for (node in placedNodes.values) {");
256
- this.indentLevel++;
257
- this.emit("node.anchor?.detach()");
258
- this.emit("node.setParent(null)");
259
- this.indentLevel--;
260
- this.emit("}");
261
- this.emit("placedNodes.clear()");
262
- this.emit("");
263
- this.emit("sceneState.reset()");
264
- this.emit('Toast.makeText(this, "Scene reset", Toast.LENGTH_SHORT).show()');
265
- this.indentLevel--;
266
- this.emit("}");
267
- this.emit("");
268
- this.emit("override fun onResume() {");
269
- this.indentLevel++;
270
- this.emit("super.onResume()");
271
- this.emit("checkARCoreAvailability()");
272
- this.indentLevel--;
273
- this.emit("}");
274
- this.emit("");
275
- this.emit("private fun checkARCoreAvailability() {");
276
- this.indentLevel++;
277
- this.emit("val availability = ArCoreApk.getInstance().checkAvailability(this)");
278
- this.emit("if (availability.isTransient) {");
279
- this.indentLevel++;
280
- this.emit("android.os.Handler(mainLooper).postDelayed({ checkARCoreAvailability() }, 200)");
281
- this.emit("return");
282
- this.indentLevel--;
283
- this.emit("}");
284
- this.emit("if (!availability.isSupported) {");
285
- this.indentLevel++;
286
- this.emit(
287
- 'Toast.makeText(this, "ARCore is not supported on this device", Toast.LENGTH_LONG).show()'
288
- );
289
- this.emit("finish()");
290
- this.indentLevel--;
291
- this.emit("}");
292
- this.indentLevel--;
293
- this.emit("}");
294
- if (this.hasGeoTraits(composition)) {
295
- this.emitGeoAnchorSetup(composition);
296
- }
297
- if (this.hasGeospatialVPSTraits(composition)) {
298
- this.emitGeospatialVPSSetup(composition);
299
- }
300
- if (this.hasDepthScanTraits(composition)) {
301
- this.emitDepthScanSetup(composition);
302
- }
303
- if (this.hasPortalARTraits(composition)) {
304
- this.emitPortalARSetup(composition);
305
- }
306
- if (this.hasHandTrackingTraits(composition)) {
307
- this.emitHandTrackingSetup(composition);
308
- }
309
- if (this.hasAuthoringTraits(composition)) {
310
- this.emitAuthoringInlineSetup(composition);
311
- }
312
- this.indentLevel--;
313
- this.emit("}");
314
- return this.lines.join("\n");
315
- }
316
- generateStateFile(composition) {
317
- this.lines = [];
318
- this.indentLevel = 0;
319
- const pkg = this.options.packageName;
320
- this.emit("// Auto-generated by HoloScript AndroidCompiler");
321
- this.emit(`// State: ${this.escapeStringValue(composition.name, "Kotlin")}`);
322
- this.emit("");
323
- this.emit(`package ${pkg}`);
324
- this.emit("");
325
- this.emit("import androidx.lifecycle.ViewModel");
326
- this.emit("import androidx.lifecycle.MutableLiveData");
327
- this.emit("import androidx.lifecycle.LiveData");
328
- this.emit("import com.google.ar.core.Plane");
329
- this.emit("");
330
- this.emit("class SceneState : ViewModel() {");
331
- this.indentLevel++;
332
- if (composition.state) {
333
- this.emit("// === State Properties ===");
334
- for (const prop of composition.state.properties) {
335
- const kotlinType = this.toKotlinType(prop.value);
336
- const kotlinValue = this.toKotlinValue(prop.value);
337
- this.emit(
338
- `private val _${this.escapeStringValue(prop.key, "Kotlin")} = MutableLiveData(${kotlinValue})`
339
- );
340
- this.emit(
341
- `val ${this.escapeStringValue(prop.key, "Kotlin")}: LiveData<${kotlinType}> get() = _${this.escapeStringValue(prop.key, "Kotlin")}`
342
- );
343
- this.emit("");
344
- }
345
- }
346
- this.emit("// === AR State ===");
347
- this.emit("private val _detectedPlanes = MutableLiveData<List<Plane>>(emptyList())");
348
- this.emit("val detectedPlanes: LiveData<List<Plane>> get() = _detectedPlanes");
349
- this.emit("");
350
- this.emit("private val _tappedNodes = MutableLiveData<String?>()");
351
- this.emit("val tappedNodes: LiveData<String?> get() = _tappedNodes");
352
- this.emit("");
353
- this.emit("fun onPlaneDetected(plane: Plane) {");
354
- this.indentLevel++;
355
- this.emit("val current = _detectedPlanes.value.orEmpty().toMutableList()");
356
- this.emit("if (!current.contains(plane)) {");
357
- this.indentLevel++;
358
- this.emit("current.add(plane)");
359
- this.emit("_detectedPlanes.value = current");
360
- this.emit('android.util.Log.d("HoloScript", "Plane detected: ${plane.type}")');
361
- this.indentLevel--;
362
- this.emit("}");
363
- this.indentLevel--;
364
- this.emit("}");
365
- this.emit("");
366
- this.emit("fun onNodeTapped(nodeId: String) {");
367
- this.indentLevel++;
368
- this.emit("_tappedNodes.value = nodeId");
369
- this.emit('android.util.Log.d("HoloScript", "Node tapped: $nodeId")');
370
- this.indentLevel--;
371
- this.emit("}");
372
- this.emit("");
373
- this.emit("fun reset() {");
374
- this.indentLevel++;
375
- this.emit("_detectedPlanes.value = emptyList()");
376
- this.emit("_tappedNodes.value = null");
377
- if (composition.state) {
378
- for (const prop of composition.state.properties) {
379
- const kotlinValue = this.toKotlinValue(prop.value);
380
- this.emit(
381
- `_${this.escapeStringValue(prop.key, "Kotlin")}.value = ${kotlinValue}`
382
- );
383
- }
384
- }
385
- this.emit('android.util.Log.d("HoloScript", "State reset")');
386
- this.indentLevel--;
387
- this.emit("}");
388
- if (composition.logic?.actions) {
389
- this.emit("");
390
- this.emit("// === Actions ===");
391
- for (const action of composition.logic.actions) {
392
- this.compileAction(action);
393
- }
394
- }
395
- this.indentLevel--;
396
- this.emit("}");
397
- return this.lines.join("\n");
398
- }
399
- generateNodeFactoryFile(composition) {
400
- this.lines = [];
401
- this.indentLevel = 0;
402
- const pkg = this.options.packageName;
403
- this.emit("// Auto-generated by HoloScript AndroidCompiler");
404
- this.emit(
405
- `// Node Factory: ${this.escapeStringValue(composition.name, "Kotlin")}`
406
- );
407
- this.emit("");
408
- this.emit(`package ${pkg}`);
409
- this.emit("");
410
- this.emit("import android.content.Context");
411
- this.emit("import com.google.ar.sceneform.math.Vector3");
412
- this.emit("import com.google.ar.sceneform.rendering.*");
413
- this.emit("import com.google.android.filament.utils.Float3");
414
- this.emit("");
415
- this.emit("object NodeFactory {");
416
- this.indentLevel++;
417
- this.emit("fun createDefaultNode(context: Context, callback: (Renderable) -> Unit) {");
418
- this.indentLevel++;
419
- if (composition.objects?.length) {
420
- const firstObj = composition.objects[0];
421
- const color = this.findObjProp(firstObj, "color");
422
- this.emit("MaterialFactory.makeOpaqueWithColor(");
423
- this.indentLevel++;
424
- this.emit("context,");
425
- this.emit(`Color(${this.toAndroidColor(color)})`);
426
- this.indentLevel--;
427
- this.emit(").thenAccept { material ->");
428
- this.indentLevel++;
429
- const meshType = this.findObjProp(firstObj, "mesh") || this.findObjProp(firstObj, "type") || "cube";
430
- const geometry = this.getSceneformGeometry(meshType);
431
- this.emit(`${geometry}.thenAccept { renderable ->`);
432
- this.indentLevel++;
433
- this.emit("renderable.material = material");
434
- this.emit("callback(renderable)");
435
- this.indentLevel--;
436
- this.emit("}");
437
- this.indentLevel--;
438
- this.emit("}");
439
- } else {
440
- this.emit("MaterialFactory.makeOpaqueWithColor(context, Color(android.graphics.Color.BLUE))");
441
- this.emit(" .thenCompose { material ->");
442
- this.emit(
443
- " ShapeFactory.makeCube(Vector3(0.1f, 0.1f, 0.1f), Vector3.zero(), material)"
444
- );
445
- this.emit(" }");
446
- this.emit(" .thenAccept { renderable -> callback(renderable) }");
447
- }
448
- this.indentLevel--;
449
- this.emit("}");
450
- this.emit("");
451
- for (const obj of composition.objects || []) {
452
- this.compileObjectFactory(obj);
453
- }
454
- this.indentLevel--;
455
- this.emit("}");
456
- return this.lines.join("\n");
457
- }
458
- compileObjectFactory(obj) {
459
- const methodName = `create${this.sanitizeName(obj.name)}`;
460
- this.emit(`fun ${methodName}(context: Context, callback: (Renderable) -> Unit) {`);
461
- this.indentLevel++;
462
- const color = this.findObjProp(obj, "color");
463
- const meshType = this.findObjProp(obj, "mesh") || this.findObjProp(obj, "type") || "cube";
464
- this.emit("MaterialFactory.makeOpaqueWithColor(");
465
- this.indentLevel++;
466
- this.emit("context,");
467
- this.emit(`Color(${this.toAndroidColor(color)})`);
468
- this.indentLevel--;
469
- this.emit(").thenAccept { material ->");
470
- this.indentLevel++;
471
- const geometry = this.getSceneformGeometry(meshType);
472
- this.emit(`${geometry}.thenAccept { renderable ->`);
473
- this.indentLevel++;
474
- this.emit("renderable.material = material");
475
- this.emit("callback(renderable)");
476
- this.indentLevel--;
477
- this.emit("}");
478
- this.indentLevel--;
479
- this.emit("}");
480
- this.indentLevel--;
481
- this.emit("}");
482
- this.emit("");
483
- }
484
- generateManifestFile(composition) {
485
- const pkg = this.options.packageName;
486
- const cls = this.options.className;
487
- const hasGeo = this.hasGeoTraits(composition);
488
- const hasGeospatialVPS = this.hasGeospatialVPSTraits(composition);
489
- const geoPermissions = hasGeo || hasGeospatialVPS ? `
490
- <!-- Geo-Anchor: Location permissions -->
491
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
492
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />` : "";
493
- const geospatialApiKeyMeta = hasGeospatialVPS ? `
494
- <!-- ARCore Geospatial API key (replace with your key from Google Cloud Console) -->
495
- <meta-data android:name="com.google.android.ar.API_KEY" android:value="\${GEOSPATIAL_API_KEY}" />` : "";
496
- return `<?xml version="1.0" encoding="utf-8"?>
497
- <!-- Auto-generated by HoloScript AndroidCompiler -->
498
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
499
- package="${pkg}">
500
-
501
- <!-- AR Required -->
502
- <uses-permission android:name="android.permission.CAMERA" />
503
- <uses-feature android:name="android.hardware.camera.ar" android:required="true" />
504
-
505
- <!-- ARCore Required -->
506
- <uses-feature android:glEsVersion="0x00030000" android:required="true" />${geoPermissions}
507
-
508
- <application
509
- android:allowBackup="true"
510
- android:label="${this.escapeStringValue(composition.name, "XML")}"
511
- android:supportsRtl="true"
512
- android:theme="@style/Theme.AppCompat.NoActionBar">
513
-
514
- <!-- ARCore metadata -->
515
- <meta-data android:name="com.google.ar.core" android:value="required" />${geospatialApiKeyMeta}
516
-
517
- <activity
518
- android:name=".${cls}Activity"
519
- android:exported="true"
520
- android:configChanges="orientation|screenSize"
521
- android:screenOrientation="locked">
522
- <intent-filter>
523
- <action android:name="android.intent.action.MAIN" />
524
- <category android:name="android.intent.category.LAUNCHER" />
525
- </intent-filter>
526
- </activity>
527
- </application>
528
- </manifest>`;
529
- }
530
- generateBuildGradle(composition) {
531
- const hasGeospatialVPS = this.hasGeospatialVPSTraits(composition);
532
- const hasDepthScan = this.hasDepthScanTraits(composition);
533
- const hasHandTracking = this.hasHandTrackingTraits(composition);
534
- const geospatialDeps = hasGeospatialVPS ? `
535
- // ARCore Geospatial API (included in com.google.ar:core >= 1.31.0)
536
- // Location services for VPS
537
- implementation 'com.google.android.gms:play-services-location:21.1.0'` : "";
538
- const depthScanDeps = hasDepthScan ? `
539
- // ARCore Depth API (M.010.02b)
540
- implementation 'com.google.ar:core:1.40.0'` : "";
541
- const handTrackingDeps = hasHandTracking ? `
542
- // MediaPipe Hands \u2014 camera-based hand tracking (M.010.04)
543
- implementation 'com.google.mediapipe:solution-hands:0.10.14'
544
- // CameraX for front camera feed
545
- implementation 'androidx.camera:camera-core:1.3.1'
546
- implementation 'androidx.camera:camera-camera2:1.3.1'
547
- implementation 'androidx.camera:camera-lifecycle:1.3.1'` : "";
548
- const hasAuthoring = this.hasAuthoringTraits(composition);
549
- const authoringDeps = hasAuthoring ? `
550
- // Spatial Authoring \u2014 speech recognition (M.010.08)
551
- implementation 'androidx.recyclerview:recyclerview:1.3.2'` : "";
552
- return `// Auto-generated by HoloScript AndroidCompiler
553
- // Source: ${this.escapeStringValue(composition.name, "Kotlin")}
554
-
555
- plugins {
556
- id 'com.android.application'
557
- id 'org.jetbrains.kotlin.android'
558
- }
559
-
560
- android {
561
- namespace '${this.options.packageName}'
562
- compileSdk ${this.options.targetSdk}
563
-
564
- defaultConfig {
565
- applicationId "${this.options.packageName}"
566
- minSdk ${this.options.minSdk}
567
- targetSdk ${this.options.targetSdk}
568
- versionCode 1
569
- versionName "1.0"
570
- }
571
-
572
- buildTypes {
573
- release {
574
- minifyEnabled false
575
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
576
- }
577
- }
578
-
579
- compileOptions {
580
- sourceCompatibility JavaVersion.VERSION_17
581
- targetCompatibility JavaVersion.VERSION_17
582
- }
583
-
584
- kotlinOptions {
585
- jvmTarget = '17'
586
- }
587
-
588
- buildFeatures {
589
- viewBinding true
590
- compose ${this.options.useJetpackCompose}
591
- }
592
- }
593
-
594
- dependencies {
595
- implementation 'androidx.core:core-ktx:1.12.0'
596
- implementation 'androidx.appcompat:appcompat:1.6.1'
597
- implementation 'com.google.android.material:material:1.11.0'
598
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
599
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
600
-
601
- // ARCore (>= 1.31.0 includes Geospatial API)
602
- implementation 'com.google.ar:core:1.41.0'
603
-
604
- // Sceneform (maintained fork)
605
- implementation 'com.gorisse.thomas:sceneform:1.22.0'${geospatialDeps}${depthScanDeps}${handTrackingDeps}${authoringDeps}
606
-
607
- // Optional: Jetpack Compose
608
- ${this.options.useJetpackCompose ? `implementation platform('androidx.compose:compose-bom:2024.01.00')
609
- implementation 'androidx.compose.ui:ui'
610
- implementation 'androidx.compose.material3:material3'
611
- implementation 'androidx.activity:activity-compose:1.8.2'` : "// Compose disabled"}
612
- }`;
613
- }
614
- compileAction(action) {
615
- const rawName = this.sanitizeName(action.name);
616
- const name = rawName.charAt(0).toLowerCase() + rawName.slice(1);
617
- this.emit(`fun ${name}() {`);
618
- this.indentLevel++;
619
- this.emit(
620
- `android.util.Log.d("HoloScript", "Action: ${this.escapeStringValue(action.name, "Kotlin")}")`
621
- );
622
- this.emit("// Action implementation");
623
- this.indentLevel--;
624
- this.emit("}");
625
- this.emit("");
626
- }
627
- // === Geo-Anchor Methods ===
628
- hasGeoTraits(composition) {
629
- const geoTraitNames = [
630
- "geo_anchor",
631
- "geo_persist",
632
- "geo_radius",
633
- "geo_compass_heading",
634
- "geo_altitude",
635
- "geo_cloud_anchor",
636
- "geo_terrain_snap",
637
- "geo_session_continuity",
638
- "geo_proximity_trigger",
639
- "geo_discoverable",
640
- "geo_arcore_geospatial",
641
- "geo_arkit_geo_anchor",
642
- // ARCore Geospatial API traits (M.010.15)
643
- "geospatial_vps",
644
- "geospatial_anchor",
645
- "geospatial_terrain_anchor",
646
- "geospatial_rooftop_anchor",
647
- "geospatial_streetscape",
648
- "geospatial_heading"
649
- ];
650
- for (const obj of composition.objects || []) {
651
- for (const trait of obj.traits || []) {
652
- const name = typeof trait === "string" ? trait : trait.name;
653
- if (geoTraitNames.includes(name)) return true;
654
- }
655
- }
656
- return false;
657
- }
658
- /**
659
- * Check if any object uses the ARCore Geospatial API VPS traits specifically.
660
- */
661
- hasGeospatialVPSTraits(composition) {
662
- const geospatialTraitNames = [
663
- "geospatial_vps",
664
- "geospatial_anchor",
665
- "geospatial_terrain_anchor",
666
- "geospatial_rooftop_anchor",
667
- "geospatial_streetscape",
668
- "geospatial_heading"
669
- ];
670
- for (const obj of composition.objects || []) {
671
- for (const trait of obj.traits || []) {
672
- const name = typeof trait === "string" ? trait : trait.name;
673
- if (geospatialTraitNames.includes(name)) return true;
674
- }
675
- }
676
- return false;
677
- }
678
- emitGeoAnchorSetup(composition) {
679
- this.emit("");
680
- this.emit("// === Geo-Anchor: GPS-pinned persistent holograms ===");
681
- this.emit("private var fusedLocationClient: com.google.android.gms.location.FusedLocationProviderClient? = null");
682
- this.emit("private val geoAnchors = mutableMapOf<String, Anchor>()");
683
- this.emit("");
684
- this.emit("private fun setupGeoAnchors() {");
685
- this.indentLevel++;
686
- this.emit("fusedLocationClient = com.google.android.gms.location.LocationServices.getFusedLocationProviderClient(this)");
687
- this.emit("");
688
- const usesGeospatial = this.compositionHasTrait(composition, "geo_arcore_geospatial");
689
- if (usesGeospatial) {
690
- this.emit("// Enable ARCore Geospatial API (VPS)");
691
- this.emit("val session = arFragment.arSceneView.session ?: return");
692
- this.emit("val config = session.config");
693
- this.emit("config.geospatialMode = Config.GeospatialMode.ENABLED");
694
- this.emit("session.configure(config)");
695
- this.emit("");
696
- }
697
- for (const obj of composition.objects || []) {
698
- const traits = obj.traits || [];
699
- const hasGeoAnchor = traits.some((t) => {
700
- const name = typeof t === "string" ? t : t.name;
701
- return name === "geo_anchor";
702
- });
703
- if (!hasGeoAnchor) continue;
704
- const geoAnchorTrait = traits.find((t) => {
705
- const name = typeof t === "string" ? t : t.name;
706
- return name === "geo_anchor";
707
- });
708
- const config = typeof geoAnchorTrait === "string" ? {} : geoAnchorTrait?.config || {};
709
- const lat = config.latitude ?? 0;
710
- const lng = config.longitude ?? 0;
711
- const altTrait = traits.find((t) => {
712
- const name = typeof t === "string" ? t : t.name;
713
- return name === "geo_altitude";
714
- });
715
- const altConfig = typeof altTrait === "string" ? {} : altTrait?.config || {};
716
- const alt = altConfig.meters ?? 0;
717
- const headingTrait = traits.find((t) => {
718
- const name = typeof t === "string" ? t : t.name;
719
- return name === "geo_compass_heading";
720
- });
721
- const headingConfig = typeof headingTrait === "string" ? {} : headingTrait?.config || {};
722
- const heading = headingConfig.degrees ?? 0;
723
- this.emit(`// Geo-anchor: ${this.escapeStringValue(obj.name, "Kotlin")}`);
724
- this.emit(`createGeoAnchor("${this.escapeStringValue(obj.name, "Kotlin")}", ${lat}, ${lng}, ${alt}, ${heading}f)`);
725
- }
726
- const usesPersist = this.compositionHasTrait(composition, "geo_persist");
727
- const usesCloud = this.compositionHasTrait(composition, "geo_cloud_anchor");
728
- if (usesPersist || usesCloud) {
729
- this.emit("");
730
- this.emit("// Restore persisted geo anchors");
731
- this.emit("restoreGeoAnchors()");
732
- }
733
- this.indentLevel--;
734
- this.emit("}");
735
- this.emit("");
736
- this.emit("private fun createGeoAnchor(name: String, lat: Double, lng: Double, alt: Double, heading: Float) {");
737
- this.indentLevel++;
738
- this.emit("val session = arFragment.arSceneView.session ?: return");
739
- this.emit("val earth = session.earth ?: return");
740
- this.emit("if (earth.trackingState != TrackingState.TRACKING) return");
741
- this.emit("");
742
- this.emit("val anchor = earth.createAnchor(lat, lng, alt, 0f, 0f, Math.sin(Math.toRadians(heading.toDouble() / 2)).toFloat(), Math.cos(Math.toRadians(heading.toDouble() / 2)).toFloat())");
743
- this.emit("geoAnchors[name] = anchor");
744
- this.emit("");
745
- this.emit("val anchorNode = AnchorNode(anchor)");
746
- this.emit("anchorNode.setParent(arFragment.arSceneView.scene)");
747
- this.emit("");
748
- this.emit("NodeFactory.createDefaultNode(this) { renderable ->");
749
- this.indentLevel++;
750
- this.emit("val node = TransformableNode(arFragment.transformationSystem)");
751
- this.emit("node.setParent(anchorNode)");
752
- this.emit("node.renderable = renderable");
753
- this.emit("placedNodes[name] = node");
754
- this.emit('android.util.Log.d("HoloScript", "Geo-anchored: $name at ($lat, $lng, $alt)")');
755
- this.indentLevel--;
756
- this.emit("}");
757
- this.indentLevel--;
758
- this.emit("}");
759
- this.emit("");
760
- if (usesPersist || usesCloud) {
761
- this.emit("private fun saveGeoAnchorToCloud(name: String, anchor: Anchor) {");
762
- this.indentLevel++;
763
- this.emit("val session = arFragment.arSceneView.session ?: return");
764
- this.emit("session.hostCloudAnchorAsync(anchor, 365) { cloudId, state ->");
765
- this.indentLevel++;
766
- this.emit("if (state == Anchor.CloudAnchorState.SUCCESS && cloudId != null) {");
767
- this.indentLevel++;
768
- this.emit('val prefs = getSharedPreferences("geo_anchors", MODE_PRIVATE)');
769
- this.emit("prefs.edit().putString(name, cloudId).apply()");
770
- this.emit('android.util.Log.d("HoloScript", "Cloud anchor saved: $name -> $cloudId")');
771
- this.indentLevel--;
772
- this.emit("}");
773
- this.indentLevel--;
774
- this.emit("}");
775
- this.indentLevel--;
776
- this.emit("}");
777
- this.emit("");
778
- this.emit("private fun restoreGeoAnchors() {");
779
- this.indentLevel++;
780
- this.emit('val prefs = getSharedPreferences("geo_anchors", MODE_PRIVATE)');
781
- this.emit("val session = arFragment.arSceneView.session ?: return");
782
- this.emit("for ((name, cloudId) in prefs.all) {");
783
- this.indentLevel++;
784
- this.emit("val id = cloudId as? String ?: continue");
785
- this.emit("session.resolveCloudAnchorAsync(id) { anchor, state ->");
786
- this.indentLevel++;
787
- this.emit("if (state == Anchor.CloudAnchorState.SUCCESS && anchor != null) {");
788
- this.indentLevel++;
789
- this.emit("geoAnchors[name] = anchor");
790
- this.emit("val anchorNode = AnchorNode(anchor)");
791
- this.emit("anchorNode.setParent(arFragment.arSceneView.scene)");
792
- this.emit('android.util.Log.d("HoloScript", "Restored cloud anchor: $name")');
793
- this.indentLevel--;
794
- this.emit("}");
795
- this.indentLevel--;
796
- this.emit("}");
797
- this.indentLevel--;
798
- this.emit("}");
799
- this.indentLevel--;
800
- this.emit("}");
801
- this.emit("");
802
- }
803
- }
804
- compositionHasTrait(composition, traitName) {
805
- for (const obj of composition.objects || []) {
806
- for (const trait of obj.traits || []) {
807
- const name = typeof trait === "string" ? trait : trait.name;
808
- if (name === traitName) return true;
809
- }
810
- }
811
- return false;
812
- }
813
- // === ARCore Geospatial API (M.010.15) ===
814
- /**
815
- * Emit the full Geospatial VPS setup: session config, Earth tracking,
816
- * anchor creation (geo/terrain/rooftop), and Streetscape Geometry access.
817
- */
818
- emitGeospatialVPSSetup(composition) {
819
- this.emit("");
820
- this.emit("// === ARCore Geospatial API: VPS street-level geo-anchoring ===");
821
- this.emit("private var earth: Earth? = null");
822
- this.emit("private val geospatialAnchors = mutableMapOf<String, Anchor>()");
823
- this.emit("");
824
- this.emit("private fun setupGeospatialVPS() {");
825
- this.indentLevel++;
826
- this.emit("val session = arFragment.arSceneView.session ?: return");
827
- this.emit("val config = session.config");
828
- this.emit("config.geospatialMode = Config.GeospatialMode.ENABLED");
829
- const usesStreetscape = this.compositionHasTrait(composition, "geospatial_streetscape");
830
- if (usesStreetscape) {
831
- this.emit("config.streetscapeGeometryMode = Config.StreetscapeGeometryMode.ENABLED");
832
- }
833
- this.emit("session.configure(config)");
834
- this.emit("");
835
- this.emit("// Monitor geospatial tracking state");
836
- this.emit("arFragment.arSceneView.scene.addOnUpdateListener {");
837
- this.indentLevel++;
838
- this.emit("earth = session.earth");
839
- this.emit("val trackingState = earth?.trackingState ?: return@addOnUpdateListener");
840
- this.emit("if (trackingState != TrackingState.TRACKING) return@addOnUpdateListener");
841
- this.emit("");
842
- this.emit("val geospatialState = earth?.cameraGeospatialPose ?: return@addOnUpdateListener");
843
- this.emit(`val horizontalAccuracy = geospatialState.horizontalAccuracy`);
844
- this.emit(`val headingAccuracy = geospatialState.headingAccuracy`);
845
- this.emit("");
846
- this.emit(`if (horizontalAccuracy > ${GEOSPATIAL_DEFAULTS.vps.accuracyThreshold} || headingAccuracy > ${GEOSPATIAL_DEFAULTS.vps.headingAccuracyThreshold}) {`);
847
- this.indentLevel++;
848
- this.emit("// Accuracy not yet sufficient for anchoring");
849
- this.emit("return@addOnUpdateListener");
850
- this.indentLevel--;
851
- this.emit("}");
852
- this.emit("");
853
- this.emit('android.util.Log.d("HoloScript", "Geospatial tracking: accuracy=$horizontalAccuracy m, heading=$headingAccuracy deg")');
854
- if (usesStreetscape) {
855
- this.emit("");
856
- this.emit("// Process Streetscape Geometry meshes");
857
- this.emit("processStreetscapeGeometry(session)");
858
- }
859
- this.indentLevel--;
860
- this.emit("}");
861
- this.emit("");
862
- for (const obj of composition.objects || []) {
863
- const traits = obj.traits || [];
864
- for (const trait of traits) {
865
- const tName = typeof trait === "string" ? trait : trait.name;
866
- const tConfig = typeof trait === "string" ? {} : trait.config || {};
867
- if (tName === "geospatial_anchor") {
868
- const lat = tConfig.latitude ?? GEOSPATIAL_DEFAULTS.anchor.latitude;
869
- const lng = tConfig.longitude ?? GEOSPATIAL_DEFAULTS.anchor.longitude;
870
- const alt = tConfig.altitude ?? GEOSPATIAL_DEFAULTS.anchor.altitude;
871
- const heading = tConfig.heading ?? GEOSPATIAL_DEFAULTS.anchor.heading;
872
- this.emit(`createGeospatialAnchor("${this.escapeStringValue(obj.name, "Kotlin")}", ${lat}, ${lng}, ${alt}, ${heading}f)`);
873
- }
874
- if (tName === "geospatial_terrain_anchor") {
875
- const lat = tConfig.latitude ?? GEOSPATIAL_DEFAULTS.terrainAnchor.latitude;
876
- const lng = tConfig.longitude ?? GEOSPATIAL_DEFAULTS.terrainAnchor.longitude;
877
- const altOffset = tConfig.altitudeOffset ?? GEOSPATIAL_DEFAULTS.terrainAnchor.altitudeOffset;
878
- const heading = tConfig.heading ?? GEOSPATIAL_DEFAULTS.terrainAnchor.heading;
879
- this.emit(`resolveTerrainAnchor("${this.escapeStringValue(obj.name, "Kotlin")}", ${lat}, ${lng}, ${altOffset}, ${heading}f)`);
880
- }
881
- if (tName === "geospatial_rooftop_anchor") {
882
- const lat = tConfig.latitude ?? GEOSPATIAL_DEFAULTS.rooftopAnchor.latitude;
883
- const lng = tConfig.longitude ?? GEOSPATIAL_DEFAULTS.rooftopAnchor.longitude;
884
- const altOffset = tConfig.altitudeOffset ?? GEOSPATIAL_DEFAULTS.rooftopAnchor.altitudeOffset;
885
- const heading = tConfig.heading ?? GEOSPATIAL_DEFAULTS.rooftopAnchor.heading;
886
- this.emit(`resolveRooftopAnchor("${this.escapeStringValue(obj.name, "Kotlin")}", ${lat}, ${lng}, ${altOffset}, ${heading}f)`);
887
- }
888
- }
889
- }
890
- this.indentLevel--;
891
- this.emit("}");
892
- this.emit("");
893
- this.emit("private fun createGeospatialAnchor(name: String, lat: Double, lng: Double, alt: Double, heading: Float) {");
894
- this.indentLevel++;
895
- this.emit("val earthRef = earth ?: return");
896
- this.emit("if (earthRef.trackingState != TrackingState.TRACKING) return");
897
- this.emit("");
898
- this.emit("val anchor = earthRef.createAnchor(lat, lng, alt, 0f, 0f, Math.sin(Math.toRadians(heading.toDouble() / 2)).toFloat(), Math.cos(Math.toRadians(heading.toDouble() / 2)).toFloat())");
899
- this.emit("geospatialAnchors[name] = anchor");
900
- this.emit("");
901
- this.emit("val anchorNode = AnchorNode(anchor)");
902
- this.emit("anchorNode.setParent(arFragment.arSceneView.scene)");
903
- this.emit("");
904
- this.emit("NodeFactory.createDefaultNode(this) { renderable ->");
905
- this.indentLevel++;
906
- this.emit("val node = TransformableNode(arFragment.transformationSystem)");
907
- this.emit("node.setParent(anchorNode)");
908
- this.emit("node.renderable = renderable");
909
- this.emit("placedNodes[name] = node");
910
- this.emit('android.util.Log.d("HoloScript", "Geospatial anchor: $name at ($lat, $lng, $alt)")');
911
- this.indentLevel--;
912
- this.emit("}");
913
- this.indentLevel--;
914
- this.emit("}");
915
- this.emit("");
916
- if (this.compositionHasTrait(composition, "geospatial_terrain_anchor")) {
917
- this.emit("private fun resolveTerrainAnchor(name: String, lat: Double, lng: Double, altOffset: Double, heading: Float) {");
918
- this.indentLevel++;
919
- this.emit("val earthRef = earth ?: return");
920
- this.emit("if (earthRef.trackingState != TrackingState.TRACKING) return");
921
- this.emit("");
922
- this.emit("val qx = 0f");
923
- this.emit("val qy = Math.sin(Math.toRadians(heading.toDouble() / 2)).toFloat()");
924
- this.emit("val qz = 0f");
925
- this.emit("val qw = Math.cos(Math.toRadians(heading.toDouble() / 2)).toFloat()");
926
- this.emit("");
927
- this.emit("earthRef.resolveAnchorOnTerrainAsync(lat, lng, altOffset, qx, qy, qz, qw) { anchor, state ->");
928
- this.indentLevel++;
929
- this.emit("if (state == Anchor.TerrainAnchorState.SUCCESS && anchor != null) {");
930
- this.indentLevel++;
931
- this.emit("geospatialAnchors[name] = anchor");
932
- this.emit("val anchorNode = AnchorNode(anchor)");
933
- this.emit("anchorNode.setParent(arFragment.arSceneView.scene)");
934
- this.emit("");
935
- this.emit("NodeFactory.createDefaultNode(this@" + this.options.className + "Activity) { renderable ->");
936
- this.indentLevel++;
937
- this.emit("val node = TransformableNode(arFragment.transformationSystem)");
938
- this.emit("node.setParent(anchorNode)");
939
- this.emit("node.renderable = renderable");
940
- this.emit("placedNodes[name] = node");
941
- this.emit('android.util.Log.d("HoloScript", "Terrain anchor resolved: $name")');
942
- this.indentLevel--;
943
- this.emit("}");
944
- this.indentLevel--;
945
- this.emit("}");
946
- this.indentLevel--;
947
- this.emit("}");
948
- this.indentLevel--;
949
- this.emit("}");
950
- this.emit("");
951
- }
952
- if (this.compositionHasTrait(composition, "geospatial_rooftop_anchor")) {
953
- this.emit("private fun resolveRooftopAnchor(name: String, lat: Double, lng: Double, altOffset: Double, heading: Float) {");
954
- this.indentLevel++;
955
- this.emit("val earthRef = earth ?: return");
956
- this.emit("if (earthRef.trackingState != TrackingState.TRACKING) return");
957
- this.emit("");
958
- this.emit("val qx = 0f");
959
- this.emit("val qy = Math.sin(Math.toRadians(heading.toDouble() / 2)).toFloat()");
960
- this.emit("val qz = 0f");
961
- this.emit("val qw = Math.cos(Math.toRadians(heading.toDouble() / 2)).toFloat()");
962
- this.emit("");
963
- this.emit("earthRef.resolveAnchorOnRooftopAsync(lat, lng, altOffset, qx, qy, qz, qw) { anchor, state ->");
964
- this.indentLevel++;
965
- this.emit("if (state == Anchor.RooftopAnchorState.SUCCESS && anchor != null) {");
966
- this.indentLevel++;
967
- this.emit("geospatialAnchors[name] = anchor");
968
- this.emit("val anchorNode = AnchorNode(anchor)");
969
- this.emit("anchorNode.setParent(arFragment.arSceneView.scene)");
970
- this.emit("");
971
- this.emit("NodeFactory.createDefaultNode(this@" + this.options.className + "Activity) { renderable ->");
972
- this.indentLevel++;
973
- this.emit("val node = TransformableNode(arFragment.transformationSystem)");
974
- this.emit("node.setParent(anchorNode)");
975
- this.emit("node.renderable = renderable");
976
- this.emit("placedNodes[name] = node");
977
- this.emit('android.util.Log.d("HoloScript", "Rooftop anchor resolved: $name")');
978
- this.indentLevel--;
979
- this.emit("}");
980
- this.indentLevel--;
981
- this.emit("}");
982
- this.indentLevel--;
983
- this.emit("}");
984
- this.indentLevel--;
985
- this.emit("}");
986
- this.emit("");
987
- }
988
- if (usesStreetscape) {
989
- this.emit("private fun processStreetscapeGeometry(session: Session) {");
990
- this.indentLevel++;
991
- this.emit("val streetscapeGeometries = session.getAllTrackables(StreetscapeGeometry::class.java)");
992
- this.emit("for (geometry in streetscapeGeometries) {");
993
- this.indentLevel++;
994
- this.emit("if (geometry.trackingState != TrackingState.TRACKING) continue");
995
- this.emit("");
996
- this.emit("val mesh = geometry.meshes.firstOrNull() ?: continue");
997
- this.emit("val type = geometry.type");
998
- this.emit('android.util.Log.d("HoloScript", "Streetscape geometry: type=$type, vertices=${mesh.vertexList.size}")');
999
- this.emit("");
1000
- this.emit("// Streetscape mesh available for occlusion, raycasting, or rendering");
1001
- this.emit("// Type is TERRAIN or BUILDING");
1002
- this.indentLevel--;
1003
- this.emit("}");
1004
- this.indentLevel--;
1005
- this.emit("}");
1006
- this.emit("");
1007
- }
1008
- }
1009
- // === Depth Scanner Methods (M.010.02b) ===
1010
- /**
1011
- * Check if any object uses depth scanner traits.
1012
- */
1013
- hasDepthScanTraits(composition) {
1014
- for (const obj of composition.objects || []) {
1015
- for (const trait of obj.traits || []) {
1016
- const name = typeof trait === "string" ? trait : trait.name;
1017
- if (DEPTH_SCANNER_TRAITS.includes(name)) return true;
1018
- }
1019
- }
1020
- return false;
1021
- }
1022
- /**
1023
- * Emit Kotlin code for depth scanning: ARCore depth, ToF, stereo,
1024
- * mesh generation, .holo conversion, realtime streaming, and export.
1025
- */
1026
- emitDepthScanSetup(composition) {
1027
- const hasMlArcore = this.compositionHasTrait(composition, "depth_ml_arcore");
1028
- const hasConfidence = this.compositionHasTrait(composition, "depth_confidence_map");
1029
- const hasAutoSelect = this.compositionHasTrait(composition, "depth_auto_select");
1030
- const hasMeshGenerate = this.compositionHasTrait(composition, "depth_mesh_generate");
1031
- const hasMeshToHolo = this.compositionHasTrait(composition, "depth_mesh_to_holo");
1032
- const hasRealtime = this.compositionHasTrait(composition, "depth_realtime");
1033
- const hasExport = this.compositionHasTrait(composition, "depth_export");
1034
- this.emit("");
1035
- this.emit("// === Depth Scanner: ARCore depth, ToF, stereo (M.010.02b) ===");
1036
- this.emit(`private val depthConfidenceThreshold = ${DEPTH_SCANNER_DEFAULTS.scan.confidenceThreshold}`);
1037
- this.emit(`private val depthMaxMeters = ${DEPTH_SCANNER_DEFAULTS.scan.maxDepthMeters}f`);
1038
- this.emit(`private val depthMeshDecimation = ${DEPTH_SCANNER_DEFAULTS.scan.meshDecimation}f`);
1039
- this.emit("private var depthImage: android.media.Image? = null");
1040
- if (hasConfidence) {
1041
- this.emit("private var depthConfidenceImage: android.media.Image? = null");
1042
- }
1043
- this.emit("");
1044
- this.emit("private fun setupDepthScanner() {");
1045
- this.indentLevel++;
1046
- this.emit("val session = arFragment.arSceneView.session ?: return");
1047
- this.emit("val config = session.config");
1048
- this.emit("config.depthMode = Config.DepthMode.AUTOMATIC");
1049
- this.emit("session.configure(config)");
1050
- this.emit('android.util.Log.d("HoloScript", "Depth scanner: DepthMode.AUTOMATIC enabled")');
1051
- this.emit("");
1052
- if (hasAutoSelect) {
1053
- this.emit("// Auto-select best depth source: ToF > ARCore ML > Stereo");
1054
- this.emit("detectDepthSource()");
1055
- this.emit("");
1056
- }
1057
- if (hasRealtime) {
1058
- this.emit("// Continuous depth frame processing in render loop");
1059
- this.emit("arFragment.arSceneView.scene.addOnUpdateListener { _ ->");
1060
- this.indentLevel++;
1061
- this.emit("val frame = arFragment.arSceneView.arFrame ?: return@addOnUpdateListener");
1062
- this.emit("processDepthFrame(frame)");
1063
- this.indentLevel--;
1064
- this.emit("}");
1065
- }
1066
- this.indentLevel--;
1067
- this.emit("}");
1068
- this.emit("");
1069
- this.emit("private fun processDepthFrame(frame: Frame) {");
1070
- this.indentLevel++;
1071
- this.emit("try {");
1072
- this.indentLevel++;
1073
- if (hasMlArcore) {
1074
- this.emit("// ARCore ML depth: acquire 16-bit depth image");
1075
- this.emit("depthImage?.close()");
1076
- this.emit("depthImage = frame.acquireDepthImage16Bits()");
1077
- } else {
1078
- this.emit("depthImage?.close()");
1079
- this.emit("depthImage = frame.acquireDepthImage16Bits()");
1080
- }
1081
- if (hasConfidence) {
1082
- this.emit("");
1083
- this.emit("// Confidence map: per-pixel confidence (0-255)");
1084
- this.emit("depthConfidenceImage?.close()");
1085
- this.emit("depthConfidenceImage = frame.acquireRawDepthConfidenceImage()");
1086
- }
1087
- if (hasMeshGenerate) {
1088
- this.emit("");
1089
- this.emit("val depthImg = depthImage ?: return");
1090
- this.emit("generateMeshFromDepth(depthImg)");
1091
- }
1092
- this.indentLevel--;
1093
- this.emit("} catch (e: Exception) {");
1094
- this.indentLevel++;
1095
- this.emit('android.util.Log.w("HoloScript", "Depth frame unavailable: ${e.message}")');
1096
- this.indentLevel--;
1097
- this.emit("}");
1098
- this.indentLevel--;
1099
- this.emit("}");
1100
- this.emit("");
1101
- if (hasAutoSelect) {
1102
- this.emit("private fun detectDepthSource() {");
1103
- this.indentLevel++;
1104
- this.emit("val session = arFragment.arSceneView.session ?: return");
1105
- this.emit("");
1106
- this.emit("// Check ToF sensor availability");
1107
- this.emit('val hasToF = packageManager.hasSystemFeature("android.hardware.sensor.proximity")');
1108
- this.emit("");
1109
- this.emit("// Check ARCore depth support");
1110
- this.emit("val hasARCoreDepth = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)");
1111
- this.emit("");
1112
- this.emit("// Check dual camera for stereo depth");
1113
- this.emit("val cameraManager = getSystemService(android.hardware.camera2.CameraManager::class.java)");
1114
- this.emit("val hasStereo = (cameraManager?.cameraIdList?.size ?: 0) >= 2");
1115
- this.emit("");
1116
- this.emit("// Priority: ToF > ARCore ML > Stereo");
1117
- this.emit("val depthSource = when {");
1118
- this.indentLevel++;
1119
- this.emit('hasToF -> "ToF"');
1120
- this.emit('hasARCoreDepth -> "ARCore_ML"');
1121
- this.emit('hasStereo -> "Stereo"');
1122
- this.emit('else -> "None"');
1123
- this.indentLevel--;
1124
- this.emit("}");
1125
- this.emit('android.util.Log.d("HoloScript", "Depth source selected: $depthSource (ToF=$hasToF, ARCore=$hasARCoreDepth, Stereo=$hasStereo)")');
1126
- this.indentLevel--;
1127
- this.emit("}");
1128
- this.emit("");
1129
- }
1130
- if (hasMeshGenerate) {
1131
- this.emit("private fun generateMeshFromDepth(depthImage: android.media.Image) {");
1132
- this.indentLevel++;
1133
- this.emit("val width = depthImage.width");
1134
- this.emit("val height = depthImage.height");
1135
- this.emit("val buffer = depthImage.planes[0].buffer");
1136
- this.emit("val vertices = mutableListOf<Vector3>()");
1137
- this.emit("val indices = mutableListOf<Int>()");
1138
- this.emit("");
1139
- this.emit("// Convert depth pixels to 3D points with decimation");
1140
- this.emit("val step = (1.0f / depthMeshDecimation).toInt().coerceAtLeast(1)");
1141
- this.emit("for (y in 0 until height step step) {");
1142
- this.indentLevel++;
1143
- this.emit("for (x in 0 until width step step) {");
1144
- this.indentLevel++;
1145
- this.emit("val depthMm = buffer.getShort((y * width + x) * 2).toInt() and 0xFFFF");
1146
- this.emit("val depthM = depthMm / 1000.0f");
1147
- this.emit("if (depthM <= 0f || depthM > depthMaxMeters) continue");
1148
- this.emit("");
1149
- this.emit("// Unproject pixel to 3D point (simplified pinhole model)");
1150
- this.emit("val fx = width / 2.0f // focal length approximation");
1151
- this.emit("val fy = height / 2.0f");
1152
- this.emit("val cx = width / 2.0f");
1153
- this.emit("val cy = height / 2.0f");
1154
- this.emit("val px = (x - cx) / fx * depthM");
1155
- this.emit("val py = (y - cy) / fy * depthM");
1156
- this.emit("vertices.add(Vector3(px, py, depthM))");
1157
- this.indentLevel--;
1158
- this.emit("}");
1159
- this.indentLevel--;
1160
- this.emit("}");
1161
- this.emit("");
1162
- this.emit("// Triangulate adjacent points into mesh");
1163
- this.emit("val cols = width / step");
1164
- this.emit("for (row in 0 until (vertices.size / cols) - 1) {");
1165
- this.indentLevel++;
1166
- this.emit("for (col in 0 until cols - 1) {");
1167
- this.indentLevel++;
1168
- this.emit("val i = row * cols + col");
1169
- this.emit("indices.add(i)");
1170
- this.emit("indices.add(i + 1)");
1171
- this.emit("indices.add(i + cols)");
1172
- this.emit("indices.add(i + 1)");
1173
- this.emit("indices.add(i + cols + 1)");
1174
- this.emit("indices.add(i + cols)");
1175
- this.indentLevel--;
1176
- this.emit("}");
1177
- this.indentLevel--;
1178
- this.emit("}");
1179
- this.emit("");
1180
- this.emit('android.util.Log.d("HoloScript", "Depth mesh: ${vertices.size} vertices, ${indices.size / 3} triangles")');
1181
- if (hasMeshToHolo) {
1182
- this.emit("convertMeshToHolo(vertices, indices)");
1183
- }
1184
- this.indentLevel--;
1185
- this.emit("}");
1186
- this.emit("");
1187
- }
1188
- if (hasMeshToHolo) {
1189
- this.emit("private fun convertMeshToHolo(vertices: List<Vector3>, indices: List<Int>) {");
1190
- this.indentLevel++;
1191
- this.emit("// Convert mesh vertices + classification to HoloScript entities");
1192
- this.emit("val holoEntities = mutableListOf<Map<String, Any>>()");
1193
- this.emit("for ((i, vertex) in vertices.withIndex()) {");
1194
- this.indentLevel++;
1195
- this.emit("holoEntities.add(mapOf(");
1196
- this.indentLevel++;
1197
- this.emit('"type" to "DepthPoint",');
1198
- this.emit('"id" to "dp_$i",');
1199
- this.emit('"position" to listOf(vertex.x, vertex.y, vertex.z),');
1200
- this.emit('"classification" to "unknown"');
1201
- this.indentLevel--;
1202
- this.emit("))");
1203
- this.indentLevel--;
1204
- this.emit("}");
1205
- this.emit('android.util.Log.d("HoloScript", "Converted ${holoEntities.size} depth points to .holo entities")');
1206
- this.indentLevel--;
1207
- this.emit("}");
1208
- this.emit("");
1209
- }
1210
- if (hasExport) {
1211
- const format = DEPTH_SCANNER_DEFAULTS.export.format;
1212
- this.emit("private fun exportDepthMesh(vertices: List<Vector3>, indices: List<Int>) {");
1213
- this.indentLevel++;
1214
- this.emit(`val format = "${format}"`);
1215
- this.emit('val filename = "depth_scan_${System.currentTimeMillis()}.$format"');
1216
- this.emit("val file = java.io.File(getExternalFilesDir(null), filename)");
1217
- this.emit("");
1218
- this.emit('if (format == "obj") {');
1219
- this.indentLevel++;
1220
- this.emit("val sb = StringBuilder()");
1221
- this.emit('sb.appendLine("# HoloScript Depth Scan Export")');
1222
- this.emit("for (v in vertices) {");
1223
- this.indentLevel++;
1224
- this.emit('sb.appendLine("v ${v.x} ${v.y} ${v.z}")');
1225
- this.indentLevel--;
1226
- this.emit("}");
1227
- this.emit("for (i in indices.indices step 3) {");
1228
- this.indentLevel++;
1229
- this.emit('sb.appendLine("f ${indices[i]+1} ${indices[i+1]+1} ${indices[i+2]+1}")');
1230
- this.indentLevel--;
1231
- this.emit("}");
1232
- this.emit("file.writeText(sb.toString())");
1233
- this.indentLevel--;
1234
- this.emit("} else {");
1235
- this.indentLevel++;
1236
- this.emit("// GLB export: binary glTF format");
1237
- this.emit('android.util.Log.d("HoloScript", "GLB export requires glTF serializer library")');
1238
- this.indentLevel--;
1239
- this.emit("}");
1240
- this.emit("");
1241
- this.emit('android.util.Log.d("HoloScript", "Depth mesh exported: ${file.absolutePath}")');
1242
- this.indentLevel--;
1243
- this.emit("}");
1244
- this.emit("");
1245
- }
1246
- }
1247
- // === NPU Scene Understanding (M.010.03) ===
1248
- /**
1249
- * Check whether the composition references any npu_* scene understanding traits.
1250
- */
1251
- hasNPUSceneTraits(composition) {
1252
- const npuNames = NPU_SCENE_TRAITS;
1253
- for (const obj of composition.objects || []) {
1254
- for (const trait of obj.traits || []) {
1255
- const name = typeof trait === "string" ? trait : trait.name;
1256
- if (npuNames.includes(name)) return true;
1257
- }
1258
- }
1259
- return false;
1260
- }
1261
- /**
1262
- * Emit a standalone Kotlin file for NPU scene understanding using
1263
- * ML Kit, TFLite, and NNAPI hardware acceleration.
1264
- */
1265
- emitNPUSceneSetup(composition) {
1266
- this.lines = [];
1267
- this.indentLevel = 0;
1268
- const pkg = this.options.packageName;
1269
- const cls = this.options.className;
1270
- const defaults = NPU_SCENE_DEFAULTS;
1271
- const usedTraits = /* @__PURE__ */ new Set();
1272
- for (const obj of composition.objects || []) {
1273
- for (const trait of obj.traits || []) {
1274
- const name = typeof trait === "string" ? trait : trait.name;
1275
- if (NPU_SCENE_TRAITS.includes(name)) {
1276
- usedTraits.add(name);
1277
- }
1278
- }
1279
- }
1280
- this.emit("// Auto-generated by HoloScript AndroidCompiler \u2014 NPU Scene Understanding");
1281
- this.emit(`// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`);
1282
- this.emit("// Requires ML Kit, CameraX, NNAPI delegate");
1283
- this.emit("// Do not edit manually \u2014 regenerate from .holo source");
1284
- this.emit("");
1285
- this.emit(`package ${pkg}`);
1286
- this.emit("");
1287
- this.emit("import android.graphics.Bitmap");
1288
- this.emit("import android.graphics.Rect");
1289
- this.emit("import android.util.Log");
1290
- this.emit("import android.util.Size");
1291
- this.emit("import androidx.camera.core.ImageAnalysis");
1292
- this.emit("import androidx.camera.core.ImageProxy");
1293
- this.emit("import androidx.lifecycle.ViewModel");
1294
- this.emit("import androidx.lifecycle.viewModelScope");
1295
- this.emit("import kotlinx.coroutines.flow.MutableStateFlow");
1296
- this.emit("import kotlinx.coroutines.flow.StateFlow");
1297
- this.emit("import kotlinx.coroutines.launch");
1298
- if (usedTraits.has("npu_detect")) {
1299
- this.emit("import com.google.mlkit.vision.common.InputImage");
1300
- this.emit("import com.google.mlkit.vision.objects.ObjectDetection");
1301
- this.emit("import com.google.mlkit.vision.objects.ObjectDetector");
1302
- this.emit("import com.google.mlkit.vision.objects.defaults.ObjectDetectorOptions");
1303
- }
1304
- if (usedTraits.has("npu_classify")) {
1305
- this.emit("import com.google.mlkit.vision.common.InputImage");
1306
- this.emit("import com.google.mlkit.vision.label.ImageLabeling");
1307
- this.emit("import com.google.mlkit.vision.label.ImageLabeler");
1308
- this.emit("import com.google.mlkit.vision.label.defaults.ImageLabelerOptions");
1309
- }
1310
- if (usedTraits.has("npu_segment")) {
1311
- this.emit("import com.google.mlkit.vision.segmentation.Segmentation");
1312
- this.emit("import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions");
1313
- }
1314
- if (usedTraits.has("npu_model_custom")) {
1315
- this.emit("import org.tensorflow.lite.Interpreter");
1316
- this.emit("import org.tensorflow.lite.nnapi.NnApiDelegate");
1317
- this.emit("import com.google.mlkit.common.model.LocalModel");
1318
- }
1319
- this.emit("");
1320
- this.emit("/** A single detection result from NPU scene understanding. */");
1321
- this.emit("data class NPUDetection(");
1322
- this.indentLevel++;
1323
- this.emit("val label: String,");
1324
- this.emit("val confidence: Float,");
1325
- this.emit("val boundingBox: Rect? = null,");
1326
- this.emit("val position: FloatArray? = null,");
1327
- this.emit("val segmentationMask: Bitmap? = null,");
1328
- this.emit("val depthValue: Float? = null");
1329
- this.indentLevel--;
1330
- this.emit(")");
1331
- this.emit("");
1332
- this.emit(`class ${cls}NPUSceneManager : ViewModel() {`);
1333
- this.indentLevel++;
1334
- this.emit("");
1335
- this.emit("private val _detections = MutableStateFlow<List<NPUDetection>>(emptyList())");
1336
- this.emit("val detections: StateFlow<List<NPUDetection>> = _detections");
1337
- this.emit("");
1338
- this.emit("private val _isProcessing = MutableStateFlow(false)");
1339
- this.emit("val isProcessing: StateFlow<Boolean> = _isProcessing");
1340
- this.emit("");
1341
- this.emit("private var inferenceCount: Int = 0");
1342
- this.emit(`private val confidenceThreshold: Float = ${defaults.confidenceThreshold}f`);
1343
- this.emit(`private val maxDetections: Int = ${defaults.maxDetections}`);
1344
- this.emit(`private val targetFPS: Int = ${defaults.targetFPS}`);
1345
- this.emit(`private val entityScale: Float = ${defaults.entityScale}f`);
1346
- this.emit(`private val labelOffsetY: Float = ${defaults.labelOffsetY}f`);
1347
- this.emit("private var frameCounter: Int = 0");
1348
- this.emit("private var lastProcessTimeMs: Long = 0");
1349
- this.emit("");
1350
- if (usedTraits.has("npu_detect")) {
1351
- this.emit("// MARK: Object Detection (npu_detect)");
1352
- this.emit("");
1353
- this.emit("private val objectDetector: ObjectDetector by lazy {");
1354
- this.indentLevel++;
1355
- this.emit("val options = ObjectDetectorOptions.Builder()");
1356
- this.emit(" .setDetectorMode(ObjectDetectorOptions.STREAM_MODE)");
1357
- this.emit(" .enableMultipleObjects()");
1358
- this.emit(" .enableClassification()");
1359
- this.emit(" .build()");
1360
- this.emit("ObjectDetection.getClient(options)");
1361
- this.indentLevel--;
1362
- this.emit("}");
1363
- this.emit("");
1364
- this.emit("fun detectObjects(image: InputImage) {");
1365
- this.indentLevel++;
1366
- this.emit("objectDetector.process(image)");
1367
- this.emit(" .addOnSuccessListener { objects ->");
1368
- this.indentLevel++;
1369
- this.emit("val results = objects");
1370
- this.emit(" .filter { it.labels.isNotEmpty() && (it.labels.firstOrNull()?.confidence ?: 0f) >= confidenceThreshold }");
1371
- this.emit(" .take(maxDetections)");
1372
- this.emit(" .map { obj ->");
1373
- this.indentLevel++;
1374
- this.emit("NPUDetection(");
1375
- this.emit(' label = obj.labels.firstOrNull()?.text ?: "unknown",');
1376
- this.emit(" confidence = obj.labels.firstOrNull()?.confidence ?: 0f,");
1377
- this.emit(" boundingBox = obj.boundingBox");
1378
- this.emit(")");
1379
- this.indentLevel--;
1380
- this.emit(" }");
1381
- this.emit("_detections.value = results");
1382
- this.emit("inferenceCount++");
1383
- this.emit('Log.d("HoloScript", "Detected ${results.size} objects")');
1384
- this.indentLevel--;
1385
- this.emit(" }");
1386
- this.emit(" .addOnFailureListener { e ->");
1387
- this.indentLevel++;
1388
- this.emit('Log.e("HoloScript", "Object detection failed", e)');
1389
- this.indentLevel--;
1390
- this.emit(" }");
1391
- this.indentLevel--;
1392
- this.emit("}");
1393
- this.emit("");
1394
- }
1395
- if (usedTraits.has("npu_classify")) {
1396
- this.emit("// MARK: Classification (npu_classify)");
1397
- this.emit("");
1398
- this.emit("private val imageLabeler: ImageLabeler by lazy {");
1399
- this.indentLevel++;
1400
- this.emit("val options = ImageLabelerOptions.Builder()");
1401
- this.emit(` .setConfidenceThreshold(${defaults.confidenceThreshold}f)`);
1402
- this.emit(" .build()");
1403
- this.emit("ImageLabeling.getClient(options)");
1404
- this.indentLevel--;
1405
- this.emit("}");
1406
- this.emit("");
1407
- this.emit("fun classifyImage(image: InputImage) {");
1408
- this.indentLevel++;
1409
- this.emit("imageLabeler.process(image)");
1410
- this.emit(" .addOnSuccessListener { labels ->");
1411
- this.indentLevel++;
1412
- this.emit("val results = labels");
1413
- this.emit(" .filter { it.confidence >= confidenceThreshold }");
1414
- this.emit(" .take(maxDetections)");
1415
- this.emit(" .map { label ->");
1416
- this.indentLevel++;
1417
- this.emit("NPUDetection(label = label.text, confidence = label.confidence)");
1418
- this.indentLevel--;
1419
- this.emit(" }");
1420
- this.emit("_detections.value = results");
1421
- this.emit("inferenceCount++");
1422
- this.indentLevel--;
1423
- this.emit(" }");
1424
- this.emit(" .addOnFailureListener { e ->");
1425
- this.indentLevel++;
1426
- this.emit('Log.e("HoloScript", "Image classification failed", e)');
1427
- this.indentLevel--;
1428
- this.emit(" }");
1429
- this.indentLevel--;
1430
- this.emit("}");
1431
- this.emit("");
1432
- }
1433
- if (usedTraits.has("npu_segment")) {
1434
- this.emit("// MARK: Segmentation (npu_segment)");
1435
- this.emit("");
1436
- this.emit("private val segmenter by lazy {");
1437
- this.indentLevel++;
1438
- this.emit("val options = SelfieSegmenterOptions.Builder()");
1439
- this.emit(" .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)");
1440
- this.emit(" .enableRawSizeMask()");
1441
- this.emit(" .build()");
1442
- this.emit("Segmentation.getClient(options)");
1443
- this.indentLevel--;
1444
- this.emit("}");
1445
- this.emit("");
1446
- this.emit("fun segmentScene(image: InputImage) {");
1447
- this.indentLevel++;
1448
- this.emit("segmenter.process(image)");
1449
- this.emit(" .addOnSuccessListener { mask ->");
1450
- this.indentLevel++;
1451
- this.emit("val buffer = mask.buffer");
1452
- this.emit("val width = mask.width");
1453
- this.emit("val height = mask.height");
1454
- this.emit("val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)");
1455
- this.emit("_detections.value = listOf(NPUDetection(");
1456
- this.emit(' label = "person_segmentation", confidence = 1.0f,');
1457
- this.emit(" segmentationMask = bitmap");
1458
- this.emit("))");
1459
- this.emit("inferenceCount++");
1460
- this.indentLevel--;
1461
- this.emit(" }");
1462
- this.emit(" .addOnFailureListener { e ->");
1463
- this.indentLevel++;
1464
- this.emit('Log.e("HoloScript", "Segmentation failed", e)');
1465
- this.indentLevel--;
1466
- this.emit(" }");
1467
- this.indentLevel--;
1468
- this.emit("}");
1469
- this.emit("");
1470
- }
1471
- if (usedTraits.has("npu_entity_pipe")) {
1472
- this.emit("// MARK: Entity Pipeline (npu_entity_pipe)");
1473
- this.emit("");
1474
- this.emit("fun mapDetectionsToEntities(detections: List<NPUDetection>, arFragment: com.google.ar.sceneform.ux.ArFragment) {");
1475
- this.indentLevel++;
1476
- this.emit("val scene = arFragment.arSceneView.scene");
1477
- this.emit("for (detection in detections) {");
1478
- this.indentLevel++;
1479
- this.emit("val bbox = detection.boundingBox ?: continue");
1480
- this.emit("val centerX = (bbox.left + bbox.right) / 2f");
1481
- this.emit("val centerY = (bbox.top + bbox.bottom) / 2f");
1482
- this.emit("val frame = arFragment.arSceneView.arFrame ?: continue");
1483
- this.emit("val hits = frame.hitTest(centerX, centerY)");
1484
- this.emit("val hit = hits.firstOrNull() ?: continue");
1485
- this.emit("val anchor = hit.createAnchor()");
1486
- this.emit("val anchorNode = com.google.ar.sceneform.AnchorNode(anchor)");
1487
- this.emit("anchorNode.setParent(scene)");
1488
- this.emit("com.google.ar.sceneform.rendering.MaterialFactory");
1489
- this.emit(" .makeOpaqueWithColor(arFragment.requireContext(),");
1490
- this.emit(" com.google.ar.sceneform.rendering.Color(0.2f, 0.6f, 1.0f, 0.6f))");
1491
- this.emit(" .thenAccept { material ->");
1492
- this.indentLevel++;
1493
- this.emit(`val renderable = com.google.ar.sceneform.rendering.ShapeFactory.makeSphere(${defaults.entityScale}f, com.google.ar.sceneform.math.Vector3.zero(), material)`);
1494
- this.emit("val node = com.google.ar.sceneform.ux.TransformableNode(arFragment.transformationSystem)");
1495
- this.emit("node.setParent(anchorNode)");
1496
- this.emit("node.renderable = renderable");
1497
- this.emit("node.name = detection.label");
1498
- this.emit('Log.d("HoloScript", "Entity created: ${detection.label}")');
1499
- this.indentLevel--;
1500
- this.emit(" }");
1501
- this.indentLevel--;
1502
- this.emit("}");
1503
- this.indentLevel--;
1504
- this.emit("}");
1505
- this.emit("");
1506
- }
1507
- if (usedTraits.has("npu_realtime")) {
1508
- this.emit("// MARK: Realtime Processing (npu_realtime)");
1509
- this.emit("");
1510
- this.emit("fun createImageAnalyzer(): ImageAnalysis.Analyzer {");
1511
- this.indentLevel++;
1512
- this.emit("return ImageAnalysis.Analyzer { imageProxy ->");
1513
- this.indentLevel++;
1514
- this.emit("frameCounter++");
1515
- this.emit("val now = System.currentTimeMillis()");
1516
- this.emit("val intervalMs = 1000L / targetFPS");
1517
- this.emit("if (now - lastProcessTimeMs < intervalMs) {");
1518
- this.indentLevel++;
1519
- this.emit("imageProxy.close()");
1520
- this.emit("return@Analyzer");
1521
- this.indentLevel--;
1522
- this.emit("}");
1523
- this.emit("lastProcessTimeMs = now");
1524
- this.emit("_isProcessing.value = true");
1525
- this.emit("");
1526
- this.emit("@androidx.camera.core.ExperimentalGetImage");
1527
- this.emit("val mediaImage = imageProxy.image");
1528
- this.emit("if (mediaImage != null) {");
1529
- this.indentLevel++;
1530
- this.emit("val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)");
1531
- if (usedTraits.has("npu_classify")) {
1532
- this.emit("classifyImage(image)");
1533
- }
1534
- if (usedTraits.has("npu_detect")) {
1535
- this.emit("detectObjects(image)");
1536
- }
1537
- if (usedTraits.has("npu_segment")) {
1538
- this.emit("segmentScene(image)");
1539
- }
1540
- this.indentLevel--;
1541
- this.emit("}");
1542
- this.emit("_isProcessing.value = false");
1543
- this.emit("imageProxy.close()");
1544
- this.indentLevel--;
1545
- this.emit("}");
1546
- this.indentLevel--;
1547
- this.emit("}");
1548
- this.emit("");
1549
- }
1550
- if (usedTraits.has("npu_model_custom")) {
1551
- this.emit("// MARK: Custom Model (npu_model_custom)");
1552
- this.emit("");
1553
- this.emit("private var customInterpreter: Interpreter? = null");
1554
- this.emit("");
1555
- this.emit("fun loadCustomModel(context: android.content.Context, assetPath: String) {");
1556
- this.indentLevel++;
1557
- this.emit("val localModel = LocalModel.Builder()");
1558
- this.emit(" .setAssetFilePath(assetPath)");
1559
- this.emit(" .build()");
1560
- this.emit("");
1561
- this.emit("// NNAPI delegate for hardware acceleration");
1562
- this.emit("val nnApiDelegate = NnApiDelegate()");
1563
- this.emit("val options = Interpreter.Options().addDelegate(nnApiDelegate)");
1564
- this.emit("");
1565
- this.emit("val modelFile = context.assets.open(assetPath)");
1566
- this.emit("val buffer = modelFile.readBytes()");
1567
- this.emit("val byteBuffer = java.nio.ByteBuffer.allocateDirect(buffer.size)");
1568
- this.emit("byteBuffer.put(buffer)");
1569
- this.emit("customInterpreter = Interpreter(byteBuffer, options)");
1570
- this.emit('Log.d("HoloScript", "Custom model loaded with NNAPI: $assetPath")');
1571
- this.indentLevel--;
1572
- this.emit("}");
1573
- this.emit("");
1574
- }
1575
- if (usedTraits.has("npu_label_overlay")) {
1576
- this.emit("// MARK: Label Overlay (npu_label_overlay)");
1577
- this.emit("");
1578
- this.emit("fun createLabelOverlay(");
1579
- this.emit(" detection: NPUDetection,");
1580
- this.emit(" arFragment: com.google.ar.sceneform.ux.ArFragment,");
1581
- this.emit(" anchorNode: com.google.ar.sceneform.AnchorNode");
1582
- this.emit(") {");
1583
- this.indentLevel++;
1584
- this.emit("com.google.ar.sceneform.rendering.ViewRenderable.builder()");
1585
- this.emit(" .setView(arFragment.requireContext(), android.widget.TextView(arFragment.requireContext()).apply {");
1586
- this.indentLevel++;
1587
- this.emit('text = "${detection.label} (${(detection.confidence * 100).toInt()}%)"');
1588
- this.emit("setTextColor(android.graphics.Color.WHITE)");
1589
- this.emit("setBackgroundColor(android.graphics.Color.argb(180, 0, 0, 0))");
1590
- this.emit("setPadding(12, 6, 12, 6)");
1591
- this.emit("textSize = 12f");
1592
- this.indentLevel--;
1593
- this.emit(" })");
1594
- this.emit(" .build()");
1595
- this.emit(" .thenAccept { renderable ->");
1596
- this.indentLevel++;
1597
- this.emit("val labelNode = com.google.ar.sceneform.Node()");
1598
- this.emit("labelNode.setParent(anchorNode)");
1599
- this.emit(`labelNode.localPosition = com.google.ar.sceneform.math.Vector3(0f, ${defaults.labelOffsetY}f, 0f)`);
1600
- this.emit("labelNode.renderable = renderable");
1601
- this.emit('Log.d("HoloScript", "Label overlay: ${detection.label}")');
1602
- this.indentLevel--;
1603
- this.emit(" }");
1604
- this.indentLevel--;
1605
- this.emit("}");
1606
- this.emit("");
1607
- }
1608
- this.indentLevel--;
1609
- this.emit("}");
1610
- return this.lines.join("\n");
1611
- }
1612
- // === Utility Methods ===
1613
- emit(line) {
1614
- const indent = this.options.indent.repeat(this.indentLevel);
1615
- this.lines.push(indent + line);
1616
- }
1617
- sanitizeName(name) {
1618
- const result = name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/^[0-9]/, "_$&");
1619
- return result.charAt(0).toUpperCase() + result.slice(1);
1620
- }
1621
- getSceneformGeometry(meshType) {
1622
- const geometries = {
1623
- cube: "ShapeFactory.makeCube(Vector3(0.1f, 0.1f, 0.1f), Vector3.zero(), material)",
1624
- box: "ShapeFactory.makeCube(Vector3(0.1f, 0.1f, 0.1f), Vector3.zero(), material)",
1625
- sphere: "ShapeFactory.makeSphere(0.05f, Vector3.zero(), material)",
1626
- cylinder: "ShapeFactory.makeCylinder(0.05f, 0.1f, Vector3.zero(), material)"
1627
- };
1628
- return geometries[meshType] || "ShapeFactory.makeCube(Vector3(0.1f, 0.1f, 0.1f), Vector3.zero(), material)";
1629
- }
1630
- findObjProp(obj, key) {
1631
- return obj.properties?.find((p) => p.key === key)?.value;
1632
- }
1633
- toKotlinType(value) {
1634
- if (value === null) return "Any?";
1635
- if (typeof value === "boolean") return "Boolean";
1636
- if (typeof value === "number") return Number.isInteger(value) ? "Int" : "Float";
1637
- if (typeof value === "string") return "String";
1638
- if (Array.isArray(value)) {
1639
- if (value.length === 3 && value.every((v) => typeof v === "number")) return "Vector3";
1640
- return "List<Any>";
1641
- }
1642
- return "Any";
1643
- }
1644
- toKotlinValue(value) {
1645
- if (value === null) return "null";
1646
- if (typeof value === "boolean") return value ? "true" : "false";
1647
- if (typeof value === "number") {
1648
- return Number.isInteger(value) ? `${value}` : `${value}f`;
1649
- }
1650
- if (typeof value === "string") return `"${this.escapeStringValue(value, "Kotlin")}"`;
1651
- if (Array.isArray(value)) {
1652
- if (value.length === 3 && value.every((v) => typeof v === "number")) {
1653
- return `Vector3(${value[0]}f, ${value[1]}f, ${value[2]}f)`;
1654
- }
1655
- return `listOf(${value.map((v) => this.toKotlinValue(v)).join(", ")})`;
1656
- }
1657
- return "null";
1658
- }
1659
- toAndroidColor(value) {
1660
- if (!value) return "android.graphics.Color.BLUE";
1661
- if (typeof value === "string") {
1662
- if (value.startsWith("#")) {
1663
- return `android.graphics.Color.parseColor("${value}")`;
1664
- }
1665
- const colors = {
1666
- red: "android.graphics.Color.RED",
1667
- green: "android.graphics.Color.GREEN",
1668
- blue: "android.graphics.Color.BLUE",
1669
- white: "android.graphics.Color.WHITE",
1670
- black: "android.graphics.Color.BLACK",
1671
- yellow: "android.graphics.Color.YELLOW",
1672
- cyan: "android.graphics.Color.CYAN",
1673
- magenta: "android.graphics.Color.MAGENTA"
1674
- };
1675
- return colors[value.toLowerCase()] || "android.graphics.Color.BLUE";
1676
- }
1677
- if (Array.isArray(value) && value.length >= 3) {
1678
- const [r, g, b, a = 1] = value;
1679
- return `android.graphics.Color.argb(${Math.round(a * 255)}, ${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)})`;
1680
- }
1681
- return "android.graphics.Color.BLUE";
1682
- }
1683
- // === Portal AR Methods (M.010.06) ===
1684
- hasPortalARTraits(composition) {
1685
- const portalNames = PORTAL_AR_TRAITS;
1686
- for (const obj of composition.objects || []) {
1687
- for (const trait of obj.traits || []) {
1688
- const name = typeof trait === "string" ? trait : trait.name;
1689
- if (portalNames.includes(name)) return true;
1690
- }
1691
- }
1692
- return false;
1693
- }
1694
- emitPortalARSetup(composition) {
1695
- this.emit("");
1696
- this.emit("// === Portal AR: full-scene holographic layer behind reality ===");
1697
- this.emit("private var portalSession: Session? = null");
1698
- this.emit("private var portalRenderer: HolographicRenderer? = null");
1699
- this.emit("private var depthTexture: Image? = null");
1700
- this.emit("private var sceneMesh: Mesh? = null");
1701
- this.emit("");
1702
- this.emit("private fun setupPortalAR() {");
1703
- this.indentLevel++;
1704
- this.emit("val session = arFragment.arSceneView.session ?: return");
1705
- this.emit("");
1706
- this.emit("// Enable depth mode for portal occlusion");
1707
- this.emit("val config = Config(session)");
1708
- this.emit("config.depthMode = Config.DepthMode.AUTOMATIC");
1709
- this.emit("config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR");
1710
- if (this.compositionHasTrait(composition, "portal_world_mesh") || this.compositionHasTrait(composition, "portal_mesh_occlusion")) {
1711
- this.emit("config.planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_AND_VERTICAL");
1712
- this.emit("// Enable scene mesh for portal world mesh integration");
1713
- this.emit("if (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {");
1714
- this.indentLevel++;
1715
- this.emit("config.depthMode = Config.DepthMode.AUTOMATIC");
1716
- this.indentLevel--;
1717
- this.emit("}");
1718
- }
1719
- this.emit("session.configure(config)");
1720
- this.emit("portalSession = session");
1721
- this.emit("");
1722
- this.emit("// Create holographic render layer behind reality");
1723
- this.emit("portalRenderer = HolographicRenderer(this)");
1724
- this.emit("portalRenderer?.initialize(session)");
1725
- this.emit("");
1726
- if (this.compositionHasTrait(composition, "portal_occlusion")) {
1727
- this.emit("// Portal occlusion: real objects block holograms via depth buffer");
1728
- this.emit("arFragment.arSceneView.scene.addOnUpdateListener { _ ->");
1729
- this.indentLevel++;
1730
- this.emit("val frame = arFragment.arSceneView.arFrame ?: return@addOnUpdateListener");
1731
- this.emit("try {");
1732
- this.indentLevel++;
1733
- this.emit("depthTexture = frame.acquireDepthImage16Bits()");
1734
- this.emit("portalRenderer?.updateDepthOcclusion(depthTexture!!)");
1735
- this.indentLevel--;
1736
- this.emit("} catch (e: Exception) {");
1737
- this.indentLevel++;
1738
- this.emit("// Depth not available on this frame");
1739
- this.indentLevel--;
1740
- this.emit("} finally {");
1741
- this.indentLevel++;
1742
- this.emit("depthTexture?.close()");
1743
- this.indentLevel--;
1744
- this.emit("}");
1745
- this.indentLevel--;
1746
- this.emit("}");
1747
- this.emit("");
1748
- }
1749
- if (this.compositionHasTrait(composition, "portal_parallax")) {
1750
- this.emit("// Portal parallax: depth-correct parallax from device pose");
1751
- this.emit("arFragment.arSceneView.scene.addOnUpdateListener { _ ->");
1752
- this.indentLevel++;
1753
- this.emit("val frame = arFragment.arSceneView.arFrame ?: return@addOnUpdateListener");
1754
- this.emit("val cameraPose = frame.camera.pose");
1755
- this.emit("portalRenderer?.applyParallaxCorrection(cameraPose)");
1756
- this.indentLevel--;
1757
- this.emit("}");
1758
- this.emit("");
1759
- }
1760
- if (this.compositionHasTrait(composition, "portal_depth_fade")) {
1761
- this.emit("// Portal depth fade: holograms fade at distance");
1762
- this.emit("portalRenderer?.enableDepthFade(nearPlane = 0.1f, farPlane = 10.0f)");
1763
- this.emit("");
1764
- }
1765
- if (this.compositionHasTrait(composition, "portal_world_mesh") || this.compositionHasTrait(composition, "portal_mesh_occlusion")) {
1766
- this.emit("// Portal world mesh: scene mesh for realistic occlusion");
1767
- this.emit("arFragment.arSceneView.scene.addOnUpdateListener { _ ->");
1768
- this.indentLevel++;
1769
- this.emit("val frame = arFragment.arSceneView.arFrame ?: return@addOnUpdateListener");
1770
- this.emit("try {");
1771
- this.indentLevel++;
1772
- this.emit("val rawDepth = frame.acquireRawDepthImage16Bits()");
1773
- this.emit("sceneMesh = portalRenderer?.reconstructMesh(rawDepth)");
1774
- this.emit("portalRenderer?.updateMeshOcclusion(sceneMesh!!)");
1775
- this.emit("rawDepth.close()");
1776
- this.indentLevel--;
1777
- this.emit("} catch (e: Exception) { /* depth not available */ }");
1778
- this.indentLevel--;
1779
- this.emit("}");
1780
- this.emit("");
1781
- }
1782
- if (this.compositionHasTrait(composition, "portal_peek_through")) {
1783
- this.emit("// Portal peek through: tilt phone to reveal holographic layer");
1784
- this.emit("val sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager");
1785
- this.emit("val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)");
1786
- this.emit("val portalTiltThreshold = 30.0f // degrees");
1787
- this.emit("sensorManager.registerListener(object : SensorEventListener {");
1788
- this.indentLevel++;
1789
- this.emit("override fun onSensorChanged(event: SensorEvent) {");
1790
- this.indentLevel++;
1791
- this.emit("val tiltAngle = Math.toDegrees(Math.atan2(event.values[1].toDouble(), event.values[2].toDouble())).toFloat()");
1792
- this.emit("portalRenderer?.setPortalVisibility(Math.abs(tiltAngle) > portalTiltThreshold)");
1793
- this.indentLevel--;
1794
- this.emit("}");
1795
- this.emit("override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}");
1796
- this.indentLevel--;
1797
- this.emit("}, accelerometer, SensorManager.SENSOR_DELAY_UI)");
1798
- this.emit("");
1799
- }
1800
- if (this.compositionHasTrait(composition, "portal_boundary")) {
1801
- this.emit("// Portal boundary: configurable shape (circle/rect)");
1802
- this.emit("val portalBoundary = PortalBoundary()");
1803
- this.emit("portalBoundary.shape = PortalBoundary.Shape.CIRCLE // configurable");
1804
- this.emit("portalBoundary.radius = 1.5f");
1805
- this.emit("portalRenderer?.setBoundary(portalBoundary)");
1806
- this.emit("");
1807
- }
1808
- if (this.compositionHasTrait(composition, "portal_lighting_match")) {
1809
- this.emit("// Portal lighting match: use ARCore light estimation");
1810
- this.emit("arFragment.arSceneView.scene.addOnUpdateListener { _ ->");
1811
- this.indentLevel++;
1812
- this.emit("val frame = arFragment.arSceneView.arFrame ?: return@addOnUpdateListener");
1813
- this.emit("val lightEstimate = frame.lightEstimate");
1814
- this.emit("if (lightEstimate != null && lightEstimate.state == LightEstimate.State.VALID) {");
1815
- this.indentLevel++;
1816
- this.emit("val intensity = lightEstimate.pixelIntensity");
1817
- this.emit("val colorCorrection = FloatArray(4)");
1818
- this.emit("lightEstimate.getColorCorrection(colorCorrection, 0)");
1819
- this.emit("portalRenderer?.updateLighting(intensity, colorCorrection)");
1820
- this.indentLevel--;
1821
- this.emit("}");
1822
- this.indentLevel--;
1823
- this.emit("}");
1824
- this.emit("");
1825
- }
1826
- this.emit('print("[HoloScript] Portal AR initialized")');
1827
- this.indentLevel--;
1828
- this.emit("}");
1829
- }
1830
- // === Camera Hand Tracking Methods (M.010.04) ===
1831
- hasHandTrackingTraits(composition) {
1832
- const handTraitNames = CAMERA_HAND_TRACKING_TRAITS;
1833
- for (const obj of composition.objects || []) {
1834
- for (const trait of obj.traits || []) {
1835
- const name = typeof trait === "string" ? trait : trait.name;
1836
- if (handTraitNames.includes(name)) return true;
1837
- }
1838
- }
1839
- return false;
1840
- }
1841
- emitHandTrackingSetup(composition) {
1842
- this.emit("");
1843
- this.emit("// === Camera Hand Tracking: MediaPipe Hands (M.010.04) ===");
1844
- this.emit("private var handsolution: com.google.mediapipe.solutions.hands.Hands? = null");
1845
- this.emit("");
1846
- const twoHands = this.compositionHasTrait(composition, "camera_hand_two_hands");
1847
- const maxHands = twoHands ? 2 : 1;
1848
- const hasPinch = this.compositionHasTrait(composition, "camera_hand_gesture_pinch");
1849
- const hasPoint = this.compositionHasTrait(composition, "camera_hand_gesture_point");
1850
- const hasPalm = this.compositionHasTrait(composition, "camera_hand_gesture_palm");
1851
- const hasFist = this.compositionHasTrait(composition, "camera_hand_gesture_fist");
1852
- const hasConfidence = this.compositionHasTrait(composition, "camera_hand_confidence");
1853
- const hasSkeleton = this.compositionHasTrait(composition, "camera_hand_skeleton");
1854
- const hasToSpatial = this.compositionHasTrait(composition, "camera_hand_to_spatial");
1855
- this.emit("private fun setupHandTracking() {");
1856
- this.indentLevel++;
1857
- this.emit("val options = com.google.mediapipe.solutions.hands.HandsOptions.builder()");
1858
- this.emit(` .setMaxNumHands(${maxHands})`);
1859
- this.emit(" .setStaticImageMode(false)");
1860
- this.emit(" .setRunningMode(com.google.mediapipe.solutions.hands.Hands.RUNNING_MODE_LIVE_STREAM)");
1861
- if (hasConfidence) {
1862
- this.emit(" .setMinHandDetectionConfidence(0.7f)");
1863
- } else {
1864
- this.emit(" .setMinHandDetectionConfidence(0.5f)");
1865
- }
1866
- this.emit(" .setMinTrackingConfidence(0.5f)");
1867
- this.emit(" .build()");
1868
- this.emit("");
1869
- this.emit("handsolution = com.google.mediapipe.solutions.hands.Hands(this, options)");
1870
- this.emit("handsolution?.setResultListener { result ->");
1871
- this.indentLevel++;
1872
- this.emit("processHandResults(result)");
1873
- this.indentLevel--;
1874
- this.emit("}");
1875
- this.emit("");
1876
- this.emit("// Start CameraX front camera feed");
1877
- this.emit("startCameraForHandTracking()");
1878
- this.indentLevel--;
1879
- this.emit("}");
1880
- this.emit("");
1881
- this.emit("private fun startCameraForHandTracking() {");
1882
- this.indentLevel++;
1883
- this.emit("val cameraProviderFuture = androidx.camera.lifecycle.ProcessCameraProvider.getInstance(this)");
1884
- this.emit("cameraProviderFuture.addListener({");
1885
- this.indentLevel++;
1886
- this.emit("val cameraProvider = cameraProviderFuture.get()");
1887
- this.emit("val preview = androidx.camera.core.Preview.Builder().build()");
1888
- this.emit("val imageAnalysis = androidx.camera.core.ImageAnalysis.Builder()");
1889
- this.emit(" .setBackpressureStrategy(androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)");
1890
- this.emit(" .build()");
1891
- this.emit("imageAnalysis.setAnalyzer(java.util.concurrent.Executors.newSingleThreadExecutor()) { imageProxy ->");
1892
- this.indentLevel++;
1893
- this.emit("handsolution?.send(imageProxy)");
1894
- this.indentLevel--;
1895
- this.emit("}");
1896
- this.emit("val cameraSelector = androidx.camera.core.CameraSelector.DEFAULT_FRONT_CAMERA");
1897
- this.emit("cameraProvider.unbindAll()");
1898
- this.emit("cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)");
1899
- this.indentLevel--;
1900
- this.emit("}, androidx.core.content.ContextCompat.getMainExecutor(this))");
1901
- this.indentLevel--;
1902
- this.emit("}");
1903
- this.emit("");
1904
- this.emit("private fun processHandResults(result: com.google.mediapipe.solutions.hands.HandsResult) {");
1905
- this.indentLevel++;
1906
- this.emit("if (result.multiHandLandmarks().isEmpty()) return");
1907
- this.emit("");
1908
- this.emit("for ((handIndex, landmarks) in result.multiHandLandmarks().withIndex()) {");
1909
- this.indentLevel++;
1910
- if (hasSkeleton) {
1911
- this.emit("// 21-joint skeleton data");
1912
- this.emit("val wrist = landmarks.landmarkList[0]");
1913
- this.emit("val thumbCmc = landmarks.landmarkList[1]");
1914
- this.emit("val thumbMcp = landmarks.landmarkList[2]");
1915
- this.emit("val thumbIp = landmarks.landmarkList[3]");
1916
- this.emit("val thumbTip = landmarks.landmarkList[4]");
1917
- this.emit("val indexMcp = landmarks.landmarkList[5]");
1918
- this.emit("val indexPip = landmarks.landmarkList[6]");
1919
- this.emit("val indexDip = landmarks.landmarkList[7]");
1920
- this.emit("val indexTip = landmarks.landmarkList[8]");
1921
- this.emit("val middleMcp = landmarks.landmarkList[9]");
1922
- this.emit("val middlePip = landmarks.landmarkList[10]");
1923
- this.emit("val middleDip = landmarks.landmarkList[11]");
1924
- this.emit("val middleTip = landmarks.landmarkList[12]");
1925
- this.emit("val ringMcp = landmarks.landmarkList[13]");
1926
- this.emit("val ringPip = landmarks.landmarkList[14]");
1927
- this.emit("val ringDip = landmarks.landmarkList[15]");
1928
- this.emit("val ringTip = landmarks.landmarkList[16]");
1929
- this.emit("val pinkyMcp = landmarks.landmarkList[17]");
1930
- this.emit("val pinkyPip = landmarks.landmarkList[18]");
1931
- this.emit("val pinkyDip = landmarks.landmarkList[19]");
1932
- this.emit("val pinkyTip = landmarks.landmarkList[20]");
1933
- this.emit("");
1934
- } else {
1935
- this.emit("// Key landmarks for gesture recognition");
1936
- this.emit("val thumbTip = landmarks.landmarkList[4]");
1937
- this.emit("val indexMcp = landmarks.landmarkList[5]");
1938
- this.emit("val indexPip = landmarks.landmarkList[6]");
1939
- this.emit("val indexTip = landmarks.landmarkList[8]");
1940
- this.emit("val middleMcp = landmarks.landmarkList[9]");
1941
- this.emit("val middlePip = landmarks.landmarkList[10]");
1942
- this.emit("val middleTip = landmarks.landmarkList[12]");
1943
- this.emit("val ringMcp = landmarks.landmarkList[13]");
1944
- this.emit("val ringPip = landmarks.landmarkList[14]");
1945
- this.emit("val ringTip = landmarks.landmarkList[16]");
1946
- this.emit("val pinkyMcp = landmarks.landmarkList[17]");
1947
- this.emit("val pinkyPip = landmarks.landmarkList[18]");
1948
- this.emit("val pinkyTip = landmarks.landmarkList[20]");
1949
- this.emit("");
1950
- }
1951
- if (hasConfidence) {
1952
- this.emit("// Filter low-confidence landmarks");
1953
- this.emit("val minConfidence = 0.7f");
1954
- this.emit("if (thumbTip.visibility < minConfidence || indexTip.visibility < minConfidence) {");
1955
- this.indentLevel++;
1956
- this.emit("continue");
1957
- this.indentLevel--;
1958
- this.emit("}");
1959
- this.emit("");
1960
- }
1961
- if (hasPinch) {
1962
- this.emit("// Pinch gesture: thumb tip close to index tip");
1963
- this.emit("val pinchDist = Math.sqrt(");
1964
- this.emit(" Math.pow((thumbTip.x - indexTip.x).toDouble(), 2.0) +");
1965
- this.emit(" Math.pow((thumbTip.y - indexTip.y).toDouble(), 2.0)");
1966
- this.emit(").toFloat()");
1967
- this.emit("if (pinchDist < 0.05f) {");
1968
- this.indentLevel++;
1969
- this.emit('android.util.Log.d("HoloScript", "Hand $handIndex: PINCH detected")');
1970
- if (hasToSpatial) {
1971
- this.emit('onSpatialInput("pinch", handIndex, thumbTip.x, thumbTip.y, thumbTip.z)');
1972
- }
1973
- this.indentLevel--;
1974
- this.emit("}");
1975
- this.emit("");
1976
- }
1977
- if (hasPoint) {
1978
- this.emit("// Point gesture: index extended, others curled");
1979
- this.emit("val indexExtended = indexTip.y < indexMcp.y");
1980
- this.emit("val middleCurled = middleTip.y > middlePip.y");
1981
- this.emit("val ringCurled = ringTip.y > ringPip.y");
1982
- this.emit("val pinkyCurled = pinkyTip.y > pinkyPip.y");
1983
- this.emit("if (indexExtended && middleCurled && ringCurled && pinkyCurled) {");
1984
- this.indentLevel++;
1985
- this.emit('android.util.Log.d("HoloScript", "Hand $handIndex: POINT detected")');
1986
- if (hasToSpatial) {
1987
- this.emit('onSpatialInput("point", handIndex, indexTip.x, indexTip.y, indexTip.z)');
1988
- }
1989
- this.indentLevel--;
1990
- this.emit("}");
1991
- this.emit("");
1992
- }
1993
- if (hasPalm) {
1994
- this.emit("// Palm gesture: all fingertips above MCPs (open hand)");
1995
- this.emit("val allExtended = indexTip.y < indexMcp.y && middleTip.y < middleMcp.y &&");
1996
- this.emit(" ringTip.y < ringMcp.y && pinkyTip.y < pinkyMcp.y");
1997
- this.emit("if (allExtended) {");
1998
- this.indentLevel++;
1999
- this.emit('android.util.Log.d("HoloScript", "Hand $handIndex: PALM detected")');
2000
- if (hasToSpatial) {
2001
- this.emit('onSpatialInput("palm", handIndex, indexTip.x, indexTip.y, indexTip.z)');
2002
- }
2003
- this.indentLevel--;
2004
- this.emit("}");
2005
- this.emit("");
2006
- }
2007
- if (hasFist) {
2008
- this.emit("// Fist gesture: all fingertips below PIPs (closed hand)");
2009
- this.emit("val allCurled = indexTip.y > indexPip.y && middleTip.y > middlePip.y &&");
2010
- this.emit(" ringTip.y > ringPip.y && pinkyTip.y > pinkyPip.y");
2011
- this.emit("if (allCurled) {");
2012
- this.indentLevel++;
2013
- this.emit('android.util.Log.d("HoloScript", "Hand $handIndex: FIST detected")');
2014
- if (hasToSpatial) {
2015
- this.emit('onSpatialInput("fist", handIndex, indexTip.x, indexTip.y, indexTip.z)');
2016
- }
2017
- this.indentLevel--;
2018
- this.emit("}");
2019
- this.emit("");
2020
- }
2021
- this.indentLevel--;
2022
- this.emit("}");
2023
- this.indentLevel--;
2024
- this.emit("}");
2025
- if (hasToSpatial) {
2026
- this.emit("");
2027
- this.emit("private fun onSpatialInput(gesture: String, handIndex: Int, x: Float, y: Float, z: Float) {");
2028
- this.indentLevel++;
2029
- this.emit("// Bridge to HoloScript spatial_input event system");
2030
- this.emit("val event = mapOf(");
2031
- this.indentLevel++;
2032
- this.emit('"type" to "hand_gesture",');
2033
- this.emit('"gesture" to gesture,');
2034
- this.emit('"handIndex" to handIndex,');
2035
- this.emit('"x" to x, "y" to y, "z" to z');
2036
- this.indentLevel--;
2037
- this.emit(")");
2038
- this.emit('android.util.Log.d("HoloScript", "SpatialInput: $event")');
2039
- this.indentLevel--;
2040
- this.emit("}");
2041
- }
2042
- }
2043
- // === Spatial Authoring Methods (M.010.08) ===
2044
- hasAuthoringTraits(composition) {
2045
- const authoringNames = SPATIAL_AUTHORING_TRAITS;
2046
- for (const obj of composition.objects || []) {
2047
- for (const trait of obj.traits || []) {
2048
- const name = typeof trait === "string" ? trait : trait.name;
2049
- if (authoringNames.includes(name)) return true;
2050
- }
2051
- }
2052
- return false;
2053
- }
2054
- emitAuthoringSetup(composition) {
2055
- this.lines = [];
2056
- this.indentLevel = 0;
2057
- this.emit("// === Spatial Authoring Setup (M.010.08) ===");
2058
- this.emit("");
2059
- const hasGyro = this.compositionHasTrait(composition, "author_gyro_place");
2060
- const hasPinch = this.compositionHasTrait(composition, "author_pinch_scale");
2061
- const hasSwipe = this.compositionHasTrait(composition, "author_swipe_browse");
2062
- const hasVoice = this.compositionHasTrait(composition, "author_voice_cmd");
2063
- const hasShake = this.compositionHasTrait(composition, "author_shake_undo");
2064
- if (hasGyro) {
2065
- this.emit(`private val gyroFilterAlpha = ${SPATIAL_AUTHORING_DEFAULTS.gyroFilterAlpha}f`);
2066
- this.emit("private var sensorManager: android.hardware.SensorManager? = null");
2067
- this.emit("private var rotationSensor: android.hardware.Sensor? = null");
2068
- }
2069
- if (hasPinch) {
2070
- this.emit(`private val pinchScaleMin = ${SPATIAL_AUTHORING_DEFAULTS.pinchScaleMin}f`);
2071
- this.emit(`private val pinchScaleMax = ${SPATIAL_AUTHORING_DEFAULTS.pinchScaleMax}f`);
2072
- this.emit("private var scaleGestureDetector: android.view.ScaleGestureDetector? = null");
2073
- }
2074
- if (hasSwipe) {
2075
- this.emit("private var gestureDetector: android.view.GestureDetector? = null");
2076
- }
2077
- if (hasVoice) {
2078
- this.emit(`private val speechLocale = "${SPATIAL_AUTHORING_DEFAULTS.speechLocale}"`);
2079
- this.emit("private var speechRecognizer: android.speech.SpeechRecognizer? = null");
2080
- }
2081
- if (hasShake) {
2082
- this.emit(`private val shakeThreshold = ${SPATIAL_AUTHORING_DEFAULTS.shakeThreshold}f`);
2083
- this.emit(`private val undoStackDepth = ${SPATIAL_AUTHORING_DEFAULTS.undoStackDepth}`);
2084
- this.emit("private val undoStack = ArrayDeque<() -> Unit>()");
2085
- }
2086
- return this.lines.join("\n");
2087
- }
2088
- emitAuthoringInlineSetup(composition) {
2089
- this.emit("");
2090
- this.emit("// --- Spatial Authoring inline setup (M.010.08) ---");
2091
- if (this.compositionHasTrait(composition, "author_gyro_place")) {
2092
- this.emit("private fun setupGyroPlacement() {");
2093
- this.indentLevel++;
2094
- this.emit("sensorManager = getSystemService(SENSOR_SERVICE) as android.hardware.SensorManager");
2095
- this.emit("rotationSensor = sensorManager?.getDefaultSensor(android.hardware.Sensor.TYPE_ROTATION_VECTOR)");
2096
- this.indentLevel--;
2097
- this.emit("}");
2098
- }
2099
- if (this.compositionHasTrait(composition, "author_pinch_scale")) {
2100
- this.emit("private fun setupPinchScale() {");
2101
- this.indentLevel++;
2102
- this.emit("scaleGestureDetector = android.view.ScaleGestureDetector(this, object : android.view.ScaleGestureDetector.SimpleOnScaleGestureListener() {");
2103
- this.indentLevel++;
2104
- this.emit("override fun onScale(detector: android.view.ScaleGestureDetector): Boolean {");
2105
- this.indentLevel++;
2106
- this.emit("val factor = detector.scaleFactor.coerceIn(pinchScaleMin, pinchScaleMax)");
2107
- this.emit("selectedNode?.localScale = selectedNode?.localScale?.let { it.scaled(factor) }");
2108
- this.emit("return true");
2109
- this.indentLevel--;
2110
- this.emit("}");
2111
- this.indentLevel--;
2112
- this.emit("})");
2113
- this.indentLevel--;
2114
- this.emit("}");
2115
- }
2116
- if (this.compositionHasTrait(composition, "author_shake_undo")) {
2117
- this.emit("private fun setupShakeUndo() {");
2118
- this.indentLevel++;
2119
- this.emit("val accelerometer = sensorManager?.getDefaultSensor(android.hardware.Sensor.TYPE_ACCELEROMETER)");
2120
- this.emit("// Shake detection triggers undoStack.removeLastOrNull()?.invoke()");
2121
- this.indentLevel--;
2122
- this.emit("}");
2123
- }
2124
- }
2125
- // ─── Haptic Feedback (M.010.05) ─────────────────────────────────────
2126
- hasHapticTraits(composition) {
2127
- const names = HAPTIC_FEEDBACK_TRAITS;
2128
- for (const obj of composition.objects || []) {
2129
- for (const trait of obj.traits || []) {
2130
- const name = typeof trait === "string" ? trait : trait.name;
2131
- if (names.includes(name)) return true;
2132
- }
2133
- }
2134
- return false;
2135
- }
2136
- emitHapticSetup(composition) {
2137
- this.lines = [];
2138
- this.indentLevel = 0;
2139
- const pkg = this.options.packageName;
2140
- const cls = this.options.className;
2141
- this.emit("// Auto-generated by HoloScript AndroidCompiler \u2014 Haptic Feedback");
2142
- this.emit(`// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`);
2143
- this.emit("// Requires Android API 26+ (VibrationEffect)");
2144
- this.emit("");
2145
- this.emit(`package ${pkg}`);
2146
- this.emit("");
2147
- this.emit("import android.content.Context");
2148
- this.emit("import android.os.Build");
2149
- this.emit("import android.os.VibrationEffect");
2150
- this.emit("import android.os.Vibrator");
2151
- this.emit("import android.os.VibratorManager");
2152
- this.emit("import android.view.HapticFeedbackConstants");
2153
- this.emit("");
2154
- this.emit(`class ${cls}HapticManager(private val context: Context) {`);
2155
- this.indentLevel++;
2156
- this.emit("");
2157
- this.emit("private val vibrator: Vibrator = if (Build.VERSION.SDK_INT >= 31) {");
2158
- this.indentLevel++;
2159
- this.emit("(context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager).defaultVibrator");
2160
- this.indentLevel--;
2161
- this.emit("} else {");
2162
- this.indentLevel++;
2163
- this.emit('@Suppress("DEPRECATION") context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator');
2164
- this.indentLevel--;
2165
- this.emit("}");
2166
- this.emit("");
2167
- this.emit("private var intensityMultiplier: Float = 1.0f");
2168
- this.emit("private var enabled: Boolean = true");
2169
- this.emit("");
2170
- if (this.compositionHasTrait(composition, "haptic_enable")) {
2171
- this.emit("fun setEnabled(on: Boolean) { enabled = on }");
2172
- this.emit("");
2173
- }
2174
- if (this.compositionHasTrait(composition, "haptic_intensity")) {
2175
- this.emit("fun setIntensity(value: Float) { intensityMultiplier = value.coerceIn(0f, 1f) }");
2176
- this.emit("");
2177
- }
2178
- this.emit("fun onTouch() { vibrate(VibrationEffect.EFFECT_CLICK) }");
2179
- this.emit("fun onGrab() { vibrate(VibrationEffect.EFFECT_HEAVY_CLICK) }");
2180
- this.emit("fun onThrow() { vibrate(VibrationEffect.EFFECT_DOUBLE_CLICK) }");
2181
- this.emit("fun onImpact() { vibrate(VibrationEffect.EFFECT_TICK) }");
2182
- this.emit("fun onSelect() { vibrate(VibrationEffect.EFFECT_CLICK) }");
2183
- this.emit("");
2184
- if (this.compositionHasTrait(composition, "haptic_proximity")) {
2185
- this.emit("fun onProximity(distance: Float) {");
2186
- this.indentLevel++;
2187
- this.emit("if (!enabled) return");
2188
- this.emit("val amplitude = ((1.0f - distance.coerceIn(0f, 1f)) * 255 * intensityMultiplier).toInt()");
2189
- this.emit("vibrator.vibrate(VibrationEffect.createOneShot(50, amplitude.coerceIn(1, 255)))");
2190
- this.indentLevel--;
2191
- this.emit("}");
2192
- this.emit("");
2193
- }
2194
- if (this.compositionHasTrait(composition, "haptic_pattern_library")) {
2195
- this.emit("fun playPattern(timings: LongArray, amplitudes: IntArray) {");
2196
- this.indentLevel++;
2197
- this.emit("if (!enabled) return");
2198
- this.emit("vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, -1))");
2199
- this.indentLevel--;
2200
- this.emit("}");
2201
- this.emit("");
2202
- }
2203
- this.emit("private fun vibrate(effectId: Int) {");
2204
- this.indentLevel++;
2205
- this.emit("if (!enabled) return");
2206
- this.emit("vibrator.vibrate(VibrationEffect.createPredefined(effectId))");
2207
- this.indentLevel--;
2208
- this.emit("}");
2209
- this.indentLevel--;
2210
- this.emit("}");
2211
- return this.lines.join("\n");
2212
- }
2213
- // ─── Nearby Connections (M.010.16) ──────────────────────────────────
2214
- hasNearbyTraits(composition) {
2215
- const names = NEARBY_CONNECTIONS_TRAITS;
2216
- for (const obj of composition.objects || []) {
2217
- for (const trait of obj.traits || []) {
2218
- const name = typeof trait === "string" ? trait : trait.name;
2219
- if (names.includes(name)) return true;
2220
- }
2221
- }
2222
- return false;
2223
- }
2224
- emitNearbySetup(composition) {
2225
- this.lines = [];
2226
- this.indentLevel = 0;
2227
- const pkg = this.options.packageName;
2228
- const cls = this.options.className;
2229
- this.emit("// Auto-generated by HoloScript AndroidCompiler \u2014 Nearby Connections");
2230
- this.emit(`// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`);
2231
- this.emit("// Requires Google Play Services Nearby");
2232
- this.emit("");
2233
- this.emit(`package ${pkg}`);
2234
- this.emit("");
2235
- this.emit("import android.content.Context");
2236
- this.emit("import android.util.Log");
2237
- this.emit("import com.google.android.gms.nearby.Nearby");
2238
- this.emit("import com.google.android.gms.nearby.connection.*");
2239
- this.emit("");
2240
- this.emit(`class ${cls}NearbyManager(private val context: Context) {`);
2241
- this.indentLevel++;
2242
- this.emit("");
2243
- this.emit('private val TAG = "HoloNearby"');
2244
- this.emit('private val SERVICE_ID = "com.holoscript.nearby"');
2245
- this.emit("private val connectedEndpoints = mutableSetOf<String>()");
2246
- this.emit("");
2247
- this.emit("private val connectionCallback = object : ConnectionLifecycleCallback() {");
2248
- this.indentLevel++;
2249
- this.emit("override fun onConnectionInitiated(endpointId: String, info: ConnectionInfo) {");
2250
- this.indentLevel++;
2251
- this.emit("Nearby.getConnectionsClient(context).acceptConnection(endpointId, payloadCallback)");
2252
- this.indentLevel--;
2253
- this.emit("}");
2254
- this.emit("override fun onConnectionResult(endpointId: String, result: ConnectionResolution) {");
2255
- this.indentLevel++;
2256
- this.emit("if (result.status.isSuccess) { connectedEndpoints.add(endpointId) }");
2257
- this.indentLevel--;
2258
- this.emit("}");
2259
- this.emit("override fun onDisconnected(endpointId: String) { connectedEndpoints.remove(endpointId) }");
2260
- this.indentLevel--;
2261
- this.emit("}");
2262
- this.emit("");
2263
- this.emit("private val payloadCallback = object : PayloadCallback() {");
2264
- this.indentLevel++;
2265
- this.emit("override fun onPayloadReceived(endpointId: String, payload: Payload) {");
2266
- this.indentLevel++;
2267
- this.emit('Log.d(TAG, "Payload received from $endpointId")');
2268
- this.indentLevel--;
2269
- this.emit("}");
2270
- this.emit("override fun onPayloadTransferUpdate(endpointId: String, update: PayloadTransferUpdate) {}");
2271
- this.indentLevel--;
2272
- this.emit("}");
2273
- this.emit("");
2274
- if (this.compositionHasTrait(composition, "nearby_advertise")) {
2275
- this.emit("fun startAdvertising() {");
2276
- this.indentLevel++;
2277
- this.emit("val options = AdvertisingOptions.Builder().setStrategy(Strategy.P2P_CLUSTER).build()");
2278
- this.emit('Nearby.getConnectionsClient(context).startAdvertising("HoloDevice", SERVICE_ID, connectionCallback, options)');
2279
- this.indentLevel--;
2280
- this.emit("}");
2281
- this.emit("");
2282
- }
2283
- if (this.compositionHasTrait(composition, "nearby_discover")) {
2284
- this.emit("fun startDiscovery() {");
2285
- this.indentLevel++;
2286
- this.emit("val options = DiscoveryOptions.Builder().setStrategy(Strategy.P2P_CLUSTER).build()");
2287
- this.emit("Nearby.getConnectionsClient(context).startDiscovery(SERVICE_ID, object : EndpointDiscoveryCallback() {");
2288
- this.indentLevel++;
2289
- this.emit("override fun onEndpointFound(endpointId: String, info: DiscoveredEndpointInfo) {");
2290
- this.indentLevel++;
2291
- this.emit('Nearby.getConnectionsClient(context).requestConnection("HoloDevice", endpointId, connectionCallback)');
2292
- this.indentLevel--;
2293
- this.emit("}");
2294
- this.emit('override fun onEndpointLost(endpointId: String) { Log.d(TAG, "Lost $endpointId") }');
2295
- this.indentLevel--;
2296
- this.emit("}, options)");
2297
- this.indentLevel--;
2298
- this.emit("}");
2299
- this.emit("");
2300
- }
2301
- if (this.compositionHasTrait(composition, "nearby_broadcast")) {
2302
- this.emit("fun broadcast(data: ByteArray) {");
2303
- this.indentLevel++;
2304
- this.emit("val payload = Payload.fromBytes(data)");
2305
- this.emit("connectedEndpoints.forEach { Nearby.getConnectionsClient(context).sendPayload(it, payload) }");
2306
- this.indentLevel--;
2307
- this.emit("}");
2308
- this.emit("");
2309
- }
2310
- this.emit("fun disconnect() {");
2311
- this.indentLevel++;
2312
- this.emit("Nearby.getConnectionsClient(context).stopAllEndpoints()");
2313
- this.emit("connectedEndpoints.clear()");
2314
- this.indentLevel--;
2315
- this.emit("}");
2316
- this.indentLevel--;
2317
- this.emit("}");
2318
- return this.lines.join("\n");
2319
- }
2320
- // ─── Foldable Display (M.010.17) ────────────────────────────────────
2321
- hasFoldableTraits(composition) {
2322
- const names = FOLDABLE_DISPLAY_TRAITS;
2323
- for (const obj of composition.objects || []) {
2324
- for (const trait of obj.traits || []) {
2325
- const name = typeof trait === "string" ? trait : trait.name;
2326
- if (names.includes(name)) return true;
2327
- }
2328
- }
2329
- return false;
2330
- }
2331
- emitFoldableSetup(composition) {
2332
- this.lines = [];
2333
- this.indentLevel = 0;
2334
- const pkg = this.options.packageName;
2335
- const cls = this.options.className;
2336
- this.emit("// Auto-generated by HoloScript AndroidCompiler \u2014 Foldable Display");
2337
- this.emit(`// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`);
2338
- this.emit("// Requires Jetpack WindowManager");
2339
- this.emit("");
2340
- this.emit(`package ${pkg}`);
2341
- this.emit("");
2342
- this.emit("import android.app.Activity");
2343
- this.emit("import android.util.Log");
2344
- this.emit("import androidx.lifecycle.ViewModel");
2345
- this.emit("import androidx.lifecycle.viewModelScope");
2346
- this.emit("import androidx.window.layout.FoldingFeature");
2347
- this.emit("import androidx.window.layout.WindowInfoTracker");
2348
- this.emit("import kotlinx.coroutines.flow.MutableStateFlow");
2349
- this.emit("import kotlinx.coroutines.flow.StateFlow");
2350
- this.emit("import kotlinx.coroutines.launch");
2351
- this.emit("");
2352
- this.emit(`class ${cls}FoldableManager : ViewModel() {`);
2353
- this.indentLevel++;
2354
- this.emit("");
2355
- this.emit("private val _foldState = MutableStateFlow<FoldState>(FoldState.FLAT)");
2356
- this.emit("val foldState: StateFlow<FoldState> = _foldState");
2357
- this.emit("");
2358
- this.emit("private val _hingeAngle = MutableStateFlow(180f)");
2359
- this.emit("val hingeAngle: StateFlow<Float> = _hingeAngle");
2360
- this.emit("");
2361
- this.emit("enum class FoldState { FLAT, HALF_OPENED, FOLDED }");
2362
- this.emit("");
2363
- this.emit("fun observeWindowLayout(activity: Activity) {");
2364
- this.indentLevel++;
2365
- this.emit("viewModelScope.launch {");
2366
- this.indentLevel++;
2367
- this.emit("WindowInfoTracker.getOrCreate(activity).windowLayoutInfo(activity).collect { layoutInfo ->");
2368
- this.indentLevel++;
2369
- this.emit("val foldingFeature = layoutInfo.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()");
2370
- this.emit("if (foldingFeature != null) {");
2371
- this.indentLevel++;
2372
- this.emit("_foldState.value = when (foldingFeature.state) {");
2373
- this.indentLevel++;
2374
- this.emit("FoldingFeature.State.HALF_OPENED -> FoldState.HALF_OPENED");
2375
- this.emit("FoldingFeature.State.FLAT -> FoldState.FLAT");
2376
- this.emit("else -> FoldState.FOLDED");
2377
- this.indentLevel--;
2378
- this.emit("}");
2379
- if (this.compositionHasTrait(composition, "foldable_hinge_angle")) {
2380
- this.emit("// Hinge angle exposed via foldingFeature orientation");
2381
- this.emit('Log.d("HoloFoldable", "Fold state: ${foldingFeature.state}, orientation: ${foldingFeature.orientation}")');
2382
- }
2383
- this.indentLevel--;
2384
- this.emit("}");
2385
- this.indentLevel--;
2386
- this.emit("}");
2387
- this.indentLevel--;
2388
- this.emit("}");
2389
- this.indentLevel--;
2390
- this.emit("}");
2391
- this.emit("");
2392
- if (this.compositionHasTrait(composition, "foldable_split_view")) {
2393
- this.emit("fun isSplitViewRecommended(): Boolean = _foldState.value == FoldState.HALF_OPENED");
2394
- this.emit("");
2395
- }
2396
- if (this.compositionHasTrait(composition, "foldable_tabletop")) {
2397
- this.emit("fun isTabletopMode(): Boolean = _foldState.value == FoldState.HALF_OPENED");
2398
- this.emit("");
2399
- }
2400
- this.indentLevel--;
2401
- this.emit("}");
2402
- return this.lines.join("\n");
2403
- }
2404
- // ─── Samsung DeX (M.010.18) ─────────────────────────────────────────
2405
- hasDexTraits(composition) {
2406
- const names = SAMSUNG_DEX_TRAITS;
2407
- for (const obj of composition.objects || []) {
2408
- for (const trait of obj.traits || []) {
2409
- const name = typeof trait === "string" ? trait : trait.name;
2410
- if (names.includes(name)) return true;
2411
- }
2412
- }
2413
- return false;
2414
- }
2415
- emitDexSetup(composition) {
2416
- this.lines = [];
2417
- this.indentLevel = 0;
2418
- const pkg = this.options.packageName;
2419
- const cls = this.options.className;
2420
- this.emit("// Auto-generated by HoloScript AndroidCompiler \u2014 Samsung DeX");
2421
- this.emit(`// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`);
2422
- this.emit("// Samsung DeX mode detection and desktop UI transition");
2423
- this.emit("");
2424
- this.emit(`package ${pkg}`);
2425
- this.emit("");
2426
- this.emit("import android.app.Activity");
2427
- this.emit("import android.content.res.Configuration");
2428
- this.emit("import android.util.Log");
2429
- this.emit("");
2430
- this.emit(`class ${cls}DexManager(private val activity: Activity) {`);
2431
- this.indentLevel++;
2432
- this.emit("");
2433
- this.emit('private val TAG = "HoloDex"');
2434
- this.emit("");
2435
- this.emit("fun isDeXMode(): Boolean {");
2436
- this.indentLevel++;
2437
- this.emit("val config = activity.resources.configuration");
2438
- this.emit("return try {");
2439
- this.indentLevel++;
2440
- this.emit("val configClass = config.javaClass");
2441
- this.emit('val semDesktopModeEnabled = configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass)');
2442
- this.emit('val enabledField = configClass.getField("semDesktopModeEnabled").getInt(config)');
2443
- this.emit("enabledField == semDesktopModeEnabled");
2444
- this.indentLevel--;
2445
- this.emit("} catch (e: Exception) { false }");
2446
- this.indentLevel--;
2447
- this.emit("}");
2448
- this.emit("");
2449
- if (this.compositionHasTrait(composition, "dex_handoff")) {
2450
- this.emit("fun onDexModeChanged(isDex: Boolean) {");
2451
- this.indentLevel++;
2452
- this.emit("if (isDex) {");
2453
- this.indentLevel++;
2454
- this.emit('Log.d(TAG, "DeX connected \u2014 switching to desktop 3D editor")');
2455
- this.emit("enableDesktopLayout()");
2456
- this.indentLevel--;
2457
- this.emit("} else {");
2458
- this.indentLevel++;
2459
- this.emit('Log.d(TAG, "DeX disconnected \u2014 switching to mobile AR")');
2460
- this.emit("enableMobileLayout()");
2461
- this.indentLevel--;
2462
- this.emit("}");
2463
- this.indentLevel--;
2464
- this.emit("}");
2465
- this.emit("");
2466
- }
2467
- if (this.compositionHasTrait(composition, "dex_desktop_controls")) {
2468
- this.emit("fun enableDesktopLayout() {");
2469
- this.indentLevel++;
2470
- this.emit("// Enable mouse/keyboard controls, enlarge UI targets");
2471
- this.emit('Log.d(TAG, "Desktop layout enabled with mouse/keyboard support")');
2472
- this.indentLevel--;
2473
- this.emit("}");
2474
- this.emit("");
2475
- }
2476
- if (this.compositionHasTrait(composition, "dex_resolution_adapt")) {
2477
- this.emit("fun adaptResolution() {");
2478
- this.indentLevel++;
2479
- this.emit("val display = activity.windowManager.defaultDisplay");
2480
- this.emit("val metrics = android.util.DisplayMetrics()");
2481
- this.emit('@Suppress("DEPRECATION") display.getRealMetrics(metrics)');
2482
- this.emit('Log.d(TAG, "External display: ${metrics.widthPixels}x${metrics.heightPixels}")');
2483
- this.indentLevel--;
2484
- this.emit("}");
2485
- this.emit("");
2486
- }
2487
- this.emit("private fun enableMobileLayout() {");
2488
- this.indentLevel++;
2489
- this.emit('Log.d(TAG, "Mobile AR layout restored")');
2490
- this.indentLevel--;
2491
- this.emit("}");
2492
- this.indentLevel--;
2493
- this.emit("}");
2494
- return this.lines.join("\n");
2495
- }
2496
- // ─── Google Lens (M.010.20) ─────────────────────────────────────────
2497
- hasLensTraits(composition) {
2498
- const names = GOOGLE_LENS_TRAITS;
2499
- for (const obj of composition.objects || []) {
2500
- for (const trait of obj.traits || []) {
2501
- const name = typeof trait === "string" ? trait : trait.name;
2502
- if (names.includes(name)) return true;
2503
- }
2504
- }
2505
- return false;
2506
- }
2507
- emitLensSetup(composition) {
2508
- this.lines = [];
2509
- this.indentLevel = 0;
2510
- const pkg = this.options.packageName;
2511
- const cls = this.options.className;
2512
- const usedTraits = /* @__PURE__ */ new Set();
2513
- for (const obj of composition.objects || []) {
2514
- for (const trait of obj.traits || []) {
2515
- const name = typeof trait === "string" ? trait : trait.name;
2516
- if (GOOGLE_LENS_TRAITS.includes(name)) {
2517
- usedTraits.add(name);
2518
- }
2519
- }
2520
- }
2521
- this.emit("// Auto-generated by HoloScript AndroidCompiler \u2014 Google Lens Integration");
2522
- this.emit(`// Source: composition "${this.escapeStringValue(composition.name, "Kotlin")}"`);
2523
- this.emit("// Requires ML Kit (vision, text, translate)");
2524
- this.emit("");
2525
- this.emit(`package ${pkg}`);
2526
- this.emit("");
2527
- this.emit("import android.graphics.Bitmap");
2528
- this.emit("import android.util.Log");
2529
- this.emit("import androidx.lifecycle.ViewModel");
2530
- this.emit("import androidx.lifecycle.viewModelScope");
2531
- this.emit("import kotlinx.coroutines.flow.MutableStateFlow");
2532
- this.emit("import kotlinx.coroutines.flow.StateFlow");
2533
- this.emit("import kotlinx.coroutines.launch");
2534
- if (usedTraits.has("lens_recognize") || usedTraits.has("lens_product_id") || usedTraits.has("lens_plant_animal_id") || usedTraits.has("lens_landmark_info")) {
2535
- this.emit("import com.google.mlkit.vision.common.InputImage");
2536
- this.emit("import com.google.mlkit.vision.objects.ObjectDetection");
2537
- this.emit("import com.google.mlkit.vision.objects.defaults.ObjectDetectorOptions");
2538
- this.emit("import com.google.mlkit.vision.label.ImageLabeling");
2539
- this.emit("import com.google.mlkit.vision.label.defaults.ImageLabelerOptions");
2540
- }
2541
- if (usedTraits.has("lens_text_overlay") || usedTraits.has("lens_translate")) {
2542
- this.emit("import com.google.mlkit.vision.text.TextRecognition");
2543
- this.emit("import com.google.mlkit.vision.text.latin.TextRecognizerOptions");
2544
- }
2545
- if (usedTraits.has("lens_translate")) {
2546
- this.emit("import com.google.mlkit.nl.translate.TranslateLanguage");
2547
- this.emit("import com.google.mlkit.nl.translate.Translation");
2548
- this.emit("import com.google.mlkit.nl.translate.TranslatorOptions");
2549
- }
2550
- this.emit("");
2551
- this.emit("data class LensDetection(val label: String, val confidence: Float, val text: String? = null)");
2552
- this.emit("");
2553
- this.emit(`class ${cls}LensManager : ViewModel() {`);
2554
- this.indentLevel++;
2555
- this.emit("");
2556
- this.emit('private val TAG = "HoloLens"');
2557
- this.emit("private val _detections = MutableStateFlow<List<LensDetection>>(emptyList())");
2558
- this.emit("val detections: StateFlow<List<LensDetection>> = _detections");
2559
- this.emit("");
2560
- if (usedTraits.has("lens_recognize")) {
2561
- this.emit("private val objectDetector by lazy {");
2562
- this.indentLevel++;
2563
- this.emit("ObjectDetection.getClient(ObjectDetectorOptions.Builder()");
2564
- this.emit(" .setDetectorMode(ObjectDetectorOptions.STREAM_MODE)");
2565
- this.emit(" .enableMultipleObjects().enableClassification().build())");
2566
- this.indentLevel--;
2567
- this.emit("}");
2568
- this.emit("");
2569
- this.emit("fun recognizeObjects(image: InputImage) {");
2570
- this.indentLevel++;
2571
- this.emit("objectDetector.process(image).addOnSuccessListener { objects ->");
2572
- this.indentLevel++;
2573
- this.emit("_detections.value = objects.flatMap { it.labels }.map { LensDetection(it.text, it.confidence) }");
2574
- this.indentLevel--;
2575
- this.emit("}");
2576
- this.indentLevel--;
2577
- this.emit("}");
2578
- this.emit("");
2579
- }
2580
- if (usedTraits.has("lens_text_overlay")) {
2581
- this.emit("private val textRecognizer by lazy { TextRecognition.getClient(TextRecognizerOptions.Builder().build()) }");
2582
- this.emit("");
2583
- this.emit("fun recognizeText(image: InputImage) {");
2584
- this.indentLevel++;
2585
- this.emit("textRecognizer.process(image).addOnSuccessListener { visionText ->");
2586
- this.indentLevel++;
2587
- this.emit('Log.d(TAG, "Recognized: ${visionText.text}")');
2588
- this.indentLevel--;
2589
- this.emit("}");
2590
- this.indentLevel--;
2591
- this.emit("}");
2592
- this.emit("");
2593
- }
2594
- if (usedTraits.has("lens_translate")) {
2595
- this.emit('fun translateText(text: String, targetLang: String = "es", callback: (String) -> Unit) {');
2596
- this.indentLevel++;
2597
- this.emit("val options = TranslatorOptions.Builder()");
2598
- this.emit(" .setSourceLanguage(TranslateLanguage.ENGLISH)");
2599
- this.emit(" .setTargetLanguage(targetLang).build()");
2600
- this.emit("val translator = Translation.getClient(options)");
2601
- this.emit("translator.downloadModelIfNeeded().addOnSuccessListener {");
2602
- this.indentLevel++;
2603
- this.emit("translator.translate(text).addOnSuccessListener { callback(it) }");
2604
- this.indentLevel--;
2605
- this.emit("}");
2606
- this.indentLevel--;
2607
- this.emit("}");
2608
- this.emit("");
2609
- }
2610
- this.indentLevel--;
2611
- this.emit("}");
2612
- return this.lines.join("\n");
2613
- }
2614
- // =====================================================================
2615
- // WebXR Browser AR (M.010.19)
2616
- // Emits JavaScript/HTML — runs in Chrome on Android, no app install.
2617
- // =====================================================================
2618
- hasWebXRTraits(composition) {
2619
- const names = WEBXR_TRAITS;
2620
- for (const obj of composition.objects || []) {
2621
- for (const trait of obj.traits || []) {
2622
- const name = typeof trait === "string" ? trait : trait.name;
2623
- if (names.includes(name)) return true;
2624
- }
2625
- }
2626
- return false;
2627
- }
2628
- collectWebXRTraits(composition) {
2629
- const usedTraits = /* @__PURE__ */ new Set();
2630
- for (const obj of composition.objects || []) {
2631
- for (const trait of obj.traits || []) {
2632
- const name = typeof trait === "string" ? trait : trait.name;
2633
- if (WEBXR_TRAITS.includes(name)) {
2634
- usedTraits.add(name);
2635
- }
2636
- }
2637
- }
2638
- return usedTraits;
2639
- }
2640
- emitWebXRSetup(composition) {
2641
- this.lines = [];
2642
- this.indentLevel = 0;
2643
- const usedTraits = this.collectWebXRTraits(composition);
2644
- const compositionName = this.escapeStringValue(composition.name, "TypeScript");
2645
- const sessionType = usedTraits.has("webxr_session") ? "immersive-ar" : usedTraits.has("webxr_inline") ? "inline" : "immersive-ar";
2646
- const refSpace = usedTraits.has("webxr_reference_space") ? "local-floor" : "local";
2647
- const requiredFeatures = [];
2648
- if (usedTraits.has("webxr_hit_test")) requiredFeatures.push("hit-test");
2649
- if (usedTraits.has("webxr_anchors")) requiredFeatures.push("anchors");
2650
- if (usedTraits.has("webxr_light_estimation")) requiredFeatures.push("light-estimation");
2651
- if (usedTraits.has("webxr_dom_overlay")) requiredFeatures.push("dom-overlay");
2652
- if (usedTraits.has("webxr_depth_sensing")) requiredFeatures.push("depth-sensing");
2653
- if (usedTraits.has("webxr_hand_tracking")) requiredFeatures.push("hand-tracking");
2654
- if (usedTraits.has("webxr_layers")) requiredFeatures.push("layers");
2655
- this.emit("<!DOCTYPE html>");
2656
- this.emit(`<html lang="en">`);
2657
- this.emit("<head>");
2658
- this.indentLevel++;
2659
- this.emit('<meta charset="utf-8">');
2660
- this.emit('<meta name="viewport" content="width=device-width, initial-scale=1.0">');
2661
- this.emit(`<title>${compositionName} \u2014 WebXR</title>`);
2662
- this.emit("<style>");
2663
- this.indentLevel++;
2664
- this.emit("body { margin: 0; overflow: hidden; }");
2665
- this.emit("canvas { display: block; }");
2666
- if (usedTraits.has("webxr_dom_overlay")) {
2667
- this.emit("#overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; }");
2668
- this.emit("#overlay .ui-panel { pointer-events: auto; background: rgba(0,0,0,0.6); color: #fff; padding: 12px; border-radius: 8px; margin: 16px; font-family: sans-serif; }");
2669
- }
2670
- this.emit("#no-xr { display: none; padding: 20px; text-align: center; font-family: sans-serif; }");
2671
- this.indentLevel--;
2672
- this.emit("</style>");
2673
- this.emit('<script type="importmap">');
2674
- this.emit('{ "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" } }');
2675
- this.emit("</script>");
2676
- this.indentLevel--;
2677
- this.emit("</head>");
2678
- this.emit("<body>");
2679
- this.indentLevel++;
2680
- if (usedTraits.has("webxr_dom_overlay")) {
2681
- this.emit('<div id="overlay">');
2682
- this.indentLevel++;
2683
- this.emit(`<div class="ui-panel">${compositionName}</div>`);
2684
- this.indentLevel--;
2685
- this.emit("</div>");
2686
- }
2687
- this.emit('<div id="no-xr">WebXR not supported on this browser/device.</div>');
2688
- this.emit('<script type="module">');
2689
- this.indentLevel++;
2690
- this.emit("import * as THREE from 'three';");
2691
- this.emit("");
2692
- this.emit("if (!navigator.xr) {");
2693
- this.indentLevel++;
2694
- this.emit("document.getElementById('no-xr').style.display = 'block';");
2695
- this.emit("throw new Error('WebXR not supported');");
2696
- this.indentLevel--;
2697
- this.emit("}");
2698
- this.emit("");
2699
- this.emit("const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });");
2700
- this.emit("renderer.setPixelRatio(window.devicePixelRatio);");
2701
- this.emit("renderer.setSize(window.innerWidth, window.innerHeight);");
2702
- this.emit("renderer.xr.enabled = true;");
2703
- this.emit("document.body.appendChild(renderer.domElement);");
2704
- this.emit("");
2705
- this.emit("const scene = new THREE.Scene();");
2706
- this.emit("const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 100);");
2707
- this.emit("");
2708
- this.emit("scene.add(new THREE.AmbientLight(0xffffff, 0.6));");
2709
- if (usedTraits.has("webxr_light_estimation")) {
2710
- this.emit("");
2711
- this.emit("// WebXR Light Estimation");
2712
- this.emit("const estimatedLight = new THREE.DirectionalLight(0xffffff, 1.0);");
2713
- this.emit("estimatedLight.position.set(0.5, 1, 0.5);");
2714
- this.emit("scene.add(estimatedLight);");
2715
- this.emit("let xrLightProbe = null;");
2716
- }
2717
- this.emit("");
2718
- this.emit("// Scene objects from HoloScript composition");
2719
- for (const obj of composition.objects || []) {
2720
- const objName = obj.name || "Object";
2721
- const shape = this.findObjProp(obj, "shape");
2722
- const color = this.findObjProp(obj, "color");
2723
- const position = this.findObjProp(obj, "position");
2724
- let geometryClass = "BoxGeometry(0.1, 0.1, 0.1)";
2725
- if (shape === "sphere") geometryClass = "SphereGeometry(0.05, 32, 32)";
2726
- else if (shape === "cylinder") geometryClass = "CylinderGeometry(0.05, 0.05, 0.1, 32)";
2727
- else if (shape === "plane") geometryClass = "PlaneGeometry(0.2, 0.2)";
2728
- const colorHex = typeof color === "string" ? color : "#4488ff";
2729
- const pos = Array.isArray(position) && position.length >= 3 ? position : [0, 0, -1];
2730
- this.emit("{");
2731
- this.indentLevel++;
2732
- this.emit(`const geometry = new THREE.${geometryClass};`);
2733
- this.emit(`const material = new THREE.MeshStandardMaterial({ color: '${colorHex}' });`);
2734
- this.emit("const mesh = new THREE.Mesh(geometry, material);");
2735
- this.emit(`mesh.name = '${this.escapeStringValue(objName, "TypeScript")}';`);
2736
- this.emit(`mesh.position.set(${pos[0]}, ${pos[1]}, ${pos[2]});`);
2737
- this.emit("scene.add(mesh);");
2738
- this.indentLevel--;
2739
- this.emit("}");
2740
- }
2741
- this.emit("");
2742
- if (usedTraits.has("webxr_hit_test")) {
2743
- this.emit("// Hit test reticle");
2744
- this.emit("const reticle = new THREE.Mesh(");
2745
- this.emit(" new THREE.RingGeometry(0.05, 0.06, 32).rotateX(-Math.PI / 2),");
2746
- this.emit(" new THREE.MeshBasicMaterial({ color: 0x00ff00 })");
2747
- this.emit(");");
2748
- this.emit("reticle.matrixAutoUpdate = false;");
2749
- this.emit("reticle.visible = false;");
2750
- this.emit("scene.add(reticle);");
2751
- this.emit("let hitTestSource = null;");
2752
- this.emit("");
2753
- }
2754
- if (usedTraits.has("webxr_framebuffer")) {
2755
- this.emit("let xrFramebuffer = null;");
2756
- this.emit("");
2757
- }
2758
- if (usedTraits.has("webxr_anchors")) {
2759
- this.emit("const anchors = new Map();");
2760
- this.emit("");
2761
- this.emit("async function createAnchor(frame, refSpace, position) {");
2762
- this.indentLevel++;
2763
- this.emit("const anchorPose = new XRRigidTransform(position);");
2764
- this.emit("const anchor = await frame.createAnchor(anchorPose, refSpace);");
2765
- this.emit("anchors.set(anchor, position);");
2766
- this.emit("return anchor;");
2767
- this.indentLevel--;
2768
- this.emit("}");
2769
- this.emit("");
2770
- }
2771
- if (usedTraits.has("webxr_hand_tracking")) {
2772
- this.emit("function updateHands(frame, refSpace) {");
2773
- this.indentLevel++;
2774
- this.emit("for (const source of frame.session.inputSources) {");
2775
- this.indentLevel++;
2776
- this.emit("if (!source.hand) continue;");
2777
- this.emit("for (const [name, jointSpace] of source.hand) {");
2778
- this.indentLevel++;
2779
- this.emit("const jointPose = frame.getJointPose(jointSpace, refSpace);");
2780
- this.emit("if (jointPose) { /* joint position: jointPose.transform.position */ }");
2781
- this.indentLevel--;
2782
- this.emit("}");
2783
- this.indentLevel--;
2784
- this.emit("}");
2785
- this.indentLevel--;
2786
- this.emit("}");
2787
- this.emit("");
2788
- }
2789
- this.emit("// Session configuration");
2790
- this.emit("const sessionOptions = {");
2791
- this.indentLevel++;
2792
- if (requiredFeatures.length > 0) {
2793
- const featStr = requiredFeatures.map((f) => `'${f}'`).join(", ");
2794
- this.emit(`requiredFeatures: [${featStr}],`);
2795
- }
2796
- if (usedTraits.has("webxr_dom_overlay")) {
2797
- this.emit("domOverlay: { root: document.getElementById('overlay') },");
2798
- }
2799
- if (usedTraits.has("webxr_depth_sensing")) {
2800
- this.emit('depthSensing: { usagePreference: ["cpu-optimized"], dataFormatPreference: ["luminance-alpha"] },');
2801
- }
2802
- this.indentLevel--;
2803
- this.emit("};");
2804
- this.emit("");
2805
- this.emit(`navigator.xr.requestSession('${sessionType}', sessionOptions).then(async (session) => {`);
2806
- this.indentLevel++;
2807
- this.emit(`const refSpace = await session.requestReferenceSpace('${refSpace}');`);
2808
- this.emit("renderer.xr.setReferenceSpace(refSpace);");
2809
- this.emit("renderer.xr.setSession(session);");
2810
- this.emit("");
2811
- if (usedTraits.has("webxr_hit_test")) {
2812
- this.emit("// Initialize hit test source");
2813
- this.emit("const viewerSpace = await session.requestReferenceSpace('viewer');");
2814
- this.emit("hitTestSource = await session.requestHitTestSource({ space: viewerSpace });");
2815
- this.emit("");
2816
- }
2817
- if (usedTraits.has("webxr_light_estimation")) {
2818
- this.emit("// Initialize light probe");
2819
- this.emit("xrLightProbe = await session.requestLightProbe();");
2820
- this.emit("");
2821
- }
2822
- if (usedTraits.has("webxr_layers")) {
2823
- this.emit("// XR Layers");
2824
- this.emit("const xrProjectionLayer = renderer.xr.getBaseLayer();");
2825
- this.emit("");
2826
- }
2827
- this.emit("renderer.setAnimationLoop((timestamp, frame) => {");
2828
- this.indentLevel++;
2829
- this.emit("if (!frame) return;");
2830
- this.emit("");
2831
- if (usedTraits.has("webxr_hit_test")) {
2832
- this.emit("if (hitTestSource) {");
2833
- this.indentLevel++;
2834
- this.emit("const hitTestResults = frame.getHitTestResults(hitTestSource);");
2835
- this.emit("if (hitTestResults.length > 0) {");
2836
- this.indentLevel++;
2837
- this.emit("const hit = hitTestResults[0];");
2838
- this.emit("reticle.visible = true;");
2839
- this.emit("reticle.matrix.fromArray(hit.getPose(refSpace).transform.matrix);");
2840
- this.indentLevel--;
2841
- this.emit("} else { reticle.visible = false; }");
2842
- this.indentLevel--;
2843
- this.emit("}");
2844
- this.emit("");
2845
- }
2846
- if (usedTraits.has("webxr_light_estimation")) {
2847
- this.emit("if (xrLightProbe) {");
2848
- this.indentLevel++;
2849
- this.emit("const estimate = frame.getLightEstimate(xrLightProbe);");
2850
- this.emit("if (estimate) {");
2851
- this.indentLevel++;
2852
- this.emit("const dir = estimate.primaryLightDirection;");
2853
- this.emit("estimatedLight.position.set(dir.x, dir.y, dir.z);");
2854
- this.emit("estimatedLight.intensity = estimate.primaryLightIntensity?.y || 1.0;");
2855
- this.indentLevel--;
2856
- this.emit("}");
2857
- this.indentLevel--;
2858
- this.emit("}");
2859
- this.emit("");
2860
- }
2861
- if (usedTraits.has("webxr_depth_sensing")) {
2862
- this.emit("// Depth sensing");
2863
- this.emit("const viewerPose = frame.getViewerPose(refSpace);");
2864
- this.emit("if (viewerPose) {");
2865
- this.indentLevel++;
2866
- this.emit("for (const view of viewerPose.views) {");
2867
- this.indentLevel++;
2868
- this.emit("const depthInfo = frame.getDepthInformation(view);");
2869
- this.emit("if (depthInfo) { /* depthInfo.data contains depth buffer */ }");
2870
- this.indentLevel--;
2871
- this.emit("}");
2872
- this.indentLevel--;
2873
- this.emit("}");
2874
- this.emit("");
2875
- }
2876
- if (usedTraits.has("webxr_hand_tracking")) {
2877
- this.emit("updateHands(frame, refSpace);");
2878
- this.emit("");
2879
- }
2880
- if (usedTraits.has("webxr_framebuffer")) {
2881
- this.emit("xrFramebuffer = renderer.xr.getBaseLayer()?.framebuffer || null;");
2882
- this.emit("");
2883
- }
2884
- this.emit("renderer.render(scene, camera);");
2885
- this.indentLevel--;
2886
- this.emit("});");
2887
- this.emit("");
2888
- this.emit("session.addEventListener('end', () => {");
2889
- this.indentLevel++;
2890
- this.emit("renderer.setAnimationLoop(null);");
2891
- if (usedTraits.has("webxr_hit_test")) {
2892
- this.emit("hitTestSource = null;");
2893
- }
2894
- this.indentLevel--;
2895
- this.emit("});");
2896
- this.indentLevel--;
2897
- this.emit("}).catch(err => { console.error('WebXR session failed:', err); document.getElementById('no-xr').style.display = 'block'; });");
2898
- this.indentLevel--;
2899
- this.emit("</script>");
2900
- this.indentLevel--;
2901
- this.emit("</body>");
2902
- this.emit("</html>");
2903
- return this.lines.join("\n");
2904
- }
2905
- };
2906
- }
2907
- });
2908
-
2909
- export { AndroidCompiler, AndroidCompiler_exports, compileToAndroid, init_AndroidCompiler };
2910
- //# sourceMappingURL=chunk-M6FU6S22.js.map
2911
- //# sourceMappingURL=chunk-M6FU6S22.js.map