bezier-spline-b.js

total 0
used 0
limit 0
/* --- title: Bezier files: ../point_src/math.js head point pointlist dragging mouse stage dragging stroke ../point_src/keyboard.js ../point_src/split.js ../point_src/curve-extras.js ../point_src/pointlock.js ../point_src/widgets.js ../point_src/random.js In this example the dirty flag is used for curve tips. */ class BezierPoint extends Point { get handles() { if(this._handles) { return this._handles } /* Install the point, ensuring it has handles. */ let handles = this.createHandles(this) this._handles = handles return handles } createHandles(p) { let pair = p.split(2, 0, Math.PI * .5) pair[0].recordedDistance = pair[0].distance2D(p) pair[0].relRads = degToRad(calculateAngle360(p, pair[0])) pair[1].recordedDistance = pair[1].distance2D(p) pair[1].relRads = degToRad(calculateAngle360(p, pair[1])) pair[0]._other = pair[1] pair[1]._other = pair[0] return pair; } } class BezierSpline extends PointList { /* A _spline_ is many line segments presented as a single continuious line. In this example the Spline has special properties. Each point has a handles - applied when the point is pushed. perform `push` then `update` for an append. Else 'add' */ showHandles = true showPoints = true // showMinimal = true mirrorHandles = true add(point) { this.push(new BezierPoint(point)) } render(ctx, extras = {}) { this.checkUpdates(extras) let showHandles = (extras.showHandles == undefined) ? this.showHandles: extras.showHandles let showPoints = (extras.showPoints == undefined) ? this.showPoints: extras.showPoints if (showHandles) { this.renderHandlebars(ctx) this.renderHandles(ctx) } this.renderCurve(ctx, extras) if (showPoints) { this.pen.fill(ctx, '#4499DD', 5) // this.pen.indicator(ctx) } } renderHandles(ctx) { this.forEach((p, i, a) => { let hConf = { radius: 4, width: 1 }; if (i == 0) { p.handles[0].pen.fill(ctx, '#444', 4) } else if (i == a.length - 1) { p.handles[1].pen.fill(ctx, '#444', 4) } else { p.handles.pen.fill(ctx, '#444', 4) } }) } renderHandlebars(ctx) { let color = '#666' this.forEach((p, i, a) => { if (i == 0) { p.handles[0].pen.line(ctx, p, color) } else if (i == a.length - 1) { p.handles[1].pen.line(ctx, p, color) } else { p.handles[0].pen.line(ctx, p, color) p.handles[1].pen.line(ctx, p, color) // p.handles.pen.line(ctx, p, {width: 1, color: 'green'}) } }) } renderCurve(ctx, conf={}) { let a = this[0] ctx.moveTo(a.x, a.y) let closeLoop = 0 this.forEach((p, i) => { let n = this[i + 1] if (n == undefined) { if (closeLoop) { n = this[0] } else { return } } ctx.bezierCurveTo( p.handles[0].x, p.handles[0].y, n.handles[1].x, n.handles[1].y, n.x, n.y ) }) ctx.strokeStyle = conf.color || 'purple' ctx.lineWidth = 3 ctx.stroke() } checkUpdates(options) { let mirror = this.mirrorHandles if (options.altMode !== undefined) { mirror = !options.altMode } this.forEach((p, i) => { if (p.wasDirty) { this.computeHandles(i, p) } p.handles.forEach(h => { if (h.wasDirty) { // store let other = h._other // this.constrainHandle(p, other) h.recordedDistance = h.distance2D(p) h.relRads = degToRad(calculateAngle360(p, h)) if (mirror) { this.mirrorHandle(p, h, h.recordedDistance.distance) } } }) }) } mirrorHandle(owner, handle, distance, scaleDistance=false) { let antipose = handle._other let radians = owner.radians + handle.relRads let antiposeDistance = distance if (scaleDistance) { let originalDistance = handle.recordedDistance.distance let ratio = originalDistance == 0 ? 0 : Math.abs(distance) / originalDistance antiposeDistance = antipose.recordedDistance.distance * ratio } antipose.x = owner.x - Math.cos(radians) * antiposeDistance antipose.y = owner.y - Math.sin(radians) * antiposeDistance antipose.recordedDistance = antipose.distance2D(owner) antipose.relRads = degToRad(calculateAngle360(owner, antipose)) antipose.dirty = false } constrainHandle(owner, handle) { let radians = owner.radians + handle.relRads let directionX = Math.cos(radians) let directionY = Math.sin(radians) let offsetX = handle.x - owner.x let offsetY = handle.y - owner.y let distance = offsetX * directionX + offsetY * directionY handle.x = owner.x + directionX * distance handle.y = owner.y + directionY * distance return distance } computeHandles(index, point) { let handles = point.handles handles.forEach(handle => { let radians = point.radians + (handle?.relRads || 0) let distance = handle?.recordedDistance?.distance || point.radius // handle.x = point.x + Math.cos(radians) * distance handle.y = point.y + Math.sin(radians) * distance }) } } addButton('button', { label: "new spline" , onclick(){ stage.generateCurve() } }) class MainStage extends Stage { canvas='playspace' mounted() { this.myKeys = {} this.keyboard.wake() this.splines = [] } generateCurve() { this.splines.push({ color: random.color([0, 360], [30, 90], [60,80]) , spline: this.createCurve() }) } createCurve() { let spline = new BezierSpline( [100, 150, 30, -30] , [200, 340, 30, -30] , [450, 340, 30, -90] , [650, 340, 30, -90] , [650, 440, 30, 40] ).cast(BezierPoint) this.dragging.add(...spline) spline.forEach((p) => { this.dragging.add(...p.handles) }) return spline } onKeydown(ev) { // console.log('onKeydown', ev) this.myKeys[ev.key] = 1 } onKeyup(ev) { // console.log('onKeyup', ev) this.myKeys[ev.key] = 0 } draw(ctx){ this.clear(ctx) let altButton = this?.myKeys['Control'] == 1 this.splines.forEach((sConf) => { let s = sConf.spline; s.render(ctx, { altMode: altButton , color: sConf.color , showHandles: sConf.showHandles , showPoints: sConf.showPoints }) }) } } ;stage = MainStage.go();
Run
Meta Data
title Bezier
imports ()
files ('../point_src/math.js', 'head', 'point', 'pointlist', 'dragging', 'mouse', 'stage', 'dragging', 'stroke', '../point_src/keyboard.js', '../point_src/split.js', '../point_src/curve-extras.js', '../point_src/pointlock.js', '../point_src/widgets.js', '../point_src/random.js')
unused_keys ()
unknown_keys ()
filepath_exists True
path bezier-spline-b.js
filepath bezier-spline-b.js
clean_files ('../point_src/math.js', '../point_src/core/head.js', '../point_src/pointpen.js', '../point_src/compass.js', '../point_src/center.js', '../point_src/point-content.js', '../point_src/pointdraw.js', '../point_src/relative-xy.js', '../point_src/pointcast.js', '../point_src/point.js', '../point_src/pointlistdraw.js', '../point_src/pointlistgradient.js', '../point_src/pointlistshape.js', '../point_src/pointlistgenerator.js', '../point_src/unpack.js', '../point_src/pointlist.js', '../point_src/pointlistpen.js', '../point_src/functions/clamp.js', '../point_src/distances.js', '../point_src/protractor.js', '../point_src/text/beta.js', '../point_src/dragging.js', '../point_src/events.js', '../point_src/automouse.js', '../point_src/stage-hooks.js', '../point_src/stage-resize.js', '../point_src/functions/resolve.js', '../point_src/stage.js', '../point_src/setunset.js', '../point_src/stroke.js', '../point_src/keyboard.js', '../point_src/split.js', '../point_src/curve-extras.js', '../point_src/pointlock.js', '../point_src/widgets.js', '../point_src/random.js')
markdown {'html': '<p>In this example the dirty flag is used for curve tips.</p>', 'content': '---\ntitle: Bezier\nfiles:\n ../point_src/math.js\n head\n point\n pointlist\n dragging\n mouse\n stage\n dragging\n stroke\n ../point_src/keyboard.js\n ../point_src/split.js\n ../point_src/curve-extras.js\n ../point_src/pointlock.js\n ../point_src/widgets.js\n ../point_src/random.js\n\nIn this example the dirty flag is used for curve tips.'}