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,