// Cut outs a sphere from voxel map (e.g. explosion in a voxel map). We modify only voxels inside the sphere - set them to full or partialy full. // Other voxel are untouched. Sphere coordinates are in world space, not relative to voxel map and is in metres. // // Method returns percent of how much voxels were removed by this explosion (value 0.0 means no voxels; value 1.0 means all voxels) // // IMPORTANT: // This is optimized version that uses cache for accessing voxels. But the cache has limits so it can be used only for not extremely large cut-outs. // Non-optimized version is: CutOutSphere - but it doesn't mean it's slow... the difference is probably just 10-20% // // Returns true if indestructible voxels has been hit (otherwise false) public static bool CutOutSphereFast(MyVoxelMap voxelMap, BoundingSphere explosion, out float voxelsCountInPercent, out MyMwcVoxelMaterialsEnum? voxelMaterial, bool isPlayerExplosion = false, float removeRatio = 1, Dictionary<MyMwcVoxelMaterialsEnum, int> exactCutOutMaterials = null) { explosion.Radius = System.Math.Min(explosion.Radius, MyExplosionsConstants.EXPLOSION_RADIUS_MAX); InvalidateCache = true; voxelMaterial = null; MyMwcVoxelMaterialsEnum newVoxelMaterialTemp; byte tempminContentValueByte; MyMwcVector3Int exactCenterOfExplosion = voxelMap.GetVoxelCoordinateFromMeters(new Vector3(explosion.Center.X, explosion.Center.Y, explosion.Center.Z)); voxelMap.FixVoxelCoord(ref exactCenterOfExplosion); voxelMap.GetMaterialAndIndestructibleContent(ref exactCenterOfExplosion, out newVoxelMaterialTemp, out tempminContentValueByte); if (voxelMaterial == null) voxelMaterial = newVoxelMaterialTemp; // We need to replace this only once int originalVoxelContentsSum = 0; // Sum of all voxel contents affected by this explosion before we extract any voxel. This value is increasing per every voxel, no matter if we realy extract it. int removedVoxelContentsSum = 0; // Sum of all voxel contents we removed by this explosion. This value increases only if we extract something from voxel map. // Get min corner of the explosion MyMwcVector3Int minCorner = voxelMap.GetVoxelCoordinateFromMeters(new Vector3( explosion.Center.X - explosion.Radius - MyVoxelConstants.VOXEL_SIZE_IN_METRES, explosion.Center.Y - explosion.Radius - MyVoxelConstants.VOXEL_SIZE_IN_METRES, explosion.Center.Z - explosion.Radius - MyVoxelConstants.VOXEL_SIZE_IN_METRES)); // Get max corner of the explosion MyMwcVector3Int maxCorner = voxelMap.GetVoxelCoordinateFromMeters(new Vector3( explosion.Center.X + explosion.Radius + MyVoxelConstants.VOXEL_SIZE_IN_METRES, explosion.Center.Y + explosion.Radius + MyVoxelConstants.VOXEL_SIZE_IN_METRES, explosion.Center.Z + explosion.Radius + MyVoxelConstants.VOXEL_SIZE_IN_METRES)); voxelMap.FixVoxelCoord(ref minCorner); voxelMap.FixVoxelCoord(ref maxCorner); MyMwcVector3Int cachedSubtract; cachedSubtract.X = minCorner.X - CACHED_BOUNDARY_IN_VOXELS; cachedSubtract.Y = minCorner.Y - CACHED_BOUNDARY_IN_VOXELS; cachedSubtract.Z = minCorner.Z - CACHED_BOUNDARY_IN_VOXELS; // We are tracking which voxels were changed, so we can invalidate only needed cells in the cache MyMwcVector3Int minChanged = maxCorner; MyMwcVector3Int maxChanged = minCorner; MyMwcVector3Int tempVoxelCoord; bool indestructible = true; for (tempVoxelCoord.X = minCorner.X; tempVoxelCoord.X <= maxCorner.X; tempVoxelCoord.X++) { for (tempVoxelCoord.Y = minCorner.Y; tempVoxelCoord.Y <= maxCorner.Y; tempVoxelCoord.Y++) { for (tempVoxelCoord.Z = minCorner.Z; tempVoxelCoord.Z <= maxCorner.Z; tempVoxelCoord.Z++) { Vector3 voxelPosition = voxelMap.GetVoxelCenterPositionAbsolute(ref tempVoxelCoord); float dist = (voxelPosition - explosion.Center).Length(); float diff = dist - explosion.Radius; // This number will tell us how much of this voxel we will remove (can be zero, can be full voxel, or between) int contentToRemove; if (diff > MyVoxelConstants.VOXEL_SIZE_IN_METRES_HALF) { contentToRemove = MyVoxelConstants.VOXEL_CONTENT_EMPTY; } else if (diff < -MyVoxelConstants.VOXEL_SIZE_IN_METRES_HALF) { contentToRemove = MyVoxelConstants.VOXEL_CONTENT_FULL; } else { // This formula will work even if diff is positive or negative contentToRemove = (int)(MyVoxelConstants.VOXEL_ISO_LEVEL - diff / MyVoxelConstants.VOXEL_SIZE_IN_METRES_HALF * MyVoxelConstants.VOXEL_ISO_LEVEL); } contentToRemove = (int)(contentToRemove * removeRatio); // Only if we have to remove something (e.g. voxels that are outside of the radius aren't affected, or voxels that are // in cornes are always out of the radius, even if they are in bounding box) if (contentToRemove > MyVoxelConstants.VOXEL_CONTENT_EMPTY) { byte minContentValueByte; MyMwcVoxelMaterialsEnum voxelMaterialTemp; voxelMap.GetMaterialAndIndestructibleContent(ref tempVoxelCoord, out voxelMaterialTemp, out minContentValueByte); int minContentValue = minContentValueByte; if (minContentValue != MyVoxelConstants.VOXEL_CONTENT_FULL) { indestructible = false; } // Alter only non-empty voxels (because we can't remove empty voxel...) int originalContent = GetCachedVoxelContent(voxelMap, ref tempVoxelCoord, ref cachedSubtract); if (originalContent > minContentValue) { // IMPORTANT: When doing transformations on 'content' value, cast it to int always!!! // It's because you can easily forget that result will be negative and if you put negative into byte, it will // be overflown and you will be surprised by results!! int newVal = originalContent - contentToRemove; if (newVal < minContentValue) newVal = minContentValue; voxelMap.SetVoxelContent((byte)newVal, ref tempVoxelCoord); SetCachedVoxelContent(voxelMap, ref tempVoxelCoord, ref cachedSubtract, (byte)newVal); // Sum of all voxel contents affected by this explosion before we extract any voxel. This value is increasing per every voxel, no matter if we realy extract it. originalVoxelContentsSum += originalContent; // Sum of all voxel contents we removed by this explosion. This value increases only if we extract something from voxel map. removedVoxelContentsSum += originalContent - newVal; if (exactCutOutMaterials != null) { int oldValue = 0; exactCutOutMaterials.TryGetValue(voxelMaterialTemp, out oldValue); exactCutOutMaterials[voxelMaterialTemp] = oldValue + removedVoxelContentsSum; } if (tempVoxelCoord.X < minChanged.X) minChanged.X = tempVoxelCoord.X; if (tempVoxelCoord.Y < minChanged.Y) minChanged.Y = tempVoxelCoord.Y; if (tempVoxelCoord.Z < minChanged.Z) minChanged.Z = tempVoxelCoord.Z; if (tempVoxelCoord.X > maxChanged.X) maxChanged.X = tempVoxelCoord.X; if (tempVoxelCoord.Y > maxChanged.Y) maxChanged.Y = tempVoxelCoord.Y; if (tempVoxelCoord.Z > maxChanged.Z) maxChanged.Z = tempVoxelCoord.Z; } } } } } InvalidateCache = false; if (removedVoxelContentsSum > 0) { // Extend borders for cleaning, so it's one pixel on both sides minChanged.X -= 1; minChanged.Y -= 1; minChanged.Z -= 1; maxChanged.X += 1; maxChanged.Y += 1; maxChanged.Z += 1; voxelMap.FixVoxelCoord(ref minChanged); voxelMap.FixVoxelCoord(ref maxChanged); // Clear all small voxel that may have been created during explosion. They can be created even outside the range of // explosion sphere, e.g. if you have three voxels in a row A, B, C, where A is 255, B is 60, and C is 255. During the // explosion you change C to 0, so now we have 255, 60, 0. Than another explosion that will change A to 0, so we // will have 0, 60, 0. But B was always outside the range of the explosion. So this is why we need to do -1/+1 and remove // B voxels too. RemoveSmallVoxelsUsingChachedVoxels(voxelMap, ref minCorner, ref maxCorner, ref cachedSubtract); // Extend borders for invalidating the cache, so it's one pixel on both sides minChanged.X -= 1; minChanged.Y -= 1; minChanged.Z -= 1; maxChanged.X += 1; maxChanged.Y += 1; maxChanged.Z += 1; voxelMap.FixVoxelCoord(ref minChanged); voxelMap.FixVoxelCoord(ref maxChanged); // Invalidate cache for voxels cells covered by explosion and some boundary voxels too voxelMap.InvalidateCache(minCorner, maxCorner); voxelMap.AddExplosion(explosion); } if (originalVoxelContentsSum > 0f) { voxelsCountInPercent = (float)removedVoxelContentsSum / (float)originalVoxelContentsSum; } else { voxelsCountInPercent = 0f; } return indestructible; }
bool PerformChangeMaterialVoxels(MyVoxelMap voxelMap, MyMwcVector3Int voxelCoord, MyMwcVoxelMaterialsEnum? voxelMaterial, ref bool changed) { MinerWars.AppCode.Game.Render.MyRender.GetRenderProfiler().StartProfilingBlock("PerformChangeMaterialVoxels"); if (voxelMaterial != null) { byte originalContent = voxelMap.GetVoxelContent(ref voxelCoord); if (originalContent == MyVoxelConstants.VOXEL_CONTENT_EMPTY) { // if there are no voxel content then do nothing MinerWars.AppCode.Game.Render.MyRender.GetRenderProfiler().EndProfilingBlock(); return false; } MyMwcVoxelMaterialsEnum originalMaterial; byte originalIndestructibleContent; voxelMap.GetMaterialAndIndestructibleContent(ref voxelCoord, out originalMaterial, out originalIndestructibleContent); if (originalMaterial == voxelMaterial.Value) { // if original material is same as new material then do nothing MinerWars.AppCode.Game.Render.MyRender.GetRenderProfiler().EndProfilingBlock(); return false; } byte indestructibleContent = MyVoxelConstants.VOXEL_CONTENT_EMPTY; voxelMap.SetVoxelMaterialAndIndestructibleContent(voxelMaterial.Value, indestructibleContent, ref voxelCoord); changed = true; MinerWars.AppCode.Game.Render.MyRender.GetRenderProfiler().EndProfilingBlock(); return true; } MinerWars.AppCode.Game.Render.MyRender.GetRenderProfiler().EndProfilingBlock(); return false; }
// This is same as normal CreateSphere method, but cache is invalidated (so that voxel changes be immediately visible) public static void CreateSphereInvalidateCache(MyVoxelMap voxelMap, BoundingSphere sphere, ref bool changed, MyMwcVoxelMaterialsEnum? material) { MinerWars.AppCode.Game.Render.MyRender.GetRenderProfiler().StartProfilingBlock("MyVoxelGenerator.CreateSphereInvalidateCache"); // Get min corner of the explosion MyMwcVector3Int minCorner = voxelMap.GetVoxelCoordinateFromMeters(new Vector3( sphere.Center.X - sphere.Radius - MyVoxelConstants.VOXEL_SIZE_IN_METRES, sphere.Center.Y - sphere.Radius - MyVoxelConstants.VOXEL_SIZE_IN_METRES, sphere.Center.Z - sphere.Radius - MyVoxelConstants.VOXEL_SIZE_IN_METRES)); // Get max corner of the explosion MyMwcVector3Int maxCorner = voxelMap.GetVoxelCoordinateFromMeters(new Vector3( sphere.Center.X + sphere.Radius + MyVoxelConstants.VOXEL_SIZE_IN_METRES, sphere.Center.Y + sphere.Radius + MyVoxelConstants.VOXEL_SIZE_IN_METRES, sphere.Center.Z + sphere.Radius + MyVoxelConstants.VOXEL_SIZE_IN_METRES)); voxelMap.FixVoxelCoord(ref minCorner); voxelMap.FixVoxelCoord(ref maxCorner); // We are tracking which voxels were changed, so we can invalidate only needed cells in the cache MyMwcVector3Int minChanged = maxCorner; MyMwcVector3Int maxChanged = minCorner; bool sphereAdded = false; MyMwcVector3Int tempVoxelCoord; for (tempVoxelCoord.X = minCorner.X; tempVoxelCoord.X <= maxCorner.X; tempVoxelCoord.X++) { for (tempVoxelCoord.Y = minCorner.Y; tempVoxelCoord.Y <= maxCorner.Y; tempVoxelCoord.Y++) { for (tempVoxelCoord.Z = minCorner.Z; tempVoxelCoord.Z <= maxCorner.Z; tempVoxelCoord.Z++) { Vector3 voxelPosition = voxelMap.GetVoxelCenterPositionAbsolute(ref tempVoxelCoord); float dist = (voxelPosition - sphere.Center).Length(); float diff = dist - sphere.Radius; byte newContent; if (diff > MyVoxelConstants.VOXEL_SIZE_IN_METRES_HALF) { newContent = MyVoxelConstants.VOXEL_CONTENT_EMPTY; } else if (diff < -MyVoxelConstants.VOXEL_SIZE_IN_METRES_HALF) { newContent = MyVoxelConstants.VOXEL_CONTENT_FULL; } else { // This formula will work even if diff is positive or negative newContent = (byte)(MyVoxelConstants.VOXEL_ISO_LEVEL - diff / MyVoxelConstants.VOXEL_SIZE_IN_METRES_HALF * MyVoxelConstants.VOXEL_ISO_LEVEL); } byte originalContent = voxelMap.GetVoxelContent(ref tempVoxelCoord); if (newContent > originalContent) { if (material.HasValue) { MyMwcVoxelMaterialsEnum originalMaterial; byte originalIndestructibleContent; voxelMap.GetMaterialAndIndestructibleContent(ref tempVoxelCoord, out originalMaterial, out originalIndestructibleContent); voxelMap.SetVoxelMaterialAndIndestructibleContent(material.Value, originalIndestructibleContent, ref tempVoxelCoord); } changed = true; voxelMap.SetVoxelContent(newContent, ref tempVoxelCoord); sphereAdded = true; if (tempVoxelCoord.X < minChanged.X) minChanged.X = tempVoxelCoord.X; if (tempVoxelCoord.Y < minChanged.Y) minChanged.Y = tempVoxelCoord.Y; if (tempVoxelCoord.Z < minChanged.Z) minChanged.Z = tempVoxelCoord.Z; if (tempVoxelCoord.X > maxChanged.X) maxChanged.X = tempVoxelCoord.X; if (tempVoxelCoord.Y > maxChanged.Y) maxChanged.Y = tempVoxelCoord.Y; if (tempVoxelCoord.Z > maxChanged.Z) maxChanged.Z = tempVoxelCoord.Z; } } } } if (sphereAdded == true) { // Extend borders for cleaning, so it's one pixel on both sides minChanged.X -= 1; minChanged.Y -= 1; minChanged.Z -= 1; maxChanged.X += 1; maxChanged.Y += 1; maxChanged.Z += 1; voxelMap.FixVoxelCoord(ref minChanged); voxelMap.FixVoxelCoord(ref maxChanged); voxelMap.InvalidateCache(minChanged, maxChanged); } MinerWars.AppCode.Game.Render.MyRender.GetRenderProfiler().EndProfilingBlock(); }