|
|
|
|
|
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) { |
|
|
|
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 } |
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
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() |
|
|
|
|
|
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 |
|
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) { |
|
|
|
|
|
} |
|
|
|
detectPoint(localPos, width, height) { |
|
const threshold = 20 |
|
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) { |
|
|
|
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 |
|
} |
|
} |
|
|
|
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 { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FLOAT_CURVE: (node, inputName, inputData, app) => { |
|
|
|
const wid = node.addCustomWidget(new CurveWidget(inputName, inputData)) |
|
|
|
return { |
|
widget: wid, |
|
minWidth: 150, |
|
minHeight: 30, |
|
} |
|
}, |
|
} |
|
}, |
|
}) |
|
|