Spaces:
Running
on
Zero
Running
on
Zero
| // Reference the shared typedefs file | |
| /// <reference path="../types/typedefs.js" /> | |
| import { app } from '../../scripts/app.js' | |
| import { infoLogger } from './comfy_shared.js' | |
| function B0(t) { | |
| return (1 - t) ** 3 / 6 | |
| } | |
| function B1(t) { | |
| return (3 * t ** 3 - 6 * t ** 2 + 4) / 6 | |
| } | |
| function B2(t) { | |
| return (-3 * t ** 3 + 3 * t ** 2 + 3 * t + 1) / 6 | |
| } | |
| function B3(t) { | |
| return t ** 3 / 6 | |
| } | |
| class CurveWidget { | |
| constructor(...args) { | |
| const [inputName, opts] = args | |
| this.name = inputName || 'Curve' | |
| this.type = 'FLOAT_CURVE' | |
| this.selectedPointIndex = null | |
| this.options = opts | |
| this.value = this.value || { 0: { x: 0, y: 0 }, 1: { x: 1, y: 1 } } | |
| } | |
| drawBSpline(ctx, width, height, posY) { | |
| const n = this.value.length - 1 | |
| const numSegments = n - 2 | |
| const numPoints = this.value.length | |
| if (numPoints < 4) { | |
| this.drawLinear(ctx, width, height, posY) | |
| } else { | |
| for (let j = 0; j <= numSegments; j++) { | |
| for (let t = 0; t <= 1; t += 0.01) { | |
| let pt = this.getBSplinePoint(j, t) | |
| let x = pt.x * width | |
| let y = posY + height - pt.y * height | |
| if (t === 0) ctx.moveTo(x, y) | |
| else ctx.lineTo(x, y) | |
| } | |
| } | |
| ctx.stroke() | |
| } | |
| } | |
| drawLinear(ctx, width, height, posY) { | |
| for (let i = 0; i < Object.keys(this.value).length - 1; i++) { | |
| let p1 = this.value[i] | |
| let p2 = this.value[i + 1] | |
| ctx.moveTo(p1.x * width, posY + height - p1.y * height) | |
| ctx.lineTo(p2.x * width, posY + height - p2.y * height) | |
| } | |
| ctx.stroke() | |
| } | |
| getBSplinePoint(i, t) { | |
| // Control points for this segment | |
| const p0 = this.value[i] | |
| const p1 = this.value[i + 1] | |
| const p2 = this.value[i + 2] | |
| const p3 = this.value[i + 3] | |
| const x = B0(t) * p0.x + B1(t) * p1.x + B2(t) * p2.x + B3(t) * p3.x | |
| const y = B0(t) * p0.y + B1(t) * p1.y + B2(t) * p2.y + B3(t) * p3.y | |
| return { x, y } | |
| } | |
| /** | |
| * @param {OnDrawWidgetParams} args | |
| */ | |
| draw(...args) { | |
| const hide = this.type !== 'FLOAT_CURVE' | |
| if (hide) { | |
| return | |
| } | |
| const [ctx, node, width, posY, height] = args | |
| const [cw, ch] = this.computeSize(width) | |
| ctx.beginPath() | |
| ctx.fillStyle = '#000' | |
| ctx.strokeStyle = '#fff' | |
| ctx.lineWidth = 2 | |
| // normalized coordinates -> canvas coordinates | |
| for (let i = 0; i < Object.keys(this.value || {}).length - 1; i++) { | |
| let p1 = this.value[i] | |
| let p2 = this.value[i + 1] | |
| ctx.moveTo(p1.x * cw, posY + ch - p1.y * ch) | |
| ctx.lineTo(p2.x * cw, posY + ch - p2.y * ch) | |
| } | |
| ctx.stroke() | |
| // points | |
| Object.values(this.value || {}).forEach((point) => { | |
| ctx.beginPath() | |
| ctx.arc(point.x * cw, posY + ch - point.y * ch, 5, 0, 2 * Math.PI) | |
| ctx.fill() | |
| }) | |
| } | |
| mouse(event, pos, node) { | |
| let x = pos[0] - node.pos[0] | |
| let y = pos[1] - node.pos[1] | |
| const width = node.size[0] | |
| const height = 300 // TODO: compute | |
| const posY = node.pos[1] | |
| const localPos = { x: pos[0], y: pos[1] - LiteGraph.NODE_WIDGET_HEIGHT } | |
| if (event.type === LiteGraph.pointerevents_method + 'down') { | |
| console.debug('Checking if a point was clicked') | |
| const clickedPointIndex = this.detectPoint(localPos, width, height) | |
| if (clickedPointIndex !== null) { | |
| this.selectedPointIndex = clickedPointIndex | |
| } else { | |
| this.addPoint(localPos, width, height) | |
| } | |
| return true | |
| } else if ( | |
| event.type === LiteGraph.pointerevents_method + 'move' && | |
| this.selectedPointIndex !== null | |
| ) { | |
| this.movePoint(this.selectedPointIndex, localPos, width, height) | |
| return true | |
| } else if ( | |
| event.type === LiteGraph.pointerevents_method + 'up' && | |
| this.selectedPointIndex !== null | |
| ) { | |
| this.selectedPointIndex = null | |
| return true | |
| } | |
| return false | |
| } | |
| callback(...args) { | |
| //value, that, node, pos, event) { | |
| } | |
| detectPoint(localPos, width, height) { | |
| const threshold = 20 // TODO: extract | |
| const keys = Object.keys(this.value) | |
| for (let i = 0; i < keys.length; i++) { | |
| const key = keys[i] | |
| const p = this.value[key] | |
| const px = p.x * width | |
| const py = height - p.y * height | |
| if ( | |
| Math.abs(localPos.x - px) < threshold && | |
| Math.abs(localPos.y - py) < threshold | |
| ) { | |
| return key | |
| } | |
| } | |
| return null | |
| } | |
| addPoint(localPos, width, height) { | |
| // add a new point based on click position | |
| const normalizedPoint = { | |
| x: localPos.x / width, | |
| y: 1 - localPos.y / height, | |
| } | |
| const keys = Object.keys(this.value) | |
| let insertIndex = keys.length | |
| for (let i = 0; i < keys.length; i++) { | |
| if (normalizedPoint.x < this.value[keys[i]].x) { | |
| insertIndex = i | |
| break | |
| } | |
| } | |
| // shift | |
| for (let i = keys.length; i > insertIndex; i--) { | |
| this.value[i] = this.value[i - 1] | |
| } | |
| this.value[insertIndex] = normalizedPoint | |
| } | |
| movePoint(index, localPos, width, height) { | |
| const point = this.value[index] | |
| point.x = Math.max(0, Math.min(1, localPos.x / width)) | |
| point.y = Math.max(0, Math.min(1, 1 - localPos.y / height)) | |
| this.value[index] = point | |
| } | |
| computeSize(width) { | |
| return [width, 300] | |
| } | |
| configure(data) { | |
| } | |
| } | |
| app.registerExtension({ | |
| name: 'mtb.curves', | |
| getCustomWidgets: () => { | |
| return { | |
| /** | |
| * @param {LGraphNode} node | |
| * @param {str} inputName | |
| * @param {[str,*]} inputData | |
| * @param {*} app | |
| * | |
| */ | |
| FLOAT_CURVE: (node, inputName, inputData, app) => { | |
| // const c = node.widgets.find((w) => w.type === "FLOAT_CURVE") | |
| const wid = node.addCustomWidget(new CurveWidget(inputName, inputData)) | |
| return { | |
| widget: wid, | |
| minWidth: 150, | |
| minHeight: 30, | |
| } | |
| }, | |
| } | |
| }, | |
| }) | |