// Tooth condition classes class Polygon { name = "Polygon"; pos?: string; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = options; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length <= 0) { // console.warn( // "Polygon render: vertices array too short or undefined", // this.vertices // ); return; } ctx.fillStyle = this.options.fillStyle; ctx.beginPath(); const vertices = [...this.vertices]; const fpos = vertices.shift(); if (!fpos || fpos.x === undefined || fpos.y === undefined) { // console.warn( // "Polygon render: first vertex missing required properties", // fpos // ); return; } ctx.moveTo(fpos.x, fpos.y); while (vertices.length > 0) { const pos = vertices.shift(); if (pos && pos.x !== undefined && pos.y !== undefined) { ctx.lineTo(pos.x, pos.y); } } ctx.lineTo(fpos.x, fpos.y); ctx.closePath(); ctx.fill(); } } class AMF { name = "AMF"; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "rgba(255, 0, 0, 0.7)", ...options }; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length <= 0) { // console.warn( // "AMF render: vertices array too short or undefined", // this.vertices // ); return; } // console.log("Rendering AMF with fillStyle:", this.options.fillStyle); ctx.fillStyle = this.options.fillStyle; ctx.beginPath(); const vertices = [...this.vertices]; const fpos = vertices.shift(); if (!fpos || fpos.x === undefined || fpos.y === undefined) { // console.warn( // "AMF render: first vertex missing required properties", // fpos // ); return; } ctx.moveTo(fpos.x, fpos.y); while (vertices.length > 0) { const pos = vertices.shift(); if (pos && pos.x !== undefined && pos.y !== undefined) { ctx.lineTo(pos.x, pos.y); } } ctx.lineTo(fpos.x, fpos.y); ctx.closePath(); ctx.fill(); } } class COF { name = "COF"; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "rgba(0, 255, 0, 0.7)", ...options }; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length <= 0) { // console.warn( // "COF render: vertices array too short or undefined", // this.vertices // ); return; } // console.log("Rendering COF with fillStyle:", this.options.fillStyle); ctx.fillStyle = this.options.fillStyle; ctx.beginPath(); const vertices = [...this.vertices]; const fpos = vertices.shift(); if (!fpos || fpos.x === undefined || fpos.y === undefined) { // console.warn( // "COF render: first vertex missing required properties", // fpos // ); return; } ctx.moveTo(fpos.x, fpos.y); while (vertices.length > 0) { const pos = vertices.shift(); if (pos && pos.x !== undefined && pos.y !== undefined) { ctx.lineTo(pos.x, pos.y); } } ctx.lineTo(fpos.x, fpos.y); ctx.closePath(); ctx.fill(); } } class FIS { name = "FIS"; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "rgba(255, 0, 255, 0.7)", ...options }; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length <= 0) { // console.warn( // "FIS render: vertices array too short or undefined", // this.vertices // ); return; } // console.log("Rendering FIS with fillStyle:", this.options.fillStyle); ctx.fillStyle = this.options.fillStyle; ctx.beginPath(); const vertices = [...this.vertices]; const fpos = vertices.shift(); if (!fpos || fpos.x === undefined || fpos.y === undefined) { // console.warn( // "FIS render: first vertex missing required properties", // fpos // ); return; } ctx.moveTo(fpos.x, fpos.y); while (vertices.length > 0) { const pos = vertices.shift(); if (pos && pos.x !== undefined && pos.y !== undefined) { ctx.lineTo(pos.x, pos.y); } } ctx.lineTo(fpos.x, fpos.y); ctx.closePath(); ctx.fill(); } } class NVT { name = "NVT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#303030ff", height: 25, ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { // console.warn( // "NVT render: vertices array too short or undefined", // this.vertices // ); return; } if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].x === undefined || this.vertices[0].y === undefined || this.vertices[1].y === undefined ) { // console.warn( // "NVT render: vertices missing required properties", // this.vertices // ); return; } const x1 = parseFloat(this.vertices[0].x.toString()) + 1; const x2 = parseFloat(this.vertices[1].x.toString()) + 1; const y1 = parseFloat(this.vertices[0].y.toString()) + 1; const y2 = parseFloat(this.vertices[1].y.toString()) + 1; const size = x2 - x1; const height = parseFloat(this.options.height.toString()); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(x1 + size / 4, y2); ctx.lineTo(x1 + size / 2, y2 + height); ctx.lineTo(x2 - size / 4, y2); ctx.closePath(); ctx.stroke(); } else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { // console.warn( // "NVT render: vertices array too short or undefined", // this.vertices // ); return; } if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].x === undefined || this.vertices[0].y === undefined || this.vertices[1].y === undefined ) { // console.warn( // "NVT render: vertices missing required properties", // this.vertices // ); return; } const x1 = parseFloat(this.vertices[0].x.toString()) + 1; const x2 = parseFloat(this.vertices[1].x.toString()) + 1; const y1 = parseFloat(this.vertices[0].y.toString()) - 1; const y2 = parseFloat(this.vertices[1].y.toString()) + 1; const size = x2 - x1; const height = parseFloat(this.options.height.toString()); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(x1 + size / 4, y1); ctx.lineTo(x1 + size / 2, y1 - height); ctx.lineTo(x2 - size / 4, y1); ctx.closePath(); ctx.stroke(); } } } // RCT class (Perawatan Saluran Akar) class RCT { name = "RCT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#303030ff", fillStyle: "#303030ff", height: 25, ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { // // console.warn( // "RCT render: vertices array too short or undefined", // this.vertices // ); return; } if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].x === undefined || this.vertices[0].y === undefined || this.vertices[1].y === undefined ) { // // console.warn( // "RCT render: vertices missing required properties", // this.vertices // ); return; } const x1 = parseFloat(this.vertices[0].x.toString()) + 1; const x2 = parseFloat(this.vertices[1].x.toString()) + 1; const y1 = parseFloat(this.vertices[0].y.toString()) + 1; const y2 = parseFloat(this.vertices[1].y.toString()) + 1; const size = x2 - x1; const height = parseFloat(this.options.height); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.beginPath(); ctx.moveTo(x1 + size / 4, y2); ctx.lineTo(x1 + size / 2, y2 + height); ctx.lineTo(x2 - size / 4, y2); ctx.closePath(); ctx.fill(); ctx.stroke(); } else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { // // console.warn( // "RCT render: vertices array too short or undefined", // this.vertices // ); return; } if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].x === undefined || this.vertices[0].y === undefined || this.vertices[1].y === undefined ) { // // console.warn( // "RCT render: vertices missing required properties", // this.vertices // ); return; } const x1 = parseFloat(this.vertices[0].x.toString()) + 1; const x2 = parseFloat(this.vertices[1].x.toString()) + 1; const y1 = parseFloat(this.vertices[0].y.toString()) - 1; const y2 = parseFloat(this.vertices[1].y.toString()) + 1; const size = x2 - x1; const height = parseFloat(this.options.height); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.beginPath(); ctx.moveTo(x1 + size / 4, y1); ctx.lineTo(x1 + size / 2, y1 - height); ctx.lineTo(x2 - size / 4, y1); ctx.closePath(); ctx.fill(); ctx.stroke(); } } } class NON { name = "NON"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "#303030ff", fontsize: 14, offsetX: 25, offsetY: 5, ...options }; // Extract surface from options if provided this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { // Log tooth position information // console.log("IPX Tooth Information:", { // position: this.pos || "unknown", // row: this.row || "unknown", // surface: this.surface || "unknown" // }); // Different validation logic based on row if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } // For rows 3 and 4, check first and second vertices if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[1].y.toString()) + (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "top"; ctx.textAlign = "left"; ctx.fillText("NON", x, y); } else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length <= 0) { return; } if ( this.vertices[0] == null || this.vertices[0].x === undefined || this.vertices[0].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[0].y.toString()) - (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize.toString()); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "bottom"; ctx.textAlign = "left"; ctx.fillText("NON", x, y); } } } class UNE { name = "UNE"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "#303030ff", fontsize: 14, offsetX: 25, offsetY: 5, ...options }; // Extract surface from options if provided this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { // Log tooth position information // console.log("IPX Tooth Information:", { // position: this.pos || "unknown", // row: this.row || "unknown", // surface: this.surface || "unknown" // }); // Different validation logic based on row if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } // For rows 3 and 4, check first and second vertices if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[1].y.toString()) + (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "top"; ctx.textAlign = "left"; ctx.fillText("UNE", x, y); } else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length <= 0) { return; } if ( this.vertices[0] == null || this.vertices[0].x === undefined || this.vertices[0].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[0].y.toString()) - (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize.toString()); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "bottom"; ctx.textAlign = "left"; ctx.fillText("UNE", x, y); } } } class PRE { name = "PRE"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "#303030ff", fontsize: 14, offsetX: 25, offsetY: 5, ...options }; // Extract surface from options if provided this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { // Log tooth position information // console.log("IPX Tooth Information:", { // position: this.pos || "unknown", // row: this.row || "unknown", // surface: this.surface || "unknown" // }); // Different validation logic based on row if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } // For rows 3 and 4, check first and second vertices if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[1].y.toString()) + (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "top"; ctx.textAlign = "left"; ctx.fillText("PRE", x, y); } else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length <= 0) { return; } if ( this.vertices[0] == null || this.vertices[0].x === undefined || this.vertices[0].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[0].y.toString()) - (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize.toString()); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "bottom"; ctx.textAlign = "left"; ctx.fillText("PRE", x, y); } } } class ANO { name = "ANO"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "#303030ff", fontsize: 14, offsetX: 25, offsetY: 5, ...options }; // Extract surface from options if provided this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { // Log tooth position information // console.log("IPX Tooth Information:", { // position: this.pos || "unknown", // row: this.row || "unknown", // surface: this.surface || "unknown" // }); // Different validation logic based on row if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } // For rows 3 and 4, check first and second vertices if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[1].y.toString()) + (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "top"; ctx.textAlign = "left"; ctx.fillText("ANO", x, y); } else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length <= 0) { return; } if ( this.vertices[0] == null || this.vertices[0].x === undefined || this.vertices[0].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[0].y.toString()) - (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize.toString()); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "bottom"; ctx.textAlign = "left"; ctx.fillText("ANO", x, y); } } } class CARIES { name = "CARIES"; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#303030ff", ...options }; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length <= 0) { // console.warn( // "CARIES render: vertices array too short or undefined", // this.vertices // ); return; } ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 4; ctx.fillStyle = "rgba(0, 0, 0, 0.1)"; // Subtle fill to soften appearance ctx.beginPath(); // const vertices = [...this.vertices]; const inset = 0; const vertices = this.vertices.map((v) => { if (v == null || v.x === undefined || v.y === undefined) { // console.warn("CARIES render: vertex missing required properties", v); return { x: 0, y: 0 }; } return { x: v.x + inset, y: v.y + inset }; }); const fpos = vertices.shift(); if (!fpos) { // console.warn( // "CARIES render: first vertex missing required properties", // fpos // ); return; } ctx.moveTo(fpos.x, fpos.y - 2); while (vertices.length > 0) { const pos = vertices.shift(); if (pos && pos.x !== undefined && pos.y !== undefined) { ctx.lineTo(pos.x, pos.y - 2); } } ctx.lineTo(fpos.x, fpos.y - 2); ctx.closePath(); ctx.fill(); ctx.stroke(); } } class CFR { name = "CFR"; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "#303030ff", ...options }; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length < 2) { // console.warn( // "CFR render: vertices array too short or undefined", // this.vertices // ); return; } if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].x === undefined || this.vertices[0].y === undefined || this.vertices[1].y === undefined ) { // console.warn( // "CFR render: vertices missing required properties", // this.vertices // ); return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const boxSize = x2 - x1; const fontsize = parseInt(boxSize.toString()); const x = x1 + boxSize / 2; const y = y1 + boxSize / 2; ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "middle"; ctx.textAlign = "center"; ctx.fillText("#", x, y); } } class FMC { name = "FMC"; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#303030ff", ...options }; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length < 2) { // console.warn( // "FMC render: vertices array too short or undefined", // this.vertices // ); return; } if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].x === undefined || this.vertices[0].y === undefined || this.vertices[1].y === undefined ) { // console.warn( // "FMC render: vertices missing required properties", // this.vertices // ); return; } const x1 = parseFloat(this.vertices[0].x.toString()) + 1; const y1 = parseFloat(this.vertices[0].y.toString()) + 1; const x2 = parseFloat(this.vertices[1].x.toString()) + 1; const y2 = parseFloat(this.vertices[1].y.toString()) + 1; const vertices = [ { x: x1, y: y1 }, { x: x2, y: y1 }, { x: x2, y: y2 }, { x: x1, y: y2 } ]; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 6; ctx.beginPath(); const fpos = vertices.shift(); if (!fpos) { // console.warn( // "FMC render: first vertex missing required properties", // fpos // ); return; } ctx.moveTo(fpos.x, fpos.y); while (vertices.length > 0) { const pos = vertices.shift(); if (pos && pos.x !== undefined && pos.y !== undefined) { ctx.lineTo(pos.x, pos.y); } } ctx.lineTo(fpos.x, fpos.y); ctx.closePath(); ctx.stroke(); } } class POC { name = "POC"; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#303030ff", ...options }; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length < 2) { // console.warn( // "POC render: vertices array too short or undefined", // this.vertices // ); return; } if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].x === undefined || this.vertices[0].y === undefined || this.vertices[1].y === undefined ) { // console.warn( // "POC render: vertices missing required properties", // this.vertices // ); return; } const x1 = parseFloat(this.vertices[0].x.toString()) + 1; const y1 = parseFloat(this.vertices[0].y.toString()) + 1; const x2 = parseFloat(this.vertices[1].x.toString()) + 1; const y2 = parseFloat(this.vertices[1].y.toString()) + 1; const vertices = [ { x: x1, y: y1 }, { x: x2, y: y1 }, { x: x2, y: y2 }, { x: x1, y: y2 } ]; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 6; ctx.beginPath(); const fpos = vertices.shift(); if (!fpos) { // console.warn( // "POC render: first vertex missing required properties", // fpos // ); return; } ctx.moveTo(fpos.x, fpos.y); while (vertices.length > 0) { const pos = vertices.shift(); if (pos && pos.x !== undefined && pos.y !== undefined) { ctx.lineTo(pos.x, pos.y); } } ctx.lineTo(fpos.x, fpos.y); ctx.closePath(); ctx.stroke(); // Draw Lines ctx.lineWidth = 1; for (let xpos = x1; xpos < x2; xpos += (x2 - x1) / 15) { xpos = Math.min(xpos, x2); ctx.beginPath(); ctx.moveTo(xpos, y1); ctx.lineTo(xpos, y2); ctx.stroke(); } } } class RRX { name = "RRX"; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#303030ff", ...options }; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length < 2) { // console.warn( // "RRX render: vertices array too short or undefined", // this.vertices // ); return; } if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].x === undefined || this.vertices[0].y === undefined || this.vertices[1].y === undefined ) { // console.warn( // "RRX render: vertices missing required properties", // this.vertices // ); return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()) + 1; const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()) + 1; const bigBoxSize = x2 - x1; const smallBoxSize = bigBoxSize / 2; const lines = [ { x1: x1 + smallBoxSize / 3, y1: y1 - smallBoxSize / 2, x2: x1 + smallBoxSize, y2: y2 + smallBoxSize / 2 }, { x1: x1 + smallBoxSize, y1: y2 + smallBoxSize / 2, x2: x1 + smallBoxSize * 2, y2: y1 - smallBoxSize } ]; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 4; for (const line of lines) { ctx.beginPath(); ctx.moveTo(line.x1, line.y1); ctx.lineTo(line.x2, line.y2); ctx.stroke(); } } } class MIS { name = "MIS"; mode?: number; vertices: { x: number; y: number }[]; options: any; constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#303030ff", ...options }; } render(ctx: CanvasRenderingContext2D) { if (!this.vertices || this.vertices.length < 2) { // console.warn( // "MIS render: vertices array too short or undefined", // this.vertices // ); return; } if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].x === undefined || this.vertices[0].y === undefined || this.vertices[1].y === undefined ) { // console.warn( // "MIS render: vertices missing required properties", // this.vertices // ); return; } const x1 = parseFloat(this.vertices[0].x.toString()) + 1; const y1 = parseFloat(this.vertices[0].y.toString()) + 1; const x2 = parseFloat(this.vertices[1].x.toString()) + 1; const y2 = parseFloat(this.vertices[1].y.toString()) + 1; const bigBoxSize = x2 - x1; const smallBoxSize = bigBoxSize / 2; const lines = [ { x1: x1 + smallBoxSize * 0.5, y1: y1 - smallBoxSize / 2, x2: x1 + smallBoxSize * 1.5, y2: y2 + smallBoxSize / 2 }, { x1: x1 + smallBoxSize * 1.5, y1: y1 - smallBoxSize / 2, x2: x1 + smallBoxSize * 0.5, y2: y2 + smallBoxSize / 2 } ]; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 4; for (const line of lines) { ctx.beginPath(); ctx.moveTo(line.x1, line.y1); ctx.lineTo(line.x2, line.y2); ctx.stroke(); } } } // IPX class (Implant + Porcelain crown) class IPX { name = "IPX"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "#303030ff", fontsize: 14, offsetX: 25, offsetY: 5, ...options }; // Extract surface from options if provided this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { // Log tooth position information // console.log("IPX Tooth Information:", { // position: this.pos || "unknown", // row: this.row || "unknown", // surface: this.surface || "unknown" // }); // Different validation logic based on row if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } // For rows 3 and 4, check first and second vertices if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[1].y.toString()) + (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "top"; ctx.textAlign = "left"; ctx.fillText("IPX", x, y); } else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length <= 0) { return; } if ( this.vertices[0] == null || this.vertices[0].x === undefined || this.vertices[0].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[0].y.toString()) - (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize.toString()); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "bottom"; ctx.textAlign = "left"; ctx.fillText("IPX", x, y); } } } // FRM_ACR class (Partial Denture/ Full Denture) class FRM_ACR { name = "FRM_ACR"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "#303030ff", fontsize: 14, offsetX: 10, offsetY: 5, ...options }; // Extract surface from options if provided this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { // Log tooth position information // console.log("IPX Tooth Information:", { // position: this.pos || "unknown", // row: this.row || "unknown", // surface: this.surface || "unknown" // }); // Different validation logic based on row if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } // For rows 3 and 4, check first and second vertices if ( this.vertices[0] == null || this.vertices[1] == null || this.vertices[0].x === undefined || this.vertices[1].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[1].y.toString()) + (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "top"; ctx.textAlign = "left"; ctx.fillText("PFD/FLD", x, y); } else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length <= 0) { return; } if ( this.vertices[0] == null || this.vertices[0].x === undefined || this.vertices[0].y === undefined ) { return; } const x = parseFloat(this.vertices[0].x.toString()) + (this.options.offsetX || 0); const y = parseFloat(this.vertices[0].y.toString()) - (this.options.offsetY || 0); const fontsize = parseInt(this.options.fontsize.toString()); ctx.fillStyle = "#000"; ctx.font = "bold " + fontsize + "px Algerian"; ctx.textBaseline = "bottom"; ctx.textAlign = "left"; ctx.fillText("PFD/FLD", x, y); } } } class BRIDGE { name = "BRIDGE"; mode?: number; startVert: { x: number; y: number }[]; endVert: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor( startVert: { x: number; y: number }[], endVert: { x: number; y: number }[], options: any = {} ) { this.startVert = startVert; this.endVert = endVert; this.options = { strokeStyle: "#303030ff", ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { let vert0, vert1; if (this.row === 1 || this.row === 2) { if ( this.startVert && this.startVert.length >= 2 && this.startVert[0] != null && this.startVert[1] != null && this.startVert[0].x !== undefined && this.startVert[1].x !== undefined && this.startVert[0].y !== undefined && this.startVert[1].y !== undefined ) { vert0 = { x1: parseFloat(this.startVert[0].x.toString()) + 1, y1: parseFloat(this.startVert[0].y.toString()) + 1, x2: parseFloat(this.startVert[1].x.toString()) - 1, y2: parseFloat(this.startVert[1].y.toString()) + 1, size: 0, cx: 0, cy: 0 }; vert0.size = vert0.x2 - vert0.x1; vert0.cx = vert0.x1 + vert0.size / 2; vert0.cy = vert0.y1 + vert0.size / 2; } if ( this.endVert && this.endVert.length >= 2 && this.endVert[0] != null && this.endVert[1] != null && this.endVert[0].x !== undefined && this.endVert[1].x !== undefined && this.endVert[0].y !== undefined && this.endVert[1].y !== undefined ) { vert1 = { x1: parseFloat(this.endVert[0].x.toString()) + 1, y1: parseFloat(this.endVert[0].y.toString()) + 1, x2: parseFloat(this.endVert[1].x.toString()) - 1, y2: parseFloat(this.endVert[1].y.toString()) + 1, size: 0, cx: 0, cy: 0 }; vert1.size = vert1.x2 - vert1.x1; vert1.cx = vert1.x1 + vert1.size / 2; vert1.cy = vert1.y1 + vert1.size / 2; } // Draw Bridge in vert0 as U shape with vertical line in middle bottom if (vert0) { const x1 = vert0.x1; const y1 = vert0.y1; const x2 = vert0.x2; const y2 = vert0.y2; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 6; ctx.beginPath(); // Draw left line ctx.moveTo(x1, y1); ctx.lineTo(x1, y2); // Draw bottom line ctx.lineTo(x2, y2); // Draw right line ctx.lineTo(x2, y1); // Draw vertical line in middle bottom ctx.moveTo((x1 + x2) / 2, y2); ctx.lineTo((x1 + x2) / 2, y2 + vert0.size / 4); ctx.stroke(); } // Draw Bridge in vert1 as U shape with vertical line in middle bottom if (vert1) { const x1 = vert1.x1; const y1 = vert1.y1; const x2 = vert1.x2; const y2 = vert1.y2; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 6; ctx.beginPath(); // Draw left line ctx.moveTo(x1, y1); ctx.lineTo(x1, y2); // Draw bottom line ctx.lineTo(x2, y2); // Draw right line ctx.lineTo(x2, y1); // Draw vertical line in middle bottom ctx.moveTo((x1 + x2) / 2, y2); ctx.lineTo((x1 + x2) / 2, y2 + vert1.size / 4); ctx.stroke(); } // JOIN BRIDGE if (vert0 && vert1) { const lor = vert1.cx - vert0.cx > 0 ? 1 : -1; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 6; ctx.beginPath(); ctx.moveTo(vert0.cx - 3 * lor, vert0.y2 + vert0.size / 4); ctx.lineTo(vert1.cx + 3 * lor, vert1.y2 + vert1.size / 4); ctx.stroke(); } } else if (this.row === 3 || this.row === 4) { if ( this.startVert && this.startVert.length >= 2 && this.startVert[0] != null && this.startVert[1] != null && this.startVert[0].x !== undefined && this.startVert[1].x !== undefined && this.startVert[0].y !== undefined && this.startVert[1].y !== undefined ) { vert0 = { x1: parseFloat(this.startVert[0].x.toString()) + 1, y1: parseFloat(this.startVert[0].y.toString()) - 1, x2: parseFloat(this.startVert[1].x.toString()) - 1, y2: parseFloat(this.startVert[1].y.toString()) - 1, size: 0, cx: 0, cy: 0 }; vert0.size = vert0.x2 - vert0.x1; vert0.cx = vert0.x2 - vert0.size / 2; vert0.cy = vert0.y2 - vert0.size / 2; } else { // // console.warn( // "BRIDGE render: startVert missing or invalid", // this.startVert // ); } if ( this.endVert && this.endVert.length >= 2 && this.endVert[0] != null && this.endVert[1] != null && this.endVert[0].x !== undefined && this.endVert[1].x !== undefined && this.endVert[0].y !== undefined && this.endVert[1].y !== undefined ) { vert1 = { x1: parseFloat(this.endVert[0].x.toString()) + 1, y1: parseFloat(this.endVert[0].y.toString()) - 1, x2: parseFloat(this.endVert[1].x.toString()) - 1, y2: parseFloat(this.endVert[1].y.toString()) - 1, size: 0, cx: 0, cy: 0 }; vert1.size = vert1.x2 - vert1.x1; vert1.cx = vert1.x2 - vert1.size / 2; vert1.cy = vert1.y2 - vert1.size / 2; } else { // // console.warn("BRIDGE render: endVert missing or invalid", this.endVert); } // Draw Bridge in vert0 as U shape with vertical line in middle top if (vert0) { const x1 = vert0.x1; const y1 = vert0.y1; const x2 = vert0.x2; const y2 = vert0.y2; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 6; ctx.beginPath(); // Draw left line ctx.moveTo(x1, y2); ctx.lineTo(x1, y1); // Draw top line ctx.lineTo(x2, y1); // Draw right line ctx.lineTo(x2, y2); // Draw vertical line in middle top ctx.moveTo((x1 + x2) / 2, y1); ctx.lineTo((x1 + x2) / 2, y1 - vert0.size / 4); ctx.stroke(); } // Draw Bridge in vert1 as U shape with vertical line in middle top if (vert1) { const x1 = vert1.x1; const y1 = vert1.y1; const x2 = vert1.x2; const y2 = vert1.y2; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 6; ctx.beginPath(); // Draw left line ctx.moveTo(x1, y2); ctx.lineTo(x1, y1); // Draw top line ctx.lineTo(x2, y1); // Draw right line ctx.lineTo(x2, y2); // Draw vertical line in middle top ctx.moveTo((x1 + x2) / 2, y1); ctx.lineTo((x1 + x2) / 2, y1 - vert1.size / 4); ctx.stroke(); } // JOIN BRIDGE if (vert0 && vert1) { const lor = vert1.cx - vert0.cx > 0 ? 1 : -1; ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = 6; ctx.beginPath(); ctx.moveTo(vert0.cx - 3 * lor, vert0.y1 - vert0.size / 4); ctx.lineTo(vert1.cx + 3 * lor, vert1.y1 - vert1.size / 4); ctx.stroke(); } } } } class HAPUS { name = "HAPUS"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { fillStyle: "rgba(200, 200, 200, 0.8)", ...options }; // Extract surface from options if provided this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { const x1 = parseFloat(this.vertices[0].x.toString()) + 1; const y1 = parseFloat(this.vertices[0].y.toString()) + 1; const x2 = parseFloat(this.vertices[1].x.toString()) + 1; const y2 = parseFloat(this.vertices[1].y.toString()) + 1; const x = x1; const y = y1; const size = x2 - x1; ctx.beginPath(); ctx.fillStyle = this.options.fillStyle; ctx.rect(x, y, size, size); ctx.fill(); } } // Arrow classes for tooth condition visualization class ARROW_TOP_LEFT { name = "ARROW_TOP_LEFT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#000000", fillStyle: "#000000", ...options }; // Extract surface from options if provided this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = x2 - (x2 - x1) / 3; const fromy = y1 - 15; const tox = fromx - 25; const toy = fromy; const headlen = 12; const lineWidth = 4; const angle = Math.atan2(toy - fromy, tox - fromx); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( tox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.fill(); }else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = x2 - (x2 - x1) / 3; const fromy = y2 + 15; const tox = fromx - 25; const toy = fromy; const headlen = 12; const lineWidth = 4; const angle = Math.atan2(toy - fromy, tox - fromx); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( tox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.fill(); } } } class ARROW_TOP_RIGHT { name = "ARROW_TOP_RIGHT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#000000", fillStyle: "#000000", ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = x1 + (x2 - x1) / 3; const fromy = y1 - 15; const tox = fromx + 25; const toy = fromy; const headlen = 12; const lineWidth = 4; const angle = Math.atan2(toy - fromy, tox - fromx); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( tox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.fill(); }else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = x1 + (x2 - x1) / 3; const fromy = y2 + 15; const tox = fromx + 25; const toy = fromy; const headlen = 12; const lineWidth = 4; const angle = Math.atan2(toy - fromy, tox - fromx); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( tox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.fill(); } } } class ARROW_TOP_TURN_LEFT { name = "ARROW_TOP_TURN_LEFT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#000000", fillStyle: "#000000", ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = (x1 + (x2 - x1) / 2) - 50; const fromy = y1 - 15; const tox = fromx + 20; const toy = fromy ; const headlen = 12; const lineWidth = 4; ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.arc(fromx + 55, fromy + 5, 5, 1.5 * Math.PI, 0.5 * Math.PI); ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox + 20, toy); ctx.lineTo(fromx + 55, toy); ctx.stroke(); const angle = Math.atan2(toy - fromy, tox); const newTox = tox +10; ctx.beginPath(); ctx.moveTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.stroke(); ctx.fill(); }else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = (x2 - (x2 - x1) / 2)+10; const fromy = y2 + 15; const tox = fromx - 20; const toy = fromy; const headlen = 12; const lineWidth = 4; ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.arc(fromx, fromy - 5, 5, 1.5 * Math.PI, 0.5 * Math.PI); ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo(fromx, toy); ctx.stroke(); const angle = Math.atan2(toy - fromy, tox); const newTox = tox - 5; ctx.beginPath(); ctx.moveTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.stroke(); ctx.fill(); } } } class ARROW_TOP_TURN_RIGHT { name = "ARROW_TOP_TURN_RIGHT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#000000", fillStyle: "#000000", ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = (x2 - (x2 - x1) /2) - 10; const fromy = y1 - 15; const tox = fromx + 20; const toy = fromy; const headlen = 12; const lineWidth = 4; ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.arc(fromx, fromy + 5, 5, 0.38 * Math.PI, 1.5 * Math.PI); ctx.stroke(); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, fromy); ctx.stroke(); const angle = Math.atan2(toy - fromy, tox - fromx); const newTox = tox + 5; ctx.beginPath(); ctx.moveTo(newTox, toy); ctx.lineTo( newTox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( newTox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(newTox, toy); ctx.lineTo( newTox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.stroke(); ctx.fill(); }else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = (x2 - (x2 - x1) / 2) + 10; const fromy = y2 + 15; const tox = fromx - 20; const toy = fromy; const headlen = 12; const lineWidth = 4; ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.arc(fromx, fromy - 5 , 5, 1.5 * Math.PI, 0.5 * Math.PI); ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo(fromx, toy); ctx.stroke(); const angle = Math.atan2(toy - fromy, tox); const newTox = tox - 5; ctx.beginPath(); ctx.moveTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.stroke(); ctx.fill(); } } } class ARROW_BOTTOM_LEFT { name = "ARROW_BOTTOM_LEFT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#000000", fillStyle: "#000000", ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = x2 - (x2 - x1) / 3; const fromy = y2 + 15; const tox = fromx - 25; const toy = fromy; const headlen = 12; const lineWidth = 4; const angle = Math.atan2(toy - fromy, tox - fromx); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( tox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.fill(); }else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = x2 - (x2 - x1) / 3; const fromy = y1 - 15; const tox = fromx - 25; const toy = fromy; const headlen = 12; const lineWidth = 4; const angle = Math.atan2(toy - fromy, tox - fromx); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( tox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.fill(); } } } class ARROW_BOTTOM_RIGHT { name = "ARROW_BOTTOM_RIGHT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#000000", fillStyle: "#000000", ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = x1 + (x2 - x1) / 3; const fromy = y2 + 15; const tox = fromx + 25; const toy = fromy; const headlen = 12; const lineWidth = 4; const angle = Math.atan2(toy - fromy, tox - fromx); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( tox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.fill(); }else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = x1 + (x2 - x1) / 3; const fromy = y1 - 15; const tox = fromx + 25; const toy = fromy; const headlen = 12; const lineWidth = 4; const angle = Math.atan2(toy - fromy, tox - fromx); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy); ctx.strokeStyle = this.options.strokeStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( tox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(tox, toy); ctx.lineTo( tox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.stroke(); ctx.fill(); } } } class ARROW_BOTTOM_TURN_LEFT { name = "ARROW_BOTTOM_TURN_LEFT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#000000", fillStyle: "#000000", ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = (x2 - (x2 - x1) / 2) + 10; const fromy = y2 + 15; const tox = fromx - 20; const toy = fromy; const headlen = 12; const lineWidth = 4; ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.arc(fromx, fromy - 5 , 5, 1.5 * Math.PI, 0.5 * Math.PI); ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox, toy); ctx.lineTo(fromx, toy); ctx.stroke(); const angle = Math.atan2(toy - fromy, tox); const newTox = tox - 5; ctx.beginPath(); ctx.moveTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.stroke(); ctx.fill(); }else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = (x1 + (x2 - x1) / 2) - 45; const fromy = y1 - 15; const tox = fromx + 20; const toy = fromy ; const headlen = 12; const lineWidth = 4; ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.arc(fromx + 55, fromy + 5, 5, 1.5 * Math.PI, 0.5 * Math.PI); ctx.stroke(); ctx.beginPath(); ctx.moveTo(tox + 20, toy); ctx.lineTo(fromx + 55, toy); ctx.stroke(); const angle = Math.atan2(toy - fromy, tox); const newTox = tox +10; ctx.beginPath(); ctx.moveTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo(newTox, toy); ctx.lineTo( newTox + headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.stroke(); ctx.fill(); } } } class ARROW_BOTTOM_TURN_RIGHT { name = "ARROW_BOTTOM_TURN_RIGHT"; mode?: number; vertices: { x: number; y: number }[]; options: any; surface?: string; // Tooth surface (T, R, B, L, M) row?: number; // Tooth row (1, 2, 3, or 4) pos?: string; // Tooth position/number constructor(vertices: { x: number; y: number }[], options: any = {}) { this.vertices = vertices; this.options = { strokeStyle: "#000000", fillStyle: "#000000", ...options }; this.surface = options.surface; // Extract row from options if provided this.row = options.row; // Extract pos from options if provided this.pos = options.pos; } render(ctx: CanvasRenderingContext2D) { if (this.row === 1 || this.row === 2) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = (x2 - (x2 - x1) / 2)-10 ; const fromy = y2 + 10; const tox = fromx + 20; const toy = fromy + 5; const headlen = 12; const lineWidth = 4; ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.arc(fromx, fromy, 5, 0.38 * Math.PI, 1.5 * Math.PI); ctx.stroke(); ctx.beginPath(); ctx.moveTo(fromx, toy); ctx.lineTo(tox, toy); ctx.stroke(); const angle = Math.atan2(toy - fromy, tox); const newTox = tox + 5; ctx.beginPath(); ctx.moveTo(newTox, toy); ctx.lineTo( newTox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( newTox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(newTox, toy); ctx.lineTo( newTox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.stroke(); ctx.fill(); }else if (this.row === 3 || this.row === 4) { if (!this.vertices || this.vertices.length < 2) { return; } const x1 = parseFloat(this.vertices[0].x.toString()); const y1 = parseFloat(this.vertices[0].y.toString()); const x2 = parseFloat(this.vertices[1].x.toString()); const y2 = parseFloat(this.vertices[1].y.toString()); const fromx = (x2 - (x2 - x1) /2) - 10; const fromy = y1 - 15; const tox = fromx + 20; const toy = fromy; const headlen = 12; const lineWidth = 4; ctx.strokeStyle = this.options.strokeStyle; ctx.fillStyle = this.options.fillStyle; ctx.lineWidth = lineWidth; ctx.beginPath(); ctx.arc(fromx, fromy + 5, 5, 0.38 * Math.PI, 1.5 * Math.PI); ctx.stroke(); ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, fromy); ctx.stroke(); const angle = Math.atan2(toy - fromy, tox - fromx); const newTox = tox + 5; ctx.beginPath(); ctx.moveTo(newTox, toy); ctx.lineTo( newTox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.lineTo( newTox - headlen * Math.cos(angle + Math.PI / 7), toy - headlen * Math.sin(angle + Math.PI / 7) ); ctx.lineTo(newTox, toy); ctx.lineTo( newTox - headlen * Math.cos(angle - Math.PI / 7), toy - headlen * Math.sin(angle - Math.PI / 7) ); ctx.stroke(); ctx.fill(); } } } export { Polygon, AMF, COF, FIS, NVT, UNE, PRE, NON, ANO, MIS, IPX, FRM_ACR, BRIDGE, HAPUS, RCT, CARIES, CFR, FMC, POC, RRX, ARROW_TOP_TURN_LEFT, ARROW_TOP_TURN_RIGHT, ARROW_BOTTOM_TURN_LEFT, ARROW_BOTTOM_TURN_RIGHT, ARROW_TOP_LEFT, ARROW_TOP_RIGHT, ARROW_BOTTOM_LEFT, ARROW_BOTTOM_RIGHT, useToothRenderer }; function useToothRenderer() { function renderCondition( ctx: CanvasRenderingContext2D, conditionName: string, vertices: { x: number; y: number }[], options: any = {} ) { const conditionClasses: Record = { Polygon, AMF, COF, FIS, NVT, UNE, PRE, NON, ANO, MIS, IPX, FRM_ACR, BRIDGE, HAPUS, RCT, CARIES, CFR, FMC, POC, RRX, ARROW_TOP_TURN_LEFT, ARROW_TOP_TURN_RIGHT, ARROW_BOTTOM_TURN_LEFT, ARROW_BOTTOM_TURN_RIGHT, ARROW_TOP_LEFT, ARROW_TOP_RIGHT, ARROW_BOTTOM_LEFT, ARROW_BOTTOM_RIGHT }; const ConditionClass = conditionClasses[conditionName]; if (!ConditionClass) { console.warn(`Unknown condition class: ${conditionName}`); return; } const conditionInstance = new ConditionClass(vertices, options); conditionInstance.render(ctx); } return { renderCondition }; }