Related
I m working on a script that let clients draw the way they want to bend iron with detailed angles. everything is OK.
Now the client want's a 3D view of it. but i have no idea of how to do it (with three.js I guess)
I need a simple 3D view.
Here is what i did till now for 2D view, it's based on FabricJS:
$(function(){
var canvas = this.__canvas = new fabric.Canvas('c', { selection: false });
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
var Lines = Array();
var Points = Array();
var circleAngles = Array();
var anglesValues = Array();
function radianToDegrees(r){
r = r * 180/Math.PI;
if(r < 0) r = -r;
if(360 - r < r) r = 360 - r;
return 180 - parseFloat(r).toFixed(0);
}
function makeCircle(left, top, line1, line2) {
var c = new fabric.Circle({
left: left,
top: top,
strokeWidth: 0,
radius: 8,
fill: '#000',
stroke: '#000'
});
c.hasControls = c.hasBorders = false;
c.line1 = line1;
c.line2 = line2;
return c;
}
function makeLine(coords) {
return new fabric.Line(coords, {
fill: 'red',
stroke: 'red',
strokeWidth: 2,
selectable: false,
evented: false,
});
}
function makeCircleAngle(angle, startAngle, endAngle){
if (angle == 1) color = 'red'; else color = '#003366';
circleAngle = new fabric.Circle({
radius: 20,
left: Lines[i].get('x1'),
top: Lines[i].get('y1'),
angle: 0,
startAngle: startAngle,
endAngle: endAngle,
strokeDashArray: [3, 2],
stroke: color,
fill: '',
selectable: false,
evented: false,
});
return circleAngle;
}
function makeText(text, x, y){
t = new fabric.Text(text, {
left: x, top: y, fontSize: 13, fill: '#003366', fontFamily: 'Arial',
selectable: false,
evented: false,
});
return t;
}
function drawLinesCanvas(){
$.each(Lines, function(i, e){
canvas.add(e);
})
}
function drawDotsCanvas(){
Points = Array();
p = makeCircle(Lines[0].get('x1'), Lines[0].get('y1'), null, Lines[0]);
Points.push(p)
canvas.add(p);
for(i = 0; i< Lines.length-1; i++){
p = makeCircle(Lines[i].get('x2'), Lines[i].get('y2'), Lines[i], Lines[i+1]);
Points.push(p)
canvas.add( p );
}
if (Lines.length-1 >= 0){
p = makeCircle(Lines[Lines.length-1].get('x2'), Lines[Lines.length-1].get('y2'), Lines[Lines.length-1]);
Points.push(p)
canvas.add( p );
}
}
function calculateAndDrawAngles(){
$.each(circleAngles, function(i, ce){
canvas.remove(ce);
})
$.each(Lines, function(i, l){
canvas.remove(l);
})
$.each(Points, function(i, p){
canvas.remove(p);
})
$.each(anglesValues, function(i, a){
canvas.remove(a);
})
if(Lines.length >= 2){
for(i=1; i<Lines.length; i++){
y11 = Lines[i].get('y1');
y12 = Lines[i].get('y2');
y21 = Lines[i-1].get('y1');
y22 = Lines[i-1].get('y2');
x11 = Lines[i].get('x1');
x12 = Lines[i].get('x2');
x21 = Lines[i-1].get('x1');
x22 = Lines[i-1].get('x2');
angle1 = Math.atan2(y11 - y12, x11 - x12);
angle2 = Math.atan2(y21 - y22, x21 - x22);
angle = angle1 - angle2;
if (angle < 0){
sStartAngle = Math.PI + angle1;
sEndAngle = angle2;
} else {
sStartAngle = angle1 - Math.PI;
sEndAngle = angle2;
}
myAngle = radianToDegrees(angle1 - angle2);
if(sStartAngle > sEndAngle) {
c = makeCircleAngle(1, sStartAngle, sEndAngle);
c1 = makeCircleAngle(2, sEndAngle, sStartAngle);
myAngleText = makeText(myAngle.toString()+'°', Lines[i].get('x1') +20, Lines[i].get('y1') + 20)
} else {
c = makeCircleAngle(2, sStartAngle, sEndAngle);
c1 = makeCircleAngle(1, sEndAngle, sStartAngle);
myAngleText = makeText(myAngle.toString()+'°', Lines[i].get('x1') - 20, Lines[i].get('y1') - 20)
}
circleAngles.push(c, c1);
canvas.add(c, c1);
canvas.add(myAngleText)
anglesValues.push(myAngleText);
}
drawLinesCanvas();
drawDotsCanvas();
}
}
canvas.on('object:moving', function(e) {
var p = e.target;
p.line1 && p.line1.set({ 'x2': p.left, 'y2': p.top });
p.line2 && p.line2.set({ 'x1': p.left, 'y1': p.top });
if (Lines.length > 1){
calculateAndDrawAngles();
}
});
canvas.on('mouse:wheel', function(opt) {
var delta = opt.e.deltaY;
var zoom = canvas.getZoom();
zoom = zoom + delta/200;
if (zoom > 20) zoom = 5;
if (zoom < 0.01) zoom = 0.5;
canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
var vpt = this.viewportTransform;
if (zoom < 400 / 1000) {
this.viewportTransform[4] = 200 - 1000 * zoom / 2;
this.viewportTransform[5] = 200 - 1000 * zoom / 2;
} else {
if (vpt[4] >= 0) {
this.viewportTransform[4] = 0;
} else if (vpt[4] < canvas.getWidth() - 1000 * zoom) {
this.viewportTransform[4] = canvas.getWidth() - 1000 * zoom;
}
if (vpt[5] >= 0) {
this.viewportTransform[5] = 0;
} else if (vpt[5] < canvas.getHeight() - 1000 * zoom) {
this.viewportTransform[5] = canvas.getHeight() - 1000 * zoom;
}
}
});
canvas.on('mouse:down', function(opt) {
var evt = opt.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
});
canvas.on('mouse:move', function(opt) {
if (this.isDragging) {
var e = opt.e;
this.viewportTransform[4] += e.clientX - this.lastPosX;
this.viewportTransform[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
});
canvas.on('mouse:up', function(opt) {
this.isDragging = false;
this.selection = true;
});
$('#addRight').on('click', function(){
fromPoint = Lines[Lines.length - 1];
Lines.push(makeLine([ fromPoint.get('x2'), fromPoint.get('y2'), fromPoint.get('x2') - 50, fromPoint.get('y2') + 50 ]))
calculateAndDrawAngles()
});
$('#addLeft').on('click', function(){
fromPoint = Lines[0];
Lines.unshift(makeLine([ fromPoint.get('x1') + 50, fromPoint.get('y1') + 50, fromPoint.get('x1'), fromPoint.get('y1') ]))
calculateAndDrawAngles()
});
function drawGrid(){
options = {
distance: 10,
width: c.width,
height: c.height,
param: {
stroke: '#ebebeb',
strokeWidth: 1,
selectable: false
}
},
gridLen = options.width / options.distance;
for (var i = 0; i < gridLen; i++) {
distance = i * options.distance,
horizontal = new fabric.Line([ distance, 0, distance, options.width], options.param),
vertical = new fabric.Line([ 0, distance, options.width, distance], options.param);
canvas.add(horizontal);
canvas.add(vertical);
if(i%5 === 0){
horizontal.set({stroke: '#cccccc'});
vertical.set({stroke: '#cccccc'});
};
canvas.sendBackwards(horizontal);
canvas.sendBackwards(vertical);
};
}
Lines = [makeLine([ 100, 50, 400, 50 ])];
drawGrid();
drawLinesCanvas();
drawDotsCanvas();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="c" width="500" height="400" style="border:1px solid #ccc;"></canvas>
<div class="text-center">
<button class="btn btn-info" id="addLeft">Ajouter un point à gauche</button>
<button class="btn btn-info" id="addRight">Ajouter un point à droite</button>
</div>
Just an option, when you can obtain the coordinates of points and use them to build a bended iron:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.setScalar(10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var obtained_data = [];
for (let i = 0; i < 10; i++) {
obtained_data.push(
new THREE.Vector2(i, Math.random() * 8 * 0.5 - 0.5) // fill the data with example coordinates
);
}
//console.log(obtained_data);
var dataLength = obtained_data.length;
var geom = new THREE.PlaneBufferGeometry(1, 10, dataLength - 1, 10); // size on Y and its segmentation is up to you
geom.rotateX(Math.PI * 0.5);
var pos = geom.getAttribute("position");
for (let i = 0; i < pos.count; i++) {
let idx = i % dataLength;
let x = obtained_data[idx].x;
let y = obtained_data[idx].y;
pos.setXY(i, x, y);
}
pos.needsUpdate = true;
geom.computeVertexNormals();
var mat = new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
});
var iron = new THREE.Mesh(geom, mat);
scene.add(iron);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
I need to set background image for this canvas animation without affecting the animation style.
This CodePen is shown below.
var c = document.getElementById('canv');
var $ = c.getContext('2d');
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;
var grav = 0.00095;
var s = [20, 15, 10, 5];
var gravX = w / 2;
var gravY = h / 2;
var nodes;
var num = 55;
var minDist = 155;
var spr = 0.0000009;
part();
run();
//random size function
function S() {
var curr = s.length;
var cur_ = Math.floor(Math.random() * curr);
return s[cur_];
}
function part() {
nodes = [];
for (var i = 0; i < num; i++) {
var node = {
hue: Math.random()*360,
rad: S(),
x: Math.random() * w,
y: Math.random() * h,
vx: Math.random() * 8 - 4,
vy: Math.random() * 8 - 4,
upd: function() {
this.x += this.vx;
this.y += this.vy;
if (this.x > w) this.x = 0;
else if (this.x < 0) this.x = w;
if (this.y > h) this.y = 0;
else if (this.y < 0) this.y = h;
},
draw: function() {
//outer ring
var g = $.createRadialGradient(this.x, this.y, this.rad * 2, this.x, this.y, this.rad);
g.addColorStop(0,'hsla(242, 55%, 15%,.7)');
g.addColorStop(.5, 'hsla(242, 50%, 10%,.5)');
g.addColorStop(1,'hsla(242, 30%, 5%,.5)');
$.fillStyle = g;
$.beginPath();
$.arc(this.x, this.y, this.rad * 2, 0, Math.PI * 2, true);
$.fill();
$.closePath();
//inner particle
var g2 = $.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.rad);
g2.addColorStop(0, 'hsla('+this.hue+', 85%, 40%, 1)');
g2.addColorStop(.5, 'hsla('+this.hue+',95%, 50%,1)');
g2.addColorStop(1,'hsla(0,0%,0%,0)');
$.fillStyle = g2;
$.beginPath();
$.arc(this.x, this.y, this.rad, 0, Math.PI * 2, true);
$.fill();
$.closePath();
}
};
nodes.push(node);
}
}
function run() {
$.globalCompositeOperation = 'source-over';
$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.fillRect(0, 0, w, h);
$.globalCompositeOperation = 'lighter';
for (i = 0; i < num; i++) {
nodes[i].upd();
nodes[i].draw();
}
for (i = 0; i < num - 1; i++) {
var n1 = nodes[i];
for (var j = i + 1; j < num; j++) {
var n2 = nodes[j];
Spr(n1, n2);
}
Grav(n1);
}
window.requestAnimationFrame(run);
}
function Spr(na, nb) {
var dx = nb.x - na.x;
var dy = nb.y - na.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
$.lineWidth = 1;
$.beginPath();
$.strokeStyle = "hsla(217, 95%, 55%, .15)";
$.moveTo(na.x, na.y);
$.lineTo(nb.x, nb.y);
$.stroke();
$.closePath();
var ax = dx * spr;
var ay = dy * spr;
na.vx += ax;
na.vy += ay;
nb.vx -= ax;
nb.vy -= ay;
}
}
function Grav(n) {
n.vx += (gravX - n.x) * grav;
n.vy += (gravY - n.y) * grav;
};
window.addEventListener('resize', function() {
c.width = w = window.innerWidth;
c.height = h = window.innerHeight;
});
body{
width:100%;
margin:0;
overflow:hidden;
}
<canvas id='canv' ></canvas>
CSS
Just replace the beginning of the run() code to:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.clearRect(0, 0, w, h);
...
Then move the color settings to CSS together with an image reference:
#canv {
background: hsla(242, 40%, 5%, .85) url(path/to/image.jpg);
}
Add background-size to the CSS rule if needed. Note that since you're using different blending modes such as lighter which depends on existing content, you may not get desired result as it will blend with an empty canvas and not a solid - the approach below should solve that in this case.
CodePen
JavaScript
As before, replace the first lines in run() but after you made sure the image you want to use has loaded, simply draw it in:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.drawImage(img, 0, 0, w, h); // img must be loaded (use onload)
...
If your image contains transparency you also need to clear the canvas first:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.clearRect(0, 0, w, h);
$.drawImage(img, 0, 0, w, h); // img must be loaded (use onload)
...
CodePen
For performance reasons I merged geometry. I have tens of thousands of cubes to display. I have that working with reasonable performance.
Now I have to deal with removing some. I almost have it but can't figure out how to make this work, so I cut my code up to make this complete sample.
In the onDocumentMouseDown function when a cube is clicked on I try to remove it. And it sort of does. But instead of removing one cube it removes two. (Then it basically acts worse) It removes the one I pointed at and the next one I added.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>Measurement</title>
<style>
body { margin: 0; }
</style>
</head>
<body>
<div id="canvas_container" style="position: absolute; left:0px; top:0px; touch-action:none;"></div>
<div id="MessageDisplay1" style="position: absolute; top: 50px; left: 50px; background-color: black; opacity: 0.8; color:white; touch-action:none;"></div>
<div id="MessageDisplay" style="position: absolute; top: 50px; left: 200px; background-color: black; opacity: 0.8; color:white; touch-action:none;"></div>
<script src="js/three.js"></script>
<script>
// player motion parameters
var motioncontrol = {
airborne: false,
bumpposition: 5.0,
bumpdegrees: 4.0,
rotationanglezx: 0,
tiltangle: 0,
distancePointZ : 10000.0,
distancePointY: 100.0,
position : new THREE.Vector3(), velocity : new THREE.Vector3(),
rotation: new THREE.Vector3(), spinning: new THREE.Vector2(),
prevposition : new THREE.Vector3(),
prevrotation : new THREE.Vector3()
};
var mouseDown = 0;
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var INTERSECTED;
motioncontrol.position.y = 15;
motioncontrol.position.x = 0;
motioncontrol.position.z = 0;
motioncontrol.rotation.x = 0;
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.cos(0);
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.sin(0);
motioncontrol.prevposition.copy(motioncontrol.position);
motioncontrol.prevrotation.copy(motioncontrol.rotation);
// Our Javascript will go here.
var domContainer = null;
domContainer = document.getElementById("canvas_container");
var camera = new THREE.PerspectiveCamera( 50, window.innerWidth/window.innerHeight, 0.1, 5000 );
var aspectratio = window.innerWidth/window.innerHeight;
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
domContainer.appendChild(renderer.domElement);
var materials = THREE.ImageUtils.loadTexture('texture/sky.jpg');
addEventListener('mousemove', onDocumentMouseMove, false);
domContainer.addEventListener('mousedown', onDocumentMouseDown, false);
domContainer.addEventListener('mouseup', onDocumentMouseUp, false);
var scene = new THREE.Scene();
scene.add(camera);
window.addEventListener('resize', resize, false);
camera.position.x = 0;
camera.position.y = 100;
camera.position.z = -100;
camera.rotation.y = Math.PI / 180.0 * 90;
var directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position.set(0, -10, 0);
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0xffffff, 3.0);
directionalLight.position.set(0, -50,-1000);
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0xffffff, 3.0);
directionalLight.position.set(0, -50, 1000);
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0xffffff, 3.0);
directionalLight.position.set(-200, -10, 0);
scene.add(directionalLight);
directionalLight = new THREE.DirectionalLight(0xffffff, 3.0);
directionalLight.position.set(200, -10, 0);
scene.add(directionalLight);
addGround(scene);
// array of unsorted geometries.
var CubeGeometryArray = new Array();
var CubeGeometryTier1 = new THREE.Geometry();
var CubeGeometryTier2 = new THREE.Geometry();
var CubeGeometryTier3 = new THREE.Geometry();
var CubeGeometryTier4 = new THREE.Geometry();
var CubeGeometryTier5 = new THREE.Geometry();
// array of materials
var CubeMaterials = new Array();
// array of meshes used for hit testing.
var CubeArray = new Array();
var cMaterialCount = 0;
var Cube20Mesh;
var Cube40Mesh;
var CubesLoaded = false;
LoadCubeMaterial();
LoadCubeMeshs();
var controls;
function resize()
{
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
function onMouseWheel(event)
{
var delta = 0;
if ( event.wheelDelta !== undefined ) {
// WebKit / Opera / Explorer 9
delta = event.wheelDelta;
} else if ( event.detail !== undefined ) {
// Firefox
delta = - event.detail;
}
if ( delta > 0 ) { // forward
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
} else if ( delta < 0 ) {
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
angle += Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
};
function onDocumentMouseMove(event)
{
event.preventDefault();
if (mouseDown > 0) {
if (((event.clientX / window.innerWidth) * 2 - 1) > mouse.x) {
motioncontrol.rotationanglezx -= motioncontrol.bumpdegrees;
if (motioncontrol.rotationanglezx < 0)
motioncontrol.rotationanglezx += 360;
var angle = (Math.PI / 180.0) * motioncontrol.rotationanglezx;
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.cos(angle) - motioncontrol.distancePointZ * Math.sin(angle);
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.sin(angle) + motioncontrol.distancePointZ * Math.cos(angle);
}
if (((event.clientX / window.innerWidth) * 2 - 1) < mouse.x) {
motioncontrol.rotationanglezx += motioncontrol.bumpdegrees;
if (motioncontrol.rotationanglezx > 360)
motioncontrol.rotationanglezx -= 360;
var angle = (Math.PI / 180.0) * motioncontrol.rotationanglezx;
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.cos(angle) - motioncontrol.distancePointZ * Math.sin(angle);
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.sin(angle) + motioncontrol.distancePointZ * Math.cos(angle);
}
}
};
function onDocumentMouseDown(event)
{
++mouseDown;
event.preventDefault();
var mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObjects(CubeArray);
if(intersects.length > 0)
{
if(intersects[0].object.name != null)
{
var offset = intersects[0].object.name * 8;
var offsetfaces = intersects[0].object.name * 12;
var index = intersects[0].object.name;
var selectedObject = scene.getObjectByName("Tier1");
scene.remove(selectedObject);
CubeArray.splice(index, 1);
CubeGeometryTier1 = CubeGeometryArray[0];
CubeGeometryTier1.vertices.splice(offset, 8);
CubeGeometryTier1.faces.splice(offsetfaces, 12);
CubeGeometryTier1.faceVertexUvs[0].splice(offsetfaces, 12);
CubeGeometryArray[0] = CubeGeometryTier1.clone();
CubeGeometryTier1.sortFacesByMaterialIndex();
var cmesh = new THREE.Mesh(CubeGeometryTier1, new THREE.MeshFaceMaterial(CubeMaterials));
cmesh.matrixAutoUpdate = false;
cmesh.updateMatrix();
cmesh.name = "Tier1";
scene.add(cmesh);
}
else
INTERSECTED = null;
}
};
function onDocumentMouseUp(event) {
mouseDown = 0;
event.preventDefault();
}
function addGround(scene1)
{
var materialg = new THREE.MeshBasicMaterial( { color: 0x333333 , side: THREE.BackSide } );
var groundmesh = new THREE.Mesh(new THREE.PlaneGeometry(9000, 4500, 1), materialg);
groundmesh.receiveShadow = true;
groundmesh.rotation.x = Math.PI / 180.0 * 90;
groundmesh.position.set(0, 0, 0);
groundmesh.name = "asphalt";
scene1.add(groundmesh);
};
function writeToScreen(message)
{
var pre = document.getElementById("MessageDisplay");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
}
function writeToScreen2(message)
{
var pre = document.getElementById("MessageDisplay1");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
}
function addCube20(name, x1,z1,azimuth,height,add)
{
var cube;
if (add)
{
if (height > 5)
height = 5;
cube = Cube20Mesh.clone();
cube.visible = true;
cube.receiveShadow = true;
cube.position.set(x1, ((height - 1) * 8.5) + 4.25, z1);
cube.rotation.y = (Math.PI / 180.0) * azimuth;
cube.name = name;
cube.updateMatrix();
AddCubeGeometry(cube.geometry, cube.matrix, height);
cube.matrixWorld = cube.matrix;
CubeArray.push(cube); // kept for hit test
}
};
function addCube40(name, x1, z1, azimuth,height,add)
{
var cube;
if (add)
{
if (height > 5)
height = 1;
cube = Cube40Mesh.clone();
cube.visible = true;
cube.receiveShadow = true;
cube.position.set(x1, ((height - 1) * 8.5) + 4.25, z1);
cube.rotation.y = (Math.PI / 180.0) * azimuth;
cube.name = name;
cube.updateMatrix();
AddCubeGeometry(cube.geometry, cube.matrix, height + 5);
cube.matrixWorld = cube.matrix;
CubeArray.push(cube); // kept for hit test
}
};
function LoadCubeMeshs()
{
var CubeGeometry20 = new THREE.BoxGeometry(20, 8.5, 9.5);
var CubeGeometry40 = new THREE.BoxGeometry(40, 8.5, 9.5);
Cube20Mesh = new THREE.Mesh(CubeGeometry20);
Cube40Mesh = new THREE.Mesh(CubeGeometry40);
CubesLoaded = true;
};
function LoadCubeMaterial()
{
CubeMaterials[0] = new THREE.MeshBasicMaterial({color:0xff0000 });
cMaterialCount++;
CubeMaterials[1] = new THREE.MeshBasicMaterial({color:0xffff00 });
cMaterialCount++;
CubeMaterials[2] = new THREE.MeshBasicMaterial({color:0xffffff });
cMaterialCount++;
CubeMaterials[3] = new THREE.MeshBasicMaterial({color:0x0000ff });
cMaterialCount++;
CubeMaterials[4] = new THREE.MeshBasicMaterial({color:0x00ffff });
cMaterialCount++;
CubeMaterials[5] = new THREE.MeshBasicMaterial({color:0x772255 });
cMaterialCount++;
CubeMaterials[6] = new THREE.MeshBasicMaterial({color:0x552277 });
cMaterialCount++;
CubeMaterials[7] = new THREE.MeshBasicMaterial({color:0x222299 });
cMaterialCount++;
CubeMaterials[8] = new THREE.MeshBasicMaterial({color:0x992222 });
cMaterialCount++;
CubeMaterials[9] = new THREE.MeshBasicMaterial({color:0x000000 });
cMaterialCount++;
};
function DisplayCubes(scene1)
{
if(CubeGeometryTier1.faces.length > 0)
{
var material = new THREE.MeshNormalMaterial();
// save the unsorted geometry.
CubeGeometryArray.push(CubeGeometryTier1.clone());
CubeGeometryTier1.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier1, new THREE.MeshFaceMaterial(CubeMaterials));
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier1";
scene1.add(Cubemesh);
}
if(CubeGeometryTier2.faces.length > 0)
{
// save the unsorted geometry.
CubeGeometryArray.push(CubeGeometryTier2.clone());
// sorting is a HUGE performance boost
CubeGeometryTier2.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier2, CubeMaterials);
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier2";
scene1.add(Cubemesh);
}
if(CubeGeometryTier3.faces.length > 0)
{
CubeGeometryArray.push(CubeGeometryTier3.clone());
CubeGeometryTier3.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier3, CubeMaterials);
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier3";
scene1.add(Cubemesh);
}
if(CubeGeometryTier4.faces.length > 0)
{
CubeGeometryArray.push(CubeGeometryTier4.clone());
CubeGeometryTier4.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier4, CubeMaterials);
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier4";
scene1.add(Cubemesh);
}
if(CubeGeometryTier5.faces.length > 0)
{
CubeGeometryArray.push(CubeGeometryTier5.clone());
CubeGeometryTier5.sortFacesByMaterialIndex();
var Cubemesh = new THREE.Mesh(CubeGeometryTier5, CubeMaterials);
Cubemesh.matrixAutoUpdate = false;
Cubemesh.updateMatrix();
Cubemesh.name = "Tier5";
scene1.add(Cubemesh);
}
};
// merging geometry for improved performance.
function AddCubeGeometry(geom, matrix,tier)
{
switch(tier)
{
case 1:
//CubeGeometryTier1.merge(geom, matrix, tier - 1);
CubeGeometryTier1.merge(geom, matrix);
break;
case 2:
CubeGeometryTier2.merge(geom, matrix,tier - 1);
break;
case 3:
CubeGeometryTier3.merge(geom, matrix,tier - 1);
break;
case 4:
CubeGeometryTier4.merge(geom, matrix,tier - 1);
break;
case 5:
CubeGeometryTier5.merge(geom, matrix,tier - 1);
break;
// forty footers
case 6:
// CubeGeometryTier1.merge(geom, matrix,tier - 1);
CubeGeometryTier1.merge(geom, matrix);
break;
case 7:
CubeGeometryTier2.merge(geom, matrix,tier - 1);
break;
case 8:
CubeGeometryTier3.merge(geom, matrix,tier - 1);
break;
case 9:
CubeGeometryTier4.merge(geom, matrix,tier - 1);
break;
case 10:
CubeGeometryTier5.merge(geom, matrix,tier - 1);
break;
default:
CubeGeometryTier1.merge(geom, matrix,0);
break;
}
};
motioncontrol.position.y = 10;
motioncontrol.position.x = -50;
motioncontrol.position.z = 0;
motioncontrol.rotation.x = 0;
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.cos(0);
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.sin(0);
function LoadCubes()
{
var cnt = 0;
for(var x = 0; x < 5; x++)
{
addCube20(cnt, 0, x * 23.0, 90, 1, true);
cnt++;
addCube40(cnt, 10, x * 43, 90, 1, true);
cnt++;
}
};
// game systems code
var keyboardControls = (function() {
var keys = { SP : 32, Q:81, E:69, W : 87, A : 65, S : 83, D : 68, UP : 38, LT : 37, DN : 40, RT : 39 };
var keysPressed = {};
(function( watchedKeyCodes ) {
var handler = function( down ) {
return function( e ) {
var index = watchedKeyCodes.indexOf( e.keyCode );
if( index >= 0 ) {
keysPressed[watchedKeyCodes[index]] = down; e.preventDefault();
}
};
};
window.addEventListener( "keydown", handler( true ), false );
window.addEventListener( "keyup", handler( false ), false );
})([
keys.SP, keys.Q, keys.E, keys.W, keys.A, keys.S, keys.D, keys.UP, keys.LT, keys.DN, keys.RT
]);
return function() {
// look around
if (keysPressed[keys.Q])
{
motioncontrol.tiltangle += motioncontrol.bumpdegrees;
if (motioncontrol.tiltangle < 0)
motioncontrol.tiltangle += 360;
var angle = (Math.PI / 180.0) * motioncontrol.tiltangle;
motioncontrol.rotation.y = motioncontrol.distancePointZ * Math.sin(angle);
}
if (keysPressed[keys.E])
{
motioncontrol.tiltangle -= motioncontrol.bumpdegrees;
if (motioncontrol.tiltangle < 0)
motioncontrol.tiltangle += 360;
var angle = (Math.PI / 180.0) * motioncontrol.tiltangle;
motioncontrol.rotation.y = motioncontrol.distancePointZ * Math.sin(angle);
}
if (keysPressed[keys.W])
{
motioncontrol.position.y += motioncontrol.bumpposition;
if (motioncontrol.position.y > 1000.0)
motioncontrol.position.y = 1000.0;
}
if (keysPressed[keys.S])
{
motioncontrol.position.y += -motioncontrol.bumpposition;
if (motioncontrol.position.y < 1.0)
motioncontrol.position.y = 1;
}
if(keysPressed[keys.A])
{
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
angle += Math.PI / 180.0 * 90;
var message = "Angle " + angle * 180/Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
if(keysPressed[keys.D])
{
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
angle += Math.PI / 180.0 * -90;
var message = "Angle " + angle * 180/Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
// forward
if (keysPressed[keys.UP])
{
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
var message = "Angle " + angle * 180/Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
// backward
if(keysPressed[keys.DN])
{
var deltaX = motioncontrol.rotation.z - motioncontrol.position.z;
var deltaY = motioncontrol.rotation.x - motioncontrol.position.x;
// var angle = Math.atan2(deltaY, deltaX);
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
angle += Math.PI;
var message = "Angle " + angle * 180/Math.PI;
motioncontrol.position.z += motioncontrol.bumpposition * Math.cos(angle);
motioncontrol.position.x += motioncontrol.bumpposition * Math.sin(angle);
}
if(keysPressed[keys.LT])
{
motioncontrol.rotationanglezx -= motioncontrol.bumpdegrees;
if (motioncontrol.rotationanglezx < 0)
motioncontrol.rotationanglezx += 360;
var angle = (Math.PI / 180.0) * motioncontrol.rotationanglezx;
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.cos(angle) - motioncontrol.distancePointZ * Math.sin(angle);
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.sin(angle) + motioncontrol.distancePointZ * Math.cos(angle);
}
if(keysPressed[keys.RT])
{
motioncontrol.rotationanglezx += motioncontrol.bumpdegrees;
if (motioncontrol.rotationanglezx > 360)
motioncontrol.rotationanglezx -= 360;
var angle = (Math.PI / 180.0) * motioncontrol.rotationanglezx;
motioncontrol.rotation.x = motioncontrol.distancePointZ * Math.cos(angle) - motioncontrol.distancePointZ * Math.sin(angle);
motioncontrol.rotation.z = motioncontrol.distancePointZ * Math.sin(angle) + motioncontrol.distancePointZ * Math.cos(angle);
}
};
})();
var updateCamera = (function() {
return function() {
camera.position.copy(motioncontrol.position);
camera.lookAt(motioncontrol.rotation);
var message = "Rotation " + motioncontrol.rotationanglezx + "<BR>";
message += "X " + motioncontrol.position.x + "<BR>";
message += "Y " + motioncontrol.position.y + "<BR>";
message += "Z " + motioncontrol.position.z + "<BR>";
message += "X " + motioncontrol.rotation.x + "<BR>";
message += "Y " + motioncontrol.rotation.y + "<BR>";
message += "Z " + motioncontrol.rotation.z + "<BR>";
var angle = Math.atan2(motioncontrol.rotation.x, motioncontrol.rotation.z);
message += "Angle " + angle * 180 / Math.PI + "<BR>";
message += "Use Arrows w,s,e,d,q,a to navigate<BR>";
writeToScreen2(message);
};
})();
function render() {
if(cMaterialCount > 9 && cMaterialCount < 200 && CubesLoaded)
{
cMaterialCount = 200;
LoadCubes();
DisplayCubes(scene);
}
keyboardControls();
updateCamera();
renderer.render( scene, camera );
requestAnimationFrame( render );
};
render();
</script>
</body>
</html>
TLDR Array Mutation and Static Offsets are a dangerous mix
First, I recommend you post fiddles of some sort of your code. I made one here of your example. Second, you could really use some DRYing to shorten and clarify your code. Third, in code of this size, I recommend separating and grouping your code somehow (files, tasks, even comment blocks). Last, in a demo like this, I see no reason to roll your own controls. Check out Orbit Camera or the like that THREE.js offers.
Anyway, I gathered you collect a large number of cubes into 10 'tiers' of THREE.Geometry for rendering purposes. Then on click (~line 180), raycast out, and try to remove just that cube from the geometry. Here's your relevant code:
intersects = raycaster.intersectObjects(CubeArray);
if(intersects.length > 0)
{
if(intersects[0].object.name != null)
{
var offset = intersects[0].object.name * 8;
var offsetfaces = intersects[0].object.name * 12;
var index = intersects[0].object.name;
var selectedObject = scene.getObjectByName("Tier1");
scene.remove(selectedObject);
CubeArray.splice(index, 1);
CubeGeometryTier1 = CubeGeometryArray[0];
CubeGeometryTier1.vertices.splice(offset, 8);
CubeGeometryTier1.faces.splice(offsetfaces, 12);
CubeGeometryTier1.faceVertexUvs[0].splice(offsetfaces, 12);
CubeGeometryArray[0] = CubeGeometryTier1.clone();
CubeGeometryTier1.sortFacesByMaterialIndex();
var cmesh = new THREE.Mesh(CubeGeometryTier1, new THREE.MeshFaceMaterial(CubeMaterials));
cmesh.matrixAutoUpdate = false;
cmesh.updateMatrix();
cmesh.name = "Tier1";
scene.add(cmesh);
}
else
INTERSECTED = null;
}
Here's how I read this snippet:
Cast out against the CubeArray
Without a hit something or if it lacks a name, return
Implict cast some string names (!) to numbers to compute location in Tier
Remove an object from the scene by the name "Tier1", regardless of any other input
Set CubeGeometryTier1 to the first index of CubeGeometryArray, regardless of raycast
Fiddle with now overwritten CubeGeometryTier1 to remove geometry
Reassign CubeGeometryArray[0] with the changed object of CubeGeometryTier1
Build a new mesh based on CubeGeometryTier1, call it "Tier1" and dump it back into the scene
I'll admit I haven't entirely traced the hundreds of line where you build your cubes, but this makes little sense to me. Assuming your use of CubeGeometry[Tier|Array] and hard coded names and indices are correct, what really grabs me is the use of static offsets when you mutate the array.
You splice CubeArray to remove that 'ghost' cube from getting picked again, but none of the other 'ghost' cubes changed, notably their offsets names, while the geometry that you rebuilt into Tier1 did. Past the spliced cube, all of them index names will be wrong.
Here's an example in simpler form:
//set up
var baseArray = [0, 1, 2, 3, 4, 5].map(i => '' + i);
const getRandomInt = (min, max) => {
return Math.floor(Math.random() * (max - min)) + min;
};
const pickRandomElementFrombaseArray = () => {
const pickedIndex = getRandomInt(0, baseArray.length);
return baseArray[pickedIndex];
};
// operationally equivilent to splicing your Tiers of their geometry
const yankIndex = (value) => {
//value is a string in this case
const index = +value;
if (index < 0 || index > baseArray.length - 1) {
throw `Unable to remove invalid index ${index}`
} else {
baseArray.splice(index, 1);
}
};
// Run the test until empty or failure
var messages = [`Starting with ${baseArray}`];
while (baseArray.length > 0) {
const pickedValue = pickRandomElementFrombaseArray();
messages.push(`Picked element ${pickedValue} to remove`);
try {
yankIndex(pickedValue);
messages.push(`Now array is ${baseArray}`);
} catch (e) {
messages.push(`ALERT: ${e}`);
break;
}
}
messages.push('Test complete');
const div = $('#div');
messages.map(msg => `<p>${msg}</p>`).forEach(msg => div.append(msg))
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title></title>
</head>
<body>
<h2>Results</h2>
<div id="div"></div>
</body>
</html>
Try it a few times. There's only a 0.8% chance the array will be exhausted before an error.
Sneaky errors can creep into code when you are dealing with mutation of arrays. Ignoring restructuring the code entirely, options that spring to mind:
Maintain an offset map on each removal. Essentially, rebuild each cube's offset on every action. You could do this in the CubeArray or, if creating/mutating 10k heavy THREE.js objects is not to your liking, yet another level of indirection that maps each cube id to an offset in the Tiers
Invisible Objects Never really remove the geometry (ie, don't splice the Tier array), just hide it. Scale the faces to 0, invisible mat, whatever. This means all the offsets you pre-generate will hold. Downsides are invisible geo isn't free, this won't help if you need to change the scene in other ways, and you'll have to scan the other hits from thee.raycast
So I'm trying to have the down arrow make the background of my canvas change. I'm having trouble just getting the button press to work itself.
Also I was told that I would have to have a function redraw all the shapes that are already there at the beginning as well, which I am also stuck on what to change.
Here is a JSFiddle of what I have going so far, any suggestions are appreciated!
https://jsfiddle.net/u8avnky2/
var mainCanvas = document.getElementById("canvas");
var mainContext = mainCanvas.getContext('2d');
//rotate canvas
function buttonClick() {
mainContext.rotate(20*Math.PI/180);
}
//key down event
window.addEventListener('keydown', function(event) {
if (event.keyCode === 40) {
fillBackgroundColor();
}
});
function fillBackgroundColor() {
var colors = ["red", "green", "blue", "orange", "purple", "yellow"];
var color = colors[Math.floor(Math.random()*colors.length)];
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
mainContext.fillStyle = color;
mainContext.fillRect(0, 0, canvas.width, canvas.height);
}
function check() {
mainContext.clearRect(square.x,square.y,square.w,square.h);
}
var circles = new Array();
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
function Circle(radius, speed, width, xPos, yPos) {
this.radius = radius;
this.speed = speed;
this.width = width;
this.xPos = xPos;
this.yPos = yPos;
this.opacity = .1 + Math.random() * .5;
this.counter = 0;
var signHelper = Math.floor(Math.random() * 2);
if (signHelper == 1) {
this.sign = -1;
} else {
this.sign = 1;
}
}
//drawing circle
Circle.prototype.update = function () {
this.counter += this.sign * this.speed;
mainContext.beginPath();
mainContext.arc(this.xPos + Math.cos(this.counter / 100) * this.radius,
this.yPos + Math.sin(this.counter / 100) * this.radius,
this.width,
0,
Math.PI * 2,
false);
mainContext.closePath();
mainContext.fillStyle = 'rgba(255, 255, 51,' + this.opacity + ')';
mainContext.fill();
};
function setupCircles() {
for (var i = 0; i < 25; i++) {
var randomX = Math.round(-200 + Math.random() * 700);
var randomY = Math.round(-200 + Math.random() * 700);
var speed = .2 + Math.random() * 3;
var size = 5 + Math.random() * 100;
var radius = 5 + Math.random() * 100;
var circle = new Circle(radius, speed, size, randomX, randomY);
circles.push(circle);
}
drawAndUpdate();
}
setupCircles();
function drawAndUpdate() {
mainContext.clearRect(0, 0, 1000, 1000);
for (var i = 0; i < circles.length; i++) {
var myCircle = circles[i];
myCircle.update();
}
requestAnimationFrame(drawAndUpdate);
}
jsFiddle : https://jsfiddle.net/CanvasCode/u8avnky2/1/
I added a variable known as color globally, so the fillBackgroundColor can access that instead.
var color = "white";
Then in your drawAndUpdate function we just do a fillRect with the color variable using the canvas width and height and it works.
function drawAndUpdate() {
mainContext.fillStyle = color;
mainContext.fillRect(0, 0, mainCanvas.width, mainCanvas.height);
for (var i = 0; i < circles.length; i++) {
var myCircle = circles[i];
myCircle.update();
}
requestAnimationFrame(drawAndUpdate);
}
I'm trying to change the shape of the particles in this script so that every time you click, the shapes change to a random shape. The shapes I want to do are circles (which they already are), squares, triangles, pentagons, and a four-leaf clover shape without the stem. I want to use the addEventListener method, but i have no idea where to even start with that. Thanks in advance, and here's the code I have so far:
http://jsfiddle.net/eampkcrr/
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = window.innerWidth;
var height = window.innerHeight;
var xCirc;
var yCirc;
var rCirc;
var animate = true;
canvas.width = width;
canvas.height = height;
makeParticles();
makeShapes();
function makeParticles() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
particles = [];
for (var i = 0; i < 3000; i++){
particles.push(new Particle());
}
}
function makeShapes() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
shapes = [];
shapes.push(new Circle());
}
function Circle() {
var r1 = 150;
var r2 = 1000;
var gradient1 = context.createRadialGradient(width/2, height/2, r1, width/2, height/2, r2);
gradient1.addColorStop(0.2, "yellow");
gradient1.addColorStop(0.8, "purple");
context.fillStyle = gradient1;
context.fillRect(0, 0, canvas.width, canvas.height);
var gradient2 = context.createRadialGradient(width/2, height/2, 120, width/2, height/2, 150);
gradient2.addColorStop(0, "black");
gradient2.addColorStop(.75, "black");
gradient2.addColorStop(1, "orange");
context.beginPath();
context.arc(width/2, height/2, 150, 0, 2 * Math.PI, true);
context.fillStyle = gradient2;
context.fill();
}
function start() {
if(animate){
window.requestAnimationFrame(start);
}
draw();
moveParticles();
}
function Particle() {
this.x = Math.floor((Math.random() * canvas.width) + 1);
this.y = Math.floor((Math.random() * canvas.height) + 1);
this.z = Math.floor((Math.random() * canvas.width));
var grad = context.createRadialGradient(this.x, this.y, Math.floor((Math.random() * 10) + 1), this.x, this.y, Math.floor((Math.random() * 10) + 1));
var colors = ["red", "green", "blue", "orange", "purple", "yellow", "white"];
grad.addColorStop(0, colors[Math.floor(Math.random()*colors.length)]);
grad.addColorStop(1, colors[Math.floor(Math.random()*colors.length)]);
this.color = grad;
this.radius = 1;
}
function draw() {
Circle();
for (var i = 0; i < particles.length; i++){
var p = particles[i];
xP = (xCenter - p.x) * (canvas.width/p.z);
xP += xCenter;
yP = (yCenter - p.y) * (canvas.width/p.z);
yP += yCenter;
rP = (canvas.width/p.z);
context.beginPath();
context.arc(xP, yP, rP, 0, 2 * Math.PI, true);
context.fillStyle = p.color;
context.fill();
xCirc -= p.x;
yCirc -= p.y;
}
}
function moveParticles() {
for (var j = 0; j < particles.length; j++){
var p = particles[j];
p.z -= 2;
if (p.z <= 0){
p.z = canvas.width;
}
}
}
start();
You can listen for click events on the canvas like this:
canvas.onclick=function(){ ... }
To change the particle shape, you can add a draw method to Particle that draws the appropriate shape based on a particle's this.particleType.
function Particle(particleType) {
this.particleType=particleType;
this.x = Math.floor((Math.random() * canvas.width) + 1);
this.y = Math.floor((Math.random() * canvas.height) + 1);
this.z = Math.floor((Math.random() * canvas.width));
var grad = context.createRadialGradient(this.x, this.y, Math.floor((Math.random() * 10) + 1), this.x, this.y, Math.floor((Math.random() * 10) + 1));
var colors = ["red", "green", "blue", "orange", "purple", "yellow", "white"];
grad.addColorStop(0, colors[Math.floor(Math.random()*colors.length)]);
grad.addColorStop(1, colors[Math.floor(Math.random()*colors.length)]);
this.color = grad;
this.radius = 1;
this.draw=function(){
// update position
var xP = (xCenter - this.x) * (canvas.width/this.z);
xP += xCenter;
var yP = (yCenter - this.y) * (canvas.width/this.z);
yP += yCenter;
var rP = (canvas.width/this.z);
// set fillStyle
context.fillStyle = this.color;
// draw on context based on the particle's current shape
switch (this.particleType){
case 'circle':
context.beginPath();
context.arc(xP, yP, rP, 0, 2 * Math.PI, true);
context.fill();
break;
case 'square':
context.fillRect(xP-rP, yP-rP, rP*2, rP*2);
break;
}
// update
xCirc -= this.x;
yCirc -= this.y;
}
}
Then your external function draw simply requests each particle to draw itself:
function draw() {
Circle();
for (var i = 0; i < particles.length; i++){
// request each particle to draw itself
var p = particles[i].draw();
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = window.innerWidth;
var height = window.innerHeight;
var xCirc;
var yCirc;
var rCirc;
var xCenter,yCenter;
var animate = true;
var pTypes=['circle','square'];
var pTypeIndex=0;
canvas.width = width;
canvas.height = height;
makeParticles();
makeShapes();
canvas.onclick=function(){
pTypeIndex++;
if(pTypeIndex>pTypes.length-1){pTypeIndex=0;}
var pType=pTypes[pTypeIndex];
for(var i=0;i<particles.length;i++){
particles[i].particleType=pType;
}
};
start();
function makeParticles() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
particles = [];
for (var i = 0; i < 500; i++){
particles.push(new Particle(pTypes[pTypeIndex]));
}
}
function makeShapes() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
shapes = [];
shapes.push(new Circle());
}
function Circle() {
var r1 = 150;
var r2 = 1000;
var gradient1 = context.createRadialGradient(width/2, height/2, r1, width/2, height/2, r2);
gradient1.addColorStop(0.2, "yellow");
gradient1.addColorStop(0.8, "purple");
context.fillStyle = gradient1;
context.fillRect(0, 0, canvas.width, canvas.height);
var gradient2 = context.createRadialGradient(width/2, height/2, 120, width/2, height/2, 150);
gradient2.addColorStop(0, "black");
gradient2.addColorStop(.75, "black");
gradient2.addColorStop(1, "orange");
context.beginPath();
context.arc(width/2, height/2, 150, 0, 2 * Math.PI, true);
context.fillStyle = gradient2;
context.fill();
}
function start() {
if(animate){
window.requestAnimationFrame(start);
}
draw();
moveParticles();
}
function Particle(particleType) {
this.particleType=particleType;
this.x = Math.floor((Math.random() * canvas.width) + 1);
this.y = Math.floor((Math.random() * canvas.height) + 1);
this.z = Math.floor((Math.random() * canvas.width));
var grad = context.createRadialGradient(this.x, this.y, Math.floor((Math.random() * 10) + 1), this.x, this.y, Math.floor((Math.random() * 10) + 1));
var colors = ["red", "green", "blue", "orange", "purple", "yellow", "white"];
grad.addColorStop(0, colors[Math.floor(Math.random()*colors.length)]);
grad.addColorStop(1, colors[Math.floor(Math.random()*colors.length)]);
this.color = grad;
this.radius = 1;
this.draw=function(){
// update position
var xP = (xCenter - this.x) * (canvas.width/this.z);
xP += xCenter;
var yP = (yCenter - this.y) * (canvas.width/this.z);
yP += yCenter;
var rP = (canvas.width/this.z);
// set fillStyle
context.fillStyle = this.color;
// draw on context
switch (this.particleType){
case 'circle':
context.beginPath();
context.arc(xP, yP, rP, 0, 2 * Math.PI, true);
context.fill();
break;
case 'square':
context.fillRect(xP-rP, yP-rP, rP*2, rP*2);
break;
}
// update
xCirc -= this.x;
yCirc -= this.y;
}
}
function draw() {
Circle();
for (var i = 0; i < particles.length; i++){
var p = particles[i].draw();
}
}
function moveParticles() {
for (var j = 0; j < particles.length; j++){
var p = particles[j];
p.z -= 2;
if (p.z <= 0){
p.z = canvas.width;
}
}
}
<h4>Click to change shapes.</h4>
<canvas id="canvas" width=300 height=300></canvas>
[ Fixed "yip" in circle <--> square conversion -- Thanks #Kaiido ]