Skip to main content

Overview

The geometry system maps canvas positions to anatomically correct tooth surfaces based on FDI notation. It handles the conversion between visual tooth regions and dental terminology.

Tooth Structure

Each tooth is divided into geometric regions stored in the teeth object:
this.teeth[key] = {
  num: 21,              // FDI tooth number
  bigBoxSize: 50,       // Tooth box size in pixels
  smallBoxSize: 25,     // Inner box size
  x1: 100, y1: 50,      // Top-left corner
  x2: 150, y2: 100,     // Bottom-right corner
  cx: 125, cy: 75,      // Center point
  
  // Surface regions
  top: { tl, tr, br, bl },      // Top surface vertices
  right: { tl, tr, br, bl },    // Right surface vertices
  bottom: { tl, tr, br, bl },   // Bottom surface vertices  
  left: { tl, tr, br, bl },     // Left surface vertices
  middle: { tl, tr, br, bl }    // Middle/occlusal surface vertices
}

Surface Vertices

Each surface has 4 vertices defining its polygon:
top: {
  tl: { x: 100, y: 50 },   // top-left
  tr: { x: 150, y: 50 },   // top-right
  br: { x: 137, y: 62 },   // bottom-right
  bl: { x: 112, y: 62 }    // bottom-left
}

Position Mapping

Canvas positions map to different anatomical surfaces depending on tooth location.

Canvas Position Codes

const canvasPositionMap = {
  T: 'top',       // or 'top'
  B: 'bottom',    // or 'bottom'
  L: 'left',      // or 'left'
  R: 'right',     // or 'right'
  M: 'middle'     // or 'middle'
}

Single Letter Codes

Treatments store position as single letters:
treatment.pos = 'T'      // Top surface
treatment.pos = 'L-B'    // Left surface, bottom
treatment.pos = '21-M'   // Tooth 21, middle surface

getCorrectSurfaceMapping(fdi)

Maps canvas positions to anatomically correct surface names based on FDI tooth number.

Signature

function getCorrectSurfaceMapping(fdi)

Parameters

fdi
number | string
required
FDI tooth number (11-18, 21-28, 31-38, 41-48 for adults; 51-55, 61-65, 71-75, 81-85 for primary)

Returns

mapping
object
Object mapping canvas positions to anatomical surface namesProperties:
  • top (string): Anatomical name for top position
  • bottom (string): Anatomical name for bottom position
  • left (string): Anatomical name for left position (mesial/distal)
  • right (string): Anatomical name for right position (mesial/distal)
  • middle (string): Anatomical name for middle position (oclusal/incisal)

FDI Quadrant Rules

Upper vs Lower Teeth

const isUpperTooth = 
  (fdiNumber >= 11 && fdiNumber <= 18) ||  // Upper right
  (fdiNumber >= 21 && fdiNumber <= 28) ||  // Upper left
  (fdiNumber >= 51 && fdiNumber <= 55) ||  // Primary upper right
  (fdiNumber >= 61 && fdiNumber <= 65)     // Primary upper left

const isLowerTooth = 
  (fdiNumber >= 31 && fdiNumber <= 38) ||  // Lower left
  (fdiNumber >= 41 && fdiNumber <= 48) ||  // Lower right
  (fdiNumber >= 71 && fdiNumber <= 75) ||  // Primary lower left
  (fdiNumber >= 81 && fdiNumber <= 85)     // Primary lower right

Base Mapping

const baseMapping = {
  top: isUpperTooth ? 'vestibular' : 'lingual',
  bottom: isUpperTooth ? 'palatina' : 'vestibular',
  middle: 'oclusal'  // Will be adjusted for incisors/canines
}

Mesial/Distal Orientation

Mesial and distal surfaces are determined by quadrant:

Right Quadrants (11-18, 41-48, 51-55, 81-85)

// Mesial faces towards midline (right side)
mesialSide = 'right'
distalSide = 'left'

Left Quadrants (21-28, 31-38, 61-65, 71-75)

// Mesial faces towards midline (left side)
mesialSide = 'left'
distalSide = 'right'

Tooth Type Adjustments

Incisors and canines use “incisal” instead of “oclusal”:
const toothInfo = getToothInfo(fdiNumber)
if (toothInfo && 
    (toothInfo.tipo.includes('incisivo') || 
     toothInfo.tipo.includes('canino'))) {
  baseMapping.middle = 'incisal'
}

Complete Implementation

function getCorrectSurfaceMapping(fdi) {
  const fdiNumber = parseInt(fdi)
  
  // Determine if upper or lower tooth
  const isUpperTooth = 
    (fdiNumber >= 11 && fdiNumber <= 18) ||
    (fdiNumber >= 21 && fdiNumber <= 28) ||
    (fdiNumber >= 51 && fdiNumber <= 55) ||
    (fdiNumber >= 61 && fdiNumber <= 65)
  
  // Base mapping for upper/lower
  const baseMapping = {
    top: isUpperTooth ? 'vestibular' : 'lingual',
    bottom: isUpperTooth ? 'palatina' : 'vestibular',
    middle: 'oclusal'
  }
  
  // Determine mesial/distal based on quadrant
  let mesialSide, distalSide
  
  if (
    // Right quadrants
    (fdiNumber >= 11 && fdiNumber <= 18) ||
    (fdiNumber >= 41 && fdiNumber <= 48) ||
    (fdiNumber >= 51 && fdiNumber <= 55) ||
    (fdiNumber >= 81 && fdiNumber <= 85)
  ) {
    mesialSide = 'right'
    distalSide = 'left'
  } else if (
    // Left quadrants  
    (fdiNumber >= 21 && fdiNumber <= 28) ||
    (fdiNumber >= 31 && fdiNumber <= 38) ||
    (fdiNumber >= 61 && fdiNumber <= 65) ||
    (fdiNumber >= 71 && fdiNumber <= 75)
  ) {
    mesialSide = 'left'
    distalSide = 'right'
  }
  
  // Assign mesial/distal
  if (mesialSide && distalSide) {
    baseMapping[mesialSide] = 'mesial'
    baseMapping[distalSide] = 'distal'
  }
  
  // Adjust for incisors and canines
  const toothInfo = getToothInfo(fdiNumber)
  if (toothInfo && 
      (toothInfo.tipo.includes('incisivo') || 
       toothInfo.tipo.includes('canino'))) {
    baseMapping.middle = 'incisal'
  }
  
  return baseMapping
}

Mapping Examples

Upper Right Central Incisor (Tooth 11)

getCorrectSurfaceMapping(11)
// Returns:
{
  top: 'vestibular',      // Facing lips
  bottom: 'palatina',     // Facing palate
  right: 'mesial',        // Towards midline
  left: 'distal',         // Away from midline
  middle: 'incisal'       // Cutting edge
}

Upper Left First Molar (Tooth 26)

getCorrectSurfaceMapping(26)
// Returns:
{
  top: 'vestibular',      // Facing cheek
  bottom: 'palatina',     // Facing palate
  left: 'mesial',         // Towards midline
  right: 'distal',        // Away from midline
  middle: 'oclusal'       // Chewing surface
}

Lower Right Canine (Tooth 43)

getCorrectSurfaceMapping(43)
// Returns:
{
  top: 'lingual',         // Facing tongue
  bottom: 'vestibular',   // Facing lips
  right: 'mesial',        // Towards midline
  left: 'distal',         // Away from midline
  middle: 'incisal'       // Cutting edge
}

Lower Left Second Molar (Tooth 37)

getCorrectSurfaceMapping(37)
// Returns:
{
  top: 'lingual',         // Facing tongue
  bottom: 'vestibular',   // Facing cheek
  left: 'mesial',         // Towards midline
  right: 'distal',        // Away from midline  
  middle: 'oclusal'       // Chewing surface
}

Surface Conversion Process

From Treatment Position to Anatomical Name

// 1. Extract position code from treatment
let surfaceCode = null
if (treatment.pos.includes('-')) {
  const parts = treatment.pos.split('-')
  surfaceCode = parts[1]  // e.g., '21-M' → 'M'
} else {
  surfaceCode = treatment.pos  // e.g., 'T'
}

// 2. Convert to full canvas position
const canvasPositionMap = {
  T: 'top', B: 'bottom', L: 'left', R: 'right', M: 'middle'
}
const fullCanvasPosition = canvasPositionMap[surfaceCode]
// 'M' → 'middle'

// 3. Get correct mapping for this tooth
const correctMapping = getCorrectSurfaceMapping(toothNum)
// For tooth 21: { top: 'vestibular', left: 'mesial', middle: 'incisal', ... }

// 4. Get anatomical surface name
const anatomical = correctMapping[fullCanvasPosition]
// 'middle' → 'incisal' (for tooth 21)

Complete Example

// Treatment on tooth 16, middle surface
const treatment = {
  name: 'COF',
  pos: '16-M',
  layer: 'pre'
}

const toothNum = 16
const surfaceCode = 'M'
const fullCanvasPosition = 'middle'
const correctMapping = getCorrectSurfaceMapping(16)
// { top: 'vestibular', bottom: 'palatina', 
//   right: 'mesial', left: 'distal', middle: 'oclusal' }

const anatomical = correctMapping['middle']
// → 'oclusal'

// Display: "Obturación - Cara/s: Oclusal"

Tooth Geometry Creation

Side Teeth (Molars, Premolars)

Most teeth use the standard 5-surface layout:
Odontogram.prototype._sideTeeth = function(
  ctx, numbers, bigBoxSize, smallBoxSize, xpos, ypos
) {
  // Draw small box in center
  ctx.rect(
    xpos + smallBoxSize/2,
    ypos + smallBoxSize/2,
    smallBoxSize,
    smallBoxSize
  )
  
  // Draw diagonal lines to corners
  // Creates 5 regions: top, right, bottom, left, middle
  
  // Store tooth geometry
  this.teeth[key] = {
    num: toothNum,
    top: { /* vertices */ },
    right: { /* vertices */ },
    bottom: { /* vertices */ },
    left: { /* vertices */ },
    middle: { /* vertices */ }
  }
}

Center Teeth (Incisors, Canines)

Incisors and canines use a modified 4-surface layout:
Odontogram.prototype._centerTeeth = function(
  ctx, numbers, bigBoxSize, smallBoxSize, xpos, ypos
) {
  // Draw horizontal line (no middle box)
  ctx.rect(
    xpos + smallBoxSize/2 + 3,
    ypos + smallBoxSize - 3,
    smallBoxSize - 6,
    0  // Height of 0 = just a line
  )
  
  // Creates 4 regions: top, right, bottom, left
  // (No middle region - uses incisal edge instead)
}

Geometry Storage

Treatments are stored in the odontogram’s geometry object:
this.geometry = {
  '100:50;150:100;125:75': [  // Key from tooth coordinates
    {
      name: 'AMF',
      pos: 'T',
      layer: 'pre',
      vertices: [/* array of {x, y} */],
      render: function(ctx) { /* ... */ }
    },
    {
      name: 'COF', 
      pos: 'M',
      layer: 'req',
      vertices: [/* ... */],
      render: function(ctx) { /* ... */ }
    }
  ]
}

Key Format

const key = x1 + ':' + y1 + ';' + x2 + ':' + y2 + ';' + cx + ':' + cy
// '100:50;150:100;125:75'

Surface Name Reference

Spanish Terms

SpanishEnglishDescription
VestibularBuccal/LabialFacing cheek or lips
PalatinaPalatalFacing palate (upper)
LingualLingualFacing tongue (lower)
MesialMesialTowards midline
DistalDistalAway from midline
OclusalOcclusalChewing surface (molars/premolars)
IncisalIncisalCutting edge (incisors/canines)

Canvas to Anatomical Mapping

Canvas PositionUpper TeethLower Teeth
TopVestibularLingual
BottomPalatinaVestibular
Right (Q1/Q4)MesialMesial
Right (Q2/Q3)DistalDistal
Left (Q1/Q4)DistalDistal
Left (Q2/Q3)MesialMesial
Middle (molars)OclusalOclusal
Middle (incisors)IncisalIncisal

Build docs developers (and LLMs) love