clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27  
28 using System;
29 using System.Collections.Generic;
30 using System.Drawing;
31 using System.Reflection;
32 using log4net;
33 using Mono.Addins;
34 using Nini.Config;
35 using OpenMetaverse;
36 using OpenMetaverse.Imaging;
37 using OpenSim.Framework;
38 using OpenSim.Region.Framework.Interfaces;
39 using OpenSim.Region.Framework.Scenes;
40  
41 namespace OpenSim.Region.CoreModules.World.LegacyMap
42 {
43 public enum DrawRoutine
44 {
45 Rectangle,
46 Polygon,
47 Ellipse
48 }
49  
50 public struct face
51 {
52 public Point[] pts;
53 }
54  
55 public struct DrawStruct
56 {
57 public DrawRoutine dr;
58 // public Rectangle rect;
59 public SolidBrush brush;
60 public face[] trns;
61 }
62  
63 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MapImageModule")]
64 public class MapImageModule : IMapImageGenerator, INonSharedRegionModule
65 {
66 private static readonly ILog m_log =
67 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
68  
69 private Scene m_scene;
70 private IConfigSource m_config;
71 private IMapTileTerrainRenderer terrainRenderer;
72 private bool m_Enabled = false;
73  
74 #region IMapImageGenerator Members
75  
76 public Bitmap CreateMapTile()
77 {
78 bool drawPrimVolume = true;
79 bool textureTerrain = false;
80 bool generateMaptiles = true;
81 Bitmap mapbmp;
82  
83 string[] configSections = new string[] { "Map", "Startup" };
84  
85 drawPrimVolume
86 = Util.GetConfigVarFromSections<bool>(m_config, "DrawPrimOnMapTile", configSections, drawPrimVolume);
87 textureTerrain
88 = Util.GetConfigVarFromSections<bool>(m_config, "TextureOnMapTile", configSections, textureTerrain);
89 generateMaptiles
90 = Util.GetConfigVarFromSections<bool>(m_config, "GenerateMaptiles", configSections, generateMaptiles);
91  
92 if (generateMaptiles)
93 {
94 if (String.IsNullOrEmpty(m_scene.RegionInfo.MaptileStaticFile))
95 {
96 if (textureTerrain)
97 {
98 terrainRenderer = new TexturedMapTileRenderer();
99 }
100 else
101 {
102 terrainRenderer = new ShadedMapTileRenderer();
103 }
104  
105 terrainRenderer.Initialise(m_scene, m_config);
106  
107 mapbmp = new Bitmap((int)m_scene.Heightmap.Width, (int)m_scene.Heightmap.Height,
108 System.Drawing.Imaging.PixelFormat.Format24bppRgb);
109 //long t = System.Environment.TickCount;
110 //for (int i = 0; i < 10; ++i) {
111 terrainRenderer.TerrainToBitmap(mapbmp);
112 //}
113 //t = System.Environment.TickCount - t;
114 //m_log.InfoFormat("[MAPTILE] generation of 10 maptiles needed {0} ms", t);
115  
116 if (drawPrimVolume)
117 {
118 DrawObjectVolume(m_scene, mapbmp);
119 }
120 }
121 else
122 {
123 try
124 {
125 mapbmp = new Bitmap(m_scene.RegionInfo.MaptileStaticFile);
126 }
127 catch (Exception)
128 {
129 m_log.ErrorFormat(
130 "[MAPTILE]: Failed to load Static map image texture file: {0} for {1}",
131 m_scene.RegionInfo.MaptileStaticFile, m_scene.Name);
132 //mapbmp = new Bitmap((int)m_scene.Heightmap.Width, (int)m_scene.Heightmap.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
133 mapbmp = null;
134 }
135  
136 if (mapbmp != null)
137 m_log.DebugFormat(
138 "[MAPTILE]: Static map image texture file {0} found for {1}",
139 m_scene.RegionInfo.MaptileStaticFile, m_scene.Name);
140 }
141 }
142 else
143 {
144 mapbmp = FetchTexture(m_scene.RegionInfo.RegionSettings.TerrainImageID);
145 }
146  
147 return mapbmp;
148 }
149  
150 public byte[] WriteJpeg2000Image()
151 {
152 try
153 {
154 using (Bitmap mapbmp = CreateMapTile())
155 {
156 if (mapbmp != null)
157 return OpenJPEG.EncodeFromImage(mapbmp, true);
158 }
159 }
160 catch (Exception e) // LEGIT: Catching problems caused by OpenJPEG p/invoke
161 {
162 m_log.Error("Failed generating terrain map: " + e);
163 }
164  
165 return null;
166 }
167  
168 #endregion
169  
170 #region Region Module interface
171  
172 public void Initialise(IConfigSource source)
173 {
174 m_config = source;
175  
176 if (Util.GetConfigVarFromSections<string>(
177 m_config, "MapImageModule", new string[] { "Startup", "Map" }, "MapImageModule") != "MapImageModule")
178 return;
179  
180 m_Enabled = true;
181 }
182  
183 public void AddRegion(Scene scene)
184 {
185 if (!m_Enabled)
186 return;
187  
188 m_scene = scene;
189  
190 m_scene.RegisterModuleInterface<IMapImageGenerator>(this);
191 }
192  
193 public void RegionLoaded(Scene scene)
194 {
195 }
196  
197 public void RemoveRegion(Scene scene)
198 {
199 }
200  
201 public void Close()
202 {
203 }
204  
205 public string Name
206 {
207 get { return "MapImageModule"; }
208 }
209  
210 public Type ReplaceableInterface
211 {
212 get { return null; }
213 }
214  
215 #endregion
216  
217 // TODO: unused:
218 // private void ShadeBuildings(Bitmap map)
219 // {
220 // lock (map)
221 // {
222 // lock (m_scene.Entities)
223 // {
224 // foreach (EntityBase entity in m_scene.Entities.Values)
225 // {
226 // if (entity is SceneObjectGroup)
227 // {
228 // SceneObjectGroup sog = (SceneObjectGroup) entity;
229 //
230 // foreach (SceneObjectPart primitive in sog.Children.Values)
231 // {
232 // int x = (int) (primitive.AbsolutePosition.X - (primitive.Scale.X / 2));
233 // int y = (int) (primitive.AbsolutePosition.Y - (primitive.Scale.Y / 2));
234 // int w = (int) primitive.Scale.X;
235 // int h = (int) primitive.Scale.Y;
236 //
237 // int dx;
238 // for (dx = x; dx < x + w; dx++)
239 // {
240 // int dy;
241 // for (dy = y; dy < y + h; dy++)
242 // {
243 // if (x < 0 || y < 0)
244 // continue;
245 // if (x >= map.Width || y >= map.Height)
246 // continue;
247 //
248 // map.SetPixel(dx, dy, Color.DarkGray);
249 // }
250 // }
251 // }
252 // }
253 // }
254 // }
255 // }
256 // }
257  
258 private Bitmap FetchTexture(UUID id)
259 {
260 AssetBase asset = m_scene.AssetService.Get(id.ToString());
261  
262 if (asset != null)
263 {
264 m_log.DebugFormat("[MAPTILE]: Static map image texture {0} found for {1}", id, m_scene.Name);
265 }
266 else
267 {
268 m_log.WarnFormat("[MAPTILE]: Static map image texture {0} not found for {1}", id, m_scene.Name);
269 return null;
270 }
271  
272 ManagedImage managedImage;
273 Image image;
274  
275 try
276 {
277 if (OpenJPEG.DecodeToImage(asset.Data, out managedImage, out image))
278 return new Bitmap(image);
279 else
280 return null;
281 }
282 catch (DllNotFoundException)
283 {
284 m_log.ErrorFormat("[MAPTILE]: OpenJpeg is not installed correctly on this system. Asset Data is empty for {0}", id);
285  
286 }
287 catch (IndexOutOfRangeException)
288 {
289 m_log.ErrorFormat("[MAPTILE]: OpenJpeg was unable to decode this. Asset Data is empty for {0}", id);
290  
291 }
292 catch (Exception)
293 {
294 m_log.ErrorFormat("[MAPTILE]: OpenJpeg was unable to decode this. Asset Data is empty for {0}", id);
295  
296 }
297 return null;
298  
299 }
300  
301 private Bitmap DrawObjectVolume(Scene whichScene, Bitmap mapbmp)
302 {
303 int tc = 0;
304 ITerrainChannel hm = whichScene.Heightmap;
305 tc = Environment.TickCount;
306 m_log.Debug("[MAPTILE]: Generating Maptile Step 2: Object Volume Profile");
307 EntityBase[] objs = whichScene.GetEntities();
308 List<float> z_sortheights = new List<float>();
309 List<uint> z_localIDs = new List<uint>();
310 Dictionary<uint, DrawStruct> z_sort = new Dictionary<uint, DrawStruct>();
311  
312 try
313 {
314 lock (objs)
315 {
316 foreach (EntityBase obj in objs)
317 {
318 // Only draw the contents of SceneObjectGroup
319 if (obj is SceneObjectGroup)
320 {
321 SceneObjectGroup mapdot = (SceneObjectGroup)obj;
322 Color mapdotspot = Color.Gray; // Default color when prim color is white
323 // Loop over prim in group
324 foreach (SceneObjectPart part in mapdot.Parts)
325 {
326 if (part == null)
327 continue;
328  
329 // Draw if the object is at least 1 meter wide in any direction
330 if (part.Scale.X > 1f || part.Scale.Y > 1f || part.Scale.Z > 1f)
331 {
332 // Try to get the RGBA of the default texture entry..
333 //
334 try
335 {
336 // get the null checks out of the way
337 // skip the ones that break
338 if (part == null)
339 continue;
340  
341 if (part.Shape == null)
342 continue;
343  
344 if (part.Shape.PCode == (byte)PCode.Tree || part.Shape.PCode == (byte)PCode.NewTree || part.Shape.PCode == (byte)PCode.Grass)
345 continue; // eliminates trees from this since we don't really have a good tree representation
346 // if you want tree blocks on the map comment the above line and uncomment the below line
347 //mapdotspot = Color.PaleGreen;
348  
349 Primitive.TextureEntry textureEntry = part.Shape.Textures;
350  
351 if (textureEntry == null || textureEntry.DefaultTexture == null)
352 continue;
353  
354 Color4 texcolor = textureEntry.DefaultTexture.RGBA;
355  
356 // Not sure why some of these are null, oh well.
357  
358 int colorr = 255 - (int)(texcolor.R * 255f);
359 int colorg = 255 - (int)(texcolor.G * 255f);
360 int colorb = 255 - (int)(texcolor.B * 255f);
361  
362 if (!(colorr == 255 && colorg == 255 && colorb == 255))
363 {
364 //Try to set the map spot color
365 try
366 {
367 // If the color gets goofy somehow, skip it *shakes fist at Color4
368 mapdotspot = Color.FromArgb(colorr, colorg, colorb);
369 }
370 catch (ArgumentException)
371 {
372 }
373 }
374 }
375 catch (IndexOutOfRangeException)
376 {
377 // Windows Array
378 }
379 catch (ArgumentOutOfRangeException)
380 {
381 // Mono Array
382 }
383  
384 Vector3 pos = part.GetWorldPosition();
385  
386 // skip prim outside of retion
387 if (!m_scene.PositionIsInCurrentRegion(pos))
388 continue;
389  
390 // skip prim in non-finite position
391 if (Single.IsNaN(pos.X) || Single.IsNaN(pos.Y) ||
392 Single.IsInfinity(pos.X) || Single.IsInfinity(pos.Y))
393 continue;
394  
395 // Figure out if object is under 256m above the height of the terrain
396 bool isBelow256AboveTerrain = false;
397  
398 try
399 {
400 isBelow256AboveTerrain = (pos.Z < ((float)hm[(int)pos.X, (int)pos.Y] + 256f));
401 }
402 catch (Exception)
403 {
404 }
405  
406 if (isBelow256AboveTerrain)
407 {
408 // Translate scale by rotation so scale is represented properly when object is rotated
409 Vector3 lscale = new Vector3(part.Shape.Scale.X, part.Shape.Scale.Y, part.Shape.Scale.Z);
410 Vector3 scale = new Vector3();
411 Vector3 tScale = new Vector3();
412 Vector3 axPos = new Vector3(pos.X, pos.Y, pos.Z);
413  
414 Quaternion llrot = part.GetWorldRotation();
415 Quaternion rot = new Quaternion(llrot.W, llrot.X, llrot.Y, llrot.Z);
416 scale = lscale * rot;
417  
418 // negative scales don't work in this situation
419 scale.X = Math.Abs(scale.X);
420 scale.Y = Math.Abs(scale.Y);
421 scale.Z = Math.Abs(scale.Z);
422  
423 // This scaling isn't very accurate and doesn't take into account the face rotation :P
424 int mapdrawstartX = (int)(pos.X - scale.X);
425 int mapdrawstartY = (int)(pos.Y - scale.Y);
426 int mapdrawendX = (int)(pos.X + scale.X);
427 int mapdrawendY = (int)(pos.Y + scale.Y);
428  
429 // If object is beyond the edge of the map, don't draw it to avoid errors
430 if (mapdrawstartX < 0
431 || mapdrawstartX > (hm.Width - 1)
432 || mapdrawendX < 0
433 || mapdrawendX > (hm.Width - 1)
434 || mapdrawstartY < 0
435 || mapdrawstartY > (hm.Height - 1)
436 || mapdrawendY < 0
437 || mapdrawendY > (hm.Height - 1))
438 continue;
439  
440 #region obb face reconstruction part duex
441 Vector3[] vertexes = new Vector3[8];
442  
443 // float[] distance = new float[6];
444 Vector3[] FaceA = new Vector3[6]; // vertex A for Facei
445 Vector3[] FaceB = new Vector3[6]; // vertex B for Facei
446 Vector3[] FaceC = new Vector3[6]; // vertex C for Facei
447 Vector3[] FaceD = new Vector3[6]; // vertex D for Facei
448  
449 tScale = new Vector3(lscale.X, -lscale.Y, lscale.Z);
450 scale = ((tScale * rot));
451 vertexes[0] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z)));
452 // vertexes[0].x = pos.X + vertexes[0].x;
453 //vertexes[0].y = pos.Y + vertexes[0].y;
454 //vertexes[0].z = pos.Z + vertexes[0].z;
455  
456 FaceA[0] = vertexes[0];
457 FaceB[3] = vertexes[0];
458 FaceA[4] = vertexes[0];
459  
460 tScale = lscale;
461 scale = ((tScale * rot));
462 vertexes[1] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z)));
463  
464 // vertexes[1].x = pos.X + vertexes[1].x;
465 // vertexes[1].y = pos.Y + vertexes[1].y;
466 //vertexes[1].z = pos.Z + vertexes[1].z;
467  
468 FaceB[0] = vertexes[1];
469 FaceA[1] = vertexes[1];
470 FaceC[4] = vertexes[1];
471  
472 tScale = new Vector3(lscale.X, -lscale.Y, -lscale.Z);
473 scale = ((tScale * rot));
474  
475 vertexes[2] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z)));
476  
477 //vertexes[2].x = pos.X + vertexes[2].x;
478 //vertexes[2].y = pos.Y + vertexes[2].y;
479 //vertexes[2].z = pos.Z + vertexes[2].z;
480  
481 FaceC[0] = vertexes[2];
482 FaceD[3] = vertexes[2];
483 FaceC[5] = vertexes[2];
484  
485 tScale = new Vector3(lscale.X, lscale.Y, -lscale.Z);
486 scale = ((tScale * rot));
487 vertexes[3] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z)));
488  
489 //vertexes[3].x = pos.X + vertexes[3].x;
490 // vertexes[3].y = pos.Y + vertexes[3].y;
491 // vertexes[3].z = pos.Z + vertexes[3].z;
492  
493 FaceD[0] = vertexes[3];
494 FaceC[1] = vertexes[3];
495 FaceA[5] = vertexes[3];
496  
497 tScale = new Vector3(-lscale.X, lscale.Y, lscale.Z);
498 scale = ((tScale * rot));
499 vertexes[4] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z)));
500  
501 // vertexes[4].x = pos.X + vertexes[4].x;
502 // vertexes[4].y = pos.Y + vertexes[4].y;
503 // vertexes[4].z = pos.Z + vertexes[4].z;
504  
505 FaceB[1] = vertexes[4];
506 FaceA[2] = vertexes[4];
507 FaceD[4] = vertexes[4];
508  
509 tScale = new Vector3(-lscale.X, lscale.Y, -lscale.Z);
510 scale = ((tScale * rot));
511 vertexes[5] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z)));
512  
513 // vertexes[5].x = pos.X + vertexes[5].x;
514 // vertexes[5].y = pos.Y + vertexes[5].y;
515 // vertexes[5].z = pos.Z + vertexes[5].z;
516  
517 FaceD[1] = vertexes[5];
518 FaceC[2] = vertexes[5];
519 FaceB[5] = vertexes[5];
520  
521 tScale = new Vector3(-lscale.X, -lscale.Y, lscale.Z);
522 scale = ((tScale * rot));
523 vertexes[6] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z)));
524  
525 // vertexes[6].x = pos.X + vertexes[6].x;
526 // vertexes[6].y = pos.Y + vertexes[6].y;
527 // vertexes[6].z = pos.Z + vertexes[6].z;
528  
529 FaceB[2] = vertexes[6];
530 FaceA[3] = vertexes[6];
531 FaceB[4] = vertexes[6];
532  
533 tScale = new Vector3(-lscale.X, -lscale.Y, -lscale.Z);
534 scale = ((tScale * rot));
535 vertexes[7] = (new Vector3((pos.X + scale.X), (pos.Y + scale.Y), (pos.Z + scale.Z)));
536  
537 // vertexes[7].x = pos.X + vertexes[7].x;
538 // vertexes[7].y = pos.Y + vertexes[7].y;
539 // vertexes[7].z = pos.Z + vertexes[7].z;
540  
541 FaceD[2] = vertexes[7];
542 FaceC[3] = vertexes[7];
543 FaceD[5] = vertexes[7];
544 #endregion
545  
546 //int wy = 0;
547  
548 //bool breakYN = false; // If we run into an error drawing, break out of the
549 // loop so we don't lag to death on error handling
550 DrawStruct ds = new DrawStruct();
551 ds.brush = new SolidBrush(mapdotspot);
552 //ds.rect = new Rectangle(mapdrawstartX, (255 - mapdrawstartY), mapdrawendX - mapdrawstartX, mapdrawendY - mapdrawstartY);
553  
554 ds.trns = new face[FaceA.Length];
555  
556 for (int i = 0; i < FaceA.Length; i++)
557 {
558 Point[] working = new Point[5];
559 working[0] = project(hm, FaceA[i], axPos);
560 working[1] = project(hm, FaceB[i], axPos);
561 working[2] = project(hm, FaceD[i], axPos);
562 working[3] = project(hm, FaceC[i], axPos);
563 working[4] = project(hm, FaceA[i], axPos);
564  
565 face workingface = new face();
566 workingface.pts = working;
567  
568 ds.trns[i] = workingface;
569 }
570  
571 z_sort.Add(part.LocalId, ds);
572 z_localIDs.Add(part.LocalId);
573 z_sortheights.Add(pos.Z);
574  
575 // for (int wx = mapdrawstartX; wx < mapdrawendX; wx++)
576 // {
577 // for (wy = mapdrawstartY; wy < mapdrawendY; wy++)
578 // {
579 // m_log.InfoFormat("[MAPDEBUG]: {0},{1}({2})", wx, (255 - wy),wy);
580 // try
581 // {
582 // // Remember, flip the y!
583 // mapbmp.SetPixel(wx, (255 - wy), mapdotspot);
584 // }
585 // catch (ArgumentException)
586 // {
587 // breakYN = true;
588 // }
589 // }
590 // if (breakYN)
591 // break;
592 // }
593 // }
594 //}
595 } // Object is within 256m Z of terrain
596 } // object is at least a meter wide
597 } // loop over group children
598 } // entitybase is sceneobject group
599 } // foreach loop over entities
600  
601 float[] sortedZHeights = z_sortheights.ToArray();
602 uint[] sortedlocalIds = z_localIDs.ToArray();
603  
604 // Sort prim by Z position
605 Array.Sort(sortedZHeights, sortedlocalIds);
606  
607 using (Graphics g = Graphics.FromImage(mapbmp))
608 {
609 for (int s = 0; s < sortedZHeights.Length; s++)
610 {
611 if (z_sort.ContainsKey(sortedlocalIds[s]))
612 {
613 DrawStruct rectDrawStruct = z_sort[sortedlocalIds[s]];
614 for (int r = 0; r < rectDrawStruct.trns.Length; r++)
615 {
616 g.FillPolygon(rectDrawStruct.brush,rectDrawStruct.trns[r].pts);
617 }
618 //g.FillRectangle(rectDrawStruct.brush , rectDrawStruct.rect);
619 }
620 }
621 }
622 } // lock entities objs
623  
624 }
625 finally
626 {
627 foreach (DrawStruct ds in z_sort.Values)
628 ds.brush.Dispose();
629 }
630  
631 m_log.Debug("[MAPTILE]: Generating Maptile Step 2: Done in " + (Environment.TickCount - tc) + " ms");
632  
633 return mapbmp;
634 }
635  
636 private Point project(ITerrainChannel hm, Vector3 point3d, Vector3 originpos)
637 {
638 Point returnpt = new Point();
639 //originpos = point3d;
640 //int d = (int)(256f / 1.5f);
641  
642 //Vector3 topos = new Vector3(0, 0, 0);
643 // float z = -point3d.z - topos.z;
644  
645 returnpt.X = (int)point3d.X;//(int)((topos.x - point3d.x) / z * d);
646 returnpt.Y = (int)((hm.Width - 1) - point3d.Y);//(int)(255 - (((topos.y - point3d.y) / z * d)));
647  
648 return returnpt;
649 }
650  
651 public Bitmap CreateViewImage(Vector3 camPos, Vector3 camDir, float fov, int width, int height, bool useTextures)
652 {
653 return null;
654 }
655 }
656 }