Lista desplegable en realidad aumentada para tus modelos 3D

Haz un combo box (drop-down list) para cargar diferentes modelos 3D en tus aplicaciones de realidad aumentada

ComboBox en Realidad Aumentada
ComboBox en Realidad Aumentada

Todos sabemos lo indispensable que es la interfaz de una app.

Gran parte del enfoque que le damos hoy en día a la realidad aumentada se centra en el seguimiento y la renderización de modelos 3D y dejamos a un lado la interfaz del usuario (UI).

Los combo box o listas despegables son indispensables en la creación de juego de realidad aumentada.

En el contexto de las aplicaciones y juegos de RA, los combos box ofrecen una solución elegante y eficiente para gestionar la complejidad sin abrumar visualmente al usuario.

Sin ellos por ejemplo personalizar un personaje o cambiar elementos del escenario podria requerir un boton individual para cada opcion llenando la pantalla y obstruyendo la vista del entorno aumentado.

Ventajas de los combobox en RA y Juego AR:

  • Optimización del Espacio Visual: En un entorno de RA, la pantalla del dispositivo es la ventana al mundo aumentado. Los combobox ocupan un espacio mĆ­nimo hasta que se activan, permitiendo que el usuario se enfoque en la experiencia inmersiva sin distracciones de la UI. Esto es vital para mantener la sensación de Ā«realidadĀ» y evitar la sobrecarga cognitiva.
  • Gestión de la Complejidad: A medida que los juegos y aplicaciones de RA crecen en caracterĆ­sticas, tambiĆ©n lo hace el nĆŗmero de opciones disponibles. Los combobox permiten agrupar lógicamente elementos relacionados (como diferentes modelos 3D, texturas, armas o personajes), haciendo que la interfaz sea mĆ”s limpia y fĆ”cil de navegar.
  • Interactividad Intuitiva: Son un patrón de interfaz de usuario ampliamente reconocido. Los usuarios ya saben cómo interactuar con ellos, lo que reduce la curva de aprendizaje y mejora la usabilidad de la aplicación de RA.
  • Flexibilidad y Dinamismo: Permiten cambiar el contenido dinĆ”micamente, como la carga de distintos modelos 3D en tiempo real, sin necesidad de recargar la aplicación o la escena completa. Esto es particularmente Ćŗtil para juegos donde los jugadores pueden alternar entre diferentes objetos o configuraciones sobre la marcha.

En resumen, los combobox no son solo un elemento de UI; son facilitadores clave para crear experiencias de realidad aumentada que son tanto poderosas en funcionalidad como limpias y agradables visualmente, un balance esencial para el Ʃxito en el desarrollo de juegos y aplicaciones de RA.

Si estas buscando mƔs herramientas para crear tus aplicaciones de realidad aumentada te recomiendo este post: Software y herramientas para crear realidad aumentada

si por el contrario estas buscando una opción gratuita y enfocada a la web te recomiendo este otro post: Realidad Aumentada con javascript – librerias

Pasemos al código y su anÔlisis:


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mind-ar@1.2.2/dist/mindar-image-aframe.prod.js"></script>
</head>
<body>
<div class="container">
<a-scene
mindar-image="imageTargetSrc: ./targets.mind; maxTrack: 2; filterMinCF:1; filterBeta:10000; warmupTolerance:0.5; missTolerance:0.5;"
color-space="sRGB"
renderer="colorManagement: true, physicallyCorrectLights: true"
vr-mode-ui="enabled: false"
device-orientation-permission-ui="enabled: false"
>
<a-assets>
<a-entity id="sphereModel" geometry="primitive: sphere; radius: 0.5" material="color: blue"></a-entity>
<a-entity id="boxModel" geometry="primitive: box; width: 0.8; height: 0.8; depth: 0.8" material="color: red"></a-entity>
<a-entity id="triangleModel" geometry="primitive: cylinder; radius: 0.6; height: 0.1; segmentsRadial: 3" material="color: green"></a-entity>
<a-entity id="hexagonModel" geometry="primitive: cylinder; radius: 0.5; height: 0.1; segmentsRadial: 6" material="color: purple"></a-entity>
<a-entity id="torusModel" geometry="primitive: torus; radius: 0.5; radiusTubular: 0.1" material="color: orange"></a-entity>
<a-asset-item id="shibaModel" src="./shiba.glb"></a-asset-item>
</a-assets>

<a-camera active="false" position="0 0 0"></a-camera>

<a-entity mindar-image-target="targetIndex: 0">
<a-entity id="model-container">
<a-text
id="initialText"
value="selecciona tu modelo del combo box"
align="center"
color="black"
position="0 0 0"
scale="0.5 0.5 0.5"
></a-text>
</a-entity>
</a-entity>
</a-scene>

<div class="controls">
<label for="modelSelector">Selecciona un modelo:</label>
<select id="modelSelector" onchange="loadModel()">
<option value="none">Selecciona tu modelo</option>
<option value="sphere">Esfera</option>
<option value="box">Cubo</option>
<option value="triangle">TriƔngulo</option>
<option value="hexagon">HexƔgono</option>
<option value="torus">Dona</option>
<option value="shiba">Modelo 3D</option>
</select>
</div>
</div>
</body>
</html>

Hablemos de lo mÔs importante de este fragmento de código.

  • <a-scene>: El corazón de la aplicación de RA. Es el contenedor principal para todo el contenido 3D y AR.
  • mindar-image=Ā»imageTargetSrc: ./targets.mind; …Ā»: Indica dónde encontrar el archivo de los marcadores (targets.mind). Aqui puedes hacer tus marcadores de MindAR Image Targets Compiler | mind-ar-js y ajusta parĆ”metros de seguimiento como maxTrack (nĆŗmero mĆ”ximo de targets o marcadores a seguir), filterMinCF, filterBeta, warmupTolerance y missTolerance para optimizar el rendimiento de seguimiento.
  • Atributos como color-space, renderer, vr-mode-ui y device-orientation-permission-ui configuran aspectos de renderizado y la interfaz de usuario de A-Frame.
  • <a-assets>: Aqui se precargan recursos pesados como modelos 3D, texturas, sonidos, etc. Esto asegura que los recursos estĆ©n disponibles antes de ser usados en la escena, mejorando la experiencia del usuario. AquĆ­ definimos:

a-entity con geometrƭas y materiales bƔsicos para la esfera, cubo, triƔngulo, hexƔgono y dona.

a-asset-item con el id=»shibaModel» y src=»./shiba.glb» para cargar nuestro modelo 3D GLB. Puedes encontrar este modelo libre para descargar desde la comunidad de https://sketchfab.com/

AquĆ­ te dejo el link de Shiba.glb Shiba – Download Free 3D model by zixisun02 (@zixisun51) [faef9fe]

  • <a-camera>: Se encarga de la gestión de la cĆ”mara para la experiencia de RA.
  • <a-entity mindar-image-target=Ā»targetIndex: 0″>: Este es el Ā«punto de anclajeĀ» para el contenido AR. Todo lo que estĆ© dentro de esta etiqueta aparecerĆ” sobre la imagen de referencia detectada por MindAR (en este caso, el primer target en targets.mind, indicado por targetIndex: 0).
  • <a-entity id=Ā»model-containerĀ»>: Un contenedor vacĆ­o inicialmente, que serĆ” poblado dinĆ”micamente con los modelos 3D seleccionados del combobox.
  • <a-text id=Ā»initialTextĀ»>: Un elemento de texto de A-Frame que muestra el mensaje inicial Ā«Elije tu modelo del combo boxĀ». Este texto se ocultarĆ” una vez que el usuario seleccione un modelo.
  • <div class=Ā»controlsĀ»>: Un contenedor HTML estĆ”ndar que alberga nuestra interfaz de usuario (el combobox). Se posiciona en la esquina superior izquierda de la pantalla.
  • <label for=Ā»modelSelectorĀ»>: La etiqueta de texto para el combobox.
  • <select id=Ā»modelSelectorĀ» onchange=Ā»loadModel()Ā»>: El elemento combobox (<select>).
  • id=Ā»modelSelectorĀ»: Un identificador Ćŗnico para acceder a Ć©l desde JavaScript.
  • onchange=Ā»loadModel()Ā»: Un evento que se dispara cada vez que el valor seleccionado en el combobox cambia, llamando a la función JavaScript loadModel().
  • <option>: Define cada una de las opciones disponibles en el combobox, con un value (valor interno para el JavaScript) y el texto visible para el usuario.

Pasemos ahora al CSS:

<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* Fuente mƔs moderna */
}
.container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.controls {
position: absolute;
top: 20px; /* Un poco mƔs de margen */
left: 20px; /* Un poco mƔs de margen */
z-index: 999;
background-color: rgba(30, 30, 50, 0.8); /* Fondo oscuro semitransparente */
padding: 15px; /* MƔs padding */
border-radius: 10px; /* Bordes mƔs redondeados */
box-shadow: 0 0 15px rgba(0, 255, 255, 0.6); /* Sombra con efecto neón */
border: 1px solid rgba(0, 255, 255, 0.8); /* Borde delgado de neón */
}
label {
color: #00ffff; /* Color de texto neón */
font-weight: bold;
margin-bottom: 10px;
display: block; /* Para que ocupe su propia lĆ­nea */
text-shadow: 0 0 5px rgba(0, 255, 255, 0.8); /* Sombra de texto neón */
}
select {
padding: 10px 15px; /* MƔs padding */
font-size: 16px;
border-radius: 6px; /* Bordes redondeados */
border: 2px solid #00ffff; /* Borde de neón */
background-color: #0d0d1a; /* Fondo muy oscuro */
color: #00ffcc; /* Texto verde cian */
appearance: none; /* Elimina el estilo predeterminado del navegador */
-webkit-appearance: none;
-moz-appearance: none;
cursor: pointer;
outline: none; /* Quita el contorno al enfocar */
box-shadow: 0 0 8px rgba(0, 255, 204, 0.7); /* Sombra interna para efecto de brillo */
transition: all 0.3s ease; /* Transición suave para hover/focus */
width: 180px; /* Ancho fijo para el select */
}
select:hover {
background-color: #1a1a33; /* Color de fondo mƔs claro al pasar el mouse */
box-shadow: 0 0 12px rgba(0, 255, 204, 0.9); /* Sombra mƔs intensa al pasar el mouse */
}
select:focus {
border-color: #66ffff; /* Borde mƔs brillante al enfocar */
box-shadow: 0 0 15px rgba(102, 255, 255, 1); /* Sombra mƔs brillante al enfocar */
}
select option {
background-color: #0d0d1a; /* Fondo oscuro para las opciones */
color: #00ffcc; /* Texto verde cian para las opciones */
}
</style>

Ahora expliquemos la parte mas importante de todo este proyecto y la creacion de aplicaciones de realidad aumentada empezando desde cero, el codigo JavaScript:


<script>
document.addEventListener("DOMContentLoaded", function () {
var modelSelector = document.getElementById("modelSelector");
modelSelector.addEventListener("change", function () {
var initialText = document.getElementById("initialText");
if (modelSelector.value !== "none") {
initialText.setAttribute("visible", "false");
} else {
initialText.setAttribute("visible", "true");
}
});
});

function loadModel() {
var selector = document.getElementById("modelSelector");
var selectedValue = selector.value;
var modelContainer = document.getElementById("model-container");

Array.from(modelContainer.children).forEach(child => {
if (child.id !== 'initialText') {
modelContainer.removeChild(child);
}
});

if (selectedValue === "none") {
document.getElementById("initialText").setAttribute("visible", "true");
return;
} else {
document.getElementById("initialText").setAttribute("visible", "false");
}

let newModel;
switch (selectedValue) {
case "sphere":
newModel = document.createElement("a-entity");
newModel.setAttribute("geometry", "primitive: sphere; radius: 0.5");
newModel.setAttribute("material", "color: blue");
break;
case "box":
newModel = document.createElement("a-entity");
newModel.setAttribute("geometry", "primitive: box; width: 0.8; height: 0.8; depth: 0.8");
newModel.setAttribute("material", "color: red");
break;
case "triangle":
newModel = document.createElement("a-entity");
newModel.setAttribute("geometry", "primitive: cylinder; radius: 0.6; height: 0.1; segmentsRadial: 3");
newModel.setAttribute("material", "color: green");
newModel.setAttribute("rotation", "90 0 0");
break;
case "hexagon":
newModel = document.createElement("a-entity");
newModel.setAttribute("geometry", "primitive: cylinder; radius: 0.5; height: 0.1; segmentsRadial: 6");
newModel.setAttribute("material", "color: purple");
newModel.setAttribute("rotation", "90 0 0");
break;
case "torus":
newModel = document.createElement("a-entity");
newModel.setAttribute("geometry", "primitive: torus; radius: 0.5; radiusTubular: 0.1");
newModel.setAttribute("material", "color: orange");
break;
case "shiba":
newModel = document.createElement("a-entity");
newModel.setAttribute("gltf-model", "#shibaModel");
newModel.setAttribute("scale", "0.1 0.1 0.1");
break;
default:
return;
}
newModel.setAttribute("position", "0 0 0");
modelContainer.appendChild(newModel);
}
</script>

  • document.addEventListener(Ā«DOMContentLoadedĀ», …): Este bloque de código se ejecuta una vez que todo el DOM (Document Object Model) de la pĆ”gina ha sido cargado. Su propósito es inicializar el comportamiento de ocultar/mostrar el texto inicial.

Detecta cambios en el modelSelector y, si se selecciona cualquier opción que no sea «Elije tu modelo», oculta el initialText. Si se vuelve a seleccionar «Elije tu modelo», lo hace visible de nuevo.

  • function loadModel(): Esta función es la que se ejecuta cada vez que el usuario selecciona una nueva opción en el combobox.

Obtención de Elementos: Recupera referencias al select y al model-container donde se cargarÔn los modelos 3D.

Limpieza del Contenedor: Array.from(modelContainer.children).forEach(…) itera sobre todos los hijos del model-container. Si un hijo no es el initialText (el texto de bienvenida), se elimina, asegurando que solo haya un modelo 3D visible a la vez.

Control del Texto Inicial: Si el valor seleccionado es «none», muestra el texto inicial y detiene la función. De lo contrario, oculta el texto.

Creación DinÔmica del Modelo (switch statement): Utiliza una sentencia switch para determinar qué modelo crear basÔndose en el selectedValue del combobox:

Para las formas bƔsicas (esfera, cubo, triƔngulo, hexƔgono, dona), crea una nueva <a-entity> y le asigna atributos geometry y material de A-Frame. Se ajusta la rotation para el triƔngulo y hexƔgono para que aparezcan planos sobre el marcador.

Para el modelo 3D GLB (shiba), crea una <a-entity> y le asigna el atributo gltf-model=»#shibaModel», referenciando el modelo precargado en <a-assets>. Es crucial el scale («0.1 0.1 0.1») aquí, ya que los modelos GLB pueden tener tamaños muy variados y a menudo necesitan ser escalados para encajar en la escena.

  • Posicionamiento y Adición: newModel.setAttribute(Ā«positionĀ», Ā«0 0 0Ā»); asegura que el modelo se posicione en el origen del mindar-image-target. Finalmente, modelContainer.appendChild(newModel); aƱade el modelo creado dinĆ”micamente a la escena de RA.

Pasemos ahora al video para ver el resultado de nuestra app RA.

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Scroll al inicio
0
Would love your thoughts, please comment.x
()
x