没有Three.js的Javascript 3d Terrain [英] Javascript 3d Terrain Without Three.js

查看:146
本文介绍了没有Three.js的Javascript 3d Terrain的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经四处寻找但是我找不到任何类似于我正在尝试做的事情,但是我没有使用Three.js(我不能使用Three.js因为我的电脑太旧而无法支持WebGL的)。以下是我到目前为止所做的事情:

I have searched around but I can't find anything like what I'm trying to do that doesn't use Three.js in some way (I can't use Three.js because my computer is too old to support Webgl). Here's what I've got so far:

HTML:

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript" src="terrain.js"></script>
    <title>Terrain</title>
</head>
<body>
<canvas id="canvas" height="400" width="400"></canvas>
</body>
</html>

Javascript:

Javascript:

var canvas, ctx, row1 = [], row2 = [], intensity = 15, width = 20, height = 20, centery = 200, centerx = 200, minus, delta = 1.6, nu = .02;

window.onload = function() {
    canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d');
    ctx.lineStyle = '#000'
    for (var i = 0; i < height; i++) {
        row2 = [];
        minus = 200
        for (var j = 0; j < width; j++) {
            row2[j] = {
                x: centerx - (minus * (delta * (nu * i))),
                y: Math.floor(Math.random() * intensity) + (height * i)
            }
            minus -= height;
        }
        ctx.beginPath();
        ctx.moveTo(row2[0].x,row2[0].y)
        for (var k = 1; k < row2.length; k++) {
            ctx.lineTo(row2[k].x,row2[k].y)
            if (k == row2.length) {ctx.clostPath()}
        }
        ctx.stroke();
        if (row1[0] && row2[0]) {
            for (var l = 0; l < row2.length; l++) {
                ctx.beginPath();
                ctx.moveTo(row2[l].x,row2[l].y)
                ctx.lineTo(row1[l].x,row1[l].y)
                ctx.closePath();
                ctx.stroke();
            }
        }
        row1 = row2;
    }
}

目前,结果看起来像一棵圣诞树,但我希望它看起来更像真实的3D线框地形。

Currently, the result looks like a Christmas tree but I want it to look more like actual 3d wireframe terrain.

推荐答案

3D线框基础知识



3D可以在任何可以移动像素的系统上完成。不考虑专用硬件Javascript可以在简单的3d之后做好。

3D wire frame basics

3D can be done on any systems that can move pixels. Thought not by dedicated hardware Javascript can do alright if you are after simple 3d.

这个答案显示了如何创建网格,旋转和移动网格,创建相机并移动它,并使用简单的moveTo和lineTo调用将整个项目投影到2D画布上。

This answers shows how to create a mesh, rotate and move it, create a camera and move it, and project the whole lot onto the 2D canvas using simple moveTo, and lineTo calls.

这个答案是一个真正的紧急工作,所以为错别字道歉(如果有的话)凌乱的代码。将在几天内(如果时间允许)清理它。如有任何问题请在评论中提出。

This answer is a real rush job so apologies for the typos (if any) and messy code. Will clean it up in the come few days (if time permits). Any questions please do ask in the comments.

更新
我有一段时间没有做任何基本的3D所以有一点点我在代码中添加了更多注释,并添加了一些额外的功能。

Update I have not done any basic 3D for some time so having a little fun I have added to the answer with more comments in the code and added some extra functionality.


  • vec3 现在有规范化,点,交叉功能。

  • mat 现在有lookat功能,如果有更多准备就绪需要。

  • mesh 现在维护自己的世界矩阵

  • 添加了框,并且创建框和线网格

  • 创建第二个矢量类型vec3S(S表示简单),它只是坐标没有功能

  • 演示现在显示如何添加更多对象,将它们放置在场景中,使用外观变换

  • vec3 now has normalise, dot, cross functions.
  • mat now has lookat function and is ready for much more if needed.
  • mesh now maintains its own world matrix
  • Added box, and line that create box and line meshs
  • Created a second vector type vec3S (S for simple) that is just coordinates no functionality
  • Demo now shows how to add more objects, position them in the scene, use a lookat transform

以下代码是3D的基础知识。它有一个网格对象,用于通过线连接的3D点(顶点)创建对象。

The code below is the basics of 3D. It has a mesh object to create objects out of 3D points (vertices) connected via lines.

旋转,移动和缩放模型的简单转换,以便将其放置在场景中。

Simple transformation for rotating, moving and scaling a model so it can be placed in the scene.

一个非常非常基本的相机,只能向前看,向上,向下,向左,向右,向内和向外移动。并且焦距可以更改。

A very very basic camera that can only look forward, move up,down, left,right, in and out. And the focal length can be changed.

仅适用于没有深度排序的行。

Only for lines as there is no depth sorting.

演示不会剪切到相机前面的平面,而只是忽略了相机背后有任何部分的线条;

The demo does not clip to the camera front plane, but rather just ignores lines that have any part behind the camera;

你必须从评论中解决剩下的问题,3D是一个很大的主题,任何一个功能都值得一个问题/答案。

You will have to work out the rest from the comments, 3D is a big subject and any one of the features is worth a question / answer all its own.

哦,3D中的坐标起源于画布的中心。 Y正向下,x正向右,z正向进入屏幕。投影是基本的,所以当你将透视图设置为400而不是距离相机400个单位的对象将具有与像素大小一对一的匹配。

Oh and coordinates in 3D are origin in center of canvas. Y positive down, x positive right, and z positive into the screen. projection is basic so when you have perspective set to 400 than a object at 400 units out from camera will have a one to one match with pixel size.

var ctx = canvas.getContext("2d");
// some usage of vecs does not need the added functionality
// and will use the basic version
const vec3Basic = { x : 0, y : 0, z: 0};
const vec3Def = {
    // Sets the vector scalars
    // Has two signatures
    // setVal(x,y,z) sets vector to {x,y,z}
    // setVal(vec) set this vector to vec
    setVal(x,y = x.y,z = x.z + (x = x.x) * 0){
        this.x = x;
        this.y = y;
        this.z = z;
    },
    // subtract v from this vector
    // Has two signatures
    // setVal(v) subtract v from this returning a new vec3
    // setVal(v,vec) subtract v from this returning result in retVec
    sub(v,retVec = vec3()){
        retVec.x = this.x - v.x;
        retVec.y = this.y - v.y;
        retVec.z = this.z - v.z;
        return retVec;
    },
    // Cross product of two vectors this and v.
    // Cross product can be thought of as get the vector
    // that is perpendicular to the plane described by the two vector we are crossing
    // Has two signatures
    // cross(vec); // returns a new vec3 as the cross product of this and vec
    // cross(vec, retVec); // set retVec as the cross product
    cross (v, retVec = vec3()){
       retVec.x = this.y * v.z - this.z * v.y;
       retVec.y = this.z * v.x - this.x * v.z;
       retVec.z = this.x * v.y - this.y * v.x;
       return retVec;
    },
    // Dot product
    // Dot product of two vectors if both normalized can be thought of as finding the cos of the angle
    // between two vectors. If not normalised the dot product will give you < 0 if v points away from
    // the plane that this vector is perpendicular to, if > 0 the v points in the same direction as the
    // plane perpendicular to this vector. if 0 then v is at 90 degs to the plane this is perpendicular to
    // Using vector dot on its self is the same as getting the length squared
    // dot(vec3); // returns a number as a float
    dot (v){ return this.x * v.x + this.y * v.y + this.z * this.z },
    // normalize normalizes a vector. A normalized vector has length equale to 1 unit
    // Has two signitures
    // normalise(); normalises this vector returning this
    // normalize(retVec); normalises this vector but puts the normalised vector in retVec returning 
    //                    returning retVec. Thiis is unchanged.
    normalize(retVec = this){
        // could have used len = this.dot(this) but for speed all functions will do calcs internaly
        const len = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
        // it is assumed that all vector are valid (have length) so no test is made to avoid
        // the divide by zero that will happen for invalid vectors.
        retVec.x = this.x / len;
        retVec.y = this.y / len;
        retVec.z = this.z / len;
    }
}
// Created as a singleton to close over working constants
const matDef = (()=>{
    // to seed up vector math the following closed over vectors are used
    // rather than create and dispose of vectors for every operation needing them
    // Currently not used
    const V1 = vec3();
    return {
        // The matrix is just 3 pointers one for each axis
        // They represent the direction and scale in 3D of each axis 
        // when you transform a point x,y,z you move x along the x axis, 
        // then y along y and z along the z axis        
        xAxis : null,
        yAxis : null,
        zAxis : null,
        // this is a position x,y,z and represents where in 3D space an objects
        // center coordinate (0,0,0) will be. It is simply added to a point
        // after it has been moved along the 3 axis.
        pos : null,
        // This function does most of the 3D work in most 3D environments.
        // It rotates, scales, translates, and a whole lot more.
        // It is a cut down of the full 4 by 4 3D matrix you will find in 
        // Libraries like three.js 
        transformVec3(vec,retVec = {}){
            retVec.x = vec.x * this.xAxis.x + vec.y * this.yAxis.x + vec.z * this.zAxis.x + this.pos.x;
            retVec.y = vec.x * this.xAxis.y + vec.y * this.yAxis.y + vec.z * this.zAxis.y + this.pos.y;
            retVec.z = vec.x * this.xAxis.z + vec.y * this.yAxis.z + vec.z * this.zAxis.z + this.pos.z;
            return retVec;
        },
        // resets the matrix
        identity(){  // default matrix
            this.xAxis.setVal(1,0,0); // x 1 unit long in the x direction
            this.yAxis.setVal(0,1,0); // y 1 unit long in the y direction
            this.zAxis.setVal(0,0,1); // z 1 unit long in the z direction
            this.pos.setVal(0,0,0);   // and position at the origin.
            
        },
        init(){  // need to call this before using due to the way I create these
                 // objects.
            this.xAxis = vec3(1,0,0);
            this.yAxis = vec3(0,1,0);
            this.zAxis = vec3(0,0,1);
            this.pos = vec3(0,0,0);
            return this; // must have this line for the constructor function to return 
        },
        setRotateY(amount){
            var x = Math.cos(amount);
            var y = Math.sin(amount);
            this.xAxis.x = x;
            this.xAxis.y = 0;
            this.xAxis.z = y;
            this.zAxis.x = -y;
            this.zAxis.y = 0;
            this.zAxis.z = x;
        },
        // creates a look at transform from the current position
        // point is a vec3.
        // No check is made to see if look at is at pos which will invalidate this matrix
        // Note scale is lost in this operation.
        lookAt(point){
            // zAxis along vector from pos to point
            this.pos.sub(point,this.zAxis).normalize();
            // use y as vertical reference
            this.yAxis.x = 0;
            this.yAxis.y = 1; 
            this.yAxis.z = 0;
            // get x axis perpendicular to the plane described by z and y axis
            // need to normalise as z and y axis may not be at 90 deg
            this.yAxis.cross(this.zAxis,this.xAxis).normalize();
            // Get the y axis that is perpendicular to z and x axis
            // Normalise is not really needed but rounding errors can be problematic
            // so the normalise just fixes some of the rounding errors.
            this.zAxis.cross(this.xAxis,this.yAxis).normalize();
        },      
            
    }
})();
// Mesh object has buffers for the 
// model as verts
// transformed mesh as tVerts
// projected 2D verts as dVerts (d for display)
// An a array of lines. Each line has two indexes that point to the 
// vert that define their ends.
// Buffers are all preallocated to stop GC slowing everything down.
const meshDef = {
    addVert(vec){
        this.verts.push(vec);
        // vec3(vec) in next line makes a copy of the vec. This is important
        // as using the same vert in the two buffers will result in strange happenings.        
        this.tVerts.push(vec3S(vec)); // transformed verts pre allocated so GC does not bite
        this.dVerts.push({x:0,y:0}); // preallocated memory for displaying 2d projection
                                     // when x and y are zero this means that it is not visible
        return this.verts.length - 1;
    },
    addLine(index1,index2){
        this.lines.push(index1,index2);
    },
    transform(matrix = this.matrix){
        for(var i = 0; i < this.verts.length; i++){
            matrix.transformVec3(this.verts[i],this.tVerts[i]);
        }
    },    
    eachVert(callback){
        for(var i = 0; i < this.verts.length; i++){
            callback(this.tVerts[i],i);
        }
    },
    eachLine(callback){
        for(var i = 0; i < this.lines.length; i+= 2){
            var ind1 = this.lines[i];
            var v1 = this.dVerts[ind1]; // get the start 
            if(v1.x !== 0 && v1.y !== 0){ // is valid
                var ind2 = this.lines[i+ 1]; // get end of line
                var v2 = this.dVerts[ind2]; 
                if(v2.x !== 0 && v2.y !== 0){ // is valid
                    callback(v1,v2);
                }
            }
        }
    },
    init(){ // need to call this befor using
        this.verts = [];
        this.lines = [];
        this.dVerts = []; 
        this.tVerts = [];
        this.matrix = mat();
        return this; // must have this line for the construtor function to return 
    }    
}
const cameraDef = {
    projectMesh(mesh){ // create a 2D mesh
        mesh.eachVert((vert,i)=>{
            var z = (vert.z + this.position.z);
            if(z < 0){  // is behind the camera then ignor it
                mesh.dVerts[i].x = mesh.dVerts[i].y = 0;
            }else{
                var s =  this.perspective / z;
                mesh.dVerts[i].x = (vert.x + this.position.x) * s;
                mesh.dVerts[i].y = (vert.y + this.position.y) * s;
            }
        })  
    },
    drawMesh(mesh){  // renders the 2D mesh
        ctx.beginPath();
        mesh.eachLine((v1,v2)=>{
            ctx.moveTo(v1.x,v1.y);
            ctx.lineTo(v2.x,v2.y);
        })
        ctx.stroke();
    }
}
// vec3S creates a basic (simple) vector
// 3 signatures
//vec3S(); // return vec 1,0,0
//vec3S(vec); // returns copy of vec
//vec3S(x,y,z); // returns {x,y,z}
function vec3S(x = {x:1,y:0,z:0},y = x.y ,z = x.z + (x = x.x) * 0){ // a 3d point
    return Object.assign({},vec3Basic,{x, y, z});
}
// vec3S creates a basic (simple) vector
// 3 signatures
//vec3S(); // return vec 1,0,0
//vec3S(vec); // returns copy of vec
//vec3S(x,y,z); // returns {x,y,z}
function vec3(x = {x:1,y:0,z:0},y = x.y ,z = x.z + (x = x.x) * 0){ // a 3d point
    return Object.assign({},vec3Def,{x,y,z});
}
function mat(){ // matrix used to rotate scale and move a 3d point
    return Object.assign({},matDef).init();
}
function mesh(){  // this is for storing objects as points in 3d and lines conecting points
    return Object.assign({},meshDef).init();
}
function camera(perspective,position){  // this is for displaying 3D
    return Object.assign({},cameraDef,{perspective,position});
}
// grid is the number of grids x,z and size is the overal size for x
function createLandMesh(gridx,gridz,size,maxHeight){ 
    var m = mesh(); // create a mesh
    var hs = size/2 ; 
    var step = size / gridx;
    for(var z = 0; z < gridz; z ++){
        for(var x = 0; x < gridx; x ++){
            // create a vertex. Y is random 
            m.addVert(vec3S(x * step - hs, (Math.random() * maxHeight), z * step-hs)); // create a vert
        }
    }
    for(var z = 0; z < gridz-1; z ++){
        for(var x = 0; x < gridx-1; x ++){
            if(x < gridx -1){ // dont go past end
                m.addLine(x + z * gridx,x + 1 + z * gridx); // add line across
            }
            if(z < gridz - 1){  // dont go past end
                m.addLine(x + z * (gridx-1),x + 1 + (z + 1) * (gridx-1));
            }
        }
    }
    return m;
}
function createBoxMesh(size){
    var s = size / 2;
    var m = mesh(); // create a mesh
    // add bottom
    m.addVert(vec3S(-s,-s,-s));
    m.addVert(vec3S( s,-s,-s));
    m.addVert(vec3S( s, s,-s));
    m.addVert(vec3S(-s, s,-s));
    // add top verts
    m.addVert(vec3S(-s,-s, s));
    m.addVert(vec3S( s,-s, s));
    m.addVert(vec3S( s, s, s));
    m.addVert(vec3S(-s, s, s));
    // add lines
    /// bottom lines
    m.addLine(0,1);
    m.addLine(1,2);
    m.addLine(2,3);
    m.addLine(3,0);
    /// top lines
    m.addLine(4,5);
    m.addLine(5,6);
    m.addLine(6,7);
    m.addLine(7,4);
    // side lines
    m.addLine(0,4);
    m.addLine(1,5);
    m.addLine(2,6);
    m.addLine(3,7);
    return m;
    
}
function createLineMesh(v1 = vec3S(),v2 = vec3S()){
    const m = mesh();
    m.addVert(v1);
    m.addVert(v2);
    m.addLine(0,1);
    return m;
}
//Create a land mesh grid 20 by 20 and 400 units by 400 units in size
var land = createLandMesh(20,20,400,20);  // create a land mesh
var box = createBoxMesh(50);
var box1 = createBoxMesh(25);
var line = createLineMesh(); // line conecting boxes
line.tVerts[0] = box.matrix.pos; // set the line transformed tVect[0] to box matrix.pos
line.tVerts[1] = box1.matrix.pos; // set the line transformed tVect[0] to box1 matrix.pos
var cam = camera(200,vec3(0,0,0)); // create a projection with focal len 200 and at 0,0,0
box.matrix.pos.setVal(0,-100,400);
box1.matrix.pos.setVal(0,-100,400);
land.matrix.pos.setVal(0,100,300); // move down 100, move away 300


var w = canvas.width;
var h = canvas.height;
var cw = w / 2;  // center of canvas
var ch = h / 2;



function update(timer){
    // next section just maintains canvas size and resets state and clears display 
    if (canvas.width !== innerWidth || canvas.height !== innerHeight) {
        cw = (w = canvas.width = innerWidth) /2;
        ch = (h = canvas.height = innerHeight) /2;
    }
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    ctx.fillStyle = "black";
    ctx.fillRect(0,0,canvas.width,canvas.height);
    // end of standard canvas maintenance 
    
    // render from center of canvas by setting canvas origin to center
    ctx.setTransform(1,0,0,1,canvas.width / 2,canvas.height / 2)


    land.matrix.setRotateY(timer/1000); // set matrix to rotation position
    land.transform();
    // move the blue box
    var t = timer/1000;
    box1.matrix.pos.setVal(Math.sin(t / 2.1) * 100,Math.sin( t / 3.2) * 100, Math.sin(t /5.3) * 90+300);
    // Make the cyan box look at the blue box
    box.matrix.lookAt(box1.matrix.pos);
    // Transform boxes from local to world space
    box1.transform();
    box.transform();

    
    // set camera x,y pos to mouse pos;
    cam.position.x = mouse.x - cw;
    cam.position.y = mouse.y - ch;
    
    // move in and out
    if (mouse.buttonRaw === 1) { cam.position.z -= 1 }
    if (mouse.buttonRaw === 4) {cam.position.z += 1 }
    
    // Converts mesh transformed verts to 2D screen coordinates
    cam.projectMesh(land);
    cam.projectMesh(box);
    cam.projectMesh(box1);
    cam.projectMesh(line);
    
    // Draw each mesh in turn
    ctx.strokeStyle = "#0F0";
    cam.drawMesh(land);
    ctx.strokeStyle = "#0FF";
    cam.drawMesh(box);
    ctx.strokeStyle = "#00F";
    cam.drawMesh(box1);
    ctx.strokeStyle = "#F00";
    cam.drawMesh(line);

    
    ctx.setTransform(1,0,0,1,cw,ch / 4);
    ctx.font = "20px arial";
    ctx.textAlign = "center";
    ctx.fillStyle = "yellow";
    ctx.fillText("Move mouse to move camera. Left right mouse move in out",0,0)

    requestAnimationFrame(update);
}
requestAnimationFrame(update);


// A mouse handler from old lib of mine just to give some interaction
// not needed for the 3d
var mouse = (function () {
    var m; // alias for mouse
    var mouse = {
        x : 0, y : 0, // mouse position
        buttonRaw : 0,                      
        buttonOnMasks : [0b1, 0b10, 0b100],  // mouse button on masks
        buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
        bounds : null,
        event(e) {
            m.bounds = m.element.getBoundingClientRect();
            m.x = e.pageX - m.bounds.left - scrollX;
            m.y = e.pageY - m.bounds.top - scrollY;
            if (e.type === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
            else if (e.type === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
            e.preventDefault();
        },
        start(element) {
            m.element = element === undefined ? document : element;
            "mousemove,mousedown,mouseup".split(",").forEach(name =>  document.addEventListener(name, mouse.event) );
            document.addEventListener("contextmenu", (e) => { e.preventDefault() }, false);
            return mouse;
        },
    }
    m = mouse;
    return mouse;
})().start(canvas);

canvas { position:absolute; top : 0px; left : 0px;}

<canvas id="canvas"></canvas>

这篇关于没有Three.js的Javascript 3d Terrain的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆