Skip to content

Instantly share code, notes, and snippets.

@Frooxius
Created June 30, 2018 20:33
Show Gist options
  • Save Frooxius/e5deca156ea7693ecfe2551d10189f5a to your computer and use it in GitHub Desktop.
Save Frooxius/e5deca156ea7693ecfe2551d10189f5a to your computer and use it in GitHub Desktop.
Neos Brush System
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BaseX;
namespace FrooxEngine
{
public abstract class BrushTip : ToolTip
{
public override bool IsTipPointing => false;
public override ToolTipTouchType TouchType => ToolTipTouchType.Physical;
public floatQ TipRotation => Slot.LocalRotationToGlobal(LocalTipRotation);
public abstract floatQ LocalTipRotation { get; }
#region BRUSH CONFIGURATION
public readonly Sync<float> FixedMinimumPointDistance;
//public readonly Sync<float> MinimumPointAngle;
public readonly Sync<float> PositionSmoothing;
public readonly Sync<float> RotationSmoothing;
public readonly Sync<float> PressureSmoothing;
public readonly Sync<float> MaxStrokeLength;
public readonly Sync<float> StrokeFadeInLength;
public readonly Sync<float> StrokeFadeOutLength;
public readonly Sync<float> StrokeGroupFinishWaitTime;
public readonly Sync<float> ActivationThreshold;
public readonly Sync<float> DeactivationThresholdRatio;
#endregion
#region MATERIAL CONFIGURATION
public class ColorMapping : SyncObject
{
public readonly SyncRef<IField<color>> Field;
public readonly Sync<float> HueOffset;
public readonly Sync<float> ValueOffset;
public readonly Sync<float> SaturationOffset;
public readonly Sync<float> AlphaMultiplier;
}
public readonly Sync<bool> PickMaterials;
public readonly Sync<bool> PickColors;
public readonly AssetRef<Material> CurrentMaterial;
public readonly SyncList<ColorMapping> ColorMappings;
readonly SyncRefList<Slot> _hideOnStroke;
// Derived classes can override this to only accept certain types of materials
protected virtual bool AcceptMaterial(IAssetProvider<Material> material) => true;
protected virtual void OnMaterialPicked() { }
protected virtual void OnColorPicked(color color) { }
readonly AssetRef<Material> _lastUsedMaterial;
readonly AssetRef<Material> _lastCreatedMaterial;
public void EnsureUnusedMaterial()
{
var previousMaterial = CurrentMaterial.Target as Component;
var createdPreviousMaterial = previousMaterial == _lastCreatedMaterial.Target && _lastCreatedMaterial.Target != null;
// Check if the material is used or if we haven't created it, so we can determine whether to make a copy
if (_lastUsedMaterial.Target == CurrentMaterial.Target ||
_lastCreatedMaterial.Target != CurrentMaterial.Target)
{
// create a copy of the material, otherwise we'd change everything that already uses it
var slot = World.RootSlot.FindOrAdd("Assets").FindOrAdd("BrushMaterials");
CurrentMaterial.Target = (IAssetProvider<Material>)slot.DuplicateComponent((Component)CurrentMaterial.Target);
// we have created this material
_lastCreatedMaterial.Target = CurrentMaterial.Target;
}
// Check if the material colors belong to the currently assigned material
if (ColorMappings.Count == 0 ||
ColorMappings[0].Field.Target.FindNearestParent<IAssetProvider<Material>>() != CurrentMaterial.Target)
{
var mat = (Component)CurrentMaterial.Target;
// If material picking is disabled and colors were configured manually, copy them over
if (ColorMappings.Count > 0 && previousMaterial?.GetType() == mat.GetType() && createdPreviousMaterial)
{
for (int i = 0; i < previousMaterial.SyncMemberCount; i++)
{
var prevField = previousMaterial.GetSyncMember(i) as IField<color>;
if (prevField == null)
continue;
var index = ColorMappings.FindIndex(m => m.Field.Target == prevField);
if (index >= 0)
ColorMappings[index].Field.Target = (IField<color>)mat.GetSyncMember(i);
}
// !!! This is not necessary, since the material types are the same, all fields should be contained
//ColorMappings.RemoveAll(m => m.Field.Target.FindNearestParent<Component>() == previousMaterial);
}
else
{
// Got to use heuristics to figure out which colors to change
var colorFields = mat.SyncMembers.OfType<IField<color>>();
ColorMappings.Clear();
// first try to find any non-white or non-black fields
var fields = colorFields.Where(f => f.Value.rgb != float3.One && f.Value.rgb != float3.Zero).ToList();
// then prefer the white fields
if (fields.Count == 0)
fields = colorFields.Where(f => f.Value.rgb == float3.One).ToList();
// just take the first one if possible
if (fields.Count == 0)
fields.Add(colorFields.FirstOrDefault());
// ignore specular colors, those shouldn't be changed
fields.RemoveAll(f => f.Name.ToLower().Contains("specular"));
if (fields.Count > 0)
{
// find reference field - the most saturated color
ColorHSV refColor = new ColorHSV(0, 0, 0);
for (int i = 0; i < fields.Count; i++)
{
var hsv = new ColorHSV(fields[i].Value);
if (hsv.s > refColor.s)
refColor = hsv;
}
// got reference color, assign fields and calculate offsets
for (int i = 0; i < fields.Count; i++)
{
var hsv = new ColorHSV(fields[i].Value);
var mapping = ColorMappings.Add();
mapping.Field.Target = fields[i];
mapping.HueOffset.Value = hsv.h - refColor.h;
mapping.ValueOffset.Value = hsv.v - refColor.v;
mapping.SaturationOffset.Value = hsv.s - refColor.s;
mapping.AlphaMultiplier.Value = hsv.a;
}
}
}
}
}
public void TryChangeCurrentMaterialColor(color color)
{
EnsureUnusedMaterial();
var targetHsv = new ColorHSV(color);
foreach (var mapping in ColorMappings)
{
var hsv = new ColorHSV(MathX.Repeat01(targetHsv.h + mapping.HueOffset),
targetHsv.s + mapping.SaturationOffset,
targetHsv.v + mapping.ValueOffset,
targetHsv.a * mapping.AlphaMultiplier);
mapping.Field.Target.Value = hsv;
}
OnColorPicked(color);
}
#endregion
#region STROKE PARAMETERS
public readonly Output<float> Pressure;
public readonly Output<float3> Position;
public readonly Output<floatQ> Rotation;
public readonly Output<float3> LastPointDelta;
public readonly Output<float3> Velocity;
public readonly Output<float3> RawDelta;
public readonly Output<float3> RawVelocity;
public readonly Output<float> RawStrokeLength;
public readonly Output<float> StrokeLength;
public readonly Output<float> NormalizedStrokeLength;
public readonly Output<float> StrokeFadeMultiplier;
public readonly Output<int> StrokeGroupIndex;
#endregion
#region LOCAL CALCULATED PROPERTIES
public bool IsStrokeActive { get; private set; }
public bool IsGroupActive { get; private set; }
public float3 CurrentPoint { get; private set; }
public float3 PreviousPoint { get; private set; }
public float3 PrevPreviousPoint { get; private set; }
#endregion
#region OVERRIDABLE CONTROL PROPERTIES
public virtual bool ForceStroke => false;
public virtual float MinimumPointDistance => FixedMinimumPointDistance.Value;
#endregion
// Methods to be derived
protected virtual void BeginStrokeGroup() { }
protected virtual void EndStrokeGroup() { }
protected virtual void BeginStroke() { }
protected virtual void EndStroke() { }
protected virtual void AddNewPoint() { }
protected virtual void UpdatePoint() { }
#region BRUSH LOGIC
double lastStrokeTime;
int pointCount;
float accumulatedStrokeLength;
IAssetProvider<Material> lastPickedMaterial;
protected override void OnAwake()
{
base.OnAwake();
PickMaterials.Value = true;
PickColors.Value = true;
FixedMinimumPointDistance.Value = 0.002f;
//MinimumPointAngle.Value = 0.5f;
PositionSmoothing.Value = 0.02f;
RotationSmoothing.Value = 0.02f;
PressureSmoothing.Value = 0.05f;
MaxStrokeLength.Value = float.MaxValue;
StrokeGroupFinishWaitTime.Value = 0.5f;
ActivationThreshold.Value = 0.05f;
DeactivationThresholdRatio.Value = 0.75f;
}
public override void Update(float primaryStrength, float2 secondaryAxis, Digital primary, Digital secondary)
{
if(ActiveTool != null && ActiveTool.TipTouch.CurrentClosestHit.Collider != null &&
ActiveTool.TipTouch.IsTouchingDistance(ActiveTool.TipTouch.CurrentClosestHit.Distance))
{
var closestHit = ActiveTool.TipTouch.CurrentClosestHit.Collider.Slot;
if (PickMaterials)
{
var materialProxy = closestHit.GetComponentInParents<AssetProxy<Material>>();
var material = materialProxy?.AssetReference.Target;
if (material != null && material != lastPickedMaterial && AcceptMaterial(material))
{
CurrentMaterial.Target = materialProxy.AssetReference.Target;
OnMaterialPicked();
}
lastPickedMaterial = material;
}
if(PickColors)
{
var colorProxy = closestHit.GetComponentInParents<IValueSource<color>>();
if (colorProxy != null)
TryChangeCurrentMaterialColor(colorProxy.Value);
}
}
Pressure.Value = MathX.Lerp(Pressure.Value, primaryStrength, MathX.Clamp01(Time.Delta / PressureSmoothing));
Position.Value = MathX.Lerp(Position.Value, Tip, MathX.Clamp01(Time.Delta / PositionSmoothing));
Rotation.Value = MathX.Slerp(Rotation.Value, TipRotation, MathX.Clamp01(Time.Delta / RotationSmoothing));
bool shouldStroke = ForceStroke;
if (IsStrokeActive)
shouldStroke |= Pressure.Value >= ActivationThreshold * DeactivationThresholdRatio;
else
shouldStroke |= Pressure.Value >= ActivationThreshold;
if(!IsStrokeActive)
{
// currently no stroke is active, check if we should activate a new one
if(shouldStroke)
{
if(!IsGroupActive)
{
BeginStrokeGroup();
Slot.ForeachComponent<IBrushTipEventReceiver>(r => r.OnBeginStrokeGroup(this));
StrokeGroupIndex.Value = -1; // will be immediatelly incremented to 0
IsGroupActive = true;
}
_lastUsedMaterial.Target = CurrentMaterial.Target;
StrokeGroupIndex.Value++;
Position.Value = Tip;
BeginStroke();
Slot.ForeachComponent<IBrushTipEventReceiver>(r => r.OnBeginStroke(this));
IsStrokeActive = true;
SetOnStrokeVisibility(false);
// Create the first point
pointCount = 0;
RunCreateNewPoint();
}
else
{
// Check if we should finish stroke group
if(IsGroupActive && Time.WorldTime - lastStrokeTime >= StrokeGroupFinishWaitTime)
{
EndStrokeGroup();
Slot.ForeachComponent<IBrushTipEventReceiver>(r => r.OnEndStrokeGroup(this));
IsGroupActive = false;
}
}
}
else
{
if(shouldStroke)
{
// continue the stroke
// check if we should create a new point
bool createNewPoint = float3.Distance(PreviousPoint, Position.Value) >= MinimumPointDistance;
// immediatelly create a new point after the very first one in a stroke
createNewPoint |= pointCount == 1;
if (createNewPoint)
RunCreateNewPoint();
else
RunUpdatePoint();
}
else
{
// finish the stroke
EndStroke();
Slot.ForeachComponent<IBrushTipEventReceiver>(r => r.OnEndStroke(this));
IsStrokeActive = false;
lastStrokeTime = Time.WorldTime;
SetOnStrokeVisibility(true);
}
}
}
void RunCreateNewPoint()
{
if (pointCount == 0)
{
accumulatedStrokeLength = 0;
CurrentPoint = PreviousPoint = PrevPreviousPoint = Position.Value;
RawStrokeLength.Value = 0f;
Pressure.Value = 0f; // force the pressure for the first point to tbe zero
}
else
{
PrevPreviousPoint = PreviousPoint;
PreviousPoint = Position.Value;
if (pointCount > 1)
accumulatedStrokeLength += float3.Distance(CurrentPoint, PreviousPoint);
}
pointCount++;
UpdateStrokeParameters();
AddNewPoint();
Slot.ForeachComponent<IBrushTipEventReceiver>(r => r.OnAddNewPoint(this));
UpdatePoint();
Slot.ForeachComponent<IBrushTipEventReceiver>(r => r.OnUpdatePoint(this));
}
void RunUpdatePoint()
{
UpdateStrokeParameters();
UpdatePoint();
Slot.ForeachComponent<IBrushTipEventReceiver>(r => r.OnUpdatePoint(this));
}
void UpdateStrokeParameters()
{
RawDelta.Value = Position.Value - CurrentPoint;
Velocity.Value = RawDelta.Value / Time.Delta;
LastPointDelta.Value = Position.Value - PreviousPoint;
RawStrokeLength.Value += RawDelta.Value.Magnitude;
StrokeLength.Value = accumulatedStrokeLength + float3.Distance(Position.Value, PreviousPoint);
NormalizedStrokeLength.Value = StrokeLength.Value / MaxStrokeLength.Value;
StrokeFadeMultiplier.Value = MathX.Clamp01(MathX.Min(
MathX.InverseLerp(0f, StrokeFadeInLength.Value, StrokeLength.Value),
MathX.InverseLerp(MaxStrokeLength - StrokeFadeOutLength, MaxStrokeLength, StrokeLength.Value)));
// update current point
CurrentPoint = Position.Value;
}
#endregion
//#region EVENTS
//Action<IBrushTipEventReceiver> _sendBeginStrokeGroup;
//Action<IBrushTipEventReceiver> _sendEndStrokeGroup;
//Action<IBrushTipEventReceiver> _sendBeginStroke;
//Action<IBrushTipEventReceiver> _sendEndStroke;
//Action<IBrushTipEventReceiver> _sendAddNewPoint;
//Action<IBrushTipEventReceiver> _sendUpdatePoint;
//void InvokeBeginStrokeGroup(IBrushTipEventReceiver r) => r.OnBeginStroke(this);
//void InvokeEndStrokeGroup(IBrushTipEventReceiver r) => r.OnEndStrokeGroup(this);
//void InvokeBeginStroke(IBrushTipEventReceiver r) => r.OnBeginStroke(this);
//void InvokeEndStroke(IBrushTipEventReceiver r) => r.OnEndStroke(this);
//void InvokeAddNewPoint(IBrushTipEventReceiver r) => r.OnAddNewPoint(this);
//void InvokeUpdatePoint(IBrushTipEventReceiver r) => r.OnUpdatePoint(this);
//#endregion
#region UTILITY
protected KnobControl SpawnRingControl()
{
var sizeKnob = Slot.AddSlot("SizeKnob");
var sizeVisual = sizeKnob.AddSlot("Visual");
_hideOnStroke.Add(sizeKnob);
sizeVisual.LocalPosition = float3.Forward * 0.05f;
sizeVisual.LocalRotation = floatQ.AxisAngle(float3.Right, 90f);
var joint = sizeKnob.AttachComponent<Joint>();
var knob = sizeKnob.AttachComponent<KnobControl>();
joint.MaxSwing.Value = 0f;
joint.MaxTwist.Value = float.PositiveInfinity;
joint.SetAxis(float3.Forward);
knob.RotationAxis.Value = float3.Forward;
knob.Rate.Value = 1f;
var knobControl = sizeVisual.AttachMesh<BevelSoliRingMesh, FresnelMaterial>();
knobControl.mesh.Radius.Value = 0.1f;
knobControl.mesh.Thickness.Value = 0.001f;
knobControl.mesh.Width.Value = 0.01f;
knobControl.material.BlendMode.Value = BlendMode.Alpha;
knobControl.material.NearColor.Value = new color(0f, 0.2f);
knobControl.material.FarColor.Value = new color(0f, 1f);
knobControl.material.FarTextureScale.Value = new float2(1f, 2f);
knobControl.material.NearTextureScale.Value = new float2(1f, 2f);
var tex = sizeVisual.AttachTexture(NeosAssets.Common.Misc.TransparentStripe);
knobControl.material.NearTexture.Target = tex;
knobControl.material.FarTexture.Target = tex;
var knobCollider = sizeVisual.AttachComponent<MeshCollider>();
knobCollider.Mesh.Target = knobControl.mesh;
return knob;
}
protected void HideOnStroke(Slot slot)
{
_hideOnStroke.Add(slot);
}
protected void SetOnStrokeVisibility(bool visible)
{
foreach (var slot in _hideOnStroke)
if (slot != null)
slot.ActiveSelf = visible;
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BaseX;
namespace FrooxEngine
{
public enum LineColorMode
{
MaterialOnly,
SingleVertexColor,
ContinuousVertexColor
}
public class GeometryLineBrushTip : BrushTip
{
public override int Version => 1;
public override float3 LocalTip
{
get
{
if (TipAnchor.Target == null)
return float3.Zero;
return Slot.GlobalPointToLocal(TipAnchor.Target.GlobalPosition);
}
}
public override floatQ LocalTipRotation
{
get
{
if (TipAnchor.Target == null)
return floatQ.Identity;
return Slot.GlobalRotationToLocal(TipAnchor.Target.GlobalRotation);
}
}
public readonly SyncRef<Slot> TipAnchor;
public class Line : SyncObject
{
public readonly Sync<SegmentedBuilder.Topology> Topology;
public readonly Sync<SegmentedBuilder.Ends> Ends;
public readonly Sync<SegmentedBuilder.Shading> Shading;
public readonly Sync<int> Points;
public readonly Sync<bool> DualSided;
public readonly Sync<bool> AbsolutePointOffsets;
public readonly Sync<float2> UVScale;
public readonly Sync<bool> ScaleUVByCircumference;
public readonly Sync<bool> PreciseUV;
public readonly SyncArray<float3> PointOffsets;
public readonly Sync<LineColorMode> ColorMode;
public readonly Sync<bool> UseTipRotation;
public readonly Sync<float> MaxSize;
public readonly SyncRef<Slot> OverrideTip;
public readonly SyncRef<Slot> OverrideTipRotation;
public readonly RootSpace OffsetSpace;
public readonly RootSpace RotationSpace;
public readonly Input<float> Size;
public readonly Input<color> Color;
public readonly Input<float3> Offset;
public readonly Input<floatQ> Rotation;
protected override void OnAwake()
{
OffsetSpace.UseLocalSpaceOf(Slot);
RotationSpace.UseLocalSpaceOf(Slot);
Topology.Value = SegmentedBuilder.Topology.Circle;
Ends.Value = SegmentedBuilder.Ends.Capped;
Shading.Value = SegmentedBuilder.Shading.Smooth;
Points.Value = 6;
DualSided.Value = false;
UVScale.Value = float2.One;
ScaleUVByCircumference.Value = true;
ColorMode.Value = LineColorMode.MaterialOnly;
MaxSize.Value = 0.02f;
}
}
public Line LineStyle
{
get
{
if (LineStyles.Count == 0)
LineStyles.Add();
return LineStyles[0];
}
}
public readonly SyncList<Line> LineStyles;
public readonly Sync<bool> UseRelativeMinimumPointDistance;
public readonly Sync<float> RelativeMinimumPointDistanceRatio;
public readonly Sync<bool> PressureAffectsSize;
public readonly SyncRefList<MeshRenderer> MaterialPreviews;
readonly DriveRef<MultiLineMesh> _previewMesh;
readonly FieldDrive<float3> _previewMeshOffset;
readonly SyncRef<Slot> _sizeKnob;
public override float MinimumPointDistance => UseRelativeMinimumPointDistance.Value ?
_lastSize * RelativeMinimumPointDistanceRatio : FixedMinimumPointDistance;
float _lastSize;
Slot _strokeGroup;
Slot _stroke;
MultiLineMesh _strokeMesh;
MultiLineMesh.Line[] _lines;
protected override void OnAwake()
{
base.OnAwake();
UseRelativeMinimumPointDistance.Value = true;
RelativeMinimumPointDistanceRatio.Value = 0.35f;
PressureAffectsSize.Value = true;
}
protected override void OnAttach()
{
base.OnAttach();
var visual = ConstructDebugVisual<PBS_Metallic>(0f);
LineStyles.Add();
MaterialPreviews.Add(visual.visual.GetComponentInChildren<MeshRenderer>());
var tip = visual.tip;
tip.GlobalPosition = visual.cone.TopPoint;
TipAnchor.Target = tip;
var previewSlot = Slot.AddSlot("Stroke Preview");
_previewMeshOffset.Target = previewSlot.Position_Field;
var previewModel = previewSlot.AttachMesh<MultiLineMesh>(CurrentMaterial.Target);
_previewMesh.Target = previewModel;
HideOnStroke(_previewMesh.Target.Slot);
MaterialPreviews.Add(previewModel.Slot.GetComponent<MeshRenderer>());
var knob = SpawnRingControl();
knob.Callback.Target = ChangeSize;
}
void ChangeSize(float delta)
{
var mul = 1f + delta;
foreach (var style in LineStyles)
style.MaxSize.Value *= mul;
}
protected override void OnChanges()
{
base.OnChanges();
foreach (var renderer in MaterialPreviews)
if(renderer != null)
renderer.Material.Target = CurrentMaterial.Target;
}
protected override void OnCommonUpdate()
{
if(_previewMeshOffset.Target == null)
_previewMeshOffset.Target = _previewMesh.Target?.Slot.Position_Field;
else
_previewMeshOffset.Target.Value = LocalTip;
var previewMesh = _previewMesh.Target;
previewMesh.Lines.EnsureExactCount(LineStyles.Count);
for (int i = 0; i < LineStyles.Count; i++)
{
var line = previewMesh.Lines[i];
var style = LineStyles[i];
AssignStyle(style, line);
line.Positions.EnsureExactCount(2);
line.Orientations.EnsureExactCount(2);
line.Scales.EnsureExactCount(2);
var position = ComputeGlobalPoint(style, Tip);
var rotation = style.UseTipRotation.Value ?
previewMesh.Slot.GlobalRotationToLocal(StyleGlobalRotation(style)) : floatQ.Identity;
line.Positions[0] = previewMesh.Slot.GlobalPointToLocal(position);
line.Positions[1] = line.Positions[0] + rotation * float3.Forward * 0.005f;
line.Orientations[0] = rotation;
line.Orientations[1] = rotation;
line.Scales[0] = style.MaxSize;
line.Scales[1] = style.MaxSize;
}
}
protected override void BeginStrokeGroup()
{
_strokeGroup = World.AddSlot("Strokes");
_strokeGroup.GlobalPosition = Tip;
_strokeGroup.GlobalScale = World.LocalUser.Root.Scale * float3.One;
var grabbable = _strokeGroup.AttachComponent<Grabbable>();
grabbable.Scalable = true;
}
protected override void BeginStroke()
{
_stroke = _strokeGroup.AddSlot("Stroke");
_stroke.GlobalPosition = Tip;
_strokeMesh = _stroke.AttachMesh<MultiLineMesh>(CurrentMaterial.Target);
_strokeMesh.HighPriorityIntegration.Value = true;
_lines = _lines.EnsureExactSize(LineStyles.Count);
for (int i = 0; i < LineStyles.Count; i++)
{
var line = _strokeMesh.Lines.Add();
var style = LineStyles[i];
AssignStyle(style, line);
_lines[i] = line;
}
}
void AssignStyle(Line style, MultiLineMesh.Line line)
{
line.Topology.Value = style.Topology;
line.Ends.Value = style.Ends;
line.Shading.Value = style.Shading;
line.UVScale.Value = style.UVScale;
line.ScaleUVByCircumference.Value = style.ScaleUVByCircumference;
line.PreciseUV.Value = style.PreciseUV;
line.Points.Value = style.Points;
line.DualSided.Value = style.DualSided;
line.AbsolutePointOffets.Value = style.AbsolutePointOffsets;
if (style.ColorMode.Value == LineColorMode.SingleVertexColor)
line.Color.Value = style.Color.Evaluate();
}
protected override void AddNewPoint()
{
for (int i = 0; i < LineStyles.Count; i++)
{
var line = _lines[i];
var style = LineStyles[i];
line.Positions.Append();
line.Scales.Append();
if (style.ColorMode.Value == LineColorMode.ContinuousVertexColor)
line.Colors.Append();
if (style.UseTipRotation.Value || style.Rotation.IsConnected)
line.Orientations.Append();
}
}
float3 ComputeGlobalPoint(Line style, float3 basePosition)
{
float3 offset = style.Offset.Evaluate();
if (style.OverrideTip.Target != null)
basePosition += style.OverrideTip.Target.GlobalPosition - Tip;
basePosition = style.OffsetSpace.Space.GlobalPointToLocal(basePosition);
basePosition += offset;
basePosition = style.OffsetSpace.Space.LocalPointToGlobal(basePosition);
return basePosition;
}
floatQ StyleGlobalRotation(Line style)
{
if (style.OverrideTipRotation.Target != null)
return style.OverrideTipRotation.Target.GlobalRotation;
return TipRotation;
}
/*floatQ ComputeGlobalRotation(Line style, floatQ baseRotation)
{
if (style.OverrideTipRotation.Target != null)
baseRotation = baseRotation * floatQ.FromToRotation(TipRotation, style.OverrideTipRotation.Target.GlobalRotation);
return baseRotation;
}*/
protected override void UpdatePoint()
{
for (int i = 0; i < LineStyles.Count; i++)
{
var line = _lines[i];
var style = LineStyles[i];
int last = line.Positions.Count - 1;
float size = style.Size.Evaluate((PressureAffectsSize.Value ? Pressure.Value : 1f) * style.MaxSize.Value);
float3 position = ComputeGlobalPoint(style, Position.Value);
line.Positions[last] = _stroke.GlobalPointToLocal(position);
line.Scales[last] = size;
if (style.ColorMode.Value == LineColorMode.ContinuousVertexColor)
line.Colors[last] = style.Color.Evaluate();
if (style.Rotation.IsConnected)
line.Orientations[last] = _stroke.GlobalRotationToLocal(style.RotationSpace.Space.LocalRotationToGlobal(style.Rotation.Evaluate()));
if (style.UseTipRotation.Value)
{
floatQ rotation = StyleGlobalRotation(style);
line.Orientations[last] = _stroke.GlobalRotationToLocal(rotation);
}
_lastSize = size;
}
}
protected override void EndStroke()
{
var collider = _stroke.AttachComponent<MeshCollider>();
collider.Mesh.Target = _strokeMesh;
_strokeMesh.HighPriorityIntegration.Value = false;
_stroke = null;
_strokeMesh = null;
Array.Clear(_lines, 0, _lines.Length);
}
protected override void EndStrokeGroup()
{
_strokeGroup = null;
}
protected override void OnLoaded(DataTreeNode node, LoadControl control)
{
if (control.GetTypeVersion(this.GetType()) == 0)
RunSynchronously(() =>
{
HideOnStroke(_sizeKnob.Target);
_sizeKnob.Target = null;
HideOnStroke(_previewMesh.Target.Slot);
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment