voxel3d
/**
* Copyright lizhi ( http://wonderfl.net/user/lizhi )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/9v5N
*/
// 地表渲染
//
// OK.. 我知道 voxel 指的别的什么... 但是许多人使用 "voxel" 这个
// 名字来表示一种渲染技术.我写这个来解释 newvox4 渲染的基本思想;
// newvox4 写的很糟糕 (名字里有个 4 是因为是继续到第四次的实验)
// 而且它是用 pascal + asm 写的. 自从我得到了一些说明的请求, 我
// 便决定用 C 写一个渲染内核, 希望它能更容易被理解. 这段程序仅仅
// 是基本的地表 (没有天空等) 而且只支持键盘,但是我想你可以自己想
// 得到应该怎样写其它的部分
//
// I'm releasing this code to the public domain for free... and as it's
// probably really obvious there's no warranty of any kind on it.
// You can do whatever you want with this source; however a credit in any
// program that uses part of this code would be really appreciated :)
//
// 欢迎给予任何评价和建议 :)
//
// Andrea "6502" Griffini, programmer
// agriff@ix.netcom.com
// http://vv.val.net/~agriffini
//
// 译者 注:
// 我在学习 3D 地表的生成算法时有幸拜读了这段程序,深受启发.
// 原来的程序已经是很清晰了, 但作者还是加入了少许优化. 我将仅有的
// 优化也去掉了 ;) 想使程序更容易被理解. 大多数英文注解被我用汉语
// 从写了. 并在许多地方描述的更详细. 希望能对大家有更多的帮助 :-)
// btw, 原来的程序是用 Watcom C 写的, 云风用 Djgpp 重写了
// 这个程序写的极为清晰, 所以它没有过多的效果修饰, 如果你能花上一定
// 时间 (云风花了 10 分钟 ;) 读懂它, 就可以进一步的增加边缘平滑, 背景
// 图案等等效果. 这些云风在自己的程序里都加上了, 并对程序做了许多优化,
// 但是为了让大家更容易的读懂, 还是在这里保留了程序的原貌.
//
// 有任何问题, 欢迎和我讨论 :)
// 云风 cloudwu@163.net
// http://www.nease.net/~cloudwu
//
//
//as3 code @author sliz http://game-develop.net/blog/
//
//
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.filters.BlurFilter;
import flash.utils.ByteArray;
import net.hires.debug.Stats;
[SWF(frameRate=60)]
public class Newvoxc extends Sprite {
private var W:int = 465;
private var H:int = 465;
private var HP1:int = H - 1;
private var CMap:Vector.<Vector.<uint>> = createArray(256, 256); //[256][256]; // 色彩值数组
private var HMap:Vector.<Vector.<uint>> = createArray(256, 256); //=[256][256]; // 地表高度数组
private var Video:ByteArray=new ByteArray //[320*200]; // 屏幕缓冲区
private var lasty:Vector.<uint> = new Vector.<uint>(W); //[320], // 画在指定列上的最后一个点
private var lastc:Vector.<uint> = new Vector.<uint>(W); //[320]; // 最后一点的颜色
private var FOV:Number = Math.PI / 4; // 45 度宽的视角
private var view:Bitmap = new Bitmap(new BitmapData(W, H, false, 0xff0000));
private var ss:Number, sa:Number, a:Number, s:Number;
private var x0:int, y0:int;
private var input:Input;
public function Newvoxc() {
addChild(view);
input = new Input(stage);
var i:int, k:int;
//
// 计算地图高度
//
ComputeMap();
//
// 主循环
//
// a = 角度
// x0,y0 = 当前坐标
// s = 固定速度
// ss = 当前向前/向后的速度
// sa = 旋转角速度
//
a = 0;
k = x0 = y0 = 0;
s = 4096;
ss = 8090;
sa = 0.1;
addEventListener(Event.ENTER_FRAME, update);
addChild(new Stats);
}
private function update(e:Event):void {
if (input.left) {
sa -= 0.005;
}else if(input.right){
sa += 0.005;
}else {
sa = 0;
}
if (input.up) {
ss += s;
}else if(input.down){
ss -= s;
}
a += sa;
//
// 刷新位置/角度
//
x0 += ss * Math.cos(a);
y0 += ss * Math.sin(a);
//
// 画一帧
//
View(x0, y0, a+(mouseX-stage.stageWidth/2)*4/stage.stageWidth);
}
//
// 将值限制在 0..255 之间
//
private function Clamp(x:int):int {
return Math.max(0, Math.min(x, 255));
}
//
// 取 x 的低字节位, 即对 255 取模 (HMap 和 CMap 都是 256 x 256 的数组)
//
private function L(x:int):int {
return x & 0xff;
}
private function createArray(x:int, y:int):Vector.<Vector.<uint>> {
var arr:Vector.<Vector.<uint>> = new Vector.<Vector.<uint>>(x);
for (var i:int = 0; i < x; i++){
arr[i]=new Vector.<uint>(y);
}
return arr;
}
private function rand():int {
return 2147483647 * Math.random();
}
//
// 地表高度和色彩表的计算
//
private function ComputeMap():void {
var p:int, i:int, j:int, k:int, k2:int, p2:int;
//
// 从一个平坦的地表开始
//
HMap[0][0] = 128;
for (p = 256; p > 1; p = p2){
p2 = p / 2;
k = p * 8 + 20;
k2 = k / 2;
for (i = 0; i < 256; i += p){
for (j = 0; j < 256; j += p){
var a:int, b:int, c:int, d:int;
a = HMap[i][j]; // a:::::::b
b = HMap[L(i + p)][j]; // ::::::::: 以 a,b,c,d
c = HMap[i][L(j + p)]; // ::::::::: 为角的区域
d = HMap[L(i + p)][L(j + p)]; // c:::::::d
HMap[i][L(j + p2)] = // 在 a,c 中点,以a,c平均高度为基准
Clamp(((a + c) >> 1) + (rand() % k - k2)); // 产生一随机的高度
HMap[L(i + p2)][L(j + p2)] = // 在 a,b,c,d 区域中心,以平均高度
Clamp(((a + b + c + d) >> 2) + (rand() % k - k2)); // 为基准,产生一随机高度
HMap[L(i + p2)][j] = // 在 a,b 中点,以a,b平均高度为基准
Clamp(((a + b) >> 1) + (rand() % k - k2)); // 产生一随机的高度
}
}
}
//
// 平滑处理
//
for (k = 0; k < 3; k++)
for (i = 0; i < 256; i++)
for (j = 0; j < 256; j++){
HMap[i][j] = (HMap[L(i + 1)][j] + HMap[i][L(j + 1)] + //将前后左右,四个点取
HMap[L(i - 1)][j] + HMap[i][L(j - 1)]) / 4; //平均值,这样做平滑
}
//
// 颜色计算 (地表高度的衍生物)
//
for (i = 0; i < 256; i++)
for (j = 0; j < 256; j++){
k = 128 + (HMap[L(i + 1)][L(j + 1)] - HMap[i][j]) * 4;
CMap[i][j] = Clamp(k); // 以坡度决定灰度
}
}
//
// 画地表的一个"部分"; 它能画出距离视点一定远处的图象
// 使用 lasty 数组中保存的上次画过的位置, 保正了这个部分不会
// 覆盖掉以前画的部分. x0,y0 和 x1,y1 和 xy 坐标描述
// 地表的高度, hy 是视点的高度, s 是由距离决定的比例因子.
// x0,y0,x1,y1 是 16.16 的定点数,
// 比例因子是 16.8 的定点值.
//
private function Line(x0:int, y0:int, x1:int, y1:int, hy:int, s:int):void {
var i:int, sx:int, sy:int;
// 计算 xy 速度
sx = (x1 - x0) / W;
sy = (y1 - y0) / W;
for (i = 0; i < W; i++){
var c:int, y:int, h:int, u0:int, v0:int, u1:int, v1:int, a:int, b:int, h0:int, h1:int, h2:int, h3:int;
//
// 计算 xy 坐标; a 和 b 将被定位于
// 一个 (0..255)(0..255) 的区间里面.
//
u0 = (x0 >> 16)&0xff;
a = (x0 >> 8)&0xff;
v0 = (y0 >> 16)&0xff;
b = (y0 >> 8)&0xff;
u1 = (u0 + 1)&0xff;
v1 = (v0 + 1)&0xff;
//
// 由周围 4 个点来决定里面的高度
//
h0 = HMap[v0][u0];
h2 = HMap[v1][u0];
h1 = HMap[v0][u1];
h3 = HMap[v1][u1];
h0 = (h0 << 8) + a * (h1 - h0);
h2 = (h2 << 8) + a * (h3 - h2);
h = ((h0 << 8) + b * (h2 - h0)) >> 16;
//
// 由周围 4 个点来决定里面的颜色 (颜色值是 16.16 的定点数)
//
h0 = CMap[v0][u0];
h2 = CMap[v1][u0];
h1 = CMap[v0][u1];
h3 = CMap[v1][u1];
h0 = (h0 << 8) + a * (h1 - h0);
h2 = (h2 << 8) + a * (h3 - h2);
c = ((h0 << 8) + b * (h2 - h0));
//
// 使用比例因子计算屏幕高度
//
y = (((h - hy) * s) >> 11) + 100;
//
// 画一列
//
if (y < (a = lasty[i])){
var sc:int, cc:int;
if (lastc[i] == -1)
lastc[i] = c;
sc = (c - lastc[i]) / (a - y);
cc = lastc[i];
if (a > HP1){
b -= (a - HP1) * W;
cc += (a - HP1) * sc;
a = HP1;
}
if (y < 0)
y = 0;
while (y < a){
var bc:uint = cc >>17;
var bi:int = 4*(a * W + i);
Video[bi] = bc;
Video[++bi] = bc;
Video[++bi] = bc;
Video[bi] = bc;
cc += sc;
b -= W;
a--;
}
lasty[i] = y;
}
lastc[i] = c;
//
// 进一步计算下一个 xy 坐标
//
x0 += sx;
y0 += sy;
}
}
//
// 画出从点 x0,y0 (16.16) 以 a 角 看到的图象
//
private function View(x0:int, y0:int, aa:Number):void {
var d:int;
var a:int, b:int, h:int, u0:int, v0:int, u1:int, v1:int, h0:int, h1:int, h2:int, h3:int;
//
// 清除屏幕缓冲
//
// memset(Video,0,320*200);
Video.clear();
Video.length = W * H*4;
//
// 初始化 last-y 和 last-color 数组
//
for (d = 0; d < W; d++){
lasty[d] = H;
lastc[d] = -1;
}
//
// 计算视点高度变量
//
// 计算 xy 坐标; a 和 b 将被定位于
// 一个 (0..255)(0..255) 的区间里面.
//
u0 = (x0 >> 16) & 0xFF;
a = (x0 >> 8) & 255;
v0 = (y0 >> 16) & 0xFF;
b = (y0 >> 8) & 255;
u1 = (u0 + 1) & 0xFF;
v1 = (v0 + 1) & 0xFF;
//
// 由周围 4 个点来决定里面的高度
//
h0 = HMap[v0][u0];
h2 = HMap[v1][u0];
h1 = HMap[v0][u1];
h3 = HMap[v1][u1];
h0 = (h0 << 8) + a * (h1 - h0);
h2 = (h2 << 8) + a * (h3 - h2);
h = ((h0 << 8) + b * (h2 - h0)) >> 16;
//
// 无覆盖的由近及远画地表
//
var sx0:int = 65536 * Math.cos(aa - FOV);
var sy0:int = 65536 * Math.sin(aa - FOV);
var sx1:int = 65536 * Math.cos(aa + FOV);
var sy1:int = 65536 * Math.sin(aa + FOV);
var hp30:uint = Math.min(60,h - mouseY);
for (d = 0; d < 200; d +=1+(d>>6)){
Line(x0 + d * sx0, y0 + d * sy0,
x0 + d * sx1, y0 + d * sy1, hp30, 25600 / (d+1));
}
//
// 将最终图象 blit 到屏幕
//
//_movedatal(_my_ds(), (unsigned)Video, _dos_ds, 0xa0000,
// 16000); //320*200/4
view.bitmapData.lock();
view.bitmapData.setPixels(view.bitmapData.rect, Video);
view.bitmapData.unlock();
}
}
}
import flash.display.Stage;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
class Input
{
private var stage:Stage;
private var leftKeyCodes:Array = [Keyboard.LEFT,"A".charCodeAt(0)];
private var rightKeyCodes:Array = [Keyboard.RIGHT,"D".charCodeAt(0)];
private var downKeyCodes:Array = [Keyboard.DOWN,"S".charCodeAt(0)];
private var upKeyCodes:Array = [Keyboard.UP, "W".charCodeAt(0)];
public var left:Boolean = false;
public var right:Boolean = false;
public var down:Boolean = false;
public var up:Boolean = false;
public function Input(stage:Stage)
{
this.stage = stage;
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
private function onKeyUp(e:KeyboardEvent):void
{
if (leftKeyCodes.indexOf(e.keyCode)!=-1) {
left = false;
}else if (rightKeyCodes.indexOf(e.keyCode)!=-1) {
right = false;
}else if (downKeyCodes.indexOf(e.keyCode)!=-1) {
down = false;
}else if (upKeyCodes.indexOf(e.keyCode)!=-1) {
up = false;
}
}
private function onKeyDown(e:KeyboardEvent):void
{
if (leftKeyCodes.indexOf(e.keyCode)!=-1) {
left = true;
right = false;
}else if (rightKeyCodes.indexOf(e.keyCode)!=-1) {
right = true;
left = false;
}else if (downKeyCodes.indexOf(e.keyCode)!=-1) {
down = true;
up = false;
}else if (upKeyCodes.indexOf(e.keyCode)!=-1) {
up = true;
left = false;
}
}
}