3d-odontogram 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -2
- package/dist/index.cjs +601 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +145 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.js +567 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/Odontogram3D.tsx","../src/engine.ts","../src/theme.ts","../src/toothNameMaps.ts"],"sourcesContent":["export { Odontogram3D } from \"./Odontogram3D\";\nexport type { Odontogram3DProps } from \"./Odontogram3D\";\nexport { OdontogramEngine } from \"./engine\";\nexport { DEFAULT_THEME, mergeTheme, toHexColor } from \"./theme\";\nexport { TOOTH_MESH_NAME_MAP_CHILD, TOOTH_MESH_NAME_MAP_ADULT } from \"./toothNameMaps\";\nexport type {\n ColorInput,\n MappedPoint,\n Odontogram3DHandle,\n OdontogramTheme,\n ToothType,\n} from \"./types\";\n","\"use client\";\n\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n} from \"react\";\nimport { OdontogramEngine } from \"./engine\";\nimport type {\n MappedPoint,\n Odontogram3DHandle,\n OdontogramTheme,\n ToothType,\n} from \"./types\";\n\nexport interface Odontogram3DProps {\n /** \"child\" (20 teeth) or \"adult\" (32 teeth). Default: \"child\". */\n toothType?: ToothType;\n /** Colors for the tooth, highlight, marker, canvas background and lighting. */\n theme?: OdontogramTheme;\n /** Base path where the `.bin.gz` model files are served from. Default: \"/assets/bin\". */\n modelBaseUrl?: string;\n /** Override the model file name per tooth type. */\n modelFileNames?: { child?: string; adult?: string };\n /** Diagnostic points to render as soon as the model finishes loading (e.g. from imported JSON). */\n initialPoints?: MappedPoint[];\n /** Fires whenever a point is added/removed/replaced. */\n onPointsChange?: (points: MappedPoint[]) => void;\n /** Fires when the model starts/finishes loading. */\n onLoadingChange?: (loading: boolean) => void;\n minDistance?: number;\n maxDistance?: number;\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * Self-contained 3D odontogram canvas: owns its own Three.js scene/renderer/model instance,\n * so multiple instances can be mounted on the same page without colliding.\n */\nexport const Odontogram3D = forwardRef<Odontogram3DHandle, Odontogram3DProps>(\n function Odontogram3D(\n {\n toothType = \"child\",\n theme,\n modelBaseUrl,\n modelFileNames,\n initialPoints,\n onPointsChange,\n onLoadingChange,\n minDistance,\n maxDistance,\n className,\n style,\n },\n ref\n ) {\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const engineRef = useRef<OdontogramEngine | null>(null);\n const isLoadingRef = useRef(true);\n const onPointsChangeRef = useRef(onPointsChange);\n const onLoadingChangeRef = useRef(onLoadingChange);\n onPointsChangeRef.current = onPointsChange;\n onLoadingChangeRef.current = onLoadingChange;\n\n // Mount once: create the engine instance for this component's lifetime.\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const engine = new OdontogramEngine(canvas, {\n theme,\n modelBaseUrl,\n modelFileNames,\n minDistance,\n maxDistance,\n onLoadingChange: (loading) => {\n isLoadingRef.current = loading;\n onLoadingChangeRef.current?.(loading);\n },\n onPointsChange: (points) => onPointsChangeRef.current?.(points),\n });\n engineRef.current = engine;\n\n engine.loadModel(toothType).then(() => {\n if (initialPoints && initialPoints.length > 0) {\n engine.setMappedPoints(initialPoints);\n }\n });\n\n const handleResize = () => engine.resize();\n window.addEventListener(\"resize\", handleResize);\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n engine.dispose();\n engineRef.current = null;\n };\n // Intentionally mount/unmount only: toothType/theme changes below are handled by\n // dedicated effects instead of recreating the whole scene.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Reload the model whenever toothType changes (but not on the very first mount).\n const previousToothType = useRef(toothType);\n useEffect(() => {\n if (previousToothType.current === toothType) return;\n previousToothType.current = toothType;\n engineRef.current?.loadModel(toothType);\n }, [toothType]);\n\n // Re-apply theme changes live, without reloading the model.\n useEffect(() => {\n if (!theme) return;\n engineRef.current?.applyTheme(theme);\n }, [theme]);\n\n const handlePointerDown = useCallback((event: React.MouseEvent<HTMLCanvasElement>) => {\n if (isLoadingRef.current) return;\n engineRef.current?.handlePointerDown(event.clientX, event.clientY);\n }, []);\n\n useImperativeHandle(\n ref,\n () => ({\n resetMapping: () => engineRef.current?.resetMapping(),\n getMappedPoints: () => engineRef.current?.getMappedPoints() ?? [],\n setMappedPoints: (points: MappedPoint[]) => engineRef.current?.setMappedPoints(points),\n updatePoint: (id: number, patch: Partial<MappedPoint>) => engineRef.current?.updatePoint(id, patch),\n applyTheme: (nextTheme: OdontogramTheme) => engineRef.current?.applyTheme(nextTheme),\n }),\n []\n );\n\n return (\n <canvas\n ref={canvasRef}\n className={className}\n style={{ width: \"100%\", height: \"100%\", ...style }}\n onMouseDown={handlePointerDown}\n />\n );\n }\n);\n","import * as THREE from \"three\";\nimport { OrbitControls } from \"three/examples/jsm/controls/OrbitControls.js\";\nimport { GLTFLoader } from \"three/examples/jsm/loaders/GLTFLoader.js\";\nimport { DEFAULT_THEME, mergeTheme, toHexColor } from \"./theme\";\nimport { mapForToothType } from \"./toothNameMaps\";\nimport type { MappedPoint, OdontogramTheme, ToothType } from \"./types\";\n\nexport interface OdontogramEngineOptions {\n theme?: OdontogramTheme;\n /** Base path where `<toothType>` model files are served from. Default: \"/assets/bin\". */\n modelBaseUrl?: string;\n /** Override the model file name per tooth type. Defaults to \"ip1.bin.gz\" (child) / \"ip3.bin.gz\" (adult). */\n modelFileNames?: { child?: string; adult?: string };\n onLoadingChange?: (loading: boolean) => void;\n onPointsChange?: (points: MappedPoint[]) => void;\n /** Camera orbit distance limits. */\n minDistance?: number;\n maxDistance?: number;\n}\n\nconst DEFAULT_MODEL_FILE_NAMES = { child: \"ip1.bin.gz\", adult: \"ip3.bin.gz\" };\n\n/**\n * Encapsulates one Three.js scene/renderer/model instance. Every instance owns its own\n * state (no module-level globals), so multiple <Odontogram3D /> can be mounted at once.\n */\nexport class OdontogramEngine {\n private canvas: HTMLCanvasElement;\n private scene: THREE.Scene;\n private camera: THREE.PerspectiveCamera;\n private renderer: THREE.WebGLRenderer;\n private controls: OrbitControls;\n private raycaster = new THREE.Raycaster();\n private pointer = new THREE.Vector2();\n private toothMeshes: THREE.Object3D[] = [];\n private animationFrameId: number | null = null;\n private theme: Required<OdontogramTheme>;\n private toothMaterialTemplate: THREE.MeshPhongMaterial;\n private mappedPoints: MappedPoint[] = [];\n private options: OdontogramEngineOptions;\n private disposed = false;\n\n constructor(canvas: HTMLCanvasElement, options: OdontogramEngineOptions = {}) {\n this.canvas = canvas;\n this.options = options;\n this.theme = mergeTheme(options.theme);\n\n this.scene = new THREE.Scene();\n\n this.camera = new THREE.PerspectiveCamera(\n 50,\n canvas.clientWidth / canvas.clientHeight || 1,\n 0.1,\n 200\n );\n this.camera.position.set(0, 2.5, 100);\n\n this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas, alpha: true });\n this.renderer.setSize(canvas.clientWidth, canvas.clientHeight);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.applyCanvasBackground();\n\n this.controls = new OrbitControls(this.camera, this.renderer.domElement);\n this.controls.enableDamping = true;\n this.controls.dampingFactor = 0.05;\n this.controls.screenSpacePanning = false;\n this.controls.minDistance = options.minDistance ?? 100;\n this.controls.maxDistance = options.maxDistance ?? 130;\n this.controls.target.set(4, 8, 0);\n\n this.toothMaterialTemplate = new THREE.MeshPhongMaterial({\n color: toHexColor(this.theme.toothColor),\n specular: 0x555555,\n shininess: 30,\n flatShading: false,\n side: THREE.DoubleSide,\n });\n\n this.setupLights();\n\n const animate = () => {\n if (this.disposed) return;\n this.animationFrameId = requestAnimationFrame(animate);\n this.controls.update();\n this.renderer.render(this.scene, this.camera);\n };\n animate();\n }\n\n private lights: {\n ambient: THREE.AmbientLight;\n key: THREE.DirectionalLight;\n fill: THREE.DirectionalLight;\n } | null = null;\n\n private setupLights() {\n const ambient = new THREE.AmbientLight(\n toHexColor(this.theme.ambientLightColor),\n this.theme.ambientLightIntensity\n );\n this.scene.add(ambient);\n\n const key = new THREE.DirectionalLight(\n toHexColor(this.theme.keyLightColor),\n this.theme.keyLightIntensity\n );\n key.position.set(10, 10, 10);\n this.scene.add(key);\n\n const fill = new THREE.DirectionalLight(\n toHexColor(this.theme.fillLightColor),\n this.theme.fillLightIntensity\n );\n fill.position.set(-10, -10, -5);\n this.scene.add(fill);\n\n this.lights = { ambient, key, fill };\n }\n\n private applyCanvasBackground() {\n this.renderer.setClearColor(\n toHexColor(this.theme.canvasBackground),\n this.theme.canvasBackgroundOpacity\n );\n }\n\n private resolveModelUrl(toothType: ToothType) {\n const base = this.options.modelBaseUrl ?? \"/assets/bin\";\n const fileNames = { ...DEFAULT_MODEL_FILE_NAMES, ...this.options.modelFileNames };\n const fileName = toothType === \"adult\" ? fileNames.adult : fileNames.child;\n return `${base.replace(/\\/$/, \"\")}/${fileName}`;\n }\n\n /** Re-apply a (partial) theme to lights, background and every tooth currently in its default state. */\n applyTheme(theme: OdontogramTheme) {\n this.theme = mergeTheme({ ...this.theme, ...theme });\n this.applyCanvasBackground();\n\n if (this.lights) {\n this.lights.ambient.color.setHex(toHexColor(this.theme.ambientLightColor));\n this.lights.ambient.intensity = this.theme.ambientLightIntensity;\n this.lights.key.color.setHex(toHexColor(this.theme.keyLightColor));\n this.lights.key.intensity = this.theme.keyLightIntensity;\n this.lights.fill.color.setHex(toHexColor(this.theme.fillLightColor));\n this.lights.fill.intensity = this.theme.fillLightIntensity;\n }\n\n this.toothMaterialTemplate.color.setHex(toHexColor(this.theme.toothColor));\n\n const mappedToothIds = new Set(this.mappedPoints.map((p) => p.toothId));\n this.forEachToothGroup((group) => {\n const isMapped = Boolean(group.userData.isMapped);\n const color = isMapped ? this.theme.highlightColor : this.theme.toothColor;\n this.setGroupColor(group, toHexColor(color));\n });\n\n // Markers use their own small sphere material, update those too.\n this.scene.children\n .filter((child) => child.name.startsWith(\"Marker-\"))\n .forEach((marker) => {\n if (marker instanceof THREE.Mesh && marker.material instanceof THREE.MeshBasicMaterial) {\n marker.material.color.setHex(toHexColor(this.theme.markerColor));\n }\n });\n\n void mappedToothIds; // kept for clarity/future use, no-op reference\n }\n\n private forEachToothGroup(callback: (group: THREE.Object3D) => void) {\n const seen = new Set<THREE.Object3D>();\n this.toothMeshes.forEach((mesh) => {\n const group = mesh.userData.parentGroup as THREE.Object3D | undefined;\n if (group && !seen.has(group)) {\n seen.add(group);\n callback(group);\n }\n });\n }\n\n private setGroupColor(group: THREE.Object3D, hex: number) {\n group.traverse((child) => {\n if (\n child instanceof THREE.Mesh &&\n child.userData.parentGroup === group &&\n child.material instanceof THREE.MeshPhongMaterial\n ) {\n child.material.color.setHex(hex);\n }\n });\n }\n\n async loadModel(toothType: ToothType) {\n const oldArchGroup = this.scene.getObjectByName(\"ArchGroup\");\n if (oldArchGroup) {\n this.scene.remove(oldArchGroup);\n oldArchGroup.traverse((child) => {\n if (child instanceof THREE.Mesh) {\n child.geometry.dispose();\n const material = child.material;\n if (Array.isArray(material)) material.forEach((m) => m.dispose());\n else material.dispose();\n }\n });\n }\n\n this.toothMeshes = [];\n this.mappedPoints = [];\n this.options.onPointsChange?.([]);\n\n const loader = new GLTFLoader();\n const modelUrl = this.resolveModelUrl(toothType);\n this.options.onLoadingChange?.(true);\n\n try {\n const gltf = await loader.loadAsync(modelUrl);\n if (this.disposed) return;\n\n const loadedModel = gltf.scene;\n loadedModel.scale.set(1.0, 1.0, 1.0);\n loadedModel.position.set(0, -2, 0);\n loadedModel.rotation.set(0, 0, 0);\n loadedModel.name = \"ArchGroup\";\n\n const nameMap = mapForToothType(toothType);\n\n loadedModel.traverse((child) => {\n if (!(child instanceof THREE.Mesh)) return;\n\n child.geometry.computeVertexNormals();\n if (child.geometry.attributes.color) {\n child.geometry.deleteAttribute(\"color\");\n }\n\n let mappedName = nameMap[child.name];\n let targetObject: THREE.Object3D = child;\n\n if (!mappedName && child.parent && child.parent.type !== \"Scene\") {\n if (nameMap[child.parent.name]) {\n mappedName = nameMap[child.parent.name];\n targetObject = child.parent;\n }\n }\n\n child.material = this.toothMaterialTemplate.clone();\n\n if (!targetObject.userData.isTooth) {\n targetObject.userData = {\n toothId: mappedName ?? child.name,\n isTooth: true,\n isMapped: false,\n markerIds: [] as number[],\n };\n }\n\n child.userData.parentGroup = targetObject;\n this.toothMeshes.push(child);\n });\n\n this.scene.add(loadedModel);\n } catch (error) {\n console.error(`[odontogram-3d] Failed to load model at ${modelUrl}:`, error);\n } finally {\n this.options.onLoadingChange?.(false);\n }\n }\n\n private removeMarkers(markerIds: number[]) {\n markerIds.forEach((id) => {\n const marker = this.scene.getObjectByName(`Marker-${id}`);\n if (marker instanceof THREE.Mesh) {\n this.scene.remove(marker);\n marker.geometry.dispose();\n marker.material.dispose();\n }\n });\n }\n\n private addMarker(id: number, position: THREE.Vector3) {\n const geometry = new THREE.SphereGeometry(0.05, 8, 8);\n const material = new THREE.MeshBasicMaterial({ color: toHexColor(this.theme.markerColor) });\n const marker = new THREE.Mesh(geometry, material);\n marker.position.copy(position);\n marker.name = `Marker-${id}`;\n this.scene.add(marker);\n }\n\n private emitPointsChange() {\n this.options.onPointsChange?.([...this.mappedPoints]);\n }\n\n /** Handle a click/tap on the canvas (expects coordinates relative to the viewport, like a MouseEvent). */\n handlePointerDown(clientX: number, clientY: number) {\n const rect = this.canvas.getBoundingClientRect();\n this.pointer.x = ((clientX - rect.left) / rect.width) * 2 - 1;\n this.pointer.y = -((clientY - rect.top) / rect.height) * 2 + 1;\n\n this.raycaster.setFromCamera(this.pointer, this.camera);\n const intersects = this.raycaster.intersectObjects(this.toothMeshes, false);\n if (intersects.length === 0) return;\n\n const clickedMesh = intersects[0].object;\n const toothGroup = clickedMesh.userData.parentGroup as THREE.Object3D | undefined;\n if (!toothGroup || !toothGroup.userData.isTooth) return;\n\n const toothId: string = toothGroup.userData.toothId;\n const point = intersects[0].point;\n const isCurrentlyMapped = Boolean(toothGroup.userData.isMapped);\n\n if (isCurrentlyMapped) {\n this.removeMarkers(toothGroup.userData.markerIds ?? []);\n this.setGroupColor(toothGroup, toHexColor(this.theme.toothColor));\n toothGroup.userData.isMapped = false;\n toothGroup.userData.markerIds = [];\n this.mappedPoints = this.mappedPoints.filter((p) => p.toothId !== toothId);\n this.emitPointsChange();\n } else {\n const newMarkerId = Date.now();\n this.setGroupColor(toothGroup, toHexColor(this.theme.highlightColor));\n toothGroup.userData.isMapped = true;\n toothGroup.userData.markerIds = [newMarkerId];\n this.addMarker(newMarkerId, point);\n\n const newPoint: MappedPoint = {\n id: newMarkerId,\n toothId,\n x: point.x.toFixed(2),\n y: point.y.toFixed(2),\n z: point.z.toFixed(2),\n type: \"Karies\",\n description: \"\",\n };\n this.mappedPoints = [...this.mappedPoints, newPoint];\n this.emitPointsChange();\n }\n }\n\n getMappedPoints(): MappedPoint[] {\n return [...this.mappedPoints];\n }\n\n /** Patch metadata (e.g. description) on an existing point without touching the 3D scene. */\n updatePoint(id: number, patch: Partial<MappedPoint>) {\n this.mappedPoints = this.mappedPoints.map((p) => (p.id === id ? { ...p, ...patch } : p));\n this.emitPointsChange();\n }\n\n resetMapping() {\n const existingMarkerIds = this.scene.children\n .filter((c) => c.name.startsWith(\"Marker-\"))\n .map((c) => Number(c.name.replace(\"Marker-\", \"\")));\n this.removeMarkers(existingMarkerIds);\n\n this.forEachToothGroup((group) => {\n this.setGroupColor(group, toHexColor(this.theme.toothColor));\n group.userData.isMapped = false;\n group.userData.markerIds = [];\n });\n\n this.mappedPoints = [];\n this.emitPointsChange();\n }\n\n /** Replace all diagnostic points and redraw markers/highlights to match (e.g. after importing JSON). */\n setMappedPoints(points: MappedPoint[]) {\n this.resetMapping();\n\n points.forEach((point) => {\n const group = this.toothMeshes.find(\n (mesh) => (mesh.userData.parentGroup as THREE.Object3D | undefined)?.userData.toothId === point.toothId\n )?.userData.parentGroup as THREE.Object3D | undefined;\n\n if (!group) return;\n\n this.setGroupColor(group, toHexColor(this.theme.highlightColor));\n group.userData.isMapped = true;\n group.userData.markerIds = [...(group.userData.markerIds ?? []), point.id];\n\n this.addMarker(point.id, new THREE.Vector3(parseFloat(point.x), parseFloat(point.y), parseFloat(point.z)));\n });\n\n this.mappedPoints = [...points];\n this.emitPointsChange();\n }\n\n resize() {\n const container = this.canvas.parentElement;\n if (!container) return;\n const { clientWidth, clientHeight } = container;\n if (clientWidth === 0 || clientHeight === 0) return;\n\n this.camera.aspect = clientWidth / clientHeight;\n this.camera.updateProjectionMatrix();\n this.renderer.setSize(clientWidth, clientHeight, false);\n this.renderer.setPixelRatio(window.devicePixelRatio);\n }\n\n dispose() {\n this.disposed = true;\n if (this.animationFrameId !== null) cancelAnimationFrame(this.animationFrameId);\n\n const oldArchGroup = this.scene.getObjectByName(\"ArchGroup\");\n if (oldArchGroup) this.scene.remove(oldArchGroup);\n\n this.scene.traverse((child) => {\n if (child instanceof THREE.Mesh) {\n child.geometry.dispose();\n const material = child.material;\n if (Array.isArray(material)) material.forEach((m) => m.dispose());\n else material?.dispose();\n }\n });\n\n this.toothMaterialTemplate.dispose();\n this.controls.dispose();\n this.renderer.dispose();\n }\n}\n\nexport { DEFAULT_THEME };\n","import * as THREE from \"three\";\nimport type { ColorInput, OdontogramTheme } from \"./types\";\n\nexport const DEFAULT_THEME: Required<OdontogramTheme> = {\n toothColor: 0xf5f5dc, // Ivory\n highlightColor: 0x3b82f6, // Tailwind blue-500\n markerColor: 0xff0000, // Red\n canvasBackground: 0xecf0f1,\n canvasBackgroundOpacity: 0,\n ambientLightColor: 0xffffff,\n ambientLightIntensity: 0.8,\n keyLightColor: 0xffffff,\n keyLightIntensity: 0.6,\n fillLightColor: 0xffffff,\n fillLightIntensity: 0.4,\n};\n\nexport function mergeTheme(theme?: OdontogramTheme): Required<OdontogramTheme> {\n return { ...DEFAULT_THEME, ...theme };\n}\n\n/** Three.js color helpers accept numbers (0xff0000) or strings (\"#ff0000\"); normalize both to a hex number. */\nexport function toHexColor(input: ColorInput): number {\n if (typeof input === \"number\") return input;\n return new THREE.Color(input).getHex();\n}\n","export const TOOTH_MESH_NAME_MAP_CHILD: { [key: string]: string } = {\n Gums_Maxilla001_0: \"Gums Maxilla\",\n Gums_Mandibula001_0: \"Gums Mandibulla\",\n A_0: \"(Left Top) Molar\",\n B_0: \"(Left Top) Premolar\",\n C_0: \"(Left Top) Canine\",\n D_0: \"(Left Top) Lateral\",\n E_0: \"(Left Top) Central\",\n F_0: \"(Right Top) Central\",\n G_0: \"(Right Top) Lateral\",\n H_0: \"(Right Top) Canine\",\n I_0: \"(Right Top) Premolar\",\n J_0: \"(Right Top) Molar\",\n K_0: \"(Left Bottom) Molar\",\n L_0: \"(Left Bottom) Premolar\",\n M_0: \"(Left Bottom) Canine\",\n N_0: \"(Left Bottom) Lateral\",\n O_0: \"(Left Bottom) Central\",\n P_0: \"(Right Bottom) Central\",\n Q_0: \"(Right Bottom) Lateral\",\n R_0: \"(Right Bottom) Canine\",\n S_0: \"(Right Bottom) Premolar\",\n T_0: \"(Right Bottom) Molar\",\n};\n\nexport const TOOTH_MESH_NAME_MAP_ADULT: { [key: string]: string } = {\n // --- Rahang Atas Kanan (Quadrant 1) ---\n \"Box023_01_-_Default_0\": \"11 (Right Top) Central Incisor\",\n \"Box024_01_-_Default_0\": \"12 (Right Top) Lateral Incisor\",\n \"Box025_01_-_Default_0\": \"13 (Right Top) Canine\",\n \"Box031_01_-_Default_0\": \"14 (Right Top) First Premolar\",\n \"Box033_01_-_Default_0\": \"15 (Right Top) Second Premolar\",\n \"Box032_01_-_Default_0\": \"16 (Right Top) First Molar\",\n \"Box035_01_-_Default_0\": \"17 (Right Top) Second Molar\",\n \"Box034_01_-_Default_0\": \"18 (Right Top) Third Molar\",\n\n // --- Rahang Atas Kiri (Quadrant 2) ---\n \"Box020_01_-_Default_0\": \"21 (Left Top) Central Incisor\",\n \"Box021_01_-_Default_0\": \"22 (Left Top) Lateral Incisor\",\n \"Box022_01_-_Default_0\": \"23 (Left Top) Canine\",\n \"Box026_01_-_Default_0\": \"24 (Left Top) First Premolar\",\n \"Box028_01_-_Default_0\": \"25 (Left Top) Second Premolar\",\n \"Box027_01_-_Default_0\": \"26 (Left Top) First Molar\",\n \"Box030_01_-_Default_0\": \"27 (Left Top) Second Molar\",\n \"Box029_01_-_Default_0\": \"28 (Left Top) Third Molar\",\n\n // --- Rahang Bawah Kiri (Quadrant 3) ---\n \"Box011_01_-_Default_0\": \"38 (Left Bottom) Third Molar\",\n \"Box013_01_-_Default_0\": \"37 (Left Bottom) Second Molar\",\n \"Box008_01_-_Default_0\": \"36 (Left Bottom) First Molar\",\n \"Box009_01_-_Default_0\": \"35 (Left Bottom) Second Premolar\",\n \"Box007_01_-_Default_0\": \"34 (Left Bottom) First Premolar\",\n \"Box003_01_-_Default_0\": \"33 (Left Bottom) Canine\",\n \"Box002_01_-_Default_0\": \"32 (Left Bottom) Lateral Incisor\",\n \"Box001_01_-_Default_0\": \"31 (Left Bottom) Central Incisor\",\n\n // --- Rahang Bawah Kanan (Quadrant 4) ---\n \"Box004_01_-_Default_0\": \"41 (Right Bottom) Central Incisor\",\n \"Box005_01_-_Default_0\": \"42 (Right Bottom) Lateral Incisor\",\n \"Box006_01_-_Default_0\": \"43 (Right Bottom) Canine\",\n \"Box015_01_-_Default_0\": \"44 (Right Bottom) First Premolar\",\n \"Box017_01_-_Default_0\": \"45 (Right Bottom) Second Premolar\",\n \"Box016_01_-_Default_0\": \"46 (Right Bottom) First Molar\",\n \"Box019_01_-_Default_0\": \"47 (Right Bottom) Second Molar\",\n \"GeoSphere001_01_-_Default_0\": \"48 (Right Bottom) Third Molar\",\n\n // --- Lain-lain ---\n \"Tube002_07_-_Default_0\": \"(Top Tube) Gums Maxilla\",\n \"Tube001_07_-_Default_0\": \"(Bottom Tube) Gums Mandibula\",\n \"lingua_02_-_Default_0\": \"(Tongue) Lingua\",\n};\n\nexport function mapForToothType(toothType: string): { [key: string]: string } {\n return toothType === \"child\" || !toothType\n ? TOOTH_MESH_NAME_MAP_CHILD\n : TOOTH_MESH_NAME_MAP_ADULT;\n}\n"],"mappings":"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","names":["THREE","_a","Odontogram3D"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
type ColorInput = number | string;
|
|
4
|
+
type ToothType = "child" | "adult";
|
|
5
|
+
interface OdontogramTheme {
|
|
6
|
+
/** Default tooth color before any diagnostic marker is placed. */
|
|
7
|
+
toothColor?: ColorInput;
|
|
8
|
+
/** Color applied to a tooth once it has a diagnostic marker. */
|
|
9
|
+
highlightColor?: ColorInput;
|
|
10
|
+
/** Color of the small sphere marker placed at the click point. */
|
|
11
|
+
markerColor?: ColorInput;
|
|
12
|
+
/** Canvas/renderer clear color (the "background" behind the 3D model). */
|
|
13
|
+
canvasBackground?: ColorInput;
|
|
14
|
+
/** Opacity of the canvas clear color, 0 = fully transparent. */
|
|
15
|
+
canvasBackgroundOpacity?: number;
|
|
16
|
+
ambientLightColor?: ColorInput;
|
|
17
|
+
ambientLightIntensity?: number;
|
|
18
|
+
keyLightColor?: ColorInput;
|
|
19
|
+
keyLightIntensity?: number;
|
|
20
|
+
fillLightColor?: ColorInput;
|
|
21
|
+
fillLightIntensity?: number;
|
|
22
|
+
}
|
|
23
|
+
interface MappedPoint {
|
|
24
|
+
id: number;
|
|
25
|
+
toothId: string;
|
|
26
|
+
x: string;
|
|
27
|
+
y: string;
|
|
28
|
+
z: string;
|
|
29
|
+
type: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
interface Odontogram3DHandle {
|
|
33
|
+
/** Remove all markers and reset every tooth back to its default color. */
|
|
34
|
+
resetMapping: () => void;
|
|
35
|
+
/** Current diagnostic points mapped on the model. */
|
|
36
|
+
getMappedPoints: () => MappedPoint[];
|
|
37
|
+
/** Replace all diagnostic points and redraw markers/highlights to match. */
|
|
38
|
+
setMappedPoints: (points: MappedPoint[]) => void;
|
|
39
|
+
/** Patch metadata (e.g. description) on an existing point without touching the 3D scene. */
|
|
40
|
+
updatePoint: (id: number, patch: Partial<MappedPoint>) => void;
|
|
41
|
+
/** Re-apply a (partial) theme without re-mounting the component. */
|
|
42
|
+
applyTheme: (theme: OdontogramTheme) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface Odontogram3DProps {
|
|
46
|
+
/** "child" (20 teeth) or "adult" (32 teeth). Default: "child". */
|
|
47
|
+
toothType?: ToothType;
|
|
48
|
+
/** Colors for the tooth, highlight, marker, canvas background and lighting. */
|
|
49
|
+
theme?: OdontogramTheme;
|
|
50
|
+
/** Base path where the `.bin.gz` model files are served from. Default: "/assets/bin". */
|
|
51
|
+
modelBaseUrl?: string;
|
|
52
|
+
/** Override the model file name per tooth type. */
|
|
53
|
+
modelFileNames?: {
|
|
54
|
+
child?: string;
|
|
55
|
+
adult?: string;
|
|
56
|
+
};
|
|
57
|
+
/** Diagnostic points to render as soon as the model finishes loading (e.g. from imported JSON). */
|
|
58
|
+
initialPoints?: MappedPoint[];
|
|
59
|
+
/** Fires whenever a point is added/removed/replaced. */
|
|
60
|
+
onPointsChange?: (points: MappedPoint[]) => void;
|
|
61
|
+
/** Fires when the model starts/finishes loading. */
|
|
62
|
+
onLoadingChange?: (loading: boolean) => void;
|
|
63
|
+
minDistance?: number;
|
|
64
|
+
maxDistance?: number;
|
|
65
|
+
className?: string;
|
|
66
|
+
style?: React.CSSProperties;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Self-contained 3D odontogram canvas: owns its own Three.js scene/renderer/model instance,
|
|
70
|
+
* so multiple instances can be mounted on the same page without colliding.
|
|
71
|
+
*/
|
|
72
|
+
declare const Odontogram3D: React.ForwardRefExoticComponent<Odontogram3DProps & React.RefAttributes<Odontogram3DHandle>>;
|
|
73
|
+
|
|
74
|
+
declare const DEFAULT_THEME: Required<OdontogramTheme>;
|
|
75
|
+
declare function mergeTheme(theme?: OdontogramTheme): Required<OdontogramTheme>;
|
|
76
|
+
/** Three.js color helpers accept numbers (0xff0000) or strings ("#ff0000"); normalize both to a hex number. */
|
|
77
|
+
declare function toHexColor(input: ColorInput): number;
|
|
78
|
+
|
|
79
|
+
interface OdontogramEngineOptions {
|
|
80
|
+
theme?: OdontogramTheme;
|
|
81
|
+
/** Base path where `<toothType>` model files are served from. Default: "/assets/bin". */
|
|
82
|
+
modelBaseUrl?: string;
|
|
83
|
+
/** Override the model file name per tooth type. Defaults to "ip1.bin.gz" (child) / "ip3.bin.gz" (adult). */
|
|
84
|
+
modelFileNames?: {
|
|
85
|
+
child?: string;
|
|
86
|
+
adult?: string;
|
|
87
|
+
};
|
|
88
|
+
onLoadingChange?: (loading: boolean) => void;
|
|
89
|
+
onPointsChange?: (points: MappedPoint[]) => void;
|
|
90
|
+
/** Camera orbit distance limits. */
|
|
91
|
+
minDistance?: number;
|
|
92
|
+
maxDistance?: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Encapsulates one Three.js scene/renderer/model instance. Every instance owns its own
|
|
96
|
+
* state (no module-level globals), so multiple <Odontogram3D /> can be mounted at once.
|
|
97
|
+
*/
|
|
98
|
+
declare class OdontogramEngine {
|
|
99
|
+
private canvas;
|
|
100
|
+
private scene;
|
|
101
|
+
private camera;
|
|
102
|
+
private renderer;
|
|
103
|
+
private controls;
|
|
104
|
+
private raycaster;
|
|
105
|
+
private pointer;
|
|
106
|
+
private toothMeshes;
|
|
107
|
+
private animationFrameId;
|
|
108
|
+
private theme;
|
|
109
|
+
private toothMaterialTemplate;
|
|
110
|
+
private mappedPoints;
|
|
111
|
+
private options;
|
|
112
|
+
private disposed;
|
|
113
|
+
constructor(canvas: HTMLCanvasElement, options?: OdontogramEngineOptions);
|
|
114
|
+
private lights;
|
|
115
|
+
private setupLights;
|
|
116
|
+
private applyCanvasBackground;
|
|
117
|
+
private resolveModelUrl;
|
|
118
|
+
/** Re-apply a (partial) theme to lights, background and every tooth currently in its default state. */
|
|
119
|
+
applyTheme(theme: OdontogramTheme): void;
|
|
120
|
+
private forEachToothGroup;
|
|
121
|
+
private setGroupColor;
|
|
122
|
+
loadModel(toothType: ToothType): Promise<void>;
|
|
123
|
+
private removeMarkers;
|
|
124
|
+
private addMarker;
|
|
125
|
+
private emitPointsChange;
|
|
126
|
+
/** Handle a click/tap on the canvas (expects coordinates relative to the viewport, like a MouseEvent). */
|
|
127
|
+
handlePointerDown(clientX: number, clientY: number): void;
|
|
128
|
+
getMappedPoints(): MappedPoint[];
|
|
129
|
+
/** Patch metadata (e.g. description) on an existing point without touching the 3D scene. */
|
|
130
|
+
updatePoint(id: number, patch: Partial<MappedPoint>): void;
|
|
131
|
+
resetMapping(): void;
|
|
132
|
+
/** Replace all diagnostic points and redraw markers/highlights to match (e.g. after importing JSON). */
|
|
133
|
+
setMappedPoints(points: MappedPoint[]): void;
|
|
134
|
+
resize(): void;
|
|
135
|
+
dispose(): void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
declare const TOOTH_MESH_NAME_MAP_CHILD: {
|
|
139
|
+
[key: string]: string;
|
|
140
|
+
};
|
|
141
|
+
declare const TOOTH_MESH_NAME_MAP_ADULT: {
|
|
142
|
+
[key: string]: string;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export { type ColorInput, DEFAULT_THEME, type MappedPoint, Odontogram3D, type Odontogram3DHandle, type Odontogram3DProps, OdontogramEngine, type OdontogramTheme, TOOTH_MESH_NAME_MAP_ADULT, TOOTH_MESH_NAME_MAP_CHILD, type ToothType, mergeTheme, toHexColor };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
type ColorInput = number | string;
|
|
4
|
+
type ToothType = "child" | "adult";
|
|
5
|
+
interface OdontogramTheme {
|
|
6
|
+
/** Default tooth color before any diagnostic marker is placed. */
|
|
7
|
+
toothColor?: ColorInput;
|
|
8
|
+
/** Color applied to a tooth once it has a diagnostic marker. */
|
|
9
|
+
highlightColor?: ColorInput;
|
|
10
|
+
/** Color of the small sphere marker placed at the click point. */
|
|
11
|
+
markerColor?: ColorInput;
|
|
12
|
+
/** Canvas/renderer clear color (the "background" behind the 3D model). */
|
|
13
|
+
canvasBackground?: ColorInput;
|
|
14
|
+
/** Opacity of the canvas clear color, 0 = fully transparent. */
|
|
15
|
+
canvasBackgroundOpacity?: number;
|
|
16
|
+
ambientLightColor?: ColorInput;
|
|
17
|
+
ambientLightIntensity?: number;
|
|
18
|
+
keyLightColor?: ColorInput;
|
|
19
|
+
keyLightIntensity?: number;
|
|
20
|
+
fillLightColor?: ColorInput;
|
|
21
|
+
fillLightIntensity?: number;
|
|
22
|
+
}
|
|
23
|
+
interface MappedPoint {
|
|
24
|
+
id: number;
|
|
25
|
+
toothId: string;
|
|
26
|
+
x: string;
|
|
27
|
+
y: string;
|
|
28
|
+
z: string;
|
|
29
|
+
type: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
interface Odontogram3DHandle {
|
|
33
|
+
/** Remove all markers and reset every tooth back to its default color. */
|
|
34
|
+
resetMapping: () => void;
|
|
35
|
+
/** Current diagnostic points mapped on the model. */
|
|
36
|
+
getMappedPoints: () => MappedPoint[];
|
|
37
|
+
/** Replace all diagnostic points and redraw markers/highlights to match. */
|
|
38
|
+
setMappedPoints: (points: MappedPoint[]) => void;
|
|
39
|
+
/** Patch metadata (e.g. description) on an existing point without touching the 3D scene. */
|
|
40
|
+
updatePoint: (id: number, patch: Partial<MappedPoint>) => void;
|
|
41
|
+
/** Re-apply a (partial) theme without re-mounting the component. */
|
|
42
|
+
applyTheme: (theme: OdontogramTheme) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface Odontogram3DProps {
|
|
46
|
+
/** "child" (20 teeth) or "adult" (32 teeth). Default: "child". */
|
|
47
|
+
toothType?: ToothType;
|
|
48
|
+
/** Colors for the tooth, highlight, marker, canvas background and lighting. */
|
|
49
|
+
theme?: OdontogramTheme;
|
|
50
|
+
/** Base path where the `.bin.gz` model files are served from. Default: "/assets/bin". */
|
|
51
|
+
modelBaseUrl?: string;
|
|
52
|
+
/** Override the model file name per tooth type. */
|
|
53
|
+
modelFileNames?: {
|
|
54
|
+
child?: string;
|
|
55
|
+
adult?: string;
|
|
56
|
+
};
|
|
57
|
+
/** Diagnostic points to render as soon as the model finishes loading (e.g. from imported JSON). */
|
|
58
|
+
initialPoints?: MappedPoint[];
|
|
59
|
+
/** Fires whenever a point is added/removed/replaced. */
|
|
60
|
+
onPointsChange?: (points: MappedPoint[]) => void;
|
|
61
|
+
/** Fires when the model starts/finishes loading. */
|
|
62
|
+
onLoadingChange?: (loading: boolean) => void;
|
|
63
|
+
minDistance?: number;
|
|
64
|
+
maxDistance?: number;
|
|
65
|
+
className?: string;
|
|
66
|
+
style?: React.CSSProperties;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Self-contained 3D odontogram canvas: owns its own Three.js scene/renderer/model instance,
|
|
70
|
+
* so multiple instances can be mounted on the same page without colliding.
|
|
71
|
+
*/
|
|
72
|
+
declare const Odontogram3D: React.ForwardRefExoticComponent<Odontogram3DProps & React.RefAttributes<Odontogram3DHandle>>;
|
|
73
|
+
|
|
74
|
+
declare const DEFAULT_THEME: Required<OdontogramTheme>;
|
|
75
|
+
declare function mergeTheme(theme?: OdontogramTheme): Required<OdontogramTheme>;
|
|
76
|
+
/** Three.js color helpers accept numbers (0xff0000) or strings ("#ff0000"); normalize both to a hex number. */
|
|
77
|
+
declare function toHexColor(input: ColorInput): number;
|
|
78
|
+
|
|
79
|
+
interface OdontogramEngineOptions {
|
|
80
|
+
theme?: OdontogramTheme;
|
|
81
|
+
/** Base path where `<toothType>` model files are served from. Default: "/assets/bin". */
|
|
82
|
+
modelBaseUrl?: string;
|
|
83
|
+
/** Override the model file name per tooth type. Defaults to "ip1.bin.gz" (child) / "ip3.bin.gz" (adult). */
|
|
84
|
+
modelFileNames?: {
|
|
85
|
+
child?: string;
|
|
86
|
+
adult?: string;
|
|
87
|
+
};
|
|
88
|
+
onLoadingChange?: (loading: boolean) => void;
|
|
89
|
+
onPointsChange?: (points: MappedPoint[]) => void;
|
|
90
|
+
/** Camera orbit distance limits. */
|
|
91
|
+
minDistance?: number;
|
|
92
|
+
maxDistance?: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Encapsulates one Three.js scene/renderer/model instance. Every instance owns its own
|
|
96
|
+
* state (no module-level globals), so multiple <Odontogram3D /> can be mounted at once.
|
|
97
|
+
*/
|
|
98
|
+
declare class OdontogramEngine {
|
|
99
|
+
private canvas;
|
|
100
|
+
private scene;
|
|
101
|
+
private camera;
|
|
102
|
+
private renderer;
|
|
103
|
+
private controls;
|
|
104
|
+
private raycaster;
|
|
105
|
+
private pointer;
|
|
106
|
+
private toothMeshes;
|
|
107
|
+
private animationFrameId;
|
|
108
|
+
private theme;
|
|
109
|
+
private toothMaterialTemplate;
|
|
110
|
+
private mappedPoints;
|
|
111
|
+
private options;
|
|
112
|
+
private disposed;
|
|
113
|
+
constructor(canvas: HTMLCanvasElement, options?: OdontogramEngineOptions);
|
|
114
|
+
private lights;
|
|
115
|
+
private setupLights;
|
|
116
|
+
private applyCanvasBackground;
|
|
117
|
+
private resolveModelUrl;
|
|
118
|
+
/** Re-apply a (partial) theme to lights, background and every tooth currently in its default state. */
|
|
119
|
+
applyTheme(theme: OdontogramTheme): void;
|
|
120
|
+
private forEachToothGroup;
|
|
121
|
+
private setGroupColor;
|
|
122
|
+
loadModel(toothType: ToothType): Promise<void>;
|
|
123
|
+
private removeMarkers;
|
|
124
|
+
private addMarker;
|
|
125
|
+
private emitPointsChange;
|
|
126
|
+
/** Handle a click/tap on the canvas (expects coordinates relative to the viewport, like a MouseEvent). */
|
|
127
|
+
handlePointerDown(clientX: number, clientY: number): void;
|
|
128
|
+
getMappedPoints(): MappedPoint[];
|
|
129
|
+
/** Patch metadata (e.g. description) on an existing point without touching the 3D scene. */
|
|
130
|
+
updatePoint(id: number, patch: Partial<MappedPoint>): void;
|
|
131
|
+
resetMapping(): void;
|
|
132
|
+
/** Replace all diagnostic points and redraw markers/highlights to match (e.g. after importing JSON). */
|
|
133
|
+
setMappedPoints(points: MappedPoint[]): void;
|
|
134
|
+
resize(): void;
|
|
135
|
+
dispose(): void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
declare const TOOTH_MESH_NAME_MAP_CHILD: {
|
|
139
|
+
[key: string]: string;
|
|
140
|
+
};
|
|
141
|
+
declare const TOOTH_MESH_NAME_MAP_ADULT: {
|
|
142
|
+
[key: string]: string;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export { type ColorInput, DEFAULT_THEME, type MappedPoint, Odontogram3D, type Odontogram3DHandle, type Odontogram3DProps, OdontogramEngine, type OdontogramTheme, TOOTH_MESH_NAME_MAP_ADULT, TOOTH_MESH_NAME_MAP_CHILD, type ToothType, mergeTheme, toHexColor };
|