Search Unity

Assets QuickDraw: GPU-accelerated immediate mode vector graphics renderer [WIP]

Discussion in 'Works In Progress - Archive' started by alexzzzz, Aug 3, 2020.

  1. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447


    What is it?


    QuickDraw is an easy to use GPU-accelerated immediate mode renderer for points, lines, icons, simple text, triangles and also more complex geometric shapes. The renderer works both in the editor, including the edit-time, and in the builds. If you need hundreds of thousands of dynamic points and lines in real-time, you are welcome.

    Philosophy

    1. The easier to use the better
    • Immediate mode rendering
    • A single line of code should be enough to draw any primitive
    • Little to no setup required for quick start
    2. Performance is a feature (but not at the expense of ease of use)
    • Let the GPU do most of the work (requires graphics capabilities that most of the mobile devices do not support)
    • No managed memory allocations other than at the startup
    3. Should work both in the builds and in the editor, including the edit-time

    What is it good for?
    • For applications that need a solution for rendering static or highly dynamic vector graphics, possibly in large amounts, where the result doesn't need to be fancy-looking at short distances.
    • A must-have for quick prototyping and rich debug graphics. The existing solutions, neither built-in nor alternative ones, don't offer pleasant experience in this area.
    Target platforms

    QuickDraw is designed for use on desktops. While other platforms are not supported, it still may work if the device supports the following graphics capabilities (some mobile devices do, an average old phone doesn't):
    • integer data type in shaders
    • 2D texture arrays
    • geometry shaders
    • at least 4 structured buffer inputs in vertex shaders
    Basic usage example
    Code (CSharp):
    1. using QuickDraw;
    2.  
    3. ...
    4. void Update()
    5. {
    6.     // A text centered at 'position', default height in meters, default color
    7.     QDraw.Text3D("Hello, World!", position);
    8.  
    9.     // A line from 'start' to 'end', default width in pixels, default color
    10.     QDraw.Line(start, end);
    11.  
    12.     // Two points at both ends of the line, default size in pixels
    13.     QDraw.Point(start, Color.cyan);
    14.     QDraw.Point(end, Color.yellow);
    15. }
    16. ...
    Features

    Geometric primitives/shapes

    Point, Line, DashedLine, DottedLine, Ray, Arrow, ArrowHead, Pivot, Frustum, Mesh, SolidMesh, Triangle, SolidTriangle, Quad, SolidQuad, Circle, SolidCircle, Wheel, Grid, Arc, DirectionalArc, SolidArc, Cube, SolidCube, Sphere, SolidSphere, Polygon, Polyline, Bounds, Hemisphere, Cylinder, Capsule, Pyramide, Cone

    Icons

    Icon is just a textured quad positioned somewhere in the world. Icons can always face the camera or be oriented in space. You can specify which icon to draw by its name or its index in the collection of icons.

    Text

    The text is rendered using a simple ASCII bitmap font and always faces the camera. QDraw.Text3D(...) is probably the easiest way to show some textual or numerical information on screen.


    Sizes

    Points, icons and lines support sizes specified either in screen pixels or in world units (usually meters). All methods that use world units have "3D" suffix at the end of their names. Pixel Size Multiplier in the settings lets you adjust all the pixel sizes to different screen resolutions and DPIs. Points and Lines also support subpixel width simulated by alpha fading.


    Colors and gradients

    You can specify the color of the primitives in advance or pass it directly to any of the drawing methods. Lines, triangles and quads also support color gradients.


    Performance

    All the QDraw.XXX() methods don't render anything right away, but rather accumulate the information in internal buffers. This makes most of these methods very fast, and also makes it possible to do the actual rendering in just a couple of draw calls at the end of the current frame, which in turn makes the rendering very fast too. Also, as if it wasn't fast enough, QDraw.Points() and QDraw.Lines() accept pointers to collections of points and lines, so you don't have to submit them for drawing one by one.

    100 000 dynamic lines (only drawing, particle simulation is on pause)
    flaming-star.jpg

    profiler.PNG

    Transformation matrices

    Like UnityEngine.Gizmos and UnityEditor.Handles, QuickDraw also supports setting up the current transformation matrix. You can draw your shapes in local coordinates and the transformation defined by the current matrix will be applied automatically.

    Standalone Megademo



    Megademo download links:

    Windows (~15 Mb)
    https://drive.google.com/file/d/17zuFJApRJElyipfbgVrCR6ZHFFpAvH14/view
    https://dl.dropbox.com/s/sn7imq323zyrnj9/QuickDraw Demo.zip

    Android (~10 Mb)
    https://drive.google.com/file/d/1bmc05Zc3FXvw5sBy3jR3VCG494dPNFa1/view
    https://dl.dropbox.com/s/ii8hwwwj6e5rao1/QuickDraw-Megademo.apk






    Todo list
    • Settings window overhaul: font preview, better icons editor, validation of values, warnings/tooltips/help
    • Fix minor technical issues
    • Beautiful documentation
     

    Attached Files:

    Last edited: Oct 5, 2020
    spiraloop, grizzly and Lars-Steenhoff like this.
  2. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Unity really lacks convenient tools for quick visualization and rich debug graphics. While debugging or prototyping we usually need
    • lines
    • dots to highlight positions/intersections
    • text labels to be able to see vertex numbers, object names, whatever...
    • grids to visualize planes or cells
    • circles, rectangles and bounding boxes to visualize some areas
    • arrows for normals and directions
    • ...
    In the editor the options are limited to UnityEngine.Debug.DrawLine/DrawRay that can only draw thin lines, UnityEngine.Gizmos that can draw lines, cubes, spheres, icons and that's pretty much it. And there's also UnityEditor.Handles that could be much more useful if it didn't require a custom editor class with a lot of boilerplate code in order to use its capabilities.

    And if there is a need to put debug graphics inside a standalone build, there is just no convenient tool for that. UnityEngine.LineRenderer is not really an option and all the editor stuff doesn't work in the builds. One day I ended up using a particle system with a texture atlas with numbers to debug lighting for my old voxel engine:
    light_debuging.JPG

    So, I have been developing QuickDraw for quite a while now to cover my personal needs and I can say that at this point it covers them pretty well.

    A couple of examples:

    Here it's used to visualize the results of a triangulation algorithm implementation. Everything is rendered by QuickDraw except the background plane that is just a regular plane with a texture.
    upload_2020-8-4_1-55-16.png
    QuickDraw couldn't render text at that time, so I had to mark different vertices with different colors and to remember which color corresponded to the first vertex, the second etc - just to be able to debug the problem if something went wrong.

    Here I was messing with a fluid simulation and used QuickDraw in its early days to render water particles and forces between them. Compared to the simulation, the rendering ended up almost free.
     
    Last edited: Aug 23, 2020
  3. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. namespace QuickDraw.Samples
    4. {
    5.     [ExecuteAlways]
    6.     internal class SimpleMeshInspector : DemoBase
    7.     {
    8.         [Header("Mesh")]
    9.         public Mesh mesh = null;
    10.  
    11.         [Header("Vertices")]
    12.         public Color vertexColor = Color.yellow;
    13.         public Color vertexLineColor = new Color(1, 1, 0, .05f);
    14.         public float vertexPointSize = 10;
    15.  
    16.         [Header("Triangles")]
    17.         public float edgeWidth = 2f;
    18.         public Color arcColor = Color.cyan;
    19.         public Color triangleIndexColor = Color.cyan;
    20.  
    21.         [Header("Normals")]
    22.         public Color normalColor = new Color(0, 1, 0, .5f);
    23.  
    24.         private Mesh cachedMesh;
    25.         private Vector3[] positions;
    26.         private Vector3[] normals;
    27.         private int[] indices;
    28.  
    29.         private void Update()
    30.         {
    31.             if (Application.isPlaying)
    32.             {
    33.                 ControlCamera();
    34.             }
    35.  
    36.             if (mesh == null)
    37.             {
    38.                 return;
    39.             }
    40.  
    41.             if (cachedMesh != mesh)
    42.             {
    43.                 positions = mesh.vertices;
    44.                 normals = mesh.normals;
    45.                 indices = mesh.triangles;
    46.                 cachedMesh = mesh;
    47.             }
    48.  
    49.             QDraw.Matrix = transform.localToWorldMatrix;
    50.             using (QDraw.Push())
    51.             {
    52.                 QDraw.LineWidth = edgeWidth;
    53.                 QDraw.Mesh(mesh);
    54.             }
    55.  
    56.             for (int i = 0; i < positions.Length; i++)
    57.             {
    58.                 QDraw.Point(positions[i], vertexPointSize, vertexColor);
    59.             }
    60.  
    61.             for (int i = 0; i < indices.Length; i += 3)
    62.             {
    63.                 var ia = indices[i + 0];
    64.                 var ib = indices[i + 1];
    65.                 var ic = indices[i + 2];
    66.  
    67.                 var pa = positions[ia];
    68.                 var pb = positions[ib];
    69.                 var pc = positions[ic];
    70.  
    71.                 var (center, radius) = GetInscribedCircle();
    72.                 var normal = Vector3.Cross(pa - pb, pb - pc).normalized;
    73.                 var arcRadius = radius / 2;
    74.  
    75.                 DrawTriangle();
    76.  
    77.                 DrawVertexIndex(ia);
    78.                 DrawVertexIndex(ib);
    79.                 DrawVertexIndex(ic);
    80.  
    81.                 void DrawTriangle()
    82.                 {
    83.                     var textHeight = radius / 4;
    84.                     QDraw.Text3D(i / 3, center, textHeight, triangleIndexColor);
    85.  
    86.                     var angle = Vector3.SignedAngle(pa - center, pc - center, normal) * Mathf.Deg2Rad;
    87.                     if (angle < 0)
    88.                     {
    89.                         angle += 2 * Mathf.PI;
    90.                     }
    91.  
    92.                     QDraw.DirectionalArc(center, normal, pa - center, arcRadius, angle, arcColor);
    93.                 }
    94.  
    95.                 void DrawVertexIndex(int index)
    96.                 {
    97.                     var position = positions[index];
    98.                     QDraw.Arrow(position, position + normals[index] * radius, normalColor);
    99.  
    100.                     var direction = (position - center).normalized;
    101.                     var labelPosition = center + direction * arcRadius * 1.2f;
    102.                     QDraw.Line(labelPosition, position, vertexLineColor);
    103.              
    104.                     var textHeight = radius / 5;
    105.                     QDraw.Text3D(index, labelPosition, textHeight, vertexColor);
    106.                 }
    107.  
    108.                 (Vector3 center, float radius) GetInscribedCircle()
    109.                 {
    110.                     var a = (pc - pb).magnitude;
    111.                     var b = (pc - pa).magnitude;
    112.                     var c = (pb - pa).magnitude;
    113.  
    114.                     var s = (a + b + c) / 2;
    115.                     var area = Mathf.Sqrt(s * (s - a) * (s - b) * (s - c));
    116.  
    117.                     var x = (a * pa.x + b * pb.x + c * pc.x) / (a + b + c);
    118.                     var y = (a * pa.y + b * pb.y + c * pc.y) / (a + b + c);
    119.                     var z = (a * pa.z + b * pb.z + c * pc.z) / (a + b + c);
    120.  
    121.                     return (new Vector3(x, y, z), area / s);
    122.                 }
    123.             }
    124.         }
    125.     }
    126. }
    upload_2020-8-9_19-5-46.png
     
    Last edited: Aug 9, 2020
  4. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    Code (CSharp):
    1. using UnityEngine;
    2. using static UnityEngine.Mathf;
    3.  
    4. namespace QuickDraw.Samples
    5. {
    6.     [ExecuteAlways]
    7.     internal class MobiusStripDemo : DemoBase
    8.     {
    9.         [Header("Möbius Strip")]
    10.         [Range(5, 500)] public int sectors = 100;
    11.         [Range(2, 200)] public int segments = 20;
    12.         [Range(0, 1)] public float speed = .1f;
    13.  
    14.         [Header("Visuals")]
    15.         [Range(0, .01f)] public float lineWidth3D = 0.003f;
    16.         [Range(0, 1)] public float opacity = .2f;
    17.         public bool doubleSided = false;
    18.  
    19.         private float time = 0;
    20.  
    21.         private void Update()
    22.         {
    23.             if (Application.isPlaying)
    24.             {
    25.                 ControlCamera();
    26.                 time += Time.deltaTime * speed;
    27.             }
    28.  
    29.             QDraw.Matrix = transform.localToWorldMatrix;
    30.             QDraw.LineWidth3D = lineWidth3D;
    31.             QDraw.DoubleSidedGeometry = doubleSided;
    32.  
    33.             for (int s = 0; s < sectors; s++)
    34.             {
    35.                 for (int t = 0; t < segments; t++)
    36.                 {
    37.                     DrawQuad(s, t);
    38.                 }
    39.             }
    40.         }
    41.  
    42.         private void DrawQuad(int s, int t)
    43.         {
    44.             var (p0, color0) = GetPosition(s, t);
    45.             var (p1, color1) = GetPosition(s + 1, t);
    46.             var (p2, color2) = GetPosition(s + 1, t + 1);
    47.             var (p3, color3) = GetPosition(s, t + 1);
    48.  
    49.             QDraw.SolidTriangle(p0, p1, p2, color0.WithAlpha(opacity), color1.WithAlpha(opacity), color2.WithAlpha(opacity));
    50.             QDraw.SolidTriangle(p0, p2, p3, color0.WithAlpha(opacity), color2.WithAlpha(opacity), color3.WithAlpha(opacity));
    51.  
    52.             QDraw.Line3D(p0, p1, color0, color1);
    53.             QDraw.Line3D(p1, p2, color1, color2);
    54.             QDraw.Line3D(p2, p3, color2, color3);
    55.             QDraw.Line3D(p3, p0, color3, color0);
    56.         }
    57.  
    58.         private (Vector3, Color) GetPosition(float s, float t)
    59.         {
    60.             s = s * 2f / sectors * PI + time;
    61.             t = t * 2f / segments - 1f;
    62.  
    63.             var x = (1 + t / 2 * Cos(s / 2)) * Cos(s);
    64.             var y = (1 + t / 2 * Cos(s / 2)) * Sin(s);
    65.             var z = t / 2 * Sin(s / 2);
    66.             var color = Color.HSVToRGB(z + .5f, 1, 1);
    67.  
    68.             return (new Vector3(x, y, z), color);
    69.         }
    70.     }
    71.  
    72.     internal static class ColorExtensions
    73.     {
    74.         public static Color WithAlpha(this Color color, float alpha)
    75.         {
    76.             return new Color(color.r, color.g, color.b, alpha);
    77.         }
    78.     }
    79. }
    upload_2020-8-7_23-29-25.png
     
    Last edited: Aug 8, 2020
  5. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. namespace QuickDraw.Samples
    4. {
    5.     [ExecuteAlways]
    6.     internal class LorenzAttractorDemo : DemoBase
    7.     {
    8.         [Header("Shape")]
    9.         [Range(.0001f, .01f)] public float step = .005f;
    10.         [Range(1, 20)] public float a = 10f;
    11.         [Range(10, 40)] public float b = 28f;
    12.         [Range(0, 20)] public float c = 8 / 3f;
    13.  
    14.         [Header("Visuals")]
    15.         [Range(0, .1f)] public float lineWidth3D = .02f;
    16.         [Range(0, 1000)] public float curveLength = 0;
    17.  
    18.         private readonly Vector3 center = new Vector3(.2f, .1f, 24f);
    19.  
    20.         private void Update()
    21.         {
    22.             if (Application.isPlaying)
    23.             {
    24.                 ControlCamera();
    25.                 curveLength += step * Time.deltaTime * 100;
    26.             }
    27.  
    28.             QDraw.Matrix = transform.localToWorldMatrix;
    29.             QDraw.Matrix *= Matrix4x4.Translate(-center);
    30.             QDraw.LineWidth3D = lineWidth3D;
    31.  
    32.             DrawLorenzAttractor();
    33.         }
    34.  
    35.         private void DrawLorenzAttractor()
    36.         {
    37.             var p0 = new Vector3(.1f, 0, 0);
    38.             var color0 = Color.clear;
    39.  
    40.             float length = curveLength;
    41.             while (length > 0)
    42.             {
    43.                 Vector3 p1;
    44.                 p1.x = p0.x + step * a * (p0.y - p0.x);
    45.                 p1.y = p0.y + step * (p0.x * (b - p0.z) - p0.y);
    46.                 p1.z = p0.z + step * (p0.x * p0.y - c * p0.z);
    47.  
    48.                 var hue = Mathf.Repeat((p1 - center).sqrMagnitude * .0008f, 1);
    49.                 var color1 = Color.HSVToRGB(hue, 1, 1);
    50.  
    51.                 QDraw.Line3D(p0, p1, color0, color1);
    52.  
    53.                 p0 = p1;
    54.                 color0 = color1;
    55.                 length -= step;
    56.             }
    57.  
    58.             QDraw.Point3D(p0, lineWidth3D * 5, color0);
    59.         }
    60.     }
    61. }
    upload_2020-8-8_20-50-35.png
     
  6. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using Random = UnityEngine.Random;
    4.  
    5. namespace QuickDraw.Samples
    6. {
    7.     [ExecuteAlways]
    8.     internal class FractalTreeDemo : DemoBase
    9.     {
    10.         [Header("Geometry")]
    11.         [Range(1, 7)] public int segments = 6;
    12.         [Range(0, 90)] public float angle = 50;
    13.         [Range(.1f, .5f)] public float minBranchLength = .5f;
    14.         [Range(.5f, 1)] public float maxBranchLength = .75f;
    15.         [Range(0, .5f)] public float windForce = .05f;
    16.  
    17.         [Header("Lines")]
    18.         public Color lineColor = new Color(.3f, .2f, .07f, 1);
    19.         [Range(0, .1f)] public float lineWidth3D = .03f;
    20.  
    21.         [Header("Icons")]
    22.         public Color iconColor = new Color(0.45f, 1f, 0, 1);
    23.         [Range(0, .2f)] public float iconSize3D = .1f;
    24.  
    25.         private Bounds bounds;
    26.         private Vector3 targetPosition = new Vector3(0, -1.5f, 0);
    27.         private Vector3 velocity = Vector3.zero;
    28.         private int seed;
    29.  
    30.         private void Start()
    31.         {
    32.             if (Application.isPlaying)
    33.             {
    34.                 InvokeRepeating(nameof(ChangeSeed), 0, 5.017f);
    35.             }
    36.         }
    37.  
    38.         private void ChangeSeed()
    39.         {
    40.             seed = DateTime.Now.Millisecond;
    41.             iconColor = Color.HSVToRGB(Random.value, 1, 1);
    42.         }
    43.  
    44.         private void Update()
    45.         {
    46.             Random.InitState(seed);
    47.  
    48.             QDraw.Matrix = transform.localToWorldMatrix;
    49.             QDraw.Matrix *= Matrix4x4.Translate(targetPosition);
    50.             QDraw.LineWidth3D = lineWidth3D;
    51.             QDraw.PointSize3D = iconSize3D;
    52.  
    53.             QDraw.SolidCircle(Vector3.zero, Vector3.up, lineWidth3D * 10, new Color(lineColor.r, lineColor.g, lineColor.b, .25f));
    54.             QDraw.Wheel(Vector3.zero, Vector3.up, lineWidth3D * 10, 6, lineColor);
    55.  
    56.             bounds = new Bounds(Vector3.zero, Vector3.zero);
    57.             DrawFractalTree(Vector3.zero, Vector3.up, 1);
    58.             targetPosition = Vector3.SmoothDamp(targetPosition, -bounds.center, ref velocity, .5f);
    59.         }
    60.  
    61.         private void DrawFractalTree(Vector3 fromPosition, Vector3 toPosition, int depth)
    62.         {
    63.             var wind = Mathf.PerlinNoise(toPosition.z + Time.time, toPosition.x);
    64.             toPosition.z += wind * windForce;
    65.  
    66.             bounds.Encapsulate(toPosition);
    67.  
    68.             if (depth > segments)
    69.             {
    70.                 return;
    71.             }
    72.  
    73.             var direction = toPosition - fromPosition;
    74.             var depthFactor = depth / (float) (segments);
    75.  
    76.             var trunkWidth = lineWidth3D * Mathf.Max(1 - depthFactor, .1f);
    77.             QDraw.Line3D(fromPosition, toPosition, trunkWidth, lineColor);
    78.  
    79.             Color.RGBToHSV(iconColor, out var hue, out var satudation, out var value);
    80.             hue *= Mathf.PerlinNoise(toPosition.x, toPosition.z);
    81.             value *= depthFactor;
    82.             var leafColor = Color.HSVToRGB(hue, satudation, value);
    83.             QDraw.Icon3D("chestnut-leaf", toPosition, Vector3.back, direction, iconSize3D, leafColor);
    84.  
    85.             DrawFractalTree(toPosition, toPosition + Quaternion.Euler(-angle, 0, 0) * direction * Random.Range(minBranchLength, maxBranchLength), depth + 1);
    86.             DrawFractalTree(toPosition, toPosition + Quaternion.Euler(angle, 0, 0) * direction * Random.Range(minBranchLength, maxBranchLength), depth + 1);
    87.             DrawFractalTree(toPosition, toPosition + Quaternion.Euler(0, 0, -angle) * direction * Random.Range(minBranchLength, maxBranchLength), depth + 1);
    88.             DrawFractalTree(toPosition, toPosition + Quaternion.Euler(0, 0, angle) * direction * Random.Range(minBranchLength, maxBranchLength), depth + 1);
    89.         }
    90.     }
    91. }
    upload_2020-8-9_18-32-55.png
     
  7. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Collections;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5.  
    6. namespace QuickDraw.Samples
    7. {
    8.     [ExecuteAlways]
    9.     internal class FancyMeshDemo : MonoBehaviour
    10.     {
    11.         [Header("Geometry")]
    12.         public Mesh mesh = null;
    13.         [Range(1, 20)] public int divisor = 20;
    14.  
    15.         [Header("Noise Settings")]
    16.         [Range(1, 4)] public int octaves = 3;
    17.         [Range(0, 5)] public float frequency = 1;
    18.         [Range(0, 1)] public float amplitude = .3f;
    19.  
    20.         [Header("Visuals")]
    21.         [Range(0, .01f)] public float lineWidth3D = .002f;
    22.         [Range(0, 1)] public float speed = .1f;
    23.  
    24.         internal static readonly SimplexNoise noiseX = new SimplexNoise(1);
    25.         internal static readonly SimplexNoise noiseY = new SimplexNoise(2);
    26.         internal static readonly SimplexNoise noiseZ = new SimplexNoise(3);
    27.  
    28.         private Mesh cachedMesh;
    29.         private NativeArray<Vector3> points;
    30.         private NativeArray<Color32> colors;
    31.         private float time;
    32.  
    33.         private void Update()
    34.         {
    35.             if (mesh == null)
    36.             {
    37.                 return;
    38.             }
    39.  
    40.             if (cachedMesh != mesh)
    41.             {
    42.                 cachedMesh = mesh;
    43.                 GenerateLines(mesh);
    44.             }
    45.  
    46.             QDraw.Matrix = transform.localToWorldMatrix;
    47.             QDraw.LineWidth3D = lineWidth3D;
    48.  
    49.             if (Application.isPlaying)
    50.             {
    51.                 time += Time.deltaTime * speed;
    52.             }
    53.  
    54.             Draw();
    55.         }
    56.  
    57.         private void Draw()
    58.         {
    59.             var distorted = new NativeArray<Vector3>(points.Length, Allocator.TempJob);
    60.  
    61.             var job = new DistortJob()
    62.             {
    63.                 originalPositions = points,
    64.                 distortedPositions = distorted,
    65.                 colors = colors,
    66.                 octaves = octaves,
    67.                 frequency = frequency,
    68.                 amplitude = amplitude,
    69.                 time = time,
    70.             };
    71.             var handle = job.Schedule(points.Length, 64);
    72.             handle.Complete();
    73.  
    74.             for (var i = 0; i < distorted.Length; i += 2)
    75.             {
    76.                 QDraw.Line3D(distorted[i], distorted[i + 1], colors[i], colors[i + 1]);
    77.             }
    78.  
    79.             distorted.Dispose();
    80.         }
    81.  
    82.         private void OnDisable()
    83.         {
    84.             if (points.IsCreated)
    85.             {
    86.                 points.Dispose();
    87.                 colors.Dispose();
    88.             }
    89.         }
    90.  
    91.         private void GenerateLines(Mesh mesh)
    92.         {
    93.             var positions = mesh.vertices;
    94.             var indices = mesh.triangles;
    95.  
    96.             var segmentEndPoints = new List<Vector3>();
    97.             var processedEdges = new List<(int, int)>();
    98.  
    99.             for (var i = 0; i < indices.Length; i += 3)
    100.             {
    101.                 var ia = indices[i + 0];
    102.                 var ib = indices[i + 1];
    103.                 var ic = indices[i + 2];
    104.  
    105.                 AddEdge(ia, ib);
    106.                 AddEdge(ib, ic);
    107.                 AddEdge(ic, ia);
    108.             }
    109.  
    110.             if (points.IsCreated)
    111.             {
    112.                 points.Dispose();
    113.                 colors.Dispose();
    114.             }
    115.  
    116.             points = new NativeArray<Vector3>(segmentEndPoints.Count, Allocator.Persistent);
    117.             colors = new NativeArray<Color32>(segmentEndPoints.Count, Allocator.Persistent);
    118.             for (var i = 0; i < segmentEndPoints.Count; i++)
    119.             {
    120.                 points[i] = segmentEndPoints[i];
    121.             }
    122.  
    123.             void AddEdge(int indexFrom, int indexTo)
    124.             {
    125.                 if (indexFrom > indexTo)
    126.                 {
    127.                     var temp = indexTo;
    128.                     indexTo = indexFrom;
    129.                     indexFrom = temp;
    130.                 }
    131.  
    132.                 if (processedEdges.Contains((indexFrom, indexTo)) == false)
    133.                 {
    134.                     processedEdges.Add((indexFrom, indexTo));
    135.                     segmentEndPoints.AddRange(SplitIntoSegments(positions[indexFrom], positions[indexTo]));
    136.                 }
    137.             }
    138.  
    139.             IEnumerable<Vector3> SplitIntoSegments(Vector3 from, Vector3 to)
    140.             {
    141.                 var t = 0f;
    142.                 var dt = 1f / divisor;
    143.                 for (var i = 0; i < divisor; i++)
    144.                 {
    145.                     yield return Vector3.LerpUnclamped(from, to, t);
    146.                     yield return Vector3.LerpUnclamped(from, to, t + dt);
    147.                     t += dt;
    148.                 }
    149.  
    150.                 yield return Vector3.LerpUnclamped(from, to, 1 - dt);
    151.                 yield return to;
    152.             }
    153.         }
    154.     }
    155.  
    156.     internal struct DistortJob : IJobParallelFor
    157.     {
    158.         public NativeArray<Vector3> originalPositions;
    159.         public NativeArray<Vector3> distortedPositions;
    160.         public NativeArray<Color32> colors;
    161.         public int octaves;
    162.         public float frequency;
    163.         public float amplitude;
    164.         public float time;
    165.  
    166.         public void Execute(int index)
    167.         {
    168.             var original = originalPositions[index];
    169.             var distortion = CalculateDistortion(original);
    170.             distortedPositions[index] = original + distortion;
    171.  
    172.             var diff = distortion.magnitude * 10;
    173.             var color0 = Color.HSVToRGB(Mathf.Repeat(diff, 1), 1, 1);
    174.             var color = Color.Lerp(Color.white, color0, diff);
    175.             colors[index] = color;
    176.         }
    177.         private Vector3 CalculateDistortion(Vector3 position)
    178.         {
    179.             var dx = FancyMeshDemo.noiseX.GetFractalValue(position.x + time, position.y, position.z, octaves, frequency, amplitude);
    180.             var dy = FancyMeshDemo.noiseY.GetFractalValue(position.x, position.y + time, position.z, octaves, frequency, amplitude);
    181.             var dz = FancyMeshDemo.noiseZ.GetFractalValue(position.x, position.y, position.z + time, octaves, frequency, amplitude);
    182.             return new Vector3(dx, dy, dz);
    183.         }
    184.     }
    185. }
    upload_2020-8-11_0-22-37.png
     
  8. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using static UnityEngine.Mathf;
    5.  
    6. namespace QuickDraw.Samples
    7. {
    8.     [ExecuteAlways]
    9.     internal class LoveTunnelDemo : MonoBehaviour
    10.     {
    11.  
    12.         [Header("Tunnel")]
    13.         [Range(0, 200)] public int tunnelLength = 100;
    14.         public AnimationCurve tunnelOpacityCurve = AnimationCurve.Linear(0, 1, 0, 1);
    15.         [Range(0, .05f)] public float interFrameStep = .0025f;
    16.         public float curveScale = 10f;
    17.         [Range(0, 1)] public float targetSpeed = .1f;
    18.         [Range(0, 5)] public float timeScale = 1;
    19.  
    20.         private readonly SimplexNoise noiseX = new SimplexNoise(0);
    21.         private readonly SimplexNoise noiseY = new SimplexNoise(1);
    22.         private readonly SimplexNoise noiseZ = new SimplexNoise(2);
    23.  
    24.         [Header("Tunnel Noise")]
    25.         [Range(0.01f, 1)] public float frequency = 1;
    26.         public float amplitude = 180;
    27.         [Range(1, 4)] public int octaves = 2;
    28.  
    29.         [Header("Heart")]
    30.         public float scale = .05f;
    31.         [Range(0, 100)] public int fillPercent = 100;
    32.         [ColorUsage(false, true)] public Color loveColor = Color.red;
    33.         [ColorUsage(false, true)] public Color hateColor = Color.blue;
    34.         [Range(0, 1)] public float maxColorDeviation = 1;
    35.  
    36.         [Header("Animation")]
    37.         public bool useHeartBeat = true;
    38.         public float heartScale = 1f;
    39.         public AnimationCurve heartScaleCurve = AnimationCurve.Constant(0, 0, 1);
    40.  
    41.         [Space]
    42.         public float saturationFrequency = .01f;
    43.         public AnimationCurve saturationCurve = AnimationCurve.Linear(0, 0, 1, 1);
    44.         [Range(0, 1)] public float saturation = 1f;
    45.  
    46.         [Space]
    47.         public float opacityFrequency = .01f;
    48.         public AnimationCurve opacityCurve = AnimationCurve.Linear(0, 0, 1, 1);
    49.         [Range(0, 1)] public float fillOpacity = .2f;
    50.  
    51.         [Space]
    52.         public float lineWidthFrequency = .1f;
    53.         public AnimationCurve lineWidthCurve = AnimationCurve.Linear(0, 0, 1, 1);
    54.         [Range(0, 10)] public float lineWidth = 5;
    55.  
    56.         private float currentTime = 0;
    57.         private float currentPosition = 0;
    58.         private float currentSpeed = .01f;
    59.         private float heartBpm;
    60.         private float heartBeatTimeOffset = 0;
    61.         private bool drawTunnel = true;
    62.  
    63.         private static readonly List<Vector3> heartShape = new List<Vector3>();
    64.  
    65.         private void Start()
    66.         {
    67.             StartCoroutine(Animate());
    68.         }
    69.  
    70.         private IEnumerator Animate()
    71.         {
    72.             if (Application.isPlaying == false)
    73.             {
    74.                 yield break;
    75.             }
    76.  
    77.             const int QUALITY = 100;
    78.             const float TIMEOUT = 2f;
    79.             const float FILL_OPACITY = .15f;
    80.  
    81.             drawTunnel = false;
    82.  
    83.             ApplyHeartBeatAnimation();
    84.             ApplySaturationAnimation(0);
    85.             ApplySolidOpacityAnimation(0);
    86.             ApplyLineWidthAnimation(0);
    87.  
    88.             QDraw.LineWidth = 20;
    89.  
    90.             yield return new WaitForSecondsRealtime(2);
    91.  
    92.             var timeout = TIMEOUT;
    93.             var currentTime = 0f;
    94.             while (currentTime <= timeout)
    95.             {
    96.                 currentTime += Time.deltaTime;
    97.                 var t = currentTime / timeout;
    98.  
    99.                 using (QDraw.Push())
    100.                 {
    101.                     QDraw.Matrix = Matrix4x4.Translate(new Vector3(0, 0, 30));
    102.                     DrawHeart(QUALITY, heartScale, (int)(t * 100), hateColor, Color.clear, 1, 0, 0);
    103.                 }
    104.  
    105.                 yield return null;
    106.             }
    107.  
    108.             timeout = TIMEOUT;
    109.             currentTime = 0f;
    110.             while (currentTime <= timeout)
    111.             {
    112.                 currentTime += Time.deltaTime;
    113.                 var t = currentTime / timeout;
    114.  
    115.                 using (QDraw.Push())
    116.                 {
    117.                     QDraw.Matrix = Matrix4x4.Translate(new Vector3(0, 0, 30));
    118.                     DrawHeart(QUALITY, heartScale, 100 - (int)(t * 100), hateColor, hateColor, 1, 0, FILL_OPACITY);
    119.                 }
    120.  
    121.                 yield return null;
    122.             }
    123.  
    124.             timeout = TIMEOUT;
    125.             currentTime = 0f;
    126.             while (currentTime <= timeout)
    127.             {
    128.                 currentTime += Time.deltaTime;
    129.                 var t = currentTime / timeout;
    130.  
    131.                 using (QDraw.Push())
    132.                 {
    133.                     QDraw.Matrix = Matrix4x4.Translate(new Vector3(0, 0, 30));
    134.                     DrawHeart(QUALITY, heartScale, (int)(t * 100), loveColor, hateColor, 1, FILL_OPACITY, FILL_OPACITY);
    135.                 }
    136.  
    137.                 yield return null;
    138.             }
    139.  
    140.             timeout = TIMEOUT;
    141.             currentTime = 0f;
    142.             var targetLength = tunnelLength;
    143.             var targetSpeed2 = this.targetSpeed;
    144.             while (currentTime <= timeout)
    145.             {
    146.                 ApplyHeartBeatAnimation();
    147.  
    148.                 currentTime += Time.deltaTime;
    149.                 var t = currentTime / timeout;
    150.  
    151.                 using (QDraw.Push())
    152.                 {
    153.                     QDraw.Matrix = Matrix4x4.TRS(new Vector3(0, 0, 30), Quaternion.identity, Vector3.one * Remap(0, 1, 1, 2, t));
    154.                     DrawHeart(QUALITY, heartScale, 100, loveColor, hateColor, Remap(0, 1, 1, 0, t), FILL_OPACITY, FILL_OPACITY);
    155.                 }
    156.  
    157.                 tunnelLength = (int)Remap(0, 1, 1, targetLength, t);
    158.                 currentSpeed = (int)Remap(0, 1, 0, targetSpeed2, t);
    159.                 DrawTunnel();
    160.  
    161.                 yield return null;
    162.             }
    163.  
    164.             drawTunnel = true;
    165.         }
    166.  
    167.  
    168.         private void Update()
    169.         {
    170.             if (drawTunnel == false)
    171.             {
    172.                 return;
    173.             }
    174.  
    175.             if (Application.isPlaying)
    176.             {
    177.                 currentTime += Time.deltaTime * timeScale;
    178.                 currentPosition += currentSpeed * Time.deltaTime * timeScale;
    179.  
    180.                 if (Input.GetKeyDown(KeyCode.Space))
    181.                 {
    182.                     BpmMeter.AddBeat();
    183.                     heartBpm = BpmMeter.CalculateBeatsPerMinute();
    184.                     heartBeatTimeOffset = Time.unscaledTime;
    185.                 }
    186.  
    187.                 if (Input.GetKeyDown(KeyCode.DownArrow))
    188.                 {
    189.                     heartBeatTimeOffset -= .1f;
    190.                 }
    191.                 if (Input.GetKeyDown(KeyCode.UpArrow))
    192.                 {
    193.                     heartBeatTimeOffset += .1f;
    194.                 }
    195.             }
    196.  
    197.             QDraw.VisibleThroughObstacles = true;
    198.             QDraw.Tint = new Color(1, 1, 1, .8f);
    199.             QDraw.LineWidth = lineWidth;
    200.  
    201.             if (useHeartBeat)
    202.             {
    203.                 ApplyHeartBeatAnimation();
    204.             }
    205.             ApplySaturationAnimation(currentTime);
    206.             ApplySolidOpacityAnimation(currentTime);
    207.             ApplyLineWidthAnimation(currentTime);
    208.  
    209.             DrawTunnel();
    210.         }
    211.  
    212.         private Vector3 currentUpVector = Vector3.up;
    213.         private Vector3 currentForwardVector = Vector3.forward;
    214.         private Vector3 targetUpVector = Vector3.up;
    215.         private Vector3 targetForwardVector = Vector3.forward;
    216.         private Vector3 upVectorVelocity = Vector3.zero;
    217.         private Vector3 forwardVectorVelocity = Vector3.zero;
    218.  
    219.         private void DrawTunnel()
    220.         {
    221.             var deltaStep = currentPosition % interFrameStep;
    222.             var firstPosition = currentPosition - deltaStep;
    223.  
    224.             var (rootPosition, _, currentSpeedFactor) = GetFrameTransform(currentPosition);
    225.             currentSpeed = targetSpeed / currentSpeedFactor * heartBpm / 60;
    226.  
    227.             var (_, rootRotation, _) = GetFrameTransform(currentPosition + interFrameStep * 5);
    228.             targetUpVector = rootRotation * Vector3.up;
    229.             targetForwardVector = rootRotation * Vector3.forward;
    230.             currentUpVector = Vector3.SmoothDamp(currentUpVector, targetUpVector, ref upVectorVelocity, .3f);
    231.             currentForwardVector = Vector3.SmoothDamp(currentForwardVector, targetForwardVector, ref forwardVectorVelocity, .3f);
    232.             rootRotation = Quaternion.LookRotation(currentForwardVector, currentUpVector);
    233.  
    234.             QDraw.Matrix = Matrix4x4.TRS(rootPosition, rootRotation, Vector3.one).inverse;
    235.  
    236.             for (var i = 0; i < tunnelLength; i++)
    237.             {
    238.                 var framePosition = firstPosition + i * interFrameStep;
    239.                 var opacity = tunnelOpacityCurve.Evaluate(Remap(currentPosition, firstPosition + 10 * interFrameStep, 0, 1, framePosition));
    240.  
    241.                 var bpmPeriod = 60 / heartBpm;
    242.                 fillPercent = Clamp(RoundToInt(Remap(0, 1, 0, 100, PingPong(framePosition + Time.unscaledTime - heartBeatTimeOffset, bpmPeriod * 2))), 0, 100);
    243.  
    244.                 using (QDraw.Push())
    245.                 {
    246.                     DrawTunnelFrame(framePosition, opacity);
    247.                 }
    248.             }
    249.  
    250.             void DrawTunnelFrame(float framePosition, float frameOpacity)
    251.             {
    252.                 var colorOffset = maxColorDeviation * (PerlinNoise(framePosition * .2f, currentTime * .01f) - .5f);
    253.                 var loveColor = ChangeColor(this.loveColor, colorOffset);
    254.                 var hateColor = ChangeColor(this.hateColor, colorOffset);
    255.                 var pointCount = FloorToInt(6 + 94 * PingPong(framePosition * 2, 1));
    256.  
    257.                 var (position, rotation, _) = GetFrameTransform(framePosition);
    258.                 QDraw.Matrix *= Matrix4x4.TRS(position, rotation, Vector3.one);
    259.                 DrawHeart(pointCount, heartScale, fillPercent, loveColor, hateColor, frameOpacity, fillOpacity, fillOpacity);
    260.             }
    261.  
    262.             (Vector3 position, Quaternion rotation, float speed) GetFrameTransform(float t)
    263.             {
    264.                 var translation = GetCurvePoint(t);
    265.                 var ahead = GetCurveDerivative(t);
    266.                 var normal = GetCurveSecondDerivative(t);
    267.                 var rotation = Quaternion.LookRotation(ahead, normal);
    268.                 return (translation, rotation, ahead.magnitude / curveScale);
    269.             }
    270.  
    271.             Vector3 GetCurvePoint(float t)
    272.             {
    273.                 var x = noiseX.GetFractalValue(t, 0, octaves, frequency, amplitude);
    274.                 var y = noiseY.GetFractalValue(t, 1, octaves, frequency, amplitude);
    275.                 var z = noiseZ.GetFractalValue(t, 2, octaves, frequency, amplitude);
    276.                 return new Vector3(x, y, z) * curveScale;
    277.             }
    278.  
    279.             Vector3 GetCurveDerivative(float t)
    280.             {
    281.                 const float DELTA = .01f;
    282.                 return (GetCurvePoint(t + DELTA) - GetCurvePoint(t - DELTA)) / (2 * DELTA);
    283.             }
    284.  
    285.             Vector3 GetCurveSecondDerivative(float t)
    286.             {
    287.                 const float DELTA = .01f;
    288.                 return (GetCurveDerivative(t + DELTA) - GetCurveDerivative(t - DELTA)) / (2 * DELTA);
    289.             }
    290.         }
    291.  
    292.         private void DrawHeart(int vertexCount, float scale, int fillPercent, Color loveColor, Color hateColor, float opacity, float loveFillOpacity, float hateFillOpacity)
    293.         {
    294.             heartShape.Clear();
    295.             heartShape.AddRange(GenerateHeartShape(vertexCount, scale * this.scale));
    296.  
    297.             var totalTriangles = heartShape.Count - 3;
    298.             var hotTriangles = RoundToInt(totalTriangles / 100f * fillPercent);
    299.             var coldTriangles = totalTriangles - hotTriangles;
    300.  
    301.             loveColor *= opacity;
    302.             hateColor *= opacity;
    303.  
    304.             var solidLoveColor = loveColor;
    305.             solidLoveColor.a *= loveFillOpacity;
    306.  
    307.             var solidHateColor = hateColor;
    308.             solidHateColor.a *= hateFillOpacity;
    309.  
    310.             for (var i = 2; i < heartShape.Count; i++)
    311.             {
    312.                 var color = i - 2 < hotTriangles ? solidLoveColor : solidHateColor;
    313.                 QDraw.SolidTriangle(heartShape[0], heartShape[i], heartShape[i - 1], color);
    314.             }
    315.  
    316.             if (coldTriangles > 0)
    317.             {
    318.                 var points = coldTriangles + 2;
    319.                 if (hotTriangles == 0)
    320.                 {
    321.                     points++;
    322.                 }
    323.  
    324.                 QDraw.Polyline(heartShape, heartShape.Count - points, points, hateColor);
    325.             }
    326.  
    327.             if (hotTriangles > 0)
    328.             {
    329.                 var points = hotTriangles + 2;
    330.                 if (coldTriangles == 0)
    331.                 {
    332.                     points++;
    333.                 }
    334.  
    335.                 QDraw.Polyline(heartShape, 0, points, loveColor);
    336.             }
    337.         }
    338.  
    339.         private static float Remap(float fromMin, float fromMax, float toMin, float toMax, float value)
    340.         {
    341.             var t = InverseLerp(fromMin, fromMax, value);
    342.             return Lerp(toMin, toMax, t);
    343.         }
    344.  
    345.         private void ApplyHeartBeatAnimation()
    346.         {
    347.             heartScale = heartScaleCurve.Evaluate((Time.unscaledTime - heartBeatTimeOffset) * heartBpm / 60f);
    348.         }
    349.  
    350.         private void ApplySaturationAnimation(float t)
    351.         {
    352.             var value = PerlinNoise(t * saturationFrequency, .2f);
    353.             saturation = saturationCurve.Evaluate(value);
    354.         }
    355.  
    356.         private void ApplySolidOpacityAnimation(float t)
    357.         {
    358.             var value = PerlinNoise(t * opacityFrequency, 10);
    359.             fillOpacity = opacityCurve.Evaluate(value);
    360.         }
    361.  
    362.         private void ApplyLineWidthAnimation(float t)
    363.         {
    364.             var value = PerlinNoise(t * lineWidthFrequency, 5);
    365.             lineWidth = lineWidthCurve.Evaluate(value);
    366.         }
    367.  
    368.         private Color ChangeColor(Color color, float amount)
    369.         {
    370.             Color.RGBToHSV(color, out var h, out _, out var v);
    371.             var s = saturation;
    372.             h = Repeat(h + amount, 1);
    373.             var result = Color.HSVToRGB(h, s, v);
    374.             result.a = color.a;
    375.             return result;
    376.         }
    377.  
    378.         private static IEnumerable<Vector3> GenerateHeartShape(int count, float scale)
    379.         {
    380.             for (var i = 0; i < count; i++)
    381.             {
    382.                 var t = i / (count - 1f) * 2 * PI;
    383.                 var x = -16 * Sin(t) * Sin(t) * Sin(t);
    384.                 var y = 13 * Cos(t) - 5 * Cos(2 * t) - 2 * Cos(3 * t) - Cos(4 * t);
    385.                 var p = new Vector3(x, y, 0) * scale;
    386.                 yield return p;
    387.             }
    388.         }
    389.     }
    390. }

    upload_2020-8-15_23-59-34.png

    The sample code is "a bit" messy right now. I have just finished putting all the things together. The heart shape is procedurally generated and drawn using two methods: QDraw.SolidTriangle() and QDraw.Polyline().

    It took me a couple of hours to get the first version working, and then a couple of evenings to figure out what exactly do I want to get as a result.

    What I really like about QuickDraw is you don't have to write boilerplate to visualize what your code is doing. No hitting Play button, no writing custom editor extensions, no OnDrawGizmos. And also no need to write to the console - QDraw.Text3D() can show debug information on the screen in real time at the exact world position it relates to.
     
    Last edited: Aug 15, 2020
    Lars-Steenhoff likes this.
  9. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    profiler.png
    editor.png
    This demo draws ~10k arrows (~20k lines) and 50k points. The profiler shows that all the drawing-related code takes less than 0,5 ms in total in the editor. The rest of the time is taken by simplex noise computation and particle simulation.

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Collections.LowLevel.Unsafe;
    3. using Unity.Jobs;
    4. using UnityEngine;
    5.  
    6. namespace QuickDraw.Samples
    7. {
    8.     [ExecuteAlways]
    9.     internal class VectorFieldDemo : DemoBase
    10.     {
    11.         #region Properties
    12.  
    13.         [Header("Vector Field")]
    14.         [Range(1, 20)] public float areaSize = 10;
    15.         [Range(2, 200)] public int fieldResolution = 100;
    16.         [Range(0, 1)] public float fieldOpacity = 1;
    17.         public Gradient gradient = null;
    18.         public Color boundsColor = new Color(.5f, .5f, .5f, .5f);
    19.  
    20.         [Header("Noise")]
    21.         [Range(0, 1)] public float frequency = .25f;
    22.         [Range(0, 1)] public float timeScale = .1f;
    23.  
    24.         [Header("Arrows")]
    25.         public float arrowWidth = 0.005f;
    26.         public float relativeArrowWidth = .15f;
    27.  
    28.         [Header("Particles")]
    29.         [Range(1, 100_000)] public int particleCount = 50_000;
    30.         [Range(0, 10)] public float particleWidth = 5;
    31.         [Range(0, 5)] public float particleSpeed = 1;
    32.         [Range(0, 1)] public float particleOpacity = 1;
    33.  
    34.         [Header("Boats")]
    35.         public bool boatsEnabled = true;
    36.         [Range(1, 10)] public int boatCount = 3;
    37.  
    38.         private float time;
    39.         private float deltaTime;
    40.         private Bounds bounds;
    41.  
    42.         internal static readonly SimplexNoise noise = new SimplexNoise();
    43.         internal static readonly System.Random random = new System.Random();
    44.         internal static Gradient gradientStatic;
    45.         private NativeArray<Line> arrows;
    46.         private NativeArray<Vector3> particles;
    47.         private NativeArray<Color32> colors;
    48.  
    49.         #endregion
    50.  
    51.         private void OnEnable()
    52.         {
    53.             gradientStatic = gradient;
    54.         }
    55.  
    56.         private void Update()
    57.         {
    58.             if (Application.isPlaying)
    59.             {
    60.                 QDraw.Matrix = transform.localToWorldMatrix;
    61.                 ControlCamera();
    62.  
    63.                 deltaTime = Time.deltaTime;
    64.                 time += deltaTime * timeScale;
    65.             }
    66.             else
    67.             {
    68.                 deltaTime = 0;
    69.             }
    70.  
    71.             bounds = new Bounds(Vector2.zero, Vector2.one * areaSize * 1.25f);
    72.  
    73.             DrawBounds();
    74.             DrawParticles();
    75.             DrawVectorField();
    76.         }
    77.  
    78.         private void DrawBounds()
    79.         {
    80.             var start = bounds.min;
    81.             var size = bounds.size;
    82.             QDraw.Quad(new Vector3(start.x, 0, start.y), new Vector3(size.x, 0, 0), new Vector3(0, 0, size.y), boundsColor);
    83.         }
    84.  
    85.         private void DrawVectorField()
    86.         {
    87.             if (arrows.Length != fieldResolution * fieldResolution * 2)
    88.             {
    89.                 if (arrows.IsCreated)
    90.                 {
    91.                     arrows.Dispose();
    92.                 }
    93.  
    94.                 arrows = new NativeArray<Line>(fieldResolution * fieldResolution * 2, Allocator.Persistent);
    95.             }
    96.             var vectorJob = new FieldArrowsJob
    97.             {
    98.                 vectors = arrows,
    99.                 cellCount = fieldResolution,
    100.                 vectorLength = areaSize / fieldResolution,
    101.                 areaSize = areaSize,
    102.                 arrowWidth = arrowWidth,
    103.                 arrowHeadWidth = relativeArrowWidth * areaSize / fieldResolution,
    104.                 fieldOpacity = fieldOpacity,
    105.                 frequency = frequency,
    106.                 time = time,
    107.             };
    108.  
    109.             var arrowCount = arrows.Length / 2;
    110.             var handle = vectorJob.Schedule(arrowCount, 64);
    111.             handle.Complete();
    112.  
    113.             unsafe
    114.             {
    115.                 QDraw.Lines3D(arrows.Length, (Line*)arrows.GetUnsafeReadOnlyPtr());
    116.             }
    117.         }
    118.  
    119.         private void DrawParticles()
    120.         {
    121.             if (particles.Length != particleCount)
    122.             {
    123.                 var particlesNew = new NativeArray<Vector3>(particleCount, Allocator.Persistent);
    124.                 var colorsNew = new NativeArray<Color32>(particleCount, Allocator.Persistent);
    125.                 GenerateParticles(particlesNew, colorsNew, areaSize, 0, particlesNew.Length);
    126.  
    127.                 if (particles.IsCreated)
    128.                 {
    129.                     NativeArray<Vector3>.Copy(particles, particlesNew, Mathf.Min(particles.Length, particlesNew.Length));
    130.                     NativeArray<Color32>.Copy(colors, colorsNew, Mathf.Min(particles.Length, particlesNew.Length));
    131.                     particles.Dispose();
    132.                     colors.Dispose();
    133.                 }
    134.  
    135.                 particles = particlesNew;
    136.                 colors = colorsNew;
    137.             }
    138.  
    139.             var job = new FieldParticlesJob
    140.             {
    141.                 particles = particles,
    142.                 colors = colors,
    143.                 particleOpacity = particleOpacity,
    144.                 areaSize = areaSize,
    145.                 bounds = bounds,
    146.                 deltaTime = deltaTime,
    147.                 frequency = frequency,
    148.                 particleSpeed = particleSpeed,
    149.                 time = time,
    150.             };
    151.  
    152.             var handle = job.Schedule(particles.Length, 64);
    153.             handle.Complete();
    154.  
    155.             unsafe
    156.             {
    157.                 QDraw.Points(particles.Length, (Vector3*)particles.GetUnsafeReadOnlyPtr(), particleWidth, (Color32*)colors.GetUnsafeReadOnlyPtr());
    158.             }
    159.  
    160.             if (boatsEnabled)
    161.             {
    162.                 for (int i = 0; i < Mathf.Min(boatCount, particles.Length); i++)
    163.                 {
    164.                     var position = particles[i];
    165.  
    166.                     var vector = GetVectorFieldValue(new Vector2(position.x, position.z), frequency, time);
    167.                     var normal = new Vector3(vector.x, 0, vector.y);
    168.                     var forward = transform.TransformDirection(Vector3.Cross(normal, Vector3.up));
    169.  
    170.                     QDraw.Icon3D("sailboat", position + .15f * Vector3.up, forward, Vector3.up, .5f, Color.white);
    171.                 }
    172.             }
    173.         }
    174.  
    175.         internal static Vector2 GetVectorFieldValue(Vector2 p, float frequency, float time)
    176.         {
    177.             const float DELTA = .01f;
    178.  
    179.             var value = GetScalarFieldValue(p, frequency, time);
    180.             var dx = (GetScalarFieldValue(p + new Vector2(DELTA, 0), frequency, time) - value) / DELTA;
    181.             var dy = (GetScalarFieldValue(p + new Vector2(0, DELTA), frequency, time) - value) / DELTA;
    182.             return new Vector2(-dy, dx);
    183.         }
    184.  
    185.         private static float GetScalarFieldValue(Vector2 p, float frequency, float time)
    186.         {
    187.             p *= frequency;
    188.             return noise.GetValue(p.x, p.y - time);
    189.         }
    190.  
    191.         internal static void GenerateParticles(NativeArray<Vector3> particles, NativeArray<Color32> colors, float areaSize, int offset, int count)
    192.         {
    193.             for (int i = 0; i < count; i++)
    194.             {
    195.                 var rx = (float)random.NextDouble();
    196.                 var rz = (float)random.NextDouble();
    197.                 var x = areaSize * (rx - .5f);
    198.                 var z = areaSize * (rz - .5f);
    199.                 particles[offset + i] = new Vector3(x, 0, z);
    200.                 colors[offset + i] = gradientStatic.Evaluate(rz);
    201.             }
    202.         }
    203.  
    204.         private void OnDisable()
    205.         {
    206.             if (arrows.IsCreated)
    207.             {
    208.                 arrows.Dispose();
    209.             }
    210.  
    211.             if (particles.IsCreated)
    212.             {
    213.                 particles.Dispose();
    214.                 colors.Dispose();
    215.             }
    216.         }
    217.     }
    218. }
    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Jobs;
    3. using UnityEngine;
    4.  
    5. namespace QuickDraw.Samples
    6. {
    7.     internal struct FieldParticlesJob : IJobParallelFor
    8.     {
    9.         public NativeArray<Vector3> particles;
    10.         public NativeArray<Color32> colors;
    11.         public float particleOpacity;
    12.         public float particleSpeed;
    13.         public float deltaTime;
    14.         public float areaSize;
    15.         public Bounds bounds;
    16.         public float time;
    17.         public float frequency;
    18.  
    19.         public void Execute(int index)
    20.         {
    21.             var p3 = particles[index];
    22.             var p2 = new Vector2(p3.x, p3.z);
    23.  
    24.             var v2 = VectorFieldDemo.GetVectorFieldValue(p2, frequency, time);
    25.             var v3 = new Vector3(v2.x, 0, v2.y);
    26.  
    27.             p3 += v3 * particleSpeed * deltaTime;
    28.             p2 = new Vector2(p3.x, p3.z);
    29.             if (bounds.Contains(p2))
    30.             {
    31.                 particles[index] = p3;
    32.             }
    33.             else
    34.             {
    35.                 VectorFieldDemo.GenerateParticles(particles, colors, areaSize, index, 1);
    36.             }
    37.  
    38.             var color = colors[index];
    39.             color.a = (byte)(particleOpacity * 255);
    40.             colors[index] = color;
    41.         }
    42.     }
    43.  
    44.     internal struct FieldArrowsJob : IJobParallelFor
    45.     {
    46.         [NativeDisableParallelForRestriction, WriteOnly] public NativeArray<Line> vectors;
    47.         public float areaSize;
    48.         public int cellCount;
    49.         public float time;
    50.         public float frequency;
    51.         public float fieldOpacity;
    52.         public float vectorLength;
    53.         public float arrowWidth;
    54.         public float arrowHeadWidth;
    55.  
    56.         public void Execute(int index)
    57.         {
    58.             var ix = index / cellCount;
    59.             var iz = index % cellCount;
    60.             var x = areaSize * (ix / (cellCount - 1f) - .5f);
    61.             var z = areaSize * (iz / (cellCount - 1f) - .5f);
    62.  
    63.             var p = new Vector2(x, z);
    64.             var vector = VectorFieldDemo.GetVectorFieldValue(p, frequency, time);
    65.             var magnitude = vector.magnitude;
    66.  
    67.             var color = VectorFieldDemo.gradientStatic.Evaluate(Mathf.InverseLerp(0, 1, magnitude));
    68.             color.a = fieldOpacity;
    69.  
    70.             var halfVector = new Vector3(vector.x * .5f, 0, vector.y * .5f) / magnitude * vectorLength;
    71.  
    72.             var start = new Vector3(p.x, 0, p.y);
    73.             var middle = start + halfVector;
    74.             var end = middle + halfVector;
    75.  
    76.             vectors[2 * index + 0] = new Line(start, middle, arrowWidth, color);
    77.             vectors[2 * index + 1] = new Line(middle, end, arrowHeadWidth, 0, color);
    78.         }
    79.     }
    80. }

    I've been spending more time on demos than I originally planned to for two reasons:
    1. The latest ones look much better than the first ones, so I don't want to stop.
    2. It helps to solidify the API and find and fix minor inconveniences.

    PS
    This doesn't mean that QuickDraw will run on every Android device!

    Screenshot_2020-08-23-22-24-51-485_com.Company.QuickDraw.jpg Screenshot_2020-08-23-22-25-16-052_com.Company.QuickDraw.jpg

    PPS
    The Megademo download links in the first post are up to date.
     
    Last edited: Aug 23, 2020
    Lars-Steenhoff likes this.
  10. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    Code (CSharp):
    1. using System;
    2. using System.Threading;
    3. using Unity.Collections;
    4. using Unity.Collections.LowLevel.Unsafe;
    5. using Unity.Jobs;
    6. using UnityEngine;
    7. using Random = System.Random;
    8.  
    9. namespace QuickDraw.Samples
    10. {
    11.     [ExecuteAlways]
    12.     internal class PointCloudDemo : MonoBehaviour
    13.     {
    14.         public GameObject house = null;
    15.         [Range(0, 10_000)] public int raycastsPerFrame = 1_000;
    16.         [Range(1, 1_000_000)] public int maxPointsCount = 500_000;
    17.         [Range(0, 0.1f)] public float pointSize = .05f;
    18.         public bool drawMesh;
    19.         public Color meshColor = new Color(1, 1, 1, .01f);
    20.         public Camera mainCamera = null;
    21.  
    22.         [Space]
    23.         private NativeArray<Vector3> points;
    24.         private NativeArray<Color32> colors;
    25.         private int currentIndex;
    26.         private int[] triangles;
    27.         private Color32[] triangleColors;
    28.         private Mesh mesh;
    29.  
    30.         private void Update()
    31.         {
    32.             if (points.Length != maxPointsCount)
    33.             {
    34.                 DisposeArrays();
    35.                 InitializeArrays();
    36.             }
    37.  
    38.             FireRaycasts();
    39.             DrawPoints();
    40.             if (drawMesh)
    41.             {
    42.                 QDraw.Mesh(mesh, Vector3.zero, meshColor);
    43.             }
    44.  
    45.             void FireRaycasts()
    46.             {
    47.                 var origin = mainCamera.transform.position;
    48.  
    49.                 // generate random raycasts
    50.                 var raycasts = new NativeArray<RaycastCommand>(raycastsPerFrame, Allocator.TempJob);
    51.                 var job = new GenerateRaycastsJob
    52.                 {
    53.                     raycasts = raycasts,
    54.                     origin = origin
    55.                 };
    56.                 var handle = job.Schedule(raycasts.Length, 64);
    57.                 handle.Complete();
    58.  
    59.                 // fire raycasts
    60.                 var hits = new NativeArray<RaycastHit>(raycastsPerFrame, Allocator.TempJob);
    61.                 RaycastCommand.ScheduleBatch(raycasts, hits, 10).Complete();
    62.  
    63.                 // convert hits to new points in the cloud
    64.                 for (var i = 0; i < raycastsPerFrame; i++)
    65.                 {
    66.                     if (hits[i].collider == null)
    67.                     {
    68.                         continue;
    69.                     }
    70.  
    71.                     points[currentIndex] = hits[i].point;
    72.                     var firstTriangleVertexIndex = hits[i].triangleIndex * 3;
    73.                     colors[currentIndex] = triangleColors[firstTriangleVertexIndex];
    74.                     currentIndex = (currentIndex + 1) % maxPointsCount;
    75.                 }
    76.  
    77.                 raycasts.Dispose();
    78.                 hits.Dispose();
    79.             }
    80.  
    81.             unsafe void DrawPoints()
    82.             {
    83.                 var positionsPtr = (Vector3*)points.GetUnsafeReadOnlyPtr();
    84.                 var colorsPtr = (Color32*)colors.GetUnsafeReadOnlyPtr();
    85.                 QDraw.Points3D(points.Length, positionsPtr, pointSize, colorsPtr);
    86.             }
    87.         }
    88.  
    89.         private void InitializeArrays()
    90.         {
    91.             points = new NativeArray<Vector3>(maxPointsCount, Allocator.Persistent);
    92.  
    93.             for (var i = 0; i < points.Length; i++)
    94.             {
    95.                 points[i] = new Vector3(float.NaN, float.NaN, float.NaN);
    96.             }
    97.  
    98.             mesh = house.GetComponentInChildren<MeshFilter>().sharedMesh;
    99.             triangles = mesh.triangles;
    100.             triangleColors = new Color32[triangles.Length];
    101.             colors = new NativeArray<Color32>(maxPointsCount, Allocator.Persistent);
    102.  
    103.             var materials = house.GetComponentInChildren<MeshRenderer>().sharedMaterials;
    104.             var submeshCount = mesh.subMeshCount;
    105.             for (var submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++)
    106.             {
    107.                 var start = mesh.GetIndexStart(submeshIndex);
    108.                 var count = mesh.GetIndexCount(submeshIndex);
    109.                 for (var i = 0; i < count; i++)
    110.                 {
    111.                     triangleColors[start + i] = materials[submeshIndex].color;
    112.                 }
    113.             }
    114.         }
    115.  
    116.         private void OnDisable()
    117.         {
    118.             DisposeArrays();
    119.         }
    120.  
    121.         private void DisposeArrays()
    122.         {
    123.             if (points.IsCreated)
    124.             {
    125.                 points.Dispose();
    126.                 colors.Dispose();
    127.             }
    128.         }
    129.  
    130.         private struct GenerateRaycastsJob : IJobParallelFor
    131.         {
    132.             [WriteOnly] public NativeArray<RaycastCommand> raycasts;
    133.             public Vector3 origin;
    134.             [ThreadStatic] private static Random random;
    135.  
    136.             public void Execute(int index)
    137.             {
    138.                 raycasts[index] = new RaycastCommand(origin, OnUnitSphere());
    139.             }
    140.  
    141.             private static Vector3 OnUnitSphere()
    142.             {
    143.                 if (random == null)
    144.                 {
    145.                     random = new Random(Thread.CurrentThread.ManagedThreadId);
    146.                 }
    147.  
    148.                 var u = (float)random.NextDouble();
    149.                 var v = (float)random.NextDouble();
    150.                 var theta = 2 * Mathf.PI * u;
    151.                 var phi = Mathf.Acos(2 * v - 1);
    152.                 var sinPhi = Mathf.Sin(phi);
    153.                 var x = sinPhi * Mathf.Cos(theta);
    154.                 var y = sinPhi * Mathf.Sin(theta);
    155.                 var z = Mathf.Cos(phi);
    156.                 return new Vector3(x, y, z);
    157.             }
    158.         }
    159.     }
    160. }
    upload_2020-9-4_0-1-52.png
     
  11. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    upload_2020-9-6_20-26-37.png
    2.png
     
  12. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Jobs;
    3. using UnityEngine;
    4.  
    5. namespace QuickDraw.Samples
    6. {
    7.     [ExecuteAlways]
    8.     internal class DigitalWindDemo : DemoBase
    9.     {
    10.         [Header("Shape Resolution")]
    11.         [Range(20, 1000)] public int countA = 200;
    12.         [Range(6, 300)] public int countB = 60;
    13.  
    14.         private struct Particle
    15.         {
    16.             public Vector3 position;
    17.             public float size;
    18.             public Color32 color;
    19.         }
    20.  
    21.         [WriteOnly] private NativeArray<Particle> particles;
    22.  
    23.         private void Update()
    24.         {
    25.             QDraw.Matrix = transform.localToWorldMatrix;
    26.  
    27.             if (Application.isPlaying)
    28.             {
    29.                 ControlCamera();
    30.             }
    31.  
    32.             if (particles.Length != countA * countB)
    33.             {
    34.                 if (particles.IsCreated)
    35.                 {
    36.                     particles.Dispose();
    37.                 }
    38.  
    39.                 particles = new NativeArray<Particle>(countA * countB, Allocator.Persistent);
    40.             }
    41.  
    42.  
    43.             DrawShape();
    44.         }
    45.  
    46.         private void DrawShape()
    47.         {
    48.             var job = new DigitalWindJob
    49.             {
    50.                 particles = particles,
    51.                 count = countA,
    52.                 count2 = countB,
    53.                 minPerlinLoopRadius = .1f,
    54.                 maxPerlinLoopRadius = 2f,
    55.                 speedA = .1f,
    56.                 speedB = .3f,
    57.                 time = Time.time,
    58.             };
    59.             var handle = job.Schedule(particles.Length, countB);
    60.             handle.Complete();
    61.  
    62.             foreach (var particle in particles)
    63.             {
    64.                 QDraw.Point(particle.position, particle.size, particle.color);
    65.             }
    66.  
    67.             for (int i = 1; i < countA; i++)
    68.             {
    69.                 for (int j = 1; j < countB; j++)
    70.                 {
    71.                     var particleA = particles[i * countB + j];
    72.                     var particleB = particles[(i - 1) * countB + j];
    73.                     var particleC = particles[i * countB + (j - 1)];
    74.  
    75.                     QDraw.Line(particleA.position, particleB.position, particleA.color, particleB.color);
    76.                     QDraw.Line(particleC.position, particleB.position, particleC.color, particleB.color);
    77.                 }
    78.             }
    79.         }
    80.  
    81.         private void OnDisable()
    82.         {
    83.             if (particles.IsCreated)
    84.             {
    85.                 particles.Dispose();
    86.             }
    87.         }
    88.  
    89.         private struct DigitalWindJob: IJobParallelFor
    90.         {
    91.             public NativeArray<Particle> particles;
    92.             public int count, count2;
    93.             public float minPerlinLoopRadius, maxPerlinLoopRadius;
    94.             public float speedA, speedB;
    95.             public float time;
    96.  
    97.             public void Execute(int index)
    98.             {
    99.                 var a = index / count2 / (count - 1f);
    100.                 var b = index % count2 / (count2 - 1f);
    101.  
    102.                 var angleA = a * Mathf.PI * 2 - time * .1f;
    103.                 var angleB = b * Mathf.PI * 2;
    104.  
    105.                 var zRotation = Quaternion.Euler(0, 0, angleA * Mathf.Rad2Deg);
    106.                 var yRotation = Quaternion.Euler(0, angleB * Mathf.Rad2Deg, 0);
    107.  
    108.                 var perlinLoopRadius = minPerlinLoopRadius + Mathf.PingPong(b * 2, 1) * (maxPerlinLoopRadius - minPerlinLoopRadius);
    109.                 var x = Mathf.Cos(angleA) * perlinLoopRadius;
    110.                 var y = Mathf.Sin(angleA) * perlinLoopRadius;
    111.                 var perlin = Mathf.PerlinNoise(x + time * speedA, y + time * speedB);
    112.  
    113.                 var position = zRotation * (yRotation * new Vector3(.5f + perlin * .5f, 0, 0) + new Vector3(.5f + perlin * 2, 0, 0));
    114.                 var center = zRotation * new Vector3(.5f + perlin * 2, 0, 0);
    115.  
    116.                 var size = 1 + Mathf.PingPong(1 + b * 2, 1) * 5;
    117.  
    118.                 var color = Color.HSVToRGB(Mathf.Repeat(time * .1f + a + Mathf.PingPong(b * 2, 1) * .15f, 1), 1, 1);
    119.                 color *= .3f + .7f * Vector3.Dot(Vector3.right, (position - center).normalized);
    120.  
    121.                 particles[index] = new Particle
    122.                 {
    123.                     position = position,
    124.                     size = size,
    125.                     color = color
    126.                 };
    127.             }
    128.         }
    129.     }
    130. }
    upload_2020-9-29_22-19-49.png
     
    Last edited: Sep 29, 2020
  13. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447

    Code (CSharp):
    1. using System.Linq;
    2. using UnityEngine;
    3. using WasapiAudio;
    4. using WasapiAudio.Core;
    5.  
    6. namespace QuickDraw.Samples
    7. {
    8.     [ExecuteAlways]
    9.     internal class DiscoSpiralDemo : MonoBehaviour
    10.     {
    11.         [Header("Spiral")]
    12.         public int rows = 200;
    13.         public int columns = 200;
    14.         public float quadWidth = .05f;
    15.         public float quadHeight = .05f;
    16.         public bool alternativeStyle = false;
    17.  
    18.         [Header("Rays")]
    19.         public Color rayColor = new Color(.06f, .05f, .08f, .3f);
    20.  
    21.         private WasapiAudio.WasapiAudio wasapiAudio;
    22.         private float[] spectrumData;
    23.         private float averageLevel;
    24.         private float velocity;
    25.         private readonly Vector3[] rays = new Vector3[100];
    26.  
    27.         public void OnEnable()
    28.         {
    29.             for (var i = 0; i < rays.Length; i++)
    30.             {
    31.                 rays[i] = Random.onUnitSphere;
    32.             }
    33.  
    34.             spectrumData = new float[columns];
    35.             wasapiAudio = new WasapiAudio.WasapiAudio(WasapiCaptureType.Loopback, columns, ScalingStrategy.Linear, 100, 20000, null, data => { spectrumData = data; });
    36.             wasapiAudio.StartListen();
    37.         }
    38.  
    39.         public void OnDisable()
    40.         {
    41.             wasapiAudio.StopListen();
    42.         }
    43.  
    44.         private void Update()
    45.         {
    46.             QDraw.Matrix = transform.localToWorldMatrix;
    47.  
    48.             for (var i = 0; i < spectrumData.Length; i++)
    49.             {
    50.                 spectrumData[i] *= 4;
    51.             }
    52.  
    53.             averageLevel = Mathf.SmoothDamp(averageLevel, spectrumData.Sum() / spectrumData.Length, ref velocity, .1f);
    54.  
    55.             DrawAll();
    56.         }
    57.  
    58.         private void DrawAll()
    59.         {
    60.             DrawSpectrum();
    61.             DrawDiscoBalls();
    62.         }
    63.  
    64.         private void DrawSpectrum()
    65.         {
    66.             for (var column = 0; column < columns; column++)
    67.             {
    68.                 var t = column / (columns - 1f);
    69.                 using (QDraw.Push())
    70.                 {
    71.                     QDraw.Matrix *= Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, t * 1444 - averageLevel * 100 + Time.time * 10), Vector3.one * (.6f + averageLevel * 4));
    72.                     DrawColumn(column, new Vector3(0, t * 4, 0));
    73.                 }
    74.             }
    75.         }
    76.  
    77.  
    78.         private void DrawColumn(int index, Vector3 start)
    79.         {
    80.             var top = (int)(spectrumData[index] * rows);
    81.             for (var row = 0; row < top; row++)
    82.             {
    83.                 var t = row / (rows - 1f);
    84.                 float hue;
    85.                 if (alternativeStyle)
    86.                 {
    87.                     hue = Mathf.Repeat(1 - spectrumData[index] - t + Time.time * .05f - (index + Time.time) * .005f, 1);
    88.                 }
    89.                 else
    90.                 {
    91.                     hue = Mathf.Repeat(1 - t * 4 + Time.time * .05f - (index + Time.time) * .005f, 1);
    92.                 }
    93.  
    94.                 var color = Color.HSVToRGB(hue, 1, 1);
    95.  
    96.                 var startPosition = start + row * quadHeight * Vector3.up;
    97.                 var width = quadWidth * Mathf.Lerp(.5f, 1.5f, t * 10);
    98.                 var height = quadHeight * .75f * Mathf.Lerp(.5f, 1.5f, t * 10);
    99.                 QDraw.SolidQuad(startPosition, new Vector3(width, 0, 0), new Vector3(0, height, 0), color);
    100.             }
    101.         }
    102.  
    103.         private void DrawDiscoBalls()
    104.         {
    105.             using (QDraw.Push())
    106.             {
    107.                 var rotation = Quaternion.Euler(0, Time.time * 10, 0);
    108.  
    109.                 QDraw.Matrix = Matrix4x4.TRS(new Vector3(-3f, 3, 0), rotation, Vector3.one);
    110.                 DrawRays();
    111.  
    112.                 QDraw.Matrix = Matrix4x4.TRS(new Vector3(3f, 3, 0), rotation, Vector3.one);
    113.                 DrawRays();
    114.             }
    115.  
    116.             void DrawRays()
    117.             {
    118.                 for (var i = 0; i < rays.Length; i++)
    119.                 {
    120.                     var start = rays[i] * .5f;
    121.                     var end = rays[i] * 10;
    122.                     QDraw.Line3D(start, end, 0, .1f, rayColor, Color.clear);
    123.                 }
    124.             }
    125.         }
    126.     }
    127. }
    upload_2020-10-5_23-19-35.png

    This is mostly an experiment. The script captures the current audio using UnityWasapiAudio and draws its spectrum in real time.

    I'm wondering how would one make visuals like that using regular Unity tools. Particles? Dynamic mesh?
     
  14. EricDaily

    EricDaily

    Joined:
    Dec 31, 2012
    Posts:
    16
    This looks soooo cool!!! ETA on Asset Store availability? ;)