/// <summary> /// Initializes the <see cref="Candlelight.SerializedPropertyX"/> class. /// </summary> static SerializedPropertyX() { // get all of the PropertyDrawer types List <System.Type> drawerTypes = ObjectX.AllTypes.Where(t => t.IsSubclassOf(typeof(PropertyDrawer))).ToList(); // associate object/attribute types with their respective drawers foreach (System.Type drawerType in drawerTypes) { CustomPropertyDrawer[] attrs = ObjectX.GetCustomAttributes <CustomPropertyDrawer>(drawerType); if (attrs.Length > 0) { System.Type baseType = customPropertyDrawerTypeField.GetValue(attrs[0]) as System.Type; if (!drawersForEachType.ContainsKey(baseType)) { drawersForEachType.Add(baseType, drawerType); } else { drawersForEachType[baseType] = drawerType; } if ((bool)customPropertyDrawerUseForChildrenField.GetValue(attrs[0])) { foreach (System.Type type in ObjectX.AllTypes) { if (!drawersForEachType.ContainsKey(type) && type.IsSubclassOf(baseType)) { drawersForEachType.Add(type, drawerType); } } } } } }
/// <summary> /// Displays a translation handle at the specified location with a customizable size and control ID that also /// respects the current scene GUI alpha. /// </summary> /// <remarks> /// Unity's built-in translation handle does not allow size specification, always renders fully opaque, and will /// result in control ID conflicts if there are multiple instances on the screen at once. /// </remarks> /// <param name="baseId">Base identifier. Each axis its own unique hash based off this value.</param> /// <param name="position">Position.</param> /// <param name="orientation">Orientation.</param> /// <param name="size">Size.</param> public static Vector3 Translation(int baseId, Vector3 position, Quaternion orientation, float size) { Color oldColor = Handles.color; size = SceneGUI.GetFixedHandleSize(position, size); Handles.color = EditorGUIX.xHandleColor * SceneGUI.CurrentAlphaScalar; GUI.SetNextControlName( ObjectX.GenerateHashCode(baseId, s_TranslationHandleHash, (int)EditAxis.X).ToString() ); position = Handles.Slider(position, orientation * Vector3.right, size, Handles.ArrowCap, 1f); Handles.color = EditorGUIX.yHandleColor * SceneGUI.CurrentAlphaScalar; GUI.SetNextControlName( ObjectX.GenerateHashCode(baseId, s_TranslationHandleHash, (int)EditAxis.Y).ToString() ); position = Handles.Slider(position, orientation * Vector3.up, size, Handles.ArrowCap, 1f); Handles.color = EditorGUIX.zHandleColor * SceneGUI.CurrentAlphaScalar; GUI.SetNextControlName( ObjectX.GenerateHashCode(baseId, s_TranslationHandleHash, (int)EditAxis.Z).ToString() ); position = Handles.Slider(position, orientation * Vector3.forward, size, Handles.ArrowCap, 1f); // TODO: add 2-axis sliders // position = Handles.DoPlanarHandle(Handles.PlaneHandle.xzPlane, position, orientation, size * 0.25f); // position = Handles.DoPlanarHandle(Handles.PlaneHandle.xyPlane, position, orientation, size * 0.25f); // position = Handles.DoPlanarHandle(Handles.PlaneHandle.yzPlane, position, orientation, size * 0.25f); Handles.color = oldColor; return(position); }
/// <summary> /// Serves as a hash function for a <see cref="Candlelight.RichTextStyle"/> object. /// </summary> /// <returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// hash table. /// </returns> public override int GetHashCode() { return(ObjectX.GenerateHashCode( m_SizeScalar.GetHashCode(), m_FontStyle.GetHashCode(), m_ShouldReplaceColor.GetHashCode(), m_ReplacementColor.GetHashCode() )); }
/// <summary> /// Serves as a hash function for a <see cref="KeywordsGlossary.Entry"/> object. /// </summary> /// <returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as /// a hash table. /// </returns> public override int GetHashCode() { int result = ObjectX.GenerateHashCode( m_Definition.GetHashCode(), m_MainForm.GetHashCode(), (m_OtherForms == null ? 0 : m_OtherForms.Count).GetHashCode() ); if (m_OtherForms != null) { for (int i = 0; i < m_OtherForms.Count; ++i) { result = ObjectX.GenerateHashCode(result, m_OtherForms[i].GetHashCode()); } } return(result); }
/// <summary> /// Displays the helix twist curve handles. /// </summary> /// <param name="baseId">Base identifier.</param> /// <param name="helix">Helix.</param> private static void DoHelixTwistHandles(int baseId, Helix helix) { float v; AnimationCurve width = helix.Width; AnimationCurve newTwist = new AnimationCurve(helix.Twist.keys); Keyframe[] newKeys = newTwist.keys; for (int i = 0; i < newKeys.Length; ++i) { // compute the disc center Vector3 discCenter = Vector3.forward * newKeys[i].time * helix.Length; v = newKeys[i].value; // create an arc handle float helixWidth = width.Evaluate(newKeys[i].time); v = ArcHandles.SolidAngle( ObjectX.GenerateHashCode(baseId, s_TwistHandleHash, i, 1), v, discCenter, Quaternion.LookRotation(Vector3.right, Vector3.forward), helixWidth, string.Format("{0:0} Degrees", v) ); Handles.DrawWireDisc(discCenter, Vector3.forward, helixWidth); newKeys[i].value = v; // create time adjustment handles for intermediary keys if (i == 0 || i == newKeys.Length - 1) { continue; } v = newKeys[i].time * helix.Length; int id = ObjectX.GenerateHashCode(baseId, s_TwistHandleHash, i, 2); v = LinearHandles.Cone(id, v, Vector3.zero, Vector3.forward, capScale: s_TimeAdjustHandleSize); v = Mathf.Clamp( v / helix.Length, Mathf.Epsilon * 2f, 1f - Mathf.Epsilon * 2f ); if (Mathf.Abs(v - newKeys[i].time) > s_RequiredMinHandleChange) { newKeys[i].time = v; } // TODO: Fix problems with keys moving over each other } newTwist.keys = newKeys; helix.Twist = newTwist; }
/// <summary> /// Display a helix length handle. /// </summary> /// <param name="baseId">Base identifier.</param> /// <param name="helix">Helix.</param> private static void DoHelixLengthHandle(int baseId, Helix helix) { float length = helix.Length; int id = ObjectX.GenerateHashCode(baseId, s_LengthHandleHash); length = LinearHandles.Arrow( id, length, Vector3.zero, Vector3.forward, string.Format("Length: {0:0.###}", length), s_LengthHandleSize ); if (Mathf.Abs(length - helix.Length) < s_RequiredMinHandleChange) { length = helix.Length; } helix.Length = length; }
/// <summary> /// Generates a hash code for the serialized properties in a list or array of /// <see cref="IPropertyBackingFieldCompatible"/> objects. /// </summary> /// <returns>A hash code.</returns> /// <param name="listField">List field.</param> /// <typeparam name="T">The element type.</typeparam> public static int GenerateSerializedPropertiesHash <T>(IList <T> listField) where T : IPropertyBackingFieldCompatible { int typeCode = typeof(T).GetHashCode(); if (listField == null) { // a null and empty collection are the same where the inspector is concerned return(ObjectX.GenerateHashCode(typeCode, 0.GetHashCode())); } int result = ObjectX.GenerateHashCode(typeCode, listField.Count.GetHashCode()); for (int i = 0; i < listField.Count; ++i) { result = ObjectX.GenerateHashCode( result, listField[i] == null ? typeCode : listField[i].GetSerializedPropertiesHash() ); } return(result); }
/// <summary> /// Displays helix width handles. /// </summary> /// <param name="baseId">Base identifier.</param> /// <param name="helix">Helix.</param> private static void DoHelixWidthHandles(int baseId, Helix helix) { float v; AnimationCurve twist = helix.Twist; AnimationCurve newWidth = helix.Width; Keyframe[] newKeys = newWidth.keys; for (int i = 0; i < newKeys.Length; ++i) { // compute the disc center Vector3 discCenter = Vector3.forward * newKeys[i].time * helix.Length; // compute the angle of the handle around the length Quaternion angle = Quaternion.AngleAxis(twist.Evaluate(newKeys[i].time), Vector3.forward); // create a cone handle with a slight offset to not overlap the arc handle v = newKeys[i].value + s_WidthHandleOffset; int id = ObjectX.GenerateHashCode(baseId, s_WidthHandleHash, i, 1); v = LinearHandles.Cone(id, v, discCenter, angle * Vector3.right, capScale: s_WidthHandleSize); newKeys[i].value = v - s_WidthHandleOffset; // create time adjustment handles for intermediary keys if (i == 0 || i == newKeys.Length - 1) { continue; } v = newKeys[i].time * helix.Length; id = ObjectX.GenerateHashCode(baseId, s_WidthHandleHash, i, 2); v = LinearHandles.Cone(id, v, Vector3.zero, Vector3.forward, capScale: s_TimeAdjustHandleSize); v = Mathf.Clamp( v / helix.Length, Mathf.Epsilon * 2f, 1f - Mathf.Epsilon * 2f ); if (Mathf.Abs(v - newKeys[i].time) > s_RequiredMinHandleChange) { newKeys[i].time = v; } // TODO: Fix problems with keys moving over each other } newWidth.keys = newKeys; helix.Width = newWidth; }
/// <summary> /// Serves as a hash function for a <see cref="ColorGradient"/> object. /// </summary> /// <returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// hash table. /// </returns> public override int GetHashCode() { return(ObjectX.GenerateHashCode( m_MinColor.GetHashCode(), m_MaxColor.GetHashCode(), m_InterpolationSpace.GetHashCode() )); }
/// <summary> /// Determines whether the specified <see cref="System.Object"/> is equal to the current /// <see cref="ColorGradient"/>. /// </summary> /// <param name="obj"> /// The <see cref="System.Object"/> to compare with the current <see cref="ColorGradient"/>. /// </param> /// <returns> /// <see langword="true"/> if the specified <see cref="System.Object"/> is equal to the current /// <see cref="ColorGradient"/>; otherwise, <see langword="false"/>. /// </returns> public override bool Equals(object obj) { return(ObjectX.Equals(ref this, obj)); }
/// <summary> /// Serves as a hash function for a <see cref="KeywordsGlossary.Entry"/> object. /// </summary> /// <returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as /// a hash table. /// </returns> public override int GetHashCode() { return(ObjectX.GenerateHashCode( m_Definition.GetHashCode(), m_MainForm.GetHashCode(), ObjectX.GenerateHashCode(m_OtherForms) )); }
/// <summary> /// Serves as a hash function for a <see cref="KeywordsGlossary.InflectedForm"/> object. /// </summary> /// <returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as /// a hash table. /// </returns> public override int GetHashCode() { return(ObjectX.GenerateHashCode(m_PartOfSpeech.GetHashCode(), this.Word.GetHashCode())); }
/// <summary> /// Serves as a hash function for a <see cref="ImmutableRectOffset"/> object. /// </summary> /// <returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// hash table. /// </returns> public override int GetHashCode() { return(ObjectX.GenerateHashCode( m_Left.GetHashCode(), m_Right.GetHashCode(), m_Top.GetHashCode(), m_Bottom.GetHashCode() )); }
/// <summary> /// Serves as a hash function for a <see cref="UnityVersion"/> object. /// </summary> /// <returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// hash table. /// </returns> public override int GetHashCode() { return(ObjectX.GenerateHashCode( this.MajorVersion.GetHashCode(), this.MinorVersion.GetHashCode(), this.MaintenanceVersion.GetHashCode() )); }
/// <summary> /// Serves as a hash function for a <see cref="ColorHSV"/> object. /// </summary> /// <returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// hash table. /// </returns> public override int GetHashCode() { return(ObjectX.GenerateHashCode( m_Hue.GetHashCode(), m_Saturation.GetHashCode(), m_Value.GetHashCode(), m_Alpha.GetHashCode() )); }
/// <summary> /// Displays a wire disc handle. /// </summary> /// <returns>The disc radius.</returns> /// <param name="id">Control identifier.</param> /// <param name="radius">Radius.</param> /// <param name="origin">Origin.</param> /// <param name="orientation">Orientation.</param> /// <param name="label">Label.</param> public static float WireDisc(int id, float radius, Vector3 origin, Quaternion orientation, string label = "") { return(DoDisc( ObjectX.GenerateHashCode(id, s_WireDiscHash), radius, origin, orientation, label, FillMode.Wire )); }
/// <summary> /// Displays a wire sphere handle. /// </summary> /// <returns>The sphere radius.</returns> /// <param name="id">Control identifier.</param> /// <param name="radius">Radius.</param> /// <param name="origin">Origin.</param> /// <param name="label">Label.</param> public static float WireSphere(int id, float radius, Vector3 origin, string label = "") { return(DoSphere(ObjectX.GenerateHashCode(id, s_WireSphereHash), radius, origin, label, FillMode.Wire)); }
/// <summary> /// Displays a shape handle of the specified type. /// </summary> /// <remarks> If the user is holding Alt, the center will stay locked in place.</remarks> /// <param name="baseId">Base identifier.</param> /// <param name="size">Size.</param> /// <param name="center">Center.</param> /// <param name="orientation">Orientation.</param> /// <param name="type">Type.</param> private static void DoShapeHandle( int baseId, ref Vector3 size, ref Vector3 center, Quaternion orientation, WireShapeType type ) { // set handle matrix Matrix4x4 oldMatrix = Handles.matrix; Handles.matrix *= Matrix4x4.TRS(center, orientation, Vector3.one); // generate control Ids int rightId = ObjectX.GenerateHashCode(baseId, (int)type, (int)EditAxis.X, 1); int leftId = ObjectX.GenerateHashCode(baseId, (int)type, (int)EditAxis.X, 2); int upId = ObjectX.GenerateHashCode(baseId, (int)type, (int)EditAxis.Y, 1); int downId = ObjectX.GenerateHashCode(baseId, (int)type, (int)EditAxis.Y, 2); int forwardId = ObjectX.GenerateHashCode(baseId, (int)type, (int)EditAxis.Z, 1); int backId = ObjectX.GenerateHashCode(baseId, (int)type, (int)EditAxis.Z, 2); int currentId = GUIUtility.hotControl; int nearestId = HandleUtility.nearestControl; // cache the center and size when a handle is clicked to use for shift/alt dragging switch (Event.current.type) { case EventType.MouseDown: if ( nearestId == leftId || nearestId == rightId || nearestId == upId || nearestId == downId || nearestId == forwardId || nearestId == backId ) { s_OnClickCenter[baseId] = center; s_OnClickSize[baseId] = size; } break; case EventType.MouseUp: s_OnClickCenter.Remove(baseId); s_OnClickSize.Remove(baseId); currentId = 0; break; } // make sure size is not negative on any dimension size = new Vector3(Mathf.Max(0f, size.x), Mathf.Max(0f, size.y), Mathf.Max(0f, size.z)); // display right/left handles float right = size.x * 0.5f; float left = -size.x * 0.5f; Vector3 offset = Handles.matrix.GetScale(); offset = new Vector3( offset.x == 0f ? 0f : 1f / offset.x, offset.y == 0f ? 0f : 1f / offset.y, offset.z == 0f ? 0f : 1f / offset.z ); offset *= s_HandleOffset; right = LinearHandles.Dot( rightId, val: right, origin: Vector3.right * offset.x, direction: Vector3.right ); right = Mathf.Max(right, left); left = LinearHandles.Dot(leftId, val: left, origin: Vector3.left * offset.x, direction: Vector3.right); left = Mathf.Min(right, left); // display up/down handles float up = size.y * 0.5f; float down = -size.y * 0.5f; up = LinearHandles.Dot(upId, val: up, origin: Vector3.up * offset.y, direction: Vector3.up); up = Mathf.Max(up, down); down = LinearHandles.Dot(downId, val: down, origin: Vector3.down * offset.y, direction: Vector3.up); down = Mathf.Min(up, down); // display forward/back handles float forward = size.z * 0.5f; float back = -size.z * 0.5f; forward = LinearHandles.Dot( forwardId, val: forward, origin: Vector3.forward * offset.z, direction: Vector3.forward ); forward = Mathf.Max(forward, back); back = LinearHandles.Dot(backId, val: back, origin: Vector3.back * offset.z, direction: Vector3.forward); back = Mathf.Min(forward, back); // store which axes are being edited EditAxis editAxis = EditAxis.None; if (currentId == rightId || currentId == leftId) { editAxis = EditAxis.X; } else if (currentId == upId || currentId == downId) { editAxis = EditAxis.Y; } else if (currentId == forwardId || currentId == backId) { editAxis = EditAxis.Z; } // apply constraints to size based on shape type size.x = Mathf.Max(0f, right - left); size.y = Mathf.Max(0f, up - down); size.z = Mathf.Max(0f, forward - back); Vector3 deltaCenter = 0.5f * new Vector3(right + left, up + down, forward + back); float delta; switch (type) { case WireShapeType.Capsule: switch (editAxis) { case EditAxis.X: delta = Mathf.Min(size.x, size.y) - size.x; size.x += delta; size.z = size.x; deltaCenter.x += delta * 0.5f; break; case EditAxis.Y: delta = Mathf.Max(size.y, size.x, size.z) - size.y; size.y += delta; deltaCenter.y += delta * 0.5f; break; case EditAxis.Z: delta = Mathf.Min(size.z, size.y) - size.z; size.z += delta; size.x = size.z; deltaCenter.z += delta * 0.5f; break; } break; case WireShapeType.Cylinder: switch (editAxis) { case EditAxis.X: delta = Mathf.Min(size.x, size.y) - size.x; size.x += delta; size.z = size.x; deltaCenter.x += delta * 0.5f; break; case EditAxis.Z: delta = Mathf.Min(size.z, size.y) - size.z; size.z += delta; size.x = size.z; deltaCenter.z += delta * 0.5f; break; } break; case WireShapeType.Sphere: switch (editAxis) { case EditAxis.X: size.y = size.z = size.x; break; case EditAxis.Y: size.x = size.z = size.y; break; case EditAxis.Z: size.x = size.y = size.z; break; } break; } // apply new center center += orientation * deltaCenter; // scale uniformly using cached size if holding shift key if (s_OnClickSize.ContainsKey(baseId) && Event.current.shift) { float scaleFactor = 1f; switch (editAxis) { case EditAxis.X: scaleFactor = s_OnClickSize[baseId].x > 0f ? size.x / s_OnClickSize[baseId].x : 0f; break; case EditAxis.Y: scaleFactor = s_OnClickSize[baseId].y > 0f ? size.y / s_OnClickSize[baseId].y : 0f; break; case EditAxis.Z: scaleFactor = s_OnClickSize[baseId].z > 0f ? size.z / s_OnClickSize[baseId].z : 0f; break; } size = s_OnClickSize[baseId] * scaleFactor; } // use cached center if holding alt key if (s_OnClickCenter.ContainsKey(baseId) && Event.current.alt) { center = s_OnClickCenter[baseId]; } // draw wire shape switch (type) { case WireShapeType.Box: DrawWireBox(size); break; case WireShapeType.Capsule: DrawWireCylinder(new CylinderProperties(height: size.y, radius: size.x * 0.5f), CylinderCap.Capsule); break; case WireShapeType.Cylinder: DrawWireCylinder(new CylinderProperties(height: size.y, radius: size.x * 0.5f), CylinderCap.Cylinder); break; case WireShapeType.Sphere: float radius = size.x * 0.5f; Handles.DrawWireDisc(Vector3.zero, Vector3.right, radius); Handles.DrawWireDisc(Vector3.zero, Vector3.up, radius); Handles.DrawWireDisc(Vector3.zero, Vector3.forward, radius); // TODO: get this working in non-identity orientations // Vector3 nml = Handles.matrix.MultiplyPoint(Vector3.zero) - Camera.current.transform.position; // float sqrMagRecip = 1f / nml.sqrMagnitude; // float sqrRadius = radius * radius; // radius = Mathf.Sqrt(sqrRadius - (sqrRadius * sqrRadius * sqrMagRecip)); // Handles.DrawWireDisc( // Vector3.zero - sqrRadius * nml * sqrMagRecip, Handles.matrix.inverse.MultiplyVector(nml), radius // ); break; } // reset handle matrix Handles.matrix = oldMatrix; }