diff --git a/Assets/Editor/tests/model/csg/CsgCasesTest.cs b/Assets/Editor/tests/model/csg/CsgCasesTest.cs index 2d6c3ed2..cb1085b1 100644 --- a/Assets/Editor/tests/model/csg/CsgCasesTest.cs +++ b/Assets/Editor/tests/model/csg/CsgCasesTest.cs @@ -55,7 +55,7 @@ public void TestOne() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } [Test] @@ -82,7 +82,7 @@ public void TestTwo() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } [Test] @@ -109,7 +109,7 @@ public void TestThree() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } [Test] @@ -136,7 +136,7 @@ public void TestFour() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } [Test] @@ -163,7 +163,7 @@ public void TestFive() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } [Test] @@ -190,7 +190,7 @@ public void TestSix() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } [Test] @@ -216,7 +216,7 @@ public void TestSeven() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } [Test] @@ -242,7 +242,7 @@ public void TestEight() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } [Test] @@ -269,7 +269,7 @@ public void TestNine() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } [Test] @@ -296,7 +296,7 @@ public void TestTen() Model m = new Model(bounds); m.AddMesh(shape1); - NUnit.Framework.Assert.IsTrue(CsgOperations.SubtractMeshFromModel(m, spatialIndex, shape2)); + NUnit.Framework.Assert.IsTrue(CsgOperations.CsgMeshFromModel(m, spatialIndex, shape2)); } } } \ No newline at end of file diff --git a/Assets/Editor/tests/model/csg/CsgOperationsTest.cs b/Assets/Editor/tests/model/csg/CsgOperationsTest.cs index 82141eac..3b0b68ef 100644 --- a/Assets/Editor/tests/model/csg/CsgOperationsTest.cs +++ b/Assets/Editor/tests/model/csg/CsgOperationsTest.cs @@ -56,7 +56,7 @@ public void TestSubtractFromModel() spatialIndex.AddMesh(meshToAdd); // Now subtract a big cube from the model: - bool subtracted = CsgOperations.SubtractMeshFromModel( + bool subtracted = CsgOperations.CsgMeshFromModel( model, spatialIndex, Primitives.AxisAlignedBox(7, Vector3.zero, Vector3.one, 1)); NUnit.Framework.Assert.IsTrue(subtracted); @@ -71,7 +71,7 @@ public void TestSubtractFromModel() NUnit.Framework.Assert.AreEqual(15, model.GetMesh(toIntersect).faceCount); // Subtract away from the scene to make sure the method returns false. - NUnit.Framework.Assert.IsFalse(CsgOperations.SubtractMeshFromModel(model, spatialIndex, + NUnit.Framework.Assert.IsFalse(CsgOperations.CsgMeshFromModel(model, spatialIndex, Primitives.AxisAlignedBox(7, Vector3.one * -3, Vector3.one, 1))); } @@ -276,10 +276,10 @@ public void SubtractCubeWithinCube() MMesh largeCube = Primitives.AxisAlignedBox(1, Vector3.zero, Vector3.one, 1); // Subtracting large cube from small cube should result in empty space. - NUnit.Framework.Assert.IsNull(CsgOperations.Subtract(smallCube, largeCube)); + NUnit.Framework.Assert.IsNull(CsgOperations.DoCsgOperation(smallCube, largeCube)); // Subtracting small cube from large cube should result in just the large cube with an invisible hole. - MMesh results = CsgOperations.Subtract(largeCube, smallCube); + MMesh results = CsgOperations.DoCsgOperation(largeCube, smallCube); NUnit.Framework.Assert.AreEqual(12, results.faceCount); // Mesh should still be valid: @@ -290,7 +290,7 @@ public void SubtractCubeWithinCube() public void SubtractCubeOverlappingCube() { // Two cubes next to each other, overlapping. - MMesh result = CsgOperations.Subtract( + MMesh result = CsgOperations.DoCsgOperation( Primitives.AxisAlignedBox(1, new Vector3(-1, 0, 0), Vector3.one, 1), Primitives.AxisAlignedBox(2, Vector3.zero, Vector3.one, 1)); @@ -301,7 +301,7 @@ public void SubtractCubeOverlappingCube() public void SubtractSphereOverlappingCube() { // A cube and a sphere, overlapping. - MMesh result = CsgOperations.Subtract( + MMesh result = CsgOperations.DoCsgOperation( Primitives.AxisAlignedBox(1, new Vector3(-1, -0.7f, -0.3f), Vector3.one, 2), Primitives.AxisAlignedIcosphere(2, new Vector3(-.2f, 0, 0), Vector3.one, 1)); diff --git a/Assets/Mogwai/Design/Icons/ic_csg_intersect.png b/Assets/Mogwai/Design/Icons/ic_csg_intersect.png new file mode 100644 index 00000000..1ae5b987 Binary files /dev/null and b/Assets/Mogwai/Design/Icons/ic_csg_intersect.png differ diff --git a/Assets/Mogwai/Design/Icons/ic_csg_intersect.png.meta b/Assets/Mogwai/Design/Icons/ic_csg_intersect.png.meta new file mode 100644 index 00000000..5f5077a7 --- /dev/null +++ b/Assets/Mogwai/Design/Icons/ic_csg_intersect.png.meta @@ -0,0 +1,116 @@ +fileFormatVersion: 2 +guid: 7ecba71d95b4629498c43c8a0ee68d91 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 16 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mogwai/Design/Icons/ic_csg_subtract.png b/Assets/Mogwai/Design/Icons/ic_csg_subtract.png new file mode 100644 index 00000000..50ec9aaa Binary files /dev/null and b/Assets/Mogwai/Design/Icons/ic_csg_subtract.png differ diff --git a/Assets/Mogwai/Design/Icons/ic_csg_subtract.png.meta b/Assets/Mogwai/Design/Icons/ic_csg_subtract.png.meta new file mode 100644 index 00000000..9bb203ec --- /dev/null +++ b/Assets/Mogwai/Design/Icons/ic_csg_subtract.png.meta @@ -0,0 +1,116 @@ +fileFormatVersion: 2 +guid: 6a1d385b53ecb544289404050bfb66bc +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 16 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mogwai/Design/Icons/ic_csg_union.png b/Assets/Mogwai/Design/Icons/ic_csg_union.png new file mode 100644 index 00000000..fd2d56ef Binary files /dev/null and b/Assets/Mogwai/Design/Icons/ic_csg_union.png differ diff --git a/Assets/Mogwai/Design/Icons/ic_csg_union.png.meta b/Assets/Mogwai/Design/Icons/ic_csg_union.png.meta new file mode 100644 index 00000000..ee00709c --- /dev/null +++ b/Assets/Mogwai/Design/Icons/ic_csg_union.png.meta @@ -0,0 +1,116 @@ +fileFormatVersion: 2 +guid: 4f4b5f6433b99af469f6dc26e8b55b7c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 16 + mipBias: -100 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/model/controller/ControllerMode.cs b/Assets/Scripts/model/controller/ControllerMode.cs index 308995b9..7a946b79 100644 --- a/Assets/Scripts/model/controller/ControllerMode.cs +++ b/Assets/Scripts/model/controller/ControllerMode.cs @@ -68,7 +68,7 @@ public enum ControllerMode /// /// Mode for deleting meshes via subtraction (csg). /// - subtract, + csg, /// /// Mode for deleting edges. /// diff --git a/Assets/Scripts/model/controller/PaletteController.cs b/Assets/Scripts/model/controller/PaletteController.cs index 14c24255..3c3a9e58 100644 --- a/Assets/Scripts/model/controller/PaletteController.cs +++ b/Assets/Scripts/model/controller/PaletteController.cs @@ -538,7 +538,7 @@ public GameObject GetToolheadForMode(ControllerMode mode) switch (mode) { case ControllerMode.insertVolume: - case ControllerMode.subtract: + case ControllerMode.csg: return shapeToolhead; case ControllerMode.insertStroke: return freeformToolhead; diff --git a/Assets/Scripts/model/controller/PeltzerController.cs b/Assets/Scripts/model/controller/PeltzerController.cs index 531ff401..9670b985 100644 --- a/Assets/Scripts/model/controller/PeltzerController.cs +++ b/Assets/Scripts/model/controller/PeltzerController.cs @@ -1431,6 +1431,7 @@ public void ChangeMode(ControllerMode newMode, GameObject toolHead = null) break; case ControllerMode.insertStroke: case ControllerMode.insertVolume: + case ControllerMode.csg: if (Config.Instance.VrHardware == VrHardware.Vive) { defaultTipPointerDefaultLocation = new Vector3(0f, 0f, -0.015f); @@ -1696,7 +1697,7 @@ public void ChangeTouchpadOverlay(TouchpadOverlay newOverlay) switch (mode) { case ControllerMode.insertVolume: - case ControllerMode.subtract: + case ControllerMode.csg: currentOverlayGO = controllerGeometry.volumeInserterOverlay; break; case ControllerMode.insertStroke: @@ -1745,7 +1746,7 @@ public void ResetTouchpadOverlay() switch (mode) { case ControllerMode.insertVolume: - case ControllerMode.subtract: + case ControllerMode.csg: ChangeTouchpadOverlay(TouchpadOverlay.VOLUME_INSERTER); break; case ControllerMode.insertStroke: diff --git a/Assets/Scripts/model/csg/CsgOperations.cs b/Assets/Scripts/model/csg/CsgOperations.cs index 36f797a2..24ca9513 100644 --- a/Assets/Scripts/model/csg/CsgOperations.cs +++ b/Assets/Scripts/model/csg/CsgOperations.cs @@ -12,14 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.Collections.Generic; -using System.Linq; using UnityEngine; using com.google.apps.peltzer.client.model.core; using com.google.apps.peltzer.client.model.util; -using com.google.apps.peltzer.client.model.render; namespace com.google.apps.peltzer.client.model.csg { @@ -28,22 +25,29 @@ public class CsgOperations { private const float COPLANAR_EPS = 0.001f; + public enum CsgOperation + { + UNION, + INTERSECT, + SUBTRACT + } + /// - /// Subtract a mesh from all intersecting meshes in a model. + /// Performs a CSG operation on all intersecting meshes in a model. /// - /// true if the subtract brush intersects with meshes in the scene. - public static bool SubtractMeshFromModel(Model model, SpatialIndex spatialIndex, MMesh toSubtract) + /// true if the brush intersects with meshes in the scene. + public static bool CsgMeshFromModel(Model model, SpatialIndex spatialIndex, MMesh brush, CsgOperation csgOp = CsgOperation.SUBTRACT) { - Bounds bounds = toSubtract.bounds; + Bounds bounds = brush.bounds; List commands = new List(); HashSet intersectingMeshIds; - if (spatialIndex.FindIntersectingMeshes(toSubtract.bounds, out intersectingMeshIds)) + if (spatialIndex.FindIntersectingMeshes(brush.bounds, out intersectingMeshIds)) { foreach (int meshId in intersectingMeshIds) { MMesh mesh = model.GetMesh(meshId); - MMesh result = Subtract(mesh, toSubtract); + MMesh result = DoCsgOperation(mesh, brush, csgOp); commands.Add(new DeleteMeshCommand(mesh.id)); // If the result is null, it means the mesh was entirely erased. No need to add a new version back. if (result != null) @@ -69,16 +73,21 @@ public static bool SubtractMeshFromModel(Model model, SpatialIndex spatialIndex, } /// - /// Subtract a mesh from another. Returns a new MMesh that is the result of the subtraction. + /// Performs CSG on two meshes. Returns a new MMesh that is the result of the operation. /// If the result is an empty space, returns null. /// - public static MMesh Subtract(MMesh subtrahend, MMesh minuend) + public static MMesh DoCsgOperation(MMesh brush, MMesh target, CsgOperation csgOp = CsgOperation.SUBTRACT) { - // If the objects don't overlap, just bail out: - - if (!subtrahend.bounds.Intersects(minuend.bounds)) + // If the objects don't overlap, we have two fast paths: + if (!brush.bounds.Intersects(target.bounds)) { - return subtrahend.Clone(); + switch (csgOp) + { + case CsgOperation.INTERSECT: + return null; + case CsgOperation.SUBTRACT: + return brush.Clone(); + } } // Our epsilons aren't very good for operations that are either very small or very big, @@ -87,8 +96,8 @@ public static MMesh Subtract(MMesh subtrahend, MMesh minuend) // // Here's a good article for comparing floating point numbers: // https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ - Vector3 operationalCenter = (subtrahend.bounds.center + minuend.bounds.center) / 2.0f; - float averageRadius = (subtrahend.bounds.extents.magnitude + minuend.bounds.extents.magnitude) / 2.0f; + Vector3 operationalCenter = (brush.bounds.center + target.bounds.center) / 2.0f; + float averageRadius = (brush.bounds.extents.magnitude + target.bounds.extents.magnitude) / 2.0f; Vector3 operationOffset = -operationalCenter; float operationScale = 1.0f / averageRadius; if (operationScale < 1.0f) @@ -97,34 +106,48 @@ public static MMesh Subtract(MMesh subtrahend, MMesh minuend) } Bounds operationBounds = new Bounds(); - foreach (int vertexId in subtrahend.GetVertexIds()) + foreach (int vertexId in brush.GetVertexIds()) { - operationBounds.Encapsulate((subtrahend.VertexPositionInModelCoords(vertexId) + operationOffset) * operationScale); + operationBounds.Encapsulate((brush.VertexPositionInModelCoords(vertexId) + operationOffset) * operationScale); } - foreach (int vertexId in minuend.GetVertexIds()) + foreach (int vertexId in target.GetVertexIds()) { - operationBounds.Encapsulate((minuend.VertexPositionInModelCoords(vertexId) + operationOffset) * operationScale); + operationBounds.Encapsulate((target.VertexPositionInModelCoords(vertexId) + operationOffset) * operationScale); } operationBounds.Expand(0.01f); CsgContext ctx = new CsgContext(operationBounds); - CsgObject leftObj = ToCsg(ctx, subtrahend, operationOffset, operationScale); - CsgObject rightObj = ToCsg(ctx, minuend, operationOffset, operationScale); - List result = CsgSubtract(ctx, leftObj, rightObj); - if (result.Count > 0) + CsgObject leftObj = ToCsg(ctx, brush, operationOffset, operationScale); + CsgObject rightObj = ToCsg(ctx, target, operationOffset, operationScale); + List result = null; + + switch (csgOp) + { + case CsgOperation.UNION: + result = CsgUnion(ctx, leftObj, rightObj); + break; + case CsgOperation.INTERSECT: + result = CsgIntersect(ctx, leftObj, rightObj); + break; + case CsgOperation.SUBTRACT: + result = CsgSubtract(ctx, leftObj, rightObj); + break; + } + + if (result != null && result.Count > 0) { HashSet combinedRemixIds = null; - if (subtrahend.remixIds != null || minuend.remixIds != null) + if (brush.remixIds != null || target.remixIds != null) { combinedRemixIds = new HashSet(); - if (subtrahend.remixIds != null) combinedRemixIds.UnionWith(subtrahend.remixIds); - if (minuend.remixIds != null) combinedRemixIds.UnionWith(minuend.remixIds); + if (brush.remixIds != null) combinedRemixIds.UnionWith(brush.remixIds); + if (target.remixIds != null) combinedRemixIds.UnionWith(target.remixIds); } return FromPolys( - subtrahend.id, - subtrahend.offset, - subtrahend.rotation, + brush.id, + brush.offset, + brush.rotation, result, operationOffset, operationScale, @@ -155,6 +178,42 @@ private static List CsgSubtract(CsgContext ctx, CsgObject leftObj, C return polys; } + /// + /// Perform union on CsgObjects + /// + public static List CsgUnion(CsgContext ctx, CsgObject leftObj, CsgObject rightObj) + { + SplitObject(ctx, leftObj, rightObj); + SplitObject(ctx, rightObj, leftObj); + SplitObject(ctx, leftObj, rightObj); + ClassifyPolygons(leftObj, rightObj); + ClassifyPolygons(rightObj, leftObj); + + FaceProperties facePropertiesForNewFaces = leftObj.polygons[0].faceProperties; + List polys = SelectPolygons(leftObj, false, null, PolygonStatus.OUTSIDE, PolygonStatus.SAME); + polys.AddRange(SelectPolygons(rightObj, false, facePropertiesForNewFaces, PolygonStatus.OUTSIDE)); + + return polys; + } + + /// + /// Perform intersection on CsgObjects + /// + public static List CsgIntersect(CsgContext ctx, CsgObject leftObj, CsgObject rightObj) + { + SplitObject(ctx, leftObj, rightObj); + SplitObject(ctx, rightObj, leftObj); + SplitObject(ctx, leftObj, rightObj); + ClassifyPolygons(leftObj, rightObj); + ClassifyPolygons(rightObj, leftObj); + + FaceProperties facePropertiesForNewFaces = leftObj.polygons[0].faceProperties; + List polys = SelectPolygons(leftObj, false, null, PolygonStatus.INSIDE, PolygonStatus.SAME); + polys.AddRange(SelectPolygons(rightObj, false, facePropertiesForNewFaces, PolygonStatus.INSIDE)); + + return polys; + } + /// /// Select all of the polygons in the object with any of the given statuses. /// diff --git a/Assets/Scripts/model/main/Features.cs b/Assets/Scripts/model/main/Features.cs index aaf9088b..220d7ce3 100644 --- a/Assets/Scripts/model/main/Features.cs +++ b/Assets/Scripts/model/main/Features.cs @@ -26,7 +26,7 @@ namespace com.google.apps.peltzer.client.model.main public class Features { // If true, CSG subtraction (subtracting one shape from another, also known as "carving") is enabled. - public static bool csgSubtractEnabled = false; + public static bool csgSubtractEnabled = true; // If true, saves creations in the Mogwai object store. public static bool saveToMogwaiObjectStore = true; diff --git a/Assets/Scripts/model/main/PeltzerMain.cs b/Assets/Scripts/model/main/PeltzerMain.cs index 053f9828..1f57d2e1 100644 --- a/Assets/Scripts/model/main/PeltzerMain.cs +++ b/Assets/Scripts/model/main/PeltzerMain.cs @@ -1704,9 +1704,11 @@ public bool OperationInProgress() return reshaper.IsReshaping(); case ControllerMode.subdivideFace: return false; + case ControllerMode.csg: case ControllerMode.subdividePlane: return false; case ControllerMode.subtract: + case ControllerMode.csg: return volumeInserter.IsFilling(); } diff --git a/Assets/Scripts/tools/VolumeInserter.cs b/Assets/Scripts/tools/VolumeInserter.cs index f0ffd092..8d503fac 100644 --- a/Assets/Scripts/tools/VolumeInserter.cs +++ b/Assets/Scripts/tools/VolumeInserter.cs @@ -95,6 +95,7 @@ public class VolumeInserter : MonoBehaviour /// showed enough knowledge of how to snap. /// private int completedSnaps = 0; + private CsgOperations.CsgOperation csgOperation; private const int SNAP_KNOW_HOW_COUNT = 3; /// @@ -146,7 +147,7 @@ private void Update() } bool activeMode = (peltzerController.mode == ControllerMode.insertVolume - || peltzerController.mode == ControllerMode.subtract) + || peltzerController.mode == ControllerMode.csg) && !PeltzerMain.Instance.peltzerController.isPointingAtMenu && PeltzerMain.Instance.introChoreographer.introIsComplete; @@ -380,8 +381,7 @@ private void CreateNewVolumeMesh(int? oldScaleDeltaToAnimateFrom = null) // Create the primitive. List newMeshes = new List(); // TODO(bug) Replace pink with wireframe - int material = peltzerController.mode == ControllerMode.subtract ? - /* pink wireframe */ MaterialRegistry.PINK_WIREFRAME_ID : peltzerController.currentMaterial; + int material = peltzerController.currentMaterial; Vector3 scale; if (peltzerController.shapesMenu.showingShapeMenu) @@ -539,9 +539,9 @@ private void InsertVolumeMesh() HapticFeedback.HapticFeedbackType.FEEDBACK_3, /* durationSeconds */ 0.05f, /* strength */ 0.3f); Primitives.Shape selectedShape = (Primitives.Shape)peltzerController.shapesMenu.CurrentItemId; } - else if (peltzerController.mode == ControllerMode.subtract) + else if (peltzerController.mode == ControllerMode.csg) { - if (CsgOperations.SubtractMeshFromModel(model, spatialIndex, meshToInsert)) + if (CsgOperations.CsgMeshFromModel(model, spatialIndex, meshToInsert, csgOperation)) { audioLibrary.PlayClip(audioLibrary.deleteSound); peltzerController.TriggerHapticFeedback( @@ -687,7 +687,7 @@ private bool IsLongTermScale() private void ControllerEventHandler(object sender, ControllerEventArgs args) { // If we are not in insert or subtract mode, do nothing. - if ((peltzerController.mode != ControllerMode.insertVolume && peltzerController.mode != ControllerMode.subtract) + if ((peltzerController.mode != ControllerMode.insertVolume && peltzerController.mode != ControllerMode.csg) || PeltzerMain.Instance.peltzerController.isPointingAtMenu) { return; @@ -845,26 +845,42 @@ private void ControllerEventHandler(object sender, ControllerEventArgs args) { if (peltzerController.mode == ControllerMode.insertVolume) { - peltzerController.ChangeMode(ControllerMode.subtract); peltzerController.shapesMenu.ChangeShapesMenuMaterial(MaterialRegistry.PINK_WIREFRAME_ID); + peltzerController.ChangeMode(ControllerMode.csg, ObjectFinder.ObjectById("ID_ToolShapes")); + csgOperation = CsgOperations.CsgOperation.SUBTRACT; } - else if (peltzerController.mode == ControllerMode.subtract) + else if (peltzerController.mode == ControllerMode.csg) { - peltzerController.ChangeMode(ControllerMode.insertVolume); - peltzerController.shapesMenu.ChangeShapesMenuMaterial(peltzerController.currentMaterial); + switch (csgOperation) + { + case CsgOperations.CsgOperation.SUBTRACT: + csgOperation = CsgOperations.CsgOperation.INTERSECT; + audioLibrary.PlayClip(audioLibrary.swipeRightSound); + peltzerController.TriggerHapticFeedback(); + break; + case CsgOperations.CsgOperation.INTERSECT: + csgOperation = CsgOperations.CsgOperation.UNION; + audioLibrary.PlayClip(audioLibrary.swipeRightSound); + peltzerController.TriggerHapticFeedback(); + break; + case CsgOperations.CsgOperation.UNION: + peltzerController.ChangeMode(ControllerMode.insertVolume, ObjectFinder.ObjectById("ID_ToolShapes")); + peltzerController.shapesMenu.ChangeShapesMenuMaterial(peltzerController.currentMaterial); + break; + } } } } private void ModeChangeEventHandler(ControllerMode oldMode, ControllerMode newMode) { - if (oldMode == ControllerMode.insertVolume || oldMode == ControllerMode.subtract) + if (oldMode == ControllerMode.insertVolume || oldMode == ControllerMode.csg) { peltzerController.shapesMenu.Hide(); UnsetAllHoverTooltips(); } - if (newMode == ControllerMode.insertVolume || newMode == ControllerMode.subtract) + if (newMode == ControllerMode.insertVolume || newMode == ControllerMode.csg) { CreateNewVolumeMesh(); @@ -905,7 +921,7 @@ private void ShapeChangedHandler(int newShapeMenuItemId) private void BlockModeChangedHandler(bool isBlockMode) { - if (peltzerController.mode == ControllerMode.insertVolume || peltzerController.mode == ControllerMode.subtract) + if (peltzerController.mode == ControllerMode.insertVolume || peltzerController.mode == ControllerMode.csg) { CreateNewVolumeMesh(); } diff --git a/Assets/Scripts/tools/utils/HeldMeshes.cs b/Assets/Scripts/tools/utils/HeldMeshes.cs index 20826647..f798d38f 100644 --- a/Assets/Scripts/tools/utils/HeldMeshes.cs +++ b/Assets/Scripts/tools/utils/HeldMeshes.cs @@ -456,7 +456,7 @@ public void StartSnapping(Model model, SpatialIndex spatialIndex) model, spatialIndex, worldSpace, - peltzerController.mode == ControllerMode.subtract, + peltzerController.mode == ControllerMode.csg, out finalVolumePreviewMeshRotation, out previewFace, out coplanarPreviewFaceVerticesAtOrigin,