Jump to content

[SA|SCM|CLEO] 3D wereld positie projecteren op de HUD


Recommended Posts

Geplaatst: (bewerkt)

3D wereld positie projecteren op de HUD

suggesties voor een duidelijkere titel zijn welkom :puh:

 

Benodigdheden

GTA:SA (eventueel met CLEO) en Sannybuilder

basis SCM-kennis (Basis SCM-scripten: tutorial door @Dutchy3010 & @PatrickW)

kennis mbt geheugen (Werken met geheugen (adressen) in CLEO: tutorial door @Crypteq)

Omdat we zaken in het geheugen aanpassen, zullen mijn voorbeelden waarschijnlijk niet op alle versies van SA werken.

---------------------------

 

Abstract

In deze tutorial toon ik hoe je een 3D punt kan projecteren op het scherm. Dit is leuk om bijvoorbeeld de health van een actor te tonen met tekst op het scherm (zoals in de video hierboven).

In SA wordt dit ook gebruikt om de prijs van een huis te tonen boven de koop pickup.

FIMCvBn.png

 

Natuurlijk kan je dit ook gebruiken om textures op het scherm te plaatsen, bijvoorbeeld save icoontjes voor huizen:

project-fs8-fs8.png.4dc74b8dff074acc44868f7f77a5fa44.png

 

Inhoud

1. Basisvoorbeeldcode

2. Theorie

3. Methode met hulp van ingebouwde functies

4. Manuele methode

5. Ingekorte manuele methode dankzij arraynotatie

6. Manuele methode zonder CLEO

7. Afwerking

 

1. Basisvoorbeeldcode

Hieronder staat een stuk code waarin een actor gespawnt wordt. Zijn health wordt door een textdraw getoond in het midden van het scherm.

De coordinaten van de actor zitten in variabelen 0@ 1@ 2@ (xyz). De bedoeling is dan om dat 3D punt te projecteren en op te slaan in 0@ en 1@ (xy).

Als placeholder worden 0@ en 1@ nu gewoon ingesteld zodat de textdraw in het midden van het scherm staat.

 

Deze code kan je gebruiken als je graag actief de tutorial meevolgt en zelf de resultaten wilt zien.

 

0247: load_model #MALE01
038B: load_requested_models

:MODEL_LOAD
00D6: if 
8248:   not model #MALE01 available 
004D: jump_if_false @MODEL_SPAWN 
0001: wait 0 ms 
0002: jump @MODEL_LOAD 

:MODEL_SPAWN
009A: 30@ = create_actor_pedtype 4 model #MALE01 at 2460.1829 -1655.1241 13.3359

0249: release_model #MALE01
 
:MAIN_LOOP
0001: wait 0

056D:   actor 30@ defined
004D: jump_if_false @MAIN_LOOP

00A0: store_actor 30@ position_to 0@ 1@ 2@
000B: 2@ += 1.0 // l+=i (float)
0226: 31@ = actor 30@ health

// ------
// hier moeten de coordinaten 0@ 1@ 2@ geprojecteerd worden
// voorlopig wordt dit in het midden van het scherm gezet
0007: 0@ = 320.0 // l=i (float)
0007: 1@ = 224.0 // l=i (float)
// ------

03F0: enable_text_draw 0
0340: set_text_draw_RGBA 255 33 255 255
033F: set_text_draw_letter_size 0.15 0.95
0342: set_text_draw_centered 1
081C: draw_text_outline 0 RGBA 0 0 0 255
060D: draw_text_shadow 1 rgba 0 0 0 255
0348: enable_text_draw_proportional 0
045A: draw_text_1number 0@ 1@ GXT 'NUMBER' number 31@  // ~$~1~

0002: jump @MAIN_LOOP

 

2. De theorie

Om een punt uit de wereld te projecteren op het scherm, moet dat punt 'simpelweg' vermenigvuldigd worden met de cameraviewmatrix.

De cameraviewmatrix staat in het geheugen op adres 0xB6FA2C, maar deze moet eerst nog getransponeerd worden (= rijen en kolommen omwisselen).

De cameraviewmatrix is een 4x4 matrix met daarin 16 floating point getallen (= 4 bytes per getal).

cameraviewmatrix 0xB6FA2C

| _11 _12 _13 _14 |
| _21 _22 _23 _24 |
| _31 _32 _33 _34 |
| _41 _42 _43 _44 |

0xB6FA2C = _11
0xB6FA30 = _12
0xB6FA34 = _13
0xB6FA38 = _14
     ..
0xB6FA68 = _44

Vermenigvuldiging (eerst transponeren (daarvoor staat het T-symbool)):

| _11 _12 _13 _14 |T | x |   | _11 _21 _31 _41 | | x |   | _11 * x + _21 * y + _31 * z + _41 * 1 |
| _21 _22 _23 _23 |  | y | = | _12 _22 _32 _42 | | y | = | _12 * x + _22 * y + _32 * z + _42 * 1 |
| _31 _32 _33 _34 |  | z |   | _13 _23 _33 _43 | | z |   | _13 * x + _23 * y + _33 * z + _43 * 1 |
| _41 _42 _43 _44 |  | 1 |   | _14 _24 _34 _44 | | 1 |   | _14 * x + _24 * y + _34 * z + _44 * 1 |

Dus:

x = _11 * _x + _21 * _y + _31 * _z + _41
y = _12 * _x + _22 * _y + _32 * _z + _42
z = _13 * _x + _23 * _y + _33 * _z + _43

_x _y _z = de 3D coordinaten
x y z = de geprojecteerde coordinaten

Na deze bewerking hebben we de geprojecteerde xy coordinaten, maar deze zijn geprojecteerd op een vlak dat niet overeenkomt met het scherm.

Het vlak is een vierkant, en z bevat de zijde van dat vierkant.

Een uitkomst kan bv zijn x = 15, y = 15, z = 30. Hieruit kun je afleiden dat de positie dus in het midden van het scherm moet staan.

Om correcte coordinaten te hebben, moeten de x en y waarden nog gedeeld worden door z.

Daarna moeten ze nog vermenigvuldigd worden met de grootte van het scherm (textdraws werken altijd op een canvas van 640x448, dus x * 640 en y * 448).

x = x * 640 / z
y = y * 448 / z

 

3. Methode met hulp van ingebouwe functies

In SA zit er al een functie die de matrixvermenigvuldiging kan doen voor ons. Die kunnen we dan aanroepen met CLEO. De functie zit op adres 0x59C890 en verwacht 3 parameters: het adres van het originele punt, het adres van de matrix, en het adres waar het resulterende punt in opgeslaan moet worden.

 

Het adres van de matrix hebben we al: 0xB6FA2C.

Om een adres te hebben van de wereldcoordinaten, kunnen we een aantal bytes zetten in ons script en het geheugenadres van die positie opvragen via een label:

0AC6: 6@ = label @PCOORDS offset

// ergens in het script, op een plaats die nooit uitgevoerd wordt
:PCOORDS
hex
00 00 00 00
00 00 00 00
00 00 00 00
end

Het label met die 12 bytes moeten op een plaats staan die nooit uitgevoerd kan worden. Dat stukje data wordt gebruikt om waardes op te slaan. Dus als het script daarover loopt, zal het hoogstwaarschijnlijk niet lukken om dit te interpreteren als correcte opcodes. Als gevolg zou het spel crashen.

 

Natuurlijk moeten de correcte coordinaten gebruikt worden, dus we kunnen dit wegschrijven naar die positie:

0A8C: write_memory 6@ size 4 value 0@ vp 0
000A: 6@ += 4 // l+=i (int)
0A8C: write_memory 6@ size 4 value 1@ vp 0
000A: 6@ += 4 // l+=i (int)
0A8C: write_memory 6@ size 4 value 2@ vp 0
000E: 6@ -= 8 // l-=i (int)

De x, y en z waarden moeten weggeschreven worden, dus 3 keer write_memory en iedere keer 4 bytes opschuiven. Op het einde wordt er 8 afgetrokken van het adres om terug het juiste adres te hebben.

Voor het resulterende punt moet er ook een label gemaakt worden met daarin 12 bytes plaats, maar die moet niet opgevuld worden. Achteraf moet uit die positie het resultaat uitgelezen worden.

Na de vermenigvuldiging moeten de xy coordinaten wel nog aangepast zodat ze de juiste scherm positie hebben, en niet zomaar op een willekeurig vlak (zie theorie).

 

De code wordt:

 

0AC6: 6@ = label @PCOORDS offset
0A8C: write_memory 6@ size 4 value 0@ vp 0 // source x
000A: 6@ += 4 // l+=i (int)
0A8C: write_memory 6@ size 4 value 1@ vp 0 // source y
000A: 6@ += 4 // l+=i (int)
0A8C: write_memory 6@ size 4 value 2@ vp 0 // source z
000E: 6@ -= 8 // l-=i (int)

0AC6: 5@ = label @PSCREEN offset

0AA5: call 0x59C890 num_params 3 pop 3 in 6@ matrix 0xB6FA2C out 5@

0A8D: 0@ = read_memory 5@ size 4 vp 0 // result x (float)
000A: 5@ += 4 // l+=i (int)
0A8D: 1@ = read_memory 5@ size 4 vp 0 // result y (float)
000A: 5@ += 4 // l+=i (int)
0A8D: 2@ = read_memory 5@ size 4 vp 0 // result z (float)

0013: 0@ *= 640.0 // l*=i (float)
0013: 1@ *= 448.0 // l*=i (float)

0073: 0@ /= 2@ // l/=l (float)
0073: 1@ /= 2@ // l/=l (float)


// ergens in het script, op een plaats die nooit uitgevoerd wordt
:PSCREEN
hex
00 00 00 00
00 00 00 00
00 00 00 00
end

:PCOORDS
hex
00 00 00 00
00 00 00 00
00 00 00 00
end

 

Let op, als je naar de andere kant kijkt, kan het zijn dat je daar opeens de textdraw ziet 'zweven'. Dit kan je zien in de video bovenaan. De oplossing hiervoor is om te kijken als de resulterende z coordinaat (2@) positief is. Als het niet positief is, dan kijkt de speler naar de andere kant en moet je de textdraw niet tonen!

 

Voor deze methode zijn er 2 variabelen nodig (buiten 0@ 1@ 2@)

 

4. Manuele methode

Natuurlijk kunnen we de matrixvermenigvuldiging ook manueel uitrekenen. Hoe dit moet staat beschreven in het theoriegedeelte. Ik denk niet dat er veel uitleg nodig is, dit vergt enkel wat meer opcodes en concentratie.

 

// 10@ 11@ 12@ worden gebruikt als tijdelijke plaats voor x y z

// x = _x * _11 + _y * _21 + _z * _31 + 1 * _41
0007: 10@ = 0.0 // l=i (float)

0087: 3@ = 0@ // l=l (float)
0A8D: 4@ = read_memory 0xB6FA2C size 4 vp 0 // _11
006B: 3@ *= 4@ // l*=l (float)
005B: 10@ += 3@  // l+=l (float)

0087: 3@ = 1@ // l=l (float)
0A8D: 4@ = read_memory 0xB6FA3C size 4 vp 0 // _21
006B: 3@ *= 4@ // l*=l (float)
005B: 10@ += 3@  // l+=l (float)

0087: 3@ = 2@ // l=l (float)
0A8D: 4@ = read_memory 0xB6FA4C size 4 vp 0 // _31
006B: 3@ *= 4@ // l*=l (float)
005B: 10@ += 3@  // l+=l (float)

0A8D: 4@ = read_memory 0xB6FA5C size 4 vp 0 // _41
005B: 10@ += 4@  // l+=l (float)

// y = _x * _12 + _y * _22 + _z * _32 + 1 * _42
0007: 11@ = 0.0 // l=i (float)

0087: 3@ = 0@ // l=l (float)
0A8D: 4@ = read_memory 0xB6FA30 size 4 vp 0 // _12
006B: 3@ *= 4@ // l*=l (float)
005B: 11@ += 3@  // l+=l (float)

0087: 3@ = 1@ // l=l (float)
0A8D: 4@ = read_memory 0xB6FA40 size 4 vp 0 // _22
006B: 3@ *= 4@ // l*=l (float)
005B: 11@ += 3@  // l+=l (float)  

0087: 3@ = 2@ // l=l (float)
0A8D: 4@ = read_memory 0xB6FA50 size 4 vp 0 // _32
006B: 3@ *= 4@ // l*=l (float)
005B: 11@ += 3@  // l+=l (float)
                                  
0A8D: 4@ = read_memory 0xB6FA60 size 4 vp 0 // _42
005B: 11@ += 4@  // l+=l (float)

// z = _x * _13 + _y * _23 + _z * _33 + 1 * _43
0007: 12@ = 0.0 // l=i (float)

0087: 3@ = 0@ // l=l (float)
0A8D: 4@ = read_memory 0xB6FA34 size 4 vp 0 // _13
006B: 3@ *= 4@ // l*=l (float)
005B: 12@ += 3@  // l+=l (float)

0087: 3@ = 1@ // l=l (float)
0A8D: 4@ = read_memory 0xB6FA44 size 4 vp 0 // _23
006B: 3@ *= 4@ // l*=l (float)
005B: 12@ += 3@  // l+=l (float)

0087: 3@ = 2@ // l=l (float)
0A8D: 4@ = read_memory 0xB6FA54 size 4 vp 0 // _33
006B: 3@ *= 4@ // l*=l (float)
005B: 12@ += 3@  // l+=l (float)

0A8D: 4@ = read_memory 0xB6FA64 size 4 vp 0 // _43
005B: 12@ += 4@  // l+=l (float)

// x *= 640.0 / z
// y *= 448.0 / z
0007: 0@ = 640.0 // l=i (float)
0007: 1@ = 448.0 // l=i (float) 

006B: 0@ *= 10@ // l*=l (float)
006B: 1@ *= 11@ // l*=l (float)

0073: 0@ /= 12@ // l/=l (float)
0073: 1@ /= 12@ // l/=l (float)

 

Zelfde opmerking als hierboven: als 2@ negatief is, moet je de textdraw niet tonen.

Voor deze methode zijn er 5 variabelen nodig (buiten 0@ 1@ 2@)

 

5. Ingekorte manuele methode dankzij arraynotatie

In de manuele methode kun je waarschijnlijk zien dat er enorm veel herhaling is. Dit kan behoorlijk simpel weggewerkt worden door gebruik te maken van de arraynotatie. ([SA|SCM] Arrays)

Het enige wat veranderd is de tijdelijke xyz variabele en het geheugenadres. De tijdelijke variabele kan vervangen worden door een arraynotatie. Voor het geheugenadres kan er simpelweg iedere keer een optelling gemaakt worden: in de vorige code kun je zien dat er iedere keer +4 wordt gedaan, en dan -0x2C voor het volgende stuk.

 

0006: 21@ = 0xB6FA2C // l=i (int)
for 20@ = 0 to 2

    0007: 10@(20@,1i) = 0.0 // l=i (float)

    0087: 3@ = 0@ // l=l (float)
    0A8D: 4@ = read_memory 21@ size 4 vp 0 // _11/_12/_13
    006B: 3@ *= 4@ // l*=l (float)
    000A: 21@ += 0x10 // l+=i (int)
    005B: 10@(20@,1i) += 3@  // l+=l (float)
    
    0087: 3@ = 1@ // l=l (float)
    0A8D: 4@ = read_memory 21@ size 4 vp 0 // _21/_22/_23
    006B: 3@ *= 4@ // l*=l (float)
    000A: 21@ += 0x10 // l+=i (int)
    005B: 10@(20@,1i) += 3@  // l+=l (float)
    
    0087: 3@ = 2@ // l=l (float)
    0A8D: 4@ = read_memory 21@ size 4 vp 0 // _31/_32/_33
    006B: 3@ *= 4@ // l*=l (float)
    000A: 21@ += 0x10 // l+=i (int)
    005B: 10@(20@,1i) += 3@  // l+=l (float)
    
    0A8D: 4@ = read_memory 21@ size 4 vp 0 // _41/_42/_43
    000E: 21@ -= 0x2C // l-=i (int)
    005B: 10@(20@,1i) += 4@  // l+=l (float)
end

0087: 2@ = 12@ // l=l (float)

// x *= 640.0 / z
// y *= 448.0 / z
0007: 0@ = 640.0 // l=i (float)
0007: 1@ = 448.0 // l=i (float) 

006B: 0@ *= 10@ // l*=l (float)
006B: 1@ *= 11@ // l*=l (float)

0073: 0@ /= 2@ // l/=l (float)
0073: 1@ /= 2@ // l/=l (float)

 

Zelfs dit kan natuurlijk nog korter. De enige verandering is de 0@ 1@ 2@ variabelen, dus dit kan simpelweg vervangen worden door nog een arraynotatie.

 

0006: 21@ = 0xB6FA2C // l=i (int)
for 20@ = 0 to 2

    0007: 10@(20@,1i) = 0.0 // l=i (float)

    for 22@ = 0 to 2
        0087: 3@ = 0@(22@,1i) // l=l (float)
        0A8D: 4@ = read_memory 21@ size 4 vp 0 // _11/_12/_13 _21/_22/_23 _31/_32/_33
        006B: 3@ *= 4@ // l*=l (float)
        000A: 21@ += 0x10 // l+=i (int)
        005B: 10@(20@,1i) += 3@  // l+=l (float)
    end
    
    0A8D: 4@ = read_memory 21@ size 4 vp 0 // _41/_42/_43
    000E: 21@ -= 0x2C // l-=i (int)
    005B: 10@(20@,1i) += 4@  // l+=l (float)

end  

0087: 2@ = 12@ // l=l (float)

// x *= 640.0 / z
// y *= 448.0 / z
0007: 0@ = 640.0 // l=i (float)
0007: 1@ = 448.0 // l=i (float) 

006B: 0@ *= 10@ // l*=l (float)
006B: 1@ *= 11@ // l*=l (float)

0073: 0@ /= 2@ // l/=l (float)
0073: 1@ /= 2@ // l/=l (float)

 

Dit is een stuk korter dan de gewone manuele code, maar er zijn ook meer variabelen nodig: 8 (= 3 meer dan de lange manuele methode) (buiten 0@ 1@ 2@)

Zelfde opmerking als hierboven: als 2@ negatief is, moet je de textdraw niet tonen.

 

6. Manuele methode zonder CLEO

Ten slotte toon ik nog hoe je dit allemaal kan doen zonder CLEO (de read_memory opcode is enkel beschikbaar als je CLEO hebt). Hiervoor wordt er ADMA gebruikt, hoe dat allemaal werkt kan je in een andere tutorial van mij lezen: [SA|SCM] ADMA en geheugenmanipulatie zonder CLEO.

Hiervoor neem ik de vorige code (met arrays), en vervang ik alle read_memory opcodes door adma geheugenmanipulatie.

 

0006: 21@ = 0xB6FA2C // l=i (int)
for 20@ = 0 to 2

    0007: 10@(20@,1i) = 0.0 // l=i (float)

    for 22@ = 0 to 2
        0087: 3@ = 0@(22@,1i) // l=l (float)
        0085: 4@ = 21@ // l=l (int)
        000E: 4@ -= 0xA49960  // l-=i (int)
        0016: 4@ /= 4 // l/=i (int)
        006B: 3@ *= &0(4@,1i) // l*=g (float)
        000A: 21@ += 0x10 // l+=i (int)
        005B: 10@(20@,1i) += 3@  // l+=l (float)
    end
    
    0085: 4@ = 21@ // l=l (int)
    000E: 4@ -= 0xA49960  // l-=i (int)
    0016: 4@ /= 4 // l/=i (int)
    000E: 21@ -= 0x2C // l-=i (int)
    005B: 10@(20@,1i) += &0(4@,1i)  // l+=l (float)

end

0087: 2@ = 12@ // l=l (float)

// x *= 640.0 / z
// y *= 448.0 / z
0007: 0@ = 640.0 // l=i (float)
0007: 1@ = 448.0 // l=i (float) 

006B: 0@ *= 10@ // l*=l (float)
006B: 1@ *= 11@ // l*=l (float)

0073: 0@ /= 2@ // l/=l (float)
0073: 1@ /= 2@ // l/=l (float)

 

Zelfde opmerking als hierboven: als 2@ negatief is, moet je de textdraw niet tonen.

 

7. Afwerking

Zoals eerder vermeld, kan het zijn dat je de textdraw ziet 'zweven' als je naar de tegengestelde richting kijkt (wordt gedemonstreerd in de video bovenaan). Dit kan opgelost worden door te kijken naar de resulterende z coordinaat (2@). Als die niet positief is, dan kijkt de speler naar de andere kant en moet je de textdraw niet tonen!

 

Een andere manier is om te checken als het te projecteren punt wel te zien is op het scherm. Dit kan je doen met opcode 002C:

00C2:   sphere_onscreen 0@ 1@ 2@ radius 0.5

Die checkt als het punt zichtbaar is op het scherm. Let op: als je ver weg bent, is het punt ook niet zichtbaar volgens deze opcode, omdat je niet oneindig ver kan kijken!

Als je dit gebruikt, kan je de berekening vermijden als het uiteindelijk toch niet op het scherm staat, dus kun je de code ietswat performanter maken. Dit kan echter niet altijd gebruikt worden: als je bijvoorbeeld een lijn tekent, kan het zijn dat één punt niet op het scherm staat. Bij deze opcode zou je dan de lijn helemaal niet tekenen, terwijl het misschien geen probleem is als je gebruikmaakt van 2@.

 

--------

En dat is 'em! Applausje voor jezelf als je deze volledig doorlopen hebt :klap:, want het is niet bepaald de simpelste code.

Plaats een reactie als je hiermee leuke dingen maakt, altijd leuk om te weten :)
 

Met schermcoordinaten kun je nog andere leuke dingen doen, zoals lijnen tekenen. Dat kan met textures, of door echt lijnen te laten tekenen door de render engine (maar dit is nog een heel stuk moeilijker).

Spoiler

DKlSOtR.png

(dit is een mod dat de landingsbanen aangeeft van custom maps in een SA-MP server)

 

:cya:

 

Bewerkt: door robin_be

Een reactie plaatsen

Je kan nu een reactie plaatsen en pas achteraf registreren. Als je al lid bent, log eerst in om met je eigen account een reactie te plaatsen.

Gast
Op dit onderwerp reageren...

×   Je hebt text geplaatst met opmaak.   Opmaak verwijderen

  Only 75 emoji are allowed.

×   Je link is automatisch ingevoegd.   In plaats daarvan weergeven als link

×   Je vorige bewerkingen zijn hersteld.   Alles verwijderen

×   You cannot paste images directly. Upload or insert images from URL.

  • Recent actief   0 leden

    • Er zijn hier geen geregistreerde gebruikers aanwezig.
×
×
  • Create New...