Сохраняемся
This commit is contained in:
@@ -73,6 +73,7 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
// —— main scene ——
|
||||
const SCENE_BG = 0x1a3d38;
|
||||
const SEAFLOOR_COLOR = 0x3f7a62;
|
||||
const RELIEF_COLOR = 0xffc107; // bright gold for path unevenness
|
||||
const RAY_COLOR = 0xffb020;
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(SCENE_BG);
|
||||
@@ -115,6 +116,17 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
let animationId = 0;
|
||||
let seafloorMesh = null; // unused for draw; height-field used for hits
|
||||
let seafloorGrid = null;
|
||||
let pathReliefMesh = null;
|
||||
let pathReliefEdges = null;
|
||||
let lastSeafloorPayload = null;
|
||||
let surveyCorridor = {
|
||||
auvX: 0,
|
||||
auvY: -20,
|
||||
headingDeg: 0,
|
||||
surveyLength: 40,
|
||||
swathAngleDeg: 90,
|
||||
auvDepth: 2.5,
|
||||
};
|
||||
let auvPivot = null;
|
||||
let objectPivot = null;
|
||||
let rayLines = null;
|
||||
@@ -190,15 +202,257 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
});
|
||||
}
|
||||
|
||||
function buildSeafloor(payload) {
|
||||
function featureReliefAbs(x, y, params) {
|
||||
if (!params) return 0;
|
||||
let z = 0;
|
||||
const bumpLists = [
|
||||
[params.hills || [], 1],
|
||||
[params.valleys || [], -1],
|
||||
[params.bumps || [], 1],
|
||||
];
|
||||
for (const [list, sign] of bumpLists) {
|
||||
for (const item of list) {
|
||||
const cx = Number(item[0]);
|
||||
const cy = Number(item[1]);
|
||||
const amp = Number(item[2]);
|
||||
const rad = Math.max(Number(item[3]) || 0.1, 1e-6);
|
||||
const d2 = (x - cx) ** 2 + (y - cy) ** 2;
|
||||
if (d2 < rad * rad * 4) {
|
||||
z += sign * amp * Math.exp(-d2 / (rad * rad));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Math.abs(z);
|
||||
}
|
||||
|
||||
function setSurveyCorridor(partial = {}) {
|
||||
surveyCorridor = { ...surveyCorridor, ...partial };
|
||||
}
|
||||
|
||||
function localRoughness(x, y) {
|
||||
if (!meshInfo) return 0;
|
||||
const z0 = sampleHeight(meshInfo, x, y);
|
||||
const eps = 0.8;
|
||||
return (
|
||||
Math.abs(sampleHeight(meshInfo, x + eps, y) - z0) +
|
||||
Math.abs(sampleHeight(meshInfo, x - eps, y) - z0) +
|
||||
Math.abs(sampleHeight(meshInfo, x, y + eps) - z0) +
|
||||
Math.abs(sampleHeight(meshInfo, x, y - eps) - z0)
|
||||
);
|
||||
}
|
||||
|
||||
function reliefIntensity(x, y) {
|
||||
const params = meshInfo?.params || lastSeafloorPayload?.params || {};
|
||||
const feature = featureReliefAbs(x, y, params);
|
||||
const rough = localRoughness(x, y);
|
||||
// Emphasize discrete bumps/hills and local slope along the future track.
|
||||
return Math.min(1, feature / 0.45 + rough / 0.55);
|
||||
}
|
||||
|
||||
function clearPathRelief() {
|
||||
clearObject(pathReliefMesh);
|
||||
clearObject(pathReliefEdges);
|
||||
pathReliefMesh = null;
|
||||
pathReliefEdges = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview of seafloor/object contacts the AUV will meet along the survey track.
|
||||
* Starts at current echosounder footprint and extends forward by surveyLength.
|
||||
*/
|
||||
function buildPathReliefPreview() {
|
||||
clearPathRelief();
|
||||
if (running || !auvPivot || !meshInfo) return;
|
||||
|
||||
const length = Math.max(1, Number(surveyLength) || 40);
|
||||
const nAlong = Math.max(28, Math.min(120, Math.ceil(length / 0.9) + 1));
|
||||
const nAcross = Math.max(24, Math.min(96, Math.max(Number(beamCount) || 45, 32)));
|
||||
const swath = degToRad(Math.max(5, Math.min(170, Number(swathAngleDeg) || 90)));
|
||||
const maxRange = Math.max(1, Number(detectionRangeM) || DETECTION_RANGE_DEFAULT);
|
||||
const hx = Math.cos(headingRad);
|
||||
const hy = Math.sin(headingRad);
|
||||
const acrossX = -Math.sin(headingRad);
|
||||
const acrossY = Math.cos(headingRad);
|
||||
const startX = auvPivot.position.x;
|
||||
const startY = auvPivot.position.y;
|
||||
const depth = Math.max(0.3, Number(auvDepth) || 2.5);
|
||||
|
||||
// Station 0: real beam hits (where rays touch seafloor/object now).
|
||||
const currentHits = castBeamHits();
|
||||
const positions = new Float32Array(nAlong * nAcross * 3);
|
||||
const colors = new Float32Array(nAlong * nAcross * 3);
|
||||
const flat = new THREE.Color(0x2f6b52);
|
||||
const mid = new THREE.Color(0xe6a820);
|
||||
const hot = new THREE.Color(0xfff176);
|
||||
const objTint = new THREE.Color(0xff6b2d);
|
||||
|
||||
const writeVertex = (row, col, x, y, z, intensity, isObject) => {
|
||||
const o = (row * nAcross + col) * 3;
|
||||
positions[o] = x;
|
||||
positions[o + 1] = y;
|
||||
positions[o + 2] = z + 0.08; // slightly above so it reads over the wireframe
|
||||
let c;
|
||||
if (isObject) {
|
||||
c = objTint;
|
||||
} else if (intensity < 0.35) {
|
||||
c = flat.clone().lerp(mid, intensity / 0.35);
|
||||
} else {
|
||||
c = mid.clone().lerp(hot, Math.min(1, (intensity - 0.35) / 0.65));
|
||||
}
|
||||
// Keep the whole swath visible; boost alpha via brightness on relief.
|
||||
colors[o] = c.r;
|
||||
colors[o + 1] = c.g;
|
||||
colors[o + 2] = c.b;
|
||||
};
|
||||
|
||||
for (let col = 0; col < nAcross; col += 1) {
|
||||
const u = nAcross === 1 ? 0.5 : col / (nAcross - 1);
|
||||
if (currentHits.length) {
|
||||
const src = currentHits[Math.min(currentHits.length - 1, Math.round(u * (currentHits.length - 1)))];
|
||||
const intensity = reliefIntensity(src.x, src.y);
|
||||
writeVertex(0, col, src.x, src.y, src.z, Math.max(intensity, src.isObject ? 1 : 0.2), !!src.isObject);
|
||||
} else {
|
||||
const angle = -swath * 0.5 + swath * u;
|
||||
const dirX = Math.sin(angle) * acrossX;
|
||||
const dirY = Math.sin(angle) * acrossY;
|
||||
const dirZ = -Math.cos(angle);
|
||||
// fallback probe from AUV
|
||||
let hitX = startX;
|
||||
let hitY = startY;
|
||||
let hitZ = sampleHeight(meshInfo, startX, startY);
|
||||
const originZ = hitZ + depth;
|
||||
const step = Math.max(0.3, maxRange / 180);
|
||||
let px = startX;
|
||||
let py = startY;
|
||||
let pz = originZ;
|
||||
for (let s = 0; s < 220; s += 1) {
|
||||
px += dirX * step;
|
||||
py += dirY * step;
|
||||
pz += dirZ * step;
|
||||
const floorZ = sampleHeight(meshInfo, px, py);
|
||||
if (pz <= floorZ) {
|
||||
hitX = px;
|
||||
hitY = py;
|
||||
hitZ = floorZ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
writeVertex(0, col, hitX, hitY, hitZ, Math.max(0.15, reliefIntensity(hitX, hitY)), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Forward stations: predicted beam footprint along the future track.
|
||||
for (let row = 1; row < nAlong; row += 1) {
|
||||
const along = (row / (nAlong - 1)) * length;
|
||||
const ax = startX + hx * along;
|
||||
const ay = startY + hy * along;
|
||||
const floorHere = sampleHeight(meshInfo, ax, ay);
|
||||
const originZ = floorHere + depth;
|
||||
for (let col = 0; col < nAcross; col += 1) {
|
||||
const u = nAcross === 1 ? 0.5 : col / (nAcross - 1);
|
||||
const angle = -swath * 0.5 + swath * u;
|
||||
const dirX = Math.sin(angle) * acrossX;
|
||||
const dirY = Math.sin(angle) * acrossY;
|
||||
const dirZ = -Math.cos(angle);
|
||||
let hitX = ax;
|
||||
let hitY = ay;
|
||||
let hitZ = floorHere;
|
||||
let isObject = false;
|
||||
const step = Math.max(0.3, maxRange / 180);
|
||||
let px = ax;
|
||||
let py = ay;
|
||||
let pz = originZ;
|
||||
for (let s = 0; s < 220; s += 1) {
|
||||
px += dirX * step;
|
||||
py += dirY * step;
|
||||
pz += dirZ * step;
|
||||
if (Math.hypot(px - ax, py - ay) + Math.abs(pz - originZ) > maxRange * 1.15) break;
|
||||
const floorZ = sampleHeight(meshInfo, px, py);
|
||||
if (pz <= floorZ) {
|
||||
hitX = px;
|
||||
hitY = py;
|
||||
hitZ = floorZ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Object occlusion preview: if object mesh is above floor along beam, tint as object.
|
||||
if (objectPivot) {
|
||||
const origin = new THREE.Vector3(ax, ay, originZ);
|
||||
const dir = new THREE.Vector3(dirX, dirY, dirZ).normalize();
|
||||
raycaster.set(origin, dir);
|
||||
raycaster.far = maxRange;
|
||||
const intersects = raycaster.intersectObject(objectPivot, true);
|
||||
if (intersects.length) {
|
||||
const dFloor = Math.hypot(hitX - ax, hitY - ay, hitZ - originZ);
|
||||
if (intersects[0].distance < dFloor) {
|
||||
hitX = intersects[0].point.x;
|
||||
hitY = intersects[0].point.y;
|
||||
hitZ = intersects[0].point.z;
|
||||
isObject = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
writeVertex(row, col, hitX, hitY, hitZ, Math.max(0.12, reliefIntensity(hitX, hitY)), isObject);
|
||||
}
|
||||
}
|
||||
|
||||
const indices = [];
|
||||
for (let row = 0; row < nAlong - 1; row += 1) {
|
||||
for (let col = 0; col < nAcross - 1; col += 1) {
|
||||
const a = row * nAcross + col;
|
||||
const b = a + 1;
|
||||
const c = a + nAcross;
|
||||
const d = c + 1;
|
||||
indices.push(a, c, b, b, c, d);
|
||||
}
|
||||
}
|
||||
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
||||
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
||||
geometry.setIndex(indices);
|
||||
geometry.computeVertexNormals();
|
||||
|
||||
pathReliefMesh = new THREE.Mesh(
|
||||
geometry,
|
||||
new THREE.MeshStandardMaterial({
|
||||
vertexColors: true,
|
||||
metalness: 0.05,
|
||||
roughness: 0.7,
|
||||
transparent: true,
|
||||
opacity: 0.88,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
}),
|
||||
);
|
||||
pathReliefMesh.renderOrder = 2;
|
||||
scene.add(pathReliefMesh);
|
||||
|
||||
pathReliefEdges = new THREE.LineSegments(
|
||||
new THREE.EdgesGeometry(geometry, 28),
|
||||
new THREE.LineBasicMaterial({
|
||||
color: RELIEF_COLOR,
|
||||
transparent: true,
|
||||
opacity: 0.95,
|
||||
}),
|
||||
);
|
||||
pathReliefEdges.renderOrder = 3;
|
||||
scene.add(pathReliefEdges);
|
||||
}
|
||||
|
||||
function buildSeafloor(payload, corridorOpts = null) {
|
||||
clearObject(seafloorMesh);
|
||||
clearObject(seafloorGrid);
|
||||
clearPathRelief();
|
||||
seafloorMesh = null;
|
||||
seafloorGrid = null;
|
||||
meshInfo = null;
|
||||
if (!payload?.mesh?.heights?.length && !payload?.mesh?.vertices?.length) return;
|
||||
const { heights, resX, resY } = payload.mesh;
|
||||
const params = payload.params || {};
|
||||
if (payload) lastSeafloorPayload = payload;
|
||||
if (corridorOpts) setSurveyCorridor(corridorOpts);
|
||||
const src = payload || lastSeafloorPayload;
|
||||
if (!src?.mesh?.heights?.length && !src?.mesh?.vertices?.length) return;
|
||||
const { heights, resX, resY } = src.mesh;
|
||||
const params = src.params || {};
|
||||
const sizeX = Number(params.sizeX) || 40;
|
||||
const sizeY = Number(params.sizeY) || 60;
|
||||
meshInfo = {
|
||||
@@ -208,13 +462,13 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
sizeX,
|
||||
sizeY,
|
||||
baseZ: params.baseZ,
|
||||
params,
|
||||
};
|
||||
|
||||
// Lightweight terrain grid (LineSegments) — infinite skirt, low vertex count
|
||||
const pad = Math.max(sizeX, sizeY, detectionRangeM || 400) * 2.5;
|
||||
const extSizeX = sizeX + pad * 2;
|
||||
const extSizeY = sizeY + pad * 2;
|
||||
// Coarse grid: enough to show relief, cheap to draw
|
||||
const nX = Math.min(64, Math.max(24, Math.round(extSizeX / Math.max(sizeX / 16, 4)) + 1));
|
||||
const nY = Math.min(64, Math.max(24, Math.round(extSizeY / Math.max(sizeY / 16, 4)) + 1));
|
||||
const halfX = extSizeX * 0.5;
|
||||
@@ -231,7 +485,6 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
pts[o + 2] = z;
|
||||
}
|
||||
}
|
||||
// Horizontal + vertical polylines as segments
|
||||
const segCount = nY * (nX - 1) + nX * (nY - 1);
|
||||
const linePos = new Float32Array(segCount * 2 * 3);
|
||||
let w = 0;
|
||||
@@ -258,7 +511,7 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
new THREE.LineBasicMaterial({
|
||||
color: SEAFLOOR_COLOR,
|
||||
transparent: true,
|
||||
opacity: 0.9,
|
||||
opacity: 0.85,
|
||||
}),
|
||||
);
|
||||
scene.add(seafloorGrid);
|
||||
@@ -557,6 +810,29 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
return normalizeModel(g, Math.max(0.05, Number(sizeM) || 2));
|
||||
}
|
||||
|
||||
/** Straight horizontal cylinder: sizeM = diameter, lengthM = length along local X. */
|
||||
function makePipelineObject(sizeM = 2, lengthM = 20) {
|
||||
const diameter = Math.max(0.05, Number(sizeM) || 2);
|
||||
const length = Math.max(0.1, Number(lengthM) || 20);
|
||||
const radius = diameter * 0.5;
|
||||
const mesh = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(radius, radius, length, 36, 1, false),
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0xf59e0b,
|
||||
metalness: 0.28,
|
||||
roughness: 0.55,
|
||||
flatShading: false,
|
||||
}),
|
||||
);
|
||||
// Default cylinder axis is Y → rotate onto X (horizontal pipeline).
|
||||
mesh.rotation.z = Math.PI / 2;
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
const g = new THREE.Group();
|
||||
g.add(mesh);
|
||||
return g;
|
||||
}
|
||||
|
||||
async function setAuvModel(file, sizeM = 10, x = 0, y = 0, depth = 2.5, headingDeg = 0) {
|
||||
clearObject(auvPivot);
|
||||
auvPivot = new THREE.Group();
|
||||
@@ -573,17 +849,43 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
placeAuv(x, y, depth, headingDeg);
|
||||
}
|
||||
|
||||
async function setObjectModel(file, sizeM = 2, x = 0, y = 0, zOffset = 0, rotXDeg = 0, rotYDeg = 0, rotZDeg = 0) {
|
||||
let lastObjectFile = null;
|
||||
let lastObjectKind = "file";
|
||||
let lastObjectSizeM = 2;
|
||||
let lastObjectLengthM = 20;
|
||||
|
||||
async function setObjectModel(
|
||||
file,
|
||||
sizeM = 2,
|
||||
x = 0,
|
||||
y = 0,
|
||||
zOffset = 0,
|
||||
rotXDeg = 0,
|
||||
rotYDeg = 0,
|
||||
rotZDeg = 0,
|
||||
options = {},
|
||||
) {
|
||||
clearObject(objectPivot);
|
||||
objectPivot = new THREE.Group();
|
||||
const targetSize = Math.max(0.05, Number(sizeM) || 2);
|
||||
const kind = options.kind === "pipe" ? "pipe" : "file";
|
||||
const lengthM = Math.max(0.1, Number(options.lengthM) || 20);
|
||||
lastObjectFile = file || null;
|
||||
lastObjectKind = kind;
|
||||
lastObjectSizeM = targetSize;
|
||||
lastObjectLengthM = lengthM;
|
||||
|
||||
let model = null;
|
||||
try {
|
||||
model = await loadObjFile(file, targetSize);
|
||||
} catch {
|
||||
model = null;
|
||||
if (kind === "pipe") {
|
||||
model = makePipelineObject(targetSize, lengthM);
|
||||
} else {
|
||||
try {
|
||||
model = await loadObjFile(file, targetSize);
|
||||
} catch {
|
||||
model = null;
|
||||
}
|
||||
if (!model) model = makeFallbackObject(targetSize);
|
||||
}
|
||||
if (!model) model = makeFallbackObject(targetSize);
|
||||
objectPivot.add(model);
|
||||
scene.add(objectPivot);
|
||||
placeObject(x, y, zOffset, rotXDeg, rotYDeg, rotZDeg);
|
||||
@@ -718,6 +1020,8 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
appendSurveyStrip(hits);
|
||||
void persistSurveySurface(false);
|
||||
}
|
||||
} else {
|
||||
buildPathReliefPreview();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,8 +1126,6 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
|
||||
async function applyScene({ seafloorPayload, auvFile, objectFile, params }) {
|
||||
runOutputDir = seafloorPayload?.outputDir || null;
|
||||
buildSeafloor(seafloorPayload);
|
||||
resetSurveySurface();
|
||||
beamCount = params.beamCount ?? 45;
|
||||
swathAngleDeg = params.swathAngleDeg ?? 90;
|
||||
detectionRangeM = Math.max(1, Number(params.detectionRangeM) || DETECTION_RANGE_DEFAULT);
|
||||
@@ -831,6 +1133,15 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
speed = Math.max(0.05, Number(params.speed) || 1.5);
|
||||
surveyLength = Math.max(1, Number(params.surveyLength) || 40);
|
||||
auvDepth = Math.max(0.3, Number(params.auvDepth) || 2.5);
|
||||
buildSeafloor(seafloorPayload, {
|
||||
auvX: params.auvX,
|
||||
auvY: params.auvY,
|
||||
headingDeg: params.auvHeadingDeg,
|
||||
surveyLength,
|
||||
swathAngleDeg,
|
||||
auvDepth,
|
||||
});
|
||||
resetSurveySurface();
|
||||
await setAuvModel(
|
||||
auvFile,
|
||||
params.auvSizeM,
|
||||
@@ -848,6 +1159,10 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
params.objectRotXDeg,
|
||||
params.objectRotYDeg,
|
||||
params.objectRotZDeg ?? params.objectYawDeg,
|
||||
{
|
||||
kind: params.objectKind,
|
||||
lengthM: params.objectLengthM,
|
||||
},
|
||||
);
|
||||
trailPoints.length = 0;
|
||||
traveled = 0;
|
||||
@@ -858,6 +1173,7 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
function start() {
|
||||
if (!auvPivot || !meshInfo) return false;
|
||||
resetSurveySurface();
|
||||
clearPathRelief();
|
||||
running = true;
|
||||
lastTs = 0;
|
||||
traveled = 0;
|
||||
@@ -869,6 +1185,7 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
function stop() {
|
||||
running = false;
|
||||
void persistSurveySurface(true);
|
||||
buildPathReliefPreview();
|
||||
}
|
||||
|
||||
function isRunning() {
|
||||
@@ -891,6 +1208,27 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
if (params.surveyLength != null) surveyLength = Math.max(1, Number(params.surveyLength) || 40);
|
||||
if (params.auvDepth != null) auvDepth = Math.max(0.3, Number(params.auvDepth) || 2.5);
|
||||
|
||||
const corridorChanged =
|
||||
params.auvX != null ||
|
||||
params.auvY != null ||
|
||||
params.auvHeadingDeg != null ||
|
||||
params.surveyLength != null ||
|
||||
params.swathAngleDeg != null ||
|
||||
params.auvDepth != null;
|
||||
if (corridorChanged) {
|
||||
setSurveyCorridor({
|
||||
auvX: params.auvX ?? surveyCorridor.auvX,
|
||||
auvY: params.auvY ?? surveyCorridor.auvY,
|
||||
headingDeg: params.auvHeadingDeg ?? surveyCorridor.headingDeg,
|
||||
surveyLength,
|
||||
swathAngleDeg,
|
||||
auvDepth,
|
||||
});
|
||||
if (lastSeafloorPayload && !running) {
|
||||
buildPathReliefPreview();
|
||||
}
|
||||
}
|
||||
|
||||
let moved = false;
|
||||
if (!running && auvPivot && (params.auvX != null || params.auvY != null || params.auvDepth != null || params.auvHeadingDeg != null)) {
|
||||
placeAuv(
|
||||
@@ -927,6 +1265,43 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
if (!running) updateRays(false);
|
||||
moved = true;
|
||||
}
|
||||
|
||||
const nextKind = params.objectKind === "pipe" ? "pipe" : params.objectKind != null ? "file" : null;
|
||||
const nextSize =
|
||||
params.objectSizeM != null ? Math.max(0.05, Number(params.objectSizeM) || 2) : null;
|
||||
const nextLen =
|
||||
params.objectLengthM != null ? Math.max(0.1, Number(params.objectLengthM) || 20) : null;
|
||||
const rebuildObject =
|
||||
objectPivot &&
|
||||
meshInfo &&
|
||||
!running &&
|
||||
((nextKind != null && nextKind !== lastObjectKind) ||
|
||||
(nextSize != null && Math.abs(nextSize - lastObjectSizeM) > 1e-6) ||
|
||||
(nextLen != null && Math.abs(nextLen - lastObjectLengthM) > 1e-6));
|
||||
if (rebuildObject) {
|
||||
void setObjectModel(
|
||||
lastObjectFile,
|
||||
nextSize ?? lastObjectSizeM,
|
||||
params.objectX ?? objectPivot.position.x,
|
||||
params.objectY ?? objectPivot.position.y,
|
||||
params.objectZ != null ? params.objectZ : lastObjectZ,
|
||||
params.objectRotXDeg != null ? params.objectRotXDeg : lastObjectRotXDeg,
|
||||
params.objectRotYDeg != null ? params.objectRotYDeg : lastObjectRotYDeg,
|
||||
params.objectRotZDeg != null
|
||||
? params.objectRotZDeg
|
||||
: params.objectYawDeg != null
|
||||
? params.objectYawDeg
|
||||
: lastObjectRotZDeg,
|
||||
{
|
||||
kind: nextKind ?? lastObjectKind,
|
||||
lengthM: nextLen ?? lastObjectLengthM,
|
||||
},
|
||||
).then(() => {
|
||||
if (!running) updateRays(false);
|
||||
fitCamera();
|
||||
});
|
||||
moved = true;
|
||||
}
|
||||
if (moved || params.fitCamera) fitCamera();
|
||||
}
|
||||
|
||||
@@ -975,6 +1350,7 @@ export function useMleSimulator(containerRef, surveyRef) {
|
||||
surveyControls?.dispose();
|
||||
clearObject(seafloorMesh);
|
||||
clearObject(seafloorGrid);
|
||||
clearPathRelief();
|
||||
clearObject(auvPivot);
|
||||
clearObject(objectPivot);
|
||||
clearObject(rayLines);
|
||||
|
||||
Reference in New Issue
Block a user