Display the mouse pointer trails... of the future!
Javascript
My program predicts the direction of the pointer by using the average of the angular change in direction of the last 20 mouse moves. It also uses the variance of the angular change to create a "cloud" of possible locations and directions of the pointer. The color of each pointer in the "cloud" is supposed to represent the likelihood of it being the new position of the mouse pointer, where darker colors represents a greater likelihood. The distance of the pointer cloud ahead of the mouse is calculated using the speed of mouse movement. It doesn't make the best predictions but it looks neat.
Here's a fiddle: http://jsfiddle.net/5hs64t7w/4/
Increasing the size of the pointer cloud is interesting to see. It can be set by changing the cloudSize
variable on the first line of the program. Here is a fiddle with a cloud size of 10: http://jsfiddle.net/5hs64t7w/5/
I used these sources to get formulas for circular mean and variance:
Circular Mean: http://en.wikipedia.org/wiki/Circular_mean
Circular Variance: http://www.ebi.ac.uk/thornton-srv/software/PROCHECK/nmr_manual/man_cv.html
Here is the code if anyone is interested:
var cloudSize = 3;
var canvas = document.getElementById('canvas_element');
var c = canvas.getContext('2d');
var prevX = -1;
var prevY = -1;
var curX = -1;
var curY = -1;
var distance = 0;
var direction = 0;
function drawMouse(x, y, angle, gray){
var grayVal = Math.round(gray*255);
var grayString = "rgb(" + grayVal + "," + grayVal +"," + grayVal + ")";
c.fillStyle = grayString;
c.strokeStyle = grayString;
c.lineWidth = 1;
c.beginPath();
c.moveTo(x, y);
c.lineTo(x + 16*Math.cos(angle + Math.PI/2.0 + Math.PI/8.0), y + 16*Math.sin(angle + Math.PI/2.0 + Math.PI/8.0));
c.moveTo(x, y);
c.lineTo(x + 16*Math.cos(angle + Math.PI/2.0 - Math.PI/8.0), y + 16*Math.sin(angle + Math.PI/2.0 - Math.PI/8.0));
c.lineTo(x + 16*Math.cos(angle + Math.PI/2.0 + Math.PI/8.0), y + 16*Math.sin(angle + Math.PI/2.0 + Math.PI/8.0));
c.stroke();
c.fill();
c.beginPath();
c.moveTo(x, y);
c.lineTo(x + 24*Math.cos(angle + Math.PI/2), y + 24*Math.sin(angle + Math.PI/2));
c.stroke();
}
function sum(array){
var s = 0.0;
for(var i=0; i<array.length; i++){
s += array[i];
}
return s;
}
var sins = [];
var coss = [];
var lengths = [];
var times = [];
var index = 0;
var limit = 20;
var variance = 0;
var prevTime = new Date().getTime();
function updateDistanceAndDirection(x, y){
var angle = Math.atan2(prevY - curY, prevX - curX);
sins[index] = Math.sin(angle);
coss[index] = Math.cos(angle);
lengths[index] = Math.sqrt((curX-prevX)*(curX-prevX) + (curY-prevY)*(curY-prevY));
var time = new Date().getTime();
times[index] = time - prevTime;
variance = 1.0 - Math.sqrt(sum(coss)*sum(coss)+sum(sins)*sum(sins))/sins.length;
direction = Math.atan2(1/sins.length*sum(sins),1/coss.length*sum(coss));
var speed = sum(lengths)/(sum(times)/200);
distance = Math.min(Math.max(40, speed), 100);
prevTime = time;
index = (index+1)%limit;
}
function drawMice(count){
c.clearRect(0, 0, canvas.width, canvas.height);
for(var i=count; i>=0; i--){
var dir = direction + i*variance;
drawMouse(curX - distance*Math.cos(dir), curY - distance*Math.sin(dir), dir - Math.PI/2, i/count);
dir = direction - i*variance;
drawMouse(curX - distance*Math.cos(dir), curY - distance*Math.sin(dir), dir - Math.PI/2, i/count);
}
}
canvas.onmousemove = function (event) {
curX = event.clientX;
curY = event.clientY;
updateDistanceAndDirection(curX, curY);
drawMice(cloudSize);
prevX = curX;
prevY = curY;
};
Java
I decided to take the time machine approach. It turns out the key ingredient of a time machine is java.awt.Robot. My program lets you move your mouse around for 10 seconds. After the 10 seconds it goes back in time and recreates your mouse movement, while predicting it perfectly.
Here's the code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class TimeMachine extends JPanel implements MouseMotionListener {
Timer timer;
int time = 10;
java.util.Timer taskTimer;
ArrayList<Point> mousePoints;
ArrayList<Long> times;
Robot robot;
int width, height;
ArrayList<Point> drawMousePoints;
public TimeMachine(){
width = 500;
height = 500;
drawMousePoints = new ArrayList<Point>();
robot = null;
try{
robot = new Robot();
}
catch(Exception e){
System.out.println("The time machine malfunctioned... Reverting to 512 BC");
}
mousePoints = new ArrayList<Point>();
times = new ArrayList<Long>();
taskTimer = new java.util.Timer();
ActionListener al = new ActionListener(){
public void actionPerformed(ActionEvent e){
time--;
if(time == 0)
rewind();
repaint();
}
};
timer = new Timer(1000, al);
start();
}
public void paint(Graphics g){
g.clearRect(0, 0, width, height);
g.drawString("Time Machine activiates in: " + time, 15, 50);
for(int i=0; i<drawMousePoints.size(); i++){
Point drawMousePoint = drawMousePoints.get(i);
drawMouse(drawMousePoint.x-getLocationOnScreen().x, drawMousePoint.y-getLocationOnScreen().y, g, Color.BLACK, Color.LIGHT_GRAY, (double)i/drawMousePoints.size());
}
}
public void drawMouse(int x, int y, Graphics g, Color line, Color fill, double alpha){
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(new Color(fill.getRed(), fill.getGreen(), fill.getBlue(), (int)Math.max(Math.min(alpha*255, 255), 0)));
g2d.fillPolygon(new int[]{x, x, x+4, x+8, x+10, x+7, x+12}, new int[]{y, y+16, y+13, y+20, y+19, y+12, y+12}, 7);
g2d.setColor(new Color(line.getRed(), line.getGreen(), line.getBlue(), (int)Math.max(Math.min(alpha*255, 255), 0)));
g2d.drawLine(x, y, x, y + 16);
g2d.drawLine(x, y+16, x+4, y+13);
g2d.drawLine(x+4, y+13, x+8, y+20);
g2d.drawLine(x+8, y+20, x+10, y+19);
g2d.drawLine(x+10, y+19, x+7, y+12);
g2d.drawLine(x+7, y+12, x+12, y+12);
g2d.drawLine(x+12, y+12, x, y);
}
public void start(){
timer.start();
prevTime = System.currentTimeMillis();
mousePoints.clear();
}
public void rewind(){
timer.stop();
long timeSum = 0;
for(int i=0; i<times.size(); i++){
timeSum += times.get(0);
final boolean done = i == times.size()-1;
taskTimer.schedule(new TimerTask(){
public void run(){
Point point = mousePoints.remove(0);
drawMousePoints.clear();
drawMousePoints.addAll(mousePoints.subList(0, Math.min(mousePoints.size(), 30)));
robot.mouseMove(point.x, point.y);
repaint();
if(done)
System.exit(0);
}
}, timeSum);
}
}
long prevTime = 0;
public void record(MouseEvent m){
if(timer.isRunning()){
long time = System.currentTimeMillis();
mousePoints.add(new Point(m.getXOnScreen(), m.getYOnScreen()));
times.add((time-prevTime)/10);
prevTime = time;
}
}
public static void main(String[] args){
TimeMachine timeMachine = new TimeMachine();
JFrame frame = new JFrame("Time Machine");
frame.setSize(timeMachine.width, timeMachine.height);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.addMouseMotionListener(timeMachine);
frame.add(timeMachine);
}
public void mouseDragged(MouseEvent m) {
record(m);
}
public void mouseMoved(MouseEvent m) {
record(m);
}
}
Vanilla Javascript
Just to get things started, here is a simple prediction based on two values. The last n
mouse postions are memorized and kept in a queue, the prediction is a simple linear extrapolation of the first and last element in the queue.
This is just the prediction code, the full code including the demo can be seen in this fiddle:
function predict(trail) {
var b = trail.pop(),
a = trail[0],
d = {
x: b.x - a.x,
y: b.y - a.y
},
m = Math.sqrt( d.x * d.x + d.y * d.y );
d.x = 5 * d.x / m;
d.y = 5 * d.y / m;
var predictions = [];
for(var i = 1; i <= 10; i++) {
predictions.push({
x: b.x + i * d.x,
y: b.y + i * d.y
});
}
return predictions;
}
The demo contains a comment in the prediction that allows you to instead use the last two elements in the queue for the prediction. Makes the result more "real-time", but also less "smooth".
If anyone wants to use the boilerplate work to implement a different prediction algorithm, feel free. It's not a lot of work anyway.