Smoothly predicting the location of the mouse cursor in javascript - javascript

I want to draw a rectangle on a canvas around the mouse cursor that smoothly follows the cursor. Unfortunately, the mousemoved event doesn't fire quickly enough, and the drawing constantly trails behind it. So I'm assuming I need to predict where the mouse is, and draw the rectangle at that point. I'm trying to write a simple library to abstract that away, but it's not tracking as closely as I'd like for fast movements (in fact, fast movements are jittery). For slow movements, it tracks fairly well, and better than the simple solution of using the raw mouse coordinates.
The basic idea is that mousemove updates a couple of external variables with the current position of the mouse. A requestAnimationFrame loop (the Watcher function) tracks these variables and their previous values over time to calculate the speed the mouse is moving at (in the x axis). When the PredictX function is called, it returns the current x position, plus the last change in x multiplied by the speed. A different reqeustAnimationFrame loop moves the rectangle based on the predicted x value.
var MouseLerp = (function () {
var MOUSELERP = {};
var current_x = 0;
var last_x = 0;
var dX = 0;
var last_time = 0;
var x_speed = 0;
var FPS = 60;
function Watcher(time) {
var dT = time - last_time
if (dT > (1000 / FPS)) {
dX = last_x - current_x;
last_x = current_x;
x_speed = dX / dT
last_time = time;
}
requestAnimationFrame(Watcher);
}
MOUSELERP.PredictX = function () {
return Math.floor((dX * x_speed) + current_x);
}
MOUSELERP.Test = function () {
var target_element = $(".container")
target_element.append('<canvas width="500" height="500" id="basecanvas"></canvas>');
var base_ctx = document.getElementById("basecanvas").getContext("2d");
var offset = target_element.offset()
var offset_x = offset.left;
var offset_y = offset.top;
var WIDTH = $(window).width();
var HEIGHT = $(window).height();
var FPS = 60;
var t1 = 0;
function updateRect(time) {
var dT = time - t1
if (dT > (1000 / FPS)) {
base_ctx.clearRect(0, 0, WIDTH, HEIGHT)
base_ctx.beginPath();
base_ctx.strokeStyle = "#FF0000";
base_ctx.lineWidth = 2;
base_ctx.rect(MOUSELERP.PredictX(), 100, 100, 100)
base_ctx.stroke();
t1 = time;
}
requestAnimationFrame(updateRect)
}
updateRect();
$(target_element).mousemove(function (event) {
current_x = event.pageX - offset_x;
});
requestAnimationFrame(Watcher);
}
MOUSELERP.Test()
return MOUSELERP;
}())
What am I doing wrong?
Here's a jsfiddle of the above: http://jsfiddle.net/p8Lr224p/
Thanks!

The mouse pointer will always be quicker than drawing, so your best bet is not to give the user's eye a reason to perceive latency. So, turn off the mouse cursor while the user is drawing. Draw a rectangle at the mouse position to visually act as the mouse cursor.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var mouseX=0;
var mouseY=0;
canvas.style.cursor="none";
$("#canvas").mousemove(function(e){handleMouseMove(e);});
function handleMouseMove(e){
ctx.clearRect(mouseX-1,mouseY-1,9,9);
mouseX=e.clientX-offsetX;
mouseY=e.clientY-offsetY;
ctx.fillRect(mouseX,mouseY,8,8);
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Less 'lagging' when mouse is invisible & canvas draws cursor.</h4>
<canvas id="canvas" width=300 height=300></canvas>
,

Related

Javascript Mouse Position location?

Im trying to build a game the objective is to hit the enimies that come out from the water by clicking on them, I have been trying to get a water jet come out of a statues mouth (which has sort of been succesful and can be seen here: http://mckenziedave.co.uk/client_files/html5_game/) however it is slighty off, I believe this is because of the game being within the game screen (div id="gamefiled"). How can I take this into factor within the JS script (script below)
var flame,
sourceX = 780,
sourceY = 215;
window.onload = function() {
flame = document.getElementById('flame');
game.addEventListener('click', function(e) {
var targetX = e.pageX,
targetY = e.pageY,
deltaX = targetX - sourceX,
deltaY = targetY - sourceY,
rad = Math.atan2(deltaY, deltaX),
deg = rad * (180 / Math.PI),
length = Math.sqrt(deltaX*deltaX+deltaY*deltaY);
fire(deg,length);
}, false);
};
function fire(deg,length) {
flame.style.opacity = 1;
flame.style.transform = 'rotate(' + deg + 'deg)'
flame.style.width = length + 'px';
setTimeout(function() {
flame.style.opacity = 0;
},780);
};
Cheers
You have to determine the offset of the div id="gamefiled", relative to the browser window in your click event and substract it from the mouse position.
I've tried playing your game and I clicked some crabs but no hits. This is probably a math error since players play the game on different resolutions on different devices. Not to mention the pixel size problem where PCs, tablets and phones don't have universal pixel sizes.
Have you tried obtaining coordinates like this:
http://www.w3schools.com/jsref/event_clientx.asp
var x = event.clientX;
var y = event.clientY;
For drawing lines (precise) I'd recommend something like a Bresenham algorithm but this is just my opinion.
You can also try a much more simple solution like this:
http://www.w3schools.com/tags/canvas_lineto.asp
You can use canvas for drawing lines like so:
function drawLine (x_start, y_start, x_end, y_end) {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(x_start, y_start);
ctx.lineTo(x_end, y_end);
//set line width and color
ctx.lineWidth = 10;
ctx.strokeStyle = '#000';
ctx.stroke();
}
I rewrote this solution from w3schools.com.

Realistic mouse movement coordinates in javascript?

In javascript, is there a way I can create a variable and a function that "simulates" smooth mouse movement? i.e., say the function simulates a user starts from lower left corner of the browser window, and then moves mouse in a random direction slowly...
The function would return x and y value of the next position the mouse would move each time it is called (would probably use something like setInterval to keep calling it to get the next mouse position). Movement should be restricted to the width and height of the screen, assuming the mouse never going off of it.
What I don't want is the mouse to be skipping super fast all over the place. I like smooth movements/positions being returned.
A "realistic mouse movement" doesn't mean anything without context :
Every mouse user have different behaviors with this device, and they won't even do the same gestures given what they have on their screen.
If you take an FPS game, the movements will in majority be in a small vertical range, along the whole horizontal screen.
Here is a "drip painting" I made by recording my mouse movements while playing some FPS game.
If we take the google home page however, I don't even use the mouse. The input is already focused, and I just use my keyboard.
On some infinite scrolling websites, my mouse can stay at the same position for dozens of minutes and just go to a link at some point.
I think that to get the more realistic mouse movements possible, you would have to record all your users' gestures, and repro them.
Also, a good strategy could be to get the coordinates of the elements that will attract user's cursor the more likely (like the "close" link under SO's question) and make movements go to those elements' coordinates.
Anyway, here I made a snippet which uses Math.random() and requestAnimationFrame() in order to make an object move smoothly, with some times of pausing, and variable speeds.
// Canvas is here only to show the output of function
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
var maxX = canvas.width = window.innerWidth;
var maxY = canvas.height = window.innerHeight;
window.onresize = function(){
maxX = canvas.width = window.innerWidth;
maxY = canvas.height = window.innerHeight;
}
gc.onclick = function(){
var coords = mouse.getCoords();
out.innerHTML = 'x : '+coords.x+'<br>y : '+coords.y;
}
var Mouse = function() {
var that = {},
size = 15,
border = size / 2,
maxSpeed = 50, // pixels per frame
maxTimePause = 5000; // ms
that.draw = function() {
if (that.paused)
return;
that.update();
// just for the example
ctx.clearRect(0, 0, canvas.width, canvas.height);
if(show.checked){
ctx.drawImage(that.img, that.x - border, that.y - border, size, size)
}
// use requestAnimationFrame for smooth update
requestAnimationFrame(that.draw);
}
that.update = function() {
// take a random position, in the same direction
that.x += Math.random() * that.speedX;
that.y += Math.random() * that.speedY;
// if we're out of bounds or the interval has passed
if (that.x <= border || that.x >= maxX - border || that.y <= 0 || that.y >= maxY - border || ++that.i > that.interval)
that.reset();
}
that.reset = function() {
that.i = 0; // reset the counter
that.interval = Math.random() * 50; // reset the interval
that.speedX = (Math.random() * (maxSpeed)) - (maxSpeed / 2); // reset the horizontal direction
that.speedY = (Math.random() * (maxSpeed)) - (maxSpeed / 2); // reset the vertical direction
// we're in one of the corner, and random returned farther out of bounds
if (that.x <= border && that.speedX < 0 || that.x >= maxX - border && that.speedX > 0)
// change the direction
that.speedX *= -1;
if (that.y <= border && that.speedY < 0 || that.y >= maxY - border && that.speedY > 0)
that.speedY *= -1;
// check if the interval was complete
if (that.x > border && that.x < maxX - border && that.y > border && that.y < maxY - border) {
if (Math.random() > .5) {
// set a pause and remove it after some time
that.paused = true;
setTimeout(function() {
that.paused = false;
that.draw();
}, (Math.random() * maxTimePause));
}
}
}
that.init = function() {
that.x = 0;
that.y = 0;
that.img = new Image();
that.img.src ="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAABJUlEQVRIic2WXbHEIAyFI6ESKgEJkVIJlYCTSqiESIiESqiEb19gL9Od3f5R5mbmPPHwBTgnIPJfChiAGbCkCQgtG7BpmgAWIALaDDyOI2bGuq40BasqIoKZATgwNAWHEEjHbkBsBhYRVJUYIwBNwVlFaVOwiDDPMylmQ1OwquY7d0CBrglYkuEeidoeOKt61I6Cq0ftKFhqR+0MOKuo2BQsInnndvnOr4JvR+0qWO5G7Q44K0XtOXDf96jqh9z9WXAy1FJ8l0qd+zbtvU7lWs7wIzkuh8SvpqqDi3zGndPQauDkzvdESm8xZvbh4mVZ7k8ud/+aR0C3YPk7mVvgkCZPVrdZV3dHVem6bju1roMPNmbAmq8kG+/ynD7ZwNsAVVz9dL0AhBrZq7F+CSQAAAAASUVORK5CYII=";
that.reset();
}
that.getCoords = function(){
return {x: that.x, y:that.y};
}
that.init()
return that;
}
var mouse = new Mouse()
mouse.draw();
html,body {margin: 0}
canvas {position: absolute; top:0; left:0;z-index:-1}
#out{font-size: 0.8em}
<label for="show">Display cursor</label><input name="show" type="checkbox" id="show" checked="true"/><br>
<button id="gc">get cursor Coords</button>
<p id="out"></p>
Last I heard the browser's mouse position cannot be altered with JavaScript, so the question really has no answer "as is". The mouse position can be locked though. I'm not certain whether it would be possible to implement a custom cursor that allows setting the position. This would include hiding and perhaps locking the stock cursor.
Having something smoothly follow the cursor is quite straight forward. You may be able to reverse this process to achieve what you need. Here's a code snippet which simply calculates the distance between the cursor and a div every frame and then moves the div 10% of that distance towards the cursor:
http://jsfiddle.net/hpp0qb0d/
var p = document.getElementById('nextmove')
var lastX,lastY,cursorX,cursorY;
window.addEventListener('mousemove', function(e){
cursorX = e.pageX;
cursorY = e.pageY;
})
setInterval(function(){
var newX = p.offsetLeft + (cursorX - lastX)/10
var newY = p.offsetTop + (cursorY - lastY)/10
p.style.left = newX+'px'
p.style.top = newY+'px'
lastX = p.offsetLeft
lastY = p.offsetTop
},20)

animating dots in canvas, strange line behaviours with speed of dots > 1 by frame

Trying to animate dots(imageData 1x1) on a javascript canvas to make a starfield.
The strange thing is when those dots move at a speed higher than 1, there is like a flickering or anything else showing not a dot but a line.
here is a fiddle to show the strangeness: http://jsfiddle.net/xp6xd8q1/1/
function clearCanvas() {
ctx.fillStyle = '#000000';
ctx.fillRect(0,0,w,h);
}
function stars() {
this.manyStars = [];
this.addStars = function(nb) {
var i,x,y;
for(i=0;i<nb;i++) {
x = Math.floor(Math.random() * w);
y = Math.floor(Math.random() * h);
this.manyStars.push({x: x,y: y,s: 5}); // dot speed is s
}
}
this.move = function() {
var i,l;
for(i=0,l = this.manyStars.length;i<l;i++) {
this.manyStars[i].x = this.manyStars[i].x - this.manyStars[i].s;
if(this.manyStars[i].x < 0) {
this.manyStars[i].x = w + this.manyStars[i].x;
}
}
}
this.drawStars = function() {
var i,l;
for(i=0,l = this.manyStars.length;i<l;i++) {
ctx.putImageData(dot,this.manyStars[i].x,this.manyStars[i].y);
}
}
}
function run() {
clearCanvas();
s.move();
s.drawStars();
window.requestAnimationFrame(run);
}
//
window.requestAnimationFrame = window.requestAnimationFrame||window.mozRequestAnimationFrame ||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame;
var cv = document.createElement('canvas');
var w = window.innerWidth, h = window.innerHeight;
cv.width = w;
cv.height = h;
var ctx = cv.getContext('2d');
document.body.appendChild(cv);
//
var dot = ctx.createImageData(1,1);
dot.data = [255,255,255,255];
s = new stars();
s.addStars(10);
window.requestAnimationFrame(run);
Any idea on this is very welcomed !
I see it too. The dots appear to be stretched when they move. I screenshotted the canvas at several speeds. The dots really are just 1x1 pixel.
I believe you may be experiencing Display Motion Blur. It's a result of how displays work, and also because the vision cells in your eye take a bit of time to readjust when light changes.
There's not really much you can do about that, except try to hide it. It becomes less and less apparent the larger your moving objects are, and the slower they move.
It also becomes less apparent when the display refresh rate increases. See this example. Since you can't control the users' monitor's refresh rates, this doesn't really help you.

Mouse Proximity to object Javascript

I'm struggling with this for a while now. I'm drawing a grid on the canvas. I add a mousemove eventHandler and track the mouseX and mouseY positions. I would like to be able to calculate the distance from the mouse position to the item in the grid. I can't seem to get this right, I've tried a few different solutions, like adding a loop in the mousemove handler and using requestAnimationFrame, but both solutions are very slow.
Here's my code below:
function setupCanvas(){
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
blockWidth = width/2 - 150;
blockHeight = height/2 - 100;
gridArray = [];
gridWidthArray = [];
ctx.fillRect(0,0,width,height);
//drawGrid();
drawInitGrid();
canvas.addEventListener('mousemove',onMouseMoveHandler);
}
function drawInitGrid(){
for(x = 0; x<16; x++){
for(y = 0; y<11; y++){
var gridBlock = new GridBlock((blockWidth) + x*20, (blockHeight) + y*20, blockWidth, blockHeight);
gridBlock.render(ctx);
gridArray.push(gridBlock);
//gridWidthArray.push(gridBlock.xPos)
}
}
}
function onMouseMoveHandler(e){
if(containerBounds(e)){
mouseX = e.offsetX;
mouseY = e.offsetY;
console.log(mouseX, mouseY);
//console.log(gridWidthArray);
for(var grid in gridArray){
//console.log(gridArray[grid].xPos)
}
}
}
I've also tried adding a mouseevent in the GridBlock object, but that also doesn't seem to work.
You can calculate the distance between any 2 points like this:
var dx=point2.x-point1.x;
var dy=point2.y-point1.y;
var distance=Math.sqrt(dx*dx+dy*dy);
Also in your fiddle your mouse position calculation should account for the offset position of the canvas within the window:
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
function onMouseMoveHandler(e){
var mouseX=parseInt(e.clientX-offsetX);
var mouseY=parseInt(e.clientY-offsetY);
}
[ Finding nearest point in grid ]
Assume you have a mouse position [mx,my] and assume you have a grid with its top-left at [0,0] and with its cell size at cellWidth X cellHeight.
Then you can calculate the grid cell closest to the mouse like this:
var cellX=parseInt((mx+cellWidth/2)/cellWidth)*cellWidth;
var cellY=parseInt((my+cellHeight/2)/cellHeight)*cellHeight;
Of course, if the grid's top-left is not at [0,0], you will have to adjust for the gridss offset.

Grass like smoothing animation on beziercurve?

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 structurised code of the above code --My hair animation(by markE)--markE`s code of hair animation
PROBLEM:--
I am able to give movements to hairs but animation should be more like wavy grass like freeflowing.Its not very smooth now.What can be done to make the hairs flow in more natural manner.
Please provide me with a small sample if possible!!!
<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"
}
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);
})();
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/
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.
Darn! Late to the party...
But LOTS of neat answers here -- I'm upvoting all !
Anyway, here's my idea:
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>
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;
}
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/

Categories