Grass like smoothing animation on beziercurve?

后端 未结 5 1768
终归单人心
终归单人心 2020-12-09 06:47

This is what I am trying to achieve--GRASS Animation(Desired animation)

This is where the project is standing currently --My hair animation

This is a more st

相关标签:
5条回答
  • 2020-12-09 07:23

    Darn! Late to the party...

    But LOTS of neat answers here -- I'm upvoting all !

    Anyway, here's my idea:

    enter image description here

    Here's code and a Fiddle: http://jsfiddle.net/m1erickson/MJjHz/

    <!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>
    
    0 讨论(0)
  • 2020-12-09 07:39

    Update: I'm currently adjusting the code to produce the requested result and commenting it.

    (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);
    })();
    
    0 讨论(0)
  • 2020-12-09 07:43

    Made this some time ago, might be useful to some people. Just adjust the variables at the beginning of the code with the values that fits your wishes:

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

    Also on http://lucasm0ta.github.io/JsGrass/

    0 讨论(0)
  • 2020-12-09 07:45

    Here is a simple hair simulation that seems to be what you are looking for. The basic idea is to draw a bezier curve (in this case I use two curves to provide thickness for the hair). The curve will have a base, a bending point, and a tip. I set the bending point halfway up the hair. The tip of the hair will rotate about the axis of the base of the hair in response to mouse movement.

    Place this code in a script tag below the canvas element declaration.

        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;
        }
    
    0 讨论(0)
  • 2020-12-09 07:46

    Waving grass algorithm

    UPDATE

    I made a reduced update to better meet what I believe is your requirements. To use mouse you just calculate the angle between the mouse point and the strain root and use that for new angle in the update.

    I have incorporated a simple mouse-move sensitive approach which makes the strains "point" towards the mouse, but you can add random angles to this as deltas and so forth. Everything you need is as said in the code - adjust as needed.

    New fiddle (based on previous with a few modifications):
    http://jsfiddle.net/AbdiasSoftware/yEwGc/

    enter image description here

    Grass simulation snaphot - 150 strains Image showing 150 strains being simulated.

    Grass simulation demo:
    http://jsfiddle.net/AbdiasSoftware/5z89V/

    This will generate a nice realistic looking grass field. The demo has 70 grass rendered (works best in Chrome or just lower the number for Firefox).

    The code is rather simple. It consists of a main object (grassObj) which contains its geometry as well as functions to calculate the angles, segments, movements and so forth. I'll show this in detail below.

    First some inits that are accessed globally by the functions:

    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';
    

    The heart - grassObj

    The main object as mentioned above is the grassObj:

    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;
    }
    

    Grass generator

    We call makeGrass to generate grass at random positions, random heights and with random segments. The function is called with number of grass to render, width and height of canvas to fill and a variation variable in percent (0 - 1 float).

    The single grass consist only of four points in total. The two middle points are spread about 1/3 and 2/3 of the total height with a little variation to break pattern. The points when rendered, are smoother using a cardinal spline with full tension to make the grass look smooth.

    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;
    }
    

    Render

    The render function just loops through the objects and updates the current geometry:

    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();
        }
    }
    

    Animate

    The main loop where we animate everything:

    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);
    }
    

    And that's all there is to it for this version.

    0 讨论(0)
提交回复
热议问题