imlixinyang commited on
Commit
5b551b7
·
1 Parent(s): cc2c6ff

add examples.

Browse files
app_gradio.py CHANGED
@@ -440,7 +440,7 @@ def process_generation_request(data, generation_system, cache_dir):
440
 
441
  splat_path = os.path.join(cache_dir, f'{file_id}.spz')
442
 
443
- export_gaussians(splat_path, scene_params, opacity_threshold=0.001, T_norm=T_norm)
444
 
445
  if not os.path.exists(splat_path):
446
  return {'error': f'{splat_path} not found'}
 
440
 
441
  splat_path = os.path.join(cache_dir, f'{file_id}.spz')
442
 
443
+ export_gaussians(splat_path, scene_params, opacity_threshold=0.00025, T_norm=T_norm)
444
 
445
  if not os.path.exists(splat_path):
446
  return {'error': f'{splat_path} not found'}
examples/.DS_Store ADDED
Binary file (6.15 kB). View file
 
examples/1.json ADDED
The diff for this file is too large to render. See raw diff
 
examples/2.json ADDED
The diff for this file is too large to render. See raw diff
 
examples/3.json ADDED
The diff for this file is too large to render. See raw diff
 
examples/4.json ADDED
The diff for this file is too large to render. See raw diff
 
examples/5.json ADDED
The diff for this file is too large to render. See raw diff
 
examples/6.json ADDED
The diff for this file is too large to render. See raw diff
 
examples/7.json ADDED
The diff for this file is too large to render. See raw diff
 
examples/8.json ADDED
The diff for this file is too large to render. See raw diff
 
index.html CHANGED
@@ -404,6 +404,52 @@
404
  position: relative !important;
405
  z-index: 1000 !important;
406
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
  @media (max-width: 1200px) {
409
  .left-panel {
@@ -502,6 +548,12 @@
502
  </div>
503
 
504
  </div>
 
 
 
 
 
 
505
  </div>
506
 
507
  <!-- Center Panel: Canvas -->
@@ -967,6 +1019,46 @@
967
  },
968
  imageIndex: 0,
969
  inputTextPrompt: "",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
970
 
971
  // Camera trajectory templates
972
  trajectoryMode: "Manual",
@@ -1238,6 +1330,194 @@
1238
  }
1239
  };
1240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1241
  // Initialize renderer and GUI when DOM is ready
1242
  function initializeApp() {
1243
  try {
@@ -1256,9 +1536,10 @@
1256
  }
1257
 
1258
  if (document.readyState === 'loading') {
1259
- document.addEventListener('DOMContentLoaded', initializeApp);
1260
  } else {
1261
  initializeApp();
 
1262
  }
1263
 
1264
  // =========================
@@ -1316,6 +1597,37 @@
1316
  return interpolatedCameras;
1317
  }
1318
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1319
  // 创建立方体的splat可视化
1320
  function createCubeSplat(size = 0.1, pointColor = [1, 1, 1]) {
1321
  const cubeSplat = new SplatMesh({
@@ -1617,6 +1929,9 @@
1617
  )).name("Resolution (NxHxW)").onChange((value) => {
1618
  updateCanvasSize();
1619
  });
 
 
 
1620
 
1621
  // Fix Configuration按钮放在最下面
1622
  const fixGenerationFOVController = step1Folder.add(guiOptions, "fixGenerationFOV").name("Fix Configuration");
@@ -1726,8 +2041,11 @@
1726
  // Step 3: Add Scene Prompts
1727
  const step3Folder = gui.addFolder('3. Add Scene Prompts');
1728
  step3Folder.add(guiOptions, "inputImagePrompt").name("Input Image Prompt");
1729
- step3Folder.add(guiOptions, "inputTextPrompt").name("Input Text Prompt");
1730
- step3Folder.add(guiOptions, "imageIndex", 0, 24, 1).name("Image Index");
 
 
 
1731
 
1732
 
1733
  // Step 4: Generate Your Scene
@@ -1750,6 +2068,12 @@
1750
  updateStatus(`Scrubbing trajectory: t=${value.toFixed(3)}`, cameraParams.length);
1751
  });
1752
  step5Folder.open();
 
 
 
 
 
 
1753
 
1754
  }
1755
  }
@@ -1950,196 +2274,9 @@
1950
  console.error("JSON parsing error:", error);
1951
  return;
1952
  }
1953
-
1954
- // 检查是否是只加载轨迹
1955
- const loadTrajectoryOnly = window.loadTrajectoryOnly;
1956
- window.loadTrajectoryOnly = false; // 重置标志
1957
-
1958
- if (loadTrajectoryOnly) {
1959
- // 只加载轨迹:清理所有已有的相机和插值相机
1960
- cameraSplats.forEach(splat => scene.remove(splat));
1961
- cameraSplats.length = 0;
1962
- cameraParams.length = 0;
1963
- interpolatedCamerasSplats.forEach(splat => scene.remove(splat));
1964
- interpolatedCamerasSplats.length = 0;
1965
- } else {
1966
- // 加载完整JSON:清理所有已有的相机和插值相机
1967
- cameraSplats.forEach(splat => scene.remove(splat));
1968
- cameraSplats.length = 0;
1969
- cameraParams.length = 0;
1970
- interpolatedCamerasSplats.forEach(splat => scene.remove(splat));
1971
- interpolatedCamerasSplats.length = 0;
1972
- }
1973
-
1974
- try {
1975
- // 兼容不同命名的字段
1976
- const imagePrompt = jsonData.image_prompt || jsonData.imagePrompt || null;
1977
- const textPrompt = jsonData.text_prompt || jsonData.textPrompt || "";
1978
- const cameras = jsonData.cameras || [];
1979
- const resolution = jsonData.resolution || [16, 480, 640];
1980
- const imageIndex = jsonData.image_index || jsonData.imageIndex || 0;
1981
-
1982
- console.log("Loaded JSON data:", {
1983
- imagePrompt,
1984
- textPrompt,
1985
- cameras: cameras.length,
1986
- resolution,
1987
- imageIndex
1988
- });
1989
-
1990
- // 处理图像提示(仅在非轨迹加载模式下)
1991
- if (!loadTrajectoryOnly && imagePrompt) {
1992
- inputImageBase64 = imagePrompt;
1993
- console.log("Image prompt loaded");
1994
- }
1995
-
1996
- // 设置文本提示(仅在非轨迹加载模式下)
1997
- if (!loadTrajectoryOnly) {
1998
- guiOptions.inputTextPrompt = textPrompt;
1999
- guiOptions.imageIndex = imageIndex;
2000
- }
2001
-
2002
- // 处理相机数据
2003
- if (cameras && cameras.length > 0) {
2004
- let jsonFirstCamera = null;
2005
- let jsonFirstPosition = null;
2006
- let jsonFirstQuaternion = null;
2007
-
2008
- // 首先获取JSON中第一个相机的位置和四元数
2009
- if (loadTrajectoryOnly && cameras.length > 0) {
2010
- const firstCameraData = cameras[0];
2011
- if (Array.isArray(firstCameraData.position) && firstCameraData.position.length === 3) {
2012
- jsonFirstPosition = new THREE.Vector3(
2013
- firstCameraData.position[0],
2014
- firstCameraData.position[1],
2015
- firstCameraData.position[2]
2016
- );
2017
- }
2018
- if (Array.isArray(firstCameraData.quaternion) && firstCameraData.quaternion.length === 4) {
2019
- jsonFirstQuaternion = new THREE.Quaternion(
2020
- firstCameraData.quaternion[1],
2021
- firstCameraData.quaternion[2],
2022
- firstCameraData.quaternion[3],
2023
- firstCameraData.quaternion[0]
2024
- );
2025
- }
2026
- }
2027
-
2028
- cameras.forEach((cameraData, index) => {
2029
- // 解析分辨率
2030
- let aspect = 1.0;
2031
- if (Array.isArray(resolution) && resolution.length === 3) {
2032
- aspect = resolution[2] / resolution[1];
2033
- } else {
2034
- aspect = guiOptions.Resolution.split('x')[2] / guiOptions.Resolution.split('x')[1];
2035
- }
2036
-
2037
- // 根据加载模式决定FOV
2038
- let fov = 60;
2039
- if (loadTrajectoryOnly) {
2040
- // 轨迹加载:使用GUI中设定的FOV
2041
- fov = guiOptions.FOV;
2042
- } else {
2043
- // 完整JSON加载:使用JSON中的FOV或默认值
2044
- if (cameraData.fx && cameraData.fy) {
2045
- fov = 2 * Math.atan(0.5 / cameraData.fx) * 180 / Math.PI;
2046
- }
2047
- }
2048
-
2049
- const cam = new THREE.PerspectiveCamera(fov, aspect);
2050
-
2051
- // 设置位置和四元数
2052
- if (Array.isArray(cameraData.position) && cameraData.position.length === 3) {
2053
- cam.position.set(cameraData.position[0], cameraData.position[1], cameraData.position[2]);
2054
- }
2055
-
2056
- if (Array.isArray(cameraData.quaternion) && cameraData.quaternion.length === 4) {
2057
- // 注意:three.js的顺序是 (x, y, z, w)
2058
- cam.quaternion.set(
2059
- cameraData.quaternion[1],
2060
- cameraData.quaternion[2],
2061
- cameraData.quaternion[3],
2062
- cameraData.quaternion[0]
2063
- );
2064
- }
2065
-
2066
- // 轨迹加载:第一个相机强制设置为原点
2067
- // if (loadTrajectoryOnly && index === 0) {
2068
- // cam.position.set(0, 0, 0);
2069
- // cam.quaternion.set(0, 0, 0, 1);
2070
- // }
2071
-
2072
- // 轨迹加载:归一化到相对于固定FOV相机的位置
2073
- if (loadTrajectoryOnly && jsonFirstPosition && jsonFirstQuaternion) {
2074
- // 参考Python代码的归���化逻辑
2075
- // 1. 计算JSON第一个相机的c2w矩阵
2076
- const jsonFirstC2W = new THREE.Matrix4();
2077
- jsonFirstC2W.compose(jsonFirstPosition, jsonFirstQuaternion, new THREE.Vector3(1, 1, 1));
2078
-
2079
- // 2. 计算当前相机的c2w矩阵
2080
- const currentC2W = new THREE.Matrix4();
2081
- currentC2W.compose(cam.position, cam.quaternion, new THREE.Vector3(1, 1, 1));
2082
-
2083
- // 3. 计算相对变换:ref_w2c @ current_c2w
2084
- const refW2C = jsonFirstC2W.clone().invert();
2085
- const relativeTransform = refW2C.clone().multiply(currentC2W);
2086
-
2087
- // 4. 将相对变换应用到原点相机上(作为参考)
2088
- const fixedC2W = new THREE.Matrix4();
2089
- fixedC2W.compose(new THREE.Vector3(0, 0, 0), new THREE.Quaternion(0, 0, 0, 1), new THREE.Vector3(1, 1, 1));
2090
-
2091
- const newTransform = fixedC2W.clone().multiply(relativeTransform);
2092
-
2093
- // 5. 提取新的位置和旋转
2094
- const newPosition = new THREE.Vector3();
2095
- const newQuaternion = new THREE.Quaternion();
2096
- const newScale = new THREE.Vector3();
2097
- newTransform.decompose(newPosition, newQuaternion, newScale);
2098
-
2099
- cam.position.copy(newPosition);
2100
- cam.quaternion.copy(newQuaternion);
2101
- }
2102
-
2103
- // 设置FOV和焦距(仅在非轨迹加载模式下)
2104
- if (!loadTrajectoryOnly && cameraData.fx && cameraData.fy) {
2105
- cam.fov = fov;
2106
- cam.aspect = cameraData.fx / cameraData.fy;
2107
- cam.updateProjectionMatrix();
2108
- } else if (loadTrajectoryOnly) {
2109
- // 轨迹加载:使用GUI中设定的FOV和aspect
2110
- cam.fov = fov;
2111
- cam.aspect = aspect;
2112
- cam.updateProjectionMatrix();
2113
- }
2114
-
2115
- const cameraSplat = createCameraSplat(cam);
2116
- cameraSplats.push(cameraSplat);
2117
- cameraParams.push({
2118
- position: cam.position.clone(),
2119
- quaternion: cam.quaternion.clone(),
2120
- fov: cam.fov,
2121
- aspect: cam.aspect,
2122
- });
2123
- scene.add(cameraSplat);
2124
- });
2125
-
2126
- console.log(cameraParams);
2127
- }
2128
-
2129
- // 设置分辨率(仅在非轨迹加载模式下)
2130
- if (!loadTrajectoryOnly && Array.isArray(resolution) && resolution.length === 3) {
2131
- guiOptions.Resolution = `${resolution[0]}x${resolution[1]}x${resolution[2]}`;
2132
- }
2133
-
2134
- // 显示成功消息
2135
- if (loadTrajectoryOnly) {
2136
- updateStatus(`Trajectory loaded: ${cameras.length} cameras`, cameraParams.length);
2137
- } else {
2138
- updateStatus(`JSON loaded: ${cameras.length} cameras`, cameraParams.length);
2139
- }
2140
- } catch (error) {
2141
- console.error("JSON data processing error:", error);
2142
- }
2143
  };
2144
  reader.readAsText(file);
2145
  };
 
404
  position: relative !important;
405
  z-index: 1000 !important;
406
  }
407
+
408
+ /* Examples gallery */
409
+ .examples-section {
410
+ margin-top: 16px;
411
+ padding-top: 12px;
412
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
413
+ }
414
+ .examples-section h3 {
415
+ margin: 0 0 8px 0;
416
+ color: #ffffff;
417
+ font-size: 1em;
418
+ }
419
+ .examples-grid {
420
+ display: grid;
421
+ grid-template-columns: repeat(4, 1fr);
422
+ gap: 8px;
423
+ }
424
+ .example-item {
425
+ position: relative;
426
+ width: 100%;
427
+ padding-top: 100%;
428
+ background: rgba(255,255,255,0.06);
429
+ border: 1px solid rgba(255,255,255,0.12);
430
+ border-radius: 6px;
431
+ overflow: hidden;
432
+ cursor: pointer;
433
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
434
+ }
435
+ .example-item:hover {
436
+ transform: translateY(-2px);
437
+ box-shadow: 0 6px 16px rgba(0,0,0,0.35);
438
+ }
439
+ .example-item img {
440
+ position: absolute;
441
+ top: 0; left: 0; right: 0; bottom: 0;
442
+ width: 100%; height: 100%; object-fit: cover;
443
+ }
444
+ .example-item .label {
445
+ position: absolute;
446
+ bottom: 0; left: 0; right: 0;
447
+ background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.65) 100%);
448
+ color: #e5e7eb;
449
+ font-size: 11px;
450
+ padding: 4px 6px;
451
+ text-align: center;
452
+ }
453
 
454
  @media (max-width: 1200px) {
455
  .left-panel {
 
548
  </div>
549
 
550
  </div>
551
+
552
+ <!-- Examples Gallery -->
553
+ <div id="examples-section" class="examples-section">
554
+ <h3>Examples</h3>
555
+ <div id="examples-grid" class="examples-grid"></div>
556
+ </div>
557
  </div>
558
 
559
  <!-- Center Panel: Canvas -->
 
1019
  },
1020
  imageIndex: 0,
1021
  inputTextPrompt: "",
1022
+ // Step 6: All-in-one JSON IO
1023
+ LoadAllFromJson: () => {
1024
+ // Full load (image/text/index/resolution/cameras)
1025
+ window.loadTrajectoryOnly = false;
1026
+ const jsonInput = document.querySelector("#json-input");
1027
+ if (jsonInput) jsonInput.click();
1028
+ },
1029
+ SaveAllToJson: () => {
1030
+ // Build JSON payload matching transmission format
1031
+ const [nStr, hStr, wStr] = guiOptions.Resolution.split('x');
1032
+ const n = parseInt(nStr), h = parseInt(hStr), w = parseInt(wStr);
1033
+ const fovDeg = guiOptions.FOV;
1034
+ const fy = 0.5 / Math.tan(0.5 * fovDeg * Math.PI / 180) * h;
1035
+ const fx = fy; // keep fx consistent with fy-derived FOV
1036
+ const cx = 0.5 * w;
1037
+ const cy = 0.5 * h;
1038
+
1039
+ const payload = {
1040
+ image_prompt: inputImageBase64 ? inputImageBase64 : null,
1041
+ text_prompt: guiOptions.inputTextPrompt || "",
1042
+ image_index: guiOptions.imageIndex || 0,
1043
+ resolution: [n, h, w],
1044
+ cameras: cameraParams.map(cam => ({
1045
+ position: [cam.position.x, cam.position.y, cam.position.z],
1046
+ quaternion: [cam.quaternion.w, cam.quaternion.x, cam.quaternion.y, cam.quaternion.z],
1047
+ fx, fy, cx, cy
1048
+ }))
1049
+ };
1050
+
1051
+ const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
1052
+ const url = URL.createObjectURL(blob);
1053
+ const a = document.createElement('a');
1054
+ a.href = url;
1055
+ a.download = `scene_all_${Date.now()}.json`;
1056
+ document.body.appendChild(a);
1057
+ a.click();
1058
+ document.body.removeChild(a);
1059
+ URL.revokeObjectURL(url);
1060
+ updateStatus('All data saved to JSON.', cameraParams.length);
1061
+ },
1062
 
1063
  // Camera trajectory templates
1064
  trajectoryMode: "Manual",
 
1330
  }
1331
  };
1332
 
1333
+ // =========================
1334
+ // Examples & JSON load utils
1335
+ // =========================
1336
+ const EXAMPLE_FILES = Array.from({ length: 8 }, (_, i) => `examples/${i + 1}.json`);
1337
+
1338
+ function processJsonLoad(jsonData, loadTrajectoryOnlyProvided) {
1339
+ // Determine mode and reset flag if coming from global
1340
+ const loadTrajectoryOnly = !!loadTrajectoryOnlyProvided;
1341
+
1342
+ // Clear existing cameras and interpolated splats
1343
+ cameraSplats.forEach(splat => scene.remove(splat));
1344
+ cameraSplats.length = 0;
1345
+ cameraParams.length = 0;
1346
+ interpolatedCamerasSplats.forEach(splat => scene.remove(splat));
1347
+ interpolatedCamerasSplats.length = 0;
1348
+
1349
+ try {
1350
+ const imagePrompt = jsonData.image_prompt || jsonData.imagePrompt || null;
1351
+ const textPrompt = jsonData.text_prompt || jsonData.textPrompt || "";
1352
+ const cameras = jsonData.cameras || [];
1353
+ const resolution = jsonData.resolution || [16, 480, 640];
1354
+ const imageIndex = jsonData.image_index || jsonData.imageIndex || 0;
1355
+
1356
+ // Prompt/image only for full load
1357
+ if (!loadTrajectoryOnly && imagePrompt) {
1358
+ inputImageBase64 = imagePrompt;
1359
+ const previewArea = document.getElementById('image-preview-area');
1360
+ const previewImg = document.getElementById('preview-img');
1361
+ if (previewImg && previewArea) {
1362
+ previewImg.src = inputImageBase64;
1363
+ previewArea.style.display = 'block';
1364
+ }
1365
+ }
1366
+
1367
+ if (!loadTrajectoryOnly) {
1368
+ guiOptions.inputTextPrompt = textPrompt;
1369
+ guiOptions.imageIndex = imageIndex;
1370
+ syncGuiPromptControls();
1371
+ }
1372
+
1373
+ // Infer FOV from first camera fy for full load
1374
+ if (!loadTrajectoryOnly && Array.isArray(resolution) && resolution.length === 3 && cameras && cameras.length > 0) {
1375
+ const H = resolution[1];
1376
+ const firstCam = cameras[0];
1377
+ if (firstCam && typeof firstCam.fy === 'number' && isFinite(firstCam.fy) && firstCam.fy > 0) {
1378
+ const inferredFov = 2 * Math.atan(0.5 * H / firstCam.fy) * 180 / Math.PI;
1379
+ guiOptions.FOV = inferredFov;
1380
+ }
1381
+ }
1382
+
1383
+ if (cameras && cameras.length > 0) {
1384
+ let jsonFirstPosition = null;
1385
+ let jsonFirstQuaternion = null;
1386
+ const firstCameraData = cameras[0];
1387
+ if (Array.isArray(firstCameraData?.position) && firstCameraData.position.length === 3) {
1388
+ jsonFirstPosition = new THREE.Vector3(
1389
+ firstCameraData.position[0],
1390
+ firstCameraData.position[1],
1391
+ firstCameraData.position[2]
1392
+ );
1393
+ }
1394
+ if (Array.isArray(firstCameraData?.quaternion) && firstCameraData.quaternion.length === 4) {
1395
+ jsonFirstQuaternion = new THREE.Quaternion(
1396
+ firstCameraData.quaternion[1],
1397
+ firstCameraData.quaternion[2],
1398
+ firstCameraData.quaternion[3],
1399
+ firstCameraData.quaternion[0]
1400
+ );
1401
+ }
1402
+
1403
+ cameras.forEach((cameraData) => {
1404
+ let aspect = 1.0;
1405
+ if (Array.isArray(resolution) && resolution.length === 3) {
1406
+ aspect = resolution[2] / resolution[1];
1407
+ } else {
1408
+ aspect = guiOptions.Resolution.split('x')[2] / guiOptions.Resolution.split('x')[1];
1409
+ }
1410
+
1411
+ let fov = 60;
1412
+ if (loadTrajectoryOnly) {
1413
+ fov = guiOptions.FOV;
1414
+ } else {
1415
+ if (Array.isArray(resolution) && resolution.length === 3 && typeof cameraData.fy === 'number' && cameraData.fy > 0) {
1416
+ const H = resolution[1];
1417
+ fov = 2 * Math.atan(0.5 * H / cameraData.fy) * 180 / Math.PI;
1418
+ guiOptions.FOV = fov;
1419
+ } else {
1420
+ fov = guiOptions.FOV;
1421
+ }
1422
+ }
1423
+
1424
+ const cam = new THREE.PerspectiveCamera(fov, aspect);
1425
+ if (Array.isArray(cameraData.position) && cameraData.position.length === 3) {
1426
+ cam.position.set(cameraData.position[0], cameraData.position[1], cameraData.position[2]);
1427
+ }
1428
+ if (Array.isArray(cameraData.quaternion) && cameraData.quaternion.length === 4) {
1429
+ cam.quaternion.set(
1430
+ cameraData.quaternion[1],
1431
+ cameraData.quaternion[2],
1432
+ cameraData.quaternion[3],
1433
+ cameraData.quaternion[0]
1434
+ );
1435
+ }
1436
+
1437
+ if (jsonFirstPosition && jsonFirstQuaternion) {
1438
+ const jsonFirstC2W = new THREE.Matrix4();
1439
+ jsonFirstC2W.compose(jsonFirstPosition, jsonFirstQuaternion, new THREE.Vector3(1, 1, 1));
1440
+ const currentC2W = new THREE.Matrix4();
1441
+ currentC2W.compose(cam.position, cam.quaternion, new THREE.Vector3(1, 1, 1));
1442
+ const refW2C = jsonFirstC2W.clone().invert();
1443
+ const relativeTransform = refW2C.clone().multiply(currentC2W);
1444
+ const fixedC2W = new THREE.Matrix4();
1445
+ fixedC2W.compose(new THREE.Vector3(0, 0, 0), new THREE.Quaternion(0, 0, 0, 1), new THREE.Vector3(1, 1, 1));
1446
+ const newTransform = fixedC2W.clone().multiply(relativeTransform);
1447
+ const newPosition = new THREE.Vector3();
1448
+ const newQuaternion = new THREE.Quaternion();
1449
+ const newScale = new THREE.Vector3();
1450
+ newTransform.decompose(newPosition, newQuaternion, newScale);
1451
+ cam.position.copy(newPosition);
1452
+ cam.quaternion.copy(newQuaternion);
1453
+ }
1454
+
1455
+ cam.fov = fov;
1456
+ cam.aspect = aspect;
1457
+ cam.updateProjectionMatrix();
1458
+
1459
+ const cameraSplat = createCameraSplat(cam);
1460
+ cameraSplats.push(cameraSplat);
1461
+ cameraParams.push({
1462
+ position: cam.position.clone(),
1463
+ quaternion: cam.quaternion.clone(),
1464
+ fov: cam.fov,
1465
+ aspect: cam.aspect,
1466
+ });
1467
+ scene.add(cameraSplat);
1468
+ });
1469
+ }
1470
+
1471
+ if (!loadTrajectoryOnly && Array.isArray(resolution) && resolution.length === 3) {
1472
+ guiOptions.Resolution = `${resolution[0]}x${resolution[1]}x${resolution[2]}`;
1473
+ }
1474
+
1475
+ if (loadTrajectoryOnly) {
1476
+ updateStatus(`Trajectory loaded: ${jsonData.cameras ? jsonData.cameras.length : 0} cameras`, cameraParams.length);
1477
+ } else {
1478
+ updateStatus(`JSON loaded: ${jsonData.cameras ? jsonData.cameras.length : 0} cameras`, cameraParams.length);
1479
+ }
1480
+ } catch (error) {
1481
+ console.error("JSON data processing error:", error);
1482
+ }
1483
+ }
1484
+
1485
+ async function renderExamples() {
1486
+ const grid = document.getElementById('examples-grid');
1487
+ if (!grid) return;
1488
+ grid.innerHTML = '';
1489
+ const items = [];
1490
+ for (const path of EXAMPLE_FILES) {
1491
+ try {
1492
+ const resp = await fetch(path);
1493
+ if (!resp.ok) continue;
1494
+ const data = await resp.json();
1495
+ const thumb = data.image_prompt || data.imagePrompt || null;
1496
+ items.push({ path, data, thumb });
1497
+ } catch (_) { /* ignore */ }
1498
+ }
1499
+ items.slice(0, 10).forEach((item, idx) => {
1500
+ const div = document.createElement('div');
1501
+ div.className = 'example-item';
1502
+ div.title = item.path;
1503
+ if (item.thumb) {
1504
+ const img = document.createElement('img');
1505
+ img.src = item.thumb;
1506
+ div.appendChild(img);
1507
+ } else {
1508
+ const label = document.createElement('div');
1509
+ label.className = 'label';
1510
+ label.textContent = `Example ${idx + 1}`;
1511
+ div.appendChild(label);
1512
+ }
1513
+ div.addEventListener('click', () => {
1514
+ // Full JSON load behavior
1515
+ processJsonLoad(item.data, false);
1516
+ });
1517
+ grid.appendChild(div);
1518
+ });
1519
+ }
1520
+
1521
  // Initialize renderer and GUI when DOM is ready
1522
  function initializeApp() {
1523
  try {
 
1536
  }
1537
 
1538
  if (document.readyState === 'loading') {
1539
+ document.addEventListener('DOMContentLoaded', () => { initializeApp(); renderExamples(); });
1540
  } else {
1541
  initializeApp();
1542
+ renderExamples();
1543
  }
1544
 
1545
  // =========================
 
1597
  return interpolatedCameras;
1598
  }
1599
 
1600
+ // 强制同步文本与索引控件显示
1601
+ function syncGuiPromptControls() {
1602
+ try {
1603
+ if (window.inputTextPromptController) {
1604
+ window.inputTextPromptController.setValue(guiOptions.inputTextPrompt);
1605
+ if (typeof window.inputTextPromptController.updateDisplay === 'function') {
1606
+ window.inputTextPromptController.updateDisplay();
1607
+ }
1608
+ }
1609
+ if (window.imageIndexController) {
1610
+ window.imageIndexController.setValue(guiOptions.imageIndex);
1611
+ if (typeof window.imageIndexController.updateDisplay === 'function') {
1612
+ window.imageIndexController.updateDisplay();
1613
+ }
1614
+ }
1615
+ } catch (e) {
1616
+ console.debug('syncGuiPromptControls error:', e);
1617
+ }
1618
+ // 再尝试一次,防止控件尚未就绪
1619
+ requestAnimationFrame(() => {
1620
+ try {
1621
+ if (window.inputTextPromptController && typeof window.inputTextPromptController.updateDisplay === 'function') {
1622
+ window.inputTextPromptController.updateDisplay();
1623
+ }
1624
+ if (window.imageIndexController && typeof window.imageIndexController.updateDisplay === 'function') {
1625
+ window.imageIndexController.updateDisplay();
1626
+ }
1627
+ } catch (_) {}
1628
+ });
1629
+ }
1630
+
1631
  // 创建立方体的splat可视化
1632
  function createCubeSplat(size = 0.1, pointColor = [1, 1, 1]) {
1633
  const cubeSplat = new SplatMesh({
 
1929
  )).name("Resolution (NxHxW)").onChange((value) => {
1930
  updateCanvasSize();
1931
  });
1932
+ // Expose for programmatic updates after JSON load
1933
+ window.fovController = fovController;
1934
+ window.resolutionController = resolutionController;
1935
 
1936
  // Fix Configuration按钮放在最下面
1937
  const fixGenerationFOVController = step1Folder.add(guiOptions, "fixGenerationFOV").name("Fix Configuration");
 
2041
  // Step 3: Add Scene Prompts
2042
  const step3Folder = gui.addFolder('3. Add Scene Prompts');
2043
  step3Folder.add(guiOptions, "inputImagePrompt").name("Input Image Prompt");
2044
+ const inputTextPromptController = step3Folder.add(guiOptions, "inputTextPrompt").name("Input Text Prompt");
2045
+ const imageIndexController = step3Folder.add(guiOptions, "imageIndex", 0, 24, 1).name("Image Index");
2046
+ // Expose for programmatic updates after JSON load
2047
+ window.inputTextPromptController = inputTextPromptController;
2048
+ window.imageIndexController = imageIndexController;
2049
 
2050
 
2051
  // Step 4: Generate Your Scene
 
2068
  updateStatus(`Scrubbing trajectory: t=${value.toFixed(3)}`, cameraParams.length);
2069
  });
2070
  step5Folder.open();
2071
+
2072
+ // Step 6: Load/Save All JSON
2073
+ const step6Folder = gui.addFolder('6. JSON (All-in-one)');
2074
+ step6Folder.add(guiOptions, "LoadAllFromJson").name("Load All from JSON");
2075
+ step6Folder.add(guiOptions, "SaveAllToJson").name("Save All to JSON");
2076
+ step6Folder.open();
2077
 
2078
  }
2079
  }
 
2274
  console.error("JSON parsing error:", error);
2275
  return;
2276
  }
2277
+ const loadTrajectoryOnly = !!window.loadTrajectoryOnly;
2278
+ window.loadTrajectoryOnly = false;
2279
+ processJsonLoad(jsonData, loadTrajectoryOnly);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2280
  };
2281
  reader.readAsText(file);
2282
  };