Architecture & Engineering

Non-Euclidean Spatial Architecture: Viewport Portal Warping, Relative Velocity Transformations & Seamless Crossing

By DopaBrain Spatial Geometry & Engine Architecture Team • Published 2026-10-01

In puzzle mechanics, impossible architecture, and sci-fi FPS arenas inspired by Portal, Antichamber, and Splitgate, non-Euclidean portals captivate players. Looking through a doorway on one side of a map and seeing a live, interactive 3D window into a completely different coordinate space—then stepping through it with zero screen fade, loading delay, or velocity hiccup—creates genuine architectural wonder.

However, naive teleportation in Roblox (such as setting HumanoidRootPart.CFrame upon touching a Part) produces severe camera snaps, destroys linear momentum, clips characters through world geometry, and provides no visual preview. In this master technical guide, we build a production-grade seamless portal framework in Luau. We derive relative CFrame coordinate mapping matrices, render real-time virtual camera perspectives using ViewportFrames and EditableImages, transpose linear/angular velocity vectors instantaneously across portal thresholds, and handle sub-stud collision handoffs.

1. The Teleportation Flaw: Why Naive CFrame Warps Break Immersion

Standard Roblox teleportation breaks spatial immersion across multiple physical and visual dimensions:

2. The Mathematical Foundation: Relative CFrame Matrices & Velocity Transposition

Portals function as coordinate space transformers connecting Portal A (entry) and Portal B (exit):

3. Complete Seamless Portal Controller Luau Implementation

Below is a complete, modular Luau portal engine running in RunService.RenderStepped for frame-synchronized perspective projection and crossing detection:

SeamlessPortalEngine.luau (Spatial Transformation & Crossing Core)
--!strict
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")

export type PortalPair = {
    PortalA: BasePart,
    PortalB: BasePart,
    Width: number,
    Height: number,
    ViewportA: ViewportFrame?,
    ViewportB: ViewportFrame?,
}

local PortalEngine = {}
PortalEngine.__index = PortalEngine

function PortalEngine.new(portalA: BasePart, portalB: BasePart, width: number, height: number)
    local self = setmetatable({}, PortalEngine)
    self.PortalA = portalA
    self.PortalB = portalB
    self.Width = width
    self.Height = height
    self.LastPositions = {} :: {[Model]: Vector3}
    return self
end

function PortalEngine:GetTransformedCFrame(inputCF: CFrame, fromPortal: BasePart, toPortal: BasePart): CFrame
    -- Flip 180 degrees around Y axis to exit pointing outward from target portal
    local relative = fromPortal.CFrame:ToObjectSpace(inputCF)
    local flipped = CFrame.Angles(0, math.pi, 0) * relative
    return toPortal.CFrame:ToWorldSpace(flipped)
end

function PortalEngine:GetTransformedVelocity(velocity: Vector3, fromPortal: BasePart, toPortal: BasePart): Vector3
    local localVel = fromPortal.CFrame:VectorToObjectSpace(velocity)
    -- Invert X and Z to match flipped 180 degree exit
    local flippedVel = Vector3.new(-localVel.X, localVel.Y, -localVel.Z)
    return toPortal.CFrame:VectorToWorldSpace(flippedVel)
end

function PortalEngine:CheckCrossing(character: Model, fromPortal: BasePart, toPortal: BasePart)
    local root = character:FindFirstChild("HumanoidRootPart") :: BasePart?
    if not root then return end

    local currentPos = root.Position
    local prevPos = self.LastPositions[character] or currentPos
    self.LastPositions[character] = currentPos

    local portalCF = fromPortal.CFrame
    local normal = portalCF.LookVector

    local prevDist = (prevPos - portalCF.Position):Dot(normal)
    local currDist = (currentPos - portalCF.Position):Dot(normal)

    -- Detect crossing from front (positive dist) to back (negative dist)
    if prevDist > 0 and currDist <= 0 then
        -- Verify crossing occurs within portal bounding rectangular aperture
        local localPos = portalCF:PointToObjectSpace(currentPos)
        if math.abs(localPos.X) <= self.Width * 0.5 and math.abs(localPos.Y) <= self.Height * 0.5 then
            -- Atomic transposition of position, velocity, and camera
            local newCF = self:GetTransformedCFrame(root.CFrame, fromPortal, toPortal)
            local newVel = self:GetTransformedVelocity(root.AssemblyLinearVelocity, fromPortal, toPortal)

            root.CFrame = newCF
            root.AssemblyLinearVelocity = newVel
            self.LastPositions[character] = newCF.Position

            local camera = Workspace.CurrentCamera
            if camera and character == Players.LocalPlayer.Character then
                camera.CFrame = self:GetTransformedCFrame(camera.CFrame, fromPortal, toPortal)
            end
        end
    end
end

return PortalEngine

4. Rendering Techniques: ViewportFrames vs Modern EditableImage Shaders

Simulating a live window into another part of the game world requires innovative rendering strategies in Roblox:

5. Collision Handoffs, Sound Propagation & Production Tuning

Ensuring players, projectiles, and vehicles transition smoothly without edge-snagging:

Frequently Asked Questions

Why do characters snag or collide with the wall when walking through a portal?

Humanoid hitboxes have volume. Even if the portal aperture is open, an arm or shoulder can graze the wall edge. To prevent snagging, assign the player to a temporary NoCollision collision group with the doorway wall for 0.1 seconds during portal transition.

How do you preserve camera orientation so the screen doesn't snap abruptly?

The camera must undergo the exact same relative coordinate transformation matrix as the character. By taking Camera.CFrame relative to Portal A, flipping it 180 degrees, and mapping it relative to Portal B, the player's view smoothly continues along their visual trajectory.

Can ViewportFrames render animated characters and particles on the other side?

ViewportFrames support rigged Humanoids and Motor6D animations, but do not natively render ParticleEmitters, Beams, or Terrain. To simulate terrain, convert surrounding terrain chunks into low-poly MeshParts placed inside the ViewportFrame.

What happens if a player places two portals facing each other to create an infinite loop?

In physics, falling between two opposing vertical portals creates continuous gravitational acceleration until terminal velocity is reached. For visual rendering, limit recursive rendering to a maximum recursion depth of 2 or 3 passes to prevent rendering stalls.

Explore More Interactive Tests & Guides

Discover personalized cognitive assessments, stress evaluations, and game psychology tools on DopaBrain.

Go to Portal Hub