Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kamil/three js globe custom layer interface #1

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 169 additions & 12 deletions debug/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,180 @@
html, body, #map { height: 100%; }
</style>
</head>

<body>
<div id='map'></div>
<script src="https://unpkg.com/three@0.149.0/build/three.min.js"></script>
<script type="module">
const vertexShader = `
varying vec2 vUv;
varying mat4 vPosition;

<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>
void main() {
vUv = uv;

vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );

#ifdef USE_INSTANCING

gl_Position = projectionMatrix * instanceMatrix * mvPosition;

#endif
}
`
const fragmentShader = `
precision highp float;
uniform sampler2D map;
varying vec2 vUv;

void main() {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`;


const planeSize = 1;
const planeBufferGeometry = new THREE.PlaneBufferGeometry(planeSize, planeSize, 2, 2);

const material =new THREE.ShaderMaterial({
side: THREE.DoubleSide,
depthTest: false,
depthWrite: false,
transparent: true,
vertexShader,
fragmentShader,
});

const mapCenter = mapboxgl.MercatorCoordinate.fromLngLat([0, 0], 0);

const Threejscustomlayer = {
type:"custom",
renderingMode:"3d",
id:"three-js",

onAdd:function (map,gl) {
this.map = map;
const mapCanvas = map.getCanvas();
this.scene = new THREE.Scene();

const { x, y, z } = mapCenter;
this.cameraTransform = new THREE.Matrix4().makeTranslation(x, y, z || 0);

this.camera = new THREE.PerspectiveCamera(45, mapCanvas.width / mapCanvas.height, 0.1, 1000);

this.renderer = new THREE.WebGLRenderer({
canvas: mapCanvas,
context: gl,
});

this.renderer.autoClear = false;

const helper = new THREE.CameraHelper(this.camera);
this.scene.add(helper);

// [-120,40]
const dir = new THREE.Vector3(-864.9573102976779, -838.0647460031414, -499.3833359378987);

//normalize the direction vector (convert to vector of length 1)
dir.normalize();

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 12.5,
center: [-122.4194, 37.7749],
style: 'mapbox://styles/mapbox/streets-v11',
hash: true,
projection: 'globe'
});
const origin = new THREE.Vector3( 0, 0, 0 );
const length = 10000;
const hex = 0xffff00;

const arrowHelper = new THREE.ArrowHelper( dir, origin, length, hex );
this.scene.add( arrowHelper );

},
render:function (gl, matrix) {
const projectionMatrix = new THREE.Matrix4().fromArray(this.map.painter.transform.customLayerMatrix());
if (this.map.transform.projection.name === 'globe') {
const globeProjection = new THREE.Matrix4().fromArray(this.map.painter.transform.globeToMercatorMatrix());
this.camera.projectionMatrix = projectionMatrix.multiply(globeProjection);
}else{
this.camera.projectionMatrix = projectionMatrix.multiply(this.cameraTransform);
}
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
},
}


const mapbox = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/dark-v11',
projection: 'globe',
center: [-105.3913669, 38.8429922],
fadeDuration: 0,
zoom: 2,
attributionControl: false,
maxZoom: 22,
minZoom: 0,
pitchWithRotate: true,
dragRotate: true,
touchZoomRotate: true,
touchPitch: true,
antialias:true,
});

const clearInstancedMesh = () =>{
const meshes = Threejscustomlayer.scene.children.filter(objects=>{
return objects.isInstancedMesh;
})
meshes.forEach(mesh=>mesh.removeFromParent())
}

const createInstancedMesh = () =>{
const quantity = 1;
const mesh = new THREE.InstancedMesh(planeBufferGeometry, material, quantity);
const isGlobe = Threejscustomlayer.map.transform.projection.name === 'globe';

for(let i = 0 ; i < quantity ; i++){
const matrix = new THREE.Matrix4();

const coordinates = [-120, 40];
const ECEFPosition = mapboxgl.LngLat.convert(coordinates).toEcef(0);
const ECEFVectorPosition = new THREE.Vector3(ECEFPosition[0],ECEFPosition[1],ECEFPosition[2]);
const mercatorCoordinates = mapboxgl.MercatorCoordinate.fromLngLat(coordinates, 0);
const {x,y,z} = mercatorCoordinates;
const adjustedMercatorCoords = new THREE.Vector3(x - mapCenter.x, y - mapCenter.y, z || 0 - (mapCenter.z || 0));


const dir = new THREE.Vector3().copy(isGlobe ? ECEFVectorPosition : adjustedMercatorCoords);
dir.normalize();
const quaternion = new THREE.Quaternion();
quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), dir);

if(isGlobe){
matrix.makeRotationFromQuaternion(quaternion);
}

matrix.scale(isGlobe ? new THREE.Vector3(10,10,10) : new THREE.Vector3(0.001,0.001,0.001));
matrix.setPosition(isGlobe ? ECEFVectorPosition : adjustedMercatorCoords);

const color = new THREE.Color("red")

mesh.setMatrixAt(i,matrix);
mesh.setColorAt(i,color);
}

mesh.needsUpdate = true;

Threejscustomlayer.scene.add(mesh)
}

mapbox.on("load",()=>{
mapbox.addLayer(Threejscustomlayer);

mapbox.on("zoom",()=>{
clearInstancedMesh();
createInstancedMesh();
})
})

</script>
<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script></script>
</body>
</html>
66 changes: 14 additions & 52 deletions debug/satellites-custom-layer.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,13 @@
const KM_TO_M = 1000;
const TIME_STEP = 3 * 1000;

const globeVertCode = `
const vertCode = `
attribute vec3 a_pos_ecef;
attribute vec3 a_pos_merc;

uniform mat4 u_projection;
uniform mat4 u_globeToMercMatrix;
uniform float u_globeToMercatorTransition;
uniform vec2 u_centerInMerc;
uniform float u_pixelsPerMeterRatio;

void main() {
vec4 p = u_projection * u_globeToMercMatrix * vec4(a_pos_ecef, 1.);
p /= p.w;
if (u_globeToMercatorTransition > 0.) {

vec4 merc = vec4(a_pos_merc, 1.);
merc.xy = (merc.xy - u_centerInMerc) * u_pixelsPerMeterRatio + u_centerInMerc;
merc.z *= u_pixelsPerMeterRatio;

merc = u_projection * merc;
merc /= merc.w;
p = mix(p, merc, u_globeToMercatorTransition);
}
gl_PointSize = 30.;
gl_Position = p;
}
`;

const mercVertCode = `
precision highp float;
attribute vec3 a_pos_merc;
uniform mat4 u_projection;

void main() {
gl_PointSize = 30.;
gl_Position = u_projection * vec4(a_pos_merc, 1.);
gl_Position = project_custom_layer(a_pos_merc, a_pos_ecef);
}
`;

Expand Down Expand Up @@ -100,8 +71,7 @@ const satellitesLayer = {
this.posEcefVbo = gl.createBuffer();
this.posMercVbo = gl.createBuffer();

this.globeProgram = createProgram(gl, globeVertCode, fragCode);
this.mercProgram = createProgram(gl, mercVertCode, fragCode);
this.program = createProgram(gl, map.customLayerVertexHeader.concat(vertCode), fragCode);

fetch('space-track-leo.txt').then(r => r.text()).then(rawData => {
const tleData = rawData.replace(/\r/g, '')
Expand Down Expand Up @@ -143,30 +113,22 @@ const satellitesLayer = {
}
},

render (gl, projectionMatrix, projection, globeToMercMatrix, transition, centerInMercator, pixelsPerMeterRatio) {
getShaderProgram () {
return this.program;
},

render (gl, projectionMatrix) {
if (this.satData) {
this.updateBuffers();

const primitiveCount = this.posEcef.length / 3;
gl.disable(gl.DEPTH_TEST);
if (projection && projection.name === 'globe') { // globe projection and globe to mercator transition
gl.useProgram(this.globeProgram);

updateVboAndActivateAttrib(gl, this.globeProgram, this.posEcefVbo, this.posEcef, "a_pos_ecef");
updateVboAndActivateAttrib(gl, this.globeProgram, this.posMercVbo, this.posMerc, "a_pos_merc");
gl.uniformMatrix4fv(gl.getUniformLocation(this.globeProgram, "u_projection"), false, projectionMatrix);
gl.uniformMatrix4fv(gl.getUniformLocation(this.globeProgram, "u_globeToMercMatrix"), false, globeToMercMatrix);
gl.uniform1f(gl.getUniformLocation(this.globeProgram, "u_globeToMercatorTransition"), transition);
gl.uniform2f(gl.getUniformLocation(this.globeProgram, "u_centerInMerc"), centerInMercator[0], centerInMercator[1]);
gl.uniform1f(gl.getUniformLocation(this.globeProgram, "u_pixelsPerMeterRatio"), pixelsPerMeterRatio);

gl.drawArrays(gl.POINTS, 0, primitiveCount);
} else { // mercator projection
gl.useProgram(this.mercProgram);
updateVboAndActivateAttrib(gl, this.mercProgram, this.posMercVbo, this.posMerc, "a_pos_merc");
gl.uniformMatrix4fv(gl.getUniformLocation(this.mercProgram, "u_projection"), false, projectionMatrix);
gl.drawArrays(gl.POINTS, 0, primitiveCount);
}
gl.useProgram(this.program);

updateVboAndActivateAttrib(gl, this.program, this.posEcefVbo, this.posEcef, "a_pos_ecef");
updateVboAndActivateAttrib(gl, this.program, this.posMercVbo, this.posMerc, "a_pos_merc");

gl.drawArrays(gl.POINTS, 0, primitiveCount);
}
}
};
14 changes: 6 additions & 8 deletions src/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -1654,14 +1654,12 @@ class Transform {
return this.mercatorMatrix.slice();
}

globeToMercatorMatrix(): ?Array<number> {
if (this.projection.name === 'globe') {
const pixelsToMerc = 1 / this.worldSize;
const m = mat4.fromScaling([], [pixelsToMerc, pixelsToMerc, pixelsToMerc]);
mat4.multiply(m, m, this.globeMatrix);
return m;
}
return undefined;
globeToMercatorMatrix(): Array<number> {
assert(this.projection.name === 'globe');
const pixelsToMerc = 1 / this.worldSize;
const m = mat4.fromScaling([], [pixelsToMerc, pixelsToMerc, pixelsToMerc]);
mat4.multiply(m, m, this.globeMatrix);
return m;
}

recenterOnTerrain() {
Expand Down
44 changes: 32 additions & 12 deletions src/render/draw_custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,27 @@ import DepthMode from '../gl/depth_mode.js';
import StencilMode from '../gl/stencil_mode.js';
import {warnOnce} from '../util/util.js';
import {globeToMercatorTransition} from './../geo/projection/globe_util.js';

import {mat4} from 'gl-matrix';
import type Painter from './painter.js';
import type {OverscaledTileID} from '../source/tile_id.js';
import type SourceCache from '../source/source_cache.js';
import type CustomStyleLayer from '../style/style_layer/custom_style_layer.js';
import MercatorCoordinate from '../geo/mercator_coordinate.js';
import assert from 'assert';

function createMercatorGlobeMatrix(projection, pixelsPerMeterRatio, centerInMerc) {
const mercToPixelMatrix = mat4.create();
mat4.identity(mercToPixelMatrix);

mercToPixelMatrix[0] = pixelsPerMeterRatio;
mercToPixelMatrix[5] = pixelsPerMeterRatio;
mercToPixelMatrix[10] = pixelsPerMeterRatio;
mercToPixelMatrix[12] = centerInMerc.x * (1.0 - pixelsPerMeterRatio);
mercToPixelMatrix[13] = centerInMerc.y * (1.0 - pixelsPerMeterRatio);

return mat4.multiply([], projection, mercToPixelMatrix);
}

function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomStyleLayer, coords: Array<OverscaledTileID>) {

const context = painter.context;
Expand All @@ -32,12 +45,7 @@ function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomSty
painter.setCustomLayerDefaults();
context.setColorMode(painter.colorModeForRenderPass());

if (painter.transform.projection.name === "globe") {
const center = painter.transform.pointMerc;
prerender.call(implementation, context.gl, painter.transform.customLayerMatrix(), painter.transform.getProjection(), painter.transform.globeToMercatorMatrix(), globeToMercatorTransition(painter.transform.zoom), [center.x, center.y], painter.transform.pixelsPerMeterRatio);
} else {
prerender.call(implementation, context.gl, painter.transform.customLayerMatrix());
}
prerender.call(implementation, context.gl, painter.transform.customLayerMatrix());

context.setDirty();
painter.setBaseState();
Expand Down Expand Up @@ -76,13 +84,25 @@ function drawCustom(painter: Painter, sourceCache: SourceCache, layer: CustomSty

context.setDepthMode(depthMode);

if (painter.transform.projection.name === "globe") {
const center = painter.transform.pointMerc;
implementation.render(context.gl, painter.transform.customLayerMatrix(), painter.transform.getProjection(), painter.transform.globeToMercatorMatrix(), globeToMercatorTransition(painter.transform.zoom), [center.x, center.y], painter.transform.pixelsPerMeterRatio);
} else {
implementation.render(context.gl, painter.transform.customLayerMatrix());
const program = implementation.getShaderProgram && implementation.getShaderProgram();
if (program) {
context.gl.useProgram(program);
layer.setUniform(context.gl, program, "u_isGlobe", +(painter.transform.projection.name === "globe"));
layer.setUniform(context.gl, program, "u_transition", globeToMercatorTransition(painter.transform.zoom));

if (painter.transform.projection.name === "globe") {
const center = painter.transform.pointMerc;
const globeProjection = mat4.multiply([], painter.transform.customLayerMatrix(), painter.transform.globeToMercatorMatrix());
const mercatorProjection = createMercatorGlobeMatrix(painter.transform.customLayerMatrix(), painter.transform.pixelsPerMeterRatio, center);
layer.setUniform(context.gl, program, "u_projection", globeProjection);
layer.setUniform(context.gl, program, "u_mercatorProjection", mercatorProjection);
} else {
layer.setUniform(context.gl, program, "u_projection", painter.transform.customLayerMatrix());
}
}

implementation.render(context.gl, painter.transform.customLayerMatrix());

context.setDirty();
painter.setBaseState();
context.bindFramebuffer.set(null);
Expand Down
Loading