- Версия(и) Minecraft
- Любая
Java:
@Mod.EventBusSubscriber(modid = ModInfo.MODID, value = Side.CLIENT)
public class ClientEventHandler {
//Off-heap буфер для загрузки матриц из видеопамяти
private static final FloatBuffer direct16 = ByteBuffer.allocateDirect(16<<2).order(ByteOrder.nativeOrder()).asFloatBuffer();
private static int mouseX,mouseY;
private static float dst;
private static final Vector4f[]
//Постоянные точки, описывающие плоскость в простанстве модели. Необходимо всего 3 точки, четвертая лишь для тестового рендера.
plane = new Vector4f[]{new Vector4f(),new Vector4f(),new Vector4f(),new Vector4f()},
//Точки плоскости после умножения на MVP матрицу. Для каждого кадра они отличаются.
pt = new Vector4f[]{new Vector4f(),new Vector4f(),new Vector4f()};
private static final Vector3f
v1v0 = new Vector3f(), v2v0 = new Vector3f(), rov0 = new Vector3f(),
n = new Vector3f(), nn = new Vector3f(),
q = new Vector3f(), nq = new Vector3f(),
ray = new Vector3f(0,0,1);
private static final Matrix4f
MVMatrix = new Matrix4f(),
PMatrix = new Matrix4f(),
MVPMatrix = new Matrix4f();
static {
plane[0].set(0,6,0,1);
plane[1].set(1,6,1,1);
plane[2].set(0,7,0,1);
plane[3].set(1,7,1,1);
}
//Вывод тестового текста на экран
@SubscribeEvent
public static void guiTest(RenderGameOverlayEvent.Post event) {
RenderGameOverlayEvent.ElementType t = event.getType();
if(t==RenderGameOverlayEvent.ElementType.TEXT){
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
font.drawString("X: "+mouseX+" Y: "+mouseY,10,10, 0xFFFF0000);
font.drawString("Dist: "+dst,10,20, 0xFFFF0000);
}
}
@SubscribeEvent
public static void postRender(RenderWorldLastEvent event){
glPushMatrix();
//Переходим к мировым координатам
glTranslated(
-TileEntityRendererDispatcher.staticPlayerX,
-TileEntityRendererDispatcher.staticPlayerY,
-TileEntityRendererDispatcher.staticPlayerZ);
//Загружаем матрицы из видеопамяти
direct16.rewind();
glGetFloat(GL_MODELVIEW_MATRIX,direct16);
MVMatrix.load(direct16);
direct16.rewind();
glGetFloat(GL_PROJECTION_MATRIX,direct16);
PMatrix.load(direct16);
Matrix4f.mul(PMatrix, MVMatrix, MVPMatrix);
//Переводим координаты плоскости в экранное пространство.
for (int i = 0; i < 3; i++) {
Matrix4f.transform(MVPMatrix, plane[i], pt[i]);
}
//Находим точку пересечения между вектором взгляда игрока и плоскостью
v1v0.set(pt[1].x-pt[0].x,pt[1].y-pt[0].y,pt[1].z-pt[0].z);
v2v0.set(pt[2].x-pt[0].x,pt[2].y-pt[0].y,pt[2].z-pt[0].z);
rov0.set(-pt[0].x,-pt[0].y,-pt[0].z);
Vector3f.cross(v1v0,v2v0,n);
Vector3f.cross(rov0,ray,q);
n.negate(nn);
q.negate(nq);
float d = 1.0F/Vector3f.dot(ray,n);
float u = d*Vector3f.dot(q,v1v0);
float v = d*Vector3f.dot(nq,v2v0);
float dist;
//Собственно проверка на коллизию, изменяя которую можно проверять коллизию более сложных форм.
if( u<0.0 || u>1.0 || v<0.0 || v>1.0 ){
dist = -1.0F;
} else {
dist = d*Vector3f.dot(nn,rov0);
}
//Конвертация UV координат в экранные координаты относительно плоскости
final ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
int i1 = scaledresolution.getScaledWidth();
int j1 = scaledresolution.getScaledHeight();
int k1 =-1;
int l1 =-1;
if(dist!=-1){
k1=(int)(v * i1);
l1=(int)(j1 - u * j1);
}
mouseX=k1;
mouseY=l1;
dst=dist;
//Тестовый рендер необходимой фигуры для визуализации.
Color c = new Color(dist!=-1?0xFF00FF00:0xFFFF0000);
glPushAttrib(GL_ALL_ATTRIB_BITS);
glDisable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBegin(GL_TRIANGLE_STRIP);
glColor3f(c.getRed()/255F,c.getGreen()/255F,c.getBlue()/255F);
for (int i = 0; i < 4; i++) {
glVertex3f(plane[i].x,plane[i].y,plane[i].z);
}
glEnd();
glPopAttrib();
glPopMatrix();
}
Java:
private static final Vec3d[] staticPlane = {
new Vec3d(0,6,0),
new Vec3d(1,6,1),
new Vec3d(0,7,0)
};
private static final Vec3d[] vecArray = new Vec3d[3];
@SubscribeEvent
public static void onTickEvent(TickEvent.PlayerTickEvent event){
EntityPlayer player = event.player;
if(player!=null){
float partialTicks = 1;
Vec3d playerPos = player.getPositionEyes(partialTicks);
Vec3d ray = player.getLook(partialTicks);
for (int i = 0; i < 3; i++) {
vecArray[i]=staticPlane[i].subtract(playerPos);
}
Vec3d v1v0 = vecArray[1].subtract(vecArray[0]);
Vec3d v2v0 = vecArray[2].subtract(vecArray[0]);
Vec3d rov0 = negate(vecArray[0]);
Vec3d n = v1v0.crossProduct(v2v0);
Vec3d q = rov0.crossProduct(ray);
Vec3d nn = negate(n);
Vec3d nq = negate(q);
float d = (float)(1.0F/ray.dotProduct(n));
float u = (float)(d*q.dotProduct(v1v0));
float v = (float)(d*nq.dotProduct(v2v0));
float dist;
if( u<0.0 || u>1.0 || v<0.0 || v>1.0 ){
dist = -1.0F;
} else {
dist = (float)(d*nn.dotProduct(rov0));
}
//Число условное необязательное конвертирование
int k1 =-1;
int l1 =-1;
if(dist!=-1){
k1=(int)(v * 1366);
l1=(int)(768 - u * 768);
}
//Дальше делаем с этими данными что хотим
if(event.side==Side.SERVER){
NetworkHandler.sendToPlayer(new CPacketServerPlaneTest(k1,l1,dist),(EntityPlayerMP)player);
} else {
ClientEventHandler.mouseX2=k1;
ClientEventHandler.mouseY2=l1;
ClientEventHandler.dst2=dist;
}
}
}
private static Vec3d negate(Vec3d source){
return new Vec3d(-source.x,-source.y,-source.z);
}
Некоторые уточнения:
- Написано это было на 1.12.2, но его можно использовать на любых версиях с незначительными изменениями;
- Старая версия привязана к камере игрока, что может привести к некорректной работе при виде от 3-го лица. Новая версия привязана к энтити игрока и работает при любой ориентации камеры;
Данный алгоритм на вход требует только MVP матрицу, 3 точки, описывающие плоскость и координаты перехода от локальных координат игрока к мировым для размещения плоскости в мире;- Новая версия алгоритма требует только 3 точки, описывающие плоскость и объект энтити игрока;
- Новая версия избавлена от GL контекста для совместимости с сервером;
- Данный алгоритм на выходе выдаёт не только UV координаты (или экранные координаты для GUI после конвертации), но и дистанцию до точки пересечения, что дает больше возможностей использования;
- С помощью данного алгоритма можно сделать интерактивный GUI, расположенный в мире, если отрендерить GUI использовав в качестве mouseX и mouseY выходные данные алгоритма в отдельный framebuffer, а затем отрендерить его как текстуру. Главное не забывать, что для адаптации ванильных и модовых GUI одного рендера мало;
- Данный алгоритм имеет высокую точность и скорость из-за использования векторной математики.
- Плоскость необходимо описывать так:
первая точка - U=0, V=0;
вторая - U=1, V=0;
третья - U=0, V=1;