import { Component, ComponentNature, Control, POSITION, sceneComponent } from '@hatiolab/things-scene' import MCSTransport from './mcs-transport' import { ConveyorMixin } from './features/conveyor-mixin' import { normalizeAngle } from './utils/normalize-angle' const NATURE: ComponentNature = { mutable: false, resizable: true, rotatable: true, properties: [ ...MCSTransport.properties, { type: 'angle', label: 'start-angle', name: 'startAngle' }, { type: 'angle', label: 'end-angle', name: 'endAngle' }, { type: 'number', label: 'ratio', name: 'ratio' }, { type: 'number', label: 'roll-width', name: 'rollWidth' } ] } var controlHandler = { ondragmove: function (point: POSITION, index: number, component: Component) { var { cx, cy, rx, ry, startAngle, endAngle } = component.model var { x, y } = component.transcoordP2S(point.x, point.y) const angle = (startAngle + endAngle) / 2 /* 원점으로부터 최대 거리 */ const dx = Math.abs(rx * Math.sin(angle)) const dy = Math.abs(ry * Math.cos(angle)) const distance = Math.sqrt(dx * dx + dy * dy) /* 원점으로부터 현재 컨트롤 위치까지의 거리 */ const px = Math.abs(cx - x) const py = Math.abs(cy - y) const pdistance = Math.sqrt(px * px + py * py) var ratio = (pdistance / distance) * 100 ratio = ratio >= 100 || ratio <= -100 ? 100 : Math.abs(ratio) component.set({ ratio }) } } var antiClockWiseControlHandler = { ondragmove: function (point: POSITION, index: number, component: Component) { var { cx, cy } = component.model var transcoorded = component.transcoordP2S(point.x, point.y) var theta = Math.atan2(-(transcoorded.y - cy), transcoorded.x - cx) if (theta > 0 && theta <= Math.PI / 2) theta = Math.PI / 2 if (theta < 0 && theta >= -Math.PI / 2) theta = -Math.PI / 2 const startAngle = (-theta + Math.PI / 2 - Math.PI * 2) % (Math.PI * 2) component.set({ startAngle }) } } var clockwiseControlHandler = { ondragmove: function (point: POSITION, index: number, component: Component) { var { cx, cy } = component.model var transcoorded = component.transcoordP2S(point.x, point.y) var theta = Math.atan2(-(transcoorded.y - cy), transcoorded.x - cx) if (theta > 0) if (theta >= Math.PI / 2) theta = Math.PI / 2 if (theta < 0) if (theta <= -Math.PI / 2) theta = -Math.PI / 2 var endAngle = -theta + Math.PI / 2 component.set({ endAngle }) } } @sceneComponent('ConveyorJoin') export default class ConveyorJoin extends ConveyorMixin(MCSTransport) { get nature() { return NATURE } getLegendFallback() { return (this.root as any).conveyorLegendTheme || super.getLegendFallback() } containable(component: Component) { return ['Shuttle', 'Port'].includes(component.state.type) } contains(x: number, y: number) { var { cx, cy, rx, ry, ratio, startAngle, endAngle } = this.state rx = Math.abs(rx) ry = Math.abs(ry) const normx = (x - cx) / (rx * 2 - 0.5) const normy = (y - cy) / (ry * 2 - 0.5) const ratiox = (x - cx) / ((rx / 100) * ratio * 2 - 0.5) const ratioy = (y - cy) / ((ry / 100) * ratio * 2 - 0.5) if (normx * normx + normy * normy < 0.25 && ratiox * ratiox + ratioy * ratioy > 0.25) { const angle = normalizeAngle(Math.atan2(-normy, normx) - Math.PI / 2) if (startAngle <= endAngle) { if (angle >= startAngle && angle <= endAngle) return true } else { // wraparound: startAngle > endAngle (예: π → 0) if (angle >= startAngle || angle <= endAngle) return true } } return false } render(context: CanvasRenderingContext2D) { var { ratio = 50, cx, cy, rx, ry, startAngle = 0, endAngle = Math.PI / 2 } = this.state context.beginPath() startAngle -= Math.PI / 2 endAngle -= Math.PI / 2 /* outer ellipse and inner ellipse */ context.ellipse(cx, cy, Math.abs(rx), Math.abs(ry), 0, startAngle, endAngle) context.ellipse(cx, cy, Math.abs((rx / 100) * ratio), Math.abs((ry / 100) * ratio), 0, endAngle, startAngle, true) context.lineTo(rx * Math.cos(startAngle) + cx, ry * Math.sin(startAngle) + cy) this.drawFill(context) this.drawStroke(context) } get path() { var { cx, cy, rx, ry } = this.state return [ { x: cx - rx, y: cy - ry }, { x: cx + rx, y: cy - ry }, { x: cx + rx, y: cy + ry }, { x: cx - rx, y: cy + ry } ] } set path(path) { var left_top = path[0] var right_bottom = path[2] this.set({ cx: left_top.x + (right_bottom.x - left_top.x) / 2, cy: left_top.y + (right_bottom.y - left_top.y) / 2, rx: (right_bottom.x - left_top.x) / 2, ry: (right_bottom.y - left_top.y) / 2 }) } get controls(): Control[] { var { cx, cy, rx, ry, ratio, startAngle, endAngle } = this.state var controls = [] as Control[] controls.push({ x: cx + ((rx + (rx * ratio) / 100) / 2) * Math.sin(startAngle), y: cy - ((ry + (ry * ratio) / 100) / 2) * Math.cos(startAngle), handler: antiClockWiseControlHandler }) controls.push({ x: cx + ((rx + (rx * ratio) / 100) / 2) * Math.sin(endAngle), y: cy - ((ry + (ry * ratio) / 100) / 2) * Math.cos(endAngle), handler: clockwiseControlHandler }) const angle = (startAngle + endAngle) / 2 controls.push({ x: cx + ((rx * ratio) / 100) * Math.sin(angle), y: cy - ((ry * ratio) / 100) * Math.cos(angle), handler: controlHandler }) return controls } } Component.memoize(ConveyorJoin.prototype, 'controls', false)