|
| 1 | +import React, { useState, useEffect, useRef } from "react"; |
| 2 | +import { states } from "jderobot-commsmanager"; |
| 3 | +import { useExercise } from "Contexts/ExerciseContext"; |
| 4 | +import { updatePath, addToPath } from "./helpers/showImageVisual"; |
| 5 | +import RobotRed from "./resources/robot_red.svg"; |
| 6 | +import RobotGreen from "./resources/robot_green.svg"; |
| 7 | +import RobotBlue from "./resources/robot_blue.svg"; |
| 8 | +import WebGUIImage from "Components/exercise/WebGUIImage"; |
| 9 | +import WebGUIContainer, { |
| 10 | + connectApplication, |
| 11 | +} from "Components/exercise/WebGUIContainer"; |
| 12 | + |
| 13 | +import house from "./resources/map.png"; |
| 14 | +import "./css/GUICanvas.css"; |
| 15 | + |
| 16 | +interface Beacon { |
| 17 | + id: string; |
| 18 | + x: number; |
| 19 | + y: number; |
| 20 | + type: string; |
| 21 | +} |
| 22 | + |
| 23 | +function WebGUI() { |
| 24 | + const [realPose, setRealPose] = useState<number[] | undefined>(); |
| 25 | + const [noisyPose, setNoisyPose] = useState<number[] | undefined>(); |
| 26 | + const [userPose, setUserPose] = useState<number[] | undefined>(); |
| 27 | + const [realPath, setRealPath] = useState<string>(""); |
| 28 | + const [noisyPath, setNoisyPath] = useState<string>(""); |
| 29 | + const [userPath, setUserPath] = useState<string>(""); |
| 30 | + const [resizedBeacons, setResizedBeacons] = useState<Beacon[]>([]); |
| 31 | + const [userImage, setUserImage] = useState<string | undefined>(undefined); |
| 32 | + const canvasRef = useRef<HTMLImageElement>(null); |
| 33 | + const exerciseContext = useExercise(); |
| 34 | + const [manager, setManager] = useState(exerciseContext.manager); |
| 35 | + |
| 36 | + useEffect(() => { |
| 37 | + setManager(exerciseContext.manager); |
| 38 | + }, [exerciseContext]); |
| 39 | + |
| 40 | + let realTrail: number[][] = []; |
| 41 | + let noisyTrail: number[][] = []; |
| 42 | + let userTrail: number[][] = []; |
| 43 | + let realLastPose: number[] | undefined = undefined; |
| 44 | + let noisyLastPose: number[] | undefined = undefined; |
| 45 | + let userLastPose: number[] | undefined = undefined; |
| 46 | + let valuesUntilValid = 0; |
| 47 | + |
| 48 | + const timeout = 0; |
| 49 | + |
| 50 | + const beacons: Beacon[] = [ |
| 51 | + { id: "tag_0", x: 518.75, y: 270.325, type: "hor" }, |
| 52 | + { id: "tag_1", x: 481.4, y: 810.775, type: "hor" }, |
| 53 | + { id: "tag_2", x: 196.395, y: 339.15, type: "vert" }, |
| 54 | + { id: "tag_3", x: 400.89, y: 79.9, type: "hor" }, |
| 55 | + { id: "tag_4", x: 844.94, y: 712.3, type: "vert" }, |
| 56 | + { id: "tag_5", x: 295.03, y: 499.8, type: "vert" }, |
| 57 | + { id: "tag_6", x: 730.4, y: 350.55, type: "hor" }, |
| 58 | + { id: "tag_7", x: 499.66, y: 140.25, type: "vert" }, |
| 59 | + ]; |
| 60 | + |
| 61 | + const resizeObserver = new ResizeObserver((entries) => { |
| 62 | + const img = entries[0].target; |
| 63 | + //or however you get a handle to the IMG |
| 64 | + const width = img.clientWidth / 1012; |
| 65 | + const height = img.clientHeight / 1012; |
| 66 | + |
| 67 | + setResizedBeacons( |
| 68 | + beacons.map((beacon) => ({ |
| 69 | + id: beacon.id, |
| 70 | + x: beacon.x * width, |
| 71 | + y: beacon.y * height, |
| 72 | + type: beacon.type, |
| 73 | + })) |
| 74 | + ); |
| 75 | + |
| 76 | + updatePath(realTrail, setRealPath, height, width); |
| 77 | + updatePath(noisyTrail, setNoisyPath, height, width); |
| 78 | + updatePath(userTrail, setUserPath, height, width); |
| 79 | + |
| 80 | + if (realLastPose) { |
| 81 | + setRealPose([ |
| 82 | + realLastPose[1] * height, |
| 83 | + realLastPose[0] * width, |
| 84 | + -1.57 - realLastPose[2], |
| 85 | + ]); |
| 86 | + } |
| 87 | + |
| 88 | + if (noisyLastPose) { |
| 89 | + setNoisyPose([ |
| 90 | + noisyLastPose[1] * height, |
| 91 | + noisyLastPose[0] * width, |
| 92 | + -1.57 - noisyLastPose[2], |
| 93 | + ]); |
| 94 | + } |
| 95 | + |
| 96 | + if (userLastPose) { |
| 97 | + setUserPose([ |
| 98 | + userLastPose[1] * height, |
| 99 | + userLastPose[0] * width, |
| 100 | + -1.57 - userLastPose[2], |
| 101 | + ]); |
| 102 | + } |
| 103 | + |
| 104 | + valuesUntilValid = 0; |
| 105 | + }); |
| 106 | + |
| 107 | + const updateCallback = (updateData: unknown) => { |
| 108 | + const data = updateData as any; |
| 109 | + const update = data.update; |
| 110 | + |
| 111 | + const img = canvasRef.current; |
| 112 | + if (img === null) { |
| 113 | + return; |
| 114 | + } |
| 115 | + const width = img.clientWidth / 1012; |
| 116 | + const height = img.clientHeight / 1012; |
| 117 | + |
| 118 | + if (update.real_pose) { |
| 119 | + const pose = update.real_pose.substring(1, update.real_pose.length - 1); |
| 120 | + const content = pose.split(",").map((item: string) => parseFloat(item)); |
| 121 | + realLastPose = content; |
| 122 | + |
| 123 | + setRealPose([ |
| 124 | + content[1] * height, |
| 125 | + content[0] * width, |
| 126 | + -1.57 - content[2], |
| 127 | + ]); |
| 128 | + if (valuesUntilValid > timeout) { |
| 129 | + updatePath(realTrail, setRealPath, height, width); |
| 130 | + addToPath(content[1], content[0], realTrail); |
| 131 | + } else { |
| 132 | + valuesUntilValid = valuesUntilValid + 1; |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + if (update.noisy_pose) { |
| 137 | + const pose = update.noisy_pose.substring(1, update.noisy_pose.length - 1); |
| 138 | + const content = pose.split(",").map((item: string) => parseFloat(item)); |
| 139 | + noisyLastPose = content; |
| 140 | + |
| 141 | + setNoisyPose([ |
| 142 | + content[1] * height, |
| 143 | + content[0] * width, |
| 144 | + -1.57 - content[2], |
| 145 | + ]); |
| 146 | + if (valuesUntilValid > timeout) { |
| 147 | + updatePath(noisyTrail, setNoisyPath, height, width); |
| 148 | + addToPath(content[1], content[0], noisyTrail); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + if (update.estimate_pose) { |
| 153 | + const pose = update.estimate_pose.substring( |
| 154 | + 1, |
| 155 | + update.estimate_pose.length - 1 |
| 156 | + ); |
| 157 | + const content = pose.split(",").map((item: string) => parseFloat(item)); |
| 158 | + userLastPose = content; |
| 159 | + |
| 160 | + setUserPose([ |
| 161 | + content[1] * height, |
| 162 | + content[0] * width, |
| 163 | + -1.57 - content[2], |
| 164 | + ]); |
| 165 | + if (valuesUntilValid > timeout) { |
| 166 | + updatePath(userTrail, setUserPath, height, width); |
| 167 | + addToPath(content[1], content[0], userTrail); |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + if (update.image) { |
| 172 | + const image = JSON.parse(update.image); |
| 173 | + if (image.shape instanceof Array) { |
| 174 | + setUserImage(`data:image/png;base64,${image.image}`); |
| 175 | + } |
| 176 | + } |
| 177 | + }; |
| 178 | + |
| 179 | + const stateCallback = (state: string) => { |
| 180 | + if (state === states.TOOLS_READY) { |
| 181 | + setRealPose(undefined); |
| 182 | + setNoisyPose(undefined); |
| 183 | + setUserPose(undefined); |
| 184 | + setUserImage(undefined); |
| 185 | + setRealPath(""); |
| 186 | + setNoisyPath(""); |
| 187 | + setUserPath(""); |
| 188 | + realTrail = []; |
| 189 | + noisyTrail = []; |
| 190 | + userTrail = []; |
| 191 | + } |
| 192 | + |
| 193 | + valuesUntilValid = 0; |
| 194 | + }; |
| 195 | + |
| 196 | + connectApplication( |
| 197 | + manager, |
| 198 | + updateCallback, |
| 199 | + stateCallback, |
| 200 | + canvasRef, |
| 201 | + resizeObserver |
| 202 | + ); |
| 203 | + |
| 204 | + return ( |
| 205 | + <WebGUIContainer> |
| 206 | + <WebGUIImage |
| 207 | + reference={canvasRef} |
| 208 | + id="map-img" |
| 209 | + src={house} |
| 210 | + style={{ left: "0" }} |
| 211 | + /> |
| 212 | + <WebGUIImage src={userImage} style={{ left: "50%" }} /> |
| 213 | + {realPose && ( |
| 214 | + <div |
| 215 | + id="real-pos" |
| 216 | + style={{ |
| 217 | + rotate: "z " + realPose[2] + "rad", |
| 218 | + top: realPose[0] - 10, |
| 219 | + left: realPose[1] - 5, |
| 220 | + }} |
| 221 | + > |
| 222 | + <img src={RobotGreen} id="real-pos" /> |
| 223 | + </div> |
| 224 | + )} |
| 225 | + {realPath && ( |
| 226 | + <svg |
| 227 | + height="100%" |
| 228 | + width="100%" |
| 229 | + xmlns="http://www.w3.org/2000/svg" |
| 230 | + style={{ zIndex: 2, position: "absolute" }} |
| 231 | + > |
| 232 | + <path |
| 233 | + xmlns="http://www.w3.org/2000/svg" |
| 234 | + d={realPath} |
| 235 | + style={{ |
| 236 | + strokeWidth: "1px", |
| 237 | + strokeLinejoin: "round", |
| 238 | + stroke: "green", |
| 239 | + fill: "none", |
| 240 | + opacity: "0.5", |
| 241 | + }} |
| 242 | + /> |
| 243 | + </svg> |
| 244 | + )} |
| 245 | + {noisyPose && ( |
| 246 | + <div |
| 247 | + id="noisy-pos" |
| 248 | + style={{ |
| 249 | + rotate: "z " + noisyPose[2] + "rad", |
| 250 | + top: noisyPose[0] - 10, |
| 251 | + left: noisyPose[1] - 5, |
| 252 | + }} |
| 253 | + > |
| 254 | + <img src={RobotBlue} id="noisy-pos" /> |
| 255 | + </div> |
| 256 | + )} |
| 257 | + {noisyPath && ( |
| 258 | + <svg |
| 259 | + height="100%" |
| 260 | + width="100%" |
| 261 | + xmlns="http://www.w3.org/2000/svg" |
| 262 | + style={{ zIndex: 2, position: "absolute" }} |
| 263 | + > |
| 264 | + <path |
| 265 | + xmlns="http://www.w3.org/2000/svg" |
| 266 | + d={noisyPath} |
| 267 | + style={{ |
| 268 | + strokeWidth: "1px", |
| 269 | + strokeLinejoin: "round", |
| 270 | + stroke: "blue", |
| 271 | + fill: "none", |
| 272 | + opacity: "0.5", |
| 273 | + }} |
| 274 | + /> |
| 275 | + </svg> |
| 276 | + )} |
| 277 | + {userPose && ( |
| 278 | + <div |
| 279 | + id="user-pos" |
| 280 | + style={{ |
| 281 | + rotate: "z " + userPose[2] + "rad", |
| 282 | + top: userPose[0] - 10, |
| 283 | + left: userPose[1] - 5, |
| 284 | + }} |
| 285 | + > |
| 286 | + <img src={RobotRed} id="user-pos" /> |
| 287 | + </div> |
| 288 | + )} |
| 289 | + {userPath && ( |
| 290 | + <svg |
| 291 | + height="100%" |
| 292 | + width="100%" |
| 293 | + xmlns="http://www.w3.org/2000/svg" |
| 294 | + style={{ zIndex: 2, position: "absolute" }} |
| 295 | + > |
| 296 | + <path |
| 297 | + xmlns="http://www.w3.org/2000/svg" |
| 298 | + d={userPath} |
| 299 | + style={{ |
| 300 | + strokeWidth: "1px", |
| 301 | + strokeLinejoin: "round", |
| 302 | + stroke: "red", |
| 303 | + fill: "none", |
| 304 | + opacity: "0.5", |
| 305 | + }} |
| 306 | + /> |
| 307 | + </svg> |
| 308 | + )} |
| 309 | + {Object.values(resizedBeacons).map((beacon) => { |
| 310 | + return ( |
| 311 | + <div |
| 312 | + key={beacon.id} |
| 313 | + className={`beacon ${beacon.type}`} |
| 314 | + style={{ |
| 315 | + top: `${beacon.y}px`, |
| 316 | + left: `${beacon.x}px`, |
| 317 | + position: "absolute", |
| 318 | + border: "2px solid rgb(255, 255, 255)", |
| 319 | + cursor: "pointer", |
| 320 | + zIndex: "5", |
| 321 | + width: `${beacon.type == "vert" ? 0 : 20}px`, |
| 322 | + height: `${beacon.type == "hor" ? 0 : 20}px`, |
| 323 | + }} |
| 324 | + title={`ID: ${beacon.id}`} |
| 325 | + /> |
| 326 | + ); |
| 327 | + })} |
| 328 | + </WebGUIContainer> |
| 329 | + ); |
| 330 | +} |
| 331 | + |
| 332 | +export default WebGUI; |
0 commit comments