opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | eva | 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 copyrightD |
||
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.Text; |
||
31 | |||
32 | using OpenSim.Framework; |
||
33 | using OpenSim.Region.Physics.Manager; |
||
34 | using OpenSim.Region.Physics.Meshing; |
||
35 | using OpenSim.Region.Physics.ConvexDecompositionDotNet; |
||
36 | |||
37 | using OMV = OpenMetaverse; |
||
38 | |||
39 | namespace OpenSim.Region.Physics.BulletSPlugin |
||
40 | { |
||
41 | public abstract class BSShape |
||
42 | { |
||
43 | private static string LogHeader = "[BULLETSIM SHAPE]"; |
||
44 | |||
45 | public int referenceCount { get; set; } |
||
46 | public DateTime lastReferenced { get; set; } |
||
47 | public BulletShape physShapeInfo { get; set; } |
||
48 | |||
49 | public BSShape() |
||
50 | { |
||
51 | referenceCount = 1; |
||
52 | lastReferenced = DateTime.Now; |
||
53 | physShapeInfo = new BulletShape(); |
||
54 | } |
||
55 | public BSShape(BulletShape pShape) |
||
56 | { |
||
57 | referenceCount = 1; |
||
58 | lastReferenced = DateTime.Now; |
||
59 | physShapeInfo = pShape; |
||
60 | } |
||
61 | |||
62 | // Get another reference to this shape. |
||
63 | public abstract BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim); |
||
64 | |||
65 | // Called when this shape is being used again. |
||
66 | // Used internally. External callers should call instance.GetReference() to properly copy/reference |
||
67 | // the shape. |
||
68 | protected virtual void IncrementReference() |
||
69 | { |
||
70 | referenceCount++; |
||
71 | lastReferenced = DateTime.Now; |
||
72 | } |
||
73 | |||
74 | // Called when this shape is done being used. |
||
75 | protected virtual void DecrementReference() |
||
76 | { |
||
77 | referenceCount--; |
||
78 | lastReferenced = DateTime.Now; |
||
79 | } |
||
80 | |||
81 | // Release the use of a physical shape. |
||
82 | public abstract void Dereference(BSScene physicsScene); |
||
83 | |||
84 | // Return 'true' if there is an allocated physics physical shape under this class instance. |
||
85 | public virtual bool HasPhysicalShape |
||
86 | { |
||
87 | get |
||
88 | { |
||
89 | if (physShapeInfo != null) |
||
90 | return physShapeInfo.HasPhysicalShape; |
||
91 | return false; |
||
92 | } |
||
93 | } |
||
94 | public virtual BSPhysicsShapeType ShapeType |
||
95 | { |
||
96 | get |
||
97 | { |
||
98 | BSPhysicsShapeType ret = BSPhysicsShapeType.SHAPE_UNKNOWN; |
||
99 | if (physShapeInfo != null && physShapeInfo.HasPhysicalShape) |
||
100 | ret = physShapeInfo.shapeType; |
||
101 | return ret; |
||
102 | } |
||
103 | } |
||
104 | |||
105 | // Returns a string for debugging that uniquily identifies the memory used by this instance |
||
106 | public virtual string AddrString |
||
107 | { |
||
108 | get |
||
109 | { |
||
110 | if (physShapeInfo != null) |
||
111 | return physShapeInfo.AddrString; |
||
112 | return "unknown"; |
||
113 | } |
||
114 | } |
||
115 | |||
116 | public override string ToString() |
||
117 | { |
||
118 | StringBuilder buff = new StringBuilder(); |
||
119 | if (physShapeInfo == null) |
||
120 | { |
||
121 | buff.Append("<noPhys"); |
||
122 | } |
||
123 | else |
||
124 | { |
||
125 | buff.Append("<phy="); |
||
126 | buff.Append(physShapeInfo.ToString()); |
||
127 | } |
||
128 | buff.Append(",c="); |
||
129 | buff.Append(referenceCount.ToString()); |
||
130 | buff.Append(">"); |
||
131 | return buff.ToString(); |
||
132 | } |
||
133 | |||
134 | #region Common shape routines |
||
135 | // Create a hash of all the shape parameters to be used as a key for this particular shape. |
||
136 | public static System.UInt64 ComputeShapeKey(OMV.Vector3 size, PrimitiveBaseShape pbs, out float retLod) |
||
137 | { |
||
138 | // level of detail based on size and type of the object |
||
139 | float lod = BSParam.MeshLOD; |
||
140 | if (pbs.SculptEntry) |
||
141 | lod = BSParam.SculptLOD; |
||
142 | |||
143 | // Mega prims usually get more detail because one can interact with shape approximations at this size. |
||
144 | float maxAxis = Math.Max(size.X, Math.Max(size.Y, size.Z)); |
||
145 | if (maxAxis > BSParam.MeshMegaPrimThreshold) |
||
146 | lod = BSParam.MeshMegaPrimLOD; |
||
147 | |||
148 | retLod = lod; |
||
149 | return pbs.GetMeshKey(size, lod); |
||
150 | } |
||
151 | |||
152 | // The creation of a mesh or hull can fail if an underlying asset is not available. |
||
153 | // There are two cases: 1) the asset is not in the cache and it needs to be fetched; |
||
154 | // and 2) the asset cannot be converted (like failed decompression of JPEG2000s). |
||
155 | // The first case causes the asset to be fetched. The second case requires |
||
156 | // us to not loop forever. |
||
157 | // Called after creating a physical mesh or hull. If the physical shape was created, |
||
158 | // just return. |
||
159 | public static BulletShape VerifyMeshCreated(BSScene physicsScene, BulletShape newShape, BSPhysObject prim) |
||
160 | { |
||
161 | // If the shape was successfully created, nothing more to do |
||
162 | if (newShape.HasPhysicalShape) |
||
163 | return newShape; |
||
164 | |||
165 | // VerifyMeshCreated is called after trying to create the mesh. If we think the asset had been |
||
166 | // fetched but we end up here again, the meshing of the asset must have failed. |
||
167 | // Prevent trying to keep fetching the mesh by declaring failure. |
||
168 | if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Fetched) |
||
169 | { |
||
170 | prim.PrimAssetState = BSPhysObject.PrimAssetCondition.FailedMeshing; |
||
171 | physicsScene.Logger.WarnFormat("{0} Fetched asset would not mesh. prim={1}, texture={2}", |
||
172 | LogHeader, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture); |
||
173 | physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,setFailed,prim={1},tex={2}", |
||
174 | prim.LocalID, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture); |
||
175 | } |
||
176 | else |
||
177 | { |
||
178 | // If this mesh has an underlying asset and we have not failed getting it before, fetch the asset |
||
179 | if (prim.BaseShape.SculptEntry |
||
180 | && prim.PrimAssetState != BSPhysObject.PrimAssetCondition.FailedAssetFetch |
||
181 | && prim.PrimAssetState != BSPhysObject.PrimAssetCondition.FailedMeshing |
||
182 | && prim.PrimAssetState != BSPhysObject.PrimAssetCondition.Waiting |
||
183 | && prim.BaseShape.SculptTexture != OMV.UUID.Zero |
||
184 | ) |
||
185 | { |
||
186 | physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,fetchAsset,objNam={1},tex={2}", |
||
187 | prim.LocalID, prim.PhysObjectName, prim.BaseShape.SculptTexture); |
||
188 | // Multiple requestors will know we're waiting for this asset |
||
189 | prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Waiting; |
||
190 | |||
191 | BSPhysObject xprim = prim; |
||
192 | Util.FireAndForget(delegate |
||
193 | { |
||
194 | // physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,inFireAndForget", xprim.LocalID); |
||
195 | RequestAssetDelegate assetProvider = physicsScene.RequestAssetMethod; |
||
196 | if (assetProvider != null) |
||
197 | { |
||
198 | BSPhysObject yprim = xprim; // probably not necessary, but, just in case. |
||
199 | assetProvider(yprim.BaseShape.SculptTexture, delegate(AssetBase asset) |
||
200 | { |
||
201 | // physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,assetProviderCallback", xprim.LocalID); |
||
202 | bool assetFound = false; |
||
203 | string mismatchIDs = String.Empty; // DEBUG DEBUG |
||
204 | if (asset != null && yprim.BaseShape.SculptEntry) |
||
205 | { |
||
206 | if (yprim.BaseShape.SculptTexture.ToString() == asset.ID) |
||
207 | { |
||
208 | yprim.BaseShape.SculptData = asset.Data; |
||
209 | // This will cause the prim to see that the filler shape is not the right |
||
210 | // one and try again to build the object. |
||
211 | // No race condition with the normal shape setting since the rebuild is at taint time. |
||
212 | yprim.PrimAssetState = BSPhysObject.PrimAssetCondition.Fetched; |
||
213 | yprim.ForceBodyShapeRebuild(false /* inTaintTime */); |
||
214 | assetFound = true; |
||
215 | } |
||
216 | else |
||
217 | { |
||
218 | mismatchIDs = yprim.BaseShape.SculptTexture.ToString() + "/" + asset.ID; |
||
219 | } |
||
220 | } |
||
221 | if (!assetFound) |
||
222 | { |
||
223 | yprim.PrimAssetState = BSPhysObject.PrimAssetCondition.FailedAssetFetch; |
||
224 | } |
||
225 | physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,fetchAssetCallback,found={1},isSculpt={2},ids={3}", |
||
226 | yprim.LocalID, assetFound, yprim.BaseShape.SculptEntry, mismatchIDs ); |
||
227 | }); |
||
228 | } |
||
229 | else |
||
230 | { |
||
231 | xprim.PrimAssetState = BSPhysObject.PrimAssetCondition.FailedAssetFetch; |
||
232 | physicsScene.Logger.ErrorFormat("{0} Physical object requires asset but no asset provider. Name={1}", |
||
233 | LogHeader, physicsScene.Name); |
||
234 | } |
||
235 | }); |
||
236 | } |
||
237 | else |
||
238 | { |
||
239 | if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedAssetFetch) |
||
240 | { |
||
241 | physicsScene.Logger.WarnFormat("{0} Mesh failed to fetch asset. prim={1}, texture={2}", |
||
242 | LogHeader, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture); |
||
243 | physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,wasFailed,prim={1},tex={2}", |
||
244 | prim.LocalID, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture); |
||
245 | } |
||
246 | if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedMeshing) |
||
247 | { |
||
248 | physicsScene.Logger.WarnFormat("{0} Mesh asset would not mesh. prim={1}, texture={2}", |
||
249 | LogHeader, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture); |
||
250 | physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,wasFailedMeshing,prim={1},tex={2}", |
||
251 | prim.LocalID, UsefulPrimInfo(physicsScene, prim), prim.BaseShape.SculptTexture); |
||
252 | } |
||
253 | } |
||
254 | } |
||
255 | |||
256 | // While we wait for the mesh defining asset to be loaded, stick in a simple box for the object. |
||
257 | BSShape fillShape = BSShapeNative.GetReference(physicsScene, prim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX); |
||
258 | physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,boxTempShape", prim.LocalID); |
||
259 | |||
260 | return fillShape.physShapeInfo; |
||
261 | } |
||
262 | |||
263 | public static String UsefulPrimInfo(BSScene pScene, BSPhysObject prim) |
||
264 | { |
||
265 | StringBuilder buff = new StringBuilder(prim.PhysObjectName); |
||
266 | buff.Append("/pos="); |
||
267 | buff.Append(prim.RawPosition.ToString()); |
||
268 | if (pScene != null) |
||
269 | { |
||
270 | buff.Append("/rgn="); |
||
271 | buff.Append(pScene.Name); |
||
272 | } |
||
273 | return buff.ToString(); |
||
274 | } |
||
275 | |||
276 | #endregion // Common shape routines |
||
277 | } |
||
278 | |||
279 | // ============================================================================================================ |
||
280 | public class BSShapeNull : BSShape |
||
281 | { |
||
282 | public BSShapeNull() : base() |
||
283 | { |
||
284 | } |
||
285 | public static BSShape GetReference() { return new BSShapeNull(); } |
||
286 | public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) { return new BSShapeNull(); } |
||
287 | public override void Dereference(BSScene physicsScene) { /* The magic of garbage collection will make this go away */ } |
||
288 | } |
||
289 | |||
290 | // ============================================================================================================ |
||
291 | public class BSShapeNative : BSShape |
||
292 | { |
||
293 | private static string LogHeader = "[BULLETSIM SHAPE NATIVE]"; |
||
294 | public BSShapeNative(BulletShape pShape) : base(pShape) |
||
295 | { |
||
296 | } |
||
297 | |||
298 | public static BSShape GetReference(BSScene physicsScene, BSPhysObject prim, |
||
299 | BSPhysicsShapeType shapeType, FixedShapeKey shapeKey) |
||
300 | { |
||
301 | // Native shapes are not shared and are always built anew. |
||
302 | return new BSShapeNative(CreatePhysicalNativeShape(physicsScene, prim, shapeType, shapeKey)); |
||
303 | } |
||
304 | |||
305 | public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) |
||
306 | { |
||
307 | // Native shapes are not shared so we return a new shape. |
||
308 | BSShape ret = null; |
||
309 | lock (physShapeInfo) |
||
310 | { |
||
311 | ret = new BSShapeNative(CreatePhysicalNativeShape(pPhysicsScene, pPrim, |
||
312 | physShapeInfo.shapeType, (FixedShapeKey)physShapeInfo.shapeKey)); |
||
313 | } |
||
314 | return ret; |
||
315 | } |
||
316 | |||
317 | // Make this reference to the physical shape go away since native shapes are not shared. |
||
318 | public override void Dereference(BSScene physicsScene) |
||
319 | { |
||
320 | // Native shapes are not tracked and are released immediately |
||
321 | lock (physShapeInfo) |
||
322 | { |
||
323 | if (physShapeInfo.HasPhysicalShape) |
||
324 | { |
||
325 | physicsScene.DetailLog("{0},BSShapeNative.Dereference,deleteNativeShape,shape={1}", BSScene.DetailLogZero, this); |
||
326 | physicsScene.PE.DeleteCollisionShape(physicsScene.World, physShapeInfo); |
||
327 | } |
||
328 | physShapeInfo.Clear(); |
||
329 | // Garbage collection will free up this instance. |
||
330 | } |
||
331 | } |
||
332 | |||
333 | private static BulletShape CreatePhysicalNativeShape(BSScene physicsScene, BSPhysObject prim, |
||
334 | BSPhysicsShapeType shapeType, FixedShapeKey shapeKey) |
||
335 | { |
||
336 | BulletShape newShape; |
||
337 | |||
338 | ShapeData nativeShapeData = new ShapeData(); |
||
339 | nativeShapeData.Type = shapeType; |
||
340 | nativeShapeData.ID = prim.LocalID; |
||
341 | nativeShapeData.Scale = prim.Scale; |
||
342 | nativeShapeData.Size = prim.Scale; |
||
343 | nativeShapeData.MeshKey = (ulong)shapeKey; |
||
344 | nativeShapeData.HullKey = (ulong)shapeKey; |
||
345 | |||
346 | if (shapeType == BSPhysicsShapeType.SHAPE_CAPSULE) |
||
347 | { |
||
348 | newShape = physicsScene.PE.BuildCapsuleShape(physicsScene.World, 1f, 1f, prim.Scale); |
||
349 | physicsScene.DetailLog("{0},BSShapeNative,capsule,scale={1}", prim.LocalID, prim.Scale); |
||
350 | } |
||
351 | else |
||
352 | { |
||
353 | newShape = physicsScene.PE.BuildNativeShape(physicsScene.World, nativeShapeData); |
||
354 | } |
||
355 | if (!newShape.HasPhysicalShape) |
||
356 | { |
||
357 | physicsScene.Logger.ErrorFormat("{0} BuildPhysicalNativeShape failed. ID={1}, shape={2}", |
||
358 | LogHeader, prim.LocalID, shapeType); |
||
359 | } |
||
360 | newShape.shapeType = shapeType; |
||
361 | newShape.isNativeShape = true; |
||
362 | newShape.shapeKey = (UInt64)shapeKey; |
||
363 | return newShape; |
||
364 | } |
||
365 | |||
366 | } |
||
367 | |||
368 | // ============================================================================================================ |
||
369 | public class BSShapeMesh : BSShape |
||
370 | { |
||
371 | private static string LogHeader = "[BULLETSIM SHAPE MESH]"; |
||
372 | public static Dictionary<System.UInt64, BSShapeMesh> Meshes = new Dictionary<System.UInt64, BSShapeMesh>(); |
||
373 | |||
374 | public BSShapeMesh(BulletShape pShape) : base(pShape) |
||
375 | { |
||
376 | } |
||
377 | public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) |
||
378 | { |
||
379 | float lod; |
||
380 | System.UInt64 newMeshKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod); |
||
381 | |||
382 | BSShapeMesh retMesh = null; |
||
383 | lock (Meshes) |
||
384 | { |
||
385 | if (Meshes.TryGetValue(newMeshKey, out retMesh)) |
||
386 | { |
||
387 | // The mesh has already been created. Return a new reference to same. |
||
388 | retMesh.IncrementReference(); |
||
389 | } |
||
390 | else |
||
391 | { |
||
392 | retMesh = new BSShapeMesh(new BulletShape()); |
||
393 | // An instance of this mesh has not been created. Build and remember same. |
||
394 | BulletShape newShape = retMesh.CreatePhysicalMesh(physicsScene, prim, newMeshKey, prim.BaseShape, prim.Size, lod); |
||
395 | |||
396 | // Check to see if mesh was created (might require an asset). |
||
397 | newShape = VerifyMeshCreated(physicsScene, newShape, prim); |
||
398 | if (!newShape.isNativeShape |
||
399 | || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedMeshing |
||
400 | || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedAssetFetch) |
||
401 | { |
||
402 | // If a mesh was what was created, remember the built shape for later sharing. |
||
403 | // Also note that if meshing failed we put it in the mesh list as there is nothing else to do about the mesh. |
||
404 | Meshes.Add(newMeshKey, retMesh); |
||
405 | } |
||
406 | |||
407 | retMesh.physShapeInfo = newShape; |
||
408 | } |
||
409 | } |
||
410 | physicsScene.DetailLog("{0},BSShapeMesh,getReference,mesh={1},size={2},lod={3}", prim.LocalID, retMesh, prim.Size, lod); |
||
411 | return retMesh; |
||
412 | } |
||
413 | public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) |
||
414 | { |
||
415 | BSShape ret = null; |
||
416 | // If the underlying shape is native, the actual shape has not been build (waiting for asset) |
||
417 | // and we must create a copy of the native shape since they are never shared. |
||
418 | if (physShapeInfo.HasPhysicalShape && physShapeInfo.isNativeShape) |
||
419 | { |
||
420 | // TODO: decide when the native shapes should be freed. Check in Dereference? |
||
421 | ret = BSShapeNative.GetReference(pPhysicsScene, pPrim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX); |
||
422 | } |
||
423 | else |
||
424 | { |
||
425 | // Another reference to this shape is just counted. |
||
426 | IncrementReference(); |
||
427 | ret = this; |
||
428 | } |
||
429 | return ret; |
||
430 | } |
||
431 | public override void Dereference(BSScene physicsScene) |
||
432 | { |
||
433 | lock (Meshes) |
||
434 | { |
||
435 | this.DecrementReference(); |
||
436 | physicsScene.DetailLog("{0},BSShapeMesh.Dereference,shape={1}", BSScene.DetailLogZero, this); |
||
437 | // TODO: schedule aging and destruction of unused meshes. |
||
438 | } |
||
439 | } |
||
440 | // Loop through all the known meshes and return the description based on the physical address. |
||
441 | public static bool TryGetMeshByPtr(BulletShape pShape, out BSShapeMesh outMesh) |
||
442 | { |
||
443 | bool ret = false; |
||
444 | BSShapeMesh foundDesc = null; |
||
445 | lock (Meshes) |
||
446 | { |
||
447 | foreach (BSShapeMesh sm in Meshes.Values) |
||
448 | { |
||
449 | if (sm.physShapeInfo.ReferenceSame(pShape)) |
||
450 | { |
||
451 | foundDesc = sm; |
||
452 | ret = true; |
||
453 | break; |
||
454 | } |
||
455 | |||
456 | } |
||
457 | } |
||
458 | outMesh = foundDesc; |
||
459 | return ret; |
||
460 | } |
||
461 | |||
462 | public delegate BulletShape CreateShapeCall(BulletWorld world, int indicesCount, int[] indices, int verticesCount, float[] vertices ); |
||
463 | private BulletShape CreatePhysicalMesh(BSScene physicsScene, BSPhysObject prim, System.UInt64 newMeshKey, |
||
464 | PrimitiveBaseShape pbs, OMV.Vector3 size, float lod) |
||
465 | { |
||
466 | return BSShapeMesh.CreatePhysicalMeshShape(physicsScene, prim, newMeshKey, pbs, size, lod, |
||
467 | (w, iC, i, vC, v) => physicsScene.PE.CreateMeshShape(w, iC, i, vC, v) ); |
||
468 | } |
||
469 | |||
470 | // Code that uses the mesher to create the index/vertices info for a trimesh shape. |
||
471 | // This is used by the passed 'makeShape' call to create the Bullet mesh shape. |
||
472 | // The actual build call is passed so this logic can be used by several of the shapes that use a |
||
473 | // simple mesh as their base shape. |
||
474 | public static BulletShape CreatePhysicalMeshShape(BSScene physicsScene, BSPhysObject prim, System.UInt64 newMeshKey, |
||
475 | PrimitiveBaseShape pbs, OMV.Vector3 size, float lod, CreateShapeCall makeShape) |
||
476 | { |
||
477 | BulletShape newShape = new BulletShape(); |
||
478 | |||
479 | IMesh meshData = null; |
||
480 | lock (physicsScene.mesher) |
||
481 | { |
||
482 | meshData = physicsScene.mesher.CreateMesh(prim.PhysObjectName, pbs, size, lod, |
||
483 | false, // say it is not physical so a bounding box is not built |
||
484 | false // do not cache the mesh and do not use previously built versions |
||
485 | ); |
||
486 | } |
||
487 | |||
488 | if (meshData != null) |
||
489 | { |
||
490 | if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Fetched) |
||
491 | { |
||
492 | // Release the fetched asset data once it has been used. |
||
493 | pbs.SculptData = new byte[0]; |
||
494 | prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Unknown; |
||
495 | } |
||
496 | |||
497 | int[] indices = meshData.getIndexListAsInt(); |
||
498 | int realIndicesIndex = indices.Length; |
||
499 | float[] verticesAsFloats = meshData.getVertexListAsFloat(); |
||
500 | |||
501 | if (BSParam.ShouldRemoveZeroWidthTriangles) |
||
502 | { |
||
503 | // Remove degenerate triangles. These are triangles with two of the vertices |
||
504 | // are the same. This is complicated by the problem that vertices are not |
||
505 | // made unique in sculpties so we have to compare the values in the vertex. |
||
506 | realIndicesIndex = 0; |
||
507 | for (int tri = 0; tri < indices.Length; tri += 3) |
||
508 | { |
||
509 | // Compute displacements into vertex array for each vertex of the triangle |
||
510 | int v1 = indices[tri + 0] * 3; |
||
511 | int v2 = indices[tri + 1] * 3; |
||
512 | int v3 = indices[tri + 2] * 3; |
||
513 | // Check to see if any two of the vertices are the same |
||
514 | if (!( ( verticesAsFloats[v1 + 0] == verticesAsFloats[v2 + 0] |
||
515 | && verticesAsFloats[v1 + 1] == verticesAsFloats[v2 + 1] |
||
516 | && verticesAsFloats[v1 + 2] == verticesAsFloats[v2 + 2]) |
||
517 | || ( verticesAsFloats[v2 + 0] == verticesAsFloats[v3 + 0] |
||
518 | && verticesAsFloats[v2 + 1] == verticesAsFloats[v3 + 1] |
||
519 | && verticesAsFloats[v2 + 2] == verticesAsFloats[v3 + 2]) |
||
520 | || ( verticesAsFloats[v1 + 0] == verticesAsFloats[v3 + 0] |
||
521 | && verticesAsFloats[v1 + 1] == verticesAsFloats[v3 + 1] |
||
522 | && verticesAsFloats[v1 + 2] == verticesAsFloats[v3 + 2]) ) |
||
523 | ) |
||
524 | { |
||
525 | // None of the vertices of the triangles are the same. This is a good triangle; |
||
526 | indices[realIndicesIndex + 0] = indices[tri + 0]; |
||
527 | indices[realIndicesIndex + 1] = indices[tri + 1]; |
||
528 | indices[realIndicesIndex + 2] = indices[tri + 2]; |
||
529 | realIndicesIndex += 3; |
||
530 | } |
||
531 | } |
||
532 | } |
||
533 | physicsScene.DetailLog("{0},BSShapeMesh.CreatePhysicalMesh,key={1},origTri={2},realTri={3},numVerts={4}", |
||
534 | BSScene.DetailLogZero, newMeshKey.ToString("X"), indices.Length / 3, realIndicesIndex / 3, verticesAsFloats.Length / 3); |
||
535 | |||
536 | if (realIndicesIndex != 0) |
||
537 | { |
||
538 | newShape = makeShape(physicsScene.World, realIndicesIndex, indices, verticesAsFloats.Length / 3, verticesAsFloats); |
||
539 | } |
||
540 | else |
||
541 | { |
||
542 | // Force the asset condition to 'failed' so we won't try to keep fetching and processing this mesh. |
||
543 | prim.PrimAssetState = BSPhysObject.PrimAssetCondition.FailedMeshing; |
||
544 | physicsScene.Logger.DebugFormat("{0} All mesh triangles degenerate. Prim={1}", LogHeader, UsefulPrimInfo(physicsScene, prim) ); |
||
545 | physicsScene.DetailLog("{0},BSShapeMesh.CreatePhysicalMesh,allDegenerate,key={1}", prim.LocalID, newMeshKey); |
||
546 | } |
||
547 | } |
||
548 | newShape.shapeKey = newMeshKey; |
||
549 | |||
550 | return newShape; |
||
551 | } |
||
552 | } |
||
553 | |||
554 | // ============================================================================================================ |
||
555 | public class BSShapeHull : BSShape |
||
556 | { |
||
557 | private static string LogHeader = "[BULLETSIM SHAPE HULL]"; |
||
558 | public static Dictionary<System.UInt64, BSShapeHull> Hulls = new Dictionary<System.UInt64, BSShapeHull>(); |
||
559 | |||
560 | public BSShapeHull(BulletShape pShape) : base(pShape) |
||
561 | { |
||
562 | } |
||
563 | public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) |
||
564 | { |
||
565 | float lod; |
||
566 | System.UInt64 newHullKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod); |
||
567 | |||
568 | BSShapeHull retHull = null; |
||
569 | lock (Hulls) |
||
570 | { |
||
571 | if (Hulls.TryGetValue(newHullKey, out retHull)) |
||
572 | { |
||
573 | // The mesh has already been created. Return a new reference to same. |
||
574 | retHull.IncrementReference(); |
||
575 | } |
||
576 | else |
||
577 | { |
||
578 | retHull = new BSShapeHull(new BulletShape()); |
||
579 | // An instance of this mesh has not been created. Build and remember same. |
||
580 | BulletShape newShape = retHull.CreatePhysicalHull(physicsScene, prim, newHullKey, prim.BaseShape, prim.Size, lod); |
||
581 | |||
582 | // Check to see if hull was created (might require an asset). |
||
583 | newShape = VerifyMeshCreated(physicsScene, newShape, prim); |
||
584 | if (!newShape.isNativeShape |
||
585 | || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedMeshing |
||
586 | || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedAssetFetch) |
||
587 | { |
||
588 | // If a mesh was what was created, remember the built shape for later sharing. |
||
589 | Hulls.Add(newHullKey, retHull); |
||
590 | } |
||
591 | retHull.physShapeInfo = newShape; |
||
592 | } |
||
593 | } |
||
594 | physicsScene.DetailLog("{0},BSShapeHull,getReference,hull={1},size={2},lod={3}", prim.LocalID, retHull, prim.Size, lod); |
||
595 | return retHull; |
||
596 | } |
||
597 | public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) |
||
598 | { |
||
599 | BSShape ret = null; |
||
600 | // If the underlying shape is native, the actual shape has not been build (waiting for asset) |
||
601 | // and we must create a copy of the native shape since they are never shared. |
||
602 | if (physShapeInfo.HasPhysicalShape && physShapeInfo.isNativeShape) |
||
603 | { |
||
604 | // TODO: decide when the native shapes should be freed. Check in Dereference? |
||
605 | ret = BSShapeNative.GetReference(pPhysicsScene, pPrim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX); |
||
606 | } |
||
607 | else |
||
608 | { |
||
609 | // Another reference to this shape is just counted. |
||
610 | IncrementReference(); |
||
611 | ret = this; |
||
612 | } |
||
613 | return ret; |
||
614 | } |
||
615 | public override void Dereference(BSScene physicsScene) |
||
616 | { |
||
617 | lock (Hulls) |
||
618 | { |
||
619 | this.DecrementReference(); |
||
620 | physicsScene.DetailLog("{0},BSShapeHull.Dereference,shape={1}", BSScene.DetailLogZero, this); |
||
621 | // TODO: schedule aging and destruction of unused meshes. |
||
622 | } |
||
623 | } |
||
624 | List<ConvexResult> m_hulls; |
||
625 | private BulletShape CreatePhysicalHull(BSScene physicsScene, BSPhysObject prim, System.UInt64 newHullKey, |
||
626 | PrimitiveBaseShape pbs, OMV.Vector3 size, float lod) |
||
627 | { |
||
628 | BulletShape newShape = new BulletShape(); |
||
629 | |||
630 | IMesh meshData = null; |
||
631 | List<List<OMV.Vector3>> allHulls = null; |
||
632 | lock (physicsScene.mesher) |
||
633 | { |
||
634 | // Pass true for physicalness as this prevents the creation of bounding box which is not needed |
||
635 | meshData = physicsScene.mesher.CreateMesh(prim.PhysObjectName, pbs, size, lod, true /* isPhysical */, false /* shouldCache */); |
||
636 | |||
637 | // If we should use the asset's hull info, fetch it out of the locked mesher |
||
638 | if (meshData != null && BSParam.ShouldUseAssetHulls) |
||
639 | { |
||
640 | Meshmerizer realMesher = physicsScene.mesher as Meshmerizer; |
||
641 | if (realMesher != null) |
||
642 | { |
||
643 | allHulls = realMesher.GetConvexHulls(size); |
||
644 | } |
||
645 | if (allHulls == null) |
||
646 | { |
||
647 | physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,assetHulls,noAssetHull", prim.LocalID); |
||
648 | } |
||
649 | } |
||
650 | } |
||
651 | |||
652 | // If there is hull data in the mesh asset, build the hull from that |
||
653 | if (allHulls != null && BSParam.ShouldUseAssetHulls) |
||
654 | { |
||
655 | int hullCount = allHulls.Count; |
||
656 | int totalVertices = 1; // include one for the count of the hulls |
||
657 | // Using the structure described for HACD hulls, create the memory sturcture |
||
658 | // to pass the hull data to the creater. |
||
659 | foreach (List<OMV.Vector3> hullVerts in allHulls) |
||
660 | { |
||
661 | totalVertices += 4; // add four for the vertex count and centroid |
||
662 | totalVertices += hullVerts.Count * 3; // one vertex is three dimensions |
||
663 | } |
||
664 | float[] convHulls = new float[totalVertices]; |
||
665 | |||
666 | convHulls[0] = (float)hullCount; |
||
667 | int jj = 1; |
||
668 | foreach (List<OMV.Vector3> hullVerts in allHulls) |
||
669 | { |
||
670 | convHulls[jj + 0] = hullVerts.Count; |
||
671 | convHulls[jj + 1] = 0f; // centroid x,y,z |
||
672 | convHulls[jj + 2] = 0f; |
||
673 | convHulls[jj + 3] = 0f; |
||
674 | jj += 4; |
||
675 | foreach (OMV.Vector3 oneVert in hullVerts) |
||
676 | { |
||
677 | convHulls[jj + 0] = oneVert.X; |
||
678 | convHulls[jj + 1] = oneVert.Y; |
||
679 | convHulls[jj + 2] = oneVert.Z; |
||
680 | jj += 3; |
||
681 | } |
||
682 | } |
||
683 | |||
684 | // create the hull data structure in Bullet |
||
685 | newShape = physicsScene.PE.CreateHullShape(physicsScene.World, hullCount, convHulls); |
||
686 | |||
687 | physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,assetHulls,hulls={1},totVert={2},shape={3}", |
||
688 | prim.LocalID, hullCount, totalVertices, newShape); |
||
689 | } |
||
690 | |||
691 | // If no hull specified in the asset and we should use Bullet's HACD approximation... |
||
692 | if (!newShape.HasPhysicalShape && BSParam.ShouldUseBulletHACD) |
||
693 | { |
||
694 | // Build the hull shape from an existing mesh shape. |
||
695 | // The mesh should have already been created in Bullet. |
||
696 | physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,bulletHACD,entry", prim.LocalID); |
||
697 | BSShape meshShape = BSShapeMesh.GetReference(physicsScene, true, prim); |
||
698 | |||
699 | if (meshShape.physShapeInfo.HasPhysicalShape) |
||
700 | { |
||
701 | HACDParams parms; |
||
702 | parms.maxVerticesPerHull = BSParam.BHullMaxVerticesPerHull; |
||
703 | parms.minClusters = BSParam.BHullMinClusters; |
||
704 | parms.compacityWeight = BSParam.BHullCompacityWeight; |
||
705 | parms.volumeWeight = BSParam.BHullVolumeWeight; |
||
706 | parms.concavity = BSParam.BHullConcavity; |
||
707 | parms.addExtraDistPoints = BSParam.NumericBool(BSParam.BHullAddExtraDistPoints); |
||
708 | parms.addNeighboursDistPoints = BSParam.NumericBool(BSParam.BHullAddNeighboursDistPoints); |
||
709 | parms.addFacesPoints = BSParam.NumericBool(BSParam.BHullAddFacesPoints); |
||
710 | parms.shouldAdjustCollisionMargin = BSParam.NumericBool(BSParam.BHullShouldAdjustCollisionMargin); |
||
711 | |||
712 | physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,hullFromMesh,beforeCall", prim.LocalID, newShape.HasPhysicalShape); |
||
713 | newShape = physicsScene.PE.BuildHullShapeFromMesh(physicsScene.World, meshShape.physShapeInfo, parms); |
||
714 | physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,hullFromMesh,shape={1}", prim.LocalID, newShape); |
||
715 | |||
716 | // Now done with the mesh shape. |
||
717 | meshShape.Dereference(physicsScene); |
||
718 | } |
||
719 | physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,bulletHACD,exit,hasBody={1}", prim.LocalID, newShape.HasPhysicalShape); |
||
720 | } |
||
721 | |||
722 | // If no other hull specifications, use our HACD hull approximation. |
||
723 | if (!newShape.HasPhysicalShape && meshData != null) |
||
724 | { |
||
725 | if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Fetched) |
||
726 | { |
||
727 | // Release the fetched asset data once it has been used. |
||
728 | pbs.SculptData = new byte[0]; |
||
729 | prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Unknown; |
||
730 | } |
||
731 | |||
732 | int[] indices = meshData.getIndexListAsInt(); |
||
733 | List<OMV.Vector3> vertices = meshData.getVertexList(); |
||
734 | |||
735 | //format conversion from IMesh format to DecompDesc format |
||
736 | List<int> convIndices = new List<int>(); |
||
737 | List<float3> convVertices = new List<float3>(); |
||
738 | for (int ii = 0; ii < indices.GetLength(0); ii++) |
||
739 | { |
||
740 | convIndices.Add(indices[ii]); |
||
741 | } |
||
742 | foreach (OMV.Vector3 vv in vertices) |
||
743 | { |
||
744 | convVertices.Add(new float3(vv.X, vv.Y, vv.Z)); |
||
745 | } |
||
746 | |||
747 | uint maxDepthSplit = (uint)BSParam.CSHullMaxDepthSplit; |
||
748 | if (BSParam.CSHullMaxDepthSplit != BSParam.CSHullMaxDepthSplitForSimpleShapes) |
||
749 | { |
||
750 | // Simple primitive shapes we know are convex so they are better implemented with |
||
751 | // fewer hulls. |
||
752 | // Check for simple shape (prim without cuts) and reduce split parameter if so. |
||
753 | if (BSShapeCollection.PrimHasNoCuts(pbs)) |
||
754 | { |
||
755 | maxDepthSplit = (uint)BSParam.CSHullMaxDepthSplitForSimpleShapes; |
||
756 | } |
||
757 | } |
||
758 | |||
759 | // setup and do convex hull conversion |
||
760 | m_hulls = new List<ConvexResult>(); |
||
761 | DecompDesc dcomp = new DecompDesc(); |
||
762 | dcomp.mIndices = convIndices; |
||
763 | dcomp.mVertices = convVertices; |
||
764 | dcomp.mDepth = maxDepthSplit; |
||
765 | dcomp.mCpercent = BSParam.CSHullConcavityThresholdPercent; |
||
766 | dcomp.mPpercent = BSParam.CSHullVolumeConservationThresholdPercent; |
||
767 | dcomp.mMaxVertices = (uint)BSParam.CSHullMaxVertices; |
||
768 | dcomp.mSkinWidth = BSParam.CSHullMaxSkinWidth; |
||
769 | ConvexBuilder convexBuilder = new ConvexBuilder(HullReturn); |
||
770 | // create the hull into the _hulls variable |
||
771 | convexBuilder.process(dcomp); |
||
772 | |||
773 | physicsScene.DetailLog("{0},BSShapeCollection.CreatePhysicalHull,key={1},inVert={2},inInd={3},split={4},hulls={5}", |
||
774 | BSScene.DetailLogZero, newHullKey, indices.GetLength(0), vertices.Count, maxDepthSplit, m_hulls.Count); |
||
775 | |||
776 | // Convert the vertices and indices for passing to unmanaged. |
||
777 | // The hull information is passed as a large floating point array. |
||
778 | // The format is: |
||
779 | // convHulls[0] = number of hulls |
||
780 | // convHulls[1] = number of vertices in first hull |
||
781 | // convHulls[2] = hull centroid X coordinate |
||
782 | // convHulls[3] = hull centroid Y coordinate |
||
783 | // convHulls[4] = hull centroid Z coordinate |
||
784 | // convHulls[5] = first hull vertex X |
||
785 | // convHulls[6] = first hull vertex Y |
||
786 | // convHulls[7] = first hull vertex Z |
||
787 | // convHulls[8] = second hull vertex X |
||
788 | // ... |
||
789 | // convHulls[n] = number of vertices in second hull |
||
790 | // convHulls[n+1] = second hull centroid X coordinate |
||
791 | // ... |
||
792 | // |
||
793 | // TODO: is is very inefficient. Someday change the convex hull generator to return |
||
794 | // data structures that do not need to be converted in order to pass to Bullet. |
||
795 | // And maybe put the values directly into pinned memory rather than marshaling. |
||
796 | int hullCount = m_hulls.Count; |
||
797 | int totalVertices = 1; // include one for the count of the hulls |
||
798 | foreach (ConvexResult cr in m_hulls) |
||
799 | { |
||
800 | totalVertices += 4; // add four for the vertex count and centroid |
||
801 | totalVertices += cr.HullIndices.Count * 3; // we pass just triangles |
||
802 | } |
||
803 | float[] convHulls = new float[totalVertices]; |
||
804 | |||
805 | convHulls[0] = (float)hullCount; |
||
806 | int jj = 1; |
||
807 | foreach (ConvexResult cr in m_hulls) |
||
808 | { |
||
809 | // copy vertices for index access |
||
810 | float3[] verts = new float3[cr.HullVertices.Count]; |
||
811 | int kk = 0; |
||
812 | foreach (float3 ff in cr.HullVertices) |
||
813 | { |
||
814 | verts[kk++] = ff; |
||
815 | } |
||
816 | |||
817 | // add to the array one hull's worth of data |
||
818 | convHulls[jj++] = cr.HullIndices.Count; |
||
819 | convHulls[jj++] = 0f; // centroid x,y,z |
||
820 | convHulls[jj++] = 0f; |
||
821 | convHulls[jj++] = 0f; |
||
822 | foreach (int ind in cr.HullIndices) |
||
823 | { |
||
824 | convHulls[jj++] = verts[ind].x; |
||
825 | convHulls[jj++] = verts[ind].y; |
||
826 | convHulls[jj++] = verts[ind].z; |
||
827 | } |
||
828 | } |
||
829 | // create the hull data structure in Bullet |
||
830 | newShape = physicsScene.PE.CreateHullShape(physicsScene.World, hullCount, convHulls); |
||
831 | } |
||
832 | newShape.shapeKey = newHullKey; |
||
833 | return newShape; |
||
834 | } |
||
835 | // Callback from convex hull creater with a newly created hull. |
||
836 | // Just add it to our collection of hulls for this shape. |
||
837 | private void HullReturn(ConvexResult result) |
||
838 | { |
||
839 | m_hulls.Add(result); |
||
840 | return; |
||
841 | } |
||
842 | // Loop through all the known hulls and return the description based on the physical address. |
||
843 | public static bool TryGetHullByPtr(BulletShape pShape, out BSShapeHull outHull) |
||
844 | { |
||
845 | bool ret = false; |
||
846 | BSShapeHull foundDesc = null; |
||
847 | lock (Hulls) |
||
848 | { |
||
849 | foreach (BSShapeHull sh in Hulls.Values) |
||
850 | { |
||
851 | if (sh.physShapeInfo.ReferenceSame(pShape)) |
||
852 | { |
||
853 | foundDesc = sh; |
||
854 | ret = true; |
||
855 | break; |
||
856 | } |
||
857 | |||
858 | } |
||
859 | } |
||
860 | outHull = foundDesc; |
||
861 | return ret; |
||
862 | } |
||
863 | } |
||
864 | |||
865 | // ============================================================================================================ |
||
866 | public class BSShapeCompound : BSShape |
||
867 | { |
||
868 | private static string LogHeader = "[BULLETSIM SHAPE COMPOUND]"; |
||
869 | public static Dictionary<string, BSShapeCompound> CompoundShapes = new Dictionary<string, BSShapeCompound>(); |
||
870 | |||
871 | public BSShapeCompound(BulletShape pShape) : base(pShape) |
||
872 | { |
||
873 | } |
||
874 | public static BSShape GetReference(BSScene physicsScene) |
||
875 | { |
||
876 | // Base compound shapes are not shared so this returns a raw shape. |
||
877 | // A built compound shape can be reused in linksets. |
||
878 | BSShapeCompound ret = new BSShapeCompound(CreatePhysicalCompoundShape(physicsScene)); |
||
879 | CompoundShapes.Add(ret.AddrString, ret); |
||
880 | return ret; |
||
881 | } |
||
882 | public override BSShape GetReference(BSScene physicsScene, BSPhysObject prim) |
||
883 | { |
||
884 | // Calling this reference means we want another handle to an existing compound shape |
||
885 | // (usually linksets) so return this copy. |
||
886 | IncrementReference(); |
||
887 | return this; |
||
888 | } |
||
889 | // Dereferencing a compound shape releases the hold on all the child shapes. |
||
890 | public override void Dereference(BSScene physicsScene) |
||
891 | { |
||
892 | lock (physShapeInfo) |
||
893 | { |
||
894 | this.DecrementReference(); |
||
895 | physicsScene.DetailLog("{0},BSShapeCompound.Dereference,shape={1}", BSScene.DetailLogZero, this); |
||
896 | if (referenceCount <= 0) |
||
897 | { |
||
898 | if (!physicsScene.PE.IsCompound(physShapeInfo)) |
||
899 | { |
||
900 | // Failed the sanity check!! |
||
901 | physicsScene.Logger.ErrorFormat("{0} Attempt to free a compound shape that is not compound!! type={1}, ptr={2}", |
||
902 | LogHeader, physShapeInfo.shapeType, physShapeInfo.AddrString); |
||
903 | physicsScene.DetailLog("{0},BSShapeCollection.DereferenceCompound,notACompoundShape,type={1},ptr={2}", |
||
904 | BSScene.DetailLogZero, physShapeInfo.shapeType, physShapeInfo.AddrString); |
||
905 | return; |
||
906 | } |
||
907 | |||
908 | int numChildren = physicsScene.PE.GetNumberOfCompoundChildren(physShapeInfo); |
||
909 | physicsScene.DetailLog("{0},BSShapeCollection.DereferenceCompound,shape={1},children={2}", |
||
910 | BSScene.DetailLogZero, physShapeInfo, numChildren); |
||
911 | |||
912 | // Loop through all the children dereferencing each. |
||
913 | for (int ii = numChildren - 1; ii >= 0; ii--) |
||
914 | { |
||
915 | BulletShape childShape = physicsScene.PE.RemoveChildShapeFromCompoundShapeIndex(physShapeInfo, ii); |
||
916 | DereferenceAnonCollisionShape(physicsScene, childShape); |
||
917 | } |
||
918 | |||
919 | lock (CompoundShapes) |
||
920 | CompoundShapes.Remove(physShapeInfo.AddrString); |
||
921 | physicsScene.PE.DeleteCollisionShape(physicsScene.World, physShapeInfo); |
||
922 | } |
||
923 | } |
||
924 | } |
||
925 | public static bool TryGetCompoundByPtr(BulletShape pShape, out BSShapeCompound outCompound) |
||
926 | { |
||
927 | lock (CompoundShapes) |
||
928 | { |
||
929 | string addr = pShape.AddrString; |
||
930 | return CompoundShapes.TryGetValue(addr, out outCompound); |
||
931 | } |
||
932 | } |
||
933 | private static BulletShape CreatePhysicalCompoundShape(BSScene physicsScene) |
||
934 | { |
||
935 | BulletShape cShape = physicsScene.PE.CreateCompoundShape(physicsScene.World, false); |
||
936 | return cShape; |
||
937 | } |
||
938 | // Sometimes we have a pointer to a collision shape but don't know what type it is. |
||
939 | // Figure out type and call the correct dereference routine. |
||
940 | // Called at taint-time. |
||
941 | private void DereferenceAnonCollisionShape(BSScene physicsScene, BulletShape pShape) |
||
942 | { |
||
943 | // TODO: figure a better way to go through all the shape types and find a possible instance. |
||
944 | physicsScene.DetailLog("{0},BSShapeCompound.DereferenceAnonCollisionShape,shape={1}", |
||
945 | BSScene.DetailLogZero, pShape); |
||
946 | BSShapeMesh meshDesc; |
||
947 | if (BSShapeMesh.TryGetMeshByPtr(pShape, out meshDesc)) |
||
948 | { |
||
949 | meshDesc.Dereference(physicsScene); |
||
950 | // physicsScene.DetailLog("{0},BSShapeCompound.DereferenceAnonCollisionShape,foundMesh,shape={1}", BSScene.DetailLogZero, pShape); |
||
951 | } |
||
952 | else |
||
953 | { |
||
954 | BSShapeHull hullDesc; |
||
955 | if (BSShapeHull.TryGetHullByPtr(pShape, out hullDesc)) |
||
956 | { |
||
957 | hullDesc.Dereference(physicsScene); |
||
958 | // physicsScene.DetailLog("{0},BSShapeCompound.DereferenceAnonCollisionShape,foundHull,shape={1}", BSScene.DetailLogZero, pShape); |
||
959 | } |
||
960 | else |
||
961 | { |
||
962 | BSShapeConvexHull chullDesc; |
||
963 | if (BSShapeConvexHull.TryGetConvexHullByPtr(pShape, out chullDesc)) |
||
964 | { |
||
965 | chullDesc.Dereference(physicsScene); |
||
966 | // physicsScene.DetailLog("{0},BSShapeCompound.DereferenceAnonCollisionShape,foundConvexHull,shape={1}", BSScene.DetailLogZero, pShape); |
||
967 | } |
||
968 | else |
||
969 | { |
||
970 | BSShapeGImpact gImpactDesc; |
||
971 | if (BSShapeGImpact.TryGetGImpactByPtr(pShape, out gImpactDesc)) |
||
972 | { |
||
973 | gImpactDesc.Dereference(physicsScene); |
||
974 | // physicsScene.DetailLog("{0},BSShapeCompound.DereferenceAnonCollisionShape,foundgImpact,shape={1}", BSScene.DetailLogZero, pShape); |
||
975 | } |
||
976 | else |
||
977 | { |
||
978 | // Didn't find it in the lists of specific types. It could be compound. |
||
979 | BSShapeCompound compoundDesc; |
||
980 | if (BSShapeCompound.TryGetCompoundByPtr(pShape, out compoundDesc)) |
||
981 | { |
||
982 | compoundDesc.Dereference(physicsScene); |
||
983 | // physicsScene.DetailLog("{0},BSShapeCompound.DereferenceAnonCollisionShape,recursiveCompoundShape,shape={1}", BSScene.DetailLogZero, pShape); |
||
984 | } |
||
985 | else |
||
986 | { |
||
987 | // If none of the above, maybe it is a simple native shape. |
||
988 | if (physicsScene.PE.IsNativeShape(pShape)) |
||
989 | { |
||
990 | // physicsScene.DetailLog("{0},BSShapeCompound.DereferenceAnonCollisionShape,assumingNative,shape={1}", BSScene.DetailLogZero, pShape); |
||
991 | BSShapeNative nativeShape = new BSShapeNative(pShape); |
||
992 | nativeShape.Dereference(physicsScene); |
||
993 | } |
||
994 | } |
||
995 | } |
||
996 | } |
||
997 | } |
||
998 | } |
||
999 | } |
||
1000 | } |
||
1001 | |||
1002 | // ============================================================================================================ |
||
1003 | public class BSShapeConvexHull : BSShape |
||
1004 | { |
||
1005 | private static string LogHeader = "[BULLETSIM SHAPE CONVEX HULL]"; |
||
1006 | public static Dictionary<System.UInt64, BSShapeConvexHull> ConvexHulls = new Dictionary<System.UInt64, BSShapeConvexHull>(); |
||
1007 | |||
1008 | public BSShapeConvexHull(BulletShape pShape) : base(pShape) |
||
1009 | { |
||
1010 | } |
||
1011 | public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) |
||
1012 | { |
||
1013 | float lod; |
||
1014 | System.UInt64 newMeshKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod); |
||
1015 | |||
1016 | physicsScene.DetailLog("{0},BSShapeConvexHull,getReference,newKey={1},size={2},lod={3}", |
||
1017 | prim.LocalID, newMeshKey.ToString("X"), prim.Size, lod); |
||
1018 | |||
1019 | BSShapeConvexHull retConvexHull = null; |
||
1020 | lock (ConvexHulls) |
||
1021 | { |
||
1022 | if (ConvexHulls.TryGetValue(newMeshKey, out retConvexHull)) |
||
1023 | { |
||
1024 | // The mesh has already been created. Return a new reference to same. |
||
1025 | retConvexHull.IncrementReference(); |
||
1026 | } |
||
1027 | else |
||
1028 | { |
||
1029 | retConvexHull = new BSShapeConvexHull(new BulletShape()); |
||
1030 | BulletShape convexShape = null; |
||
1031 | |||
1032 | // Get a handle to a mesh to build the hull from |
||
1033 | BSShape baseMesh = BSShapeMesh.GetReference(physicsScene, false /* forceRebuild */, prim); |
||
1034 | if (baseMesh.physShapeInfo.isNativeShape) |
||
1035 | { |
||
1036 | // We get here if the mesh was not creatable. Could be waiting for an asset from the disk. |
||
1037 | // In the short term, we return the native shape and a later ForceBodyShapeRebuild should |
||
1038 | // get back to this code with a buildable mesh. |
||
1039 | // TODO: not sure the temp native shape is freed when the mesh is rebuilt. When does this get freed? |
||
1040 | convexShape = baseMesh.physShapeInfo; |
||
1041 | } |
||
1042 | else |
||
1043 | { |
||
1044 | convexShape = physicsScene.PE.BuildConvexHullShapeFromMesh(physicsScene.World, baseMesh.physShapeInfo); |
||
1045 | convexShape.shapeKey = newMeshKey; |
||
1046 | ConvexHulls.Add(convexShape.shapeKey, retConvexHull); |
||
1047 | physicsScene.DetailLog("{0},BSShapeConvexHull.GetReference,addingNewlyCreatedShape,shape={1}", |
||
1048 | BSScene.DetailLogZero, convexShape); |
||
1049 | } |
||
1050 | |||
1051 | // Done with the base mesh |
||
1052 | baseMesh.Dereference(physicsScene); |
||
1053 | |||
1054 | retConvexHull.physShapeInfo = convexShape; |
||
1055 | } |
||
1056 | } |
||
1057 | return retConvexHull; |
||
1058 | } |
||
1059 | public override BSShape GetReference(BSScene physicsScene, BSPhysObject prim) |
||
1060 | { |
||
1061 | // Calling this reference means we want another handle to an existing shape |
||
1062 | // (usually linksets) so return this copy. |
||
1063 | IncrementReference(); |
||
1064 | return this; |
||
1065 | } |
||
1066 | // Dereferencing a compound shape releases the hold on all the child shapes. |
||
1067 | public override void Dereference(BSScene physicsScene) |
||
1068 | { |
||
1069 | lock (ConvexHulls) |
||
1070 | { |
||
1071 | this.DecrementReference(); |
||
1072 | physicsScene.DetailLog("{0},BSShapeConvexHull.Dereference,shape={1}", BSScene.DetailLogZero, this); |
||
1073 | // TODO: schedule aging and destruction of unused meshes. |
||
1074 | } |
||
1075 | } |
||
1076 | // Loop through all the known hulls and return the description based on the physical address. |
||
1077 | public static bool TryGetConvexHullByPtr(BulletShape pShape, out BSShapeConvexHull outHull) |
||
1078 | { |
||
1079 | bool ret = false; |
||
1080 | BSShapeConvexHull foundDesc = null; |
||
1081 | lock (ConvexHulls) |
||
1082 | { |
||
1083 | foreach (BSShapeConvexHull sh in ConvexHulls.Values) |
||
1084 | { |
||
1085 | if (sh.physShapeInfo.ReferenceSame(pShape)) |
||
1086 | { |
||
1087 | foundDesc = sh; |
||
1088 | ret = true; |
||
1089 | break; |
||
1090 | } |
||
1091 | |||
1092 | } |
||
1093 | } |
||
1094 | outHull = foundDesc; |
||
1095 | return ret; |
||
1096 | } |
||
1097 | } |
||
1098 | // ============================================================================================================ |
||
1099 | public class BSShapeGImpact : BSShape |
||
1100 | { |
||
1101 | private static string LogHeader = "[BULLETSIM SHAPE GIMPACT]"; |
||
1102 | public static Dictionary<System.UInt64, BSShapeGImpact> GImpacts = new Dictionary<System.UInt64, BSShapeGImpact>(); |
||
1103 | |||
1104 | public BSShapeGImpact(BulletShape pShape) : base(pShape) |
||
1105 | { |
||
1106 | } |
||
1107 | public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) |
||
1108 | { |
||
1109 | float lod; |
||
1110 | System.UInt64 newMeshKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod); |
||
1111 | |||
1112 | physicsScene.DetailLog("{0},BSShapeGImpact,getReference,newKey={1},size={2},lod={3}", |
||
1113 | prim.LocalID, newMeshKey.ToString("X"), prim.Size, lod); |
||
1114 | |||
1115 | BSShapeGImpact retGImpact = null; |
||
1116 | lock (GImpacts) |
||
1117 | { |
||
1118 | if (GImpacts.TryGetValue(newMeshKey, out retGImpact)) |
||
1119 | { |
||
1120 | // The mesh has already been created. Return a new reference to same. |
||
1121 | retGImpact.IncrementReference(); |
||
1122 | } |
||
1123 | else |
||
1124 | { |
||
1125 | retGImpact = new BSShapeGImpact(new BulletShape()); |
||
1126 | BulletShape newShape = retGImpact.CreatePhysicalGImpact(physicsScene, prim, newMeshKey, prim.BaseShape, prim.Size, lod); |
||
1127 | |||
1128 | // Check to see if mesh was created (might require an asset). |
||
1129 | newShape = VerifyMeshCreated(physicsScene, newShape, prim); |
||
1130 | newShape.shapeKey = newMeshKey; |
||
1131 | if (!newShape.isNativeShape |
||
1132 | || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedMeshing |
||
1133 | || prim.PrimAssetState == BSPhysObject.PrimAssetCondition.FailedAssetFetch) |
||
1134 | { |
||
1135 | // If a mesh was what was created, remember the built shape for later sharing. |
||
1136 | // Also note that if meshing failed we put it in the mesh list as there is nothing |
||
1137 | // else to do about the mesh. |
||
1138 | GImpacts.Add(newMeshKey, retGImpact); |
||
1139 | } |
||
1140 | |||
1141 | retGImpact.physShapeInfo = newShape; |
||
1142 | } |
||
1143 | } |
||
1144 | return retGImpact; |
||
1145 | } |
||
1146 | |||
1147 | private BulletShape CreatePhysicalGImpact(BSScene physicsScene, BSPhysObject prim, System.UInt64 newMeshKey, |
||
1148 | PrimitiveBaseShape pbs, OMV.Vector3 size, float lod) |
||
1149 | { |
||
1150 | return BSShapeMesh.CreatePhysicalMeshShape(physicsScene, prim, newMeshKey, pbs, size, lod, |
||
1151 | (w, iC, i, vC, v) => physicsScene.PE.CreateGImpactShape(w, iC, i, vC, v) ); |
||
1152 | } |
||
1153 | |||
1154 | public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) |
||
1155 | { |
||
1156 | BSShape ret = null; |
||
1157 | // If the underlying shape is native, the actual shape has not been build (waiting for asset) |
||
1158 | // and we must create a copy of the native shape since they are never shared. |
||
1159 | if (physShapeInfo.HasPhysicalShape && physShapeInfo.isNativeShape) |
||
1160 | { |
||
1161 | // TODO: decide when the native shapes should be freed. Check in Dereference? |
||
1162 | ret = BSShapeNative.GetReference(pPhysicsScene, pPrim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX); |
||
1163 | } |
||
1164 | else |
||
1165 | { |
||
1166 | // Another reference to this shape is just counted. |
||
1167 | IncrementReference(); |
||
1168 | ret = this; |
||
1169 | } |
||
1170 | return ret; |
||
1171 | } |
||
1172 | // Dereferencing a compound shape releases the hold on all the child shapes. |
||
1173 | public override void Dereference(BSScene physicsScene) |
||
1174 | { |
||
1175 | lock (GImpacts) |
||
1176 | { |
||
1177 | this.DecrementReference(); |
||
1178 | physicsScene.DetailLog("{0},BSShapeGImpact.Dereference,shape={1}", BSScene.DetailLogZero, this); |
||
1179 | // TODO: schedule aging and destruction of unused meshes. |
||
1180 | } |
||
1181 | } |
||
1182 | // Loop through all the known hulls and return the description based on the physical address. |
||
1183 | public static bool TryGetGImpactByPtr(BulletShape pShape, out BSShapeGImpact outHull) |
||
1184 | { |
||
1185 | bool ret = false; |
||
1186 | BSShapeGImpact foundDesc = null; |
||
1187 | lock (GImpacts) |
||
1188 | { |
||
1189 | foreach (BSShapeGImpact sh in GImpacts.Values) |
||
1190 | { |
||
1191 | if (sh.physShapeInfo.ReferenceSame(pShape)) |
||
1192 | { |
||
1193 | foundDesc = sh; |
||
1194 | ret = true; |
||
1195 | break; |
||
1196 | } |
||
1197 | |||
1198 | } |
||
1199 | } |
||
1200 | outHull = foundDesc; |
||
1201 | return ret; |
||
1202 | } |
||
1203 | } |
||
1204 | |||
1205 | // ============================================================================================================ |
||
1206 | public class BSShapeAvatar : BSShape |
||
1207 | { |
||
1208 | private static string LogHeader = "[BULLETSIM SHAPE AVATAR]"; |
||
1209 | public BSShapeAvatar() : base() |
||
1210 | { |
||
1211 | } |
||
1212 | public static BSShape GetReference(BSPhysObject prim) |
||
1213 | { |
||
1214 | return new BSShapeNull(); |
||
1215 | } |
||
1216 | public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) |
||
1217 | { |
||
1218 | return new BSShapeNull(); |
||
1219 | } |
||
1220 | public override void Dereference(BSScene physicsScene) { } |
||
1221 | |||
1222 | // From the front: |
||
1223 | // A---A |
||
1224 | // / \ |
||
1225 | // B-------B |
||
1226 | // / \ +Z |
||
1227 | // C-----------C | |
||
1228 | // \ / -Y --+-- +Y |
||
1229 | // \ / | |
||
1230 | // \ / -Z |
||
1231 | // D-----D |
||
1232 | // \ / |
||
1233 | // E-E |
||
1234 | |||
1235 | // From the top A and E are just lines. |
||
1236 | // B, C and D are hexagons: |
||
1237 | // |
||
1238 | // C1--C2 +X |
||
1239 | // / \ | |
||
1240 | // C0 C3 -Y --+-- +Y |
||
1241 | // \ / | |
||
1242 | // C5--C4 -X |
||
1243 | |||
1244 | // Zero goes directly through the middle so the offsets are from that middle axis |
||
1245 | // and up and down from a middle horizon (A and E are the same distance from the zero). |
||
1246 | // The height, width and depth is one. All scaling is done by the simulator. |
||
1247 | |||
1248 | // Z component -- how far the level is from the middle zero |
||
1249 | private const float Aup = 0.5f; |
||
1250 | private const float Bup = 0.4f; |
||
1251 | private const float Cup = 0.3f; |
||
1252 | private const float Dup = -0.4f; |
||
1253 | private const float Eup = -0.5f; |
||
1254 | |||
1255 | // Y component -- distance from center to x0 and x3 |
||
1256 | private const float Awid = 0.25f; |
||
1257 | private const float Bwid = 0.3f; |
||
1258 | private const float Cwid = 0.5f; |
||
1259 | private const float Dwid = 0.3f; |
||
1260 | private const float Ewid = 0.2f; |
||
1261 | |||
1262 | // Y component -- distance from center to x1, x2, x4 and x5 |
||
1263 | private const float Afwid = 0.0f; |
||
1264 | private const float Bfwid = 0.2f; |
||
1265 | private const float Cfwid = 0.4f; |
||
1266 | private const float Dfwid = 0.2f; |
||
1267 | private const float Efwid = 0.0f; |
||
1268 | |||
1269 | // X component -- distance from zero to the front or back of a level |
||
1270 | private const float Adep = 0f; |
||
1271 | private const float Bdep = 0.3f; |
||
1272 | private const float Cdep = 0.5f; |
||
1273 | private const float Ddep = 0.2f; |
||
1274 | private const float Edep = 0f; |
||
1275 | |||
1276 | private OMV.Vector3[] avatarVertices = { |
||
1277 | new OMV.Vector3( 0.0f, -Awid, Aup), // A0 |
||
1278 | new OMV.Vector3( 0.0f, +Awid, Aup), // A3 |
||
1279 | |||
1280 | new OMV.Vector3( 0.0f, -Bwid, Bup), // B0 |
||
1281 | new OMV.Vector3(+Bdep, -Bfwid, Bup), // B1 |
||
1282 | new OMV.Vector3(+Bdep, +Bfwid, Bup), // B2 |
||
1283 | new OMV.Vector3( 0.0f, +Bwid, Bup), // B3 |
||
1284 | new OMV.Vector3(-Bdep, +Bfwid, Bup), // B4 |
||
1285 | new OMV.Vector3(-Bdep, -Bfwid, Bup), // B5 |
||
1286 | |||
1287 | new OMV.Vector3( 0.0f, -Cwid, Cup), // C0 |
||
1288 | new OMV.Vector3(+Cdep, -Cfwid, Cup), // C1 |
||
1289 | new OMV.Vector3(+Cdep, +Cfwid, Cup), // C2 |
||
1290 | new OMV.Vector3( 0.0f, +Cwid, Cup), // C3 |
||
1291 | new OMV.Vector3(-Cdep, +Cfwid, Cup), // C4 |
||
1292 | new OMV.Vector3(-Cdep, -Cfwid, Cup), // C5 |
||
1293 | |||
1294 | new OMV.Vector3( 0.0f, -Dwid, Dup), // D0 |
||
1295 | new OMV.Vector3(+Ddep, -Dfwid, Dup), // D1 |
||
1296 | new OMV.Vector3(+Ddep, +Dfwid, Dup), // D2 |
||
1297 | new OMV.Vector3( 0.0f, +Dwid, Dup), // D3 |
||
1298 | new OMV.Vector3(-Ddep, +Dfwid, Dup), // D4 |
||
1299 | new OMV.Vector3(-Ddep, -Dfwid, Dup), // D5 |
||
1300 | |||
1301 | new OMV.Vector3( 0.0f, -Ewid, Eup), // E0 |
||
1302 | new OMV.Vector3( 0.0f, +Ewid, Eup), // E3 |
||
1303 | }; |
||
1304 | |||
1305 | // Offsets of the vertices in the vertices array |
||
1306 | private enum Ind : int |
||
1307 | { |
||
1308 | A0, A3, |
||
1309 | B0, B1, B2, B3, B4, B5, |
||
1310 | C0, C1, C2, C3, C4, C5, |
||
1311 | D0, D1, D2, D3, D4, D5, |
||
1312 | E0, E3 |
||
1313 | } |
||
1314 | |||
1315 | // Comments specify trianges and quads in clockwise direction |
||
1316 | private Ind[] avatarIndices = { |
||
1317 | Ind.A0, Ind.B0, Ind.B1, // A0,B0,B1 |
||
1318 | Ind.A0, Ind.B1, Ind.B2, Ind.B2, Ind.A3, Ind.A0, // A0,B1,B2,A3 |
||
1319 | Ind.A3, Ind.B2, Ind.B3, // A3,B2,B3 |
||
1320 | Ind.A3, Ind.B3, Ind.B4, // A3,B3,B4 |
||
1321 | Ind.A3, Ind.B4, Ind.B5, Ind.B5, Ind.A0, Ind.A3, // A3,B4,B5,A0 |
||
1322 | Ind.A0, Ind.B5, Ind.B0, // A0,B5,B0 |
||
1323 | |||
1324 | Ind.B0, Ind.C0, Ind.C1, Ind.C1, Ind.B1, Ind.B0, // B0,C0,C1,B1 |
||
1325 | Ind.B1, Ind.C1, Ind.C2, Ind.C2, Ind.B2, Ind.B1, // B1,C1,C2,B2 |
||
1326 | Ind.B2, Ind.C2, Ind.C3, Ind.C3, Ind.B3, Ind.B2, // B2,C2,C3,B3 |
||
1327 | Ind.B3, Ind.C3, Ind.C4, Ind.C4, Ind.B4, Ind.B3, // B3,C3,C4,B4 |
||
1328 | Ind.B4, Ind.C4, Ind.C5, Ind.C5, Ind.B5, Ind.B4, // B4,C4,C5,B5 |
||
1329 | Ind.B5, Ind.C5, Ind.C0, Ind.C0, Ind.B0, Ind.B5, // B5,C5,C0,B0 |
||
1330 | |||
1331 | Ind.C0, Ind.D0, Ind.D1, Ind.D1, Ind.C1, Ind.C0, // C0,D0,D1,C1 |
||
1332 | Ind.C1, Ind.D1, Ind.D2, Ind.D2, Ind.C2, Ind.C1, // C1,D1,D2,C2 |
||
1333 | Ind.C2, Ind.D2, Ind.D3, Ind.D3, Ind.C3, Ind.C2, // C2,D2,D3,C3 |
||
1334 | Ind.C3, Ind.D3, Ind.D4, Ind.D4, Ind.C4, Ind.C3, // C3,D3,D4,C4 |
||
1335 | Ind.C4, Ind.D4, Ind.D5, Ind.D5, Ind.C5, Ind.C4, // C4,D4,D5,C5 |
||
1336 | Ind.C5, Ind.D5, Ind.D0, Ind.D0, Ind.C0, Ind.C5, // C5,D5,D0,C0 |
||
1337 | |||
1338 | Ind.E0, Ind.D0, Ind.D1, // E0,D0,D1 |
||
1339 | Ind.E0, Ind.D1, Ind.D2, Ind.D2, Ind.E3, Ind.E0, // E0,D1,D2,E3 |
||
1340 | Ind.E3, Ind.D2, Ind.D3, // E3,D2,D3 |
||
1341 | Ind.E3, Ind.D3, Ind.D4, // E3,D3,D4 |
||
1342 | Ind.E3, Ind.D4, Ind.D5, Ind.D5, Ind.E0, Ind.E3, // E3,D4,D5,E0 |
||
1343 | Ind.E0, Ind.D5, Ind.D0, // E0,D5,D0 |
||
1344 | |||
1345 | }; |
||
1346 | |||
1347 | } |
||
1348 | } |