до изменения генератора датасета
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -63,6 +63,14 @@ function sampleHeight(meshInfo, x, y) {
|
|||||||
return (h00 * (1 - tx) + h10 * tx) * (1 - ty) + (h01 * (1 - tx) + h11 * tx) * ty;
|
return (h00 * (1 - tx) + h10 * tx) * (1 - ty) + (h01 * (1 - tx) + h11 * tx) * ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Flat seafloor datum (without corridor relief). AUV altitude is measured from this. */
|
||||||
|
function referenceSeafloorZ(meshInfo) {
|
||||||
|
if (!meshInfo) return -5;
|
||||||
|
const base = Number(meshInfo.baseZ);
|
||||||
|
if (Number.isFinite(base)) return base;
|
||||||
|
return sampleHeight(meshInfo, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('vue').Ref} containerRef main 3D view
|
* @param {import('vue').Ref} containerRef main 3D view
|
||||||
* @param {import('vue').Ref} surveyRef bottom-right survey surface panel
|
* @param {import('vue').Ref} surveyRef bottom-right survey surface panel
|
||||||
@@ -959,7 +967,7 @@ export function useGboSimulator(containerRef, surveyRef) {
|
|||||||
|
|
||||||
function placeAuv(x, y, depth, headingDeg) {
|
function placeAuv(x, y, depth, headingDeg) {
|
||||||
if (!auvPivot) return;
|
if (!auvPivot) return;
|
||||||
const floorZ = sampleHeight(meshInfo, x, y);
|
const floorZ = referenceSeafloorZ(meshInfo);
|
||||||
auvPivot.position.set(x, y, floorZ + Math.max(0.3, Number(depth) || 2.5));
|
auvPivot.position.set(x, y, floorZ + Math.max(0.3, Number(depth) || 2.5));
|
||||||
headingRad = degToRad(headingDeg);
|
headingRad = degToRad(headingDeg);
|
||||||
auvPivot.rotation.set(0, 0, headingRad);
|
auvPivot.rotation.set(0, 0, headingRad);
|
||||||
@@ -1016,8 +1024,8 @@ export function useGboSimulator(containerRef, surveyRef) {
|
|||||||
traveled += step;
|
traveled += step;
|
||||||
const nx = auvPivot.position.x + Math.cos(headingRad) * step;
|
const nx = auvPivot.position.x + Math.cos(headingRad) * step;
|
||||||
const ny = auvPivot.position.y + Math.sin(headingRad) * step;
|
const ny = auvPivot.position.y + Math.sin(headingRad) * step;
|
||||||
const floorZ = sampleHeight(meshInfo, nx, ny);
|
// Constant altitude vs flat datum — do not follow seafloor relief.
|
||||||
auvPivot.position.set(nx, ny, floorZ + auvDepth);
|
auvPivot.position.set(nx, ny, referenceSeafloorZ(meshInfo) + auvDepth);
|
||||||
updateRays(true);
|
updateRays(true);
|
||||||
updateTrail();
|
updateTrail();
|
||||||
onStatus({
|
onStatus({
|
||||||
|
|||||||
@@ -63,6 +63,14 @@ function sampleHeight(meshInfo, x, y) {
|
|||||||
return (h00 * (1 - tx) + h10 * tx) * (1 - ty) + (h01 * (1 - tx) + h11 * tx) * ty;
|
return (h00 * (1 - tx) + h10 * tx) * (1 - ty) + (h01 * (1 - tx) + h11 * tx) * ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Flat seafloor datum (without corridor relief). AUV altitude is measured from this. */
|
||||||
|
function referenceSeafloorZ(meshInfo) {
|
||||||
|
if (!meshInfo) return -5;
|
||||||
|
const base = Number(meshInfo.baseZ);
|
||||||
|
if (Number.isFinite(base)) return base;
|
||||||
|
return sampleHeight(meshInfo, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('vue').Ref} containerRef main 3D view
|
* @param {import('vue').Ref} containerRef main 3D view
|
||||||
* @param {import('vue').Ref} surveyRef bottom-right survey surface panel
|
* @param {import('vue').Ref} surveyRef bottom-right survey surface panel
|
||||||
@@ -257,8 +265,8 @@ export function useMleSimulator(containerRef, surveyRef) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preview of seafloor/object contacts the AUV will meet along the survey track.
|
* Preview of the future survey swath on the seafloor only (relief / unevenness).
|
||||||
* Starts at current echosounder footprint and extends forward by surveyLength.
|
* Does not wrap or outline the bottom object — object contacts appear only during motion.
|
||||||
*/
|
*/
|
||||||
function buildPathReliefPreview() {
|
function buildPathReliefPreview() {
|
||||||
clearPathRelief();
|
clearPathRelief();
|
||||||
@@ -277,122 +285,65 @@ export function useMleSimulator(containerRef, surveyRef) {
|
|||||||
const startY = auvPivot.position.y;
|
const startY = auvPivot.position.y;
|
||||||
const depth = Math.max(0.3, Number(auvDepth) || 2.5);
|
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 positions = new Float32Array(nAlong * nAcross * 3);
|
||||||
const colors = new Float32Array(nAlong * nAcross * 3);
|
const colors = new Float32Array(nAlong * nAcross * 3);
|
||||||
const flat = new THREE.Color(0x2f6b52);
|
const flat = new THREE.Color(0x2f6b52);
|
||||||
const mid = new THREE.Color(0xe6a820);
|
const mid = new THREE.Color(0xe6a820);
|
||||||
const hot = new THREE.Color(0xfff176);
|
const hot = new THREE.Color(0xfff176);
|
||||||
const objTint = new THREE.Color(0xff6b2d);
|
|
||||||
|
|
||||||
const writeVertex = (row, col, x, y, z, intensity, isObject) => {
|
const writeVertex = (row, col, x, y, z, intensity) => {
|
||||||
const o = (row * nAcross + col) * 3;
|
const o = (row * nAcross + col) * 3;
|
||||||
positions[o] = x;
|
positions[o] = x;
|
||||||
positions[o + 1] = y;
|
positions[o + 1] = y;
|
||||||
positions[o + 2] = z + 0.08; // slightly above so it reads over the wireframe
|
positions[o + 2] = z + 0.08; // slightly above so it reads over the wireframe
|
||||||
let c;
|
const c =
|
||||||
if (isObject) {
|
intensity < 0.35
|
||||||
c = objTint;
|
? flat.clone().lerp(mid, intensity / 0.35)
|
||||||
} else if (intensity < 0.35) {
|
: mid.clone().lerp(hot, Math.min(1, (intensity - 0.35) / 0.65));
|
||||||
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] = c.r;
|
||||||
colors[o + 1] = c.g;
|
colors[o + 1] = c.g;
|
||||||
colors[o + 2] = c.b;
|
colors[o + 2] = c.b;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let col = 0; col < nAcross; col += 1) {
|
const probeSeafloorHit = (ax, ay, originZ, dirX, dirY, dirZ) => {
|
||||||
const u = nAcross === 1 ? 0.5 : col / (nAcross - 1);
|
let hitX = ax;
|
||||||
if (currentHits.length) {
|
let hitY = ay;
|
||||||
const src = currentHits[Math.min(currentHits.length - 1, Math.round(u * (currentHits.length - 1)))];
|
let hitZ = sampleHeight(meshInfo, ax, ay);
|
||||||
const intensity = reliefIntensity(src.x, src.y);
|
const step = Math.max(0.3, maxRange / 180);
|
||||||
writeVertex(0, col, src.x, src.y, src.z, Math.max(intensity, src.isObject ? 1 : 0.2), !!src.isObject);
|
let px = ax;
|
||||||
} else {
|
let py = ay;
|
||||||
const angle = -swath * 0.5 + swath * u;
|
let pz = originZ;
|
||||||
const dirX = Math.sin(angle) * acrossX;
|
for (let s = 0; s < 220; s += 1) {
|
||||||
const dirY = Math.sin(angle) * acrossY;
|
px += dirX * step;
|
||||||
const dirZ = -Math.cos(angle);
|
py += dirY * step;
|
||||||
// fallback probe from AUV
|
pz += dirZ * step;
|
||||||
let hitX = startX;
|
if (Math.hypot(px - ax, py - ay) + Math.abs(pz - originZ) > maxRange * 1.15) break;
|
||||||
let hitY = startY;
|
const floorZ = sampleHeight(meshInfo, px, py);
|
||||||
let hitZ = sampleHeight(meshInfo, startX, startY);
|
if (pz <= floorZ) {
|
||||||
const originZ = hitZ + depth;
|
hitX = px;
|
||||||
const step = Math.max(0.3, maxRange / 180);
|
hitY = py;
|
||||||
let px = startX;
|
hitZ = floorZ;
|
||||||
let py = startY;
|
break;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
return { hitX, hitY, hitZ };
|
||||||
|
};
|
||||||
|
|
||||||
// Forward stations: predicted beam footprint along the future track.
|
// Stations along track: seafloor footprint only (ignore object mesh until simulation).
|
||||||
for (let row = 1; row < nAlong; row += 1) {
|
// Beam origins stay at constant altitude above the flat datum (not terrain-following).
|
||||||
const along = (row / (nAlong - 1)) * length;
|
const originZ = referenceSeafloorZ(meshInfo) + depth;
|
||||||
|
for (let row = 0; row < nAlong; row += 1) {
|
||||||
|
const along = nAlong === 1 ? 0 : (row / (nAlong - 1)) * length;
|
||||||
const ax = startX + hx * along;
|
const ax = startX + hx * along;
|
||||||
const ay = startY + hy * along;
|
const ay = startY + hy * along;
|
||||||
const floorHere = sampleHeight(meshInfo, ax, ay);
|
|
||||||
const originZ = floorHere + depth;
|
|
||||||
for (let col = 0; col < nAcross; col += 1) {
|
for (let col = 0; col < nAcross; col += 1) {
|
||||||
const u = nAcross === 1 ? 0.5 : col / (nAcross - 1);
|
const u = nAcross === 1 ? 0.5 : col / (nAcross - 1);
|
||||||
const angle = -swath * 0.5 + swath * u;
|
const angle = -swath * 0.5 + swath * u;
|
||||||
const dirX = Math.sin(angle) * acrossX;
|
const dirX = Math.sin(angle) * acrossX;
|
||||||
const dirY = Math.sin(angle) * acrossY;
|
const dirY = Math.sin(angle) * acrossY;
|
||||||
const dirZ = -Math.cos(angle);
|
const dirZ = -Math.cos(angle);
|
||||||
let hitX = ax;
|
const { hitX, hitY, hitZ } = probeSeafloorHit(ax, ay, originZ, dirX, dirY, dirZ);
|
||||||
let hitY = ay;
|
writeVertex(row, col, hitX, hitY, hitZ, Math.max(0.12, reliefIntensity(hitX, hitY)));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -893,7 +844,7 @@ export function useMleSimulator(containerRef, surveyRef) {
|
|||||||
|
|
||||||
function placeAuv(x, y, depth, headingDeg) {
|
function placeAuv(x, y, depth, headingDeg) {
|
||||||
if (!auvPivot) return;
|
if (!auvPivot) return;
|
||||||
const floorZ = sampleHeight(meshInfo, x, y);
|
const floorZ = referenceSeafloorZ(meshInfo);
|
||||||
auvPivot.position.set(x, y, floorZ + Math.max(0.3, Number(depth) || 2.5));
|
auvPivot.position.set(x, y, floorZ + Math.max(0.3, Number(depth) || 2.5));
|
||||||
headingRad = degToRad(headingDeg);
|
headingRad = degToRad(headingDeg);
|
||||||
auvPivot.rotation.set(0, 0, headingRad);
|
auvPivot.rotation.set(0, 0, headingRad);
|
||||||
@@ -915,7 +866,7 @@ export function useMleSimulator(containerRef, surveyRef) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function castBeamHits() {
|
function castBeamHits({ includeObject = true } = {}) {
|
||||||
if (!auvPivot || !meshInfo) return [];
|
if (!auvPivot || !meshInfo) return [];
|
||||||
const origin = auvPivot.position.clone();
|
const origin = auvPivot.position.clone();
|
||||||
const count = Math.max(1, Math.min(256, Number(beamCount) || 45));
|
const count = Math.max(1, Math.min(256, Number(beamCount) || 45));
|
||||||
@@ -923,7 +874,7 @@ export function useMleSimulator(containerRef, surveyRef) {
|
|||||||
const maxRange = Math.max(1, Number(detectionRangeM) || DETECTION_RANGE_DEFAULT);
|
const maxRange = Math.max(1, Number(detectionRangeM) || DETECTION_RANGE_DEFAULT);
|
||||||
raycaster.far = maxRange;
|
raycaster.far = maxRange;
|
||||||
const across = new THREE.Vector3(-Math.sin(headingRad), Math.cos(headingRad), 0);
|
const across = new THREE.Vector3(-Math.sin(headingRad), Math.cos(headingRad), 0);
|
||||||
if (objectPivot) objectPivot.updateMatrixWorld(true);
|
if (includeObject && objectPivot) objectPivot.updateMatrixWorld(true);
|
||||||
|
|
||||||
const hits = [];
|
const hits = [];
|
||||||
for (let i = 0; i < count; i += 1) {
|
for (let i = 0; i < count; i += 1) {
|
||||||
@@ -952,10 +903,10 @@ export function useMleSimulator(containerRef, surveyRef) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object first-hit (mesh raycast) — echosounder return if closer than seafloor
|
// Object first-hit (mesh raycast) — only during simulation / survey accumulation
|
||||||
let objPoint = null;
|
let objPoint = null;
|
||||||
let objDist = Infinity;
|
let objDist = Infinity;
|
||||||
if (objectPivot) {
|
if (includeObject && objectPivot) {
|
||||||
raycaster.set(origin, beamDir);
|
raycaster.set(origin, beamDir);
|
||||||
const intersects = raycaster.intersectObject(objectPivot, true);
|
const intersects = raycaster.intersectObject(objectPivot, true);
|
||||||
if (intersects.length && intersects[0].distance <= maxRange) {
|
if (intersects.length && intersects[0].distance <= maxRange) {
|
||||||
@@ -996,7 +947,8 @@ export function useMleSimulator(containerRef, surveyRef) {
|
|||||||
}
|
}
|
||||||
if (!auvPivot || !meshInfo) return;
|
if (!auvPivot || !meshInfo) return;
|
||||||
const origin = auvPivot.position.clone();
|
const origin = auvPivot.position.clone();
|
||||||
const hits = castBeamHits();
|
// Before motion starts, rays and path preview ignore the object (seafloor only).
|
||||||
|
const hits = castBeamHits({ includeObject: !!accumulateSurvey || running });
|
||||||
const positions = new Float32Array(hits.length * 2 * 3);
|
const positions = new Float32Array(hits.length * 2 * 3);
|
||||||
for (let i = 0; i < hits.length; i += 1) {
|
for (let i = 0; i < hits.length; i += 1) {
|
||||||
const o = i * 6;
|
const o = i * 6;
|
||||||
@@ -1060,8 +1012,8 @@ export function useMleSimulator(containerRef, surveyRef) {
|
|||||||
traveled += step;
|
traveled += step;
|
||||||
const nx = auvPivot.position.x + Math.cos(headingRad) * step;
|
const nx = auvPivot.position.x + Math.cos(headingRad) * step;
|
||||||
const ny = auvPivot.position.y + Math.sin(headingRad) * step;
|
const ny = auvPivot.position.y + Math.sin(headingRad) * step;
|
||||||
const floorZ = sampleHeight(meshInfo, nx, ny);
|
// Constant altitude vs flat datum — do not follow seafloor relief.
|
||||||
auvPivot.position.set(nx, ny, floorZ + auvDepth);
|
auvPivot.position.set(nx, ny, referenceSeafloorZ(meshInfo) + auvDepth);
|
||||||
updateRays(true);
|
updateRays(true);
|
||||||
updateTrail();
|
updateTrail();
|
||||||
onStatus({
|
onStatus({
|
||||||
|
|||||||
@@ -221,11 +221,11 @@ function onShotSurvey() {
|
|||||||
<input v-model.number="store.auvX" type="number" step="0.5" :disabled="store.running" />
|
<input v-model.number="store.auvX" type="number" step="0.5" :disabled="store.running" />
|
||||||
</label>
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Y (старт)</span>
|
<span>Y</span>
|
||||||
<input v-model.number="store.auvY" type="number" step="0.5" :disabled="store.running" />
|
<input v-model.number="store.auvY" type="number" step="0.5" :disabled="store.running" />
|
||||||
</label>
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Высота над дном, м</span>
|
<span>Высота над дном (без неровностей), м</span>
|
||||||
<input v-model.number="store.auvDepth" type="number" min="0.3" step="0.1" :disabled="store.running" />
|
<input v-model.number="store.auvDepth" type="number" min="0.3" step="0.1" :disabled="store.running" />
|
||||||
</label>
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
|
|||||||
@@ -241,11 +241,11 @@ function onShotSurvey() {
|
|||||||
<input v-model.number="store.auvX" type="number" step="0.5" :disabled="store.running" />
|
<input v-model.number="store.auvX" type="number" step="0.5" :disabled="store.running" />
|
||||||
</label>
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Y (старт)</span>
|
<span>Y</span>
|
||||||
<input v-model.number="store.auvY" type="number" step="0.5" :disabled="store.running" />
|
<input v-model.number="store.auvY" type="number" step="0.5" :disabled="store.running" />
|
||||||
</label>
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Высота над дном, м</span>
|
<span>Высота над дном (без неровностей), м</span>
|
||||||
<input v-model.number="store.auvDepth" type="number" min="0.3" step="0.1" :disabled="store.running" />
|
<input v-model.number="store.auvDepth" type="number" min="0.3" step="0.1" :disabled="store.running" />
|
||||||
</label>
|
</label>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
|
|||||||
Reference in New Issue
Block a user