1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using LibRender2; 5 using LibRender2.Trains; 6 using OpenBveApi.Interface; 7 using OpenBveApi.Math; 8 using OpenBveApi.Objects; 9 using OpenBveApi.Runtime; 10 using OpenTK.Graphics.OpenGL; 11 using TrainManager.Car; 12 using Vector2 = OpenTK.Vector2; 13 14 namespace OpenBve.Graphics.Renderers 15 { 16 internal class Touch 17 { 18 private struct PickedObject 19 { 20 internal int NameDepth; 21 internal int[] Names; 22 internal double MinDepth; 23 internal double MaxDepth; 24 } 25 26 private readonly NewRenderer renderer; 27 private readonly List<ObjectState> touchableObject; 28 private readonly FrameBufferObject fbo; 29 private ObjectState prePickedObject; 30 Touch(NewRenderer renderer)31 internal Touch(NewRenderer renderer) 32 { 33 this.renderer = renderer; 34 touchableObject = new List<ObjectState>(); 35 36 if (!renderer.ForceLegacyOpenGL) 37 { 38 fbo = new FrameBufferObject(); 39 fbo.Bind(); 40 fbo.SetTextureBuffer(FrameBufferObject.TargetBuffer.Color, PixelInternalFormat.R32f, PixelFormat.Red, PixelType.Float, renderer.Screen.Width, renderer.Screen.Height); 41 fbo.DrawBuffers(new[] { DrawBuffersEnum.ColorAttachment0 }); 42 fbo.UnBind(); 43 } 44 } 45 UpdateViewport()46 internal void UpdateViewport() 47 { 48 if (renderer.AvailableNewRenderer) 49 { 50 fbo.Bind(); 51 fbo.SetTextureBuffer(FrameBufferObject.TargetBuffer.Color, PixelInternalFormat.R32f, PixelFormat.Red, PixelType.Float, renderer.Screen.Width, renderer.Screen.Height); 52 fbo.DrawBuffers(new[] { DrawBuffersEnum.ColorAttachment0 }); 53 fbo.UnBind(); 54 } 55 } 56 ShowObject(ObjectState state)57 private void ShowObject(ObjectState state) 58 { 59 touchableObject.Add(state); 60 61 if (renderer.AvailableNewRenderer && state.Prototype.Mesh.VAO == null) 62 { 63 VAOExtensions.CreateVAO(ref state.Prototype.Mesh, state.Prototype.Dynamic, renderer.pickingShader.VertexLayout, renderer); 64 } 65 } 66 PreRender()67 private void PreRender() 68 { 69 touchableObject.Clear(); 70 71 if (!Loading.SimulationSetup) 72 { 73 return; 74 } 75 76 if (renderer.Camera.CurrentMode != CameraViewMode.Interior && renderer.Camera.CurrentMode != CameraViewMode.InteriorLookAhead) 77 { 78 return; 79 } 80 81 CarBase Car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; 82 int add = Car.CarSections[0].CurrentAdditionalGroup + 1; 83 84 if (add >= Car.CarSections[0].Groups.Length) 85 { 86 return; 87 } 88 89 TouchElement[] TouchElements = Car.CarSections[0].Groups[add].TouchElements; 90 91 if (TouchElements == null) 92 { 93 return; 94 } 95 96 foreach (TouchElement element in TouchElements) 97 { 98 ShowObject(element.Element.internalObject); 99 } 100 } 101 RenderScene()102 internal void RenderScene() 103 { 104 PreRender(); 105 106 if (!touchableObject.Any()) 107 { 108 //Drop out early if the pre-render process reveals no available touch faces for a minor boost 109 return; 110 } 111 112 renderer.ResetOpenGlState(); 113 114 if (renderer.AvailableNewRenderer) 115 { 116 fbo.Bind(); 117 GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f); 118 GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 119 renderer.pickingShader.Activate(); 120 renderer.pickingShader.SetCurrentProjectionMatrix(renderer.CurrentProjectionMatrix); 121 122 for (int i = 0; i < touchableObject.Count; i++) 123 { 124 renderer.pickingShader.SetObjectIndex(i + 1); 125 126 foreach (MeshFace face in touchableObject[i].Prototype.Mesh.Faces) 127 { 128 renderer.RenderFace(renderer.pickingShader, touchableObject[i], face); 129 } 130 } 131 132 //Must deactivate and unbind here 133 renderer.pickingShader.Deactivate(); 134 fbo.UnBind(); 135 } 136 137 // for debug 138 if (renderer.DebugTouchMode) 139 { 140 GL.DepthMask(false); 141 GL.Disable(EnableCap.DepthTest); 142 143 if (renderer.AvailableNewRenderer) 144 { 145 renderer.DefaultShader.Activate(); 146 renderer.ResetShader(renderer.DefaultShader); 147 renderer.DefaultShader.SetCurrentProjectionMatrix(renderer.CurrentProjectionMatrix); 148 149 foreach (ObjectState objectState in touchableObject) 150 { 151 foreach (MeshFace face in objectState.Prototype.Mesh.Faces) 152 { 153 renderer.RenderFace(renderer.DefaultShader, objectState, face, true); 154 } 155 } 156 157 renderer.DefaultShader.Deactivate(); 158 } 159 else 160 { 161 foreach (ObjectState objectState in touchableObject) 162 { 163 foreach (MeshFace face in objectState.Prototype.Mesh.Faces) 164 { 165 renderer.RenderFaceImmediateMode(objectState, face, true); 166 } 167 } 168 } 169 } 170 } 171 ParseFBO(Vector2 point, int deltaX, int deltaY)172 private ObjectState ParseFBO(Vector2 point, int deltaX, int deltaY) 173 { 174 Vector2 topLeft = point - new Vector2(deltaX, deltaY) / 2.0f; 175 float[,] objectIndices = new float[deltaX, deltaY]; 176 177 fbo.Bind(); 178 GL.ReadBuffer(ReadBufferMode.ColorAttachment0); 179 GL.ReadPixels((int)topLeft.X, renderer.Screen.Height - (int)topLeft.Y, deltaX, deltaY, PixelFormat.Red, PixelType.Float, objectIndices); 180 GL.ReadBuffer(ReadBufferMode.None); 181 fbo.UnBind(); 182 183 foreach (float objectIndex in objectIndices) 184 { 185 int index = (int)objectIndex - 1; 186 187 if (index >= 0 && index < touchableObject.Count) 188 { 189 return touchableObject[index]; 190 } 191 } 192 193 return null; 194 } 195 196 /// <summary>Make a projection matrix that can be used to limit drawing to small areas of the viewport.</summary> 197 /// <param name="point">Center of picking area at window coordinates</param> 198 /// <param name="delta">Width and height of picking area in window coordinates</param> CreatePickMatrix(Vector2 point, Vector2 delta)199 private Matrix4D CreatePickMatrix(Vector2 point, Vector2 delta) 200 { 201 if (delta.X <= 0 || delta.Y <= 0) 202 { 203 return Matrix4D.Identity; 204 } 205 206 Matrix4D translateMatrix = Matrix4D.CreateTranslation((renderer.Screen.Width - 2 * point.X) / delta.X, (2 * point.Y - renderer.Screen.Height) / delta.Y, 0); 207 Matrix4D scaleMatrix = Matrix4D.Scale(renderer.Screen.Width / delta.X, renderer.Screen.Height / delta.Y, 1.0); 208 209 return renderer.CurrentProjectionMatrix * scaleMatrix * translateMatrix; 210 } 211 ParseSelectBuffer(int[] selectBuffer)212 private static List<PickedObject> ParseSelectBuffer(int[] selectBuffer) 213 { 214 List<PickedObject> pickedObjects = new List<PickedObject>(); 215 int position = 0; 216 217 try 218 { 219 while (position < selectBuffer.Length) 220 { 221 if (selectBuffer[position] == 0) 222 { 223 break; 224 } 225 226 PickedObject pickedObject = new PickedObject 227 { 228 NameDepth = selectBuffer[position++], 229 MinDepth = (double)selectBuffer[position++] / int.MaxValue, 230 MaxDepth = (double)selectBuffer[position++] / int.MaxValue 231 }; 232 pickedObject.Names = new int[pickedObject.NameDepth]; 233 234 for (int i = 0; i < pickedObject.NameDepth; i++) 235 { 236 pickedObject.Names[i] = selectBuffer[position++]; 237 } 238 239 pickedObjects.Add(pickedObject); 240 } 241 242 return pickedObjects; 243 } 244 catch (IndexOutOfRangeException) 245 { 246 if (position >= selectBuffer.Length) 247 { 248 return pickedObjects; 249 } 250 251 throw; 252 } 253 } 254 RenderSceneSelection(Vector2 point, Vector2 delta)255 private ObjectState RenderSceneSelection(Vector2 point, Vector2 delta) 256 { 257 // Pre 258 PreRender(); 259 renderer.ResetOpenGlState(); 260 int[] selectBuffer = new int[2048]; 261 GL.SelectBuffer(selectBuffer.Length, selectBuffer); 262 GL.RenderMode(RenderingMode.Select); 263 renderer.PushMatrix(MatrixMode.Projection); 264 renderer.CurrentProjectionMatrix = CreatePickMatrix(point, delta); 265 int partID = 0; 266 GL.InitNames(); 267 GL.PushName(0); 268 269 // Rendering 270 foreach (ObjectState objectState in touchableObject) 271 { 272 GL.LoadName(partID); 273 274 foreach (MeshFace face in objectState.Prototype.Mesh.Faces) 275 { 276 renderer.RenderFaceImmediateMode(objectState, face); 277 } 278 279 partID++; 280 } 281 282 // Post 283 GL.PopName(); 284 renderer.PopMatrix(MatrixMode.Projection); 285 int hits = GL.RenderMode(RenderingMode.Render); 286 287 if (hits <= 0) 288 { 289 return null; 290 } 291 292 List<PickedObject> pickedObjects = ParseSelectBuffer(selectBuffer); 293 return pickedObjects.Any() ? touchableObject[pickedObjects.OrderBy(x => x.MinDepth).First().Names[0]] : null; 294 } 295 MoveCheck(Vector2 Point, out Cursor.Status Status)296 internal bool MoveCheck(Vector2 Point, out Cursor.Status Status) 297 { 298 if (!Loading.SimulationSetup) 299 { 300 Status = Cursor.Status.Default; 301 return false; 302 } 303 304 if (renderer.Camera.CurrentMode != CameraViewMode.Interior && renderer.Camera.CurrentMode != CameraViewMode.InteriorLookAhead) 305 { 306 Status = Cursor.Status.Default; 307 return false; 308 } 309 310 Status = Cursor.Status.Default; 311 312 CarBase Car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; 313 int add = Car.CarSections[0].CurrentAdditionalGroup + 1; 314 315 if (add >= Car.CarSections[0].Groups.Length) 316 { 317 return false; 318 } 319 320 TouchElement[] TouchElements = Car.CarSections[0].Groups[add].TouchElements; 321 322 if (TouchElements == null) 323 { 324 return false; 325 } 326 327 ObjectState pickedObject = renderer.AvailableNewRenderer ? ParseFBO(Point, 5, 5) : RenderSceneSelection(Point, new Vector2(5.0f)); 328 329 foreach (TouchElement TouchElement in TouchElements.Where(x => x.Element.internalObject == pickedObject)) 330 { 331 foreach (int index in TouchElement.ControlIndices) 332 { 333 switch (Interface.CurrentControls[index].Command) 334 { 335 case Translations.Command.PowerIncrease: 336 case Translations.Command.BrakeIncrease: 337 case Translations.Command.ReverserForward: 338 Status = Cursor.Status.Plus; 339 break; 340 case Translations.Command.PowerDecrease: 341 case Translations.Command.BrakeDecrease: 342 case Translations.Command.ReverserBackward: 343 Status = Cursor.Status.Minus; 344 break; 345 } 346 } 347 } 348 349 return pickedObject != null; 350 } 351 TouchCheck(Vector2 Point)352 internal void TouchCheck(Vector2 Point) 353 { 354 if (!Loading.SimulationSetup) 355 { 356 return; 357 } 358 359 if (renderer.Camera.CurrentMode != CameraViewMode.Interior && renderer.Camera.CurrentMode != CameraViewMode.InteriorLookAhead) 360 { 361 return; 362 } 363 364 CarBase Car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; 365 int add = Car.CarSections[0].CurrentAdditionalGroup + 1; 366 367 if (add >= Car.CarSections[0].Groups.Length) 368 { 369 return; 370 } 371 372 TouchElement[] TouchElements = Car.CarSections[0].Groups[add].TouchElements; 373 374 if (TouchElements == null) 375 { 376 return; 377 } 378 379 ObjectState pickedObject = renderer.AvailableNewRenderer ? ParseFBO(Point, 5, 5) : RenderSceneSelection(Point, new Vector2(5.0f)); 380 381 foreach (TouchElement TouchElement in TouchElements.Where(x => x.Element.internalObject == pickedObject)) 382 { 383 foreach (int index in TouchElement.ControlIndices) 384 { 385 Interface.CurrentControls[index].AnalogState = 1.0; 386 Interface.CurrentControls[index].DigitalState = DigitalControlState.Pressed; 387 MainLoop.AddControlRepeat(index); 388 } 389 } 390 391 prePickedObject = pickedObject; 392 } 393 LeaveCheck(Vector2 Point)394 internal void LeaveCheck(Vector2 Point) 395 { 396 if (!Loading.SimulationSetup) 397 { 398 return; 399 } 400 401 if (renderer.Camera.CurrentMode != CameraViewMode.Interior && renderer.Camera.CurrentMode != CameraViewMode.InteriorLookAhead) 402 { 403 return; 404 } 405 406 CarBase Car = TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar]; 407 int add = Car.CarSections[0].CurrentAdditionalGroup + 1; 408 if (add >= Car.CarSections[0].Groups.Length) 409 { 410 return; 411 } 412 413 TouchElement[] TouchElements = Car.CarSections[0].Groups[add].TouchElements; 414 415 if (TouchElements == null) 416 { 417 return; 418 } 419 420 ObjectState pickedObject = renderer.AvailableNewRenderer ? ParseFBO(Point, 5, 5) : RenderSceneSelection(Point, new Vector2(5.0f)); 421 422 foreach (TouchElement TouchElement in TouchElements) 423 { 424 if (TouchElement.Element.internalObject == pickedObject) 425 { 426 Car.CarSections[0].CurrentAdditionalGroup = TouchElement.JumpScreenIndex; 427 Car.ChangeCarSection(CarSectionType.Interior); 428 429 foreach (var index in TouchElement.SoundIndices.Where(x => x >= 0 && Car.Sounds.Touch != null && x < Car.Sounds.Touch.Length)) 430 { 431 Car.Sounds.Touch[index].Play(TrainManager.PlayerTrain.Cars[TrainManager.PlayerTrain.DriverCar], false); 432 } 433 } 434 435 // HACK: Normally terminate the command issued once. 436 if (TouchElement.Element.internalObject == pickedObject || (pickedObject != prePickedObject && TouchElement.Element.internalObject == prePickedObject)) 437 { 438 foreach (int index in TouchElement.ControlIndices) 439 { 440 Interface.CurrentControls[index].AnalogState = 0.0; 441 Interface.CurrentControls[index].DigitalState = DigitalControlState.Released; 442 MainLoop.RemoveControlRepeat(index); 443 } 444 } 445 } 446 } 447 } 448 } 449