We coded a spinning 3d shape in js. There's a flicker in the render of the top triangle, we think it's because the z sorting is not working correctly. How do we resolve this?
Here's a jsfiddle.
Here's the z sorting code:
// z sorting
// dots_for_rendering.sort((a,b) => Math.sqrt((b.x)**2 + (b.y)**2) - Math.sqrt((a.x)**2 + (a.y)**2))
for (var i = 0; i < polygons.length; i++) {
polygons[i].maxz = -Infinity;
polygons[i].minz = Infinity;
polygons[i].midz = 0;
for (var j = 0; j < polygons[i].verticies.length; j++) {
var z = rotated_verticies[polygons[i].verticies[j]].vector[2];
if (z > polygons[i].maxz) {
polygons[i].maxz = z;
}
if (z < polygons[i].minz) {
polygons[i].minz = z;
}
polygons[i].midz += z;
}
polygons[i].midz /= polygons[i].verticies.length;
}
polygons.sort((a, b) => b.midz - a.midz)
// polygons.sort((a,b) => Math.max(b.maxz - a.minz, b.minz - a.maxz))
// polygons.sort((a,b) => {
// if (a.minz < b.maxz) {
// return 0;
// }
// if (b.minz < a.maxz) {
// return -1;
// }
// return 0;
// })
Here's a code snippet:
class Tensor {
constructor(){
var input = this.takeInput(...arguments);
this.vector = input;
}
takeInput() {
var a = true;
for (var arg of arguments) {
if (typeof arg !== "number"){
a = false
}
}
if (a && arguments[2] !== true){
return new Array(...arguments);
}
else {
if (arguments[0] instanceof Tensor){
return arguments[0].vector;
}
else {
if (typeof arguments[0] === "number" && typeof arguments[1] === "number" && arguments[2] === true) {
var res = [];
for (var i = 0; i < arguments[0]; i++) {
res.push(arguments[1]);
}
return res;
}
}
}
}
// used for + - * /
change(f, input){
for (var i in this.vector) {
this.vector[i] = f(this.vector[i], input[i]);
}
return this;
}
copy() {
return new Tensor(...this.vector);
}
dimentions() {
return this.vector.length;
}
//-----------
len() {
var s = 0;
for (var dim of this.vector) {
s += dim ** 2;
}
return Math.sqrt(s);
}
norm() {
return this.div(this.dimentions(), this.len(), true)
}
add() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x + y, input);
}
sub() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x - y, input);
}
mult() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x * y, input);
}
div() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x / y, input);
}
dot() {
var input = this.takeInput(...arguments);
var res = 0;
for (var i in this.vector) {
res += this.vector[i] * input[i]
}
return res;
}
rotate() {
// WARNING: only for 3D currently!!!
var input = this.takeInput(...arguments);
var [x, y, z] = this.vector;
// rotate Z
var t_x = x * Math.cos(input[2]) - y * Math.sin(input[2])
y = y * Math.cos(input[2]) + x * Math.sin(input[2])
x = t_x
// rotate X
var t_y = y * Math.cos(input[0]) - z * Math.sin(input[0])
z = z * Math.cos(input[0]) + y * Math.sin(input[0])
y = t_y
// rotate Y
t_x = x * Math.cos(input[1]) + z * Math.sin(input[1])
z = z * Math.cos(input[1]) - x * Math.sin(input[1])
x = t_x
this.vector = [x, y, z];
return this;
}
}
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext("2d")
w = 300
h = 286
fov = 0.1
scale = 65;
offset = new Tensor(w / 2 - 5, h / 2 - 92, 0.1);
light = new Tensor(3.5, 0.5, 1).norm();
canvas.width = w;
canvas.height = h;
var verticies = [];
verticies.push(new Tensor(0.5, 1, 0))
verticies.push(new Tensor(0.5, -1, 0))
verticies.push(new Tensor(-1, 0, 0))
verticies.push(new Tensor(0, 0, 2))
var polygons = [];
polygons.push({
verticies: [0, 3, 1],
color: 'red',
nf: 1
});
polygons.push({
verticies: [2, 3, 0],
color: 'blue',
nf: 1
});
polygons.push({
verticies: [2, 3, 1],
color: 'green',
nf: -1
});
polygons.push({
verticies: [0, 1, 2],
color: 'yellow',
nf: -1
});
for (var i = 0; i < polygons.length; i++) {
polygons[i].id = i;
}
theta = new Tensor(1.5 * Math.PI, 0, 1.5 * Math.PI);
function loop() {
ctx.clearRect(0, 0, w, h);
rotated_verticies = [];
for (var i = 0; i < verticies.length; i++) {
rotated_verticies.push(verticies[i].copy().rotate(theta));
}
// z sorting
// dots_for_rendering.sort((a,b) => Math.sqrt((b.x)**2 + (b.y)**2) - Math.sqrt((a.x)**2 + (a.y)**2))
for (var i = 0; i < polygons.length; i++) {
polygons[i].maxz = -Infinity;
polygons[i].minz = Infinity;
polygons[i].midz = 0;
for (var j = 0; j < polygons[i].verticies.length; j++) {
var z = rotated_verticies[polygons[i].verticies[j]].vector[2];
// z += 1 * (Math.random() * 2 - 1)
if (z > polygons[i].maxz) {
polygons[i].maxz = z;
}
if (z < polygons[i].minz) {
polygons[i].minz = z;
}
polygons[i].midz += z;
}
polygons[i].midz /= polygons[i].verticies.length;
}
polygons.sort((a, b) => b.midz - a.midz)
// polygons.sort((a,b) => Math.max(b.maxz - a.minz, b.minz - a.maxz))
// polygons.sort((a,b) => {
// if (a.minz < b.maxz) {
// return 0;
// }
// if (b.minz < a.maxz) {
// return -1;
// }
// return 0;
// })
for (var i = 0; i < polygons.length; i++) {
var polygon_2 = [];
for (var j = 0; j < polygons[i].verticies.length; j++) {
var v = rotated_verticies[polygons[i].verticies[j]]
polygon_2.push(v.vector);
}
var norm = getNormal(polygon_2, polygons[i].nf);
// var rotated_light = light.copy().rotate(theta);
var brightness = Math.max(0, norm.dot(light))
//ctx.fillStyle = "hsl(31, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)";
ctx.fillStyle = "hsl(190, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)";
// ctx.fillStyle = polygons[i].color
ctx.beginPath();
for (var j = 0; j < polygons[i].verticies.length; j++) {
var vertex = rotated_verticies[polygons[i].verticies[j]].copy();
vertex.mult(scale, scale, 1);
vertex.add(offset);
var n = 1 + vertex.vector[2] * fov;
vertex.div(n, n, 1)
// console.log(vertex.vector)
if (j == 0) {
ctx.moveTo(vertex.vector[0], vertex.vector[1]);
} else {
ctx.lineTo(vertex.vector[0], vertex.vector[1]);
}
}
ctx.closePath();
ctx.fill()
// ctx.stroke()
polygons[i].mid = new Tensor(3, 0, true);
for (var k = 0; k < polygons[i].verticies.length; k++) {
var vertex = rotated_verticies[polygons[i].verticies[k]].copy();
vertex.mult(scale, scale, 1);
vertex.add(offset);
var n = 1 + vertex.vector[2] * fov;
vertex.div(n, n, 1)
polygons[i].mid.add(vertex);
}
polygons[i].mid.div(3, polygons[i].verticies.length, true);
ctx.fillStyle = "red"
ctx.font = '50px serif';
// ctx.fillText(polygons[i].id + ", " + polygons[i].nf, polygons[i].mid.vector[0], polygons[i].mid.vector[1])
}
// theta.add(theta.vector[0] + (0.01*mouseY - theta.vector[0]) * 0.1, 0, theta.vector[2] + (-0.01*mouseX - theta.vector[2]) * 0.1)
theta.add(0, -0.0375, 0);
// fov = (mouseX - w/2) * 0.001
requestAnimationFrame(loop);
}
loop();
// setInterval(loop, 1000 / 60)
function getNormal(polygon, nf) {
var Ax = polygon[1][0] - polygon[0][0];
var Ay = polygon[1][1] - polygon[0][1];
var Az = polygon[1][2] - polygon[0][2];
var Bx = polygon[2][0] - polygon[0][0];
var By = polygon[2][1] - polygon[0][1];
var Bz = polygon[2][2] - polygon[0][2];
var Nx = Ay * Bz - Az * By
var Ny = Az * Bx - Ax * Bz
var Nz = Ax * By - Ay * Bx
return new Tensor(nf * Nx, nf * Ny, nf * Nz);
}
function len(p1, p2) {
return Math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2]) ** 2);
}
mouseX = 0
mouseY = 0
onmousemove = (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name=”ad.size” content=”width=300,height=600”>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>...</title>
</head>
<body>
<canvas id="canvas" width="300" height="238"></canvas>
</body>
</html>
** Edit:
Ok, we've significantly edited the code, see this fiddle, and the following code snippet below. It's still not working correctly, we think it's something to do with the first line of this piece of code, any ideas?
if (polygons[i].mid.copy().sub(camera).dot(norm) < 0) {
var pathelem = document.createElementNS("http://www.w3.org/2000/svg", "path");
pathelem.setAttribute("d", path);
pathelem.setAttribute("fill", "hsl(31, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)");
svg.appendChild(pathelem);
}
class Tensor {
constructor(){
var input = this.takeInput(...arguments);
this.vector = input;
}
takeInput() {
var a = true;
for (var arg of arguments) {
if (typeof arg !== "number"){
a = false
}
}
if (a && arguments[2] !== true){
return new Array(...arguments);
}
else {
if (arguments[0] instanceof Tensor){
return arguments[0].vector;
}
else {
if (typeof arguments[0] === "number" && typeof arguments[1] === "number" && arguments[2] === true) {
var res = [];
for (var i = 0; i < arguments[0]; i++) {
res.push(arguments[1]);
}
return res;
}
}
}
}
// used for + - * /
change(f, input){
for (var i in this.vector) {
this.vector[i] = f(this.vector[i], input[i]);
}
return this;
}
copy() {
return new Tensor(...this.vector);
}
dimentions() {
return this.vector.length;
}
//-----------
len() {
var s = 0;
for (var dim of this.vector) {
s += dim ** 2;
}
return Math.sqrt(s);
}
norm() {
return this.div(this.dimentions(), this.len(), true)
}
add() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x + y, input);
}
sub() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x - y, input);
}
mult() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x * y, input);
}
div() {
var input = this.takeInput(...arguments);
return this.change((x, y) => x / y, input);
}
dot() {
var input = this.takeInput(...arguments);
var res = 0;
for (var i in this.vector) {
res += this.vector[i] * input[i]
}
return res;
}
rotate() {
// WARNING: only for 3D currently!!!
var input = this.takeInput(...arguments);
var [x, y, z] = this.vector;
// rotate Z
var t_x = x * Math.cos(input[2]) - y * Math.sin(input[2])
y = y * Math.cos(input[2]) + x * Math.sin(input[2])
x = t_x
// rotate X
var t_y = y * Math.cos(input[0]) - z * Math.sin(input[0])
z = z * Math.cos(input[0]) + y * Math.sin(input[0])
y = t_y
// rotate Y
t_x = x * Math.cos(input[1]) + z * Math.sin(input[1])
z = z * Math.cos(input[1]) - x * Math.sin(input[1])
x = t_x
this.vector = [x, y, z];
return this;
}
}
var svg = document.getElementById('svg')
w = 300
h = 286
fov = 0.1
scale = 65;
camera = new Tensor(-w / 2 + 5, -h / 2 + 92, 0.1);
light = new Tensor(3.5, 0.5, 1).norm();
svg.setAttribute('width', w);
svg.setAttribute('height', h);
var vertices = [
new Tensor(0.5, 1, 0),
new Tensor(0.5, -1, 0),
new Tensor(-1, 0, 0),
new Tensor(0, 0, 2)
];
var polygons = [];
polygons.push({
vertices: [0, 3, 1],
color: 'red',
nf: 1
});
polygons.push({
vertices: [2, 3, 0],
color: 'blue',
nf: 1
});
polygons.push({
vertices: [2, 3, 1],
color: 'green',
nf: -1
});
polygons.push({
vertices: [0, 1, 2],
color: 'yellow',
nf: 1
});
for (var i = 0; i < polygons.length; i++) {
polygons[i].id = i;
}
theta = new Tensor(1.5 * Math.PI, 0, 1.5 * Math.PI);
function loop() {
// ctx.clearRect(0, 0, w, h);
svg.innerHTML = "";
rotated_vertices = [];
for (var i = 0; i < vertices.length; i++) {
rotated_vertices.push(vertices[i].copy().rotate(theta));
}
// z sorting
// dots_for_rendering.sort((a,b) => Math.sqrt((b.x)**2 + (b.y)**2) - Math.sqrt((a.x)**2 + (a.y)**2))
for (var i = 0; i < polygons.length; i++) {
polygons[i].maxz = -Infinity;
polygons[i].minz = Infinity;
polygons[i].midz = 0;
for (var j = 0; j < polygons[i].vertices.length; j++) {
var z = rotated_vertices[polygons[i].vertices[j]].vector[2];
// z += 1 * (Math.random() * 2 - 1)
if (z > polygons[i].maxz) {
polygons[i].maxz = z;
}
if (z < polygons[i].minz) {
polygons[i].minz = z;
}
polygons[i].midz += z;
}
polygons[i].midz /= polygons[i].vertices.length;
polygons[i].mid = new Tensor(3, 0, true);
for (var k = 0; k < polygons[i].vertices.length; k++) {
var vertex = rotated_vertices[polygons[i].vertices[k]].copy();
vertex.mult(scale, scale, 1);
vertex.sub(camera);
var n = 1 + vertex.vector[2] * fov;
vertex.div(n, n, 1)
polygons[i].mid.add(vertex);
}
polygons[i].mid.div(3, polygons[i].vertices.length, true);
}
polygons.sort((a, b) => b.midz - a.midz)
// polygons.sort((a,b) => Math.max(b.maxz - a.minz, b.minz - a.maxz))
// polygons.sort((a,b) => {
// if (a.minz < b.maxz) {
// return 0;
// }
// if (b.minz < a.maxz) {
// return -1;
// }
// return 0;
// })
for (var i = 0; i < polygons.length; i++) {
var polygons_embedded_point_coords = [];
for (var j = 0; j < polygons[i].vertices.length; j++) {
var v = rotated_vertices[polygons[i].vertices[j]]
polygons_embedded_point_coords.push(v.vector);
}
var norm = getNormal(polygons_embedded_point_coords, polygons[i].nf);
// var rotated_light = light.copy().rotate(theta);
var brightness = Math.max(0, norm.dot(light))
// ctx.fillStyle = "hsl(31, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)";
// ctx.fillStyle = polygons[i].color
// ctx.beginPath();
var path = [];
for (var j = 0; j < polygons[i].vertices.length; j++) {
var vertex = rotated_vertices[polygons[i].vertices[j]].copy();
vertex.mult(scale, scale, 1);
vertex.sub(camera);
var n = 1 + vertex.vector[2] * fov;
vertex.div(n, n, 1)
// console.log(vertex.vector)
if (j == 0) {
// ctx.moveTo(vertex.vector[0], vertex.vector[1]);
path.push("M "+vertex.vector[0]+" "+vertex.vector[1]);
} else {
path.push("L "+vertex.vector[0]+" "+vertex.vector[1]);
// ctx.lineTo(vertex.vector[0], vertex.vector[1]);
}
}
// that should work
if (polygons[i].mid.copy().sub(camera).dot(norm) < 0) {
var pathelem = document.createElementNS("http://www.w3.org/2000/svg", "path");
pathelem.setAttribute("d", path);
pathelem.setAttribute("fill", "hsl(31, "+100+"%, "+(Math.min(9.0*brightness + 40, 100))+"%)");
svg.appendChild(pathelem);
}
// ctx.fillStyle = "red"
// ctx.font = '15px serif';
//
// ctx.fillText(polygons[i].id + ", " + polygons[i].nf, polygons[i].mid.vector[0], polygons[i].mid.vector[1])
}
// theta.add(theta.vector[0] + (0.01*mouseY - theta.vector[0]) * 0.1, 0, theta.vector[2] + (-0.01*mouseX - theta.vector[2]) * 0.1)
theta.add(0, -0.0375, 0);
// fov = (mouseX - w/2) * 0.001
requestAnimationFrame(loop);
}
loop();
// setInterval(loop, 1000 / 60)
function getNormal(vertices, nf) {
var Ax = vertices[1][0] - vertices[0][0];
var Ay = vertices[1][1] - vertices[0][1];
var Az = vertices[1][2] - vertices[0][2];
var Bx = vertices[2][0] - vertices[0][0];
var By = vertices[2][1] - vertices[0][1];
var Bz = vertices[2][2] - vertices[0][2];
var Nx = Ay * Bz - Az * By
var Ny = Az * Bx - Ax * Bz
var Nz = Ax * By - Ay * Bx
return new Tensor(nf * Nx, nf * Ny, nf * Nz);
}
function len(p1, p2) {
return Math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2]) ** 2);
}
mouseX = 0
mouseY = 0
onmousemove = (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<script src="Tensor.js"></script>
<script src="script-tensors-svg.js" async defer></script>
</head>
<body>
<!-- <canvas id="canvas"></canvas> -->
<svg id="svg" xmlns="http://www.w3.org/2000/svg"></svg>
</body>
</html>
Sorting by average Z just doesn't give you a reliable rendering order. Since your shape is convex, though, you don't need to sort at all.
Make sure the vertices of each triangle are sorted so that you can consistently get a surface normal that points outward. Then, just don't render any triangles with normals that point away from the camera, i.e.:
if (vector_from_camera_to_poly_midpoint \dot poly_normal < 0) {
//render the poly
}
Now you will only render the side of the object that is facing the camera -- none of the polygons will overlap, so you can render them in any order.
Related
I want to make an artwork with p5.js but there is a problem with the code that I couldn't solve. I want to learn specific color percentage of an image. The code works well at the background() function but it is not working with the image.
let a;
let img;
let f;
let k;
let r;
let b;
let g;
function preload() {
img = loadImage("assets/knoll.jpg");
}
function setup() {
createCanvas(img.width, img.height);
pixelDensity(1);
noLoop();
}
function draw() {
background(84);
loadPixels();
let f = 0;
var k = 0;
let r;
let g;
let b;
for (let i = 0; i <= width * height * 4; i++) {
if (
pixels[i + 0] === 84 &&
pixels[i + 1] === 84 &&
pixels[i + 2] === 84 &&
pixels[i + 3] === 255
) {
f = f + 1;
}
}
updatePixels();
print((f * 100) / (width * height));
}
This version is working with the background.
let a;
let img;
let f;
let k;
let r;
let b;
let g;
function preload() {
img = loadImage("assets/knoll.jpg");
}
function setup() {
createCanvas(img.width, img.height);
pixelDensity(1);
image(img, 0, 0);
noLoop();
}
function draw() {
img.loadPixels();
let f = 0;
var k = 0;
let r;
let g;
let b;
//for (let x = 0; x <= width; x ++) {
// for(let y = 0; y <= height; y ++){
//var i = (x + y * width) * 4;
for (let i = 0; i <= width * height * 4; i++) {
if (
img.pixels[i + 0] === 84 &&
img.pixels[i + 1] === 84 &&
img.pixels[i + 2] === 84 &&
img.pixels[i + 3] === 255
) {
f = f + 1;
}
}
img.updatePixels();
print((f * 100) / (width * height));
}
But result of the second version is always zero.
Is it real to fill all polygons? Codepen. As I get it ThreeGeoJSON can not fill polygons, outlines only. Also I've tried Earcut for triangulation.
drawThreeGeo(data, radius, 'sphere', {color: 'yellow' // I want to edit fill color of lands, not outline color})
I suggest you to use better map: countries.geojson
The solution consists of following steps, for each shape:
Put vertices inside of shape, so that when triangulated, it could bend around the globe,
Run https://github.com/mapbox/delaunator to build triangulated mesh,
Step 2 will create triangles outside the shape too, we need to remove them by looking into each triangle, and deciding if it belongs to shape or not,
Bend the triangulated mesh with convertCoordinates
You can test my jsfiddle: http://jsfiddle.net/mmalex/pg5a4132/
Warning: it is quite slow because of high level of detail of input.
The complete solution:
/* Draw GeoJSON
Iterates through the latitude and longitude values, converts the values to XYZ coordinates, and draws the geoJSON geometries.
*/
let TRIANGULATION_DENSITY = 5; // make it smaller for more dense mesh
function verts2array(coords) {
let flat = [];
for (let k = 0; k < coords.length; k++) {
flat.push(coords[k][0], coords[k][1]);
}
return flat;
}
function array2verts(arr) {
let coords = [];
for (let k = 0; k < arr.length; k += 2) {
coords.push([arr[k], arr[k + 1]]);
}
return coords;
}
function findBBox(points) {
let min = {
x: 1e99,
y: 1e99
};
let max = {
x: -1e99,
y: -1e99
};
for (var point_num = 0; point_num < points.length; point_num++) {
if (points[point_num][0] < min.x) {
min.x = points[point_num][0];
}
if (points[point_num][0] > max.x) {
max.x = points[point_num][0];
}
if (points[point_num][1] < min.y) {
min.y = points[point_num][1];
}
if (points[point_num][1] > max.y) {
max.y = points[point_num][1];
}
}
return {
min: min,
max: max
};
}
function isInside(point, vs) {
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
var x = point[0],
y = point[1];
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0],
yi = vs[i][1];
var xj = vs[j][0],
yj = vs[j][1];
var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
}
function genInnerVerts(points) {
let res = [];
for (let k = 0; k < points.length; k++) {
res.push(points[k]);
}
let bbox = findBBox(points);
let step = TRIANGULATION_DENSITY;
let k = 0;
for (let x = bbox.min.x + step / 2; x < bbox.max.x; x += step) {
for (let y = bbox.min.y + step / 2; y < bbox.max.y; y += step) {
let newp = [x, y];
if (isInside(newp, points)) {
res.push(newp);
}
k++;
}
}
return res;
}
function removeOuterTriangles(delaunator, points) {
let newTriangles = [];
for (let k = 0; k < delaunator.triangles.length; k += 3) {
let t0 = delaunator.triangles[k];
let t1 = delaunator.triangles[k + 1];
let t2 = delaunator.triangles[k + 2];
let x0 = delaunator.coords[2 * t0];
let y0 = delaunator.coords[2 * t0 + 1];
let x1 = delaunator.coords[2 * t1];
let y1 = delaunator.coords[2 * t1 + 1];
let x2 = delaunator.coords[2 * t2];
let y2 = delaunator.coords[2 * t2 + 1];
let midx = (x0 + x1 + x2) / 3;
let midy = (y0 + y1 + y2) / 3;
let midp = [midx, midy];
if (isInside(midp, points)) {
newTriangles.push(t0, t1, t2);
}
}
delaunator.triangles = newTriangles;
}
var x_values = [];
var y_values = [];
var z_values = [];
var progressEl = $("#progress");
var clickableObjects = [];
var someColors = [0x909090, 0x808080, 0xa0a0a0, 0x929292, 0x858585, 0xa9a9a9];
function drawThreeGeo(json, radius, shape, options) {
var json_geom = createGeometryArray(json);
var convertCoordinates = getConversionFunctionName(shape);
for (var geom_num = 0; geom_num < json_geom.length; geom_num++) {
console.log("Processing " + geom_num + " of " + json_geom.length + " shapes");
// if (geom_num !== 17) continue;
// if (geom_num > 10) break;
if (json_geom[geom_num].type == 'Point') {
convertCoordinates(json_geom[geom_num].coordinates, radius);
drawParticle(y_values[0], z_values[0], x_values[0], options);
} else if (json_geom[geom_num].type == 'MultiPoint') {
for (let point_num = 0; point_num < json_geom[geom_num].coordinates.length; point_num++) {
convertCoordinates(json_geom[geom_num].coordinates[point_num], radius);
drawParticle(y_values[0], z_values[0], x_values[0], options);
}
} else if (json_geom[geom_num].type == 'LineString') {
for (let point_num = 0; point_num < json_geom[geom_num].coordinates.length; point_num++) {
convertCoordinates(json_geom[geom_num].coordinates[point_num], radius);
}
drawLine(y_values, z_values, x_values, options);
} else if (json_geom[geom_num].type == 'Polygon') {
let group = createGroup(geom_num);
let randomColor = someColors[Math.floor(someColors.length * Math.random())];
for (let segment_num = 0; segment_num < json_geom[geom_num].coordinates.length; segment_num++) {
let coords = json_geom[geom_num].coordinates[segment_num];
let refined = genInnerVerts(coords);
let flat = verts2array(refined);
let d = new Delaunator(flat);
removeOuterTriangles(d, coords);
let delaunayVerts = array2verts(d.coords);
for (let point_num = 0; point_num < delaunayVerts.length; point_num++) {
// convertCoordinates(refined[point_num], radius);
convertCoordinates(delaunayVerts[point_num], radius);
}
// drawLine(y_values, z_values, x_values, options);
drawMesh(group, y_values, z_values, x_values, d.triangles, randomColor);
}
} else if (json_geom[geom_num].type == 'MultiLineString') {
for (let segment_num = 0; segment_num < json_geom[geom_num].coordinates.length; segment_num++) {
let coords = json_geom[geom_num].coordinates[segment_num];
for (let point_num = 0; point_num < coords.length; point_num++) {
convertCoordinates(json_geom[geom_num].coordinates[segment_num][point_num], radius);
}
drawLine(y_values, z_values, x_values);
}
} else if (json_geom[geom_num].type == 'MultiPolygon') {
let group = createGroup(geom_num);
let randomColor = someColors[Math.floor(someColors.length * Math.random())];
for (let polygon_num = 0; polygon_num < json_geom[geom_num].coordinates.length; polygon_num++) {
for (let segment_num = 0; segment_num < json_geom[geom_num].coordinates[polygon_num].length; segment_num++) {
let coords = json_geom[geom_num].coordinates[polygon_num][segment_num];
let refined = genInnerVerts(coords);
let flat = verts2array(refined);
let d = new Delaunator(flat);
removeOuterTriangles(d, coords);
let delaunayVerts = array2verts(d.coords);
for (let point_num = 0; point_num < delaunayVerts.length; point_num++) {
// convertCoordinates(refined[point_num], radius);
convertCoordinates(delaunayVerts[point_num], radius);
}
// drawLine(y_values, z_values, x_values, options);
drawMesh(group, y_values, z_values, x_values, d.triangles, randomColor)
}
}
} else {
throw new Error('The geoJSON is not valid.');
}
}
progressEl.text("Complete!");
}
function createGeometryArray(json) {
var geometry_array = [];
if (json.type == 'Feature') {
geometry_array.push(json.geometry);
} else if (json.type == 'FeatureCollection') {
for (var feature_num = 0; feature_num < json.features.length; feature_num++) {
geometry_array.push(json.features[feature_num].geometry);
}
} else if (json.type == 'GeometryCollection') {
for (var geom_num = 0; geom_num < json.geometries.length; geom_num++) {
geometry_array.push(json.geometries[geom_num]);
}
} else {
throw new Error('The geoJSON is not valid.');
}
//alert(geometry_array.length);
return geometry_array;
}
function getConversionFunctionName(shape) {
var conversionFunctionName;
if (shape == 'sphere') {
conversionFunctionName = convertToSphereCoords;
} else if (shape == 'plane') {
conversionFunctionName = convertToPlaneCoords;
} else {
throw new Error('The shape that you specified is not valid.');
}
return conversionFunctionName;
}
function convertToSphereCoords(coordinates_array, sphere_radius) {
var lon = coordinates_array[0];
var lat = coordinates_array[1];
x_values.push(Math.cos(lat * Math.PI / 180) * Math.cos(lon * Math.PI / 180) * sphere_radius);
y_values.push(Math.cos(lat * Math.PI / 180) * Math.sin(lon * Math.PI / 180) * sphere_radius);
z_values.push(Math.sin(lat * Math.PI / 180) * sphere_radius);
}
function convertToPlaneCoords(coordinates_array, radius) {
var lon = coordinates_array[0];
var lat = coordinates_array[1];
var plane_offset = radius / 2;
z_values.push((lat / 180) * radius);
y_values.push((lon / 180) * radius);
}
function drawParticle(x, y, z, options) {
var particle_geom = new THREE.Geometry();
particle_geom.vertices.push(new THREE.Vector3(x, y, z));
var particle_material = new THREE.ParticleSystemMaterial(options);
var particle = new THREE.ParticleSystem(particle_geom, particle_material);
scene.add(particle);
clearArrays();
}
function drawLine(x_values, y_values, z_values, options) {
var line_geom = new THREE.Geometry();
createVertexForEachPoint(line_geom, x_values, y_values, z_values);
var line_material = new THREE.LineBasicMaterial(options);
var line = new THREE.Line(line_geom, line_material);
scene.add(line);
clearArrays();
}
function createGroup(idx) {
var group = new THREE.Group();
group.userData.userText = "_" + idx;
scene.add(group);
return group;
}
function drawMesh(group, x_values, y_values, z_values, triangles, color) {
var geometry = new THREE.Geometry();
for (let k = 0; k < x_values.length; k++) {
geometry.vertices.push(
new THREE.Vector3(x_values[k], y_values[k], z_values[k])
);
}
for (let k = 0; k < triangles.length; k += 3) {
geometry.faces.push(new THREE.Face3(triangles[k], triangles[k + 1], triangles[k + 2]));
}
geometry.computeVertexNormals()
var mesh = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
side: THREE.DoubleSide,
color: color,
wireframe: true
}));
clickableObjects.push(mesh);
group.add(mesh);
clearArrays();
}
function createVertexForEachPoint(object_geometry, values_axis1, values_axis2, values_axis3) {
for (var i = 0; i < values_axis1.length; i++) {
object_geometry.vertices.push(new THREE.Vector3(values_axis1[i],
values_axis2[i], values_axis3[i]));
}
}
function clearArrays() {
x_values.length = 0;
y_values.length = 0;
z_values.length = 0;
}
var scene = new THREE.Scene();
var raycaster = new THREE.Raycaster();
var camera = new THREE.PerspectiveCamera(32, window.innerWidth / window.innerHeight, 0.5, 1000);
var radius = 200;
camera.position.x = 140.7744005681177;
camera.position.y = 160.30950538100814;
camera.position.z = 131.8637122564268;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
var light = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);
scene.add(light);
var light = new THREE.AmbientLight(0x505050); // soft white light
scene.add(light);
var geometry = new THREE.SphereGeometry(radius, 32, 32);
var material = new THREE.MeshPhongMaterial({
color: 0x1e90ff
});
var sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
var test_json = $.getJSON("https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson", function(data) {
drawThreeGeo(data, radius + 1, 'sphere', {
color: 'yellow'
})
});
var controls = new THREE.TrackballControls(camera);
controls.rotateSpeed *= 0.5;
controls.zoomSpeed *= 0.5;
controls.panSpeed *= 0.5;
controls.minDistance = 10;
controls.maxDistance = 5000;
function render() {
controls.update();
requestAnimationFrame(render);
renderer.setClearColor(0x1e90ff, 1);
renderer.render(scene, camera);
}
render()
function convert_lat_lng(lat, lng, radius) {
var phi = (90 - lat) * Math.PI / 180,
theta = (180 - lng) * Math.PI / 180,
position = new THREE.Vector3();
position.x = radius * Math.sin(phi) * Math.cos(theta);
position.y = radius * Math.cos(phi);
position.z = radius * Math.sin(phi) * Math.sin(theta);
return position;
}
// this will be 2D coordinates of the current mouse position, [0,0] is middle of the screen.
var mouse = new THREE.Vector2();
var hoveredObj; // this objects is hovered at the moment
// Following two functions will convert mouse coordinates
// from screen to three.js system (where [0,0] is in the middle of the screen)
function updateMouseCoords(event, coordsObj) {
coordsObj.x = ((event.clientX - renderer.domElement.offsetLeft + 0.5) / window.innerWidth) * 2 - 1;
coordsObj.y = -((event.clientY - renderer.domElement.offsetTop + 0.5) / window.innerHeight) * 2 + 1;
}
function onMouseMove(event) {
updateMouseCoords(event, mouse);
latestMouseProjection = undefined;
clickedObj = undefined;
raycaster.setFromCamera(mouse, camera); {
var intersects = raycaster.intersectObjects(clickableObjects);
let setGroupColor = function(group, colorHex) {
for (let i = 0; i < group.children.length; i++) {
if (!group.children[i].userData.color) {
group.children[i].userData.color = hoveredObj.parent.children[i].material.color.clone();
group.children[i].material.color.set(colorHex);
group.children[i].material.needsUpdate = true;
}
}
}
let resetGroupColor = function(group) {
// set all shapes of the group to initial color
for (let i = 0; i < group.children.length; i++) {
if (group.children[i].userData.color) {
group.children[i].material.color = group.children[i].userData.color;
delete group.children[i].userData.color;
group.children[i].material.needsUpdate = true;
}
}
}
if (intersects.length > 0) {
latestMouseProjection = intersects[0].point;
// reset colors for previously hovered group
if (hoveredObj) {
resetGroupColor(hoveredObj.parent);
}
hoveredObj = intersects[0].object;
if (!hoveredObj.parent) return;
// set colors for hovered group
setGroupColor(hoveredObj.parent, 0xff0000);
} else {
if (!hoveredObj || !hoveredObj.parent) return;
// nothing is hovered => just reset colors on the last group
resetGroupColor(hoveredObj.parent);
hoveredObj = undefined;
console.log("<deselected>");
}
}
}
window.addEventListener('mousemove', onMouseMove, false);
You'd need to split each country into a separate geometry, use a raycaster to find out which country the mouse is over, then change its material.color. You can see raycasting in action in this example with source code available on the bottom-right corner. The key lines in that example are:
function onDocumentMouseMove( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function render() {
// find intersections
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
if ( INTERSECTED != intersects[ 0 ].object ) {
if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
INTERSECTED = intersects[ 0 ].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex( 0xff0000 );
}
} else {
if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
INTERSECTED = null;
}
renderer.render( scene, camera );
}
I am working on a augmented reality app for browsers which detects a QR code on a DIN A4 paper and projects an 3D oject in the room.
So far i have a working solution which is working with ARUCO codes, but for my app i need an QR code to project an 3D object in the right perspective. This works also with ARUCO codes, but just on close distance. If the marker is to far away it does not work for me. The solution of this is, to scan a QR code, because the contours can be detected on larger dinstances.
I have a solution which is working with QR codes but the code is wirtten in C++.
I have tried to recode te C++ program to JavaScript.
This is the woking solution which works fine with ARUCO codes in JavaScript:
var JS = http://jeromeetienne.github.io/slides/augmentedrealitywiththreejs/
this is the basic file: https://github.com/jeromeetienne/arplayerforthreejs
This is the code in C++ which is working with QR codes:
var C++ = https://github.com/xingdi-eric-yuan/qr-decoder
So far i have wrote the code from crdecoder.cpp in JavaScript.
Instead of the ARUCO tracking I want wo use the script from qrdecoder.cpp to detect a QR code and get the position.
The code should already detect the contours from an QR code and write it in "this.vecpair;" but it is still not working...
The interface for the decoding in JS code is in the file "threex.jsarucomarker.js" and the function is "QR.Detector();"
And this is my still not finished JS script which is a mix of the ARUCO aruco.js code from JS and the QR logic from the C++ qrdecoder.cpp script.
var QR = QR || {};
QR.Marker = function(id, corners){
this.id = id;
this.corners = corners;
};
QR.Detector = function(){
this.grey = new CV.Image();
this.thres = new CV.Image();
this.homography = new CV.Image();
this.binary = [];
this.cont = [];
this.vec4i = [];
this.contours = this.cont.contours = [];
};
QR.Detector.prototype.detect = function(image){
CV.grayscale(image, this.grey);
CV.adaptiveThreshold(this.grey, this.thres, 2, 7);
this.contours = CV.findContours(this.thres, this.binary);
//this.contours = this.findLimitedConturs(this.thres, 8.00, 0.2 * image.width * image.height);
// console.log(this.contours);
this.vecpair = this.getContourPair(this.contours);
console.log(this.vecpair);
// ARUCO CODE.. MAYBE NOT NECESSARY
//this.candidates = this.findCandidates(this.contours, image.width * 0.10, 0.05, 10);
//this.candidates = this.clockwiseCorners(this.candidates);
//this.candidates = this.notTooNear(this.candidates, 10);
//return this.findMarkers(this.grey, this.candidates, 49);
};
/* C++
struct FinderPattern{
Point topleft;
Point topright;
Point bottomleft;
FinderPattern(Point a, Point b, Point c) : topleft(a), topright(b), bottomleft(c) {}
};
bool compareContourAreas ( std::vector<cv::Point> contour1, std::vector<cv::Point> contour2 ) {
double i = fabs( contourArea(cv::Mat(contour1)) );
double j = fabs( contourArea(cv::Mat(contour2)) );
return ( i > j );
}
*/
QR.Detector.prototype.compareContourAreas = function(c1,c2){
var i = abs(CV.contourArea(c1));
var j = abs(CV.contourArea(c2));
console.log(i+' -- '+j);
return (i > j);
};
/* C++
Point getContourCentre(CONT& vec){
double tempx = 0.0, tempy = 0.0;
for(int i=0; i<vec.size(); i++){
tempx += vec[i].x;
tempy += vec[i].y;
}
return Point(tempx / (double)vec.size(), tempy / (double)vec.size());
}
*/
QR.Detector.prototype.getContourCentre = function(vec){
};
/* C++
bool isContourInsideContour(CONT& in, CONT& out){
for(int i = 0; i<in.size(); i++){
if(pointPolygonTest(out, in[i], false) <= 0) return false;
}
return true;
}
*/
QR.Detector.prototype.isContourInsideContour = function(c_in, c_out){
for(var i = 0; i<c_in.length; i++){
//console.log('-- '+c_out+' -- '+c_in[i]);
if(CV.pointPolygonTest(c_out, c_in[i]) == false) return false;
}
return true;
};
/* C++
vector<CONT > findLimitedConturs(Mat contour, float minPix, float maxPix){
vector<CONT > contours;
vector<Vec4i> hierarchy;
findContours(contour, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
cout<<"contours.size = "<<contours.size()<<endl;
int m = 0;
while(m < contours.size()){
if(contourArea(contours[m]) <= minPix){
contours.erase(contours.begin() + m);
}else if(contourArea(contours[m]) > maxPix){
contours.erase(contours.begin() + m);
}else ++ m;
}
cout<<"contours.size = "<<contours.size()<<endl;
return contours;
}
*/
QR.Detector.prototype.findLimitedConturs = function(contour, minPix, maxPix){
this.contours = this.cont.contours = [];
this.hierarchy = this.vec4i.hierarchy = [];
CV.findContours(contour, this.contours);
// console.log(this.contours);
var m = 0;
while(m < this.contours.length){
if(CV.contourArea(this.contours[m]) <= minPix){
this.contours.splice(this.contours[0] + m,1);
}else if(CV.contourArea(this.contours[m]) > maxPix){
this.contours.splice(this.contours[0] + m,1);
}else ++ m;
}
// console.log(this.contours.length);
return this.contours;
};
/*
vector<vector<CONT > > getContourPair(vector<CONT > &contours){
vector<vector<CONT > > vecpair;
vector<bool> bflag(contours.size(), false);
for(int i = 0; i<contours.size() - 1; i++){
if(bflag[i]) continue;
vector<CONT > temp;
temp.push_back(contours[i]);
for(int j = i + 1; j<contours.size(); j++){
if(isContourInsideContour(contours[j], contours[i])){
temp.push_back(contours[j]);
bflag[j] = true;
}
}
if(temp.size() > 1){
vecpair.push_back(temp);
}
}
bflag.clear();
for(int i=0; i<vecpair.size(); i++){
sort(vecpair[i].begin(), vecpair[i].end(), compareContourAreas);
}
return vecpair;
}
*/
QR.Detector.prototype.getContourPair = function(contours){
this.vecpair = this.cont.vecpair = [];
var bflag = new Array(contours.length, false); // similar to c++: vector<bool> bflag(contours.size(), false);?
for(var i = 0; i<contours.length - 1; i++){
if(bflag[i] == false){ //similar to c++: if(bflag[i]) continue; ??
var temp = this.cont.temp = [];
//console.log(contours[i]);
temp.push(contours[i]); //similar to c++: temp.push_back(contours[i]); ??
for(var j = i + 1; j<contours.length; j++){
if(this.isContourInsideContour(contours[j], contours[i])){
temp.push(contours[j]);
bflag[j] = true;
// console.log('true');
}
}
if(temp.length > 1){
this.vecpair.push(temp);
}
}
}
//console.log(this.vecpair);
bflag = [];
//console.log(this.vecpair.length);
for(i=0; i<this.vecpair.length; i++){
// sort(this.vecpair[0], this.vecpair[this.vecpair.length], compareContourAreas);
this.vecpair.sort(function(){
console.log('hier');
this.compareContourAreas(this.vecpair[i], this.vecpair[i].length);
});
// console.log(this.vecpair);
}
return this.vecpair;
};
/* C++
void eliminatePairs(vector<vector<CONT > >& vecpair, double minRatio, double maxRatio){
cout<<"maxRatio = "<<maxRatio<<endl;
int m = 0;
bool flag = false;
while(m < vecpair.size()){
flag = false;
if(vecpair[m].size() < 3){
vecpair.erase(vecpair.begin() + m);
continue;
}
for(int i=0; i<vecpair[m].size() - 1; i++){
double area1 = contourArea(vecpair[m][i]);
double area2 = contourArea(vecpair[m][i + 1]);
if(area1 / area2 < minRatio || area1 / area2 > maxRatio){
vecpair.erase(vecpair.begin() + m);
flag = true;
break;
}
}
if(!flag){
++ m;
}
}
if(vecpair.size() > 3){
eliminatePairs(vecpair, minRatio, maxRatio * 0.9);
}
}
*/
QR.Detector.prototype.eliminatePairs = function(){};
/* C++
double getDistance(Point a, Point b){
return sqrt(pow((a.x - b.x), 2) + pow((a.y - b.y), 2));
}
*/
QR.Detector.prototype.getDistance = function(){};
/* C++
FinderPattern getFinderPattern(vector<vector<CONT > > &vecpair){
Point pt1 = getContourCentre(vecpair[0][vecpair[0].size() - 1]);
Point pt2 = getContourCentre(vecpair[1][vecpair[1].size() - 1]);
Point pt3 = getContourCentre(vecpair[2][vecpair[2].size() - 1]);
double d12 = getDistance(pt1, pt2);
double d13 = getDistance(pt1, pt3);
double d23 = getDistance(pt2, pt3);
double x1, y1, x2, y2, x3, y3;
double Max = max(d12, max(d13, d23));
Point p1, p2, p3;
if(Max == d12){
p1 = pt1;
p2 = pt2;
p3 = pt3;
}else if(Max == d13){
p1 = pt1;
p2 = pt3;
p3 = pt2;
}else if(Max == d23){
p1 = pt2;
p2 = pt3;
p3 = pt1;
}
x1 = p1.x;
y1 = p1.y;
x2 = p2.x;
y2 = p2.y;
x3 = p3.x;
y3 = p3.y;
if(x1 == x2){
if(y1 > y2){
if(x3 < x1){
return FinderPattern(p3, p2, p1);
}else{
return FinderPattern(p3, p1, p2);
}
}else{
if(x3 < x1){
return FinderPattern(p3, p1, p2);
}else{
return FinderPattern(p3, p2, p1);
}
}
}else{
double newy = (y2 - y1) / (x2 - x1) * x3 + y1 - (y2 - y1) / (x2 - x1) * x1;
if(x1 > x2){
if(newy < y3){
return FinderPattern(p3, p2, p1);
}else{
return FinderPattern(p3, p1, p2);
}
}else{
if(newy < y3){
return FinderPattern(p3, p1, p2);
}else{
return FinderPattern(p3, p2, p1);
}
}
}
}
*/
QR.Detector.prototype.getFinderPattern = function(){};
This are my added CV functions for the detector
The basic file is "cv.js" from the JavaScript project above https://github.com/jeromeetienne/arplayerforthreejs
This functions should work similar to the C++ versions
pointPolygonTest() = http://docs.opencv.org/2.4/doc/tutorials/imgproc/shapedescriptors/point_polygon_test/point_polygon_test.html
contourArea() = http://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#double
//src: http://jsfromhell.com/math/is-point-in-poly
CV.pointPolygonTest = function(poly, pt){
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
&& (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
&& (c = !c);
return c;
};
//http://stackoverflow.com/questions/16285134/calculating-polygon-area
CV.contourArea = function(cont){
//console.log('cont: '+cont);
var area = 0; // Accumulates area in the loop
var j = cont.length-1; // The last vertex is the 'previous' one to the first
for (var i=0; i<cont.length; i++)
{
area = area + (cont[j].x+cont[i].x) * (cont[j].y+cont[i].y)
//area = area + (X[j]+X[i]) * (Y[j]-Y[i]);
j = i; //j is previous vertex to i
}
return area/2;
};
Working JavaScript Version wich detects the contours of an QR Code
var CV = CV || {};
CV.Image = function(width, height, data){
this.width = width || 0;
this.height = height || 0;
this.data = data || [];
};
CV.grayscale = function(imageSrc, imageDst){
var src = imageSrc.data, dst = imageDst.data, len = src.length,
i = 0, j = 0;
for (; i < len; i += 4){
dst[j ++] =
(src[i] * 0.299 + src[i + 1] * 0.587 + src[i + 2] * 0.114 + 0.5) & 0xff;
}
imageDst.width = imageSrc.width;
imageDst.height = imageSrc.height;
return imageDst;
};
CV.threshold = function(imageSrc, imageDst, threshold){
var src = imageSrc.data, dst = imageDst.data,
len = src.length, tab = [], i;
for (i = 0; i < 256; ++ i){
tab[i] = i <= threshold? 0: 255;
}
for (i = 0; i < len; ++ i){
dst[i] = tab[ src[i] ];
}
imageDst.width = imageSrc.width;
imageDst.height = imageSrc.height;
return imageDst;
};
CV.adaptiveThreshold = function(imageSrc, imageDst, kernelSize, threshold){
var src = imageSrc.data, dst = imageDst.data, len = src.length, tab = [], i;
CV.stackBoxBlur(imageSrc, imageDst, kernelSize);
for (i = 0; i < 768; ++ i){
tab[i] = (i - 255 <= -threshold)? 255: 0;
}
for (i = 0; i < len; ++ i){
dst[i] = tab[ src[i] - dst[i] + 255 ];
}
imageDst.width = imageSrc.width;
imageDst.height = imageSrc.height;
return imageDst;
};
CV.otsu = function(imageSrc){
var src = imageSrc.data, len = src.length, hist = [],
threshold = 0, sum = 0, sumB = 0, wB = 0, wF = 0, max = 0,
mu, between, i;
for (i = 0; i < 256; ++ i){
hist[i] = 0;
}
for (i = 0; i < len; ++ i){
hist[ src[i] ] ++;
}
for (i = 0; i < 256; ++ i){
sum += hist[i] * i;
}
for (i = 0; i < 256; ++ i){
wB += hist[i];
if (0 !== wB){
wF = len - wB;
if (0 === wF){
break;
}
sumB += hist[i] * i;
mu = (sumB / wB) - ( (sum - sumB) / wF );
between = wB * wF * mu * mu;
if (between > max){
max = between;
threshold = i;
}
}
}
return threshold;
};
CV.stackBoxBlurMult =
[1, 171, 205, 293, 57, 373, 79, 137, 241, 27, 391, 357, 41, 19, 283, 265];
CV.stackBoxBlurShift =
[0, 9, 10, 11, 9, 12, 10, 11, 12, 9, 13, 13, 10, 9, 13, 13];
CV.BlurStack = function(){
this.color = 0;
this.next = null;
};
CV.stackBoxBlur = function(imageSrc, imageDst, kernelSize){
var src = imageSrc.data, dst = imageDst.data,
height = imageSrc.height, width = imageSrc.width,
heightMinus1 = height - 1, widthMinus1 = width - 1,
size = kernelSize + kernelSize + 1, radius = kernelSize + 1,
mult = CV.stackBoxBlurMult[kernelSize],
shift = CV.stackBoxBlurShift[kernelSize],
stack, stackStart, color, sum, pos, start, p, x, y, i;
stack = stackStart = new CV.BlurStack();
for (i = 1; i < size; ++ i){
stack = stack.next = new CV.BlurStack();
}
stack.next = stackStart;
pos = 0;
for (y = 0; y < height; ++ y){
start = pos;
color = src[pos];
sum = radius * color;
stack = stackStart;
for (i = 0; i < radius; ++ i){
stack.color = color;
stack = stack.next;
}
for (i = 1; i < radius; ++ i){
stack.color = src[pos + i];
sum += stack.color;
stack = stack.next;
}
stack = stackStart;
for (x = 0; x < width; ++ x){
dst[pos ++] = (sum * mult) >>> shift;
p = x + radius;
p = start + (p < widthMinus1? p: widthMinus1);
sum -= stack.color - src[p];
stack.color = src[p];
stack = stack.next;
}
}
for (x = 0; x < width; ++ x){
pos = x;
start = pos + width;
color = dst[pos];
sum = radius * color;
stack = stackStart;
for (i = 0; i < radius; ++ i){
stack.color = color;
stack = stack.next;
}
for (i = 1; i < radius; ++ i){
stack.color = dst[start];
sum += stack.color;
stack = stack.next;
start += width;
}
stack = stackStart;
for (y = 0; y < height; ++ y){
dst[pos] = (sum * mult) >>> shift;
p = y + radius;
p = x + ( (p < heightMinus1? p: heightMinus1) * width );
sum -= stack.color - dst[p];
stack.color = dst[p];
stack = stack.next;
pos += width;
}
}
return imageDst;
};
CV.gaussianBlur = function(imageSrc, imageDst, imageMean, kernelSize){
var kernel = CV.gaussianKernel(kernelSize);
imageDst.width = imageSrc.width;
imageDst.height = imageSrc.height;
imageMean.width = imageSrc.width;
imageMean.height = imageSrc.height;
CV.gaussianBlurFilter(imageSrc, imageMean, kernel, true);
CV.gaussianBlurFilter(imageMean, imageDst, kernel, false);
return imageDst;
};
CV.gaussianBlurFilter = function(imageSrc, imageDst, kernel, horizontal){
var src = imageSrc.data, dst = imageDst.data,
height = imageSrc.height, width = imageSrc.width,
pos = 0, limit = kernel.length >> 1,
cur, value, i, j, k;
for (i = 0; i < height; ++ i){
for (j = 0; j < width; ++ j){
value = 0.0;
for (k = -limit; k <= limit; ++ k){
if (horizontal){
cur = pos + k;
if (j + k < 0){
cur = pos;
}
else if (j + k >= width){
cur = pos;
}
}else{
cur = pos + (k * width);
if (i + k < 0){
cur = pos;
}
else if (i + k >= height){
cur = pos;
}
}
value += kernel[limit + k] * src[cur];
}
dst[pos ++] = horizontal? value: (value + 0.5) & 0xff;
}
}
return imageDst;
};
CV.gaussianKernel = function(kernelSize){
var tab =
[ [1],
[0.25, 0.5, 0.25],
[0.0625, 0.25, 0.375, 0.25, 0.0625],
[0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125] ],
kernel = [], center, sigma, scale2X, sum, x, i;
if ( (kernelSize <= 7) && (kernelSize % 2 === 1) ){
kernel = tab[kernelSize >> 1];
}else{
center = (kernelSize - 1.0) * 0.5;
sigma = 0.8 + (0.3 * (center - 1.0) );
scale2X = -0.5 / (sigma * sigma);
sum = 0.0;
for (i = 0; i < kernelSize; ++ i){
x = i - center;
sum += kernel[i] = Math.exp(scale2X * x * x);
}
sum = 1 / sum;
for (i = 0; i < kernelSize; ++ i){
kernel[i] *= sum;
}
}
return kernel;
};
CV.findContours = function(imageSrc, binary){
var width = imageSrc.width, height = imageSrc.height, contours = [],
src, deltas, pos, pix, nbd, outer, hole, i, j;
src = CV.binaryBorder(imageSrc, binary);
deltas = CV.neighborhoodDeltas(width + 2);
pos = width + 3;
nbd = 1;
for (i = 0; i < height; ++ i, pos += 2){
for (j = 0; j < width; ++ j, ++ pos){
pix = src[pos];
if (0 !== pix){
outer = hole = false;
if (1 === pix && 0 === src[pos - 1]){
outer = true;
}
else if (pix >= 1 && 0 === src[pos + 1]){
hole = true;
}
if (outer || hole){
++ nbd;
contours.push( CV.borderFollowing(src, pos, nbd, {x: j, y: i}, hole, deltas) );
}
}
}
}
return contours;
};
CV.borderFollowing = function(src, pos, nbd, point, hole, deltas){
var contour = [], pos1, pos3, pos4, s, s_end, s_prev;
contour.hole = hole;
s = s_end = hole? 0: 4;
do{
s = (s - 1) & 7;
pos1 = pos + deltas[s];
if (src[pos1] !== 0){
break;
}
}while(s !== s_end);
if (s === s_end){
src[pos] = -nbd;
contour.push( {x: point.x, y: point.y} );
}else{
pos3 = pos;
s_prev = s ^ 4;
while(true){
s_end = s;
do{
pos4 = pos3 + deltas[++ s];
}while(src[pos4] === 0);
s &= 7;
if ( ( (s - 1) >>> 0) < (s_end >>> 0) ){
src[pos3] = -nbd;
}
else if (src[pos3] === 1){
src[pos3] = nbd;
}
contour.push( {x: point.x, y: point.y} );
s_prev = s;
point.x += CV.neighborhood[s][0];
point.y += CV.neighborhood[s][1];
if ( (pos4 === pos) && (pos3 === pos1) ){
break;
}
pos3 = pos4;
s = (s + 4) & 7;
}
}
return contour;
};
CV.neighborhood =
[ [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1] ];
CV.neighborhoodDeltas = function(width){
var deltas = [], len = CV.neighborhood.length, i = 0;
for (; i < len; ++ i){
deltas[i] = CV.neighborhood[i][0] + (CV.neighborhood[i][1] * width);
}
return deltas.concat(deltas);
};
CV.approxPolyDP = function(contour, epsilon){
var slice = {start_index: 0, end_index: 0},
right_slice = {start_index: 0, end_index: 0},
poly = [], stack = [], len = contour.length,
pt, start_pt, end_pt, dist, max_dist, le_eps,
dx, dy, i, j, k;
epsilon *= epsilon;
k = 0;
for (i = 0; i < 3; ++ i){
max_dist = 0;
k = (k + right_slice.start_index) % len;
start_pt = contour[k];
if (++ k === len) {k = 0;}
for (j = 1; j < len; ++ j){
pt = contour[k];
if (++ k === len) {k = 0;}
dx = pt.x - start_pt.x;
dy = pt.y - start_pt.y;
dist = dx * dx + dy * dy;
if (dist > max_dist){
max_dist = dist;
right_slice.start_index = j;
}
}
}
if (max_dist <= epsilon){
poly.push( {x: start_pt.x, y: start_pt.y} );
}else{
slice.start_index = k;
slice.end_index = (right_slice.start_index += slice.start_index);
right_slice.start_index -= right_slice.start_index >= len? len: 0;
right_slice.end_index = slice.start_index;
if (right_slice.end_index < right_slice.start_index){
right_slice.end_index += len;
}
stack.push( {start_index: right_slice.start_index, end_index: right_slice.end_index} );
stack.push( {start_index: slice.start_index, end_index: slice.end_index} );
}
while(stack.length !== 0){
slice = stack.pop();
end_pt = contour[slice.end_index % len];
start_pt = contour[k = slice.start_index % len];
if (++ k === len) {k = 0;}
if (slice.end_index <= slice.start_index + 1){
le_eps = true;
}else{
max_dist = 0;
dx = end_pt.x - start_pt.x;
dy = end_pt.y - start_pt.y;
for (i = slice.start_index + 1; i < slice.end_index; ++ i){
pt = contour[k];
if (++ k === len) {k = 0;}
dist = Math.abs( (pt.y - start_pt.y) * dx - (pt.x - start_pt.x) * dy);
if (dist > max_dist){
max_dist = dist;
right_slice.start_index = i;
}
}
le_eps = max_dist * max_dist <= epsilon * (dx * dx + dy * dy);
}
if (le_eps){
poly.push( {x: start_pt.x, y: start_pt.y} );
}else{
right_slice.end_index = slice.end_index;
slice.end_index = right_slice.start_index;
stack.push( {start_index: right_slice.start_index, end_index: right_slice.end_index} );
stack.push( {start_index: slice.start_index, end_index: slice.end_index} );
}
}
return poly;
};
CV.warp = function(imageSrc, imageDst, contour, warpSize){
var src = imageSrc.data, dst = imageDst.data,
width = imageSrc.width, height = imageSrc.height,
pos = 0,
sx1, sx2, dx1, dx2, sy1, sy2, dy1, dy2, p1, p2, p3, p4,
m, r, s, t, u, v, w, x, y, i, j;
m = CV.getPerspectiveTransform(contour, warpSize - 1);
r = m[8];
s = m[2];
t = m[5];
for (i = 0; i < warpSize; ++ i){
r += m[7];
s += m[1];
t += m[4];
u = r;
v = s;
w = t;
for (j = 0; j < warpSize; ++ j){
u += m[6];
v += m[0];
w += m[3];
x = v / u;
y = w / u;
sx1 = x >>> 0;
sx2 = (sx1 === width - 1)? sx1: sx1 + 1;
dx1 = x - sx1;
dx2 = 1.0 - dx1;
sy1 = y >>> 0;
sy2 = (sy1 === height - 1)? sy1: sy1 + 1;
dy1 = y - sy1;
dy2 = 1.0 - dy1;
p1 = p2 = sy1 * width;
p3 = p4 = sy2 * width;
dst[pos ++] =
(dy2 * (dx2 * src[p1 + sx1] + dx1 * src[p2 + sx2]) +
dy1 * (dx2 * src[p3 + sx1] + dx1 * src[p4 + sx2]) ) & 0xff;
}
}
imageDst.width = warpSize;
imageDst.height = warpSize;
return imageDst;
};
CV.getPerspectiveTransform = function(src, size){
var rq = CV.square2quad(src);
rq[0] /= size;
rq[1] /= size;
rq[3] /= size;
rq[4] /= size;
rq[6] /= size;
rq[7] /= size;
return rq;
};
CV.square2quad = function(src){
var sq = [], px, py, dx1, dx2, dy1, dy2, den;
px = src[0].x - src[1].x + src[2].x - src[3].x;
py = src[0].y - src[1].y + src[2].y - src[3].y;
if (0 === px && 0 === py){
sq[0] = src[1].x - src[0].x;
sq[1] = src[2].x - src[1].x;
sq[2] = src[0].x;
sq[3] = src[1].y - src[0].y;
sq[4] = src[2].y - src[1].y;
sq[5] = src[0].y;
sq[6] = 0;
sq[7] = 0;
sq[8] = 1;
}else{
dx1 = src[1].x - src[2].x;
dx2 = src[3].x - src[2].x;
dy1 = src[1].y - src[2].y;
dy2 = src[3].y - src[2].y;
den = dx1 * dy2 - dx2 * dy1;
sq[6] = (px * dy2 - dx2 * py) / den;
sq[7] = (dx1 * py - px * dy1) / den;
sq[8] = 1;
sq[0] = src[1].x - src[0].x + sq[6] * src[1].x;
sq[1] = src[3].x - src[0].x + sq[7] * src[3].x;
sq[2] = src[0].x;
sq[3] = src[1].y - src[0].y + sq[6] * src[1].y;
sq[4] = src[3].y - src[0].y + sq[7] * src[3].y;
sq[5] = src[0].y;
}
return sq;
};
CV.isContourConvex = function(contour){
var orientation = 0, convex = true,
len = contour.length, i = 0, j = 0,
cur_pt, prev_pt, dxdy0, dydx0, dx0, dy0, dx, dy;
prev_pt = contour[len - 1];
cur_pt = contour[0];
dx0 = cur_pt.x - prev_pt.x;
dy0 = cur_pt.y - prev_pt.y;
for (; i < len; ++ i){
if (++ j === len) {j = 0;}
prev_pt = cur_pt;
cur_pt = contour[j];
dx = cur_pt.x - prev_pt.x;
dy = cur_pt.y - prev_pt.y;
dxdy0 = dx * dy0;
dydx0 = dy * dx0;
orientation |= dydx0 > dxdy0? 1: (dydx0 < dxdy0? 2: 3);
if (3 === orientation){
convex = false;
break;
}
dx0 = dx;
dy0 = dy;
}
return convex;
};
CV.perimeter = function(poly){
var len = poly.length, i = 0, j = len - 1,
p = 0.0, dx, dy;
for (; i < len; j = i ++){
dx = poly[i].x - poly[j].x;
dy = poly[i].y - poly[j].y;
p += Math.sqrt(dx * dx + dy * dy) ;
}
return p;
};
CV.minEdgeLength = function(poly){
var len = poly.length, i = 0, j = len - 1,
min = Infinity, d, dx, dy;
for (; i < len; j = i ++){
dx = poly[i].x - poly[j].x;
dy = poly[i].y - poly[j].y;
d = dx * dx + dy * dy;
if (d < min){
min = d;
}
}
return Math.sqrt(min);
};
CV.countNonZero = function(imageSrc, square){
var src = imageSrc.data, height = square.height, width = square.width,
pos = square.x + (square.y * imageSrc.width),
span = imageSrc.width - width,
nz = 0, i, j;
for (i = 0; i < height; ++ i){
for (j = 0; j < width; ++ j){
if ( 0 !== src[pos ++] ){
++ nz;
}
}
pos += span;
}
return nz;
};
CV.binaryBorder = function(imageSrc, dst){
var src = imageSrc.data, height = imageSrc.height, width = imageSrc.width,
posSrc = 0, posDst = 0, i, j;
for (j = -2; j < width; ++ j){
dst[posDst ++] = 0;
}
for (i = 0; i < height; ++ i){
dst[posDst ++] = 0;
for (j = 0; j < width; ++ j){
dst[posDst ++] = (0 === src[posSrc ++]? 0: 1);
}
dst[posDst ++] = 0;
}
for (j = -2; j < width; ++ j){
dst[posDst ++] = 0;
}
return dst;
};
//src: http://jsfromhell.com/math/is-point-in-poly
CV.pointPolygonTest = function(poly, pt){
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
&& (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
&& (c = !c);
return c;
};
//http://stackoverflow.com/questions/16285134/calculating-polygon-area
CV.contourArea = function(cont){
//console.log('cont: '+cont);
var area = 0; // Accumulates area in the loop
var j = cont.length-1; // The last vertex is the 'previous' one to the first
for (var i=0; i<cont.length; i++)
{
area = area + (cont[j].x+cont[i].x) * (cont[j].y+cont[i].y);
//area = area + (X[j]+X[i]) * (Y[j]-Y[i]);
j = i; //j is previous vertex to i
}
return area/2;
};
This is my codepen source, it's about particle stream.
I have already tried Three.js doc, couldn't found any about glow options...
demo resource link
this is javascript & stylesheet & html code:
var mContainer;
var mCamera, mRenderer;
var mControls;
var mScene;
var mParticleCount = 100000; // <-- change this number!
var mParticleSystem;
var mTime = 0.0;
var mTimeStep = (1 / 60);
var mDuration = 20;
window.onload = function() {
init();
};
function init() {
initTHREE();
initControls();
initParticleSystem();
requestAnimationFrame(tick);
window.addEventListener('resize', resize, false);
}
function initTHREE() {
mRenderer = new THREE.WebGLRenderer({
antialias: true
});
mRenderer.setSize(window.innerWidth, window.innerHeight);
mContainer = document.getElementById('three-container');
mContainer.appendChild(mRenderer.domElement);
mCamera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 5000);
mCamera.position.set(0, 600, 600);
mScene = new THREE.Scene();
var light;
light = new THREE.PointLight(0xffffff, 4, 1000, 2);
light.position.set(0, 400, 0);
mScene.add(light);
}
function initControls() {
mControls = new THREE.OrbitControls(mCamera, mRenderer.domElement);
}
function initParticleSystem() {
var material = new THREE.ShaderMaterial({
uniforms: {
color: {
type: "c",
value: new THREE.Color(0xffffff)
}
},
alphaTest: 0.9
});
var geometrys = new THREE.BufferGeometry();
var geometry = new THREE.Points(geometrys, material);
var prefabGeometry = new THREE.PlaneGeometry(2, 2);
var bufferGeometry = new THREE.BAS.PrefabBufferGeometry(prefabGeometry, mParticleCount);
bufferGeometry.computeVertexNormals();
// generate additional geometry data
var aOffset = bufferGeometry.createAttribute('aOffset', 1);
var aStartPosition = bufferGeometry.createAttribute('aStartPosition', 3);
var aControlPoint1 = bufferGeometry.createAttribute('aControlPoint1', 3);
var aControlPoint2 = bufferGeometry.createAttribute('aControlPoint2', 3);
var aEndPosition = bufferGeometry.createAttribute('aEndPosition', 3);
var aAxisAngle = bufferGeometry.createAttribute('aAxisAngle', 4);
var aColor = bufferGeometry.createAttribute('color', 3);
var i, j, offset;
// buffer time offset
var delay;
for (i = 0, offset = 0; i < mParticleCount; i++) {
delay = i / mParticleCount * mDuration;
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aOffset.array[offset++] = delay;
}
}
// buffer start positions
var x, y, z;
for (i = 0, offset = 0; i < mParticleCount; i++) {
x = -1000;
y = 0;
z = 0;
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aStartPosition.array[offset++] = x;
aStartPosition.array[offset++] = y;
aStartPosition.array[offset++] = z;
}
}
// buffer control points
for (i = 0, offset = 0; i < mParticleCount; i++) {
x = THREE.Math.randFloat(-400, 400);
y = THREE.Math.randFloat(400, 600);
z = THREE.Math.randFloat(-1200, -800);
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aControlPoint1.array[offset++] = x;
aControlPoint1.array[offset++] = y;
aControlPoint1.array[offset++] = z;
}
}
for (i = 0, offset = 0; i < mParticleCount; i++) {
x = THREE.Math.randFloat(-400, 400);
y = THREE.Math.randFloat(-600, -400);
z = THREE.Math.randFloat(800, 1200);
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aControlPoint2.array[offset++] = x;
aControlPoint2.array[offset++] = y;
aControlPoint2.array[offset++] = z;
}
}
// buffer end positions
for (i = 0, offset = 0; i < mParticleCount; i++) {
x = 1000;
y = 0;
z = 0;
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aEndPosition.array[offset++] = x;
aEndPosition.array[offset++] = y;
aEndPosition.array[offset++] = z;
}
}
// buffer axis angle
var axis = new THREE.Vector3();
var angle = 0;
for (i = 0, offset = 0; i < mParticleCount; i++) {
axis.x = THREE.Math.randFloatSpread(2);
axis.y = THREE.Math.randFloatSpread(2);
axis.z = THREE.Math.randFloatSpread(2);
axis.normalize();
angle = Math.PI * THREE.Math.randInt(16, 32);
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aAxisAngle.array[offset++] = axis.x;
aAxisAngle.array[offset++] = axis.y;
aAxisAngle.array[offset++] = axis.z;
aAxisAngle.array[offset++] = angle;
}
}
// buffer color
var color = new THREE.Color();
var h, s, l;
for (i = 0, offset = 0; i < mParticleCount; i++) {
h = i / mParticleCount;
s = THREE.Math.randFloat(0.4, 0.6);
l = THREE.Math.randFloat(0.4, 0.6);
color.setHSL(h, s, l);
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aColor.array[offset++] = color.r;
aColor.array[offset++] = color.g;
aColor.array[offset++] = color.b;
}
}
var material = new THREE.BAS.PhongAnimationMaterial(
// custom parameters & THREE.MeshPhongMaterial parameters
{
vertexColors: THREE.VertexColors,
shading: THREE.FlatShading,
side: THREE.DoubleSide,
uniforms: {
uTime: {
type: 'f',
value: 0
},
uDuration: {
type: 'f',
value: mDuration
}
},
shaderFunctions: [
THREE.BAS.ShaderChunk['quaternion_rotation'],
THREE.BAS.ShaderChunk['cubic_bezier']
],
shaderParameters: [
'uniform float uTime;',
'uniform float uDuration;',
'attribute float aOffset;',
'attribute vec3 aStartPosition;',
'attribute vec3 aControlPoint1;',
'attribute vec3 aControlPoint2;',
'attribute vec3 aEndPosition;',
'attribute vec4 aAxisAngle;'
],
shaderVertexInit: [
'float tProgress = mod((uTime + aOffset), uDuration) / uDuration;',
'float angle = aAxisAngle.w * tProgress;',
'vec4 tQuat = quatFromAxisAngle(aAxisAngle.xyz, angle);'
],
shaderTransformNormal: [
'objectNormal = rotateVector(tQuat, objectNormal);'
],
shaderTransformPosition: [
'transformed = rotateVector(tQuat, transformed);',
'transformed += cubicBezier(aStartPosition, aControlPoint1, aControlPoint2, aEndPosition, tProgress);'
]
},
// THREE.MeshPhongMaterial uniforms
{
specular: 0xff0000,
shininess: 20
}
);
mParticleSystem = new THREE.Mesh(bufferGeometry, material);
// because the bounding box of the particle system does not reflect its on-screen size
// set this to false to prevent the whole thing from disappearing on certain angles
mParticleSystem.frustumCulled = false;
mScene.add(mParticleSystem);
}
function tick() {
update();
render();
mTime += mTimeStep;
mTime %= mDuration;
requestAnimationFrame(tick);
}
function update() {
mControls.update();
mParticleSystem.material.uniforms['uTime'].value = mTime;
}
function render() {
mRenderer.render(mScene, mCamera);
}
function resize() {
mCamera.aspect = window.innerWidth / window.innerHeight;
mCamera.updateProjectionMatrix();
mRenderer.setSize(window.innerWidth, window.innerHeight);
}
/////////////////////////////
// buffer animation system
/////////////////////////////
THREE.BAS = {};
THREE.BAS.ShaderChunk = {};
THREE.BAS.ShaderChunk["animation_time"] = "float tDelay = aAnimation.x;\nfloat tDuration = aAnimation.y;\nfloat tTime = clamp(uTime - tDelay, 0.0, tDuration);\nfloat tProgress = ease(tTime, 0.0, 1.0, tDuration);\n";
THREE.BAS.ShaderChunk["cubic_bezier"] = "vec3 cubicBezier(vec3 p0, vec3 c0, vec3 c1, vec3 p1, float t)\n{\n vec3 tp;\n float tn = 1.0 - t;\n\n tp.xyz = tn * tn * tn * p0.xyz + 3.0 * tn * tn * t * c0.xyz + 3.0 * tn * t * t * c1.xyz + t * t * t * p1.xyz;\n\n return tp;\n}\n";
THREE.BAS.ShaderChunk["ease_in_cubic"] = "float ease(float t, float b, float c, float d) {\n return c*(t/=d)*t*t + b;\n}\n";
THREE.BAS.ShaderChunk["ease_in_quad"] = "float ease(float t, float b, float c, float d) {\n return c*(t/=d)*t + b;\n}\n";
THREE.BAS.ShaderChunk["ease_out_cubic"] = "float ease(float t, float b, float c, float d) {\n return c*((t=t/d - 1.0)*t*t + 1.0) + b;\n}\n";
THREE.BAS.ShaderChunk["quaternion_rotation"] = "vec3 rotateVector(vec4 q, vec3 v)\n{\n return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);\n}\n\nvec4 quatFromAxisAngle(vec3 axis, float angle)\n{\n float halfAngle = angle * 0.5;\n return vec4(axis.xyz * sin(halfAngle), cos(halfAngle));\n}\n";
THREE.BAS.PrefabBufferGeometry = function(prefab, count) {
THREE.BufferGeometry.call(this);
this.prefabGeometry = prefab;
this.prefabCount = count;
this.prefabVertexCount = prefab.vertices.length;
this.bufferDefaults();
};
THREE.BAS.PrefabBufferGeometry.prototype = Object.create(THREE.BufferGeometry.prototype);
THREE.BAS.PrefabBufferGeometry.prototype.constructor = THREE.BAS.PrefabBufferGeometry;
THREE.BAS.PrefabBufferGeometry.prototype.bufferDefaults = function() {
var prefabFaceCount = this.prefabGeometry.faces.length;
var prefabIndexCount = this.prefabGeometry.faces.length * 3;
var prefabVertexCount = this.prefabVertexCount = this.prefabGeometry.vertices.length;
var prefabIndices = [];
//console.log('prefabCount', this.prefabCount);
//console.log('prefabFaceCount', prefabFaceCount);
//console.log('prefabIndexCount', prefabIndexCount);
//console.log('prefabVertexCount', prefabVertexCount);
//console.log('triangles', prefabFaceCount * this.prefabCount);
for (var h = 0; h < prefabFaceCount; h++) {
var face = this.prefabGeometry.faces[h];
prefabIndices.push(face.a, face.b, face.c);
}
var indexBuffer = new Uint32Array(this.prefabCount * prefabIndexCount);
var positionBuffer = new Float32Array(this.prefabCount * prefabVertexCount * 3);
this.setIndex(new THREE.BufferAttribute(indexBuffer, 1));
this.addAttribute('position', new THREE.BufferAttribute(positionBuffer, 3));
for (var i = 0, offset = 0; i < this.prefabCount; i++) {
for (var j = 0; j < prefabVertexCount; j++, offset += 3) {
var prefabVertex = this.prefabGeometry.vertices[j];
positionBuffer[offset] = prefabVertex.x;
positionBuffer[offset + 1] = prefabVertex.y;
positionBuffer[offset + 2] = prefabVertex.z;
}
for (var k = 0; k < prefabIndexCount; k++) {
indexBuffer[i * prefabIndexCount + k] = prefabIndices[k] + i * prefabVertexCount;
}
}
};
// todo test
THREE.BAS.PrefabBufferGeometry.prototype.bufferUvs = function() {
var prefabFaceCount = this.prefabGeometry.faces.length;
var prefabVertexCount = this.prefabVertexCount = this.prefabGeometry.vertices.length;
var prefabUvs = [];
for (var h = 0; h < prefabFaceCount; h++) {
var face = this.prefabGeometry.faces[h];
var uv = this.prefabGeometry.faceVertexUvs[0][h];
prefabUvs[face.a] = uv[0];
prefabUvs[face.b] = uv[1];
prefabUvs[face.c] = uv[2];
}
var uvBuffer = this.createAttribute('uv', 2);
for (var i = 0, offset = 0; i < this.prefabCount; i++) {
for (var j = 0; j < prefabVertexCount; j++, offset += 2) {
var prefabUv = prefabUvs[j];
uvBuffer.array[offset] = prefabUv.x;
uvBuffer.array[offset + 1] = prefabUv.y;
}
}
};
/**
* based on BufferGeometry.computeVertexNormals
* calculate vertex normals for a prefab, and repeat the data in the normal buffer
*/
THREE.BAS.PrefabBufferGeometry.prototype.computeVertexNormals = function() {
var index = this.index;
var attributes = this.attributes;
var positions = attributes.position.array;
if (attributes.normal === undefined) {
this.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(positions.length), 3));
}
var normals = attributes.normal.array;
var vA, vB, vC,
pA = new THREE.Vector3(),
pB = new THREE.Vector3(),
pC = new THREE.Vector3(),
cb = new THREE.Vector3(),
ab = new THREE.Vector3();
var indices = index.array;
var prefabIndexCount = this.prefabGeometry.faces.length * 3;
for (var i = 0; i < prefabIndexCount; i += 3) {
vA = indices[i + 0] * 3;
vB = indices[i + 1] * 3;
vC = indices[i + 2] * 3;
pA.fromArray(positions, vA);
pB.fromArray(positions, vB);
pC.fromArray(positions, vC);
cb.subVectors(pC, pB);
ab.subVectors(pA, pB);
cb.cross(ab);
normals[vA] += cb.x;
normals[vA + 1] += cb.y;
normals[vA + 2] += cb.z;
normals[vB] += cb.x;
normals[vB + 1] += cb.y;
normals[vB + 2] += cb.z;
normals[vC] += cb.x;
normals[vC + 1] += cb.y;
normals[vC + 2] += cb.z;
}
for (var j = 1; j < this.prefabCount; j++) {
for (var k = 0; k < prefabIndexCount; k++) {
normals[j * prefabIndexCount + k] = normals[k];
}
}
this.normalizeNormals();
attributes.normal.needsUpdate = true;
};
THREE.BAS.PrefabBufferGeometry.prototype.createAttribute = function(name, itemSize) {
var buffer = new Float32Array(this.prefabCount * this.prefabVertexCount * itemSize);
var attribute = new THREE.BufferAttribute(buffer, itemSize);
this.addAttribute(name, attribute);
return attribute;
};
THREE.BAS.PrefabBufferGeometry.prototype.setAttribute4 = function(name, data) {
var offset = 0;
var array = this.geometry.attributes[name].array;
var i, j;
for (i = 0; i < data.length; i++) {
var v = data[i];
for (j = 0; j < this.prefabVertexCount; j++) {
array[offset++] = v.x;
array[offset++] = v.y;
array[offset++] = v.z;
array[offset++] = v.w;
}
}
this.geometry.attributes[name].needsUpdate = true;
};
THREE.BAS.PrefabBufferGeometry.prototype.setAttribute3 = function(name, data) {
var offset = 0;
var array = this.geometry.attributes[name].array;
var i, j;
for (i = 0; i < data.length; i++) {
var v = data[i];
for (j = 0; j < this.prefabVertexCount; j++) {
array[offset++] = v.x;
array[offset++] = v.y;
array[offset++] = v.z;
}
}
this.geometry.attributes[name].needsUpdate = true;
};
THREE.BAS.PrefabBufferGeometry.prototype.setAttribute2 = function(name, data) {
var offset = 0;
var array = this.geometry.attributes[name].array;
var i, j;
for (i = 0; i < this.prefabCount; i++) {
var v = data[i];
for (j = 0; j < this.prefabVertexCount; j++) {
array[offset++] = v.x;
array[offset++] = v.y;
}
}
this.geometry.attributes[name].needsUpdate = true;
};
THREE.BAS.BaseAnimationMaterial = function(parameters) {
THREE.ShaderMaterial.call(this);
this.shaderFunctions = [];
this.shaderParameters = [];
this.shaderVertexInit = [];
this.shaderTransformNormal = [];
this.shaderTransformPosition = [];
this.setValues(parameters);
};
THREE.BAS.BaseAnimationMaterial.prototype = Object.create(THREE.ShaderMaterial.prototype);
THREE.BAS.BaseAnimationMaterial.prototype.constructor = THREE.BAS.BaseAnimationMaterial;
// abstract
THREE.BAS.BaseAnimationMaterial.prototype._concatVertexShader = function() {
return '';
};
THREE.BAS.BaseAnimationMaterial.prototype._concatFunctions = function() {
return this.shaderFunctions.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype._concatParameters = function() {
return this.shaderParameters.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype._concatVertexInit = function() {
return this.shaderVertexInit.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype._concatTransformNormal = function() {
return this.shaderTransformNormal.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype._concatTransformPosition = function() {
return this.shaderTransformPosition.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype.setUniformValues = function(values) {
for (var key in values) {
if (key in this.uniforms) {
var uniform = this.uniforms[key];
var value = values[key];
// todo add matrix uniform types
switch (uniform.type) {
case 'c': // color
uniform.value.set(value);
break;
case 'v2': // vectors
case 'v3':
case 'v4':
uniform.value.copy(value);
break;
case 'f': // float
case 't': // texture
uniform.value = value;
}
}
}
};
THREE.BAS.PhongAnimationMaterial = function(parameters, uniformValues) {
THREE.BAS.BaseAnimationMaterial.call(this, parameters);
var phongShader = THREE.ShaderLib['phong'];
this.uniforms = THREE.UniformsUtils.merge([phongShader.uniforms, this.uniforms]);
this.lights = true;
this.vertexShader = this._concatVertexShader();
this.fragmentShader = phongShader.fragmentShader;
// todo add missing default defines
uniformValues.map && (this.defines['USE_MAP'] = '');
uniformValues.normalMap && (this.defines['USE_NORMALMAP'] = '');
this.setUniformValues(uniformValues);
};
THREE.BAS.PhongAnimationMaterial.prototype = Object.create(THREE.BAS.BaseAnimationMaterial.prototype);
THREE.BAS.PhongAnimationMaterial.prototype.constructor = THREE.BAS.PhongAnimationMaterial;
THREE.BAS.PhongAnimationMaterial.prototype._concatVertexShader = function() {
// based on THREE.ShaderLib.phong
return [
"#define PHONG",
"varying vec3 vViewPosition;",
"#ifndef FLAT_SHADED",
" varying vec3 vNormal;",
"#endif",
THREE.ShaderChunk["common"],
THREE.ShaderChunk["uv_pars_vertex"],
THREE.ShaderChunk["uv2_pars_vertex"],
THREE.ShaderChunk["displacementmap_pars_vertex"],
THREE.ShaderChunk["envmap_pars_vertex"],
THREE.ShaderChunk["lights_phong_pars_vertex"],
THREE.ShaderChunk["color_pars_vertex"],
THREE.ShaderChunk["morphtarget_pars_vertex"],
THREE.ShaderChunk["skinning_pars_vertex"],
THREE.ShaderChunk["shadowmap_pars_vertex"],
THREE.ShaderChunk["logdepthbuf_pars_vertex"],
this._concatFunctions(),
this._concatParameters(),
"void main() {",
this._concatVertexInit(),
THREE.ShaderChunk["uv_vertex"],
THREE.ShaderChunk["uv2_vertex"],
THREE.ShaderChunk["color_vertex"],
THREE.ShaderChunk["beginnormal_vertex"],
this._concatTransformNormal(),
THREE.ShaderChunk["morphnormal_vertex"],
THREE.ShaderChunk["skinbase_vertex"],
THREE.ShaderChunk["skinnormal_vertex"],
THREE.ShaderChunk["defaultnormal_vertex"],
"#ifndef FLAT_SHADED", // Normal computed with derivatives when FLAT_SHADED
" vNormal = normalize( transformedNormal );",
"#endif",
THREE.ShaderChunk["begin_vertex"],
this._concatTransformPosition(),
THREE.ShaderChunk["displacementmap_vertex"],
THREE.ShaderChunk["morphtarget_vertex"],
THREE.ShaderChunk["skinning_vertex"],
THREE.ShaderChunk["project_vertex"],
THREE.ShaderChunk["logdepthbuf_vertex"],
" vViewPosition = - mvPosition.xyz;",
THREE.ShaderChunk["worldpos_vertex"],
THREE.ShaderChunk["envmap_vertex"],
THREE.ShaderChunk["lights_phong_vertex"],
THREE.ShaderChunk["shadowmap_vertex"],
"}"
].join("\n");
};
body {
margin: 0;
overflow: hidden;
cursor: move;
}
<div id="three-container"></div>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/THREE.OrbitControls.js"></script>
Have a Look at THREEX.geometric glow
I need some expert help. When I make canvas background transparent the line colors the whole canvas (shown in code below).
How do I stop/fix this? I want the line stay as a single line that doesn't color the canvas.
Math.clamp = function(x, min, max) {
return x < min ? min : (x > max ? max : x);
};
// canvas settings
var viewWidth = window.innerWidth,
viewHeight = window.innerHeight,
drawingCanvas = document.getElementById("drawing_canvas"),
ctx,
timeStep = (10 / 100),
time = 0;
var lineTension = 0.067,
lineDamping = 0.068,
waveSpreadFactor = 0.1;
var previousMousePosition = {
x: 0,
y: 0
},
currentMousePosition = {
x: 0,
y: 0
},
actualMousePosition = {
x: 0,
y: 0
};
var line,
lineSegmentCount = 64,
lineMaxForce = 32,
lineStrokeGradient;
var audioCtx,
nodeCount = 64,
oscillatorNodes = [],
gainNodes = [];
var segmentsPerNode = lineSegmentCount / nodeCount;
function initGui() {
}
function goBananas() {
lineTension = 0.2;
line.anchors[Math.floor(Math.random() * line.anchors.length)].
vel = lineMaxForce;
}
function resetLine() {
line.reset();
for (var i = 0; i < nodeCount; i++) {
oscillatorNodes[i].detune.value = 100;
gainNodes[i].gain.value = 0;
}
lineTension = 0.0025;
lineDamping = 0.05;
waveSpreadFactor = 0.1;
}
function initDrawingCanvas() {
drawingCanvas.width = viewWidth;
drawingCanvas.height = viewHeight;
window.addEventListener('resize', resizeHandler);
window.addEventListener('mousemove', mouseMoveHandler);
setInterval(updateMousePosition, (1000 / 30));
ctx = drawingCanvas.getContext('2d');
ctx.lineWidth = 5;
line = new Line(0, viewHeight * 0.5, viewWidth, lineSegmentCount);
// line.anchors[0].vel = viewHeight * 0.25;
lineStrokeGradient = ctx.createLinearGradient(0, 0, 0, viewHeight);
lineStrokeGradient.addColorStop(0, '#0ff');
}
function initWebAudio() {
audioCtx = new(window.AudioContext || window.webkitAudioContext)();
for (var i = 0; i < nodeCount; i++) {
var oscillatorNode = audioCtx.createOscillator();
var gainNode = audioCtx.createGain();
oscillatorNode.connect(gainNode);
gainNode.connect(audioCtx.destination);
gainNode.gain.value = 0;
oscillatorNode.type = 'saw';
oscillatorNode.detune.value = 200;
oscillatorNode.frequency.value = 1200 * (i / 60);
oscillatorNode.start();
oscillatorNodes[i] = oscillatorNode;
gainNodes[i] = gainNode;
}
}
function resizeHandler() {
drawingCanvas.width = viewWidth = window.innerWidth;
drawingCanvas.height = viewHeight = window.innerHeight;
if (ctx) {
ctx.lineWidth = 4;
line.resize(viewWidth, viewHeight * 0.5);
}
}
function mouseMoveHandler(event) {
actualMousePosition.x = Math.floor(event.clientX);
actualMousePosition.y = Math.floor(event.clientY);
}
function updateMousePosition() {
previousMousePosition.x = currentMousePosition.x;
previousMousePosition.y = currentMousePosition.y;
currentMousePosition.x = actualMousePosition.x;
currentMousePosition.y = actualMousePosition.y;
}
function update() {
var px = Math.min(previousMousePosition.x, currentMousePosition.x),
py = Math.min(previousMousePosition.y, currentMousePosition.y),
pw = Math.max(1, Math.abs(previousMousePosition.x - currentMousePosition.x)),
ph = Math.max(1, Math.abs(previousMousePosition.y - currentMousePosition.y)),
force = Math.clamp(currentMousePosition.y -
previousMousePosition.y, -lineMaxForce, lineMaxForce);
var pixels = ctx.getImageData(px, py, pw, ph),
data = pixels.data;
for (var i = 0; i < data.length; i += 3) {
var r = data[i + 0],
g = data[i + 1],
b = data[i + 2],
x = (i % ph) + px;
if (r + g + b > 0) {
line.ripple(x, force);
}
}
line.update();
for (var j = 0; j < gainNodes.length; j++) {
var anchor = line.anchors[j * segmentsPerNode],
gain = Math.clamp(Math.abs(anchor.vel) / viewHeight * 0.5, 0, 3),
detune = Math.clamp(anchor.pos / viewHeight * 100, 0, 300);
gainNodes[j].gain.value = gain;
oscillatorNodes[j].detune.value = detune;
}
}
function draw() {
ctx.strokeStyle = lineStrokeGradient;
line.draw();
}
window.onload = function() {
initDrawingCanvas();
initWebAudio();
initGui();
requestAnimationFrame(loop);
};
function loop() {
update();
draw();
time += timeStep;
requestAnimationFrame(loop);
}
Line = function(x, y, length, segments) {
this.x = x;
this.y = y;
this.length = length;
this.segments = segments;
this.segmentLength = this.length / this.segments;
this.anchors = [];
for (var i = 0; i <= this.segments; i++) {
this.anchors[i] = {
target: this.y,
pos: this.y,
vel: 0,
update: function() {
var dy = this.pos - this.target,
acc = -lineTension * dy - lineDamping * this.vel;
this.pos += this.vel;
this.vel += acc;
},
reset: function() {
this.pos = this.target;
this.vel = 0;
}
};
}
};
Line.prototype = {
resize: function(length, targetY) {
this.length = length;
this.segmentLength = this.length / this.segments;
for (var i = 0; i <= this.segments; i++) {
this.anchors[i].target = targetY;
}
},
reset: function() {
for (var i = 0; i <= this.segments; i++) {
this.anchors[i].reset();
}
},
ripple: function(origin, amplitude) {
var i = Math.floor((origin - this.x) / this.segmentLength);
if (i >= 0 && i <= this.segments) {
this.anchors[i].vel = amplitude;
}
},
update: function() {
var lDeltas = [],
rDeltas = [],
i;
for (i = 0; i <= this.segments; i++) {
this.anchors[i].update();
}
for (i = 0; i <= this.segments; i++) {
if (i > 0) {
lDeltas[i] = waveSpreadFactor * (this.anchors[i].pos - this.anchors[i - 1].pos);
this.anchors[i - 1].vel += lDeltas[i];
}
if (i < this.segments) {
rDeltas[i] = waveSpreadFactor * (this.anchors[i].pos - this.anchors[i + 1].pos);
this.anchors[i + 1].vel += rDeltas[i];
}
}
for (i = 0; i <= this.segments; i++) {
if (i > 0) {
this.anchors[i - 1].pos += lDeltas[i];
}
if (i < this.segments) {
this.anchors[i + 1].pos += rDeltas[i];
}
}
},
draw: function() {
ctx.beginPath();
for (var i = 0; i <= this.segments; i++) {
ctx.lineTo(
this.x + this.segmentLength * i,
this.anchors[i].pos
);
}
ctx.stroke();
}
};
From the code you posted, the problem seems to be a missing
ctx.clearRect(0, 0, viewWidth, viewHeight)
at the beginning of your "draw" function.
See a working example here