WebGL的繪製三次方赫密特曲線( Cubic hermite curve )的切線
前言
在先前的 WebGL的繪製三次方貝茲曲線( Cubic bezier curve )的切線 裡繪製了貝茲曲線的切線,這次來繪製三次方赫密特曲線( Cubic hermite curve )的切線,在此把學習的過程做個紀錄。
內容
在先前的 WebGL的繪製三次方赫密特曲線( Cubic hermite curve ) 裡已經有一個繪製曲線的範例,這次的範例會從該專案改出來。在之前不確定"微分"後就可以得到切線,這次算是得到一個驗證,所以如果將赫密特曲線拿去為分會得到以下
切線公式 |
接著將這個公式套用到先前的範例,程式碼如下
HTML 的部分
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <canvas id="myCanvas1" width=400 height=300></canvas> <br> <input id="btnDrawCtrlPoints"type="button" value="DrawCtrlPoints"/> <br> <input id="btnDrawCubicHermiteCurve"type="button" value="DrawCubicHermiteCurve"/> <br> <input id="btnDrawCubicBezierCurve"type="button" value="DrawCubicBezierCurve"/> <input type="range" min="2" max="100" value="30" class="slider" id="sliderLerp"> <label id="labelLerpValue">30</label> <br> <input type="checkbox" id="isShowTangent" checked><label>Show tangent </label> <input type="range" min="0" max="1" value="0.5" step="0.001" class="slider" id="sliderTangent"> <label id="labelTangentValue">0.5</label> </body> </html>
Javascript 的部分
let canvas1 = document.getElementById('myCanvas1'); let glCTX1 = canvas1.getContext('webgl'); let vboPrimitiveCon = 0; let vbo=createDynamicBuffer(glCTX1); let shaderProg = createShader(glCTX1); let curveType = 0; // function createShader(glContext){ let vertShader = glContext.createShader(glContext.VERTEX_SHADER); glContext.shaderSource( vertShader , 'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}' ); glContext.compileShader(vertShader); let fragShader = glContext.createShader(glContext.FRAGMENT_SHADER); glContext.shaderSource( fragShader, 'void main(void){gl_FragColor=vec4(1,1,1,1);}' ); glContext.compileShader(fragShader); let prog = glContext.createProgram(); glContext.attachShader(prog, vertShader); glContext.attachShader(prog, fragShader); glContext.linkProgram(prog); glContext.useProgram(prog); return prog; } function createDynamicBuffer(glContext){ let vertexBuf = glContext.createBuffer(); glContext.bindBuffer(glContext.ARRAY_BUFFER, vertexBuf); let dataArray=new Float32Array([ 0.0, 0.5, 0.0, -0.5,-0.5, 0.0, -0.5,-0.5, 0.0, 0.5,-0.5, 0.0, 0.5,-0.5, 0.0, 0.0, 0.5, 0.0 ]); glContext.bufferData( glContext.ARRAY_BUFFER, 3000, glContext.DYNAMIC_DRAW ); //write deafult data... glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray); vboPrimitiveCon = dataArray.length / 3; return vertexBuf; } function simpleDraw(glContext){ glContext.useProgram(shaderProg); // glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height); glContext.clearColor(0, 0, 1, 1); glContext.clear(glContext.COLOR_BUFFER_BIT); // glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo); let posLoc = glContext.getAttribLocation(shaderProg, "pos"); glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0); glContext.enableVertexAttribArray(posLoc); glContext.drawArrays(glContext.LINES, 0, vboPrimitiveCon); } function generateLineListData(ar){ let tagAr=[]; let mod=ar.length%3; let elementAmount=(ar.length-mod)/3; if(elementAmount>=2 && mod===0){ tagAr.push(ar[0]); tagAr.push(ar[1]); tagAr.push(ar[2]); // for(let i=3;i<(ar.length-3);i+=3){ tagAr.push(ar[i]); tagAr.push(ar[i+1]); tagAr.push(ar[i+2]); // tagAr.push(ar[i]); tagAr.push(ar[i+1]); tagAr.push(ar[i+2]); } // tagAr.push(ar[ar.length-3]); tagAr.push(ar[ar.length-2]); tagAr.push(ar[ar.length-1]); } return new Float32Array(tagAr); } function generateLineListArrayData(ar){ let tagAr=[]; let mod=ar.length%3; let elementAmount=(ar.length-mod)/3; if(elementAmount>=2 && mod===0){ tagAr.push(ar[0]); tagAr.push(ar[1]); tagAr.push(ar[2]); // for(let i=3;i<(ar.length-3);i+=3){ tagAr.push(ar[i]); tagAr.push(ar[i+1]); tagAr.push(ar[i+2]); // tagAr.push(ar[i]); tagAr.push(ar[i+1]); tagAr.push(ar[i+2]); } // tagAr.push(ar[ar.length-3]); tagAr.push(ar[ar.length-2]); tagAr.push(ar[ar.length-1]); } return tagAr; } function generateBezierCurve(p0,p1,p2,lerp){ if(lerp < 2) return []; // let tagAr = []; for(let i=0;i < lerp;i++){ let t = i/(lerp-1); let invT = 1.0-t; let part0Value = invT * invT; let part1Value = 2 * t * invT; let part2Value = t * t; let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ]; let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ]; let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ]; tagAr.push(part0[0] + part1[0] + part2[0]); tagAr.push(part0[1] + part1[1] + part2[1]); tagAr.push(part0[2] + part1[2] + part2[2]); } return tagAr; } function generateCubicBezierCurve(p0,p1,p2,p3,lerp){ if(lerp < 2) return []; // let tagAr = []; for(let i=0;i < lerp;i++){ let t = i/(lerp-1); let invT = 1.0-t; let part0Value = invT * invT * invT; let part1Value = 3 * t * invT * invT; let part2Value = 3 * t * t * invT; let part3Value = t * t * t; let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ]; let part1 = [part1Value*p1[0], part1Value*p1[1], part1Value*p1[2] ]; let part2 = [part2Value*p2[0], part2Value*p2[1], part2Value*p2[2] ]; let part3 = [part3Value*p3[0], part3Value*p3[1], part3Value*p3[2] ]; tagAr.push(part0[0] + part1[0] + part2[0] + part3[0]); tagAr.push(part0[1] + part1[1] + part2[1] + part3[1]); tagAr.push(part0[2] + part1[2] + part2[2] + part3[2]); } return tagAr; } function calCubicHermiteCurvePosition(p0,s0,p1,s1,t){ let invT = 1.0-t; let part0Value = (1 + ( 2 * t ) ) * ( invT * invT ); let part1Value = t * ( ( invT * invT ) ); let part2Value = ( t * t ) * ( 3 - ( 2 * t) ); let part3Value = ( t * t ) * ( t - 1.0 ); let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ]; let part1 = [part1Value*s0[0], part1Value*s0[1], part1Value*s0[2] ]; let part2 = [part2Value*p1[0], part2Value*p1[1], part2Value*p1[2] ]; let part3 = [part3Value*s1[0], part3Value*s1[1], part3Value*s1[2] ]; let position = [ part0[0] + part1[0] + part2[0] + part3[0], part0[1] + part1[1] + part2[1] + part3[1], part0[2] + part1[2] + part2[2] + part3[2] ]; return position; } function generateCubicHermiteCurve(p0,s0,p1,s1,lerp){ if(lerp < 2) return []; // let tagAr = []; for(let i=0;i < lerp;i++){ let t = i/(lerp-1); let pos = calCubicHermiteCurvePosition(p0,s0,p1,s1,t); tagAr.push( pos[0] ); tagAr.push( pos[1] ); tagAr.push( pos[2] ); } return tagAr; } function calCubicHermiteCurveTangent(p0,s0,p1,s1,t){ let part0Value = ( 6 * t * t ) - ( 6 * t); let part1Value = ( 3 * t * t ) - ( 4 * t) + 1; let part2Value = ( 6 * t) - ( 6 * t * t ); let part3Value = ( 3 * t * t ) - ( 2 * t ); let part0 = [part0Value*p0[0], part0Value*p0[1], part0Value*p0[2] ]; let part1 = [part1Value*s0[0], part1Value*s0[1], part1Value*s0[2] ]; let part2 = [part2Value*p1[0], part2Value*p1[1], part2Value*p1[2] ]; let part3 = [part3Value*s1[0], part3Value*s1[1], part3Value*s1[2] ]; let tangent = [ part0[0] + part1[0] + part2[0] + part3[0], part0[1] + part1[1] + part2[1] + part3[1], part0[2] + part1[2] + part2[2] + part3[2] ]; return tangent; } function myRender(){ simpleDraw(glCTX1); // window.requestAnimationFrame(myRender); } // let ctrlPointList=[ [-0.9,0.0,0.0], [-0.45,0.9,0.0], [0.45,0.9,0.0], [0.9,0.0,0.0], ]; let tagLerpValue=document.getElementById("sliderLerp").value; let tagTangentValue=document.getElementById("sliderTangent").value; function UpdateCubicBezierCurveData(){ let data=generateCubicBezierCurve( ctrlPointList[0], ctrlPointList[1], ctrlPointList[2], ctrlPointList[3], tagLerpValue); let dataArray=generateLineListData(data); glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo); glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray); vboPrimitiveCon = dataArray.length / 3; } function UpdateCubicHermiteCurveData(){ let data=generateCubicHermiteCurve( ctrlPointList[0], ctrlPointList[1], ctrlPointList[3], ctrlPointList[2], tagLerpValue); // let dataArray=generateLineListArrayData(data); let isShowTangent = document.getElementById("isShowTangent").checked; if(isShowTangent){ let tagT = tagTangentValue; let pos = calCubicHermiteCurvePosition( ctrlPointList[0], ctrlPointList[1], ctrlPointList[3], ctrlPointList[2], tagT ); let tangent = calCubicHermiteCurveTangent( ctrlPointList[0], ctrlPointList[1], ctrlPointList[3], ctrlPointList[2], tagT ); dataArray.push(pos[0]); dataArray.push(pos[1]); dataArray.push(pos[2]); dataArray.push(pos[0]+tangent[0]); dataArray.push(pos[1]+tangent[1]); dataArray.push(pos[2]+tangent[2]); } let tagDataArray = new Float32Array( dataArray ); glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo); glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,tagDataArray); vboPrimitiveCon = dataArray.length / 3; } document.getElementById("btnDrawCtrlPoints").onclick=function(evt){ let data = []; for(let i=0;i<ctrlPointList.length;i++) data.push(...ctrlPointList[i]); // let dataArray=generateLineListData(data); glCTX1.bindBuffer(glCTX1.ARRAY_BUFFER, vbo); glCTX1.bufferSubData(glCTX1.ARRAY_BUFFER,0,dataArray); vboPrimitiveCon = dataArray.length / 3; } document.getElementById("btnDrawCubicBezierCurve").onclick=function(evt){ UpdateCubicBezierCurveData(); curveType = 1; } document.getElementById("btnDrawCubicHermiteCurve").onclick=function(evt){ UpdateCubicHermiteCurveData(); curveType = 2; } document.getElementById("sliderLerp").oninput=function(evt){ tagLerpValue=this.value; //UpdateCurveData(); switch( curveType ){ case 1: UpdateCubicBezierCurveData(); break; case 2: UpdateCubicHermiteCurveData(); break; } document.getElementById("labelLerpValue").innerHTML = this.value; } document.getElementById("isShowTangent").onclick=function(){ UpdateCubicHermiteCurveData(); } document.getElementById("sliderTangent").oninput=function(evt){ tagTangentValue=this.value; UpdateCubicHermiteCurveData(); document.getElementById("labelTangentValue").innerHTML = this.value; } window.onload = function(){ window.requestAnimationFrame(myRender); }
執行結果如下
範例的執行結果 |
這次的改法跟 WebGL的繪製三次方貝茲曲線( Cubic bezier curve )的切線 的改法是一樣的,所以做不多做說明。
這次算是對微分後得到切線方程式做一個驗證,以後碰到的曲線方程式都可以利用微分得到切線,算是這次的收穫。
參考資料
相關文章與資料
WebGL的繪製三次方赫密特曲線( Cubic hermite curve )
WebGL的繪製三次方貝茲曲線( Cubic bezier curve )的切線
沒有留言:
張貼留言