From 1a82412603f05ebde34a1ae911e4fddf40053185 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 3 Apr 2024 01:59:18 -0500 Subject: [PATCH] Version 0.10.0 Alpha.2 [Prerelease] Warning: This commit hash may not be preserved! Debug state: Some collider pairs may explode on contact due to improper contact generation when using UnitySim. However, issues with compound colliders have been resolved since the previous alpha. Calligraphics single-font functionality is intact, though multi-font pathways have limited testing. --- .../BakingSystems/FontSmartBlobberSystem.cs | 74 +- .../Authoring/TextRendererAuthoring.cs | 49 +- Calligraphics/Components/FontBlob.cs | 54 +- Calligraphics/Components/TextComponents.cs | 86 +- Calligraphics/Components/TextConfiguration.cs | 139 --- .../Components/TextRenderingComponents.cs | 45 + .../ExtraShaderGraphNodes/LatiosTextNode.cs | 12 +- .../FixedStack512Bytes.cs} | 5 +- .../FixedStack512Bytes.cs.meta} | 0 Calligraphics/Internal/FontMaterialSet.cs | 69 ++ .../Internal/FontMaterialSet.cs.meta | 11 + Calligraphics/Internal/GlyphGeneration.cs | 101 +- .../Internal/RichText/CalliExtensions.cs | 4 +- ...li_FontStyleStack.cs => FontStyleStack.cs} | 22 +- ...leStack.cs.meta => FontStyleStack.cs.meta} | 0 .../Internal/RichText/HighLightState.cs | 13 +- .../{CalliOffset.cs => RectOffsets.cs} | 40 +- ...alliOffset.cs.meta => RectOffsets.cs.meta} | 0 .../Internal/RichText/RichTextAttribute.cs | 24 - .../Internal/RichText/RichTextParser.cs | 879 +++++++++--------- .../RichText/RichTextTagIdentifier.cs | 27 + ....cs.meta => RichTextTagIdentifier.cs.meta} | 0 .../Internal/RichText/RichTextTagType.cs | 4 +- .../Internal/RichText/TagUnitType.cs | 4 +- .../Internal/RichText/TagValueType.cs | 4 +- Calligraphics/Internal/TextConfiguration.cs | 140 +++ .../TextConfiguration.cs.meta | 0 .../ShaderLibrary/TextGlyphParsing.hlsl | 19 +- .../Systems/AnimateTextTransitionSystem.cs | 1 - Calligraphics/Systems/GenerateGlyphsSystem.cs | 103 +- .../Rendering/TextRenderingDispatchSystem.cs | 541 +++++++++-- .../Rendering/TextRenderingUpdateSystem.cs | 156 +++- Core/GameplayToolkit/DynamicHashMap.cs | 2 +- Core/Utilities/BlobBuilderExtensions.cs | 9 +- Kinemation/ACL_Unity/AclUnityDecompression.cs | 180 +++- .../Plugins/OSX/AArch64/AclUnity.dylib | Bin 311819 -> 311819 bytes .../Plugins/OSX/X86/libAclUnity.dylib | Bin 342112 -> 342112 bytes .../Plugins/OSX/X86/libAclUnity_AVX.dylib | Bin 342488 -> 342488 bytes Kinemation/Components/AnimationComponents.cs | 157 +++- .../Components/OptimizedSkeletonAspect.cs | 22 + .../OptimizedSkeletonAspectInternals.cs | 29 +- Kinemation/Utilities/Blenders.cs | 63 +- .../ApplyMecanimLayersToExposedBonesSystem.cs | 2 +- .../CompoundColliderSmartBlobberSystem.cs | 6 +- .../UnitySim/UnitySimShapeMassUtilities.cs | 65 +- .../Queries/PointRayCollider/PointRayBox.cs | 13 +- README.md | 3 +- package.json | 2 +- 48 files changed, 2201 insertions(+), 978 deletions(-) delete mode 100644 Calligraphics/Components/TextConfiguration.cs rename Calligraphics/{Components/FixedStack64Bytes.cs => Internal/FixedStack512Bytes.cs} (88%) rename Calligraphics/{Components/FixedStack64Bytes.cs.meta => Internal/FixedStack512Bytes.cs.meta} (100%) create mode 100644 Calligraphics/Internal/FontMaterialSet.cs create mode 100644 Calligraphics/Internal/FontMaterialSet.cs.meta rename Calligraphics/Internal/RichText/{Calli_FontStyleStack.cs => FontStyleStack.cs} (91%) rename Calligraphics/Internal/RichText/{Calli_FontStyleStack.cs.meta => FontStyleStack.cs.meta} (100%) rename Calligraphics/Internal/RichText/{CalliOffset.cs => RectOffsets.cs} (62%) rename Calligraphics/Internal/RichText/{CalliOffset.cs.meta => RectOffsets.cs.meta} (100%) delete mode 100644 Calligraphics/Internal/RichText/RichTextAttribute.cs create mode 100644 Calligraphics/Internal/RichText/RichTextTagIdentifier.cs rename Calligraphics/Internal/RichText/{RichTextAttribute.cs.meta => RichTextTagIdentifier.cs.meta} (100%) create mode 100644 Calligraphics/Internal/TextConfiguration.cs rename Calligraphics/{Components => Internal}/TextConfiguration.cs.meta (100%) diff --git a/Calligraphics/Authoring/BakingSystems/FontSmartBlobberSystem.cs b/Calligraphics/Authoring/BakingSystems/FontSmartBlobberSystem.cs index b390279..8ac3d44 100644 --- a/Calligraphics/Authoring/BakingSystems/FontSmartBlobberSystem.cs +++ b/Calligraphics/Authoring/BakingSystems/FontSmartBlobberSystem.cs @@ -99,31 +99,32 @@ protected override void OnUpdate() }).WithEntityQueryOptions(EntityQueryOptions.IncludePrefab | EntityQueryOptions.IncludeDisabledEntities).WithoutBurst().Run(); } - public static BlobAssetReference BakeFont(FontAsset font, Material material) + public static unsafe BlobAssetReference BakeFont(FontAsset font, Material material) { float materialPadding = material.GetPaddingForText(false, false); var builder = new BlobBuilder(Allocator.Temp); - ref FontBlob FontBlobFont = ref builder.ConstructRoot(); - FontBlobFont.scale = font.faceInfo.scale; - FontBlobFont.pointSize = font.faceInfo.pointSize; - FontBlobFont.baseLine = font.faceInfo.baseline; - FontBlobFont.ascentLine = font.faceInfo.ascentLine; - FontBlobFont.descentLine = font.faceInfo.descentLine; - FontBlobFont.lineHeight = font.faceInfo.lineHeight; - FontBlobFont.regularStyleSpacing = font.regularStyleSpacing; - FontBlobFont.regularStyleWeight = font.regularStyleWeight; - FontBlobFont.boldStyleSpacing = font.boldStyleSpacing; - FontBlobFont.boldStyleWeight = font.boldStyleWeight; - FontBlobFont.italicsStyleSlant = font.italicStyleSlant; - FontBlobFont.capLine = font.faceInfo.capLine; - FontBlobFont.atlasWidth = font.atlasWidth; - FontBlobFont.atlasHeight = font.atlasHeight; - FontBlobFont.materialPadding = materialPadding; - - var glyphPairAdjustments = font.GetGlyphPairAdjustmentRecordLookup(); - - BlobBuilderArray glyphBuilder = builder.Allocate(ref FontBlobFont.characters, font.characterTable.Count); + ref FontBlob fontBlobRoot = ref builder.ConstructRoot(); + fontBlobRoot.scale = font.faceInfo.scale; + fontBlobRoot.pointSize = font.faceInfo.pointSize; + fontBlobRoot.baseLine = font.faceInfo.baseline; + fontBlobRoot.ascentLine = font.faceInfo.ascentLine; + fontBlobRoot.descentLine = font.faceInfo.descentLine; + fontBlobRoot.lineHeight = font.faceInfo.lineHeight; + fontBlobRoot.regularStyleSpacing = font.regularStyleSpacing; + fontBlobRoot.regularStyleWeight = font.regularStyleWeight; + fontBlobRoot.boldStyleSpacing = font.boldStyleSpacing; + fontBlobRoot.boldStyleWeight = font.boldStyleWeight; + fontBlobRoot.italicsStyleSlant = font.italicStyleSlant; + fontBlobRoot.capLine = font.faceInfo.capLine; + fontBlobRoot.atlasWidth = font.atlasWidth; + fontBlobRoot.atlasHeight = font.atlasHeight; + fontBlobRoot.materialPadding = materialPadding; + + var glyphPairAdjustments = font.GetGlyphPairAdjustmentRecordLookup(); + Span hashCounts = stackalloc int[64]; + hashCounts.Clear(); + BlobBuilderArray glyphBuilder = builder.Allocate(ref fontBlobRoot.characters, font.characterTable.Count); for (int i = 0; i < font.characterTable.Count; i++) { var character = font.characterTable[i]; @@ -185,7 +186,7 @@ public static BlobAssetReference BakeFont(FontAsset font, Material mat } //Get vertices and uvs - var vertices = GetVertices(character.glyph.metrics, materialPadding, font.atlasPadding / 2f, FontBlobFont.baseScale); + var vertices = GetVertices(character.glyph.metrics, materialPadding, font.atlasPadding / 2f, fontBlobRoot.baseScale); BlobBuilderArray verticesBuilder = builder.Allocate(ref glyphBlob.vertices, vertices.Length); for (int j = 0; j < vertices.Length; j++) { @@ -206,18 +207,43 @@ public static BlobAssetReference BakeFont(FontAsset font, Material mat uv2Builder[j] = uv2s[j]; } - glyphBuilder[i] = glyphBlob; + hashCounts[BlobTextMeshGlyphExtensions.GetGlyphHash(glyphBlob.unicode)]++; } } + var hashes = builder.Allocate(ref fontBlobRoot.glyphLookupMap, 64); + Span hashArrays = stackalloc HashArray[64]; + for (int i = 0; i < hashes.Length; i++) + { + hashArrays[i] = new HashArray + { + hashArray = (GlyphLookup*)builder.Allocate(ref hashes[i], hashCounts[i]).GetUnsafePtr() + }; + hashCounts[i] = 0; + } + + for (int i = 0; i < glyphBuilder.Length; i++) + { + if (glyphBuilder[i].unicode == 0) // Is this the right way to rule out null glyphs? + continue; + var hash = BlobTextMeshGlyphExtensions.GetGlyphHash(glyphBuilder[i].unicode); + hashArrays[hash].hashArray[hashCounts[hash]] = new GlyphLookup { unicode = glyphBuilder[i].unicode, index = i }; + hashCounts[hash]++; + } + var result = builder.CreateBlobAssetReference(Allocator.Persistent); builder.Dispose(); - FontBlobFont = result.Value; + fontBlobRoot = result.Value; return result; } + unsafe struct HashArray + { + public GlyphLookup* hashArray; + } + private static FixedList64Bytes GetVertices(GlyphMetrics glyphMetrics, float materialPadding, float stylePadding, float currentElementScale) { float2 topLeft; diff --git a/Calligraphics/Authoring/TextRendererAuthoring.cs b/Calligraphics/Authoring/TextRendererAuthoring.cs index e6812d2..7c32870 100644 --- a/Calligraphics/Authoring/TextRendererAuthoring.cs +++ b/Calligraphics/Authoring/TextRendererAuthoring.cs @@ -1,8 +1,9 @@ +using System; +using System.Collections.Generic; using Latios.Authoring; +using Latios.Calligraphics.Rendering; using Latios.Calligraphics.Rendering.Authoring; using Latios.Kinemation.Authoring; -using System; -using System.Collections.Generic; using Unity.Collections; using Unity.Entities; using Unity.Entities.Graphics; @@ -21,14 +22,14 @@ public class TextRendererAuthoring : MonoBehaviour [TextArea(5, 10)] public string text; - public float fontSize = 12f; - public bool wordWrap = true; - public float maxLineWidth = float.MaxValue; + public float fontSize = 12f; + public bool wordWrap = true; + public float maxLineWidth = float.MaxValue; public HorizontalAlignmentOptions horizontalAlignment = HorizontalAlignmentOptions.Left; - public VerticalAlignmentOptions verticalAlignment = VerticalAlignmentOptions.Top; - public bool isOrthographic; - public FontStyles fontStyle; - public FontWeight fontWeight; + public VerticalAlignmentOptions verticalAlignment = VerticalAlignmentOptions.Top; + public bool isOrthographic; + public FontStyles fontStyle; + public FontWeight fontWeight; public Color32 color = UnityEngine.Color.white; @@ -39,7 +40,7 @@ public class TextRendererAuthoring : MonoBehaviour public struct FontMaterialPair { public FontAsset font; - public Material overrideMaterial; + public Material overrideMaterial; public Material material => overrideMaterial == null ? font.material : overrideMaterial; } @@ -58,11 +59,15 @@ public override void Bake(TextRendererAuthoring authoring) AddFontRendering(entity, authoring.fontsAndMaterials[0]); if (authoring.fontsAndMaterials.Count > 1) { - var additionalEntities = AddBuffer(entity).Reinterpret(); + AddComponent(entity); + AddBuffer(entity); + var additionalEntities = AddBuffer(entity).Reinterpret(); for (int i = 1; i < authoring.fontsAndMaterials.Count; i++) { var newEntity = CreateAdditionalEntity(TransformUsageFlags.Renderable); AddFontRendering(newEntity, authoring.fontsAndMaterials[i]); + AddComponent(newEntity); + AddBuffer(newEntity); additionalEntities.Add(newEntity); } } @@ -72,14 +77,14 @@ public override void Bake(TextRendererAuthoring authoring) calliString.Append(authoring.text); AddComponent(entity, new TextBaseConfiguration { - fontSize = authoring.fontSize, - color = authoring.color, - maxLineWidth = math.select(float.MaxValue, authoring.maxLineWidth, authoring.wordWrap), + fontSize = authoring.fontSize, + color = authoring.color, + maxLineWidth = math.select(float.MaxValue, authoring.maxLineWidth, authoring.wordWrap), lineJustification = authoring.horizontalAlignment, verticalAlignment = authoring.verticalAlignment, - isOrthographic = authoring.isOrthographic, - fontStyle = authoring.fontStyle, - fontWeight = authoring.fontWeight, + isOrthographic = authoring.isOrthographic, + fontStyle = authoring.fontStyle, + fontWeight = authoring.fontWeight, }); } @@ -92,16 +97,16 @@ void AddFontRendering(Entity entity, FontMaterialPair fontMaterialPair) var layer = GetLayer(); this.BakeTextBackendMeshAndMaterial(new MeshRendererBakeSettings { - targetEntity = entity, + targetEntity = entity, renderMeshDescription = new RenderMeshDescription { FilterSettings = new RenderFilterSettings { - Layer = layer, + Layer = layer, RenderingLayerMask = (uint)(1 << layer), - ShadowCastingMode = ShadowCastingMode.Off, - ReceiveShadows = false, - MotionMode = MotionVectorGenerationMode.Object, + ShadowCastingMode = ShadowCastingMode.Off, + ReceiveShadows = false, + MotionMode = MotionVectorGenerationMode.Object, StaticShadowCaster = false, }, LightProbeUsage = LightProbeUsage.Off, diff --git a/Calligraphics/Components/FontBlob.cs b/Calligraphics/Components/FontBlob.cs index 5788ec1..a99a24d 100644 --- a/Calligraphics/Components/FontBlob.cs +++ b/Calligraphics/Components/FontBlob.cs @@ -1,3 +1,4 @@ +using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using UnityEngine.TextCore.LowLevel; @@ -7,12 +8,13 @@ namespace Latios.Calligraphics //TODO: Underlay, Bold, Smallcaps public struct FontBlob { - public BlobArray characters; - public float ascentLine; - public float descentLine; - public float lineHeight; - public float pointSize; - public float scale; + public BlobArray characters; + public BlobArray > glyphLookupMap; + public float ascentLine; + public float descentLine; + public float lineHeight; + public float pointSize; + public float scale; public float baseLine; public float atlasWidth; @@ -22,7 +24,7 @@ public struct FontBlob public float regularStyleWeight; public float boldStyleSpacing; public float boldStyleWeight; - public byte italicsStyleSlant; + public byte italicsStyleSlant; public float capLine; @@ -40,14 +42,14 @@ public struct FontBlob public float baseScale => 1f / pointSize * scale * .1f; - public const float smallCapsScaleMultiplier = .8f; + public const float smallCapsScaleMultiplier = .8f; public const float orthographicScaleMultiplier = 10f; } public struct GlyphBlob { - public uint glyphIndex; - public uint unicode; + public uint glyphIndex; + public uint unicode; public BlobArray vertices; public BlobArray uv; public BlobArray uv2; @@ -84,13 +86,13 @@ public struct GlyphBlob public struct AdjustmentPair { public FontFeatureLookupFlags fontFeatureLookupFlags; - public GlyphAdjustment firstAdjustment; - public GlyphAdjustment secondAdjustment; + public GlyphAdjustment firstAdjustment; + public GlyphAdjustment secondAdjustment; } public struct GlyphAdjustment { - public uint glyphUnicode; + public uint glyphUnicode; public float xPlacement; public float yPlacement; public float xAdvance; @@ -100,29 +102,41 @@ public struct GlyphAdjustment { xPlacement = a.xPlacement + b.xPlacement, yPlacement = a.yPlacement + b.yPlacement, - xAdvance = a.xAdvance + b.xAdvance, - yAdvance = a.yAdvance + b.yAdvance, + xAdvance = a.xAdvance + b.xAdvance, + yAdvance = a.yAdvance + b.yAdvance, }; } } + public struct GlyphLookup + { + public uint unicode; + public int index; + } + public static class BlobTextMeshGlyphExtensions { - public static bool TryGetGlyph(ref this BlobArray glyphs, uint character, out int index) + public static bool TryGetGlyphIndex(ref this FontBlob font, uint character, out int index) { - index = -1; + ref var hashArray = ref font.glyphLookupMap[GetGlyphHash(character)]; + index = -1; - for (int i = 0; i < glyphs.Length; i++) + for (int i = 0; i < hashArray.Length; i++) { - if (glyphs[i].unicode == character) + if (hashArray[i].unicode == character) { - index = i; + index = hashArray[i].index; return true; } } return false; } + + public static int GetGlyphHash(uint unicode) + { + return (int)(unicode & 0x3f); + } } } diff --git a/Calligraphics/Components/TextComponents.cs b/Calligraphics/Components/TextComponents.cs index 37f16ea..58d58d7 100644 --- a/Calligraphics/Components/TextComponents.cs +++ b/Calligraphics/Components/TextComponents.cs @@ -34,36 +34,39 @@ public struct TextBaseConfiguration : IComponentData /// public Color32 color; /// - /// The horizontal alignment modes of the text + /// The horizontal alignment mode of the text /// - public HorizontalAlignmentOptions lineJustification; + public HorizontalAlignmentOptions lineJustification + { + get => (HorizontalAlignmentOptions)((m_alignmentWeightOrtho & 0x70) >> 4); + set => m_alignmentWeightOrtho = (ushort)((m_alignmentWeightOrtho & ~0x70) | ((ushort)value << 4)); + } /// - /// The horizontal alignment modes of the text + /// The vertical alignment mode of the text /// - public VerticalAlignmentOptions verticalAlignment; - public bool isOrthographic; - public FontStyles fontStyle; - public FontWeight fontWeight; - } - - /// - /// An additional rendered text entity containing a different font and material. - /// Currently unsupported. - /// - [InternalBufferCapacity(0)] - public struct AdditionalFontMaterialEntity : IBufferElementData - { - public EntityWith entity; - } + public VerticalAlignmentOptions verticalAlignment + { + get => (VerticalAlignmentOptions)((m_alignmentWeightOrtho & 0x380) >> 7); + set => m_alignmentWeightOrtho = (ushort)((m_alignmentWeightOrtho & ~0x380) | ((ushort)value << 7)); + } + public FontWeight fontWeight + { + get => (FontWeight)(m_alignmentWeightOrtho & 0x0f); + set => m_alignmentWeightOrtho = (ushort)((m_alignmentWeightOrtho & ~0x0f) | (ushort)value); + } + public FontStyles fontStyle + { + get => (FontStyles)m_fontStyleFlags; + set => m_fontStyleFlags = (ushort)value; + } + public bool isOrthographic + { + get => (m_alignmentWeightOrtho & 0x8000) != 0; + set => m_alignmentWeightOrtho = (ushort)((m_alignmentWeightOrtho & 0x7fff) | (value ? 0x8000 : 0)); + } - /// - /// A per-glyph index into the font and material that should be used to render it. - /// Currently unsupported. - /// - [InternalBufferCapacity(0)] - public struct FontMaterialSelectorForGlyph : IBufferElementData - { - public byte fontMaterialIndex; + private ushort m_fontStyleFlags; // 6 bits unused, but Unity may add more. + ushort m_alignmentWeightOrtho; // 5 bits unused. } /// @@ -111,19 +114,40 @@ public enum WriteMask : byte /// /// Horizontal text alignment options. /// - public enum HorizontalAlignmentOptions + public enum HorizontalAlignmentOptions : byte { - Left = 0x1, Center = 0x2, Right = 0x4, Justified = 0x8, Flush = 0x10, Geometry = 0x20 + Left, + Center, + Right, + Justified, + Flush, + Geometry } /// /// Vertical text alignment options. /// - public enum VerticalAlignmentOptions + public enum VerticalAlignmentOptions : byte { - Top = 0x100, Middle = 0x200, Bottom = 0x400, Baseline = 0x800, Geometry = 0x1000, Capline = 0x2000, + Top, + Middle, + Bottom, + Baseline, + Geometry, + Capline, } - public enum FontWeight { Thin = 100, ExtraLight = 200, Light = 300, Regular = 400, Medium = 500, SemiBold = 600, Bold = 700, Heavy = 800, Black = 900 }; + public enum FontWeight + { + Thin, + ExtraLight, + Light, + Regular, + Medium, + SemiBold, + Bold, + Heavy, + Black + }; } diff --git a/Calligraphics/Components/TextConfiguration.cs b/Calligraphics/Components/TextConfiguration.cs deleted file mode 100644 index 2ada362..0000000 --- a/Calligraphics/Components/TextConfiguration.cs +++ /dev/null @@ -1,139 +0,0 @@ -using Latios.Calligraphics; -using Unity.Collections; -using UnityEngine; -using UnityEngine.TextCore.Text; - -public struct TextConfiguration -{ - /// - /// m_htmlTag is a scratchpad for storing substrings prior to parsing. - /// Would not be needed if CalliString.SubString() would work...does not for some reason - /// (error that underlying Callibyte buffer is null) - /// - public FixedString128Bytes m_htmlTag; - - //metrics - public float m_fontScaleMultiplier; // Used for handling of superscript and subscript. - public float m_currentFontSize; - public FixedStack64Bytes m_sizeStack; - - public FontStyles m_FontStyleInternal; - public FontWeight m_FontWeightInternal; - public Calli_FontStyleStack m_fontStyleStack; - public FixedStack64Bytes m_FontWeightStack; - - public HorizontalAlignmentOptions m_lineJustification; - public FixedStack64Bytes m_lineJustificationStack; - - public float m_baselineOffset; - public FixedStack64Bytes m_baselineOffsetStack; - - public Color32 m_htmlColor; - public Color32 m_underlineColor; - public Color32 m_strikethroughColor; - - public FixedStack64Bytes m_colorStack; - public FixedStack64Bytes m_strikethroughColorStack; - public FixedStack64Bytes m_underlineColorStack; - - public short m_ItalicAngle; - public FixedStack64Bytes m_ItalicAngleStack; - - public float m_lineOffset; - public float m_lineHeight; - - public float m_cSpacing; - public float m_monoSpacing; - public float m_xAdvance; - - public float tag_LineIndent; - public float tag_Indent; - public FixedStack64Bytes m_indentStack; - public bool tag_NoParsing; - - public float m_marginWidth; - public float m_marginHeight; - public float m_marginLeft; - public float m_marginRight; - public float m_width; - - public bool m_isNonBreakingSpace; - - public bool m_isParsingText; - - public Matrix4x4 m_FXMatrix; - public bool m_isFXMatrixSet; - - public FixedStack64Bytes m_HighlightStateStack; - public int m_characterCount; - - - public TextConfiguration(TextBaseConfiguration textBaseConfiguration) - { - m_htmlTag = new FixedString128Bytes(); - - m_fontScaleMultiplier = 1; - m_currentFontSize = textBaseConfiguration.fontSize; - m_sizeStack = new FixedStack64Bytes(); - m_sizeStack.Add(m_currentFontSize); - - - m_FontStyleInternal = textBaseConfiguration.fontStyle; - m_FontWeightInternal = (m_FontStyleInternal & FontStyles.Bold) == FontStyles.Bold ? FontWeight.Bold : textBaseConfiguration.fontWeight; - m_FontWeightStack = new FixedStack64Bytes(); - m_FontWeightStack.Add(m_FontWeightInternal); - m_fontStyleStack = new Calli_FontStyleStack(); - - m_lineJustification = textBaseConfiguration.lineJustification; - m_lineJustificationStack = new FixedStack64Bytes(); - m_lineJustificationStack.Add(m_lineJustification); - - m_baselineOffset = 0; - m_baselineOffsetStack = new FixedStack64Bytes(); - m_baselineOffsetStack.Add(0); - - m_htmlColor = textBaseConfiguration.color; - m_underlineColor = Color.white; - m_strikethroughColor = Color.white; - - m_colorStack = new FixedStack64Bytes(); - m_colorStack.Add(m_htmlColor); - m_underlineColorStack = new FixedStack64Bytes(); - m_underlineColorStack.Add(m_htmlColor); - m_strikethroughColorStack = new FixedStack64Bytes(); - m_strikethroughColorStack.Add(m_htmlColor); - - m_ItalicAngle = 0; - m_ItalicAngleStack = new FixedStack64Bytes(); - - m_lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). - m_lineHeight = float.MinValue; //TMP_Math.FLOAT_UNSET -->is there a better way to do this? - - m_cSpacing = 0; // Amount of space added between characters as a result of the use of the tag. - m_monoSpacing = 0; - m_xAdvance = 0; // Used to track the position of each character. - - tag_LineIndent = 0; // Used for indentation of text. - tag_Indent = 0; - m_indentStack = new FixedStack64Bytes(); - m_indentStack.Add(tag_Indent); - tag_NoParsing = false; - - m_marginWidth = 0; - m_marginHeight = 0; - m_marginLeft = 0; - m_marginRight = 0; - m_width = -1; - - m_isNonBreakingSpace = false; - - m_isParsingText = false; - m_FXMatrix = Matrix4x4.identity; - m_isFXMatrixSet = false; - - m_HighlightStateStack = new FixedStack64Bytes(); - - m_characterCount = 0; // Total characters in the CalliString - } -} - diff --git a/Calligraphics/Components/TextRenderingComponents.cs b/Calligraphics/Components/TextRenderingComponents.cs index 54b133f..87ba245 100644 --- a/Calligraphics/Components/TextRenderingComponents.cs +++ b/Calligraphics/Components/TextRenderingComponents.cs @@ -45,6 +45,13 @@ public struct TextShaderIndex : IComponentData public uint glyphCount; } + // Only present if there are child fonts + [MaterialProperty("_latiosTextGlyphMaskBase")] + public struct TextMaterialMaskShaderIndex : IComponentData + { + public uint firstMaskIndex; + } + public struct RenderGlyph : IBufferElementData { public float2 blPosition; @@ -132,9 +139,47 @@ public enum Flags : byte public Flags flags; } + /// + /// An additional rendered text entity containing a different font and material. + /// The additional entity shares the RenderGlyph buffer, and uses a mask to identify + /// the glyphs to render. + /// + [InternalBufferCapacity(0)] + public struct AdditionalFontMaterialEntity : IBufferElementData + { + public EntityWith entity; + } + + /// + /// A per-glyph index into the font and material that should be used to render it. + /// Index 0 is this entity. Index 1 is the first entity in AdditionalFontMaterialEntity buffer. + /// + [InternalBufferCapacity(0)] + public struct FontMaterialSelectorForGlyph : IBufferElementData + { + public byte fontMaterialIndex; + } + + /// + /// A buffer that should be present on every entity posessing or referenced by the + /// AdditionalFontMaterialEntity buffer. This buffer contains the GPU mask representation, + /// and its contents will automatically be maintained by the Calligraphics rendering backend. + /// Public so that you can add/remove it or maybe even read it (if you are brave). + /// + [InternalBufferCapacity(0)] + public struct RenderGlyphMask : IBufferElementData + { + public uint lowerOffsetUpperMask16; + } + internal struct GlyphCountThisFrame : IComponentData { public uint glyphCount; } + + internal struct MaskCountThisFrame : IComponentData + { + public uint maskCount; + } } diff --git a/Calligraphics/Editor/ExtraShaderGraphNodes/LatiosTextNode.cs b/Calligraphics/Editor/ExtraShaderGraphNodes/LatiosTextNode.cs index e08ab56..ff95089 100644 --- a/Calligraphics/Editor/ExtraShaderGraphNodes/LatiosTextNode.cs +++ b/Calligraphics/Editor/ExtraShaderGraphNodes/LatiosTextNode.cs @@ -96,6 +96,15 @@ public override void CollectShaderProperties(PropertyCollector properties, Gener hidden = true, value = Vector2.zero }); + properties.AddShaderProperty(new Vector1ShaderProperty() + { + displayName = $"Text Material Mask Index", + overrideReferenceName = "_latiosTextGlyphMaskBase", + overrideHLSLDeclaration = true, + hlslDeclarationOverride = HLSLDeclaration.HybridPerInstance, + hidden = true, + value = 0f + }); base.CollectShaderProperties(properties, generationMode); } @@ -113,7 +122,8 @@ public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMo if (generationMode == GenerationMode.ForReals) { sb.AppendLine("uint2 baseIndex = asuint(UNITY_ACCESS_HYBRID_INSTANCED_PROP(_latiosTextGlyphBase, float2));"); - sb.AppendLine("GlyphVertex glyph = sampleGlyph(IN.VertexID, baseIndex.x, baseIndex.y);"); + sb.AppendLine("uint maskIndex = asuint(UNITY_ACCESS_HYBRID_INSTANCED_PROP(_latiosTextGlyphMaskBase, float));"); // We rely on the default from AddShaderProperty + sb.AppendLine("GlyphVertex glyph = sampleGlyph(IN.VertexID, baseIndex.x, baseIndex.y, maskIndex);"); sb.AppendLine("{0} = glyph.position;", GetVariableNameForSlot(kPositionOutputSlotId)); sb.AppendLine("{0} = glyph.normal;", GetVariableNameForSlot(kNormalOutputSlotId)); sb.AppendLine("{0} = glyph.tangent;", GetVariableNameForSlot(kTangentOutputSlotId)); diff --git a/Calligraphics/Components/FixedStack64Bytes.cs b/Calligraphics/Internal/FixedStack512Bytes.cs similarity index 88% rename from Calligraphics/Components/FixedStack64Bytes.cs rename to Calligraphics/Internal/FixedStack512Bytes.cs index be8f100..bd17706 100644 --- a/Calligraphics/Components/FixedStack64Bytes.cs +++ b/Calligraphics/Internal/FixedStack512Bytes.cs @@ -2,10 +2,9 @@ namespace Latios.Calligraphics { - - public struct FixedStack64Bytes where T : unmanaged + internal struct FixedStack512Bytes where T : unmanaged { - FixedList64Bytes m_buffer; + FixedList512Bytes m_buffer; public bool IsEmpty => m_buffer.IsEmpty; public void Add(in T item) => m_buffer.Add(in item); public T Pop() diff --git a/Calligraphics/Components/FixedStack64Bytes.cs.meta b/Calligraphics/Internal/FixedStack512Bytes.cs.meta similarity index 100% rename from Calligraphics/Components/FixedStack64Bytes.cs.meta rename to Calligraphics/Internal/FixedStack512Bytes.cs.meta diff --git a/Calligraphics/Internal/FontMaterialSet.cs b/Calligraphics/Internal/FontMaterialSet.cs new file mode 100644 index 0000000..b4be09c --- /dev/null +++ b/Calligraphics/Internal/FontMaterialSet.cs @@ -0,0 +1,69 @@ +using Latios.Calligraphics.Rendering; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Entities; +using Unity.Mathematics; + +namespace Latios.Calligraphics +{ + internal unsafe struct FontMaterialSet + { + FixedList4096Bytes m_fontMaterialArray; + FixedList512Bytes m_fontToEntityIndexArray; + DynamicBuffer m_selectorBuffer; + bool m_hasMultipleFonts; + + public ref FontBlob this[int index] => ref m_fontMaterialArray[index].font; + + public void WriteFontMaterialIndexForGlyph(int index) + { + if (!m_hasMultipleFonts) + return; + var remap = m_fontToEntityIndexArray[index]; + m_selectorBuffer.Add(new FontMaterialSelectorForGlyph { fontMaterialIndex = remap }); + } + + public void Initialize(BlobAssetReference singleFont) + { + m_hasMultipleFonts = false; + m_fontMaterialArray.Clear(); + m_fontMaterialArray.Add(new FontMaterial(singleFont)); + } + + public void Initialize(BlobAssetReference baseFont, + DynamicBuffer selectorBuffer, + DynamicBuffer entities, + ref ComponentLookup blobLookup) + { + Initialize(baseFont); + m_selectorBuffer = selectorBuffer; + m_selectorBuffer.Clear(); + m_hasMultipleFonts = true; + m_fontToEntityIndexArray.Clear(); + for (int i = 0; i < entities.Length; i++) + { + if (blobLookup.TryGetComponent(entities[i].entity, out var blobRef)) + { + if (blobRef.blob.IsCreated) + { + m_fontMaterialArray.Add(new FontMaterial(blobRef.blob)); + m_fontToEntityIndexArray.Add((byte)i); + } + } + } + } + + unsafe struct FontMaterial + { + FontBlob* m_fontBlobPtr; + + public ref FontBlob font => ref *m_fontBlobPtr; + + public FontMaterial(BlobAssetReference blobRef) + { + m_fontBlobPtr = (FontBlob*)blobRef.GetUnsafePtr(); + } + } + } +} + diff --git a/Calligraphics/Internal/FontMaterialSet.cs.meta b/Calligraphics/Internal/FontMaterialSet.cs.meta new file mode 100644 index 0000000..68c5385 --- /dev/null +++ b/Calligraphics/Internal/FontMaterialSet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7e2880bddb2695c4a9a5d556678802ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Calligraphics/Internal/GlyphGeneration.cs b/Calligraphics/Internal/GlyphGeneration.cs index 286dc41..cb2eeb8 100644 --- a/Calligraphics/Internal/GlyphGeneration.cs +++ b/Calligraphics/Internal/GlyphGeneration.cs @@ -1,6 +1,5 @@ using Latios.Calligraphics.Rendering; using Latios.Calligraphics.RichText; -using Latios.Calligraphics.RichText.Parsing; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; @@ -8,32 +7,33 @@ namespace Latios.Calligraphics { - public static class GlyphGeneration + internal static class GlyphGeneration { internal static unsafe void CreateRenderGlyphs(ref DynamicBuffer renderGlyphs, ref GlyphMappingWriter mappingWriter, - ref FontBlob font, - in DynamicBuffer calliBytes, + ref FontMaterialSet fontMaterialSet, + in DynamicBuffer calliBytes, in TextBaseConfiguration baseConfiguration) { renderGlyphs.Clear(); //initialized textConfiguration which stores all fields that are modified by RichText Tags - var richTextAttributes = new FixedList512Bytes(); - var textConfiguration = new TextConfiguration(baseConfiguration); + var richTextTagIdentifiers = new FixedList512Bytes(); + var textConfiguration = new TextConfiguration(baseConfiguration); - float2 cumulativeOffset = new float2(); // Tracks text progression and word wrap - float2 adjustmentOffset = new float2(); //Tracks placement adjustments - int characterCount = 0; - int lastWordStartCharacterGlyphIndex = 0; + float2 cumulativeOffset = new float2(); // Tracks text progression and word wrap + float2 adjustmentOffset = new float2(); //Tracks placement adjustments + int characterCount = 0; + int lastWordStartCharacterGlyphIndex = 0; FixedList512Bytes characterGlyphIndicesWithPreceedingSpacesInLine = default; - int accumulatedSpaces = 0; - int startOfLineGlyphIndex = 0; - bool prevWasSpace = false; - int lineCount = 0; - bool isLineStart = true; - - var calliString = new CalliString(calliBytes); + int accumulatedSpaces = 0; + int startOfLineGlyphIndex = 0; + bool prevWasSpace = false; + int lineCount = 0; + bool isLineStart = true; + ref FontBlob font = ref fontMaterialSet[0]; + + var calliString = new CalliString(calliBytes); var characterEnumerator = calliString.GetEnumerator(); while (characterEnumerator.MoveNext()) { @@ -46,7 +46,7 @@ internal static unsafe void CreateRenderGlyphs(ref DynamicBuffer re { textConfiguration.m_isParsingText = true; // Check if Tag is valid. If valid, skip to the end of the validated tag. - if (RichTextParser.ValidateHtmlTag(in calliString, ref characterEnumerator, ref font, in baseConfiguration, ref textConfiguration, ref richTextAttributes)) + if (RichTextParser.ValidateHtmlTag(in calliString, ref characterEnumerator, ref font, in baseConfiguration, ref textConfiguration, ref richTextTagIdentifiers)) { // Continue to next character continue; @@ -67,12 +67,12 @@ internal static unsafe void CreateRenderGlyphs(ref DynamicBuffer re //Handle line break if (unicode.value == 10) //Line feed { - var glyphsLine = renderGlyphs.AsNativeArray().GetSubArray(startOfLineGlyphIndex, renderGlyphs.Length - startOfLineGlyphIndex); + var glyphsLine = renderGlyphs.AsNativeArray().GetSubArray(startOfLineGlyphIndex, renderGlyphs.Length - startOfLineGlyphIndex); var overrideMode = textConfiguration.m_lineJustification; if ((overrideMode) == HorizontalAlignmentOptions.Justified) { // Don't perform justified spacing for the last line in the paragraph. - overrideMode |= HorizontalAlignmentOptions.Left; + overrideMode = HorizontalAlignmentOptions.Left; } ApplyHorizontalAlignmentToGlyphs(ref glyphsLine, ref characterGlyphIndicesWithPreceedingSpacesInLine, @@ -81,44 +81,45 @@ internal static unsafe void CreateRenderGlyphs(ref DynamicBuffer re lineCount++; isLineStart = true; - cumulativeOffset.x = 0; + cumulativeOffset.x = 0; cumulativeOffset.y -= font.lineHeight * font.baseScale * baseConfiguration.fontSize; continue; } - if (font.characters.TryGetGlyph(math.asuint(unicode.value), out var glyphIndex)) + if (font.TryGetGlyphIndex(math.asuint(unicode.value), out var glyphIndex)) { - ref var glyphBlob = ref font.characters[glyphIndex]; - var renderGlyph = new RenderGlyph + ref var glyphBlob = ref font.characters[glyphIndex]; + var renderGlyph = new RenderGlyph { blPosition = GetBottomLeftPosition(ref font, ref glyphBlob, textConfiguration.m_currentFontSize, adjustmentOffset + cumulativeOffset, false, false), trPosition = GetTopRightPosition(ref font, ref glyphBlob, textConfiguration.m_currentFontSize, adjustmentOffset + cumulativeOffset, false, false), - blUVA = glyphBlob.bottomLeftUV, - trUVA = glyphBlob.topRightUV, - blUVB = glyphBlob.bottomLeftUV2, - tlUVB = glyphBlob.topLeftUV2, - trUVB = glyphBlob.topRightUV2, - brUVB = glyphBlob.bottomRightUV2, + blUVA = glyphBlob.bottomLeftUV, + trUVA = glyphBlob.topRightUV, + blUVB = glyphBlob.bottomLeftUV2, + tlUVB = glyphBlob.topLeftUV2, + trUVB = glyphBlob.topRightUV2, + brUVB = glyphBlob.bottomRightUV2, blColor = textConfiguration.m_htmlColor, tlColor = textConfiguration.m_htmlColor, trColor = textConfiguration.m_htmlColor, brColor = textConfiguration.m_htmlColor, unicode = glyphBlob.unicode, - scale = textConfiguration.m_currentFontSize, + scale = textConfiguration.m_currentFontSize, }; var baseScale = font.baseScale; renderGlyphs.Add(renderGlyph); + fontMaterialSet.WriteFontMaterialIndexForGlyph(0); mappingWriter.AddCharNoTags(characterCount - 1, true); mappingWriter.AddCharWithTags(characterEnumerator.CurrentCharIndex, true); mappingWriter.AddBytes(characterEnumerator.CurrentByteIndex, unicode.LengthInUtf8Bytes(), true); adjustmentOffset = float2.zero; - var xAdvanceAdjustment = 0f; - var yAdvanceAdjustment = 0f; + var xAdvanceAdjustment = 0f; + var yAdvanceAdjustment = 0f; var xPlacementAdjustment = 0f; var yPlacementAdjustment = 0f; @@ -132,8 +133,8 @@ internal static unsafe void CreateRenderGlyphs(ref DynamicBuffer re if (glyphAdjustment.secondAdjustment.glyphUnicode == math.asuint(peekChar.value)) { - xAdvanceAdjustment = glyphAdjustment.firstAdjustment.xAdvance * renderGlyph.scale * baseScale; - yAdvanceAdjustment = glyphAdjustment.firstAdjustment.yAdvance * renderGlyph.scale * baseScale; + xAdvanceAdjustment = glyphAdjustment.firstAdjustment.xAdvance * renderGlyph.scale * baseScale; + yAdvanceAdjustment = glyphAdjustment.firstAdjustment.yAdvance * renderGlyph.scale * baseScale; xPlacementAdjustment = glyphAdjustment.firstAdjustment.xPlacement * renderGlyph.scale * baseScale; yPlacementAdjustment = glyphAdjustment.firstAdjustment.yPlacement * renderGlyph.scale * baseScale; break; @@ -255,8 +256,8 @@ static float2 GetBottomLeftPosition(ref FontBlob font, ref GlyphBlob glyph, floa if (isItalics) { // Shift Top vertices forward by half (Shear Value * height of character) and Bottom vertices back by same amount. - float shear = font.italicsStyleSlant * 0.01f; - float midPoint = ((font.capLine - font.baseLine) / 2) * font.baseScale * scale; + float shear = font.italicsStyleSlant * 0.01f; + float midPoint = ((font.capLine - font.baseLine) / 2) * font.baseScale * scale; float2 bottomShear = new float2(shear * (((glyph.horizontalBearingY - glyph.height - font.materialPadding - midPoint)) * font.baseScale * scale), 0); bottomLeft += bottomShear; @@ -286,8 +287,8 @@ static float2 GetTopRightPosition(ref FontBlob font, ref GlyphBlob glyph, float if (isItalics) { // Shift Top vertices forward by half (Shear Value * height of character) and Bottom vertices back by same amount. - float shear = font.italicsStyleSlant * 0.01f; - float midPoint = ((font.capLine - font.baseLine) / 2) * font.baseScale * scale; + float shear = font.italicsStyleSlant * 0.01f; + float midPoint = ((font.capLine - font.baseLine) / 2) * font.baseScale * scale; float2 topShear = new float2(shear * ((glyph.horizontalBearingY + font.materialPadding - midPoint) * font.baseScale * scale), 0); topRight += topShear; @@ -297,7 +298,7 @@ static float2 GetTopRightPosition(ref FontBlob font, ref GlyphBlob glyph, float } static unsafe void ApplyHorizontalAlignmentToGlyphs(ref NativeArray glyphs, - ref FixedList512Bytes characterGlyphIndicesWithPreceedingSpacesInLine, + ref FixedList512Bytes characterGlyphIndicesWithPreceedingSpacesInLine, float width, HorizontalAlignmentOptions alignMode) { @@ -328,9 +329,9 @@ static unsafe void ApplyHorizontalAlignmentToGlyphs(ref NativeArray } else // Justified { - float nudgePerSpace = (width - glyphsPtr[glyphs.Length - 1].trPosition.x) / characterGlyphIndicesWithPreceedingSpacesInLine.Length; + float nudgePerSpace = (width - glyphsPtr[glyphs.Length - 1].trPosition.x) / characterGlyphIndicesWithPreceedingSpacesInLine.Length; float accumulatedOffset = 0f; - int indexInIndices = 0; + int indexInIndices = 0; for (int i = 0; i < glyphs.Length; i++) { while (indexInIndices < characterGlyphIndicesWithPreceedingSpacesInLine.Length && @@ -347,15 +348,19 @@ static unsafe void ApplyHorizontalAlignmentToGlyphs(ref NativeArray characterGlyphIndicesWithPreceedingSpacesInLine.Clear(); } - static unsafe void ApplyVerticalAlignmentToGlyphs(ref DynamicBuffer glyphs, int fullLineCount, VerticalAlignmentOptions alignMode, ref FontBlob font, float fontSize) + static unsafe void ApplyVerticalAlignmentToGlyphs(ref DynamicBuffer glyphs, + int fullLineCount, + VerticalAlignmentOptions alignMode, + ref FontBlob font, + float fontSize) { var glyphsPtr = (RenderGlyph*)glyphs.GetUnsafePtr(); if ((alignMode) == VerticalAlignmentOptions.Top) { // Positions were calculated relative to the baseline. // Shift everything down so that y = 0 is on the ascent line. - var offset = font.ascentLine - font.baseLine; - offset *= font.baseScale * fontSize; + var offset = font.ascentLine - font.baseLine; + offset *= font.baseScale * fontSize; for (int i = 0; i < glyphs.Length; i++) { glyphsPtr[i].blPosition.y -= offset; @@ -365,8 +370,8 @@ static unsafe void ApplyVerticalAlignmentToGlyphs(ref DynamicBuffer else if ((alignMode) == VerticalAlignmentOptions.Middle) { float newlineSpace = (fullLineCount - 1) * font.lineHeight * font.baseScale * fontSize; - float fullHeight = newlineSpace + (font.ascentLine - font.baseLine) * font.baseScale * fontSize; - var offset = fullHeight / 2f - font.ascentLine * font.baseScale * fontSize; + float fullHeight = newlineSpace + (font.ascentLine - font.baseLine) * font.baseScale * fontSize; + var offset = fullHeight / 2f - font.ascentLine * font.baseScale * fontSize; for (int i = 0; i < glyphs.Length; i++) { glyphsPtr[i].blPosition.y += offset; @@ -377,7 +382,7 @@ static unsafe void ApplyVerticalAlignmentToGlyphs(ref DynamicBuffer { // Todo: Should we just leave the y = 0 on the baseline instead of the descent line? float newlineSpace = (fullLineCount - 1) * font.lineHeight * font.baseScale * fontSize; - var offset = newlineSpace + (font.baseLine - font.descentLine) * font.baseScale * fontSize; + var offset = newlineSpace + (font.baseLine - font.descentLine) * font.baseScale * fontSize; for (int i = 0; i < glyphs.Length; i++) { glyphsPtr[i].blPosition.y += offset; diff --git a/Calligraphics/Internal/RichText/CalliExtensions.cs b/Calligraphics/Internal/RichText/CalliExtensions.cs index bf86dea..9550b63 100644 --- a/Calligraphics/Internal/RichText/CalliExtensions.cs +++ b/Calligraphics/Internal/RichText/CalliExtensions.cs @@ -1,9 +1,9 @@ using Unity.Collections; using UnityEngine; -namespace Latios.Calligraphics.Extensions +namespace Latios.Calligraphics { - public static class Calli_Ext + internal static class CalligraphicsInternalExtensions { public static void GetSubString(this in CalliString calliString, ref FixedString128Bytes htmlTag, int startIndex, int length) { diff --git a/Calligraphics/Internal/RichText/Calli_FontStyleStack.cs b/Calligraphics/Internal/RichText/FontStyleStack.cs similarity index 91% rename from Calligraphics/Internal/RichText/Calli_FontStyleStack.cs rename to Calligraphics/Internal/RichText/FontStyleStack.cs index 55eba2e..d18ded4 100644 --- a/Calligraphics/Internal/RichText/Calli_FontStyleStack.cs +++ b/Calligraphics/Internal/RichText/FontStyleStack.cs @@ -1,11 +1,11 @@ using UnityEngine.TextCore.Text; -namespace Latios.Calligraphics +namespace Latios.Calligraphics.RichText { /// /// Structure used to track basic XML tags which are binary (on / off) /// - public struct Calli_FontStyleStack + internal struct FontStyleStack { public byte bold; public byte italic; @@ -23,16 +23,16 @@ public struct Calli_FontStyleStack /// public void Clear() { - bold = 0; - italic = 0; - underline = 0; + bold = 0; + italic = 0; + underline = 0; strikethrough = 0; - highlight = 0; - superscript = 0; - subscript = 0; - uppercase = 0; - lowercase = 0; - smallcaps = 0; + highlight = 0; + superscript = 0; + subscript = 0; + uppercase = 0; + lowercase = 0; + smallcaps = 0; } public byte Add(FontStyles style) diff --git a/Calligraphics/Internal/RichText/Calli_FontStyleStack.cs.meta b/Calligraphics/Internal/RichText/FontStyleStack.cs.meta similarity index 100% rename from Calligraphics/Internal/RichText/Calli_FontStyleStack.cs.meta rename to Calligraphics/Internal/RichText/FontStyleStack.cs.meta diff --git a/Calligraphics/Internal/RichText/HighLightState.cs b/Calligraphics/Internal/RichText/HighLightState.cs index de26b00..12ac16e 100644 --- a/Calligraphics/Internal/RichText/HighLightState.cs +++ b/Calligraphics/Internal/RichText/HighLightState.cs @@ -1,16 +1,15 @@ -using Latios.Calligraphics.Extensions; using UnityEngine; -namespace Latios.Calligraphics +namespace Latios.Calligraphics.RichText { - public struct HighlightState + internal struct HighlightState { - public Color32 color; - public Calli_Offset padding; + public Color32 color; + public RectOffsets padding; - public HighlightState(Color32 color, Calli_Offset padding) + public HighlightState(Color32 color, RectOffsets padding) { - this.color = color; + this.color = color; this.padding = padding; } diff --git a/Calligraphics/Internal/RichText/CalliOffset.cs b/Calligraphics/Internal/RichText/RectOffsets.cs similarity index 62% rename from Calligraphics/Internal/RichText/CalliOffset.cs rename to Calligraphics/Internal/RichText/RectOffsets.cs index c131e12..868c87c 100644 --- a/Calligraphics/Internal/RichText/CalliOffset.cs +++ b/Calligraphics/Internal/RichText/RectOffsets.cs @@ -1,6 +1,6 @@ -namespace Latios.Calligraphics +namespace Latios.Calligraphics.RichText { - public struct Calli_Offset + internal struct RectOffsets { public float left { get { return m_Left; } set { m_Left = value; } } @@ -17,7 +17,7 @@ public struct Calli_Offset /// /// /// - public static Calli_Offset zero { get { return k_ZeroOffset; } } + public static RectOffsets zero { get { return k_ZeroOffset; } } // ============================================= // Private backing fields for public properties. @@ -28,7 +28,7 @@ public struct Calli_Offset float m_Top; float m_Bottom; - static readonly Calli_Offset k_ZeroOffset = new Calli_Offset(0F, 0F, 0F, 0F); + static readonly RectOffsets k_ZeroOffset = new RectOffsets(0F, 0F, 0F, 0F); /// /// @@ -37,11 +37,11 @@ public struct Calli_Offset /// /// /// - public Calli_Offset(float left, float right, float top, float bottom) + public RectOffsets(float left, float right, float top, float bottom) { - m_Left = left; - m_Right = right; - m_Top = top; + m_Left = left; + m_Right = right; + m_Top = top; m_Bottom = bottom; } @@ -50,30 +50,30 @@ public Calli_Offset(float left, float right, float top, float bottom) /// /// /// - public Calli_Offset(float horizontal, float vertical) + public RectOffsets(float horizontal, float vertical) { - m_Left = horizontal; - m_Right = horizontal; - m_Top = vertical; + m_Left = horizontal; + m_Right = horizontal; + m_Top = vertical; m_Bottom = vertical; } - public static bool operator ==(Calli_Offset lhs, Calli_Offset rhs) + public static bool operator ==(RectOffsets lhs, RectOffsets rhs) { return lhs.m_Left == rhs.m_Left && - lhs.m_Right == rhs.m_Right && - lhs.m_Top == rhs.m_Top && - lhs.m_Bottom == rhs.m_Bottom; + lhs.m_Right == rhs.m_Right && + lhs.m_Top == rhs.m_Top && + lhs.m_Bottom == rhs.m_Bottom; } - public static bool operator !=(Calli_Offset lhs, Calli_Offset rhs) + public static bool operator !=(RectOffsets lhs, RectOffsets rhs) { return !(lhs == rhs); } - public static Calli_Offset operator *(Calli_Offset a, float b) + public static RectOffsets operator *(RectOffsets a, float b) { - return new Calli_Offset(a.m_Left * b, a.m_Right * b, a.m_Top * b, a.m_Bottom * b); + return new RectOffsets(a.m_Left * b, a.m_Right * b, a.m_Top * b, a.m_Bottom * b); } public override int GetHashCode() @@ -86,7 +86,7 @@ public override bool Equals(object obj) return base.Equals(obj); } - public bool Equals(Calli_Offset other) + public bool Equals(RectOffsets other) { return base.Equals(other); } diff --git a/Calligraphics/Internal/RichText/CalliOffset.cs.meta b/Calligraphics/Internal/RichText/RectOffsets.cs.meta similarity index 100% rename from Calligraphics/Internal/RichText/CalliOffset.cs.meta rename to Calligraphics/Internal/RichText/RectOffsets.cs.meta diff --git a/Calligraphics/Internal/RichText/RichTextAttribute.cs b/Calligraphics/Internal/RichText/RichTextAttribute.cs deleted file mode 100644 index bfe72ee..0000000 --- a/Calligraphics/Internal/RichText/RichTextAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Latios.Calligraphics.RichText -{ - public struct RichTextAttribute - { - public RichTextTagType tagType; - public int nameHashCode; - public int valueHashCode; - public TagValueType valueType; - public int valueStartIndex; //bytes position, not char! - public int valueLength; //byte length, not char! - public TagUnitType unitType; - public static RichTextAttribute Empty => new RichTextAttribute - { - tagType = RichTextTagType.INVALID, - nameHashCode = 0, - valueHashCode = 0, - valueStartIndex = 0, - valueLength = 0, - valueType = TagValueType.None, - unitType = TagUnitType.Pixels, - }; - } -} - diff --git a/Calligraphics/Internal/RichText/RichTextParser.cs b/Calligraphics/Internal/RichText/RichTextParser.cs index fc74467..c53239d 100644 --- a/Calligraphics/Internal/RichText/RichTextParser.cs +++ b/Calligraphics/Internal/RichText/RichTextParser.cs @@ -1,9 +1,8 @@ -using Latios.Calligraphics.Extensions; using Unity.Collections; using UnityEngine; using UnityEngine.TextCore.Text; -namespace Latios.Calligraphics.RichText.Parsing +namespace Latios.Calligraphics.RichText { internal static class RichTextParser { @@ -17,72 +16,72 @@ internal static class RichTextParser internal static bool ValidateHtmlTag( in CalliString calliString, ref CalliString.Enumerator enumerator, - ref FontBlob currenFont,//will need to be replaced with List of FontAssets to allow switching + ref FontBlob currenFont, //will need to be replaced with List of FontAssets to allow switching in TextBaseConfiguration baseConfiguration, ref TextConfiguration textConfiguration, - ref FixedList512Bytes richTextAttributes) //this is just a cache to avoid allocation + ref FixedList512Bytes richTextTagIndentifiers) //this is just a cache to avoid allocation { - richTextAttributes.Clear(); - int tagCharCount = 0; - int tagByteCount = 0; - int startByteIndex = enumerator.CurrentByteIndex; - ParserState attributeFlag = ParserState.Zero; - - int attributeIndex = richTextAttributes.Length; - richTextAttributes.Add(RichTextAttribute.Empty); - ref var currentAttribute = ref richTextAttributes.ElementAt(attributeIndex); - TagValueType tagValueType = currentAttribute.valueType = TagValueType.None; - TagUnitType tagUnitType = currentAttribute.unitType = TagUnitType.Pixels; - - bool isTagSet = false; + richTextTagIndentifiers.Clear(); + int tagCharCount = 0; + int tagByteCount = 0; + int startByteIndex = enumerator.CurrentByteIndex; + ParserState tagIndentifierFlag = ParserState.Zero; + + int tagIndentifierIndex = richTextTagIndentifiers.Length; + richTextTagIndentifiers.Add(RichTextTagIdentifier.Empty); + ref var currentTagIndentifier = ref richTextTagIndentifiers.ElementAt(tagIndentifierIndex); + TagValueType tagValueType = currentTagIndentifier.valueType = TagValueType.None; + TagUnitType tagUnitType = currentTagIndentifier.unitType = TagUnitType.Pixels; + + bool isTagSet = false; bool isValidHtmlTag = false; - Unicode.Rune unicode = Unicode.BadRune; - int charCount = 0; + Unicode.Rune unicode = Unicode.BadRune; + int charCount = 0; while (enumerator.MoveNext() && (unicode = enumerator.Current) != Unicode.BadRune && unicode != '<') { - if (unicode == '>') // ASCII Code of End HTML tag '>' + if (unicode == '>') // ASCII Code of End HTML tag '>' { isValidHtmlTag = true; break; } - int byteCount = unicode.LengthInUtf8Bytes(); - tagCharCount += 1; - tagByteCount += byteCount; + int byteCount = unicode.LengthInUtf8Bytes(); + tagCharCount += 1; + tagByteCount += byteCount; - if (attributeFlag == ParserState.One) + if (tagIndentifierFlag == ParserState.One) { if (tagValueType == TagValueType.None) { - // Check for attribute type + // Check for tagIndentifier type if (unicode == '+' || unicode == '-' || unicode == '.' || Unicode.Rune.IsDigit(unicode)) { - tagUnitType = TagUnitType.Pixels; - tagValueType = currentAttribute.valueType = TagValueType.NumericalValue; - currentAttribute.valueStartIndex = enumerator.CurrentByteIndex - unicode.LengthInUtf8Bytes(); - currentAttribute.valueLength += byteCount; + tagUnitType = TagUnitType.Pixels; + tagValueType = currentTagIndentifier.valueType = TagValueType.NumericalValue; + currentTagIndentifier.valueStartIndex = enumerator.CurrentByteIndex - unicode.LengthInUtf8Bytes(); + currentTagIndentifier.valueLength += byteCount; } else if (unicode == '#') { - tagUnitType = TagUnitType.Pixels; - tagValueType = currentAttribute.valueType = TagValueType.ColorValue; - currentAttribute.valueStartIndex = enumerator.CurrentByteIndex - unicode.LengthInUtf8Bytes(); - currentAttribute.valueLength += byteCount; + tagUnitType = TagUnitType.Pixels; + tagValueType = currentTagIndentifier.valueType = TagValueType.ColorValue; + currentTagIndentifier.valueStartIndex = enumerator.CurrentByteIndex - unicode.LengthInUtf8Bytes(); + currentTagIndentifier.valueLength += byteCount; } else if (unicode == '"') { - tagUnitType = TagUnitType.Pixels; - tagValueType = currentAttribute.valueType = TagValueType.StringValue; - currentAttribute.valueStartIndex = enumerator.CurrentByteIndex; + tagUnitType = TagUnitType.Pixels; + tagValueType = currentTagIndentifier.valueType = TagValueType.StringValue; + currentTagIndentifier.valueStartIndex = enumerator.CurrentByteIndex; } else { - tagUnitType = TagUnitType.Pixels; - tagValueType = currentAttribute.valueType = TagValueType.StringValue; - currentAttribute.valueStartIndex = enumerator.CurrentByteIndex - unicode.LengthInUtf8Bytes(); - currentAttribute.valueHashCode = (currentAttribute.valueHashCode << 5) + currentAttribute.valueHashCode ^ unicode.value; - currentAttribute.valueLength += byteCount; + tagUnitType = TagUnitType.Pixels; + tagValueType = currentTagIndentifier.valueType = TagValueType.StringValue; + currentTagIndentifier.valueStartIndex = enumerator.CurrentByteIndex - unicode.LengthInUtf8Bytes(); + currentTagIndentifier.valueHashCode = (currentTagIndentifier.valueHashCode << 5) + currentTagIndentifier.valueHashCode ^ unicode.value; + currentTagIndentifier.valueLength += byteCount; } } else @@ -92,45 +91,45 @@ internal static bool ValidateHtmlTag( // Check for termination of numerical value. if (unicode == 'p' || unicode == 'e' || unicode == '%' || unicode == ' ') { - attributeFlag = ParserState.Two; - tagValueType = TagValueType.None; + tagIndentifierFlag = ParserState.Two; + tagValueType = TagValueType.None; switch (unicode.value) { case 'e': - currentAttribute.unitType = tagUnitType = TagUnitType.FontUnits; + currentTagIndentifier.unitType = tagUnitType = TagUnitType.FontUnits; break; case '%': - currentAttribute.unitType = tagUnitType = TagUnitType.Percentage; + currentTagIndentifier.unitType = tagUnitType = TagUnitType.Percentage; break; default: - currentAttribute.unitType = tagUnitType = TagUnitType.Pixels; + currentTagIndentifier.unitType = tagUnitType = TagUnitType.Pixels; break; } - attributeIndex += 1; - richTextAttributes.Add(RichTextAttribute.Empty); - currentAttribute = ref richTextAttributes.ElementAt(attributeIndex); + tagIndentifierIndex += 1; + richTextTagIndentifiers.Add(RichTextTagIdentifier.Empty); + currentTagIndentifier = ref richTextTagIndentifiers.ElementAt(tagIndentifierIndex); } - else if (attributeFlag != ParserState.Two) + else if (tagIndentifierFlag != ParserState.Two) { - currentAttribute.valueLength += byteCount; + currentTagIndentifier.valueLength += byteCount; } } else if (tagValueType == TagValueType.ColorValue) { if (unicode != ' ') { - currentAttribute.valueLength += byteCount; + currentTagIndentifier.valueLength += byteCount; } else { - attributeFlag = ParserState.Two; - tagValueType = TagValueType.None; - tagUnitType = TagUnitType.Pixels; - attributeIndex += 1; - richTextAttributes.Add(RichTextAttribute.Empty); - currentAttribute = ref richTextAttributes.ElementAt(attributeIndex); + tagIndentifierFlag = ParserState.Two; + tagValueType = TagValueType.None; + tagUnitType = TagUnitType.Pixels; + tagIndentifierIndex += 1; + richTextTagIndentifiers.Add(RichTextTagIdentifier.Empty); + currentTagIndentifier = ref richTextTagIndentifiers.ElementAt(tagIndentifierIndex); } } else if (tagValueType == TagValueType.StringValue) @@ -138,46 +137,46 @@ internal static bool ValidateHtmlTag( // Compute HashCode value for the named tag. if (unicode != '"') { - currentAttribute.valueHashCode = (currentAttribute.valueHashCode << 5) + currentAttribute.valueHashCode ^ unicode.value; - currentAttribute.valueLength += byteCount; + currentTagIndentifier.valueHashCode = (currentTagIndentifier.valueHashCode << 5) + currentTagIndentifier.valueHashCode ^ unicode.value; + currentTagIndentifier.valueLength += byteCount; } else { - attributeFlag = ParserState.Two; - tagValueType = TagValueType.None; - tagUnitType = TagUnitType.Pixels; - attributeIndex += 1; - richTextAttributes.Add(RichTextAttribute.Empty); - currentAttribute = ref richTextAttributes.ElementAt(attributeIndex); + tagIndentifierFlag = ParserState.Two; + tagValueType = TagValueType.None; + tagUnitType = TagUnitType.Pixels; + tagIndentifierIndex += 1; + richTextTagIndentifiers.Add(RichTextTagIdentifier.Empty); + currentTagIndentifier = ref richTextTagIndentifiers.ElementAt(tagIndentifierIndex); } } } } - if (unicode == '=') // '=' - attributeFlag = ParserState.One; + tagIndentifierFlag = ParserState.One; - // Compute HashCode for the name of the attribute - if (attributeFlag == ParserState.Zero && unicode == ' ') + // Compute HashCode for the name of the tagIndentifier + if (tagIndentifierFlag == ParserState.Zero && unicode == ' ') { - if (isTagSet) return false; + if (isTagSet) + return false; - isTagSet = true; - attributeFlag = ParserState.Two; + isTagSet = true; + tagIndentifierFlag = ParserState.Two; - tagValueType = TagValueType.None; - tagUnitType = TagUnitType.Pixels; - attributeIndex += 1; - richTextAttributes.Add(RichTextAttribute.Empty); - currentAttribute = ref richTextAttributes.ElementAt(attributeIndex); + tagValueType = TagValueType.None; + tagUnitType = TagUnitType.Pixels; + tagIndentifierIndex += 1; + richTextTagIndentifiers.Add(RichTextTagIdentifier.Empty); + currentTagIndentifier = ref richTextTagIndentifiers.ElementAt(tagIndentifierIndex); } - if (attributeFlag == ParserState.Zero) - currentAttribute.nameHashCode = (currentAttribute.nameHashCode << 3) - currentAttribute.nameHashCode + unicode.value; + if (tagIndentifierFlag == ParserState.Zero) + currentTagIndentifier.nameHashCode = (currentTagIndentifier.nameHashCode << 3) - currentTagIndentifier.nameHashCode + unicode.value; - if (attributeFlag == ParserState.Two && unicode == ' ') - attributeFlag = ParserState.Zero; + if (tagIndentifierFlag == ParserState.Two && unicode == ' ') + tagIndentifierFlag = ParserState.Zero; } if (!isValidHtmlTag) @@ -185,15 +184,15 @@ internal static bool ValidateHtmlTag( return false; } - ref var firstAttribute = ref richTextAttributes.ElementAt(0); + ref var firstTagIndentifier = ref richTextTagIndentifiers.ElementAt(0); calliString.GetSubString(ref textConfiguration.m_htmlTag, startByteIndex, tagByteCount); //#region Rich Text Tag Processing //#if !RICH_TEXT_ENABLED // Special handling of the no parsing tag tag - if (textConfiguration.tag_NoParsing && (firstAttribute.nameHashCode != 53822163 && firstAttribute.nameHashCode != 49429939)) + if (textConfiguration.tag_NoParsing && (firstTagIndentifier.nameHashCode != 53822163 && firstTagIndentifier.nameHashCode != 49429939)) return false; - else if (firstAttribute.nameHashCode == 53822163 || firstAttribute.nameHashCode == 49429939) + else if (firstTagIndentifier.nameHashCode == 53822163 || firstTagIndentifier.nameHashCode == 49429939) { textConfiguration.tag_NoParsing = false; return true; @@ -202,11 +201,11 @@ internal static bool ValidateHtmlTag( // Color tag just starting with hex (no assignment) if (textConfiguration.m_htmlTag[0] == 35) { - firstAttribute.tagType = RichTextTagType.Color; + firstTagIndentifier.tagType = RichTextTagType.Color; // tagCharCount == 4: Color <#FFF> 3 Hex values (short form) // tagCharCount == 5: Color <#FFF7> 4 Hex values with alpha (short form) - // tagCharCount == 7: Color <#FF00FF> - // tagCharCount == 9: Color <#FF00FF00> with alpha + // tagCharCount == 7: Color <#FF00FF> + // tagCharCount == 9: Color <#FF00FF00> with alpha if (tagCharCount == 4 || tagCharCount == 5 || tagCharCount == 7 || tagCharCount == 9) { textConfiguration.m_htmlColor = HexCharsToColor(textConfiguration.m_htmlTag, tagCharCount); @@ -219,72 +218,76 @@ internal static bool ValidateHtmlTag( float value = 0; float fontScale; - switch (firstAttribute.nameHashCode) + switch (firstTagIndentifier.nameHashCode) { - case 98: // - case 66: // - textConfiguration.m_FontStyleInternal |= FontStyles.Bold; + case 98: // + case 66: // + textConfiguration.m_fontStyleInternal |= FontStyles.Bold; textConfiguration.m_fontStyleStack.Add(FontStyles.Bold); - textConfiguration.m_FontWeightInternal = FontWeight.Bold; + textConfiguration.m_fontWeightInternal = FontWeight.Bold; return true; - case 427: // - case 395: // + case 427: // + case 395: // if ((baseConfiguration.fontStyle & FontStyles.Bold) != FontStyles.Bold) { if (textConfiguration.m_fontStyleStack.Remove(FontStyles.Bold) == 0) { - textConfiguration.m_FontStyleInternal &= ~FontStyles.Bold; - textConfiguration.m_FontWeightInternal = textConfiguration.m_FontWeightStack.Peek(); + textConfiguration.m_fontStyleInternal &= ~FontStyles.Bold; + textConfiguration.m_fontWeightInternal = textConfiguration.m_fontWeightStack.Peek(); } } return true; - case 105: // - case 73: // - firstAttribute.tagType = RichTextTagType.Italic; - textConfiguration.m_FontStyleInternal |= FontStyles.Italic; + case 105: // + case 73: // + firstTagIndentifier.tagType = RichTextTagType.Italic; + textConfiguration.m_fontStyleInternal |= FontStyles.Italic; textConfiguration.m_fontStyleStack.Add(FontStyles.Italic); - if (richTextAttributes.Length > 0 && richTextAttributes[1].nameHashCode == 276531 || richTextAttributes[1].nameHashCode == 186899) + if (richTextTagIndentifiers.Length > 0 && richTextTagIndentifiers[1].nameHashCode == 276531 || richTextTagIndentifiers[1].nameHashCode == 186899) { - // Reject tag if value is invalid. - calliString.GetSubString(ref textConfiguration.m_htmlTag, richTextAttributes[1].valueStartIndex, richTextAttributes[1].valueLength); + calliString.GetSubString(ref textConfiguration.m_htmlTag, richTextTagIndentifiers[1].valueStartIndex, richTextTagIndentifiers[1].valueLength); if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; - textConfiguration.m_ItalicAngle = (short)value; + textConfiguration.m_italicAngle = (short)value; // Make sure angle is within valid range. - if (textConfiguration.m_ItalicAngle < -180 || textConfiguration.m_ItalicAngle > 180) return false; + if (textConfiguration.m_italicAngle < -180 || textConfiguration.m_italicAngle > 180) + return false; } else - textConfiguration.m_ItalicAngle = currenFont.italicsStyleSlant; + textConfiguration.m_italicAngle = currenFont.italicsStyleSlant; - textConfiguration.m_ItalicAngleStack.Add(textConfiguration.m_ItalicAngle); + textConfiguration.m_italicAngleStack.Add(textConfiguration.m_italicAngle); return true; - case 434: // - case 402: // + case 434: // + case 402: // if ((baseConfiguration.fontStyle & FontStyles.Italic) != FontStyles.Italic) { - textConfiguration.m_ItalicAngle = textConfiguration.m_ItalicAngleStack.RemoveExceptRoot(); + textConfiguration.m_italicAngle = textConfiguration.m_italicAngleStack.RemoveExceptRoot(); if (textConfiguration.m_fontStyleStack.Remove(FontStyles.Italic) == 0) - textConfiguration.m_FontStyleInternal &= ~FontStyles.Italic; + textConfiguration.m_fontStyleInternal &= ~FontStyles.Italic; } return true; - case 115: // - case 83: // - textConfiguration.m_FontStyleInternal |= FontStyles.Strikethrough; + case 115: // + case 83: // + textConfiguration.m_fontStyleInternal |= FontStyles.Strikethrough; textConfiguration.m_fontStyleStack.Add(FontStyles.Strikethrough); - if (richTextAttributes.Length > 0 && richTextAttributes[1].nameHashCode == 281955 || richTextAttributes[1].nameHashCode == 192323) + if (richTextTagIndentifiers.Length > 0 && richTextTagIndentifiers[1].nameHashCode == 281955 || richTextTagIndentifiers[1].nameHashCode == 192323) { - calliString.GetSubString(ref textConfiguration.m_htmlTag, richTextAttributes[1].valueStartIndex, richTextAttributes[1].valueLength); - charCount = richTextAttributes[1].valueLength - richTextAttributes[1].valueStartIndex; - textConfiguration.m_strikethroughColor = HexCharsToColor(textConfiguration.m_htmlTag, charCount); - textConfiguration.m_strikethroughColor.a = textConfiguration.m_htmlColor.a < textConfiguration.m_strikethroughColor.a ? (byte)(textConfiguration.m_htmlColor.a) : (byte)(textConfiguration.m_strikethroughColor.a); + calliString.GetSubString(ref textConfiguration.m_htmlTag, richTextTagIndentifiers[1].valueStartIndex, richTextTagIndentifiers[1].valueLength); + charCount = richTextTagIndentifiers[1].valueLength - richTextTagIndentifiers[1].valueStartIndex; + textConfiguration.m_strikethroughColor = HexCharsToColor(textConfiguration.m_htmlTag, charCount); + textConfiguration.m_strikethroughColor.a = textConfiguration.m_htmlColor.a < + textConfiguration.m_strikethroughColor.a ? (byte)(textConfiguration.m_htmlColor.a) : (byte)(textConfiguration + . + m_strikethroughColor + .a); } else textConfiguration.m_strikethroughColor = textConfiguration.m_htmlColor; @@ -292,26 +295,28 @@ internal static bool ValidateHtmlTag( textConfiguration.m_strikethroughColorStack.Add(textConfiguration.m_strikethroughColor); return true; - case 444: // - case 412: // + case 444: // + case 412: // if ((baseConfiguration.fontStyle & FontStyles.Strikethrough) != FontStyles.Strikethrough) { if (textConfiguration.m_fontStyleStack.Remove(FontStyles.Strikethrough) == 0) - textConfiguration.m_FontStyleInternal &= ~FontStyles.Strikethrough; + textConfiguration.m_fontStyleInternal &= ~FontStyles.Strikethrough; } textConfiguration.m_strikethroughColor = textConfiguration.m_strikethroughColorStack.RemoveExceptRoot(); return true; - case 117: // - case 85: // - textConfiguration.m_FontStyleInternal |= FontStyles.Underline; + case 117: // + case 85: // + textConfiguration.m_fontStyleInternal |= FontStyles.Underline; textConfiguration.m_fontStyleStack.Add(FontStyles.Underline); - if (richTextAttributes.Length > 0 && richTextAttributes[1].nameHashCode == 281955 || richTextAttributes[1].nameHashCode == 192323) + if (richTextTagIndentifiers.Length > 0 && richTextTagIndentifiers[1].nameHashCode == 281955 || richTextTagIndentifiers[1].nameHashCode == 192323) { - calliString.GetSubString(ref textConfiguration.m_htmlTag, richTextAttributes[1].valueStartIndex, richTextAttributes[1].valueLength); - charCount = richTextAttributes[1].valueLength - richTextAttributes[1].valueStartIndex; - textConfiguration.m_underlineColor = HexCharsToColor(textConfiguration.m_htmlTag, charCount); - textConfiguration.m_underlineColor.a = textConfiguration.m_htmlColor.a < textConfiguration.m_underlineColor.a ? (byte)(textConfiguration.m_htmlColor.a) : (byte)(textConfiguration.m_underlineColor.a); + calliString.GetSubString(ref textConfiguration.m_htmlTag, richTextTagIndentifiers[1].valueStartIndex, richTextTagIndentifiers[1].valueLength); + charCount = richTextTagIndentifiers[1].valueLength - richTextTagIndentifiers[1].valueStartIndex; + textConfiguration.m_underlineColor = HexCharsToColor(textConfiguration.m_htmlTag, charCount); + textConfiguration.m_underlineColor.a = textConfiguration.m_htmlColor.a < + textConfiguration.m_underlineColor.a ? (byte)(textConfiguration.m_htmlColor.a) : (byte)(textConfiguration. + m_underlineColor.a); } else textConfiguration.m_underlineColor = textConfiguration.m_htmlColor; @@ -319,58 +324,58 @@ internal static bool ValidateHtmlTag( textConfiguration.m_underlineColorStack.Add(textConfiguration.m_underlineColor); return true; - case 446: // - case 414: // + case 446: // + case 414: // if ((baseConfiguration.fontStyle & FontStyles.Underline) != FontStyles.Underline) { textConfiguration.m_underlineColor = textConfiguration.m_underlineColorStack.RemoveExceptRoot(); if (textConfiguration.m_fontStyleStack.Remove(FontStyles.Underline) == 0) - textConfiguration.m_FontStyleInternal &= ~FontStyles.Underline; + textConfiguration.m_fontStyleInternal &= ~FontStyles.Underline; } textConfiguration.m_underlineColor = textConfiguration.m_underlineColorStack.RemoveExceptRoot(); return true; - case 43045: // - case 30245: // - textConfiguration.m_FontStyleInternal |= FontStyles.Highlight; + case 43045: // + case 30245: // + textConfiguration.m_fontStyleInternal |= FontStyles.Highlight; textConfiguration.m_fontStyleStack.Add(FontStyles.Highlight); - Color32 highlightColor = new Color32(255, 255, 0, 64); - Calli_Offset highlightPadding = Calli_Offset.zero; + Color32 highlightColor = new Color32(255, 255, 0, 64); + RectOffsets highlightPadding = RectOffsets.zero; - // Handle Mark Tag and potential attributes - for (int i = 0; i < richTextAttributes.Length && richTextAttributes[i].nameHashCode != 0; i++) + // Handle Mark Tag and potential tagIndentifiers + for (int i = 0; i < richTextTagIndentifiers.Length && richTextTagIndentifiers[i].nameHashCode != 0; i++) { - int nameHashCode = richTextAttributes[i].nameHashCode; + int nameHashCode = richTextTagIndentifiers[i].nameHashCode; switch (nameHashCode) { // Mark tag case 43045: case 30245: - if (richTextAttributes[i].valueType == TagValueType.ColorValue) + if (richTextTagIndentifiers[i].valueType == TagValueType.ColorValue) { - //is this a bug in TMP Pro? -->should be richTextAttributes[i] and not firstAttribute - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); - charCount = firstAttribute.valueLength - firstAttribute.valueStartIndex; + //is this a bug in TMP Pro? -->should be richTextTagIndentifiers[i] and not firstTagIndentifier + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); + charCount = firstTagIndentifier.valueLength - firstTagIndentifier.valueStartIndex; highlightColor = HexCharsToColor(textConfiguration.m_htmlTag, charCount); } break; - // Color attribute + // Color tagIndentifier case 281955: - calliString.GetSubString(ref textConfiguration.m_htmlTag, richTextAttributes[i].valueStartIndex, richTextAttributes[i].valueLength); - charCount = richTextAttributes[i].valueLength - richTextAttributes[i].valueStartIndex; + calliString.GetSubString(ref textConfiguration.m_htmlTag, richTextTagIndentifiers[i].valueStartIndex, richTextTagIndentifiers[i].valueLength); + charCount = richTextTagIndentifiers[i].valueLength - richTextTagIndentifiers[i].valueStartIndex; highlightColor = HexCharsToColor(textConfiguration.m_htmlTag, charCount); break; - // Padding attribute + // Padding tagIndentifier case 15087385: - //int paramCount = GetAttributeParameters(calliString, richTextAttributes[i].valueStartIndex, richTextAttributes[i].valueLength, ref m_attributeParameterValues); + //int paramCount = GetTagIndentifierParameters(calliString, richTextTagIndentifiers[i].valueStartIndex, richTextTagIndentifiers[i].valueLength, ref m_tagIndentifierParameterValues); //if (paramCount != 4) return false; - //highlightPadding = new Calli_Offset(m_attributeParameterValues[0], m_attributeParameterValues[1], m_attributeParameterValues[2], m_attributeParameterValues[3]); + //highlightPadding = new Calli_Offset(m_tagIndentifierParameterValues[0], m_tagIndentifierParameterValues[1], m_tagIndentifierParameterValues[2], m_tagIndentifierParameterValues[3]); //highlightPadding *= baseConfiguration.fontSize * 0.01f * (richtextAdjustments.m_isOrthographic ? 1 : 0.1f); break; } @@ -379,71 +384,73 @@ internal static bool ValidateHtmlTag( highlightColor.a = textConfiguration.m_htmlColor.a < highlightColor.a ? (byte)(textConfiguration.m_htmlColor.a) : (byte)(highlightColor.a); HighlightState state = new HighlightState(highlightColor, highlightPadding); - textConfiguration.m_HighlightStateStack.Add(state); + textConfiguration.m_highlightStateStack.Add(state); return true; - case 155892: // - case 143092: // + case 155892: // + case 143092: // if ((baseConfiguration.fontStyle & FontStyles.Highlight) != FontStyles.Highlight) { - textConfiguration.m_HighlightStateStack.RemoveExceptRoot(); + textConfiguration.m_highlightStateStack.RemoveExceptRoot(); if (textConfiguration.m_fontStyleStack.Remove(FontStyles.Highlight) == 0) - textConfiguration.m_FontStyleInternal &= ~FontStyles.Highlight; + textConfiguration.m_fontStyleInternal &= ~FontStyles.Highlight; } return true; - case 6552: // - case 4728: // + case 6552: // + case 4728: // textConfiguration.m_fontScaleMultiplier *= currenFont.subscriptSize > 0 ? currenFont.subscriptSize : 1; textConfiguration.m_baselineOffsetStack.Add(textConfiguration.m_baselineOffset); - fontScale = (textConfiguration.m_currentFontSize / currenFont.pointSize * currenFont.scale * (baseConfiguration.isOrthographic ? 1 : 0.1f)); + fontScale = + (textConfiguration.m_currentFontSize / currenFont.pointSize * currenFont.scale * (baseConfiguration.isOrthographic ? 1 : 0.1f)); textConfiguration.m_baselineOffset += currenFont.subscriptOffset * fontScale * textConfiguration.m_fontScaleMultiplier; textConfiguration.m_fontStyleStack.Add(FontStyles.Subscript); - textConfiguration.m_FontStyleInternal |= FontStyles.Subscript; + textConfiguration.m_fontStyleInternal |= FontStyles.Subscript; return true; - case 22673: // - case 20849: // - if ((textConfiguration.m_FontStyleInternal & FontStyles.Subscript) == FontStyles.Subscript) + case 22673: // + case 20849: // + if ((textConfiguration.m_fontStyleInternal & FontStyles.Subscript) == FontStyles.Subscript) { if (textConfiguration.m_fontScaleMultiplier < 1) { - textConfiguration.m_baselineOffset = textConfiguration.m_baselineOffsetStack.Pop(); + textConfiguration.m_baselineOffset = textConfiguration.m_baselineOffsetStack.Pop(); textConfiguration.m_fontScaleMultiplier /= currenFont.subscriptSize > 0 ? currenFont.subscriptSize : 1; } if (textConfiguration.m_fontStyleStack.Remove(FontStyles.Subscript) == 0) - textConfiguration.m_FontStyleInternal &= ~FontStyles.Subscript; + textConfiguration.m_fontStyleInternal &= ~FontStyles.Subscript; } return true; - case 6566: // - case 4742: // + case 6566: // + case 4742: // textConfiguration.m_fontScaleMultiplier *= currenFont.superscriptSize > 0 ? currenFont.superscriptSize : 1; textConfiguration.m_baselineOffsetStack.Add(textConfiguration.m_baselineOffset); - fontScale = (textConfiguration.m_currentFontSize / currenFont.pointSize * currenFont.scale * (baseConfiguration.isOrthographic ? 1 : 0.1f)); + fontScale = + (textConfiguration.m_currentFontSize / currenFont.pointSize * currenFont.scale * (baseConfiguration.isOrthographic ? 1 : 0.1f)); textConfiguration.m_baselineOffset += currenFont.superscriptOffset * fontScale * textConfiguration.m_fontScaleMultiplier; textConfiguration.m_fontStyleStack.Add(FontStyles.Superscript); - textConfiguration.m_FontStyleInternal |= FontStyles.Superscript; + textConfiguration.m_fontStyleInternal |= FontStyles.Superscript; return true; - case 22687: // - case 20863: // - if ((textConfiguration.m_FontStyleInternal & FontStyles.Superscript) == FontStyles.Superscript) + case 22687: // + case 20863: // + if ((textConfiguration.m_fontStyleInternal & FontStyles.Superscript) == FontStyles.Superscript) { if (textConfiguration.m_fontScaleMultiplier < 1) { - textConfiguration.m_baselineOffset = textConfiguration.m_baselineOffsetStack.Pop(); + textConfiguration.m_baselineOffset = textConfiguration.m_baselineOffsetStack.Pop(); textConfiguration.m_fontScaleMultiplier /= currenFont.superscriptSize > 0 ? currenFont.superscriptSize : 1; } if (textConfiguration.m_fontStyleStack.Remove(FontStyles.Superscript) == 0) - textConfiguration.m_FontStyleInternal &= ~FontStyles.Superscript; + textConfiguration.m_fontStyleInternal &= ~FontStyles.Superscript; } return true; - case -330774850: // - case 2012149182: // - firstAttribute.tagType = RichTextTagType.FontWeight; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case -330774850: // + case 2012149182: // + firstTagIndentifier.tagType = RichTextTagType.FontWeight; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -451,51 +458,51 @@ internal static bool ValidateHtmlTag( switch ((int)value) { case 100: - textConfiguration.m_FontWeightInternal = FontWeight.Thin; + textConfiguration.m_fontWeightInternal = FontWeight.Thin; break; case 200: - textConfiguration.m_FontWeightInternal = FontWeight.ExtraLight; + textConfiguration.m_fontWeightInternal = FontWeight.ExtraLight; break; case 300: - textConfiguration.m_FontWeightInternal = FontWeight.Light; + textConfiguration.m_fontWeightInternal = FontWeight.Light; break; case 400: - textConfiguration.m_FontWeightInternal = FontWeight.Regular; + textConfiguration.m_fontWeightInternal = FontWeight.Regular; break; case 500: - textConfiguration.m_FontWeightInternal = FontWeight.Medium; + textConfiguration.m_fontWeightInternal = FontWeight.Medium; break; case 600: - textConfiguration.m_FontWeightInternal = FontWeight.SemiBold; + textConfiguration.m_fontWeightInternal = FontWeight.SemiBold; break; case 700: - textConfiguration.m_FontWeightInternal = FontWeight.Bold; + textConfiguration.m_fontWeightInternal = FontWeight.Bold; break; case 800: - textConfiguration.m_FontWeightInternal = FontWeight.Heavy; + textConfiguration.m_fontWeightInternal = FontWeight.Heavy; break; case 900: - textConfiguration.m_FontWeightInternal = FontWeight.Black; + textConfiguration.m_fontWeightInternal = FontWeight.Black; break; } - textConfiguration.m_FontWeightStack.Add(textConfiguration.m_FontWeightInternal); + textConfiguration.m_fontWeightStack.Add(textConfiguration.m_fontWeightInternal); return true; - case -1885698441: // - case 457225591: // - textConfiguration.m_FontWeightStack.RemoveExceptRoot(); + case -1885698441: // + case 457225591: // + textConfiguration.m_fontWeightStack.RemoveExceptRoot(); - if (textConfiguration.m_FontStyleInternal == FontStyles.Bold) - textConfiguration.m_FontWeightInternal = FontWeight.Bold; + if (textConfiguration.m_fontStyleInternal == FontStyles.Bold) + textConfiguration.m_fontWeightInternal = FontWeight.Bold; else - textConfiguration.m_FontWeightInternal = textConfiguration.m_FontWeightStack.Peek(); + textConfiguration.m_fontWeightInternal = textConfiguration.m_fontWeightStack.Peek(); return true; - case 6380: // - case 4556: // - firstAttribute.tagType = RichTextTagType.Position; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 6380: // + case 4556: // + firstTagIndentifier.tagType = RichTextTagType.Position; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -513,14 +520,14 @@ internal static bool ValidateHtmlTag( return true; } return false; - case 22501: // - case 20677: // + case 22501: // + case 20677: // return true; - case 16034505: // - case 11642281: // - firstAttribute.tagType = RichTextTagType.VerticalOffset; + case 16034505: // + case 11642281: // + firstTagIndentifier.tagType = RichTextTagType.VerticalOffset; // Reject tag if value is invalid. - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -537,9 +544,9 @@ internal static bool ValidateHtmlTag( return false; } return false; - case 54741026: // - case 50348802: // - firstAttribute.tagType = RichTextTagType.VerticalOffset; + case 54741026: // + case 50348802: // + firstTagIndentifier.tagType = RichTextTagType.VerticalOffset; textConfiguration.m_baselineOffset = 0; return true; //case 43991: // @@ -558,18 +565,18 @@ internal static bool ValidateHtmlTag( ////case 800: //
//// m_forceLineBreak = true; //// return true; - case 43969: // - case 31169: // + case 43969: // + case 31169: // textConfiguration.m_isNonBreakingSpace = true; return true; - case 156816: // - case 144016: // + case 156816: // + case 144016: // textConfiguration.m_isNonBreakingSpace = false; return true; - case 45545: // - case 32745: // - firstAttribute.tagType = RichTextTagType.Size; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 45545: // + case 32745: // + firstTagIndentifier.tagType = RichTextTagType.Size; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -577,19 +584,19 @@ internal static bool ValidateHtmlTag( switch (tagUnitType) { case TagUnitType.Pixels: - if (calliString[5] == 43) // + if (calliString[5] == 43) // { textConfiguration.m_currentFontSize = baseConfiguration.fontSize + value; textConfiguration.m_sizeStack.Add(textConfiguration.m_currentFontSize); return true; } - else if (calliString[5] == 45) // + else if (calliString[5] == 45) // { textConfiguration.m_currentFontSize = baseConfiguration.fontSize + value; textConfiguration.m_sizeStack.Add(textConfiguration.m_currentFontSize); return true; } - else // + else // { textConfiguration.m_currentFontSize = value; textConfiguration.m_sizeStack.Add(textConfiguration.m_currentFontSize); @@ -605,15 +612,15 @@ internal static bool ValidateHtmlTag( return true; } return false; - case 158392: // - case 145592: // + case 158392: // + case 145592: // textConfiguration.m_currentFontSize = textConfiguration.m_sizeStack.RemoveExceptRoot(); return true; //case 41311: // //case 28511: // - // int fontHashCode = firstAttribute.valueHashCode; - // int materialAttributeHashCode = richTextAttributes[1].nameHashCode; - // int materialHashCode = richTextAttributes[1].valueHashCode; + // int fontHashCode = firstTagIndentifier.valueHashCode; + // int materialTagIndentifierHashCode = richTextTagIndentifiers[1].nameHashCode; + // int materialHashCode = richTextTagIndentifiers[1].valueHashCode; // // Special handling for or // if (fontHashCode == 764638571 || fontHashCode == 523367755) @@ -641,12 +648,12 @@ internal static bool ValidateHtmlTag( // if (tempFont == null) // { // // Check for anyone registered to this callback - // tempFont = OnFontAssetRequest?.Invoke(fontHashCode, new string(calliString, firstAttribute.valueStartIndex, firstAttribute.valueLength)); + // tempFont = OnFontAssetRequest?.Invoke(fontHashCode, new string(calliString, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength)); // if (tempFont == null) // { // // Load Font Asset - // tempFont = Resources.Load(TMP_Settings.defaultFontAssetPath + new string(calliString, firstAttribute.valueStartIndex, firstAttribute.valueLength)); + // tempFont = Resources.Load(TMP_Settings.defaultFontAssetPath + new string(calliString, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength)); // } // if (tempFont == null) @@ -657,7 +664,7 @@ internal static bool ValidateHtmlTag( // } // // HANDLE NEW MATERIAL - // if (materialAttributeHashCode == 0 && materialHashCode == 0) + // if (materialTagIndentifierHashCode == 0 && materialHashCode == 0) // { // // No material specified then use default font asset material. // m_currentMaterial = tempFont.material; @@ -666,7 +673,7 @@ internal static bool ValidateHtmlTag( // m_materialReferenceStack.Add(m_materialReferences[m_currentMaterialIndex]); // } - // else if (materialAttributeHashCode == 103415287 || materialAttributeHashCode == 72669687) // using material attribute + // else if (materialTagIndentifierHashCode == 103415287 || materialTagIndentifierHashCode == 72669687) // using material tagIndentifier // { // if (MaterialReferenceManager.TryGetMaterial(materialHashCode, out tempMaterial)) // { @@ -679,7 +686,7 @@ internal static bool ValidateHtmlTag( // else // { // // Load new material - // tempMaterial = Resources.Load(TMP_Settings.defaultFontAssetPath + new string(calliString, richTextAttributes[1].valueStartIndex, richTextAttributes[1].valueLength)); + // tempMaterial = Resources.Load(TMP_Settings.defaultFontAssetPath + new string(calliString, richTextTagIndentifiers[1].valueStartIndex, richTextTagIndentifiers[1].valueLength)); // if (tempMaterial == null) // return false; @@ -713,7 +720,7 @@ internal static bool ValidateHtmlTag( // } //case 103415287: // //case 72669687: // - // materialHashCode = firstAttribute.valueHashCode; + // materialHashCode = firstTagIndentifier.valueHashCode; // // Special handling for or // if (materialHashCode == 764638571 || materialHashCode == 523367755) @@ -729,7 +736,6 @@ internal static bool ValidateHtmlTag( // return true; // } - // // Check if material // if (MaterialReferenceManager.TryGetMaterial(materialHashCode, out tempMaterial)) // { @@ -745,7 +751,7 @@ internal static bool ValidateHtmlTag( // else // { // // Load new material - // tempMaterial = Resources.Load(TMP_Settings.defaultFontAssetPath + new string(calliString, firstAttribute.valueStartIndex, firstAttribute.valueLength)); + // tempMaterial = Resources.Load(TMP_Settings.defaultFontAssetPath + new string(calliString, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength)); // if (tempMaterial == null) // return false; @@ -776,10 +782,10 @@ internal static bool ValidateHtmlTag( // return true; // } - case 320078: // - case 230446: // - firstAttribute.tagType = RichTextTagType.Space; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 320078: // + case 230446: // + firstTagIndentifier.tagType = RichTextTagType.Space; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -797,12 +803,13 @@ internal static bool ValidateHtmlTag( return false; } return false; - case 276254: // - case 186622: // - firstAttribute.tagType = RichTextTagType.Alpha; - if (firstAttribute.valueLength != 3) return false; + case 276254: // + case 186622: // + firstTagIndentifier.tagType = RichTextTagType.Alpha; + if (firstTagIndentifier.valueLength != 3) + return false; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); textConfiguration.m_htmlColor.a = (byte)(HexToInt((char)textConfiguration.m_htmlTag[1]) * 16 + HexToInt((char)textConfiguration.m_htmlTag[2])); return true; @@ -820,12 +827,12 @@ internal static bool ValidateHtmlTag( // TMP_TextInfo.Resize(ref m_textInfo.linkInfo, index + 1); // m_textInfo.linkInfo[index].textComponent = this; - // m_textInfo.linkInfo[index].hashCode = firstAttribute.valueHashCode; + // m_textInfo.linkInfo[index].hashCode = firstTagIndentifier.valueHashCode; // m_textInfo.linkInfo[index].linkTextfirstCharacterIndex = m_characterCount; - // m_textInfo.linkInfo[index].linkIdFirstCharacterIndex = startIndex + firstAttribute.valueStartIndex; - // m_textInfo.linkInfo[index].linkIdLength = firstAttribute.valueLength; - // m_textInfo.linkInfo[index].SetLinkID(calliString, firstAttribute.valueStartIndex, firstAttribute.valueLength); + // m_textInfo.linkInfo[index].linkIdFirstCharacterIndex = startIndex + firstTagIndentifier.valueStartIndex; + // m_textInfo.linkInfo[index].linkIdLength = firstTagIndentifier.valueLength; + // m_textInfo.linkInfo[index].SetLinkID(calliString, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // } // return true; //case 155913: // @@ -840,40 +847,40 @@ internal static bool ValidateHtmlTag( // } // } // return true; - case 275917: // - case 186285: // - switch (firstAttribute.valueHashCode) + case 275917: // + case 186285: // + switch (firstTagIndentifier.valueHashCode) { - case 3774683: // + case 3774683: // textConfiguration.m_lineJustification = HorizontalAlignmentOptions.Left; textConfiguration.m_lineJustificationStack.Add(textConfiguration.m_lineJustification); return true; - case 136703040: // + case 136703040: // textConfiguration.m_lineJustification = HorizontalAlignmentOptions.Right; textConfiguration.m_lineJustificationStack.Add(textConfiguration.m_lineJustification); return true; - case -458210101: // + case -458210101: // textConfiguration.m_lineJustification = HorizontalAlignmentOptions.Center; textConfiguration.m_lineJustificationStack.Add(textConfiguration.m_lineJustification); return true; - case -523808257: // + case -523808257: // textConfiguration.m_lineJustification = HorizontalAlignmentOptions.Justified; textConfiguration.m_lineJustificationStack.Add(textConfiguration.m_lineJustification); return true; - case 122383428: // + case 122383428: // textConfiguration.m_lineJustification = HorizontalAlignmentOptions.Flush; textConfiguration.m_lineJustificationStack.Add(textConfiguration.m_lineJustification); return true; } return false; - case 1065846: // - case 976214: // + case 1065846: // + case 976214: // textConfiguration.m_lineJustification = textConfiguration.m_lineJustificationStack.RemoveExceptRoot(); return true; - case 327550: // - case 237918: // - firstAttribute.tagType = RichTextTagType.Width; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 327550: // + case 237918: // + firstTagIndentifier.tagType = RichTextTagType.Width; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -891,16 +898,16 @@ internal static bool ValidateHtmlTag( break; } return true; - case 1117479: // - case 1027847: // + case 1117479: // + case 1027847: // textConfiguration.m_width = -1; return true; - case 281955: // or - case 192323: // - // 3 Hex (short hand) - firstAttribute.tagType = RichTextTagType.Color; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); - tagCharCount -= 6; //remove "color=" from char count + case 281955: // or + case 192323: // + // 3 Hex (short hand) + firstTagIndentifier.tagType = RichTextTagType.Color; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); + tagCharCount -= 6; //remove "color=" from char count if (textConfiguration.m_htmlTag[0] == 35 && tagCharCount == 4) { textConfiguration.m_htmlColor = HexCharsToColor(textConfiguration.m_htmlTag, tagCharCount); @@ -930,45 +937,45 @@ internal static bool ValidateHtmlTag( } // - switch (firstAttribute.valueHashCode) + switch (firstTagIndentifier.valueHashCode) { - case 125395: // + case 125395: // textConfiguration.m_htmlColor = Color.red; textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; - case -992792864: // + case -992792864: // textConfiguration.m_htmlColor = new Color32(173, 216, 230, 255); textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; - case 3573310: // + case 3573310: // textConfiguration.m_htmlColor = Color.blue; textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; - case 3680713: // + case 3680713: // textConfiguration.m_htmlColor = new Color32(128, 128, 128, 255); textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; - case 117905991: // + case 117905991: // textConfiguration.m_htmlColor = Color.black; textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; - case 121463835: // + case 121463835: // textConfiguration.m_htmlColor = Color.green; textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; - case 140357351: // + case 140357351: // textConfiguration.m_htmlColor = Color.white; textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; - case 26556144: // + case 26556144: // textConfiguration.m_htmlColor = new Color32(255, 128, 0, 255); textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; - case -36881330: // + case -36881330: // textConfiguration.m_htmlColor = new Color32(160, 32, 240, 255); textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; - case 554054276: // + case 554054276: // textConfiguration.m_htmlColor = Color.yellow; textConfiguration.m_colorStack.Add(textConfiguration.m_htmlColor); return true; @@ -977,9 +984,9 @@ internal static bool ValidateHtmlTag( //case 100149144: // //case 69403544: // - // firstAttribute.tagType = RichTextTagType.Gradient; - // calliString.GetSubString(ref richtextAdjustments.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); - // int gradientPresetHashCode = firstAttribute.valueHashCode; + // firstTagIndentifier.tagType = RichTextTagType.Gradient; + // calliString.GetSubString(ref richtextAdjustments.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); + // int gradientPresetHashCode = firstTagIndentifier.valueHashCode; // //TMP_ColorGradient tempColorGradientPreset; // //// Check if Color Gradient Preset has already been loaded. @@ -992,7 +999,7 @@ internal static bool ValidateHtmlTag( // // // Load Color Gradient Preset // // if (tempColorGradientPreset == null) // // { - // // tempColorGradientPreset = Resources.Load(TMP_Settings.defaultColorGradientPresetsPath + new string(calliString, firstAttribute.valueStartIndex, firstAttribute.valueLength)); + // // tempColorGradientPreset = Resources.Load(TMP_Settings.defaultColorGradientPresetsPath + new string(calliString, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength)); // // } // // if (tempColorGradientPreset == null) @@ -1004,17 +1011,17 @@ internal static bool ValidateHtmlTag( // richtextAdjustments.m_colorGradientPresetIsTinted = false; - // // Check Attributes - // for (int i = 1; i < richTextAttributes.Length && richTextAttributes[i].nameHashCode != 0; i++) + // // Check TagIndentifiers + // for (int i = 1; i < richTextTagIndentifiers.Length && richTextTagIndentifiers[i].nameHashCode != 0; i++) // { - // // Get attribute name - // int nameHashCode = richTextAttributes[i].nameHashCode; + // // Get tagIndentifier name + // int nameHashCode = richTextTagIndentifiers[i].nameHashCode; // switch (nameHashCode) // { // case 45819: // tint // case 33019: // TINT - // calliString.GetSubString(ref richtextAdjustments.m_htmlTag, richTextAttributes[i].valueStartIndex, richTextAttributes[i].valueLength); + // calliString.GetSubString(ref richtextAdjustments.m_htmlTag, richTextTagIndentifiers[i].valueStartIndex, richTextTagIndentifiers[i].valueLength); // if (ConvertToFloat(ref richtextAdjustments.m_htmlTag, out value) != ParseError.None) // richtextAdjustments.m_colorGradientPresetIsTinted = value != 0; // break; @@ -1031,10 +1038,10 @@ internal static bool ValidateHtmlTag( // m_colorGradientPreset = m_colorGradientStack.Remove(); // return true; - case 1983971: // - case 1356515: // - firstAttribute.tagType = RichTextTagType.CharacterSpacing; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 1983971: // + case 1356515: // + firstTagIndentifier.tagType = RichTextTagType.CharacterSpacing; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -1051,9 +1058,10 @@ internal static bool ValidateHtmlTag( return false; } return true; - case 7513474: // - case 6886018: // - if (!textConfiguration.m_isParsingText) return true; + case 7513474: // + case 6886018: // + if (!textConfiguration.m_isParsingText) + return true; // Adjust xAdvance to remove extra space from last character. if (textConfiguration.m_characterCount > 0) @@ -1063,10 +1071,10 @@ internal static bool ValidateHtmlTag( } textConfiguration.m_cSpacing = 0; return true; - case 2152041: // - case 1524585: // - firstAttribute.tagType = RichTextTagType.MonoSpace; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 2152041: // + case 1524585: // + firstTagIndentifier.tagType = RichTextTagType.MonoSpace; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -1083,20 +1091,20 @@ internal static bool ValidateHtmlTag( return false; } return true; - case 7681544: // - case 7054088: // + case 7681544: // + case 7054088: // textConfiguration.m_monoSpacing = 0; return true; - case 280416: // + case 280416: // return false; - case 1071884: // - case 982252: // + case 1071884: // + case 982252: // textConfiguration.m_htmlColor = textConfiguration.m_colorStack.RemoveExceptRoot(); return true; - case 2068980: // - case 1441524: // - firstAttribute.tagType = RichTextTagType.Indent; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 2068980: // + case 1441524: // + firstTagIndentifier.tagType = RichTextTagType.Indent; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -1118,15 +1126,15 @@ internal static bool ValidateHtmlTag( textConfiguration.m_xAdvance = textConfiguration.tag_Indent; return true; - case 7598483: // - case 6971027: // + case 7598483: // + case 6971027: // textConfiguration.tag_Indent = textConfiguration.m_indentStack.RemoveExceptRoot(); //m_xAdvance = tag_Indent; return true; - case 1109386397: // - case -842656867: // - firstAttribute.tagType = RichTextTagType.LineIndent; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 1109386397: // + case -842656867: // + firstTagIndentifier.tagType = RichTextTagType.LineIndent; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -1146,18 +1154,18 @@ internal static bool ValidateHtmlTag( textConfiguration.m_xAdvance += textConfiguration.tag_LineIndent; return true; - case -445537194: // - case 1897386838: // + case -445537194: // + case 1897386838: // textConfiguration.tag_LineIndent = 0; return true; //case 2246877: // //case 1619421: // - // int spriteAssetHashCode = firstAttribute.valueHashCode; + // int spriteAssetHashCode = firstTagIndentifier.valueHashCode; // TMP_SpriteAsset tempSpriteAsset; // m_spriteIndex = -1; // // CHECK TAG FORMAT - // if (firstAttribute.valueType == TagValueType.None || firstAttribute.valueType == TagValueType.NumericalValue) + // if (firstTagIndentifier.valueType == TagValueType.None || firstTagIndentifier.valueType == TagValueType.NumericalValue) // { // // No Sprite Asset is assigned to the text object // if (m_spriteAsset != null) @@ -1195,10 +1203,10 @@ internal static bool ValidateHtmlTag( // if (tempSpriteAsset == null) // { // // - // tempSpriteAsset = OnSpriteAssetRequest?.Invoke(spriteAssetHashCode, new string(calliString, firstAttribute.valueStartIndex, firstAttribute.valueLength)); + // tempSpriteAsset = OnSpriteAssetRequest?.Invoke(spriteAssetHashCode, new string(calliString, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength)); // if (tempSpriteAsset == null) - // tempSpriteAsset = Resources.Load(TMP_Settings.defaultSpriteAssetPath + new string(calliString, firstAttribute.valueStartIndex, firstAttribute.valueLength)); + // tempSpriteAsset = Resources.Load(TMP_Settings.defaultSpriteAssetPath + new string(calliString, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength)); // } // if (tempSpriteAsset == null) @@ -1211,7 +1219,7 @@ internal static bool ValidateHtmlTag( // } // // Handling of legacy tag format. - // if (firstAttribute.valueType == TagValueType.NumericalValue) // + // if (firstTagIndentifier.valueType == TagValueType.NumericalValue) // // { // int index = (int)ConvertToFloat(ref richtextAdjustments.m_htmlTag, out value); @@ -1227,25 +1235,25 @@ internal static bool ValidateHtmlTag( // m_spriteColor = s_colorWhite; // m_tintSprite = false; - // // Handle Sprite Tag Attributes - // for (int i = 0; i < richTextAttributes.Length && richTextAttributes[i].nameHashCode != 0; i++) + // // Handle Sprite Tag TagIndentifiers + // for (int i = 0; i < richTextTagIndentifiers.Length && richTextTagIndentifiers[i].nameHashCode != 0; i++) // { - // //Debug.Log("Attribute[" + i + "].nameHashCode=" + richTextAttributes[i].nameHashCode + " Value:" + ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextAttributes[i].valueStartIndex, richTextAttributes[i].valueLength)); - // int nameHashCode = richTextAttributes[i].nameHashCode; + // //Debug.Log("TagIndentifier[" + i + "].nameHashCode=" + richTextTagIndentifiers[i].nameHashCode + " Value:" + ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextTagIndentifiers[i].valueStartIndex, richTextTagIndentifiers[i].valueLength)); + // int nameHashCode = richTextTagIndentifiers[i].nameHashCode; // int index = 0; // switch (nameHashCode) // { // case 43347: // // case 30547: // - // m_currentSpriteAsset = TMP_SpriteAsset.SearchForSpriteByHashCode(m_currentSpriteAsset, richTextAttributes[i].valueHashCode, true, out index); + // m_currentSpriteAsset = TMP_SpriteAsset.SearchForSpriteByHashCode(m_currentSpriteAsset, richTextTagIndentifiers[i].valueHashCode, true, out index); // if (index == -1) return false; // m_spriteIndex = index; // break; // case 295562: // // case 205930: // - // index = (int)ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextAttributes[1].valueStartIndex, richTextAttributes[1].valueLength); + // index = (int)ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextTagIndentifiers[1].valueStartIndex, richTextTagIndentifiers[1].valueLength); // // Reject tag if value is invalid. // if (index == Int16.MinValue) return false; @@ -1257,19 +1265,19 @@ internal static bool ValidateHtmlTag( // break; // case 45819: // tint // case 33019: // TINT - // m_tintSprite = ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextAttributes[i].valueStartIndex, richTextAttributes[i].valueLength) != 0; + // m_tintSprite = ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextTagIndentifiers[i].valueStartIndex, richTextTagIndentifiers[i].valueLength) != 0; // break; // case 281955: // color=#FF00FF80 // case 192323: // COLOR - // m_spriteColor = HexCharsToColor(calliString, richTextAttributes[i].valueStartIndex, richTextAttributes[i].valueLength); + // m_spriteColor = HexCharsToColor(calliString, richTextTagIndentifiers[i].valueStartIndex, richTextTagIndentifiers[i].valueLength); // break; // case 39505: // anim="0,16,12" start, end, fps // case 26705: // ANIM - // //Debug.Log("Start: " + richTextAttributes[i].valueStartIndex + " Length: " + richTextAttributes[i].valueLength); - // int paramCount = GetAttributeParameters(calliString, richTextAttributes[i].valueStartIndex, richTextAttributes[i].valueLength, ref m_attributeParameterValues); + // //Debug.Log("Start: " + richTextTagIndentifiers[i].valueStartIndex + " Length: " + richTextTagIndentifiers[i].valueLength); + // int paramCount = GetTagIndentifierParameters(calliString, richTextTagIndentifiers[i].valueStartIndex, richTextTagIndentifiers[i].valueLength, ref m_tagIndentifierParameterValues); // if (paramCount != 3) return false; - // m_spriteIndex = (int)m_attributeParameterValues[0]; + // m_spriteIndex = (int)m_tagIndentifierParameterValues[0]; // if (m_isParsingText) // { @@ -1277,7 +1285,7 @@ internal static bool ValidateHtmlTag( // // It is possible for a sprite to get animated when it ends up being truncated. // // Should consider moving the animation of the sprite after text geometry upload. - // spriteAnimator.DoSpriteAnimation(m_characterCount, m_currentSpriteAsset, m_spriteIndex, (int)m_attributeParameterValues[1], (int)m_attributeParameterValues[2]); + // spriteAnimator.DoSpriteAnimation(m_characterCount, m_currentSpriteAsset, m_spriteIndex, (int)m_tagIndentifierParameterValues[1], (int)m_tagIndentifierParameterValues[2]); // } // break; @@ -1299,57 +1307,57 @@ internal static bool ValidateHtmlTag( // m_textElementType = TMP_TextElementType.Sprite; // return true; - case 730022849: // - case 514803617: // - textConfiguration.m_FontStyleInternal |= FontStyles.LowerCase; + case 730022849: // + case 514803617: // + textConfiguration.m_fontStyleInternal |= FontStyles.LowerCase; textConfiguration.m_fontStyleStack.Add(FontStyles.LowerCase); return true; - case -1668324918: // - case -1883544150: // + case -1668324918: // + case -1883544150: // if ((baseConfiguration.fontStyle & FontStyles.LowerCase) != FontStyles.LowerCase) { if (textConfiguration.m_fontStyleStack.Remove(FontStyles.LowerCase) == 0) - textConfiguration.m_FontStyleInternal &= ~FontStyles.LowerCase; + textConfiguration.m_fontStyleInternal &= ~FontStyles.LowerCase; } return true; - case 13526026: // - case 9133802: // - case 781906058: // - case 566686826: // - textConfiguration.m_FontStyleInternal |= FontStyles.UpperCase; + case 13526026: // + case 9133802: // + case 781906058: // + case 566686826: // + textConfiguration.m_fontStyleInternal |= FontStyles.UpperCase; textConfiguration.m_fontStyleStack.Add(FontStyles.UpperCase); return true; - case 52232547: // - case 47840323: // - case -1616441709: // - case -1831660941: // + case 52232547: // + case 47840323: // + case -1616441709: // + case -1831660941: // if ((baseConfiguration.fontStyle & FontStyles.UpperCase) != FontStyles.UpperCase) { if (textConfiguration.m_fontStyleStack.Remove(FontStyles.UpperCase) == 0) - textConfiguration.m_FontStyleInternal &= ~FontStyles.UpperCase; + textConfiguration.m_fontStyleInternal &= ~FontStyles.UpperCase; } return true; - case 766244328: // - case 551025096: // - textConfiguration.m_FontStyleInternal |= FontStyles.SmallCaps; + case 766244328: // + case 551025096: // + textConfiguration.m_fontStyleInternal |= FontStyles.SmallCaps; textConfiguration.m_fontStyleStack.Add(FontStyles.SmallCaps); return true; - case -1632103439: // - case -1847322671: // + case -1632103439: // + case -1847322671: // if ((baseConfiguration.fontStyle & FontStyles.SmallCaps) != FontStyles.SmallCaps) { if (textConfiguration.m_fontStyleStack.Remove(FontStyles.SmallCaps) == 0) - textConfiguration.m_FontStyleInternal &= ~FontStyles.SmallCaps; + textConfiguration.m_fontStyleInternal &= ~FontStyles.SmallCaps; } return true; - case 2109854: // - case 1482398: // - // Check value type - switch (firstAttribute.valueType) + case 2109854: // + case 1482398: // + // Check value type + switch (firstTagIndentifier.valueType) { case TagValueType.NumericalValue: - firstAttribute.tagType = RichTextTagType.Margin; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + firstTagIndentifier.tagType = RichTextTagType.Margin; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -1364,30 +1372,31 @@ internal static bool ValidateHtmlTag( textConfiguration.m_marginLeft = value * (baseConfiguration.isOrthographic ? 1 : 0.1f) * textConfiguration.m_currentFontSize; break; case TagUnitType.Percentage: - textConfiguration.m_marginLeft = (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; + textConfiguration.m_marginLeft = + (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; break; } - textConfiguration.m_marginLeft = textConfiguration.m_marginLeft >= 0 ? textConfiguration.m_marginLeft : 0; + textConfiguration.m_marginLeft = textConfiguration.m_marginLeft >= 0 ? textConfiguration.m_marginLeft : 0; textConfiguration.m_marginRight = textConfiguration.m_marginLeft; return true; case TagValueType.None: - for (int i = 1; i < richTextAttributes.Length && richTextAttributes[i].nameHashCode != 0; i++) + for (int i = 1; i < richTextTagIndentifiers.Length && richTextTagIndentifiers[i].nameHashCode != 0; i++) { - currentAttribute = ref richTextAttributes.ElementAt(i); - // Get attribute name - int nameHashCode = richTextAttributes[i].nameHashCode; + currentTagIndentifier = ref richTextTagIndentifiers.ElementAt(i); + // Get tagIndentifier name + int nameHashCode = richTextTagIndentifiers[i].nameHashCode; switch (nameHashCode) { case 42823: // - currentAttribute.tagType = RichTextTagType.Margin; - calliString.GetSubString(ref textConfiguration.m_htmlTag, currentAttribute.valueStartIndex, currentAttribute.valueLength); + currentTagIndentifier.tagType = RichTextTagType.Margin; + calliString.GetSubString(ref textConfiguration.m_htmlTag, currentTagIndentifier.valueStartIndex, currentTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; - switch (richTextAttributes[i].unitType) + switch (richTextTagIndentifiers[i].unitType) { case TagUnitType.Pixels: textConfiguration.m_marginLeft = value * (baseConfiguration.isOrthographic ? 1 : 0.1f); @@ -1396,20 +1405,21 @@ internal static bool ValidateHtmlTag( textConfiguration.m_marginLeft = value * (baseConfiguration.isOrthographic ? 1 : 0.1f) * textConfiguration.m_currentFontSize; break; case TagUnitType.Percentage: - textConfiguration.m_marginLeft = (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; + textConfiguration.m_marginLeft = + (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; break; } textConfiguration.m_marginLeft = textConfiguration.m_marginLeft >= 0 ? textConfiguration.m_marginLeft : 0; break; - case 315620: // - currentAttribute.tagType = RichTextTagType.Margin; - calliString.GetSubString(ref textConfiguration.m_htmlTag, currentAttribute.valueStartIndex, currentAttribute.valueLength); + case 315620: // + currentTagIndentifier.tagType = RichTextTagType.Margin; + calliString.GetSubString(ref textConfiguration.m_htmlTag, currentTagIndentifier.valueStartIndex, currentTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; - switch (richTextAttributes[i].unitType) + switch (richTextTagIndentifiers[i].unitType) { case TagUnitType.Pixels: textConfiguration.m_marginRight = value * (baseConfiguration.isOrthographic ? 1 : 0.1f); @@ -1418,7 +1428,8 @@ internal static bool ValidateHtmlTag( textConfiguration.m_marginRight = value * (baseConfiguration.isOrthographic ? 1 : 0.1f) * textConfiguration.m_currentFontSize; break; case TagUnitType.Percentage: - textConfiguration.m_marginRight = (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; + textConfiguration.m_marginRight = + (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; break; } textConfiguration.m_marginRight = textConfiguration.m_marginRight >= 0 ? textConfiguration.m_marginRight : 0; @@ -1429,15 +1440,15 @@ internal static bool ValidateHtmlTag( } return false; - case 7639357: // - case 7011901: // - textConfiguration.m_marginLeft = 0; + case 7639357: // + case 7011901: // + textConfiguration.m_marginLeft = 0; textConfiguration.m_marginRight = 0; return true; - case 1100728678: // - case -855002522: // - firstAttribute.tagType = RichTextTagType.Margin; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 1100728678: // + case -855002522: // + firstTagIndentifier.tagType = RichTextTagType.Margin; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -1451,15 +1462,16 @@ internal static bool ValidateHtmlTag( textConfiguration.m_marginLeft = value * (baseConfiguration.isOrthographic ? 1 : 0.1f) * textConfiguration.m_currentFontSize; break; case TagUnitType.Percentage: - textConfiguration.m_marginLeft = (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; + textConfiguration.m_marginLeft = + (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; break; } textConfiguration.m_marginLeft = textConfiguration.m_marginLeft >= 0 ? textConfiguration.m_marginLeft : 0; return true; - case -884817987: // - case -1690034531: // - firstAttribute.tagType = RichTextTagType.Margin; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case -884817987: // + case -1690034531: // + firstTagIndentifier.tagType = RichTextTagType.Margin; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -1473,15 +1485,16 @@ internal static bool ValidateHtmlTag( textConfiguration.m_marginRight = value * (baseConfiguration.isOrthographic ? 1 : 0.1f) * textConfiguration.m_currentFontSize; break; case TagUnitType.Percentage: - textConfiguration.m_marginRight = (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; + textConfiguration.m_marginRight = + (textConfiguration.m_marginWidth - (textConfiguration.m_width != -1 ? textConfiguration.m_width : 0)) * value / 100; break; } textConfiguration.m_marginRight = textConfiguration.m_marginRight >= 0 ? textConfiguration.m_marginRight : 0; return true; - case 1109349752: // - case -842693512: // - firstAttribute.tagType = RichTextTagType.LineHeight; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 1109349752: // + case -842693512: // + firstTagIndentifier.tagType = RichTextTagType.LineHeight; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; @@ -1500,17 +1513,17 @@ internal static bool ValidateHtmlTag( break; } return true; - case -445573839: // - case 1897350193: // - textConfiguration.m_lineHeight = float.MinValue; //TMP_Math.FLOAT_UNSET -->is there a better way to do this? + case -445573839: // + case 1897350193: // + textConfiguration.m_lineHeight = float.MinValue; //TMP_Math.FLOAT_UNSET -->is there a better way to do this? return true; - case 15115642: // - case 10723418: // + case 15115642: // + case 10723418: // textConfiguration.tag_NoParsing = true; return true; //case 1913798: // //case 1286342: // - // int actionID = firstAttribute.valueHashCode; + // int actionID = firstTagIndentifier.valueHashCode; // if (m_isParsingText) // { @@ -1518,11 +1531,10 @@ internal static bool ValidateHtmlTag( // Debug.Log("Action ID: [" + actionID + "] First character index: " + m_characterCount); - // } // //if (m_isParsingText) // //{ - // // TMP_Action action = TMP_Action.GetAction(firstAttribute.valueHashCode); + // // TMP_Action action = TMP_Action.GetAction(firstTagIndentifier.valueHashCode); // //} // return true; //case 7443301: // @@ -1549,29 +1561,29 @@ internal static bool ValidateHtmlTag( //case 1015979: // // m_isFXMatrixSet = false; // return true; - case 2227963: // - case 1600507: // - // TODO: Add ability to use Random Rotation - firstAttribute.tagType = RichTextTagType.Rotate; - calliString.GetSubString(ref textConfiguration.m_htmlTag, firstAttribute.valueStartIndex, firstAttribute.valueLength); + case 2227963: // + case 1600507: // + // TODO: Add ability to use Random Rotation + firstTagIndentifier.tagType = RichTextTagType.Rotate; + calliString.GetSubString(ref textConfiguration.m_htmlTag, firstTagIndentifier.valueStartIndex, firstTagIndentifier.valueLength); // Reject tag if value is invalid. if (ConvertToFloat(ref textConfiguration.m_htmlTag, out value) != ParseError.None) return false; - textConfiguration.m_FXMatrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, value), Vector3.one); + textConfiguration.m_fxMatrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, value), Vector3.one); textConfiguration.m_isFXMatrixSet = true; return true; - case 7757466: // - case 7130010: // + case 7757466: // + case 7130010: // textConfiguration.m_isFXMatrixSet = false; return true; - case 317446: // - case 227814: //
- //switch (richTextAttributes[1].nameHashCode) + case 317446: //
+ case 227814: //
+ //switch (richTextTagIndentifiers[1].nameHashCode) //{ // case 327550: // width - // float tableWidth = ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextAttributes[1].valueStartIndex, richTextAttributes[1].valueLength); + // float tableWidth = ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextTagIndentifiers[1].valueStartIndex, richTextTagIndentifiers[1].valueLength); // // Reject tag if value is invalid. // if (tableWidth == Int16.MinValue) return false; @@ -1591,31 +1603,31 @@ internal static bool ValidateHtmlTag( // break; //} return false; - case 1107375: //
- case 1017743: // + case 1107375: // + case 1017743: // return true; - case 926: // - case 670: // + case 926: // + case 670: // return true; - case 3229: // - case 2973: // + case 3229: // + case 2973: // return true; - case 916: // - case 660: // - // Set style to bold and center alignment + case 916: // + case 660: // + // Set style to bold and center alignment return true; - case 3219: // - case 2963: // + case 3219: // + case 2963: // return true; - case 912: // - case 656: // + case 912: // + case 656: // // Style options - //for (int i = 1; i < richTextAttributes.Length && richTextAttributes[i].nameHashCode != 0; i++) + //for (int i = 1; i < richTextTagIndentifiers.Length && richTextTagIndentifiers[i].nameHashCode != 0; i++) //{ - // switch (richTextAttributes[i].nameHashCode) + // switch (richTextTagIndentifiers[i].nameHashCode) // { // case 327550: // width - // float tableWidth = ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextAttributes[i].valueStartIndex, richTextAttributes[i].valueLength); + // float tableWidth = ConvertToFloat(ref richtextAdjustments.m_htmlTag, richTextTagIndentifiers[i].valueStartIndex, richTextTagIndentifiers[i].valueLength); // switch (tagUnitType) // { @@ -1631,7 +1643,7 @@ internal static bool ValidateHtmlTag( // } // break; // case 275917: // align - // switch (richTextAttributes[i].valueHashCode) + // switch (richTextTagIndentifiers[i].valueHashCode) // { // case 3774683: // left // Debug.Log("TD align=\"left\"."); @@ -1651,8 +1663,8 @@ internal static bool ValidateHtmlTag( //} return false; - case 3215: // - case 2959: // + case 3215: // + case 2959: // return false; } } @@ -1662,7 +1674,6 @@ internal static bool ValidateHtmlTag( return false; } - /// /// Extracts a float value from char[] given a start index and length. /// @@ -1670,12 +1681,12 @@ internal static bool ValidateHtmlTag( /// static ParseError ConvertToFloat(ref FixedString128Bytes htmlTag, out float value) { - value = 0; + value = 0; int subOffset = 0; return htmlTag.Parse(ref subOffset, ref value); } /// - /// Method to convert Hex color values to Color32. Accessing CalliString via indexer[] + /// Method to convert Hex color values to Color32. Accessing CalliString via indexer[] /// works only when CalliString consists ONLY of 1 byte chars!!! /// /// diff --git a/Calligraphics/Internal/RichText/RichTextTagIdentifier.cs b/Calligraphics/Internal/RichText/RichTextTagIdentifier.cs new file mode 100644 index 0000000..5b28226 --- /dev/null +++ b/Calligraphics/Internal/RichText/RichTextTagIdentifier.cs @@ -0,0 +1,27 @@ +namespace Latios.Calligraphics.RichText +{ + // Often referred to as "attributes" though we refrain from that term due to aliasing + // with both graphics attributes and C# language attributes. + internal struct RichTextTagIdentifier + { + public int nameHashCode; + public int valueHashCode; + public int valueStartIndex; //bytes position, not char! + public int valueLength; //byte length, not char! + public TagUnitType unitType; + public TagValueType valueType; + public RichTextTagType tagType; + + public static RichTextTagIdentifier Empty => new RichTextTagIdentifier + { + tagType = RichTextTagType.INVALID, + nameHashCode = 0, + valueHashCode = 0, + valueStartIndex = 0, + valueLength = 0, + valueType = TagValueType.None, + unitType = TagUnitType.Pixels, + }; + } +} + diff --git a/Calligraphics/Internal/RichText/RichTextAttribute.cs.meta b/Calligraphics/Internal/RichText/RichTextTagIdentifier.cs.meta similarity index 100% rename from Calligraphics/Internal/RichText/RichTextAttribute.cs.meta rename to Calligraphics/Internal/RichText/RichTextTagIdentifier.cs.meta diff --git a/Calligraphics/Internal/RichText/RichTextTagType.cs b/Calligraphics/Internal/RichText/RichTextTagType.cs index eb50701..9be3a84 100644 --- a/Calligraphics/Internal/RichText/RichTextTagType.cs +++ b/Calligraphics/Internal/RichText/RichTextTagType.cs @@ -1,6 +1,6 @@ -namespace Latios.Calligraphics +namespace Latios.Calligraphics.RichText { - public enum RichTextTagType : byte + internal enum RichTextTagType : byte { INVALID, Anchor, // diff --git a/Calligraphics/Internal/RichText/TagUnitType.cs b/Calligraphics/Internal/RichText/TagUnitType.cs index f128497..e32b3c5 100644 --- a/Calligraphics/Internal/RichText/TagUnitType.cs +++ b/Calligraphics/Internal/RichText/TagUnitType.cs @@ -1,6 +1,6 @@ -namespace Latios.Calligraphics +namespace Latios.Calligraphics.RichText { - public enum TagUnitType + internal enum TagUnitType : byte { Pixels = 0x0, FontUnits = 0x1, diff --git a/Calligraphics/Internal/RichText/TagValueType.cs b/Calligraphics/Internal/RichText/TagValueType.cs index dd811ce..57b0fc5 100644 --- a/Calligraphics/Internal/RichText/TagValueType.cs +++ b/Calligraphics/Internal/RichText/TagValueType.cs @@ -1,6 +1,6 @@ -namespace Latios.Calligraphics +namespace Latios.Calligraphics.RichText { - public enum TagValueType : byte + internal enum TagValueType : byte { None, NumericalValue, diff --git a/Calligraphics/Internal/TextConfiguration.cs b/Calligraphics/Internal/TextConfiguration.cs new file mode 100644 index 0000000..557ca8a --- /dev/null +++ b/Calligraphics/Internal/TextConfiguration.cs @@ -0,0 +1,140 @@ +using Latios.Calligraphics.RichText; +using Unity.Collections; +using UnityEngine; +using UnityEngine.TextCore.Text; + +namespace Latios.Calligraphics +{ + internal struct TextConfiguration + { + /// + /// m_htmlTag is a scratchpad for storing substrings prior to parsing. + /// Would not be needed if CalliString.SubString() would work...does not for some reason + /// (error that underlying Callibyte buffer is null) + /// + public FixedString128Bytes m_htmlTag; + + //metrics + public float m_fontScaleMultiplier; // Used for handling of superscript and subscript. + public float m_currentFontSize; + public FixedStack512Bytes m_sizeStack; + + public FontStyles m_fontStyleInternal; + public FontWeight m_fontWeightInternal; + public FontStyleStack m_fontStyleStack; + public FixedStack512Bytes m_fontWeightStack; + + public HorizontalAlignmentOptions m_lineJustification; + public FixedStack512Bytes m_lineJustificationStack; + + public float m_baselineOffset; + public FixedStack512Bytes m_baselineOffsetStack; + + public Color32 m_htmlColor; + public Color32 m_underlineColor; + public Color32 m_strikethroughColor; + + public FixedStack512Bytes m_colorStack; + public FixedStack512Bytes m_strikethroughColorStack; + public FixedStack512Bytes m_underlineColorStack; + + public short m_italicAngle; + public FixedStack512Bytes m_italicAngleStack; + + public float m_lineOffset; + public float m_lineHeight; + + public float m_cSpacing; + public float m_monoSpacing; + public float m_xAdvance; + + public float tag_LineIndent; + public float tag_Indent; + public FixedStack512Bytes m_indentStack; + public bool tag_NoParsing; + + public float m_marginWidth; + public float m_marginHeight; + public float m_marginLeft; + public float m_marginRight; + public float m_width; + + public bool m_isNonBreakingSpace; + + public bool m_isParsingText; + + public Matrix4x4 m_fxMatrix; + public bool m_isFXMatrixSet; + + public FixedStack512Bytes m_highlightStateStack; + public int m_characterCount; + + public TextConfiguration(TextBaseConfiguration textBaseConfiguration) + { + m_htmlTag = new FixedString128Bytes(); + + m_fontScaleMultiplier = 1; + m_currentFontSize = textBaseConfiguration.fontSize; + m_sizeStack = new FixedStack512Bytes(); + m_sizeStack.Add(m_currentFontSize); + + m_fontStyleInternal = textBaseConfiguration.fontStyle; + m_fontWeightInternal = (m_fontStyleInternal & FontStyles.Bold) == FontStyles.Bold ? FontWeight.Bold : textBaseConfiguration.fontWeight; + m_fontWeightStack = new FixedStack512Bytes(); + m_fontWeightStack.Add(m_fontWeightInternal); + m_fontStyleStack = new FontStyleStack(); + + m_lineJustification = textBaseConfiguration.lineJustification; + m_lineJustificationStack = new FixedStack512Bytes(); + m_lineJustificationStack.Add(m_lineJustification); + + m_baselineOffset = 0; + m_baselineOffsetStack = new FixedStack512Bytes(); + m_baselineOffsetStack.Add(0); + + m_htmlColor = textBaseConfiguration.color; + m_underlineColor = Color.white; + m_strikethroughColor = Color.white; + + m_colorStack = new FixedStack512Bytes(); + m_colorStack.Add(m_htmlColor); + m_underlineColorStack = new FixedStack512Bytes(); + m_underlineColorStack.Add(m_htmlColor); + m_strikethroughColorStack = new FixedStack512Bytes(); + m_strikethroughColorStack.Add(m_htmlColor); + + m_italicAngle = 0; + m_italicAngleStack = new FixedStack512Bytes(); + + m_lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). + m_lineHeight = float.MinValue; //TMP_Math.FLOAT_UNSET -->is there a better way to do this? + + m_cSpacing = 0; // Amount of space added between characters as a result of the use of the tag. + m_monoSpacing = 0; + m_xAdvance = 0; // Used to track the position of each character. + + tag_LineIndent = 0; // Used for indentation of text. + tag_Indent = 0; + m_indentStack = new FixedStack512Bytes(); + m_indentStack.Add(tag_Indent); + tag_NoParsing = false; + + m_marginWidth = 0; + m_marginHeight = 0; + m_marginLeft = 0; + m_marginRight = 0; + m_width = -1; + + m_isNonBreakingSpace = false; + + m_isParsingText = false; + m_fxMatrix = Matrix4x4.identity; + m_isFXMatrixSet = false; + + m_highlightStateStack = new FixedStack512Bytes(); + + m_characterCount = 0; // Total characters in the CalliString + } + } +} + diff --git a/Calligraphics/Components/TextConfiguration.cs.meta b/Calligraphics/Internal/TextConfiguration.cs.meta similarity index 100% rename from Calligraphics/Components/TextConfiguration.cs.meta rename to Calligraphics/Internal/TextConfiguration.cs.meta diff --git a/Calligraphics/ShaderLibrary/TextGlyphParsing.hlsl b/Calligraphics/ShaderLibrary/TextGlyphParsing.hlsl index 31efd83..2583286 100644 --- a/Calligraphics/ShaderLibrary/TextGlyphParsing.hlsl +++ b/Calligraphics/ShaderLibrary/TextGlyphParsing.hlsl @@ -14,9 +14,10 @@ struct GlyphVertex #if defined(UNITY_DOTS_INSTANCING_ENABLED) uniform ByteAddressBuffer _latiosTextBuffer; +uniform ByteAddressBuffer _latiosTextMaskBuffer; #endif -GlyphVertex sampleGlyph(uint vertexId, uint textBase, uint glyphCount) +GlyphVertex sampleGlyph(uint vertexId, uint textBase, uint glyphCount, uint maskBase) { GlyphVertex vertex = (GlyphVertex)0; #if defined(UNITY_DOTS_INSTANCING_ENABLED) @@ -26,15 +27,27 @@ GlyphVertex sampleGlyph(uint vertexId, uint textBase, uint glyphCount) vertex.position = asfloat(~0u); return vertex; } + uint glyphBase = 96 * (textBase + (vertexId >> 2)); + if (maskBase > 0) + { + uint mask = _latiosTextMaskBuffer.Load(4 * (maskBase + (vertexId >> 6))); + uint bit = (vertexId >> 2) & 0xf; + bit += 16; + if ((mask & (1 << bit)) == 0) + { + vertex.position = asfloat(~0u); + return vertex; + } + bit -= 16; + glyphBase = 96 * (textBase + (mask & 0xffff) + bit); + } const bool isBottomLeft = (vertexId & 0x3) == 0; const bool isTopLeft = (vertexId & 0x3) == 1; const bool isTopRight = (vertexId & 0x3) == 2; const bool isBottomRight = (vertexId & 0x3) == 3; - const uint glyphBase = 96 * (textBase + (vertexId >> 2)); const uint4 glyphMeta = _latiosTextBuffer.Load4(glyphBase + 80); - vertex.normal = float3(0, 0, -1); vertex.tangent = float3(1, 0, 0); diff --git a/Calligraphics/Systems/AnimateTextTransitionSystem.cs b/Calligraphics/Systems/AnimateTextTransitionSystem.cs index 3531b27..c1e5a3b 100644 --- a/Calligraphics/Systems/AnimateTextTransitionSystem.cs +++ b/Calligraphics/Systems/AnimateTextTransitionSystem.cs @@ -1,6 +1,5 @@ using Latios.Calligraphics.Rendering; using Latios.Calligraphics.RichText; -using Latios.Calligraphics.RichText.Parsing; using Unity.Burst; using Unity.Burst.Intrinsics; using Unity.Collections; diff --git a/Calligraphics/Systems/GenerateGlyphsSystem.cs b/Calligraphics/Systems/GenerateGlyphsSystem.cs index 249ac2f..cdfd327 100644 --- a/Calligraphics/Systems/GenerateGlyphsSystem.cs +++ b/Calligraphics/Systems/GenerateGlyphsSystem.cs @@ -3,6 +3,7 @@ using Unity.Burst.Intrinsics; using Unity.Collections; using Unity.Entities; +using Unity.Mathematics; using static Unity.Entities.SystemAPI; @@ -14,19 +15,21 @@ namespace Latios.Calligraphics.Systems [DisableAutoCreation] public partial struct GenerateGlyphsSystem : ISystem { - EntityQuery m_singleFontQuery; - EntityQuery m_multiFontQuery; + EntityQuery m_query; + + bool m_skipChangeFilter; [BurstCompile] public void OnCreate(ref SystemState state) { - m_singleFontQuery = state.Fluent() - .With(true) - .With(false) - .With(true) - .With(true) - .With(false) - .Build(); + m_query = state.Fluent() + .With( true) + .With( false) + .With( true) + .With(true) + .With( false) + .Build(); + m_skipChangeFilter = (state.WorldUnmanaged.Flags & WorldFlags.Editor) == WorldFlags.Editor; } [BurstCompile] @@ -34,54 +37,84 @@ public void OnUpdate(ref SystemState state) { state.Dependency = new Job { - calliByteHandle = GetBufferTypeHandle(true), - fontBlobReferenceHandle = GetComponentTypeHandle(true), - glyphMappingElementHandle = GetBufferTypeHandle(false), - glyphMappingMaskHandle = GetComponentTypeHandle(true), - renderGlyphHandle = GetBufferTypeHandle(false), + additionalEntitiesHandle = GetBufferTypeHandle(true), + calliByteHandle = GetBufferTypeHandle(true), + fontBlobReferenceHandle = GetComponentTypeHandle(true), + fontBlobReferenceLookup = GetComponentLookup(true), + glyphMappingElementHandle = GetBufferTypeHandle(false), + glyphMappingMaskHandle = GetComponentTypeHandle(true), + lastSystemVersion = m_skipChangeFilter ? 0 : state.LastSystemVersion, + renderGlyphHandle = GetBufferTypeHandle(false), + selectorHandle = GetBufferTypeHandle(false), textBaseConfigurationHandle = GetComponentTypeHandle(true), - textRenderControlHandle = GetComponentTypeHandle(false), - }.ScheduleParallel(m_singleFontQuery, state.Dependency); + textRenderControlHandle = GetComponentTypeHandle(false), + }.ScheduleParallel(m_query, state.Dependency); } [BurstCompile] public partial struct Job : IJobChunk { - public BufferTypeHandle renderGlyphHandle; - public BufferTypeHandle glyphMappingElementHandle; - public ComponentTypeHandle textRenderControlHandle; + public BufferTypeHandle renderGlyphHandle; + public BufferTypeHandle glyphMappingElementHandle; + public BufferTypeHandle selectorHandle; + public ComponentTypeHandle textRenderControlHandle; + + [ReadOnly] public ComponentTypeHandle glyphMappingMaskHandle; + [ReadOnly] public BufferTypeHandle calliByteHandle; + [ReadOnly] public ComponentTypeHandle textBaseConfigurationHandle; + [ReadOnly] public ComponentTypeHandle fontBlobReferenceHandle; + [ReadOnly] public BufferTypeHandle additionalEntitiesHandle; + [ReadOnly] public ComponentLookup fontBlobReferenceLookup; - [ReadOnly] public ComponentTypeHandle glyphMappingMaskHandle; - [ReadOnly] public BufferTypeHandle calliByteHandle; - [ReadOnly] public ComponentTypeHandle textBaseConfigurationHandle; - [ReadOnly] public ComponentTypeHandle fontBlobReferenceHandle; + public uint lastSystemVersion; private GlyphMappingWriter m_glyphMappingWriter; [BurstCompile] public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { - var calliBytesBuffers = chunk.GetBufferAccessor(ref calliByteHandle); - var renderGlyphBuffers = chunk.GetBufferAccessor(ref renderGlyphHandle); - var glyphMappingBuffers = chunk.GetBufferAccessor(ref glyphMappingElementHandle); - var glyphMappingMasks = chunk.GetNativeArray(ref glyphMappingMaskHandle); + if (!(chunk.DidChange(ref glyphMappingMaskHandle, lastSystemVersion) || + chunk.DidChange(ref calliByteHandle, lastSystemVersion) || + chunk.DidChange(ref textBaseConfigurationHandle, lastSystemVersion) || + chunk.DidChange(ref fontBlobReferenceHandle, lastSystemVersion))) + return; + + var calliBytesBuffers = chunk.GetBufferAccessor(ref calliByteHandle); + var renderGlyphBuffers = chunk.GetBufferAccessor(ref renderGlyphHandle); + var glyphMappingBuffers = chunk.GetBufferAccessor(ref glyphMappingElementHandle); + var glyphMappingMasks = chunk.GetNativeArray(ref glyphMappingMaskHandle); var textBaseConfigurations = chunk.GetNativeArray(ref textBaseConfigurationHandle); - var fontBlobReferences = chunk.GetNativeArray(ref fontBlobReferenceHandle); - var textRenderControls = chunk.GetNativeArray(ref textRenderControlHandle); + var fontBlobReferences = chunk.GetNativeArray(ref fontBlobReferenceHandle); + var textRenderControls = chunk.GetNativeArray(ref textRenderControlHandle); + + // Optional + var selectorBuffers = chunk.GetBufferAccessor(ref selectorHandle); + var additionalEntitiesBuffers = chunk.GetBufferAccessor(ref additionalEntitiesHandle); + bool hasMultipleFonts = selectorBuffers.Length > 0 && additionalEntitiesBuffers.Length > 0; + + FontMaterialSet fontMaterialSet = default; for (int indexInChunk = 0; indexInChunk < chunk.Count; indexInChunk++) { - var calliBytes = calliBytesBuffers[indexInChunk]; - var renderGlyphs = renderGlyphBuffers[indexInChunk]; - var fontBlobReference = fontBlobReferences[indexInChunk]; + var calliBytes = calliBytesBuffers[indexInChunk]; + var renderGlyphs = renderGlyphBuffers[indexInChunk]; + var fontBlobReference = fontBlobReferences[indexInChunk]; var textBaseConfiguration = textBaseConfigurations[indexInChunk]; - var textRenderControl = textRenderControls[indexInChunk]; + var textRenderControl = textRenderControls[indexInChunk]; m_glyphMappingWriter.StartWriter(glyphMappingMasks.Length > 0 ? glyphMappingMasks[indexInChunk].mask : default); + if (hasMultipleFonts) + { + fontMaterialSet.Initialize(fontBlobReference.blob, selectorBuffers[indexInChunk], additionalEntitiesBuffers[indexInChunk], ref fontBlobReferenceLookup); + } + else + { + fontMaterialSet.Initialize(fontBlobReference.blob); + } GlyphGeneration.CreateRenderGlyphs(ref renderGlyphs, ref m_glyphMappingWriter, - ref fontBlobReference.blob.Value, + ref fontMaterialSet, in calliBytes, in textBaseConfiguration); @@ -91,7 +124,7 @@ public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useE m_glyphMappingWriter.EndWriter(ref mapping, renderGlyphs.Length); } - textRenderControl.flags = TextRenderControl.Flags.Dirty; + textRenderControl.flags = TextRenderControl.Flags.Dirty; textRenderControls[indexInChunk] = textRenderControl; } } diff --git a/Calligraphics/Systems/Rendering/TextRenderingDispatchSystem.cs b/Calligraphics/Systems/Rendering/TextRenderingDispatchSystem.cs index b7bd1c0..3c96aea 100644 --- a/Calligraphics/Systems/Rendering/TextRenderingDispatchSystem.cs +++ b/Calligraphics/Systems/Rendering/TextRenderingDispatchSystem.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using Latios.Kinemation; using Latios.Kinemation.Systems; using Unity.Burst; @@ -22,9 +23,13 @@ namespace Latios.Calligraphics.Rendering.Systems [DisableAutoCreation] public partial class TextRenderingDispatchSystem : CullingComputeDispatchSubSystemBase { - ComputeShader m_uploadShader; + ComputeShader m_uploadGlyphsShader; + ComputeShader m_uploadMasksShader; - EntityQuery m_query; + EntityQuery m_glyphsQuery; + EntityQuery m_masksQuery; + EntityQuery m_allQuery; + EntityQuery m_glyphsAndMasksQuery; // Shader bindings int _src; @@ -33,28 +38,42 @@ public partial class TextRenderingDispatchSystem : CullingComputeDispatchSubSyst int _meta; int _latiosTextBuffer; + int _latiosTextMaskBuffer; - static GraphicsBufferBroker.StaticID kGlyphsBufferID = GraphicsBufferBroker.ReservePersistentBuffer(); - static GraphicsBufferBroker.StaticID kGlyphsUploadID = GraphicsBufferBroker.ReserveUploadPool(); + static GraphicsBufferBroker.StaticID kGlyphsBufferID = GraphicsBufferBroker.ReservePersistentBuffer(); + static GraphicsBufferBroker.StaticID kGlyphsUploadID = GraphicsBufferBroker.ReserveUploadPool(); + static GraphicsBufferBroker.StaticID kGlyphMasksBufferID = GraphicsBufferBroker.ReservePersistentBuffer(); + static GraphicsBufferBroker.StaticID kGlyphMasksUploadID = GraphicsBufferBroker.ReserveUploadPool(); protected override void OnCreate() { - m_query = Fluent.With(true).With(true).With(true) - .With(false, true).With(true, true).Build(); + m_glyphsQuery = Fluent.With(true).With(false) + .With( true, true).Build(); + m_masksQuery = Fluent.With(false).With(true) + .With( true, true).Build(); + m_allQuery = Fluent.WithAnyEnabled(true).With(true) + .With( false, true).With(true, true).Build(); + m_glyphsAndMasksQuery = Fluent.With(true) + .With(true) + .With( true, true).Build(); var copyByteAddressShader = Resources.Load("CopyBytes"); - m_uploadShader = Resources.Load("UploadGlyphs"); + m_uploadGlyphsShader = Resources.Load("UploadGlyphs"); + m_uploadMasksShader = Resources.Load("UploadBytes"); _src = Shader.PropertyToID("_src"); _dst = Shader.PropertyToID("_dst"); _startOffset = Shader.PropertyToID("_startOffset"); _meta = Shader.PropertyToID("_meta"); _latiosTextBuffer = Shader.PropertyToID("_latiosTextBuffer"); + _latiosTextMaskBuffer = Shader.PropertyToID("_latiosTextMaskBuffer"); if (!worldBlackboardEntity.HasManagedStructComponent()) throw new System.InvalidOperationException("Calligraphics must be installed after Kinemation."); var broker = worldBlackboardEntity.GetManagedStructComponent().graphicsBufferBroker; - broker.InitializePersistentBuffer(kGlyphsBufferID, 128, 4, GraphicsBuffer.Target.Raw, copyByteAddressShader); + broker.InitializePersistentBuffer(kGlyphsBufferID, 128 * 96, 4, GraphicsBuffer.Target.Raw, copyByteAddressShader); broker.InitializeUploadPool(kGlyphsUploadID, 4, GraphicsBuffer.Target.Raw); + broker.InitializePersistentBuffer(kGlyphMasksBufferID, 128 * 4, 4, GraphicsBuffer.Target.Raw, copyByteAddressShader); + broker.InitializeUploadPool(kGlyphMasksUploadID, 4, GraphicsBuffer.Target.Raw); } protected override IEnumerable UpdatePhase() @@ -69,38 +88,111 @@ protected override IEnumerable UpdatePhase() if (terminate) break; - int textIndex = worldBlackboardEntity.GetBuffer(true).Reinterpret() - .AsNativeArray().IndexOf(ComponentType.ReadOnly()); + var materials = worldBlackboardEntity.GetBuffer(true).Reinterpret().AsNativeArray(); + int textIndex = materials.IndexOf(ComponentType.ReadOnly()); ulong textMaterialMaskLower = (ulong)textIndex >= 64UL ? 0UL : (1UL << textIndex); ulong textMaterialMaskUpper = (ulong)textIndex >= 64UL ? (1UL << (textIndex - 64)) : 0UL; + int fontIndex = materials.IndexOf(ComponentType.ReadOnly()); + ulong fontMaterialMaskLower = (ulong)fontIndex >= 64UL ? 0UL : (1U << fontIndex); + ulong fontMaterialMaskUpper = (ulong)fontIndex >= 64UL ? (1UL << (fontIndex - 64)) : 0UL; - var streamCount = CollectionHelper.CreateNativeArray(1, WorldUpdateAllocator); - streamCount[0] = m_query.CalculateChunkCountWithoutFiltering(); - var streamConstructJh = NativeStream.ScheduleConstruct(out var stream, streamCount, default, WorldUpdateAllocator); - var collectJh = new GatherUploadOperationsJob + var materialMasksJh = new UpdateMaterialMasksJob { - glyphCountThisFrameLookup = SystemAPI.GetComponentLookup(false), - glyphCountThisPass = 0, + glyphMasksHandle = SystemAPI.GetBufferTypeHandle(true), + glyphMaterialMaskLower = textMaterialMaskLower, + glyphMaterialMaskUpper = textMaterialMaskUpper, glyphsHandle = SystemAPI.GetBufferTypeHandle(true), - materialMaskLower = textMaterialMaskLower, - materialMaskUpper = textMaterialMaskUpper, + maskMaterialMaskLower = fontMaterialMaskLower, + maskMaterialMaskUpper = fontMaterialMaskUpper, materialPropertyDirtyMaskHandle = SystemAPI.GetComponentTypeHandle(false), perCameraMaskHandle = SystemAPI.GetComponentTypeHandle(false), - perFrameMaskHandle = SystemAPI.GetComponentTypeHandle(true), - streamWriter = stream.AsWriter(), - textShaderIndexHandle = SystemAPI.GetComponentTypeHandle(false), - trcHandle = SystemAPI.GetComponentTypeHandle(true), - worldBlackboardEntity = worldBlackboardEntity - }.Schedule(m_query, JobHandle.CombineDependencies(streamConstructJh, Dependency)); - - var payloads = new NativeList(1, WorldUpdateAllocator); - var requiredUploadBufferSize = new NativeReference(WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory); - Dependency = new MapPayloadsToUploadBufferJob + perFrameMaskHandle = SystemAPI.GetComponentTypeHandle(true) + }.ScheduleParallel(m_allQuery, Dependency); + + var foundChildrenDependenciesJh = materialMasksJh; + var glyphsWithChildrenCount = m_glyphsAndMasksQuery.CalculateChunkCountWithoutFiltering(); + var map = new NativeParallelHashMap(glyphsWithChildrenCount, WorldUpdateAllocator); + if (glyphsWithChildrenCount > 0) + { + foundChildrenDependenciesJh = new FindCulledGlyphHoldersWithVisibleChildrenJob + { + additionalEntitiesHandle = SystemAPI.GetBufferTypeHandle(true), + esil = SystemAPI.GetEntityStorageInfoLookup(), + map = map.AsParallelWriter(), + perCameraMaskHandle = SystemAPI.GetComponentTypeHandle(true), + perFrameMaskHandle = SystemAPI.GetComponentTypeHandle(true) + }.ScheduleParallel(m_glyphsAndMasksQuery, materialMasksJh); + } + + var glyphStreamCount = CollectionHelper.CreateNativeArray(1, WorldUpdateAllocator); + glyphStreamCount[0] = m_glyphsQuery.CalculateChunkCountWithoutFiltering(); + var glyphStreamConstructJh = NativeStream.ScheduleConstruct(out var glyphStream, glyphStreamCount, default, WorldUpdateAllocator); + var collectGlyphsJh = new GatherGlyphUploadOperationsJob + { + additonalEntitiesHandle = SystemAPI.GetBufferTypeHandle(true), + glyphCountThisFrameLookup = SystemAPI.GetComponentLookup(false), + glyphCountThisPass = 0, + glyphsHandle = SystemAPI.GetBufferTypeHandle(true), + glyphMaskHandle = SystemAPI.GetBufferTypeHandle(true), + map = map, + perCameraMaskHandle = SystemAPI.GetComponentTypeHandle(true), + perFrameMaskHandle = SystemAPI.GetComponentTypeHandle(true), + streamWriter = glyphStream.AsWriter(), + textShaderIndexHandle = SystemAPI.GetComponentTypeHandle(false), + trcHandle = SystemAPI.GetComponentTypeHandle(true), + worldBlackboardEntity = worldBlackboardEntity + }.Schedule(m_glyphsQuery, JobHandle.CombineDependencies(glyphStreamConstructJh, foundChildrenDependenciesJh)); + + var glyphPayloads = new NativeList(1, WorldUpdateAllocator); + var requiredGlyphUploadBufferSize = new NativeReference(WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory); + var finalFirstPhaseJh = new MapPayloadsToUploadBufferJob { - streamReader = stream.AsReader(), - payloads = payloads, - requiredUploadBufferSize = requiredUploadBufferSize - }.Schedule(collectJh); + streamReader = glyphStream.AsReader(), + payloads = glyphPayloads, + requiredUploadBufferSize = requiredGlyphUploadBufferSize + }.Schedule(collectGlyphsJh); + + var maskPayloads = new NativeList(1, WorldUpdateAllocator); + var requiredMaskUploadBufferSize = new NativeReference(WorldUpdateAllocator, NativeArrayOptions.UninitializedMemory); + + if (glyphsWithChildrenCount > 0) + { + var maskStreamCount = CollectionHelper.CreateNativeArray(1, WorldUpdateAllocator); + maskStreamCount[0] = m_masksQuery.CalculateChunkCountWithoutFiltering(); + var maskStreamConstructJh = NativeStream.ScheduleConstruct(out var maskStream, maskStreamCount, default, WorldUpdateAllocator); + + var collectMasksJh = new GatherMaskUploadOperationsJob + { + glyphMasksHandle = SystemAPI.GetBufferTypeHandle(true), + maskCountThisFrameLookup = SystemAPI.GetComponentLookup(false), + maskCountThisPass = 0, + maskShaderIndexHandle = SystemAPI.GetComponentTypeHandle(false), + perCameraMaskHandle = SystemAPI.GetComponentTypeHandle(true), + perFrameMaskHandle = SystemAPI.GetComponentTypeHandle(true), + streamWriter = maskStream.AsWriter(), + worldBlackboardEntity = worldBlackboardEntity + }.Schedule(m_masksQuery, JobHandle.CombineDependencies(maskStreamConstructJh, materialMasksJh)); + + var batchMasksJh = new MapPayloadsToUploadBufferJob + { + streamReader = maskStream.AsReader(), + payloads = maskPayloads, + requiredUploadBufferSize = requiredMaskUploadBufferSize + }.Schedule(collectMasksJh); + + var copyPropertiesJh = new CopyGlyphShaderIndicesJob + { + additionalEntitiesHandle = SystemAPI.GetBufferTypeHandle(true), + perCameraMaskHandle = SystemAPI.GetComponentTypeHandle(true), + perFrameMaskHandle = SystemAPI.GetComponentTypeHandle(true), + shaderIndexHandle = SystemAPI.GetComponentTypeHandle(true), + shaderIndexLookup = SystemAPI.GetComponentLookup(false) + }.ScheduleParallel(m_glyphsAndMasksQuery, collectGlyphsJh); + + finalFirstPhaseJh = JobHandle.CombineDependencies(finalFirstPhaseJh, batchMasksJh, copyPropertiesJh); + } + + Dependency = finalFirstPhaseJh; // Fetching this now because culling jobs are still running (hopefully). var graphicsBroker = worldBlackboardEntity.GetManagedStructComponent().graphicsBufferBroker; @@ -112,7 +204,7 @@ protected override IEnumerable UpdatePhase() if (terminate) break; - if (payloads.IsEmpty) + if (glyphPayloads.IsEmpty) { // skip rest of loop. yield return true; @@ -126,43 +218,88 @@ protected override IEnumerable UpdatePhase() continue; } - var uploadBuffer = graphicsBroker.GetUploadBuffer(kGlyphsUploadID, math.max(requiredUploadBufferSize.Value, 128) * 24); - var metaBuffer = graphicsBroker.GetMetaUint4UploadBuffer((uint)payloads.Length); + var glyphUploadBuffer = graphicsBroker.GetUploadBuffer(kGlyphsUploadID, math.max(requiredGlyphUploadBufferSize.Value, 128) * 24); + var glyphMetaBuffer = graphicsBroker.GetMetaUint4UploadBuffer((uint)glyphPayloads.Length); - Dependency = new WriteUploadsToBuffersJob + var finalSecondPhaseJh = new WriteGlyphsUploadsToBuffersJob { - payloads = payloads.AsDeferredJobArray(), - glyphsUploadBuffer = uploadBuffer.LockBufferForWrite(0, (int)requiredUploadBufferSize.Value), - metaUploadBuffer = metaBuffer.LockBufferForWrite(0, payloads.Length) - }.Schedule(payloads, 1, Dependency); + payloads = glyphPayloads.AsDeferredJobArray(), + glyphsUploadBuffer = glyphUploadBuffer.LockBufferForWrite(0, (int)requiredGlyphUploadBufferSize.Value), + metaUploadBuffer = glyphMetaBuffer.LockBufferForWrite(0, glyphPayloads.Length) + }.Schedule(glyphPayloads, 1, Dependency); + + GraphicsBuffer maskUploadBuffer = default; + GraphicsBuffer maskMetaBuffer = default; + + if (glyphsWithChildrenCount > 0) + { + maskUploadBuffer = graphicsBroker.GetUploadBuffer(kGlyphMasksUploadID, math.max(requiredMaskUploadBufferSize.Value, 128)); + maskMetaBuffer = graphicsBroker.GetMetaUint3UploadBuffer((uint)maskPayloads.Length); + + var maskJh = new WriteMasksUploadsToBuffersJob + { + payloads = maskPayloads.AsDeferredJobArray(), + masksUploadBuffer = maskUploadBuffer.LockBufferForWrite(0, (int)requiredMaskUploadBufferSize.Value), + metaUploadBuffer = maskMetaBuffer.LockBufferForWrite(0, maskPayloads.Length) + }.Schedule(maskPayloads, 1, Dependency); + + finalSecondPhaseJh = JobHandle.CombineDependencies(finalSecondPhaseJh, maskJh); + } yield return true; if (!GetPhaseActions(CullingComputeDispatchState.Dispatch, out terminate)) continue; - uploadBuffer.UnlockBufferAfterWrite((int)requiredUploadBufferSize.Value); - metaBuffer.UnlockBufferAfterWrite(payloads.Length); + glyphUploadBuffer.UnlockBufferAfterWrite((int)requiredGlyphUploadBufferSize.Value); + glyphMetaBuffer.UnlockBufferAfterWrite(glyphPayloads.Length); + + if (glyphsWithChildrenCount > 0) + { + maskUploadBuffer.UnlockBufferAfterWrite((int)requiredMaskUploadBufferSize.Value); + maskMetaBuffer.UnlockBufferAfterWrite((int)maskPayloads.Length); + } if (terminate) break; - var persistentBuffer = graphicsBroker.GetPersistentBuffer(kGlyphsBufferID, - math.max(worldBlackboardEntity.GetComponentData().glyphCount, 128) * 24); - m_uploadShader.SetBuffer(0, _dst, persistentBuffer); - m_uploadShader.SetBuffer(0, _src, uploadBuffer); - m_uploadShader.SetBuffer(0, _meta, metaBuffer); + var persistentGlyphBuffer = graphicsBroker.GetPersistentBuffer(kGlyphsBufferID, + math.max(worldBlackboardEntity.GetComponentData().glyphCount, 128) * 24); + m_uploadGlyphsShader.SetBuffer(0, _dst, persistentGlyphBuffer); + m_uploadGlyphsShader.SetBuffer(0, _src, glyphUploadBuffer); + m_uploadGlyphsShader.SetBuffer(0, _meta, glyphMetaBuffer); - for (uint dispatchesRemaining = (uint)payloads.Length, offset = 0; dispatchesRemaining > 0;) + for (uint dispatchesRemaining = (uint)glyphPayloads.Length, offset = 0; dispatchesRemaining > 0;) { uint dispatchCount = math.min(dispatchesRemaining, 65535); - m_uploadShader.SetInt(_startOffset, (int)offset); - m_uploadShader.Dispatch(0, (int)dispatchCount, 1, 1); + m_uploadGlyphsShader.SetInt(_startOffset, (int)offset); + m_uploadGlyphsShader.Dispatch(0, (int)dispatchCount, 1, 1); offset += dispatchCount; dispatchesRemaining -= dispatchCount; } - Shader.SetGlobalBuffer(_latiosTextBuffer, persistentBuffer); + Shader.SetGlobalBuffer(_latiosTextBuffer, persistentGlyphBuffer); + + var persistentMaskBuffer = graphicsBroker.GetPersistentBuffer(kGlyphMasksBufferID, + math.max(worldBlackboardEntity.GetComponentData().maskCount, 128)); + + if (glyphsWithChildrenCount > 0) + { + m_uploadMasksShader.SetBuffer(0, _dst, persistentMaskBuffer); + m_uploadMasksShader.SetBuffer(0, _src, maskUploadBuffer); + m_uploadMasksShader.SetBuffer(0, _meta, maskMetaBuffer); + + for (uint dispatchesRemaining = (uint)maskPayloads.Length, offset = 0; dispatchesRemaining > 0;) + { + uint dispatchCount = math.min(dispatchesRemaining, 65535); + m_uploadMasksShader.SetInt(_startOffset, (int)offset); + m_uploadMasksShader.Dispatch(0, (int)dispatchCount, 1, 1); + offset += dispatchCount; + dispatchesRemaining -= dispatchCount; + } + + Shader.SetGlobalBuffer(_latiosTextMaskBuffer, persistentMaskBuffer); + } yield return true; } @@ -177,25 +314,20 @@ unsafe struct UploadPayload public uint controls; } - // Schedule Single + // Schedule Parallel [BurstCompile] - struct GatherUploadOperationsJob : IJobChunk + struct UpdateMaterialMasksJob : IJobChunk { [ReadOnly] public ComponentTypeHandle perFrameMaskHandle; - [ReadOnly] public ComponentTypeHandle trcHandle; [ReadOnly] public BufferTypeHandle glyphsHandle; + [ReadOnly] public BufferTypeHandle glyphMasksHandle; public ComponentTypeHandle perCameraMaskHandle; public ComponentTypeHandle materialPropertyDirtyMaskHandle; - public ComponentTypeHandle textShaderIndexHandle; - public ComponentLookup glyphCountThisFrameLookup; - public Entity worldBlackboardEntity; - - public uint glyphCountThisPass; - public ulong materialMaskLower; - public ulong materialMaskUpper; - - [NativeDisableParallelForRestriction] public NativeStream.Writer streamWriter; + public ulong glyphMaterialMaskLower; + public ulong glyphMaterialMaskUpper; + public ulong maskMaterialMaskLower; + public ulong maskMaterialMaskUpper; public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { @@ -206,34 +338,185 @@ public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bo if ((upper | lower) == 0) return; - ref var dirtyMask = ref chunk.GetChunkComponentRefRW(ref materialPropertyDirtyMaskHandle); - dirtyMask.lower.Value |= materialMaskLower; - dirtyMask.upper.Value |= materialMaskUpper; + ref var dirtyMask = ref chunk.GetChunkComponentRefRW(ref materialPropertyDirtyMaskHandle); + + var glyphMasksBuffers = chunk.GetBufferAccessor(ref glyphMasksHandle); + if (glyphMasksBuffers.Length > 0) + { + var enumerator = new ChunkEntityEnumerator(true, new v128(lower, upper), chunk.Count); + while (enumerator.NextEntityIndex(out int i)) + { + var buffer = glyphMasksBuffers[i]; + if (buffer.Length == 0) + { + ref var bitHolder = ref i >= 64 ? ref cameraMask.upper : ref cameraMask.lower; + bitHolder.SetBits(i % 64, false); + } + } + if ((cameraMask.upper.Value | cameraMask.lower.Value) != 0) + { + dirtyMask.lower.Value |= maskMaterialMaskLower; + dirtyMask.upper.Value |= maskMaterialMaskUpper; + dirtyMask.lower.Value |= glyphMaterialMaskLower; + dirtyMask.upper.Value |= glyphMaterialMaskUpper; + } + } + else + { + var glyphsBuffers = chunk.GetBufferAccessor(ref glyphsHandle); + var enumerator = new ChunkEntityEnumerator(true, new v128(lower, upper), chunk.Count); + while (enumerator.NextEntityIndex(out int i)) + { + var buffer = glyphsBuffers[i]; + if (buffer.Length == 0) + { + ref var bitHolder = ref i >= 64 ? ref cameraMask.upper : ref cameraMask.lower; + bitHolder.SetBits(i % 64, false); + } + } + if ((cameraMask.upper.Value | cameraMask.lower.Value) != 0) + { + dirtyMask.lower.Value |= glyphMaterialMaskLower; + dirtyMask.upper.Value |= glyphMaterialMaskUpper; + } + } + } + } + + // Schedule Parallel + [BurstCompile] + struct FindCulledGlyphHoldersWithVisibleChildrenJob : IJobChunk + { + [ReadOnly] public ComponentTypeHandle perFrameMaskHandle; + [ReadOnly] public BufferTypeHandle additionalEntitiesHandle; + [ReadOnly] public ComponentTypeHandle perCameraMaskHandle; + [ReadOnly] public EntityStorageInfoLookup esil; + + public NativeParallelHashMap.ParallelWriter map; + + public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) + { + var cameraMask = chunk.GetChunkComponentData(ref perCameraMaskHandle); + var frameMask = chunk.GetChunkComponentData(ref perFrameMaskHandle); + var lower = cameraMask.lower.Value & (~frameMask.lower.Value); + var upper = cameraMask.upper.Value & (~frameMask.upper.Value); + upper = ~upper; + lower = ~lower; + BitField64 lowerMask = default; + lowerMask.SetBits(0, true, math.min(chunk.Count, 64)); + BitField64 upperMask = default; + if (chunk.Count > 64) + upperMask.SetBits(0, true, chunk.Count - 64); + upper &= upperMask.Value; + lower &= lowerMask.Value; + + if ((upper | lower) == 0) + return; + + var entitiesBuffers = chunk.GetBufferAccessor(ref additionalEntitiesHandle); + var enumerator = new ChunkEntityEnumerator(true, new v128(lower, upper), chunk.Count); + while (enumerator.NextEntityIndex(out int i)) + { + bool survive = false; + foreach (var entity in entitiesBuffers[i]) + { + var info = esil[entity.entity]; + var childCameraMask = chunk.GetChunkComponentData(ref perCameraMaskHandle); + var childFrameMask = chunk.GetChunkComponentData(ref perFrameMaskHandle); + if (info.IndexInChunk >= 64) + { + if (!childFrameMask.upper.IsSet(info.IndexInChunk - 64) && childCameraMask.upper.IsSet(info.IndexInChunk - 64)) + { + survive = true; + break; + } + } + else + { + if (!childFrameMask.lower.IsSet(info.IndexInChunk) && childCameraMask.lower.IsSet(info.IndexInChunk)) + { + survive = true; + break; + } + } + } + if (!survive) + { + if (i >= 64) + upper ^= 1u << (i - 64); + else + lower ^= 1u << i; + } + } + if ((upper | lower) != 0) + { + map.TryAdd(chunk, new v128(lower, upper)); + } + } + } + + // Schedule Single + [BurstCompile] + struct GatherGlyphUploadOperationsJob : IJobChunk + { + [ReadOnly] public ComponentTypeHandle perFrameMaskHandle; + [ReadOnly] public ComponentTypeHandle trcHandle; + [ReadOnly] public BufferTypeHandle glyphsHandle; + [ReadOnly] public BufferTypeHandle glyphMaskHandle; + [ReadOnly] public ComponentTypeHandle perCameraMaskHandle; + [ReadOnly] public BufferTypeHandle additonalEntitiesHandle; + [ReadOnly] public NativeParallelHashMap map; + public ComponentTypeHandle textShaderIndexHandle; + public ComponentLookup glyphCountThisFrameLookup; + public Entity worldBlackboardEntity; + + public uint glyphCountThisPass; + + [NativeDisableParallelForRestriction] public NativeStream.Writer streamWriter; + + public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) + { + var cameraMask = chunk.GetChunkComponentData(ref perCameraMaskHandle); + var frameMask = chunk.GetChunkComponentData(ref perFrameMaskHandle); + var lower = cameraMask.lower.Value & (~frameMask.lower.Value); + var upper = cameraMask.upper.Value & (~frameMask.upper.Value); + + if (chunk.Has(ref additonalEntitiesHandle)) + { + // If any child is being rendered, we still need to upload glyphs. + if (map.TryGetValue(chunk, out var extraBits)) + { + lower |= extraBits.ULong0; + upper |= extraBits.ULong1; + } + } + if ((upper | lower) == 0) + return; ref var glyphCountThisFrame = ref glyphCountThisFrameLookup.GetRefRW(worldBlackboardEntity).ValueRW.glyphCount; streamWriter.BeginForEachIndex(unfilteredChunkIndex); var trcs = chunk.GetNativeArray(ref trcHandle); var glyphsBuffers = chunk.GetBufferAccessor(ref glyphsHandle); + var masksBuffers = chunk.GetBufferAccessor(ref glyphMaskHandle); var shaderIndices = chunk.GetNativeArray(ref textShaderIndexHandle); var enumerator = new ChunkEntityEnumerator(true, new v128(lower, upper), chunk.Count); while (enumerator.NextEntityIndex(out int i)) { - var trc = trcs[i].flags; - var buffer = glyphsBuffers[i]; + var trc = trcs[i].flags; + var buffer = glyphsBuffers[i]; + int glyphCount = buffer.Length; + if (masksBuffers.Length > 0) + { + glyphCount = masksBuffers[i].Length * 16; + } shaderIndices[i] = new TextShaderIndex { firstGlyphIndex = glyphCountThisFrame, - glyphCount = (uint)buffer.Length + glyphCount = (uint)glyphCount }; - if (buffer.Length == 0) - { - ref var bitHolder = ref i >= 64 ? ref cameraMask.upper : ref cameraMask.lower; - bitHolder.SetBits(i % 64, false); - } - streamWriter.Write(new UploadPayload { ptr = buffer.GetUnsafeReadOnlyPtr(), @@ -250,6 +533,94 @@ public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bo } } + // Schedule Single + [BurstCompile] + struct GatherMaskUploadOperationsJob : IJobChunk + { + [ReadOnly] public ComponentTypeHandle perFrameMaskHandle; + [ReadOnly] public BufferTypeHandle glyphMasksHandle; + [ReadOnly] public ComponentTypeHandle perCameraMaskHandle; + public ComponentTypeHandle maskShaderIndexHandle; + public ComponentLookup maskCountThisFrameLookup; + public Entity worldBlackboardEntity; + + public uint maskCountThisPass; + + [NativeDisableParallelForRestriction] public NativeStream.Writer streamWriter; + + public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) + { + ref var cameraMask = ref chunk.GetChunkComponentRefRW(ref perCameraMaskHandle); + var frameMask = chunk.GetChunkComponentData(ref perFrameMaskHandle); + var lower = cameraMask.lower.Value & (~frameMask.lower.Value); + var upper = cameraMask.upper.Value & (~frameMask.upper.Value); + if ((upper | lower) == 0) + return; + + ref var maskCountThisFrame = ref maskCountThisFrameLookup.GetRefRW(worldBlackboardEntity).ValueRW.maskCount; + + streamWriter.BeginForEachIndex(unfilteredChunkIndex); + var glyphMasksBuffers = chunk.GetBufferAccessor(ref glyphMasksHandle); + var shaderIndices = chunk.GetNativeArray(ref maskShaderIndexHandle); + + var enumerator = new ChunkEntityEnumerator(true, new v128(lower, upper), chunk.Count); + while (enumerator.NextEntityIndex(out int i)) + { + var buffer = glyphMasksBuffers[i]; + shaderIndices[i] = new TextMaterialMaskShaderIndex + { + firstMaskIndex = maskCountThisFrame + }; + + streamWriter.Write(new UploadPayload + { + ptr = buffer.GetUnsafeReadOnlyPtr(), + length = (uint)buffer.Length, + uploadBufferStart = maskCountThisPass, + persistentBufferStart = maskCountThisFrame, + controls = 0 + }); + maskCountThisPass += (uint)buffer.Length; + maskCountThisFrame += (uint)buffer.Length; + } + + streamWriter.EndForEachIndex(); + } + } + + // Schedule Parallel + [BurstCompile] + struct CopyGlyphShaderIndicesJob : IJobChunk + { + [ReadOnly] public ComponentTypeHandle perFrameMaskHandle; + [ReadOnly] public ComponentTypeHandle perCameraMaskHandle; + [ReadOnly] public ComponentTypeHandle shaderIndexHandle; + [ReadOnly] public BufferTypeHandle additionalEntitiesHandle; + [NativeDisableContainerSafetyRestriction] public ComponentLookup shaderIndexLookup; + + public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) + { + ref var cameraMask = ref chunk.GetChunkComponentRefRW(ref perCameraMaskHandle); + var frameMask = chunk.GetChunkComponentData(ref perFrameMaskHandle); + var lower = cameraMask.lower.Value & (~frameMask.lower.Value); + var upper = cameraMask.upper.Value & (~frameMask.upper.Value); + if ((upper | lower) == 0) + return; + + var additionalEntitiesBuffers = chunk.GetBufferAccessor(ref additionalEntitiesHandle); + var shaderIndices = chunk.GetNativeArray(ref shaderIndexHandle); + + var enumerator = new ChunkEntityEnumerator(true, new v128(lower, upper), chunk.Count); + while (enumerator.NextEntityIndex(out int i)) + { + foreach (var entity in additionalEntitiesBuffers[i]) + { + shaderIndexLookup[entity.entity] = shaderIndices[i]; + } + } + } + } + [BurstCompile] struct MapPayloadsToUploadBufferJob : IJob { @@ -279,8 +650,9 @@ public void Execute() } } + // Schedule Parallel [BurstCompile] - struct WriteUploadsToBuffersJob : IJobParallelForDefer + struct WriteGlyphsUploadsToBuffersJob : IJobParallelForDefer { [ReadOnly] public NativeArray payloads; public NativeArray metaUploadBuffer; @@ -294,6 +666,23 @@ public unsafe void Execute(int index) UnsafeUtility.MemCpy(dstPtr, payload.ptr, sizeof(RenderGlyph) * payload.length); } } + + // Schedule Parallel + [BurstCompile] + struct WriteMasksUploadsToBuffersJob : IJobParallelForDefer + { + [ReadOnly] public NativeArray payloads; + public NativeArray metaUploadBuffer; + [NativeDisableParallelForRestriction] public NativeArray masksUploadBuffer; + + public unsafe void Execute(int index) + { + var payload = payloads[index]; + metaUploadBuffer[index] = new uint3(payload.uploadBufferStart, payload.persistentBufferStart, payload.length); + var dstPtr = masksUploadBuffer.GetSubArray((int)payload.uploadBufferStart, (int)payload.length).GetUnsafePtr(); + UnsafeUtility.MemCpy(dstPtr, payload.ptr, sizeof(uint) * payload.length); + } + } } } diff --git a/Calligraphics/Systems/Rendering/TextRenderingUpdateSystem.cs b/Calligraphics/Systems/Rendering/TextRenderingUpdateSystem.cs index 34190da..9df275d 100644 --- a/Calligraphics/Systems/Rendering/TextRenderingUpdateSystem.cs +++ b/Calligraphics/Systems/Rendering/TextRenderingUpdateSystem.cs @@ -20,7 +20,8 @@ public partial struct TextRenderingUpdateSystem : ISystem { LatiosWorldUnmanaged latiosWorld; - EntityQuery m_query; + EntityQuery m_singleFontQuery; + EntityQuery m_multiFontQuery; bool m_skipChangeFilter; [BurstCompile] @@ -28,9 +29,11 @@ public void OnCreate(ref SystemState state) { latiosWorld = state.GetLatiosWorldUnmanaged(); - m_query = state.Fluent().With(true).With().With().With().Build(); + m_singleFontQuery = state.Fluent().With(true).With().Without().Build(); + m_multiFontQuery = state.Fluent().With(true).With().Build(); latiosWorld.worldBlackboardEntity.AddComponent(); + latiosWorld.worldBlackboardEntity.AddComponent(); m_skipChangeFilter = (state.WorldUnmanaged.Flags & WorldFlags.Editor) == WorldFlags.Editor; } @@ -43,19 +46,33 @@ public void OnDestroy(ref SystemState state) public void OnUpdate(ref SystemState state) { latiosWorld.worldBlackboardEntity.SetComponentData(new GlyphCountThisFrame { glyphCount = 0 }); + latiosWorld.worldBlackboardEntity.SetComponentData(new MaskCountThisFrame { maskCount = 1 }); // Zero reserved for no mask - state.Dependency = new Job + state.Dependency = new SingleFontJob { glyphHandle = GetBufferTypeHandle(true), boundsHandle = GetComponentTypeHandle(false), controlHandle = GetComponentTypeHandle(false), materialMeshInfoHandle = GetComponentTypeHandle(false), lastSystemVersion = m_skipChangeFilter ? 0 : state.LastSystemVersion - }.ScheduleParallel(m_query, state.Dependency); + }.ScheduleParallel(m_singleFontQuery, state.Dependency); + + state.Dependency = new MultiFontJob + { + additionalEntityHandle = GetBufferTypeHandle(true), + boundsLookup = GetComponentLookup(false), + controlLookup = GetComponentLookup(false), + entityHandle = GetEntityTypeHandle(), + glyphHandle = GetBufferTypeHandle(true), + glyphMaskLookup = GetBufferLookup(false), + lastSystemVersion = m_skipChangeFilter ? 0 : state.LastSystemVersion, + materialMeshInfoLookup = GetComponentLookup(false), + selectorHandle = GetBufferTypeHandle(true) + }.ScheduleParallel(m_multiFontQuery, state.Dependency); } [BurstCompile] - struct Job : IJobChunk + struct SingleFontJob : IJobChunk { [ReadOnly] public BufferTypeHandle glyphHandle; public ComponentTypeHandle boundsHandle; @@ -65,7 +82,7 @@ struct Job : IJobChunk public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { - if (!(chunk.DidChange(ref glyphHandle, lastSystemVersion) || chunk.DidChange(ref controlHandle, lastSystemVersion))) + if (!chunk.DidChange(ref controlHandle, lastSystemVersion)) return; var ctrlRO = chunk.GetComponentDataPtrRO(ref controlHandle); @@ -127,6 +144,133 @@ public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bo } } } + + [BurstCompile] + struct MultiFontJob : IJobChunk + { + [ReadOnly] public EntityTypeHandle entityHandle; + [ReadOnly] public BufferTypeHandle glyphHandle; + [ReadOnly] public BufferTypeHandle selectorHandle; + [ReadOnly] public BufferTypeHandle additionalEntityHandle; + [NativeDisableParallelForRestriction] public ComponentLookup boundsLookup; + [NativeDisableParallelForRestriction] public ComponentLookup controlLookup; + [NativeDisableParallelForRestriction] public ComponentLookup materialMeshInfoLookup; + [NativeDisableParallelForRestriction] public BufferLookup glyphMaskLookup; + public uint lastSystemVersion; + + public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) + { + var entities = chunk.GetNativeArray(entityHandle); + if (!controlLookup.DidChange(entities[0], lastSystemVersion)) + return; + + var glyphBuffers = chunk.GetBufferAccessor(ref glyphHandle); + var selectorBuffers = chunk.GetBufferAccessor(ref selectorHandle); + var entityBuffers = chunk.GetBufferAccessor(ref additionalEntityHandle); + + FixedList4096Bytes instances = default; + + for (int entityIndex = 0; entityIndex < chunk.Count; entityIndex++) + { + var entity = entities[entityIndex]; + + var ctrl = controlLookup[entity]; + if ((ctrl.flags & TextRenderControl.Flags.Dirty) != TextRenderControl.Flags.Dirty) + continue; + ctrl.flags &= ~TextRenderControl.Flags.Dirty; + + var glyphBuffer = glyphBuffers[entityIndex].AsNativeArray(); + var selectorBuffer = selectorBuffers[entityIndex].AsNativeArray().Reinterpret(); + var entityBuffer = entityBuffers[entityIndex].AsNativeArray().Reinterpret(); + + instances.Add(new FontMaterialInstance + { + masks = glyphMaskLookup[entity].Reinterpret(), + aabb = new Aabb(float.MaxValue, float.MinValue), + entity = entity + }); + + for (int i = 0; i < entityBuffer.Length; i++) + { + instances.Add(new FontMaterialInstance + { + masks = glyphMaskLookup[entityBuffer[i]].Reinterpret(), + aabb = new Aabb(float.MaxValue, float.MinValue), + entity = entityBuffer[i] + }); + } + + var glyphCount = math.min(glyphBuffer.Length, selectorBuffer.Length); + for (int i = 0; i < glyphCount; i++) + { + var glyph = glyphBuffer[i]; + var c = (glyph.blPosition + glyph.trPosition) / 2f; + var e = math.length(c - glyph.blPosition); + e += glyph.shear; + + var selectIndex = selectorBuffer[i]; + ref var instance = ref instances.ElementAt(selectIndex); + + instance.aabb = Physics.CombineAabb(instance.aabb, new Aabb(new float3(c - e, 0f), new float3(c + e, 0f))); + + if (instance.masks.Length > 0) + { + ref var lastMask = ref instance.masks.ElementAt(instance.masks.Length - 1); + var offset = lastMask & 0xffff; + if (i - offset < 16) + { + var bit = i - offset + 16; + lastMask |= 1u << (byte)bit; + continue; + } + } + instance.masks.Add((uint)i + 0x10000); + } + + for (int i = 0; i < instances.Length; i++) + { + ref var instance = ref instances.ElementAt(i); + + Physics.GetCenterExtents(instance.aabb, out var center, out var extents); + if (glyphBuffer.Length == 0) + { + center = 0f; + extents = 0f; + } + boundsLookup[instance.entity] = new RenderBounds { Value = new AABB { Center = center, Extents = extents } }; + controlLookup[instance.entity] = ctrl; + ref var mmi = + ref materialMeshInfoLookup.GetRefRW(instance.entity).ValueRW; + + var quadCount = instance.masks.Length * 16; + if (quadCount == 16) + quadCount = math.countbits(instance.masks[0] & 0xffff0000); + if (quadCount <= 8) + mmi.SubMesh = 0; + else if (glyphBuffer.Length <= 64) + mmi.SubMesh = 1; + else if (glyphBuffer.Length <= 512) + mmi.SubMesh = 2; + else if (glyphBuffer.Length <= 4096) + mmi.SubMesh = 3; + else if (glyphBuffer.Length <= 16384) + mmi.SubMesh = 4; + else + { + UnityEngine.Debug.LogWarning("Glyphs in RenderGlyph buffer exceeds max capacity of 16384 and will be truncated."); + mmi.SubMesh = 4; + } + } + } + } + + struct FontMaterialInstance + { + public DynamicBuffer masks; + public Aabb aabb; + public Entity entity; + } + } } } diff --git a/Core/GameplayToolkit/DynamicHashMap.cs b/Core/GameplayToolkit/DynamicHashMap.cs index 704281d..f528ed1 100644 --- a/Core/GameplayToolkit/DynamicHashMap.cs +++ b/Core/GameplayToolkit/DynamicHashMap.cs @@ -228,7 +228,7 @@ public struct Pair internal bool isOccupied { - get => (meta & 0x10000000) != 0; + get => (meta & 0x80000000) != 0; set => meta = (meta & 0x7fffffff) | math.select(0u, 1u, value) << 31; } diff --git a/Core/Utilities/BlobBuilderExtensions.cs b/Core/Utilities/BlobBuilderExtensions.cs index cea2d3c..130a205 100644 --- a/Core/Utilities/BlobBuilderExtensions.cs +++ b/Core/Utilities/BlobBuilderExtensions.cs @@ -1,4 +1,5 @@ -using Unity.Collections; +using System; +using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; @@ -22,6 +23,12 @@ unsafe public static void AllocateFixedString(ref this BlobBuilder builder, r res[i] = fixedString[i]; } } + + // Todo: Find a new home for this? + public unsafe static ReadOnlySpan AsSpan(ref this BlobArray blobArray) where T : unmanaged + { + return new ReadOnlySpan(blobArray.GetUnsafePtr(), blobArray.Length); + } } } diff --git a/Kinemation/ACL_Unity/AclUnityDecompression.cs b/Kinemation/ACL_Unity/AclUnityDecompression.cs index 16a11ae..28efef9 100644 --- a/Kinemation/ACL_Unity/AclUnityDecompression.cs +++ b/Kinemation/ACL_Unity/AclUnityDecompression.cs @@ -20,7 +20,6 @@ public enum KeyframeInterpolationMode : byte Nearest = 3 } - // Warning: If you do not provide enough elements to outputBuffer, this may cause data corruption or even hard crash public static void SamplePose(void* compressedTransformsClip, NativeArray outputBuffer, float time, KeyframeInterpolationMode keyframeInterpolationMode) { CheckCompressedClipIsValid(compressedTransformsClip); @@ -48,7 +47,6 @@ public static void SamplePose(void* compressedTransformsClip, NativeArray } } - // Warning: If you do not provide enough elements to outputBuffer, this may cause data corruption or even hard crash public static void SamplePoseBlendedFirst(void* compressedTransformsClip, NativeArray outputBuffer, float blendFactor, @@ -121,6 +119,141 @@ public static void SamplePoseBlendedAdd(void* compressedTran } } + public static void SamplePoseMasked(void* compressedTransformsClip, + NativeArray outputBuffer, + ReadOnlySpan mask, + float time, + KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedTransformsClip); + + var header = ClipHeader.Read(compressedTransformsClip); + + if (header.clipType == ClipHeader.ClipType.Scalars) + { + ThrowIfWrongType(); + } + + CheckOutputArrayIsSufficient(outputBuffer, header.trackCount); + CheckMaskSpanIsSufficient(mask, header.trackCount); + + compressedTransformsClip = (byte*)compressedTransformsClip + 16; + void* compressedScalesClip = header.clipType == + ClipHeader.ClipType.SkeletonWithUniformScales ? (byte*)compressedTransformsClip + header.offsetToUniformScalesStartInBytes : null; + fixed (ulong* maskPtr = &mask[0]) + { + if (X86.Avx2.IsAvx2Supported) + { + AVX.samplePoseMasked(compressedTransformsClip, compressedScalesClip, (float*)outputBuffer.GetUnsafePtr(), maskPtr, time, (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.samplePoseMasked(compressedTransformsClip, + compressedScalesClip, + (float*)outputBuffer.GetUnsafePtr(), + maskPtr, + time, + (byte)keyframeInterpolationMode); + } + } + } + + public static void SamplePoseMaskedBlendedFirst(void* compressedTransformsClip, + NativeArray outputBuffer, + ReadOnlySpan mask, + float blendFactor, + float time, + KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedTransformsClip); + + var header = ClipHeader.Read(compressedTransformsClip); + + if (header.clipType == ClipHeader.ClipType.Scalars) + { + ThrowIfWrongType(); + } + + CheckOutputArrayIsSufficient(outputBuffer, header.trackCount); + CheckMaskSpanIsSufficient(mask, header.trackCount); + + compressedTransformsClip = (byte*)compressedTransformsClip + 16; + void* compressedScalesClip = header.clipType == + ClipHeader.ClipType.SkeletonWithUniformScales ? (byte*)compressedTransformsClip + header.offsetToUniformScalesStartInBytes : null; + + fixed (ulong* maskPtr = &mask[0]) + { + if (X86.Avx2.IsAvx2Supported) + { + AVX.samplePoseMaskedBlendedFirst(compressedTransformsClip, + compressedScalesClip, + (float*)outputBuffer.GetUnsafePtr(), + maskPtr, + blendFactor, + time, + (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.samplePoseMaskedBlendedFirst(compressedTransformsClip, + compressedScalesClip, + (float*)outputBuffer.GetUnsafePtr(), + maskPtr, + blendFactor, + time, + (byte)keyframeInterpolationMode); + } + } + } + + public static void SamplePoseMaskedBlendedAdd(void* compressedTransformsClip, + NativeArray outputBuffer, + ReadOnlySpan mask, + float blendFactor, + float time, + KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedTransformsClip); + + var header = ClipHeader.Read(compressedTransformsClip); + + if (header.clipType == ClipHeader.ClipType.Scalars) + { + ThrowIfWrongType(); + } + + CheckOutputArrayIsSufficient(outputBuffer, header.trackCount); + CheckMaskSpanIsSufficient(mask, header.trackCount); + + compressedTransformsClip = (byte*)compressedTransformsClip + 16; + void* compressedScalesClip = header.clipType == + ClipHeader.ClipType.SkeletonWithUniformScales ? (byte*)compressedTransformsClip + header.offsetToUniformScalesStartInBytes : null; + + fixed (ulong* maskPtr = &mask[0]) + { + if (X86.Avx2.IsAvx2Supported) + { + AVX.samplePoseMaskedBlendedAdd(compressedTransformsClip, + compressedScalesClip, + (float*)outputBuffer.GetUnsafePtr(), + maskPtr, + blendFactor, + time, + (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.samplePoseMaskedBlendedAdd(compressedTransformsClip, + compressedScalesClip, + (float*)outputBuffer.GetUnsafePtr(), + maskPtr, + blendFactor, + time, + (byte)keyframeInterpolationMode); + } + } + } + public static Qvvs SampleBone(void* compressedTransformsClip, int boneIndex, float time, KeyframeInterpolationMode keyframeInterpolationMode) { CheckCompressedClipIsValid(compressedTransformsClip); @@ -152,7 +285,6 @@ public static Qvvs SampleBone(void* compressedTransformsClip, int boneIndex, flo return qvv; } - // Warning: If you do not provide enough elements to outputBuffer, this may cause data corruption or even hard crash public static void SampleFloats(void* compressedFloatsClip, NativeArray outputBuffer, float time, KeyframeInterpolationMode keyframeInterpolationMode) { CheckCompressedClipIsValid(compressedFloatsClip); @@ -178,6 +310,39 @@ public static void SampleFloats(void* compressedFloatsClip, NativeArray o } } + public static void SampleFloatsMasked(void* compressedFloatsClip, + NativeArray outputBuffer, + ReadOnlySpan mask, + float time, + KeyframeInterpolationMode keyframeInterpolationMode) + { + CheckCompressedClipIsValid(compressedFloatsClip); + + var header = ClipHeader.Read(compressedFloatsClip); + + if (header.clipType != ClipHeader.ClipType.Scalars) + { + ThrowIfWrongType(); + } + + CheckOutputArrayIsSufficient(outputBuffer, header.trackCount); + CheckMaskSpanIsSufficient(mask, header.trackCount); + + compressedFloatsClip = (byte*)compressedFloatsClip + 16; + + fixed (ulong* maskPtr = &mask[0]) + { + if (X86.Avx2.IsAvx2Supported) + { + AVX.sampleFloatsMasked(compressedFloatsClip, (float*)outputBuffer.GetUnsafePtr(), maskPtr, time, (byte)keyframeInterpolationMode); + } + else + { + NoExtensions.sampleFloatsMasked(compressedFloatsClip, (float*)outputBuffer.GetUnsafePtr(), maskPtr, time, (byte)keyframeInterpolationMode); + } + } + } + public static float SampleFloat(void* compressedFloatsClip, int trackIndex, float time, KeyframeInterpolationMode keyframeInterpolationMode) { CheckCompressedClipIsValid(compressedFloatsClip); @@ -236,6 +401,15 @@ static void CheckOutputArrayIsSufficient(NativeArray outputBuffer, short throw new ArgumentException("outputBuffer does not contain enough elements"); } + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] + static void CheckMaskSpanIsSufficient(ReadOnlySpan mask, short trackCount) + { + if (mask.IsEmpty) + throw new ArgumentException("mask is invalid"); + if (mask.Length * 64 < trackCount) + throw new ArgumentException("mask does not contain enough elements"); + } + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] static void CheckIndexIsValid(int index, short trackCount) { diff --git a/Kinemation/ACL_Unity/Plugins/OSX/AArch64/AclUnity.dylib b/Kinemation/ACL_Unity/Plugins/OSX/AArch64/AclUnity.dylib index 2a5f2b0e81b68fcd570ace1668639c411bb8f4fa..6070e1e7eccfb34b84ab74ed369d670d7d846ebf 100644 GIT binary patch delta 17159 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 delta 17311 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 diff --git a/Kinemation/ACL_Unity/Plugins/OSX/X86/libAclUnity.dylib b/Kinemation/ACL_Unity/Plugins/OSX/X86/libAclUnity.dylib index 650ca813571870b68c0812894f89428a24ae499e..191dd037e991ac55a582bde1ab231c46ff3a72bc 100644 GIT binary patch delta 30207 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

+>k;LjDD#F%?*dk$&oS>~B>D#aB}m4nLJw0B55 z_a|G_1H$S1Y|A@j7fra!#;21dbmLuiE1hg{cllC9|9+R{WWbVpyPWOMAaBtDcUbpK zvfBON=PG*r9eZ&m*+XgZE&JLfq%NVa|H*PKWFKvH(?0V(Qj5?AH`u#bQ{4Vz{AY(oRLFX^QTR~9`Wmuk4kPm@n z0aV6ZMD$4rERQ03IOGaorr>{ri13kH8r3pE84L&-TB6omRfdN8Tw=wmi9c;~i9K0O#?#2(*qAk>argVsfqTlB0_D&t z&}~HRdeG+J^Vq2iAg2>{uiz@^bWtg|04iOA%1>4F6%g9&BI|G%*2Y_F$Ozi|66?Jd zBzkg*ry+<#iGd(7bm}>jcvT5&F%UH>xCIigfnbpM2ULEqqRW(_EqYvFN+Ahkg=@(K zN-nS#ACgIQ?RmE1Lo%60ooCNKB(r6g^P2IFI$O>3d5qJ988Rox^ zyiza!4EA}@DJ2XH9E}`}UGLrK9Ba9Ov}Eb)NDDguSGIEoeLAymzYF5R3!R zX$TJeg6bLRv6H;_*H6B*_q~6yL+|y3_A6q>jpQr((Fs<*k$gt)A7`sSA)9HxpON+fA0J1m zw!;Cfo;Sv7FMh|5+duz|l+t=vk7=m!M^w^&YSA_0nElHy$ZLc?K5X~fLPF%0@?pg1 zD}xV#SFSn)0J>^ezELU2gP{Zm!#v9K1ArLn_wFG!WgBU%Y(u)R0+lJqc!;e7|8oCa zr3s2UgznZ@8KKggRtBF1JH7=={vn`wRiz2Nbdcp7BL1xD*AS!7!|eU9AqYHqkiCG) zMga#=3Gkh;3!ua~?mSnN!J+T$Mx| ze)N8&2iUUh;Jd94u-k{B^|b{kpv5Z(R9~^**iJ?hxy^pnBI>ckon!&M{GE7{sCnMEzCnBOP4eAs-&f`^j)MDZL*Jn z$Vv8RyGR<1diFiviH}#C_A5|ZHSKZV0>l^HGvDL1vw)l5Awk;>d$no(Tzm1W`@Mba zUeb`%yAMpNG0fVdl8#b~t}DM~YrZ9m=`Fjx(RZ+$w|t=1YZGok;AInDz8erj{pRgq zso#^YyIwwa{dV!O+nI}v z1a;npW7mkC`H_s4d+l-*SC9t-cyk9Iz|Q&PH=PplHFi(4J^MCpdkwx)C2ih@w&%~r z9wN1rW808WGr%$1xB(vAiiDx@+t2``8*D{kyS+RetmhP1&uLt8=5OQncnD>`%pO~C zS-ZbNdo*`AgY8w9RcVQzf()=Cb#3nnYr70K^-~{w&##iHb8Jzn0v8abk;YNR(Z(^xv7`6jtno2hewu{S z5!3DaPLoe*)Xu3$?QZJa|50iUpf&)J;UfYmFmcCW9Qvv${g$DRr5s|i^r?2+Su#r( z=rdWgax>K{25`VS(l`dVXES@FizK)P?YENkEFt53r!2!zG|W31O0+z`V>Y|JguI|l zDzc$7VF%RgnG$wWIdl_1?}kgU_PH|nu}b|}w*AsI@&}>EK4AN=lSI!WS(*h8UTTlH zK_1d3A3)7NwMzrRf` zxd(d8gRLm5r<*BjmcJ>hLma6vr61DAS)SZ1pUb{^42QzwbJ^L)*h0$OW=V`8RZ$AcINnfD&2(22xv_07V^8q5-3aeF5bv}^F2YT4EmN}Pxmt$0P~Dj>j$pb>A{$LVOcOn1C) zzvoKxWqR&)`)*I#*Mr`IbZQ-XM@RFAvA^rnALzlMY*zp^(Ho(xB7puzw~w&@9!S^d zXzD;VuOaP8kG;x%Xh?r{AD4im-D2%PwxSVzph-L8NYd_kYo`fhghC|yU5Nh2+h1jY{ z`UXukGM6Yik`6Yq@lo^yO^;@+ThY$6SF}C36^$lD$Hqlb13TH8_LLhX)Ai(ojq?)i)e3mcA^7qK)>k7Zg-$1ftxyGLt5k= zrC*5C6WbpqTX~sXM3|w^p;GY%l3NR=p30gkFqZ}QXO$`7l{)521MFF z?m-*q=ywtJAA8X?G96r>{iiQYpvRlCt$pcn_nqNL=>1{1J-r`vfCk>T7xbqkgmh&2 z{b?Qhw*x43p@zTgKMbbeSslylpAMyZnU1~54EI3ge1G=q2-=3a-(=ZG=`T23p7vY+ zp^Hf%sfVJ)a80d)y-GWtT_O)^fpB!d|qe zh3vO6)RjhFWn18W)ML3XNK;d}EMgI@_v&9)&{H8J99v|v?7R6ltnnJXA8#B|7c{prl@4QirDpWG{Owo+dL`Oa^9Gp56qe8R4(0W zlO;0;UX2gW7#IEVB|Nd2rvQ!mSTg+|!EbBW6W)hNOJ)Z=L}z89f{MS}9;A;^Vs1j! z9$podXWSD4M{$__j(R*_t-}$B%lb4D)hr9FOqD~T+_)$&4@hG=7AlMJUdmGN0}3=Y zYqJ9CSpIl=cuI`Lik*&)I>qH0_YQ#v4rt93w1c1+D9VD8vMmcX8Pf|YMRs0^T*_iN zUP_RbFlGg2#56pXmHK(+10|yf+R7S|lNGzk@@x3v^a@2;ypNS^rOksEY){HISstf9 z(*2+VBBe*UwaYGek*r6P%;4NCao7qH7M!s2T2zUe))>YHgi+NvT=;2g?aABW2$|X{$(YI*{s8 z*8VTj`wpZ;$>&m%ux0D0`$Xay3a%J zwCM&zMq6ywW*JU;lO#II+*7ZN$nTRCV2y_!#$@Rq9tLtl%0akY(omk(G0>x!H5}!Z zva=crdLU3#$(RZqa6nrg0(P&ybTCvqnHBC%-cx+cU{Z~%vHI5h5BICne@iW+Q$y5JpK4M|UFtNoG?SE{ zN$dz24Nuq z;@Zr~ILL1-dJ3&WRBPy9?xIe=TWW$@(v+gIq^ikLf}`=$v8NseC9le444TOtaKH(h z;W)RH?bUIr(Qq6X%@koY#v!U;Pi{7b(ge+H_TnmM+=Hu8Su;m{VsPDGkjE`1KM$y4 z#A>uswHRDpbJ)G!G@997gS9=Meg7J52E|QY)P9||G&@f8VHjW8avsKai9sD-Q`Ww6 zaGCT9lIjta7yaNcNgZ1@71phi7p8~j_jo~@jv6@*kgqlQe){!CAZw?=q5t5<@JWbT z3x?%ys+XQmQ;($Q-A^xmgz{5-V$tYep4s^i=uj3hm3k*+W#S_1=;&~9x+qhE8CSdA zalf+ob?CC?Tx?dG40W?GHawJaAxkAOf|K|=su{zUPNhwQRy=|`B))CLLLz<10qBkK z;X(LqNQq$E`_cyCssq577_9@qqIF9++AW4Fql)3^&2~LAXC6xddUF<$K2@WS@7?ey8pe$_-zoPP#VUW6Vx$lY_llN~>9L~gP zLLp@V+36~v*LF`Z`IwG*%HrmbdxR|*H&hD*5b9DGljXHY8?F45!Yt%t3sPuv_SzfN zJA9V`w&N^o2>iC(4L_RxL{=6Lc7ajehdV4r6i~pnS z!Q-;_`NK{Eqs-2}K@DUNd-w)TcG+K}Px@gtZ8~kxsID>nNwAXfHuNht<5X&6SbJs! zBq`%AC?hYzWow`G{cP)Wnn6dMV)1`bBl~teZOJyyp!GU%$(D5nXduRt83~0^@gLB| zn1565srbi{?f~|SjDsLT)#TF-nkLI?%D${dG95?uvnk26t&dZCTasxEn=zC|xE5G2 z4gVU8ngNT%ZPxish>cYRR=!CCBeBEjS6t&PT(V_dw5rL<1g=S32&nR>s!B~&6;|>l z?aJ0A&`oh_tfE;FEK}U6W~qalSuwXMUszzJ#y!bR$=m@2Uzn7PzA(h4cc20e z!EEV3YG@A0R(0Jz4==cGk3<`?daVN*$?DZdsX1zi*xgpJWwU5#_{%sv(+hQ0T{KWH z(l$ILV*pz^i-tA*7jn01r5=wfDk@j)Szw(}dGJZgDF;&y zt#aCbG3qW(MxBEesTws)PN7ZNf8L-9`yvI5d0!+LL6)@*{I)#NjCleY^R7KO_?C5O z(_qX+>`npo58j87f>o!0T>%FygGH(heKlOOp*pEF)Ef<;kL75e{42VsQ_Pe~XY@kL zJ_w)Um^b-FBV+nue+b<4eL9{}GFrk46|3xCAQ{suz~tXjO}-*E^d%<$JeAfbkC^{# z+JJOn?Pt@Vut#y0d{cg@3=yWga?F@s>>3lEXURWZ*3+sp8_9tf%OkMWD@WP<*V=n0^ioBK=szI_h7~x&$pZYhN}LQ5WN~zQW#@ zy`==R8Fl6QY;Qd6eR0nm+9;%UNTtt$%QpF^{xwSfV%w5w0ITOO`^3LUzwTTCOI-+iu^+KnwcU-%QA-u}Px7mVGiJ4MSB|IOa1DP1J^Zg#H^@j1tG-xT z$WrIhSnS`ZdaAzQ*+yuaRVG>M?YZSy-l0buMSooWqsYZLP8ME zktqXN(*?Al{mFd#HVH64fQ6&-WLtU{tX^% zH_^m=`vWtD8v@7~yDfqy%gb5VA{yMKf1cyD7uPN`w(kui5tWMbsdBt@#Uau&o7+-DdwfgPtWVF(vovEmYOz`!Xzo&OlLCAA)?# zXgGxJ7l*Lej!%`v>+RpXOCyQj%r9WpbV?>3fAE#094W2I9xtXn0(C}ZSP8NzBPNEx zC%Lzjn$4FYjXq`RswK28dvgg5aZ#z*I)HWEz(rNW2|xt>D?0?Oxy;u>yLn6mAHzmU zN3t;%I>;-`d(}-)xh@8e_B;#qCw=T{_7eMe|y-(3<7cen4&_bZ;C=wJ|QcaO1`qG;)o|SvVZCVxCm(|X;N4`&uI?|fW%%Tm#k4U{VVop~;U?c$SV z5FS5ctr+3dw>Mu&e}%O9mkd%?_3goo zZXWI~ z7}~1?6Ql&&yRW5PDA{D6{}Fve$VPkWdOAraAL9xa3~7<48)yrF!Z$+Zw58qW6PiTo zLz1OxG1~&48*7VMn@#`~ir~#4 z{wV9d8CH-r_TcYq*=v z;O~rOV?`G`t*^e>;ZxoTglw@N-$QeWvKJ258|#A(g3oBy(pxIJil~CUOhq^{Ik|@l-4D4cz!c&GaG)C zwkNZ3Pq;x3PP5NDMi-L$n;i!&?9<9Y+p!r3tvlJw2L23VHc1_`T1hx&zmu74%W?X! zA!OOb^-V3)q67Z0NIBl%_-$nSPtfM%O;&z_`nO#43hn?2umeB{lmY{`~>26)<1*B})uXK9P72f}pWQ`9&QKe(R_tT3ZZ&r+XGnbbTF7!u1Zs+JoZ>=j} z=x}yt$>->gVLdPdWy-2$9SiNntksvbG-a(+^{7Nyo&tsqJ5T42s-&I+t;g)tm#Tk{ z-V=j)6zrT$`&}vmik*Kd%;w=!Bm*C zB4aFt`8R8rvUG=HEJxBxx+uGkn3PWnXZdsPRAZdka$SAbrA#ixgAZh+T&wLCmGc^{ z&g)6&FdT691F1fx03w7w)|8N{79llzB)=3}EHqmCkhGGnqRD!^nbZL$OS$?A`xIoB zd@9OPpiTJ1T-~CWPcf}y?u%2TwW3S;(XoTk@XbdrXwS-f=pJJ^k$)T7^LlKw&ZE(3 zH@ZbDyWua#K6yNAoM>h(OiP?~ZZ)ps`Ts?(J4Wl9GH^TqjyuXbvv7mayK2cfO&PQR z*dk3Tu7=a#Xr^pbO?f7oLsf#!7dT2WjqI2`tH4!Q$*7^zj01e`NLff#7n8DK^7T~# z3X8u)YY+ex;+eE9hr#A37}@eHdnL00cFoSm{+^ko`IirMJPw3Fvp9#DYwe8}$Z zrj5PlsPjS`?8mR4I%S5^I2A>!P>q|^fvVEkFeg!YQAgmoiD{a$niw{k((~a%|2!{K z#}g^t+2~^0)UR@As&Z*%->}Oc&;(XiOzV0(xOGxC)0aTv#g9dmz}=z#9k#TDp7&H2 zf@jMlDWwhjb1!YevadiS@)f(W7w*F5zF-Zf!PXOSmHM!XE3^iUuc`@*3`A95S5@VC zsAJi<9D9EQ#o^3NVmEIjmJXJ96=d(X&$)_GPVCs0HaAWT!qQ7tsJ-~wR z(O`Y!g^oF49O`>;RXn~w%eY4$(*Zr1`97T)YAwGHGnw&eIPCqfGh=M92Hx@0%81MZ z&^2YTCEQ-;Z(2gh5%%aIo$1+V9^&zYc$1|*qW>YKtmqN_8j7^Xv=1 z{_H9Ji8RK;;}uu}OW@cEd)E@wrDpT(5A$J`^eM|VF81H&bbjc|Y4@nKx|C%+UCcgs zL0=(DSndltqu$FomX!X&x>eBVe-X$VhZSok#7U*W?6V3wvSFCF`AytzM;uXO1lR^n z#boWmAt-C8D!D6AUQTM^0^*lB6T1y#bFr*pHp?S&AeAZmoybAvi)Bhuc7MzD^h1&9 z`*oRlDY=%z`NeK({&Gl4sai&-`t-NlS6}rhEy~`^?r%-6K~FB5Z_5Ef>)b)mID;c0(1c%{3sl(~gq`iWzQ%V5?9^Ss(Wv?o03oLwLG< zkLNY@Df=Ku=#N1H+?M_cd#;tYSo@^4UsZf?bXGmyPU>KxDvVbsIC$AiH9WA!dsVes z)ub>m16(Ut8_GHsbP)x|36|7`ZU6AV4AwrnPILVcKEploKN6Ft{_&a2Q9)qPQHT; z?Ne>Y-(D=sRzgOw=`OM{D8>antQ~G#5nSKZj z=#zgjpl7N=!hj}wS?)L)(A}3C&<#GZRR-i*m5+drp@sdJA@GWO4{oq34T#^|2?LrB z26WfSfa3qffViqo2GsAR22|OoSuPrmhpFOq?{T~K|`Z|q}b?8402ogTre#U}1 zISj`?M?)Bps}=q%_g^gTpTh@(zzvA)bCp{M)SQOvsQA|A?biU}8A;tV^$-t#V$;`6 zZt8vA4=}h#98?xxV2j-39&Wvw;u+O@EIZ~Vckmn29-b+t@c7vS+~rxM z5jRcEGI4>2ME6bDhHy`XqRa=@Fb~;6tgNr691iLC6i+z{Zc9J&lw)g;8wQrDgsJ-$ zpCaLQttTTmR&wPRRT2#3ms8PR~j`CYDSq;2^+ra+g zB`Y=4;Xo^?1dxNl5CJ6{*l=%Iae3#M3VX}3{u?L0tc9}aY`T{m+cx&4SWRlC8+fXA z&!|>G(DMr{`@Esm-m*9S+|Ll@{9?0TV49L{t@ zcZbI@la=Em*CBRh_mOwHt=k_@Ng01DNtt%jn0^p06AJJW z5U)zMw&hnP`{AnO5L}hOhcoc`7haVF8`GawUJk;a8tCPg_>1*&aOee- zEf=OfZMq6BDgWQ4R<0VjpSgAadKi_`evJ$`eHfM#jAzx5vtpz{SIv9fS+jXNh%G&zVz49_jNcY=>F9#7!kL zC~|A&TUV~*Caxb@R9(3?U6;%jD{?b7eh%DORQlI22mjiy`ImYX$^8oqv&z3Bz`x+H z4Hii1O(b5vM`u*<%XjK%w*@syZ?lRyG)%bwz6F5+MUt+f;y8YmF|a^lEVGU z)EmkD>V@R)Ch#pTL?rNcdisg^IxsFN$*4@f$(GFq<*(F|{aMYLa+`SJ0>U-FQC(BJ ztz4O?y5_oMD8Lu{gGZF9u34|rHTnH)=3Jn_uMLd#WIqoxn7H;zv}OT%IEzE76nH;0R${(ftnl-EO&LfYu0 z;@*%@hcdE{Bu#`eZJ;ErfU+FQ&!J5COp^9MnR8r{e!=nz+;Fso@}VSkfihVl(h4ZE zA%E@FPA6@TB~n;Bomna#0|5n86eSTU84**7v$D8GSHKaEHSpiDbYq+g&+hCg?^ z1!Xk+y`Zi=;4c#?5X$UpL}~-2^e4!JGWj-4OQj`H<^)n{E7rH9lGFj}p{(7( z3j{<_sRLBxMcFrY(>);d4bVk6_DFi@8kPpFEKguNqjlZPdJP{rh~uNRGC?a1uk!jx ztrYS^8Lic)4bkLl@&rByVjs2rk-St8ko0N32ugZ3!>BWwC5F{d%^#i`ZG}d=5^9T=LoGhgR^LqBMRTWekL(^r9@)Lh zJ<5Bh4a@dUGnLm!b1kot=ZgQOxo@w-86@f43`wdDEj^pdO9OnD=?`%BN?y7}5fy&a zj+eijjI`9Qf2hNA&VQ~d-NK)9jh73eVdWC8304#p=8oJ@nc6@inf^peB} zUXtQrQ)&S80^Z>S`_`zAU#P-qNYC5CoU%IF4p1fZP${^{RhJ(i{y=G>=vll$cf_K}?O$VSv>Jk~sWGgSn5u z`UGZ#c%Za_@)L&e$3(CwK+a|BZps8VRM?g4PccK zPL3!5w*!1G2^h8od{%%XBEIOq91f|g(ZhCu&tVP5Z3CYU{v4m}#bK)kN8`uZ@RT8J z27$Q0;PXa3&afT+tOTFk8k`frVUPM+gXnAU%-3Lpfy37{xVRyQV*)sRbO?v)aX7IRhf6g$xiyE=8*qGa8xDsDb65{YY<$*f zaCSEi2N*aWH&T3N7{LFr!N}gc;(LuCxgUq+hMXaK2#5P=FmAy3WNUCF+(P41qQST^ z<1?g@)*x)m_+)A@Zq)c}YXts}3gC{7&u?1ZRkR%;DV{YXM+akY4Ayaap{eQe<28;UTrkE3&35WqEM?y7|jWWw&eunW)5G` z;DpaO{CYc%FFI}?v{V-_o7Y8ns@nt(Pz3xE|MY3M?hpt&z=#Kf#L!HA5RP}kBY$3J zj_<4S3s~^O-l9GlKLk*nS)=6;W^Qs)1S4{MPHKV#ECiL;;CM}tA~?ggd!R& z_zf=A_|0lagH=d?0z^d9H=2)xfGY*y3p`lhNI2q=UqnD>qB*~S5%tfJG*>Yo0SXWy zQw<{*AmEn^sG=AABBbi3@eBB+{P4#@l`)tQ5ZMPbfoG&9K)^!4c8GlNK^RBC5sw`x zPEgTOvQZmB4MuwB5ip|w!GHuPK*V$(7`XreBOV19;0GUsk>3%I{2~V2W90k-MtUa& z%&GuK0u;c{LXvbw6ChwAKC_qG|wZMAO z9R(}|By;(d3OM4Czg$zGr^YW}!5=O7VQuF_s4*Y`3J_uMFik)tGp^7DHx$)^jGxCO z=>v@{S|uANss)*dkxyu30!BPeND=(tgD?(Cme@GQQ@*AA^E9n7;{8|WamfqC_ zh?rg|pk*K``|F5D0V3wVukj05@FzGa;D|^5#L=iQ*w)lo-UD92dyGBEslEzo3>=RF z#F-*X;}@_{AX>%-sz%TekNnZNx!~INohCr7&=^pGDi7c@?ubVL;?QzS;}@_{z?t6> zuZ{pHfbC0>y|fWPjo|q=0f`QH6d+DTGc|qz3x3Qd33>4j^jMAjaEemfm+oi+#IXtq zUN!=bcoZOxUGsYL4hZ<)_#N@cUp$$&FS(ky0C8*+0@9r3&=HRU#IbIn#xG#O?>q&L zc;rtuYv;fhnt)^t76Q_A>IhbPfg>ITh;w5c>_@l=3Rv(P+^h3D;*sAh4w@S^0Rk2R z{^3U2!4&c82)xZ3DAD-EK~?YB5g1#DqxgrK00AQd3dqJ6c(6bLj(Ai+9NpXW=L!fI>D3W*S_vKTW)vXK`sXzP z0u}#u0GDqXHuN z(0Kq?K){v!y6XH@c$j|_fH`Y?Mr#5DECeKkaXiAPfFmCHMRKEU0#`u5f*%ru>Ld6o z@won|05sxDv@;{3-egO;q1ZNvi z^F=WJNPq%FzUZ=0K+7Kq0S0`52a9+IdW4Z*VGG56L%i$CV7#UE3 zJbZx%3lv~r4L9iOn$KzEB0(m|6P?x_M?C5qjcvfwVJL4-z=A*7DFAcCBfpk#GfRJH z0z?K*2rzK@mHIm3Q9zNFpldXYD@}P#AYj3-cj9-%qXKHi5KZVeO@K%t3IWMZ0vz!uK%^MwjNl3gSnx+WPob8Q zL_G3iN)p?bg8st=h#aL5kmhs_cEqCqk;7c3@e5e+8=UwZ@yHK3PqlscXIi-eA_*!4 zq~QxZSj2ONN*D!*9O(p&U%*I@HXp51$t(FC@yMUtPhCPm6HS0fvI+r3P7#_d%Tz=A)=iQf^A{Ke)mNGeI)M`?4Y!9swu6^kq{;?)t*vb-4@zsULu{v0m8Y6LWT z!EY96;WL^50Sf`ePDAL3M+NYn7oR?GBEuy?z=A(g#X3x(Mvr*lhl^vaVxA^I!2c$| z5swOpH1$S}U%-Mt(MbVEyc$A^y!J^=fPjSn=LpUbj|zx9_^2^_1OzPji=B3FN4y$B zip2RvO@M%ffMh2(a>SzoBGKM>ELT9lmHbZYfFoWFAw_2XHBEqkg@81ty~`1g3W!Yp zUX5SCg5Tg|Lk@TzLWZ zjN=LjSO_TB&VmTXJJ2JH{NgHN&N$95V8r7>Bsq;hB_7)60<^n}OG1DK3jy1m6v%VH zqXOctquY3{fPe+RbNnLkl_8|K9Qi~OAYdUNyC!y{%8woKsDQXkd7<$OSn%U*uOq*P zuL>c{wcD2!6L=2o=Q&S-iihz>0u&%_cZxLu0u}=EaKr8egPxF?P8?@;`-<-O+Ylu|56uh7S+gy?TA|? zpCsOnfDs=8KWR>pmm?ne#Vyo_8oz)Azf-%E6k810V*J_#Y!{*0u}FqH8@eU05iUP#VW zQUOOi@{7CJl^VZ*1wWVuKAx2V@XNZdbz@}nmrhrjk*)Y_6{PU76tIw_cUqAg@u-Qo z1wO9v3s~?=)%oH38132^2~+`iZ46J(RIWg@1`7ej0V=w(CysdJHw@?e2Q_{H3;xJz z3V=mA5}*KaP5qlDK){s(f~qUvh)4dkx3~gRXY&pSSnyX{lhGm^2~dEz7hj|a5U>!S zZ&+OcM?CUN8@K}AbGQNm7W@egBcQHGjs)rmXczIpngDSzF9hgcsjh${9{CfGa0NDN z`~nvI4kN%2BS6xrewO4uDGd=T0kc?4vYT-2NtLu=sRfoCa_2%&zR!EgdA>A_NpcIbx1GX|OQ5 ZK^iQkV4?;KjngzZ@rb?PTiyBv{~v-1#47** delta 29187 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 diff --git a/Kinemation/Components/AnimationComponents.cs b/Kinemation/Components/AnimationComponents.cs index 0a3a679..a847c47 100644 --- a/Kinemation/Components/AnimationComponents.cs +++ b/Kinemation/Components/AnimationComponents.cs @@ -124,7 +124,7 @@ public unsafe TransformQvvs SampleBone(int boneIndex, float time, KeyframeInterp /// Samples the animation clip for the entire skeleton at the given time weighted by the blendWeight. /// This method uses a special fast-path. /// - /// The blender which contains context information about previous sampling operations + /// The skeleton aspect on which to apply the animation /// The time value to sample the the clip in seconds. /// A weight factor to use for blending in the range of (0f, 1f] /// This value is automatically clamped to a value between 0f and the clip's duration. @@ -140,12 +140,36 @@ public unsafe void SamplePose(ref OptimizedSkeletonAspect optimizedSkeletonAspec if (optimizedSkeletonAspect.BeginSampleTrueIfAdditive(out var buffer)) AclUnity.Decompression.SamplePoseBlendedAdd(compressedClipDataAligned16.GetUnsafePtr(), buffer, blendWeight, time, mode); - else if (blendWeight == 1f) - AclUnity.Decompression.SamplePose(compressedClipDataAligned16.GetUnsafePtr(), buffer, time, mode); else AclUnity.Decompression.SamplePoseBlendedFirst(compressedClipDataAligned16.GetUnsafePtr(), buffer, blendWeight, time, mode); } + ///

+ /// Samples the animation clip for parts of the skeleton specified by the mask at the given time weighted by the blendWeight. + /// This method uses a special fast-path. + /// + /// The skeleton aspect on which to apply the animation + /// A bit array where each bit specifies if the bone at that index should be sampled + /// The time value to sample the the clip in seconds. + /// A weight factor to use for blending in the range of (0f, 1f] + /// This value is automatically clamped to a value between 0f and the clip's duration. + /// The mechanism used to sample a time value between two keyframes + public unsafe void SamplePose(ref OptimizedSkeletonAspect optimizedSkeletonAspect, + ReadOnlySpan mask, + float time, + float blendWeight, + KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + CheckSkeletonIsBigEnoughForClip(in optimizedSkeletonAspect, boneCount); + + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + + if (optimizedSkeletonAspect.BeginSampleTrueIfAdditive(out var buffer)) + AclUnity.Decompression.SamplePoseMaskedBlendedAdd(compressedClipDataAligned16.GetUnsafePtr(), buffer, mask, blendWeight, time, mode); + else + AclUnity.Decompression.SamplePoseMaskedBlendedFirst(compressedClipDataAligned16.GetUnsafePtr(), buffer, mask, blendWeight, time, mode); + } + /// /// Samples the animation clip for the entire set of transforms at the given time weighted by the blendWeight. /// This method uses a special fast-path. @@ -166,18 +190,115 @@ public unsafe void SamplePose(ref BufferPoseBlender blender, if (blender.sampledFirst) AclUnity.Decompression.SamplePoseBlendedAdd(compressedClipDataAligned16.GetUnsafePtr(), blender.bufferAsQvvs, blendWeight, time, mode); - else if (blendWeight == 1f) + else { - AclUnity.Decompression.SamplePose(compressedClipDataAligned16.GetUnsafePtr(), blender.bufferAsQvvs, time, mode); + AclUnity.Decompression.SamplePoseBlendedFirst(compressedClipDataAligned16.GetUnsafePtr(), blender.bufferAsQvvs, blendWeight, time, mode); blender.sampledFirst = true; } + } + + /// + /// Samples the animation clip for the transforms selected by the mask at the given time weighted by the blendWeight. + /// This method uses a special fast-path. + /// + /// The blender which contains context information about previous sampling operations + /// A bit array where each bit specifies if the bone at that index should be sampled + /// The time value to sample the the clip in seconds. + /// A weight factor to use for blending in the range of (0f, 1f] + /// This value is automatically clamped to a value between 0f and the clip's duration. + /// The mechanism used to sample a time value between two keyframes + public unsafe void SamplePose(ref BufferPoseBlender blender, + ReadOnlySpan mask, + float time, + float blendWeight = 1f, + KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + CheckBlenderIsBigEnoughForClip(in blender, boneCount); + + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + + if (blender.sampledFirst) + AclUnity.Decompression.SamplePoseMaskedBlendedAdd(compressedClipDataAligned16.GetUnsafePtr(), blender.bufferAsQvvs, mask, blendWeight, time, mode); else { - AclUnity.Decompression.SamplePoseBlendedFirst(compressedClipDataAligned16.GetUnsafePtr(), blender.bufferAsQvvs, blendWeight, time, mode); + AclUnity.Decompression.SamplePoseMaskedBlendedFirst(compressedClipDataAligned16.GetUnsafePtr(), blender.bufferAsQvvs, mask, blendWeight, time, mode); blender.sampledFirst = true; } } + /// + /// Samples the animation clip for the entire set of transforms at the given time raw into a buffer without any blending. + /// worldIndex values are undefined. This method uses a special fast-path. + /// + /// The raw local transforms array to overwrite with the sampled data. + /// The time value to sample the the clip in seconds. + /// The mechanism used to sample a time value between two keyframes + public unsafe void SamplePose(ref NativeArray localTransforms, + float time, + KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + AclUnity.Decompression.SamplePose(compressedClipDataAligned16.GetUnsafePtr(), localTransforms.Reinterpret(), time, mode); + } + + /// + /// Samples the animation clip for the entire set of transforms at the given time raw into a buffer with blending. + /// New transforms can be specified to either overwrite existing values or be added via weighting. This method uses a special fast-path. + /// + /// The raw local transforms array to overwrite with the sampled data. + /// The time value to sample the the clip in seconds. + /// A weight factor to use for blending in the range of (0f, 1f] + /// If true, the existing transforms and accumulated weights are overwritten. + /// If false, the new transform values are added to the existing values. + /// The mechanism used to sample a time value between two keyframes + public unsafe void SamplePose(ref NativeArray localTransforms, + float time, + float blendWeight, + bool overwrite, + KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + if (overwrite) + AclUnity.Decompression.SamplePoseBlendedFirst(compressedClipDataAligned16.GetUnsafePtr(), localTransforms.Reinterpret(), blendWeight, time, mode); + else + AclUnity.Decompression.SamplePoseBlendedAdd(compressedClipDataAligned16.GetUnsafePtr(), localTransforms.Reinterpret(), blendWeight, time, mode); + } + + /// + /// Samples the animation clip for the transforms selected by the mask at the given time raw into a buffer with blending. + /// New transforms can be specified to either overwrite existing values or be added via weighting. This method uses a special fast-path. + /// + /// The raw local transforms array to overwrite with the sampled data. + /// A bit array where each bit specifies if the bone at that index should be sampled + /// The time value to sample the the clip in seconds. + /// A weight factor to use for blending in the range of (0f, 1f] + /// If true, the existing transforms and accumulated weights are overwritten. + /// If false, the new transform values are added to the existing values. + /// The mechanism used to sample a time value between two keyframes + public unsafe void SamplePose(ref NativeArray localTransforms, + ReadOnlySpan mask, + float time, + float blendWeight, + bool overwrite, + KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + if (overwrite) + AclUnity.Decompression.SamplePoseMaskedBlendedFirst(compressedClipDataAligned16.GetUnsafePtr(), + localTransforms.Reinterpret(), + mask, + blendWeight, + time, + mode); + else + AclUnity.Decompression.SamplePoseMaskedBlendedAdd(compressedClipDataAligned16.GetUnsafePtr(), + localTransforms.Reinterpret(), + mask, + blendWeight, + time, + mode); + } + /// /// The size the clip would be if uncompressed, ignoring padding bytes /// @@ -212,7 +333,7 @@ public struct ParameterClipSetBlob /// /// Equivalent to the FixedString128Bytes.GetHashCode() for each parameter name /// - public BlobArray parameterNameHashes; + public BlobArray parameterNameHashes; public BlobArray parameterNames; } @@ -298,7 +419,7 @@ public unsafe float SampleParameter(int parameterIndex, float time, KeyframeInte } /// - /// Samples the animation clip for all parameters at the given time at once + /// Samples the animation clip for all parameters at the given time at once. This method uses a special fast-path. /// /// The array of floats where the parameters should be stored. If the array is not large enough, a safety exception is thrown. /// @@ -314,6 +435,26 @@ public unsafe void SampleAllParameters(NativeArray destination, AclUnity.Decompression.SampleFloats(compressedClipDataAligned16.GetUnsafePtr(), destination, time, mode); } + /// + /// Samples the animation clip for the selected set of parameters specified by the mask at the given time at once. + /// This method uses a special fast-path. + /// + /// The array of floats where the parameters should be stored. If the array is not large enough, a safety exception is thrown. + /// A bit array where each bit specifies if the parameter at that index should be sampled + /// + /// The time value to sample the the clip in seconds. + /// This value is automatically clamped to a value between 0f and the clip's duration. + /// The mechanism used to sample a time value between two keyframes + public unsafe void SampleSelectedParameters(NativeArray destination, + ReadOnlySpan mask, + float time, + KeyframeInterpolationMode keyframeInterpolationMode = KeyframeInterpolationMode.Interpolate) + { + CheckBufferIsBigEnoughForClip(destination, parameterCount); + var mode = (AclUnity.Decompression.KeyframeInterpolationMode)keyframeInterpolationMode; + AclUnity.Decompression.SampleFloatsMasked(compressedClipDataAligned16.GetUnsafePtr(), destination, mask, time, mode); + } + [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] void CheckBufferIsBigEnoughForClip(NativeArray buffer, short parameterCount) { diff --git a/Kinemation/Components/OptimizedSkeletonAspect.cs b/Kinemation/Components/OptimizedSkeletonAspect.cs index e0d7765..0585087 100644 --- a/Kinemation/Components/OptimizedSkeletonAspect.cs +++ b/Kinemation/Components/OptimizedSkeletonAspect.cs @@ -92,6 +92,8 @@ namespace Latios.Kinemation /// To ensure the contents are valid, call CopyFromPrevious() if isDirty was false. /// This method sets isDirty and needsSync to true. Root-space transforms are not automatically synced /// when you make changes to this array. You must call EndSamplingAndSync() when you are done making changes. + /// The worldIndex of each transform is treated as a float representing the accumulated weights of all samples. + /// Assign any transform's worldIndex with math.asint(1f) to disable normalization fo that transform during sync. /// If you want the hierarchy to remain in sync at all times, iterate through the bones property instead. /// public NativeArray rawLocalTransformsRW @@ -102,6 +104,24 @@ public NativeArray rawLocalTransformsRW return m_boneTransforms.Reinterpret().AsNativeArray().GetSubArray(m_currentBaseRootIndexWrite + boneCount, boneCount); } } + + /// + /// Specifies if the next sample will overwrite transforms (true) or if they will be added to existing transforms (false). + /// This value will automatically be set to false after each SamplePose() call made on this skeleton. + /// InertialBlend(), EndSamplingAndSync(), and CopyFromPrevious() will all set this value to true. + /// You may wish to explicitly set this value when using masked sampling to start and end animation override masks. + /// + public bool nextSampleWillOverwrite + { + get => (m_skeletonState.ValueRO.state & OptimizedSkeletonState.Flags.NextSampleShouldAdd) != OptimizedSkeletonState.Flags.NextSampleShouldAdd; + set + { + if (value) + m_skeletonState.ValueRW.state &= ~OptimizedSkeletonState.Flags.NextSampleShouldAdd; + else + m_skeletonState.ValueRW.state |= OptimizedSkeletonState.Flags.NextSampleShouldAdd; + } + } #endregion #region Modification Methods @@ -168,6 +188,7 @@ public void CopyFromPrevious() { var array = m_boneTransforms.AsNativeArray(); array.GetSubArray(m_currentBaseRootIndexWrite, boneCount * 2).CopyFrom(array.GetSubArray(m_previousBaseRootIndex, boneCount * 2)); + InitWeights(array.GetSubArray(m_currentBaseRootIndexWrite, boneCount * 2).Reinterpret()); ref var state = ref m_skeletonState.ValueRW.state; state |= OptimizedSkeletonState.Flags.IsDirty; state &= ~(OptimizedSkeletonState.Flags.NeedsSync | OptimizedSkeletonState.Flags.NextSampleShouldAdd); @@ -190,6 +211,7 @@ public void ForceInitialize() { m_boneTransforms.Resize(requiredBones * 6, NativeArrayOptions.UninitializedMemory); var array = m_boneTransforms.AsNativeArray(); + InitWeights(array.GetSubArray(0, requiredBones).Reinterpret()); array.GetSubArray(requiredBones, requiredBones).CopyFrom(array.GetSubArray(0, requiredBones)); Sync(true); array.GetSubArray(requiredBones * 2, requiredBones * 2).CopyFrom(array.GetSubArray(0, requiredBones * 2)); diff --git a/Kinemation/Components/OptimizedSkeletonAspectInternals.cs b/Kinemation/Components/OptimizedSkeletonAspectInternals.cs index 7bcc7cf..da105cb 100644 --- a/Kinemation/Components/OptimizedSkeletonAspectInternals.cs +++ b/Kinemation/Components/OptimizedSkeletonAspectInternals.cs @@ -70,8 +70,8 @@ unsafe void StartInertialBlendInternal(float previousDeltaTime, float maxBlendDu for (int i = 0; i < boneCount; i++) { - var currentNormalized = currentLocals[i]; - currentNormalized.rotation = math.normalize(currentNormalized.rotation); + var currentNormalized = currentLocals[i]; + currentNormalized.NormalizeBone(); inertialBlends[i].StartNewBlend(in currentNormalized, previousLocals[i], twoAgoLocals[i], rcpTime, maxBlendDuration); } } @@ -91,7 +91,7 @@ unsafe void InertialBlendInternal(float timeSinceStartOfBlend) var ptrs = (TransformQvvs*)currentLocals.GetUnsafePtr(); for (int i = 0; i < currentLocals.Length; i++) { - ptrs[i].rotation = math.normalize(ptrs[i].rotation); + ptrs[i].NormalizeBone(); inertialBlends[i].Blend(ref ptrs[i], in blendTimes); } } @@ -127,21 +127,28 @@ void Sync(bool forceSync0 = false) rootTransforms[0] = TransformQvvs.identity; for (int i = 1; i < boneCount; i++) { - var parent = math.max(0, parentIndices[i]); - var local = localTransforms[i]; - local.rotation.value = math.normalize(local.rotation.value); - rootTransforms[i] = qvvs.mul(rootTransforms[parent], in local); + var parent = math.max(0, parentIndices[i]); + var local = localTransforms[i]; + local.NormalizeBone(); + rootTransforms[i] = qvvs.mul(rootTransforms[parent], in local); } { - var local = localTransforms[0]; - local.rotation.value = math.normalize(local.rotation.value); - localTransforms[0] = local; - rootTransforms[0] = local; + var local = localTransforms[0]; + local.NormalizeBone(); + localTransforms[0] = local; + rootTransforms[0] = local; } m_skeletonState.ValueRW.state &= ~(OptimizedSkeletonState.Flags.NeedsSync | OptimizedSkeletonState.Flags.NextSampleShouldAdd); m_skeletonState.ValueRW.state |= OptimizedSkeletonState.Flags.IsDirty; SyncHistory(); } + + unsafe void InitWeights(NativeArray bones) + { + var ptr = (TransformQvvs*)bones.GetUnsafePtr(); + for (int i = 0; i < bones.Length; i++) + ptr[i].worldIndex = math.asint(1f); + } } public partial struct OptimizedBone diff --git a/Kinemation/Utilities/Blenders.cs b/Kinemation/Utilities/Blenders.cs index 744d6be..fb10b59 100644 --- a/Kinemation/Utilities/Blenders.cs +++ b/Kinemation/Utilities/Blenders.cs @@ -18,8 +18,8 @@ namespace Latios.Kinemation /// To discard existing sampled poses and begin sampling new poses, simply create a new /// instance of BufferPoseBlender using the same input NativeArray. /// - /// To finish sampling, call NormalizeRotations(). Prior to this, any transforms in the array - /// may have unnormalized rotations, which can cause unwanted artifacts if used as is. + /// To finish sampling, call Normalize(). Prior to this, any transforms in the array + /// may have unnormalized transforms, which can cause unwanted artifacts if used as is. /// /// You may also use a BufferPoseBlender to compute root-space transforms. /// @@ -37,20 +37,25 @@ public BufferPoseBlender(NativeArray localSpaceBuffer) { bufferAsQvvs = localSpaceBuffer.Reinterpret(); sampledFirst = false; - normalized = true; + normalized = false; } /// - /// Normalizes the rotations to be valid quaternion rotations. Call this once after sampling all blended poses. - /// The result of GetLocalTransformsView() will contain valid quaternion rotations after calling this method. - /// You can perform IK operations after calling this method but before calling ApplyBoneHierarchyAndFinish(). + /// Normalizes the transforms to be valid using the accumulated weights stored in translation.w of each bone. + /// Call this once after sampling all blended poses. + /// The buffer passed in will contain valid local-space transforms after calling this method. /// - public unsafe void NormalizeRotations() + public unsafe void Normalize() { - var bufferPtr = (TransformQvvs*)bufferAsQvvs.GetUnsafePtr(); + var bufferPtr = (AclUnity.Qvvs*)bufferAsQvvs.GetUnsafePtr(); for (int i = 0; i < bufferAsQvvs.Length; i++, bufferPtr++) { - bufferPtr->rotation = math.normalize(bufferPtr->rotation); + if (bufferPtr->translation.w == 1f) + continue; + var weight = 1f / bufferPtr->translation.w; + bufferPtr->rotation = math.normalize(bufferPtr->rotation); + bufferPtr->translation *= weight; + bufferPtr->stretchScale *= weight; } normalized = true; } @@ -60,41 +65,57 @@ public unsafe void NormalizeRotations() /// The transform at index 0 is assumed to represent root motion, and so all other transforms ignore it, /// even if they specify index 0 as a parent. /// - /// - /// - public void ComputeRootSpaceTransforms(ref BlobArray parentIndices, ref NativeArray rootSpaceBuffer) + /// The parent index of each bone + /// The buffer to write the destination values. This is allowed to be the same as the local-space buffer. + public void ComputeRootSpaceTransforms(ReadOnlySpan parentIndices, ref NativeArray rootSpaceBuffer) { var localSpaceBuffer = bufferAsQvvs.Reinterpret(); if (!normalized) { + var temp = localSpaceBuffer[0]; + temp.NormalizeBone(); rootSpaceBuffer[0] = TransformQvvs.identity; for (int i = 1; i < localSpaceBuffer.Length; i++) { - var parent = math.max(0, parentIndices[i]); - var local = localSpaceBuffer[i]; - local.rotation.value = math.normalize(local.rotation.value); - rootSpaceBuffer[i] = qvvs.mul(rootSpaceBuffer[parent], in local); + var parent = math.max(0, parentIndices[i]); + var local = localSpaceBuffer[i]; + local.NormalizeBone(); + rootSpaceBuffer[i] = qvvs.mul(rootSpaceBuffer[parent], in local); } { - var local = localSpaceBuffer[0]; - local.rotation.value = math.normalize(local.rotation.value); - localSpaceBuffer[0] = local; - rootSpaceBuffer[0] = local; + localSpaceBuffer[0] = temp; + rootSpaceBuffer[0] = temp; } } else { + var temp = localSpaceBuffer[0]; rootSpaceBuffer[0] = TransformQvvs.identity; for (int i = 1; i < localSpaceBuffer.Length; i++) { var parent = math.max(0, parentIndices[i]); rootSpaceBuffer[i] = qvvs.mul(rootSpaceBuffer[parent], localSpaceBuffer[i]); } - rootSpaceBuffer[0] = localSpaceBuffer[0]; + rootSpaceBuffer[0] = localSpaceBuffer[0] = temp; } } // Todo: Baked Root space? Custom parent indices? Convert to Root-Space in-place? Convert to World-Space? } + + public static class BoneNormalizationExtensions + { + public static void NormalizeBone(ref this TransformQvvs localTransform) + { + var w = math.asfloat(localTransform.worldIndex); + if (w == 1f) + return; + w = 1 / w; + ref var t = ref UnsafeUtility.As(ref localTransform); + t.rotation = math.normalize(t.rotation); + t.translation *= w; + t.stretchScale *= w; + } + } } diff --git a/Mimic/Mecanim/Systems/ApplyMecanimLayersToExposedBonesSystem.cs b/Mimic/Mecanim/Systems/ApplyMecanimLayersToExposedBonesSystem.cs index a5a8e06..75646ec 100644 --- a/Mimic/Mecanim/Systems/ApplyMecanimLayersToExposedBonesSystem.cs +++ b/Mimic/Mecanim/Systems/ApplyMecanimLayersToExposedBonesSystem.cs @@ -214,7 +214,7 @@ public unsafe void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bo clipSet.clips[clipWeights[i].mecanimClipIndex].SamplePose(ref blender, time, blendWeight); } - blender.NormalizeRotations(); + blender.Normalize(); // Begin write-back with inertial blending bool startInertialBlend = controller.triggerStartInertialBlend; diff --git a/PsyshockPhysics/Physics/Authoring/CompoundColliderSmartBlobberSystem.cs b/PsyshockPhysics/Physics/Authoring/CompoundColliderSmartBlobberSystem.cs index 3d09502..1104fcb 100644 --- a/PsyshockPhysics/Physics/Authoring/CompoundColliderSmartBlobberSystem.cs +++ b/PsyshockPhysics/Physics/Authoring/CompoundColliderSmartBlobberSystem.cs @@ -176,7 +176,7 @@ public void Execute(ref SmartBlobberResult result, in DynamicBuffer