public void Adjust(Vector3 adjustDirection)
    {
        MergeStoredSelected();
        // now we can safely look only the addSelected property and the selectedThings list
        // and ignore the storedSelected property and the storedSelectedThings list

        int  adjustDirFaceI         = Voxel.FaceIForDirection(adjustDirection);
        int  oppositeAdjustDirFaceI = Voxel.OppositeFaceI(adjustDirFaceI);
        int  adjustAxis             = Voxel.FaceIAxis(adjustDirFaceI);
        bool negativeAdjustAxis     = adjustDirFaceI % 2 == 0;

        // sort selectedThings in order along the adjustDirection vector
        selectedThings.Sort(delegate(Selectable a, Selectable b)
        {
            // positive means A is greater than B
            // so positive means B will be adjusted before A
            Vector3 aCenter = a.bounds.center;
            Vector3 bCenter = b.bounds.center;
            float diff      = 0;
            switch (adjustAxis)
            {
            case 0:
                diff = bCenter.x - aCenter.x;
                break;

            case 1:
                diff = bCenter.y - aCenter.y;
                break;

            case 2:
                diff = bCenter.z - aCenter.z;
                break;
            }
            if (negativeAdjustAxis)
            {
                diff = -diff;
            }
            if (diff > 0)
            {
                return(1);
            }
            if (diff < 0)
            {
                return(-1);
            }
            if (a is VoxelFaceReference && b is VoxelFaceReference)
            {
                var aFace = (VoxelFaceReference)a;
                var bFace = (VoxelFaceReference)b;
                if (aFace.faceI == oppositeAdjustDirFaceI)
                {
                    if (bFace.faceI != oppositeAdjustDirFaceI)
                    {
                        return(-1); // move one substance back before moving other forward
                    }
                    else if (bFace.faceI == oppositeAdjustDirFaceI)
                    {
                        return(1);
                    }
                }
            }
            return(0);
        });

        // HashSets prevent duplicate elements
        var  voxelsToUpdate   = new HashSet <Voxel>();
        bool createdSubstance = false;
        bool temporarilyBlockPushingANewSubstance = false;

        for (int i = 0; i < selectedThings.Count; i++)
        {
            Selectable thing = selectedThings[i];
            if (thing is ObjectMarker)
            {
                var        obj       = ((ObjectMarker)thing).objectEntity;
                Vector3Int objNewPos = obj.position + Vector3ToInt(adjustDirection);
                MoveObject(obj, objNewPos);

                Voxel objNewVoxel = VoxelAt(objNewPos, false);
                if (objNewVoxel != null && objNewVoxel.substance == null &&
                    !objNewVoxel.faces[oppositeAdjustDirFaceI].IsEmpty() &&
                    !objNewVoxel.faces[oppositeAdjustDirFaceI].addSelected)
                {
                    // carve a hole for the object if it's being pushed into a wall
                    objNewVoxel.faces[oppositeAdjustDirFaceI].addSelected = true;
                    selectedThings.Insert(i + 1, new VoxelFaceReference(objNewVoxel, oppositeAdjustDirFaceI));
                }
                continue;
            }
            else if (!(thing is VoxelFaceReference))
            {
                continue;
            }
            VoxelFaceReference faceRef = (VoxelFaceReference)thing;

            Voxel   oldVoxel = faceRef.voxel;
            Vector3 oldPos   = oldVoxel.transform.position;
            Vector3 newPos   = oldPos + adjustDirection;
            Voxel   newVoxel = VoxelAt(newPos, true);

            int  faceI         = faceRef.faceI;
            int  oppositeFaceI = Voxel.OppositeFaceI(faceI);
            bool pushing       = adjustDirFaceI == oppositeFaceI;
            bool pulling       = adjustDirFaceI == faceI;

            if (pulling && (!newVoxel.faces[oppositeFaceI].IsEmpty()) && !newVoxel.faces[oppositeFaceI].addSelected)
            {
                // usually this means there's another substance. push it away before this face
                if (substanceToCreate != null && newVoxel.substance == substanceToCreate)
                {
                    // substance has already been created there!
                    // substanceToCreate has never existed in the map before Adjust() was called
                    // so it must have been created earlier in the loop
                    // remove selection
                    oldVoxel.faces[faceI].addSelected = false;
                    selectedThings[i] = new VoxelFaceReference(null, -1);
                    voxelsToUpdate.Add(oldVoxel);
                }
                else
                {
                    newVoxel.faces[oppositeFaceI].addSelected = true;
                    selectedThings.Insert(i, new VoxelFaceReference(newVoxel, oppositeFaceI));
                    i -= 1;
                    // need to move the other substance out of the way first
                    temporarilyBlockPushingANewSubstance = true;
                }
                continue;
            }

            VoxelFace movingFace = oldVoxel.faces[faceI];
            movingFace.addSelected = false;
            Substance movingSubstance = oldVoxel.substance;

            bool  blocked           = false; // is movement blocked?
            Voxel newSubstanceBlock = null;

            if (pushing)
            {
                for (int sideNum = 0; sideNum < 4; sideNum++)
                {
                    int sideFaceI = Voxel.SideFaceI(faceI, sideNum);
                    if (oldVoxel.faces[sideFaceI].IsEmpty())
                    {
                        // add side
                        Vector3 sideFaceDir       = Voxel.DirectionForFaceI(sideFaceI);
                        Voxel   sideVoxel         = VoxelAt(oldPos + sideFaceDir, true);
                        int     oppositeSideFaceI = Voxel.OppositeFaceI(sideFaceI);

                        // if possible, the new side should have the properties of the adjacent side
                        Voxel adjacentSideVoxel = VoxelAt(oldPos - adjustDirection + sideFaceDir, false);
                        if (adjacentSideVoxel != null && !adjacentSideVoxel.faces[oppositeSideFaceI].IsEmpty() &&
                            movingSubstance == adjacentSideVoxel.substance)
                        {
                            sideVoxel.faces[oppositeSideFaceI]             = adjacentSideVoxel.faces[oppositeSideFaceI];
                            sideVoxel.faces[oppositeSideFaceI].addSelected = false;
                        }
                        else
                        {
                            sideVoxel.faces[oppositeSideFaceI] = movingFace;
                        }
                        voxelsToUpdate.Add(sideVoxel);
                    }
                }

                if (!oldVoxel.faces[oppositeFaceI].IsEmpty())
                {
                    blocked = true;
                }
                oldVoxel.Clear();
                if (substanceToCreate != null && !temporarilyBlockPushingANewSubstance)
                {
                    newSubstanceBlock = CreateSubstanceBlock(oldPos, substanceToCreate, movingFace);
                }
            }
            else if (pulling && substanceToCreate != null)
            {
                newSubstanceBlock = CreateSubstanceBlock(newPos, substanceToCreate, movingFace);
                oldVoxel.faces[faceI].addSelected = false;
                blocked = true;
            }
            else if (pulling)
            {
                if (movingSubstance == null && newVoxel != null && newVoxel.objectEntity != null)
                {
                    // blocked by object
                    oldVoxel.faces[faceI].addSelected = false;
                    selectedThings[i] = new VoxelFaceReference(null, -1);
                    voxelsToUpdate.Add(oldVoxel);
                    continue;
                }

                for (int sideNum = 0; sideNum < 4; sideNum++)
                {
                    int   sideFaceI         = Voxel.SideFaceI(faceI, sideNum);
                    int   oppositeSideFaceI = Voxel.OppositeFaceI(sideFaceI);
                    Voxel sideVoxel         = VoxelAt(newPos + Voxel.DirectionForFaceI(sideFaceI), false);
                    if (sideVoxel == null || sideVoxel.faces[oppositeSideFaceI].IsEmpty() || movingSubstance != sideVoxel.substance)
                    {
                        // add side
                        // if possible, the new side should have the properties of the adjacent side
                        if (!oldVoxel.faces[sideFaceI].IsEmpty())
                        {
                            newVoxel.faces[sideFaceI]             = oldVoxel.faces[sideFaceI];
                            newVoxel.faces[sideFaceI].addSelected = false;
                        }
                        else
                        {
                            newVoxel.faces[sideFaceI] = movingFace;
                        }
                    }
                    else
                    {
                        // delete side
                        sideVoxel.faces[oppositeSideFaceI].Clear();
                        voxelsToUpdate.Add(sideVoxel);
                    }
                }

                Voxel blockingVoxel = VoxelAt(newPos + adjustDirection, false);
                if (blockingVoxel != null && !blockingVoxel.faces[oppositeFaceI].IsEmpty())
                {
                    if (movingSubstance == blockingVoxel.substance)
                    {
                        blocked = true;
                        blockingVoxel.faces[oppositeFaceI].Clear();
                        voxelsToUpdate.Add(blockingVoxel);
                    }
                }
                oldVoxel.faces[faceI].Clear();
            }
            else // sliding
            {
                oldVoxel.faces[faceI].addSelected = false;

                if (newVoxel.faces[faceI].IsEmpty() || newVoxel.substance != movingSubstance)
                {
                    blocked = true;
                }
            }

            if (!blocked)
            {
                // move the face
                newVoxel.faces[faceI]             = movingFace;
                newVoxel.faces[faceI].addSelected = true;
                newVoxel.substance = movingSubstance;
                selectedThings[i]  = new VoxelFaceReference(newVoxel, faceI);
            }
            else
            {
                // clear the selection; will be deleted later
                selectedThings[i] = new VoxelFaceReference(null, -1);
                if (pulling && substanceToCreate == null)
                {
                    newVoxel.substance = movingSubstance;
                }
            }

            if (newSubstanceBlock != null)
            {
                createdSubstance = true;
                if (!newSubstanceBlock.faces[adjustDirFaceI].IsEmpty())
                {
                    newSubstanceBlock.faces[adjustDirFaceI].addSelected = true;
                    selectedThings.Insert(0, new VoxelFaceReference(newSubstanceBlock, adjustDirFaceI));
                    i += 1;
                }
            }

            voxelsToUpdate.Add(newVoxel);
            voxelsToUpdate.Add(oldVoxel);

            temporarilyBlockPushingANewSubstance = false;
        } // end for each selected face

        foreach (Voxel voxel in voxelsToUpdate)
        {
            VoxelModified(voxel);
        }

        for (int i = selectedThings.Count - 1; i >= 0; i--)
        {
            Selectable thing = selectedThings[i];
            if ((thing is VoxelFaceReference) &&
                ((VoxelFaceReference)thing).voxel == null)
            {
                selectedThings.RemoveAt(i);
            }
        }
        selectionChanged = true;

        if (substanceToCreate != null && createdSubstance)
        {
            substanceToCreate = null;
        }

        AutoSetMoveAxesEnabled();
    } // end Adjust()