Spaces:
Sleeping
Sleeping
Zhen Ye commited on
Commit ·
b2e7d79
1
Parent(s): 1c2827d
fixed radar view
Browse files
LaserPerception/LaserPerception.css
CHANGED
|
@@ -750,7 +750,7 @@ input[type="number"]:focus {
|
|
| 750 |
|
| 751 |
.engage-grid {
|
| 752 |
display: grid;
|
| 753 |
-
grid-template-columns:
|
| 754 |
gap: 12px;
|
| 755 |
min-height: 0;
|
| 756 |
transition: grid-template-columns 0.3s ease;
|
|
@@ -769,11 +769,19 @@ input[type="number"]:focus {
|
|
| 769 |
flex-direction: column;
|
| 770 |
gap: 12px;
|
| 771 |
min-height: 0;
|
| 772 |
-
max-width: 320px;
|
| 773 |
}
|
| 774 |
|
| 775 |
.radar {
|
| 776 |
-
height:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 777 |
}
|
| 778 |
|
| 779 |
.strip {
|
|
|
|
| 750 |
|
| 751 |
.engage-grid {
|
| 752 |
display: grid;
|
| 753 |
+
grid-template-columns: 2.5fr 1.5fr;
|
| 754 |
gap: 12px;
|
| 755 |
min-height: 0;
|
| 756 |
transition: grid-template-columns 0.3s ease;
|
|
|
|
| 769 |
flex-direction: column;
|
| 770 |
gap: 12px;
|
| 771 |
min-height: 0;
|
|
|
|
| 772 |
}
|
| 773 |
|
| 774 |
.radar {
|
| 775 |
+
height: 540px;
|
| 776 |
+
display: flex;
|
| 777 |
+
flex-direction: column;
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
.radar canvas {
|
| 781 |
+
flex: 1;
|
| 782 |
+
width: 100%;
|
| 783 |
+
height: 100%;
|
| 784 |
+
display: block;
|
| 785 |
}
|
| 786 |
|
| 787 |
.strip {
|
LaserPerception/LaserPerception.html
CHANGED
|
@@ -400,7 +400,7 @@
|
|
| 400 |
</div>
|
| 401 |
</h3>
|
| 402 |
|
| 403 |
-
<div class="viewbox" style="min-height:
|
| 404 |
<video id="videoEngage" playsinline muted></video>
|
| 405 |
<canvas id="engageOverlay" class="overlay"></canvas>
|
| 406 |
<div class="watermark">LOCK · DIST · DWELL · AIMPOINT · FIRE/ASSESS</div>
|
|
|
|
| 400 |
</div>
|
| 401 |
</h3>
|
| 402 |
|
| 403 |
+
<div class="viewbox" style="min-height: 420px;">
|
| 404 |
<video id="videoEngage" playsinline muted></video>
|
| 405 |
<canvas id="engageOverlay" class="overlay"></canvas>
|
| 406 |
<div class="watermark">LOCK · DIST · DWELL · AIMPOINT · FIRE/ASSESS</div>
|
LaserPerception/LaserPerception.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
| 19 |
const clamp = (x, a, b) => Math.min(b, Math.max(a, x));
|
| 20 |
const lerp = (a, b, t) => a + (b - a) * t;
|
| 21 |
const now = () => performance.now();
|
|
|
|
| 22 |
|
| 23 |
const state = {
|
| 24 |
videoUrl: null,
|
|
@@ -177,6 +178,9 @@
|
|
| 177 |
const chipFeed = $("#chipFeed");
|
| 178 |
|
| 179 |
const btnEngage = $("#btnEngage");
|
|
|
|
|
|
|
|
|
|
| 180 |
const btnPause = $("#btnPause");
|
| 181 |
const btnReset = $("#btnReset");
|
| 182 |
const btnToggleSidebar = $("#btnToggleSidebar");
|
|
@@ -2311,24 +2315,7 @@
|
|
| 2311 |
const ay = b.y + b.h * d.aim.rely;
|
| 2312 |
drawAimpoint(ctx, ax, ay, isSel);
|
| 2313 |
|
| 2314 |
-
//
|
| 2315 |
-
const range = Math.round(d.baseRange_m || 0);
|
| 2316 |
-
const dwell = (d.baseDwell_s != null) ? d.baseDwell_s.toFixed(1) : "—";
|
| 2317 |
-
const pk = (d.pkill != null) ? Math.round(d.pkill * 100) : "—";
|
| 2318 |
-
|
| 2319 |
-
ctx.font = "bold 14px " + getComputedStyle(document.body).fontFamily;
|
| 2320 |
-
const tag = `${d.id} · ${d.label} · R=${range}m · DWELL=${dwell}s · Pk=${pk}%`;
|
| 2321 |
-
const tw = ctx.measureText(tag).width;
|
| 2322 |
-
const tx = clamp(b.x, 6, w - tw - 12);
|
| 2323 |
-
const ty = clamp(b.y - 12, 18, h - 12);
|
| 2324 |
-
|
| 2325 |
-
ctx.fillStyle = "rgba(0,0,0,.50)";
|
| 2326 |
-
ctx.strokeStyle = "rgba(255,255,255,.14)";
|
| 2327 |
-
ctx.lineWidth = 1;
|
| 2328 |
-
roundRect(ctx, tx - 6, ty - 14, tw + 12, 18, 8, true, true);
|
| 2329 |
-
|
| 2330 |
-
ctx.fillStyle = "rgba(255,255,255,.86)";
|
| 2331 |
-
ctx.fillText(tag, tx, ty);
|
| 2332 |
});
|
| 2333 |
|
| 2334 |
// click-to-select on canvas (manual aimpoint override can be added later)
|
|
@@ -2801,11 +2788,16 @@
|
|
| 2801 |
} else if (tr.state === "ASSESS") {
|
| 2802 |
tr.assessT += dtSec;
|
| 2803 |
if (tr.assessT >= assessS) {
|
| 2804 |
-
|
| 2805 |
-
|
| 2806 |
-
|
| 2807 |
-
|
| 2808 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2809 |
}
|
| 2810 |
}
|
| 2811 |
|
|
@@ -2908,10 +2900,7 @@
|
|
| 2908 |
if (!killed) {
|
| 2909 |
drawAimpoint(ctx, ax, ay, isSel);
|
| 2910 |
} else {
|
| 2911 |
-
// killed marker
|
| 2912 |
-
ctx.fillStyle = "rgba(34,197,94,.95)";
|
| 2913 |
-
ctx.font = "14px " + getComputedStyle(document.body).fontFamily;
|
| 2914 |
-
ctx.fillText("NEUTRALIZED", b.x + 10, b.y + 22);
|
| 2915 |
}
|
| 2916 |
|
| 2917 |
// dwell ring
|
|
@@ -2924,23 +2913,7 @@
|
|
| 2924 |
ctx.stroke();
|
| 2925 |
}
|
| 2926 |
|
| 2927 |
-
//
|
| 2928 |
-
const rangeTag = Number.isFinite(displayRange.range)
|
| 2929 |
-
? `${Math.round(displayRange.range)}m (${displayRange.source})`
|
| 2930 |
-
: "—";
|
| 2931 |
-
const tag = `${tr.id} · R=${rangeTag} · DWELL=${reqD.toFixed(1)}s · ΔP=${margin >= 0 ? "+" : ""}${margin.toFixed(1)}kW`;
|
| 2932 |
-
ctx.font = "bold 14px " + getComputedStyle(document.body).fontFamily;
|
| 2933 |
-
const tw = ctx.measureText(tag).width;
|
| 2934 |
-
const tx = clamp(b.x, 6, w - tw - 12);
|
| 2935 |
-
const ty = clamp(b.y - 12, 18, h - 12);
|
| 2936 |
-
|
| 2937 |
-
ctx.fillStyle = "rgba(0,0,0,.55)";
|
| 2938 |
-
ctx.strokeStyle = "rgba(255,255,255,.14)";
|
| 2939 |
-
ctx.lineWidth = 1;
|
| 2940 |
-
roundRect(ctx, tx - 6, ty - 14, tw + 12, 18, 8, true, true);
|
| 2941 |
-
|
| 2942 |
-
ctx.fillStyle = "rgba(255,255,255,.86)";
|
| 2943 |
-
ctx.fillText(tag, tx, ty);
|
| 2944 |
|
| 2945 |
// engagement strip indicator near bbox bottom
|
| 2946 |
const st = tr.state || "TRACK";
|
|
@@ -2950,9 +2923,7 @@
|
|
| 2950 |
ctx.fillRect(b.x, b.y + b.h + 4, clamp(b.w * 0.55, 70, b.w), 5);
|
| 2951 |
ctx.globalAlpha = 1;
|
| 2952 |
|
| 2953 |
-
|
| 2954 |
-
ctx.font = "11px " + getComputedStyle(document.body).fontFamily;
|
| 2955 |
-
ctx.fillText(st, b.x, b.y + b.h + 18);
|
| 2956 |
|
| 2957 |
// beam line to selected aimpoint
|
| 2958 |
if (state.tracker.beamOn && isSel && !killed) {
|
|
@@ -3026,6 +2997,14 @@
|
|
| 3026 |
// ========= Radar rendering =========
|
| 3027 |
function renderRadar() {
|
| 3028 |
const ctx = radarCanvas.getContext("2d");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3029 |
const w = radarCanvas.width, h = radarCanvas.height;
|
| 3030 |
ctx.clearRect(0, 0, w, h);
|
| 3031 |
|
|
@@ -3033,7 +3012,7 @@
|
|
| 3033 |
ctx.fillStyle = "rgba(0,0,0,.35)";
|
| 3034 |
ctx.fillRect(0, 0, w, h);
|
| 3035 |
|
| 3036 |
-
const cx = w * 0.5, cy = h * 0.
|
| 3037 |
const R = Math.min(w, h) * 0.42;
|
| 3038 |
|
| 3039 |
// rings
|
|
@@ -3065,7 +3044,7 @@
|
|
| 3065 |
ctx.fill();
|
| 3066 |
|
| 3067 |
// tracks as blips
|
| 3068 |
-
const tracks = state.tracker.tracks
|
| 3069 |
tracks.forEach(tr => {
|
| 3070 |
const areaRange = rangeFromArea(tr);
|
| 3071 |
const displayRange = getTrackDisplayRange(tr);
|
|
@@ -3087,16 +3066,17 @@
|
|
| 3087 |
const py = cy + Math.sin(bearing) * rad;
|
| 3088 |
|
| 3089 |
// color by engagement state
|
| 3090 |
-
const col = tr.
|
| 3091 |
-
(tr.state === "
|
| 3092 |
-
"rgba(
|
|
|
|
| 3093 |
|
| 3094 |
ctx.fillStyle = col;
|
| 3095 |
ctx.beginPath();
|
| 3096 |
ctx.arc(px, py, 5, 0, Math.PI * 2);
|
| 3097 |
ctx.fill();
|
| 3098 |
|
| 3099 |
-
ctx.fillStyle = "rgba(255,255,255,.75)";
|
| 3100 |
ctx.font = "11px " + getComputedStyle(document.body).fontFamily;
|
| 3101 |
const rangeLabel = Number.isFinite(displayRange.range)
|
| 3102 |
? `${Math.round(displayRange.range)}m`
|
|
|
|
| 19 |
const clamp = (x, a, b) => Math.min(b, Math.max(a, x));
|
| 20 |
const lerp = (a, b, t) => a + (b - a) * t;
|
| 21 |
const now = () => performance.now();
|
| 22 |
+
const ENABLE_KILL = false;
|
| 23 |
|
| 24 |
const state = {
|
| 25 |
videoUrl: null,
|
|
|
|
| 178 |
const chipFeed = $("#chipFeed");
|
| 179 |
|
| 180 |
const btnEngage = $("#btnEngage");
|
| 181 |
+
|
| 182 |
+
// Debug hook for console inspection
|
| 183 |
+
window.__LP_STATE__ = state;
|
| 184 |
const btnPause = $("#btnPause");
|
| 185 |
const btnReset = $("#btnReset");
|
| 186 |
const btnToggleSidebar = $("#btnToggleSidebar");
|
|
|
|
| 2315 |
const ay = b.y + b.h * d.aim.rely;
|
| 2316 |
drawAimpoint(ctx, ax, ay, isSel);
|
| 2317 |
|
| 2318 |
+
// no text overlay on first-frame view
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2319 |
});
|
| 2320 |
|
| 2321 |
// click-to-select on canvas (manual aimpoint override can be added later)
|
|
|
|
| 2788 |
} else if (tr.state === "ASSESS") {
|
| 2789 |
tr.assessT += dtSec;
|
| 2790 |
if (tr.assessT >= assessS) {
|
| 2791 |
+
if (ENABLE_KILL) {
|
| 2792 |
+
tr.killed = true;
|
| 2793 |
+
tr.state = "KILL";
|
| 2794 |
+
state.tracker.beamOn = false; // stop beam after kill to make it dramatic
|
| 2795 |
+
chipBeam.textContent = "BEAM:OFF";
|
| 2796 |
+
log(`Target ${tr.id} assessed neutralized.`, "g");
|
| 2797 |
+
} else {
|
| 2798 |
+
tr.state = "ASSESS";
|
| 2799 |
+
tr.assessT = 0;
|
| 2800 |
+
}
|
| 2801 |
}
|
| 2802 |
}
|
| 2803 |
|
|
|
|
| 2900 |
if (!killed) {
|
| 2901 |
drawAimpoint(ctx, ax, ay, isSel);
|
| 2902 |
} else {
|
| 2903 |
+
// killed marker (no text overlay)
|
|
|
|
|
|
|
|
|
|
| 2904 |
}
|
| 2905 |
|
| 2906 |
// dwell ring
|
|
|
|
| 2913 |
ctx.stroke();
|
| 2914 |
}
|
| 2915 |
|
| 2916 |
+
// no text overlay on engage view
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2917 |
|
| 2918 |
// engagement strip indicator near bbox bottom
|
| 2919 |
const st = tr.state || "TRACK";
|
|
|
|
| 2923 |
ctx.fillRect(b.x, b.y + b.h + 4, clamp(b.w * 0.55, 70, b.w), 5);
|
| 2924 |
ctx.globalAlpha = 1;
|
| 2925 |
|
| 2926 |
+
// no state text overlay on engage view
|
|
|
|
|
|
|
| 2927 |
|
| 2928 |
// beam line to selected aimpoint
|
| 2929 |
if (state.tracker.beamOn && isSel && !killed) {
|
|
|
|
| 2997 |
// ========= Radar rendering =========
|
| 2998 |
function renderRadar() {
|
| 2999 |
const ctx = radarCanvas.getContext("2d");
|
| 3000 |
+
const rect = radarCanvas.getBoundingClientRect();
|
| 3001 |
+
const dpr = devicePixelRatio || 1;
|
| 3002 |
+
const targetW = Math.max(1, Math.floor(rect.width * dpr));
|
| 3003 |
+
const targetH = Math.max(1, Math.floor(rect.height * dpr));
|
| 3004 |
+
if (radarCanvas.width !== targetW || radarCanvas.height !== targetH) {
|
| 3005 |
+
radarCanvas.width = targetW;
|
| 3006 |
+
radarCanvas.height = targetH;
|
| 3007 |
+
}
|
| 3008 |
const w = radarCanvas.width, h = radarCanvas.height;
|
| 3009 |
ctx.clearRect(0, 0, w, h);
|
| 3010 |
|
|
|
|
| 3012 |
ctx.fillStyle = "rgba(0,0,0,.35)";
|
| 3013 |
ctx.fillRect(0, 0, w, h);
|
| 3014 |
|
| 3015 |
+
const cx = w * 0.5, cy = h * 0.5;
|
| 3016 |
const R = Math.min(w, h) * 0.42;
|
| 3017 |
|
| 3018 |
// rings
|
|
|
|
| 3044 |
ctx.fill();
|
| 3045 |
|
| 3046 |
// tracks as blips
|
| 3047 |
+
const tracks = state.tracker.tracks;
|
| 3048 |
tracks.forEach(tr => {
|
| 3049 |
const areaRange = rangeFromArea(tr);
|
| 3050 |
const displayRange = getTrackDisplayRange(tr);
|
|
|
|
| 3066 |
const py = cy + Math.sin(bearing) * rad;
|
| 3067 |
|
| 3068 |
// color by engagement state
|
| 3069 |
+
const col = tr.killed ? "rgba(148,163,184,.65)" :
|
| 3070 |
+
(tr.state === "FIRE" ? "rgba(239,68,68,.9)" :
|
| 3071 |
+
(tr.state === "ASSESS" ? "rgba(245,158,11,.9)" :
|
| 3072 |
+
"rgba(124,58,237,.9)"));
|
| 3073 |
|
| 3074 |
ctx.fillStyle = col;
|
| 3075 |
ctx.beginPath();
|
| 3076 |
ctx.arc(px, py, 5, 0, Math.PI * 2);
|
| 3077 |
ctx.fill();
|
| 3078 |
|
| 3079 |
+
ctx.fillStyle = tr.killed ? "rgba(148,163,184,.75)" : "rgba(255,255,255,.75)";
|
| 3080 |
ctx.font = "11px " + getComputedStyle(document.body).fontFamily;
|
| 3081 |
const rangeLabel = Number.isFinite(displayRange.range)
|
| 3082 |
? `${Math.round(displayRange.range)}m`
|