Skip to content

Commit adebbf2

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

File tree

1 file changed

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

1 file changed

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

0 commit comments

Comments
 (0)