问题描述
我一直在关注和翻译关于如何创建光线投射引擎的 YouTube 教程,因为制作视频的人用 C++ 创建了他们的视频,而我正在用 Java 制作我的视频。
教程链接:https://www.youtube.com/watch?v=PC1RaETIx3Y
在大多数情况下,按照创作者的教程已经完全符合预期。但是,当我尝试实施他们使用的地板铸造时,我不断收到以下错误:
java.lang.Arrayindexoutofboundsexception: https://i.stack.imgur.com/lxF27.png
在堆栈跟踪之后,它说我尝试从地板和天花板阵列的地图位置获取颜色值时出错:
int mpF = mapF[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
int mpC = mapC[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
我曾尝试为地板铸造实施其他解决方案,但我很难理解它在做什么,而且这些解决方案不适合我正在构建的引擎。我想知道是否有人知道我的实现可能有什么问题,因为我的计算在功能上应该与创建者的相同。我也会接受提供不同解决方案的答案,该解决方案利用引擎的实现方式。
他们的代码:
for(y=lineOff+lineH;y<320;y++)
{
float dy=y-(320/2.0),deg=degToRad(ra),raFix=cos(degToRad(FixAng(pa-ra)));
tx=px/2 + cos(deg)*158*32/dy/raFix;
ty=py/2 - sin(deg)*158*32/dy/raFix;
int mp=mapF[(int)(ty/32.0)*mapX+(int)(tx/32.0)]*32*32;
float c=All_Textures[((int)(ty)&31)*32 + ((int)(tx)&31)+mp]*0.7;
glColor3f(c/1.3,c/1.3,c);glPointSize(8);glBegin(GL_POINTS);glVertex2i(r*8+530,y);glEnd();
mp=mapC[(int)(ty/32.0)*mapX+(int)(tx/32.0)]*32*32;
c=All_Textures[((int)(ty)&31)*32 + ((int)(tx)&31)+mp]*0.7;
glColor3f(c/2.0,c/1.2,c/2.0);glPointSize(8);glBegin(GL_POINTS);glVertex2i(r*8+530,320-y);glEnd();
}
我的代码:
//Draws Floor And Ceiling Tiles
for(int y = (int)(lineO+lineH); y<HEIGHT; y++) {
float dy = y - ((float)HEIGHT/2f),raFix = (float) Math.cos(ca);
texX = px/2 + (float)Math.cos(ra)*(HEIGHT/2)*32/dy/raFix;
texY = py/2 - (float)Math.sin(ra)*(HEIGHT/2)*32/dy/raFix;
int mpF = mapF[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
int mpC = mapC[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
Color c = colors[textures[((int)(texY)&31)*32 + ((int)(texX)&31)+mpF]];
g.setColor(getShade(c,ratio));
g.drawLine(drawX,y,drawX,y);
c = colors[textures[((int)(texY)&31)*32 + ((int)(texX)&31)+mpC]];
g.setColor(getShade(c,HEIGHT-y,HEIGHT-y);
}
注意事项:
-
我的引擎通过使用 int 数组来使用“纹理”,并获取 java.awt.Color 对象,该对象对应于 java.awt.Color 数组中纹理数组的值
-
我的引擎不像其他光线投射器那样使用 x 平面、y 平面或 z 缓冲区,因为引擎只需要光线的命中位置、光线的角度、玩家的位置和玩家位置和射线位置之间的距离,以正确计算需要在屏幕上绘制的内容。
-
我的引擎使用三个 int 数组,第一个是 mapW,它代表墙壁的布局和纹理。第二个是 mapF,它是可步行空间的地板纹理。第三个是 mapC,它在概念上与 floor int 数组相同,但用于天花板。
-
任何潜在的解决方案都需要让我能够轻松访问我从中获取纹理的地板/天花板瓷砖的价值,因为这样做将允许我以后实施更复杂的照明/阴影技术。
-
如果它会更有帮助,这里是我在其中进行光线投射的整个 java 文件:
package launcher;
import java.awt.Basicstroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import SwingUI.ComputerController;
import SwingUI.Program;
import world.Texture;
//This class extends a class I made in another Java Project,which I exported as a JAR file
//The class Program,is a JFrame subclass that implements the game loop
public class Game extends Program{
private static final long serialVersionUID = 1L;
private int mapX = 16,mapY = 16,mapS = 64,mapLen = mapX*mapY;
private int door = 4;
private int[] mapW = {
1,1,2,//16
1,//32
1,//48
1,4,//64
1,//80
1,//96
1,//112
1,//128
1,3,//144
1,//160
1,//176
1,//192
1,//208
1,//224
1,//240
1,//256
};
private int[] mapF = {
0,//16
0,//32
0,//48
0,//64
0,//80
0,//96
0,//112
0,//128
0,//144
0,//160
0,//176
0,//192
0,//208
0,//224
0,//240
0,//256
};
private int[] mapC = {
0,//256
};
private int[] textures = Texture.textures;
private Color[] colors = Texture.colors;
public Game(String title) {
super(title);
}
@Override
public void init() {
setResizable(false);
px = 100;
py = 100;
}
float px,centX,py,centY,pdx,pdy,pa;
float velX,velY;
boolean forward,backward,left,right;
boolean tl,tr;
/**
* Updates all game objects
* In this case,it handles the camera
*/
@Override
public void updateObjects() {
forward = ComputerController.keys[KeyEvent.VK_W];
left = ComputerController.keys[KeyEvent.VK_A];
backward = ComputerController.keys[KeyEvent.VK_S];
right = ComputerController.keys[KeyEvent.VK_D];
tl = ComputerController.keys[KeyEvent.VK_LEFT];
tr = ComputerController.keys[KeyEvent.VK_RIGHT];
if(tl)
pa -= 0.05;
if(tr)
pa += 0.05;
if(pa > PI2)
pa -= PI2;
if(pa < 0)
pa += PI2;
pdx = (float)Math.cos(pa)*5;
pdy = (float)Math.sin(pa)*5;
velX = 0;
velY = 0;
if(forward) {
velX += pdx;
velY += pdy;
}
else if(backward) {
velX -= pdx;
velY -= pdy;
}
if(left) {
velX += pdy;
velY -= pdx;
}
else if(right) {
velX -= pdy;
velY += pdx;
}
int mpx = (int)(centX/mapS),mpy = (int)(centY/mapS),mpxO = (int)((centX+velX)/mapS),mpyO = (int)((centY+velY)/mapS);
if(mapW[mpy*mapX + mpxO] == 0)
px+=velX;
if(mapW[mpyO*mapX + mpx] == 0)
py+=velY;
if(ComputerController.keys[KeyEvent.VK_E]) {
mpxO = (int)((centX+pdx*10)/mapS);
mpyO = (int)((py+pdy*10)/mapS);
if(mapW[mpyO*mapX + mpxO] == door)
mapW[mpyO*mapX + mpxO] = 0;
}
centX = px+3;
centY = py+3;
}
Basicstroke brushSize = new Basicstroke(16);
Basicstroke reset = new Basicstroke(1);
/**
* Draws everything to the screen
*/
@Override
public void renderObjects(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setstroke(reset);
g2d.setColor(Color.GREEN);
g2d.fillRect(0,WIDTH,HEIGHT);
castRays(g2d);
}
int fov = 64,fineness=4,totalRays = (int) (fov*fineness*(16/brushSize.getlinewidth())),renderdistance = 8;
float PI = (float) Math.PI,PI2 = (float) (2*Math.PI),PIHalf = (float) (Math.PI/2),PI3Half = (float) (3*Math.PI/2);
float DR = (float) Math.toradians(1),DROffset = (float) (Math.toradians(1)/fineness),drawXOffset = brushSize.getlinewidth()/fineness;
/**
* Implements Raycasting rendering
*/
private void castRays(Graphics2D g) {
int r,mxH,myH,mxV,myV,mpH,mpV,dofH,dofV;
float ra=fixAngle(pa-(DR*fov/2)),xo1=0,yo1=0,xo2=0,yo2=0;
g.setstroke(new Basicstroke(4));
for(r=0; r < totalRays; r++) {
dofH=0; dofV=0;
float atan = (float) (-1/Math.tan(ra)),nTan = (float) -Math.tan(ra);
float distH = 1000000000,hx=centX,hy=centY,distV = 1000000000,vx=centX,vy=centY;
// Horizontal Line Check!!!!
if(ra>PI) {hy = (float) (((int)centY/mapS)*mapS-0.0001); hx = (centY-hy)*atan+centX; yo1 = -mapS; xo1 = -yo1*atan;}
else if(ra<PI) {hy = ((int)centY/mapS)*mapS+mapS; hx = (centY-hy)*atan+centX; yo1 = mapS; xo1 = -yo1*atan;}
else if(ra==PI || ra==0) {hx=centX;hy=centY;dofH=8;}
for(dofH=0; dofH < mapX; dofH++) {
mxH=(int)hx/mapS; myH=(int)hy/mapS; mpH=myH*mapX+mxH;
boolean withinMapH = hitWall(mpH);
if(withinMapH) {distH=getdistance(hx,hy,centY);dofH=mapX;}
else {hx+=xo1;hy+=yo1;}
}
// Vertical Line Check!!!!
if(ra>PIHalf&&ra<PI3Half) {vx = (float) (((int)centX/mapS)*mapS-0.0001); vy = (centX-vx)*nTan+centY; xo2 = -mapS; yo2 = -xo2*nTan;}
else if(ra<PIHalf||ra>PI3Half) {vx = ((int)centX/mapS)*mapS+mapS; vy = (centX-vx)*nTan+centY; xo2 = mapS; yo2 = -xo2*nTan;}
else if(ra==PI||ra==0) {vy=centX;vx=centY;dofV=8;}
for(dofV=0; dofV < mapY; dofV++) {
mxV=(int)vx/mapS; myV=(int)vy/mapS; mpV=myV*mapX+mxV;
boolean withinMapV = hitWall(mpV);
if(withinMapV) {distV=getdistance(vx,vy,centY);dofV=mapY;}
else {vx+=xo2;vy+=yo2;}
}
//Get Shortest distance
float distT = 0,rx = 0,ry = 0;
if(distH<distV) {distT=distH;rx=hx;ry=hy;}
else if(distV<distH) {distT=distV;rx=vx;ry=vy;}
//Fix Fish-eye effect
float ca = fixAngle(pa-ra);
distT*=Math.cos(ca);
//Prepare for drawing Psuedo 3D Environment
float lineH = (mapS*WIDTH)/distT;
int drawX = r*(int)drawXOffset;
float stepY=32/lineH;
float texYOff=0;
if(lineH > HEIGHT) {
texYOff = (lineH-HEIGHT)/2;
lineH = HEIGHT;
}
float lineO = HEIGHT/2-lineH/2;
int mx=(int)rx/mapS,my=(int)ry/mapS,mp=my*mapX+mx,mt = mapW[mp]-1;
float texY=texYOff*stepY+mt*32,texX=0;
if(rx==vx) {
texX=(int)(ry/2)%32;
if(ra>PIHalf && ra<PI3Half) {texX = 31-texX;}
}
else {
texX=(int)(rx/2)%32;
if(ra<PI) {texX = 31-texX;}
}
//Draws Walls
float ratio = distT/(mapS*renderdistance);
for(int y = 0; y < (int)lineH; y++) {
Color c = colors[textures[(int)texY*32+(int)texX]];
g.setColor(getShade(c,ratio));
g.drawLine(drawX,y+(int)(lineO),y+(int)(lineO));
texY+=stepY;
}
//Draws Floor And Ceiling Tiles
for(int y = (int)(lineO+lineH); y<HEIGHT; y++)
{
float dy = y - ((float)HEIGHT/2f),raFix = (float) Math.cos(ca);
texX = px/2 + (float)Math.cos(ra)*(HEIGHT/2)*32/dy/raFix;
texY = py/2 - (float)Math.sin(ra)*(HEIGHT/2)*32/dy/raFix;
int mpF = mapF[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
int mpC = mapC[(int)(texY/32.0)*mapX+(int)(texX/32.0)]*32*32;
Color c = colors[textures[((int)(texY)&31)*32 + ((int)(texX)&31)+mpF]];
g.setColor(c);
g.drawLine(drawX,y);
c = colors[textures[((int)(texY)&31)*32 + ((int)(texX)&31)+mpC]];
g.setColor(c);
g.drawLine(drawX,HEIGHT-y);
}
//Get Next Angle
ra = fixAngle(ra+DROffset);
}
}
/**
* Detects if a ray has hit a wall
* @param mp
* The world-position of the ray's end point
* @return
* True if the position is within bounds and is a wall tile
*/
public boolean hitWall(int mp) {
return (mp >= 0 && mp < mapLen && mapW[mp] > 0);
}
/**
* Resets angles to the correct value if they are above 2PI radians (360 degrees) or below 0 radians (0 degrees)
* @param angle
* The angle we are fixing
* @return
* The fixed angle
*/
public float fixAngle(float angle) {
if(angle > PI2) {
angle -= PI2;
}
if(angle < 0) {
angle += PI2;
}
return angle;
}
/**
*
* Gets the distance between two points
* @param ax
* the X Coordinate of the first point
* @param ay
* the Y Coordinate of the first point
*
* @param bx
* the X Coordinate of the second point
* @param by
* the Y Coordinate of the second point
* @return
* The distance between the two points
*/
public float getdistance(float ax,float ay,float bx,float by) {
return (float)Math.sqrt( (bx-ax)*(bx-ax) + (by-ay)*(by-ay) );
}
/**
*
* Gets a shade of a given color relative to the ratio given
* @param color
* The color we want to change
* @param ratio
* What percent it is close to the color black
* @return
* The new shade
*/
private Color getShade(Color color,float ratio){
int r = (int) Math.round(Math.max(0,color.getRed() - 255 * ratio));
int g = (int) Math.round(Math.max(0,color.getGreen() - 255 * ratio));
int b = (int) Math.round(Math.max(0,color.getBlue() - 255 * ratio));
int rgb = (r << 16) | (g << 8) | b;
return new Color(rgb);
}
/**
* Creates GUI on launch
*/
public static void main(String[] args) {
new Game("RAYCASTER");
}
}
-
如果角度大于 2PI 弧度(360 度)或小于 0 弧度(0 度),则 fixAngle 方法会将角度重置为正确的值。例如,角度为 5PI/2 将返回 PI/2,角度为 -3PI/2 将返回 PI/2
-
getShade 方法返回给定颜色相对于给定比率的阴影。比率是所需颜色接近黑色的百分比(十进制形式)
解决方法
我会在回答您的问题时提供更多帮助。我最近遇到了同样的问题,碰巧和你在同一个教程中工作。我遇到了和你一样的问题,我花了 3 天时间才找到解决方案。我很抱歉没有理解它背后的数学,因为我还在高中,我没有学过三角学。但是,经过反复试验,我找到了解决方案。我真的希望这对您的项目有所帮助,并祝您未来的项目好运!
for (int y=(int)(lineO+lineH); y<height; y++)
{
//------------------------draw floor------------------------\\
float dy=y-(320/2.0),raFix=cos(ca);
tx=px/2 + cos(ra)*158*32/dy/raFix;
ty=py/2 + sin(ra)*158*32/dy/raFix;
int MP=mapF[(int)(ty/32.0)*mapX+(int)(tx/32.0)]*32*32;
float c=All_Textures[((int)(ty)&31)*32 + ((int)(tx)&31)+MP]*0.7;
fill(c/1.3*255,c/1.3*255,c*255);//code to change the color of the boxes
rect(r*8+530,y,8,8);//code to draw the boxes
//------------------------draw ceiling------------------------\\
mp=mapC[(int)(ty/32.0)*mapX+(int)(tx/32.0)]*32*32;
c=All_Textures[((int)(ty)&31)*32 + ((int)(tx)&31)+mp]*0.7;
fill(c/2.0*255,c/1.2*255,c/2.0*255); //code to change the color of the boxes
rect(r*8+530,320-y,8); //code to draw the boxes
}