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

【Unity, Teddy, 3Dモデリング】カメラを90°回転したときにモデルが生成されない問題を解消

2018-11-04
【Unity, Teddy, 3Dモデリング】カメラを90°回転したときにモデルが生成されない問題を解消

はじめに

この記事は前回の記事の続きです。カメラを90℃回転させたときにTeddyで作成したモデルが生成されない問題に取り組みます。

前回の記事

【Unity, Teddy】カメラを移動したときのオブジェクト生成位置のずれを解消

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;
 
        //シーン開始時のカメラのベクトル
        Vector3 initialCameraPosition;
        
        void Start () {
            //カメラが無いなら追加
            if(camera == null) {
                camera = Camera.main;
            }
 
            screenpoints = new List<Vector2>();
            points = new List<Vector3>();
            puppets = new List<Puppet>();
            lineMat = new Material(lineShader);
 
 
            simulate = false;
 
            initialCameraPosition = camera.transform.position;
 
        }
 
        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;
 
                //Game画面上で黒い手書きの線の表示
                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)
        {
            //カメラのRotateの角度を取得
            var cameraQuaternion = camera.transform.rotation;
 
 
            //(Closureクラスが見つからない。たぶんTeddyRuntime.dllの中に入っていると思われます)
            var closure = Closure.GetClosure(screens);
            if (closure.Count > 2)
            {
                closure.RemoveAt(0);
                closure.RemoveAt(closure.Count - 1);
            }
            screens.AddRange(closure);
 
            //screenの座標を引数にして、camera.ScreenToWorldPointの座標を返す
            var world = screens.Select(screen => {
                
                var makePos = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
 
 
                //cameraが90°、または-90°のとき、手書きの円で作ったポイントをxy座標へ転写
                if (cameraQuaternion.eulerAngles.y == 90f)
                {
                    
                    makePos = Quaternion.Euler(0f, 90f, 0f) * makePos;
 
                }
                else if(cameraQuaternion.eulerAngles.y == -90f || cameraQuaternion.eulerAngles.y == 270f)
                {
                    makePos = Quaternion.Euler(0f, -90f, 0f) * makePos;
 
                }
 
                return makePos; 
 
            }).ToList();//ToList()で、screens全体にbody.isKinematic = true;を付与
 
            //go オブジェクトをカメラの向きに合わせる
            //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.(APIの部分)
            var lowpoly = TeddyUtility.CreateMesh(points, 200, 0.85f);
            var collider = go.GetComponent<MeshCollider>();
            collider.sharedMesh = lowpoly;
 
            TeddyUtility.CreateTeddy(go, points, 600, 0.85f);
 
            //生成したオブジェクトの色を変更する
            go.GetComponent<Renderer>().material.color = Color.red;
 
            //カメラ移動した分のベクトルを取得
            var offsetPos = camera.transform.position - initialCameraPosition;
 
            //goをoffsetPositionの分だけ移動
            var goPos = go.transform.position;
 
 
            //カメラがのy軸が0, 90, 180°, 270のとき
            if(cameraQuaternion.eulerAngles.y == 0f)
            {
                goPos = new Vector3(0f, goPos.y, offsetPos.z);
 
            }
            else if(cameraQuaternion.eulerAngles.y == 180f)
            {
 
                //カメラを基準にx軸対象にPuppetを移動させる
                offsetPos.z -= (offsetPos.z - camera.transform.position.z) * 2f;
                goPos = new Vector3(0f, goPos.y, offsetPos.z);
 
 
            }
            else if(cameraQuaternion.eulerAngles.y == 90f)
            {
                go.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
                goPos = new Vector3(offsetPos.x + zOffset, goPos.y, 0f);
 
 
            }
            else if (cameraQuaternion.eulerAngles.y == -90f || cameraQuaternion.eulerAngles.y == 270f)
            {
                go.transform.rotation = Quaternion.Euler(0f, 90f, 0f);
                goPos = new Vector3(offsetPos.x - zOffset, goPos.y, 0f);
 
            }
 
            go.transform.position = goPos;
 
 
        }
 
        //-------------------------------
        //生成したオブジェクトのisKinematicの切り替え
        //-------------------------------
        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();
        }
 
    }
 
 
 
}
 

カメラを90°(―90°)回転させたときの問題点

カメラを90°回転させると、円の中にオブジェクトが現れません(実際には、厚みの無いペラペラのオブジェクトが生成されております)。なので、横からでも3Dオブジェクトを生成できるようにする必要があります。

手書きの円の座標をxy座標へ転写する

Teddyはxy平面上に手書きの円を書き、z方向へ膨らませる仕様になっていますので、それに合わせたコーディングを考えていきます。

カメラを90°回転させた場合、手書きの円はワールド座標でyz平面上のデータになるので、それを一旦xy平面上へ転写し、オブジェクトを生成した後に、yz平面上に戻すという流れを行います。

TeddyRuntimeDemo.cs [Create()メソッド]

        //-----------------------------------------
        //手書きのオブジェクトを生成する
        //------------------------------------------
        void Create (GameObject go, List<Vector2> screens)
        {
            //カメラのRotateの角度を取得
            var cameraQuaternion = camera.transform.rotation;
 
 
            //(Closureクラスが見つからない。たぶんTeddyRuntime.dllの中に入っていると思われます)
            var closure = Closure.GetClosure(screens);
            if (closure.Count > 2)
            {
                closure.RemoveAt(0);
                closure.RemoveAt(closure.Count - 1);
            }
            screens.AddRange(closure);
 
            //screenの座標を引数にして、camera.ScreenToWorldPointの座標を返す
            var world = screens.Select(screen => {
                
                var makePos = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
 
 
                //cameraが90°、または-90°のとき、手書きの円で作ったポイントをxy座標へ転写
                if (cameraQuaternion.eulerAngles.y == 90f)
                {
                    
                    makePos = Quaternion.Euler(0f, 90f, 0f) * makePos;
 
                }
                else if(cameraQuaternion.eulerAngles.y == -90f || cameraQuaternion.eulerAngles.y == 270f)
                {
                    makePos = Quaternion.Euler(0f, -90f, 0f) * makePos;
 
                }
 
                return makePos; 
 
            }).ToList();//ToList()で、screens全体にbody.isKinematic = true;を付与
 
            //go オブジェクトをカメラの向きに合わせる
            //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.(APIの部分)
            var lowpoly = TeddyUtility.CreateMesh(points, 200, 0.85f);
            var collider = go.GetComponent<MeshCollider>();
            collider.sharedMesh = lowpoly;
 
            TeddyUtility.CreateTeddy(go, points, 600, 0.85f);
 
            //生成したオブジェクトの色を変更する
            go.GetComponent<Renderer>().material.color = Color.red;
 
            //カメラ移動した分のベクトルを取得
            var offsetPos = camera.transform.position - initialCameraPosition;
 
            //goをoffsetPositionの分だけ移動
            var goPos = go.transform.position;
 
 
            //カメラがのy軸が0, 90, 180°, 270のとき
            if(cameraQuaternion.eulerAngles.y == 0f)
            {
                goPos = new Vector3(0f, goPos.y, offsetPos.z);
 
            }
            else if(cameraQuaternion.eulerAngles.y == 180f)
            {
 
                //カメラを基準にx軸対象にPuppetを移動させる
                offsetPos.z -= (offsetPos.z - camera.transform.position.z) * 2f;
                goPos = new Vector3(0f, goPos.y, offsetPos.z);
 
 
            }
            else if(cameraQuaternion.eulerAngles.y == 90f)
            {
                go.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
                goPos = new Vector3(offsetPos.x + zOffset, goPos.y, 0f);
 
 
            }
            else if (cameraQuaternion.eulerAngles.y == -90f || cameraQuaternion.eulerAngles.y == 270f)
            {
                go.transform.rotation = Quaternion.Euler(0f, 90f, 0f);
                goPos = new Vector3(offsetPos.x - zOffset, goPos.y, 0f);
 
            }
 
            go.transform.position = goPos;
 
 
        }
 

var world = screens.Select(screen => {・・・}).ToList() ; はマウスのスクリーン座標(screen)を引数として受け取って、ワールド座標へ変換(var makePos)してworldのリストに格納する計算です。

上記のコードは非常に難解なので理解する必要はありませんが、ラムダ式の見方は重要なので、分からない場合はこちらをご覧ください。

【Unity, C#】超入門 ラムダ式の書き方、使い方

この数式の中で、カメラのy軸のRotate が90°(もしくは―90°)のとき、makePosを一旦カメラと同じ方向へ回転させ、手書きの円をxy平面へ転写しています。

最後に、Create()メソッドの最後の方に追加した、go.transform.rotation = Quaternion.Euler (0f, -90f, 0f); で生成したオブジェクトを手書きの円の位置へ戻しています。その結果、以下のようになります。

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