Created
September 3, 2017 00:19
-
-
Save MegaLoler/01ec3d27d3d50da7cf5b40fae10014c2 to your computer and use it in GitHub Desktop.
Bouncing point mass structures on springs in a 3d tunnel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Bouncing Vubes are Godo</title> | |
<style> | |
#id | |
{ | |
padding: 0px; | |
margin: 0px; | |
border: 0px; | |
} | |
body | |
{ | |
padding: 0px; | |
margin: 0px; | |
border: 0px; | |
} | |
</style> | |
<script> | |
var canvas, context; | |
var simulation; | |
var pointRadius = 2; | |
var mouseZ = 0; | |
var mouseRadius = 32; | |
var touchingPoint = null; | |
var mouseConstraint = new Constraint(null, new Point([0, 0, 0]), 0); | |
var roundingTolerance = 1000000; | |
window.onmousedown = function() | |
{ | |
mouseConstraint.a = touchingPoint; | |
} | |
window.onmouseup = function() | |
{ | |
mouseConstraint.a = null; | |
} | |
function unit() | |
{ | |
return context.canvas.height / 2.0; | |
} | |
function unitToPixel(value, context) | |
{ | |
return value * unit(); | |
} | |
function pixelToUnit(value, context) | |
{ | |
return value / unit(); | |
} | |
function pixelToVector(vector, context) | |
{ | |
var converted = []; | |
for(var value of vector) | |
{ | |
converted.push(pixelToUnit(value, context)); | |
} | |
return converted; | |
} | |
function vectorToPixel(vector, context) | |
{ | |
var converted = []; | |
for(var value of vector) | |
{ | |
converted.push(unitToPixel(value, context)); | |
} | |
return converted; | |
} | |
function centerVector(vector, context) | |
{ | |
return [vector[0] + context.canvas.width / 2.0, vector[1] + context.canvas.height / 2.0].concat(vector.slice(2)); | |
} | |
function decenterVector(vector, context) | |
{ | |
return [vector[0] - context.canvas.width / 2.0, vector[1] - context.canvas.height / 2.0]; | |
} | |
function projectVector(vector, fieldOfView) | |
{ | |
var result = vector.slice(); // copy | |
while(result.length > 2) | |
{ | |
var distance = result.pop(); | |
var mul = Math.pow(fieldOfView, distance); | |
for(var i in result) | |
{ | |
result[i] *= mul; | |
} | |
} | |
return result; | |
} | |
function vectorToPixelAndCenter(vector, context) | |
{ | |
return centerVector(vectorToPixel(vector, context), context); | |
} | |
function vectorProjectToPixelAndCenter(vector, context, fieldOfView) | |
{ | |
return centerVector(vectorToPixel(projectVector(vector, fieldOfView), context), context); | |
} | |
function arraySafeGet(array, index) | |
{ | |
if(array[index] == undefined) | |
{ | |
return 0; | |
} | |
else | |
{ | |
return array[index]; | |
} | |
} | |
function vectorAdd(a, b) | |
{ | |
var result = []; | |
var longest; | |
if(a.length > b.length) | |
{ | |
longest = a; | |
} | |
else | |
{ | |
longest = b; | |
} | |
for(var i in longest) | |
{ | |
var j = arraySafeGet(a, i) + arraySafeGet(b, i); | |
result.push(j); | |
} | |
return result; | |
} | |
function vectorSubtract(a, b) | |
{ | |
var result = []; | |
var longest; | |
if(a.length > b.length) | |
{ | |
longest = a; | |
} | |
else | |
{ | |
longest = b; | |
} | |
for(var i in longest) | |
{ | |
var j = arraySafeGet(a, i) - arraySafeGet(b, i); | |
result.push(j); | |
} | |
return result; | |
} | |
function vectorMultiply(a, b) | |
{ | |
var result = []; | |
var longest; | |
if(a.length > b.length) | |
{ | |
longest = a; | |
} | |
else | |
{ | |
longest = b; | |
} | |
for(var i in longest) | |
{ | |
var j = arraySafeGet(a, i) * arraySafeGet(b, i); | |
result.push(j); | |
} | |
return result; | |
} | |
function vectorDivide(a, b) | |
{ | |
var result = []; | |
var longest; | |
if(a.length > b.length) | |
{ | |
longest = a; | |
} | |
else | |
{ | |
longest = b; | |
} | |
for(var i in longest) | |
{ | |
var j = arraySafeGet(a, i) / arraySafeGet(b, i); | |
result.push(j); | |
} | |
return result; | |
} | |
function vectorMultiplyConstant(vector, coefficient) | |
{ | |
var result = []; | |
for(var value of vector) | |
{ | |
result.push(value * coefficient); | |
} | |
return result; | |
} | |
function vectorDivideConstant(vector, coefficient) | |
{ | |
var result = []; | |
for(var value of vector) | |
{ | |
result.push(value / coefficient); | |
} | |
return result; | |
} | |
function getDistance(a, b) | |
{ | |
var difference = vectorSubtract(a, b); | |
var sum = 0; | |
for(var value of difference) | |
{ | |
sum += Math.pow(value, 2); | |
} | |
return Math.sqrt(sum); | |
} | |
function Point(position) | |
{ | |
this.position = position; | |
this.previousPosition = position; | |
this.static = false; | |
this.mass = 1; | |
this.getVelocity = Point_getVelocity; | |
this.integrate = Point_integrate; | |
this.draw = Point_draw; | |
this.getPixelPosition = Point_getPixelPosition; | |
} | |
function Point_getVelocity() | |
{ | |
return vectorSubtract(this.position, this.previousPosition) | |
} | |
function Point_getPixelPosition(fieldOfView) | |
{ | |
return vectorProjectToPixelAndCenter(this.position, context, fieldOfView); | |
} | |
function Point_draw(context, fieldOfView) | |
{ | |
var position = this.getPixelPosition(fieldOfView); | |
var x = position[0] - pointRadius; | |
var y = position[1] - pointRadius; | |
context.fillRect(x, y, pointRadius * 2 + 1, pointRadius * 2 + 1); | |
} | |
function Point_integrate() | |
{ | |
if(this.static) return; | |
var currentPosition = this.position; | |
this.position = vectorAdd(this.position, this.getVelocity()); | |
this.previousPosition = currentPosition; | |
} | |
function Constraint(a, b, distance) | |
{ | |
this.a = a; | |
this.b = b; | |
this.distance = distance; | |
this.impulseDivision = 10; | |
this.apply = Constraint_apply; | |
this.draw = Constraint_draw; | |
} | |
function Constraint_draw(context, fieldOfView) | |
{ | |
if(this.a == null || this.b == null) return; | |
var position1 = vectorProjectToPixelAndCenter(this.a.position, context, fieldOfView); | |
var position2 = vectorProjectToPixelAndCenter(this.b.position, context, fieldOfView); | |
context.beginPath(); | |
context.moveTo(position1[0], position1[1]); | |
context.lineTo(position2[0], position2[1]); | |
context.stroke(); | |
} | |
function Constraint_apply() | |
{ | |
if(this.a == null || this.b == null) return; | |
var distance = getDistance(this.a.position, this.b.position); | |
if(distance == 0) return; | |
var penetration = distance - this.distance; | |
var direction = vectorSubtract(this.b.position, this.a.position); | |
direction = vectorDivideConstant(direction, distance); | |
var force = penetration / this.impulseDivision; | |
var totalMass = this.a.mass + this.b.mass; | |
var weightA = this.b.mass / totalMass; | |
var weightB = this.a.mass / totalMass; | |
var forceA = vectorMultiplyConstant(direction, force * weightA); | |
var forceB = vectorMultiplyConstant(direction, -force * weightB); | |
this.a.position = vectorAdd(this.a.position, forceA); | |
this.b.position = vectorAdd(this.b.position, forceB); | |
} | |
function Simulation() | |
{ | |
this.sweeps = 5; | |
this.gravity = 0.00008; | |
this.airFriction = 0.005; | |
this.collisionImpulseDivision = 3; | |
this.borderDistance = 0.5; | |
this.fieldOfView = 2; | |
this.points = []; | |
this.constraints = []; | |
this.draw = Simulation_draw; | |
this.update = Simulation_update; | |
this.step = Simulation_step; | |
this.getPointReflection1 = Simulation_getPointReflection1; | |
this.getPointReflection2 = Simulation_getPointReflection2; | |
this.getPointReflection3 = Simulation_getPointReflection3; | |
this.getPointReflection4 = Simulation_getPointReflection4; | |
this.getConstraintReflection1 = Simulation_getConstraintReflection1; | |
this.getConstraintReflection2 = Simulation_getConstraintReflection2; | |
this.getConstraintReflection3 = Simulation_getConstraintReflection3; | |
this.getConstraintReflection4 = Simulation_getConstraintReflection4; | |
} | |
function Simulation_getPointReflection1(point) | |
{ | |
var reflectionPosition = vectorAdd(point.position, [0, this.borderDistance]); | |
var multiplyMask = Array(reflectionPosition.length).fill(1); | |
multiplyMask[1] = -1; | |
reflectionPosition = vectorMultiply(reflectionPosition, multiplyMask); | |
reflectionPosition = vectorAdd(reflectionPosition, [0, -this.borderDistance]); | |
return new Point(reflectionPosition); | |
} | |
function Simulation_getPointReflection2(point) | |
{ | |
var reflectionPosition = vectorAdd(point.position, [0, -this.borderDistance]); | |
var multiplyMask = Array(reflectionPosition.length).fill(1); | |
multiplyMask[1] = -1; | |
reflectionPosition = vectorMultiply(reflectionPosition, multiplyMask); | |
reflectionPosition = vectorAdd(reflectionPosition, [0, this.borderDistance]); | |
return new Point(reflectionPosition); | |
} | |
function Simulation_getPointReflection3(point) | |
{ | |
var ratio = context.canvas.width / context.canvas.height; | |
var reflectionPosition = vectorAdd(point.position, [this.borderDistance * ratio]); | |
var multiplyMask = Array(reflectionPosition.length).fill(1); | |
multiplyMask[0] = -1; | |
reflectionPosition = vectorMultiply(reflectionPosition, multiplyMask); | |
reflectionPosition = vectorAdd(reflectionPosition, [-this.borderDistance * ratio]); | |
return new Point(reflectionPosition); | |
} | |
function Simulation_getPointReflection4(point) | |
{ | |
var ratio = context.canvas.width / context.canvas.height; | |
var reflectionPosition = vectorAdd(point.position, [-this.borderDistance * ratio]); | |
var multiplyMask = Array(reflectionPosition.length).fill(1); | |
multiplyMask[0] = -1; | |
reflectionPosition = vectorMultiply(reflectionPosition, multiplyMask); | |
reflectionPosition = vectorAdd(reflectionPosition, [this.borderDistance * ratio]); | |
return new Point(reflectionPosition); | |
} | |
function Simulation_getConstraintReflection1(constraint) | |
{ | |
return new Constraint(this.getPointReflection1(constraint.a), this.getPointReflection1(constraint.b), 0); | |
} | |
function Simulation_getConstraintReflection2(constraint) | |
{ | |
return new Constraint(this.getPointReflection2(constraint.a), this.getPointReflection2(constraint.b), 0); | |
} | |
function Simulation_getConstraintReflection3(constraint) | |
{ | |
return new Constraint(this.getPointReflection3(constraint.a), this.getPointReflection3(constraint.b), 0); | |
} | |
function Simulation_getConstraintReflection4(constraint) | |
{ | |
return new Constraint(this.getPointReflection4(constraint.a), this.getPointReflection4(constraint.b), 0); | |
} | |
function Simulation_draw(context) | |
{ | |
for(var constraint of this.constraints) | |
{ | |
// reflection | |
context.strokeStyle = "#888888"; | |
if(constraint.a != null && constraint.b != null) | |
{ | |
this.getConstraintReflection1(constraint).draw(context, this.fieldOfView); | |
this.getConstraintReflection2(constraint).draw(context, this.fieldOfView); | |
this.getConstraintReflection3(constraint).draw(context, this.fieldOfView); | |
this.getConstraintReflection4(constraint).draw(context, this.fieldOfView); | |
} | |
} | |
touchingPoint = null; | |
for(var point of this.points) | |
{ | |
// reflection | |
context.fillStyle = "#888888"; | |
this.getPointReflection1(point).draw(context, this.fieldOfView); | |
this.getPointReflection2(point).draw(context, this.fieldOfView); | |
this.getPointReflection3(point).draw(context, this.fieldOfView); | |
this.getPointReflection4(point).draw(context, this.fieldOfView); | |
} | |
for(var i = 0; i < 24; i++) | |
{ | |
var d = Math.pow(this.borderDistance, (i + 1) / 2); | |
context.strokeStyle = "rgba(255, 100, 0, 1)"; | |
var ratio = context.canvas.width / context.canvas.height; | |
var x = (context.canvas.width - context.canvas.width * d) / 2; | |
var y = (context.canvas.height - context.canvas.height * d) / 2; | |
context.strokeRect(x, y, context.canvas.width * d, context.canvas.height * d); | |
context.strokeStyle = "rgba(255, 100, 0, " + (d * 0.5 + 0.01) + ")"; | |
context.beginPath(); | |
context.moveTo(x, 0); | |
context.lineTo(x, context.canvas.height); | |
context.stroke(); | |
context.beginPath(); | |
context.moveTo(context.canvas.width - x, 0); | |
context.lineTo(context.canvas.width - x, context.canvas.height); | |
context.stroke(); | |
context.beginPath(); | |
context.moveTo(0, y); | |
context.lineTo(context.canvas.width, y); | |
context.stroke(); | |
context.beginPath(); | |
context.moveTo(0, context.canvas.height - y); | |
context.lineTo(context.canvas.width, context.canvas.height - y); | |
context.stroke(); | |
} | |
context.strokeStyle = "rgba(255, 100, 0, 1)"; | |
context.beginPath(); | |
context.moveTo(0, 0); | |
context.lineTo(context.canvas.width, context.canvas.height); | |
context.stroke(); | |
context.beginPath(); | |
context.moveTo(0, context.canvas.height); | |
context.lineTo(context.canvas.width, 0); | |
context.stroke(); | |
for(var constraint of this.constraints) | |
{ | |
// non reflection | |
context.strokeStyle = "#000000"; | |
constraint.draw(context, this.fieldOfView); | |
} | |
touchingPoint = null; | |
for(var point of this.points) | |
{ | |
// non reflection | |
var position = point.getPixelPosition(this.fieldOfView); | |
var distance = getDistance(position, [mouseX, mouseY]); | |
if(distance < mouseRadius) | |
{ | |
context.fillStyle = "#FF0000"; | |
touchingPoint = point; | |
} | |
else | |
{ | |
context.fillStyle = "#000000"; | |
} | |
point.draw(context, this.fieldOfView); | |
} | |
} | |
function Simulation_step(context) | |
{ | |
for(var point of this.points) | |
{ | |
point.position[1] += this.gravity; | |
} | |
for(var point of this.points) | |
{ | |
var force = vectorMultiplyConstant(point.getVelocity(), -this.airFriction); | |
point.position = vectorAdd(point.position, force); | |
} | |
for(var constraint of this.constraints) | |
{ | |
constraint.apply(); | |
} | |
for(var point of this.points) | |
{ | |
var ratio = context.canvas.width / context.canvas.height; | |
var top = -this.borderDistance; | |
var bottom = this.borderDistance; | |
var left = top * ratio; | |
var right = bottom * ratio; | |
var x = point.position[0]; | |
var y = point.position[1]; | |
var topPenetration = top - y; | |
var bottomPenetration = y - bottom; | |
var leftPenetration = left - x; | |
var rightPenetration = x - right; | |
if(topPenetration > 0) | |
{ | |
point.position[1] += topPenetration / this.collisionImpulseDivision; | |
} | |
if(bottomPenetration > 0) | |
{ | |
point.position[1] -= bottomPenetration / this.collisionImpulseDivision; | |
} | |
if(leftPenetration > 0) | |
{ | |
point.position[0] += leftPenetration / this.collisionImpulseDivision; | |
} | |
if(rightPenetration > 0) | |
{ | |
point.position[0] -= rightPenetration / this.collisionImpulseDivision; | |
} | |
} | |
for(var constraint of this.constraints) | |
{ | |
constraint.apply(); | |
} | |
for(var point of this.points) | |
{ | |
point.integrate(); | |
} | |
} | |
function Simulation_update(context) | |
{ | |
for(var i = 0; i < this.sweeps; i++) | |
{ | |
this.step(context); | |
} | |
} | |
function draw() | |
{ | |
context.clearRect(0, 0, canvas.width, canvas.height); | |
simulation.draw(context); | |
} | |
function physics() | |
{ | |
simulation.update(context); | |
} | |
function loop() | |
{ | |
mouseConstraint.b.position = pixelToVector(decenterVector([mouseX, mouseY], context), context).concat([mouseZ]); | |
physics(); | |
draw(); | |
enterLoop(); | |
} | |
function enterLoop() | |
{ | |
window.requestAnimationFrame(loop); | |
} | |
function generateParallelConstraints(half1, half2, radius) | |
{ | |
// non cross section | |
/*for(var i in half1) | |
{ | |
var point1 = half1[i]; | |
var point2 = half2[i]; | |
var constraint = new Constraint(point1, point2, radius); | |
simulation.constraints.push(constraint); | |
}*/ | |
// cross section | |
for(var point1 of half1) | |
{ | |
for(var point2 of half2) | |
{ | |
var constraint = new Constraint(point1, point2, getDistance(point1.position, point2.position)); | |
simulation.constraints.push(constraint); | |
} | |
} | |
} | |
function generateNCube(n, radius, translation) | |
{ | |
if(n == 0) | |
{ | |
var point = new Point(translation); | |
simulation.points.push(point); | |
return [point]; | |
} | |
var translationHalf1 = Array(n).fill(0); | |
var translationHalf2 = Array(n).fill(0); | |
translationHalf1[n - 1] = -radius; | |
translationHalf2[n - 1] = radius; | |
var half1 = generateNCube(n - 1, radius, vectorAdd(translation, translationHalf1)); | |
var half2 = generateNCube(n - 1, radius, vectorAdd(translation, translationHalf2)); | |
generateParallelConstraints(half1, half2, radius * 2); | |
return half1.concat(half2); | |
} | |
function round(value, place) | |
{ | |
return Math.floor(value * place) / place; | |
} | |
function vectorIsEqualExceptIndex(a, b, exceptionIndex) | |
{ | |
for(var i = 0; i < a.length; i++) | |
{ | |
if(i == exceptionIndex) continue; | |
var value1 = round(a[i], roundingTolerance); | |
var value2 = round(b[i], roundingTolerance); | |
if(value1 != value2) return false; | |
} | |
return true; | |
} | |
function vectorIsEqual(a, b) | |
{ | |
for(var i = 0; i < a.length; i++) | |
{ | |
var value1 = round(a[i], roundingTolerance); | |
var value2 = round(b[i], roundingTolerance); | |
if(value1 != value2) return false; | |
} | |
return true; | |
} | |
function findPairPointAlongAxis(point, points, axis) | |
{ | |
for(var pair of points) | |
{ | |
if(vectorIsEqual(point.position, pair.position)) continue; | |
if(vectorIsEqualExceptIndex(point.position, pair.position, axis)) | |
{ | |
return pair; | |
} | |
} | |
// on of a kind | |
return point; | |
} | |
function reorderPointsForFlip(points, axis) | |
{ | |
console.log(points); | |
var reordered = []; | |
for(var point of points) | |
{ | |
var pair = findPairPointAlongAxis(point, points, axis); | |
reordered.push(pair); | |
} | |
return reordered; | |
} | |
// rotations = | |
// [0] = xy plane | |
// [1] = yz plane | |
// [2] = zw plane | |
// etc | |
function generateNSphere(n, radius, slices, translation, rotations) | |
{ | |
if(n == 1) | |
{ | |
var position1 = [radius]; | |
var position2 = [-radius]; | |
for(var i = 0; i < rotations.length; i++) | |
{ | |
var angle = rotations[i]; | |
var distance = position1[i]; | |
var positionDimension1 = Math.cos(angle) * distance; | |
var positionDimension2 = Math.sin(angle) * distance; | |
var rotationPlaneDimension1 = i; | |
var rotationPlaneDimension2 = i + 1; | |
position1[rotationPlaneDimension1] = positionDimension1; | |
position1[rotationPlaneDimension2] = positionDimension2; | |
position2[rotationPlaneDimension1] = -positionDimension1; | |
position2[rotationPlaneDimension2] = -positionDimension2; | |
} | |
var point1 = new Point(vectorAdd(translation, position1)); | |
var point2 = new Point(vectorAdd(translation, position2)); | |
simulation.points.push(point1); | |
simulation.points.push(point2); | |
// braces | |
//var constraint = new Constraint(point1, point2, getDistance(point1.position, point2.position)); | |
//simulation.constraints.push(constraint); | |
return [point1, point2]; | |
} | |
var totalPoints = []; | |
var previousPoints = null; | |
var originalPoints = null; | |
for(var i = 0; i < slices + 2; i++) | |
{ | |
if(i < slices + 1) | |
{ | |
rotations[n - 2] = Math.PI * (i / (slices + 1)); | |
var newPoints = generateNSphere(n - 1, radius, slices, translation, rotations); | |
} | |
else | |
{ | |
var newPoints = reorderPointsForFlip(originalPoints, n - 2); | |
} | |
if(previousPoints != null) | |
{ | |
// frame | |
/*for(var j = 0; j < newPoints.length; j++) | |
{ | |
var point1 = newPoints[j]; | |
var point2 = previousPoints[j]; | |
var constraint = new Constraint(point1, point2, getDistance(point1.position, point2.position)); | |
simulation.constraints.push(constraint); | |
}*/ | |
} | |
else | |
{ | |
originalPoints = newPoints; | |
} | |
if(i == slices + 1) break; | |
previousPoints = newPoints; | |
totalPoints = totalPoints.concat(newPoints); | |
} | |
return totalPoints; | |
} | |
function generateConstraintNet(points) | |
{ | |
for(var a of points) | |
{ | |
for(var b of points) | |
{ | |
if(a != b) | |
{ | |
var constraint = new Constraint(a, b, getDistance(a.position, b.position)); | |
simulation.constraints.push(constraint); | |
} | |
} | |
} | |
} | |
function deleteDuplicatePoints(points) | |
{ | |
for(var i = 0; i < points.length - 1; i++) | |
{ | |
var a = points[i]; | |
for(var j = i + 1; j < points.length; j++) | |
{ | |
var b = points[j]; | |
if(vectorIsEqual(a.position, b.position)) | |
{ | |
points.splice(j, 1); | |
j--; | |
} | |
} | |
} | |
} | |
function setup() | |
{ | |
canvas = document.getElementById("canvas"); | |
context = canvas.getContext("2d"); | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
mouseConstraint.impulseDivision = 100; | |
//mouseConstraint.b.position.mass = 100; | |
simulation = new Simulation(); | |
simulation.constraints.push(mouseConstraint); | |
simulation.points.push(mouseConstraint.b); | |
mouseConstraint.b.static = true; | |
generateNCube(3, 0.2, [-0.4, 0, -0.2]) | |
var spherePoints = generateNSphere(3, 0.2, 3, [0.4, 0, -0.2], []); | |
deleteDuplicatePoints(simulation.points); | |
deleteDuplicatePoints(spherePoints); | |
generateConstraintNet(spherePoints); | |
/*var a = new Point([-0.2, 0]); | |
var b = new Point([0.2, 0]); | |
simulation.points.push(a); | |
simulation.points.push(b); | |
simulation.constraints.push(new Constraint(a, b, 0.4));*/ | |
} | |
function init() | |
{ | |
setup(); | |
enterLoop(); | |
} | |
window.onload = init; | |
var mouseX, mouseY; | |
// STOLEN MOUSE TRACKING CODE BC LAZY >< | |
document.onmousemove = function(event) | |
{ | |
var dot, eventDoc, doc, body, pageX, pageY; | |
event = event || window.event; // IE-ism | |
// If pageX/Y aren't available and clientX/Y are, | |
// calculate pageX/Y - logic taken from jQuery. | |
// (This is to support old IE) | |
if (event.pageX == null && event.clientX != null) { | |
eventDoc = (event.target && event.target.ownerDocument) || document; | |
doc = eventDoc.documentElement; | |
body = eventDoc.body; | |
event.pageX = event.clientX + | |
(doc && doc.scrollLeft || body && body.scrollLeft || 0) - | |
(doc && doc.clientLeft || body && body.clientLeft || 0); | |
event.pageY = event.clientY + | |
(doc && doc.scrollTop || body && body.scrollTop || 0) - | |
(doc && doc.clientTop || body && body.clientTop || 0 ); | |
} | |
// Use event.pageX / event.pageY here | |
mouseX = event.pageX; | |
mouseY = event.pageY; | |
} | |
// OK THATS ALL TE STOLEN CODE | |
</script> | |
</head> | |
<body> | |
<canvas id="canvas"></canvas> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment