Определить ультрафиолетовое воздействие на стену в двигателе полигонального лучевого - PullRequest
0 голосов
/ 05 марта 2020

Чтобы узнать что-то новое, я пишу 3D-игру с лучевыми лучами в Javascript.

Я уже изучил превосходное руководство Javidx9 по YT; https://www.youtube.com/watch?v=xW8skO7MFYw&t=16s

А также;

Учебное пособие по компьютерной графике Лоде https://lodev.org/cgtutor/raycasting.html

Учебник по созданию лучей для разработки игр И другие цели Ф. Пермади https://permadi.com/1996/05/ray-casting-tutorial-table-of-contents/

Эти уроки имеют один аспект - все их карты основаны на сетке. Это облегчает текстурирование стен: я sh.

Чтобы сделать еще один шаг вперед go, мой raycaster вместо этого работает с полигональной картой секторов - поэтому мое тестирование лучей / стен выполняется с помощью линии алгоритм пересечения, который я украл из 'net.

. Это делает его достаточно отличным от приведенных выше руководств, так что теперь я не уверен, как go насчет добавления текстур, в частности; получая U из UV-координат, которые мне нужно определить для точки, которую я ударил по краю (стене).

Я использую Canvas drawImage, так как он работает быстрее, чем рисование объектов попиксельно и может сделайте необходимое масштабирование для меня. Это делает «верхнюю» V-координату очень легкой - она ​​всегда равна 0. У меня теперь горизонтальные полосатые текстурированные стены: D

Что касается U-координаты; Я думаю, что я должен преобразовать свою точку пересечения со стеной из мирового пространства в «пространство стены», т.е. мне нужно знать, как далеко вдоль стены я ударил (середина была бы 0,5). Это даст мне U, да?

Я вполне уверен, что скоординированная нормализация к стене будет хорошей отправной точкой. Я, вероятно, в конечном итоге получу растянутые текстуры, но думаю, что справлюсь с некоторым разделением по ширине стены.

Вот класс и метод __RenderScreenColumn, который отображает один столбец экрана. Он вызывается для каждого столбца экрана. Там есть дополнительные вещи для обработки рендеринга соседних секторов карты, но я думаю, что это не имеет отношения к этой проблеме.

Мне кажется, что эта проблема может быть решена с помощью матричной проекции, как в «реальном» 3D движок. Однако они мне пока не нужны (я вращаюсь только в 2D; это 2D-движок), поэтому предпочел бы избегать их, если это возможно.

import Canvas from "../Engine/Canvas.js";
import V2 from "../Engine/V2.js";
import Polygon from "../Engine/Polygon.js";

export default class GameView3D {

    OnNewGame(_game) {

        this.__game = _game;
        this.__ctx = Canvas.Create(this.__game.camera.screen);

        // The 'unit' height of walls.
        this.__wallScale = this.__game.camera.screen[1];
    }

    OnUpdate(_deltaTime, _fps) {

        // Don't draw if the game is not ready or the player is out of map.
        if(!this.__game || this.__game.localPlayer.sectorIndex < 0) return;

        this.__ctx.fillStyle = "#000";
        this.__ctx.fillRect(0, 0, this.__game.camera.screen[0], this.__game.camera.screen[1]);

        // Draw map.

        for(let index = 0; index < this.__game.camera.screen[0]; index += this.__game.config.castingResolution[0])
            this.__RenderScreenColumn(index, this.__game.localPlayer.sectorIndex, 0);
    }

    /*
    Draw a sector, screen-column-by-screen-column.
    @param {int} _column The screen-column we're rendering.
    @param {int} _sectorIndex The index into map sectors, identifying the sector we're casting from. Initially this will be the camera's sector.
    @param {float} _rayLength The length of the ray from the camera at which to start casting. Initially 0, this will be incremented as we iterate through the ray.
    @param {[_renderedSectorIndices]} This is passed recursively by the method in order to track which sectors do not need to be re-drawn at this column.
    */
    __RenderScreenColumn(_column, _sectorIndex, _rayLength, _renderedSectorIndices = []) {

        // Keep a frame-by-frame record of which sectors we've drawn this column; so we don't try to draw them more than once.
        _renderedSectorIndices.push(_sectorIndex);

        let sector = this.__game.map.sectors[_sectorIndex],

            // We 're flipping the X (mirroring the output) otherwise it does not match the top down view.
            x = this.__game.camera.screen[0] - _column,

            // Create a ray from the camera pointing in to the scene.
            rayAngle = (this.__game.camera.transform.rotation - this.__game.camera.fovHalf) + (_column / this.__game.camera.screen[0]) * this.__game.camera.fov,
            rayDirection = [ Math.sin(rayAngle), Math.cos(rayAngle) ],
            rayLength = _rayLength;

        // Scan forward along the ray.
        while(rayLength < this.__game.camera.depth) {

            let lastTestPoint = V2.Add(this.__game.camera.transform.position, V2.Mul(rayDirection, rayLength));

            rayLength += this.__game.config.castingResolution[1];

            // The current test point of the ray.
            let testPoint = V2.Add(this.__game.camera.transform.position, V2.Mul(rayDirection, rayLength)),

                // TODO!
                fisheyeCorrectedLength = rayLength;// * rayDirection[0];

            // TODO; Have we hit a floor?
            // TODO; Have we hit a sprite?
            // At some point we should record the range of pixel rows we've already drawn this column.
            // Then when drawing things behind things, we dont draw pixels in those already-drawn ranges.

            // Have we hit a wall?
            // As we test, we'll hit or more likely cross a wall.
            // This will store the exact intersection between our casting ray and the wall. Or null if there wasn't an intersection.
            let intersectionPoint = null,

                // If an intersection was found, this is the index of the edge it was found on.
                edgeIndex = null;

            // Find the intersection point of where we're looking and one of the edges in our sector.
            // If we find an intersection, that's the edge/wall we're looking at.
            for(edgeIndex = 0; edgeIndex < sector.edges.length; edgeIndex++) {

                // Test intersection point between wall and a line from camera to the test point.
                intersectionPoint = Polygon.LineIntersect([ lastTestPoint, testPoint ], sector.edges[edgeIndex]);
                if(intersectionPoint) break;
            }

            // No wall, continue to next depth.
            if(!intersectionPoint) continue;

            // We've hit a wall.
            // However, if it is a portal, draw the sector of which it views instead of the wall. It's not really there :D

            let neighbourIndex = sector.portalIndices[edgeIndex];

            if(neighbourIndex > -1) {

                // NB. At some point this is about where we might add in ceiling height?

                // If we've already drawn this neighbour sector, break so we don't draw anything else this column.
                if(_renderedSectorIndices.indexOf(neighbourIndex) > -1) break;

                // Draw the neighbour sector's column through the portal, then break.
                this.__RenderScreenColumn(_column, neighbourIndex, rayLength, _renderedSectorIndices);

                // NB. At some point this is about where we might add in floor height?
                break;
            }

            // The edge with which we've tested against.
            let edge = sector.edges[edgeIndex],

                // The height of the edge, adjusted for perspective due to distance.
                wallHeight = this.__wallScale / fisheyeCorrectedLength,
                wallHeightHalf = wallHeight / 2,

                // The top and bottom positions of the wall.
                wallTop = this.__game.camera.screenHalf[1] - wallHeightHalf,
                wallBottom = wallTop + wallHeight,

                // The top and bottom positions of the wall, clipped to the screen.
                wallClipTop = wallTop < 0 ? 0 : wallTop,
                wallClipBottom = wallBottom > this.__game.camera.screen[1] ? this.__game.camera.screen[1] : wallBottom,
                wallClipHeight = wallClipBottom - wallClipTop;

            // Draw the wall.

            this.__ctx.fillStyle = "#ccc";
            this.__ctx.fillRect(x, wallClipTop, 1, wallClipHeight);

            // Texturing.
            // TODO; Store texure index in map file somewhere so we can pick different ones. For now, one texture for every wall.

            let texture = this.__game.textures[0],
                edgeLength = V2.Distance(edge[0], edge[1]),

                // These should be normalized.
                uv = [

                    0,  // U: Need to figure out where on the edge we hit, 0 - 1?
                    0   // V: We're always going to draw from the top of the texture; drawImage will handle scaling for us! And seemingly cheaply too.
                ];

            this.__ctx.drawImage(

                texture.image,              // image

                uv[0] * texture.size[0],    // sourceX
                uv[1] * texture.size[1],    // sourceY
                1,                          // sourceW
                texture.size[1],            // sourceH

                x,                          // destX
                wallClipTop,                // destY
                1,                          // destW
                wallClipHeight              // destH
            );

            // No need to draw anything else for this column.
            // ... for now. Perhaps there will be ceiling/floor code here? Not sure yet.
            break;
        }
    }
};

screenshot showing 2D top down view and 3D raycasted view wall texture

TLDR;

Как найти ультрафиолетовое излучение "плоскости", пораженной лучом в движке полигонального вещания?

Или

Учитывая две пересекающиеся линии с результирующей точкой пересечения, как мне "нормализовать" эту точку к одному из этих ребер, чтобы определить, как далеко вдоль этого ребра мы пересеклись?

Спасибо очень за ваше время и помощь.

...