Poly API: Recuperarea activelor 3D pentru aplicațiile Android VR și AR

Autor: Peter Berry
Data Creației: 14 Lang L: none (month-012) 2021
Data Actualizării: 4 Mai 2024
Anonim
Creating AR and VR Projects with Unreal Engine | Webinar
Video: Creating AR and VR Projects with Unreal Engine | Webinar

Conţinut


Aveți o idee grozavă pentru o aplicație mobilă pentru Realitate Virtuală (VR) sau Realitate Augmentată (AR), dar nu aveți idee cum să vă aduceți viziunea?

Dacă nu sunteți un dezvoltator Android, care se dovedește a fi și un artist 3D experimentat, atunci crearea tuturor activelor necesare pentru a oferi o experiență imersivă, de 360 ​​de grade, poate fi un proces descurajant.

Doar pentru că nu aveți timpul, resursele sau experiența necesară pentru a crea modele 3D, nu înseamnă că nu puteți construi o aplicație mobilă excelentă pentru VR sau AR! Există o gamă uriașă de resurse 3D disponibile gratuit pe World Wide Web, plus toate API-urile, cadrele și bibliotecile de care aveți nevoie pentru a descărca și a reda aceste active în aplicațiile dvs. Android.

Citiți Următorul: Acum puteți vizita orice site web folosind Daydream VR. Chiar și acela.

În acest articol, vom analiza Poly, un depozit online și API care vă pune mii de active 3D la îndemână. Până la sfârșitul acestui articol, veți fi creat o aplicație care preia un activ 3D Poly la timpul de execuție, apoi îl redă folosind popularul procesare pentru biblioteca Android.


Afișarea resurselor 3D cu Poly

Dacă ați făcut vreodată o dezvoltare în Unity, atunci depozitul Poly este similar cu Unity Asset Store - cu excepția faptului că totul în Poly este gratuit!

Multe dintre modelele 3D ale Poly sunt publicate sub licența Creative Commons, astfel încât nu sunteți liber să utilizați, modificați și remizați aceste active, atât timp cât acordați creatorului un credit adecvat.

Toate modelele 3D ale Poly sunt concepute pentru a fi compatibile cu platformele VR și AR ale Google, cum ar fi Daydream și ARCore, dar le poți folosi oriunde și oricum dorești - potențial, le poți folosi chiar și cu ARKit-ul Apple!

Când vine vorba de preluarea și afișarea activelor Poly, aveți două opțiuni. În primul rând, puteți descărca activele pe computerul dvs. și apoi să le importați în Android Studio, astfel încât acestea să fie livrate cu aplicația dvs. și să contribuie la dimensiunea APK-ului sau puteți recupera aceste active în timpul funcționării folosind API-ul Poly.


Poly API multiplă platformă, bazată pe REST, oferă acces programatic, numai în citire, la colecția uriașă de modele 3D ale Poly. Acest lucru este mai complicat decât legarea de active cu APK-ul dvs., dar există mai multe avantaje pentru regăsirea activelor Poly în timpul perioadei de execuție, mai ales că ajută la menținerea dimensiunii APK sub control, ceea ce poate afecta câți oameni descarcă aplicația.

De asemenea, puteți utiliza API-ul Poly pentru a oferi utilizatorilor mai multe opțiuni, de exemplu dacă dezvoltați un joc mobil, atunci puteți permite utilizatorilor să aleagă dintre o serie de modele de caractere.

Deoarece nu puteți modifica modelele Poly, puteți chiar să permiteți utilizatorilor să-și regleze caracterul ales, de exemplu, modificând culoarea părului sau a ochilor sau combinând-o cu alte elemente Poly, cum ar fi diferite arme și armuri. În acest fel, API-ul Poly vă poate ajuta să furnizați o gamă impresionantă de active 3D, cu mult spațiu de personalizare a experienței - și totul pentru o muncă relativ redusă. Utilizatorii dvs. vor fi convinși că ați petrecut o tonă de timp, creând meticulos toate aceste modele 3D!

Crearea unui proiect de modelare 3D

Vom crea o aplicație care recuperează un anumit activ Poly la prima lansare a aplicației, apoi va afișa acel element în modul ecran complet, la cererea utilizatorului.

Pentru a ne ajuta să regăsim acest activ, voi folosi Fuel, care este o bibliotecă de rețele HTTP pentru Kotlin și Android. Începeți prin a crea un proiect nou cu setările la alegere, dar atunci când vi se solicită optați pentru „Includeți suportul Kotlin”.

Toate apelurile pe care le efectuați către API-ul Poly trebuie să includă o cheie API, care este utilizată pentru a identifica aplicația și pentru a impune limitele de utilizare. În timpul dezvoltării și testării, veți utiliza adesea o cheie API fără restricții, dar dacă aveți de gând să eliberați această aplicație, trebuie să utilizați o cheie API restricționată pentru Android.

Pentru a crea o cheie restrânsă, va trebui să cunoașteți certificatul de semnare SHA-1 al proiectului dvs., deci să obținem aceste informații acum:

  • Selectați fila „Gradle” din Android Studio (unde este poziționat cursorul în imaginea următoare). Se deschide un panou „Proiecte Gradle”.

  • În panoul „Proiecte Gradle”, faceți dublu clic pentru a extinde „rădăcina” proiectului dvs., apoi selectați „Sarcini> Android> Raport de semnare”. Acesta deschide un panou nou în partea de jos a ferestrei Android Studio.
  • Selectați butonul „Comutați executări / mod text” (unde este poziționat cursorul în imaginea următoare).

Panoul „Run” se va actualiza acum pentru a afișa o mulțime de informații despre proiectul dvs., inclusiv amprenta sa SHA-1.

Creați un cont Google Platformă

Pentru a achiziționa cheia API necesară, veți avea nevoie de un cont Google Cloud Platform (GPC).

Dacă nu aveți un cont, atunci vă puteți înscrie la o probă gratuită de 12 luni, accesând pagina de încercare Cloud Cloud pentru pagina gratuită și urmând instrucțiunile. Rețineți că este necesară o carte de credit sau card de debit, dar în conformitate cu pagina Întrebări frecvente, aceasta este utilizată doar pentru a vă verifica identitatea și „nu veți fi taxat sau facturat în timpul procesului dvs. gratuit”.

Obțineți cheia Poly API

După ce v-ați înscris cu toții, puteți activa API-ul poli și creați cheia:

  • Întoarceți-vă la Consola GCP.
  • Selectați pictograma căptușită în colțul din stânga sus și alegeți „API-uri și servicii> Panou de bord”.
  • Selectați „Activați API-urile și serviciile.”
  • În meniul din stânga, alegeți „Altele”.
  • Selectați cardul „Poly API”.
  • Faceți clic pe butonul „Activați”.
  • După câteva momente, vei fi dus pe un nou ecran; deschideți meniul lateral și alegeți „API-uri și servicii> Credențiale”.

  • În fereastra pop-up ulterioară, selectați „Restrict key”.
  • Dați-i cheii un nume distinctiv.
  • În „Restricții de aplicare”, selectați „Aplicații Android”.
  • Selectați „Adăugați numele pachetului și amprenta digitală.”
  • Copiați / inserați amprenta SHA-1 a proiectului dvs. în câmpul „Amprentare certificat de semnare”.
  • Introduceți numele pachetului proiectului (acesta apare în Manifest și în partea de sus a fiecărui fișier de clasă).
  • Faceți clic pe „Salvați”.

Acum veți fi accesat pe ecranul „Credențiale” al proiectului dvs., care conține o listă cu toate cheile API dvs., inclusiv tasta API compatibilă poli pe care tocmai ați creat-o.

Dependențe de proiect: extensii de combustibil, P3D și Kotlin

Pentru a recupera și afișa activele Poly, vom avea nevoie de o mână de ajutor din partea unor biblioteci suplimentare:

  • Combustibil. În prezent, Poly nu are un set de instrumente oficial pentru Android, așa că va trebui să lucrați direct cu API utilizând interfața REST. Pentru a simplifica acest proces, voi folosi biblioteca de rețea HTTP Fuel.
  • Procesare pentru Android. Voi folosi randamentul P3D al acestei biblioteci pentru a afișa activul Poly.

Deschideți fișierul build.gradle al proiectului și adăugați aceste două biblioteci ca dependențe ale proiectului:

dependences {implement fileTree (include:, dir: libs) implementare "org.jetbrains.kotlin: kotlin-stdlib-jre7: $ kotlin_version" implementare com.android.support:appcompat-v7:27.1.1 // Adăugați biblioteca Combustibil / / implementare com.github.kittinunf.fuel: fuel-android: 1.13.0 // Adăugarea procesării pentru motorul Android // implementarea org.p5android: Processing-core: 4.0.1}

Pentru a face codul nostru mai concis, voi folosi și extensii Android Kotlin, deci adăugăm acest plugin în timp ce avem fișierul build.gradle deschis:

apply plugin: kotlin-android-extensions

În cele din urmă, întrucât preluăm activul de pe Internet, aplicația noastră are nevoie de permisiunea internetului. Deschideți manifestul și adăugați următoarele:

Adăugarea cheii API

De fiecare dată când aplicația noastră solicită un activ de la Poly, trebuie să includă o cheie API validă. Folosesc un text de locatar, dar tu trebuie sa înlocuiți acest marcator cu propria dvs. cheie API dacă aplicația va funcționa vreodată.

Adaug și o verificare, astfel încât aplicația să afișeze un avertisment dacă uitați să înlocuiți textul „INSERT-YOUR-API-KEY”:

import android.os.Bundle import android.support.v7.app.AppCompatActivity Clasa MainActivity: AppCompatActivity () {company object {const val APIKey = "INSERT-YOUR-API-KEY"} anulează distracția onCreate (salvatInstanceState: Bundle?) { super.onCreate (salvatInstanceState) setContentView (R.layout.activity_main) // Dacă cheia API începe cu „INSERT” ... // dacă (APIKey.startsWith ("INSERT")) {// afișează următorul toast ... .// Toast.makeText (aceasta, "Nu ați actualizat cheia API", Toast.LENGTH_SHORT) .show ()} else {... ... ...

Recuperarea activului

Puteți alege orice material pe site-ul Google Poly, dar voi folosi acest model al planetei Pământ.

Regăsiți un material folosind ID-ul său, care apare la sfârșitul slug-ului URL (evidențiat în ecranul anterior). Combinăm acest ID de activ cu gazda Poly API, care este „https://poly.googleapis.com/v1”.

import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.github.kittinunf.fuel.android.extension.responseJson import com.github.kittinunf.fuel .httpDownload import com.github.kittinunf.fuel.httpGet import kotlinx.android.synthetic.main.activity_main. * import java.io.File class MainActivity: AppCompatActivity () {object company {const val APIKey = "INSERT-YOUR-API -KEY "val assetURL =" https://poly.googleapis.com/v1/assets/94XG1XUy10q "} anulează distracția onCreate (salvatInstanceState: Bundle?) {Super.onCreate (salvatInstanceState) setContentView (R.layout.activity_main) if ( APIKey.startsWith ("INSERT")) {Toast.makeText (aceasta, "Nu ați actualizat cheia API", Toast.LENGTH_SHORT) .show ()} else {

În continuare, trebuie să facem o solicitare GET către adresa URL a activului, folosind metoda httpGet (). De asemenea, specific faptul că tipul de răspuns trebuie să fie JSON:

import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.github.kittinunf.fuel.android.extension.responseJson import com.github.kittinunf.fuel .httpDownload import com.github.kittinunf.fuel.httpGet import kotlinx.android.synthetic.main.activity_main. * import java.io.File class MainActivity: AppCompatActivity () {object company {const val APIKey = "INSERT-YOUR-API -KEY "val assetURL =" https://poly.googleapis.com/v1/assets/94XG1XUy10q "} anulează distracția onCreate (salvatInstanceState: Bundle?) {Super.onCreate (salvatInstanceState) setContentView (R.layout.activity_main) if ( APIKey.startsWith ("INSERT")) {Toast.makeText (aceasta, "Nu ați actualizat cheia API", Toast.LENGTH_SHORT) .show ()} else {// Efectuați un apel la server și apoi treceți datele folosind Metoda „listOf” // assetURL.httpGet (listOf („cheie” pentru APIKey)). ResponseJson {cerere, răspuns, rezultat -> // Faceți ceva cu răspunsul // rezultat.fold ({val as set = it.obj ()

Activul poate avea mai multe formate, cum ar fi OBJ, GLTF și FBX. Trebuie să stabilim dacă activul este în format OBJ.

În acest pas, recuperez și numele și adresa URL a tuturor fișierelor pe care trebuie să le descărcăm,
inclusiv fișierul principal al activului („root”), plus orice fișier de material și textură asociat („resurse”).

Dacă aplicația noastră nu poate prelua activul corect, atunci va afișa un toast care va informa utilizatorul.

import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.github.kittinunf.fuel.android.extension.responseJson import com.github.kittinunf.fuel .httpDownload import com.github.kittinunf.fuel.httpGet import kotlinx.android.synthetic.main.activity_main. * import java.io.File class MainActivity: AppCompatActivity () {object company {const val APIKey = "INSERT-YOUR-API -KEY "val assetURL =" https://poly.googleapis.com/v1/assets/94XG1XUy10q "} anulează distracția onCreate (salvatInstanceState: Bundle?) {Super.onCreate (salvatInstanceState) setContentView (R.layout.activity_main) if ( APIKey.startsWith ("INSERT")) {Toast.makeText (aceasta, "Nu ați actualizat cheia API", Toast.LENGTH_SHORT) .show ()} else {// Faceți o solicitare GET la adresa URL a activului // assetURL. httpGet (listOf ("cheie" pentru APIKey)). responseJson {cerere, răspuns, rezultat -> // Faceți ceva cu răspunsul // rezultat.fold ({val asset = it.obj () var objectURL: String? = null var materialLibraryName: String? = null var materialLibraryURL: șir? = null // Verificați formatul activului, folosind tabloul „formate” // val assetFormats = asset.getJSONArray („formate”) // Buclați toate formatele // pentru (i în 0 până la assetFormats.length ()) { val currentFormat = assetFormats.getJSONObject (i) // Utilizați formatType pentru a identifica tipul de format al acestei resurse. Dacă formatul este OBJ ... .// if (currentFormat.getString ("formatType") == "OBJ") {//...then recuperați fișierul „root” al acestei resurse, adică fișierul OBJ // objectURL = currentFormat. getJSONObject ("root") .getString ("url") // Recuperați toate dependențele fișierului rădăcină // materialLibraryName = currentFormat.getJSONArray ("resurse") .getJSONObject (0) .getString ("relativePath") materialLibraryURL = currentFormat.getJSONAr ("resurse") .getJSONObject (0) .getString ("url") break}} objectURL !! httpDownload (). destination {_, _ -> File (filesDir, "globeAsset.obj")} .response {_ , _, rezultat -> result.fold ({}, {// Dacă nu puteți localiza sau descărca fișierul OBJ, atunci afișați o eroare // Toast.makeText (aceasta, "Nu se poate descărca resursa", Toast.LENGTH_SHORT ) .show ()})} materialLibraryURL !!. httpDownload (). destination {_, _ -> File (filesDir, materialLibraryName)} .response {_, _, result -> result.fold ({}, {Toast. makeText (aceasta, "Imposibil de descărcat resursa", Toast.LENGTH_SHORT) .show ()})}}, { Toast.makeText (aceasta, "Nu se poate descărca resursa", Toast.LENGTH_SHORT) .show ()})}}}

În acest moment, dacă instalați proiectul pe smartphone-ul sau tableta Android sau pe dispozitivul virtual Android (AVD), atunci activul se va descărca cu succes, dar aplicația nu îl va afișa. Să rezolvăm acest lucru acum!

Crearea unui al doilea ecran: Adăugarea navigației

Vom afișa activul în modul ecran complet, astfel încât să actualizăm fișierul nostru principal_activity.xml pentru a include un buton care, atunci când este atins, va lansa Activitatea pe ecran complet.

Acum adăugăm onClickListener la sfârșitul fișierului MainActivity.kt:

import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.github.kittinunf.fuel.android.extension.responseJson import com.github.kittinunf.fuel .httpDownload import com.github.kittinunf.fuel.httpGet import kotlinx.android.synthetic.main.activity_main. * import java.io.File class MainActivity: AppCompatActivity () {object company {const val APIKey = "INSERT-YOUR-API -KEY "val assetURL =" https://poly.googleapis.com/v1/assets/94XG1XUy10q "} anulează distracția onCreate (salvatInstanceState: Bundle?) {Super.onCreate (salvatInstanceState) setContentView (R.layout.activity_main) if ( APIKey.startsWith ("INSERT")) {Toast.makeText (acesta, "Nu ați actualizat cheia API", Toast.LENGTH_SHORT) .show ()} else {assetURL.httpGet (listOf ("cheie" pentru APIKey)). responseJson {request, response, result -> result.fold ({val asset = it.obj () var objectURL: String? = null var materialLibraryName: String? = null var materialLibraryURL: Str ING? = null val assetFormats = asset.getJSONArray ("formate") pentru (i în 0 până la assetFormats.length ()) {val currentFormat = assetFormats.getJSONObject (i) if (currentFormat.getString ("formatType") == "OBJ" ) {objectURL = currentFormat.getJSONObject ("root") .getString ("url") materialLibraryName = currentFormat.getJSONArray ("resurse") .getJSONObject (0) .getString ("relativePath") materialLibraryURL = currentFormat.getJSONArray ) .getJSONObject (0) .getString ("url") break}} objectURL !! httpDownload (). destination {_, _ -> File (filesDir, "globeAsset.obj")} .response {_, _, rezultat -> result.fold ({}, {Toast.makeText (aceasta, "Imposibil de descărcat resursa", Toast.LENGTH_SHORT) .show ()})} materialLibraryURL !!. httpDownload (). destination {_, _ -> File (filesDir, materialLibraryName)} .response {_, _, result -> result.fold ({}, {Toast.makeText (aceasta, "Nu se poate descărca resursa", Toast.LENGTH_SHORT) .show ()})}}, {Toast.makeText (aceasta, "Imposibil de descărcat resursa", Toast.LENGTH_SHORT) .sh ow ()})} // Implementarea unui buton // displayButton.setOnClickListener {val intent = Intent (aceasta, SecondActivity :: class.java) startActivity (intent); }}}

Construirea unei pânze 3D

Acum, creăm Activitatea în care vom afișa activul nostru în modul ecran complet:

  • Faceți clic pe fișierul MainActivity.kt al proiectului dvs. și selectați „Nou> Fișier / clasă Kotlin”.
  • Deschideți funcția verticală „Kind” și selectați „Class”.
  • Dați acestei clase numele „SecondActivity”, apoi faceți clic pe „OK”.

Pentru a desena un obiect 3D, avem nevoie de o pânză 3D! Voi folosi procesarea pentru randarea P3D a bibliotecii Android, ceea ce înseamnă extinderea clasei PApplet, trecerea peste metoda de setări () și apoi trecerea P3D ca argument la metoda FullScreen (). De asemenea, trebuie să creăm o proprietate care să reprezinte activul Poly ca obiect PShape.

private fun displayAsset () {val canvas3D = obiect: PApplet () {var polyAsset: PShape? = null override settings fun () {fullscreen (PConstants.P3D)}

În continuare, trebuie să inițializăm obiectul PShape, trecând peste metoda setup (), apelând metoda loadShape () și apoi trecând calea absolută a fișierului .obj:

înlocuiește configurația distractivă () {polyAsset = loadShape (File (filesDir, "globeAsset.obj"). absolutePath)}

Pe baza pânzei P3D

Pentru a desena această pânză 3D, trebuie să înlocuim metoda draw ():

Anulează distracția de remiză () {fundal (0) formă (poliAsset)}}

În mod implicit, multe dintre activele preluate de la API-ul Poly sunt pe partea mai mică, așa că dacă rulați acest cod acum, este posibil să nu vedeți nici măcar activul, în funcție de configurația ecranului. Atunci când creați scene 3D, veți crea de obicei o cameră personalizată, astfel încât utilizatorul să poată explora scena și să vă vadă elementele 3D de la 360 de grade. Cu toate acestea, acest lucru nu depășește domeniul de aplicare al acestui articol, astfel încât voi modifica dimensiunea și poziția activului manual, pentru a mă asigura că acesta se potrivește confortabil pe ecran.

Puteți crește dimensiunea activului, trecând o valoare negativă metodei scalei ():

scală (-10f)

Puteți ajusta poziția activului în spațiul virtual 3D folosind metoda translate () și următoarele coordonate:

  • X. Poziționează activul de-a lungul axei orizontale.
  • Y. Poziționează activul de-a lungul axei verticale.
  • Z. Aceasta este axa „adâncime / înălțime”, care transformă un obiect 2D într-un obiect 3D. Valorile pozitive creează impresia că obiectul vine spre tine, iar valorile negative creează impresia că obiectul se îndepărtează de tine.

Rețineți că transformările sunt cumulate, deci tot ceea ce se întâmplă după ce funcția acumulează efectul.

Folosesc următoarele:

traduce (-50f, -100f, 10f)

Iată codul completat:

Înlocuiește fun draw () {background (0) scale (-10f) translate (-50f, -100f) // Atrageți activul apelând metoda forme () // forma (polyAsset)}}

În continuare, trebuie să creăm fișierul de layout corespunzător, unde vom adăuga panza 3D ca widget FrameLayout:

  • Faceți clic pe folderul „res> layout” al proiectului dvs.
  • Selectați „File file resource”.
  • Dați acestui fișier numele „Activity_second”, apoi faceți clic pe „OK”.

Acum avem FrameLayout „asset_view”, trebuie să anunțăm SecondActivity despre asta! Întoarceți-vă la fișierul SecondActivity.kt, creați o nouă instanță PFragment și indicați-o în direcția widget-ului „asset_view”:

import android.os.Bundle import android.support.v7.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_second. * import processing.android.PFragment import processing.core.PApplet import processing.core.PConstants import processing.core .PShape import clasa java.io.File SecondActivity: AppCompatActivity () {anulează distracția onCreate (salvatInstanceState: Bundle?) {Super.onCreate (salvatInstanceState) setContentView (R.layout.activity_second) displayAsset ()} private fun displayAsset ()} private fun displayAsset () canvas3D = obiect: PApplet () {var polyAsset: PShape? = null override fun settings () {fullscreen (PConstants.P3D)} override fun setup () {polyAsset = loadShape (File (filesDir, "globeAsset.obj"). absolutePath)} override fun draw () {background (0) scale (-10f) translate (-50f, -100f) formă (polyAsset)}} // Adăugați următoarele // val assetView = PFragment (canvas3D) assetView.setView (asset_view, this)}}

Ultimul pas, este adăugarea celei de-a doua activități la manifestul tău:

// Adăugați următoarele //

Testarea proiectului tău

Acum suntem gata să testăm proiectul finalizat! Instalați-l pe dispozitivul Android sau pe AVD și asigurați-vă că aveți o conexiune la Internet activă. De îndată ce aplicația se lansează, va descărca activul și apoi îl puteți vizualiza dând butonul „Afișare afișare” o apăsare.

Puteți descărca acest proiect complet de la GitHub.

Înveliți

În acest articol, am analizat cum să utilizăm API-ul Poly pentru a prelua un activ 3D la timpul de execuție și cum să afișăm acel material folosind biblioteca de procesare pentru Android. Credeți că API-ul Poly are potențialul de a face dezvoltarea VR și AR accesibilă mai multor persoane? Spuneți-ne în comentariile de mai jos!

Legate de

  • Google va aduce aplicații AR la „sute de milioane” de dispozitive Android în 2018
  • Google vă va învăța despre AI și despre învățarea automată gratuit
  • 15 cele mai bune jocuri VR pentru Google Cardboard
  • 10 cele mai bune aplicații VR pentru Google Cardboard
  • Ce este Google Fuchsia? Acesta este noul Android?
  • Ce este Google Duplex? - caracteristici, data lansării și multe altele
  • Cum să creați o aplicație VR pentru Android în doar 7 minute
  • Căști VR mobile - care sunt cele mai bune opțiuni?

Încercați ă configurați un cont VPN (rețea privată virtuală) pe telefonul dv. Android? De fapt, nu ete prea greu de făcut, dar ce ete exact un VPN și de ce ar trebui ă foloiți unul? Exact ata ne ...

Configurarea unui router poate părea o arcină decurajantă, dar ete mult mai ușor decât crezi. Trebuie doar ă știți ce cablu merge unde și apoi ă configurați câteva etări pentru ca routerul ă...

Noi Recomandăm