@holoscript/core 6.0.4 → 7.0.0

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