6각 계산 수정

This commit is contained in:
hyojun.choi 2024-07-19 14:20:37 +09:00
parent 40d44c20e8
commit ae096b2f00
5 changed files with 275 additions and 95 deletions

View File

@ -169,7 +169,7 @@ export default function Roof2() {
{ x: 1088, y: 42 }, { x: 1088, y: 42 },
] ]
if (canvas) { if (canvas) {
const polygon = new QPolygon(type1, { const polygon = new QPolygon(type4, {
fill: 'transparent', fill: 'transparent',
stroke: 'black', stroke: 'black',
strokeWidth: 1, strokeWidth: 1,

View File

@ -13,6 +13,9 @@ export class QLine extends fabric.Group {
idx idx
type = 'QLine' type = 'QLine'
parent parent
isAlreadyInterSection = false
interSectionPoints = []
#lengthTxt = 0 #lengthTxt = 0
constructor(points, option = { isActiveLengthText: true }, lengthTxt) { constructor(points, option = { isActiveLengthText: true }, lengthTxt) {

View File

@ -1,6 +1,7 @@
import { fabric } from 'fabric' import { fabric } from 'fabric'
import { import {
calculateIntersection, calculateIntersection,
calculateIntersection2,
distanceBetweenPoints, distanceBetweenPoints,
findClosestLineToPoint, findClosestLineToPoint,
findTopTwoIndexesByDistance, findTopTwoIndexesByDistance,
@ -317,13 +318,13 @@ export default class QPolygon extends fabric.Group {
if (this.lines.length === 4) { if (this.lines.length === 4) {
this.#drawHelpLineInRect(chon) this.#drawHelpLineInRect(chon)
} else if (this.lines.length === 6) { } else if (this.lines.length === 6 || this.lines.length === 8) {
// TODO : 6각형 // TODO : 6각형
this.#drawHelpLineInHexagon(chon) this.#drawHelpLineInHexagon2(chon)
} else if (this.lines.length === 8) { } /* else if (this.lines.length === 8) {
// TODO : 8각형 // TODO : 8각형
this.#drawHelpLineInOctagon(chon) this.#drawHelpLineInOctagon(chon)
} }*/
} }
/** /**
@ -525,13 +526,97 @@ export default class QPolygon extends fabric.Group {
this.canvas.add(ridge) this.canvas.add(ridge)
} }
} }
#drawHelpLineInHexagon2(chon) {
const oneSideLines = [...this.lines].map((line) => {
let newX1, newY1, newX2, newY2
if (line.direction === 'top') {
newX1 = line.x2
newY1 = line.y2
newX2 = line.x1
newY2 = line.y1
line.x1 = newX1
line.y1 = newY1
line.x2 = newX2
line.y2 = newY2
line.direction = 'bottom'
} else if (line.direction === 'left') {
newX1 = line.x2
newY1 = line.y2
newX2 = line.x1
newY2 = line.y1
line.x1 = newX1
line.y1 = newY1
line.x2 = newX2
line.y2 = newY2
line.direction = 'right'
}
return line
})
const centerLines = []
const helpLines = []
const ridgeStartPoints = []
const horizontalLines = oneSideLines.filter((line) => line.direction === 'right')
const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom')
const horizontalMaxLength = horizontalLines.reduce((max, obj) => Math.max(max, obj.length), 0)
const verticalMaxLength = verticalLines.reduce((max, obj) => Math.max(max, obj.length), 0)
// 모든 가로선의 중심선을 긋는다.
horizontalLines.forEach((line, index) => {
const nextLine = horizontalLines[(index + 1) % horizontalLines.length]
const startCenterX = Math.max(line.x1, nextLine.x1)
const startCenterY = (line.y1 + nextLine.y1) / 2
let endCenterX = line.length >= nextLine.length ? startCenterX + line.length : startCenterX + nextLine.length
const endCenterY = startCenterY
if (endCenterX > Math.max(line.x2, nextLine.x2)) {
endCenterX = Math.max(line.x2, nextLine.x2)
}
const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], {
fontSize: this.fontSize,
stroke: 'red',
strokeWidth: 1,
direction: 'horizontal',
})
// this.addWithUpdate(centerLine)
centerLines.push(centerLine)
})
// 모든 세로선의 중심선을 긋는다.
verticalLines.forEach((line, index) => {
const nextLine = verticalLines[(index + 1) % verticalLines.length]
const startCenterX = (line.x1 + nextLine.x1) / 2
const startCenterY = Math.min(line.y1, nextLine.y1)
const endCenterX = startCenterX
let endCenterY = line.length >= nextLine.length ? startCenterY + line.length : startCenterY + nextLine.length
if (endCenterY > Math.max(line.y2, nextLine.y2)) {
endCenterY = Math.max(line.y2, nextLine.y2)
}
const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], {
fontSize: this.fontSize,
stroke: 'blue',
strokeWidth: 1,
direction: 'vertical',
})
// this.addWithUpdate(centerLine)
centerLines.push(centerLine)
})
const maxLength = horizontalMaxLength < verticalMaxLength ? horizontalMaxLength : verticalMaxLength
#drawHelpLineInHexagon(chon) {
const historyLines = []
const helpPoints = []
const notInterSectionLines = []
const ridge = []
const maxLength = this.lines.reduce((max, obj) => Math.min(max, obj.length), 999999)
this.points.forEach((point, index) => { this.points.forEach((point, index) => {
const wallPoint = this.wall.points[index] const wallPoint = this.wall.points[index]
// 두 점의 좌표 // 두 점의 좌표
@ -545,8 +630,139 @@ export default class QPolygon extends fabric.Group {
// x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성 // x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성
const angle = Math.atan2(y2 - y1, x2 - x1) const angle = Math.atan2(y2 - y1, x2 - x1)
newX2 = x1 + (maxLength + 50) * Math.cos(angle) newX2 = Math.floor(x1 + (maxLength / 2 + 50) * Math.cos(angle))
newY2 = y1 + (maxLength + 50) * Math.sin(angle) newY2 = Math.floor(y1 + (maxLength / 2 + 50) * Math.sin(angle))
const line = new QLine([x1, y1, newX2, newY2], {
fontSize: this.fontSize,
stroke: 'green',
idx: index,
})
this.addWithUpdate(line)
helpLines.push(line)
this.canvas.renderAll()
})
helpLines.forEach((line, index) => {
if (line.isAlreadyInterSection) {
return
}
const nextLine = helpLines[(index + 1 + helpLines.length) % helpLines.length]
this.canvas.renderAll()
let intersectionPoint = calculateIntersection(line, nextLine)
if (!intersectionPoint) {
return
}
line.set({ isAlreadyInterSection: true })
nextLine.set({ isAlreadyInterSection: true })
const helpLine1 = new QLine([nextLine.x1, nextLine.y1, intersectionPoint.x, intersectionPoint.y], {
fontSize: this.fontSize,
stroke: 'skyblue',
})
const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], {
fontSize: this.fontSize,
stroke: 'skyblue',
})
ridgeStartPoints.push(intersectionPoint)
this.addWithUpdate(helpLine1)
this.addWithUpdate(helpLine2)
this.removeWithUpdate(nextLine)
this.removeWithUpdate(line)
this.canvas.renderAll()
})
// 안만나는 선들
const notInterSectionLines = helpLines.filter((line) => !line.isAlreadyInterSection)
const ridgeEndPoints = []
const interSectionPoints = []
notInterSectionLines.forEach((line, index) => {
line.line.set({ strokeWidth: (index + 1) * 5 })
centerLines.forEach((centerLine) => {
const interSectionPoint = calculateIntersection2(line, centerLine)
if (!this.inPolygon(interSectionPoint) || !interSectionPoint) {
return
}
line.interSectionPoints.push(interSectionPoint)
interSectionPoints.push(interSectionPoint)
})
})
ridgeStartPoints.forEach((point, index) => {
let arrivalPoint
let distance = Infinity
let startPoint
interSectionPoints.forEach((interSectionPoint) => {
if (Math.abs(point.x - interSectionPoint.x) < 3 || Math.abs(point.y - interSectionPoint.y) < 3) {
if (distanceBetweenPoints(point, interSectionPoint) < distance) {
startPoint = point
distance = distanceBetweenPoints(point, interSectionPoint)
arrivalPoint = interSectionPoint
}
}
})
if (arrivalPoint) {
const line = notInterSectionLines.filter((line) => line.interSectionPoints.includes(arrivalPoint))[0]
const ridge = new QLine([startPoint.x, startPoint.y, arrivalPoint.x, arrivalPoint.y], {
stroke: 'black',
fontSize: this.fontSize,
})
const helpLine = new QLine([line.x1, line.y1, arrivalPoint.x, arrivalPoint.y], {
stroke: 'red',
fontSize: this.fontSize,
})
ridgeEndPoints.push(arrivalPoint)
this.addWithUpdate(ridge)
this.addWithUpdate(helpLine)
this.removeWithUpdate(line)
this.canvas.renderAll()
}
})
ridgeEndPoints.forEach((point, index) => {
const currentRidgeEndPoint = ridgeEndPoints[index]
const nextRidgeEndPoint = ridgeEndPoints[(index + 1) % ridgeEndPoints.length]
const ridgeConnectLine = new QLine([currentRidgeEndPoint.x, currentRidgeEndPoint.y, nextRidgeEndPoint.x, nextRidgeEndPoint.y], {
fontSize: this.fontSize,
stroke: 'green',
})
this.addWithUpdate(ridgeConnectLine)
this.canvas.renderAll()
})
}
#drawHelpLineInHexagon(chon) {
const historyLines = []
const helpPoints = []
const notInterSectionLines = []
const ridge = []
const maxLength = this.lines.reduce((max, obj) => Math.max(max, obj.length), 0)
this.points.forEach((point, index) => {
const wallPoint = this.wall.points[index]
// 두 점의 좌표
const x1 = point.x
const y1 = point.y
const x2 = wallPoint.x
const y2 = wallPoint.y
let newX2, newY2
// x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성
const angle = Math.atan2(y2 - y1, x2 - x1)
newX2 = x1 + (maxLength / 2) * Math.cos(angle)
newY2 = y1 + (maxLength / 2) * Math.sin(angle)
const line = new QLine([x1, y1, newX2, newY2], { const line = new QLine([x1, y1, newX2, newY2], {
fontSize: this.fontSize, fontSize: this.fontSize,

View File

@ -13,8 +13,8 @@ export const fontSizeState = atom({
export const canvasSizeState = atom({ export const canvasSizeState = atom({
key: 'canvasSize', key: 'canvasSize',
default: { default: {
vertical: 1000, vertical: 1500,
horizontal: 1000, horizontal: 1500,
}, },
}) })

View File

@ -1,3 +1,5 @@
import { atan2, chain, derivative, e, evaluate, log, pi, pow, round, sqrt, intersect } from 'mathjs'
/** /**
* Collection of function to use on canvas * Collection of function to use on canvas
*/ */
@ -9,18 +11,14 @@ export function polygonPositionHandler(dim, finalMatrix, fabricObject) {
let y = fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y let y = fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y
return fabric.util.transformPoint( return fabric.util.transformPoint(
{ x, y }, { x, y },
fabric.util.multiplyTransformMatrices( fabric.util.multiplyTransformMatrices(fabricObject.canvas.viewportTransform, fabricObject.calcTransformMatrix()),
fabricObject.canvas.viewportTransform,
fabricObject.calcTransformMatrix(),
),
) )
} }
function getObjectSizeWithStroke(object) { function getObjectSizeWithStroke(object) {
let stroke = new fabric.Point( let stroke = new fabric.Point(object.strokeUniform ? 1 / object.scaleX : 1, object.strokeUniform ? 1 / object.scaleY : 1).multiply(
object.strokeUniform ? 1 / object.scaleX : 1, object.strokeWidth,
object.strokeUniform ? 1 / object.scaleY : 1, )
).multiply(object.strokeWidth)
return new fabric.Point(object.width + stroke.x, object.height + stroke.y) return new fabric.Point(object.width + stroke.x, object.height + stroke.y)
} }
@ -28,20 +26,12 @@ function getObjectSizeWithStroke(object) {
export function actionHandler(eventData, transform, x, y) { export function actionHandler(eventData, transform, x, y) {
let polygon = transform.target, let polygon = transform.target,
currentControl = polygon.controls[polygon.__corner], currentControl = polygon.controls[polygon.__corner],
mouseLocalPosition = polygon.toLocalPoint( mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center'),
new fabric.Point(x, y),
'center',
'center',
),
polygonBaseSize = getObjectSizeWithStroke(polygon), polygonBaseSize = getObjectSizeWithStroke(polygon),
size = polygon._getTransformedDimensions(0, 0) size = polygon._getTransformedDimensions(0, 0)
polygon.points[currentControl.pointIndex] = { polygon.points[currentControl.pointIndex] = {
x: x: (mouseLocalPosition.x * polygonBaseSize.x) / size.x + polygon.pathOffset.x,
(mouseLocalPosition.x * polygonBaseSize.x) / size.x + y: (mouseLocalPosition.y * polygonBaseSize.y) / size.y + polygon.pathOffset.y,
polygon.pathOffset.x,
y:
(mouseLocalPosition.y * polygonBaseSize.y) / size.y +
polygon.pathOffset.y,
} }
return true return true
} }
@ -50,8 +40,7 @@ export function actionHandler(eventData, transform, x, y) {
export function anchorWrapper(anchorIndex, fn) { export function anchorWrapper(anchorIndex, fn) {
return function (eventData, transform, x, y) { return function (eventData, transform, x, y) {
let fabricObject = transform.target let fabricObject = transform.target
let originX = let originX = fabricObject?.points[anchorIndex].x - fabricObject.pathOffset.x
fabricObject?.points[anchorIndex].x - fabricObject.pathOffset.x
let originY = fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y let originY = fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y
let absolutePoint = fabric.util.transformPoint( let absolutePoint = fabric.util.transformPoint(
{ {
@ -63,12 +52,8 @@ export function anchorWrapper(anchorIndex, fn) {
let actionPerformed = fn(eventData, transform, x, y) let actionPerformed = fn(eventData, transform, x, y)
let newDim = fabricObject._setPositionDimensions({}) let newDim = fabricObject._setPositionDimensions({})
let polygonBaseSize = getObjectSizeWithStroke(fabricObject) let polygonBaseSize = getObjectSizeWithStroke(fabricObject)
let newX = let newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x
(fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / let newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y
polygonBaseSize.x
let newY =
(fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) /
polygonBaseSize.y
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5) fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5)
return actionPerformed return actionPerformed
} }
@ -104,9 +89,7 @@ export function addDistanceTextToPolygon(polygon) {
for (let i = 0; i < points.length; i++) { for (let i = 0; i < points.length; i++) {
const start = points[i] const start = points[i]
const end = points[(i + 1) % points.length] // 다음 점 (마지막 점의 경우 첫번째 점으로) const end = points[(i + 1) % points.length] // 다음 점 (마지막 점의 경우 첫번째 점으로)
const distance = Math.sqrt( const distance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)) // 두 점 사이의 거리 계산
Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2),
) // 두 점 사이의 거리 계산
const text = new fabric.Textbox(distance.toFixed(2), { const text = new fabric.Textbox(distance.toFixed(2), {
// 소수 둘째자리까지 표시 // 소수 둘째자리까지 표시
@ -160,10 +143,7 @@ export const getStartIndex = (lines) => {
let smallestY1 = lines[0].y1 let smallestY1 = lines[0].y1
for (let i = 1; i < lines.length; i++) { for (let i = 1; i < lines.length; i++) {
if ( if (lines[i].x1 < smallestX1 || (lines[i].x1 === smallestX1 && lines[i].y1 < smallestY1)) {
lines[i].x1 < smallestX1 ||
(lines[i].x1 === smallestX1 && lines[i].y1 < smallestY1)
) {
smallestIndex = i smallestIndex = i
smallestX1 = lines[i].x1 smallestX1 = lines[i].x1
smallestY1 = lines[i].y1 smallestY1 = lines[i].y1
@ -184,10 +164,7 @@ export const getStartIndexPoint = (points) => {
let smallestY1 = points[0].y let smallestY1 = points[0].y
for (let i = 1; i < points.length; i++) { for (let i = 1; i < points.length; i++) {
if ( if (points[i].x < smallestX1 || (points[i].x === smallestX1 && points[i].y < smallestY1)) {
points[i].x < smallestX1 ||
(points[i].x === smallestX1 && points[i].y < smallestY1)
) {
smallestIndex = i smallestIndex = i
smallestX1 = points[i].x smallestX1 = points[i].x
smallestY1 = points[i].y smallestY1 = points[i].y
@ -316,50 +293,40 @@ export const getDirectionByPoint = (a, b) => {
} }
/** /**
* line을 두개를 이용해서 교차점을 찾는 함수 * 선분의 교차점을 찾는 함수입니다. 함수는 선분이 실제로 교차하는 지점 내에서 교차하는지 확인합니다.
* @param line1 * @param {Object} line1 번째 선분, {x1, y1, x2, y2} 형태의 객체
* @param line2 * @param {Object} line2 번째 선분, {x1, y1, x2, y2} 형태의 객체
* @returns {{x: number, y: number}|null} * @returns {{x: number, y: number}|null} 교차점의 좌표를 반환하거나, 교차점이 없으면 null을 반환합니다.
*/ */
export function calculateIntersection(line1, line2) { export function calculateIntersection(line1, line2) {
const x1 = line1.x1, const { x1: x1_1, y1: y1_1, x2: x2_1, y2: y2_1 } = line1
y1 = line1.y1, const { x1: x1_2, y1: y1_2, x2: x2_2, y2: y2_2 } = line2
x2 = line1.x2,
y2 = line1.y2
const x3 = line2.x1,
y3 = line2.y1,
x4 = line2.x2,
y4 = line2.y2
const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) const denominator = (x1_1 - x2_1) * (y1_2 - y2_2) - (y1_1 - y2_1) * (x1_2 - x2_2)
if (denom === 0) return null // 선분이 평행하거나 일치 if (denominator === 0) return null // 선분이 평행하거나 일치하는 경우
const intersectX = const t = ((x1_1 - x1_2) * (y1_2 - y2_2) - (y1_1 - y1_2) * (x1_2 - x2_2)) / denominator
((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom const u = -((x1_1 - x2_1) * (y1_1 - y1_2) - (y1_1 - y2_1) * (x1_1 - x1_2)) / denominator
const intersectY =
((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom
// 교차점이 두 선분의 x 좌표 범위 내에 있는지 확인 // t와 u가 모두 0과 1 사이에 있을 때, 선분 내에서 교차
if ( if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
intersectX < Math.min(x1, x2) || const intersectionX = x1_1 + t * (x2_1 - x1_1)
intersectX > Math.max(x1, x2) || const intersectionY = y1_1 + t * (y2_1 - y1_1)
intersectX < Math.min(x3, x4) || return { x: Math.round(intersectionX), y: Math.round(intersectionY) }
intersectX > Math.max(x3, x4)
) {
return null // 교차점이 선분 범위 밖에 있음
} }
return { x: intersectX, y: intersectY } return null // 교차점이 선분의 범위 내에 없음
}
export const calculateIntersection2 = (line1, line2) => {
const result = intersect([line1.x1, line1.y1], [line1.x2, line1.y2], [line2.x1, line2.y1], [line2.x2, line2.y2])
return { x: Math.round(result[0]), y: Math.round(result[1]) }
} }
function findOrthogonalPoint(x1, y1, x2, y2, x3, y3, x4, y4) { function findOrthogonalPoint(x1, y1, x2, y2, x3, y3, x4, y4) {
// Calculate the intersection point of two lines // Calculate the intersection point of two lines
const intersectionX = const intersectionX = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4))
((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / const intersectionY = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4))
((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4))
const intersectionY =
((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) /
((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4))
return { x: intersectionX, y: intersectionY } return { x: intersectionX, y: intersectionY }
} }
@ -399,9 +366,7 @@ export const sortedPoints = (points) => {
// y값이 같은 point가 많은 경우 그 중 x값이 가장 큰걸 찾는다. // y값이 같은 point가 많은 경우 그 중 x값이 가장 큰걸 찾는다.
const temp = copyPoints.filter((point) => point.y === currentPoint.y) const temp = copyPoints.filter((point) => point.y === currentPoint.y)
// temp중 x값이 가장 큰 값 // temp중 x값이 가장 큰 값
const max = temp.reduce((prev, current) => const max = temp.reduce((prev, current) => (prev.x >= current.x ? prev : current))
prev.x >= current.x ? prev : current,
)
resultPoints.push(max) resultPoints.push(max)
currentPoint = max currentPoint = max
copyPoints.splice(copyPoints.indexOf(max), 1) copyPoints.splice(copyPoints.indexOf(max), 1)
@ -414,9 +379,7 @@ export const sortedPoints = (points) => {
// x값이 같은 point가 많은 경우 그 중 y값이 가장 큰걸 찾는다. // x값이 같은 point가 많은 경우 그 중 y값이 가장 큰걸 찾는다.
const temp = copyPoints.filter((point) => point.x === currentPoint.x) const temp = copyPoints.filter((point) => point.x === currentPoint.x)
// temp중 y값이 가장 큰 값 // temp중 y값이 가장 큰 값
const max = temp.reduce((prev, current) => const max = temp.reduce((prev, current) => (prev.y >= current.y ? prev : current))
prev.y >= current.y ? prev : current,
)
resultPoints.push(max) resultPoints.push(max)
currentPoint = max currentPoint = max
@ -481,9 +444,7 @@ export function calculateDistance(point, line) {
const x0 = point.x const x0 = point.x
const y0 = point.y const y0 = point.y
const numerator = Math.abs( const numerator = Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1)
(y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1,
)
const denominator = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)) const denominator = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2))
return numerator / denominator return numerator / denominator
} }