最大最小法


在 函式圖形繪製 的範例中,發現到有一些座標點必須是被遮蓋住,而不應被繪製出來的。

在處理隱藏面的問題時,必須根據繪製圖形來決定不同的方法,對於單純的座標點繪製,可以使用簡單的最大最小法來讓被遮蓋的座標點不被繪製。

最大最小法的原理很簡單,繪製時必須從最接近我們的點開始繪製,也就是由最大的Z座標點開始繪製;最大最小點是根據Y座標的值來決定哪些點應該被繪製,假設繪製圖形時是使用以下的迴圈:
for(z = 200; z >= -200; z-=10) {
    for(x = -200; x <= 200; x++) {
        //計算座標並繪點
        ....
    }
}


假設前三次繪圖在Y的坡度上是漸增的,如下所示:


必須記錄每一個x位置上的ymax與ymin,如果在第四次繪製時某些位置上的Y坡度是下降的,則那些位置上的Y必然是位於ymax與ymin之間,此時這些點可以不繪製,如下圖所示:



記得在一開始必須將ymax設定為最小的Y值,ymin設定為最大的Y值;以下是將函式繪圖中的範例應用最大最小法繪製的方法與結果:
  • Demo.java
package cc.openhome;

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JApplet;

import static java.lang.Math.*;

public class Demo extends JApplet {
private int orgX;
private int orgY;
private int[] ymax;
private int[] ymin;

public void init() {
super.init();
setBackground(Color.black);
setSize(640, 480);
orgX = getWidth() / 2;
orgY = getHeight() / 2;
ymax = new int[600];
ymin = new int[600];
}

public void paint(Graphics g) {
g.setColor(Color.yellow);

// 重置最大最小點
for(int i = 0; i < ymax.length; i++) {
ymax[i] = 0;
ymin[i] = ymax.length;
}

// 從斜角繪製
// 繞 x 軸轉 30 度,繞 y 軸轉 -30 度
double rotateX = toRadians(30);
double rotateY = toRadians(-30);

double sinRotateX = sin(rotateX);
double cosRotateX = cos(rotateX);
double sinRotateY = sin(rotateY);
double cosRotateY = cos(rotateY);

for(int z = 200; z >= -200; z-=10) {
for(int x = -200; x <= 200; x++) {
double y = 30*(cos(toRadians(sqrt(x * x + z * z)))
+ cos(toRadians(3 * sqrt(x * x + z * z))));

// 立體旋轉,從斜角繪製,調整繪圖中心至視窗中心
int pointX = (int) (orgX + x * cosRotateY + z * sinRotateY);
int pointY = (int) (orgY - (y * cosRotateX -
(-x * sinRotateY + z * cosRotateY) * sinRotateX));

// 最大最小法
if(pointY < ymin[pointX]) {
ymin[pointX] = pointY;
g.drawLine(pointX, pointY, pointX, pointY);
} else if(pointY > ymax[pointX]) {
ymax[pointX] = pointY;
g.drawLine(pointX, pointY, pointX, pointY);
}
}
}
}
}

以下是使用HTML5 Canvas的方式(如果瀏覽器支援HTML5 Canvas,例如最新版的Firexfox、Chrome、IE9等,可以直接將下面的內容存為HTML或按下檔名連結,直接載入瀏覽器執行觀看結果:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=Big5" http-equiv="content-type">
<script type="text/javascript">
window.onload = function() {
function toRadians(angle) {
return angle * Math.PI / 180;
}
var sin = Math.sin;
var cos = Math.cos;
var sqrt = Math.sqrt;

var ymax = new Array(600);
var ymin = [];
for(var i = 0; i < ymax.length; i++) {
// ymax[i] = 0;
ymin[i] = ymax.length;
}

var canvas1 = document.getElementById('canvas1');
var context = canvas1.getContext('2d');

var orgX = canvas1.width / 2;
var orgY = canvas1.height / 2;

var rotateX = toRadians(30);
var rotateY = toRadians(-30);

var sinRotateX = Math.sin(rotateX);
var cosRotateX = Math.cos(rotateX);
var sinRotateY = Math.sin(rotateY);
var cosRotateY = Math.cos(rotateY);

context.beginPath();
for(var z = 200; z >= -200; z-=10) {
for(var x = -200; x <= 200; x++) {
var y = 30*(cos(toRadians(sqrt(x * x + z * z)))
+ cos(toRadians(3 * sqrt(x * x + z * z))));

// 立體旋轉,從斜角繪製,調整繪圖中心至視窗中心
var pointX = parseInt(
orgX + x * cosRotateY + z * sinRotateY);
var pointY = parseInt(orgY - (y * cosRotateX -
(-x * sinRotateY + z * cosRotateY) * sinRotateX));

// 最大最小法
if(pointY < ymin[pointX]) {
ymin[pointX] = pointY;
context.moveTo(pointX, pointY);
context.lineTo(pointX + 1, pointY + 1);
} else if(pointY > ymax[pointX]) {
ymax[pointX] = pointY;
context.moveTo(pointX, pointY);
context.lineTo(pointX + 1, pointY + 1);
}
}
}
context.stroke();
context.closePath();
};
</script>
</head>
<body>
<canvas id="canvas1" width="640" height="480"></canvas>
</body>
</html>

在Firefox中的結果如下: