Created
June 30, 2018 20:33
-
-
Save Frooxius/e5deca156ea7693ecfe2551d10189f5a to your computer and use it in GitHub Desktop.
Neos Brush System
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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