16 August, 2025

Face snapping

Operáciou, ktorá je podobná operácii vrcholového prichytávania, je prichytávanie polygónu (Face). Niekde sa uvádza Face ako štvorec (obdĺžnik), ktorý súvisí s dvoma trojuholníkmi a niekde sa uvádza ako polygón. V každom prípade sa jedná o synonymum. Vytvoriť teda Face snapping je už na základe Vertex snappingu relatívne jednoduchá záležitosť. Pri algoritme Vertex snappingu sa pri nájdení jedného vertexu, ostatné vertexy dohľadávali. V tomto prípade to bude analogické. Zistia sa súradnice (x, y, z) jednotlivých vertexov pre štvorec (Face) a nad týmito súradnicami vertexov sa postupne budú generovať úsečky, v prípade Unity to bude predstavovať trieda LineRenderer. Určite by bolo možné použiť aj objekt štvorca, ak by existovala nejaká funkcia pre tento objekt, avšak aj štvorec obsahuje 4 vrcholy, ktoré by sme museli definovať, priradiť. Takže z hľadiska výpočtového času by bolo vykreslenie za pomoci úsečiek alebo štvorca ekvivalentné. Je zaujímavé spomenúť jednu vec. V počítačovej grafike pojem hrana ako taká prakticky neexistuje. Je len záležitosťou indexu v indices poli. Teda, nie je prakticky možné označiť hranu explicitne z nejakých súradníc tak ako je možné označiť jednotlivé vrcholy za pomoci špecifických súradníc, x, y, z. Mohla by existovať nejaká metaprezentácia, napr. grafová reprezentácia z matematiky, ale v konečnom dôsledku by musela byť táto reprezentácia pregenerovaná do indices poľa. Pretože na vykreslenie objektov v 3D súradnicovej sústave v priemyselných grafických štandardoch, DirectX/OpenGL a mnohých iných, je nutné definovať vrcholy a hrany. Aj keď definujeme hranu, nevieme ju explicitne označiť akosi jednotkovo. Že teda zavoláme nejakú funkciu, kde budú uložené hrany a po dopyte napr. s označením hrany H7, nám systém vráti hranu H7. Mohlo by to existovať, ale museli by sme si my dodatočne doimplementovať vrstvu, ktorá by toto riešila. Takže z toho vyplýva, že na označenie hrany budeme potrebovať 2 vrcholy, pretože hrana ako taká je definovaná dvoma vrcholmi.

Moje programové kódy pre LineFace a MeshFace snapping:

(GIT, LineFace: https://github.com/indicatrix-git/tycoon-terrain-enhanced/blob/main/SnapLineFace.cs)

(GIT, MeshFace: https://github.com/indicatrix-git/tycoon-terrain-enhanced/blob/main/SnapMeshFace.cs)

public Vector3 SnapLineFace(Vector3 hitPoint)
    {
        float[] Xaxis = new float[4];
        float[] Yaxis = new float[4];
        float[] Zaxis = new float[4];

        int xx = (int)hitPoint.x; float x = hitPoint.x - xx;
        int zz = (int)hitPoint.z; float z = hitPoint.z - zz;


        if (x >= 0.0f && x <= 1.0f)
        {
            Xaxis[0] = xx;
            Xaxis[1] = xx;
            Xaxis[2] = xx + 1;
            Xaxis[3] = xx + 1;
        }


        if (z >= 0.0f && z <= 1.0f)
        {
            Zaxis[0] = zz;
            Zaxis[1] = zz + 1;
            Zaxis[2] = zz + 1;
            Zaxis[3] = zz;
        }


        Yaxis[0] = FunctionalValueY(Xaxis[0], Zaxis[0]);
        Yaxis[1] = FunctionalValueY(Xaxis[1], Zaxis[1]);
        Yaxis[2] = FunctionalValueY(Xaxis[2], Zaxis[2]);
        Yaxis[3] = FunctionalValueY(Xaxis[3], Zaxis[3]);


        lineRenderer01.SetPosition(0, new Vector3(Xaxis[0], Yaxis[0], Zaxis[0]));
        lineRenderer01.SetPosition(1, new Vector3(Xaxis[1], Yaxis[1], Zaxis[1]));

        lineRenderer02.SetPosition(0, new Vector3(Xaxis[1], Yaxis[1], Zaxis[1]));
        lineRenderer02.SetPosition(1, new Vector3(Xaxis[2], Yaxis[2], Zaxis[2]));

        lineRenderer03.SetPosition(0, new Vector3(Xaxis[2], Yaxis[2], Zaxis[2]));
        lineRenderer03.SetPosition(1, new Vector3(Xaxis[3], Yaxis[3], Zaxis[3]));

        lineRenderer04.SetPosition(0, new Vector3(Xaxis[0], Yaxis[0], Zaxis[0]));
        lineRenderer04.SetPosition(1, new Vector3(Xaxis[3], Yaxis[3], Zaxis[3]));


        return GetCenterFace(hitPoint);
    }



    public Vector3 SnapMeshFace(Vector3 hitPoint)
    {
        Vector3[] verticesFace;
        Vector3[] normalsFace;
        Vector2[] uvFace;
        int[] trianglesFace;

        float[] Xaxis = new float[4];
        float[] Yaxis = new float[4];
        float[] Zaxis = new float[4];

        int xx = (int)hitPoint.x; float x = hitPoint.x - xx;
        int zz = (int)hitPoint.z; float z = hitPoint.z - zz;

        float dy = 0.01f;

        if (x >= 0.0f && x <= 1.0f)
        {
            Xaxis[0] = xx;
            Xaxis[1] = xx;
            Xaxis[2] = xx + 1;
            Xaxis[3] = xx + 1;
        }


        if (z >= 0.0f && z <= 1.0f)
        {
            Zaxis[0] = zz;
            Zaxis[1] = zz + 1;
            Zaxis[2] = zz + 1;
            Zaxis[3] = zz;
        }


        Yaxis[0] = FunctionalValueY(Xaxis[0], Zaxis[0]);
        Yaxis[1] = FunctionalValueY(Xaxis[1], Zaxis[1]);
        Yaxis[2] = FunctionalValueY(Xaxis[2], Zaxis[2]);
        Yaxis[3] = FunctionalValueY(Xaxis[3], Zaxis[3]);


        verticesFace = new Vector3[]
        {
            new Vector3(Xaxis[0], Yaxis[0] + dy, Zaxis[0]),
            new Vector3(Xaxis[1], Yaxis[1] + dy, Zaxis[1]),
            new Vector3(Xaxis[2], Yaxis[2] + dy, Zaxis[2]),
            new Vector3(Xaxis[3], Yaxis[3] + dy, Zaxis[3])
        };


        trianglesFace = new int[]
        {
            0, 1, 2,
            2, 3, 0
        };


        normalsFace = new Vector3[]
        {
            -Vector3.forward,
            -Vector3.forward,
            -Vector3.forward,
            -Vector3.forward
        };


        uvFace = new Vector2[]
        {
            new Vector2(Xaxis[0], Zaxis[0]),
            new Vector2(Xaxis[1], Zaxis[1]),
            new Vector2(Xaxis[2], Zaxis[2]),
            new Vector2(Xaxis[3], Zaxis[3])
        };


        meshFace.Clear();
        meshFace.vertices = verticesFace;
        meshFace.triangles = trianglesFace;
        meshFace.normals = normalsFace;
        meshFace.uv = uvFace;
        meshFace.RecalculateNormals();

        return GetCenterFace(hitPoint);
    }
Uvedený kód je platný pre tzv. LineFace a tzv. MeshFace. Je to analogické, v prvom prípade máme Face pozostávajúci z 4 úsečiek a v druhom prípade máme Face pozostávajúci z Mesh-u. Mesh je pojem z počítačovej grafiky, ktorý označuje plochu, ktorá môže mať definovanú farbu alebo aj textúru (obrázok). V prípade LineFace je to z kódu pochopiteľné v zmysle toho, že teda nájdeme 4 vertexy a tieto 4 vertexy priradíme jednotlivým úsečkám. V prípade MeshFace je to analogické, kde vytvoríme bázicky vertices a indices (triangleFace) a dodatočne aj UV mapy a normálu. Rovnako pre 4 vertexy v rámci tvorby Mesh plôšky, priradíme jednotlivé súradnice týmto vertexom. Pre indices maticu už len priradíme priradené 4 vertexy v smere hodinových ručičiek v niektorej z 3 konštelácií podľa definície tvorby Mesh plôšky. Pri VerticesFace figuruje malý prírastok dy pre y súradnicu (y = 0.01). Je to z toho dôvodu, aby bolo vidieť samotný Face. Ak by tam tento prírastok nebol, tak úroveň plôšky, Face by bola na rovnakej úrovni ako úroveň samotných vertexov pre terén, ktorý obsahuje tiež textúru. Viditeľnosť dvoch Mesh objektov s textúrou s rovnakými súradnicami znemožňuje viditeľnosť jedného z nich, resp. v limitnom prípade by figurovala Mesh plôška pod úrovňou terénu a teda toto je najpravdepodobnejší dôvod toho, že sa samotná Face plôška nezobrazí na teréne, ale tesne pod ňou. Z toho dôvodu som zadal prírastok dy na y súradnicu. Pridávam ešte kód pre získanie funkčnej hodnoty zo súradníc x a z:
float FunctionalValueY(float snapX, float snapZ)
    {
        float snapY = 0;

        for (int Ci = 0, Cz = 0; Cz <= TerrainManager.instance.terrainWidth; Cz++)
        {
            for (int Cx = 0; Cx <= TerrainManager.instance.terrainWidth; Cx++, Ci++)
            {
                if (TerrainManager.instance.coords[Ci].x == snapX &&
                    TerrainManager.instance.coords[Ci].z == snapZ)
                {
                    snapY = TerrainManager.instance.coords[Ci].y;
                }
            }
        }
        return snapY;
    }
Napísal som to úplne intuitívne, tak jednoducho. Samozrejme, je možné to napísať sofistikovanejšie, ale minimálne v prvej verzii môjho kódu to nechávam zatiaľ v takejto základnej forme a možno to už bude aj konečná forma. V nasledujúcom videu to popisujem podrobnejšie:
Share:

0 comments:

Post a Comment