Skip to content

Question: how could I show layer always (regardless of altitude)? #158

Description

@heathhenley

TL;DR - I want to make things on my custom layer that are below map in altitude always visible, haven't figured out the right way to do it.

I'm working on drawing a r3f scene on a maplibre map. I'm drawing things below the map (at negative altitude - though my canvas is a 0), but I'm having trouble understanding how to control their display. For the use case I'm working on, they should not be occluded (even though they are below). They seem to be or not be depending on the zoom / pitch or with minor rotations in the camera angle.

This doesn't seem to depend on any gl depth testing, depth writing, or render order settings on my r3f components that I've tried, and it's happening with overlay mode on or not. I think I can see the same behavior in the sun example. Eg, if I understand correctly the near part of the loop in the video is always under the map, but wiggling the camera just a bit changes if it's visible or not.

Screen.Recording.2025-09-17.144322.mp4

If anyone an provide any help with understanding what is happening there it would be greatly appreciated. I tried dumping camera settings each frame and I don't see anything obviously different between the occluded / clipped / culled positions and not.

If I move the whole thing up so the that the lowest thing in my canvas is at an altitude >= 0, it never does this, but I don't think I can make that set up work.

I know there's probably something trivially simple I just don't understand yet, I would welcome any suggestions.

Activity

  1. changed the title [-]Question: how could I show layer always (regardless of altitude)[/-] [+]Question: how could I show layer always (regardless of altitude)?[/+] on Sep 18, 2025
  2. heathhenley commented on Sep 18, 2025

    @heathhenley
    Author

    Here's a minimal sandbox: https://codesandbox.io/p/sandbox/6432yj

    I would like to know to make sure the blue box is always visible no matter the camera angle... 🤔

    20250918-2016-37.1421325.mp4
  3. heathhenley commented on Sep 22, 2025

    @heathhenley
    Author

    This seemed to be because of the far plane of the view port that maplibre-gl uses. I found a way to hack in new near/far values to make sure it's not clipped. Very much a hack and only relevant to maplibre-gl but as a POC to see how it functionally can be done it looks like this:

    diff --git a/src/core/canvas-in-layer/use-render.ts b/src/core/canvas-in-layer/use-render.ts
    index 6988751..5b0a039 100644
    --- a/src/core/canvas-in-layer/use-render.ts
    +++ b/src/core/canvas-in-layer/use-render.ts
    @@ -1,5 +1,5 @@
     import { RootState } from "@react-three/fiber";
    -import { Matrix4Tuple, PerspectiveCamera } from "three";
    +import { Matrix4, Matrix4Tuple, PerspectiveCamera } from "three";
     import { UseBoundStore } from "zustand";
     import { MapInstance } from "../generic-map";
     import { syncCamera } from "../sync-camera";
    @@ -15,13 +15,39 @@ export function useRender({
       frameloop?: 'always' | 'demand',
       r3m: R3M
     }) {
    +
    +  const newNear = 0.01;
    +  const newFar = 1e10;
    +
    +  const adjustNearFar = (pvOriginal: Matrix4, pOriginal: Matrix4, near: number, far: number) => {        
    +    const pv = pvOriginal.clone();
    +    const pInv = pOriginal.clone().invert();
    +    // hack new near and far
    +    const newP = pOriginal.clone();
    +    newP.elements[10] = (far + near) / (near - far);
    +    newP.elements[14] = (2 * far * near) / (near - far);
    +
    +    // re-multiply by v
    +    const v = pInv.multiply(pv);
    +    const newPV = newP.multiply(v);
    +    return newPV;
    +  }
    +
    +
       const render = useFunction((_gl: WebGL2RenderingContext, projViewMx: number[] | {defaultProjectionData: {mainMatrix: Record<string, number>}}) => {
    -    const pVMx = 'defaultProjectionData' in projViewMx ? Object.values(projViewMx.defaultProjectionData.mainMatrix) : projViewMx;
    -    r3m.viewProjMx.splice(0, 16, ...pVMx)
    +    //const pVMx = 'defaultProjectionData' in projViewMx ? Object.values(projViewMx.defaultProjectionData.mainMatrix) : projViewMx;
    +    const pv = adjustNearFar(
    +      new Matrix4().fromArray(projViewMx.defaultProjectionData.mainMatrix),
    +      new Matrix4().fromArray(projViewMx.projectionMatrix),
    +      newNear,
    +      newFar
    +    );
    +    //console.log('pv', pv.elements);
    +    r3m.viewProjMx.splice(0, 16, ...pv.elements);
         const state = useThree.getState();
         const camera = state.camera as PerspectiveCamera;
         const {gl, advance} = state;
    -    syncCamera(camera as PerspectiveCamera, origin, pVMx as Matrix4Tuple);
    +    syncCamera(camera as PerspectiveCamera, origin, pv.elements);
         gl.resetState();
         advance(Date.now() * 0.001, true);
         if (!frameloop || frameloop === 'always') map.triggerRepaint();

    Then things are no longer culled inconsistently based on zoom. I'm not sure if there's already a way that this is exposed so that I could do the near/far hack in client code instead? Obviously that would be better than modifying r3m. Happy to contribute though if you have some guidance on how / what should be exposed to either make it possible via client code or to add some option to override near/far. I haven't looked into what would be needed for it work with mapbox.

    Ex:

    20250922-2000-17.0675060.mp4
  4. heathhenley commented on Sep 22, 2025

    @heathhenley
    Author

    Probably not ideal for performance but I found you can override them on loading the map using map.transform.overrideNearFarZ

  5. Mhamad6000 commented on Oct 4, 2025

    @Mhamad6000

    i have same issue how did you solve this ?

  6. heathhenley commented on Oct 4, 2025

    @heathhenley
    Author

    @Mhamad6000 In raw three.js you can use this approach maplibre/maplibre-gl-js#6443

    Otherwise, I'm calling map.transform.overrideNearFarZ with a huge value for far in the onLoad hook on the Map component.

    Something like:

     <Map
      onLoad={(e: MapEvent) => {
        const map = e.target;
          map.transform.overrideNearFarZ(0.1, 1e13);
        }}    
      // other props
    >
    
    ... canvas child etc
    
    </Map>
  7. Mhamad6000 commented on Oct 5, 2025

    @Mhamad6000

    @Mhamad6000 In raw three.js you can use this approach maplibre/maplibre-gl-js#6443

    Otherwise, I'm calling map.transform.overrideNearFarZ with a huge value for far in the onLoad hook on the Map component.

    Something like:

    <Map
    onLoad={(e: MapEvent) => {
    const map = e.target;
    map.transform.overrideNearFarZ(0.1, 1e13);
    }}
    // other props

    ... canvas child etc

    Thanks for the suggestion! I tried that code, but when I zoom out, the entire map becomes invisible. It seems like setting a very large far value causes rendering issues.

  8. heathhenley commented on Oct 5, 2025

    @heathhenley
    Author

    Yeah you'll probably have to dig in then for your use case. If it disappears when far away, maybe your value isn't actually large enough - you could probably find a way to log the defaults (without the override) to see what they are at different pitches / zooms.

    Ideally, you wouldn't override them on maplibre's render, only when when setting the values for the three.js render (the linked issue is an example of that). It's easy to do with raw custom layer (like the in maplibre-gl three.js example), but I didn't see an easy way to hook into only that part on react-three-map - unless I missed it it's not exposed. One hacky way is above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions