java畫圖板之3D地形圖(hashmap初步使用)

    之前我們已經嘗試過了繪製平面山水畫了,這次我們用與之相類似的方法,繪製“3D”地形圖。
    爲什麼加了引號?因爲我們的點的座標還是二維的,所以只是看起來有3D的效果,但是我們可以擴展一下,用同樣的方法,將點的座標改爲三維,繪製出來的就是真正的三維圖像了。

原理

    首先是原理,這是原理圖:
原理圖

  • 首先,我們在畫板上任取三個點P1,P2,P3
  • 然後,計算出他們互相之間的中點M1,M2,M3,將這三個點沿Y軸方向隨機平移一段距離,這一部與繪製山脈的類似,得到下圖:
    平移後圖像
  • 最後遞歸迭代,在迭代完成後連線

數據存儲

    但是,如果只是按照上面的步驟設計,就會存在一個問題,就是其中會有一些邊被重複計算中點,這就會導致多出來一些線條(如下圖紅圈處所示)
重複線條
    要想解決這個問題,就需要我們將每次計算出的中點存儲起來,在下一次計算時判斷要計算的中點是否已經被計算並存儲過了:如果有,就直接獲取存儲的點的座標;如果沒有再進行計算。
    剛開始,我準備自己創建一個數據結構來進行存儲,但是,在判斷和內存方面出了點問題,等錯誤找出來再分享吧。現在我所用的是哈希表(java.util.HashMap)來存儲數據。

    哈希表

部分參考博客:https://blog.csdn.net/woshimaxiao1/article/details/83661464
                         https://www.cnblogs.com/yuanblog/p/4441017.html
    HashMap是數組和鏈表結合的一種數據結構,以數組爲主幹,每個數組元素都是一個鏈表。哈希表性能十分之高,在不考慮哈希衝突的情況下,時間複雜度僅爲O(1)。
哈希表

  • 數組:採用一段連續的存儲單元來存儲數據。
    – 對於指定下標的查找,時間複雜度爲O(1)
    – 通過給定值進行查找,需要遍歷數組,逐一比對給定關鍵字和數組元素,時間複雜度爲O(n)
    – 對於有序數組,則可採用二分查找,插值查找,斐波那契查找等方式,可將查找複雜度提高爲O(log(n))
    – 對於一般的插入刪除操作,涉及到數組元素的移動,其平均複雜度也爲O(n)

  • 線性鏈表:
    – 對於鏈表的新增,刪除等操作(在找到指定操作位置後),僅需處理結點間的引用即可,時間複雜度爲O(1)
    – 查找操作需要遍歷鏈表逐一進行比對,複雜度爲O(n)

  • 二叉樹:
    – 對一棵相對平衡的有序二叉樹,對其進行插入,查找,刪除等操作,平均複雜度均爲O(log(n))。

    在用HashMap存儲數據時,需要存入兩個值,一個是數據的原值,另一個是數據的key值。key值的作用是負責定位數據在數組中的位置,如果該位置處還沒有數據,就直接存入,但是不可避免的會出現位置已經被其他數據佔據的情況,就將數據以鏈表的形式存儲在該位置上。當我們需要查找數據時,就將要查找數據的key值轉換爲HashMap數組的下標,然後再去查找對應的鏈表。這就好比是將所有的數據分成了若干類,存放在數組的各個元素中,查找的時候只需要在對應的那一類數據中查找即可。

    實戰

    在實際使用HashMap時,我們只需要直接調用java.util.HashMap包中的方法即可。
原理圖2
    我們在對P1P2這條邊經過偏移後的點M1’進行存儲時,將P1P2的中點M1的座標作爲key值,偏移後的M1’的座標作爲數據值,因爲我們的M1’的縱座標是隨機的,不適合用來做key值。

//用哈希表來存儲數據
HashMap<Point, Point> hashmap = new HashMap<Point, Point>(1000);

public void showPad(Point p1, Point p2, Point p3, int timescounter, double range) {
		//最終繪製的點
		Point m[] = new Point[3];
		//每條邊的中點
		Point n[] = new Point[3];
		n[0] = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
		n[1] = new Point((p2.x + p3.x) / 2, (p2.y + p3.y) / 2);
		n[2] = new Point((p3.x + p1.x) / 2, (p3.y + p1.y) / 2);
		
		//給最終繪製的點先賦初值
		for (int i = 0; i < 3; i++) {
			m[i] = new Point();
			m[i].x = n[i].x;
			m[i].y = n[i].y;
		}
		
		for (int i = 0; i < 3; i++) {
			if (hashmap.get(n[i]) == null) {//hashmap中沒有這個key值
				//隨機修改m[i]的縱座標
				m[i].y += (int) ((Math.random() * 2 - 1) * range);
				//存入hashmap
				hashmap.put(n[i], m[i]);
			} else {//hashmap中有這個key值
				//直接將對應的原值賦給m[i]
				m[i] = hashmap.get(n[i]);
			}
		}
		
		//遞歸和繪製
		if (timescounter++ < times) {
			range *= rate;
			showPad(p1, m[0], m[2], timescounter, range);
			showPad(m[0], p2, m[1], timescounter, range);
			showPad(m[2], m[1], p3, timescounter, range);
			showPad(m[0], m[1], m[2], timescounter, range);
		} else {
			g.drawLine(p1.x, p1.y, m[0].x, m[0].y);
			g.drawLine(m[0].x, m[0].y, p2.x, p2.y);
			g.drawLine(p2.x, p2.y, m[1].x, m[1].y);
			g.drawLine(m[1].x, m[1].y, p3.x, p3.y);
			g.drawLine(p3.x, p3.y, m[2].x, m[2].y);
			g.drawLine(m[2].x, m[2].y, p1.x, p1.y);
			g.drawLine(m[0].x, m[0].y, m[1].x, m[1].y);
			g.drawLine(m[1].x, m[1].y, m[2].x, m[2].y);
			g.drawLine(m[2].x, m[2].y, m[0].x, m[0].y);
		}
	}

    每次運行調用hashmap時最好clear()一下,如果數據比較多,可以使用resize()進行擴容
    關於遞歸的思路和繪製山脈類似,不再贅述,如有不清楚的,請見:java畫圖板之平面山水畫(一)
    最後是關於代碼中的Point類,建議使用java.awt.Point中的Point類,因爲這個類的數據在hashmap中是以這種方式儲存的:
Point儲存
每一項等號左邊是key值,右邊是數據值。如果是自己寫的Point類好像不是這個效果。(也有可能是我編寫的問題 T_T)

結果展示

range=40:
range=40結果
range=70:
range=70結果
    之後,我儘量完善畫圖板的功能,並與其他的知識相結合嘗試着做一些聊天室、小遊戲等項目。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章