mirror of
https://github.com/raysan5/raylib.git
synced 2026-09-03 04:43:34 +00:00
243 lines
11 KiB
C
243 lines
11 KiB
C
/*******************************************************************************************
|
|
*
|
|
* raylib [textures] example - portal window
|
|
*
|
|
* Example demonstrates rendering a second scene to a texture and projecting it onto a
|
|
* quad to create the illusion of a portal window looking into another place. The second
|
|
* scene is rendered with an off-axis ("oblique frustum") projection matched to the
|
|
* viewer's actual position relative to the window, so the illusion holds up correctly
|
|
* as the player moves and looks around, rather than only looking right from one spot
|
|
*
|
|
* Example complexity rating: [★★★★] 4/4
|
|
*
|
|
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
|
*
|
|
* Example contributed by PanicTitan (@PanicTitan) and reviewed by Ramon Santamaria (@raysan5)
|
|
*
|
|
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
|
|
* BSD-like license that allows static linking with closed source software
|
|
*
|
|
* Copyright (c) 2025 PanicTitan (@PanicTitan)
|
|
*
|
|
********************************************************************************************/
|
|
|
|
#include "raylib.h"
|
|
|
|
#include "raymath.h"
|
|
#include "rlgl.h"
|
|
|
|
//------------------------------------------------------------------------------------
|
|
// Module Functions Declaration
|
|
//------------------------------------------------------------------------------------
|
|
// Begin portal mode 3D view
|
|
static void BeginPortalMode3D(Vector3 eye, Vector3 bottomLeft, Vector3 bottomRight, Vector3 topLeft, float nearPlane, float farPlane);
|
|
// End portal 3D mode and returns to default 2D orthographic mode
|
|
static void EndPortalMode3D(void);
|
|
|
|
//------------------------------------------------------------------------------------
|
|
// Program main entry point
|
|
//------------------------------------------------------------------------------------
|
|
int main(void)
|
|
{
|
|
// Initialization
|
|
//--------------------------------------------------------------------------------------
|
|
const int screenWidth = 800;
|
|
const int screenHeight = 450;
|
|
|
|
InitWindow(screenWidth, screenHeight, "raylib [textures] example - portal window");
|
|
|
|
// Camera to navigate the "real world" (Dimension A)
|
|
Camera3D camera = { 0 };
|
|
camera.position = (Vector3){ 0.0f, 2.5f, 7.0f };
|
|
camera.target = (Vector3){ 0.0f, 1.8f, 0.0f };
|
|
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
|
|
camera.fovy = 45.0f;
|
|
camera.projection = CAMERA_PERSPECTIVE;
|
|
|
|
// The archway opening, in Dimension A world space (used both to draw the frame and
|
|
// as the window rectangle the oblique projection is built from)
|
|
Vector3 archBottomLeft = { -1.5f, 0.0f, 0.0f };
|
|
Vector3 archBottomRight = { 1.5f, 0.0f, 0.0f };
|
|
Vector3 archTopLeft = { -1.5f, 4.0f, 0.0f };
|
|
|
|
// The archway sits at portalA and looks out onto portalB, far away in world space.
|
|
// Every frame, Dimension B gets rendered to a texture using the same relative eye
|
|
// and window position, shifted by the offset between the two portals
|
|
Vector3 portalA = { 0.0f, 0.0f, 0.0f };
|
|
Vector3 portalB = { 0.0f, 0.0f, -60.0f };
|
|
RenderTexture2D portalView = LoadRenderTexture(480, 640);
|
|
|
|
DisableCursor(); // Lock cursor for first-person free camera controls
|
|
SetTargetFPS(60);
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
// Main game loop
|
|
while (!WindowShouldClose()) // Detect window close button or ESC key
|
|
{
|
|
// Update
|
|
//----------------------------------------------------------------------------------
|
|
UpdateCamera(&camera, CAMERA_FREE);
|
|
float time = (float)GetTime();
|
|
|
|
// Eye and window corners, shifted into Dimension B so they match the player's
|
|
// actual position and viewing angle relative to the archway
|
|
Vector3 offset = Vector3Subtract(portalB, portalA);
|
|
Vector3 eyeInB = Vector3Add(camera.position, offset);
|
|
Vector3 blInB = Vector3Add(archBottomLeft, offset);
|
|
Vector3 brInB = Vector3Add(archBottomRight, offset);
|
|
Vector3 tlInB = Vector3Add(archTopLeft, offset);
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Draw
|
|
//----------------------------------------------------------------------------------
|
|
// Render Dimension B into an offscreen texture, using an oblique projection so
|
|
// its perspective lines up with the archway exactly as the real camera sees it
|
|
BeginTextureMode(portalView);
|
|
|
|
ClearBackground((Color){ 10, 5, 20, 255 });
|
|
|
|
BeginPortalMode3D(eyeInB, blInB, brInB, tlInB, 0.05f, 100.0f);
|
|
|
|
DrawGrid(30, 0.8f);
|
|
|
|
// Floating pulsing core sphere
|
|
Vector3 corePos = Vector3Add(portalB, (Vector3){ 0.0f, 2.0f + sinf(time * 2.5f) * 0.4f, -4.0f });
|
|
DrawSphere(corePos, 1.2f, PURPLE);
|
|
DrawSphereWires(corePos, 1.25f, 16, 16, MAGENTA);
|
|
|
|
// Orbiting cubes
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
float angle = time * 1.5f + i * (PI / 2.0f);
|
|
Vector3 pos = Vector3Add(portalB, (Vector3){
|
|
sinf(angle) * 2.5f,
|
|
2.0f + cosf(time * 3.0f + i) * 0.5f,
|
|
-4.0f + cosf(angle) * 2.5f });
|
|
|
|
DrawCube(pos, 0.5f, 0.5f, 0.5f, LIME);
|
|
DrawCubeWires(pos, 0.52f, 0.52f, 0.52f, DARKGREEN);
|
|
}
|
|
|
|
EndPortalMode3D();
|
|
|
|
EndTextureMode();
|
|
|
|
BeginDrawing();
|
|
|
|
ClearBackground(RAYWHITE);
|
|
|
|
BeginMode3D(camera);
|
|
|
|
DrawGrid(20, 1.0f);
|
|
|
|
// Side pillars
|
|
DrawCube((Vector3){ -3.5f, 2.0f, 0.0f }, 0.8f, 4.0f, 0.8f, DARKGRAY);
|
|
DrawCubeWires((Vector3){ -3.5f, 2.0f, 0.0f }, 0.8f, 4.0f, 0.8f, ORANGE);
|
|
DrawCube((Vector3){ 3.5f, 2.0f, 0.0f }, 0.8f, 4.0f, 0.8f, DARKGRAY);
|
|
DrawCubeWires((Vector3){ 3.5f, 2.0f, 0.0f }, 0.8f, 4.0f, 0.8f, ORANGE);
|
|
|
|
// Golden archway frame and solid base
|
|
DrawCubeWires((Vector3){ 0.0f, 2.0f, 0.0f }, 3.2f, 4.2f, 0.2f, GOLD);
|
|
DrawCube((Vector3){ 0.0f, 0.05f, 0.0f }, 3.4f, 0.1f, 0.6f, MAROON);
|
|
|
|
// Solid backing wall, only ever seen if looking at the archway from behind
|
|
DrawCube((Vector3){ 0.0f, 2.0f, -0.05f }, 3.1f, 4.1f, 0.05f, MAROON);
|
|
|
|
// The portal opening itself: a plain quad textured with the Dimension B
|
|
// render, filling the archway exactly, so nothing "leaks" outside its shape
|
|
rlSetTexture(portalView.texture.id);
|
|
rlBegin(RL_QUADS);
|
|
rlColor4ub(255, 255, 255, 255);
|
|
rlNormal3f(0.0f, 0.0f, 1.0f);
|
|
rlTexCoord2f(0.0f, 0.0f); rlVertex3f(archBottomLeft.x, archBottomLeft.y, archBottomLeft.z);
|
|
rlTexCoord2f(1.0f, 0.0f); rlVertex3f(archBottomRight.x, archBottomRight.y, archBottomRight.z);
|
|
rlTexCoord2f(1.0f, 1.0f); rlVertex3f(archBottomRight.x, 4.0f, archBottomRight.z);
|
|
rlTexCoord2f(0.0f, 1.0f); rlVertex3f(archTopLeft.x, archTopLeft.y, archTopLeft.z);
|
|
rlEnd();
|
|
rlSetTexture(0);
|
|
|
|
EndMode3D();
|
|
|
|
// HUD overlay
|
|
DrawRectangle(15, 15, 520, 100, Fade(BLACK, 0.75f));
|
|
DrawRectangleLines(15, 15, 520, 100, GOLD);
|
|
DrawText("PORTAL WINDOW", 28, 25, 20, GOLD);
|
|
DrawText("Look through the golden arch into Dimension B", 28, 52, 20, RAYWHITE);
|
|
DrawText("Controls: Mouse to look | WASD to move", 28, 80, 20, GRAY);
|
|
|
|
EndDrawing();
|
|
//----------------------------------------------------------------------------------
|
|
}
|
|
|
|
// De-Initialization
|
|
//--------------------------------------------------------------------------------------
|
|
UnloadRenderTexture(portalView);
|
|
|
|
CloseWindow(); // Close window and OpenGL context
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
return 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Module Functions Definition
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Starts a 3D mode using an off-axis ("oblique frustum") projection, built directly from
|
|
// an eye point and 3 corners of a rectangular window, instead of a fovy centered straight
|
|
// ahead of the camera. This is the standard technique for rendering a scene as seen through
|
|
// a window that isn't necessarily faced head-on (also used for multi-monitor and VR
|
|
// rendering) - the key difference from a normal Camera3D is that the frustum is allowed
|
|
// to be asymmetric, so perspective lines through the window line up correctly from any
|
|
// eye position, instead of behaving like a flat image pasted onto the window
|
|
static void BeginPortalMode3D(Vector3 eye, Vector3 bottomLeft, Vector3 bottomRight, Vector3 topLeft, float nearPlane, float farPlane)
|
|
{
|
|
Vector3 right = Vector3Normalize(Vector3Subtract(bottomRight, bottomLeft));
|
|
Vector3 up = Vector3Normalize(Vector3Subtract(topLeft, bottomLeft));
|
|
Vector3 normal = Vector3Normalize(Vector3CrossProduct(right, up));
|
|
|
|
// Vectors from the eye to 3 corners of the window, used to project the window onto
|
|
// the near plane and read off how far it extends left/right/bottom/top of the eye
|
|
Vector3 toBL = Vector3Subtract(bottomLeft, eye);
|
|
Vector3 toBR = Vector3Subtract(bottomRight, eye);
|
|
Vector3 toTL = Vector3Subtract(topLeft, eye);
|
|
|
|
float dist = -Vector3DotProduct(toBL, normal);
|
|
if (dist < 0.01f) dist = 0.01f; // Keep the eye from crossing the window plane
|
|
|
|
float scale = nearPlane/dist;
|
|
|
|
rlDrawRenderBatchActive();
|
|
|
|
rlMatrixMode(RL_PROJECTION);
|
|
rlPushMatrix();
|
|
rlSetMatrixProjection(MatrixFrustum(
|
|
Vector3DotProduct(right, toBL)*scale, Vector3DotProduct(right, toBR)*scale,
|
|
Vector3DotProduct(up, toBL)*scale, Vector3DotProduct(up, toTL)*scale,
|
|
nearPlane, farPlane));
|
|
|
|
// View orientation is fixed to the window's own plane (looking straight through it
|
|
// along its normal), NOT aimed at any target - that's what the asymmetric frustum
|
|
// above is for, and is what lets the eye move off to one side without distorting
|
|
rlMatrixMode(RL_MODELVIEW);
|
|
rlLoadIdentity();
|
|
rlMultMatrixf(MatrixToFloat(MatrixLookAt(eye, Vector3Subtract(eye, normal), up)));
|
|
|
|
rlEnableDepthTest();
|
|
}
|
|
|
|
// End portal 3D mode and returns to default 2D orthographic mode
|
|
// NOTE: Similar implementation to EndMode3D()
|
|
static void EndPortalMode3D(void)
|
|
{
|
|
rlDrawRenderBatchActive(); // Update and draw internal render batch
|
|
|
|
rlMatrixMode(RL_PROJECTION); // Switch to projection matrix
|
|
rlPopMatrix(); // Restore previous matrix (projection) from matrix stack
|
|
|
|
rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix
|
|
rlLoadIdentity(); // Reset current matrix (modelview)
|
|
|
|
rlDisableDepthTest(); // Disable DEPTH_TEST for 2D
|
|
}
|