图形学 三次Hermite曲线绘制实现代码 javascript:es6+h5:canvas
2019/11/20UPD:更正起点终点切矢量的取均值
期末要考三次hermite曲线绘制编程题,熟悉一下,(虽然考的是opengl版本
1 <!DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 8 <link rel="stylesheet" type="text/css" href="2d.css"> 9 <script src="jquery.min.js"></script> 10 <title>三次hermite曲线绘制</title> 11 <style> 12 </style> 13 </head> 14 15 <body> 16 <canvas id="chart"></canvas> 17 <script> 18 19 class Point { 20 constructor(x, y) { 21 this.x = Math.round(x) || 0; 22 this.y = Math.round(y) || 0; 23 } 24 set(x, y) { 25 this.x = Math.round(x) || 0; 26 this.y = Math.round(y) || 0; 27 } 28 } 29 class Line { 30 constructor(start, end) { 31 this.start = (start === undefined) ? new Point() : start; 32 this.end = (end === undefined) ? new Point() : end; 33 } 34 set(start, end) { 35 this.start = (start === undefined) ? new Point() : start; 36 this.end = (end === undefined) ? new Point() : end; 37 } 38 } 39 40 let chart = document.getElementById("chart"); 41 let gridStep = 2;//网格大小,未画出 42 let bigStep = 5; 43 let gIsLButtonDown = false; 44 let change = -1;//更改下标 45 let p = new Array();//原始点 46 let h = new Array();//基函数 47 let pre_point = new Point();//拖动点 48 const num = 1000;//每段直线画点个数 49 50 51 52 function lpTodp(x) { 53 return x * gridStep; 54 } 55 56 function dpTolp(x) { 57 return Math.trunc(x / gridStep); 58 } 59 60 function drawPoint(ctx, x, y, color, step) { 61 if (color) 62 ctx.fillStyle = color; 63 if (!step) 64 step = gridStep; 65 ctx.fillRect(lpTodp(x), lpTodp(y), step, step); 66 } 67 68 function drawLine(ctx, line) {//bresenham 69 let dx, dy, upInc, downInc, x, y, d, isSwap = false; 70 let tx, ty, e, dx2, dy2; 71 let x1 = line.end.x, y1 = line.end.y; 72 let x0 = line.start.x, y0 = line.start.y; 73 dx = Math.abs(x1 - x0), dy = Math.abs(y1 - y0); 74 if (dx < dy) { 75 [x0, y0] = [y0, x0]; 76 [x1, y1] = [y1, x1]; 77 [dx, dy] = [dy, dx]; 78 isSwap = true; 79 } 80 tx = x0 <= x1 ? 1 : -1; 81 ty = y0 <= y1 ? 1 : -1; 82 e = -dx, x = x0, y = y0; 83 dx2 = dx * 2, dy2 = dy * 2; 84 while (x != x1) { 85 isSwap ? drawPoint(ctx, y, x) : drawPoint(ctx, x, y); 86 x += tx, e += dy2; 87 if (e > 0) { 88 y += ty; 89 e -= dx2; 90 } 91 } 92 isSwap ? drawPoint(ctx, y, x) : drawPoint(ctx, x, y); 93 } 94 function draw() { 95 if (!chart.getContext) return; 96 const ctx = chart.getContext("2d"); 97 //开始代码 98 ctx.fillStyle = "rgb(255, 255, 255)"; 99 //绘制矩形 100 101 const w = $("#chart").width(); 102 const h = $("#chart").height(); 103 104 ctx.fillRect(0, 0, w, h); 105 106 for (let q of p) 107 drawPoint(ctx, q.x, q.y, "#ff0000", bigStep); 108 ctx.fillStyle = "rgba(0, 0, 0,0.5)"; 109 drawHermite(); 110 } 111 112 113 function getBaseFunction(t) {//基函数计算 114 const t2 = t * t; 115 const t3 = t2 * t; 116 let cur = new Array(4); 117 cur[0] = 2 * t3 - 3 * t2 + 1; 118 cur[1] = -2 * t3 + 3 * t2; 119 cur[2] = t3 - 2 * t2 + t; 120 cur[3] = t3 - t2; 121 return cur; 122 } 123 124 function drawHermite() { 125 if (p.length < 4) 126 return; 127 h = new Array();//置空 128 const delta = 1.0 / num; 129 const ctx = chart.getContext("2d"); 130 for (let i = 1; i < num; i++) { 131 const t = i * 1.0 / num; 132 h.push(getBaseFunction(t)); 133 } 134 const m0 = new Point(p[1].x - p[0].x, p[1].y - p[0].y); 135 const m1 = new Point(p[2].x - p[1].x, p[2].y - p[1].y); 136 const m2 = new Point(p[3].x - p[2].x, p[3].y - p[2].y); 137 const k0 = new Point((m0.x + m1.x) / 2, (m0.y + m1.y) / 2);//取均值作为切线 138 const k1 = new Point((m1.x + m2.x) / 2, (m1.y + m2.y) / 2); 139 drawLine(ctx, new Line(p[0], p[1])); 140 let pre = undefined; 141 for (let i = 0; i < num - 1; i++) { 142 const x = p[1].x * h[i][0] + p[2].x * h[i][1] + k0.x * h[i][2] + k1.x * h[i][3]; 143 const y = p[1].y * h[i][0] + p[2].y * h[i][1] + k0.y * h[i][2] + k1.y * h[i][3]; 144 const now = new Point(x, y); 145 if (pre) 146 drawLine(ctx, new Line(pre, now)); 147 pre = now; 148 } 149 drawLine(ctx, new Line(pre, p[2])); 150 drawLine(ctx, new Line(p[2], p[3])); 151 } 152 153 function getPoint(x, y) {//获取坐标 154 const cur = new Point(x, y); 155 if (change == -1) {//获取拖动坐标 156 for (let i = 0; i < p.length; i++) 157 if (p[i].x == cur.x && p[i].y == cur.y) { 158 pre_point = p[i]; 159 change = i; 160 } 161 } 162 if (p.length == 4) 163 return; 164 p.push(cur); 165 } 166 function movePoint(x, y, f) { 167 if (change != -1) 168 pre_point.set(x, y); 169 if (f) 170 change = -1; 171 } 172 function resizeCanvas() { 173 // 将chart的大小设置为窗口大小, 但是为了避免偶尔溢出, 略微减去4个像素 174 $('#chart').attr("width", $(window).get(0).innerWidth - 4); 175 $('#chart').attr("height", $(window).get(0).innerHeight - 4); 176 draw(); 177 }; 178 179 $(function () { 180 $(window).resize(resizeCanvas); 181 resizeCanvas(); 182 183 $('canvas').mousedown(function (e) { 184 var x = e.originalEvent.x || e.originalEvent.layerX || 0; 185 var y = e.originalEvent.y || e.originalEvent.layerY || 0; 186 x = dpTolp(x); 187 y = dpTolp(y); 188 gIsLButtonDown = true; 189 getPoint(x, y); 190 draw(); 191 }); 192 193 $('canvas').mousemove(function (e) { 194 if (gIsLButtonDown) { 195 let x = e.originalEvent.x || e.originalEvent.layerX || 0; 196 let y = e.originalEvent.y || e.originalEvent.layerY || 0; 197 x = dpTolp(x); 198 y = dpTolp(y); 199 movePoint(x, y); 200 draw(); 201 } 202 }); 203 204 $('canvas').mouseup(function (e) { 205 if (gIsLButtonDown) { 206 let x = e.originalEvent.x || e.originalEvent.layerX || 0; 207 let y = e.originalEvent.y || e.originalEvent.layerY || 0; 208 x = dpTolp(x); 209 y = dpTolp(y); 210 movePoint(x, y, 1); 211 draw(); 212 gIsLButtonDown = false; 213 } 214 }); 215 }); 216 </script> 217 </body> 218 219 </html>
实现效果(可拖拽端点)