예제 #1
0
    protected static void applyBrush(Vox.VoxelEditor editor, Ray mouseLocation)
    {
        byte opacity = byte.MaxValue;

        if (UnityEngine.Event.current.shift)
        {
            opacity = byte.MinValue;
        }
        Vector3 point = editor.getBrushPoint(mouseLocation);

        switch (editor.selectedBrush)
        {
        case 0:
            Vox.SphereModifier sphereMod = new Vox.SphereModifier(editor, point, editor.sphereBrushSize, new Vox.Voxel(editor.sphereBrushSubstance, opacity), true);
            sphereMod.overwriteShape = !editor.sphereSubstanceOnly;
            sphereMod.apply();
            break;

        case 1:
            Vox.CubeModifier cubeMod = new Vox.CubeModifier(editor, point, editor.cubeBrushDimensions, new Vox.Voxel(editor.cubeBrushSubstance, opacity), true);
            cubeMod.overwriteShape = !editor.cubeSubstanceOnly;
            cubeMod.apply();
            break;

        case 2:
            Vox.BlurModifier blur = new Vox.BlurModifier(editor, point, editor.smoothBrushSize, editor.smoothBrushStrength, true);
            blur.blurRadius = editor.smoothBrushBlurRadius;
            blur.apply();
            break;
        }
    }
예제 #2
0
    public void generateVoxels(Vox.VoxelEditor editor)
    {
        editor.wipe();
        generationParameters.setTo(editor);
        editor.initialize();
        switch (selectedGenerationMode)
        {
        case 0:
            editor.setToHeight();
            break;

        case 1:
            editor.setToSphere();
            break;

        case 2:
            editor.setToProcedural();
            break;

        case 3:
            editor.heightmaps          = generationParameters.heightmaps;
            editor.heightmapSubstances = generationParameters.heightmapSubstances;
            editor.setToHeightmap();
            break;
        }
        editor.generateRenderers();
        setupGeneration = false;
    }
예제 #3
0
    public void OnSceneGUI()
    {
        Vox.VoxelEditor editor = (Vox.VoxelEditor)target;
        if (editor.selectedMode != 1)
        {
            return;
        }
        int controlId = GUIUtility.GetControlID(FocusType.Passive);

        switch (UnityEngine.Event.current.GetTypeForControl(controlId))
        {
        case EventType.MouseDown:
            if (UnityEngine.Event.current.button == 0)
            {
                GUIUtility.hotControl = controlId;
                applyBrush(editor, HandleUtility.GUIPointToWorldRay(UnityEngine.Event.current.mousePosition));
                UnityEngine.Event.current.Use();
            }
            break;

        case EventType.MouseUp:
            if (UnityEngine.Event.current.button == 0)
            {
                GUIUtility.hotControl = 0;
                UnityEngine.Event.current.Use();
            }
            break;

        case EventType.MouseMove:
            SceneView.RepaintAll();
            break;
        }
    }
예제 #4
0
    protected Vox.Mutator buildMutator(Vox.VoxelEditor editor, Vector3 point)
    {
        // check for subtraction mode
        byte opacity = byte.MaxValue;

        if (editor.isSubtracting())
        {
            opacity = byte.MinValue;
        }

        // create mutator (and maybe apply)
        switch (editor.selectedBrush)
        {
        case 0:
            Vox.SphereMutator sphereMod = new Vox.SphereMutator(point, editor.sphereBrushSize, new Vox.Voxel(editor.sphereBrushSubstance, opacity));
            sphereMod.overwriteShape = !editor.sphereSubstanceOnly;
            return(sphereMod);

        case 1:
            Vox.CubeMutator cubeMod = new Vox.CubeMutator(editor, point, editor.cubeBrushDimensions, new Vox.Voxel(editor.cubeBrushSubstance, opacity), true);
            cubeMod.overwriteShape = !editor.cubeSubstanceOnly;
            return(cubeMod);

        default:
            Vox.BlurMutator blurMod = new Vox.BlurMutator(editor, point, editor.smoothBrushSize, editor.smoothBrushStrength);
            blurMod.blurRadius = editor.smoothBrushBlurRadius;
            return(blurMod);
        }
    }
예제 #5
0
    public override void OnInspectorGUI()
    {
        labelBigFont             = new GUIStyle(GUI.skin.label);
        labelBigFont.margin      = new RectOffset(labelBigFont.margin.left, labelBigFont.margin.right, labelBigFont.margin.top + 10, labelBigFont.margin.bottom);
        labelBigFont.fontSize    = 16;
        foldoutBigFont           = new GUIStyle(EditorStyles.foldout);
        foldoutBigFont.margin    = new RectOffset(foldoutBigFont.margin.left, foldoutBigFont.margin.right, foldoutBigFont.margin.top + 10, foldoutBigFont.margin.bottom);
        foldoutBigFont.fontSize  = 16;
        foldoutBigFont.alignment = TextAnchor.LowerLeft;
        buttonBigFont            = new GUIStyle(GUI.skin.button);
        buttonBigFont.fontSize   = 14;
        tabsBigFont             = new GUIStyle(GUI.skin.button);
        tabsBigFont.fixedHeight = 30;

        Vox.VoxelEditor editor = (Vox.VoxelEditor)target;

        if (editor.generating())
        {
            string label = "Generating Skin...";
            EditorGUI.ProgressBar(GUILayoutUtility.GetRect(new GUIContent(label), GUI.skin.button),
                                  VoxelEditorProgressController.getGenerationProgress(editor), label);
            Repaint();
            return;
        }

        serializedObject.Update();

        if (setupGeneration)
        {
            doGenerationGUI(editor);
            return;
        }
        else
        {
            if (!editor.hasVoxelData())
            {
                GUI.enabled = false;
            }
            editor.selectedMode = GUILayout.Toolbar(editor.selectedMode, modes, tabsBigFont, GUILayout.Height(30));
            GUI.enabled         = true;

            switch (editor.selectedMode)
            {
            case 0:
                doManageGUI(editor);
                break;

            case 1:
                doSculptGUI(editor);
                break;

            case 2:
                doMaskGUI(editor);
                break;
            }
        }

        // finally, apply the changes
        serializedObject.ApplyModifiedProperties();
    }
예제 #6
0
 public void setTo(Vox.VoxelEditor editor)
 {
     editor.baseSize         = baseSize;
     editor.maxDetail        = maxDetail;
     editor.createColliders  = createColliders;
     editor.useStaticMeshes  = useStaticMeshes;
     editor.saveMeshes       = saveMeshes;
     editor.reduceMeshes     = reduceMeshes;
     editor.reductionAmount  = reductionAmount;
     editor.heightPercentage = heightPercentage;
     editor.spherePercentage = spherePercentage;
 }
예제 #7
0
    protected void doMaskGUI(Vox.VoxelEditor editor)
    {
        editor.maskDisplayAlpha = doSliderFloatField("Mask Display Transparency", editor.maskDisplayAlpha, 0, 1);

        // mask list
        showMasks = doBigFoldout(showMasks, "Masks");
        if (showMasks)
        {
            SerializedProperty voxelMasks = serializedObject.FindProperty("masks");
            // EditorGUILayout.PropertyField(voxelMasks, new GUIContent("Sculpting Masks"), true);
            InspectorList.doArrayGUISimple(ref voxelMasks);
        }
    }
예제 #8
0
 public void setFrom(Vox.VoxelEditor editor)
 {
     baseSize         = editor.baseSize;
     maxDetail        = editor.maxDetail;
     maxChange        = editor.maxChange;
     proceduralSeed   = editor.proceduralSeed;
     createColliders  = editor.createColliders;
     useStaticMeshes  = editor.useStaticMeshes;
     saveMeshes       = editor.saveMeshes;
     reduceMeshes     = editor.reduceMeshes;
     reductionAmount  = editor.reductionAmount;
     heightPercentage = editor.heightPercentage;
     spherePercentage = editor.spherePercentage;
 }
예제 #9
0
    public void OnSceneGUI()
    {
        Vox.VoxelEditor editor = (Vox.VoxelEditor)target;
        if (editor.selectedMode != 1)
        {
            return;
        }

        if (editor.pathPoints != null && editor.pathPoints.Length > 0 && editor.isSelectedBrushPathable() && editor.showPositionHandles)
        {
            for (int i = 0; i < editor.pathPoints.Length; ++i)
            {
                editor.pathPoints[i] = UnityEditor.Handles.PositionHandle(editor.pathPoints[i], Quaternion.identity);
            }
        }

        int controlId = GUIUtility.GetControlID(FocusType.Passive);

        switch (UnityEngine.Event.current.GetTypeForControl(controlId))
        {
        case EventType.MouseDown:
            if (UnityEngine.Event.current.button == 0)
            {
                GUIUtility.hotControl = controlId;
                applyBrush(editor, HandleUtility.GUIPointToWorldRay(UnityEngine.Event.current.mousePosition));
                UnityEngine.Event.current.Use();
            }
            break;

        case EventType.MouseUp:
            if (UnityEngine.Event.current.button == 0)
            {
                GUIUtility.hotControl = 0;
                UnityEngine.Event.current.Use();
            }
            break;

        case EventType.MouseMove:
            SceneView.RepaintAll();
            break;

        case EventType.KeyDown:
            if (UnityEngine.Event.current.keyCode == KeyCode.Escape)
            {
                editor.pathPoints = null;
            }
            break;
        }
    }
예제 #10
0
    protected void doGenerationGUI(Vox.VoxelEditor editor)
    {
        GUILayout.Label("Properties", labelBigFont);
        doTreeSizeGUI(generationParameters);

        // general properties
        doGeneralPropertiesGUI(generationParameters);

        // substances
        doSubstancesGUI(serializedObject);

        // generation mode
        GUILayout.Label("Generation Mode", labelBigFont);
        selectedGenerationMode = GUILayout.Toolbar(selectedGenerationMode, generationModes);
        switch (selectedGenerationMode)
        {
        case 0:
            doFlatGenerationGUI();
            break;

        case 1:
            doSphereGenerationGUI();
            break;

        case 2:
            doProceduralGenerationGUI();
            break;

        case 3:
            doHeightmapGenerationGUI();
            break;
        }

        // confirmation
        GUILayout.Label("Confirmation", labelBigFont);
        if (GUILayout.Button("Generate", buttonBigFont) && validateSubstances(editor))
        {
            if (EditorUtility.DisplayDialog("Generate Voxels?", "Are you sure you want to generate the voxel terain from scratch?  Any previous work will be overriden.", "Generate", "Cancel"))
            {
                generateVoxels(editor);
            }
        }
        if (GUILayout.Button("Cancel Generation", buttonBigFont))
        {
            setupGeneration = false;
        }
        EditorGUILayout.Separator();
    }
예제 #11
0
        public static VoxelEditor createEmpty()
        {
            GameObject ob = new GameObject();

            ob.name = "Voxel Object";
            VoxelEditor editor = ob.AddComponent <VoxelEditor>();

            editor.voxelSubstances = new VoxelSubstance[1];
            VoxelSubstance sub = new VoxelSubstance("Base",
                                                    UnityEditor.AssetDatabase.LoadAssetAtPath <Material>(DEFAULT_MATERIAL_PATH),
                                                    UnityEditor.AssetDatabase.LoadAssetAtPath <Material>(DEFAULT_BLEND_MATERIAL_PATH),
                                                    UnityEditor.AssetDatabase.LoadAssetAtPath <PhysicMaterial>(DEFAULT_PHYSICS_MATERIAL_PATH));

            editor.voxelSubstances[0] = sub;
            return(editor);
        }
예제 #12
0
 protected void doGeneralPropertiesGUI(Vox.VoxelEditor editor)
 {
     editor.createColliders = EditorGUILayout.Toggle(new GUIContent("Generate Colliders"), editor.createColliders);
     editor.useStaticMeshes = EditorGUILayout.Toggle(new GUIContent("Use Static Meshes"), editor.useStaticMeshes);
     editor.saveMeshes      = EditorGUILayout.Toggle(new GUIContent("Save Meshes To Scene"), editor.saveMeshes);
     editor.reduceMeshes    = EditorGUILayout.Toggle(new GUIContent("Reduce Meshes"), editor.reduceMeshes);
     if (editor.reduceMeshes)
     {
         editor.reductionAmount = doSliderFloatField("Mesh Reduction Level", editor.reductionAmount, 0, 0.5f);
     }
     //editor.maxDetail = (byte)EditorGUILayout.IntField(new GUIContent("Voxel Power"), editor.maxDetail);
     // if (createColliders != editor.createColliders || useStaticMeshes != editor.useStaticMeshes) {
     //  editor.createColliders = createColliders;
     //  editor.useStaticMeshes = useStaticMeshes;
     //     editor.generateRenderers();
     // }
 }
예제 #13
0
    protected bool validateSubstances(Vox.VoxelEditor editor)
    {
        if (editor.voxelSubstances == null || editor.voxelSubstances.Length < 1)
        {
            EditorUtility.DisplayDialog("Invalid Substances", "There must be at least one voxel substance defined to generate the voxel object.", "OK");
            return(false);
        }
        int i = 1;

        foreach (Vox.VoxelSubstance sub in editor.voxelSubstances)
        {
            if (sub.renderMaterial == null || sub.blendMaterial == null)
            {
                EditorUtility.DisplayDialog("Invalid Substance", "Substance " + i + ", " + sub.name + ", must have a render material and a blend material set.", "OK");
                return(false);
            }
            ++i;
        }
        return(true);
    }
예제 #14
0
    protected void applyBrush(Vox.VoxelEditor editor, Ray mouseLocation)
    {
        // get point clicked on
        System.Nullable <Vector3> point = editor.getBrushPoint(mouseLocation);
        if (point == null)
        {
            return;
        }

        // check if control pressed.  If so, add point to pathList
        if (editor.isPathing())
        {
            editor.addPathPoint(point.Value);
            return;
        }

        // check for showPositionHandles
        if (editor.showPositionHandles && editor.isSelectedBrushPathable() &&
            editor.pathPoints != null && editor.pathPoints.Length > 0)
        {
            return;
        }

        // create mutator
        Vox.Mutator mutator = buildMutator(editor, point.Value);

        // apply mutator
        if (mutator == null)
        {
            return;
        }
        Vox.LocalMutator localMutator = mutator as Vox.LocalMutator;
        if (localMutator != null && editor.pathPoints != null && editor.pathPoints.Length > 0)
        {
            editor.addPathPoint(point.Value);
            mutator           = new Vox.LineMutator(editor.pathPoints, localMutator);
            editor.pathPoints = null;
        }
        mutator.apply(editor);
    }
예제 #15
0
    protected void doTreeSizeGUI(Vox.VoxelEditor editor)
    {
        // world detail
        EditorGUILayout.LabelField("Voxel Power", editor.maxDetail.ToString());

        long dimension = 1 << editor.maxDetail;

        ++EditorGUI.indentLevel;
        EditorGUILayout.LabelField("Voxels Per Side", dimension.ToString(numFormInt));
        EditorGUILayout.LabelField("Max Voxel Count", Mathf.Pow(dimension, 3).ToString(numFormInt));
        --EditorGUI.indentLevel;
        EditorGUILayout.Separator();

        // world dimension
        EditorGUILayout.LabelField("World Size (m)", editor.baseSize.ToString());
        ++EditorGUI.indentLevel;
        EditorGUILayout.LabelField("World Area", Mathf.Pow(editor.baseSize / 1000, 2).ToString(numForm) + " square km");
        EditorGUILayout.LabelField("World Volume", Mathf.Pow(editor.baseSize / 1000, 3).ToString(numForm) + " cubic km");
        --EditorGUI.indentLevel;
        EditorGUILayout.Separator();

        EditorGUILayout.LabelField("Voxel Size", (editor.baseSize / dimension).ToString(numForm) + " m");
        EditorGUILayout.Separator();
    }
예제 #16
0
 protected static float maxBrushSize(Vox.VoxelEditor editor)
 {
     return(Mathf.Max(editor.voxelSize() * 20, 50));
 }
예제 #17
0
    protected void doSculptGUI(Vox.VoxelEditor editor)
    {
        // brush ghost
        editor.ghostBrushAlpha = doSliderFloatField("Brush Ghost Opacity", editor.ghostBrushAlpha, 0, 1);

        editor.gridEnabled = EditorGUILayout.Toggle("Snap to Grid", editor.gridEnabled);
        if (editor.gridEnabled)
        {
            ++EditorGUI.indentLevel;
            editor.gridUseVoxelUnits = EditorGUILayout.Toggle("Use Voxel Units", editor.gridUseVoxelUnits);
            if (editor.gridUseVoxelUnits)
            {
                float voxelSize = editor.baseSize / (1 << editor.maxDetail);
                editor.gridSize = EditorGUILayout.FloatField("Grid Spacing (Voxels)", editor.gridSize / voxelSize) * voxelSize;
            }
            else
            {
                editor.gridSize = EditorGUILayout.FloatField("Grid Spacing (Meters)", editor.gridSize);
            }
            --EditorGUI.indentLevel;
        }

        // brush list
        GUILayout.Label("Brush", labelBigFont);
        editor.selectedBrush = GUILayout.Toolbar(editor.selectedBrush, brushes, GUILayout.MinHeight(20));

        // brush substance type
        string[] substances = new string[editor.voxelSubstances.Length];
        for (int i = 0; i < substances.Length; ++i)
        {
            substances[i] = editor.voxelSubstances[i].name;
        }

        // brush size
        switch (editor.selectedBrush)
        {
        case 0:
            GUILayout.Label("Hold 'Shift' to subtract.");
            editor.sphereBrushSize     = doSliderFloatField("Sphere Radius (m)", editor.sphereBrushSize, 0, 100);
            editor.sphereSubstanceOnly = GUILayout.Toggle(editor.sphereSubstanceOnly, "Change Substance Only");
            GUILayout.Label("Substance", labelBigFont);
            editor.sphereBrushSubstance = (byte)GUILayout.SelectionGrid(editor.sphereBrushSubstance, substances, 1);
            break;

        case 1:
            GUILayout.Label("Hold 'Shift' to subtract.");
            GUILayout.BeginHorizontal();
            GUILayout.Label("Dimensions (m)");
            editor.cubeBrushDimensions.x = EditorGUILayout.FloatField(editor.cubeBrushDimensions.x);
            editor.cubeBrushDimensions.y = EditorGUILayout.FloatField(editor.cubeBrushDimensions.y);
            editor.cubeBrushDimensions.z = EditorGUILayout.FloatField(editor.cubeBrushDimensions.z);
//			SerializedProperty cubeBrushDimensions = ob.FindProperty("cubeBrushDimensions");
//			EditorGUILayout.PropertyField(cubeBrushDimensions, new GUIContent("Rectangle Brush Dimensions"), true);
            GUILayout.EndHorizontal();

            editor.cubeSubstanceOnly = GUILayout.Toggle(editor.cubeSubstanceOnly, "Change Substance Only");
            GUILayout.Label("Substance", labelBigFont);
            editor.cubeBrushSubstance = (byte)GUILayout.SelectionGrid(editor.cubeBrushSubstance, substances, 1);
            break;

        case 2:
            editor.smoothBrushSize       = doSliderFloatField("Radius (m)", editor.smoothBrushSize, 0, 100);
            editor.smoothBrushStrength   = doSliderFloatField("Strength", editor.smoothBrushStrength, 0, 5);
            editor.smoothBrushBlurRadius = EditorGUILayout.IntField("Blur Radius", editor.smoothBrushBlurRadius);
            break;
        }
    }
예제 #18
0
    protected void doManageGUI(Vox.VoxelEditor editor)
    {
        // actions
        GUILayout.Label("Actions", labelBigFont);
        if (GUILayout.Button("Generate New"))
        {
            setupGeneration      = true;
            generationParameters = new VoxelEditorParameters();
            generationParameters.setFrom(editor);
        }
        if (editor.hasVoxelData())
        {
            if (GUILayout.Button("Erase"))
            {
                if (EditorUtility.DisplayDialog("Erase Voxels?", "Are you sure you want to erase all voxel data?", "Erase", "Cancel"))
                {
                    editor.wipe();
                }
            }
            if (GUILayout.Button(editor.hasRenderers()? "Reskin": "Skin", buttonBigFont) && validateSubstances(editor))
            {
                editor.generateRenderers();
            }
            if (editor.hasRenderers() && GUILayout.Button("Clear Skin") && validateSubstances(editor))
            {
                editor.clearRenderers();
            }
            if (GUILayout.Button("Export"))
            {
                editor.export(EditorUtility.SaveFilePanel("Choose File to Export To", "", "Voxels", "vox"));
            }
        }
        if (GUILayout.Button("Import"))
        {
            if (!editor.import(EditorUtility.OpenFilePanel("Choose File to Import From", "", "vox")))
            {
                EditorUtility.DisplayDialog("Wrong Voxel Format", "The file you chose was an unknown or incompatible voxel format version.", "OK");
            }
        }

        if (!editor.hasVoxelData())
        {
            return;
        }

        GUILayout.Label("Properties", labelBigFont);

        doGeneralPropertiesGUI(editor);

        // TODO: implement LOD and uncomment this
//		// LOD
//		SerializedProperty useLod = ob.FindProperty("useLod");
//		EditorGUILayout.PropertyField(useLod, new GUIContent("Use Level of Detail"));
//		if (useLod.boolValue) {
//			++EditorGUI.indentLevel;
//			SerializedProperty lodDetail = ob.FindProperty("lodDetail");
//			EditorGUILayout.PropertyField(lodDetail, new GUIContent("Target Level of Detail"));
//			if (lodDetail.floatValue > 1000)
//				lodDetail.floatValue = 1000;
//			else if (lodDetail.floatValue < 0.1f)
//				lodDetail.floatValue = 0.1f;
//
//			SerializedProperty curLodDetail = ob.FindProperty("curLodDetail");
//			if (Application.isPlaying) {
//				EditorGUILayout.PropertyField(curLodDetail, new GUIContent("Current Level of Detail"));
//			} else {
//				EditorGUILayout.PropertyField(curLodDetail, new GUIContent("Starting Level of Detail"));
//			}
//
//			if (curLodDetail.floatValue > 1000)
//				curLodDetail.floatValue = 1000;
//			else if (curLodDetail.floatValue < 0.1f)
//				curLodDetail.floatValue = 0.1f;
//			--EditorGUI.indentLevel;
//		}

        // do substances
        doSubstancesGUI(serializedObject);


        // show statistics
        showStatistics = doBigFoldout(showStatistics, "Statistics");
        if (showStatistics)
        {
            EditorGUILayout.LabelField("Chunk Count: " + editor.renderers.Count);
            doTreeSizeGUI(editor);
            EditorGUILayout.LabelField("Vertex Count: " + editor.vertexCount);
            EditorGUILayout.LabelField("Triangle Count: " + editor.triangleCount);
        }
    }
예제 #19
0
    protected void doSculptGUI(Vox.VoxelEditor editor)
    {
        // brush ghost
        editor.ghostBrushAlpha = doSliderFloatField("Brush Ghost Opacity", editor.ghostBrushAlpha, 0, 1);

        editor.gridEnabled = EditorGUILayout.Toggle("Snap to Grid", editor.gridEnabled);
        if (editor.gridEnabled)
        {
            ++EditorGUI.indentLevel;
            editor.gridUseVoxelUnits = EditorGUILayout.Toggle("Use Voxel Units", editor.gridUseVoxelUnits);
            if (editor.gridUseVoxelUnits)
            {
                float voxelSize = editor.baseSize / (1 << editor.maxDetail);
                editor.gridSize = EditorGUILayout.FloatField("Grid Spacing (Voxels)", editor.gridSize / voxelSize) * voxelSize;
            }
            else
            {
                editor.gridSize = EditorGUILayout.FloatField("Grid Spacing (Meters)", editor.gridSize);
            }
            --EditorGUI.indentLevel;
        }

        // brush list
        GUILayout.Label("Brush", labelBigFont);
        editor.selectedBrush = GUILayout.Toolbar(editor.selectedBrush, brushes, GUILayout.MinHeight(20));

        // brush substance type
        string[] substances = new string[editor.voxelSubstances.Length];
        for (int i = 0; i < substances.Length; ++i)
        {
            substances[i] = editor.voxelSubstances[i].name;
        }

        // brush size
        switch (editor.selectedBrush)
        {
        case 0:
            GUILayout.Label("Hold 'Shift' to subtract.");
            editor.sphereBrushSize     = doSliderFloatField("Sphere Radius (m)", editor.sphereBrushSize, 0, maxBrushSize(editor));
            editor.sphereSubstanceOnly = GUILayout.Toggle(editor.sphereSubstanceOnly, "Change Substance Only");
            GUILayout.Label("Substance", labelBigFont);
            editor.sphereBrushSubstance = (byte)GUILayout.SelectionGrid(editor.sphereBrushSubstance, substances, 1);
            break;

        case 1:
            GUILayout.Label("Hold 'Shift' to subtract.");
            GUILayout.BeginHorizontal();
            GUILayout.Label("Dimensions (m)");
            editor.cubeBrushDimensions.x = EditorGUILayout.FloatField(editor.cubeBrushDimensions.x);
            editor.cubeBrushDimensions.y = EditorGUILayout.FloatField(editor.cubeBrushDimensions.y);
            editor.cubeBrushDimensions.z = EditorGUILayout.FloatField(editor.cubeBrushDimensions.z);
            GUILayout.EndHorizontal();

            editor.cubeSubstanceOnly = GUILayout.Toggle(editor.cubeSubstanceOnly, "Change Substance Only");
            GUILayout.Label("Substance", labelBigFont);
            editor.cubeBrushSubstance = (byte)GUILayout.SelectionGrid(editor.cubeBrushSubstance, substances, 1);
            break;

        case 2:
            editor.smoothBrushSize       = doSliderFloatField("Radius (m)", editor.smoothBrushSize, 0, maxBrushSize(editor));
            editor.smoothBrushStrength   = doSliderFloatField("Strength", editor.smoothBrushStrength, 0, 5);
            editor.smoothBrushBlurRadius = EditorGUILayout.IntField("Blur Radius", editor.smoothBrushBlurRadius);
            break;
        }

        // PATH GUI
        if (editor.selectedBrush != 0 && editor.selectedBrush != 1)
        {
            return;
        }
        GUILayout.Label("Path Tool", labelBigFont);


        if (editor.pathPoints != null && editor.pathPoints.Length > 0)
        {
            GUILayout.Label("Hold 'Control' to place another point.");
            editor.showPositionHandles = GUILayout.Toggle(editor.showPositionHandles, "Show drag handles (disable click to complete).");
            SerializedProperty prop = serializedObject.FindProperty("pathPoints");
            InspectorList.doArrayGUISimple(ref prop);
            serializedObject.ApplyModifiedProperties();
            if (GUILayout.Button("Clear Path"))
            {
                editor.pathPoints = null;
            }
            else if (GUILayout.Button("Apply Path"))
            {
                Vox.LocalMutator mut = (Vox.LocalMutator)buildMutator(editor, editor.pathPoints[0]);
                if (editor.pathPoints.Length > 1)
                {
                    new Vox.LineMutator(editor.pathPoints, mut).apply(editor);
                }
                else
                {
                    mut.apply(editor);
                }
            }
        }
        else
        {
            GUILayout.Label("Hold 'Control' to start a path.");
            if (GUILayout.Button("Start Path"))
            {
                editor.addPathPoint(editor.transform.position);
            }
        }
    }