import { Point } from '@flatten-js/core'; import { toast } from 'react-toastify'; import { CURSOR_SIZE, GRIP_COLOR, GRIP_SIZE, GUIDE_LINE_COLOR, GUIDE_LINE_STYLE, GUIDE_LINE_WIDTH, SNAP_POINT_COLOR, SNAP_POINT_SIZE, } from '../App.consts'; import { type SnapPoint, SnapPointType } from '../App.types'; /** 기준점 십자 반길이(px) — 가로세로 20px. */ const ORIGIN_MARKER_SIZE = 10; import type { DrawController } from '../drawControllers/DrawController'; import type { ScreenCanvasDrawController } from '../drawControllers/screenCanvas.drawController'; import type { Entity } from '../entities/Entity'; import { getLayerById, isEntityHighlighted, isEntitySelected } from '../state'; import { getGrips } from './grips'; import { isEntityHidden } from './visibility'; export function drawEntities(drawController: DrawController, entities: Entity[]) { for (const entity of entities) { const layer = getLayerById(entity.layerId); if (!layer) { toast.error(`Failed to find layer for entity: ${entity?.id}`); console.error('Failed to find layer for entity: ', entity); continue; } if (!layer?.isVisible || layer.isFrozen) { continue; // 꺼졌거나 동결된 도면층은 그리지 않는다 } if (isEntityHidden(entity)) { continue; // ISOLATEOBJECTS·HIDEOBJECTS로 숨긴 객체 } drawController.setOpacity(entity.opacity ?? layer.opacity ?? 1); drawController.setLineStyles( isEntityHighlighted(entity), isEntitySelected(entity), entity.lineColor, entity.lineWidth, [] ); entity.draw(drawController); drawController.setOpacity(1); } } export function drawDebugEntities(drawController: DrawController, debugEntities: Entity[]) { for (const debugEntity of debugEntities) { drawController.setLineStyles( isEntityHighlighted(debugEntity), isEntitySelected(debugEntity), '#FF5500', 1, [] ); debugEntity.draw(drawController); } } /** * Draw the point to which the mouse will snap when the user clicks to draw the next point * @param drawController * @param snapPointInfo * @param isMarked indicates that the point has been hovered lang enough to draw guides from this point */ export function drawSnapPoint( drawController: ScreenCanvasDrawController, snapPointInfo: SnapPoint | null, isMarked: boolean ) { if (!snapPointInfo) return; const snapPoint = snapPointInfo.point; const screenSnapPoint = drawController.worldToTarget(snapPoint); drawController.setLineStyles(false, false, SNAP_POINT_COLOR, 1, []); if (isMarked) { // We will draw a plus sign inside the current snap point to indicate that it is marked drawController.drawLineScreen( new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y), new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y) ); drawController.drawLineScreen( new Point(screenSnapPoint.x, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); } switch (snapPointInfo.type) { case SnapPointType.LineEndPoint: // Endpoint is marked with a square // top drawController.drawLineScreen( new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2) ); // right drawController.drawLineScreen( new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); // bottom drawController.drawLineScreen( new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); // left drawController.drawLineScreen( new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); break; case SnapPointType.LineMidPoint: // Midpoint is shown with a triangle drawController.drawLineScreen( new Point(screenSnapPoint.x, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); drawController.drawLineScreen( new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); drawController.drawLineScreen( new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x, screenSnapPoint.y - SNAP_POINT_SIZE / 2) ); break; case SnapPointType.AngleGuide: // Angle guide is shown with an hourglass drawController.drawLineScreen( new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2) ); drawController.drawLineScreen( new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); drawController.drawLineScreen( new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); drawController.drawLineScreen( new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2) ); break; case SnapPointType.Intersection: // Intersection is shown with a cross drawController.drawLineScreen( new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); drawController.drawLineScreen( new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); break; case SnapPointType.CircleCenter: // Circle center is shown with a circle drawController.drawArcScreen(screenSnapPoint, SNAP_POINT_SIZE / 2, 0, 2 * Math.PI, true); break; case SnapPointType.CircleCardinal: // Circle cardinal is shown with a diamond drawController.drawLineScreen( new Point(screenSnapPoint.x, screenSnapPoint.y - SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y) ); drawController.drawLineScreen( new Point(screenSnapPoint.x - SNAP_POINT_SIZE / 2, screenSnapPoint.y), new Point(screenSnapPoint.x, screenSnapPoint.y + SNAP_POINT_SIZE / 2) ); drawController.drawLineScreen( new Point(screenSnapPoint.x, screenSnapPoint.y + SNAP_POINT_SIZE / 2), new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y) ); drawController.drawLineScreen( new Point(screenSnapPoint.x + SNAP_POINT_SIZE / 2, screenSnapPoint.y), new Point(screenSnapPoint.x, screenSnapPoint.y - SNAP_POINT_SIZE / 2) ); break; } } export function drawHelpers(drawController: DrawController, helperEntities: Entity[]) { for (const entity of helperEntities) { drawController.setLineStyles( isEntityHighlighted(entity), isEntitySelected(entity), GUIDE_LINE_COLOR, GUIDE_LINE_WIDTH, GUIDE_LINE_STYLE ); entity.draw(drawController); } } export function drawCursor(drawController: ScreenCanvasDrawController) { drawController.setLineStyles(false, false, '#FFF', 1, []); const screenMouseLocation = drawController.getScreenMouseLocation(); drawController.drawLineScreen( new Point(screenMouseLocation.x, screenMouseLocation.y - CURSOR_SIZE), new Point(screenMouseLocation.x, screenMouseLocation.y + CURSOR_SIZE) ); drawController.drawLineScreen( new Point(screenMouseLocation.x - CURSOR_SIZE, screenMouseLocation.y), new Point(screenMouseLocation.x + CURSOR_SIZE, screenMouseLocation.y) ); } /** 좌표 0,0 기준점 — 화면 크기 고정 십자 + 작은 원. 도면 개체가 아니라 그리기 겹판(저장 · 선택 · 스냅 대상 아님). */ export function drawOriginMarker(drawController: ScreenCanvasDrawController) { const origin = drawController.worldToTarget(new Point(0, 0)); drawController.setOpacity(0.6); drawController.setLineStyles(false, false, '#ffffff', 1, []); // 자동색 — 바탕 따라 흰/검정 drawController.drawLineScreen( new Point(origin.x - ORIGIN_MARKER_SIZE, origin.y), new Point(origin.x + ORIGIN_MARKER_SIZE, origin.y) ); drawController.drawLineScreen( new Point(origin.x, origin.y - ORIGIN_MARKER_SIZE), new Point(origin.x, origin.y + ORIGIN_MARKER_SIZE) ); drawController.drawArcScreen(origin, ORIGIN_MARKER_SIZE / 3, 0, 2 * Math.PI, true); drawController.setOpacity(1); } /** 선택한 객체의 그립(편집점)을 화면 크기 고정 사각형으로 그린다 */ export function drawGrips(drawController: ScreenCanvasDrawController, entities: Entity[]) { for (const entity of entities) { for (const grip of getGrips(entity)) { const screenPoint = drawController.worldToTarget(grip.point); drawController.fillRectScreen( screenPoint.x - GRIP_SIZE / 2, screenPoint.y - GRIP_SIZE / 2, GRIP_SIZE, GRIP_SIZE, GRIP_COLOR ); } } }