| 1 | using System; | 
 
 
 
 
 | 2 | using System.Collections.Generic; | 
 
 
 
 
 | 3 | using System.IO; | 
 
 
 
 
 | 4 | using System.Xml; | 
 
 
 
 
 | 5 | using Oni.Motoko; | 
 
 
 
 
 | 6 | using Oni.Physics; | 
 
 
 
 
 | 7 | using Oni.Totoro; | 
 
 
 
 
 | 8 |  | 
 
 
 
 
 | 9 | namespace Oni | 
 
 
 
 
 | 10 | { | 
 
 
 
 
 | 11 | internal class SceneExporter | 
 
 
 
 
 | 12 | { | 
 
 
 
 
 | 13 | private readonly InstanceFileManager fileManager; | 
 
 
 
 
 | 14 | private readonly string outputDirPath; | 
 
 
 
 
 | 15 | private readonly TextureDaeWriter textureWriter; | 
 
 
 
 
 | 16 | private readonly GeometryDaeWriter geometryWriter; | 
 
 
 
 
 | 17 | private readonly BodyDaeWriter bodyWriter; | 
 
 
 
 
 | 18 | private string basePath; | 
 
 
 
 
 | 19 |  | 
 
 
 
 
 | 20 | private class SceneNode | 
 
 
 
 
 | 21 | { | 
 
 
 
 
 | 22 | public string Name; | 
 
 
 
 
 | 23 | public readonly List<Geometry> Geometries = new List<Geometry>(); | 
 
 
 
 
 | 24 | public readonly List<SceneNodeAnimation> Animations = new List<SceneNodeAnimation>(); | 
 
 
 
 
 | 25 | public readonly List<SceneNode> Nodes = new List<SceneNode>(); | 
 
 
 
 
 | 26 | public Body Body; | 
 
 
 
 
 | 27 | public bool IsCamera; | 
 
 
 
 
 | 28 | } | 
 
 
 
 
 | 29 |  | 
 
 
 
 
 | 30 | private class SceneNodeAnimation | 
 
 
 
 
 | 31 | { | 
 
 
 
 
 | 32 | public int Start; | 
 
 
 
 
 | 33 | public ObjectAnimation ObjectAnimation; | 
 
 
 
 
 | 34 | } | 
 
 
 
 
 | 35 |  | 
 
 
 
 
 | 36 | public SceneExporter(InstanceFileManager fileManager, string outputDirPath) | 
 
 
 
 
 | 37 | { | 
 
 
 
 
 | 38 | this.fileManager = fileManager; | 
 
 
 
 
 | 39 | this.outputDirPath = outputDirPath; | 
 
 
 
 
 | 40 |  | 
 
 
 
 
 | 41 | textureWriter = new TextureDaeWriter(outputDirPath); | 
 
 
 
 
 | 42 | geometryWriter = new GeometryDaeWriter(textureWriter); | 
 
 
 
 
 | 43 | bodyWriter = new BodyDaeWriter(geometryWriter); | 
 
 
 
 
 | 44 | } | 
 
 
 
 
 | 45 |  | 
 
 
 
 
 | 46 | public void ExportScene(string sourceFilePath) | 
 
 
 
 
 | 47 | { | 
 
 
 
 
 | 48 | basePath = Path.GetDirectoryName(sourceFilePath); | 
 
 
 
 
 | 49 |  | 
 
 
 
 
 | 50 | var scene = new Dae.Scene(); | 
 
 
 
 
 | 51 |  | 
 
 
 
 
 | 52 | var settings = new XmlReaderSettings | 
 
 
 
 
 | 53 | { | 
 
 
 
 
 | 54 | IgnoreWhitespace = true, | 
 
 
 
 
 | 55 | IgnoreProcessingInstructions = true, | 
 
 
 
 
 | 56 | IgnoreComments = true | 
 
 
 
 
 | 57 | }; | 
 
 
 
 
 | 58 |  | 
 
 
 
 
 | 59 | var nodes = new List<SceneNode>(); | 
 
 
 
 
 | 60 |  | 
 
 
 
 
 | 61 | using (var xml = XmlReader.Create(sourceFilePath, settings)) | 
 
 
 
 
 | 62 | { | 
 
 
 
 
 | 63 | scene.Name = xml.GetAttribute("Name"); | 
 
 
 
 
 | 64 | xml.ReadStartElement("Scene"); | 
 
 
 
 
 | 65 |  | 
 
 
 
 
 | 66 | while (xml.IsStartElement()) | 
 
 
 
 
 | 67 | nodes.Add(ReadNode(xml)); | 
 
 
 
 
 | 68 |  | 
 
 
 
 
 | 69 | xml.ReadEndElement(); | 
 
 
 
 
 | 70 | } | 
 
 
 
 
 | 71 |  | 
 
 
 
 
 | 72 | foreach (var node in nodes) | 
 
 
 
 
 | 73 | { | 
 
 
 
 
 | 74 | scene.Nodes.Add(WriteNode(node, null)); | 
 
 
 
 
 | 75 | } | 
 
 
 
 
 | 76 |  | 
 
 
 
 
 | 77 | Dae.Writer.WriteFile(Path.Combine(outputDirPath, Path.GetFileNameWithoutExtension(sourceFilePath)) + ".dae", scene); | 
 
 
 
 
 | 78 | } | 
 
 
 
 
 | 79 |  | 
 
 
 
 
 | 80 | private string ResolvePath(string path) | 
 
 
 
 
 | 81 | { | 
 
 
 
 
 | 82 | return Path.Combine(basePath, path); | 
 
 
 
 
 | 83 | } | 
 
 
 
 
 | 84 |  | 
 
 
 
 
 | 85 |  | 
 
 
 
 
 | 86 | private SceneNode ReadNode(XmlReader xml) | 
 
 
 
 
 | 87 | { | 
 
 
 
 
 | 88 | var node = new SceneNode | 
 
 
 
 
 | 89 | { | 
 
 
 
 
 | 90 | Name = xml.GetAttribute("Name") | 
 
 
 
 
 | 91 | }; | 
 
 
 
 
 | 92 |  | 
 
 
 
 
 | 93 | xml.ReadStartElement("Node"); | 
 
 
 
 
 | 94 |  | 
 
 
 
 
 | 95 | while (xml.IsStartElement()) | 
 
 
 
 
 | 96 | { | 
 
 
 
 
 | 97 | switch (xml.LocalName) | 
 
 
 
 
 | 98 | { | 
 
 
 
 
 | 99 | case "Geometry": | 
 
 
 
 
 | 100 | ReadGeometry(xml, node); | 
 
 
 
 
 | 101 | break; | 
 
 
 
 
 | 102 | case "Body": | 
 
 
 
 
 | 103 | ReadBody(xml, node); | 
 
 
 
 
 | 104 | break; | 
 
 
 
 
 | 105 | case "Camera": | 
 
 
 
 
 | 106 | ReadCamera(xml, node); | 
 
 
 
 
 | 107 | break; | 
 
 
 
 
 | 108 | case "Animation": | 
 
 
 
 
 | 109 | ReadAnimation(xml, node); | 
 
 
 
 
 | 110 | break; | 
 
 
 
 
 | 111 | case "Node": | 
 
 
 
 
 | 112 | node.Nodes.Add(ReadNode(xml)); | 
 
 
 
 
 | 113 | break; | 
 
 
 
 
 | 114 | default: | 
 
 
 
 
 | 115 | Console.WriteLine("Unknown element name {0}", xml.LocalName); | 
 
 
 
 
 | 116 | xml.Skip(); | 
 
 
 
 
 | 117 | break; | 
 
 
 
 
 | 118 | } | 
 
 
 
 
 | 119 | } | 
 
 
 
 
 | 120 |  | 
 
 
 
 
 | 121 | xml.ReadEndElement(); | 
 
 
 
 
 | 122 |  | 
 
 
 
 
 | 123 | return node; | 
 
 
 
 
 | 124 | } | 
 
 
 
 
 | 125 |  | 
 
 
 
 
 | 126 | private void ReadGeometry(XmlReader xml, SceneNode node) | 
 
 
 
 
 | 127 | { | 
 
 
 
 
 | 128 | var file = fileManager.OpenFile(ResolvePath(xml.ReadElementContentAsString())); | 
 
 
 
 
 | 129 | var geometry = GeometryDatReader.Read(file.Descriptors[0]); | 
 
 
 
 
 | 130 |  | 
 
 
 
 
 | 131 | node.Geometries.Add(geometry); | 
 
 
 
 
 | 132 | } | 
 
 
 
 
 | 133 |  | 
 
 
 
 
 | 134 | private void ReadBody(XmlReader xml, SceneNode node) | 
 
 
 
 
 | 135 | { | 
 
 
 
 
 | 136 | var file = fileManager.OpenFile(ResolvePath(xml.ReadElementContentAsString())); | 
 
 
 
 
 | 137 | var body = BodyDatReader.Read(file.Descriptors[0]); | 
 
 
 
 
 | 138 |  | 
 
 
 
 
 | 139 | node.Body = body; | 
 
 
 
 
 | 140 |  | 
 
 
 
 
 | 141 | ReadBodyNode(node, body.Root); | 
 
 
 
 
 | 142 | } | 
 
 
 
 
 | 143 |  | 
 
 
 
 
 | 144 | private static void ReadBodyNode(SceneNode node, BodyNode bodyNode) | 
 
 
 
 
 | 145 | { | 
 
 
 
 
 | 146 | node.Name = bodyNode.Name; | 
 
 
 
 
 | 147 | node.Geometries.Add(bodyNode.Geometry); | 
 
 
 
 
 | 148 |  | 
 
 
 
 
 | 149 | foreach (var childBodyNode in bodyNode.Nodes) | 
 
 
 
 
 | 150 | { | 
 
 
 
 
 | 151 | var childNode = new SceneNode(); | 
 
 
 
 
 | 152 | node.Nodes.Add(childNode); | 
 
 
 
 
 | 153 | ReadBodyNode(childNode, childBodyNode); | 
 
 
 
 
 | 154 | } | 
 
 
 
 
 | 155 | } | 
 
 
 
 
 | 156 |  | 
 
 
 
 
 | 157 | private void ReadAnimation(XmlReader xml, SceneNode node) | 
 
 
 
 
 | 158 | { | 
 
 
 
 
 | 159 | var startValue = xml.GetAttribute("Start"); | 
 
 
 
 
 | 160 | var isMax = xml.GetAttribute("Type") == "Max"; | 
 
 
 
 
 | 161 | var noRotation = xml.GetAttribute("NoRotation") == "true"; | 
 
 
 
 
 | 162 | var filePath = xml.ReadElementContentAsString(); | 
 
 
 
 
 | 163 |  | 
 
 
 
 
 | 164 | var start = string.IsNullOrEmpty(startValue) ? 0 : int.Parse(startValue); | 
 
 
 
 
 | 165 | var file = fileManager.OpenFile(ResolvePath(filePath)); | 
 
 
 
 
 | 166 |  | 
 
 
 
 
 | 167 | if (node.Body != null) | 
 
 
 
 
 | 168 | { | 
 
 
 
 
 | 169 | var animations = AnimationDatReader.Read(file.Descriptors[0]).ToObjectAnimation(node.Body); | 
 
 
 
 
 | 170 |  | 
 
 
 
 
 | 171 | ReadBodyAnimation(start, node, node.Body.Root, animations); | 
 
 
 
 
 | 172 | } | 
 
 
 
 
 | 173 | else | 
 
 
 
 
 | 174 | { | 
 
 
 
 
 | 175 | node.Animations.Add(new SceneNodeAnimation | 
 
 
 
 
 | 176 | { | 
 
 
 
 
 | 177 | Start = start, | 
 
 
 
 
 | 178 | ObjectAnimation = ObjectDatReader.ReadAnimation(file.Descriptors[0]) | 
 
 
 
 
 | 179 | }); | 
 
 
 
 
 | 180 |  | 
 
 
 
 
 | 181 | if (noRotation) | 
 
 
 
 
 | 182 | { | 
 
 
 
 
 | 183 | foreach (var key in node.Animations.Last().ObjectAnimation.Keys) | 
 
 
 
 
 | 184 | key.Rotation = Quaternion.Identity; | 
 
 
 
 
 | 185 | } | 
 
 
 
 
 | 186 | else if (isMax) | 
 
 
 
 
 | 187 | { | 
 
 
 
 
 | 188 | foreach (var key in node.Animations.Last().ObjectAnimation.Keys) | 
 
 
 
 
 | 189 | key.Rotation *= Quaternion.CreateFromAxisAngle(Vector3.UnitX, MathHelper.HalfPi); | 
 
 
 
 
 | 190 | } | 
 
 
 
 
 | 191 | } | 
 
 
 
 
 | 192 | } | 
 
 
 
 
 | 193 |  | 
 
 
 
 
 | 194 | private void ReadBodyAnimation(int start, SceneNode node, BodyNode bodyNode, ObjectAnimation[] animations) | 
 
 
 
 
 | 195 | { | 
 
 
 
 
 | 196 | node.Animations.Add(new SceneNodeAnimation | 
 
 
 
 
 | 197 | { | 
 
 
 
 
 | 198 | Start = start, | 
 
 
 
 
 | 199 | ObjectAnimation = animations[bodyNode.Index] | 
 
 
 
 
 | 200 | }); | 
 
 
 
 
 | 201 |  | 
 
 
 
 
 | 202 | for (int i = 0; i < node.Nodes.Count; i++) | 
 
 
 
 
 | 203 | ReadBodyAnimation(start, node.Nodes[i], bodyNode.Nodes[i], animations); | 
 
 
 
 
 | 204 | } | 
 
 
 
 
 | 205 |  | 
 
 
 
 
 | 206 | private void ReadCamera(XmlReader xml, SceneNode node) | 
 
 
 
 
 | 207 | { | 
 
 
 
 
 | 208 | node.IsCamera = true; | 
 
 
 
 
 | 209 |  | 
 
 
 
 
 | 210 | xml.Skip(); | 
 
 
 
 
 | 211 | } | 
 
 
 
 
 | 212 |  | 
 
 
 
 
 | 213 |  | 
 
 
 
 
 | 214 | private Dae.Node WriteNode(SceneNode node, List<ObjectAnimationKey> parentFrames) | 
 
 
 
 
 | 215 | { | 
 
 
 
 
 | 216 | var daeNode = new Dae.Node | 
 
 
 
 
 | 217 | { | 
 
 
 
 
 | 218 | Name = node.Name | 
 
 
 
 
 | 219 | }; | 
 
 
 
 
 | 220 |  | 
 
 
 
 
 | 221 | foreach (var geometry in node.Geometries) | 
 
 
 
 
 | 222 | daeNode.Instances.Add(geometryWriter.WriteGeometryInstance(geometry, geometry.Name)); | 
 
 
 
 
 | 223 |  | 
 
 
 
 
 | 224 | if (node.IsCamera) | 
 
 
 
 
 | 225 | WriteCamera(daeNode); | 
 
 
 
 
 | 226 |  | 
 
 
 
 
 | 227 | List<ObjectAnimationKey> frames = null; | 
 
 
 
 
 | 228 |  | 
 
 
 
 
 | 229 | if (node.Animations.Count > 0) | 
 
 
 
 
 | 230 | { | 
 
 
 
 
 | 231 | frames = BuildFrames(node); | 
 
 
 
 
 | 232 | WriteAnimation(daeNode, BuildLocalFrames(node.Body == null ? parentFrames : null, frames)); | 
 
 
 
 
 | 233 | } | 
 
 
 
 
 | 234 |  | 
 
 
 
 
 | 235 | foreach (var child in node.Nodes) | 
 
 
 
 
 | 236 | daeNode.Nodes.Add(WriteNode(child, frames)); | 
 
 
 
 
 | 237 |  | 
 
 
 
 
 | 238 | return daeNode; | 
 
 
 
 
 | 239 | } | 
 
 
 
 
 | 240 |  | 
 
 
 
 
 | 241 | private static List<ObjectAnimationKey> BuildFrames(SceneNode node) | 
 
 
 
 
 | 242 | { | 
 
 
 
 
 | 243 | var frames = new List<ObjectAnimationKey>(); | 
 
 
 
 
 | 244 |  | 
 
 
 
 
 | 245 | foreach (var animation in node.Animations) | 
 
 
 
 
 | 246 | { | 
 
 
 
 
 | 247 | var animFrames = animation.ObjectAnimation.Interpolate(); | 
 
 
 
 
 | 248 | var start = animation.Start; | 
 
 
 
 
 | 249 |  | 
 
 
 
 
 | 250 | if (frames.Count > 0) | 
 
 
 
 
 | 251 | start += frames.Last().Time + 1; | 
 
 
 
 
 | 252 |  | 
 
 
 
 
 | 253 | foreach (var frame in animFrames) | 
 
 
 
 
 | 254 | frame.Time += start; | 
 
 
 
 
 | 255 |  | 
 
 
 
 
 | 256 | if (frames.Count > 0) | 
 
 
 
 
 | 257 | { | 
 
 
 
 
 | 258 | while (frames.Last().Time >= animFrames.First().Time) | 
 
 
 
 
 | 259 | { | 
 
 
 
 
 | 260 | frames.RemoveAt(frames.Count - 1); | 
 
 
 
 
 | 261 | } | 
 
 
 
 
 | 262 |  | 
 
 
 
 
 | 263 | while (frames.Last().Time + 1 < animFrames.First().Time) | 
 
 
 
 
 | 264 | { | 
 
 
 
 
 | 265 | frames.Add(new ObjectAnimationKey | 
 
 
 
 
 | 266 | { | 
 
 
 
 
 | 267 | Time = frames.Last().Time + 1, | 
 
 
 
 
 | 268 | Rotation = frames.Last().Rotation, | 
 
 
 
 
 | 269 | Translation = frames.Last().Translation, | 
 
 
 
 
 | 270 | Scale = frames.Last().Scale | 
 
 
 
 
 | 271 | }); | 
 
 
 
 
 | 272 | } | 
 
 
 
 
 | 273 | } | 
 
 
 
 
 | 274 |  | 
 
 
 
 
 | 275 | frames.AddRange(animFrames); | 
 
 
 
 
 | 276 | } | 
 
 
 
 
 | 277 |  | 
 
 
 
 
 | 278 | return frames; | 
 
 
 
 
 | 279 | } | 
 
 
 
 
 | 280 |  | 
 
 
 
 
 | 281 | private static List<ObjectAnimationKey> BuildLocalFrames(List<ObjectAnimationKey> parentFrames, List<ObjectAnimationKey> frames) | 
 
 
 
 
 | 282 | { | 
 
 
 
 
 | 283 | var localFrames = frames; | 
 
 
 
 
 | 284 |  | 
 
 
 
 
 | 285 | if (parentFrames != null) | 
 
 
 
 
 | 286 | { | 
 
 
 
 
 | 287 | localFrames = new List<ObjectAnimationKey>(localFrames.Count); | 
 
 
 
 
 | 288 |  | 
 
 
 
 
 | 289 | for (int i = 0; i < frames.Count; i++) | 
 
 
 
 
 | 290 | { | 
 
 
 
 
 | 291 | var frame = frames[i]; | 
 
 
 
 
 | 292 | var parentFrame = parentFrames[i]; | 
 
 
 
 
 | 293 |  | 
 
 
 
 
 | 294 | localFrames.Add(new ObjectAnimationKey | 
 
 
 
 
 | 295 | { | 
 
 
 
 
 | 296 | Time = frame.Time, | 
 
 
 
 
 | 297 | Scale = frame.Scale / parentFrame.Scale, | 
 
 
 
 
 | 298 | Rotation = Quaternion.Conjugate(parentFrame.Rotation) * frame.Rotation, | 
 
 
 
 
 | 299 | Translation = Vector3.Transform(frame.Translation - parentFrame.Translation, parentFrame.Rotation.Inverse()) / parentFrame.Scale | 
 
 
 
 
 | 300 | }); | 
 
 
 
 
 | 301 | } | 
 
 
 
 
 | 302 | } | 
 
 
 
 
 | 303 |  | 
 
 
 
 
 | 304 | return localFrames; | 
 
 
 
 
 | 305 | } | 
 
 
 
 
 | 306 |  | 
 
 
 
 
 | 307 | private static void WriteAnimation(Dae.Node node, List<ObjectAnimationKey> frames) | 
 
 
 
 
 | 308 | { | 
 
 
 
 
 | 309 | var times = new float[frames.Count]; | 
 
 
 
 
 | 310 | var interpolations = new string[times.Length]; | 
 
 
 
 
 | 311 | var positions = new Vector3[frames.Count]; | 
 
 
 
 
 | 312 | var angles = new Vector3[frames.Count]; | 
 
 
 
 
 | 313 |  | 
 
 
 
 
 | 314 | for (int i = 0; i < times.Length; ++i) | 
 
 
 
 
 | 315 | times[i] = frames[i].Time / 60.0f; | 
 
 
 
 
 | 316 |  | 
 
 
 
 
 | 317 | for (int i = 0; i < interpolations.Length; i++) | 
 
 
 
 
 | 318 | interpolations[i] = "LINEAR"; | 
 
 
 
 
 | 319 |  | 
 
 
 
 
 | 320 | for (int i = 0; i < frames.Count; i++) | 
 
 
 
 
 | 321 | positions[i] = frames[i].Translation; | 
 
 
 
 
 | 322 |  | 
 
 
 
 
 | 323 | for (int i = 0; i < frames.Count; i++) | 
 
 
 
 
 | 324 | angles[i] = frames[i].Rotation.ToEulerXYZ(); | 
 
 
 
 
 | 325 |  | 
 
 
 
 
 | 326 | var translate = node.Transforms.Translate("translate", positions[0]); ; | 
 
 
 
 
 | 327 | var rotateX = node.Transforms.Rotate("rotX", Vector3.UnitX, angles[0].X); | 
 
 
 
 
 | 328 | var rotateY = node.Transforms.Rotate("rotY", Vector3.UnitY, angles[0].Y); | 
 
 
 
 
 | 329 | var rotateZ = node.Transforms.Rotate("rotZ", Vector3.UnitZ, angles[0].Z); | 
 
 
 
 
 | 330 | var scale = node.Transforms.Scale("scale", frames[0].Scale); | 
 
 
 
 
 | 331 |  | 
 
 
 
 
 | 332 | WriteSampler(times, interpolations, i => positions[i].X, translate, "X"); | 
 
 
 
 
 | 333 | WriteSampler(times, interpolations, i => positions[i].Y, translate, "Y"); | 
 
 
 
 
 | 334 | WriteSampler(times, interpolations, i => positions[i].Z, translate, "Z"); | 
 
 
 
 
 | 335 | WriteSampler(times, interpolations, i => angles[i].X, rotateX, "ANGLE"); | 
 
 
 
 
 | 336 | WriteSampler(times, interpolations, i => angles[i].Y, rotateY, "ANGLE"); | 
 
 
 
 
 | 337 | WriteSampler(times, interpolations, i => angles[i].Z, rotateZ, "ANGLE"); | 
 
 
 
 
 | 338 | } | 
 
 
 
 
 | 339 |  | 
 
 
 
 
 | 340 | private static void WriteSampler(float[] times, string[] interpolations, Func<int, float> getValue, Dae.Transform transform, string targetName) | 
 
 
 
 
 | 341 | { | 
 
 
 
 
 | 342 | var values = new float[times.Length]; | 
 
 
 
 
 | 343 |  | 
 
 
 
 
 | 344 | for (int i = 0; i < values.Length; ++i) | 
 
 
 
 
 | 345 | values[i] = getValue(i); | 
 
 
 
 
 | 346 |  | 
 
 
 
 
 | 347 | transform.BindAnimation(targetName, new Dae.Sampler | 
 
 
 
 
 | 348 | { | 
 
 
 
 
 | 349 | Inputs = { | 
 
 
 
 
 | 350 | new Dae.Input(Dae.Semantic.Input, new Dae.Source(times, 1)), | 
 
 
 
 
 | 351 | new Dae.Input(Dae.Semantic.Output, new Dae.Source(values, 1)), | 
 
 
 
 
 | 352 | new Dae.Input(Dae.Semantic.Interpolation, new Dae.Source(interpolations, 1)) | 
 
 
 
 
 | 353 | } | 
 
 
 
 
 | 354 | }); | 
 
 
 
 
 | 355 | } | 
 
 
 
 
 | 356 |  | 
 
 
 
 
 | 357 | private static void WriteCamera(Dae.Node daeNode) | 
 
 
 
 
 | 358 | { | 
 
 
 
 
 | 359 | daeNode.Instances.Add(new Dae.CameraInstance | 
 
 
 
 
 | 360 | { | 
 
 
 
 
 | 361 | Target = new Dae.Camera | 
 
 
 
 
 | 362 | { | 
 
 
 
 
 | 363 | XFov = 45.0f, | 
 
 
 
 
 | 364 | AspectRatio = 4.0f / 3.0f, | 
 
 
 
 
 | 365 | ZNear = 1.0f, | 
 
 
 
 
 | 366 | ZFar = 10000.0f | 
 
 
 
 
 | 367 | } | 
 
 
 
 
 | 368 | }); | 
 
 
 
 
 | 369 | } | 
 
 
 
 
 | 370 | } | 
 
 
 
 
 | 371 | } |