1

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:

enter image description here

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>
VVK
  • 435
  • 2
  • 27
  • How many potential textures are there? I ask because there are [limits to the number of textures you can send to the shader per draw call](https://stackoverflow.com/questions/41020683/max-number-of-textures-in-webgl), and because you're using a single plane, you're only making one call. If you fall within the limits, you could send an array of textures to the shader, and devise a scheme to inform which vertex should use which texture (like a psuedo UV). – TheJim01 Jun 17 '20 at 21:07
  • Something ~60 textures. According to https://webglreport.com/, max combined texture units 80, so I'm under this limit. – VVK Jun 17 '20 at 21:20
  • You need to use the textures in the fragment shader here — your limit is likely to be lower, maybe 16? – Don McCurdy Jun 17 '20 at 22:59
  • If you're talking about Max Texture Image Units, yes, it's 16. – VVK Jun 17 '20 at 23:01
  • If it is not possible to use apr. 60 textures for one geometry, when what about cutting process into several draw calls and binding them together? – VVK Jun 18 '20 at 01:40
  • You could split your geometry into draw groups. Each group is a separate draw call, and can be assigned its own material. Each material can have a different subset of your list of textures. – TheJim01 Jun 18 '20 at 14:45
  • TheJim01, yes, that's could be a solution. Do you any draw call groups example? – VVK Jun 18 '20 at 16:42
  • Pretty sure that it has to be done via https://threejs.org/docs/#api/en/core/BufferGeometry.groups – VVK Jun 18 '20 at 16:49
  • Ok, I have made a simple sketch with addGroup() method. However, does it have any limit? Can I use it for 60 times (60 materials)? – VVK Jun 18 '20 at 18:01

1 Answers1

0

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>
VVK
  • 435
  • 2
  • 27