javascript - 光线投射器中的鱼眼效果
问题描述
我正在构建这个 raycaster 作为学校项目,我只是不知道如何修复这个鱼眼效果,我已经做了多次数学,查了一下,似乎没有任何效果,下面是一个效果示例:
现在,我正在使用公式,dist *= Math.cos(r.angle - player.angle);
其中 dist 是光线长度,r 是光线。如上所示,这种清晰的剂量可以完全发挥作用,但它绝对会破坏效果,没有这个它会一团糟,仍然......我现在所拥有的不是我希望的效果,任何人都可以帮助我。
谢谢大家!
如果您需要完整的代码(我会建议在全屏或单独的选项卡中操作,因为否则它确实有效):
var c = document.getElementById('c');
var ctx = c.getContext('2d');
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;
var mapTileSize = [];
var walls = [];
var FOV = Math.PI/180 * 90
var map = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ],
]
var COLORS = {
tile:"grey",
none:"white",
clear:"lightgrey",
wallx:"red",
wally:"blue",
floor:"green",
sky:"blue",
threeDwally: "#d1d1d1",
threeDwallx: "#bababa"
}
var player = {
x: null,
y: null,
angle: 0,
rotationDirection: 0,
moveDirection: 0,
moveSpeed: 1,
rotationSpeed: 0.03,
}
function isUndefined(_arr, _index1, _index2) {
try {
return _arr[_index1][_index2] == undefined;
} catch (e) {
return true;
}
}
function genWalls(arr,tilesize){
var walls = [];
for (y = 0; y < arr.length; y++) {
for (x = 0; x < arr.length; x++) {
// _
// wall: ^
if (!isUndefined(arr, y - 1, x)) {
if (arr[y - 1][x] == 0 && arr[y][x] == 1) {
var wall = {
x1: x * tilesize,
y1: y * tilesize,
x2: x * tilesize + tilesize,
y2: y * tilesize,
axis: "x"
}
walls.push(wall)
}
} else {
var wall = {
x1: x * tilesize,
y1: y * tilesize + 1,
x2: x * tilesize + tilesize,
y2: y * tilesize + 1,
axis: "x"
}
walls.push(wall)
}
// wall: |<
if (!isUndefined(arr, y, x - 1)) {
if (arr[y][x - 1] == 0 && arr[y][x] == 1) {
var wall = {
x1: x * tilesize,
y1: y * tilesize,
x2: x * tilesize,
y2: y * tilesize + tilesize,
axis: "y"
}
walls.push(wall)
}
} else {
var wall = {
x1: x * tilesize + 1,
y1: y * tilesize,
x2: x * tilesize + 1,
y2: y * tilesize + tilesize,
axis: "y"
}
walls.push(wall)
}
// wall: _
if (!isUndefined(arr, y + 1, x)) {
if (arr[y + 1][x] == 0 && arr[y][x] == 1) {
var wall = {
x1: x * tilesize,
y1: y * tilesize + tilesize,
x2: x * tilesize + tilesize,
y2: y * tilesize + tilesize,
axis : "x"
}
walls.push(wall)
}
} else {
var wall = {
x1: x * tilesize,
y1: y * tilesize + tilesize - 1,
x2: x * tilesize + tilesize,
y2: y * tilesize + tilesize - 1,
axis : "x"
}
walls.push(wall)
}
// wall: >|
if (!isUndefined(arr, y, x + 1)) {
if (arr[y][x + 1] == 0 && arr[y][x] == 1) {
var wall = {
x1: x * tilesize + tilesize,
y1: y * tilesize,
x2: x * tilesize + tilesize,
y2: y * tilesize + tilesize,
axis : "y"
}
walls.push(wall)
}
} else {
var wall = {
x1: x * tilesize + tilesize - 1,
y1: y * tilesize,
x2: x * tilesize + tilesize - 1,
y2: y * tilesize + tilesize,
axis : "y"
}
walls.push(wall)
}
}
}
return walls;
}
function drawMinimap(offX,offY,size,rays){
mapTileSize = size/map.length;
for(y=0; y<map.length; y++){
for(x=0; x<map[y].length; x++){
switch(map[y][x]){
case 1:
ctx.fillStyle = COLORS.tile
break;
case 0:
ctx.fillStyle = COLORS.none
break;
}
ctx.fillRect(x * mapTileSize + offX, y * mapTileSize + offY, mapTileSize, mapTileSize)
}
}
//uncomment for wall render
/*
walls.forEach(wall => {
ctx.lineWidth = 2;
ctx.beginPath()
if(wall.axis == "x"){ctx.strokeStyle = COLORS.wallx;}else{ctx.strokeStyle = COLORS.wally;}
ctx.moveTo(wall.x1 + offX,wall.y1 + offY);
ctx.lineTo(wall.x2 + offX,wall.y2 + offY);
ctx.stroke();
ctx.lineWidth = 1;
})
*/
//drawing player
if(player.x == null || player.y == null){player.x = 1.5 * mapTileSize; player.y = 1.5 * mapTileSize}
ctx.beginPath();
ctx.arc(player.x + offX, player.y + offY, 5, 0, Math.PI * 2);
ctx.strokeStyle = COLORS.player;
ctx.stroke();
//uncomment for marker
/*
ctx.beginPath();
ctx.moveTo(player.x + offX, player.y + offY);
ctx.lineTo(player.x + 20 * Math.cos(player.angle) + offX, player.y + 20 * Math.sin(player.angle) + offY)
ctx.strokeStyle = COLORS.player;
ctx.stroke();
*/
//rendering rays
rays.forEach(r=>{
ctx.beginPath();
ctx.moveTo(r.x1 + offX, r.y1 + offY);
ctx.lineTo(r.x2 + offX, r.y2 + offY)
if(r.axis == "x"){ctx.strokeStyle = COLORS.wallx;}else{ctx.strokeStyle = COLORS.wally;}
ctx.stroke();
})
}
function updatePlayer() {
player.angle += player.rotationDirection * player.rotationSpeed;
var newY = player.y + player.moveDirection * (player.moveSpeed) * Math.sin(player.angle);
var newX = player.x + player.moveDirection * (player.moveSpeed) * Math.cos(player.angle);
if(typeof mapTileSize !== 'undefined'){
tileVal = map[Math.floor(newY/mapTileSize)][Math.floor(newX/mapTileSize)]
if (tileVal != 1) {
player.x = newX;
player.y = newY;
}
}
}
function distance(x1,y1,x2,y2) {
return Math.sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) )
}
function change(input,input_start,input_end,output_start,output_end){
return output_start + ((output_end - output_start) / (input_end - input_start)) * (input - input_start)
}
function castRays(){
var rays = [];
for (rayAngle = player.angle - FOV/2; rayAngle < player.angle + FOV/2; rayAngle += Math.PI/180){
var orginX = player.x;
var orginY = player.y;
collidedPositions = [];
walls.forEach(w=>{
//#region Math
x3 = orginX;
x4 = orginX + Math.cos(rayAngle);
y3 = orginY;
y4 = orginY + Math.sin(rayAngle);
x1 = w.x1;
x2 = w.x2;
y1 = w.y1;
y2 = w.y2;
denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom
if (t > 0 && t < 1 && u > 0) {
collidedPositions.push({colY: y1 + t * y2 - t*y1,colX: x1 + t * x2 - t*x1,wall:w})
}
//#endregion
})
var shortestPosition = null;
collidedPositions.forEach(p => {
if(shortestPosition == null){
shortestPosition = p;
}
if(distance(orginX,orginY,p.colX,p.colY) < distance(orginX,orginY,shortestPosition.colX,shortestPosition.colY)){
shortestPosition = p;
}
})
var ray = {
x1:orginX,
y1:orginY,
x2:shortestPosition.colX,
y2:shortestPosition.colY,
axis:shortestPosition.wall.axis,
angle:rayAngle,
length:distance(orginX,orginY,shortestPosition.colX,shortestPosition.colY),
}
rays.push(ray)
}
return rays;
}
function draw3dRays(rayList){
segmentWidth = w / (FOV * 180/Math.PI)
rayList.forEach((r,i) => {
dist = r.length;
dist *= Math.cos(r.angle - player.angle);
height = change(dist,0,h/2,h-300,0);
if(r.axis == "x"){ctx.fillStyle = COLORS.threeDwallx;}else{ctx.fillStyle = COLORS.threeDwally;}
ctx.fillRect(i*segmentWidth,h/2 - height/2,segmentWidth+ 1,height)
ctx.fillStyle = COLORS.floor;
ctx.fillRect(i*segmentWidth,h/2 + height/2,segmentWidth+ 1,height)
ctx.fillStyle = COLORS.sky;
ctx.fillRect(i*segmentWidth,0,segmentWidth+ 1,h/2 - height/2)
})
}
function setup(){
drawMinimap(10,10,300,[]);
walls = genWalls(map,mapTileSize)
requestAnimationFrame(loop)
}
function loop(){
ctx.fillStyle = COLORS.clear;
ctx.fillRect(0,0,w,h);
updatePlayer();
rays = castRays()
draw3dRays(rays)
drawMinimap(10,10,300,rays);
requestAnimationFrame(loop)
}
document.onkeydown = (e) => {
switch (e.key) {
case "w":
player.moveDirection = 1;
break;
case "a":
player.rotationDirection = -1;
break;
case "s":
player.moveDirection = -1;
break;
case "d":
player.rotationDirection = 1;
break;
}
}
document.onkeyup = (e) => {
switch (e.key) {
case "w":
player.moveDirection = 0;
break;
case "a":
player.rotationDirection = 0;
break;
case "s":
player.moveDirection = 0;
break;
case "d":
player.rotationDirection = 0;
break;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Raycasting</title>
</head>
<body onload="setup()"style="margin:0px;overflow:hidden;">
<canvas id="c"></canvas>
<script src="main.js"></script>
</body>
</html>
解决方案
推荐阅读
- javascript - 如何启用此javascript中默认选择的第一个选项卡?
- sql - 从文件名中提取数据并使用存储过程将其存储在表中
- asciidoc - 您如何为参考书目链接显示圆括号而不是方括号?
- android-studio - RecyclerView 中每个按钮的 TimePicker
- typescript - Angular 5 Typescript 编辑文档预填充文本无法正确保存
- python-3.x - 在scrapy教程的第一步之后出错
- php - 不使用 foreach 无法获取明文
- node.js - 在 UI 和 AWS 服务之间引入 node.js 层
- laravel - Laravel webpack 使用一个简单的 jquery 插件
- r - 拆分没有分隔符和不均匀长度的值