Skip to content

Instantly share code, notes, and snippets.

@shshaw
Created October 20, 2016 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shshaw/7eec087ea22442f1444c543ded941bf9 to your computer and use it in GitHub Desktop.
Save shshaw/7eec087ea22442f1444c543ded941bf9 to your computer and use it in GitHub Desktop.
Hose curve generation
<svg id="svg">
<path id="path" d="" /></path>
</svg>
console.clear();
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
/*////////////////////////////////////////*/
// http://stackoverflow.com/questions/16227300/how-to-draw-bezier-curves-with-native-javascript-code-without-ctx-beziercurveto
function bezier(p0, p1, p2, p3){
var cX = 3 * (p1.x - p0.x),
bX = 3 * (p2.x - p1.x) - cX,
aX = p3.x - p0.x - cX - bX;
var cY = 3 * (p1.y - p0.y),
bY = 3 * (p2.y - p1.y) - cY,
aY = p3.y - p0.y - cY - bY;
return function(t){
var x = (aX * Math.pow(t, 3)) + (bX * Math.pow(t, 2)) + (cX * t) + p0.x;
var y = (aY * Math.pow(t, 3)) + (bY * Math.pow(t, 2)) + (cY * t) + p0.y;
return {x: x, y: y};
}
};
function getCurve(points, accuracy){
accuracy = accuracy || 0.033;
var b = bezier(points[0], points[1], points[2], points[3]),
curve = [];
for (var i = 0; i < 1; i += accuracy ){
curve.push(b(i));
}
curve.push(b(1));
return curve;
}
/*////////////////////////////////////////*/
function drawPoint(point,fill){
ctx[ fill ? 'fill' : 'stroke' ]();
}
/*////////////////////////////////////////*/
function Hose(start, end) {
if (!(this instanceof Hose)) { return new Hose(start, end); }
if ( arguments.length === 1 ){
for (var key in start) {
if ( start.hasOwnProperty(key) ) { this[key] = start[key]; }
}
} else {
this.start = start;
this.end = end;
}
this.length = this.getLength();
this.update();
return this;
}
Hose.prototype = {
constructor: Hose,
// 0 = sharp, 1 = rounded
straightness: 0.7,
offset: 0.5,
length: null,
// Increase the length of the rope
extend: 0,
invertBend: true,
accuracy: 0.02,
pointCount: 100,
update: function(){
var length = this.getLength();
var diff = this.length - length;
diff = ( diff > 0 ? diff : 0 );
diff = ( this.invertBend ? -diff : diff ) * (this.extend + 1);
var percent = length / this.length;
var angle = Math.atan2(this.dy, this.dx),
sin = Math.sin(-angle),
cos = Math.cos(-angle),
distanceX = Math.min( sin * diff , this.length * 0.6),
distanceY = Math.min( cos * diff , this.length * 0.6);
var midX = this.dx * this.straightness * percent,
// - ( this.dy * this.bend ),// * this.offset,
midY = this.dy * this.straightness * percent;
// - ( this.dx * this.bend );// * this.offset;
this.cp1 = this.cp1 || {};
this.cp1.x = this.start.x + (distanceX + midX);///percent ;
this.cp1.y = this.start.y + (distanceY + midY);///percent;
this.cp2 = this.cp2 || {};
this.cp2.x = this.end.x + (distanceX - midX);// / percent;
this.cp2.y = this.end.y + (distanceY - midY);// / percent;
this.controls = [this.start, this.cp1, this.cp2, this.end];
this.draw();
},
getLength: function(){
return ( this.start && this.end ? Math.sqrt(( this.dx * this.dx ) + ( this.dy * this.dy )) : null );
},
draw: function(){
// var c = this.points;
// ctx.beginPath();
// ctx.moveTo( c[0].x, c[0].y);
// for (var i = 0 ; i < c.length; i ++){ ctx.lineTo(c[i].x, c[i].y); }
// ctx.stroke();
this.renderCanvas(ctx);
ctx.lineWidth = 1;
ctx.fillStyle = ctx.strokeStyle = 'red';
for (var i = 0; i < this.controls.length; i++ ){
ctx.beginPath();
ctx.arc(this.controls[i].x, this.controls[i].y, 4, 0, 2 * Math.PI);
ctx[ i === 1 || i === 2 ? 'fill' : 'stroke' ]();
}
},
renderCanvas: function(ctx){
ctx.beginPath();
ctx.fillStyle = ctx.strokeStyle = 'black';
ctx.lineWidth = 25;
ctx.moveTo( this.start.x, this.start.y);
ctx.bezierCurveTo( this.cp1.x, this.cp1.y, this.cp2.x, this.cp2.y, this.end.x, this.end.y);
ctx.stroke();
},
renderSVG: function(){
return ['M', this.start.x, this.start.y, 'C', this.cp1.x, this.cp1.y, this.cp2.x, this.cp2.y, this.end.x, this.end.y].join(' ');
}
};
var radToDeg = 180 / Math.PI; // rads to degs, range (-180, 180)
Object.defineProperties(Hose.prototype, {
'dx': { get: function(){ return this.end.x - this.start.x; } },
'dy': { get: function(){ return this.end.y - this.start.y; } },
'points': { get: function(){ return getCurve(this.controls, 1 / ( this.pointCount - 1) ); } },
// 'start': {
// set: function(val){
// this.s = val;
// this.length = this.getLength();
// }
// },
// 'end': {
// set: function(val){
// this. = val;
// this.length = this.getLength();
// }
// }
// 'length': {
// get: function(){
// return Math.sqrt(( this.dx * this.dx ) + ( this.dy * this.dy ));
// }
// },
// 'midpoint': {
// get: function(){
// return {
// x: ( this.start.x + this.end.x ) / 2,
// y: ( this.start.y + this.end.y ) / 2
// }
// }
// }
});
var centerY = Math.round( window.innerHeight / 2 );
// var r = new Hose({ x: 100, y: centerY }, { x: 500, y: centerY });
// //r.update();
var offset = 80;
var length = 300;
var leg1 = new Hose({
invertBend: true,
start: { x: 100, y: centerY - length/2 },
end: { x: 100, y: centerY + (length/2) }
});
var leg2 = new Hose({
invertBend: true,
start: { x: 100 + offset, y: centerY - (length/2.1) },
end: { x: 100 + offset, y: centerY + (length/2.1) }
});
console.log(leg1.points.length);
/*////////////////////////////////////////*/
// TweenMax.fromTo(r, 2,{
// extrude: 0.5,
// bend: -0.25,
// },{
// extrude: -0.5,
// bend: 0.5,
// repeat: -1,
// yoyo: true,
// onUpdate: r.update,
// onUpdateScope: r,
// repeatDelay: 3,
// ease: Elastic.easeInOut,//Linear.easeNone,//SteppedEase.config(5)
// });
/*////////////////////////////////////////*/
function update(e){
if ( mousedown ) {
leg1.start.x = e.clientX;
leg1.start.y = e.clientY;
leg2.start.x = e.clientX + offset;
leg2.start.y = e.clientY;
}
}
var mousedown = false;
canvas.addEventListener('mousedown',function(e){
mousedown = true;
update(e);
});
canvas.addEventListener('mousemove',update);
canvas.addEventListener('mouseup',function(e){
update(e);
mousedown = false;
});
/*////////////////////////////////////////*/
var gui = new dat.GUI();
// var myType = gui.add(myOptions, 'type', [ 'random', 'unify', 'reverse', 'vary', 'compact' ]),
//length = glui.add(r, 'length', -500, 500).step(10),
//extrude = gui.add(r, 'extrude', -2, 2).step(0.1),
straightness = gui.add(leg1, 'straightness', -1, 1).step(0.1);
//offset = gui.add(r, 'offset', -1, 1).step(0.1);
var svg = document.getElementById('svg');
var path = document.getElementById('path');
svg.setAttribute('width', window.innerWidth);
svg.setAttribute('height', window.innerHeight);
function draw(){
requestAnimationFrame(draw);
ctx.clearRect(0, 0, canvas.width, canvas.height);
leg2.straightness = leg1.straightness;
leg2.update();
leg1.update();
path.setAttribute('d',leg1.renderSVG());
}
requestAnimationFrame(draw);
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.1/dat.gui.min.js"></script>
body {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width=%2210%22 height=%2210%22%3E%0A %3Crect width=%2210%22 height=%2210%22 stroke=%22%23EEE%22 fill=%22none%22 %2F%3E%0A%3C%2Fsvg%3E%0A");
}
canvas, svg {
position: absolute;
top: 0;
left: 0;
}
svg { pointer-events: none; stroke: #F00; stroke-width: 2; z-index: 2; fill: none; }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment