【发布时间】:2021-04-16 20:26:11
【问题描述】:
我一直在关注并翻译一个关于如何创建光线投射引擎的 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, ratio));
g.drawLine(drawX, HEIGHT-y, drawX, 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,1,1,1,1,1,1, 2,2,2,2,2,2,2,2, //16
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //32
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //48
1,0,0,0,0,0,0,4, 0,0,0,0,0,0,0,2, //64
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //80
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //96
1,0,0,0,0,0,0,1, 2,0,0,0,0,0,0,2, //112
1,1,1,1,1,1,1,1, 2,2,2,4,2,2,2,2, //128
1,1,1,1,1,1,1,1, 3,3,3,0,3,3,3,3, //144
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //160
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //176
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //192
1,0,0,0,0,0,0,0, 4,0,0,0,0,0,0,3, //208
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //224
1,0,0,0,0,0,0,1, 3,0,0,0,0,0,0,3, //240
1,1,1,1,1,1,1,1, 3,3,3,3,3,3,3,3, //256
};
private int[] mapF = {
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //16
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //32
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //48
0,1,1,1,1,1,1,0, 1,1,1,1,1,1,1,0, //64
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //80
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //96
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //112
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //128
0,0,0,0,0,0,0,0, 0,0,0,1,0,0,0,0, //144
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //160
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //176
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //192
0,1,1,1,1,1,1,1, 0,1,1,1,1,1,1,0, //208
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //224
0,1,1,1,1,1,1,0, 0,1,1,1,1,1,1,0, //240
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //256
};
private int[] mapC = {
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //16
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //32
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //48
0,2,2,2,2,2,2,0, 2,2,2,2,2,2,2,0, //64
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //80
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //96
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //112
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, //128
0,0,0,0,0,0,0,0, 0,0,0,2,0,0,0,0, //144
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //160
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //176
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //192
0,2,2,2,2,2,2,2, 0,2,2,2,2,2,2,0, //208
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //224
0,2,2,2,2,2,2,0, 0,2,2,2,2,2,2,0, //240
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,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, 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,centX,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,centX,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), drawX, 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, drawX, y);
c = colors[textures[((int)(texY)&31)*32 + ((int)(texX)&31)+mpC]];
g.setColor(c);
g.drawLine(drawX, HEIGHT-y, 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 方法返回给定颜色相对于给定比率的阴影。比率是所需颜色接近黑色的百分比(以十进制形式)
【问题讨论】:
-
仔细阅读异常信息,您会发现问题所在:-11 不能是数组的索引。这意味着您的计算返回一个否定。价值。如果它返回的数字大于数组元素的数量,你也会得到一个 IndexOutOfBoundException。
-
我知道,但是,我使用的地板铸造公式在功能上与教程使用的公式相同,但我得到的值会导致 indexOutOfBounds 异常,我似乎无法弄清楚为什么这样做
-
你不应该盲目复制不起作用的东西,而是研究算法并纠正或修改它。
-
这个问题的全部意义在于获得帮助以了解算法的工作原理以及我做错了什么,因为我找不到使用创建者使用的相同方法的其他资源。所有其他在线资源都使用 lodev 或 permadi 的光线投射版本,其中包括 x/y 平面和 z 缓冲区,正如我的问题所述,我没有也无法实现,因为它们不适合引擎我正在使用。
标签: java c++ indexoutofboundsexception raycasting texture-mapping