feat: initialize Streamingle Utilities package

This commit is contained in:
KINDNICK
2026-07-23 22:43:07 +09:00
commit e9bbf8481a
107 changed files with 9746 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
using UnityEngine;
namespace Streamingle.Utils.RoughConstraints
{
[ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Aim Constraint")]
public class RoughAimConstraint : RoughConstraintBase
{
[Header("Aim")]
public Vector3 aimAxis = Vector3.forward;
public Vector3 upAxis = Vector3.up;
public Vector3 worldUpVector = Vector3.up;
public Vector3 rotationOffset;
protected override void ExecuteConstraint(float followT)
{
if (!TryGetWeightedPosition(out var targetPosition))
{
return;
}
var direction = targetPosition - transform.position;
if (direction.sqrMagnitude < 0.000001f)
{
return;
}
var targetRotation = LookRotationForAxis(direction, worldUpVector, aimAxis, upAxis);
targetRotation *= Quaternion.Euler(rotationOffset);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, followT * weight);
}
protected override void CaptureOffsetsFromCurrentPose()
{
if (!TryGetWeightedPosition(out var targetPosition))
{
return;
}
var direction = targetPosition - transform.position;
if (direction.sqrMagnitude < 0.000001f)
{
return;
}
var baseRotation = LookRotationForAxis(direction, worldUpVector, aimAxis, upAxis);
rotationOffset = (Quaternion.Inverse(baseRotation) * transform.rotation).eulerAngles;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 70b32d75aafa49d781fe9cd7389d78b5
+356
View File
@@ -0,0 +1,356 @@
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Streamingle.Utils.RoughConstraints
{
public enum RoughConstraintUpdateMode
{
Update,
LateUpdate,
FixedUpdate,
Manual
}
[ExecuteAlways]
public abstract class RoughConstraintBase : MonoBehaviour
{
[Header("Constraint")]
public bool constraintActive = true;
public bool locked = true;
[Range(0f, 1f)]
public float weight = 1f;
public List<RoughConstraintSource> sources = new List<RoughConstraintSource>();
[Header("Rough Follow")]
[Min(0f)]
public float damping = 12f;
public RoughConstraintUpdateMode updateMode = RoughConstraintUpdateMode.LateUpdate;
public bool useUnscaledTime;
public bool snapOnEnable = true;
#if UNITY_EDITOR
private bool editorUpdateRegistered;
#endif
protected float CurrentDeltaTime
{
get
{
if (!Application.isPlaying)
{
return 1f / 60f;
}
if (updateMode == RoughConstraintUpdateMode.FixedUpdate)
{
return useUnscaledTime ? Time.fixedUnscaledDeltaTime : Time.fixedDeltaTime;
}
return useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
}
}
protected float FollowT
{
get
{
if (damping <= 0f)
{
return 1f;
}
return 1f - Mathf.Exp(-damping * Mathf.Max(0f, CurrentDeltaTime));
}
}
protected virtual void OnEnable()
{
#if UNITY_EDITOR
RegisterEditorUpdate();
#endif
if (!snapOnEnable)
{
return;
}
SnapToSources();
}
protected virtual void OnDisable()
{
#if UNITY_EDITOR
UnregisterEditorUpdate();
#endif
}
private void Update()
{
if (!Application.isPlaying)
{
if (updateMode != RoughConstraintUpdateMode.Manual)
{
Evaluate();
}
return;
}
if (updateMode == RoughConstraintUpdateMode.Update)
{
Evaluate();
}
}
private void LateUpdate()
{
if (!Application.isPlaying)
{
return;
}
if (updateMode == RoughConstraintUpdateMode.LateUpdate)
{
Evaluate();
}
}
private void FixedUpdate()
{
if (!Application.isPlaying)
{
return;
}
if (updateMode == RoughConstraintUpdateMode.FixedUpdate)
{
Evaluate();
}
}
#if UNITY_EDITOR
private void RegisterEditorUpdate()
{
if (editorUpdateRegistered)
{
return;
}
EditorApplication.update += EditorUpdate;
editorUpdateRegistered = true;
}
private void UnregisterEditorUpdate()
{
if (!editorUpdateRegistered)
{
return;
}
EditorApplication.update -= EditorUpdate;
editorUpdateRegistered = false;
}
private void EditorUpdate()
{
if (this == null)
{
UnregisterEditorUpdate();
return;
}
if (Application.isPlaying || updateMode == RoughConstraintUpdateMode.Manual)
{
return;
}
Evaluate();
}
#endif
public void Evaluate()
{
if (!CanEvaluate())
{
return;
}
ExecuteConstraint(FollowT);
}
public void SnapToSources()
{
if (!CanEvaluate())
{
return;
}
ExecuteConstraint(1f);
}
public void SetLocked(bool value)
{
if (value && !locked)
{
CaptureOffsetsFromCurrentPose();
}
locked = value;
}
public void ActivateConstraint()
{
constraintActive = true;
locked = true;
SnapToSources();
}
protected abstract void ExecuteConstraint(float followT);
protected virtual void CaptureOffsetsFromCurrentPose()
{
}
private bool CanEvaluate()
{
return constraintActive && locked && weight > 0f;
}
protected bool TryGetWeightedPosition(out Vector3 position)
{
position = Vector3.zero;
var totalWeight = 0f;
for (var i = 0; i < sources.Count; i++)
{
var source = sources[i];
if (source.sourceTransform == null || source.weight <= 0f)
{
continue;
}
position += source.sourceTransform.position * source.weight;
totalWeight += source.weight;
}
if (totalWeight <= 0f)
{
return false;
}
position /= totalWeight;
return true;
}
protected bool TryGetWeightedRotation(out Quaternion rotation)
{
rotation = Quaternion.identity;
var hasRotation = false;
var accumulatedWeight = 0f;
for (var i = 0; i < sources.Count; i++)
{
var source = sources[i];
if (source.sourceTransform == null || source.weight <= 0f)
{
continue;
}
var sourceRotation = source.sourceTransform.rotation;
if (!hasRotation)
{
rotation = sourceRotation;
accumulatedWeight = source.weight;
hasRotation = true;
continue;
}
var lerpWeight = source.weight / (accumulatedWeight + source.weight);
rotation = Quaternion.Slerp(rotation, sourceRotation, lerpWeight);
accumulatedWeight += source.weight;
}
return hasRotation;
}
protected bool TryGetWeightedScale(out Vector3 scale)
{
scale = Vector3.zero;
var totalWeight = 0f;
for (var i = 0; i < sources.Count; i++)
{
var source = sources[i];
if (source.sourceTransform == null || source.weight <= 0f)
{
continue;
}
scale += source.sourceTransform.localScale * source.weight;
totalWeight += source.weight;
}
if (totalWeight <= 0f)
{
return false;
}
scale /= totalWeight;
return true;
}
protected static Vector3 ApplyAxisMask(Vector3 current, Vector3 target, Vector3Bool constrainedAxis)
{
if (!constrainedAxis.x) target.x = current.x;
if (!constrainedAxis.y) target.y = current.y;
if (!constrainedAxis.z) target.z = current.z;
return target;
}
protected static Vector3 ApplyEulerAxisMask(Quaternion current, Quaternion target, Vector3Bool constrainedAxis)
{
var currentEuler = current.eulerAngles;
var targetEuler = target.eulerAngles;
if (!constrainedAxis.x) targetEuler.x = currentEuler.x;
if (!constrainedAxis.y) targetEuler.y = currentEuler.y;
if (!constrainedAxis.z) targetEuler.z = currentEuler.z;
return targetEuler;
}
protected static Quaternion LookRotationForAxis(Vector3 direction, Vector3 worldUp, Vector3 localForward, Vector3 localUp)
{
var safeDirection = SafeNormal(direction, Vector3.forward);
var safeWorldUp = SafeUp(safeDirection, worldUp);
var safeLocalForward = SafeNormal(localForward, Vector3.forward);
var safeLocalUp = SafeUp(safeLocalForward, localUp);
var targetWorldRotation = Quaternion.LookRotation(safeDirection, safeWorldUp);
var localAxisRotation = Quaternion.LookRotation(safeLocalForward, safeLocalUp);
return targetWorldRotation * Quaternion.Inverse(localAxisRotation);
}
private static Vector3 SafeNormal(Vector3 value, Vector3 fallback)
{
return value.sqrMagnitude > 0.000001f ? value.normalized : fallback;
}
private static Vector3 SafeUp(Vector3 forward, Vector3 up)
{
var safeUp = SafeNormal(up, Vector3.up);
if (Mathf.Abs(Vector3.Dot(forward, safeUp)) < 0.999f)
{
return safeUp;
}
return Mathf.Abs(Vector3.Dot(forward, Vector3.up)) < 0.999f ? Vector3.up : Vector3.right;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 113d932d171c415fa3b62ab75bdf3de7
@@ -0,0 +1,14 @@
using System;
using UnityEngine;
namespace Streamingle.Utils.RoughConstraints
{
[Serializable]
public struct RoughConstraintSource
{
public Transform sourceTransform;
[Range(0f, 1f)]
public float weight;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7192f25e37a54bc385855e11d7f3abc3
@@ -0,0 +1,52 @@
using UnityEngine;
namespace Streamingle.Utils.RoughConstraints
{
[ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Look At Constraint")]
public class RoughLookAtConstraint : RoughConstraintBase
{
[Header("Look At")]
public Vector3 forwardAxis = Vector3.forward;
public Vector3 worldUpVector = Vector3.up;
public Vector3 targetPositionOffset;
public Vector3 rotationOffset;
protected override void ExecuteConstraint(float followT)
{
if (!TryGetWeightedPosition(out var targetPosition))
{
return;
}
targetPosition += targetPositionOffset;
var direction = targetPosition - transform.position;
if (direction.sqrMagnitude < 0.000001f)
{
return;
}
var targetRotation = LookRotationForAxis(direction, worldUpVector, forwardAxis, Vector3.up);
targetRotation *= Quaternion.Euler(rotationOffset);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, followT * weight);
}
protected override void CaptureOffsetsFromCurrentPose()
{
if (!TryGetWeightedPosition(out var targetPosition))
{
return;
}
targetPosition += targetPositionOffset;
var direction = targetPosition - transform.position;
if (direction.sqrMagnitude < 0.000001f)
{
return;
}
var baseRotation = LookRotationForAxis(direction, worldUpVector, forwardAxis, Vector3.up);
rotationOffset = (Quaternion.Inverse(baseRotation) * transform.rotation).eulerAngles;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0ff851637cd645758f43435b7ee8fb42
@@ -0,0 +1,67 @@
using UnityEngine;
namespace Streamingle.Utils.RoughConstraints
{
[ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Parent Constraint")]
public class RoughParentConstraint : RoughConstraintBase
{
[Header("Position")]
public Vector3Bool constrainedPositionAxis = Vector3Bool.All;
public Vector3 positionOffset;
public bool positionOffsetInSourceSpace = true;
[Header("Rotation")]
public Vector3Bool constrainedRotationAxis = Vector3Bool.All;
public Vector3 rotationOffset;
protected override void ExecuteConstraint(float followT)
{
var blend = followT * weight;
if (TryGetWeightedPosition(out var targetPosition))
{
if (positionOffsetInSourceSpace && sources.Count > 0 && sources[0].sourceTransform != null)
{
targetPosition += sources[0].sourceTransform.TransformVector(positionOffset);
}
else
{
targetPosition += positionOffset;
}
targetPosition = ApplyAxisMask(transform.position, targetPosition, constrainedPositionAxis);
transform.position = Vector3.Lerp(transform.position, targetPosition, blend);
}
if (TryGetWeightedRotation(out var targetRotation))
{
targetRotation *= Quaternion.Euler(rotationOffset);
var maskedEuler = ApplyEulerAxisMask(transform.rotation, targetRotation, constrainedRotationAxis);
targetRotation = Quaternion.Euler(maskedEuler);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, blend);
}
}
protected override void CaptureOffsetsFromCurrentPose()
{
if (TryGetWeightedPosition(out var sourcePosition))
{
var delta = transform.position - sourcePosition;
if (positionOffsetInSourceSpace && sources.Count > 0 && sources[0].sourceTransform != null)
{
positionOffset = sources[0].sourceTransform.InverseTransformVector(delta);
}
else
{
positionOffset = delta;
}
}
if (TryGetWeightedRotation(out var sourceRotation))
{
rotationOffset = (Quaternion.Inverse(sourceRotation) * transform.rotation).eulerAngles;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 015afbd1174749d889a7b3542929c39b
@@ -0,0 +1,52 @@
using UnityEngine;
namespace Streamingle.Utils.RoughConstraints
{
[ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Position Constraint")]
public class RoughPositionConstraint : RoughConstraintBase
{
[Header("Position")]
public Vector3Bool constrainedAxis = Vector3Bool.All;
public Vector3 positionOffset;
public bool offsetInSourceSpace = true;
protected override void ExecuteConstraint(float followT)
{
if (!TryGetWeightedPosition(out var targetPosition))
{
return;
}
if (offsetInSourceSpace && sources.Count > 0 && sources[0].sourceTransform != null)
{
targetPosition += sources[0].sourceTransform.TransformVector(positionOffset);
}
else
{
targetPosition += positionOffset;
}
targetPosition = ApplyAxisMask(transform.position, targetPosition, constrainedAxis);
transform.position = Vector3.Lerp(transform.position, targetPosition, followT * weight);
}
protected override void CaptureOffsetsFromCurrentPose()
{
if (!TryGetWeightedPosition(out var sourcePosition))
{
return;
}
var delta = transform.position - sourcePosition;
if (offsetInSourceSpace && sources.Count > 0 && sources[0].sourceTransform != null)
{
positionOffset = sources[0].sourceTransform.InverseTransformVector(delta);
}
else
{
positionOffset = delta;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b1874b6468e0419f925ee085ce478bfb
@@ -0,0 +1,36 @@
using UnityEngine;
namespace Streamingle.Utils.RoughConstraints
{
[ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Rotation Constraint")]
public class RoughRotationConstraint : RoughConstraintBase
{
[Header("Rotation")]
public Vector3Bool constrainedAxis = Vector3Bool.All;
public Vector3 rotationOffset;
protected override void ExecuteConstraint(float followT)
{
if (!TryGetWeightedRotation(out var targetRotation))
{
return;
}
targetRotation *= Quaternion.Euler(rotationOffset);
var maskedEuler = ApplyEulerAxisMask(transform.rotation, targetRotation, constrainedAxis);
targetRotation = Quaternion.Euler(maskedEuler);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, followT * weight);
}
protected override void CaptureOffsetsFromCurrentPose()
{
if (!TryGetWeightedRotation(out var sourceRotation))
{
return;
}
rotationOffset = (Quaternion.Inverse(sourceRotation) * transform.rotation).eulerAngles;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 23820ffe249c4b7bad25455ca97fc8d3
@@ -0,0 +1,35 @@
using UnityEngine;
namespace Streamingle.Utils.RoughConstraints
{
[ExecuteAlways]
[AddComponentMenu("Streamingle Utilities/Constraints/Rough Scale Constraint")]
public class RoughScaleConstraint : RoughConstraintBase
{
[Header("Scale")]
public Vector3Bool constrainedAxis = Vector3Bool.All;
public Vector3 scaleOffset;
protected override void ExecuteConstraint(float followT)
{
if (!TryGetWeightedScale(out var targetScale))
{
return;
}
targetScale += scaleOffset;
targetScale = ApplyAxisMask(transform.localScale, targetScale, constrainedAxis);
transform.localScale = Vector3.Lerp(transform.localScale, targetScale, followT * weight);
}
protected override void CaptureOffsetsFromCurrentPose()
{
if (!TryGetWeightedScale(out var sourceScale))
{
return;
}
scaleOffset = transform.localScale - sourceScale;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c58bfc39585a4c61bef1c73bf3fbc9a0
+21
View File
@@ -0,0 +1,21 @@
using System;
namespace Streamingle.Utils.RoughConstraints
{
[Serializable]
public struct Vector3Bool
{
public bool x;
public bool y;
public bool z;
public Vector3Bool(bool x, bool y, bool z)
{
this.x = x;
this.y = y;
this.z = z;
}
public static Vector3Bool All => new Vector3Bool(true, true, true);
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6b3fce5b0afa436abaaba5ea0261354d