multiple heights
I considered about very simple situation "If all wall are simply vertical but have several height".
In this situation, I think, we can simply draw all the walls intersecting the ray (with Painter Algorithm like way).
Following code is I tried. (not beautiful code!).
And result image is this:

//
//* I used OpenCV to drawing(cv::Mat and drawing func) and 2D-vector operation(cv::Vec2d)
//
//3D Data : as Height Field.
constexpr int HF_SX = 11;
constexpr int HF_SY = 8;
const int HF[HF_SY][HF_SX] = {
2,2,2,3,5,2,3,0,1,3,3,
2,2,2,3,5,2,1,0,1,4,3,
2,2,1,3,5,2,1,0,1,3,3,
3,0,0,1,2,0,0,0,1,1,2,
2,1,1,1,0,0,0,2,0,1,1,
3,0,0,0,0,1,0,1,0,0,1,
2,0,1,0,1,1,0,0,0,0,0,
0,0,0,0,1,1,1,0,0,0,0,
};
//Camera Location and Direction
const double CamH = 1.75;
const cv::Vec2d CamPos( 5.65, 7.6 );
const cv::Vec2d CamFront( 0, -1 );
const cv::Vec2d CamRight( -CamFront[1], CamFront[0] );
//Rendering Image Size
constexpr int ScrnW =240*2;
constexpr int ScrnH = 180*2;
//Camera Intrinsic Parameters (Pinhole Camera Model)
constexpr double PC_x0 = ScrnW * 0.5;
constexpr double PC_y0 = ScrnH * 0.5;
constexpr double PC_f = 0.75 * ScrnW * 0.5;
//Return_data_type for the function EnumCheckPoint below
struct CheckPoint
{
CheckPoint( const cv::Vec2d &P, bool IsHorizontal ) : Pos(P), IsHorizontalEdge( IsHorizontal ) {}
cv::Vec2d Pos;
bool IsHorizontalEdge;
};
//Enumerate points along a line segment(From - To) where x or y are integers.
//The result is stored in the vector rDst.
void EnumCheckPoint( const cv::Vec2d &From, const cv::Vec2d &To, std::vector<CheckPoint> &rDst )
{
rDst.clear();
const cv::Vec2d d = To - From;
const int MinX = (int)ceil( (std::min)(From[0],To[0]) );
const int MaxX = (int)floor( (std::max)(From[0],To[0]) );
if( MinX<=MaxX )
{
const double dy_per_x = d[1]/d[0];
for( int x=MinX; x<=MaxX; ++x )
{ rDst.emplace_back( cv::Vec2d{ (double)x, From[1]+(x-From[0])*dy_per_x }, false ); }
}
const int MinY = (int)ceil( (std::min)(From[1],To[1]) );
const int MaxY = (int)floor( (std::max)(From[1],To[1]) );
if( MinY <= MaxY )
{
const double dx_per_y = d[0]/d[1];
for( int y=MinY; y<=MaxY; ++y )
{ rDst.emplace_back( cv::Vec2d{ From[0]+(y-From[1])*dx_per_y, (double)y }, true ); }
}
auto Dist = [&From]( const CheckPoint &P )->double
{
auto D = P.Pos - From;
return fabs(D[0]) + fabs(D[1]);
};
std::sort(
rDst.begin(), rDst.end(),
[&Dist]( const CheckPoint &lhs, const CheckPoint &rhs )->bool{ return ( Dist(lhs) < Dist(rhs) ); }
);
}
//Return_data_type for the function Cvt_CPs_to_Walls below
struct Wall
{
Wall( const cv::Vec2d &P, const cv::Vec2d &N, int th, int bh )
: LayHitPos(P), UnitNormal(N), TopHeight(th), BottomHeight(bh) {}
cv::Vec2d LayHitPos;
cv::Vec2d UnitNormal;
int TopHeight;
int BottomHeight;
};
//Create Wall(where the height changes) Data
void Cvt_CPs_to_Walls( const std::vector<CheckPoint> &CPs, std::vector<Wall> &rDstWalls )
{
rDstWalls.clear();
rDstWalls.reserve( CPs.size() );
for( const auto &CP : CPs )
{
int x = (int)CP.Pos[0];
int y = (int)CP.Pos[1];
if( CP.IsHorizontalEdge )
{
if( x<0 || x>=HF_SX )continue;
if( y<=0 || y>=HF_SY )continue;
int Hu = HF[y-1][x];
int Hd = HF[y][x];
if( Hu == Hd )continue;
if( Hu > Hd ){ rDstWalls.emplace_back( CP.Pos, cv::Vec2d{ 0,1 }, Hu, Hd ); }
else{ rDstWalls.emplace_back( CP.Pos, cv::Vec2d{ 0,-1 }, Hd, Hu ); }
}
else
{
if( y<0 || y>=HF_SY )continue;
if( x<=0 || x>=HF_SX )continue;
int Hl = HF[y][x-1];
int Hr = HF[y][x];
if( Hl == Hr )continue;
if( Hl > Hr ){ rDstWalls.emplace_back( CP.Pos, cv::Vec2d{ 1,0 }, Hl, Hr ); }
else{ rDstWalls.emplace_back( CP.Pos, cv::Vec2d{ -1,0 }, Hr, Hl ); }
}
}
}
//Draw the range (x,ybegin) - (x,yend) as Ground Plane which height is GndH
void DrawGndPlane( cv::Mat &Scrn, int x, int ybegin, int yend, double GndH )
{
for( int y=ybegin; y<yend; ++y )
{
double tan = (y - PC_y0) / PC_f;
double depth_of_gnd = (CamH - GndH) / tan;
Scrn.at< cv::Vec3b >( y,x ) = cv::Vec3b( 0, (unsigned char)std::min(255.0, 255/depth_of_gnd), 0);
}
}
//main
int main()
{
//Rendering Image Buffer
cv::Mat Scrn( ScrnH, ScrnW, CV_8UC3 );
Scrn = cv::Scalar( 192, 32, 32 ); //Fill with Bkgnd Color (as Blue Sky)
//------------------------------------
//Ray Casting
std::vector< CheckPoint > CPs; //Working buffer
std::vector<Wall> Walls; //Working buffer
//for all column(x)
for( int x=0; x<Scrn.cols; ++x )
{
//Calculate ray (unit directional vector)
double theta = atan( (x - PC_x0)/PC_f );
cv::Vec2d ray = cos(theta)*CamFront + sin(theta)*CamRight;
//Get the terrain information needed to draw this column(x)
EnumCheckPoint( CamPos, CamPos + ray*15, CPs );
Cvt_CPs_to_Walls( CPs, Walls );
//Rendering
int RenderedTop = Scrn.rows; //(indicate the range y>=RenderedTop is already rendered)
int GndPlaneH = HF[ (int)CamPos[1] ][ (int)CamPos[0] ];
for( const auto &W : Walls )
{
//Calculate this Wall's render y range (y0 - y1)
auto depth = abs( W.LayHitPos[1] - CamPos[1] );
double ry0 = PC_f * (W.TopHeight - CamH) / depth;
double ry1 = PC_f * (CamH - W.BottomHeight) / depth;
int y0 = cvRound( PC_y0 - ry0 );
int y1 = cvRound( PC_y0 + ry1 );
if( y0 >= RenderedTop )continue;
if( W.UnitNormal.dot( ray ) <= 0 )
{//When the wall faces the camera
GndPlaneH = W.BottomHeight;
DrawGndPlane( Scrn, x, y1, RenderedTop, GndPlaneH );
//Draw Wall
y1 = std::min( y1, RenderedTop-1 );
cv::line( Scrn, cv::Point(x,y0), cv::Point(x,y1), cv::Scalar(0,0, std::min(255.0, 1.5*255/depth)) );
}
else
{//Else( when the wall faces away )
GndPlaneH = W.TopHeight;
DrawGndPlane( Scrn, x, std::max(0,y0), RenderedTop, GndPlaneH );
}
RenderedTop = y0;
}
//Draws the ground plane up to the y-coordinate of the horizon if needed.
DrawGndPlane( Scrn, x, cvRound(PC_y0), RenderedTop, GndPlaneH );
}
//Show Rednering Result
cv::imshow( "Result", Scrn );
cv::waitKey();
return 0;
}