diff --git a/app/src/androidTest/java/com/github/lookupgroup27/lookup/model/map/renderables/LabelTest.kt b/app/src/androidTest/java/com/github/lookupgroup27/lookup/util/opengl/LabelUtilsTest.kt similarity index 60% rename from app/src/androidTest/java/com/github/lookupgroup27/lookup/model/map/renderables/LabelTest.kt rename to app/src/androidTest/java/com/github/lookupgroup27/lookup/util/opengl/LabelUtilsTest.kt index 830f81c7a..b3e72e998 100644 --- a/app/src/androidTest/java/com/github/lookupgroup27/lookup/model/map/renderables/LabelTest.kt +++ b/app/src/androidTest/java/com/github/lookupgroup27/lookup/util/opengl/LabelUtilsTest.kt @@ -1,29 +1,14 @@ -package com.github.lookupgroup27.lookup.model.map.renderables +package com.github.lookupgroup27.lookup.util.opengl import android.graphics.Bitmap import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.github.lookupgroup27.lookup.model.map.renderables.label.Label -import com.github.lookupgroup27.lookup.model.map.renderables.label.LabelUtils import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith /** Instrumented tests for LabelUtils and the Label class. */ @RunWith(AndroidJUnit4::class) -class LabelTest { - - /** Tests that a Label object can be created successfully. */ - @Test - fun testLabelCreation() { - val text = "Star A" - val position = floatArrayOf(1.0f, 2.0f, 3.0f) - val label = Label(text, position) - - assertEquals("Label text should be 'Star A'", "Star A", label.text) - assertArrayEquals( - "Label position should match", floatArrayOf(1.0f, 2.0f, 3.0f), label.position, 0.0f) - assertNull("Label textureId should be null by default", label.textureId) - } +class LabelUtilsTest { /** Tests that the bitmap is created successfully. */ @Test @@ -33,7 +18,7 @@ class LabelTest { assertNotNull("Bitmap should not be null", bitmap) assertEquals("Bitmap width should be 256", 256, bitmap.width) - assertEquals("Bitmap height should be 128", 128, bitmap.height) + assertEquals("Bitmap height should be 256", 256, bitmap.height) } /** Tests that the bitmap contains non-transparent pixels. */ diff --git a/app/src/main/assets/shaders/label_fragment_shader.glsl b/app/src/main/assets/shaders/label_fragment_shader.glsl new file mode 100644 index 000000000..bb765f066 --- /dev/null +++ b/app/src/main/assets/shaders/label_fragment_shader.glsl @@ -0,0 +1,8 @@ +precision mediump float; + +uniform sampler2D uTexture; +varying vec2 vTexCoordinate; + +void main() { + gl_FragColor = texture2D(uTexture, vTexCoordinate); +} \ No newline at end of file diff --git a/app/src/main/assets/shaders/label_vertex_shader.glsl b/app/src/main/assets/shaders/label_vertex_shader.glsl new file mode 100644 index 000000000..8771db411 --- /dev/null +++ b/app/src/main/assets/shaders/label_vertex_shader.glsl @@ -0,0 +1,13 @@ +uniform mat4 uModelMatrix; +uniform mat4 uViewMatrix; +uniform mat4 uProjMatrix; + +attribute vec4 aPosition; +attribute vec2 aTexCoordinate; + +varying vec2 vTexCoordinate; + +void main() { + gl_Position = uProjMatrix * uViewMatrix * uModelMatrix * aPosition; + vTexCoordinate = aTexCoordinate; +} \ No newline at end of file diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/MapRenderer.kt b/app/src/main/java/com/github/lookupgroup27/lookup/model/map/MapRenderer.kt index 7f90d1da2..738c9bcc8 100644 --- a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/MapRenderer.kt +++ b/app/src/main/java/com/github/lookupgroup27/lookup/model/map/MapRenderer.kt @@ -91,7 +91,7 @@ class MapRenderer( // Bind the texture and render the SkyBox GLES20.glDepthMask(false) textureManager.bindTexture(skyBoxTextureHandle) - // skyBox.draw(camera) commented out until the texture is changed to see stars + skyBox.draw(camera) GLES20.glDepthMask(true) val deltaTime = computeDeltaTime() diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/CircleRenderer.kt b/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/CircleRenderer.kt index 6a67eb69c..433b4536c 100644 --- a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/CircleRenderer.kt +++ b/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/CircleRenderer.kt @@ -7,8 +7,8 @@ import com.github.lookupgroup27.lookup.model.map.renderables.utils.GeometryUtils import com.github.lookupgroup27.lookup.model.map.skybox.buffers.ColorBuffer import com.github.lookupgroup27.lookup.model.map.skybox.buffers.IndexBuffer import com.github.lookupgroup27.lookup.model.map.skybox.buffers.VertexBuffer -import com.github.lookupgroup27.lookup.util.ShaderUtils.readShader import com.github.lookupgroup27.lookup.util.opengl.ShaderProgram +import com.github.lookupgroup27.lookup.util.opengl.ShaderUtils.readShader /** * Renderer for creating circular 2D objects in an OpenGL environment. diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/Planet.kt b/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/Planet.kt index c12238a14..2520bc565 100644 --- a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/Planet.kt +++ b/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/Planet.kt @@ -4,6 +4,8 @@ import android.content.Context import android.opengl.GLES20 import android.opengl.Matrix import com.github.lookupgroup27.lookup.model.map.Camera +import com.github.lookupgroup27.lookup.ui.map.renderables.Label +import com.github.lookupgroup27.lookup.util.opengl.Position import com.github.lookupgroup27.lookup.util.opengl.TextureManager /** @@ -28,7 +30,7 @@ import com.github.lookupgroup27.lookup.util.opengl.TextureManager */ open class Planet( private val context: Context, - val name: String? = "Planet", + val name: String = "Planet", val position: FloatArray = floatArrayOf(0.0f, 0.0f, -2.0f), var textureId: Int, numBands: Int = SphereRenderer.DEFAULT_NUM_BANDS, @@ -43,6 +45,8 @@ open class Planet( protected var textureHandle: Int = 0 private var textureManager: TextureManager + private val label = + Label(context, name, Position(position[0], position[1], position[2]), 0.1f, scale) // New properties for rotation private var rotationAngle: Float = 0f // Current rotation angle in degrees @@ -85,6 +89,7 @@ open class Planet( * @param camera The camera used for rendering the scene. */ fun draw(camera: Camera, transformMatrix: FloatArray? = null) { + label.draw(camera) val modelMatrix = FloatArray(16) val billboardMatrix = FloatArray(16) val mvpMatrix = FloatArray(16) diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/SphericalRenderable.kt b/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/SphericalRenderable.kt index 03501d7f8..6011fd5a7 100644 --- a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/SphericalRenderable.kt +++ b/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/SphericalRenderable.kt @@ -7,8 +7,8 @@ import com.github.lookupgroup27.lookup.model.map.skybox.buffers.ColorBuffer import com.github.lookupgroup27.lookup.model.map.skybox.buffers.IndexBuffer import com.github.lookupgroup27.lookup.model.map.skybox.buffers.TextureBuffer import com.github.lookupgroup27.lookup.model.map.skybox.buffers.VertexBuffer -import com.github.lookupgroup27.lookup.util.ShaderUtils.readShader import com.github.lookupgroup27.lookup.util.opengl.ShaderProgram +import com.github.lookupgroup27.lookup.util.opengl.ShaderUtils.readShader /** * Base class for rendering spherical 3D objects in an OpenGL environment. diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/label/Label.kt b/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/label/Label.kt deleted file mode 100644 index 17222e843..000000000 --- a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/label/Label.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.lookupgroup27.lookup.model.map.renderables.label - -/** - * Data class representing a label in the skymap. - * - * @property text The text of the label. - * @property position The position of the label in 3D space (x, y, z). - * @property textureId The OpenGL texture ID for the label's bitmap (optional). - */ -data class Label( - val text: String, - val position: FloatArray, - var textureId: Int? = null // Optional texture ID for OpenGL -) diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/ui/map/renderables/Label.kt b/app/src/main/java/com/github/lookupgroup27/lookup/ui/map/renderables/Label.kt new file mode 100644 index 000000000..3a8854672 --- /dev/null +++ b/app/src/main/java/com/github/lookupgroup27/lookup/ui/map/renderables/Label.kt @@ -0,0 +1,182 @@ +package com.github.lookupgroup27.lookup.ui.map.renderables + +import android.content.Context +import android.opengl.GLES20 +import android.opengl.GLUtils +import android.opengl.Matrix +import com.github.lookupgroup27.lookup.model.map.Camera +import com.github.lookupgroup27.lookup.util.opengl.BufferUtils.toBuffer +import com.github.lookupgroup27.lookup.util.opengl.LabelUtils +import com.github.lookupgroup27.lookup.util.opengl.Position +import com.github.lookupgroup27.lookup.util.opengl.ShaderProgram +import com.github.lookupgroup27.lookup.util.opengl.ShaderUtils.readShader +import java.nio.FloatBuffer + +/** + * A label that displays text in the 3D world. + * + * @param context The application context + * @param text The text to display on the label + * @param pos The position of the label TODO Implement this in others OpenGL classes + * @param size The size of the label + * @param objectSize The size of the object to label + */ +class Label(context: Context, text: String, var pos: Position, size: Float, objectSize: Float) { + private val shaderProgram: ShaderProgram + private val textureId: Int + private val vertexBuffer: FloatBuffer + private val texCoordBuffer: FloatBuffer + + companion object { + private const val PADDING = 0.025f + } + + init { + // Initialize shader program + val vertexShaderCode = readShader(context, "label_vertex_shader.glsl") + val fragmentShaderCode = readShader(context, "label_fragment_shader.glsl") + + shaderProgram = ShaderProgram(vertexShaderCode, fragmentShaderCode) + + // Define vertices for a quad that will display the label + // These coordinates represent a quad that fills the screen + val vertices = + floatArrayOf( + -size, + -size - objectSize - PADDING, + 0f, // Bottom left + size, + -size - objectSize - PADDING, + 0f, // Bottom right + -size, + size - objectSize - PADDING, + 0f, // Top left + size, + size - objectSize - PADDING, + 0f // Top right + ) + vertexBuffer = vertices.toBuffer() + + // Define texture coordinates + val texCoords = + floatArrayOf( + 0f, + 1f, // Bottom left + 1f, + 1f, // Bottom right + 0f, + 0f, // Top left + 1f, + 0f // Top right + ) + texCoordBuffer = texCoords.toBuffer() + + // Initialize Label texture + val textureHandles = IntArray(1) + GLES20.glGenTextures(1, textureHandles, 0) + textureId = textureHandles[0] + + // Bind texture + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId) + + // Set texture parameters + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR) + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR) + + // Load bitmap to OpenGL + val bitmap = LabelUtils.createLabelBitmap(text) + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0) + } + + /** + * Draws the label in the 3D world. + * + * @param camera The camera used to render the label + */ + fun draw(camera: Camera) { + GLES20.glEnable(GLES20.GL_BLEND) + GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA) + shaderProgram.use() + + // Get attribute and uniform locations + val modelMatrixHandle = GLES20.glGetUniformLocation(shaderProgram.programId, "uModelMatrix") + val viewMatrixHandle = GLES20.glGetUniformLocation(shaderProgram.programId, "uViewMatrix") + val projMatrixHandle = GLES20.glGetUniformLocation(shaderProgram.programId, "uProjMatrix") + val positionHandle = GLES20.glGetAttribLocation(shaderProgram.programId, "aPosition") + val texCoordHandle = GLES20.glGetAttribLocation(shaderProgram.programId, "aTexCoordinate") + val textureHandle = GLES20.glGetUniformLocation(shaderProgram.programId, "uTexture") + + // Bind texture + GLES20.glActiveTexture(GLES20.GL_TEXTURE0) + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId) + GLES20.glUniform1i(textureHandle, 0) + + // Set up vertex and texture coordinate buffers + vertexBuffer.position(0) + GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer) + GLES20.glEnableVertexAttribArray(positionHandle) + + texCoordBuffer.position(0) + GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, texCoordBuffer) + GLES20.glEnableVertexAttribArray(texCoordHandle) + + val billboardMatrix = FloatArray(16) + val modelMatrix = camera.modelMatrix.clone() + + // Extract camera look direction from view matrix + val lookX = -camera.viewMatrix[2] + val lookY = -camera.viewMatrix[6] + val lookZ = -camera.viewMatrix[10] + + // Create billboard rotation (this is the vector compared to the object that points up for the + // text + // Example we take a text Hello, we have: + // ↑ Hello + val upX = camera.viewMatrix[1] + val upY = camera.viewMatrix[5] + val upZ = camera.viewMatrix[9] + + // Calculate right vector (cross product) + val rightX = upY * lookZ - upZ * lookY + val rightY = upZ * lookX - upX * lookZ + val rightZ = upX * lookY - upY * lookX + + // Set billboard matrix + billboardMatrix[0] = -rightX + billboardMatrix[1] = -rightY + billboardMatrix[2] = -rightZ + billboardMatrix[3] = 0f + + billboardMatrix[4] = upX + billboardMatrix[5] = upY + billboardMatrix[6] = upZ + billboardMatrix[7] = 0f + + billboardMatrix[8] = lookX + billboardMatrix[9] = lookY + billboardMatrix[10] = lookZ + billboardMatrix[11] = 0f + + billboardMatrix[12] = 0f + billboardMatrix[13] = 0f + billboardMatrix[14] = 0f + billboardMatrix[15] = 1f + + // First translate to position + Matrix.translateM(modelMatrix, 0, pos.x, pos.y, pos.z) + + // Then apply billboard rotation + val rotatedMatrix = FloatArray(16) + Matrix.multiplyMM(rotatedMatrix, 0, modelMatrix, 0, billboardMatrix, 0) + + // Set the MVP matrix + GLES20.glUniformMatrix4fv(modelMatrixHandle, 1, false, rotatedMatrix, 0) + GLES20.glUniformMatrix4fv(viewMatrixHandle, 1, false, camera.viewMatrix, 0) + GLES20.glUniformMatrix4fv(projMatrixHandle, 1, false, camera.projMatrix, 0) + + // Draw the text + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4) + + GLES20.glDisable(GLES20.GL_BLEND) + } +} diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/util/BufferUtils.kt b/app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/BufferUtils.kt similarity index 94% rename from app/src/main/java/com/github/lookupgroup27/lookup/util/BufferUtils.kt rename to app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/BufferUtils.kt index 3ab47c48d..f1f8cea6f 100644 --- a/app/src/main/java/com/github/lookupgroup27/lookup/util/BufferUtils.kt +++ b/app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/BufferUtils.kt @@ -1,4 +1,4 @@ -package com.github.lookupgroup27.lookup.util +package com.github.lookupgroup27.lookup.util.opengl import java.nio.ByteBuffer import java.nio.ByteOrder diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/label/LabelUtils.kt b/app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/LabelUtils.kt similarity index 71% rename from app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/label/LabelUtils.kt rename to app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/LabelUtils.kt index 85e98912d..65c0d706e 100644 --- a/app/src/main/java/com/github/lookupgroup27/lookup/model/map/renderables/label/LabelUtils.kt +++ b/app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/LabelUtils.kt @@ -1,4 +1,4 @@ -package com.github.lookupgroup27.lookup.model.map.renderables.label +package com.github.lookupgroup27.lookup.util.opengl import android.graphics.Bitmap import android.graphics.Canvas @@ -15,19 +15,19 @@ object LabelUtils { */ fun createLabelBitmap(text: String): Bitmap { val width = 256 // Width of the bitmap - val height = 128 // Height of the bitmap + val height = 256 // Height of the bitmap val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - canvas.drawColor(android.graphics.Color.BLACK) // Background color - val paint = Paint() paint.color = android.graphics.Color.WHITE // Text color paint.textSize = 32f paint.isAntiAlias = true - val x = 20f - val y = height / 2f + val x = width / 2f - paint.measureText(text) / 2 + val y = height / 2f - (paint.descent() + paint.ascent()) / 2 + + val canvas = Canvas(bitmap) + canvas.drawColor(android.graphics.Color.TRANSPARENT) // Background color canvas.drawText(text, x, y, paint) return bitmap diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/Position.kt b/app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/Position.kt new file mode 100644 index 000000000..5e206a728 --- /dev/null +++ b/app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/Position.kt @@ -0,0 +1,3 @@ +package com.github.lookupgroup27.lookup.util.opengl + +data class Position(val x: Float, val y: Float, val z: Float) diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/util/ShaderUtils.kt b/app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/ShaderUtils.kt similarity index 92% rename from app/src/main/java/com/github/lookupgroup27/lookup/util/ShaderUtils.kt rename to app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/ShaderUtils.kt index 119c81e54..c895882a8 100644 --- a/app/src/main/java/com/github/lookupgroup27/lookup/util/ShaderUtils.kt +++ b/app/src/main/java/com/github/lookupgroup27/lookup/util/opengl/ShaderUtils.kt @@ -1,4 +1,4 @@ -package com.github.lookupgroup27.lookup.util +package com.github.lookupgroup27.lookup.util.opengl import android.content.Context import android.util.Log