すくまりのメモ帳すくまりのメモ帳
← 記事一覧に戻る

【Unity, Teddy, 3Dモデリング】Teddy をプログラミングなしで使ってみた

2018-10-27
【Unity, Teddy, 3Dモデリング】Teddy をプログラミングなしで使ってみた

はじめに

この記事ではUnityのAssetであるTeddyを使い方を紹介します。

Teddyの挿入

Teddyをアセットストアからインポートしたら、

GameObject > Teddy を選択してください。

すると、Hierarchy に「Teddy」が生成されて、

Scene に「Creating a Freeform Object」ウインドウが表示されます。

後はマウスをドラッグして絵を書くだけです。

Teddyの操作方法

基本的な操作は公式のチュートリアルに書いてあるので、

映像でざっくりと説明します。

オブジェクトの生成

操作はドラッグのみです。

# of faces: 

立体化されたメッシュ面の数です。

これが増えるとより滑らかになりますが、CPUの負荷が大きくなるので、最低値で十分だと思います。

Smooth

生成したオブジェクトの面取りを行います。

thickness : 

生成したオブジェクトの厚みを変えます。

オブジェクトのカッティング

Cut ボタンを押してからドラッグするだけです。

オブジェクトのペイント

オブジェクトを選択して色を選び、ドラッグで描けます。

公式API

      // Create 3D Mesh from noise applied circumference.
 
                using System.Collections;
                using System.Collections.Generic;
 
                using UnityEngine;
 
                using Teddy;
 
                public class Example : MonoBehaviour {
                    public float radius = 1f;
                	void Start () {
                        var points = new List<Vector2>();
                        var pi2 = Mathf.PI * 2f;
                        for(float r = 0f; r < pi2; r += (pi2 / 20f)) {
                            var x = Mathf.Cos(r);
                            var y = Mathf.Sin(r);
                            points.Add((new Vector2(x, y)).normalized * (1.0f + radius * Mathf.PerlinNoise(r, 0f)));
                        }
                        var filter = gameObject.AddComponent<MeshFilter>();
                        filter.sharedMesh = TeddyUtility.CreateMesh(points, 600, 0.85f);
                	}
                }
                

http://uniteddy.info/ja/api/

私の場合、これをそのままアタッチしても動かすことができませんでした。

とりあえず、Demoシーンを見てみたいを思います。

TeddyRuntimeDemo.cs
using UnityEngine;
using UnityEngine.EventSystems;
 
using System;
using System.Linq;
using System.Collections;
//List用
using System.Collections.Generic;
 
using Teddy;
 
namespace TeddyDemo
{
 
    public class TeddyRuntimeDemo : MonoBehaviour {
 
        public enum OperationMode
        {
            Default,
            Draw,
            Move,
            Cut
        };
 
        [SerializeField] new Camera camera;
        [SerializeField] Shader lineShader;
        [SerializeField] GameObject prefab;
        [SerializeField] bool simulate;
 
        Material lineMat;
 
        OperationMode mode;
 
        List<Vector2> screenpoints;
        List<Vector3> points;
        List<Puppet> puppets;
 
        Puppet selected;
        Vector3 origin;
        Vector3 startPoint;
 
        const float zOffset = 10f;
 
        void Start () {
            //カメラが無いなら追加
            if(camera == null) {
                camera = Camera.main;
            }
 
            screenpoints = new List<Vector2>();
            points = new List<Vector3>();
            puppets = new List<Puppet>();
            lineMat = new Material(lineShader);
        }
 
        void Update () {
            //ボタンなどのuGUIをクリックしたときは画面クリックを無視する
            if (EventSystem.current.IsPointerOverGameObject()) return;
 
            //マウスの座標を取得
            var screen = Input.mousePosition;
 
 
            switch (mode)
            {
 
                case OperationMode.Default:
 
                    if (Input.GetMouseButtonDown(0))
                    {
                        //Listを全て消去
                        points.Clear();
						screenpoints.Clear();
 
                        // メインカメラからクリックしたポジションに向かってRayを撃つ。
                        var ray = camera.ScreenPointToRay(screen);
 
                        RaycastHit hit;
 
                        //Rayとオブジェクトが衝突していないかを調べる
                        if (Physics.Raycast(ray.origin, ray.direction, out hit, float.MaxValue))
                        {
                            //RayがあたったオブジェクトのPuppetクラスを取得
                            var puppet = hit.collider.GetComponent<Puppet>();
 
                            //RayがヒットしたオブジェクトにPuppetクラスがあったら
                            if(puppet != null)
                            {
                                startPoint = camera.ScreenToWorldPoint(screen);
 
                                //オブジェクトに物理演算を付与
                                selected = puppet;
                                selected.Select();//body.isKinematic = true;
 
                                //クリックしたスクリーン座標をRayがヒットしたオブジェクトのワールド座標にする
                                startPoint = hit.point;
 
                                //Rayの始点をヒットしたオブジェクトの座標へ移動
                                origin = selected.transform.position;
 
                                mode = OperationMode.Move;
                            } else
                            {
                                mode = OperationMode.Draw;
                            }
                        }
                        //Rayの先にオブジェクトが無い場合
                        else
                        {
                            mode = OperationMode.Draw;
                        }
                    }
                    //右ボタンをクリックでカット
                    else if(Input.GetMouseButtonDown(1))
                    {
                        points.Clear();
						screenpoints.Clear();
                        mode = OperationMode.Cut;
                    }
 
                    break;
 
                case OperationMode.Draw:
                    if (Input.GetMouseButtonUp(0))
                    {
                        Build();
                        mode = OperationMode.Default;
                    }
                    else
                    {
                        var point = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
                        points.Add(point);
						screenpoints.Add(new Vector2(screen.x, screen.y));
                    }
                    break;
 
                case OperationMode.Move:
 
                    if (Input.GetMouseButtonUp(0))
                    {
                        selected.Unselect();
                        Simulate(simulate);
 
                        selected = null;
 
                        mode = OperationMode.Default;
                    }
                    else
                    {
                        var currentPoint = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
                        var offset = currentPoint - startPoint;
                        selected.transform.position = origin + offset;
                    }
 
                    break;
 
                case OperationMode.Cut:
                    if(Input.GetMouseButtonUp(1))
                    {
                        Cut();
                        mode = OperationMode.Default;
                    } else
                    {
                        var point = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset * 0.5f));
                        points.Add(point);
						screenpoints.Add(new Vector2(screen.x, screen.y));
                    }
 
                    break;
 
            }
 
        }
 
        void Build ()
        {
            if (screenpoints.Count > 5)
            {
                var go = Instantiate(prefab);
                try
                {
                    Create(go, screenpoints);
                    var puppet = go.GetComponent<Puppet>();
                    puppets.Add(puppet);
                    Simulate(simulate);
                } catch(Exception)
                {
                    Destroy(go);
                }
            }
        }
 
        void Cut()
        {
            for(int i = 0, n = screenpoints.Count; i < n; i++)
            {
                var screen = screenpoints[i];
                var ray = camera.ScreenPointToRay(screen);
                RaycastHit hit;
                if (Physics.Raycast(ray.origin, ray.direction, out hit, float.MaxValue))
                {
                    var teddy = hit.collider.GetComponent<Teddy.Teddy>();
                    if(teddy != null)
                    {
                        try
                        {
                            TeddyUtility.CutMesh(teddy, camera, screenpoints);
 
                            // Copy material for sub meshes
                            var filter = teddy.GetComponent<MeshFilter>();
                            var renderer = teddy.GetComponent<MeshRenderer>();
                            var mat = renderer.sharedMaterial;
                            int count = filter.sharedMesh.subMeshCount;
                            var materials = new Material[count];
                            for(int j = 0; j < count; j++)
                            {
                                materials[j] = mat;
                            }
                            renderer.sharedMaterials = materials;
                        } catch
                        {
                        }
                        break;
                    }
                }
            }
        }
 
        public void Reset()
        {
            Simulate(true);
            puppets.ForEach(puppet => {
                puppet.Ignore();
                Destroy(puppet.gameObject, 10f);
            });
            puppets.Clear();
        }
 
        void Create (GameObject go, List<Vector2> screens)
        {
            var closure = Closure.GetClosure(screens);
            if (closure.Count > 2)
            {
                closure.RemoveAt(0);
                closure.RemoveAt(closure.Count - 1);
            }
            screens.AddRange(closure);
 
			var world = screens.Select(screen => {
				return camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
			}).ToList();
            go.transform.rotation = camera.transform.rotation;
 
            var points = world.Select(p => new Vector2(p.x, p.y)).ToList();
 
            // create lowpoly mesh for MeshCollider vertex count limit.
            var lowpoly = TeddyUtility.CreateMesh(points, 200, 0.85f);
            var collider = go.GetComponent<MeshCollider>();
            collider.sharedMesh = lowpoly;
 
            TeddyUtility.CreateTeddy(go, points, 600, 0.85f);
        }
 
        public void OnToggleSimulate(bool flag)
        {
            simulate = flag;
            Simulate(simulate);
        }
 
        void Simulate (bool flag)
        {
            puppets.ForEach(puppet =>
            {
                puppet.body.isKinematic = !flag;
            });
        }
 
        void OnRenderObject()
        {
            GL.PushMatrix();
 
            lineMat.SetPass(0);
 
            GL.Begin(GL.LINES);
            GL.Color(Color.black);
 
            for(int i = 0, n = points.Count; i < n - 1; i++)
            {
                GL.Vertex(points[i]);
                GL.Vertex(points[i + 1]);
            }
 
            GL.End();
            GL.PopMatrix();
        }
 
    }
 
 
 
}
 
Puppet.cs
using UnityEngine;
using Random = UnityEngine.Random;
 
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
 
namespace TeddyDemo {
 
	[RequireComponent (typeof(Rigidbody), typeof(MeshFilter), typeof(MeshRenderer)) ]
	[RequireComponent (typeof(MeshCollider)) ]
	public class Puppet : MonoBehaviour {
 
		public Rigidbody body {
			get {
				if(_body == null) {
					_body = GetComponent<Rigidbody>();
				}
				return _body;
			}
		}
 
		MeshFilter filter {
			get {
				if(_filter == null) {
					_filter = GetComponent<MeshFilter>();
				}
				return _filter;
			}
		}
 
		MeshCollider col {
			get {
				if(_collider == null) {
					_collider = GetComponent<MeshCollider>();
				}
				return _collider;
			}
		}
 
		Rigidbody _body;
		MeshFilter _filter;
		MeshCollider _collider;
 
		void Start () {
		}
 
		public void Ignore () {
			col.enabled = false;
		}
 
		public void Select () {
            //物理演算を受け付ける
			body.isKinematic = true;
		}
 
		public void Unselect () {
			body.isKinematic = false;
		}
 
	}
 
}
 
 

忙しい人のためのUnity入門講座へ