2018年10月29日 星期一

理解 html 上的 canvas 直接透過 webgl API 繪 3D 圖

<html><head><meta charset="utf-8"/></head><body><script>
// webGL z-axis 稱為 depth, 以螢幕當基準點 0, 螢幕後面是負值, 前面是正, 螢幕左右是 x 軸, 上下是 y 軸, 螢幕中心是 (0, 0, 0)
// webGL 用的座標稱為卷座標 (clip coordinate), 座標值 x, y, z 都介於 -1 與 1 之間的浮點數
// 物件以原點(0,0,0)當參考點, 稱為世界座標 (world coordinate)
// Vertices 是 3D 物件的頂點座標
// 世界座標可以經由矩陣轉換成 webGL 的卷座標: clip_coordinate = Pmatrix * Vmatix * Mmatrix * world_coordinate
//   Mmatrix 是物體移動的矩陣,讓物件可以在世界中自由變換位置甚至旋轉及放大(Translate, scale, rotate)
//   Vmatrix 可以當成觀景矩陣,如果將相機觀景點當成原點,以相對觀點來看,等同將世界座標的零點座標(0,0,0)逆轉換(invere transform)也就是移動世界
//   Pmatrix 是投射矩陣, 將世界座標最後投射到 GL 用的 -1 到 1 之間的繪圖座標點, 同時可以限定在 canvas 的畫布之內
// vertex and index buffer 用於描述模型的幾何結構, mesh 稱為網目(面), 一般會用三角形的碎形面(fragment)建構物體的表面
// ESSL : Embedded System Shader Language 內嵌系統渲染程式語言
// GLSL : OpenGL ES Shader Language 必須包括點及碎面的渲染程式(Vertex and Fragment shader program)
// Shader Language 用來定義 vertices, transformations, materials, lights, camera 的交互作用下產生的一幅影像
// vertex Shader 點渲染作用於 3D 座標點及線上 , 碎面渲染(Fragment Shader)作用於充實物件表面
// drawElements(),drawArrays() 單元或陣列繪畫
// 參考資料:
//      1.  https://webglfundamentals.org/webgl/lessons/webgl-drawing-multiple-things.html
//      2.  https://www.tutorialspoint.com/webgl/
//      3.  https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.1.10.pdf
// Init time:
//      1. create shader program, lookup attribute and uniform location
//      2. create buufers and upload data
//      3. create textures and upload data
// Rander time:
//      1. useProgram
//      2. setup attribute and uniform
//      3. drawArrays or drawElements
// GLSL 所有變數使用前必須先宣告資料型態, 基本資料型態:
// void, float, bool, int, vect2, vect3, vect4, bvect2, bvect3, bvect4, ivect2, ivect3, ivect4, mat2, mat3, mat4, sampler1D, sampler2D, sampler3D, samplerCube, sampler1Dshadow, sampler2Dshadow
// void 是忽視的資料, float 是浮點數資料, bool 是布林數資料, int 是整數資料  (V FBI)
// float 可以使用 E 或 e 在科學記號表示式中作為 10 的指數(10^), 句點分離整數與小數
// bool 僅可以用 true, false 賦值
// int 整數前置可以用 0 代表 8 進制整數, 用 0x 或 0X 代表 16 進制整數, 否則一般就是 10 進制整數
// vect 是浮點數的向量, bvect 是布林數的向量, ivect 是整數值的向量
// 方陣是以行為主(column-major order),也就是向量陣列的方陣,  mat2 是 2x2 方陣, mat3 是 3x3 方陣, mat4 是 4x4 方陣
// 資料整合可以用 struct newType { } 群聚起來成為新的資料型態(newType), 往後資料宣告時就可加以利用
// 資料型態修飾詞(Type Qualifiers) 可以用 const, attribute, uniform, varying, in, out, inout 關鍵字來修飾變數
// 在函式外部宣告的變數, 它是整體區域(Global scope)變數, 僅可以加上 const, attribute, uniform, varying 其中之一的修飾詞
// 函式輸出入參數只能用 in, out, inout, const 其中之一的修飾詞
// const 顧名思義就是不能修改的常數
// attribute 修飾詞用於將變數當成參數傳遞(像是將 vertices 從 OpenGL 一一傳給 vertext shader)時來使用, 對 vertex shader 而言它是常數
// attirbute 只可以用來修飾 float, vect, mat 等浮點數, 它不能用於修飾陣列(array)或是結構(struct)的資料型態.
// uniform 作用於整體區域的宣告, 基本上它是唯讀的, 可以直接透過 API 或是間接經由 OpenGL 初始化.
// varying 提供 vertex shadow 與 fragement shadow 及固定函式之間作為資料交換介面使用
const π = Math.PI;
let tan = Math.tan;
let cos = Math.cos;
let sin = Math.sin;
class matriX4 { // 4x4 方陣
    constructor()   { this.m = [1,0,0,0,  0,1,0,0,  0,0,1,0,  0,0,0,1];  }
    translate(x,y,z){ // bind this with objet I4
        this.m[12] += x;
        this.m[13] += y;
        this.m[14] += z;
        return this;
    }
    scale(sx,sy,sz) { // scale depend on sx,sy,sz)
        this.m[0] *= sx;
        this.m[5] *= sy;
        this.m[10]*= sz;
        return this;
    }
    translatex(s)   { this.m[12] += s; return this;}
    translatey(s)   { this.m[13] += s; return this;}
    translatez(s)   { this.m[14] += s; return this;}
    scalex(s)       { this.m[0]  *= s; return this;}
    scaley(s)       { this.m[5]  *= s; return this;}
    scalez(s)       { this.m[10] *= s; return this;}
    rotatex(θ)      {
        let c = cos(θ);
        let s = sin(θ);
        let m = this.m;
        let mv1 = m[1], mv5 = m[5], mv9 = m[9];
        m[1]  = m[1]*c - m[2]*s;
        m[5]  = m[5]*c - m[6]*s;
        m[9]  = m[9]*c - m[10]*s;
        m[2]  = m[2]*c + mv1*s;
        m[6]  = m[6]*c + mv5*s;
        m[10] = m[10]*c+ mv9*s;
        return this;
    }   
    rotatey(θ)      {
        let c = cos(θ);
        let s = sin(θ);
        let m = this.m;
        let mv0 = m[0], mv4 = m[4], mv8 = m[8];
        m[0]  = c*m[0] + s*m[2];
        m[4]  = c*m[4] + s*m[6];
        m[8]  = c*m[8] + s*m[10];
        m[2]  = c*m[2] - s*mv0;
        m[6]  = c*m[6] - s*mv4;
        m[10] = c*m[10]- s*mv8;
        return this;
    }      
    rotatez(θ)      {
        let c = cos(θ);
        let s = sin(θ);
        let m = this.m;
        let mv0 = m[0], mv4 = m[4], mv8 = m[8];
        m[0] = c*m[0] - s*m[1];
        m[4] = c*m[4] - s*m[5];
        m[8] = c*m[8] - s*m[9];
        m[1] = c*m[1] + s*mv0 ;
        m[5] = c*m[5] + s*mv4 ;
        m[9] = c*m[9] + s*mv8 ;
        return this;
    }
    projector(θ, near, far, width, height) {
        if (near < 1e-4) near = 1e-4;   
        let depth = far - near;
        let factor = tan((π-θ)/2); //  0< θ < π
        this.width = width;
        this.height= height;
        this.m[0]  = factor*height/width;
        this.m[5]  = factor;
        this.m[10] = -(near+far)/depth;// width > height
        this.m[11] = -1;
        this.m[14] = -2*near*far/depth;
        this.m[15] = 0 ;
        return this;
    }
}
class canvas3D{ 
    constructor(width,height,depth) {
        let div    = document.createElement("div");
        let canvas = document.createElement("canvas");   
        this.context = canvas.getContext('webgl'); 
        this.border = 8;
         document.body.style= "background-color: #080808;";
        canvas.style.border = this.border+"px solid";
        document.body.appendChild(div);              
        div.align="center";   
         div.appendChild(canvas);
        canvas.width = width;
        canvas.height= height;
        this.width   = canvas.width;
        this.height  = canvas.height;
        this.depth   = depth;     
        this.shaderGPU= 0;
        this.program = this.context.createProgram();
    }   
    set assemble(str)   {
        this.context.shaderSource(this.shader, str);
        this.context.compileShader(this.shader);
        this.context.attachShader(this.program, this.shader);
        if( ++this.shaderGPU == 2 ) {
            this.context.linkProgram(this.program);
            this.context.useProgram(this.program);
            this.context.enable(this.context.DEPTH_TEST);
            this.context.viewport(0, 0, this.width, this.height);
            console.log("Shader Run: "+this.shaderGPU)
        }
    }
    set vertext(code)   {
        this.shader = this.context.createShader(this.context.VERTEX_SHADER);
        this.assemble = code;
    }
    set fragment(code)  {
        this.shader = this.context.createShader(this.context.FRAGMENT_SHADER);
        this.assemble = code;
    }   
    set scale(name)     { // remember the scalename to be used in projector
        let location = this.context.getUniformLocation(this.program, name);
        this.context.uniform3f(location, this.width, this.height, this.depth);
        this.scalename = name;
    }
    uniform(name)       { // use fat arrow function ()=>{} to bind this with canvas3D object
        let location = this.context.getUniformLocation(this.program, name);
        return { transfer:(m) => { this.context.uniformMatrix4fv(location, false, m); } }
    }
    attribute(name)     { // transfer the attributes one by one
        let location = this.context.getAttribLocation(this.program, name);
        return { transfer:(size) => {
                this.context.vertexAttribPointer(location, size, this.context.FLOAT, false, 0, 0);
                this.context.enableVertexAttribArray(location);
            }
        }
    }
    adddata(array)      {
        let buffer = this.context.createBuffer();
        this.context.bindBuffer(this.context.ARRAY_BUFFER, buffer);
        this.context.bufferData(this.context.ARRAY_BUFFER, new Float32Array(array), this.context.STATIC_DRAW);
        return buffer;
    }
    indexbuffer(array)  {
        this.index = this.context.createBuffer();
        this.context.bindBuffer(this.context.ELEMENT_ARRAY_BUFFER, this.index);
        this.context.bufferData(this.context.ELEMENT_ARRAY_BUFFER, new Uint16Array(array), this.context.STATIC_DRAW);
        this.length = array.length;
        return this;
    }
    pointbuffer(array)  {
        this.buffer = this.adddata(array);
        this.context.bindBuffer(this.context.ARRAY_BUFFER, this.buffer);
        return this;
    }
    colorbuffer(array)  {
        this.color = this.adddata(array);
        this.context.bindBuffer(this.context.ARRAY_BUFFER, this.color);
        return this;
    }  
    get draw()      {
        this.context.bindBuffer(this.context.ELEMENT_ARRAY_BUFFER, this.index);
        this.context.drawElements(this.context.TRIANGLES , this.length , this.context.UNSIGNED_SHORT,0);
    }
    get clear()         { this.context.clear(this.context.COLOR_BUFFER_BIT | this.context.DEPTH_BUFFER_BIT); return this; }       
    projector(θ, near, far) {
        return  new matriX4().projector(θ, near, far, this.width, this.height);
    }   
}
let vertex = [
            -1,-1,-1,
             1,-1,-1,
             1, 1,-1,
            -1, 1,-1,
            -1,-1, 1,
             1,-1, 1,
             1, 1, 1,
            -1, 1, 1,
            -1,-1,-1,
            -1, 1,-1,
            -1, 1, 1,
            -1,-1, 1,
             1,-1,-1,
             1, 1,-1,
             1, 1, 1,
             1,-1, 1,
            -1,-1,-1,
            -1,-1, 1,
             1,-1, 1,
             1,-1,-1,
            -1, 1,-1,
            -1, 1, 1,
             1, 1, 1,
             1, 1,-1,
];
let colors = [
            5,3,7, 5,3,7, 5,3,7, 5,3,7,
            1,1,3, 1,1,3, 1,1,3, 1,1,3,
            0,0,1, 0,0,1, 0,0,1, 0,0,1,
            1,0,0, 1,0,0, 1,0,0, 1,0,0,
            1,1,0, 1,1,0, 1,1,0, 1,1,0,
            0,1,0, 0,1,0, 0,1,0, 0,1,0
]; 
let indices= [
            0,1,2, 0,2,3, 4,5,6, 4,6,7,
            8,9,10, 8,10,11, 12,13,14, 12,14,15,
            16,17,18, 16,18,19, 20,21,22, 20,22,23
];
var my = new canvas3D(500, 500, 500);
my.vertext     = 'attribute vec3 position;attribute vec3 color;'+
                 'uniform mat4 Pmatrix;uniform mat4 Vmatrix;uniform mat4 Mmatrix;'+
                 'varying vec3 vColor;'+
                 'void main(void) { gl_Position  = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);vColor = color;}';
my.fragment    = 'precision mediump float;'+
                 'varying vec3 vColor;'+
                 'void main(void) { gl_FragColor = vec4(vColor, 1.);}';
let abs = (x) => { return x < 0 ? -x:x;}
let z = -10;            // back camera off in z direction to see target
let θ = π/2 ;               // field of view angle  = 90 degree for the camera
let near = 1;               // nearest distance between camera and target in z direction
let far  = near + abs(z);   // farest distance between camera and target in z direction
let Δz = z;                 // camera back off
let Δy = abs(Δz)*tan(θ/2);  // camera pull up
my.indexbuffer(indices);
my.colorbuffer(colors).attribute('color').transfer(3);          // 3 per time
my.pointbuffer(vertex).attribute('position').transfer(3);          // 3 per time
my.uniform("Pmatrix").transfer(my.projector(θ, near, far).m);   // perspective project
my.uniform("Vmatrix").transfer(new matriX4().translate(0,Δy,Δz).rotatex(θ).m);// camera matrix
let mat4GPU = my.uniform("Mmatrix");                            // get GPU mat4 location
let matrix  = new matriX4().rotatex(π/4).rotatey(π/4);          // matrix of the target to use in animatiton
    let Δθ    = π/5;
    let Δs    = 1;
    var animatiton = function() {     
            mat4GPU.transfer(matrix.translatez(Δs).rotatez(Δθ).m);
            my.clear.draw;         
            z += Δs ;
            console.log(abs(z)+":"+abs(Δz))  ; 
            if( abs(z) <= abs(Δz) ) setTimeout(animatiton, 500);
    }
    animatiton();   
</script></body></html>        

2018年10月28日 星期日

VS Code 關閉惱人的提示訊息

在設定欄(File -> Preferences -> Settings ), 修改 Uert Settings 增加:
    "editor.hover.enabled":false,
    "editor.minimap.enabled": false,
    "update.channel": "none" 


然後存檔就可以了.

2018年10月13日 星期六

寫一個簡單在 HTML canvas 裡繪圖的 Javascript 程式

<html><head></head><body><script>
// 存成 draw.html 檔案後, 用支援 ES6 的瀏覽器開啟
class myCanvas{
    constructor(name,maxx,maxy){
        let div     = document.createElement('div');   
        let canvas     = document.createElement('canvas');   
        let ctx     = canvas.getContext("2d");   
        let textnode= document.createTextNode(name);
        document.body.appendChild(textnode);
        document.body.appendChild(div);
        div.appendChild(canvas);                               
        div.align="center";
        canvas.style.border = "1px solid";//外框 1 點
        canvas.width = maxx;     //{0 < x < maxx}
        canvas.height = maxy;    //{-maxy < y < maxy}
        this.context = ctx;        // must init first, will be used later
        this._ispaint = false;    // must init second
        this.limitx = canvas.width;
        this.limity = canvas.height;
        this.yscale = 95/100; // 95%
        this.color = "green";   
        this.dashline = [4,1]; // 4px solid, 1px space
        this.drawline(0,     0, maxx, 0);   
        this.drawline(0, -maxy,    0, maxy);
        this.color = "red";       
        this.dashline = [1,4]; // 1px solid, 4px space
        this.drawline(0, -maxy, maxx, -maxy);
        this.drawline(0 , maxy, maxx,  maxy);           
        this.dashline = [5,0]; // 5px solid
        this.drawtext("0",0,0);
        this.drawtext( "x="+maxx, maxx, 0);
        this.drawtext( "+"+maxy, 0,-maxy);
        this.drawtext( "-"+maxy, 0, maxy);   
    } // object create complete
    // following method need object to be created first   
    set start(ispaint) { // keep track of the paint state
        if( ispaint )    {
            if( ! this.start ) this.context.beginPath();
            this._ispaint = true;
        } else {
            this.move(0,0);// back to the origin   
            if( this.start ) this.context.stroke();
            this._ispaint = false;
        }
    }
    get start()    { return this._ispaint;     }
    set color(c){ this.context.strokeStyle=c;}
    set dashline(x){this.context.setLineDash(x); }
    close()        { this.start = false;          }
    move(x,y)    { this.context.moveTo(x,y);     }
    rescale(sx,sy){ this.context.scale(sx, sy); }
    mirror()    { this.context.scale(1, -1); }   
    set yscale(sy){
        // 先移動原點至中心, y 再縮小比例並鏡射
        this.context.lineWidth = 1; // 線寬 1 點
        this.context.translate(0, this.limity/2); // y center
        this.rescale(1, -sy/2); //
        //ctx.rotate(5*Math.PI/180);// 座標旋轉角度
    }
    drawline(x1,y1,x2,y2){
        if( ! this.start ) this.start = true
        this.context.moveTo(x1,y1);
        this.context.lineTo(x2,y2);
        this.start = false; // auto offline
    }
    lineto(x, y){
        if( ! this.start ) this.start = true
        this.context.lineTo(x,y);
    }
    drawtext(msg, x, y, align){
        this.rescale(1, -1);// text force y mirror back
        let fontpx=24;   
        if( (x + msg.length * fontpx) >= this.limitx )  {
                  this.context.textAlign="right";
        } else  this.context.textAlign="left";
        if( y<=0 ) y = y + fontpx;   
        this.context.font=fontpx + "px Comic Sans MS";
        this.context.fillText(msg, x, y);// 充實字體
        this.rescale(1, -1);// mirror back again
    }
};
let pen    = new myCanvas("Sin",600,300);
pen.color="blue";
for (let x=0; x < pen.limitx; x++) {
    pen.lineto(x, pen.limity * Math.sin(2*Math.PI*x*4/pen.limitx));       
}
pen.close();
</script></body></html>

2018年10月3日 星期三

投資債券利用 Javascript 計算報酬率

假設想購買一檔債券基金, 手續費 2.4%, 計劃投入資金一年, 每個月購買 3000 圓美金的基金(假設一年內的基金市值沒有變化), 一共付了 12 期費用, 債券基金年配息 5.35%, 每月領息, 繳款期間不領息, 直接投入基金, 當繳費期滿一年後, 持續領息至少再花一年, 未來當債券價格回到當初投入價格時才全部贖回, 試計算獲利率, 並換算一下 IRR 的年化報酬率:
// debt.js
function irr(CT) {
    var maxrate = 100/100 // 100%
    var minrate = 0.0
    var k =1000           // prevent infinit loop
    var NPV,IRR
    while (k-- > 0) {
        [NPV, IRR]  = [0.0, (maxrate + minrate) / 2];
        CT.forEach( (c,i) => NPV = NPV + c/((1+IRR)**i) );
        if (Math.abs(NPV) < 1e-6 || (maxrate - minrate) < 1e-6)  break;
        if (NPV > 0)  minrate = IRR    // too small, increase IRR by changing low bound
        else          maxrate = IRR    // too big, decrease IRR by changing high bound
    }
    return [NPV, IRR]
}
// 投資債券期間現金流量
var CT = [ ]        // 初始化現金流量矩陣
var paytime = 12;    // 投資一年,分期付款
var gan = 2*paytime + 1; // 投資領息期限
var C0  = 3e3;      // 每期投入資金
var r   = 5.35/paytime/100;  // 每期債息利率, 年利率=5.35%, debt rate
var FUND  = C0 * (1 - 2.4/100);// 手續費率/每期 = 2.4%, pay discount
var BC = FUND;      // 初期基金
var COST = C0;
var S = 0.0;
CT.push(-C0);       // 初期費用
for (var i = 1; i < gan; i++) { // 續期現金流量
    income = BC*r            // 每期收入
    if (i == (gan - 1)) CT.push(BC); // 期末贖回全部基金
    else if (i < paytime) {    // 續期繳費           
         CT.push(income - C0);// 現金流量 = 利息收入 - 每期應付資金
         COST = COST + C0 - income;// 累計成本
         BC = BC + FUND;    // 基金累積
    } else {
        CT.push(income);
        S = S + income;// 累積利息
    }
}
CT.forEach( (x,i) => console.log("第" + (i+1) + "期淨收 " + Math.round(x*10)/10) );
console.log("=================\n實繳 " + Math.round(COST*10)/10 + "\t,利息收入 " + Math.round(S*10)/10);
console.log("投資共" + Math.round(gan*100/paytime)/100 + "年,報酬率: " + Math.round(S*1e4/COST)/1e2 + "%");
var [v, r] = irr(CT);
console.log("換算 IRR 年化報酬率: " + Math.round(r*paytime*1e4)/1e2 + "%" + "\t,NPV = " +  v);
執行 js debt 看輸出結果:
第1期淨收 -3000
第2期淨收 -2986.9
第3期淨收 -2973.9
第4期淨收 -2960.8
第5期淨收 -2947.8
第6期淨收 -2934.7
第7期淨收 -2921.7
第8期淨收 -2908.6
第9期淨收 -2895.6
第10期淨收 -2882.5
第11期淨收 -2869.5
第12期淨收 -2856.4
第13期淨收 156.6
第14期淨收 156.6
第15期淨收 156.6
第16期淨收 156.6
第17期淨收 156.6
第18期淨收 156.6
第19期淨收 156.6
第20期淨收 156.6
第21期淨收 156.6
第22期淨收 156.6
第23期淨收 156.6
第24期淨收 156.6
第25期淨收 35136
=================
實繳 35138.4    ,利息收入 1879.8
投資共2.08年,報酬率: 5.35%
換算 IRR 年化報酬率: 3.43%    ,NPV = -0.2703545208569267