book-openChangelog

Version 1.7.0

Added ... Multilingual Text Lip Sync

  • TextLipSyncLanguage enum. New language selector for text-to-viseme conversion: Auto, English, Russian, Chinese, Japanese, Korean. Can be set in the Inspector or changed at runtime via CrystalTextLipSync.Language.

  • Auto detection. CrystalTextToViseme.DetectLanguage() scans the input text for Unicode script ranges (Cyrillic → Russian, Kana → Japanese, Hangul → Korean, CJK Ideographs → Chinese, Latin → English). Kana presence alongside CJK is recognised as Japanese.

  • Russian (Cyrillic) mapping. All 33 Russian letters mapped to visemes by phonetic category: vowels (а/е/ё/и/о/у/ы/э/ю/я), bilabials (п/б/м → PP), labiodentals (ф/в → FF), alveolars (д/т/л → DD), nasals (н → NN), velars (к/г/х → KK), fricatives (с/з/ц → SS), postalveolars (ш/щ/ч/ж → CH), trill (р → RR), semi-vowel (й → I), and hard/soft signs (ъ/ь → SIL).

  • Japanese mapping. 86-entry lookup table maps every hiragana (U+3041...U+3096) to its vowel sound (a/i/u/e/o/n). Katakana uses the same table offset by 0x60. CJK kanji distributed deterministically across five vowel visemes for visual variety.

  • Korean mapping. Hangul syllable blocks (U+AC00...U+D7A3) decomposed into Jamo; the medial vowel index (21 entries) determines the viseme. Compatibility Jamo vowels (U+314F...U+3163) also supported.

  • Chinese mapping. CJK Ideographs (U+4E00...U+9FFF, U+3400...U+4DBF) distributed across vowel visemes. Bopomofo (Zhuyin) phonetic symbols (U+3100...U+312F) mapped to consonant and vowel visemes.

  • CJK punctuation as silence. IsSilenceChar() recognises CJK Symbols & Punctuation (U+3000...U+303F) and fullwidth punctuation (U+FF00 block) as silence across all languages.

  • English label clarified. TextLipSyncLanguage.English explicitly documented as working for all Latin-alphabet languages (German, French, Spanish, Italian, Portuguese, etc.).

  • Backward-compatible API. The original Generate(text, charsPerSecond) overload is preserved and delegates to the new Generate(text, language, charsPerSecond) with Auto.

Added ... Setup Wizard: Eye Movement Toggle & Text Language Dropdown

  • Enable Eye Movement sub-toggle. When "Add Eye Blink & Movement" is checked, an indented "Enable Eye Movement" toggle appears (default: on). Sets CrystalEyeBlink.EnableEyeMovement on the created component, allowing users to add blinking without gaze movement.

  • Text Language dropdown. When "Add Text Lip Sync" is checked, an indented "Text Language" dropdown appears (default: Auto). Tooltip explains each language option and clarifies that English covers all Latin-script languages. The selected language is applied to the component and logged in the setup summary.

Added ... GC2 Crystal Eye Look At Bridge

  • RigCrystalEyeLookAt IK rig. New GC2 IK rig that bridges Game Creator 2's Look At IK system with CrystalEyeBlink's look target. When the character's RigLookTo has an active target, CrystalEyeBlink's eyes automatically track the same point. When the look target is cleared (via "Stop Looking At"), CrystalEyeBlink returns to idle eye movement. Add it to the character's IK rig list after "Look at Targets".

  • "Crescent" blink variants incorrectly preferred over standard blinks. On DAZ Genesis 9 models, facs_ctrl_EyeBlinkCrescentLeft/Right (squint-style partial blinks) were auto-mapped instead of the correct facs_bs_EyeBlinkLeft/Right (full eye closures). Both names contain "eye" + "blink" and scored identically at 500 pts; the crescent variants won simply by appearing at lower blendshape indices.

  • −300 score penalty for "crescent" token. ClassifyBlinkBlendshape() now detects the crescent token and reduces the match score by 300 (floored at 1), so standard EyeBlink* shapes (500 pts) always outrank EyeBlinkCrescent* shapes (200 pts).

Fixed ... DAZ Genesis 9 Brow Sync Side Detection

  • HD2 suffix blocked left/right detection. Genesis 9 FACS HD brow blendshapes (e.g. facs_bs_BrowOuterUpRight_HD2) end with hd2 after canonicalization, so DetectBrowSide() could not match EndsWith("right") or EndsWith("left"). All brow shapes were classified as "Both" instead of per-eye Left/Right.

  • Suffix stripping in DetectBrowSide(). The method now strips trailing hd2 and hd suffixes before checking for side indicators, correctly mapping BrowOuterUpRight_HD2 → Right and BrowSqueezeLeft_HD2 → Left.

  • Genesis 9 brow controller preference. Auto-detection now runs a Genesis 9-specific brow preference pass on targetMesh: it prioritizes per-eye brow up/down controller blendshapes when present (bipolar morphs using signed weights), falls back to per-eye raise/lower candidates, and avoids using Brow Raise (Both) / Brow Lower (Both) whenever valid Left/Right mappings exist.

Added ... Multi-Mesh Look Blendshape Support (DAZ Genesis 9)

  • Genesis 9 switched to bone-only eye movement. For DAZ Genesis 9, auto-detection now enables Force Bone Rotation, clears Look Mesh, and clears all eye movement look blendshape indices (Left/Right/Both) so Eye Movement Blendshape Mapping stays None.

  • Applied consistently across entry points. The same Genesis 9 bone-only behavior now runs when the component is added, when Setup Wizard configures Eye Blink, and when Detect All / Detect Eye Movement are used.

  • Per-eye bone fallback in hybrid mode. Eye movement now falls back to bone rotation per eye and per axis when look blendshapes are unmapped (None). If one eye/axis has a blendshape and the other does not, blendshape drives the mapped side while bones drive the unmapped side.

  • Fallback Phase 2 scan. For non-Genesis 9 models, the previous two-phase approach is preserved: Phase 1 scans targetMesh for both look and brow shapes; Phase 2 scans all other meshes if no look blendshapes were found.

  • Editor inspector support. The Look Blendshapes section shows the Look Mesh field with its own blendshape name dropdowns. Brow Sync dropdowns always reference the body mesh. Detection dialogs report whether look blendshapes were found on a separate mesh.

Added ... Genesis 9 Unity Import Guide (GitBook Markdown)

  • New troubleshooting/setup guide. Added a dedicated Genesis 9 guide documenting known DAZ3D→Unity import/export issues and recommended Crystal LipSync setup.

  • Eye movement recommendation. For Genesis 9, the guide recommends bone-driven eye movement (Force Bone Rotation on) with Look Mesh = None and all Eye Movement look blendshape slots left at None.

  • Blendshape profile recommendation. For Genesis 9 lip blendshape profiles, the guide recommends reducing vowel viseme strengths (A, E, I, O, U) to about 0.3 instead of 1.0.

  • Jaw recommendation. Guide recommends using a conservative jaw rotation limit around for jaw-bone-driven mouth motion on Genesis 9 imports.

Fixed ... DAZ Genesis 8.1 FACS Viseme Auto-Mapping

  • Missing FACS viseme codes. Added "sh" to the CH viseme codes and "iy" to the I viseme codes. DAZ Genesis 8.1 FACS blendshapes facs_ctrl_vSH and facs_ctrl_vIY were not recognised, causing CH and I to fall back to incorrect blendshapes.

  • Dedicated FACS viseme controls outscored by incidental matches. Added a +300 scoring bonus when blendshape tokens contain the DAZ FACS pattern ctrl v <code>. Without this, generic blendshapes like SmileOpenFullFace (matching code open for AA at 500 pts) and facs_bs_TongueOut (matching code tongue_out for TH at 500 pts) would tie or beat the correct facs_ctrl_vAA / facs_ctrl_vTH blendshapes. The bonus ensures dedicated viseme controls always win.

  • PBMStomachDepth false-positive on CH. The substring ch inside "stomachdepth" was matching at Tier 6 (50 pts), incorrectly mapping CH to a body morph. Now that facs_ctrl_vSH is recognised and boosted, it correctly claims the CH slot.

  • Affected visemes and corrections:

    • PP: facs_ctrl_MouthPressfacs_ctrl_vM (FACS bilabial)

    • TH: facs_bs_TongueOutfacs_ctrl_vTH (FACS dental)

    • DD: facs_jnt_TongueUpfacs_ctrl_vT (FACS alveolar)

    • CH: PBMStomachDepthfacs_ctrl_vSH (FACS postalveolar)

    • AA: SmileOpenFullFacefacs_ctrl_vAA (FACS open vowel)

    • I: SFD_Gen8F_TC_TipWidefacs_ctrl_vIY (FACS close front vowel)

Changed ... DAZ Genesis 9 Jaw Bone Setup

  • Genesis 9 jaw bone max angle set to 8° in Combined mode. Genesis 9 blendshapes have lighter jaw-opening deltas than older Genesis models (2/3/8/8.1), so the jaw bone needs a larger rotation range for natural mouth movement. Previously Genesis 9 fell into the generic DAZ branch at 4°.

  • CharacterModelType.DAZGenesis9 enum value. The Setup Wizard now distinguishes Genesis 9 from older Genesis models. DetectModelType() checks for "genesis9" in mesh/GameObject names before the generic "genesis" pattern.

  • All DAZ-specific wizard behaviors extended to Genesis 9. SIL mapping auto-clear, separate L/R blink (clearing "Both"), eyelash blink synchronisation, and jaw bone axis/direction configuration now apply to both DAZGenesis and DAZGenesis9.

Fixed ... DAZ Genesis 9 Look Blendshape Auto-Detection

  • Genesis 9 FACS look blendshapes not detected. The CrystalEyeBlink auto-detection could not find look blendshapes on DAZ Genesis 9 models. After mesh-prefix stripping and canonicalization, names like facs_bs_EyeLookUpRight become facsbseyelookupright ... the facsbs/facsctrl/baseanimefacscbs FACS prefixes remained in the string, preventing any existing matcher (ARKit, DAZ eCTRL, VRM, generic) from recognising them.

  • TryMapGenesis9Look() method. New matcher that strips Genesis 9 FACS prefixes (facsbs, facsctrl, facscbs, baseanimefacscbs) to extract the core ARKit-like pattern, then matches:

    • Per-eye directional: EyeLookUp, EyeLookDown, EyeLookIn, EyeLookOut + Left/Right (8 blendshapes). Uses ARKit In/Out conventions (In = toward nose, Out = away).

    • Bipolar up/down: EyeLookUp-Down, EyeLookUp-DownLeft, EyeLookUp-DownRight ... mapped conditionally (only fills slots still at -1) to avoid overwriting superior per-axis direct mappings.

    • Bipolar side-to-side: EyeLookSide-Side, EyeLookSide-SideLeft, EyeLookSide-SideRight ... same conditional logic.

  • BaseAnime_facs_cbs_ variants also detected. The baseanimefacscbs prefix is stripped first (longest match), covering Genesis 9 BaseAnime facial blendshapes.

  • facs_ctrl_ preferred over facs_bs_. Since facs_ctrl_ blendshapes appear after facs_bs_ in the mesh and direct matches overwrite unconditionally, the controller-level blendshape (higher quality) automatically wins for per-eye slots.

  • "squeeze" added to BrowLowerTokens. Genesis 9 facs_bs_BrowSqueezeLeft/Right blendshapes (brow compression) are now detected as brow-lower candidates.

  • "facsctrl", "facscbs", "baseanimefacscbs" added to BrowStripPrefixes. Ensures brow core-name analysis strips Genesis 9 FACS prefixes cleanly before checking raise/lower/side tokens.

Added ... Genesis 9 FACS Mouth Supplement Mappings

  • Supplementary FACS mouth blendshapes for Genesis 9. When a Genesis 9 model has optional FACS mouth blendshapes (facs_ctrl_MouthClose, facs_ctrl_MouthPucker, facs_ctrl_MouthStretch, facs_ctrl_MouthFunnel, facs_bs_TongueOut, facs_ctrl_TongueUp, etc.), they are now automatically added as additional entries on the viseme mappings after the primary facs_ctrl_v* auto-map. These supplement ... not replace ... the existing viseme shapes, enhancing mouth articulation quality.

  • HasGenesis9FacsMouthShapes() detection. Returns true if ≥3 of the 6 signature FACS mouth names (mouthclose, mouthfunnel, mouthpucker, mouthstretch, tongueout, tongueup) are found on the mesh.

  • Genesis9FacsSupplementMappings[15][] preset table. Defines per-viseme supplementary entries with conservative weights:

    • PP: MouthClose 80% + MouthCompress 40%

    • FF: MouthFunnel 50%

    • TH: TongueOut 40%

    • DD: TongueUp 30% + MouthStretch 15%

    • CH: MouthFunnel 50% + MouthPurse 30%

    • SS: MouthStretch 40%

    • NN: MouthClose 40%

    • RR: MouthPucker 25%

    • AA: MouthFunnel 15%

    • E: MouthStretch 50%

    • I: MouthStretch 30%

    • O: MouthPucker 60%

    • U: MouthPucker 70%

  • ApplyGenesis9FacsSupplement(). Builds a core-name → index lookup by stripping both mesh prefix and FACS prefix, then adds matching entries via AddMapping(). Returns the number of entries added. Prefers facs_ctrl_ over facs_bs_ variants (higher quality controller-level blendshapes).

  • Setup Wizard integration. After the standard auto-map for non-ARKit models, the wizard checks HasGenesis9FacsMouthShapes() and applies the supplement. Log shows the count of added entries.

  • Editor Auto-Map integration. The "Auto-Map" button in the BlendshapeTarget inspector also applies the supplement when Genesis 9 FACS mouth shapes are detected. The results dialog shows the supplement count.

Version 1.6.0

Added ... Yarn Spinner Integration

  • CrystalLipSync.YarnSpinner assembly. New optional assembly (CrystalLipSync.YarnSpinner.asmdef) referencing CrystalLipSync.Runtime and YarnSpinner.Unity.

  • CrystalYarnLipSync component. Per-character bridge between Yarn Spinner and CrystalLipSync. Static registry maps Yarn character names to components. Auto-provisioning creates instances on-demand if a character GameObject is found by name.

  • CrystalYarnTextPresenter component. DialoguePresenterBase that feeds dialogue text to CrystalTextLipSync for text-driven lip sync during Yarn dialogue. Supports per-character targeting via CrystalYarnLipSync registry.

  • CrystalYarnVoiceOverPresenter component. DialoguePresenterBase replacing Yarn Spinner's VoiceOverPresenter. Routes voice-over audio clips to the character's AudioSource for audio-driven lip sync. Handles missing clips and missing AudioSource gracefully without prematurely advancing dialogue.

  • Demo Yarn scripts. CrystalLipSyncDemo.yarn (text-driven demo) and CrystalVoiceOverDemo.yarn (audio-driven demo) with setup instructions.

  • Audio folder with README. Instructions for naming voice-over clips to match Yarn Spinner's localisation system.

  • .yarnproject localisation config. Added en.assets = "../Audio" to CrystalVoiceOverDemoProject.yarnproject so Yarn Spinner locates audio clips correctly.

Fixed ... Yarn Spinner Integration

  • Dialogue flashing / premature line advance. CrystalYarnVoiceOverPresenter no longer calls RequestNextLine() when no audio clip is present or when the character has no AudioSource. Previously this cancelled all active presenters immediately, causing dialogue to flash and disappear.

  • VisemeType cast error (CS1503). Fixed SetMapping(LipSyncMood.Neutral, v, mapping[v])SetMapping(LipSyncMood.Neutral, (VisemeType)v, mapping[v]) in CrystalYarnLipSync.

Added ... Natural Eye Movement System

  • Eye movement merged into CrystalEyeBlink. The existing blink component now also drives natural idle eye movement, providing lifelike gaze behaviour in all situations (dialogue, idle, gameplay) without requiring a separate component.

  • Saccade system. Large eye movements every 2...5 seconds with Gaussian center-biased random targets. Configurable amplitude (default 15°) and speed (default 400°/s).

  • Micro-saccade system. Small involuntary eye movements (~0.5°) every ~600ms, adding subtle liveliness between saccades.

  • Slow drift. Continuous low-amplitude drift (default 1°) simulating natural fixation instability.

  • Vestibulo-ocular reflex (VOR). Counter-rotates eyes when the head moves, keeping gaze stable during head motion. Tracks head bone rotation delta per frame and applies inverse offset to eye gaze. Decay rate returns eyes to center when head stops moving.

  • Blendshape-driven gaze. Applies gaze offsets to up/down/left/right look blendshapes (per-eye or combined). Preferred method when look blendshapes are detected.

  • Bone rotation fallback. When no look blendshapes are found but eye bones exist, rotates eye bones directly (up to configurable maxBoneAngle, default 20°). Captures baseline rotations on init to avoid overriding existing animations.

  • Auto-detect look blendshapes. AutoDetectLookBlendshapes(root) scans all meshes for look direction blendshapes. Supports ARKit (eyeLookUpLeft, eyeLookInLeft, etc.), DAZ (eCTRLEyesUpDownL, eCTRLEyesSideSideR), VRM (Fcl_EYE_LookUp, etc.), and generic patterns (eyeLookUp, eyesLookLeft).

  • Auto-detect bones. AutoDetectBones() finds head and eye bones via Animator.GetBoneTransform(HumanBodyBones) with name-based fallback for non-humanoid rigs (CC_Base_Head, lEye, rEye, mixamorig:Head, etc.).

  • 12 look blendshape indices. Per-eye (LookUpLeft, LookUpRight, LookDownLeft, LookDownRight, LookLeftLeft, LookLeftRight, LookRightLeft, LookRightRight) and combined (LookUpBoth, LookDownBoth, LookLeftBoth, LookRightBoth) for maximum model compatibility.

  • RedetectEyeMovement() public API. Re-runs bone and look blendshape detection at runtime.

Changed ... Eye Movement

  • CrystalEyeBlinkEditor updated. Inspector now uses foldout sections: "Eye Blink" and "Eye Movement". Eye movement section shows all movement parameters, bone fields, per-eye and combined look blendshape dropdowns, and "Detect Eye Movement" / "Detect All" buttons.

  • Setup Wizard updated for eye movement. The wizard now calls AutoDetectBones() and AutoDetectLookBlendshapes() after creating the CrystalEyeBlink component. Log messages report detected look blendshape count and bone fallback status. Toggle label changed to "Add Eye Blink & Movement" with updated tooltip.

  • Welcome text updated. Wizard description now mentions "natural blinking + eye movement".

Added ... Synkinetic Brow Movement

  • Brow sync system. Eyebrows now subtly follow vertical gaze direction ... looking up raises the brows, looking down lowers them. This mimics the natural synkinetic reflex between eye and brow muscles.

  • browSyncIntensity setting. Controls how strongly brows follow gaze (0...1, default 0.25). Brow raise is applied at full intensity, brow lowering at 50% for a natural asymmetric response.

  • 6 brow blendshape indices. browRaiseLeft/Right/Both and browLowerLeft/Right/Both, supporting per-eye and combined brow blendshapes.

  • Auto-detection of brow blendshapes. Scans for brow raise/lower blendshapes across ARKit (browOuterUpLeft/Right, browInnerUp, browDownLeft/Right), DAZ Genesis (eCTRLBrowRaiseInner/Outer, eCTRLBrowDown), VRM (Fcl_BRW_Raise/Down), iClone CC (Brow_Raise_Inner_L/R, Brow_Drop_L/R), and generic patterns.

  • Brow sync section in CrystalEyeBlinkEditor. Inspector shows brow sync intensity slider and per-eye + combined brow raise/lower blendshape dropdowns under the Eye Movement foldout.

  • Wizard and detection dialogs updated. Setup wizard log and Detect All / Detect Eye Movement dialogs now report brow blendshape detection results.

Notes ... Eye Movement

  • Eye movement is enabled by default when look blendshapes or eye bones are detected. Disable via the enableEyeMovement toggle in the inspector.

  • Saccade targets use a Gaussian distribution biased toward the center of the eye's range, producing natural-looking gaze patterns rather than uniform random movements.

  • VOR requires a head bone to be assigned. Without it, VOR is silently skipped.

  • The system works across all model types: ARKit (per-eye look blendshapes), iClone CC (per-eye), DAZ Genesis (combined up/down + side-to-side), VRM (combined), and generic humanoid rigs (bone rotation fallback).

Added ... Look Target Tracking

  • Optional look-at target. Assign a Transform to lookTarget and the eyes will track it in world space. Leave it empty for pure natural idle gaze.

  • Runtime-switchable target. LookTarget property can be set or cleared at any time. Gaze smoothly transitions when the target changes or is removed.

  • Harmonized with natural movement. At full lookTargetWeight (1.0), 15% of the natural eye movement (micro-saccades, drift) is preserved as subtle perturbation, keeping the gaze alive rather than mechanically locked on.

  • lookTargetWeight (0...1). Blends between natural idle gaze and target tracking. At 0 the target is ignored; at 1 the eyes lock on with micro-movement overlay.

  • lookTargetSmoothing (1...30, default 8). Controls transition speed when the target moves. Higher values produce snappier tracking.

  • lookTargetMaxAngle (5...60°, default 35°). Maximum angular deflection the eyes will follow. Targets outside this cone are clamped to the edge.

  • WorldToGaze() helper. Converts a world-space position to normalised −1...1 gaze coordinates relative to the head bone (or eye midpoint / character root as fallback). Uses Atan2 decomposition into yaw and pitch, normalised by lookTargetMaxAngle.

  • Smooth decay on target removal. When the look target is cleared, smoothedTargetGaze decays toward zero using the same smoothing rate, producing a natural return to idle gaze.

Changed ... Look Target

  • CrystalEyeBlinkEditor updated. Inspector now shows a "Look Target" section inside the Eye Movement foldout with Transform field, weight slider, smoothing, and max angle. Info box updated to describe the feature.

  • UpdateEyeMovement() pipeline extended. Natural gaze is now computed as naturalGaze, then blended with the smoothed look target direction before being applied to blendshapes/bones.

Fixed ... Look Target

  • WorldToGaze() using wrong reference frame. Was using headBone.forward/right/up for direction decomposition, but head bone local axes vary wildly between rigs (CC3 Z-forward, ARKit Y-forward, etc.), causing eyes to track the wrong direction (e.g. looking down-left instead of down-right). Fixed by caching a gazeReferenceTransform from the Animator root transform in InitEyeMovement() and using its stable forward/right/up for yaw/pitch decomposition while keeping head bone position as the gaze origin.

  • Enhanced debug logging. Debug output now shows GazeRef transform name, all 12 blendshape indices, and look-target specifics (target position, head position, direction vectors, raw gaze).

Fixed ... DAZ Genesis 8 Brow Mapping

  • JCM corrective morphs hijacking brow slots. DAZ Genesis 8 exports include JCM (Joint Controlled Morph) correctives like eJCMOwen8_eCTRLBrowUp-DownR that contain brow keywords. These were being matched as brow blendshapes and stealing slots from the actual brow morphs. Fixed by filtering names containing ejcm or jcm in AutoDetectLookBlendshapes().

  • Bipolar brow morphs not handled. DAZ eCTRLBrowUp-Down is a single bipolar morph (positive = raise, negative = lower) but was only mapped to either raise or lower. TryMapBrow() now detects when the core name contains both "up" and "down", mapping the same index to both raise and lower slots. ApplyBrowSync() detects shared raise/lower indices and uses SetBipolarWeight() with a net signed value.

Added ... Multi-Blendshape Brow Support

  • BrowBlendEntry class. New [Serializable] class (matching the viseme BlendEntry pattern) with blendshapeIndex (int, default −1) and weight (float, 0...200%, default 100%).

  • 6 extras arrays. browRaiseLeftExtra, browRaiseRightExtra, browRaiseBothExtra, browLowerLeftExtra, browLowerRightExtra, browLowerBothExtra ... each a BrowBlendEntry[] supporting additional brow blendshapes per slot.

  • AssignBrowSlot() static method. Auto-detection routes overflow blendshapes to extras when the primary slot is already taken (with duplicate prevention). For example, DAZ G8's outer brow (eCTRLBrowUp-Down) maps to the primary slot and inner brow (eCTRLBrowInnerUp-Down) maps to extras automatically.

  • ApplyBrowExtras() method. Drives all extra entries per brow slot with per-entry weight scaling during ApplyBrowSync().

  • Editor "+" button for brow extras. Each of the 6 brow slots now shows a "+" button to manually add additional blendshape entries. Extra rows display a blendshape dropdown, weight field (0...200%), and "−" remove button ... matching the viseme multi-blendshape UI pattern.

  • CountBrowMappings() helper. Detect dialogs now include extras in their brow blendshape count.

Added ... Hybrid Blendshape/Bone Eye Movement

  • Automatic per-axis hybrid mode. The gaze system now independently checks horizontal and vertical blendshape coverage. If look blendshapes only cover one axis (e.g. DAZ eCTRLEyesUpDown for vertical only), the missing axis is automatically driven by bone rotation. No configuration required.

  • HasVerticalLookBS() / HasHorizontalLookBS() helpers. Check whether any vertical (up/down) or horizontal (left/right) look blendshapes are mapped, respectively. HasAnyLookBlendshape() now delegates to these.

  • ApplyGazeToBones() axis parameters. Accepts applyYaw and applyPitch booleans so bone rotation can be applied selectively for unmapped axes only, without affecting the axis driven by blendshapes.

  • Debug mode shows hybrid state. Debug logging now shows Mode=Hybrid(H=Bone,V=BS) (or similar) when partial blendshape coverage is detected, alongside Blendshapes, Bones, and ForcedBones.

Notes

  • The hybrid mode is fully automatic ... no toggle or setting needed. If all look blendshapes are mapped, behaviour is identical to before (pure blendshape mode). If none are mapped, it falls back to pure bone mode. Partial coverage triggers hybrid.

  • Force Bone Rotation still overrides everything to bones-only regardless of blendshape mappings.

Fixed ... Brow "Both" Slot Clearing

  • Combined brow morphs discarded when per-eye brow morphs exist. The over-driving prevention logic was clearing browRaiseBothIndex and browLowerBothIndex entirely when per-eye brow slots were also populated. On DAZ Genesis 8, this caused eCTRLBrowInnerUp-Down (a combined both-eyes morph) to be silently dropped. Fixed by demoting the "Both" primary to an extra on each per-eye slot instead of discarding it. Affects Genesis 3, 8, and 9 (which all have both per-eye and combined brow morphs). Genesis 2 is unaffected (only combined morphs exist).

Changed ... Jaw Bone Defaults

  • iClone CC jaw bone max angle reduced from 10° to 8°. Provides more natural jaw movement in Combined mode. DAZ Genesis remains at 4°, ARKit/FACS at 6°.

Changed ... Setup Wizard & GC2 Integration

  • Setup Wizard no longer blocks Eye Blink on Game Creator 2 characters. The wizard previously detected GameCreator.Runtime.Characters.Character and disabled the "Add Eye Blink & Movement" toggle, assuming GC2's built-in IK eye blink would conflict. This restriction has been removed ... CrystalEyeBlink now offers significantly more features (eye movement, brow sync, look targets, hybrid blendshape/bone mode) and should be used instead.

  • RigCrystalEyeBlink marked [Obsolete]. Users should use the Setup Wizard to add the CrystalEyeBlink component directly, which provides eye movement, brow sync, look target tracking, and hybrid blendshape/bone support that the GC2 IK rig does not.

  • RigCrystalLipSync marked [Obsolete]. Users should use the Setup Wizard (Tools → Crystal LipSync → Setup Wizard) for better cross-model compatibility, auto-detection, and feature coverage.

Version 1.5.0

Added

  • Multi-blendshape per viseme architecture. Each VisemeBlendshapeMapping now supports a primary blendshape plus an array of BlendEntry additional entries, each with an independent weight. LateUpdate iterates all entries per viseme. Fully backward-compatible ... existing single-blendshape mappings continue to work unchanged.

  • BlendEntry class. New serializable class holding blendshapeIndex + weight for additional blend contributions beyond the primary slot.

  • AddMapping() / ClearMapping() API on BlendshapeTarget. AddMapping fills the primary slot first, then appends to additionalEntries. ClearMapping resets both primary and additional entries for a given viseme.

  • ARKit / FACS model detection. IsARKitModel(smr) checks for ≥6 ARKit signature blendshape names (jawOpen, mouthFunnel, mouthPucker, mouthSmileLeft/Right, mouthClose, mouthStretchLeft/Right, etc.).

  • ARKit preset combination mappings. ARKitPresetMappings[15][] provides tuned multi-blendshape combinations for all 15 Oculus visemes using anatomical ARKit blendshapes:

    • SIL: (none)

    • PP: mouthClose 100% + mouthPressLeft/Right 60%

    • FF: mouthFunnel 80% + mouthLowerDownLeft/Right 40%

    • TH: jawOpen 25% + mouthFunnel 30%

    • DD: jawOpen 20% + mouthStretchLeft/Right 20%

    • KK: jawOpen 30%

    • CH: mouthFunnel 70% + mouthShrugUpper/Lower 40%

    • SS: mouthStretchLeft/Right 50% + jawOpen 10%

    • NN: mouthClose 50% + jawOpen 10%

    • RR: mouthRollLower 60% + mouthRollUpper 40% + mouthPucker 30%

    • AA: jawOpen 80% + mouthFunnel 20%

    • E: mouthStretchLeft/Right 60% + jawOpen 25%

    • I: mouthSmileLeft/Right 70% + jawOpen 15%

    • O: mouthPucker 80% + jawOpen 35%

    • U: mouthPucker 90% + jawOpen 20%

  • ApplyARKitPreset(). Builds a canonical name→index lookup from the mesh, clears existing mappings, and applies all preset entries via AddMapping().

  • ARKit integration in Setup Wizard. Step 3 now detects ARKit models and branches to ApplyARKitPreset() instead of the standard single-blendshape auto-map.

  • ARKit integration in Editor Auto-Map. The "Auto-Map by Name" button in the BlendshapeTarget inspector detects ARKit models and applies the preset. Standard auto-map also clears any leftover additionalEntries.

  • Editor UI for multi-blendshape mappings. Each viseme row in the BlendshapeTarget inspector now shows a "+" button to add additional blendshape entries listed as indented rows with "−" remove buttons. Copy, Paste, and Clear operations all handle additional entries.

Fixed

  • ARKit preset typo. Corrected "mouthstretchtleft" → "mouthstretchleft" (extra 't') in ARKitSignatureNames and three preset entries (DD, SS, E). Without this fix, mouthStretchLeft would silently fail to match.

Notes

  • ARKit/FACS models use anatomical muscle blendshapes rather than pre-composed viseme shapes. A single blendshape per viseme produces barely visible lip movement. The multi-blendshape preset combines 2...3 ARKit shapes per viseme to approximate each mouth pose.

  • Preset weights are starting values tuned for typical ARKit-rigged models. Users can fine-tune individual weights via the inspector's per-entry weight sliders.

  • The multi-blendshape architecture is generic and not limited to ARKit ... users can manually add additional blendshape entries for any model type via the "+" button in the inspector.

Added (continued)

  • ARKit jaw bone max angle (6°). The Setup Wizard now sets the jaw bone max angle to 6° for ARKit/FACS models in Combined mode, sitting between iClone CC (15°) and DAZ Genesis (4°). ARKit preset blendshapes include moderate jaw contribution, so a mid-range bone angle is appropriate.

  • Eyelash blink synchronisation for DAZ Genesis. The wizard now auto-detects auxiliary meshes (eyelashes) that share blink blendshape names with the body mesh via FindAuxiliaryBlinkMeshes(). If a CrystalBlendshapeSynchronizer already exists from viseme sync (step 3b), the eyelash mesh is merged into it; otherwise a new synchronizer is created. This ensures DAZ eyelashes blink in sync with the body mesh.

  • FindAuxiliaryBlinkMeshes() in AutoMapper. Finds auxiliary meshes sharing blink blendshape names (canonical match) with the master body mesh, given the detected blink L/R/Both indices.

Changed

  • Eye blink "Blink Both (combined)" cleared for DAZ and iClone. The wizard now sets BlinkBothIndex = -1 (None) after auto-detection for DAZ Genesis and iClone CC models. These models have separate L/R blink blendshapes, so using a combined mapping would double-drive the blink.

Version 1.4.0

Added

  • Blendshape Synchronizer ... new runtime component (CrystalBlendshapeSynchronizer) that copies blendshape weights from a master mesh to auxiliary (slave) meshes every frame. CC3/CC4/CC5 and DAZ Genesis models have auxiliary meshes (eyelashes, beards, stubble, clothing) that duplicate the body's blendshapes and need to stay in sync during lip sync.

  • Auto-detection of auxiliary meshes ... new CrystalLipSyncAutoMapper.FindAuxiliaryVisemeMeshes() method scans the character hierarchy for SkinnedMeshRenderers that share blendshape names with the master mesh. Uses two-tier detection: Criterion A matches viseme-scoring blendshapes (CC models), Criterion B detects high general overlap (≥ 5 shared blendshapes) for DAZ Genesis eyelashes and similar face-adjacent meshes that duplicate body morphs without matching viseme naming. Clothing, hair, and accessory meshes are excluded from Criterion B via NonAuxiliaryMeshPatterns.

  • Synchronizer editor inspector (CrystalBlendshapeSynchronizerEditor) with Auto-Detect Auxiliaries and Rebuild Mappings buttons, per-mesh sync pair counts, and add/remove controls.

  • Combined lip sync mode ... the Setup Wizard now supports a third target mode (Combined) that adds both a BlendshapeTarget and a JawBoneTarget to the same character. CC/iClone and DAZ Genesis models use blendshapes for lip shaping and jaw bone rotation for mouth opening ... both are needed for realistic results.

  • Jaw bone auto-detection in wizard ... OnTargetChanged now probes the hierarchy for jaw bone transforms and auto-selects Combined mode when both viseme blendshapes and a jaw bone are found.

  • Model-specific jaw bone configuration ... the Setup Wizard detects whether the character is iClone/CC or DAZ Genesis and automatically configures the JawBoneTarget:

    • iClone/CC: Z axis, inverted direction, 15° max angle.

    • DAZ Genesis: X axis, positive direction, 4° max angle (blendshapes handle most jaw opening).

  • DAZ Genesis SIL mapping auto-clear ... the wizard clears the SIL (Silence) blendshape mapping on DAZ Genesis models to prevent the rest-pose blendshape from pushing the jaw into the upper face.

  • SetRotationAxis(), SetInvertDirection(), SetMaxAngle() ... new public API on CrystalLipSyncJawBoneTarget for programmatic configuration.

  • DAZ Genesis preferred mesh patterns ... FindBestVisemeMesh and CrystalEyeBlink.AutoDetectBlendshapes now recognize Genesis2...9 mesh naming (Genesis8Male.Shape, Genesis8Female.Shape, etc.) as preferred body meshes, preventing auxiliary meshes like asjeans_16097.Shape from being selected as the primary target.

  • IsPreferredMesh() public API on CrystalLipSyncAutoMapper ... reusable helper for any component that needs to identify the main body mesh vs. auxiliaries.

Fixed

  • 3D spatialized audio producing silence ... GetSpectrumData / GetOutputData return post-spatialization data, which is near-zero when the AudioListener is far from the source. The controller now auto-detects spatialBlend > 0 and switches to direct AudioClip PCM sampling (AudioClip.GetData), bypassing the entire Unity audio pipeline including distance rolloff. Debug log shows path=clip-direct-3d.

  • Separator-agnostic blendshape matching ... new Canonicalize() method strips all separators (underscores, spaces, hyphens, dots) and lowercases names before comparison. iClone CC models export blendshapes inconsistently across versions: Dental_Lip (CC4), Dental Lip (CC3), DentalLip (some exports), dental-lip ... all now match correctly at Tier 1 (1000 points).

  • Non-viseme blendshape false positives ... added early-exit blacklist for non-viseme tokens (brow, eye, cheek, nose, squint, blink, etc.). Blendshapes like Brow_Raise_Inner_L no longer falsely match the NN viseme via the substring "nn" inside "inner".

  • Directional indicator false positives ... new IsDirectionalIndicator() guard prevents single-letter tokens l/r/d/u from matching viseme codes when they're actually directional suffixes. Jaw_Rotate_D no longer matches DD (the "D" means Down), Lips_Drop_R_Side no longer matches RR (the "R" means Right).

  • Multi-word code matching ... Tier 2 now checks multi-word codes (e.g., dental_lip) as phrases against joined tokens, so CC3 names with spaces (Dental Lip → tokens ["dental", "lip"]) correctly match at score 500.

  • Tier 3 prefix pattern normalization ... multi-word codes in prefix patterns ("v dental lip") are now normalized so they match regardless of original separator style.

  • Tier 6 canonical substring matching ... weak substring matches now use canonicalized forms so partial code matches work across all separator variants.

  • Mesh selection picking wrong GameObject ... FindBestVisemeMesh previously picked CC_Base_EyeOcclusion or Stubble over CC_Base_Body because they shared blendshape names and scored similarly. Fixed with:

    • Non-viseme mesh exclusion list (eyeocclusion, tearline, eyelash, stubble, tearduct, hair, teeth, tongue, clothing, etc.)

    • +100,000 score bonus for preferred mesh names (body, face, head, Genesis patterns)

  • Eye blink picking wrong mesh ... CrystalEyeBlink.AutoDetectBlendshapes now applies the same preferred-mesh bonus, preventing auxiliary meshes from being selected for eye blink detection.

  • Combined mode jaw clipping on DAZ Genesis ... in Combined mode, DAZ viseme blendshapes already include jaw-opening vertex deltas (applied in bind pose before skinning). A 15° bone rotation on top of that caused the lower jaw to arc into the upper mouth. Fixed by reducing max angle to 4° for DAZ Genesis (blendshapes do the heavy lifting) while keeping 15° for iClone/CC (blendshapes don't include jaw opening).

Changed

  • Setup Wizard now automatically adds the CrystalBlendshapeSynchronizer component when auxiliary meshes with matching viseme blendshapes are detected during setup.

  • Setup Wizard target mode dropdown now has three options: Blendshape, Jaw Bone, Combined ... with context-aware auto-detection helpbox.

  • Setup Wizard detects iClone/CC vs DAZ Genesis models via DetectModelType() and configures jaw axis, direction, max angle, and SIL mapping accordingly.

  • ScoreMesh refactored into ScoreMeshDetailed (returns distinct viseme count alongside total score) for better mesh ranking.

Version 1.3.2

Added

Reallusion Character Creator (CC3/CC4/CC5) auto-mapping. CrystalLipSyncAutoMapper now recognizes Character Creator "Pairs" viseme blendshape naming and maps it to the Oculus 15-viseme setup (including CC3, CC4/CC5 V_ prefixed names, and CC Direct short codes like AE, EE, Er, Oh, Ah, Th).

DAZ Studio Genesis auto-mapping (Genesis 2 / 3 / 8). The auto-mapper now detects DAZ's Preston-Blair 10-viseme morph sets and maps them to the closest Oculus visemes (e.g., Rest→SIL, M→PP, F→FF, L→DD, K→KK, AA→AA, EH→E, OW→O, UW→U, W→U).

Improved DAZ fallback detection. Added support so Genesis 2 "vsm" prefixed morph names are more reliably recognized in fallback matching.

Developer docs update. Added a MatchRules doc comment listing all supported naming conventions (Oculus/VRC, Character Creator, CC Direct, DAZ, descriptive/ARKit).

Fixed

Eye blink auto-detection for DAZ internal morph names. Updated the tokenizer used by CrystalEyeBlink and the GC2 IK eye blink variant to correctly split acronym-to-word boundaries, fixing detection for DAZ exports such as PHMEyesClosed, CTRLEyesClosed, and eCTRLEyesClosed(L/R).

Notes

Character Creator: Tongue_Narrow and Tongue_Lower don't have a strong Oculus equivalent and are left unmapped (manual assignment recommended if you use them).

DAZ Genesis 8.1 / 9: These generations rely on FACS morphs (not dedicated visemes). Eye blink auto-detection works out of the box, but lip sync viseme matching is only best-effort unless you provide dedicated viseme shapes or assign blendshapes manually.

DAZ viseme coverage: DAZ's Preston-Blair set has fewer shapes than Oculus 15, so some Oculus slots (e.g., TH/CH/SS/NN/RR) may remain unmapped depending on the model.

Version 1.3.1

Added

WebGL build-target awareness in the Setup Wizard. The wizard now detects when the active build target is WebGL and adjusts its setup options accordingly. Baked Lip Sync option for WebGL. When targeting WebGL, the wizard shows a Baked Lip Sync toggle to add CrystalBakedLipSync to your character. WebGL baking notice. The wizard now displays a warning explaining that real-time audio analysis isn't available on WebGL and that lip sync must be baked offline via Tools → Crystal LipSync → Bake Lip Sync.

Changed Microphone Lip Sync disabled on WebGL. The wizard now automatically disables the microphone option when the build target is WebGL, with an info message explaining Unity's platform limitation and noting that WebGL microphone support is planned for a future update. Wizard guidance updated. The character help box now lists CrystalBakedLipSync among the components the wizard can provision. Safety guard for WebGL. The setup process now prevents adding microphone-related components when building for WebGL to avoid unsupported configurations.

Version 1.3.0

Added

  • WebGL real-time audio lip sync.

    • New WebGL path captures raw PCM via OnAudioFilterRead, runs a pure-managed FFT, and feeds the same viseme classification pipeline used on desktop. Includes CrystalLipSyncAudioCapture, CrystalFFT, controller auto-routing on WebGL, plus a forceAudioCapture toggle for testing and an IsUsingAudioCapture property.

  • Pre-Baked (offline) Lip Sync workflow.

    • Bake AudioClips into CrystalLipSyncClipData assets and play them back at runtime with zero-cost analysis using CrystalBakedLipSync. Includes the Bake Window (Tools → Crystal Lip Sync → Bake Lip Sync) with batch baking, preview timeline, profile import, and re-bake-in-place.

  • Game Creator 2:

    • Baked Lip Sync integration.

  • CrystalBakedClipLookup (AudioClip → baked data map) with editor utilities.

  • GC2 instructions: Play Baked Lip Sync and Stop Baked Lip Sync (with optional audio playback + wait-to-complete).

  • Direct AudioClip sampling when AudioSource is silenced.

    • CrystalLipSyncController now detects near-zero volume and switches to a clip-sampling path (AudioClip.GetData() + managed FFT), bypassing Unity's audio pipeline. (Requires AudioClip set to Decompress On Load or Compressed In Memory, not Streaming.)

  • Jaw Bone Target (no blendshapes required).

    • Added CrystalLipSyncJawBoneTarget (Viseme/Volume/Combined drive modes) plus a custom editor with auto-detect, live debug view, and a test-pose slider. Setup Wizard now supports selecting Blendshape vs JawBone target mode and auto-suggests based on the character.

  • Auto-mapper: Daz Studio Genesis 2/3/8 support.

    • Expanded viseme mapping for Daz's eCTRLv blendshape conventions with consonant codes + mouth-context gating.

Changed

  • Integrations now auto-use baked data when available.

  • CrystalDialogueLipSync (GC2) can automatically start baked playback when a matching baked clip exists.

  • CrystalDialogueSystemLipSync (Pixel Crushers Dialogue System) can monitor the speaker AudioSource, resolve the playing clip via the lookup, and start baked playback...works with Resources/Addressables/AssetBundles.

  • Bake Window: optional auto-populate lookup.

    • Assign a CrystalBakedClipLookup and baked assets are registered automatically while baking.

  • Analyzer pipeline refactor (backwards compatible).

    • Internal processing was split so the Unity spectrum path and the managed FFT path share the same logic. Added an overload for offline baking that accepts an explicit deltaTime (since Time.unscaledDeltaTime is meaningless there). Controller reinitialization now properly refreshes the capture component when the capture path is active.

Fixed

  • GC2 real-time lip sync could be silent when AudioSource volume = 0 (Unity 6).

    • Some setups force volume to 0 to avoid double-audio; Unity then provides zero spectrum/filter data. Fixed via direct clip sampling + managed FFT fallback.

  • Baked Lip Sync not applying on WebGL in some cases (priority/execution order).

    • On WebGL, real-time buffers can be empty while baked sampling is valid; baked playback now correctly takes priority so viseme weights get applied.

  • Auto-mapper false E-viseme matches on Daz blendshape names.

    • Prevented the leading e prefix from incorrectly scoring as an E-vowel match across many blendshapes.

Version 1.2.0

Added

  • PixelCrushers Dialogue System integration (CrystalDialogueSystemLipSync).

  • New drop-in component: add it to your Dialogue Manager and text lip sync works automatically for every conversation line using the Dialogue System's OnConversationLine / OnConversationLineEnd callbacks.

  • Automatically resolves the speaker from Subtitle.speakerInfo.transform

  • Finds CrystalTextLipSync on the speaker (root or children)

  • Configurable characters-per-second (CPS)

  • Optional: skip text lip sync when the Sequence field contains voice/audio commands (AudioWait, Audio, Voice)

Fixed

  • Text lip sync viseme weights being overwritten by the controller.

  • CrystalLipSyncController was running FFT analysis every frame even when no audio was active, overwriting VisemeWeights[] with zeros and wiping text-driven visemes before they could be applied. The controller now skips FFT analysis when inactive (IsActive == false), preserving externally-written weights (e.g., from CrystalTextLipSync).

  • Text lip sync ending instantly for Dialogue Entries without delays/audio waits.

  • In some sequences, OnConversationLineEnd could fire immediately after OnConversationLine when the Sequence field contained no Delay() / AudioWait(), canceling PlayText before visemes had time to animate. Removed the immediate stop on line end so text lip sync now plays to natural completion, and is cleaned up on the next line start or conversation end.

Version 1.1.0

Added

  • Added Text to Lipsync Option B Demo Scene for GC2 (Install through the GC2 Install menu)

Changed

  • Setup Wizard: automatically disables CrystalEyeBlink on Game Creator 2 Characters.

  • The wizard now detects GC2's Character component (via a loose, string-based type check with no hard dependency) and disables the Eye Blink toggle with an info message. GC2 Characters already use their own IK-based eye blink system, so CrystalEyeBlink would be redundant. The wizard still compiles and works even if GC2 isn't installed.

  • Dialogue tracking is now fully event-driven (no Update polling).

  • Removed the Update() polling loop and switched to GC2 dialogue lifecycle events for cleaner and more reliable behavior.

  • Debug logs are now controlled by a toggle.

  • Logs only appear when Show Debug Logs (showDebugLogs) is enabled; normal operation stays silent.

  • Removed EyeBlinking MonoBehavior Component from GC2 Characters in the GC2 Demo Scenes - They are redundant since in GC2 you are using the IK Eyeblink of Crystal LipSync

Fixed

  • Text lip sync not triggering during dialogue.

  • Fixed an issue where dialogue text was not available in the EventStartNext timing. The integration now resolves text directly via node.GetText(Args.EMPTY).

  • First dialogue node being missed.

  • Fixed a race where subscribing in Update() happened too late, after the first node event already fired. The integration now subscribes in OnEnable() using Dialogue.EventAnyStart / Dialogue.EventAnyFinish, ensuring the very first node is captured.

Version 1.0.0

Initial release

Last updated