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