Files
general-template/composables/apps/medical/useToothRenderer.ts
Yusron alamsyah 6bb6a1d430 first commit
2026-03-13 10:45:28 +07:00

2647 lines
76 KiB
TypeScript

// 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<string, any> = {
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
};
}