Вопрос или проблема
Я пытаюсь отобразить хронометр и постоянно обновляемые данные, такие как пройденное расстояние в OpenGL. Также есть кнопки для управления хронометром. Я планирую нарисовать текст на текстуре, а затем добавить текстуру к прямоугольной форме. (если есть лучший способ, пожалуйста, сообщите мне)
У меня уже есть код для моего хронометра и других данных, но как передать эти данные в OpenGL?
(Используя Android Studio, OpenGL ES 2.0)
мой код на java;
package com.example.code;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.widget.Chronometer;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.Locale;
public class Activity extends AppCompatActivity implements LocationListener {
private static final String TAG = "SpeedMeasurementActivity";
private LocationManager locationManager;
//private TextView currentSpeedTextView;
//private TextView distanceTextView;
private Chronometer chronometer;
private FloatingActionButton startStopButton;
private boolean isRunning = false;
private double totalDistance = 0;
private Location lastLocation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_no_ar);
// Инициализация представлений
//currentSpeedTextView = findViewById(R.id.speed_text_view);
//distanceTextView = findViewById(R.id.distance_text_view);
chronometer = findViewById(R.id.time_text_view);
startStopButton = findViewById(R.id.fab_startstop);
FloatingActionButton back = findViewById(R.id.fab_back);
// Инициализация менеджера местоположения
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
startStopButton.setOnClickListener(v -> {
if (!isRunning) {
startMeasurement();
} else {
stopMeasurement();
}
});
// Настройка кнопки назад
back.setOnClickListener(v -> {
Intent intent1 = new Intent(getApplication(), ActivityMain.class);
startActivity(intent1);
finish();
});
}
private void startMeasurement() {
isRunning = true;
startStopButton.setImageResource(R.drawable.ic_action_stop);
startStopButton.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(code.this, R.color.red)));
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, this);
} catch (SecurityException e) {
Log.e(TAG, "request Location Updates error.", e);
}
}
private void stopMeasurement() {
isRunning = false;
startStopButton.setImageResource(R.drawable.ic_action_play);
startStopButton.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(code.this, R.color.green)));
chronometer.stop();
locationManager.removeUpdates(this);
}
@Override
public void onLocationChanged(@NonNull Location location) {
if (isRunning) {
double speed = location.getSpeed() * 3.6; // Преобразование м/с в км/ч
currentSpeedTextView.setText(String.format(Locale.getDefault(), "%.1f км/ч", speed));
if (lastLocation != null) {
float distance = lastLocation.distanceTo(location);
totalDistance += distance;
distanceTextView.setText(String.format(Locale.getDefault(), "%.2f м", totalDistance));
}
lastLocation = location;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
locationManager.removeUpdates(this);
}
// Реализуйте другие методы LocationListener (onStatusChanged, onProviderEnabled, onProviderDisabled)
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
public void onProviderEnabled(@NonNull String provider) {}
@Override
public void onProviderDisabled(@NonNull String provider) {}
}
MyGlSurfaceView.java;
package com.example.code;
import android.content.Context;
import android.opengl.GLSurfaceView;
public class MyGLSurfaceView extends GLSurfaceView {
private final Context mContext;
public GLRenderer renderer;
private static final String TAG = "MyGLSurfaceView";
public MyGLSurfaceView(Context context) {
super(context);
mContext=context;
setEGLContextClientVersion(2);
setEGLConfigChooser(8, 8, 8, 8, 16, 0);
renderer = new GLRenderer(context);
setRenderer(renderer);
}
}
TexRectangular.java;
package com.example.code;
import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
public class TexRectangular {
private FloatBuffer vertexBuffer;
private ByteBuffer indexBuffer;
private FloatBuffer normalBuffer;
private FloatBuffer texcoordBuffer;
private float[] vertexs= new float[4*3];
private byte[] indexs= {
0,2,1,3
};
private float[] normals= {
0f,0f,1f,
0f,0f,1f,
0f,0f,1f,
0f,0f,1f
};
float textcoords[] = {
0f, 0f,
1f, 0f,
0f, 1f,
1f, 1f
};
TexRectangular() {
setRectangular(1f, 1f);
}
TexRectangular(float width, float height) {
setRectangular(width, height);
}
public void setRectangular(float width, float height) {
float top=height*.5f;
float bottom=-top;
float right=width*.5f;
float left=-right;
float[] vertexs= {
left, top, 0f,
right, top, 0f,
left, bottom, 0f,
right, bottom, 0f
};
vertexBuffer=BufferUtil.makeFloatBuffer(vertexs);
indexBuffer=BufferUtil.makeByteBuffer(indexs);
normalBuffer=BufferUtil.makeFloatBuffer(normals);
texcoordBuffer = BufferUtil.makeFloatBuffer(textcoords);
}
public void draw(float r,float g,float b,float a, float shininess) {
GLES20.glVertexAttribPointer(GLES.texcoordHandle, 2,
GLES20.GL_FLOAT, false, 0, texcoordBuffer);
GLES20.glVertexAttribPointer(GLES.positionHandle, 3,
GLES20.GL_FLOAT, false, 0, vertexBuffer);
GLES20.glVertexAttribPointer(GLES.normalHandle, 3,
GLES20.GL_FLOAT, false, 0, normalBuffer);
GLES20.glUniform4f(GLES.materialAmbientHandle, r, g, b, a);
GLES20.glUniform4f(GLES.materialDiffuseHandle, r, g, b, a);
GLES20.glUniform4f(GLES.materialSpecularHandle, 1f, 1f, 1f, a);
GLES20.glUniform1f(GLES.materialShininessHandle, shininess);
GLES20.glUniform4f(GLES.objectColorHandle, r, g, b, a);
//
indexBuffer.position(0);
GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP,
4, GLES20.GL_UNSIGNED_BYTE, indexBuffer);
}
}
StringTexture.java;
package com.example.code;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import javax.microedition.khronos.opengles.GL10;
public class StringTexture {
private int TextureId=-1;
private int TextureUnitNumber=0;
StringTexture(String text, float textSize, int txtcolor, int bkcolor, int textureidnumber) {
TextureUnitNumber = textureidnumber;
makeStringTexture(text, textSize, txtcolor, bkcolor);
}
StringTexture(String text, float textSize, int txtcolor, int bkcolor) {
makeStringTexture(text, textSize, txtcolor, bkcolor);
}
public void makeStringTexture(String text, float textSize, int txtcolor, int bkcolor) {
Paint paint = new Paint();
paint.setTextSize(textSize);
paint.setAntiAlias(true);
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
paint.getTextBounds(text, 0, text.length(), new Rect(0, 0, (int) textSize * text.length(), (int) textSize));
int textWidth = (int) paint.measureText(text);
int textHeight = (int) (Math.abs(fontMetrics.top) + fontMetrics.bottom);
if (textWidth == 0) textWidth = 10;
if (textHeight == 0) textHeight = 10;
int bitmapsize = 2;
while (bitmapsize < textWidth) bitmapsize *= 2;
while (bitmapsize < textHeight) bitmapsize *= 2;
Bitmap bitmap = Bitmap.createBitmap(bitmapsize, bitmapsize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
paint.setColor(bkcolor);
canvas.drawRect(new Rect(0, 0, bitmapsize, bitmapsize), paint);
paint.setColor(txtcolor);
canvas.drawText(text, bitmapsize / 2 - textWidth / 2, bitmapsize / 2 - (fontMetrics.ascent + fontMetrics.descent) / 2, paint);
int FIRST_INDEX = 0;
final int DEFAULT_OFFSET = 0;
final int[] textures = new int[1];
if (TextureId!=-1) {
textures[FIRST_INDEX]=TextureId;
GLES20.glDeleteTextures(1, textures, DEFAULT_OFFSET);
}
GLES20.glGenTextures(1, textures, DEFAULT_OFFSET);
TextureId = textures[FIRST_INDEX];
// GLES20.glActiveTexture(GLES20.GL_TEXTURE0+TextureUnitNumber);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, TextureId);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
}
public void setTexture() {
GLES20.glActiveTexture(GLES20.GL_TEXTURE0+TextureUnitNumber);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, TextureId);
GLES20.glUniform1i(GLES.textureHandle, TextureUnitNumber);
}
}
MyGLRenderer.java;
package com.example.code;
import android.content.Context;
import android.graphics.Color;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MyGLRenderer implements GLSurfaceView.Renderer {
private final Context mContext;
private boolean validProgram=false;
private float aspect;
private float viewingangle = 45f;
private float[] LightPos={0f,0f,3f,0f};//x,y,z,1
private float[] pMatrix=new float[16];
private float[] mMatrix=new float[16];
private float[] cMatrix=new float[16];
private TexRectangular MyTexRectangular = new TexRectangular();
private StringTexture Speed;
private StringTexture Distance;
private StringTexture Time;
private static float[] DummyFloat= new float[1];
private static final FloatBuffer DummyBuffer=BufferUtil.makeFloatBuffer(DummyFloat);
MyGLRenderer(final Context context) {
mContext = context;
}
private String speedText = "";
private String distanceText = "";
private String timeText = "";
@Override
public void onSurfaceCreated(GL10 gl10,EGLConfig eglConfig) {
validProgram = GLES.makeProgram();
GLES20.glEnableVertexAttribArray(GLES.positionHandle);
GLES20.glEnableVertexAttribArray(GLES.normalHandle);
GLES20.glEnableVertexAttribArray(GLES.texcoordHandle);
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
GLES20.glEnable(GLES20.GL_CULL_FACE);
GLES20.glFrontFace(GLES20.GL_CCW);
GLES20.glCullFace(GLES20.GL_BACK);
GLES20.glUniform4f(GLES.lightAmbientHandle, 0.5f, 0.5f, 0.5f, 1.0f);
GLES20.glUniform4f(GLES.lightDiffuseHandle, 1f, 1f, 1f, 1.0f);
GLES20.glUniform4f(GLES.lightSpecularHandle, 0f, 0f, 0f, 0f);
GLES20.glClearColor(0f, 0f, 0f, 1.0f);
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
}
//установить текст скорости
public void setSpeedText(float f){
//int speedInt;
String str;
//speed = f;
str = String.valueOf(f);
speedText = str + "км/ч";
}
// установить текст расстояния
public void setDistanceText(float f){
//int distanceInt;
String str;
//distance = f;
str = String.valueOf(f);
distanceText = str + "м";
}
// установить текст таймера
public void setTimeText(float f){
//int timeInt;
String str;
//time = f;
str = String.valueOf(f);
timeText = str;
}
@Override
public void onSurfaceChanged(GL10 gl10,int w,int h) {
GLES20.glViewport(0,0,w,h);
aspect=(float)w/(float)h;
}
@Override
public void onDrawFrame(GL10 glUnused) {
if (!validProgram) return;
GLES20.glVertexAttribPointer(GLES.positionHandle, 3, GLES20.GL_FLOAT, false, 0, DummyBuffer);
GLES20.glVertexAttribPointer(GLES.normalHandle, 3, GLES20.GL_FLOAT, false, 0, DummyBuffer);
GLES20.glVertexAttribPointer(GLES.texcoordHandle, 2, GLES20.GL_FLOAT, false, 0, DummyBuffer);
Speed = new StringTexture(speedText,16, Color.WHITE, Color.BLACK);
Distance = new StringTexture(distanceText,16, Color.WHITE, Color.BLACK);
Time = new StringTexture(timeText,16, Color.WHITE, Color.BLACK);
GLES.disableTexture();
GLES.enableShading();
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT |
GLES20.GL_DEPTH_BUFFER_BIT);
GLES.gluPerspective(pMatrix,
viewingangle,
aspect,
.01f,
120.0f);
GLES.setPMatrix(pMatrix);
float[] c1Matrix=new float[16];
float[] c2Matrix=new float[16];
Matrix.setLookAtM(c1Matrix, 0,
0f,0f,0f,
0.0f, 0.0f, -1.0f,
0.0f, 1.0f, 0.0f);
Matrix.multiplyMM(cMatrix, 0, c1Matrix, 0, c2Matrix, 0); //cMatrix = c1Matrix * c2Matrix
GLES.setCMatrix(cMatrix);
GLES.setLightPosition(LightPos);
GLES.disableShading();
GLES.enableShading();
GLES.enableTexture();
// Нарисуйте текст скорости
Matrix.setIdentityM(mMatrix, 0);
Matrix.translateM(mMatrix, 0, 0f, 1.0f, 0f); // Позиция для текста скорости
Matrix.scaleM(mMatrix, 0, 0.4f, 0.01f, 0.4f);
GLES.updateMatrix(mMatrix);
Speed.setTexture();
MyTexRectangular.draw(1f, 1f, 1f, 1f, 20.f);
// Нарисуйте текст расстояния
Matrix.setIdentityM(mMatrix, 0);
Matrix.translateM(mMatrix, 0, 0f, 2.0f, 0f); // Позиция для текста расстояния
Matrix.scaleM(mMatrix, 0, 0.4f, 0.01f, 0.4f);
GLES.updateMatrix(mMatrix);
Distance.setTexture();
MyTexRectangular.draw(1f, 1f, 1f, 1f, 20.f);
// Нарисуйте текст времени
Matrix.setIdentityM(mMatrix, 0);
Matrix.translateM(mMatrix, 0, 0f, 3.0f, 0f); // Позиция для текста времени
Matrix.scaleM(mMatrix, 0, 0.4f, 0.01f, 0.4f);
GLES.updateMatrix(mMatrix);
Time.setTexture();
MyTexRectangular.draw(1f, 1f, 1f, 1f, 20.f);
GLES.disableTexture();
}
}
п/с: если что-то не так с моим кодом, пожалуйста, скажите мне! Я все еще столкнулся с проблемами, особенно с MyGLRenderer.java
Ответ или решение
Отображение непрерывно обновляющихся данных, таких как хронометр и информация о пройденном расстоянии на OpenGL ES 2.0 для Android, может быть достигнуто с помощью внедрения текстур, которые обновляются каждый раз при изменении данных. Ваш подход к отрисовке текста на текстуре и добавлению текстуры на прямоугольную поверхность является подходящим. Давайте детально разберем ваш вопрос и предложим некоторые улучшения.
Шаги для создания приложения
-
Подготовка текстур для текста:
Вы уже создали классStringTexture
, который генерирует текстуру из заданной строки. Убедитесь, что текстура обновляется каждый раз, когда изменяются данные о скорости, расстоянии или времени. ВMyGLRenderer.java
вы можете обновить текстуры после получения новых данных. Например:@Override public void onDrawFrame(GL10 glUnused) { if (!validProgram) return; // Обновляем текстуры с данными Speed = new StringTexture(speedText, 16, Color.WHITE, Color.BLACK); Distance = new StringTexture(distanceText, 16, Color.WHITE, Color.BLACK); Time = new StringTexture(timeText, 16, Color.WHITE, Color.BLACK); // Далее идет код отрисовки ... }
-
Передача данных из Activity в MyGLRenderer:
Ваша основнаяActivity
должна вызывать методы установки текста вMyGLRenderer
, когда новые данные получены. Для этого создайте метод, который будет принимать значения скорости, расстояния и времени, и обновлять соответствующие поля. Например:private void updateGLRenderer() { runOnUiThread(() -> { // Обновите текстовые данные в рендерере myGLSurfaceView.renderer.setSpeedText(speed); myGLSurfaceView.renderer.setDistanceText(totalDistance); myGLSurfaceView.renderer.setTimeText(elapsedTime); }); }
-
Использование OpenGL для отрисовки текстур с текстом:
Убедитесь, что вы правильно вызываете функции OpenGL для установки текстуры и ее отрисовки как прямоугольника на экране, как вы это уже сделали в методеonDrawFrame
. -
Оптимизация обновления текстур:
Чтобы избежать создания текстуры каждый кадр и улучшить производительность, вы можете обновлять текстуры только при необходимости. Например, если скорость или расстояние изменились, создайте новые текстуры только в этом случае.
Примечания к коду
-
Управление потоками:
Помните, что обновление данных текущее и отрисовка должны выполняться в основном потоке. Ваша реализация, использующаяrunOnUiThread()
, эффективна для этого. -
Обработка жизненного цикла:
Убедитесь, что вашаActivity
корректно управляет обновлением местоположения и сохраняет состояние, когда она становится невидимой (например, вonPause()
). -
Выделение своих текстур:
ВStringTexture.java
вы можете добавить метод для уничтожения текстур, чтобы избежать утечек памяти.
Заключение
Таким образом, вы можете ввести непрерывное обновление данных на OpenGL ES 2.0 в вашем проекте. Основной акцент следует сделать на правильной передаче данных из Activity
в рендерер и эффективном обновлении текстур. Регулярные оптимизации и минимизация создания объектов во время отрисовки обеспечат вашему приложению хорошую производительность. Если у вас возникнут дополнительные вопросы или необходимо более детальное объяснение определенных аспектов, не стесняйтесь задавать их.