Exploding Voxels

Hello, I am trying to create a function that makes a voxel-based model explode.

Say, a monster is killed and now what should happen (in theory):

  1. Iterate throught the mesh’s triangles/vertices, and instantiate a simple cube gameobject (with rigidbody) at each voxel position (basically “replacing” a block with an instantiated cube).
  2. Destroying the model, and applying outward force to all the instantiated cubes.
  3. ???
  4. Profit!

I haven’t found any kind of information about such a function, so I’m aksing here if anyone has any idea how to do it. Since the models are not really (as in, generated by script) voxel-based, but rather have a voxel style (they consist of faces that all have the same dimension), I don’t know how to get the right coordinates to spawn cubes at. The models are what you know from Minecraft or 3D Dot Game Heroes (where the idea for the voxel eplosions comes from, as well):

If you have a collider, you can get the coordinates of the collision.

Ok…? I’m not sure what you mean, which collison are you talking about :slight_smile: ?

Oh sorry, I though you wanted to hit the models and apply force on the point of impact.

It looks like it just makes a light appear (so you don’t see the mesh switch off), then instantiates a mesh particle.

You can choose this in ParticleSystem > Renderer options. Do a Sphere Shape at the rough size of the mesh, with Collision set to World, high Dampening, and mid Bounce. Gravity should be low, with Start Speed at whatever you want.

You can have multiple particle systems to get more than one mesh colour.

That’s a workaround I could live with, actually. Good idea. So far, I have recreated a model inside Unity by placing cubes of the right dimension at each space that has a voxel in the model. I group the cubes by their color, and the whole thing is made a prefab and when the model explodes, it gets instantiated, every cube gets it’s color, and a rigidbody and an explosion force. Works good so far for a small model.

Just stumbled upon this thread again :slight_smile: I can now say taht I have written a voxelizer that can turn any gameobject into voxels (actually, small cubes with colliders and a seperate 3-dimensional array to store the color information of each cube.

Hi could you please describe how did you do that? This is exactly what I was looking for. Would be great if you could help me.

I will post my script later this day.

Voxelizer.cs: The mains cript. Put it on a GameObject called “Manager” (the script is only needed once). Don’t put it on the obejct you want to voxelize!

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using UnityEditor; //for PrefabUtility

public class Voxelizer : MonoBehaviour {

    public GameObject objectToVoxelize;
    public GameObject voxelizedObject;
    public float delay = 0.5f;
    Vector3 p0;
    Vector3 p1;
    private Vector3 voxelPos;
    public GameObject voxelPrefab;
    public bool waitForTime = false;
    public bool waitOneFrame = false;
    public bool fill = true;
    public float fillPercentage_s = 100f;
    public float fillPercentage_i = 100f;
    public Color[] fillColors;
    public bool createPB = true;
    public bool keepVO = true;

    // Use this for initialization
    void Start () {
        LoadVoxelPrefab();
    }
  
    // Update is called once per frame
    void Update () {
        if(Input.GetKeyDown(KeyCode.X)) {
            StartCoroutine(Voxelize(objectToVoxelize, 16, fill, fillPercentage_s, fillPercentage_i, fillColors, createPB, "Models", keepVO, true));
        }

        Manager.helper.DrawCross(p0, 0.03125f, Color.grey, 0);
        Manager.helper.DrawCross(p1, 0.03125f, Color.grey, 0);
    }

    public void LoadVoxelPrefab() {
        voxelPrefab = Resources.Load("Models/Misc/Voxel") as GameObject;
    }

    public void StartVoxelize(GameObject go, int resolution, bool fillInside, float fillPercentage_shell, float fillPercentage_inside, Color[] insideColorArray, bool createPrefab, string prefabPath, bool keepVoxelizedObject, bool useMeshName) {
        StartCoroutine(Voxelize(go, resolution, fillInside, fillPercentage_shell, fillPercentage_inside, insideColorArray, createPrefab, prefabPath, keepVoxelizedObject, useMeshName));
    }

    public IEnumerator Voxelize(GameObject go, int resolution, bool fillInside,  float fillPercentage_shell, float fillPercentage_inside, Color[] insideColorArray, bool createPrefab, string prefabPath, bool keepVoxelizedObject, bool useMeshName) {

        float voxelSize = 1f / resolution;
        float voxelSizeHalf = voxelSize / 2;



        Mesh mesh = Manager.helper.GetMesh(go);
        go.SetActive(false);

        GameObject goClone = GameObject.CreatePrimitive(PrimitiveType.Cube);
        goClone.name = go.name + "_temp";
        goClone.transform.position = go.transform.position;
        goClone.transform.rotation = go.transform.rotation;

        Collider[] colliders = goClone.GetComponents<Collider>();
        foreach(Collider collider in colliders) {
            if (Application.isPlaying) {
                Destroy(collider);
            }
            else {
                DestroyImmediate(collider);
            }
        }

        goClone.AddComponent<MeshCollider>();
        goClone.GetComponent<MeshCollider>().sharedMesh = mesh;
        goClone.GetComponent<MeshFilter>().sharedMesh = mesh;
        if(Application.isPlaying) {
            goClone.GetComponent<MeshRenderer>().material = go.GetComponent<Renderer>().material;
        }
        else {
            goClone.GetComponent<MeshRenderer>().material = go.GetComponent<Renderer>().sharedMaterial;
        }

        goClone.layer = go.layer;


        /*
        if(go.GetComponent<Rigidbody>() != null) {
            go.GetComponent<Rigidbody>().isKinematic = true;
        }
        go.GetComponent<Collider>().enabled = false;
        */


        /*
        go.SetActive(false);

        GameObject goClone = Instantiate(go, go.transform.position, go.transform.rotation) as GameObject;


        Mesh mesh = GetMesh(goClone);
        Renderer renderer = Manager.helper.GetRenderer(goClone);
      
        if(goClone.GetComponent<Rigidbody>() != null) {
            if (Application.isPlaying) {
                Destroy(goClone.GetComponent<Rigidbody>());
            }
            else {
                DestroyImmediate(goClone.GetComponent<Rigidbody>());
            }
          
        }

        Collider[] colliders = goClone.GetComponents<Collider>();
        foreach(Collider collider in colliders) {
            if (Application.isPlaying) {
                Destroy(collider);
            }
            else {
                DestroyImmediate(collider);
            }
        }

        yield return null;

        goClone.AddComponent<MeshCollider>();
        goClone.GetComponent<MeshCollider>().sharedMesh = mesh;

        goClone.SetActive(true);
        */


        Texture2D texture = null;
        if(Application.isPlaying) {
            texture = goClone.GetComponent<Renderer>().material.mainTexture as Texture2D;
        }
        else {
            texture = goClone.GetComponent<Renderer>().sharedMaterial.mainTexture as Texture2D;
        }
      
        string layerName = LayerMask.LayerToName(goClone.layer);
        LayerMask layerMask = new LayerMask();
        layerMask |= (1 << LayerMask.NameToLayer(layerName));
      
        Vector3 p0_pre = goClone.GetComponent<Renderer>().bounds.min;
        Vector3 p1_pre = goClone.GetComponent<Renderer>().bounds.max;

        //Debug.Log("p0_pre = " + p0_pre.x + " , " + p0_pre.y + " , " + p0_pre.z);
        //Debug.Log("p1_pre = " + p1_pre.x + " , " + p1_pre.y + " , " + p1_pre.z);

        Vector3 p0_pre_aligned = WorldToVoxelSpace(p0_pre, resolution, true);
        Vector3 p1_pre_aligned = WorldToVoxelSpace(p1_pre, resolution, true);
        //Debug.Log("p0_pre_aligned = " + p0_pre_aligned.x + " , " + p0_pre_aligned.y + " , " + p0_pre_aligned.z);
      
        float offset_x = 0f;
        float offset_y = 0f;
        float offset_z = 0f;
      
        if(p0_pre.x >= p0_pre_aligned.x) {
            offset_x = (p0_pre.x - p0_pre_aligned.x) * -1;
        }
        else {
            offset_x = p0_pre_aligned.x - p0_pre.x;
        }
      
        if(p0_pre.y >= p0_pre_aligned.y) {
            offset_y = (p0_pre.y - p0_pre_aligned.y) * -1;
        }
        else {
            offset_y = p0_pre_aligned.y - p0_pre.y;
        }
      
        if(p0_pre.z >= p0_pre_aligned.z) {
            offset_z = (p0_pre.z - p0_pre_aligned.z) * -1;
        }
        else {
            offset_z = p0_pre_aligned.z - p0_pre.z;
        }
      
        Vector3 offset = new Vector3(offset_x - voxelSizeHalf, offset_y - voxelSizeHalf, offset_z - voxelSizeHalf);

        //Debug.Log("offset = " + offset.x + " , " + offset.y + " , " + offset.z);
      
        goClone.transform.position = new Vector3(goClone.transform.position.x + offset.x, goClone.transform.position.y + offset.y, goClone.transform.position.z + offset.z);
      
        Vector3 p0_post = goClone.GetComponent<Renderer>().bounds.min;
        Vector3 p1_post = goClone.GetComponent<Renderer>().bounds.max;
      
        //Debug.Log("p0_post = " + p0_post.x + " , " + p0_post.y + " , " + p0_post.z);

      
        p0 = WorldToVoxelSpace(goClone.GetComponent<Renderer>().bounds.min + new Vector3(voxelSizeHalf, voxelSizeHalf, voxelSizeHalf), resolution, true);
        p1 = WorldToVoxelSpace(goClone.GetComponent<Renderer>().bounds.max - new Vector3(voxelSizeHalf, voxelSizeHalf, voxelSizeHalf), resolution, true);
        //Debug.Log("p0 = " + p0.x + " , " + p0.y + " , " + p0.z);
        //Debug.Log("p1 = " + p1.x + " , " + p1.y + " , " + p1.z);
      
        GameObject go_vox_root_prefab = Resources.Load("Models/Misc/EmptyGameObject") as GameObject;
        GameObject go_vox_root = Instantiate(go_vox_root_prefab, goClone.transform.position, goClone.transform.rotation) as GameObject;
        if(useMeshName == true) {
            go_vox_root.name = mesh.name + "_voxelized";
        }
        else {
            go_vox_root.name = go.name + "_voxelized";
        }

      
      
        Vector3 p0_voxelSpace = VoxelToLocalVoxelSpace(p0, resolution, Vector3.zero);
        Vector3 p1_voxelSpace = VoxelToLocalVoxelSpace(p1, resolution, Vector3.zero);
      
        //Debug.Log("p0_voxelSpace = " + p0_voxelSpace.x + " , " + p0_voxelSpace.y + " , " + p0_voxelSpace.z);
        //Debug.Log("p1_voxelSpace = " + p1_voxelSpace.x + " , " + p1_voxelSpace.y + " , " + p1_voxelSpace.z);
      
        int length_0 = (int)(p1_voxelSpace.x - p0_voxelSpace.x) + 1;
        int length_1 = (int)(p1_voxelSpace.y - p0_voxelSpace.y) + 1;
        int length_2 = (int)(p1_voxelSpace.z - p0_voxelSpace.z) + 1;

        //Debug.Log("length_0 = " + length_0 + ", length_1 = " + length_1 + ", length_2 = " + length_2);

        go_vox_root.AddComponent<VoxelObject>();
        go_vox_root.GetComponent<VoxelObject>().voxelSpaceArray = new VoxelSpace[length_0,length_1,length_2];
        VoxelSpace[,,] voxelSpaceArray = go_vox_root.GetComponent<VoxelObject>().voxelSpaceArray;
      
        int x = 0;
        int y = 0;
        int z = 0;

        for(x = 0; x < voxelSpaceArray.GetLength(0); x++) {
            for(y = 0; y < voxelSpaceArray.GetLength(1); y++) {
                for(z = 0; z < voxelSpaceArray.GetLength(2); z++) {
                    voxelSpaceArray[x,y,z] = new VoxelSpace();
                }
            }
        }

        //Register outer voxels
        for(x = 0; x < voxelSpaceArray.GetLength(0); x++) {
            for(y = 0; y < voxelSpaceArray.GetLength(1); y++) {
                for(z = 0; z < voxelSpaceArray.GetLength(2); z++) {
                    Vector3 voxelWorldPos = new Vector3(p0.x + x * voxelSize, p0.y + y * voxelSize, p0.z + z * voxelSize);

                    Manager.helper.DrawCross(voxelWorldPos, voxelSizeHalf / 4, Color.blue, delay);
                    /*
                    Vector3 voxelCorner_000 = new Vector3(p0.x + x * voxelSize - voxelSizeHalf, p0.y + y * voxelSize - voxelSizeHalf, p0.z + z * voxelSize - voxelSizeHalf);
                    Vector3 voxelCorner_001 = new Vector3(p0.x + x * voxelSize - voxelSizeHalf, p0.y + y * voxelSize - voxelSizeHalf, p0.z + z * voxelSize + voxelSizeHalf);
                    Vector3 voxelCorner_010 = new Vector3(p0.x + x * voxelSize - voxelSizeHalf, p0.y + y * voxelSize + voxelSizeHalf, p0.z + z * voxelSize - voxelSizeHalf);
                    Vector3 voxelCorner_011 = new Vector3(p0.x + x * voxelSize - voxelSizeHalf, p0.y + y * voxelSize + voxelSizeHalf, p0.z + z * voxelSize + voxelSizeHalf);
                    Vector3 voxelCorner_100 = new Vector3(p0.x + x * voxelSize + voxelSizeHalf, p0.y + y * voxelSize - voxelSizeHalf, p0.z + z * voxelSize - voxelSizeHalf);
                    Vector3 voxelCorner_101 = new Vector3(p0.x + x * voxelSize + voxelSizeHalf, p0.y + y * voxelSize - voxelSizeHalf, p0.z + z * voxelSize + voxelSizeHalf);
                    Vector3 voxelCorner_110 = new Vector3(p0.x + x * voxelSize + voxelSizeHalf, p0.y + y * voxelSize + voxelSizeHalf, p0.z + z * voxelSize - voxelSizeHalf);
                    Vector3 voxelCorner_111 = new Vector3(p0.x + x * voxelSize + voxelSizeHalf, p0.y + y * voxelSize + voxelSizeHalf, p0.z + z * voxelSize + voxelSizeHalf);
                    */

                    Manager.helper.DrawCube(voxelWorldPos, voxelSize, Color.magenta, delay);

                    /*
                    Vector3 ray_z_positive_origin = voxelWorldPos - Vector3.forward * voxelSize;
                    Vector3 ray_z_positive_dir = Vector3.forward;
                    Ray ray_z_positive = new Ray(ray_z_positive_origin, ray_z_positive_dir);
                    DrawCross(ray_z_positive_origin, voxelSizeHalf / 4, Color.green, delay);
                    //Debug.DrawRay(ray_z_positive_origin, ray_z_positive_dir, Color.green, delay);
                    RaycastHit ray_z_positive_hit = new RaycastHit();
                    if(Physics.Raycast(ray_z_positive_origin, ray_z_positive_dir, out ray_z_positive_hit, voxelSize, layerMask)) {
                        Debug.DrawLine(ray_z_positive_origin, ray_z_positive_hit.point, Color.red, delay);

                        //Center of the voxelspace that was hit
                        Vector3 insideHit_aligned_center = WorldToVoxelSpace(ray_z_positive_hit.point + ray_z_positive_dir * voxelSizeHalf, resolution, true);
                        //Debug.Log("insideHit_aligned_center: " + insideHit_aligned_center.x + " , " + insideHit_aligned_center.y + " , " + insideHit_aligned_center.z);

                        //the xyz position of the voxel that was hit, in GLOBAL voxelspace
                        Vector3 insideHit_voxelSpace = VoxelToLocalVoxelSpace(insideHit_aligned_center, resolution, Vector3.zero);
                        //Debug.Log("insideHit_voxelSpace: " + insideHit_voxelSpace.x + " , " + insideHit_voxelSpace.y + " , " + insideHit_voxelSpace.z);

                        //the voxelspace xyz position in LOCAL voxelspace
                        Coordinate voxelPos = new Coordinate((int)(insideHit_voxelSpace.x - p0_voxelSpace.x), (int)(insideHit_voxelSpace.y - p0_voxelSpace.y), (int)(insideHit_voxelSpace.z - p0_voxelSpace.z));
                        //Debug.Log("voxelPos: " + voxelPos.x + " , " + voxelPos.y + " , " + voxelPos.z);

                        VoxelSpace voxelSpace = voxelSpaceArray[voxelPos.x, voxelPos.y, voxelPos.z];
                        voxelSpace.hit_s = true;

                        if(voxelSpace.filled == false) {
                            voxelSpace.filled = true;
                            Vector2 pixelUV = ray_z_positive_hit.textureCoord;
                            pixelUV.x *= texture.width;
                            pixelUV.y *= texture.height;
                            Color color = texture.GetPixel((int)pixelUV.x, (int)pixelUV.y);
                            CreateVoxelInArray(voxelSpace, insideHit_aligned_center, color, resolution, go_vox_root.transform);
                        }
                    }
                    */

                    Vector3[] ray_origin_array = new Vector3[] {
                        voxelWorldPos - Vector3.forward * voxelSize,
                        voxelWorldPos + Vector3.forward * voxelSize,
                        voxelWorldPos - Vector3.right * voxelSize,
                        voxelWorldPos + Vector3.right * voxelSize,
                        voxelWorldPos - Vector3.up * voxelSize,
                        voxelWorldPos + Vector3.up * voxelSize
                    };

                    Vector3[] ray_dir_array = new Vector3[] {
                        Vector3.forward,
                        -Vector3.forward,
                        Vector3.right,
                        -Vector3.right,
                        Vector3.up,
                        -Vector3.up
                    };




                    int i = 0;
                    for(i = 0; i < 6; i++) {
                        Manager.helper.DrawCross(ray_origin_array[i], voxelSizeHalf / 4, Color.green, delay);
                        //Debug.DrawRay(ray_origin_array[i], ray_dir_array[i], Color.green, delay);
                        Ray ray = new Ray(ray_origin_array[i], ray_dir_array[i]);
                        RaycastHit hit = new RaycastHit();


                        RaycastHit[] hitArray = Physics.RaycastAll(ray, voxelSize, layerMask).OrderBy(h=>h.distance).ToArray();
                        bool hasHit = false;
                        foreach(RaycastHit hitCur in hitArray) {
                            if(hitCur.collider.gameObject == goClone) {
                                hasHit = true;
                                hit = hitCur;
                                break;
                            }
                        }


                        //if(Physics.Raycast(ray, out hit, voxelSize, layerMask)) {
                        if(hasHit == true) {
                            Debug.DrawLine(ray.origin, hit.point, Color.red, delay);

                            //Center of the voxelspace that was hit
                            Vector3 insideHit_aligned_center = WorldToVoxelSpace(hit.point + ray.direction * voxelSizeHalf, resolution, true);
                            //Debug.Log("insideHit_aligned_center: " + insideHit_aligned_center.x + " , " + insideHit_aligned_center.y + " , " + insideHit_aligned_center.z);
                          
                            //the xyz position of the voxel that was hit, in GLOBAL voxelspace
                            Vector3 insideHit_voxelSpace = VoxelToLocalVoxelSpace(insideHit_aligned_center, resolution, Vector3.zero);
                            //Debug.Log("insideHit_voxelSpace: " + insideHit_voxelSpace.x + " , " + insideHit_voxelSpace.y + " , " + insideHit_voxelSpace.z);
                          
                            //the voxelspace xyz position in LOCAL voxelspace
                            Coordinate voxelPos = new Coordinate((int)(insideHit_voxelSpace.x - p0_voxelSpace.x), (int)(insideHit_voxelSpace.y - p0_voxelSpace.y), (int)(insideHit_voxelSpace.z - p0_voxelSpace.z));
                            //Debug.Log("voxelPos: " + voxelPos.x + " , " + voxelPos.y + " , " + voxelPos.z);
                          
                            VoxelSpace voxelSpace = voxelSpaceArray[voxelPos.x, voxelPos.y, voxelPos.z];
                            if(i == 0) {voxelSpace.hit_s = true;}
                            else if(i == 1) {voxelSpace.hit_n = true;}
                            else if(i == 2) {voxelSpace.hit_w = true;}
                            else if(i == 3) {voxelSpace.hit_e = true;}
                            else if(i == 4) {voxelSpace.hit_b = true;}
                            else if(i == 5) {voxelSpace.hit_t = true;}

                            if(voxelSpace.filled == false) {
                                voxelSpace.filled = true;

                                if(fillPercentage_shell > 0) {
                                    if(UnityEngine.Random.Range(0f, 100f) <= fillPercentage_shell) {
                                        Vector2 pixelUV = hit.textureCoord;
                                        pixelUV.x *= texture.width;
                                        pixelUV.y *= texture.height;
                                        Color color = texture.GetPixel((int)pixelUV.x, (int)pixelUV.y);
                                        CreateVoxelInArray(voxelSpace, insideHit_aligned_center, color, resolution, go_vox_root.transform);
                                    }
                                }
                            }
                        }
                    }
                    if(waitForTime == true) {
                        if(delay > 0) {
                            yield return new WaitForSeconds(delay);
                        }
                    }

                    else if(waitOneFrame == true) {
                        yield return null;
                    }
                }
            }
        }

        //Fill in inner voxels
        if(fillInside == true) {
            for(x = 0; x < voxelSpaceArray.GetLength(0); x++) {
                for(y = 0; y < voxelSpaceArray.GetLength(1); y++) {
                    for(z = 0; z < voxelSpaceArray.GetLength(2); z++) {

                        if(waitForTime == true) {
                            if(delay > 0) {
                                yield return new WaitForSeconds(delay);
                            }
                        }
                      
                        else if(waitOneFrame == true) {
                            yield return null;
                        }

                        VoxelSpace voxelSpace = voxelSpaceArray[x,y,z];
                        Vector3 voxelWorldPos = new Vector3(p0.x + x * voxelSize, p0.y + y * voxelSize, p0.z + z * voxelSize);
                        Manager.helper.DrawCube(voxelWorldPos, voxelSize, Color.magenta, delay);

                        if(voxelSpace.filled == false) {
                            continue;
                        }
                        if(z + 1 >= voxelSpaceArray.GetLength(2)) {
                            continue;
                        }
                        int i = 0;
                        int toFill = 0;
                        for(i = z + 1; i < voxelSpaceArray.GetLength(2); i++) {
                            if(i == z + 1 && voxelSpaceArray[x,y,i].filled == true) {
                                break;
                            }
                            toFill ++;
                            if(i == voxelSpaceArray.GetLength(2) - 1 && voxelSpaceArray[x,y,i].filled == false) {
                                toFill = 0;
                                break;
                            }
                            if(voxelSpaceArray[x,y,i].filled == true) {
                                break;
                            }
                        }

                        if(toFill > 0) {
                            for(i = z + 1; i < z + 1 + toFill; i++) {

                                Vector3 voxelWorldPos_temp = new Vector3(p0.x + x * voxelSize, p0.y + y * voxelSize, p0.z + i * voxelSize);
                                Manager.helper.DrawCube(voxelWorldPos_temp, voxelSize, Color.yellow, delay);

                                Vector3 pos = new Vector3(p0.x + x * voxelSize, p0.y + y * voxelSize, p0.z + i * voxelSize);
                                if(voxelSpaceArray[x,y,i].filled == false) {
                                    if(fillPercentage_inside > 0) {
                                        if(UnityEngine.Random.Range(0f, 100f) > fillPercentage_inside) {
                                            continue;
                                        }
                                    }
                                    else {
                                        continue;
                                    }

                                    Color insideColor = Color.white;
                                    if(insideColorArray.Length > 0) {
                                        if(insideColorArray.Length == 1) {
                                            insideColor = insideColorArray[0];
                                        }
                                        else {
                                            insideColor = insideColorArray[UnityEngine.Random.Range (0, insideColorArray.Length)];
                                        }
                                    }
                                    CreateVoxelInArray(voxelSpaceArray[x,y,i], pos, insideColor, resolution, go_vox_root.transform);
                                }

                                if(waitForTime == true) {
                                    if(delay > 0) {
                                        yield return new WaitForSeconds(delay);
                                    }
                                }
                            }
                            break;
                        }

                    }
                }
            }
        }

        go_vox_root.transform.parent = goClone.transform;
        goClone.transform.position = go.transform.position;
        go_vox_root.transform.parent = null;

        if (Application.isPlaying) {
            Destroy(goClone);
        }
        else {
            DestroyImmediate(goClone);
        }
      


        go.SetActive(true);
        /*
        go.GetComponent<Collider>().enabled = true;//Not neccessary anymore since RayCastAll is used to get multiple hits on teh same layer, and then all hits are checked if they belong to goClone
        if(go.GetComponent<Rigidbody>()) {
            go.GetComponent<Rigidbody>().isKinematic = false;//Not neccessary anymore since RayCastAll is used to get multiple hits on teh same layer, and then all hits are checked if they belong to goClone
        }
        */



        go_vox_root.AddComponent<MeshFilter>();
        go_vox_root.GetComponent<MeshFilter>().mesh = mesh;

        voxelizedObject = go_vox_root;
        voxelizedObject.GetComponent<VoxelObject>().SerializeVoxelSpaceArray();

        if(createPrefab == true) {
            PrefabUtility.CreatePrefab("Assets/Resources/" + prefabPath + "/" + go_vox_root.name + ".prefab", voxelizedObject, ReplacePrefabOptions.ReplaceNameBased);
        }

        if(keepVoxelizedObject == false) {
            if (Application.isPlaying) {
                Destroy(go_vox_root);
            }
            else {
                DestroyImmediate(go_vox_root);
            }
        }
        else {
            voxelizedObject.GetComponent<VoxelObject>().AssignColors();
        }

        yield return null;
    }



    public Vector3 WorldToVoxelSpace(Vector3 worldPos, int resolution, bool center) {
        return WorldToVoxelSpace(worldPos.x, worldPos.y, worldPos.z, resolution, center);
    }
    public Vector3 WorldToVoxelSpace(float worldPos_x, float worldPos_y, float worldPos_z, int resolution, bool center) {
        return new Vector3(WorldToVoxelSpace(worldPos_x, resolution, center), WorldToVoxelSpace(worldPos_y, resolution, center), WorldToVoxelSpace(worldPos_z, resolution, center));
    }
    public float WorldToVoxelSpace(float worldPos, int resolution, bool center) {
        float voxelSize = 1f / resolution;
        float offset = 0;
        if(center == true) {
            offset = voxelSize / 2;
        }
        float voxelSizeHalf = voxelSize / 2;
        return (float)(Math.Round((worldPos + voxelSizeHalf) * resolution) / (float)resolution) - offset;
    }
  
    public Vector3 VoxelToLocalVoxelSpace(Vector3 voxelSpacePos, int resolution, Vector3 pos_min) {
        return VoxelToLocalVoxelSpace(voxelSpacePos.x, voxelSpacePos.y, voxelSpacePos.z, resolution, pos_min);
    }
    public Vector3 VoxelToLocalVoxelSpace(float voxelSpacePos_x, float voxelSpacePos_y, float voxelSpacePos_z, int resolution, Vector3 pos_min) {
        return new Vector3(VoxelToLocalVoxelSpace(voxelSpacePos_x, resolution, pos_min.x), VoxelToLocalVoxelSpace(voxelSpacePos_y, resolution, pos_min.y),VoxelToLocalVoxelSpace(voxelSpacePos_z, resolution, pos_min.z));
    }
    public float VoxelToLocalVoxelSpace(float pos, int resolution, float pos_min) {
        float voxelSize = 1f / resolution;
        return Mathf.Floor(pos / voxelSize);
    }
  
  
    public void CreateVoxelInArray(VoxelSpace voxelSpace, Vector3 pos, Color color, int resolution, Transform parent) {
      
        float voxelSize = 1f / resolution;
        voxelSpace.pos = pos;
        voxelSpace.filled = true;
        voxelSpace.color = color;
        //voxelSpace.voxel = GameObject.CreatePrimitive(PrimitiveType.Cube);
        voxelSpace.voxel = Instantiate(voxelPrefab, pos, Quaternion.identity) as GameObject;
        voxelSpace.voxel.layer = LayerMask.NameToLayer("Voxel");
        voxelSpace.voxel.transform.localScale = new Vector3(voxelSize, voxelSize, voxelSize);
        //voxelSpace.voxel.GetComponent<MeshRenderer>().material.color = color;
        voxelSpace.voxel.transform.parent = parent;
    }
  
    public void CreateVoxelInArray(VoxelSpace voxelSpace, Vector3 pos, Vector2 textureCoords, int resolution, Transform parent) {
      
        float voxelSize = 1f / resolution;
        voxelSpace.pos = pos;
        voxelSpace.filled = true;
        voxelSpace.color = Color.red;
        //voxelSpace.voxel = GameObject.CreatePrimitive(PrimitiveType.Cube);
        voxelSpace.voxel = Instantiate(voxelPrefab, pos, Quaternion.identity) as GameObject;
        //voxelSpace.voxel.layer = LayerMask.NameToLayer("Voxel");
        voxelSpace.voxel.transform.localScale = new Vector3(voxelSize, voxelSize, voxelSize);
      
        voxelSpace.voxel.transform.parent = parent;
      
        Mesh mesh = voxelSpace.voxel.GetComponent<MeshFilter>().sharedMesh;
        Vector2[] uvs = new Vector2[mesh.uv.Length];
        for(int i = 0; i < uvs.Length; i++) {
            uvs[i] = textureCoords;
        }
        mesh.uv = uvs;
        voxelSpace.voxel.GetComponent<MeshFilter>().sharedMesh = mesh;
    }
}

VoxelObject.cs: This will get added to the voxelized object by script, so you don’t have to add it manually. Just Put the file somewhere in your Assets folder.

using UnityEngine;
using System.Collections;

public class VoxelObject : MonoBehaviour {

    public VoxelSpace[,,] voxelSpaceArray;

    public X[] voxelSpaceArray2;

    void Start() {
        if(voxelSpaceArray2 != null) {
            AssignColors();
        }

    }

    public void AssignColors() {
        if(voxelSpaceArray2 == null) {
            return;
        }

        for(int x = 0; x < voxelSpaceArray2.Length; x++) {
            for(int y = 0; y < voxelSpaceArray2[x].y.Length; y++) {
                for(int z = 0; z < voxelSpaceArray2[x].y[y].z.Length; z++) {
                    VoxelSpace voxelSpace = voxelSpaceArray2[x].y[y].z[z];
                    GameObject voxel = voxelSpace.voxel;
                    if(voxel == null) {
                        continue;
                    }
                    if(Application.isPlaying == true) {
                        voxel.GetComponent<Renderer>().material.color = voxelSpace.color;
                    }
                    else {
                        voxel.GetComponent<Renderer>().sharedMaterial.color = voxelSpace.color;
                    }

                }
            }
        }
    }

    public void SerializeVoxelSpaceArray() {
        if(voxelSpaceArray == null) {
            return;
        }

        int x = 0;
        int y = 0;
        int z = 0;

        voxelSpaceArray2 = new X[voxelSpaceArray.GetLength(0)];
        for(x = 0; x < voxelSpaceArray.GetLength(0); x++) {
            voxelSpaceArray2[x] = new X();
            voxelSpaceArray2[x].y = new Y[voxelSpaceArray.GetLength(1)];
            for(y = 0; y < voxelSpaceArray.GetLength(1); y++) {
                voxelSpaceArray2[x].y[y] = new Y();
                voxelSpaceArray2[x].y[y].z = new VoxelSpace[voxelSpaceArray.GetLength(2)];
                for(z = 0; z < voxelSpaceArray.GetLength(2); z++) {

                    voxelSpaceArray2[x].y[y].z[z] = new VoxelSpace();
                    VoxelSpace voxelSpace = voxelSpaceArray[x,y,z];

                    voxelSpaceArray2[x].y[y].z[z] = new VoxelSpace();
                    VoxelSpace voxelSpace2 = voxelSpaceArray2[x].y[y].z[z];
                    //voxelSpace2 = voxelSpace;//don't call this; it will erase all references after the loops are finished; Assign references by hand further down below
                    if(voxelSpace.voxel == null) {
                        continue;
                    }

                    voxelSpace2.voxel = voxelSpace.voxel;
                    voxelSpace2.color = voxelSpace.color;
                }
            }
        }
    }

}
[System.Serializable]
public class X {
    public Y[] y;

    public X() {
        y = new Y[1];
    }

}
[System.Serializable]
public class Y {
    public VoxelSpace[] z;

    public Y() {
        z = new VoxelSpace[1];
    }
}

[System.Serializable]
public class VoxelSpace {
  
    public Vector3 pos;
    public bool filled = false;
    public bool hit_t;
    public bool hit_n;
    public bool hit_e;
    public bool hit_s;
    public bool hit_w;
    public bool hit_b;
    public GameObject voxel;
    public Color color;
}

VoxelizerWindow.cs: Editor Script. Put it into your Editor folder. You can open the window with Windows/Voxelizer. Use it to actually voxelize an object.

createPrefab: Should a prefab of the voxelized object be created?
keepVoxelizedObject: Should the voxelized obejct be destroyed after voxelizing (useful if you just want to create a prefab without needing the voxelized object in your scene right now)
fillInside: Should the inside of the voxelized object be filled with voxels, too? (Performance heavy depending on the size of the object). If no, then only the outer shell will be made out of voxels.
fillPercentage_shell: What percentage of the outer voxels will actually be created.
fillPercentage_inside: If the inside is filled, what percentage of the inner voxels will be created.

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;

class VoxelizerWindow : EditorWindow {
    [MenuItem ("Window/Voxelizer")]
  
    public static void  ShowWindow () {
        EditorWindow.GetWindow(typeof(VoxelizerWindow), false, "Voxelizer", true);
    }
  
    private int buttonSize = 32;
    private Vector2 scrollPos_window = Vector2.zero;

    private GameObject selectedObject;
    private Voxelizer voxelizer;
    private bool createPrefab = true;
    private string prefabPath = "Models";
    private bool keepVoxelizedObject = false;
    private bool useMeshName = true;
    private bool fillInside = true;
    private float fillPercentage_shell = 100;
    private float fillPercentage_inside = 100;

  
    void Update() {
        if(Selection.activeObject != null) {
            selectedObject = (GameObject)Selection.activeObject;
            if(selectedObject.GetComponent<VoxelObject>()) {
            }
        }
        else {
            selectedObject = null;
        }
    }
  
    public void OnInspectorUpdate()    {
      
        // This will only get called 10 times per second.
        Repaint();
      
    }

    void OnFocus() {
        GameObject managerObject = GameObject.Find("Manager");
        if(managerObject.GetComponent<Voxelizer>()) {
            voxelizer = managerObject.GetComponent<Voxelizer>();
        }
        else {
            Debug.Log("Can't find Voxelizer component on Manager!");
        }

    }
  
    void OnGUI () {



        if(voxelizer == null) {
            GUILayout.Label("Voxelizer not found.", EditorStyles.boldLabel);
            return;
        }

        GUILayout.BeginHorizontal();
        GUILayout.FlexibleSpace();

        string selectedGameObjectName = "No GameObject selected.";
        if(selectedObject!= null) {
            selectedGameObjectName = Selection.activeGameObject.name;
        }

        GUILayout.Label(selectedGameObjectName, EditorStyles.boldLabel);

        GUILayout.FlexibleSpace();
        GUILayout.EndHorizontal();

        GUILayout.Box("", new GUILayoutOption[]{GUILayout.ExpandWidth(true), GUILayout.Height(1)});

        voxelizer.voxelPrefab = EditorGUILayout.ObjectField("Voxel Prefab", voxelizer.voxelPrefab, typeof(GameObject), true, GUILayout.ExpandWidth(true)) as GameObject;
        if(GUILayout.Button(new GUIContent("Load Voxel Prefab", ""), GUILayout.Width(buttonSize * 4))) {
            voxelizer.LoadVoxelPrefab();
        }

        createPrefab = EditorGUILayout.BeginToggleGroup( new GUIContent("Create Prefab", ""), createPrefab);
        prefabPath = EditorGUILayout.TextField("Prefab Path", prefabPath);
        EditorGUILayout.EndToggleGroup();


        if(GUILayout.Toggle(keepVoxelizedObject, new GUIContent("Keep Voxelized Object", ""), GUILayout.Width(buttonSize * 6)) != keepVoxelizedObject) {
            Event.current.Use();
            keepVoxelizedObject = !keepVoxelizedObject;
        }
        if(GUILayout.Toggle(useMeshName, new GUIContent("Use Mesh Name", ""), GUILayout.Width(buttonSize * 6)) != useMeshName) {
            Event.current.Use();
            useMeshName = !useMeshName;
        }

        if(GUILayout.Toggle(fillInside, new GUIContent("Fill Inside", ""), GUILayout.Width(buttonSize * 6)) != fillInside) {
            Event.current.Use();
            fillInside = !fillInside;
        }
        fillPercentage_shell = EditorGUILayout.FloatField("Fill Percentage (Shell)", fillPercentage_shell);
        fillPercentage_inside = EditorGUILayout.FloatField("Fill Percentage (Inside)", fillPercentage_inside);

        GUILayout.Box("", new GUILayoutOption[]{GUILayout.ExpandWidth(true), GUILayout.Height(1)});

        if(GUILayout.Button(new GUIContent("Voxelize!", ""), GUILayout.Width(buttonSize * 4))) {
            if(voxelizer.voxelPrefab == null) {
                Debug.Log("voxelizer.voxelPrefab is null!");
                return;
            }
            voxelizer.StartVoxelize(selectedObject, 16, fillInside, fillPercentage_shell, fillPercentage_inside, new Color[]{Color.red}, createPrefab, prefabPath, keepVoxelizedObject, useMeshName);
        }

        scrollPos_window = EditorGUILayout.BeginScrollView(scrollPos_window);
      
      
      
        EditorGUILayout.EndScrollView();
    }

}
1 Like

Thank you very much for sharing your scripts and sorry for the late reply. :slight_smile:

Hi, the last 2 weeks I was on vacation with no access to my PC. Now I’m trying your scripts and it works so far, except one thing. I’m a little bit confused about the Manager class in the Voxelizer script. Sorry for the silly question but I don’t know how I should use and access this Manager class. Nevertheless thank you for your help and for sharing your work.

I see… Well, the script is integrated into my project so you have to modify it a bit. Basically you have to:

  1. Create GameObject that has the name “Manager”, and put the Voxelizer.cs script on it.
  2. Check the sripts I posted and for any mention of the word “Manager”. Apart from the mangerObject variable in the editor extension script, this always refers to a static Manager class I use. When DrawCross or DrawCube is called, this is just for debugging to draw lines in the editor view; it’s not really needed but helps visualize the process. These lines can be commented out or you call your own method that draws lines (via Debug.Drawray or Debug.DrawRay, for example). On one occasion, a method called GetMesh is called; this just returns a mesh of a GameObject, so you can easily adapt it to your own needs, for example by using go.GetComponent().mesh.

Thank you it works now. Your scripts are awesome and are a good starting point. Many thanks :slight_smile:

Sorry to revive a dead thread but your script seems to be exactly what I was looking for but it doesn’t seem to be working for me. I created the gameobject and attached the voxelizer script like instructed as well as placed the VoxelizerWindow script in the Editor folder, however the voxelizer does not appear as a drop down under Window. Here are errors I’m getting that may or may not be related:

Assets/Voxelizer.cs(37,17): error CS0103: The name `Manager’ does not exist in the current context

Assets/Voxelizer.cs(38,17): error CS0103: The name `Manager’ does not exist in the current context

Assets/Voxelizer.cs(345,57): error CS0246: The type or namespace name `Coordinate’ could not be found. Are you missing a using directive or an assembly reference?

I should mention that this is all in an otherwise empty project made solely to test this. Any help would be very much appreciated:)

Please read the last post I wrote. It explains that the DrawCube dunctions are not neccessary to the operation, they are just for Debugging purposes.
Coordinate is a custom class that is basically a Vector3 with ints instead of floats.

public class Coordinate {
     public int x;
     public int y;
     public int z;
     public Coordinate(){}
      public Coordinate(int x1, int y1, int z1){
          x = x1;
          y = y1;
          z = z1;
      }
}

Thanks and that solved those errors, however now these errors are popping up:

Assets/Voxelizer.cs(493,63): error CS0103: The name mesh' does not exist in the current context Assets/Voxelizer.cs(200,44): error CS0103: The name mesh’ does not exist in the current context
Assets/Voxelizer.cs(76,65): error CS0103: The name mesh' does not exist in the current context Assets/Voxelizer.cs(75,67): error CS0103: The name mesh’ does not exist in the current context

Upon commenting these lines out, The Voxelizer shows up but when I try to voxelize an object, returns this error:

NullReferenceException: Object reference not set to an instance of an object
Voxelizer+c__Iterator0.MoveNext () (at Assets/Voxelizer.cs:138)
UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
Voxelizer:StartVoxelize(GameObject, Int32, Boolean, Single, Single, Color[ ], Boolean, String, Boolean, Boolean) (at Assets/Voxelizer.cs:46)
VoxelizerWindow:OnGUI() (at Assets/Editor/VoxelizerWindow.cs:115)
UnityEditor.DockArea:OnGUI()

I apologize if I’m making a rookie mistake here, it could very well be a fault on my part.

I didn’t say that all lines with “Manager” in them can be commented out. Only those with the Debug functions, like DrawCube.
You removed the line that delcares the variable “mesh”. Please read what I wrote again (as I told you before), the answers are all there.

Right, and I have read through it multiple times, I only tried commenting those lines out because unity wasn’t processing the line that declares mesh(the line with GetMesh). I know you said it can be adapted, however I can’t figure out how to do so. Again, I know i must seem like a complete moron, so feel free to ignore this post, just a rookie trying to learn the ropes here.

Mesh mesh = go.GetComponent<MeshRenderer>().mesh;