THREE.JS Multiple textures (60+) on one complex geometry (terrain) - three.js

Let's say I want to create a dynamic terrain based on level-of-detail algorithm and a very high-res satellite texture (i.e. 32.768x32.768px). Eventually, I have created tiles out of this image according to levels:
level1 4 tiles
level2 16 tiles
level3 64 tiles
level4 256 tiles
level5 1024 tiles
All these tiles are 1024x1024
I have attached a very basic snippet, which has already divided plane (THREE.BufferGeometry) according to camera position.
The Delaunator from Mapbox works pretty fast, however I don't know how to place textures onto plane like on following illustration:
I bet it has to be done with shader, but don't have any ideas how to do it.
Merging these tiles to one large texture leads us to 32.768x32.768, so it's the dead end.
Yeah, it could be multi-texturing with THREE.MeshFaceMaterial(materials) and assigning each material (tile) to certain vertices. However, I'm looking for straight GLSL solution capable of processing ~60-65 textures of at least 512x512, better 1024x1024.
PS: Yes, I have already version where this single plane is divided to small ones, but I'm looking for single plane approach, since there are crack on tile joints similar as described in this article, Figure 8.
PS: Please ignore quadtree algrorithm. I am asking for general solution of rendering 60-70 textures for one geometry.
//https://hofk.de/main/discourse.threejs/2018/Triangulation/Triangulation.html
////by Mapbox https://github.com/mapbox/delaunator
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Delaunator = factory());
}(this, (function () { 'use strict';
var EPSILON = Math.pow(2, -52);
var Delaunator = function Delaunator(coords) {
var this$1 = this;
var n = coords.length >> 1;
if (n > 0 && typeof coords[0] !== 'number') { throw new Error('Expected coords to contain numbers.'); }
this.coords = coords;
var maxTriangles = 2 * n - 5;
var triangles = this.triangles = new Uint32Array(maxTriangles * 3);
var halfedges = this.halfedges = new Int32Array(maxTriangles * 3);
this._hashSize = Math.ceil(Math.sqrt(n));
var hullPrev = this.hullPrev = new Uint32Array(n);
var hullNext = this.hullNext = new Uint32Array(n);
var hullTri = this.hullTri = new Uint32Array(n);
var hullHash = new Int32Array(this._hashSize).fill(-1);
var ids = new Uint32Array(n);
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
for (var i = 0; i < n; i++) {
var x = coords[2 * i];
var y = coords[2 * i + 1];
if (x < minX) { minX = x; }
if (y < minY) { minY = y; }
if (x > maxX) { maxX = x; }
if (y > maxY) { maxY = y; }
ids[i] = i;
}
var cx = (minX + maxX) / 2;
var cy = (minY + maxY) / 2;
var minDist = Infinity;
var i0, i1, i2;
for (var i$1 = 0; i$1 < n; i$1++) {
var d = dist(cx, cy, coords[2 * i$1], coords[2 * i$1 + 1]);
if (d < minDist) {
i0 = i$1;
minDist = d;
}
}
var i0x = coords[2 * i0];
var i0y = coords[2 * i0 + 1];
minDist = Infinity;
for (var i$2 = 0; i$2 < n; i$2++) {
if (i$2 === i0) { continue; }
var d$1 = dist(i0x, i0y, coords[2 * i$2], coords[2 * i$2 + 1]);
if (d$1 < minDist && d$1 > 0) {
i1 = i$2;
minDist = d$1;
}
}
var i1x = coords[2 * i1];
var i1y = coords[2 * i1 + 1];
var minRadius = Infinity;
for (var i$3 = 0; i$3 < n; i$3++) {
if (i$3 === i0 || i$3 === i1) { continue; }
var r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i$3], coords[2 * i$3 + 1]);
if (r < minRadius) {
i2 = i$3;
minRadius = r;
}
}
var i2x = coords[2 * i2];
var i2y = coords[2 * i2 + 1];
if (minRadius === Infinity) {
throw new Error('No Delaunay triangulation exists for this input.');
}
if (orient(i0x, i0y, i1x, i1y, i2x, i2y)) {
var i$4 = i1;
var x$1 = i1x;
var y$1 = i1y;
i1 = i2;
i1x = i2x;
i1y = i2y;
i2 = i$4;
i2x = x$1;
i2y = y$1;
}
var center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
this._cx = center.x;
this._cy = center.y;
var dists = new Float64Array(n);
for (var i$5 = 0; i$5 < n; i$5++) {
dists[i$5] = dist(coords[2 * i$5], coords[2 * i$5 + 1], center.x, center.y);
}
quicksort(ids, dists, 0, n - 1);
this.hullStart = i0;
var hullSize = 3;
hullNext[i0] = hullPrev[i2] = i1;
hullNext[i1] = hullPrev[i0] = i2;
hullNext[i2] = hullPrev[i1] = i0;
hullTri[i0] = 0;
hullTri[i1] = 1;
hullTri[i2] = 2;
hullHash[this._hashKey(i0x, i0y)] = i0;
hullHash[this._hashKey(i1x, i1y)] = i1;
hullHash[this._hashKey(i2x, i2y)] = i2;
this.trianglesLen = 0;
this._addTriangle(i0, i1, i2, -1, -1, -1);
for (var k = 0, xp = (void 0), yp = (void 0); k < ids.length; k++) {
var i$6 = ids[k];
var x$2 = coords[2 * i$6];
var y$2 = coords[2 * i$6 + 1];
if (k > 0 && Math.abs(x$2 - xp) <= EPSILON && Math.abs(y$2 - yp) <= EPSILON) { continue; }
xp = x$2;
yp = y$2;
if (i$6 === i0 || i$6 === i1 || i$6 === i2) { continue; }
var start = 0;
for (var j = 0, key = this._hashKey(x$2, y$2); j < this._hashSize; j++) {
start = hullHash[(key + j) % this$1._hashSize];
if (start !== -1 && start !== hullNext[start]) { break; }
}
start = hullPrev[start];
var e = start, q = (void 0);
while (q = hullNext[e], !orient(x$2, y$2, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1])) {
e = q;
if (e === start) {
e = -1;
break;
}
}
if (e === -1) { continue; }
var t = this$1._addTriangle(e, i$6, hullNext[e], -1, -1, hullTri[e]);
hullTri[i$6] = this$1._legalize(t + 2);
hullTri[e] = t;
hullSize++;
var n$1 = hullNext[e];
while (q = hullNext[n$1], orient(x$2, y$2, coords[2 * n$1], coords[2 * n$1 + 1], coords[2 * q], coords[2 * q + 1])) {
t = this$1._addTriangle(n$1, i$6, q, hullTri[i$6], -1, hullTri[n$1]);
hullTri[i$6] = this$1._legalize(t + 2);
hullNext[n$1] = n$1;
hullSize--;
n$1 = q;
}
if (e === start) {
while (q = hullPrev[e], orient(x$2, y$2, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1])) {
t = this$1._addTriangle(q, i$6, e, -1, hullTri[e], hullTri[q]);
this$1._legalize(t + 2);
hullTri[q] = t;
hullNext[e] = e;
hullSize--;
e = q;
}
}
this$1.hullStart = hullPrev[i$6] = e;
hullNext[e] = hullPrev[n$1] = i$6;
hullNext[i$6] = n$1;
hullHash[this$1._hashKey(x$2, y$2)] = i$6;
hullHash[this$1._hashKey(coords[2 * e], coords[2 * e + 1])] = e;
}
this.hull = new Uint32Array(hullSize);
for (var i$7 = 0, e$1 = this.hullStart; i$7 < hullSize; i$7++) {
this$1.hull[i$7] = e$1;
e$1 = hullNext[e$1];
}
this.hullPrev = this.hullNext = this.hullTri = null;
this.triangles = triangles.subarray(0, this.trianglesLen);
this.halfedges = halfedges.subarray(0, this.trianglesLen);
};
Delaunator.from = function from (points, getX, getY) {
if ( getX === void 0 ) getX = defaultGetX;
if ( getY === void 0 ) getY = defaultGetY;
var n = points.length;
var coords = new Float64Array(n * 2);
for (var i = 0; i < n; i++) {
var p = points[i];
coords[2 * i] = getX(p);
coords[2 * i + 1] = getY(p);
}
return new Delaunator(coords);
};
Delaunator.prototype._hashKey = function _hashKey (x, y) {
return Math.floor(pseudoAngle(x - this._cx, y - this._cy) * this._hashSize) % this._hashSize;
};
Delaunator.prototype._legalize = function _legalize (a) {
var this$1 = this;
var ref = this;
var triangles = ref.triangles;
var coords = ref.coords;
var halfedges = ref.halfedges;
var b = halfedges[a];
var a0 = a - a % 3;
var b0 = b - b % 3;
var al = a0 + (a + 1) % 3;
var ar = a0 + (a + 2) % 3;
var bl = b0 + (b + 2) % 3;
if (b === -1) { return ar; }
var p0 = triangles[ar];
var pr = triangles[a];
var pl = triangles[al];
var p1 = triangles[bl];
var illegal = inCircle(
coords[2 * p0], coords[2 * p0 + 1],
coords[2 * pr], coords[2 * pr + 1],
coords[2 * pl], coords[2 * pl + 1],
coords[2 * p1], coords[2 * p1 + 1]);
if (illegal) {
triangles[a] = p1;
triangles[b] = p0;
var hbl = halfedges[bl];
if (hbl === -1) {
var e = this.hullStart;
do {
if (this$1.hullTri[e] === bl) {
this$1.hullTri[e] = a;
break;
}
e = this$1.hullNext[e];
} while (e !== this.hullStart);
}
this._link(a, hbl);
this._link(b, halfedges[ar]);
this._link(ar, bl);
var br = b0 + (b + 1) % 3;
this._legalize(a);
return this._legalize(br);
}
return ar;
};
Delaunator.prototype._link = function _link (a, b) {
this.halfedges[a] = b;
if (b !== -1) { this.halfedges[b] = a; }
};
Delaunator.prototype._addTriangle = function _addTriangle (i0, i1, i2, a, b, c) {
var t = this.trianglesLen;
this.triangles[t] = i0;
this.triangles[t + 1] = i1;
this.triangles[t + 2] = i2;
this._link(t, a);
this._link(t + 1, b);
this._link(t + 2, c);
this.trianglesLen += 3;
return t;
};
function pseudoAngle(dx, dy) {
var p = dx / (Math.abs(dx) + Math.abs(dy));
return (dy > 0 ? 3 - p : 1 + p) / 4;
}
function dist(ax, ay, bx, by) {
var dx = ax - bx;
var dy = ay - by;
return dx * dx + dy * dy;
}
function orient(px, py, qx, qy, rx, ry) {
return (qy - py) * (rx - qx) - (qx - px) * (ry - qy) < 0;
}
function inCircle(ax, ay, bx, by, cx, cy, px, py) {
var dx = ax - px;
var dy = ay - py;
var ex = bx - px;
var ey = by - py;
var fx = cx - px;
var fy = cy - py;
var ap = dx * dx + dy * dy;
var bp = ex * ex + ey * ey;
var cp = fx * fx + fy * fy;
return dx * (ey * cp - bp * fy) -
dy * (ex * cp - bp * fx) +
ap * (ex * fy - ey * fx) < 0;
}
function circumradius(ax, ay, bx, by, cx, cy) {
var dx = bx - ax;
var dy = by - ay;
var ex = cx - ax;
var ey = cy - ay;
var bl = dx * dx + dy * dy;
var cl = ex * ex + ey * ey;
var d = 0.5 / (dx * ey - dy * ex);
var x = (ey * bl - dy * cl) * d;
var y = (dx * cl - ex * bl) * d;
return x * x + y * y;
}
function circumcenter(ax, ay, bx, by, cx, cy) {
var dx = bx - ax;
var dy = by - ay;
var ex = cx - ax;
var ey = cy - ay;
var bl = dx * dx + dy * dy;
var cl = ex * ex + ey * ey;
var d = 0.5 / (dx * ey - dy * ex);
var x = ax + (ey * bl - dy * cl) * d;
var y = ay + (dx * cl - ex * bl) * d;
return {x: x, y: y};
}
function quicksort(ids, dists, left, right) {
if (right - left <= 20) {
for (var i = left + 1; i <= right; i++) {
var temp = ids[i];
var tempDist = dists[temp];
var j = i - 1;
while (j >= left && dists[ids[j]] > tempDist) { ids[j + 1] = ids[j--]; }
ids[j + 1] = temp;
}
} else {
var median = (left + right) >> 1;
var i$1 = left + 1;
var j$1 = right;
swap(ids, median, i$1);
if (dists[ids[left]] > dists[ids[right]]) { swap(ids, left, right); }
if (dists[ids[i$1]] > dists[ids[right]]) { swap(ids, i$1, right); }
if (dists[ids[left]] > dists[ids[i$1]]) { swap(ids, left, i$1); }
var temp$1 = ids[i$1];
var tempDist$1 = dists[temp$1];
while (true) {
do { i$1++; } while (dists[ids[i$1]] < tempDist$1);
do { j$1--; } while (dists[ids[j$1]] > tempDist$1);
if (j$1 < i$1) { break; }
swap(ids, i$1, j$1);
}
ids[left + 1] = ids[j$1];
ids[j$1] = temp$1;
if (right - i$1 + 1 >= j$1 - left) {
quicksort(ids, dists, i$1, right);
quicksort(ids, dists, left, j$1 - 1);
} else {
quicksort(ids, dists, left, j$1 - 1);
quicksort(ids, dists, i$1, right);
}
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function defaultGetX(p) {
return p[0];
}
function defaultGetY(p) {
return p[1];
}
return Delaunator;
})));
var folder = "17_50438_37354_50534_37450/satellite/";
var levels = [0x0B132B, 0x1C2541, 0x3A506B, 0x5BC0BE, 0x6FFFE9];
var mouseDown = false;
var renderer, scene, camera, controls, loader, terrain, glsl, uniforms, root, tree;
var colors = [0xFFFFFF, 0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF];
class Node{
constructor(level_, index_, centerX_, centerY_, width_, height_, resolution_){
this.level = level_;
this.index = index_;
this.w = width_;
this.h = height_;
this.x = centerX_;
this.y = centerY_;
this.resolution = resolution_;
this.lifetime = 128;
this.edges = this.getEdges();
}
getEdges = function(){
return [
new THREE.Vector3(this.x - this.w / 2, 0, this.y - this.h / 2),
new THREE.Vector3(this.x + this.w / 2, 0, this.y - this.h / 2),
new THREE.Vector3(this.x - this.w / 2, 0, this.y + this.h / 2),
new THREE.Vector3(this.x + this.w / 2, 0, this.y + this.h / 2)
];
}
}
class Quadtree{
constructor(root_, levels_, distance_){
var this_ = this;
this.levels = levels_;
this.distance = distance_;
this.root = root_;
this.nodes = [];
this.nodes = this.splitNode(0, this.root, false);
this.generateLevels();
this.last = [...this.nodes];
this.tiles = {};
this.debug = {};
this.points = [];
}
generateLevels = function(){
for(var i = 0; i < this.levels; i++){
var tmpNodes = [];
for(var j = 0; j < this.nodes.length; j++){
tmpNodes.push(...this.splitNode(j, this.nodes[j], true));
}
this.nodes = tmpNodes;
}
}
update = function(){
var this_ = this;
this.nodes = [];
this.nodes = this.splitNode(0, this.root, false);
this.generateLevels();
this.debug = {};
this.last = [...this.nodes];
}
splitNode = function(index_, parent_, check_){
if((parent_.level < this.levels && this.sqrtDistance(parent_) < this.distance) || !check_){
var lt = new Node(parent_.level + 1, { x: parent_.index.x * 2, y: parent_.index.y * 2 }, parent_.x - parent_.w / 4, parent_.y - parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
var rt = new Node(parent_.level + 1, { x: parent_.index.x * 2, y: parent_.index.y * 2 + 1 }, parent_.x + parent_.w / 4, parent_.y - parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
var lb = new Node(parent_.level + 1, { x: parent_.index.x * 2 + 1, y: parent_.index.y * 2 }, parent_.x - parent_.w / 4, parent_.y + parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
var rb = new Node(parent_.level + 1, { x: parent_.index.x * 2 + 1, y: parent_.index.y * 2 + 1 }, parent_.x + parent_.w / 4, parent_.y + parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
return [lt, rt, lb, rb];
}
return [parent_];
}
sqrtDistance = function(node_){
var target = new THREE.Vector2(camera.position.x, camera.position.z).lerp(new THREE.Vector2(controls.target.x, controls.target.z), 1.0);
var x1 = node_.x - node_.w / 2.0;
var y1 = node_.y - node_.h / 2.0;
var x2 = node_.x + node_.w / 2.0;
var y2 = node_.y + node_.h / 2.0;
var rx = (x1 + x2) / 2.0;
var ry = (y1 + y2) / 2.0;
var rwidth = node_.w;
var rheight = node_.h;
var dx = Math.max(Math.abs(target.x - rx) - rwidth / 2, 0);
var dy = Math.max(Math.abs(target.y - ry) - rheight / 2, 0);
return Math.sqrt(dx * dx + dy * dy);
}
}
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
loader = new THREE.TextureLoader();
loader.crossOrigin = "";
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 51200);
camera.position.set(-2048, 2048, -2048);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 8;
controls.maxDistance = 5120;
controls.maxPolarAngle = Math.PI / 2;
camera.position.set(208.48355078304965, 45.28894677815297, 310.34089790619583);
controls.target.set(233.437242880138, -1.1266992511037067e-14, 279.779814968453);
root = new Node(0, {x: 0, y: 0}, 0, 0, 2048, 2048, 64);
partition = new Quadtree(root, 5, 2048.0 / 16.0);
var points = [];
partition.nodes.forEach(function(node_){
points.push(...node_.edges);
});
var geometry = new THREE.BufferGeometry().setFromPoints(points);
var indexDelaunay = Delaunator.from(points.map(v => { return [v.x, v.z]; }) );
var meshIndex = [];
for (let i = 0; i < indexDelaunay.triangles.length; i++){ meshIndex.push(indexDelaunay.triangles[i]); }
geometry.setIndex(meshIndex);
geometry.computeVertexNormals();
var plane = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial({ wireframe: true }));
scene.add(plane);
animate();
document.addEventListener("mousedown", function(){ mouseDown = true; }, false);
document.addEventListener("mouseup", function(){ mouseDown = false; }, false);
document.addEventListener("mousemove", onMouseUpdate, false);
renderer.domElement.addEventListener("wheel", onMouseUpdate, false);
function animate(){
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
function onMouseUpdate(e_){
partition.update(new THREE.Vector2(camera.position.x, camera.position.y));
var points = [];
partition.nodes.forEach(function(node_){
points.push(...node_.edges);
});
var geometry = new THREE.BufferGeometry().setFromPoints(points);
var indexDelaunay = Delaunator.from(points.map(v => { return [v.x, v.z]; }) );
var meshIndex = [];
for (let i = 0; i < indexDelaunay.triangles.length; i++){ meshIndex.push(indexDelaunay.triangles[i]); }
geometry.setIndex(meshIndex);
geometry.computeVertexNormals();
plane.geometry = geometry;
}
body { margin: 0; }
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>GLSL Intersection</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://unpkg.com/three#0.116.0/build/three.min.js"></script>
<script src="https://unpkg.com/three#0.116.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
</body>
</html>

That's a simple sketch to TheJim01 proposal of splitting geometry render to different draw calls by geometry.addGroup method. The snippet has a crossOrigin issue, so it's just for reference.
I don't know if it's capable of doing 60-70 textures/materials for one geometry, however the following code could be used for >16 materials.
//https://hofk.de/main/discourse.threejs/2018/Triangulation/Triangulation.html
////by Mapbox https://github.com/mapbox/delaunator
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Delaunator = factory());
}(this, (function () { 'use strict';
var EPSILON = Math.pow(2, -52);
var Delaunator = function Delaunator(coords) {
var this$1 = this;
var n = coords.length >> 1;
if (n > 0 && typeof coords[0] !== 'number') { throw new Error('Expected coords to contain numbers.'); }
this.coords = coords;
var maxTriangles = 2 * n - 5;
var triangles = this.triangles = new Uint32Array(maxTriangles * 3);
var halfedges = this.halfedges = new Int32Array(maxTriangles * 3);
this._hashSize = Math.ceil(Math.sqrt(n));
var hullPrev = this.hullPrev = new Uint32Array(n);
var hullNext = this.hullNext = new Uint32Array(n);
var hullTri = this.hullTri = new Uint32Array(n);
var hullHash = new Int32Array(this._hashSize).fill(-1);
var ids = new Uint32Array(n);
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
for (var i = 0; i < n; i++) {
var x = coords[2 * i];
var y = coords[2 * i + 1];
if (x < minX) { minX = x; }
if (y < minY) { minY = y; }
if (x > maxX) { maxX = x; }
if (y > maxY) { maxY = y; }
ids[i] = i;
}
var cx = (minX + maxX) / 2;
var cy = (minY + maxY) / 2;
var minDist = Infinity;
var i0, i1, i2;
for (var i$1 = 0; i$1 < n; i$1++) {
var d = dist(cx, cy, coords[2 * i$1], coords[2 * i$1 + 1]);
if (d < minDist) {
i0 = i$1;
minDist = d;
}
}
var i0x = coords[2 * i0];
var i0y = coords[2 * i0 + 1];
minDist = Infinity;
for (var i$2 = 0; i$2 < n; i$2++) {
if (i$2 === i0) { continue; }
var d$1 = dist(i0x, i0y, coords[2 * i$2], coords[2 * i$2 + 1]);
if (d$1 < minDist && d$1 > 0) {
i1 = i$2;
minDist = d$1;
}
}
var i1x = coords[2 * i1];
var i1y = coords[2 * i1 + 1];
var minRadius = Infinity;
for (var i$3 = 0; i$3 < n; i$3++) {
if (i$3 === i0 || i$3 === i1) { continue; }
var r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i$3], coords[2 * i$3 + 1]);
if (r < minRadius) {
i2 = i$3;
minRadius = r;
}
}
var i2x = coords[2 * i2];
var i2y = coords[2 * i2 + 1];
if (minRadius === Infinity) {
throw new Error('No Delaunay triangulation exists for this input.');
}
if (orient(i0x, i0y, i1x, i1y, i2x, i2y)) {
var i$4 = i1;
var x$1 = i1x;
var y$1 = i1y;
i1 = i2;
i1x = i2x;
i1y = i2y;
i2 = i$4;
i2x = x$1;
i2y = y$1;
}
var center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
this._cx = center.x;
this._cy = center.y;
var dists = new Float64Array(n);
for (var i$5 = 0; i$5 < n; i$5++) {
dists[i$5] = dist(coords[2 * i$5], coords[2 * i$5 + 1], center.x, center.y);
}
quicksort(ids, dists, 0, n - 1);
this.hullStart = i0;
var hullSize = 3;
hullNext[i0] = hullPrev[i2] = i1;
hullNext[i1] = hullPrev[i0] = i2;
hullNext[i2] = hullPrev[i1] = i0;
hullTri[i0] = 0;
hullTri[i1] = 1;
hullTri[i2] = 2;
hullHash[this._hashKey(i0x, i0y)] = i0;
hullHash[this._hashKey(i1x, i1y)] = i1;
hullHash[this._hashKey(i2x, i2y)] = i2;
this.trianglesLen = 0;
this._addTriangle(i0, i1, i2, -1, -1, -1);
for (var k = 0, xp = (void 0), yp = (void 0); k < ids.length; k++) {
var i$6 = ids[k];
var x$2 = coords[2 * i$6];
var y$2 = coords[2 * i$6 + 1];
if (k > 0 && Math.abs(x$2 - xp) <= EPSILON && Math.abs(y$2 - yp) <= EPSILON) { continue; }
xp = x$2;
yp = y$2;
if (i$6 === i0 || i$6 === i1 || i$6 === i2) { continue; }
var start = 0;
for (var j = 0, key = this._hashKey(x$2, y$2); j < this._hashSize; j++) {
start = hullHash[(key + j) % this$1._hashSize];
if (start !== -1 && start !== hullNext[start]) { break; }
}
start = hullPrev[start];
var e = start, q = (void 0);
while (q = hullNext[e], !orient(x$2, y$2, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1])) {
e = q;
if (e === start) {
e = -1;
break;
}
}
if (e === -1) { continue; }
var t = this$1._addTriangle(e, i$6, hullNext[e], -1, -1, hullTri[e]);
hullTri[i$6] = this$1._legalize(t + 2);
hullTri[e] = t;
hullSize++;
var n$1 = hullNext[e];
while (q = hullNext[n$1], orient(x$2, y$2, coords[2 * n$1], coords[2 * n$1 + 1], coords[2 * q], coords[2 * q + 1])) {
t = this$1._addTriangle(n$1, i$6, q, hullTri[i$6], -1, hullTri[n$1]);
hullTri[i$6] = this$1._legalize(t + 2);
hullNext[n$1] = n$1;
hullSize--;
n$1 = q;
}
if (e === start) {
while (q = hullPrev[e], orient(x$2, y$2, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1])) {
t = this$1._addTriangle(q, i$6, e, -1, hullTri[e], hullTri[q]);
this$1._legalize(t + 2);
hullTri[q] = t;
hullNext[e] = e;
hullSize--;
e = q;
}
}
this$1.hullStart = hullPrev[i$6] = e;
hullNext[e] = hullPrev[n$1] = i$6;
hullNext[i$6] = n$1;
hullHash[this$1._hashKey(x$2, y$2)] = i$6;
hullHash[this$1._hashKey(coords[2 * e], coords[2 * e + 1])] = e;
}
this.hull = new Uint32Array(hullSize);
for (var i$7 = 0, e$1 = this.hullStart; i$7 < hullSize; i$7++) {
this$1.hull[i$7] = e$1;
e$1 = hullNext[e$1];
}
this.hullPrev = this.hullNext = this.hullTri = null;
this.triangles = triangles.subarray(0, this.trianglesLen);
this.halfedges = halfedges.subarray(0, this.trianglesLen);
};
Delaunator.from = function from (points, getX, getY) {
if ( getX === void 0 ) getX = defaultGetX;
if ( getY === void 0 ) getY = defaultGetY;
var n = points.length;
var coords = new Float64Array(n * 2);
for (var i = 0; i < n; i++) {
var p = points[i];
coords[2 * i] = getX(p);
coords[2 * i + 1] = getY(p);
}
return new Delaunator(coords);
};
Delaunator.prototype._hashKey = function _hashKey (x, y) {
return Math.floor(pseudoAngle(x - this._cx, y - this._cy) * this._hashSize) % this._hashSize;
};
Delaunator.prototype._legalize = function _legalize (a) {
var this$1 = this;
var ref = this;
var triangles = ref.triangles;
var coords = ref.coords;
var halfedges = ref.halfedges;
var b = halfedges[a];
var a0 = a - a % 3;
var b0 = b - b % 3;
var al = a0 + (a + 1) % 3;
var ar = a0 + (a + 2) % 3;
var bl = b0 + (b + 2) % 3;
if (b === -1) { return ar; }
var p0 = triangles[ar];
var pr = triangles[a];
var pl = triangles[al];
var p1 = triangles[bl];
var illegal = inCircle(
coords[2 * p0], coords[2 * p0 + 1],
coords[2 * pr], coords[2 * pr + 1],
coords[2 * pl], coords[2 * pl + 1],
coords[2 * p1], coords[2 * p1 + 1]);
if (illegal) {
triangles[a] = p1;
triangles[b] = p0;
var hbl = halfedges[bl];
if (hbl === -1) {
var e = this.hullStart;
do {
if (this$1.hullTri[e] === bl) {
this$1.hullTri[e] = a;
break;
}
e = this$1.hullNext[e];
} while (e !== this.hullStart);
}
this._link(a, hbl);
this._link(b, halfedges[ar]);
this._link(ar, bl);
var br = b0 + (b + 1) % 3;
this._legalize(a);
return this._legalize(br);
}
return ar;
};
Delaunator.prototype._link = function _link (a, b) {
this.halfedges[a] = b;
if (b !== -1) { this.halfedges[b] = a; }
};
Delaunator.prototype._addTriangle = function _addTriangle (i0, i1, i2, a, b, c) {
var t = this.trianglesLen;
this.triangles[t] = i0;
this.triangles[t + 1] = i1;
this.triangles[t + 2] = i2;
this._link(t, a);
this._link(t + 1, b);
this._link(t + 2, c);
this.trianglesLen += 3;
return t;
};
function pseudoAngle(dx, dy) {
var p = dx / (Math.abs(dx) + Math.abs(dy));
return (dy > 0 ? 3 - p : 1 + p) / 4;
}
function dist(ax, ay, bx, by) {
var dx = ax - bx;
var dy = ay - by;
return dx * dx + dy * dy;
}
function orient(px, py, qx, qy, rx, ry) {
return (qy - py) * (rx - qx) - (qx - px) * (ry - qy) < 0;
}
function inCircle(ax, ay, bx, by, cx, cy, px, py) {
var dx = ax - px;
var dy = ay - py;
var ex = bx - px;
var ey = by - py;
var fx = cx - px;
var fy = cy - py;
var ap = dx * dx + dy * dy;
var bp = ex * ex + ey * ey;
var cp = fx * fx + fy * fy;
return dx * (ey * cp - bp * fy) -
dy * (ex * cp - bp * fx) +
ap * (ex * fy - ey * fx) < 0;
}
function circumradius(ax, ay, bx, by, cx, cy) {
var dx = bx - ax;
var dy = by - ay;
var ex = cx - ax;
var ey = cy - ay;
var bl = dx * dx + dy * dy;
var cl = ex * ex + ey * ey;
var d = 0.5 / (dx * ey - dy * ex);
var x = (ey * bl - dy * cl) * d;
var y = (dx * cl - ex * bl) * d;
return x * x + y * y;
}
function circumcenter(ax, ay, bx, by, cx, cy) {
var dx = bx - ax;
var dy = by - ay;
var ex = cx - ax;
var ey = cy - ay;
var bl = dx * dx + dy * dy;
var cl = ex * ex + ey * ey;
var d = 0.5 / (dx * ey - dy * ex);
var x = ax + (ey * bl - dy * cl) * d;
var y = ay + (dx * cl - ex * bl) * d;
return {x: x, y: y};
}
function quicksort(ids, dists, left, right) {
if (right - left <= 20) {
for (var i = left + 1; i <= right; i++) {
var temp = ids[i];
var tempDist = dists[temp];
var j = i - 1;
while (j >= left && dists[ids[j]] > tempDist) { ids[j + 1] = ids[j--]; }
ids[j + 1] = temp;
}
} else {
var median = (left + right) >> 1;
var i$1 = left + 1;
var j$1 = right;
swap(ids, median, i$1);
if (dists[ids[left]] > dists[ids[right]]) { swap(ids, left, right); }
if (dists[ids[i$1]] > dists[ids[right]]) { swap(ids, i$1, right); }
if (dists[ids[left]] > dists[ids[i$1]]) { swap(ids, left, i$1); }
var temp$1 = ids[i$1];
var tempDist$1 = dists[temp$1];
while (true) {
do { i$1++; } while (dists[ids[i$1]] < tempDist$1);
do { j$1--; } while (dists[ids[j$1]] > tempDist$1);
if (j$1 < i$1) { break; }
swap(ids, i$1, j$1);
}
ids[left + 1] = ids[j$1];
ids[j$1] = temp$1;
if (right - i$1 + 1 >= j$1 - left) {
quicksort(ids, dists, i$1, right);
quicksort(ids, dists, left, j$1 - 1);
} else {
quicksort(ids, dists, left, j$1 - 1);
quicksort(ids, dists, i$1, right);
}
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function defaultGetX(p) {
return p[0];
}
function defaultGetY(p) {
return p[1];
}
return Delaunator;
})));
//https://webglreport.com/?v=2
//https://victorbush.com/2015/01/tessellated-terrain/
//https://spite.github.io/rstats/
var folder = "17_50438_37354_50534_37450/satellite/";
var levels = [0x0B132B, 0x1C2541, 0x3A506B, 0x5BC0BE, 0x6FFFE9];
var mouseDown = false;
var renderer, scene, camera, controls, loader, terrain, glsl, uniforms, root, tree;
var colors = [0xFFFFFF, 0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF];
class Node{
constructor(level_, index_, centerX_, centerY_, width_, height_, resolution_){
this.level = level_;
this.index = index_;
this.w = width_;
this.h = height_;
this.x = centerX_;
this.y = centerY_;
this.resolution = resolution_;
this.lifetime = 128;
this.edges = this.getEdges();
}
getEdges = function(){
return [
new THREE.Vector3(this.x - this.w / 2, 0, this.y - this.h / 2),
new THREE.Vector3(this.x + this.w / 2, 0, this.y - this.h / 2),
new THREE.Vector3(this.x - this.w / 2, 0, this.y + this.h / 2),
new THREE.Vector3(this.x + this.w / 2, 0, this.y + this.h / 2)
];
}
}
class Quadtree{
constructor(root_, levels_, distance_){
var this_ = this;
this.levels = levels_;
this.distance = distance_;
this.root = root_;
this.nodes = [];
this.nodes = this.splitNode(0, this.root, false);
this.generateLevels();
this.last = [...this.nodes];
this.tiles = {};
this.debug = {};
this.points = [];
}
generateLevels = function(){
for(var i = 0; i < this.levels; i++){
var tmpNodes = [];
for(var j = 0; j < this.nodes.length; j++){
tmpNodes.push(...this.splitNode(j, this.nodes[j], true));
}
this.nodes = tmpNodes;
}
}
update = function(){
var this_ = this;
this.nodes = [];
this.nodes = this.splitNode(0, this.root, false);
this.generateLevels();
this.debug = {};
this.last = [...this.nodes];
}
splitNode = function(index_, parent_, check_){
if((parent_.level < this.levels && this.sqrtDistance(parent_) < this.distance) || !check_){
var lt = new Node(parent_.level + 1, { x: parent_.index.x * 2, y: parent_.index.y * 2 }, parent_.x - parent_.w / 4, parent_.y - parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
var rt = new Node(parent_.level + 1, { x: parent_.index.x * 2, y: parent_.index.y * 2 + 1 }, parent_.x + parent_.w / 4, parent_.y - parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
var lb = new Node(parent_.level + 1, { x: parent_.index.x * 2 + 1, y: parent_.index.y * 2 }, parent_.x - parent_.w / 4, parent_.y + parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
var rb = new Node(parent_.level + 1, { x: parent_.index.x * 2 + 1, y: parent_.index.y * 2 + 1 }, parent_.x + parent_.w / 4, parent_.y + parent_.h / 4, parent_.w / 2, parent_.h / 2, parent_.resolution / 2);
return [lt, rt, lb, rb];
}
return [parent_];
}
sqrtDistance = function(node_){
var target = new THREE.Vector2(camera.position.x, camera.position.z).lerp(new THREE.Vector2(controls.target.x, controls.target.z), 1.0);
var x1 = node_.x - node_.w / 2.0;
var y1 = node_.y - node_.h / 2.0;
var x2 = node_.x + node_.w / 2.0;
var y2 = node_.y + node_.h / 2.0;
var rx = (x1 + x2) / 2.0;
var ry = (y1 + y2) / 2.0;
var rwidth = node_.w;
var rheight = node_.h;
var dx = Math.max(Math.abs(target.x - rx) - rwidth / 2, 0);
var dy = Math.max(Math.abs(target.y - ry) - rheight / 2, 0);
return Math.sqrt(dx * dx + dy * dy);
}
}
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
loader = new THREE.TextureLoader();
loader.crossOrigin = "";
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 51200);
camera.position.set(-2048, 2048, -2048);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 8;
controls.maxDistance = 5120;
controls.maxPolarAngle = Math.PI / 2;
camera.position.set(208.48355078304965, 45.28894677815297, 310.34089790619583);
controls.target.set(233.437242880138, -1.1266992511037067e-14, 279.779814968453);
var points = [
new THREE.Vector3(-1024, 0, -1024),
new THREE.Vector3(0, 0, -1024),
new THREE.Vector3(-1024, 0, 0),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(0, 0, -1024),
new THREE.Vector3(1024, 0, -1024),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(1024, 0, 0),
new THREE.Vector3(-1024, 0, 0),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(-1024, 0, 1024),
new THREE.Vector3(0, 0, 1024),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(1024, 0, 0),
new THREE.Vector3(0, 0, 1024),
new THREE.Vector3(1024, 0, 1024),
];
var geometry = new THREE.BufferGeometry().setFromPoints(points);
var indices = [];
//0-1
indices.push(0, 1, 3);
indices.push(3, 2, 0);
//2-3
indices.push(4, 5, 7);
indices.push(7, 6, 4);
//4-5
indices.push(8, 9, 11);
indices.push(11, 10, 8);
indices.push(12, 13, 15);
indices.push(15, 14, 12);
geometry.setIndex( indices );
geometry.addGroup(0, 6, 0);
geometry.addGroup(6, 6, 1);
geometry.addGroup(12, 6, 2);
geometry.addGroup(18, 6, 3);
var quad_uvs =
[
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0
];
uvs = new Float32Array(quad_uvs);
geometry.setAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
geometry.computeVertexNormals();
var materials = [
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("0_0.jpg")}),
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("1_0.jpg")}),
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("0_1.jpg")}),
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("1_1.jpg")})
];
var plane = new THREE.Mesh(geometry, materials);
plane.rotation.set(Math.PI, 0, 0);
scene.add(plane);
animate();
function animate(){
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
body { margin: 0; }
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>GLSL Intersection</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://unpkg.com/three#0.116.0/build/three.min.js"></script>
<script src="https://unpkg.com/three#0.116.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
</body>
</html>

Related

Three Js update vertices,normals and indices

this Code renders this :
render of the code
the problem i'm having is can't update the vertices,
in my code i'm trying to update three points just to see if it works.
the geometry has a normal vector and indices so maybe i should update those too.
export default (scene) => {
const noise = new Noise(Math.random());
const geometry = new THREE.BufferGeometry();
let indices = [];
let vertices = [];
let normals = [];
const size = 50;
const segments = 32;
const halfSize = size / 2;
const segmentSize = size / segments;
let xoff = 0,
yoff = 0,
zofff = 0.01;
for (let i = 0; i <= segments; i++) {
const y = i * segmentSize - halfSize;
yoff += 0.01;
for (let j = 0; j <= segments; j++) {
xoff += 0.01;
const dd = noise.perlin3(xoff, yoff, zofff) * 3;
const x = j * segmentSize - halfSize;
vertices.push(x, dd, -y);
normals.push(1, 0, 1);
}
}
for (let i = 0; i < segments; i++) {
for (let j = 0; j < segments; j++) {
const a = i * (segments + 1) + (j + 1);
const b = i * (segments + 1) + j;
const c = (i + 1) * (segments + 1) + j;
const d = (i + 1) * (segments + 1) + (j + 1);
// generate two faces (triangles) per iteration
indices.push(a, b, d); // face one
indices.push(b, c, d); // face two
}
}
geometry.setIndex(indices);
geometry.addAttribute(
'position',
new THREE.Float32BufferAttribute(vertices, 3).setDynamic(true),
);
geometry.addAttribute(
'normal',
new THREE.Float32BufferAttribute(normals, 3).setDynamic(true),
);
const material = new THREE.MeshPhongMaterial({
side: THREE.DoubleSide,
wireframe: true,
color: '0xffffff',
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
let zoff = 0;
function update() {
zoff += 0.1;
vertices[1] = Math.random() * 10;
vertices[4] = Math.random() * 10;
vertices[7] = Math.random() * 10;
mesh.geometry.attributes.position.needsUpdate = true;
mesh.geometry.verticesNeedUpdate = true;
}
Seems, you have to change the buffer attribute with positions, not the initial array.
Thus in your update it should look like this:
function update() {
zoff += 0.1; // the reason to have it here is unknown
geometry.attributes.position.setY(0, Math.random() * 10);
geometry.attributes.position.setY(1, Math.random() * 10);
geometry.attributes.position.setY(2, Math.random() * 10);
geometry.attributes.position.needsUpdate = true;
}

Image transformation matrix

I want to practice pixel manipulation with matrix for extract an image from another.
This is what I have done with css transformation matrix :
https://www.noelshack.com/2017-18-1493893008-capture-2.png
With the Left image 'L' I have place 4 points around the image and in the right image 'R' I find the content of the transformation.
For that i use the property transform of the css but i want to do the manipulation manually.
CSS version :
matrix3d(1.5456325781948308,1.6561987730956724,0,0.0012239101773909712,-0.4663849104791486,2.218793881308064,0,0.0009095626603861196,0,0,1,0,12.247969030166722,-17.754955132517754,0,0.9951722722714726)
Matrix 'M':
[[1.5456325781948308, 1.6561987730956724, 0, 0.0012239101773909712],
[-0.4663849104791486, 2.218793881308064, 0, 0.0009095626603861196],
[0, 0, 1, 0],
[12.247969030166722, -17.754955132517754, 0, 0.9951722722714726]]
I want to know for each pixel in the image R what are their pixel related position in the image L.
For example (0,0) in R is (52,203) in R.
For that i do this calculation.
M * P = P'
P is the pixel position in R image
P' is the pixel position in L image
P matrix is define like that:
[[x],
[y],
[0],
[1]]
So for the 0,0 position, I do this :
[[1.5456325781948308, 1.6561987730956724, 0, 0.0012239101773909712],
[-0.4663849104791486, 2.218793881308064, 0, 0.0009095626603861196],
[0, 0, 1, 0],
[12.247969030166722, -17.754955132517754, 0, 0.9951722722714726]]
X
[[0],
[0],
[0],
[1]]
=
[[0.0012239101773909712],
[0.0009095626603861196],
[0],
[0.9951722722714726]]
This is the result, but the 2 first component :
(0.0012239101773909712, 0.0009095626603861196)
is too smaller than expected. can you help me to find the problem.
scincerly,
MatrixCuriosity.
These are homogeneous coordinates. So given some [x1, y1, z1, 1] as input you obtain some [x2, y2, z2, w2] but the actual position they describe is [x2/w2, y2/w2, z2/w2], i.e. you have to divide by the last coordinate.
But this doesn't lead to the result you expected. Nor does replacing the matrix with its adjunct (or equivalently inverse), nor its transpose. Both of these are conventions that are easy to get wrong, so without spending too much thought about which version you actually have and should have, trying all four alternatives (with and without adjunct, with and without transpose) solves a huge number of trivial problems.
But not yours. So my next best bet would be that the coordinates you expect are measured from some corner of the image, while the CSS property transform-origin is at it's initial value of 50% 50% 0 so the origin of the coordinate system is in fact in the center of the object.
Actually sharing the HTML and CSS for this might have allowed me to verify this assumption. Now you have to check whether this applies to you. I remember that when I last created a projective image transformation demo to answer a question about finding the transform, I deliberately set transform-origin: 0 0; (and the various vendor-prefixed versions of this) to avoid such problems.
Thanks a lot MvG.
I follow your link and I find what I want [https://math.stackexchange.com/a/339033]
Just one thing, I have to invert the C matrix to find the pixel related L<-R
I share my code for give an idea of what you have to do
You can find my implementation in the function computeMat()
<style>
body {
touch-action: none;
overflow-y: hidden;
}
#canvas_toeic
{
position:absolute;
top:0;
left:0;
}
</style>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/mathjs/3.12.2/math.min.js"></script>
</head>
<body>
<canvas id="canvas_toeic" width="600" height="400">
</canvas>
<script type="text/javascript">
var image = new Image();
image.src = 'image.jpg';
image.onload = function() {
var c = document.getElementById("canvas_toeic");
var ratio = image.width / image.height;
var canvasWidth = document.body.clientWidth;
var canvasHeight = canvasWidth / ratio;
if(document.body.clientHeight < canvasHeight)
{
canvasHeight = document.body.clientHeight;
canvasWidth = canvasHeight * ratio;
}
var canvasLargeur = canvasWidth;
var canvasLongueur = canvasHeight;
if(canvasLargeur < canvasHeight) {
canvasLargeur = canvasHeight;
canvasLongueur = canvasWidth;
}
var canvasPixelRatio = canvasLargeur / image.width;
c.setAttribute("width", canvasWidth);
c.setAttribute("height", canvasHeight);
var ctx = c.getContext("2d");
var idPoint = -1;
var points = [];
for(var i = 0; i < 4; i++)
points[i] = {x:0, y:0};
var marginImage = Math.round(40 * canvasPixelRatio);
points[0].x = marginImage;
points[0].y = marginImage;
points[1].x = marginImage;
points[1].y = canvasHeight - marginImage;
points[2].x = canvasWidth - marginImage;
points[2].y = canvasHeight - marginImage;
points[3].x = canvasWidth - marginImage;
points[3].y = marginImage;
function draw(points) {
console.log("draw");
// Fond
ctx.fillStyle = "#222";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.drawImage(image, marginImage, marginImage, canvasWidth - marginImage * 2, canvasHeight - marginImage * 2); // this fait référence à l'objet courant (=image)
if(idPoint == -1)
ctx.lineWidth = 3 * canvasPixelRatio;
else
ctx.lineWidth = 5 * canvasPixelRatio;
ctx.beginPath(); // Début du chemin
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "rgba(64, 128, 255, 0.5)";
ctx.moveTo(points[0].x, points[0].y); // Le tracé part du point 50,50
for(var i = 0; i < 4; i++)
ctx.lineTo(points[i].x, points[i].y); // Un segment est ajouté vers 200,200
ctx.closePath(); // Fermeture du chemin (facultative)
ctx.stroke();
for(var i = 0; i < 4; i++)
{
var radius = 30 * canvasPixelRatio;
if(idPoint == i)
radius = 60 * canvasPixelRatio;
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, radius, 0, Math.PI*2, true);
ctx.strokeStyle = "#FF8800";
ctx.fillStyle = "rgba(255, 128, 0, 0.5)";
ctx.fill();
ctx.stroke();
}
if(idPoint != -1)
{
var zoomWidth = canvasWidth / 3;
var zoomHeight = canvasHeight / 3;
var zoomMargin = 5;
var zoomAroundWidth = 50;
var zoomAroundHeight = zoomAroundWidth / ratio;
var positionMouse = points[idPoint];
var imagePositionX = (positionMouse.x - marginImage) / (canvasWidth - marginImage * 2) * image.width;
var imagePositionY = (positionMouse.y - marginImage) / (canvasHeight - marginImage * 2) * image.height;
var zoomX = 0;
var zoomY = 0;
if(imagePositionX < image.width / 2)
zoomX = canvasWidth - zoomWidth;
if(imagePositionY < image.height / 2)
zoomY = canvasHeight - zoomHeight;
ctx.fillStyle = "#F08";
ctx.fillRect(zoomX, zoomY, zoomWidth, zoomHeight);
ctx.drawImage(image, imagePositionX - zoomAroundWidth, imagePositionY - zoomAroundHeight, zoomAroundWidth * 2, zoomAroundHeight * 2, zoomX + zoomMargin, zoomY + zoomMargin, zoomWidth - zoomMargin * 2, zoomHeight - zoomMargin * 2);
ctx.lineWidth = 3 * canvasPixelRatio;
ctx.beginPath();
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "rgba(255, 0, 0, 0.5)";
ctx.moveTo(zoomX, zoomY + zoomHeight / 2);
ctx.lineTo(zoomX + zoomWidth, zoomY + zoomHeight / 2);
ctx.moveTo(zoomX + zoomWidth / 2, zoomY);
ctx.lineTo(zoomX + zoomWidth / 2, zoomY + zoomHeight);
ctx.closePath();
ctx.stroke();
}
}
function nearPoint(points, x, y)
{
var radiusDetection = 60 * canvasPixelRatio;
var distances = [];
for(i = 0; i < 4; i++) {
var mx = x - points[i].x;
var my = y - points[i].y;
distances[i] = Math.sqrt(mx * mx + my * my);
}
minI = 0;
minD = distances[0];
for(i = 1; i < 4; i++)
{
if(minD > distances[i])
{
minD = distances[i];
minI = i;
}
}
if(minD <= radiusDetection)
return minI;
return -1;
}
function getTouchPosition(e)
{
var target = null;
var mouse = null;
if(e.changedTouches != undefined)
{
var touches = e.changedTouches;
mouse = touches[0];
target = touches[0].target;
}
else if(e.originalTarget != undefined)
{
mouse = e;
target = e.originalTarget;
}
var coordX = 0;
var coordY = 0;
if(mouse.layerX != undefined)
{
coordX = mouse.layerX;
coordY = mouse.layerY;
}
else
{
coordX = mouse.pageX;
coordY = mouse.pageY;
}
var x = coordX - target.offsetLeft;
var y = coordY - target.offsetTop;
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x >= canvasWidth) x = canvasWidth - 1;
if(y >= canvasHeight) y = canvasHeight - 1;
return {'x':x, 'y':y};
}
function mouseDown(e)
{
var position = getTouchPosition(e);
idPoint = nearPoint(points, position.x, position.y);
if(idPoint == -1)
{
if(position.x < marginImage * 3 && position.y < marginImage * 3)
{
computeMat();
}
}
}
function mouseUp(e)
{
if(idPoint != -1)
{
idPoint = -1;
draw(points);
}
}
function mouseMove(e)
{
if(idPoint != -1)
{
var position = getTouchPosition(e);
points[idPoint].x = position.x;
points[idPoint].y = position.y;
draw(points);
}
}
function cancelDefault(e)
{
e.preventDefault();
}
function matStep12(pts)
{
var matP = [
[pts[0].x, pts[1].x, pts[2].x],
[pts[0].y, pts[1].y, pts[2].y],
[1, 1, 1]
];
var vecP = [[pts[3].x], [pts[3].y], [1]];
var matPi = math.inv(matP);
var vecPi = math.multiply(matPi, vecP);
var result = [
[pts[0].x * vecPi[0][0], pts[1].x * vecPi[1][0], pts[2].x * vecPi[2][0]],
[pts[0].y * vecPi[0][0], pts[1].y * vecPi[1][0], pts[2].y * vecPi[2][0]],
[vecPi[0][0], vecPi[1][0], vecPi[2][0]]
];
return result;
}
function distance(a, b)
{
var mx = b.x - a.x;
var my = b.y - a.y;
return Math.sqrt(mx * mx + my * my);
}
function computeMat()
{
var pts = getPointRelativePosition();
var widthT = distance(pts[0], pts[3]);
var widthB = distance(pts[1], pts[2]);
var heightL = distance(pts[0], pts[1]);
var heightR = distance(pts[2], pts[3]);
var maxWidth = (widthT > widthB) ? widthT : widthB;
var maxHeight = (heightL > heightR) ? heightL : heightR;
var imgWidth = Math.round(maxWidth);
var imgHeight = Math.round(maxHeight);
var matA = matStep12(pts);
var matB = matStep12([{x:0,y:0}, {x:0,y:maxHeight}, {x:maxWidth,y:maxHeight}, {x:maxWidth,y:0}]);
var matC = math.multiply(matB, math.inv(matA));
var matCi = math.inv(matC);
console.log('width:' + imgWidth + ', height:' + imgHeight);
printMat(matC);
// construct image with transformation matrice
imageData = ctx.createImageData(imgWidth, imgHeight);
var tempCanvas = document.createElement('canvas');
var tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = image.width;
tempCanvas.height = image.height;
tempCtx.drawImage(image, 0, 0, image.width, image.height);
var imageDataSrc = tempCtx.getImageData(0, 0, image.width, image.height);
var mz = [matCi[0][2], matCi[1][2], matCi[2][2]];
for(var y = 0; y < imgHeight; y++)
{
var my = [matCi[0][1] * y, matCi[1][1] * y, matCi[2][1] * y];
var offsetY = y * imgWidth;
for(var x = 0; x < imgWidth; x++)
{
var mx = [matCi[0][0] * x, matCi[1][0] * x, matCi[2][0] * x];
var cx = mx[0] + my[0] + mz[0];
var cy = mx[1] + my[1] + mz[1];
var cz = mx[2] + my[2] + mz[2];
var px = Math.round(cx / cz);
var py = Math.round(cy / cz);
if(px < 0.0 || py < 0.0 || px >= image.width || py >= image.height)
{
imageData.data[pixelIndex] = 0;
imageData.data[pixelIndex + 1] = 255;
imageData.data[pixelIndex + 2] = 0;
imageData.data[pixelIndex + 3] = 255;
}
else
{
var pixelIndex = (offsetY + x) * 4;
var pixelIndexSrc = (py * image.width + px) * 4;
imageData.data[pixelIndex] = imageDataSrc.data[pixelIndexSrc];
imageData.data[pixelIndex + 1] = imageDataSrc.data[pixelIndexSrc + 1];
imageData.data[pixelIndex + 2] = imageDataSrc.data[pixelIndexSrc + 2];
imageData.data[pixelIndex + 3] = 255;
}
}
}
// here to do, image analysis
}
function getPointRelativePosition()
{
var pointOrigin = [];
for(i = 0; i < 4; i++)
{
pointOrigin[i] = {x:(points[i].x - marginImage) * image.width / (canvasWidth - marginImage * 2), y:(points[i].y - marginImage) * image.height / (canvasHeight - marginImage * 2)};
}
return pointOrigin;
}
function getPointPosition()
{
var pointOrigin = [];
for(i = 0; i < 4; i++)
{
pointOrigin[i] = {x:(points[i].x - marginImage) / (canvasWidth - marginImage * 2), y:(points[i].y - marginImage) / (canvasHeight - marginImage * 2)};
}
return pointOrigin;
}
function printPoint(pts)
{
var result = '';
for(var i = 0; i < 4; i++)
{
result += "{x:" + pts[i].x + ", y:" + pts[i].y + "},\n";
}
console.log(result);
}
function printMat(mat)
{
var result = '';
for(var i = 0; i < mat.length; i++)
{
result += "[";
for(var j = 0; j < mat[i].length; j++)
{
result += mat[i][j] + ", ";
}
result += "],\n";
}
console.log(result);
}
function canvasResize()
{
if(canvasWidth != document.body.clientWidth && canvasHeight != document.body.clientHeight)
{
var transformPoint = getPointPosition();
ratio = image.width / image.height;
canvasWidth = document.body.clientWidth;
canvasHeight = canvasWidth / ratio;
if(document.body.clientHeight < canvasHeight)
{
canvasHeight = document.body.clientHeight;
canvasWidth = canvasHeight * ratio;
}
canvasLargeur = canvasWidth;
canvasLongueur = canvasHeight;
if(canvasLargeur < canvasHeight) {
canvasLargeur = canvasHeight;
canvasLongueur = canvasWidth;
}
canvasPixelRatio = canvasLargeur / image.width;
c.setAttribute("width", canvasWidth);
c.setAttribute("height", canvasHeight);
marginImage = Math.round(40 * canvasPixelRatio);
for(i = 0; i < 4; i++)
{
points[i].x = transformPoint[i].x * (canvasWidth - marginImage * 2) + marginImage;
points[i].y = transformPoint[i].y * (canvasHeight - marginImage * 2) + marginImage;
}
draw(points);
}
}
c.addEventListener("mousedown", mouseDown, false);
c.addEventListener("mouseup", mouseUp, false);
c.addEventListener("mousemove", mouseMove, false);
c.addEventListener("touchstart", mouseDown, false);
c.addEventListener("touchend", mouseUp, false);
c.addEventListener("touchmove", mouseMove, false);
document.addEventListener("touchstart", cancelDefault, true);
document.addEventListener("touchend", cancelDefault, true);
document.addEventListener("touchmove", cancelDefault, true);
setInterval(canvasResize, 30);
draw(points);
};
</script>

Setting background image in canvas animation

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

Changing shapes using addEventListener in HTML5

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 ]

ThreeJS test works fine in Chrome, but nothing shows in IE10

I have this little test script which I'll try to include below. It works fine in Chrome but not in IE10. IE10 gives me a nice white screen. I tried putting in the meta-equiv thing to help IE10 get the hint, but that did not change anything (in either browser). Please help.
<!-- language: lang-js -->
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="three.min.js"></script>
<script defer="defer">
// http://www.aerotwist.com/tutorials/getting-started-with-three-js/
var cubes = [];
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// camera
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 5000);
camera.position.z = 800;
// scene
var scene = new THREE.Scene();
// material
var phongMaterial = new THREE.MeshPhongMaterial({ambient: 0x555555,
color: 0x555555,
specular: 0xffffff,
shininess: 50,
side: THREE.FrontSide,
shading: THREE.SmoothShading});
var phongBack = new THREE.MeshPhongMaterial({ambient: 0x555555,
color: 0x995555,
specular: 0xffffff,
shininess: 50,
side: THREE.BackSide,
shading: THREE.SmoothShading});
var materials = [phongMaterial, phongBack];
// cube
//var cube = new THREE.Mesh(new THREE.CubeGeometry(200, 200, 200), material);
var geom = new THREE.Geometry();
var a = 100;
var b = 100;
var c = 300;
var geom = new THREE.Geometry();
var halfPi = (Math.PI / 2.0);
var u = -halfPi;
var uInc = Math.PI / 200.0;
var v = - Math.PI;
var vInc = uInc * 2.0;
var vertexNdx = 0;
var vs = [];
var on = true;
while (u < halfPi) {
var oneLine = [];
vs.push(oneLine);
while (v < Math.PI) {
var x = a * Math.cos(u) * Math.cos(v);
var y = b * Math.cos(u) * Math.sin(v);
var z = c * Math.sin(v) * Math.sin(u);
x += Math.random();
y += Math.random();
z += Math.random();
var v1 = new THREE.Vector3(x, y, z);
geom.vertices.push(v1);
oneLine.push(vertexNdx++);
if (on)
{
if (vs.length > 1 && oneLine.length > 1)
{
var uNdx = vs.length - 1;
var vNdx = oneLine.length - 1;
geom.faces.push(new THREE.Face3(vs[uNdx - 1][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx]));
}
//on = false;
}
else
{
on = true;
}
v += vInc;
}
v = -Math.PI;
u += uInc;
}
var oneLine = vs[0];
var uNdx = vs.length - 1;
for (var vNdx = 1; vNdx < oneLine.length; vNdx++)
{
geom.faces.push(new THREE.Face3(vs[0][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx]));
}
geom.computeFaceNormals();
var cube = new THREE.SceneUtils.createMultiMaterialObject(geom, materials);
cube.overdraw = true;
cube.rotation.x = Math.PI * 0.1;
scene.add(cube);
cubes.push(cube);
var geom = new THREE.Geometry();
var a = 100;
var b = 100;
var c = 300;
var geom = new THREE.Geometry();
var halfPi = (Math.PI / 2.0);
var u = -halfPi;
var uInc = Math.PI / 200.0;
var v = - Math.PI;
var vInc = uInc * 2.0;
var vertexNdx = 0;
var vs = [];
var on = true;
while (u < halfPi) {
var oneLine = [];
vs.push(oneLine);
var xRand = Math.random();
while (v < Math.PI) {
var x = a * Math.cos(u) * Math.cos(v);
var y = b * Math.cos(u) * Math.sin(v);
var z = c * Math.sin(v) * Math.sin(u);
x += xRand;
y += Math.random();
z += Math.random();
var v1 = new THREE.Vector3(x, y, z);
geom.vertices.push(v1);
oneLine.push(vertexNdx++);
if (on)
{
if (vs.length > 1 && oneLine.length > 1)
{
var uNdx = vs.length - 1;
var vNdx = oneLine.length - 1;
geom.faces.push(new THREE.Face3(vs[uNdx - 1][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx]));
}
//on = false;
}
else
{
on = true;
}
v += vInc;
}
v = -Math.PI;
u += uInc;
}
var oneLine = vs[0];
var uNdx = vs.length - 1;
for (var vNdx = 1; vNdx < oneLine.length; vNdx++)
{
geom.faces.push(new THREE.Face3(vs[0][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx]));
}
geom.applyMatrix(new THREE.Matrix4().translate(new THREE.Vector3(200, 0, 0)));
geom.computeFaceNormals();
var cube = new THREE.SceneUtils.createMultiMaterialObject(geom, materials);
cube.overdraw = true;
cube.rotation.x = Math.PI * 0.1;
scene.add(cube);
cubes.push(cube);
var geom = new THREE.Geometry();
var a = 100;
var b = 100;
var c = 300;
var geom = new THREE.Geometry();
var halfPi = (Math.PI / 2.0);
var u = -halfPi;
var uInc = Math.PI / 200.0;
var v = - Math.PI;
var vInc = uInc * 2.0;
var vertexNdx = 0;
var vs = [];
var on = true;
while (u < halfPi) {
var oneLine = [];
vs.push(oneLine);
var yRand = Math.random();
while (v < Math.PI) {
var x = a * Math.cos(u) * Math.cos(v);
var y = b * Math.cos(u) * Math.sin(v);
var z = c * Math.sin(v) * Math.sin(u);
x += Math.random();
y += yRand;
z += Math.random();
var v1 = new THREE.Vector3(x, y, z);
geom.vertices.push(v1);
oneLine.push(vertexNdx++);
if (on)
{
if (vs.length > 1 && oneLine.length > 1)
{
var uNdx = vs.length - 1;
var vNdx = oneLine.length - 1;
geom.faces.push(new THREE.Face3(vs[uNdx - 1][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[uNdx - 1][vNdx],
vs[uNdx][vNdx]));
}
//on = false;
}
else
{
on = true;
}
v += vInc;
}
v = -Math.PI;
u += uInc;
}
var oneLine = vs[0];
var uNdx = vs.length - 1;
for (var vNdx = 1; vNdx < oneLine.length; vNdx++)
{
geom.faces.push(new THREE.Face3(vs[0][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx - 1]));
geom.faces.push(new THREE.Face3(vs[uNdx][vNdx - 1],
vs[0][vNdx],
vs[uNdx][vNdx]));
}
geom.applyMatrix(new THREE.Matrix4().translate(new THREE.Vector3(-200, 0, 0)));
geom.computeFaceNormals();
var cube = new THREE.SceneUtils.createMultiMaterialObject(geom, materials);
cube.overdraw = true;
cube.rotation.x = Math.PI * 0.1;
scene.add(cube);
cubes.push(cube);
// add subtle ambient lighting
var ambientLight = new THREE.AmbientLight(0xdddddd);
scene.add(ambientLight);
// directional lighting
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
var screenW = window.innerWidth;
var screenH = window.innerHeight;
var spdx = 0, spdy = 0, mouseX = 0, mouseY = 0, mouseDown = false;
document.addEventListener('mousemove', function(event) {
mouseX = event.clientX;
mouseY = event.clientY;
}, false);
document.body.addEventListener('mousedown', function(event) {
mouseDown = true;
}, false);
document.body.addEventListener('mouseup', function(event) {
mouseDown = false;
}, false);
function animate() {
spdy = (screenH / 2 - mouseY) / 40;
spdx = (screenW / 2 - mouseX) / 40;
if (mouseDown) {
for (var loop = 0; loop < cubes.length; loop++) {
var cube = cubes[loop];
cube.rotation.x = spdy;
cube.rotation.y = spdx;
}
}
renderer.render(scene, camera);
requestAnimationFrame(function(){
animate();
});
};
// start animation
animate();
</script>
</body>
</html>
And feel free to steal my little play test code if you like it. If you make something that looks cool, let me see it! I'm looking for organic-looking shapes that are made mathematically.
IE10 does not support WebGL. I think your code will work if you just switch from WebGLRenderer to CanvasRenderer, but the lighting won't be as accurate.

Resources