Javascript 贝塞尔曲线上的草状平滑动画?

Javascript 贝塞尔曲线上的草状平滑动画?,javascript,animation,canvas,bezier,requestanimationframe,Javascript,Animation,Canvas,Bezier,Requestanimationframe,这就是我想要实现的——(理想的动画) 这就是项目目前所处的位置 这是上述代码的一个更结构化的代码——我的头发动画(由markE编写) 问题:-- 我可以给头发移动,但动画应该更像波浪草一样自由流动。它现在不是很平滑。可以做些什么使头发以更自然的方式流动。 如果可能的话,请给我一个小样品 <canvas id="myCanvas" width="500" height="500" style="background-color: antiquewhite" ></canv

这就是我想要实现的——(理想的动画)

这就是项目目前所处的位置

这是上述代码的一个更结构化的代码——我的头发动画(由markE编写)

问题:--

我可以给头发移动,但动画应该更像波浪草一样自由流动。它现在不是很平滑。可以做些什么使头发以更自然的方式流动。 如果可能的话,请给我一个小样品

    <canvas id="myCanvas" width="500" height="500" style="background-color: antiquewhite" ></canvas>

JAVASCRIPT

//mouse position
var x2=0;
var y2=0;

window.addEventListener("mousemove",function(){moving(event);init()},false)

//these variables define the bend in our bezier curve
var bend9=0;
var bend8=0;
var bend7=0;
var bend6=0;
var bend5=0;
var bend4=0;
var bend3=0;
var bend2=0;
var bend1=0;

//function to get the mouse cordinates
function moving(event) {
    bend_value();//this function is defined below
    try
    {
        x2 = event.touches[0].pageX;
        y2 = event.touches[0].pageY;
    }
    catch (error)
    {
        try
        {
            x2 = event.clientX;
            y2 = event.clientY;
        }
        catch (e)
        {
        }
    }

    try
    {
        event.preventDefault();
    }
    catch (e)
    {
    }
    if(between(y2,204,237) && between(x2,115,272))
    {
    console.log("Xmove="+x2,"Ymove="+y2)
    }

}

//function for declaring range of bezier curve
function between(val, min, max)
{
    return val >= min && val <= max;
}

(function() {
    hair = function() {
        return this;
    };

    hair.prototype={

     draw_hair:function(a,b,c,d,e,f,g,h){

            var sx  =136+a;//start position of curve.used in moveTo(sx,sy)
            var sy  =235+b;
            var cp1x=136+c;//control point 1
            var cp1y=222+d;
            var cp2x=136+e;//control point 2
            var cp2y=222+f;
            var endx=136+g;//end points
            var endy=210+h;

         var canvas = document.getElementById('myCanvas');
         var context = canvas.getContext('2d');
//         context.clearRect(0, 0,500,500);
         context.strokeStyle="grey";
         context.lineWidth="8";
         context.beginPath();
         context.moveTo(sx,sy);
         context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,endx,endy);
         context.lineCap = 'round';
         context.stroke();
//         context.restore();
//         context.save();
    }
};
})();

//this function provides and calculate the bend on mousemove
function bend_value(){
    var ref1=135;//this is ref point for  hair or curve no 1
    var ref2=150;//hair no 2 and so on
    var ref3=165;
    var ref4=180;
    var ref5=195;
    var ref6=210;
    var ref7=225;
    var ref8=240;
    var ref9=255;
if(between(x2,115,270) && between(y2,205,236))
{
    if(x2>=135 && x2<=145){bend1=(x2-ref1)*(2.2);}
    if(x2<=135 && x2>=125){bend1=(x2-ref1)*(2.2);}

    if(x2>=150 && x2<=160){bend2=(x2-ref2)*(2.2);}
    if(x2<=150 && x2>=140){bend2=(x2-ref2)*(2.2);}

    if(x2>=165 && x2<=175){bend3=(x2-ref3)*(2.2);}
    if(x2<=165 && x2>=155){bend3=(x2-ref3)*(2.2);}

    if(x2>=180 && x2<=190){bend4=(x2-ref4)*(2.2);}
    if(x2<=180 && x2>=170){bend4=(x2-ref4)*(2.2);}

    if(x2>=195 && x2<=205){bend5=(x2-ref5)*(2.2);}
    if(x2<=195 && x2>=185){bend5=(x2-ref5)*(2.2);}

    if(x2>=210 && x2<=220){bend6=(x2-ref6)*(2.2);}
    if(x2<=210 && x2>=200){bend6=(x2-ref6)*(2.2);}

    if(x2>=225 && x2<=235){bend7=(x2-ref7)*(2.2);}
    if(x2<=225 && x2>=215){bend7=(x2-ref7)*(2.2);}

    if(x2>=240 && x2<=250){bend8=(x2-ref8)*(2.2);}
    if(x2<=240 && x2>=230){bend8=(x2-ref8)*(2.2);}

    if(x2>=255 && x2<=265){bend9=(x2-ref9)*(2.2);}
    if(x2<=255 && x2>=245){bend9=(x2-ref9)*(2.2);}
    }
}

function init(){//this function draws each hair/curve
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');
    var clear=context.clearRect(0, 0,500,500);
    var save=context.save();

//   /* console.log("bend2="+bend2)
//    console.log("bend3="+bend3)
//    console.log("bend4="+bend4)
//    console.log("bend5="+bend5)
//    console.log("bend6="+bend6)
//    console.log("bend7="+bend7)
//    console.log("bend8="+bend8)
//    console.log("bend9="+bend9)*/

    hd1 = new hair();//hd1 stands for hair draw 1.this is an instance created for drawing hair no 1
    clear;
    hd1.draw_hair(0,0,0,0,0,0,0+bend1/2,0);//these parameters passed to function drawhair and bend is beint retrieved from function bend_value()
    save;

    hd2 = new hair();
    clear;
    hd2.draw_hair(15,0,15,0,15,0,15+bend2/2,0);
    save;

    hd3 = new hair();
    clear;
    hd3.draw_hair(30,0,30,0,30,0,30+bend3/2,0);
    save;

    hd4 = new hair();
    clear;
    hd4.draw_hair(45,0,45,0,45,0,45+bend4/2,0);
    save;

    hd5 = new hair();
    clear;
    hd5.draw_hair(60,0,60,0,60,0,60+bend5/2,0);
    save;
 }

window.onload = function() {
    init();
    disableSelection(document.body)
}

function disableSelection(target){
    if (typeof target.onselectstart!="undefined") //IE
        target.onselectstart=function(){return false}
    else if (typeof target.style.MozUserSelect!="undefined") //Firefox
        target.style.MozUserSelect="none"
    else //All other ie: Opera
        target.onmousedown=function(){return false}
    target.style.cursor = "default"
}
//鼠标位置
var x2=0;
var y2=0;
addEventListener(“mousemove”,function(){moving(event);init()},false)
//这些变量定义了贝塞尔曲线中的弯曲
var bend9=0;
var bend8=0;
var bend7=0;
var bend6=0;
var bend5=0;
var bend4=0;
var bend3=0;
var bend2=0;
var bend1=0;
//函数获取鼠标坐标
功能移动(事件){
bend_value();//此函数定义如下
尝试
{
x2=事件。触摸[0]。页面X;
y2=事件。触摸[0]。页面;
}
捕获(错误)
{
尝试
{
x2=event.clientX;
y2=event.clientY;
}
捕获(e)
{
}
}
尝试
{
event.preventDefault();
}
捕获(e)
{
}
如果(介于(Y2204237)和(X2115272)之间)
{
console.log(“Xmove=“+x2,”Ymove=“+y2”)
}
}
//用于声明贝塞尔曲线范围的函数
函数介于(val、min、max)之间
{

return val>=min&&val=135&&x2=150&&x2=165&&x2=180&&x2=195&&x2=210&&x2=225&&x2=240&&x2=255&&x2这是一个简单的头发模拟,似乎就是您想要的。基本思想是绘制一条贝塞尔曲线(在这种情况下,我使用两条曲线来提供头发的厚度)。曲线将有一个底部、一个弯曲点和一个尖端。我将弯曲点设置在头发的一半。头发尖端将围绕头发底部的轴旋转,以响应鼠标移动

将此代码放置在画布元素声明下方的脚本标记中

    function Point(x, y) {
        this.x = x;
        this.y = y;
    }

    function Hair( )  {
        this.height = 100;   // hair height
        this.baseWidth = 3;    // hair base width.
        this.thickness = 1.5; // hair thickness
        this.points = {};

        this.points.base1 = new Point(Math.random()*canvas.width, canvas.height);

        // The point at which the hair will bend. I set it to the middle of the hair, but you can adjust this.
        this.points.bendPoint1 = new Point(this.points.base1.x-this.thickness, this.points.base1.y - this.height / 2)

        this.points.bendPoint2 = new Point(this.points.bendPoint1.x, this.points.bendPoint1.y-this.thickness); // complement of bendPoint1 - we use this because the hair has thickness
        this.points.base2 = new Point(this.points.base1.x + this.baseWidth, this.points.base1.y) // complement of base1 - we use this because the hair has thickness
    }

    Hair.prototype.paint = function(mouseX, mouseY, direction) {    
        ctx.save();

        // rotate the the tip of the hair
        var tipRotationAngle = Math.atan(Math.abs(this.points.base1.y - mouseY)/Math.abs(this.points.base1.x - mouseX));

        // if the mouse is on the other side of the hair, adjust the angle
        if (mouseX < this.points.base1.x) {
            tipRotationAngle = Math.PI - tipRotationAngle;
        }

        // if the mouse isn't close enough to the hair, it shouldn't affect the hair
        if (mouseX < this.points.base1.x - this.height/2 || mouseX > this.points.base1.x + this.height/2 || mouseY < this.points.base1.y - this.height || mouseY > this.points.base1.y) {
            tipRotationAngle = Math.PI/2; // 90 degrees, which means the hair is straight
        }

        // Use the direction of the mouse to as a lazy way to simulate the direction the hair should bend.
        // Note that in real life, the direction that the hair should bend has nothing to do with the direction of motion. It actually depends on which side of the hair the force is being applied.
        // Figuring out which side of the hair the force is being applied is a little tricky, so I took this shortcut.
        // If you run your finger along a comb quickly, this approximation will work. However if you are in the middle of the comb and slowly change direction, you will notice that the force is still applied in the opposite direction of motion as you slowly back off the set of tines.
        if ((mouseX < this.points.base1.x && direction == 'right') || (mouseX > this.points.base1.x && direction == 'left')) {
            tipRotationAngle = Math.PI/2; // 90 degrees, which means the hair is straight
        }

        var tipPoint = new Point(this.points.base1.x + this.baseWidth + this.height*Math.cos(tipRotationAngle), this.points.base1.y - this.height*Math.sin(tipRotationAngle));

        ctx.beginPath();
        ctx.moveTo(this.points.base1.x, this.points.base1.y); // start at the base
        ctx.bezierCurveTo(this.points.base1.x, this.points.base1.y, this.points.bendPoint1.x, this.points.bendPoint1.y, tipPoint.x, tipPoint.y); // draw a curve to the tip of the hair
        ctx.bezierCurveTo(tipPoint.x, tipPoint.y, this.points.bendPoint2.x, this.points.bendPoint2.y, this.points.base2.x, this.points.base2.y); // draw a curve back down to the base using the complement points since the hair has thickness.
        ctx.closePath(); // complete the path so we have a shape that we can fill with color
        ctx.fillStyle='rgb(0,0,0)';
        ctx.fill();

        ctx.restore();
    }  

    // I used global variables to keep the example simple, but it is generally best to avoid using global variables
    window.canvas = document.getElementById('myCanvas');
    window.ctx = canvas.getContext('2d');
    ctx.fillStyle = 'rgb(200,255,255)'; // background color
    window.hair = [];
    window.prevClientX = 0;

    for (var i = 0; i < 100; i++) {
        hair.push(new Hair());
    }

    // initial draw
    ctx.fillRect(0,0,canvas.width,canvas.height); // clear canvas
    for (var i = 0; i < hair.length; i++) {
        hair[i].paint(0, 0, 'right');
    }

    window.onmousemove = function(e)    {
        ctx.fillRect(0,0,canvas.width,canvas.height); // clear canvas

        for (var i = 0; i < hair.length; i++) {
            hair[i].paint(e.clientX, e.clientY, e.clientX > window.prevClientX ? 'right' : 'left');
        }
        window.prevClientX = e.clientX;
    }
功能点(x,y){
这个.x=x;
这个。y=y;
}
功能发(){
this.height=100;//头发高度
this.baseWidth=3;//发底宽度。
this.thickness=1.5;//头发厚度
this.points={};
this.points.base1=新点(Math.random()*canvas.width、canvas.height);
//头发将弯曲的点。我将其设置为头发的中间,但您可以调整此点。
this.points.bendPoint1=新点(this.points.base1.x-this.this.this.this.this.points.base1.y-this.height/2)
this.points.bendPoint2=新点(this.points.bendPoint1.x,this.points.bendPoint1.y-this.this.this.this.this厚度);//bendPoint1的补充-我们使用它是因为头发有厚度
this.points.base2=新点(this.points.base1.x+this.baseWidth,this.points.base1.y)//base1的补充-我们使用它是因为头发有厚度
}
Hair.prototype.paint=函数(mouseX、mouseY、direction){
ctx.save();
//旋转头发的顶端
var tipRotationAngle=Math.atan(Math.abs(this.points.base1.y-mouseY)/Math.abs(this.points.base1.x-mouseX));
//如果鼠标位于头发的另一侧,请调整角度
if(mouseXthis.points.base1.x+this.height/2 | | mouseYthis.points.base1.y){
tipRotationAngle=Math.PI/2;//90度,表示头发是直的
}
//使用鼠标的方向来模拟头发弯曲的方向。
//请注意,在现实生活中,头发弯曲的方向与运动方向无关,它实际上取决于施加力的头发的哪一侧。
//要弄清楚施加力的头发的哪一侧有点棘手,所以我走了这条捷径。
如果你用手指快速地移动梳子,这种近似是可行的。但是如果你在梳子中间,慢慢地改变方向,你会注意到,当你慢慢地从一组尖齿上退下来时,力仍然被施加在相反的运动方向上。
if((mouseXthis.points.base1.x&&direction='left')){
tipRotationAngle=Math.PI/2;//90度,表示头发是直的
}
var tipPoint=新点(this.points.base1.x+this.baseWidth+this.height*Math.cos(tipRotationAngle),this.points.base1.y-this.height*Math.sin(tipRotationAngle));
ctx.beginPath();
ctx.moveTo(this.points.base1.x,this.points.base1.y);//从底部开始
ctx.bezierCurveTo(this.points.base1.x,this.points.base1.y,this.points.bendPoint1.x,this.points.bendPoint1.y,tipPoint.x,tipPoint.y);//绘制一条到发尖的曲线
ctx.bezierCurveTo(tipPoint.x,tipPoint.y,this.points.bendPoint2.x,this.points.bendPoint2.y,this.points.base2.x,this.points.base2.y);//因为头发有厚度,所以使用补充点绘制一条曲线回到底部。
ctx.closePath();//完成路径,这样我们就有了一个可以用颜色填充的形状
ctx.fillStyle='rgb(0,0,0');
ctx.fill();
ctx.restore();
}  
//我使用全局变量来保持示例的简单性,但通常最好避免使用全局变量
window.canvas=document.getElementById('myCanvas');
window.ctx=canvas.getContext('2d');
ctx.fillStyle='rgb(200255255)';//背景色
window.hair=[];
window.prevClientX=0;
(弗吉尼亚州)
var numOfGrass = 70,  /// number of grass strains
    grass,

    /// get canvas context
    ctx = canvas.getContext('2d'),
    w = canvas.width,
    h = canvas.height,

    /// we use an animated image for the background
    /// The image also clears the canvas for each loop call
    /// I rendered the clouds in a 3D software.
    img = document.createElement('img'),
    ix = 0,  /// background image position
    iw = -1; /// used for with and initial for flag

    /// load background image, use it whenever it's ready
    img.onload = function() {iw = this.width}
    img.src = 'http://i.imgur.com/zzjtzG7.jpg';
function grassObj(x, y, seg1, seg2, maxAngle) {

    /// exposed properties we need for rendering
    this.x = x;        /// bottom position of grass
    this.y = y;
    this.seg1 = seg1;  /// segments of grass
    this.seg2 = seg2; 
    this.gradient = getGradient(Math.random() * 50 + 50, 100 * Math.random() + 170);

    this.currentAngle; ///current angle that will be rendered

    /// internals used for calculating new angle, goal, difference and speed
    var counter,       /// counter between 0-1 for ease-in/out
        delta,         /// random steps in the direction goal rel. c.angle.
        angle,         /// current angle, does not change until goal is reached
        diff,          /// diff between goal and angle
        goal = getAngle();

    /// internal: returns an angel between 0 and maxAngle
    function getAngle() {
        return maxAngle * Math.random();
    }

    /// ease in-out function
    function easeInOut(t) {
        return t < 0.5 ? 4 * t * t * t : (t-1) * (2 * t - 2) * (2 * t - 2) + 1;
    }

    /// sets a new goal for grass to move to. Does the main calculations
    function newGoal() {
        angle = goal;        /// set goal as new angle when reached
        this.currentAngle = angle;
        goal = getAngle();   /// get new goal
        diff = goal - angle; /// calc diff
        counter = 0;         /// reset counter

        delta = (4 * Math.random() + 1) / 100;
    }

    /// creates a gradient for this grass to increase realism
    function getGradient(min, max) {

        var g = ctx.createLinearGradient(0, 0, 0, h);
        g.addColorStop(1,   'rgb(0,' + parseInt(min) + ', 0)');
        g.addColorStop(0,   'rgb(0,' + parseInt(max) + ', 0)');

        return g;
    }

    /// this is called from animation loop. Counts and keeps tracks of 
    /// current position and calls new goal when current goal is reached
    this.update = function() {

        /// count from 0 to 1 with random delta value   
        counter += delta;

        /// if counter passes 1 then goal is reached -> get new goal
        if (counter > 1) {
            newGoal();
            return;
        }

        /// ease in/out function
        var t = easeInOut(counter);

        /// update current angle for render
        this.currentAngle = angle + t * diff;
    }

    /// init
    newGoal();

    return this;
}
function makeGrass(numOfGrass, width, height, hVariation) {

    /// setup variables
    var x, y, seg1, seg2, angle,
        hf = height * hVariation,  /// get variation
        i = 0,
        grass = [];                /// array to hold the grass

    /// generate grass
    for(; i < numOfGrass; i++) {

        x = width * Math.random();        /// random x position
        y = height - hf * Math.random();  /// random height

        /// break grass into 3 segments with random variation
        seg1 = y / 3 + y * hVariation * Math.random() * 0.1;
        seg2 = (y / 3 * 2) + y * hVariation * Math.random() * 0.1;

        grass.push(new grassObj(x, y, seg1, seg2, 15 * Math.random() + 50));
}

    return grass;
}
function renderGrass(ctx, grass) {

    /// local vars for animation
    var len = grass.length,
        i = 0,
        gr, pos, diff, pts, x, y;

    /// renders background when loaded
    if (iw > -1) {
        ctx.drawImage(img, ix--, 0);
        if (ix < -w) {
            ctx.drawImage(img, ix + iw, 0);
        }
        if (ix <= -iw) ix = 0;
    } else {
        ctx.clearRect(0, 0, w, h);
    }

    /// loops through the grass object and renders current state
    for(; gr = grass[i]; i++) {

        x = gr.x;
        y = gr.y;

        ctx.beginPath();

        /// calculates the end-point based on length and angle
        /// Angle is limited [0, 60] which we add 225 deg. to get
        /// it upwards. Alter 225 to make grass lean more to a side.
        pos = lineToAngle(ctx, x, h, y, gr.currentAngle + 225);

        /// diff between end point and root point
        diff = (pos[0] - x)

        pts = [];

        /// starts at bottom, goes to top middle and then back
        /// down with a slight offset to make the grass

        pts.push(x); /// first couple at bottom
        pts.push(h);

        /// first segment 1/4 of the difference
        pts.push(x + (diff / 4));
        pts.push(h - gr.seg1);

        /// second segment 2/3 of the difference
        pts.push(x + (diff / 3 * 2));
        pts.push(h - gr.seg2);

        pts.push(pos[0]);    /// top point
        pts.push(pos[1]);

        /// re-use previous data, but go backward down to root again
        /// with a slight offset
        pts.push(x + (diff / 3 * 2) + 10);
        pts.push(h - gr.seg2);

        pts.push(x + (diff / 4) + 12);
        pts.push(h - gr.seg1 + 10);

        pts.push(x + 15); /// end couple at bottom
        pts.push(h);

        /// smooth points (extended context function, see demo)
        ctx.curve(pts, 0.8, 5);

        ctx.closePath();

        /// fill grass with its gradient
        ctx.fillStyle = gr.gradient;
        ctx.fill();
    }
}
function animate() {

    /// update each grass objects
    for(var i = 0;i < grass.length; i++) grass[i].update();            

    /// render them
    renderGrass(ctx, grass);

    /// loop
    requestAnimationFrame(animate);
}
(function() { // The code is encapsulated in a self invoking function  to isolate the scope
  "use strict";

   // The following lines creates shortcuts to the constructors of the Box2D types used
   var B2Vec2 = Box2D.Common.Math.b2Vec2,
      B2BodyDef = Box2D.Dynamics.b2BodyDef,
      B2Body = Box2D.Dynamics.b2Body,
      B2FixtureDef = Box2D.Dynamics.b2FixtureDef,
      B2Fixture = Box2D.Dynamics.b2Fixture,
      B2World = Box2D.Dynamics.b2World,
      B2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
      B2RevoluteJoint = Box2D.Dynamics.Joints.b2RevoluteJoint,
      B2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;

  // This makes sure that there is a method to request a callback to update the graphics for next frame
  window.requestAnimationFrame =
    window.requestAnimationFrame || // According to the standard
    window.mozRequestAnimationFrame || // For mozilla
    window.webkitRequestAnimationFrame || // For webkit
    window.msRequestAnimationFrame || // For ie
    function (f) { window.setTimeout(function () { f(Date.now()); }, 1000/60); }; // If everthing else fails

  var world = new B2World(new B2Vec2(0, -10), true), // Create a world with gravity
      physicalObjects = [], // Maintain a list of the simulated objects
      windInput = 0, // The input for the wind in the current frame
      wind = 0, // The current wind (smoothing the input values + randomness)
      STRAW_COUNT = 10, // Number of straws
      GRASS_RESET_SPEED = 2, // How quick should the straw reset to its target angle
      POWER_MOUSE_WIND = 120, // How much does the mouse affect the wind
      POWER_RANDOM_WIND = 180; // How much does the randomness affect the wind

  // GrassPart is a prototype for a piece of a straw. It has the following properties
  //  position: the position of the piece
  //  density: the density of the piece
  //  target: the target angle of the piece
  //  statik: a boolean stating if the piece is static (i.e. does not move)
  function GrassPart(position, density, target, statik) {
    this.width = 0.05;
    this.height = 0.5;
    this.target = target;

    // To create a physical body in Box2D you have to setup a body definition
    // and create at least one fixture.
    var bdef = new B2BodyDef(), fdef = new B2FixtureDef();
    // In this example we specify if the body is static or not (the grass roots 
    // has to be static to keep the straw in its position), and its original
    // position.
    bdef.type = statik? B2Body.b2_staticBody : B2Body.b2_dynamicBody;
    bdef.position.SetV(position);

    // The fixture of the piece is a box with a given density. The negative group index
    // makes sure that the straws does not collide.
    fdef.shape = new B2PolygonShape();
    fdef.shape.SetAsBox(this.width/2, this.height/2);
    fdef.density = density;
    fdef.filter.groupIndex = -1;

    // The body and fixture is created and added to the world
    this.body = world.CreateBody(bdef);
    this.body.CreateFixture(fdef);
  }

  // This method is called for every frame of animation. It strives to reset the original
  // angle of the straw (the joint). The time parameter is unused here but contains the
  // current time.
  GrassPart.prototype.update = function (time) {
    if (this.joint) {
      this.joint.SetMotorSpeed(GRASS_RESET_SPEED*(this.target - this.joint.GetJointAngle()));
    }
  };

  // The link method is used to link the pieces of the straw together using a joint
  // other: the piece to link to
  // torque: the strength of the joint (stiffness)
  GrassPart.prototype.link = function(other, torque) {
    // This is all Box2D specific. Look it up in the manual.
    var jdef = new B2RevoluteJointDef();
    var p = this.body.GetWorldPoint(new B2Vec2(0, 0.5)); // Get the world coordinates of where the joint
    jdef.Initialize(this.body, other.body, p);
    jdef.maxMotorTorque = torque;
    jdef.motorSpeed = 0;
    jdef.enableMotor = true;

    // Add the joint to the world
    this.joint = world.CreateJoint(jdef);
  };

  // A prototype for a straw of grass
  // position: the position of the bottom of the root of the straw
  function Grass(position) {
    var pos = new B2Vec2(position.x, position.y);
    var angle = 1.2*Math.random() - 0.6; // Randomize the target angle

    // Create three pieces, the static root and to more, and place them in line.
    // The second parameter is the stiffness of the joints. It controls how the straw bends.
    // The third is the target angle and different angles are specified for the pieces.
    this.g1 = new GrassPart(pos, 1, angle/4, true); // This is the static root
    pos.Add(new B2Vec2(0, 1));
    this.g2 = new GrassPart(pos, 0.75, angle);
    pos.Add(new B2Vec2(0, 1));
    this.g3 = new GrassPart(pos, 0.5);

    // Link the pieces into a straw
    this.g1.link(this.g2, 20);
    this.g2.link(this.g3, 3);

    // Add the pieces to the list of simulate objects
    physicalObjects.push(this.g1);
    physicalObjects.push(this.g2);
    physicalObjects.push(this.g3);
  }

  Grass.prototype.draw = function (context) {
      var p = new B2Vec2(0, 0.5);
      var p1 = this.g1.body.GetWorldPoint(p);
      var p2 = this.g2.body.GetWorldPoint(p);
      var p3 = this.g3.body.GetWorldPoint(p);

      context.strokeStyle = 'grey';
      context.lineWidth = 0.4;
      context.lineCap = 'round';

      context.beginPath();
      context.moveTo(p1.x, p1.y);
      context.quadraticCurveTo(p2.x, p2.y, p3.x, p3.y);
      context.stroke();
  };

    var lastX, grass = [], context = document.getElementById('canvas').getContext('2d');

    function updateGraphics(time) {
      window.requestAnimationFrame(updateGraphics);

      wind = 0.95*wind + 0.05*(POWER_MOUSE_WIND*windInput + POWER_RANDOM_WIND*Math.random() - POWER_RANDOM_WIND/2);
      windInput = 0;
      world.SetGravity(new B2Vec2(wind, -10));

      physicalObjects.forEach(function(obj) { if (obj.update) obj.update(time); });
      world.Step(1/60, 8, 3);
      world.ClearForces();

      context.clearRect(0, 0, context.canvas.width, context.canvas.height);
      context.save();
      context.translate(context.canvas.width/2, context.canvas.height/2);
      context.scale(context.canvas.width/20, -context.canvas.width/20);
      grass.forEach(function (o) { o.draw(context); });
      context.restore();
    }

    document.getElementsByTagName('body')[0].addEventListener("mousemove", function (e) {
      windInput = Math.abs(lastX - e.x) < 200? 0.2*(e.x - lastX) : 0;
      lastX = e.x;
    });

    var W = 8;
    for (var i = 0; i < STRAW_COUNT; i++) {
      grass.push(new Grass(new B2Vec2(W*(i/(STRAW_COUNT-1))-W/2, -1)));
    }

    window.requestAnimationFrame(updateGraphics);
})();
<!doctype html>
<html>
  <head>

    <link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
    <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
    <script src="http://code.jquery.com/ui/1.10.1/jquery-ui.js"></script>

    <style>
      body { font-family: arial; padding:15px; }
      canvas { border: 1px solid red;}
      input[type="text"]{width:35px;}
    </style>

  </head>
  <body>
      <p>Move mouse across hairs</p>
      <canvas height="100" width="250" id="canvas"></canvas>
  <script>

      $(function() {

        var canvas=document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        var canvasOffset=$("#canvas").offset();
        var offsetX=canvasOffset.left;
        var offsetY=canvasOffset.top;

        var cHeight=canvas.height;
        var showControls=false;
        var lastMouseX=0;

        // preset styling CONSTANTS
        var SWAY=.55;     // max endpoint sway from center 
        var C1Y=.40;      // fixed Y of cp#1
        var C2SWAY=.20    // max cp#2 sway from center
        var C2Y=.75;      // fixed Y of cp#2
        var YY=20;        // max height of ellipse at top of hair
        var PIPERCENT=Math.PI/100;

        var hairs=[];

        // create hairs
        var newHairX=40;
        var hairCount=20;
        for(var i=0;i<hairCount;i++){
            var randomLength=50+parseInt(Math.random()*5);
            addHair(newHairX+(i*8),randomLength);
        }

        function addHair(x,length){
            hairs.push({
                x:x,
                length:length,
                left:0,
                right:0,
                top:0,
                s:{x:0,y:0},
                c1:{x:0,y:0},
                c2:{x:0,y:0},
                e:{x:0,y:0},
                isInMotion:false,
                currentX:0
            });
        }

        for(var i=0;i<hairs.length;i++){
            var h=hairs[i];
            setHairPointsFixed(h);
            setHairPointsPct(h,50);
            draw(h);
        }

        function setHairPointsFixed(h){
            h.s.x   = h.x;
            h.s.y   = cHeight;
            h.c1.x  = h.x;
            h.c1.y  = cHeight-h.length*C1Y;
            h.c2.y  = cHeight-h.length*C2Y;
            h.top   = cHeight-h.length;
            h.left  = h.x-h.length*SWAY;
            h.right = h.x+h.length*SWAY;
        }


        function setHairPointsPct(h,pct){
            // endpoint
            var a=Math.PI+PIPERCENT*pct;
            h.e.x  = h.x - ((h.length*SWAY)*Math.cos(a));
            h.e.y  = h.top + (YY*Math.sin(a));
            // controlpoint#2
            h.c2.x = h.x + h.length*(C2SWAY*2*pct/100-C2SWAY);
        }

        //////////////////////////////

        function handleMouseMove(e){
          mouseX=parseInt(e.clientX-offsetX);
          mouseY=parseInt(e.clientY-offsetY);

          // draw this frame based on mouse moves
          ctx.clearRect(0,0,canvas.width,canvas.height);
          for(var i=0;i<hairs.length;i++){
              hairMoves(hairs[i],mouseX,mouseY);
          }

          lastMouseX=mouseX;
        }
        $("#canvas").mousemove(function(e){handleMouseMove(e);});



        function hairMoves(h,mouseX,mouseY){

          // No hair movement if not touching hair
          if(mouseY<cHeight-h.length-YY){
              if(h.isInMotion){
                  h.isInMotion=false;
                  setHairPointsPct(h,50);
              }
              draw(h);
              return;
          }

          // No hair movement if too deep in hair
          if(mouseY>h.c1.y){
              draw(h);
              return;
          }

          //
          var pct=50;
          if(mouseX>=h.left && mouseX<=h.right){

              if(h.isInMotion){

                  var pct=-(mouseX-h.right)/(h.right-h.left)*100;
                  setHairPointsPct(h,pct);
                  draw(h);

              }else{

                  // if hair is at rest 
                  // but mouse has just contacted hair
                  // set hair in motion
                  if(   (lastMouseX<=h.x && mouseX>=h.x )
                      ||(lastMouseX>=h.x && mouseX<=h.x )
                  ){
                      h.isInMotion=true;
                      var pct=-(mouseX-h.right)/(h.right-h.left)*100;
                  }
                  setHairPointsPct(h,pct);
                  draw(h);

              }

          }else{
              if(h.isInMotion){
                  h.isInMotion=false;
                  setHairPointsPct(h,50);
              };
              draw(h);
          }

        }


        function dot(pt,color){
            ctx.beginPath();
            ctx.arc(pt.x,pt.y,5,0,Math.PI*2,false);
            ctx.closePath();
            ctx.fillStyle=color;
            ctx.fill();
        }


        function draw(h){

            ctx.beginPath();
            ctx.moveTo(h.s.x,h.s.y);
            ctx.bezierCurveTo(h.c1.x,h.c1.y,h.c2.x,h.c2.y,h.e.x,h.e.y);
            ctx.strokeStyle="orange";
            ctx.lineWidth=3;
            ctx.stroke();

            if(showControls){
                dot(h.s,"green");
                dot(h.c1,"red");
                dot(h.c2,"blue");
                dot(h.e,"purple");

                ctx.beginPath();
                ctx.rect(h.left,h.top-YY,(h.right-h.left),h.length*(1-C1Y)+YY)
                ctx.lineWidth=1;
                ctx.strokeStyle="lightgray";
                ctx.stroke();
            }

        }


    });
    </script>

  </body>   
</html>
...
Mheight = 1;
height = 33;
width = 17;
distance = 10;
randomness = 14;
angle = Math.PI / 2;
...