001package jmri.jmrit.vsdecoder.listener; 002 003import java.util.regex.Matcher; 004import java.util.regex.Pattern; 005import java.util.regex.PatternSyntaxException; 006import javax.vecmath.Vector3d; 007import javax.vecmath.Vector3f; 008import jmri.util.PhysicalLocation; 009import org.jdom2.Element; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Represents a defined spot for viewing (and therefore listening to) a layout. 015 * 016 * <hr> 017 * This file is part of JMRI. 018 * <p> 019 * JMRI is free software; you can redistribute it and/or modify it under 020 * the terms of version 2 of the GNU General Public License as published 021 * by the Free Software Foundation. See the "COPYING" file for a copy 022 * of this license. 023 * <p> 024 * JMRI is distributed in the hope that it will be useful, but WITHOUT 025 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 026 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 027 * for more details. 028 * 029 * @author Mark Underwood Copyright (C) 2012 030 */ 031public class ListeningSpot { 032 033 private Vector3d _location; 034 private Vector3d _up; 035 private Vector3d _lookAt; 036 private String _name; 037 038 private static final Vector3d _atVector = new Vector3d(0.0d, 1.0d, 0.0d); 039 private static final Vector3d _upVector = new Vector3d(0.0d, 0.0d, 1.0d); 040 041 public ListeningSpot() { 042 _name = null; 043 _location = new Vector3d(); 044 _up = _upVector; 045 _lookAt = _atVector; 046 } 047 048 public ListeningSpot(Vector3f position) { 049 _name = null; 050 _location = new Vector3d(position); 051 _up = _upVector; 052 _lookAt = _atVector; 053 } 054 055 public ListeningSpot(Vector3d position) { 056 this(null, position); 057 } 058 059 public ListeningSpot(String name, Vector3d position) { 060 _name = name; 061 _location = position; 062 _lookAt = _atVector; 063 _up = _upVector; 064 } 065 066 public ListeningSpot(String name, Vector3d loc, Vector3d up, Vector3d at) { 067 _name = name; 068 _location = loc; 069 _up = up; 070 _lookAt = at; 071 } 072 073 public ListeningSpot(Element e) { 074 this.setXml(e); 075 } 076 077 public String getName() { 078 return _name; 079 } 080 081 public Vector3d getLocation() { 082 return _location; 083 } 084 085 public PhysicalLocation getPhysicalLocation() { 086 return (new PhysicalLocation(_location.x, _location.y, _location.z)); 087 } 088 089 public Vector3d getUpVector() { 090 return _up; 091 } 092 093 public Vector3d getLookAtVector() { 094 return _lookAt; 095 } 096 097 /* TRig notes 098 * Trig x = map y 099 * Trig y = map x 100 * bearing = theta 101 * azimuth = 90 - rho 102 * map y = r sin (90-azimuth) cos bearing 103 * map x = r sin (90-azimuth) sin bearing 104 * map z = r cos (90-azimuth) 105 * r = sqrt( x^2 + y^2 + z^2 ) 106 * bearing = theta = atan(map x / map y) 107 * azimuth = 90 - rho = 90 - acos(z / r) 108 */ 109 public Double getBearing() { 110 // bearing = theta = atan(map x / map y) 111 Vector3d lav= getLookAtVector(); 112 Double b = Math.toDegrees(Math.atan(lav.x / lav.y)); 113 114 // lookAt point is behind listener 115 if (lav.y < 0.0d) { 116 b = b + 180.0d; 117 } else if (b < 0.0d) { 118 b = b + 360.0d; 119 } 120 return b; 121 } 122 123 public Double getAzimuth() { 124 // r = sqrt( x^2 + y^2 + z^2 ) 125 // azimuth = 90 - rho = 90 - acos(z / r) 126 Vector3d lav = getLookAtVector(); 127 Double r = Math.sqrt(lav.x * lav.x + lav.y * lav.y + lav.z * lav.z); 128 return 90 - Math.toDegrees(Math.acos(lav.z / r)); 129 } 130 131 public void setName(String n) { 132 _name = n; 133 } 134 135 public void setLocation(Vector3d loc) { 136 _location = loc; 137 } 138 139 public void setLocation(Double x, Double y, Double z) { 140 if (x == null) { 141 x = 0.0d; 142 } else { 143 x = checkLimits(x); 144 x = roundDecimal(x); 145 } 146 if (y == null) { 147 y = 0.0d; 148 } else { 149 y = checkLimits(y); 150 y = roundDecimal(y); 151 } 152 if (z == null) { 153 z = 0.0d; 154 } else { 155 z = checkLimits(z); 156 z = roundDecimal(z); 157 } 158 _location = new Vector3d(x, y, z); 159 } 160 161 public void setLocation(PhysicalLocation l) { 162 _location = new Vector3d(l.getX(), l.getY(), l.getZ()); 163 } 164 165 public void setUpVector(Vector3d up) { 166 _up = up; 167 } 168 169 public void setLookAtVector(Vector3d at) { 170 _lookAt = at; 171 } 172 173 public void setOrientation(PhysicalLocation target) { 174 Vector3d la = new Vector3d(); 175 // Calculate the look-at vector 176 la.sub(target.toVector3d(), _location); // la = target - location 177 la.normalize(); 178 _lookAt = la; 179 // Calculate the up vector 180 _up = calcUpFromLookAt(la); 181 } 182 183 private Vector3d calcUpFromLookAt(Vector3d la) { 184 Vector3d _la = la; 185 _la.normalize(); 186 Vector3d up = new Vector3d(); 187 up.cross(_la, _upVector); 188 up.cross(up, _la); 189 up.normalize(); 190 return up; 191 } 192 193 public void setOrientation(Double bearing, Double azimuth) { 194 // Convert bearing + azimuth to look-at and up vectors. 195 // Bearing measured clockwise from Y axis. 196 // Azimuth measured up (or down) from X/Y plane. 197 // map y = r sin (90-azimuth) cos bearing 198 // map x = r sin (90-azimuth) sin bearing 199 // map z = r cos (90-azimuth) 200 // Assumes r = 1; 201 if (bearing == null) { 202 bearing = 0.0d; 203 } 204 205 if (azimuth == null) { 206 azimuth = 0.0d; 207 } 208 209 if (azimuth > 90.0d) { 210 azimuth = 180.0d - azimuth; 211 } else if (azimuth < -90.0d) { 212 azimuth = -180.0d - azimuth; 213 } 214 215 double y = Math.sin(Math.toRadians(90 - azimuth)) * Math.cos(Math.toRadians(bearing)); 216 double x = Math.sin(Math.toRadians(90 - azimuth)) * Math.sin(Math.toRadians(bearing)); 217 double z = Math.cos(Math.toRadians(90 - azimuth)); 218 _lookAt = new Vector3d(x, y, z); 219 _up = calcUpFromLookAt(_lookAt); 220 221 _lookAt.x = roundDecimal(_lookAt.x); 222 _lookAt.y = roundDecimal(_lookAt.y); 223 _lookAt.z = roundDecimal(_lookAt.z); 224 225 _up.x = roundDecimal(_up.x); 226 _up.y = roundDecimal(_up.y); 227 _up.z = roundDecimal(_up.z); 228 } 229 230 public Boolean equals(ListeningSpot other) { 231 if ((this._name.equals(other.getName())) 232 && (this._location == other.getLocation()) 233 && (this._up == other.getUpVector()) 234 && (this._lookAt == other.getLookAtVector())) { 235 return true; 236 } else { 237 return false; 238 } 239 } 240 241 private Vector3d parseVector3d(String pos) { 242 if (pos == null) { 243 return null; 244 } 245 246 // position is stored as a tuple string "(x,y,z)" 247 // Regex [-+]?[0-9]*\.?[0-9]+ 248 String syntax = "\\((\\s*[-+]?[0-9]*\\.?[0-9]+),(\\s*[-+]?[0-9]*\\.?[0-9]+),(\\s*[-+]?[0-9]*\\.?[0-9]+)\\)"; 249 try { 250 Pattern p = Pattern.compile(syntax); 251 Matcher m = p.matcher(pos); 252 if (!m.matches()) { 253 log.error("String does not match a valid position pattern. syntax: {}, string: {}", syntax, pos); 254 return null; 255 } 256 // ++debug 257 String xs = m.group(1); 258 String ys = m.group(2); 259 String zs = m.group(3); 260 log.debug("Loading Vector3d: x = {} y = {} z = {}", xs, ys, zs); 261 // --debug 262 return (new Vector3d(Double.parseDouble(m.group(1)), Double.parseDouble(m.group(2)), Double.parseDouble(m.group(3)))); 263 } catch (PatternSyntaxException e) { 264 log.error("Malformed Vector3d syntax! {}", syntax); 265 return null; 266 } catch (IllegalStateException e) { 267 log.error("Group called before match operation executed syntax: {}, string: {}, {}", syntax, pos, e.toString()); 268 return null; 269 } catch (IndexOutOfBoundsException e) { 270 log.error("Index out of bounds: {}, string: {}, {}", syntax, pos, e.toString()); 271 return null; 272 } 273 } 274 275 @Override 276 public String toString() { 277 if ((_location == null) || (_lookAt == null) || (_up == null)) { 278 return "ListeningSpot (undefined)"; 279 } else { 280 return ("ListeningSpot Name: " + _name + " Location: " + _location.toString() + " LookAt: " + _lookAt.toString() + " Up: " + _up.toString()); 281 } 282 } 283 284 public Element getXml(String elementName) { 285 Element me = new Element(elementName); 286 me.setAttribute("name", (_name == null ? "default" : _name)); 287 me.setAttribute("location", _location.toString()); 288 me.setAttribute("up", _up.toString()); 289 me.setAttribute("look_at", _lookAt.toString()); 290 return me; 291 } 292 293 public void setXml(Element e) { 294 if (e != null) { 295 log.debug("ListeningSpot: {}", e.getAttributeValue("name")); 296 _name = e.getAttributeValue("name"); 297 _location = parseVector3d(e.getAttributeValue("location")); 298 _up = parseVector3d(e.getAttributeValue("up")); 299 _lookAt = parseVector3d(e.getAttributeValue("look_at")); 300 } 301 } 302 303 private static double roundDecimal(double value) { 304 return (double) Math.round(value * 100) / 100; 305 } 306 307 private static final double MAX_DIST = 9999.99d; 308 309 private static double checkLimits(double value) { 310 value = Math.max(-MAX_DIST, value); 311 value = Math.min(MAX_DIST, value); 312 return value; 313 } 314 315 private static final Logger log = LoggerFactory.getLogger(ListeningSpot.class); 316 317}