10 #include 10^ #include 10 #include 15 16 static const char recipe[] = 15`16 `1: "`01:lidjehfhfhhideiefedefedefekedeiefedefedefejfdeiefedefedefejeeieefed`1:" 15`16 `1: "`01:efedefeiekedefedefedefeiekedefedefedefeiefefedefedefedefeieghfhfhfhm`1:"; 10 10 int main() 10 { 20 using namespace sf; 100 // Create the main window 100 RenderWindow window(VideoMode(3840, 2160), "Hello", Style::Default, ContextSettings(24,0,2)); 101 window.setVerticalSyncEnabled(true); 110 110 // Configure OpenGL features. 110 window.resetGLStates(); // Enables {VERTEX,TEXTURE_COORD,COLOR}_ARRAY, TEXTURE_2D. Disables LIGHTING,CULL_FACE. 110 glDisableClientState(GL_NORMAL_ARRAY); // Disable normals, not used. 110 glEnable(GL_DEPTH_TEST); // SFML disables z-buffer. Re-enable, because we need it. 110 glClearDepth(1.f); 120 120 // Load some textures 120 Texture tx[7]; 121 tx[0].loadFromFile("resources/bottom.jpg"); 121^`122 tx[`0:0`1:1`01:].loadFromFile("resources/top.jpg"); 121^^`122 tx[`0:0`1:2`01:].loadFromFile("resources/left.jpg"); 121^^^`122 tx[`0:0`1:3`01:].loadFromFile("resources/right.jpg"); 121^^^^`122 tx[`0:0`1:4`01:].loadFromFile("resources/back.jpg"); 121^^^^^`122 tx[`0:0`1:5`01:].loadFromFile("resources/front.jpg"); 121^^^^^^ `122 tx[`0:0`1:6`01:].loadFromFile("resources/wall3.jpg"); 123 tx[6].generateMipmap(); 130 130 // Construct the world geometry from axis-aligned cuboids made of triangles. 130 std::vector tri; 140 auto addcuboid = [&](unsigned mask, 141 std::array x, std::array z, std::array y, 141^ std::array c, std::array u, std::array v) 142 { 145 auto ext = [](auto m,unsigned n,unsigned b=1) { return (m >> (n*b)) & ~(~0u << b); }; // extracts bits 143 // Generates: For six vertices, color(rgb), coordinate(xyz) and texture coord(uv). 144`147 std::array p{&c[0],&c[0],&c[0], &x[0],&y[0],&z[0], &u[0],&v[0]`0:,`01:}; 148 // capflag(1 bit), mask(3 bits), X(4 bits), Y(4 bits), Z(4 bits), U(4 bits), V(4 bits) 146`148 for(unsigned m: std::array{0x960339,0xA9F339,0x436039,0x4C6F39,0x406C39,0x4F6339`0:,`01:})`1: // bottom, top, four sides 149 if(std::uint64_t s = (m>>23) * 0b11'000'111 * (~0llu/255); mask & m) 149 for(unsigned n = 0; n < 6*8; ++n) 151 tri.push_back( p[n%8][ext(m, ext(012345444u, n%8, 3)*4 - ext(0123341u, n/8, 3)) << ext(s,n)] ); 150 // 123341 = order of vertices in two triangles; 12345444 = nibble indexes in "m" for each of 8 values 142 }; 130`137 // Part 1: Skybox. Perfect cube.`1: Size is mostly irrelevant, as long as it's farther than the near clipping plane. 137 // "Mostly irrelevant", because its distance from viewer still influences how much fog affects it. 136 // As an easy exercise, test and see what happens if you stretch the cube! 135 // The first three {}s are the X, Y and Z extremes of the cube. 135 // It could be arbitrary rotated around Y axis, but it’s easiest to specify axis-aligned coordinates. 135 // The next one controls the darkness/lightness (three values are provided for top, bottom and cap respectively) 135 // And the last two control the texture offsets: min,max,and max for horizontal surfaces. 131`132 addcuboid(7<<20, {-10,10}, {-10,10}, {-10,10}, { 1, 1, 1}`1:, {0,1,1}, {0,1,1}); 133^^ // Part 2: Floor plane 133^^`134 addcuboid(1<<20, {-30,30}, {-30,30}, {0,1}, {.3,.3,.4}`1:, {0,0,60}, {0,0,60}`01:); 152 // Part 3: Random "buildings" 152 for(int rem=0,p=0,z=-14; z<15; ++z) 152 for(int x=-21; x<21; ++x) 152 { 155 if(!rem--) { rem = recipe[p++] - 'd'; if(rem&8) rem+=414; } // RLE compression, odd/even coding 155 if(float w=.5f, h = (p&1) ? (std::rand() % 2)*.05f : .8f*(4+std::rand()%8); h) // Random height 155 addcuboid(6<<20, {x-w,x+w}, {z-w,z+w}, {0,h}, {.2f+(rand()%1000)*.4e-3f,1,.4f+(h>.1f)}, {0,1,1},{0,h,1}); 152 } 160 glColorPointer(3, GL_FLOAT, 8*sizeof(GLfloat), &tri[0]); 160^ glVertexPointer(3, GL_FLOAT, 8*sizeof(GLfloat), &tri[3]); 160^^ glTexCoordPointer(2, GL_FLOAT, 8*sizeof(GLfloat), &tri[6]); 170 170 // Setup up the view port, the clipping planes, the aspect ratio and the field of vision (FoV) 170 glMatrixMode(GL_PROJECTION); 171 glViewport(0, 0, window.getSize().x, window.getSize().y); 171`172 GLfloat near=.03f, far=50.f, `1:ratio = `01:near * window.getSize().x / window.getSize().y; 172`173 glFrustum(-ratio, ratio`1:, -near, near, near, far); 180 180 // Start game loop 185 float rx=0,ry=1,rz=-.1, mx=0,my=0,mz=-2.5, lx=3.86,ly=-0.29,lz=15.9, aa=0.92,ab=-0.18,ac=-0.35,ad=0.07, fog=4; 185 GLfloat tform[16]{1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}; // Initialize default transformation matrix. 180`181 for(`0:;;`1:std::map keys; window.isOpen() && !keys[Keyboard::Escape]; window.display()`01:) 180 { fprintf(stderr, "lx=%g,ly=%g,lz=%g, aa=%g,ab=%g,ac=%g,ad=%g\n", lx,ly,lz, aa,ab,ac,ad); 190 // Process events 190 for(Event event; window.pollEvent(event); ) 190 switch(event.type) 190 { 195 case Event::Closed: keys[Keyboard::Escape] = true; default: break; 195^ case Event::KeyPressed: keys[event.key.code] = true; break; 196^ case Event::KeyReleased: keys[event.key.code] = false; break; 190 } 198 if(keys[Keyboard::V]) { for(std::size_t p=6*6*8; p0.1)tri[p+4] *= 0.95; fog *= 0.95; } 198 200 // The input scheme is the same as in Descent, the game by Parallax Interactive. 200 // Mouse input is not handled for now. 201 bool up = keys[Keyboard::Up] || keys[Keyboard::Numpad8]; 202 bool down = keys[Keyboard::Down] || keys[Keyboard::Numpad2], alt = keys[Keyboard::LAlt]|| keys[Keyboard::RAlt]; 203^ bool left = keys[Keyboard::Left] || keys[Keyboard::Numpad4], rleft = keys[Keyboard::Q] || keys[Keyboard::Numpad7]; 203^^ bool right = keys[Keyboard::Right]|| keys[Keyboard::Numpad6], rright= keys[Keyboard::E] || keys[Keyboard::Numpad9]; 201 bool fwd = keys[Keyboard::A], sup = keys[Keyboard::Subtract], sleft = keys[Keyboard::Numpad1]; 201 bool back = keys[Keyboard::Z], sdown = keys[Keyboard::Add], sright= keys[Keyboard::Numpad3]; 300 300 // Apply rotation delta with hysteresis: newvalue = input*eagerness + oldvalue*(1-eagerness) 300 rx = rx*.8f + .2f*(up - down) * !alt; 300^ ry = ry*.8f + .2f*(right - left) * !alt; 300^^ rz = rz*.8f + .2f*(rright - rleft); 310 if(float rlen = std::sqrt(rx*rx + ry*ry + rz*rz); rlen > 1e-3f) // Still rotating? 310 { 320 // Create rotation quaternion (q), relative to the current angle that the player is looking towards. 320 float theta = rlen*.03f, c = std::cos(theta*.5f), s = std::sin(theta*.5f)/rlen; 320 auto [qa,qb,qc,qd] = std::array{ c, 320 s*(tform[0]*rx + tform[1]*ry + tform[2]*rz), 320^ s*(tform[4]*rx + tform[5]*ry + tform[6]*rz), 320^^ s*(tform[8]*rx + tform[9]*ry + tform[10]*rz) }; 330 // Update player angle (a) by multiplying it by the rotation quaternion (r) 330`331 `0: `1:std::tie(aa,ab,ac,ad) = std::tuple{`01: qa*aa - qb*ab - qc*ac - qd*ad, 330^ qb*aa + qa*ab + qd*ac - qc*ad, 330^^ qc*aa - qd*ab + qa*ac + qb*ad, 330^^^ qd*aa + qc*ab - qb*ac + qa*ad }; 340 // Recalculate the rotation matrix from the new player angle (a). 341`342`343`344 tform[0] = 1-2*(ac*ac+ad*ad); tform[1] = `01:1-2*(ac*ac+ad*ad);`23: 2*(ab*ac+aa*ad);`0123: tform[2] = `012:1-2*(ac*ac+ad*ad);`0: tform[`3: 2*(ab*ad-aa*ac); 342^`343`344 tform[4] = 2*(ab*ac-aa*ad); tform[5] = `0:1-2*(ac*ac+ad*ad);`12:1-2*(ab*ab+ad*ad);`012: tform[`01:2`2:6`012:] = `01:1-2*(ac*ac+ad*ad);`2: 2*(ac*ad+aa*ab); 342^^`343`344 tform[8] = 2*(ab*ad+aa*ac); tform[`0:1`12:9`012:] = `0:1-2*(ac*ac+ad*ad);`12: 2*(ac*ad-aa*ab);`012: tform[`01:2] `2:10]`012:= `01:1-2*(ac*ac+ad*ad);`2:1-2*(ab*ab+ac*ac); 350 // Note: The above cos() and sin() were the ONLY trigonometric calculations 350 // in this rotation code. And they only control the rate of turning, 350 // not the angle of turning. You could replace them with compile-time 350 // constants and the only downside would be a constant rate of turning 350 // (either on or off). Such is the power of quaternions. 350 // Note: It is possible, due to floating point inaccuracies, for the player 350 // angle to eventually become denormalized (not a unit vector). 350 // The symptoms would be progressively wonkier rotations, I presume. 350 // One can re-normalize it by multiplying aa,bb,cc,dd each with the result 350 // of 1/std::sqrt(aa*aa+bb*bb+cc*cc+dd*dd). I omitted that for brevity. 310 } 400 400 // Apply player movement delta with hysteresis 400 float Mx = (sleft || (alt && left)) - (sright || (alt && right)); 400^ float My = (sdown || (alt && down)) - (sup || (alt && up)); 401 float Mz = fwd - back; 405 float mlen = std::sqrt(Mx*Mx + My*My + Mz*Mz)/0.07; if(mlen < 1e-3f) mlen = 1; 410 // The new movement is relative to the angle that player is looking towards. 410 mx = mx*.9f + .1f*(tform[0]*Mx + tform[1]*My + tform[2]*Mz)/mlen; 410^ my = my*.9f + .1f*(tform[4]*Mx + tform[5]*My + tform[6]*Mz)/mlen; 410^^ mz = mz*.9f + .1f*(tform[8]*Mx + tform[9]*My + tform[10]*Mz)/mlen; 420 // Update player position (l) by the movement vector (m) 420 lx += mx; ly += my; lz += mz; 430 // Note: We don't do any clipping here. Player is free to move through walls and floors. 430 // Adding collision checks is a quite a complex topic, a good example of the 80/20 rule, 430 // especially if you want to do it properly and have the character slide off the surface etc. 430 // So, I neglected that in favor of brevity. 540 540 // Set up fog. 540 glEnable(GL_FOG); 540 glFogi(GL_FOG_MODE, GL_EXP); 540^ glFogfv(GL_FOG_COLOR, &std::array{.5f,.51f,.54f}[0]); 540^^ glFogf(GL_FOG_DENSITY, fog/far); 380 380 // Instruct OpenGL about the view rotation 380 glMatrixMode(GL_MODELVIEW); 380 glLoadMatrixf(tform); 500 500 // Render the skybox without zbuffer. View is still in origo, where the skybox is centered. 500 glClear(GL_DEPTH_BUFFER_BIT); 500 glDepthMask(GL_FALSE); 500 for(unsigned n=0; n<6; ++n) 500 { 510 Texture::bind(&tx[n]); 510 glDrawArrays(GL_TRIANGLES, n*6, 6); 500 } 210 210`520 // `0:Turn zbuffer on`1:After the skybox has been rendered, add the player coordinate & turn zbuffer on 440 glTranslatef(lx, ly, lz); 210 glDepthMask(GL_TRUE); 210 210`521 // Render everything`1: else using a single texture 210 Texture::bind(&tx[6]); 211 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 211^ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 211 glDrawArrays(GL_TRIANGLES, 6*6, tri.size()/8-6*6); 180 } 10 } 10 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1