Иконка ресурса

Быстрая коллизия прицела и плоскости

Версия(и) 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;
1.png
2.png
3.png
Автор
NotYuki
Просмотры
922
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок

Другие ресурсы пользователя NotYuki

Сверху