2647 lines
76 KiB
TypeScript
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
|
|
};
|
|
}
|