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