Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MRDL -> MRTK: Line creation / rendering classes #1305

Merged
merged 12 commits into from
Nov 8, 2017

Conversation

Railboy
Copy link
Contributor

@Railboy Railboy commented Nov 6, 2017

lines

This pull request includes classes for line creation, manipulation & rendering, including:

Lines

  • Basic Line
  • Bezier
  • Parabola
  • Ellipse
  • Rectangle
  • Spline

Renderers

  • Instanced Meshes
  • Strip Mesh
  • LineRenderer
  • ParticleSystem

These classes are required in the upcoming Navigation & Pointers Academy tutorial. Since time is a factor I'm creating a separate pull request to ensure they aren't held up by revisions of unrelated Stage 2/3 MRDL elements.

A test scene has been added to Holotoolkit-Examples/UX/Scenes/LineExamples.
@cre8ivepark we could use your input on this scene as it's a bit ugly.

Looking forward to your feedback /suggestions,
-L

@@ -0,0 +1,7 @@
//---------------------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's up with this .fx asset?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure! It seems like it's getting auto-generated, but I don't know how or why. @keveleigh noticed this in the other pull request as well. Theories, anyone?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disable the material import on the mesh asset.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's where we're stumped, it's already disabled.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we remove this, and it's meta file until we get it sorted out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, removed.

using System;
using UnityEngine;

namespace MRTK.UX
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably update the namespace to match the toolkits.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How's MRTK.UX -> Holotoolkit.Unity.UX?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic. Also, please be sure to make sure the namespaces in the rest of this folder are also correct.

public int CompareTo(Distorter other)
{
if (other == null)
return 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: braces

/// <returns></returns>
protected abstract Vector3 DistortScaleInternal(Vector3 point, float strength);

protected void OnEnable()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should these also be virtual?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make OnEnable / OnDisable virtual.

DistortPoint/ScaleInternal are abstract to make it clear that new Distorters must implement those functions in order to work.

public class FastSimplexNoise
{
private const double STRETCH_2D = -0.211324865405187;
private const double STRETCH_3D = -1.0 / 6.0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do the math if we know what the values are going to be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tells you that the value should probably be normalized:
const float SOME_NORMALIZED_VALUE = 1f / 25;

This is a little more vague:
const float SOME_MYSTERIOUS_VALUE = 0.04f;

I'll update the other numbers to follow the same 1 / x format, for consistency.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what summary xml comments are for.

new int[] { 3, 1, 1, 1, 0, 3, 1, 1, 0, 1, 3, 1, 0, 1, 1, 3, 0, 1, 1, 1, 4, 1, 1, 1, 1 },
new int[] { 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 2, 1, 1, 0, 0, 2, 1, 0, 1, 0, 2, 1, 0, 0, 1, 2, 0, 1, 1, 0, 2, 0, 1, 0, 1, 2, 0, 0, 1, 1 },
new int[] { 3, 1, 1, 1, 0, 3, 1, 1, 0, 1, 3, 1, 0, 1, 1, 3, 0, 1, 1, 1, 2, 1, 1, 0, 0, 2, 1, 0, 1, 0, 2, 1, 0, 0, 1, 2, 0, 1, 1, 0, 2, 0, 1, 0, 1, 2, 0, 0, 1, 1 }
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably cache all these new integer arrays as well instead of creating them each time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

}
seed = seed * 6364136223846793005L + 1442695040888963407L;
seed = seed * 6364136223846793005L + 1442695040888963407L;
seed = seed * 6364136223846793005L + 1442695040888963407L;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these values be constant?

public class Bezeir : LineBase
{
[Serializable]
public struct PointSet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're referencing this PointSet outside of Bezeir we should move it into it's own class, otherwise it should be private.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made them private. The only other class that needs to know about them is the Bezier editor.


#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(Bezeir))]
public class CustomEditor : LineBaseEditor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Editor scripts should be in their own classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one's worth discussing. There are benefits to keeping some editors nested under the scripts they target like this.

Eg the latest Spline class, where 'points' is now private. Other scripts are forced to access these points via GetPoint and SetPoint, which keeps things clean. But the Editor script still has direct access to the 'points' array, allowing it to do some error-checking stuff that wouldn't be possible otherwise.

To get the same functionality with a separated Editor you'd have to define editor-only public properties or some other potentially confusing kludge.

Devs also benefit from seeing that a custom editor has been declared and from seeing the logic inside it without having to go on a scavenger hunt.

/// <returns></returns>
protected abstract float GetUnclampedWorldLengthInternal();

// Public
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regions would be better

Vector3 origin = transform.TransformPoint(OriginOffset);
rotationVector = (point - origin).normalized;
break;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra line spacing

}

if (rotationVector.magnitude < MinRotationMagnitude)
return transform.rotation;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: braces

/// <summary>
/// Base class for line editors (splines, bezeirs, parabolas, etc)
/// </summary>
public class LineBaseEditor : MRTKEditor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Editor scripts should live in an Editor folder

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This extends MRTKEditor, which must be kept outside Editor folders in order to function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really really don't like this pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I can tell it makes your skin crawl, lol. Maybe we should open a dedicated issue to discuss the pros / cons.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your skin crawl

🤣 I wouldn't go that far, but yes. It goes against all the industry standard best practices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It goes against all the industry standard best practices.

Unity-land is a weird place, man. Sometimes down is up and black is white. I long for the clarity of a pure C# repo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, Unity development is a different beast. I think a lot of traditional software engineers get tripped up with some things.


int previewResolution = Mathf.Min(linePreviewResolutionSelected, linePreviewResolutionUnselected);
if (selected)
previewResolution = linePreviewResolutionSelected;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: braces

if (wrap)
return WrapIndex(index, Objects.Count);
else
return Mathf.Clamp(index, 0, Objects.Count - 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was there an update for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's been committed.

[Range(0f, 1f)]
public float NormalizedDistance = 0f;

private void Update() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: formatting

public Vector3 AxisSpeed = Vector3.one;
public Vector3 AxisOffset = Vector3.zero;


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra line spacing

private Vector3[] prevPoints;
private System.Random randomPosition;
private System.Random randomRotation;
private FastSimplexNoise noise = new FastSimplexNoise();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: fields should be declared at the top of the class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not seeing this change yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been committed as well.

public class LineParticles : LineRendererBase
{
const int GlobalMaxParticles = 2048;
const float GlobalParticleStartLifetime = 0.5f;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private or public accessors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think they're needed since they're const & can't be modified.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's always good to know, that way future devs know what scope they're in.

Copy link
Contributor Author

@Railboy Railboy Nov 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused because a const can't use get / set accessors. Are you thinking we should change these to public properties that return constant values?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just helpful to know if this information should have the ability to be accessed outside the scope of just this class. Idk it's really not a big deal here. Just my 2c

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, I just use const all the time so I wanted to get on the same page. If you post an 'ideal scenario' I'll modify the class.

main.playOnAwake = false;
main.maxParticles = Mathf.Min (MaxParticles, GlobalMaxParticles);
main.simulationSpace = ParticleSystemSimulationSpace.World;
//main.useUnscaledTime = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not being used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

public class LineUnity : LineRendererBase
{
const string DefaultLineShader = "Particles/Alpha Blended";
const string DefaultLineShaderColor = "_TintColor";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing access modifiers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed since they're const.
I could make them static with a Get accessor? eg:
public static string DefaultLineShader { get { return "Particles / Alpha Blended"; } }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what happens if someone tries to use a custom shader for line rendering?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are just failsafe values. If they specify a material and property name, that material's shader is used. If they leave the material null, a material is generated at runtime using the "Particles/Alpha Blended" shader.

{
#region structs
[Serializable]
public struct SplinePoint
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: put in it's own file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved.


public static readonly Vector3 DefaultUpVector = Vector3.up;

public enum InterpolationEnum
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each of the enums in this class could be in their own files as well, and probably don't need to be in this LineUtils class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved all enums out of the LineUtils class.

We should still keep them in a single file, either LineUtility.cs, or possible in LineBase.cs alongside the LineBase class. Or maybe we could compromise and put them in a single enum-only class called LineBaseEnums.cs.

Here's my reasoning - clumping enums together with the class that interprets them is informative. It's a form of documentation that says: these things all related - taken together, they form a complete picture of this object's possible behavior.

Breaking them up into separate files destroys that information while adding complexity. Devs have to work backwards from variables one-by-one, forensically reconstructing relationships that should be clear at a moment's glance.

protected virtual void OnEnable()
{
if (source == null)
source = gameObject.GetComponent<LineBase>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noticed we get the line base line a lot. Maybe we should also require the component on the classes that use it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LineBase is abstract so we can't require it. But this actually is why we created the [UseWithAttribute] - I'll add those attributes now.

return;

if (source == null)
source = gameObject.GetComponent<LineBase>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could even make source a property and get the component if it hasn't yet been initialized.

Also: braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.


MeshFilter stripMeshFilter = stripMeshRenderer.GetComponent<MeshFilter>();
if (stripMeshFilter == null)
stripMeshFilter = stripMeshRenderer.gameObject.AddComponent<MeshFilter>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could use gameObject.EnsureComponent<MeshFilter>() here too.
Also : braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Really useful extension.

private void OnDisable()
{
if (lineMatInstance != null)
GameObject.Destroy(lineMatInstance);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit braces.

Also, are we handling the case if we're rendering the line when we're not in playmode? (or does this not get rendered if we're not playing?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only renders the mesh in play mode. Otherwise it uses gizmos.


public static void GenerateStripMesh(List<Vector3> positionList, List<Color> colorList, List<float> thicknessList, float uvOffsetLocal, List<Vector3> forwardList, Mesh mesh)
{
// TODO store these as local variables to reduce allocations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go ahead and do this TODO. seems simple enough

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (lineRenderer == null)
lineRenderer = gameObject.GetComponent<UnityEngine.LineRenderer>();
if (lineRenderer == null)
lineRenderer = gameObject.AddComponent<UnityEngine.LineRenderer>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensure component here too

{
lineRenderer.enabled = true;

switch (this.StepMode)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why call this.StepMode are we hiding an inherited property?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy cruft. Removed.

[SerializeField]
private UnityEngine.LineRenderer lineRenderer;

private Vector3[] positions;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fields should be declared at the top of the class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved.


public static class LineUtils
{
#region enums
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can probably remove this region now

public static Vector3 GetVectorCollectionBlend(Vector3[] vectorCollection, float normalizedLength, bool repeat)
{
if (vectorCollection.Length == 0)
return Vector3.zero;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more braces ;) Here and down the rest of this class

protected override Vector3 GetPointInternal(float normalizedDistance)
{
if (points == null || points.Length != 8)
points = new Vector3[8];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

braces here and below

return;

// Only draw a gizmo if we don't have a line renderer
LineRendererBase lr = gameObject.GetComponent<LineRendererBase>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should cache this instead of getting the component each update.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally I'd agree but I don't want to add a new field to the class for the sake of a Gizmos function. Given that Lines can have multiple LineRenderers I worry that its presence would be confusing for devs.

Since we only do the GetComponent call while the editor isn't playing it won't be a performance issue.

return points[NumPoints - 1].Point;
}
if (point1Index < 0)
return points[0].Point;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit braces

protected override Vector3 GetPointInternal(int pointIndex)
{
if (pointIndex < 0 || pointIndex >= points.Length)
throw new IndexOutOfRangeException();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

braces

float arrayValueLength = 1f / points.Length;
int indexA = Mathf.FloorToInt(normalizedLength * points.Length);
if (indexA >= points.Length)
indexA = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

braces

// Convenience buttons for adding / removing points
protected override void DrawCustomFooter()
{

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra space


Spline line = (Spline)target;

HashSet<int> overlappingPointIndexes = new HashSet<int>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to create a new hash set each time we draw the footer?
Maybe we can cache this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cached as a static variable.

{
if (GUILayout.Button(" + Add Points to Start"))
{
List<SplinePoint> points = new List<SplinePoint>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably cache this list and clear it each time we add a point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cached as a static variable.

for (int j = 0; j < line.points.Length; j++)
{
if (j == i)
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

braces

else if (drawOverrideAttributes.Length > 0)
{
if (drawOverrideAttributes.Length > 1)
DrawWarning("You should only use one DrawOverride attribute per member. Drawing " + drawOverrideAttributes[0].GetType().Name + " only.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

braces


Handles.color = handleColorAxis;
if (autoSize)
handleSize = Mathf.Lerp(handleSize, HandleUtility.GetHandleSize(position) * handleSize, 0.75f);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

braces here and below

@Railboy
Copy link
Contributor Author

Railboy commented Nov 8, 2017

Holy guacamole that was a lot of style changes.

Everything has been committed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants