Skip to content

Instantly share code, notes, and snippets.

@onotchi
Last active June 11, 2017 10:36
Show Gist options
  • Save onotchi/33ccd019a6eb5d1962406aded0d6c2d5 to your computer and use it in GitHub Desktop.
Save onotchi/33ccd019a6eb5d1962406aded0d6c2d5 to your computer and use it in GitHub Desktop.
指定範囲のモデルをボクセルで生成し直す
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Onoty3D.VoxelGenerator.Scripts
{
[Serializable]
public class InsideChecker
{
//生成したボクセルに設定するレイヤー
public LayerMask TargetLayer;
//判定時に使うRayの距離
public float RayDistance = 1f;
//Rayの太さ
public float RayRadius = 0.01f;
//視覚化
[NonSerialized]
public bool Visualization = false;
//Rayを飛ばす方向
private static readonly Vector3[] DIRECTIONS = new Vector3[]
{
Vector3.right,
Vector3.left,
Vector3.up,
Vector3.down,
Vector3.forward,
Vector3.back,
};
private Ray _rayBuff = default(Ray);
private RaycastHit _hitBuff;
private Dictionary<int, int[]> _trianglesDic = new Dictionary<int, int[]>();
private Dictionary<int, List<int[]>> _subMeshTrianglesDic = new Dictionary<int, List<int[]>>();
/// <summary>
/// 対象位置がメッシュに含まれるかの判定
/// </summary>
/// <param name="point">対象位置</param>
/// <param name="color">含まれた場合、</param>
/// <returns></returns>
public bool IsInside(Vector3 point, out Color color)
{
color = Color.black;
var distance = 0f;
var hit = default(RaycastHit);
//上下左右前後からRayを飛ばして、すべてメッシュに当たれば、対象のpositionは
//メッシュの内側にあると判断する
foreach (var direction in InsideChecker.DIRECTIONS)
{
if (this.IsHit(point, direction))
{
if (this._hitBuff.distance > distance)
{
//一番直近のhit情報を確保
hit = this._hitBuff;
distance = hit.distance;
}
continue;
}
//一つでもhitしなければ、内側ではないと判断
return false;
}
//内側と判定された時、色情報を取得
var meshCollider = hit.transform.GetComponentInChildren<MeshCollider>();
var renderer = hit.transform.GetComponentInChildren<Renderer>();
if (renderer != null)
{
//未知のメッシュの場合、ポリゴン情報を取得
var id = meshCollider.GetInstanceID();
if (!this._trianglesDic.ContainsKey(id))
{
this.SetTriangleInfo(id, meshCollider);
}
//マテリアルの特定と色の取得
var material = renderer.materials[this.GetSubMeshIndex(id, hit.triangleIndex)];
var textrue = material.mainTexture as Texture2D;
if (textrue == null)
{
if (material.HasProperty("_Color"))
{
color = material.color;
}
}
else
{
var uv = hit.textureCoord;
uv.x *= textrue.width;
uv.y *= textrue.height;
color = textrue.GetPixel((int)uv.x, (int)uv.y);
}
}
return true;
}
private void SetTriangleInfo(int id, MeshCollider meshCollider)
{
this._trianglesDic[id] = meshCollider.sharedMesh.triangles;
var list = this._subMeshTrianglesDic[id] = new List<int[]>(meshCollider.sharedMesh.subMeshCount);
for (int i = 0; i < meshCollider.sharedMesh.subMeshCount; i++)
{
list.Add(meshCollider.sharedMesh.GetTriangles(i));
}
}
private bool IsHit(Vector3 point, Vector3 direction)
{
this._rayBuff.origin = point - direction * this.RayDistance;
this._rayBuff.direction = direction;
if (this.Visualization)
{
Debug.DrawRay(this._rayBuff.origin, this._rayBuff.direction * this.RayDistance, Color.red, 0.1f, false);
}
//if (Physics.Raycast(this._ray, out this._hit, Distance))
if (Physics.SphereCast(this._rayBuff, this.RayRadius, out this._hitBuff, this.RayDistance, this.TargetLayer))
{
return this._hitBuff.collider is MeshCollider;
}
else
{
return false;
}
}
private int GetSubMeshIndex(int id, int hitTriangleIndex)
{
if (this._subMeshTrianglesDic[id].Count == 1)
{
return 0;
}
var hitTriangle = new int[]
{
this._trianglesDic[id][hitTriangleIndex * 3],
this._trianglesDic[id][hitTriangleIndex * 3 + 1],
this._trianglesDic[id][hitTriangleIndex * 3 + 2]
};
for (int i = 0; i < this._subMeshTrianglesDic[id].Count; i++)
{
var subMeshTriangles = this._subMeshTrianglesDic[id][i];
for (int j = 0; j < subMeshTriangles.Length; j += 3)
{
if (subMeshTriangles[j] == hitTriangle[0]
&& subMeshTriangles[j + 1] == hitTriangle[1]
&& subMeshTriangles[j + 2] == hitTriangle[2])
{
return i;
}
}
}
return 0;
}
}
}
using System;
namespace Onoty3D.VoxelGenerator.Scripts
{
[Serializable]
public class VoxelCount
{
public int X = 10;
public int Y = 10;
public int Z = 10;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Onoty3D.VoxelGenerator.Scripts
{
public class VoxelGenerator : MonoBehaviour
{
//ボクセル判定範囲
public VoxelTargetArea TargetArea;
//内外判定クラスインスタンス
public InsideChecker InsideChecker;
//生成したボクセルに設定するレイヤー
public LayerMask VoxelLayer;
//生成したボクセルの親
public Transform Parent;
//ボクセルに付加するマテリアルのベース
public Material BaseMaterial;
//ボクセルに付加するマテリアルのシェーダーの色割当パラメータ
public string MaterialColorName = "_Color";
//視覚化
public bool Visualization = false;
//private InsideChecker _insideChecker;
private Dictionary<Color, Material> _materialDic = new Dictionary<Color, Material>();
private Transform _transform;
//ボクセルひとつのサイズ
private Vector3 _voxelScale;
//ボクセル生成エリアの始点
private Vector3 _startPosition;
//ボクセル判定場所
private Vector3 _targetPosition;
//ボクセル生成場所
private Vector3 _voxelPosition;
//ボクセルの色
private Color _voxelColor;
public void Generate()
{
this.Initialize();
if (this.Visualization)
{
StartCoroutine(GenerateCoreVisualization());
}
else
{
this.GenerateCore();
}
}
// Use this for initialization
private void Start()
{
this._transform = this.transform;
}
// Update is called once per frame
private void Update()
{
}
private void OnDrawGizmos()
{
if (this.TargetArea == null)
{
return;
}
Gizmos.color = Color.red;
Gizmos.DrawWireCube(this.transform.position, this.TargetArea.transform.localScale);
}
private void Initialize()
{
this._voxelScale = this.TargetArea.GetVoxelScale();
this._startPosition = this.TargetArea.GetStartPosion();
this._targetPosition = this.TargetArea.transform.position;
this._voxelPosition = Vector3.zero;
this._voxelColor = Color.black;
this.InsideChecker.Visualization = this.Visualization;
}
private void GenerateCore()
{
//ぐるぐる回してボクセル生成
//下から積み上げたいのでY→X→Z順でインクリメント
for (int y = 0; y < this.TargetArea.VoxelCount.Y; y++)
{
this._voxelPosition.y = this._startPosition.y + this._voxelScale.y * y;
for (int x = 0; x < this.TargetArea.VoxelCount.X; x++)
{
this._voxelPosition.x = this._startPosition.x + this._voxelScale.x * x;
for (int z = 0; z < this.TargetArea.VoxelCount.Z; z++)
{
this._voxelPosition.z = this._startPosition.z + this._voxelScale.z * z;
if (this.InsideChecker.IsInside(this._targetPosition + this._voxelPosition, out this._voxelColor))
{
this.SpawnVoxel();
}
}
}
}
}
private IEnumerator GenerateCoreVisualization()
{
WaitForSeconds wait = new WaitForSeconds(0.01f);
//ぐるぐる回してボクセル生成
//下から積み上げたいのでY→X→Z順でインクリメント
for (int y = 0; y < this.TargetArea.VoxelCount.Y; y++)
{
this._voxelPosition.y = this._startPosition.y + this._voxelScale.y * y;
for (int x = 0; x < this.TargetArea.VoxelCount.X; x++)
{
this._voxelPosition.x = this._startPosition.x + this._voxelScale.x * x;
for (int z = 0; z < this.TargetArea.VoxelCount.Z; z++)
{
this._voxelPosition.z = this._startPosition.z + this._voxelScale.z * z;
if (this.InsideChecker.IsInside(this._targetPosition + this._voxelPosition, out this._voxelColor))
{
this.SpawnVoxel();
yield return wait;
}
}
}
}
}
private void SpawnVoxel()
{
var voxel = GameObject.CreatePrimitive(PrimitiveType.Cube);
//GameObject.Destroy(voxel.GetComponent<BoxCollider>());
voxel.layer = (int)Mathf.Log(this.VoxelLayer.value, 2);
voxel.transform.position = this._transform.position + this._voxelPosition;
voxel.transform.localScale = this._voxelScale;
//voxel.AddComponent<Rigidbody>();
voxel.transform.SetParent(this.Parent);
//マテリアルセット
//同色のものは共通して使う
if (!this._materialDic.ContainsKey(this._voxelColor))
{
this._materialDic[this._voxelColor] = Instantiate(this.BaseMaterial);
this._materialDic[this._voxelColor].SetColor(this.MaterialColorName, this._voxelColor);
}
voxel.GetComponent<MeshRenderer>().material = this._materialDic[this._voxelColor];
}
}
}
using UnityEditor;
using UnityEngine;
namespace Onoty3D.VoxelGenerator.Scripts.Editor
{
[CustomEditor(typeof(VoxelGenerator))]
public class VoxelGeneratorEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
//元のInspector部分を表示
base.OnInspectorGUI();
if (GUILayout.Button("Generate"))
{
if (Application.isPlaying)
{
var generator = target as VoxelGenerator;
generator.Generate();
}
}
}
}
}
using System.Linq;
using UnityEngine;
namespace Onoty3D.VoxelGenerator.Scripts
{
public class VoxelTargetArea : MonoBehaviour
{
public VoxelCount VoxelCount;
private Transform _transform;
public Vector3 GetVoxelScale()
{
return new Vector3(
this._transform.localScale.x / (float)this.VoxelCount.X
, this._transform.localScale.y / (float)this.VoxelCount.Y
, this._transform.localScale.z / (float)this.VoxelCount.Z
);
}
public Vector3 GetStartPosion()
{
return -this._transform.localScale / 2f + GetVoxelScale() / 2f; ;
}
// Use this for initialization
private void Start()
{
this._transform = this.transform;
}
// Update is called once per frame
private void Update()
{
}
private void OnDrawGizmos()
{
if (this.VoxelCount == null)
{
return;
}
var transform = this.transform;
Gizmos.color = Color.blue;
Gizmos.DrawWireCube(transform.position, transform.localScale);
//if (Application.isPlaying)
//{
// return;
//}
var voxelScale = new Vector3(
transform.localScale.x / (float)this.VoxelCount.X
, transform.localScale.y / (float)this.VoxelCount.Y
, transform.localScale.z / (float)this.VoxelCount.Z
);
var startPosition = -this.transform.localScale / 2f + voxelScale / 2f;
var voxelPosition = Vector3.zero;
var scale = Vector3.one * 0.01f;
for (int x = 0; x < this.VoxelCount.X; x++)
{
voxelPosition.x = startPosition.x + voxelScale.x * x;
for (int y = 0; y < this.VoxelCount.Y; y++)
{
voxelPosition.y = startPosition.y + voxelScale.y * y;
for (int z = 0; z < this.VoxelCount.Z; z++)
{
voxelPosition.z = startPosition.z + voxelScale.z * z;
if (x == 0
|| y == 0
|| z == 0
|| x == this.VoxelCount.X - 1
|| y == this.VoxelCount.Y - 1
|| z == this.VoxelCount.Z - 1
)
{
Gizmos.DrawCube(transform.position + voxelPosition, scale);
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment