Skip to content

Commit c65b9db

Browse files
committed
Add LocationMarker shape
1 parent 2de677e commit c65b9db

File tree

1 file changed

+223
-0
lines changed
  • android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/shapes

1 file changed

+223
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package net.mullvad.mullvadvpn.lib.map.internal.shapes
2+
3+
import android.opengl.GLES20
4+
import android.opengl.Matrix
5+
import androidx.compose.ui.graphics.Color
6+
import java.nio.FloatBuffer
7+
import kotlin.math.cos
8+
import kotlin.math.sin
9+
import net.mullvad.mullvadvpn.lib.map.data.LocationMarkerColors
10+
import net.mullvad.mullvadvpn.lib.map.internal.COLOR_COMPONENT_SIZE
11+
import net.mullvad.mullvadvpn.lib.map.internal.VERTEX_COMPONENT_SIZE
12+
import net.mullvad.mullvadvpn.lib.map.internal.initArrayBuffer
13+
import net.mullvad.mullvadvpn.lib.map.internal.initShaderProgram
14+
import net.mullvad.mullvadvpn.lib.map.internal.toFloatArray
15+
import net.mullvad.mullvadvpn.model.LatLong
16+
17+
internal class LocationMarker(val colors: LocationMarkerColors) {
18+
19+
private val shaderProgram: Int
20+
private val attribLocations: AttribLocations
21+
private val uniformLocation: UniformLocation
22+
private val positionBuffer: Int
23+
private val colorBuffer: Int
24+
private val ringSizes: List<Int>
25+
26+
private data class AttribLocations(val vertexPosition: Int, val vertexColor: Int)
27+
28+
private data class UniformLocation(val projectionMatrix: Int, val modelViewMatrix: Int)
29+
30+
init {
31+
val rings = createRings()
32+
ringSizes = rings.map { (positions, _) -> positions.size }
33+
34+
val positionFloatArray = joinMultipleArrays(rings.map { it.vertices })
35+
val positionFloatBuffer = FloatBuffer.wrap(positionFloatArray)
36+
37+
val colorFloatArray = joinMultipleArrays(rings.map { it.verticesColor })
38+
val colorFloatBuffer = FloatBuffer.wrap(colorFloatArray)
39+
40+
positionBuffer = initArrayBuffer(positionFloatBuffer)
41+
colorBuffer = initArrayBuffer(colorFloatBuffer)
42+
43+
shaderProgram = initShaderProgram(vertexShaderCode, fragmentShaderCode)
44+
45+
attribLocations =
46+
AttribLocations(
47+
vertexPosition = GLES20.glGetAttribLocation(shaderProgram, "aVertexPosition"),
48+
vertexColor = GLES20.glGetAttribLocation(shaderProgram, "aVertexColor")
49+
)
50+
uniformLocation =
51+
UniformLocation(
52+
projectionMatrix = GLES20.glGetUniformLocation(shaderProgram, "uProjectionMatrix"),
53+
modelViewMatrix = GLES20.glGetUniformLocation(shaderProgram, "uModelViewMatrix")
54+
)
55+
}
56+
57+
fun draw(projectionMatrix: FloatArray, viewMatrix: FloatArray, latLong: LatLong, size: Float) {
58+
val modelViewMatrix = viewMatrix.copyOf()
59+
60+
GLES20.glUseProgram(shaderProgram)
61+
62+
Matrix.rotateM(modelViewMatrix, 0, latLong.longitude.value, 0f, 1f, 0f)
63+
Matrix.rotateM(modelViewMatrix, 0, latLong.latitude.value, -1f, 0f, 0f)
64+
65+
Matrix.scaleM(modelViewMatrix, 0, size, size, 1f)
66+
67+
// Translate marker to put it above the globe
68+
Matrix.translateM(modelViewMatrix, 0, 0f, 0f, MARKER_TRANSLATE_Z_FACTOR)
69+
70+
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, positionBuffer)
71+
GLES20.glVertexAttribPointer(
72+
attribLocations.vertexPosition,
73+
VERTEX_COMPONENT_SIZE,
74+
GLES20.GL_FLOAT,
75+
false,
76+
0,
77+
0,
78+
)
79+
GLES20.glEnableVertexAttribArray(attribLocations.vertexPosition)
80+
81+
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, colorBuffer)
82+
GLES20.glVertexAttribPointer(
83+
attribLocations.vertexColor,
84+
COLOR_COMPONENT_SIZE,
85+
GLES20.GL_FLOAT,
86+
false,
87+
0,
88+
0,
89+
)
90+
GLES20.glEnableVertexAttribArray(attribLocations.vertexColor)
91+
92+
GLES20.glUniformMatrix4fv(uniformLocation.projectionMatrix, 1, false, projectionMatrix, 0)
93+
GLES20.glUniformMatrix4fv(uniformLocation.modelViewMatrix, 1, false, modelViewMatrix, 0)
94+
95+
var offset = 0
96+
for (ringSize in ringSizes) {
97+
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, offset, ringSize)
98+
// Add number off vertices in the ring to the offset
99+
offset += ringSize / VERTEX_COMPONENT_SIZE
100+
}
101+
}
102+
103+
// Returns vertex positions and color values for a circle.
104+
// `offset` is a vector of x, y and z values determining how much to offset the circle
105+
// position from origo
106+
private fun circleFanVertices(
107+
numEdges: Int,
108+
radius: Float,
109+
offset: FloatArray = floatArrayOf(0.0f, 0.0f, 0.0f),
110+
centerColor: Color,
111+
ringColor: Color,
112+
): Ring {
113+
require(numEdges > 2) { "Number of edges must be greater than 2" }
114+
115+
// Edges + center + first point
116+
val points = numEdges + 2
117+
118+
val positions = FloatArray(points * VERTEX_COMPONENT_SIZE)
119+
val positionsColor = FloatArray(points * COLOR_COMPONENT_SIZE)
120+
121+
// Start adding the center the center point
122+
offset.forEachIndexed { index, value -> positions[index] = value }
123+
centerColor.toFloatArray().forEachIndexed { index, value -> positionsColor[index] = value }
124+
125+
val ringColorArray = ringColor.toFloatArray()
126+
127+
for (i in 1 until points) {
128+
129+
val angle = (i.toFloat() / numEdges) * 2f * Math.PI
130+
val posIndex = i * VERTEX_COMPONENT_SIZE
131+
positions[posIndex] = offset[0] + radius * cos(angle).toFloat()
132+
positions[posIndex + 1] = offset[1] + radius * sin(angle).toFloat()
133+
positions[posIndex + 2] = offset[2]
134+
135+
val colorIndex = i * COLOR_COMPONENT_SIZE
136+
ringColorArray.forEachIndexed { index, value ->
137+
positionsColor[colorIndex + index] = value
138+
}
139+
}
140+
141+
return Ring(positions, positionsColor)
142+
}
143+
144+
private fun joinMultipleArrays(arrays: List<FloatArray>): FloatArray {
145+
val result = FloatArray(arrays.sumOf { it.size })
146+
var offset = 0
147+
for (array in arrays) {
148+
array.copyInto(result, offset)
149+
offset += array.size
150+
}
151+
return result
152+
}
153+
154+
private fun createRings(): List<Ring> =
155+
listOf(
156+
circleFanVertices(
157+
32,
158+
0.5f,
159+
floatArrayOf(0.0f, 0.0f, 0.0f),
160+
colors.perimeterColors,
161+
colors.perimeterColors,
162+
), // Semi-transparent outer
163+
circleFanVertices(
164+
16,
165+
0.28f,
166+
floatArrayOf(0.0f, -0.05f, 0.00001f),
167+
colors.shadowColor,
168+
colors.shadowColor.copy(alpha = 0.0f),
169+
), // Shadow
170+
circleFanVertices(
171+
32,
172+
0.185f,
173+
floatArrayOf(0.0f, 0.0f, 0.00002f),
174+
colors.ringBorderColor,
175+
colors.ringBorderColor,
176+
), // White ring
177+
circleFanVertices(
178+
32,
179+
0.15f,
180+
floatArrayOf(0.0f, 0.0f, 0.00003f),
181+
colors.centerColor,
182+
colors.centerColor,
183+
) // Center colored circle
184+
)
185+
186+
fun onRemove() {
187+
GLES20.glDeleteBuffers(2, intArrayOf(positionBuffer, colorBuffer), 0)
188+
GLES20.glDeleteProgram(shaderProgram)
189+
}
190+
191+
companion object {
192+
private const val MARKER_TRANSLATE_Z_FACTOR = 1.0001f
193+
194+
// Vertex, and fragment shader code is taken from Mullvad Desktop 3dmap.ts
195+
private val vertexShaderCode =
196+
"""
197+
attribute vec3 aVertexPosition;
198+
attribute vec4 aVertexColor;
199+
200+
uniform mat4 uModelViewMatrix;
201+
uniform mat4 uProjectionMatrix;
202+
203+
varying lowp vec4 vColor;
204+
205+
void main(void) {
206+
gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
207+
vColor = aVertexColor;
208+
}
209+
"""
210+
.trimIndent()
211+
private val fragmentShaderCode =
212+
"""
213+
varying lowp vec4 vColor;
214+
215+
void main(void) {
216+
gl_FragColor = vColor;
217+
}
218+
"""
219+
.trimIndent()
220+
}
221+
}
222+
223+
private data class Ring(val vertices: FloatArray, val verticesColor: FloatArray)

0 commit comments

Comments
 (0)