feat: implement authentic adjacent bead spacing for realistic abacus appearance

This commit perfects the visual authenticity of the soroban abacus by implementing
proper spacing between beads that matches how a real abacus behaves.

Key improvements:
1. Adjacent Bead Spacing System:
   - Adjacent beads of same type (active-to-active, inactive-to-inactive): 0.5pt spacing (nearly touching)
   - Transition gaps (active-to-inactive): 8pt spacing (clear visual separation)
   - Bar-to-bead gaps: 1pt (active) or 8pt (inactive) as appropriate

2. Physical Abacus Authenticity:
   - Active beads cluster together near the reckoning bar
   - Inactive beads cluster together away from active beads
   - Clear visual distinction between active and inactive states
   - Matches the tactile grouping behavior of real soroban beads

3. Enhanced Visual Impact:
   - Numbers like 2,3,4: Active earth beads touch each other
   - Numbers like 6,7,8,9: Both active and inactive groups clearly defined
   - Number 0: All inactive beads form cohesive groups
   - Number 5: Clean separation between active heaven and inactive earth

Technical Implementation:
- New adjacent-spacing parameter (0.5pt) for same-type bead pairs
- Updated positioning calculations throughout earth bead logic
- Maintained rod bounds calculation compatibility
- Preserved all existing gap logic for different-type transitions

The result is a much more authentic and visually appealing representation that
accurately reflects how beads behave on a physical soroban abacus.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-09-10 09:25:24 -05:00
parent 6c9553825a
commit f28256dc60
2 changed files with 13 additions and 12 deletions

View File

@@ -32,7 +32,8 @@
// Drawing parameters scaled by base-size
let rod-width = 3pt * base-size
let bead-size = 12pt * base-size
let bead-spacing = 4pt * base-size
let bead-spacing = 4pt * base-size // Original spacing (will be overridden for adjacent same-type beads)
let adjacent-spacing = 0.5pt * base-size // Minimal spacing for adjacent beads of same type
let column-spacing = 25pt * base-size
let heaven-earth-gap = 30pt * base-size
let bar-thickness = 2pt * base-size
@@ -143,15 +144,15 @@
#let furthest-earth-y = if earth-active > 0 {
// Position of the last inactive earth bead (or last active if all are active)
if earth-active == 4 {
// All earth beads are active - furthest is the 4th active bead
heaven-earth-gap + bar-thickness + active-gap + bead-size / 2 + 3 * (bead-size + bead-spacing)
// All earth beads are active - furthest is the 4th active bead (using adjacent spacing)
heaven-earth-gap + bar-thickness + active-gap + bead-size / 2 + 3 * (bead-size + adjacent-spacing)
} else {
// Some inactive beads - furthest is the last inactive bead
heaven-earth-gap + bar-thickness + active-gap + bead-size / 2 + earth-active * (bead-size + bead-spacing) + inactive-gap + (4 - 1 - earth-active) * (bead-size + bead-spacing)
heaven-earth-gap + bar-thickness + active-gap + bead-size / 2 + (earth-active - 1) * (bead-size + adjacent-spacing) + bead-size / 2 + inactive-gap + bead-size / 2 + (4 - 1 - earth-active) * (bead-size + adjacent-spacing)
}
} else {
// No active beads: furthest is the last inactive bead
heaven-earth-gap + bar-thickness + inactive-gap + bead-size / 2 + 3 * (bead-size + bead-spacing)
// No active beads: furthest is the last inactive bead (using adjacent spacing)
heaven-earth-gap + bar-thickness + inactive-gap + bead-size / 2 + 3 * (bead-size + adjacent-spacing)
}
// Calculate rod bounds (from outermost visible bead to outermost visible bead)
@@ -207,16 +208,16 @@
#for i in range(4) [
#let is-active = i < earth-active
#let earth-y = if is-active {
// Active beads: positioned near reckoning bar with better spacing, in sequence
heaven-earth-gap + bar-thickness + active-gap + bead-size / 2 + i * (bead-size + bead-spacing)
// Active beads: positioned near reckoning bar, adjacent beads touch
heaven-earth-gap + bar-thickness + active-gap + bead-size / 2 + i * (bead-size + adjacent-spacing)
} else {
// Inactive beads: positioned after the active beads + gap, or after reckoning bar + gap if no active beads
if earth-active > 0 {
// Position after the last active bead + gap
heaven-earth-gap + bar-thickness + active-gap + bead-size / 2 + earth-active * (bead-size + bead-spacing) + inactive-gap + (i - earth-active) * (bead-size + bead-spacing)
// Position after the last active bead + gap, then adjacent inactive beads touch
heaven-earth-gap + bar-thickness + active-gap + bead-size / 2 + (earth-active - 1) * (bead-size + adjacent-spacing) + bead-size / 2 + inactive-gap + bead-size / 2 + (i - earth-active) * (bead-size + adjacent-spacing)
} else {
// No active beads: position after reckoning bar + gap
heaven-earth-gap + bar-thickness + inactive-gap + bead-size / 2 + i * (bead-size + bead-spacing)
// No active beads: position after reckoning bar + gap, adjacent inactive beads touch
heaven-earth-gap + bar-thickness + inactive-gap + bead-size / 2 + i * (bead-size + adjacent-spacing)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB