Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <h2>Waving grass algorithm</h2> <p><strong>UPDATE</strong></p> <p>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.</p> <p>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.</p> <p>New fiddle (based on previous with a few modifications):<br> <a href="http://jsfiddle.net/AbdiasSoftware/yEwGc/" rel="nofollow noreferrer">http://jsfiddle.net/AbdiasSoftware/yEwGc/</a></p> <p><img src="https://i.stack.imgur.com/az9b9.png" alt="enter image description here"></p> <p><img src="https://i.stack.imgur.com/1NUzB.png" alt="Grass simulation snaphot - 150 strains"> <em>Image showing 150 strains being simulated.</em></p> <p>Grass simulation demo:<br> <a href="http://jsfiddle.net/AbdiasSoftware/5z89V/" rel="nofollow noreferrer">http://jsfiddle.net/AbdiasSoftware/5z89V/</a></p> <p>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).</p> <p>The code is rather simple. It consists of a main object (<code>grassObj</code>) which contains its geometry as well as functions to calculate the angles, segments, movements and so forth. I'll show this in detail below.</p> <p>First some inits that are accessed globally by the functions:</p> <pre><code>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'; </code></pre> <h2>The heart - grassObj</h2> <p>The main object as mentioned above is the <code>grassObj</code>:</p> <pre><code>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 &lt; 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 -&gt; get new goal if (counter &gt; 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; } </code></pre> <h2>Grass generator</h2> <p>We call <code>makeGrass</code> 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).</p> <p>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.</p> <pre><code>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 &lt; 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; } </code></pre> <h2>Render</h2> <p>The render function just loops through the objects and updates the current geometry:</p> <pre><code>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 &gt; -1) { ctx.drawImage(img, ix--, 0); if (ix &lt; -w) { ctx.drawImage(img, ix + iw, 0); } if (ix &lt;= -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(); } } </code></pre> <h2>Animate</h2> <p>The main loop where we animate everything:</p> <pre><code>function animate() { /// update each grass objects for(var i = 0;i &lt; grass.length; i++) grass[i].update(); /// render them renderGrass(ctx, grass); /// loop requestAnimationFrame(animate); } </code></pre> <p>And that's all there is to it for this version.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload