add sim mode..

This commit is contained in:
2026-08-20 13:22:27 +09:00
parent f29b44da07
commit e1d7b7d532
20 changed files with 1079 additions and 14828 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

+135 -224
View File
@@ -239,7 +239,6 @@
#downloadMap,
#pathToggle,
#sensorToggle,
#splatToggle,
#poseToggle,
#goalToggle,
#obstacleToggle,
@@ -258,7 +257,6 @@
#downloadMap,
#pathToggle,
#sensorToggle,
#splatToggle,
#poseToggle,
#goalToggle,
#obstacleToggle {
@@ -269,25 +267,6 @@
align-items:center;
gap:6px;
}
#splatToggle {
display:inline-flex;
align-items:center;
justify-content:center;
position:absolute;
top:16px;
left:16px;
z-index:10;
width:14px;
min-width:14px;
min-height:14px;
height:14px;
padding:0;
border-color:rgba(203,213,225,.3);
border-radius:2px;
background:rgba(148,163,184,.3);
box-shadow:none;
}
#splatToggle:hover { background:rgba(148,163,184,.5); border-color:rgba(226,232,240,.45); }
#cameraPresetSelect {
min-width:86px;
padding:0 24px 0 9px;
@@ -301,7 +280,6 @@
#downloadMap:hover,
#pathToggle:hover,
#sensorToggle:hover,
#splatToggle:hover,
#poseToggle:hover,
#goalToggle:hover,
#obstacleToggle:hover,
@@ -332,10 +310,6 @@
border-color:rgba(103,232,249,.55);
color:#fff;
}
#splatToggle.active {
background:rgba(148,163,184,.62);
border-color:rgba(226,232,240,.58);
}
#goalToggle.active {
background:rgba(22,163,74,.62);
border-color:rgba(134,239,172,.55);
@@ -388,13 +362,11 @@
.editor-mode #rosTopicStatusWrap {
display:none;
}
#splatToggle { display:none; }
.nav2-slam #goalToggle, .nav2-slam #poseToggle { display:none; }
.nav2-navigation #downloadWrap { display:none; }
.nav2-navigation #topLeftControls { display:block; }
.nav2-navigation #keyGuide { display:none; }
.nav2-navigation #rosTopicStatusWrap { bottom:16px; }
.nav2-navigation.real-runtime #splatToggle { display:inline-flex; }
.real-runtime #topLeftControls { display:none; }
.real-runtime #keyGuide { display:none; }
</style>
@@ -403,7 +375,6 @@
<div id="topLeftControls">
<button id="obstacleToggle" title="벽 배치" aria-label="벽 배치" aria-pressed="false">Wall</button>
</div>
<button id="splatToggle" title="3D 맵 표시" aria-label="3D 맵 표시" aria-pressed="false"></button>
<div id="topRightControls">
<div id="downloadWrap">
<button id="downloadMap" title="맵 저장" aria-label="맵 저장" disabled><span>SAVE</span></button>
@@ -452,8 +423,7 @@
{
"imports": {
"three": "../vendor/three/build/three.module.js",
"three/addons/": "../vendor/three/examples/jsm/",
"@mkkellogg/gaussian-splats-3d": "./vendor/gaussian-splats-3d.module.js"
"three/addons/": "../vendor/three/examples/jsm/"
}
}
</script>
@@ -464,7 +434,6 @@ import { TransformControls } from 'three/addons/controls/TransformControls.js';
import { ColladaLoader } from 'three/addons/loaders/ColladaLoader.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { STLLoader } from 'three/addons/loaders/STLLoader.js';
import * as GaussianSplats3D from '@mkkellogg/gaussian-splats-3d';
const viewerMapKey = new URLSearchParams(window.location.search).get('mapKey');
const params = new URLSearchParams(location.search);
@@ -479,6 +448,7 @@ const SENSOR_POINT_CLOUD_MAX_AGE_MS = 3_000;
// regular visualization sessions never poll `/camera-frame`.
const ENABLE_CAMERA_PREVIEW = false;
const CUSTOM_WORLD = !REAL_RUNTIME && viewerMapKey === 'nav2-custom';
const SAVED_GAZEBO_MAP_WORLD = !REAL_RUNTIME && viewerMapKey === 'nav2-saved-gazebo-map';
const activeWorldModel = viewerMapKey === 'nav2-turtlebot3-house' ? 'turtlebot3_house' : 'turtlebot3_world';
const MKMINI_WORLD_SINGLE_PILLAR = USE_MKMINI_MODEL && activeWorldModel === 'turtlebot3_world';
const MKMINI_RENDER_LENGTH = 0.84;
@@ -502,7 +472,6 @@ const mapSaveOverlay = document.getElementById('mapSaveOverlay');
const downloadMapButton = document.getElementById('downloadMap');
const pathToggleButton = document.getElementById('pathToggle');
const sensorToggleButton = document.getElementById('sensorToggle');
const splatToggleButton = document.getElementById('splatToggle');
const poseToggleButton = document.getElementById('poseToggle');
const goalToggleButton = document.getElementById('goalToggle');
const obstacleToggleButton = document.getElementById('obstacleToggle');
@@ -513,7 +482,6 @@ const rosTopicStatusToggle = document.getElementById('rosTopicStatusToggle');
const rosTopicStatusList = document.getElementById('rosTopicStatusList');
const EDITOR_MODE = params.get('editor') === '1';
const NAV2_VIEW_MODE = params.get('mode') === 'navigation' ? 'navigation' : 'slam';
const SHOW_REMOTE_NAVIGATION_ROOM = REAL_RUNTIME && NAV2_VIEW_MODE === 'navigation';
// A navigation session must keep the selected saved map as its fixed
// reference. The runtime /map topic may later emit a partial local update.
const LOCK_NAVIGATION_MAP = REAL_RUNTIME && NAV2_VIEW_MODE === 'navigation';
@@ -601,10 +569,10 @@ const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.05, 1
// Three.js OrbitControls 기본 조작이 자연스럽게 동작하도록 사선 시점에서 시작합니다.
camera.position.set(4, 6, 5);
camera.up.set(0, 1, 0);
const renderer = new THREE.WebGLRenderer({ antialias:!SHOW_REMOTE_NAVIGATION_ROOM });
const renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(SHOW_REMOTE_NAVIGATION_ROOM ? 1 : Math.min(devicePixelRatio, 1.25));
renderer.shadowMap.enabled = !SHOW_REMOTE_NAVIGATION_ROOM;
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.25));
renderer.shadowMap.enabled = true;
renderer.domElement.tabIndex = 0;
document.body.appendChild(renderer.domElement);
@@ -615,7 +583,7 @@ controls.mouseButtons = {
RIGHT: THREE.MOUSE.DOLLY,
};
renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault());
controls.enableDamping = !SHOW_REMOTE_NAVIGATION_ROOM;
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.target.set(0, 0, 0);
camera.lookAt(controls.target);
@@ -658,15 +626,6 @@ gazeboWorldRoot.add(runtimeLayoutObjects);
const navigationMapRoot = new THREE.Group();
navigationMapRoot.name = 'navigation-occupancy-map';
sdfRoot.add(navigationMapRoot);
let remoteNavigationSplatViewer = null;
let remoteNavigationSplatScene = null;
let remoteNavigationSplatTransform = null;
let remoteNavigationSplatEnabled = false;
let remoteNavigationSplatLoading = null;
// 화면의 빨간 X축과 평행하게 오른쪽으로 미세 보정합니다.
const remoteNavigationSplatBasePosition = new THREE.Vector3(-1.6822845855746674, 1.3107954323633903, -0.8021416967574936);
const remoteNavigationSplatBaseQuaternion = new THREE.Quaternion(-0.10999094822602469, 0.6984998148234114, 0.6984998148234114, 0.10999094822602469);
const remoteNavigationSplatBaseScale = new THREE.Vector3(3.672624762730334, 3.672624762730334, 3.5309628207501587);
const pointCloudGeometry = SHOW_POINT_CLOUD ? new THREE.BufferGeometry() : null;
let pointCloudLayer = null;
let hasGlobalMapPointCloud = false;
@@ -894,6 +853,10 @@ let lastMkMiniSteeringUpdateMs = null;
let lastMkMiniMotionSample = null;
let hasSimulatorMkMiniSteering = false;
let laserGroup = null;
let laserFillMesh = null;
let laserLineSegments = null;
let laserOpenSpaceLineSegments = null;
let laserFrontBeam = null;
let slamSocket = null;
let controlSocket = null;
let latestSlamState = null;
@@ -908,6 +871,10 @@ let navigationMapLocked = false;
let navigationMapFocused = false;
let latestScanMetadata = null;
let latestScanRanges = null;
let lastLaserRenderAtMs = 0;
let lastMiniMapRenderAtMs = 0;
let pendingMiniMapState = null;
let miniMapRenderTimer = null;
let pointCloudRequestInFlight = false;
let pointCloudRefreshTimer = null;
let savedMapPointCloudLoaded = false;
@@ -1432,70 +1399,10 @@ function createMkMiniFrontWheelPivots(model) {
return pivots;
}
async function loadRemoteNavigationRoom() {
const viewer = new GaussianSplats3D.Viewer({
selfDrivenMode: false,
threeScene: scene,
renderer,
camera,
useBuiltInControls: false,
ignoreDevicePixelRatio: true,
gpuAcceleratedSort: false,
sharedMemoryForWorkers: false,
integerBasedSort: true,
halfPrecisionCovariancesOnGPU: true,
dynamicScene: true,
renderMode: GaussianSplats3D.RenderMode.Always,
sceneRevealMode: GaussianSplats3D.SceneRevealMode.Instant,
antialiased: false,
sphericalHarmonicsDegree: 0,
enableOptionalEffects: false,
inMemoryCompressionLevel: 2,
});
await viewer.addSplatScene('./assets/room_0731_navigation.ply', {
position: remoteNavigationSplatBasePosition.toArray(),
rotation: remoteNavigationSplatBaseQuaternion.toArray(),
scale: remoteNavigationSplatBaseScale.toArray(),
splatAlphaRemovalThreshold: 0,
showLoadingUI: false,
});
remoteNavigationSplatViewer = viewer;
remoteNavigationSplatScene = viewer.splatMesh.getScene(0);
remoteNavigationSplatTransform = null;
updateRemoteNavigationRoomTransform();
}
async function setRemoteNavigationSplatVisible(visible) {
if (!SHOW_REMOTE_NAVIGATION_ROOM) return;
splatToggleButton.disabled = true;
try {
if (visible && !remoteNavigationSplatViewer) {
if (!remoteNavigationSplatLoading) {
remoteNavigationSplatLoading = loadRemoteNavigationRoom();
}
await remoteNavigationSplatLoading;
}
remoteNavigationSplatEnabled = visible;
if (remoteNavigationSplatScene) {
remoteNavigationSplatScene.visible = visible;
if (visible) {
remoteNavigationSplatTransform = null;
updateRemoteNavigationRoomTransform();
}
}
remoteNavigationSplatViewer?.forceRenderNextFrame();
} catch (error) {
console.warn('remote navigation room render failed', error);
remoteNavigationSplatEnabled = false;
} finally {
splatToggleButton.disabled = false;
splatToggleButton.classList.toggle('active', remoteNavigationSplatEnabled);
splatToggleButton.setAttribute('aria-pressed', String(remoteNavigationSplatEnabled));
}
}
const LASER_HOLE_R = 0.14;
const LASER_RENDER_INTERVAL_MS = 100;
const LASER_OPEN_SPACE_RAY_STRIDE = 4;
const MINI_MAP_RENDER_INTERVAL_MS = 100;
const LASER_OFFSET_X = USE_MKMINI_MODEL ? 0.15 : -0.032;
const LASER_OFFSET_Y = 0;
// Fallback height when an explicit ROS scan pose is unavailable.
@@ -1507,9 +1414,12 @@ function setRobotPose(pose) {
targetRobotPose = {
x: Number(pose.x ?? targetRobotPose.x),
y: Number(pose.y ?? targetRobotPose.y),
z: Number(pose.z ?? targetRobotPose.z),
roll: Number(pose.roll ?? targetRobotPose.roll),
pitch: Number(pose.pitch ?? targetRobotPose.pitch),
// Gazebo navigation is a ground-plane simulation. A saved 3D map can
// include an elevated RTAB-Map pose, but that must not lift the rendered
// vehicle after the learner supplies a 2D initial pose.
z: REAL_RUNTIME ? Number(pose.z ?? targetRobotPose.z) : 0,
roll: REAL_RUNTIME ? Number(pose.roll ?? targetRobotPose.roll) : 0,
pitch: REAL_RUNTIME ? Number(pose.pitch ?? targetRobotPose.pitch) : 0,
yaw: Number(pose.yaw ?? targetRobotPose.yaw),
};
updateMkMiniSteeringTarget(pose);
@@ -1750,11 +1660,72 @@ function laserDirectionFromPose(pose, angle) {
return new THREE.Vector3(Math.cos(angle), Math.sin(angle), 0).applyQuaternion(q).normalize();
}
function ensureLaserScanLayer() {
if (laserGroup) {
laserGroup.visible = true;
return;
}
laserGroup = new THREE.Group();
laserGroup.name = 'live-laser-scan';
const fillGeometry = new THREE.BufferGeometry();
laserFillMesh = new THREE.Mesh(
fillGeometry,
new THREE.MeshBasicMaterial({
color: 0x005cff,
transparent: true,
opacity: 0.6,
side: THREE.DoubleSide,
depthWrite: false,
}),
);
laserFillMesh.frustumCulled = false;
const lineGeometry = new THREE.BufferGeometry();
laserLineSegments = new THREE.LineSegments(
lineGeometry,
new THREE.LineBasicMaterial({ color: 0x3366ff, transparent: true, opacity: 0.5 }),
);
laserLineSegments.frustumCulled = false;
const openSpaceGeometry = new THREE.BufferGeometry();
laserOpenSpaceLineSegments = new THREE.LineSegments(
openSpaceGeometry,
new THREE.LineBasicMaterial({ color: 0x6f9dff, transparent: true, opacity: 0.28 }),
);
laserOpenSpaceLineSegments.frustumCulled = false;
laserFrontBeam = new THREE.Mesh(
new THREE.CylinderGeometry(0.006, 0.006, 1, 16),
new THREE.MeshBasicMaterial({ color: 0x005cff }),
);
laserFrontBeam.frustumCulled = false;
laserGroup.add(laserFillMesh, laserLineSegments, laserOpenSpaceLineSegments, laserFrontBeam);
sdfRoot.add(laserGroup);
}
function updateLaserGeometry(geometry, values) {
let position = geometry.getAttribute('position');
if (!position || position.array.length < values.length) {
const capacity = Math.max(1024, Math.ceil(values.length * 1.25));
position = new THREE.BufferAttribute(new Float32Array(capacity), 3);
position.setUsage(THREE.DynamicDrawUsage);
geometry.setAttribute('position', position);
}
position.array.set(values);
position.needsUpdate = true;
geometry.setDrawRange(0, values.length / 3);
}
function clearLaserScan() {
if (!laserGroup) return;
sdfRoot.remove(laserGroup);
disposeObject(laserGroup);
laserGroup = null;
laserGroup.visible = false;
laserFillMesh.visible = false;
laserLineSegments.visible = false;
laserOpenSpaceLineSegments.visible = false;
laserFrontBeam.visible = false;
}
function setNavigationSensorVisible(visible) {
@@ -1767,6 +1738,7 @@ function setNavigationSensorVisible(visible) {
window.clearInterval(sensorPointCloudRefreshTimer);
sensorPointCloudRefreshTimer = null;
}
lastLaserRenderAtMs = 0;
clearLaserScan();
clearSensorPointCloud();
drawMiniMap(latestSlamState);
@@ -1775,7 +1747,7 @@ function setNavigationSensorVisible(visible) {
if (SHOW_SENSOR_POINT_CLOUD) {
void refreshSensorPointCloud();
if (!sensorPointCloudRefreshTimer) {
sensorPointCloudRefreshTimer = window.setInterval(() => void refreshSensorPointCloud(), 500);
sensorPointCloudRefreshTimer = window.setInterval(() => void refreshSensorPointCloud(), 750);
}
}
if (latestScanMetadata && latestScanRanges) {
@@ -1796,6 +1768,11 @@ function drawLaserScan(scan, scanPose = (
return;
}
if (!scan?.ranges?.length) return;
// The state socket and scan binary socket can deliver the same scan nearly
// together. Rebuilding transparent scan geometry twice stalls the viewer.
const now = performance.now();
if (now - lastLaserRenderAtMs < LASER_RENDER_INTERVAL_MS) return;
lastLaserRenderAtMs = now;
clearLaserScan();
const sourcePose = scanPose || currentRobotPose;
@@ -1830,7 +1807,7 @@ function drawLaserScan(scan, scanPose = (
endpoints.push({ start, point, hit: valid });
if (valid) {
linePoints.push(start.x, start.y, start.z, point.x, point.y, point.z);
} else if (showOpenSpaceRays) {
} else if (showOpenSpaceRays && i % LASER_OPEN_SPACE_RAY_STRIDE === 0) {
// Match Gazebo's ray visualization: no-return rays remain visible, but
// deliberately subdued so detected obstacles stay the primary signal.
openSpaceLinePoints.push(start.x, start.y, start.z, point.x, point.y, point.z);
@@ -1850,32 +1827,13 @@ function drawLaserScan(scan, scanPose = (
fillPoints.push(next.start.x, next.start.y, next.start.z);
}
const grp = new THREE.Group();
const fillMat = new THREE.MeshBasicMaterial({
color: 0x005cff,
transparent: true,
opacity: 0.6,
side: THREE.DoubleSide,
depthWrite: false,
});
const lineMat = new THREE.LineBasicMaterial({ color: 0x3366ff, transparent: true, opacity: 0.5 });
if (fillPoints.length) {
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.Float32BufferAttribute(fillPoints, 3));
grp.add(new THREE.Mesh(geo, fillMat));
}
const lineGeo = new THREE.BufferGeometry();
lineGeo.setAttribute('position', new THREE.Float32BufferAttribute(linePoints, 3));
grp.add(new THREE.LineSegments(lineGeo, lineMat));
if (openSpaceLinePoints.length) {
const openSpaceGeo = new THREE.BufferGeometry();
openSpaceGeo.setAttribute('position', new THREE.Float32BufferAttribute(openSpaceLinePoints, 3));
grp.add(new THREE.LineSegments(
openSpaceGeo,
new THREE.LineBasicMaterial({ color: 0x6f9dff, transparent: true, opacity: 0.28 }),
));
}
ensureLaserScanLayer();
updateLaserGeometry(laserFillMesh.geometry, fillPoints);
laserFillMesh.visible = fillPoints.length > 0;
updateLaserGeometry(laserLineSegments.geometry, linePoints);
laserLineSegments.visible = linePoints.length > 0;
updateLaserGeometry(laserOpenSpaceLineSegments.geometry, openSpaceLinePoints);
laserOpenSpaceLineSegments.visible = openSpaceLinePoints.length > 0;
const frontIndex = Math.max(0, Math.min(scan.ranges.length - 1, Math.round((0 - angleMin) / angleInc)));
const front = endpoints[frontIndex];
@@ -1889,22 +1847,20 @@ function drawLaserScan(scan, scanPose = (
);
const len = delta.length();
if (len > 0.01) {
const beam = new THREE.Mesh(
new THREE.CylinderGeometry(0.006, 0.006, len, 16),
new THREE.MeshBasicMaterial({ color: 0x005cff })
);
beam.position.set(
laserFrontBeam.position.set(
(front.start.x + front.point.x) / 2,
(front.start.y + front.point.y) / 2,
(front.start.z + front.point.z) / 2
);
beam.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), delta.normalize());
grp.add(beam);
laserFrontBeam.scale.set(1, len, 1);
laserFrontBeam.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), delta.normalize());
laserFrontBeam.visible = true;
} else {
laserFrontBeam.visible = false;
}
} else {
laserFrontBeam.visible = false;
}
laserGroup = grp;
sdfRoot.add(grp);
}
function updatePointGeometry(geometry, points) {
@@ -1923,6 +1879,13 @@ function updatePointGeometry(geometry, points) {
positions[count++] = z;
}
if (count === 0) {
geometry.deleteAttribute('position');
geometry.boundingBox = null;
geometry.boundingSphere = null;
return;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions.slice(0, count), 3));
geometry.computeBoundingSphere();
}
@@ -2061,6 +2024,19 @@ async function refreshCameraFrame() {
}
function drawMiniMap(state) {
pendingMiniMapState = state;
if (miniMapRenderTimer) return;
const elapsed = performance.now() - lastMiniMapRenderAtMs;
const wait = Math.max(0, MINI_MAP_RENDER_INTERVAL_MS - elapsed);
miniMapRenderTimer = window.setTimeout(() => {
miniMapRenderTimer = null;
lastMiniMapRenderAtMs = performance.now();
drawMiniMapNow(pendingMiniMapState);
}, wait);
}
function drawMiniMapNow(state) {
if (!miniCtx) return;
const w = miniMap.width;
const h = miniMap.height;
@@ -2247,7 +2223,6 @@ function updateNavigationMapTransform() {
// estimate and must not move the static world geometry.
gazeboWorldRoot.position.set(0, 0, 0);
gazeboWorldRoot.rotation.set(0, 0, 0);
updateRemoteNavigationRoomTransform();
return;
}
gazeboWorldRoot.position.set(0, 0, 0);
@@ -2263,61 +2238,6 @@ function updateNavigationMapTransform() {
0,
);
navigationMapRoot.rotation.set(0, 0, -yaw);
updateRemoteNavigationRoomTransform();
}
function updateRemoteNavigationRoomTransform() {
if (!remoteNavigationSplatEnabled || !remoteNavigationSplatScene || NAV2_VIEW_MODE !== 'navigation') return;
if (navigationSceneUsesMapFrame()) {
if (remoteNavigationSplatTransform?.frame === 'map') return;
remoteNavigationSplatScene.position.copy(remoteNavigationSplatBasePosition);
remoteNavigationSplatScene.quaternion.copy(remoteNavigationSplatBaseQuaternion);
remoteNavigationSplatScene.scale.copy(remoteNavigationSplatBaseScale);
remoteNavigationSplatViewer.splatMesh.updateTransforms();
remoteNavigationSplatTransform = { frame: 'map' };
return;
}
const transform = navigationMapToOdom || { x:0, y:0, yaw:0 };
if (remoteNavigationSplatTransform
&& remoteNavigationSplatTransform.frame === 'odom'
&& Math.abs(remoteNavigationSplatTransform.x - (transform.x || 0)) < 0.03
&& Math.abs(remoteNavigationSplatTransform.y - (transform.y || 0)) < 0.03
&& Math.abs(remoteNavigationSplatTransform.yaw - (transform.yaw || 0)) < 0.01) {
return;
}
const yaw = transform.yaw || 0;
const c = Math.cos(yaw);
const s = Math.sin(yaw);
// The Gaussian scene is authored in map coordinates. Compose the same
// map <- odom inverse used by the occupancy-map root before each render.
const mapToOdomMatrix = new THREE.Matrix4().compose(
new THREE.Vector3(
-c * (transform.x || 0) - s * (transform.y || 0),
0,
-s * (transform.x || 0) + c * (transform.y || 0),
),
new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), -yaw),
new THREE.Vector3(1, 1, 1),
);
const sceneMatrix = mapToOdomMatrix.multiply(new THREE.Matrix4().compose(
remoteNavigationSplatBasePosition,
remoteNavigationSplatBaseQuaternion,
remoteNavigationSplatBaseScale,
));
sceneMatrix.decompose(
remoteNavigationSplatScene.position,
remoteNavigationSplatScene.quaternion,
remoteNavigationSplatScene.scale,
);
remoteNavigationSplatViewer.splatMesh.updateTransforms();
remoteNavigationSplatTransform = {
frame: 'odom',
x: transform.x || 0,
y: transform.y || 0,
yaw,
};
}
function renderNavigationOccupancyMap() {
@@ -2615,11 +2535,6 @@ sensorToggleButton.addEventListener('click', () => {
focusViewer();
});
splatToggleButton.addEventListener('click', () => {
void setRemoteNavigationSplatVisible(!remoteNavigationSplatEnabled);
focusViewer();
});
cameraPreviewToggle?.addEventListener('click', () => {
const collapsed = cameraPreviewWrap?.classList.toggle('is-collapsed') ?? false;
cameraPreviewToggle.setAttribute('aria-expanded', String(!collapsed));
@@ -3161,7 +3076,7 @@ function applySlamState(state) {
updateNavigationPlan(state?.plan);
// Remote navigation is rendered in the map frame, matching the saved map,
// room model, AMCL pose, route and goal. Local Gazebo remains odom-based.
const visualizationPose = (SHOW_POINT_CLOUD || (REAL_RUNTIME && NAV2_VIEW_MODE === 'navigation' && state?.pose_3d))
const visualizationPose = (REAL_RUNTIME && (SHOW_POINT_CLOUD || (NAV2_VIEW_MODE === 'navigation' && state?.pose_3d)))
? (state?.pose_3d || state?.pose)
: (NAV2_VIEW_MODE === 'navigation'
? (navigationSceneUsesMapFrame() ? (state?.pose_map || state?.pose) : state?.pose)
@@ -3230,7 +3145,7 @@ if (SHOW_POINT_CLOUD) {
}
if (SHOW_SENSOR_POINT_CLOUD && navigationSensorVisible) {
void refreshSensorPointCloud();
sensorPointCloudRefreshTimer = window.setInterval(() => void refreshSensorPointCloud(), 500);
sensorPointCloudRefreshTimer = window.setInterval(() => void refreshSensorPointCloud(), 750);
}
if (ENABLE_CAMERA_PREVIEW && REAL_RUNTIME) {
void refreshCameraFrame();
@@ -3240,6 +3155,7 @@ if (ENABLE_CAMERA_PREVIEW && REAL_RUNTIME) {
window.addEventListener('beforeunload', () => {
if (pointCloudRefreshTimer) window.clearInterval(pointCloudRefreshTimer);
if (sensorPointCloudRefreshTimer) window.clearInterval(sensorPointCloudRefreshTimer);
if (miniMapRenderTimer) window.clearTimeout(miniMapRenderTimer);
if (cameraFrameRefreshTimer) window.clearInterval(cameraFrameRefreshTimer);
if (cameraFrameObjectUrl) URL.revokeObjectURL(cameraFrameObjectUrl);
});
@@ -3459,7 +3375,7 @@ window.addEventListener('blur', () => {
// A static Gazebo model can contain assets that the browser cannot render.
// Keep the ROS stream available even when the decorative world mesh fails.
const worldSceneTasks = CUSTOM_WORLD || REAL_RUNTIME
const worldSceneTasks = CUSTOM_WORLD || SAVED_GAZEBO_MAP_WORLD || REAL_RUNTIME
? []
: [buildFromSdf().catch((error) => console.warn('world model render failed', error))];
const remoteNavigationSceneTasks = [];
@@ -3488,12 +3404,7 @@ function animate(){
updateSmoothedRobotPose();
updateMkMiniFrontWheelSteering();
controls.update();
if (remoteNavigationSplatViewer && remoteNavigationSplatEnabled) {
remoteNavigationSplatViewer.update();
remoteNavigationSplatViewer.render();
} else {
renderer.render(scene, camera);
}
renderer.render(scene, camera);
}
animate();
</script>
File diff suppressed because it is too large Load Diff