Newer
Older
HoloAnatomy / Assets / HoloToolkit-Examples / SpatialUnderstanding / Scripts / LineDrawer.cs
SURFACEBOOK2\jackwynne on 25 May 2018 20 KB v1
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using HoloToolkit.Unity;
using System;

namespace HoloToolkit.Examples.SpatialUnderstandingFeatureOverview
{
    public class LineDrawer : MonoBehaviour
    {
        // Consts
        public const float DefaultLineWidth = 0.001f;
        public const float DefaultBasisLength = 0.2f;

        // Structs
        public class Line
        {
            // Functions
            public Line()
            {
            }
            public Line(Vector3 _p0, Vector3 _p1, Color _c0, Color _c1, float _lineWidth = DefaultLineWidth)
            {
                p0 = _p0;
                p1 = _p1;
                c0 = _c0;
                c1 = _c1;
                lineWidth = _lineWidth;
                isValid = true;
            }
            public bool Set_IfDifferent(Vector3 _p0, Vector3 _p1, Color _c0, Color _c1, float _lineWidth)
            {
                isValid = true;
                if ((p0 != _p0) || (p1 != _p1) || (c0 != _c0) || (c1 != _c1) || (lineWidth != _lineWidth))
                {
                    p0 = _p0;
                    p1 = _p1;
                    c0 = _c0;
                    c1 = _c1;
                    lineWidth = _lineWidth;
                    return true;
                }
                return false;
            }

            // Data
            public Vector3 p0;
            public Vector3 p1;
            public Color c0;
            public Color c1;
            public float lineWidth;
            public bool isValid;
        }
        public class LineData
        {
            public int LineIndex;
            public List<Line> Lines = new List<Line>();
            public MeshRenderer Renderer;
            public MeshFilter Filter;
        }
        public class AnimationCurve3
        {
            public void AddKey(float time, Vector3 pos)
            {
                CurveX.AddKey(time, pos.x);
                CurveY.AddKey(time, pos.y);
                CurveZ.AddKey(time, pos.z);
            }
            public Vector3 Evaluate(float time)
            {
                return new Vector3(CurveX.Evaluate(time), CurveY.Evaluate(time), CurveZ.Evaluate(time));
            }

            public AnimationCurve CurveX = new AnimationCurve();
            public AnimationCurve CurveY = new AnimationCurve();
            public AnimationCurve CurveZ = new AnimationCurve();
        }
        public class AnimatedBox
        {
            public const float InitialPositionForwardMaxDistance = 2.0f;
            public const float AnimationTime = 2.5f;
            public const float DelayPerItem = 0.35f;

            public AnimatedBox(
                float timeDelay,
                Vector3 center,
                Quaternion rotation,
                Color color,
                Vector3 halfSize,
                float lineWidth = DefaultLineWidth * 3.0f)
            {
                TimeDelay = timeDelay;
                Center = center;
                Rotation = rotation;
                Color = color;
                HalfSize = halfSize;
                LineWidth = lineWidth;

                // If no time delay, go ahead and lock the animation now
                if (TimeDelay <= 0.0f)
                {
                    SetupAnimation();
                }
            }

            public bool Update(float deltaTime)
            {
                Time += deltaTime;

                // Delay animation setup until after the time delay
                if (!IsAnimationSetup &&
                    (Time >= TimeDelay))
                {
                    SetupAnimation();
                }

                return (Time >= TimeDelay);
            }

            private void SetupAnimation()
            {
                if (!SpatialUnderstanding.Instance.AllowSpatialUnderstanding)
                {
                    return;
                }

                // Calculate the forward distance for the animation start point
                Vector3 rayPos = CameraCache.Main.transform.position;
                Vector3 rayVec = CameraCache.Main.transform.forward * InitialPositionForwardMaxDistance;
                IntPtr raycastResultPtr = HoloToolkit.Unity.SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticRaycastResultPtr();
                HoloToolkit.Unity.SpatialUnderstandingDll.Imports.PlayspaceRaycast(
                    rayPos.x, rayPos.y, rayPos.z, rayVec.x, rayVec.y, rayVec.z,
                    raycastResultPtr);
                SpatialUnderstandingDll.Imports.RaycastResult rayCastResult = HoloToolkit.Unity.SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticRaycastResult();
                Vector3 animOrigin = (rayCastResult.SurfaceType != HoloToolkit.Unity.SpatialUnderstandingDll.Imports.RaycastResult.SurfaceTypes.Invalid) ?
                    rayPos + rayVec.normalized * Mathf.Max((rayCastResult.IntersectPoint - rayPos).magnitude - 0.3f, 0.0f) :
                    rayPos + rayVec * InitialPositionForwardMaxDistance;

                // Create the animation (starting it on the ground in front of the camera
                SpatialUnderstandingDll.Imports.QueryPlayspaceAlignment(SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignmentPtr());
                SpatialUnderstandingDll.Imports.PlayspaceAlignment alignment = SpatialUnderstanding.Instance.UnderstandingDLL.GetStaticPlayspaceAlignment();
                AnimPosition.AddKey(TimeDelay + 0.0f, new Vector3(animOrigin.x, alignment.FloorYValue, animOrigin.z));
                AnimPosition.AddKey(TimeDelay + AnimationTime * 0.5f, new Vector3(animOrigin.x, alignment.FloorYValue + 1.25f, animOrigin.z));
                AnimPosition.AddKey(TimeDelay + AnimationTime * 0.6f, new Vector3(animOrigin.x, alignment.FloorYValue + 1.0f, animOrigin.z));
                AnimPosition.AddKey(TimeDelay + AnimationTime * 0.95f, Center);
                AnimPosition.AddKey(TimeDelay + AnimationTime * 1.0f, Center);

                AnimScale.AddKey(TimeDelay + 0.0f, 0.0f);
                AnimScale.AddKey(TimeDelay + AnimationTime * 0.5f, 0.5f);
                AnimScale.AddKey(TimeDelay + AnimationTime * 0.8f, 1.0f);
                AnimScale.AddKey(TimeDelay + AnimationTime * 1.0f, 1.0f);

                AnimRotation.AddKey(TimeDelay + 0.0f, -1.5f);
                AnimRotation.AddKey(TimeDelay + AnimationTime * 0.2f, -0.5f);
                AnimRotation.AddKey(TimeDelay + AnimationTime * 0.9f, 0.0f);
                AnimRotation.AddKey(TimeDelay + AnimationTime * 1.0f, 0.0f);

                IsAnimationSetup = true;
            }
            public bool IsAnimationComplete { get { return IsAnimationSetup && (Time >= (AnimatedBox.AnimationTime + TimeDelay)); } }

            public Vector3 Center;
            public Quaternion Rotation;
            public Color Color;
            public Vector3 HalfSize;
            public float LineWidth;

            public bool IsAnimationSetup;
            public float Time;
            public float TimeDelay;
            public AnimationCurve AnimScale = new AnimationCurve();
            public AnimationCurve3 AnimPosition = new AnimationCurve3();
            public AnimationCurve AnimRotation = new AnimationCurve();

        }

        // Config
        public Material MaterialLine;

        // Privates
        private LineData lineData = new LineData();

        // Functions
        protected virtual void OnDestroy()
        {
            // Line renderer
            if (lineData != null)
            {
                if (lineData.Renderer != null)
                {
                    Destroy(lineData.Renderer);
                }
                if (lineData.Filter != null)
                {
                    Destroy(lineData.Filter);
                }
            }
            lineData = null;
        }

        protected const int PointsOnCircle = 50;
        protected bool Draw_Circle(Vector3 center, Vector3 normal, Color color, float radius = 0.25f, float lineWidth = DefaultLineWidth)
        {
            bool returnValue = false;
            float theta = 0;
            float radPerPoint = (2.0f * Mathf.PI) / (float)PointsOnCircle;
            Quaternion q = Quaternion.FromToRotation(Vector3.up, normal);
            Vector3 start = q * new Vector3(Mathf.Cos(theta) * radius, 0.0f, Mathf.Sin(theta) * radius) + center;

            for (int i = 1; i <= PointsOnCircle; i++)
            {
                theta = (i % PointsOnCircle) * radPerPoint;

                Vector3 end = q * new Vector3(Mathf.Cos(theta) * radius, 0.0f, Mathf.Sin(theta) * radius) + center;
                returnValue |= Draw_Line(start, end, color, color, lineWidth);

                start = end;
            }

            return returnValue;
        }

        protected bool Draw_Circle_Partial(Vector3 center, Vector3 normal, Color color, float radius = 0.25f, float lineWidth = DefaultLineWidth, float circleAngleArc = 360.0f)
        {
            bool returnValue = false;
            float theta = 0;
            float radPerPoint = (circleAngleArc * Mathf.Deg2Rad * 0.5f) / (float)PointsOnCircle;
            Quaternion q = Quaternion.FromToRotation(Vector3.up, normal);
            Vector3 start0 = q * new Vector3(Mathf.Cos(theta) * radius, 0.0f, Mathf.Sin(theta) * radius) + center;
            Vector3 start1 = q * new Vector3(Mathf.Cos(theta) * -radius, 0.0f, Mathf.Sin(theta) * -radius) + center;

            int maxPointCount = (circleAngleArc < 360.0f) ? (PointsOnCircle - 1) : PointsOnCircle;
            for (int i = 1; i <= maxPointCount; i++)
            {
                theta = (i % PointsOnCircle) * radPerPoint;

                Vector3 end0 = q * new Vector3(Mathf.Cos(theta) * radius, 0.0f, Mathf.Sin(theta) * radius) + center;
                Vector3 end1 = q * new Vector3(Mathf.Cos(theta) * -radius, 0.0f, Mathf.Sin(theta) * -radius) + center;
                returnValue |= Draw_Line(start0, end0, color, color, lineWidth);
                returnValue |= Draw_Line(start1, end1, color, color, lineWidth);

                start0 = end0;
                start1 = end1;
            }

            return returnValue;
        }

        protected bool Draw_Cube(Vector3 point, Color color, float halfSize = DefaultLineWidth)
        {
            return Draw_Line(point - Vector3.right * halfSize, point + Vector3.right * halfSize, color, color, halfSize);
        }

        protected bool Draw_AnimatedBox(AnimatedBox box)
        {
            // Update the time
            if (!box.Update(Time.deltaTime))
            {
                return false;
            }
            if (box.IsAnimationComplete)
            {
                // Animation is done, just pass through
                return Draw_Box(box.Center, box.Rotation, box.Color, box.HalfSize, box.LineWidth);
            }

            // Draw it using the current animation state
            return Draw_Box(
                box.AnimPosition.Evaluate(box.Time),
                box.Rotation * Quaternion.AngleAxis(360.0f * box.AnimRotation.Evaluate(box.Time), Vector3.up),
                box.Color,
                box.HalfSize * box.AnimScale.Evaluate(box.Time),
                box.LineWidth);
        }

        protected bool Draw_Box(Vector3 center, Quaternion rotation, Color color, Vector3 halfSize, float lineWidth = DefaultLineWidth)
        {
            bool needsUpdate = false;

            Vector3 basisX = rotation * Vector3.right;
            Vector3 basisY = rotation * Vector3.up;
            Vector3 basisZ = rotation * Vector3.forward;
            Vector3[] pts =
            {
            center + basisX * halfSize.x + basisY * halfSize.y + basisZ * halfSize.z,
            center + basisX * halfSize.x + basisY * halfSize.y - basisZ * halfSize.z,
            center - basisX * halfSize.x + basisY * halfSize.y - basisZ * halfSize.z,
            center - basisX * halfSize.x + basisY * halfSize.y + basisZ * halfSize.z,

            center + basisX * halfSize.x - basisY * halfSize.y + basisZ * halfSize.z,
            center + basisX * halfSize.x - basisY * halfSize.y - basisZ * halfSize.z,
            center - basisX * halfSize.x - basisY * halfSize.y - basisZ * halfSize.z,
            center - basisX * halfSize.x - basisY * halfSize.y + basisZ * halfSize.z
        };

            // Bottom
            needsUpdate |= Draw_Line(pts[0], pts[1], color, color, lineWidth);
            needsUpdate |= Draw_Line(pts[1], pts[2], color, color, lineWidth);
            needsUpdate |= Draw_Line(pts[2], pts[3], color, color, lineWidth);
            needsUpdate |= Draw_Line(pts[3], pts[0], color, color, lineWidth);

            // Top
            needsUpdate |= Draw_Line(pts[4], pts[5], color, color, lineWidth);
            needsUpdate |= Draw_Line(pts[5], pts[6], color, color, lineWidth);
            needsUpdate |= Draw_Line(pts[6], pts[7], color, color, lineWidth);
            needsUpdate |= Draw_Line(pts[7], pts[4], color, color, lineWidth);

            // Vertical lines
            needsUpdate |= Draw_Line(pts[0], pts[4], color, color, lineWidth);
            needsUpdate |= Draw_Line(pts[1], pts[5], color, color, lineWidth);
            needsUpdate |= Draw_Line(pts[2], pts[6], color, color, lineWidth);
            needsUpdate |= Draw_Line(pts[3], pts[7], color, color, lineWidth);

            return needsUpdate;
        }

        protected bool Draw_Line(Vector3 start, Vector3 end, Color colorStart, Color colorEnd, float lineWidth = DefaultLineWidth)
        {
            // Create up a new line (unless it's already created)
            while (lineData.LineIndex >= lineData.Lines.Count)
            {
                lineData.Lines.Add(new Line());
            }

            // Set it
            bool needsUpdate = lineData.Lines[lineData.LineIndex].Set_IfDifferent(transform.InverseTransformPoint(start), transform.InverseTransformPoint(end), colorStart, colorEnd, lineWidth);

            // Inc out count
            ++lineData.LineIndex;

            return needsUpdate;
        }

        protected bool Draw_TransformBasis(Transform transformToDraw, float basisLength = DefaultBasisLength, float lineWidth = DefaultLineWidth * 2.0f)
        {
            // Basis
            bool needsUpdate = false;
            needsUpdate |= Draw_Line(transformToDraw.transform.position, transformToDraw.transform.position + transformToDraw.transform.right * basisLength, Color.red, Color.red, lineWidth);
            needsUpdate |= Draw_Line(transformToDraw.transform.position, transformToDraw.transform.position + transformToDraw.transform.up * basisLength, Color.green, Color.green, lineWidth);
            needsUpdate |= Draw_Line(transformToDraw.transform.position, transformToDraw.transform.position + transformToDraw.transform.forward * basisLength, Color.blue, Color.blue, lineWidth);

            return needsUpdate;
        }

        private void Lines_LineDataToMesh()
        {
            // Allocate them up
            Vector3[] verts = new Vector3[lineData.Lines.Count * 8];
            int[] tris = new int[lineData.Lines.Count * 12 * 3];
            Color[] colors = new Color[verts.Length];

            // Build the data
            for (int i = 0; i < lineData.Lines.Count; ++i)
            {
                // Base index calculations
                int vert = i * 8;
                int v0 = vert;
                int tri = i * 12 * 3;

                // Setup
                Vector3 dirUnit = (lineData.Lines[i].p1 - lineData.Lines[i].p0).normalized;
                Vector3 normX = Vector3.Cross((Mathf.Abs(dirUnit.y) >= 0.99f) ? Vector3.right : Vector3.up, dirUnit).normalized;
                Vector3 normy = Vector3.Cross(normX, dirUnit);

                // Vertices
                verts[vert] = lineData.Lines[i].p0 + normX * lineData.Lines[i].lineWidth + normy * lineData.Lines[i].lineWidth; colors[vert] = lineData.Lines[i].c0; ++vert;
                verts[vert] = lineData.Lines[i].p0 - normX * lineData.Lines[i].lineWidth + normy * lineData.Lines[i].lineWidth; colors[vert] = lineData.Lines[i].c0; ++vert;
                verts[vert] = lineData.Lines[i].p0 - normX * lineData.Lines[i].lineWidth - normy * lineData.Lines[i].lineWidth; colors[vert] = lineData.Lines[i].c0; ++vert;
                verts[vert] = lineData.Lines[i].p0 + normX * lineData.Lines[i].lineWidth - normy * lineData.Lines[i].lineWidth; colors[vert] = lineData.Lines[i].c0; ++vert;

                verts[vert] = lineData.Lines[i].p1 + normX * lineData.Lines[i].lineWidth + normy * lineData.Lines[i].lineWidth; colors[vert] = lineData.Lines[i].c1; ++vert;
                verts[vert] = lineData.Lines[i].p1 - normX * lineData.Lines[i].lineWidth + normy * lineData.Lines[i].lineWidth; colors[vert] = lineData.Lines[i].c1; ++vert;
                verts[vert] = lineData.Lines[i].p1 - normX * lineData.Lines[i].lineWidth - normy * lineData.Lines[i].lineWidth; colors[vert] = lineData.Lines[i].c1; ++vert;
                verts[vert] = lineData.Lines[i].p1 + normX * lineData.Lines[i].lineWidth - normy * lineData.Lines[i].lineWidth; colors[vert] = lineData.Lines[i].c1; ++vert;

                // Indices
                tris[tri + 0] = (v0 + 0); tris[tri + 1] = (v0 + 5); tris[tri + 2] = (v0 + 4); tri += 3;
                tris[tri + 0] = (v0 + 1); tris[tri + 1] = (v0 + 5); tris[tri + 2] = (v0 + 0); tri += 3;

                tris[tri + 0] = (v0 + 1); tris[tri + 1] = (v0 + 6); tris[tri + 2] = (v0 + 5); tri += 3;
                tris[tri + 0] = (v0 + 2); tris[tri + 1] = (v0 + 6); tris[tri + 2] = (v0 + 1); tri += 3;

                tris[tri + 0] = (v0 + 2); tris[tri + 1] = (v0 + 7); tris[tri + 2] = (v0 + 6); tri += 3;
                tris[tri + 0] = (v0 + 3); tris[tri + 1] = (v0 + 7); tris[tri + 2] = (v0 + 2); tri += 3;

                tris[tri + 0] = (v0 + 3); tris[tri + 1] = (v0 + 7); tris[tri + 2] = (v0 + 4); tri += 3;
                tris[tri + 0] = (v0 + 3); tris[tri + 1] = (v0 + 4); tris[tri + 2] = (v0 + 0); tri += 3;

                tris[tri + 0] = (v0 + 0); tris[tri + 1] = (v0 + 3); tris[tri + 2] = (v0 + 2); tri += 3;
                tris[tri + 0] = (v0 + 0); tris[tri + 1] = (v0 + 2); tris[tri + 2] = (v0 + 1); tri += 3;

                tris[tri + 0] = (v0 + 5); tris[tri + 1] = (v0 + 6); tris[tri + 2] = (v0 + 7); tri += 3;
                tris[tri + 0] = (v0 + 5); tris[tri + 1] = (v0 + 7); tris[tri + 2] = (v0 + 4); tri += 3;
            }

            // Create up the components
            if (lineData.Filter == null)
            {
                lineData.Filter = gameObject.AddComponent<MeshFilter>();
            }
            if (lineData.Renderer == null)
            {
                lineData.Renderer = gameObject.AddComponent<MeshRenderer>();
                lineData.Renderer.material = MaterialLine;
            }

            // Create or clear the mesh
            Mesh mesh = null;
            if (lineData.Filter.mesh != null)
            {
                mesh = lineData.Filter.mesh;
                mesh.Clear();
            }
            else
            {
                mesh = new Mesh();
                mesh.name = "LineDrawer.Lines_LineDataToMesh";
            }

            // Set them into the mesh
            mesh.vertices = verts;
            mesh.triangles = tris;
            mesh.colors = colors;
            mesh.RecalculateBounds();
            mesh.RecalculateNormals();

            lineData.Filter.mesh = mesh;

            // If no triangles, hide it
            lineData.Renderer.enabled = (lineData.Lines.Count == 0) ? false : true;

            // Line index reset
            lineData.LineIndex = 0;
        }

        protected void LineDraw_Begin()
        {
            lineData.LineIndex = 0;
            for (int i = 0; i < lineData.Lines.Count; ++i)
            {
                lineData.Lines[i].isValid = false;
            }
        }

        protected void LineDraw_End(bool needsUpdate)
        {
            if (lineData == null)
            {
                return;
            }

            // Check if we have any not dirty
            int i = 0;
            while (i < lineData.Lines.Count)
            {
                if (!lineData.Lines[i].isValid)
                {
                    needsUpdate = true;
                    lineData.Lines.RemoveAt(i);
                    continue;
                }
                ++i;
            }

            // Do the update (if needed)
            if (needsUpdate)
            {
                Lines_LineDataToMesh();
            }
        }
    }
}