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