Converting 2D Image into 3D Using three.js and Other Tools [closed] - javascript

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I have an image of a data visualization that I want to make into 3D. I was wondering how I would be able to convert this image into 3D using three.js and other possible tools. I have no idea what to do.
I heard that there is something called WebGL Javascript API so maybe I can use that but I don't know how to begin and whether or not it can be used in this situation. I also read somewhere that you cannot make a 3D image from a 2D one without some information about the 3rd dimension. This missing information can come from a second photo, an AI software, or a 3D digital model.
I also read that I can make a 3D model of the image in Blender and then import it into three.js.
Does anyone know how I can do this?
I want to try to do the coding in JS fiddle if it's possible unless other software/tools are needed outside of JS fiddle. I see that you can import the three.js library there.
The current visualization I have made is
My code for this visualization if you need it is:
$(function() {
var dataEx = [
['1 Visit', 352000],
['2 Visits', 88000],
['3+ Visits', 42000]
],
len = dataEx.length,
sum = 0,
minHeight = 0.05,
data = [];
//specify your percent of prior visit value manually here:
var perc = [100, 25, 48];
for (var i = 0; i < len; i++) {
sum += dataEx[i][1];
}
for (var i = 0; i < len; i++) {
var t = dataEx[i],
r = t[1] / sum;
data[i] = {
name: t[0],
y: (r > minHeight ? t[1] : sum * minHeight),
percent: perc[i], // <----- this here is manual input
//percent: Math.round(r * 100), <--- this here is mathematical
label: t[1]
}
}
console.log(dataEx, data)
$('#container').highcharts({
chart: {
type: 'funnel',
marginRight: 100,
events: {
load: function() {
var chart = this;
Highcharts.each(chart.series[0].data, function(p, i) {
var bBox = p.dataLabel.getBBox()
p.dataLabel.attr({
x: (chart.plotWidth - chart.plotLeft) / 2,
'text-anchor': 'middle',
y: p.labelPos.y - (bBox.height / 2)
})
})
},
redraw: function() {
var chart = this;
Highcharts.each(chart.series[0].data, function(p, i) {
p.dataLabel.attr({
x: (chart.plotWidth - chart.plotLeft) / 2,
'text-anchor': 'middle',
y: p.labelPos.y - (bBox.height / 2)
})
})
}
},
},
//Manually changing the default colors of each category of series
colors: ['#FF5733', '#FFA533', '#1FC009'],
title: {
text: 'New Guest Return Funnel',
x: -45
},
credits: {
enabled: false
},
tooltip: {
//enabled: false
formatter: function() {
return '<b>' + this.key +
'</b><br/>Percent of Prior Visit: '+ this.point.percent + '%<br/>Guests: ' + Highcharts.numberFormat(this.point.label, 0);
}
},
plotOptions: {
series: {
allowPointSelect: true,
borderWidth: 12,
animation: {
duration: 400
},
dataLabels: {
enabled: true,
connectorWidth: 0,
distance: 0,
formatter: function() {
var point = this.point;
console.log(point);
return '<b>' + point.name + '</b> (' + Highcharts.numberFormat(point.label, 0) + ')<br/>' + point.percent + '%';
},
minSize: '10%',
color: 'black',
softConnector: true
},
neckWidth: '30%',
neckHeight: '0%',
width: '50%',
height: '110%'
//old options are as follows:
//neckWidth: '50%',
//neckHeight: '50%',
//-- Other available options
//height: '200'
// width: pixels or percent
}
},
legend: {
enabled: false
},
series: [{
name: 'Unique users',
data: data
}]
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="http://code.highcharts.com/modules/funnel.js"></script>
<script src="http://code.highcharts.com/modules/exporting.js"></script>
<div id="container" style="width: 500px; height: 400px; margin: 0 auto"></div>

I made you a quick and dirty example. Hopefully it gives you some ideas.
var dataEx = [
['1 Visit', 352000],
['2 Visits', 88000],
['3+ Visits', 42000]
],
len = dataEx.length,
sum = 0,
minHeight = 0.05,
data = [];
//specify your percent of prior visit value manually here:
var perc = [100, 25, 48];
for (var i = 0; i < len; i++) {
sum += dataEx[i][1];
}
for (var i = 0; i < len; i++) {
var t = dataEx[i],
r = t[1] / sum;
data[i] = {
name: t[0],
y: (r > minHeight ? t[1] : sum * minHeight),
percent: perc[i], // <----- this here is manual input
//percent: Math.round(r * 100), <--- this here is mathematical
label: t[1]
}
}
console.log(dataEx, data)
var renderer = new THREE.WebGLRenderer();
var w = 300;
var h = 200;
renderer.setSize(w, h);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
45, // Field of view
w / h, // Aspect ratio
0.1, // Near
10000 // Far
);
controls = new THREE.OrbitControls(camera, renderer.domElement);
camera.position.set(0, 20, 15);
camera.lookAt(new THREE.Vector3(0, 60, 0));
controls.target.set(0, 10, 0);
var light = new THREE.PointLight(0xFFFFFF);
light.position.set(20, 20, 20);
scene.add(light);
var light1 = new THREE.AmbientLight(0x808080);
light1.position.set(20, 20, 20);
scene.add(light1);
var light2 = new THREE.PointLight(0xFFFFFF);
light2.position.set(-20, 20, -20);
scene.add(light2);
var light3 = new THREE.PointLight(0xFFFFFF);
light3.position.set(-20, -20, -20);
scene.add(light3);
function makeCanvasTexture(color, text) {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 256;
var ctx = canvas.getContext('2d');
ctx.textAlign = "center";
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.font = "30px Arial";
ctx.fillText(text, (canvas.width / 2) | 0, (canvas.height / 2) | 0);
var tex = new THREE.Texture(canvas)
tex.minFilter = THREE.LinearMipMapLinearFilter;
tex.magFilter = THREE.LinearFilter;
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
tex.needsUpdate = true;
return tex;
}
function makePlane(color, text, percent, position) {
var geom = new THREE.PlaneGeometry(1, 1);
var material = new THREE.MeshLambertMaterial({
color: 'white',
side: THREE.DoubleSide,
map: makeCanvasTexture(color, text)
});
var npct = percent / 100;
material.map.repeat.set(1, npct);
material.map.offset.set(0, 0.5 * (1 - npct));
var mesh = new THREE.Mesh(geom, material);
mesh.position.y = position;
mesh.scale.y *= percent / 100;
return mesh;
}
renderer.setClearColor(0xdddddd, 1);
var root = new THREE.Object3D();
scene.add(root);
root.scale.multiplyScalar(10);
var yOffset = 0;
var colors = ['red', 'green', 'yellow']
for (var i = 0; i < data.length; i++) {
yOffset += data[i].percent / 200
var plane = makePlane(colors[i], data[i].name, data[i].percent, yOffset);
root.add(plane);
yOffset += data[i].percent / 200
yOffset += 0.05
}
(function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
var pnow = performance.now()*0.001;
controls.target.y = (Math.sin(pnow)*10)+10
})();
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>

Related

Integrate GSAP with canvas to make a curvy timeline

I'm currently working on a canvas timeline-like animation.
This is what I made so far...
$(function() {
'use strict';
var canvas = document.querySelector('#canvas');
var ctx = canvas.getContext('2d');
var s = 20;
var arr = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100];
var colorP = ['#ff5454', '#ffa144', '#ffe256', '#aaff75', '#8cd8ff', '#b5b6ff', '#b882ff'];
var dots = [];
var rDots = [];
function init() {
var reverse = true;
for (var i = 0; i < 100; i++) {
var dot = new Object();
var height = null;
if (arr.indexOf(i) != -1) {
dot.x = s;
dot.y = 50;
dot.r = 3;
dot.c = 'red';
dot.f = 'rgba(0,0,0,0)';
dot.t = '1';
dot.s = 0;
rDots.push(dot);
} else {
dot.x = s;
dot.y = 50;
dot.r = 1;
dot.c = 'red';
dot.f = '';
dot.t = '';
dot.s = 0;
}
s += 10;
dots.push(dot);
};
function tween() {
height = Math.floor(Math.random() * (75 - 25) + 25);
TweenMax.staggerTo(dots, 5, {
y: height,
yoyo: true,
repeat: 'repeat',
repeatDelay: 1,
ease: Sine.easeInOut
}, 0.5);
};
tween();
setInterval(function() {
tween()
}, 4800);
}
init();
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < dots.length - 1; i++) {
ctx.beginPath();
ctx.moveTo(dots[i].x, dots[i].y);
ctx.lineTo(dots[i + 1].x, dots[i + 1].y);
ctx.lineWidth = 3;
ctx.strokeStyle = 'red';
ctx.stroke();
};
for (var i = 0; i < dots.length; i++) {
ctx.beginPath();
ctx.arc(dots[i].x, dots[i].y, dots[i].r, 0, 2 * Math.PI);
ctx.strokeStyle = dots[i].c;
ctx.lineWidth = 1;
ctx.fillStyle = dots[i].f;
ctx.fill();
ctx.stroke();
ctx.font = dots[i].s + 'px Arial';
ctx.textAlign = 'center';
ctx.fillStyle = '#FFF';
ctx.fillText(dots[i].t, dots[i].x, dots[i].y + 4);
};
setTimeout(function() {
draw();
}, 5);
}
draw();
function hover(e, bool) {
var dot = canvas.getBoundingClientRect();
var x = e.clientX - dot.left;
var y = e.clientY - dot.top;
for (var i = 0; i < rDots.length; i++) {
if (x == rDots[i].x) {
TweenMax.to(rDots[i], 0.1, {
r: 10,
f: 'red',
s: 8
});
$('body').css('cursor', 'pointer');
} else {
TweenMax.to(rDots[i], 0.1, {
r: 3,
f: 'rgba(0,0,0,0)',
s: 0
});
}
};
};
$(canvas).on('mousemove', function(e) {
hover(e, true);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" height="100" width="1050" style="background: #EEE"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TweenMax.min.js"></script>
The idea is,
I want it to swing randomly (checked)
and when the cursor close in the knot, it will enlarge and show the text in it...
I tried to use x and y axis to do the trick,
but it doesn't work well...
Then I tried to make another function to draw a bigger circle to cover the original knot,
but since my draw() keeping clear the canvas, so I failed again...
Wondering is there any better ways to make it work?
any suggestions or hints are welcome!
You may find jCanvas helpful.
It's a JavaScript library that wraps the HTML5 canvas API, letting you add certain object-like functionality using a jQuery-style syntax. You could refactor your code a bit and use a mouseOver effect rather than binding a mousemove event to the canvas, which will let you create a smother animation.
Also, if you increase the area of the rDots.x that's triggering your animation and set your Tween time interval to something a bit longer than 0.1 that makes the animation slightly less jerky as well.
Not sure if this solves your issue, but I hope it helps!
Ok, I've work my way out.
$(function() {
'use strict';
var dots = [],
eDots = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100],
rDots = [],
stagger = 0;
var canvas = document.querySelector('#canvas'),
ctx = canvas.getContext('2d');
//initialize all the dots obj
function init() {
for (var i = 0; i < 100; i++) {
if (eDots.indexOf(i) != -1) {
var dot = {
xAxis: stagger,
yAxis: 50,
radius: 3,
color: 'rgba(0,0,0,0)',
num: 1,
};
rDots.push(dot);
} else {
var dot = {
xAxis: stagger,
yAxis: 50,
radius: .5,
color: 'rgba(0,0,0,0)',
num: ''
};
}
dots.push(dot);
stagger += 10;
}
};
init();
//Save position property for click event
function getSize() {
for (var i = 0; i < rDots.length; i++) {
rDots[i].top = rDots[i].yAxis - rDots[i].radius;
rDots[i].right = rDots[i].xAxis + rDots[i].radius;
rDots[i].bottom = rDots[i].yAxis + rDots[i].radius;
rDots[i].left = rDots[i].xAxis - rDots[i].radius;
}
}
getSize();
//Hover event dots to change style
function hover() {
$(canvas).bind('mousemove', function(e) {
var dot = canvas.getBoundingClientRect(),
x = e.clientX - dot.left,
y = e.clientY - dot.top;
for (var i = 0; i < rDots.length; i++) {
ctx.beginPath();
ctx.arc(rDots[i].xAxis, rDots[i].yAxis, rDots[i].radius, 0, 2 * Math.PI);
//rDots[i].radius = ctx.isPointInPath(x, y) ? 10 : 3;
//rDots[i].color = ctx.isPointInPath(x, y) ? 'red' : 'rgba(0, 0, 0, 0)';
if (ctx.isPointInPath(x, y)) {
TweenMax.to(rDots[i], 0.1, {
radius: 10,
color: 'red',
});
$(canvas).css({
cursor: 'pointer'
});
return;
} else {
TweenMax.to(rDots[i], 0.1, {
radius: 3,
color: 'rgba(0,0,0,0)'
});
}
ctx.stroke();
ctx.fill();
$(canvas).css({
cursor: 'default'
});
}
});
};
hover();
//Setup click event for functioning purpose
function click(e) {
var dot = canvas.getBoundingClientRect(),
x = e.clientX - dot.left,
y = e.clientY - dot.top;
for (var i = 0; i < rDots.length; i++) {
if (x < rDots[i].right && x > rDots[i].left && y > rDots[i].top && y < rDots[i].bottom) {
console.log('This is dot ' + i);
}
}
};
$(canvas).on('click', function(e) {
click(e);
})
//Let the line start to twist
function tween() {
var height = Math.floor(Math.random() * (75 - 25) + 25);
TweenMax.staggerTo(dots, 4, {
yAxis: height,
yoyo: true,
repeat: 'repeat',
repeatDelay: 1,
ease: Sine.easeInOut
}, 0.5);
setTimeout(function() {
tween();
}, 3800);
}
tween();
//Let's get some paint
function draw() {
//clear canvas for animate
ctx.clearRect(0, 0, canvas.width, canvas.height);
//draw the lines
for (var i = 0; i < dots.length - 1; i++) {
ctx.moveTo(dots[i].xAxis, dots[i].yAxis);
ctx.lineTo(dots[i + 1].xAxis, dots[i + 1].yAxis);
ctx.lineWidth = 3;
ctx.stroke();
}
//draw the dots
for (var i = 0; i < dots.length; i++) {
ctx.beginPath();
ctx.arc(dots[i].xAxis, dots[i].yAxis, dots[i].radius, 0, 2 * Math.PI);
ctx.strokeStyle = 'red';
ctx.strokeWidth = '1px';
ctx.fillStyle = dots[i].color;
ctx.stroke();
ctx.fill()
};
setTimeout(function() {
draw();
}, 10);
}
draw();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TweenMax.min.js"></script>
<canvas id="canvas" height="100" width="1000" style="background:#EEE"></canvas>
Turn's out all I need is to do is use isPointOnPath to get the path's axis,
then manipulate the certain dot's property with if statement, in my case is it's radius and color.
simple enough...
Can't believe I couldn't figured it out.
I guess I need some sleep now XD

Draggable sprites on a wallpaper

I'm trying to reproduce something like this: http://carbure.co/.
After inspection the website uses matter.js, a physics engine. Below is a (failed) code attempt, and I'm having trouble getting it to work given the terrible docs.
Does anyone have any idea how else I can achieve this?
Many thanks
$(window).load(function() {
var w = $(window).innerWidth();
var h = $(window).innerHeight();
// Matter.js module aliases
var Engine = Matter.Engine;
var World = Matter.World;
var Bodies = Matter.Bodies;
var Body = Matter.Body;
var Constraint = Matter.Constraint;
var Composite = Matter.Composite;
var Composites = Matter.Composites;
var MouseConstraint = Matter.MouseConstraint;
// create a Matter.js engine
var engine = Engine.create({
render: {
element: document.body,
options: {
width: w,
height: h,
wireframes: false,
background: '#fff'
}
}
});
// add a mouse controlled constraint
var mouseConstraint = MouseConstraint.create(engine);
World.add(engine.world, mouseConstraint);
var addToWorld = [];
// create random poly's and a ground
var ranPolygons = Math.random() * 10 + 5 >> 0;
var prevPoly;
for (var i = 0; i < ranPolygons; i++) {
var polyRadius = Math.random() * 40 + 40 >> 0;
var polySides = 1;
var x = Math.random() * (w - polyRadius * 2) + polyRadius >> 0;
var y = Math.random() * (h / 2 - polyRadius * 2) + polyRadius >> 0;
var isStatic = Math.random() * 1 < 0.2;
var poly = Bodies.polygon(x, y, polySides, polyRadius, {
render: {
fillStyle: isStatic ? '#0134CB' : makePattern(),
strokeStyle: isStatic ? 'transparent' : '#0134CB',
lineWidth: Math.random() * 5 + 2 >> 0
},
density: Math.random() * 0.1,
isStatic: isStatic,
restitution: Math.random() * 1
});
addToWorld.push(poly);
// add borders
var border = 5;
var halfBorder = border / 2;
var borders = [
Bodies.rectangle(w / 2, halfBorder, w + border, border, {
isStatic: true,
render: {
fillStyle: 'transparent',
strokeStyle: 'transparent'
}
}),
Bodies.rectangle(w / 2, h - halfBorder, w + border, border, {
isStatic: true,
render: {
fillStyle: 'transparent',
strokeStyle: 'transparent'
}
}),
Bodies.rectangle(halfBorder, h / 2, border, h + border, {
isStatic: true,
render: {
fillStyle: 'transparent',
strokeStyle: 'transparent'
}
}),
Bodies.rectangle(w - halfBorder, h / 2, border, h + border, {
isStatic: true,
render: {
fillStyle: 'transparent',
strokeStyle: 'transparent'
}
}),
];
addToWorld = addToWorld.concat(borders);
// add all of the bodies to the world
World.add(engine.world, addToWorld);
// run the engine
runner = Engine.run(engine)
// setTimeout(ranGrav, 2000);
engine.world.gravity.y = 0;
engine.world.gravity.x = 0;
$(engine.render.canvas).css({
width: '100%',
height: '100vh'
})
});
I got your code running. It had a number of issues. First and foremost, the missing bracket belonged to the loop:
for (var i = 0; i < ranPolygons; i++) {
Besides that I also had to run the renderer:
Render.run(render);
And I got rid of this bit, because it was unnecessary and was throwing a warning:
$(engine.render.canvas).css({
width: '100%',
height: '100vh'
});
https://jsfiddle.net/jx3vn7da/

How to change the color of this (pure js)?

I am attempting to implement this mousefollow into my website:
http://codepen.io/hakimel/pen/KanIi
But I would like my own custom colors instead of what is default displayed. I changed the fillcolor value and was able to change to one specific color but I would like 4 different colors. So I have tried adding specific colors to this function next to fillColor: but no such luck. I also created a fillcolor2: property like:
for (var i = 0; i < QUANTITY; i++) {
var particle = {
size: 1,
position: { x: mouseX, y: mouseY },
offset: { x: 0, y: 0 },
shift: { x: mouseX, y: mouseY },
speed: 0.01+Math.random()*0.04,
targetSize: 1,
fillColor: '#bf3e27',
fillColor2: '#1c305c',
orbit: RADIUS*.1 + (RADIUS * .5 * Math.random())
};
and added:
context.fillStyle = particle.fillColor2;
context.strokeStyle = particle.fillColor2;
But that did not work either. I have also tried to copy and paste the same js code just to see if it would work, and just changed the fillcolor, but it would only display the last one pasted.
Can anyone show me how to get 4 separate colors the easiest way, I feel like I am vastly over-complicating this but obviously a beginner and getting rather frustrated with this?
Lastly, I would like the 4 different colors to span different radii and I messed around with the different RADIUS variables but it is pretty much impossible to figure out how to accomplish what I would like while only having one color. So there will be 4 of each color, I changed the QUANTITY to:
var QUANTITY = 16;
I need the first 4 colors radius of 10 so for the first one I set:
var RADIUS = 10;
Ideally I need the first 4 to be color (#AAAAAA) radius of 10 like it is, but need the second 4 to be color (#BBBBBBB) between radius 10 and 30, the third color (#CCCCCC) to be between radius of 30-50, and the last fourth color (#DDDDDD) to be between 50 and 70.
Any suggestions?
You could replace the definition of QUANTITY, COLOR and RADIUS with an array of these, and at the same time define ranges for RADIUS:
var GROUPS = [
{
QUANTITY: 4,
RADIUS: [ 5, 10],
COLOR: 0x888888
},
{
QUANTITY: 4,
RADIUS: [10, 30],
COLOR: 0xAA80AA
},
{
QUANTITY: 4,
RADIUS: [30, 50],
COLOR: 0xA0A0CC
},
{
QUANTITY: 4,
RADIUS: [50, 70],
COLOR: 0xFFE0E0
}
];
Then inside the createParticles function you would iterate over those GROUPS:
for (var g = 0; g < GROUPS.length; g++) {
var attribs = GROUPS[g];
for (var i = 0; i < attribs.QUANTITY; i++) {
var particle = {
size: 1,
position: { x: mouseX, y: mouseY },
offset: { x: 0, y: 0 },
shift: { x: mouseX, y: mouseY },
speed: 0.01+Math.random()*0.04,
targetSize: 1,
fillColor: '#' + attribs.COLOR.toString(16),
orbit: attribs.RADIUS[0] +
(attribs.RADIUS[1]-attribs.RADIUS[0]) * Math.random()
};
particles.push( particle );
}
}
Here is a snippet:
// One of my first <canvas> experiments, woop! :D
var SCREEN_WIDTH = window.innerWidth;
var SCREEN_HEIGHT = window.innerHeight;
var GROUPS = [
{
QUANTITY: 4,
RADIUS: [ 5, 10],
COLOR: 0x888888
},
{
QUANTITY: 4,
RADIUS: [10, 30],
COLOR: 0xAA80AA
},
{
QUANTITY: 4,
RADIUS: [30, 50],
COLOR: 0xA0A0CC
},
{
QUANTITY: 4,
RADIUS: [50, 70],
COLOR: 0xFFE0E0
}
];
var RADIUS_SCALE = 1;
var RADIUS_SCALE_MIN = 1;
var RADIUS_SCALE_MAX = 1.5;
var canvas;
var context;
var particles;
var mouseX = SCREEN_WIDTH * 0.5;
var mouseY = SCREEN_HEIGHT * 0.5;
var mouseIsDown = false;
function init() {
canvas = document.getElementById( 'world' );
if (canvas && canvas.getContext) {
context = canvas.getContext('2d');
// Register event listeners
window.addEventListener('mousemove', documentMouseMoveHandler, false);
window.addEventListener('mousedown', documentMouseDownHandler, false);
window.addEventListener('mouseup', documentMouseUpHandler, false);
document.addEventListener('touchstart', documentTouchStartHandler, false);
document.addEventListener('touchmove', documentTouchMoveHandler, false);
window.addEventListener('resize', windowResizeHandler, false);
createParticles();
windowResizeHandler();
setInterval( loop, 1000 / 60 );
}
}
function createParticles() {
particles = [];
for (var g = 0; g < GROUPS.length; g++) {
var attribs = GROUPS[g];
for (var i = 0; i < attribs.QUANTITY; i++) {
var particle = {
size: 1,
position: { x: mouseX, y: mouseY },
offset: { x: 0, y: 0 },
shift: { x: mouseX, y: mouseY },
speed: 0.01+Math.random()*0.04,
targetSize: 1,
fillColor: '#' + attribs.COLOR.toString(16),
orbit: attribs.RADIUS[0] +
(attribs.RADIUS[1]-attribs.RADIUS[0]) * Math.random()
};
particles.push( particle );
}
}
}
function documentMouseMoveHandler(event) {
mouseX = event.clientX - (window.innerWidth - SCREEN_WIDTH) * .5;
mouseY = event.clientY - (window.innerHeight - SCREEN_HEIGHT) * .5;
}
function documentMouseDownHandler(event) {
mouseIsDown = true;
}
function documentMouseUpHandler(event) {
mouseIsDown = false;
}
function documentTouchStartHandler(event) {
if(event.touches.length == 1) {
event.preventDefault();
mouseX = event.touches[0].pageX - (window.innerWidth - SCREEN_WIDTH) * .5;;
mouseY = event.touches[0].pageY - (window.innerHeight - SCREEN_HEIGHT) * .5;
}
}
function documentTouchMoveHandler(event) {
if(event.touches.length == 1) {
event.preventDefault();
mouseX = event.touches[0].pageX - (window.innerWidth - SCREEN_WIDTH) * .5;;
mouseY = event.touches[0].pageY - (window.innerHeight - SCREEN_HEIGHT) * .5;
}
}
function windowResizeHandler() {
SCREEN_WIDTH = window.innerWidth;
SCREEN_HEIGHT = window.innerHeight;
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
}
function loop() {
if( mouseIsDown ) {
RADIUS_SCALE += ( RADIUS_SCALE_MAX - RADIUS_SCALE ) * (0.02);
}
else {
RADIUS_SCALE -= ( RADIUS_SCALE - RADIUS_SCALE_MIN ) * (0.02);
}
RADIUS_SCALE = Math.min( RADIUS_SCALE, RADIUS_SCALE_MAX );
context.fillStyle = 'rgba(0,0,0,0.05)';
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
for (i = 0, len = particles.length; i < len; i++) {
var particle = particles[i];
var lp = { x: particle.position.x, y: particle.position.y };
// Rotation
particle.offset.x += particle.speed;
particle.offset.y += particle.speed;
// Follow mouse with some lag
particle.shift.x += ( mouseX - particle.shift.x) * (particle.speed);
particle.shift.y += ( mouseY - particle.shift.y) * (particle.speed);
// Apply position
particle.position.x = particle.shift.x + Math.cos(i + particle.offset.x) * (particle.orbit*RADIUS_SCALE);
particle.position.y = particle.shift.y + Math.sin(i + particle.offset.y) * (particle.orbit*RADIUS_SCALE);
// Limit to screen bounds
particle.position.x = Math.max( Math.min( particle.position.x, SCREEN_WIDTH ), 0 );
particle.position.y = Math.max( Math.min( particle.position.y, SCREEN_HEIGHT ), 0 );
particle.size += ( particle.targetSize - particle.size ) * 0.05;
if( Math.round( particle.size ) == Math.round( particle.targetSize ) ) {
particle.targetSize = 1 + Math.random() * 7;
}
context.beginPath();
context.fillStyle = particle.fillColor;
context.strokeStyle = particle.fillColor;
context.lineWidth = particle.size;
context.moveTo(lp.x, lp.y);
context.lineTo(particle.position.x, particle.position.y);
context.stroke();
context.arc(particle.position.x, particle.position.y, particle.size/2, 0, Math.PI*2, true);
context.fill();
}
}
window.onload = init;
body {
background-color: #000000;
padding: 0;
margin: 0;
overflow: hidden;
}
<canvas id='world'></canvas>
Define the settings for the particles in an array
// I've changed the color for better visibility
var ParticleSettings = [
{color: "#f00", radiusMin: 10, radiusMax: 10},
{color: "#0f0", radiusMin: 10, radiusMax: 30},
{color: "#00f", radiusMin: 30, radiusMax: 50}
];
To get the correct settings for a particle (in this case its index) use this function
function getParticleSettings(particleIndex) {
var settingsIndex = Math.floor(particleIndex / 4) % ParticleSettings.length,
settings = ParticleSettings[settingsIndex];
return {
color: settings.color,
radius: getRandom(settings.radiusMin, settings.radiusMax)
};
}
getRandom is just a small helper function to get a random number between two boundaries
function getRandom(min, max) {
return Math.floor((Math.random() * (max - min)) + min)
}
In createParticle we then have to change the for loop to get the settings for the current particle
for (var i = 0; i < QUANTITY; i++) {
// get the settings for the current particle
var particleSettings = getParticleSettings(i);
var particle = {
size: 1,
position: { x: mouseX, y: mouseY },
offset: { x: 0, y: 0 },
shift: { x: mouseX, y: mouseY },
speed: 0.01+Math.random()*0.04,
targetSize: 1,
fillColor: particleSettings.color, // <--
orbit: particleSettings.radius // <--
};
particles.push( particle );
}
This would be the result of the changes above.
You could define the colors in an array since you are using a for loop you can index them like this:
var colors = ['#AAAAAA', '#BBBBBB', '#CCCCCC', '#DDDDDD'];
var particle = {
...
fillColor: colors[i],
...
}
This will now only work for the first four colors, but since you stated you have a quantity of 16, you could use 2 for loops to achieve this. Something like this:
//will run 16 times
for(var i = 0; i < QUANTITY; i++) {
for(var x = 0; x < 4; x++) {
var particle = {
//note that i'm using x here since the array has only 4 keys.
fillColor: colors[x];
}
}
}

kineticjs performance lag

I am working on a radial control similar to the HTML5 wheel of fortune example. I've modified the original here with an example of some additional functionality I require: http://jsfiddle.net/fEm9P/ When you click on the inner kinetic wedges they will shrink and expand within the larger wedges. Unfortunately when I rotate the wheel it lags behind the pointer. It's not too bad here but it's really noticeable on a mobile.
I know this is due to the fact that I'm not caching the wheel. When I do cache the wheel (uncomment lines 239-249) the inner wedges no longer respond to mouse/touch but the response on rotation is perfect. I have also tried adding the inner wedges to a separate layer and caching the main wheel only. I then rotate the inner wheel with the outer one. Doing it this way is a little better but still not viable on mobile.
Any suggestions would be greatly appreciated.
Stephen
//constants
var MAX_ANGULAR_VELOCITY = 360 * 5;
var NUM_WEDGES = 25;
var WHEEL_RADIUS = 410;
var ANGULAR_FRICTION = 0.2;
// globals
var angularVelocity = 360;
var lastRotation = 0;
var controlled = false;
var target, activeWedge, stage, layer, wheel,
pointer, pointerTween, startRotation, startX, startY;
var currentVolume, action;
function purifyColor(color) {
var randIndex = Math.round(Math.random() * 3);
color[randIndex] = 0;
return color;
}
function getRandomColor() {
var r = 100 + Math.round(Math.random() * 55);
var g = 100 + Math.round(Math.random() * 55);
var b = 100 + Math.round(Math.random() * 55);
var color = [r, g, b];
color = purifyColor(color);
color = purifyColor(color);
return color;
}
function bind() {
wheel.on('mousedown', function(evt) {
var mousePos = stage.getPointerPosition();
angularVelocity = 0;
controlled = true;
target = evt.targetNode;
startRotation = this.rotation();
startX = mousePos.x;
startY = mousePos.y;
});
// add listeners to container
document.body.addEventListener('mouseup', function() {
controlled = false;
action = null;
if(angularVelocity > MAX_ANGULAR_VELOCITY) {
angularVelocity = MAX_ANGULAR_VELOCITY;
}
else if(angularVelocity < -1 * MAX_ANGULAR_VELOCITY) {
angularVelocity = -1 * MAX_ANGULAR_VELOCITY;
}
angularVelocities = [];
}, false);
document.body.addEventListener('mousemove', function(evt) {
var mousePos = stage.getPointerPosition();
var x1, y1;
if(action == 'increase') {
x1 = (mousePos.x-(stage.getWidth() / 2));
y1 = (mousePos.y-WHEEL_RADIUS+20);
var r = Math.sqrt(x1 * x1 + y1 * y1);
if (r>500){
r=500;
} else if (r<100){
r=100;
};
currentVolume.setRadius(r);
layer.draw();
} else {
if(controlled && mousePos && target) {
x1 = mousePos.x - wheel.x();
y1 = mousePos.y - wheel.y();
var x2 = startX - wheel.x();
var y2 = startY - wheel.y();
var angle1 = Math.atan(y1 / x1) * 180 / Math.PI;
var angle2 = Math.atan(y2 / x2) * 180 / Math.PI;
var angleDiff = angle2 - angle1;
if ((x1 < 0 && x2 >=0) || (x2 < 0 && x1 >=0)) {
angleDiff += 180;
}
wheel.setRotation(startRotation - angleDiff);
}
};
}, false);
}
function getRandomReward() {
var mainDigit = Math.round(Math.random() * 9);
return mainDigit + '\n0\n0';
}
function addWedge(n) {
var s = getRandomColor();
var reward = getRandomReward();
var r = s[0];
var g = s[1];
var b = s[2];
var angle = 360 / NUM_WEDGES;
var endColor = 'rgb(' + r + ',' + g + ',' + b + ')';
r += 100;
g += 100;
b += 100;
var startColor = 'rgb(' + r + ',' + g + ',' + b + ')';
var wedge = new Kinetic.Group({
rotation: n * 360 / NUM_WEDGES,
});
var wedgeBackground = new Kinetic.Wedge({
radius: WHEEL_RADIUS,
angle: angle,
fillRadialGradientStartRadius: 0,
fillRadialGradientEndRadius: WHEEL_RADIUS,
fillRadialGradientColorStops: [0, startColor, 1, endColor],
fill: '#64e9f8',
fillPriority: 'radial-gradient',
stroke: '#ccc',
strokeWidth: 2,
rotation: (90 + angle/2) * -1
});
wedge.add(wedgeBackground);
var text = new Kinetic.Text({
text: reward,
fontFamily: 'Calibri',
fontSize: 50,
fill: 'white',
align: 'center',
stroke: 'yellow',
strokeWidth: 1,
listening: false
});
text.offsetX(text.width()/2);
text.offsetY(WHEEL_RADIUS - 15);
wedge.add(text);
volume = createVolumeControl(angle, endColor);
wedge.add(volume);
wheel.add(wedge);
}
var activeWedge;
function createVolumeControl(angle, colour){
var volume = new Kinetic.Wedge({
radius: 100,
angle: angle,
fill: colour,
stroke: '#000000',
rotation: (90 + angle/2) * -1
});
volume.on("mousedown touchstart", function() {
currentVolume = this;
action='increase';
});
return volume;
}
function animate(frame) {
// wheel
var angularVelocityChange = angularVelocity * frame.timeDiff * (1 - ANGULAR_FRICTION) / 1000;
angularVelocity -= angularVelocityChange;
if(controlled) {
angularVelocity = ((wheel.getRotation() - lastRotation) * 1000 / frame.timeDiff);
}
else {
wheel.rotate(frame.timeDiff * angularVelocity / 1000);
}
lastRotation = wheel.getRotation();
// pointer
var intersectedWedge = layer.getIntersection({x: stage.width()/2, y: 50});
if (intersectedWedge && (!activeWedge || activeWedge._id !== intersectedWedge._id)) {
pointerTween.reset();
pointerTween.play();
activeWedge = intersectedWedge;
}
}
function init() {
stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 500
});
layer = new Kinetic.Layer();
wheel = new Kinetic.Group({
x: stage.getWidth() / 2,
y: WHEEL_RADIUS + 20
});
for(var n = 0; n < NUM_WEDGES; n++) {
addWedge(n);
}
pointer = new Kinetic.Wedge({
fillRadialGradientStartPoint: 0,
fillRadialGradientStartRadius: 0,
fillRadialGradientEndPoint: 0,
fillRadialGradientEndRadius: 30,
fillRadialGradientColorStops: [0, 'white', 1, 'red'],
stroke: 'white',
strokeWidth: 2,
lineJoin: 'round',
angle: 30,
radius: 30,
x: stage.getWidth() / 2,
y: 20,
rotation: -105,
shadowColor: 'black',
shadowOffset: {x:3,y:3},
shadowBlur: 2,
shadowOpacity: 0.5
});
// add components to the stage
layer.add(wheel);
layer.add(pointer);
stage.add(layer);
pointerTween = new Kinetic.Tween({
node: pointer,
duration: 0.1,
easing: Kinetic.Easings.EaseInOut,
y: 30
});
pointerTween.finish();
var radiusPlus2 = WHEEL_RADIUS + 2;
wheel.cache({
x: -1* radiusPlus2,
y: -1* radiusPlus2,
width: radiusPlus2 * 2,
height: radiusPlus2 * 2
}).offset({
x: radiusPlus2,
y: radiusPlus2
});
layer.draw();
// bind events
bind();
var anim = new Kinetic.Animation(animate, layer);
//document.getElementById('debug').appendChild(layer.hitCanvas._canvas);
// wait one second and then spin the wheel
setTimeout(function() {
anim.start();
}, 1000);
}
init();
I made a couple of changes to the script which greatly improved the response time. The first was replacing layer.draw() with layer.batchDraw(). As the draw function was being called on each touchmove event it was making the interaction clunky. BatchDraw on the other hand will stack up draw requests internally "limit the number of redraws per second based on the maximum number of frames per second" (http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-batch-draw).
The jumping around of the canvas I seeing originally when I cached/cleared the wheel was due to the fact that I wasn't resetting the offset on the wheel when I cleared the cache.
http://jsfiddle.net/leydar/a7tkA/5
wheel.clearCache().offset({
x: 0,
y: 0
});
I hope this is of benefit to someone else. It's still not perfectly responsive but it's at least going in the right direction.
Stephen

Kinetic.js – creating a grid

I'm new to Kintetic.js and I'm trying to do a grid. The width is 800px and the height is 400px. And I want squares (20x20) to cover that area. Every square has a 1px border. So something like this:
var box = new Kinetic.Rect({
width: 20,
height: 20,
fill: 'transparent',
stroke: 'rgba(0, 0, 0, 0.02)'
});
And to fill the canvas, I have a crappy for-loop like this:
for (var i = 0; i <= this.field.getWidth(); i = i + 20) {
for (var i2 = 0; i2 <= this.field.getHeight(); i2 = i2 + 20) {
var cbox = box.clone({x: i, y: i2});
this.grid.add(cbox);
}
}
this.grid is a Kinetic.Layer. The first problem with this code is that it is very slow and I get like a 500ms delay before the grid shows up. But the worst thing is that if I put an mouseover and mouseout event on the cbox to change the fill color, that renders really really slow. This is how I do it:
cbox.on('mouseover', function () {
this.setFill('black');
self.grid.draw();
});
cbox.on('mouseout', function () {
this.setFill('transparent');
self.grid.draw();
});
So my question is how can I improve the code and performance of this?
How about to make grid with lines and use one rect for cursor highlighting?
Here i wrote the example for you:
http://jsfiddle.net/e_aksenov/R72Xu/30/
var CELL_SIZE = 35,
w = 4,
h = 5,
W = w * CELL_SIZE,
H = h * CELL_SIZE;
var make_grid = function(layer) {
var back = new Kinetic.Rect({
x: 0,
y: 0,
width: W,
height: H,
fill: "yellow"
});
layer.add(back);
for (i = 0; i < w + 1; i++) {
var I = i * CELL_SIZE;
var l = new Kinetic.Line({
stroke: "black",
points: [I, 0, I, H]
});
layer.add(l);
}
for (j = 0; j < h + 1; j++) {
var J = j * CELL_SIZE;
var l2 = new Kinetic.Line({
stroke: "black",
points: [0, J, W, J]
});
layer.add(l2);
}
return back; //to attach mouseover listener
};
var cursor_bind = function(layer, grid_rect, rect) {
grid_rect.on('mouseover', function(e) {
var rx = Math.floor(e.x / CELL_SIZE);
var ry = Math.floor(e.y / CELL_SIZE);
rect.setPosition(rx * CELL_SIZE, ry * CELL_SIZE);
layer.draw();
});
};
var stage = new Kinetic.Stage({
container: "kinetic",
width: 800,
height: 600,
draggable: true
});
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: 0,
y: 0,
width: CELL_SIZE,
height: CELL_SIZE,
fill: "#00D2FF",
stroke: "black",
strokeWidth: 4
});
var gr = make_grid(layer);
cursor_bind(layer, gr, rect);
// add the shape to the layer
layer.add(rect);
// add the layer to the stage
stage.add(layer);​

Categories

Resources