0

Cómo crear un planetario virtual con Blender Game Engine

¡Saludos, homo fabers! Hoy voy a documentar un proyecto en el que estoy trabajando, que trata de construir un planetario virtual con Blender Game Engine. Con él podremos ver el cielo real que se ve desde cualquier punto del planeta, e incluso ver el cielo del pasado o del futuro. ¡Es un proyecto muy interesante para todos aquellos que os guste la astronomía!

Para seguir este tutorial es necesario tener un por mano la interfaz de Blender y haber programado en Python. Algunos conocimentos astronómicos tampoco vendrían mal 😉

Bien, ¿qué tendremos que hacer?

  • En primer lugar, poner música. ¡No se puede trabajar sin ella! Hoy os propongo Los Planetas, de Gustav Holst.
  • Importar la librería ephem dentro de Blender. ¿Qué es ephem? Se trata de una librería para realizar cálculos astronómicos basada en XEphem (que se utiliza para calcular trayectorias de planetas, satélites…). Nos facilitará mucho la tarea de posicionar los objetos celestes.
  • Construiremos mallas para las estrellas, los planetas, y configuraremos la escena para que parezca un cielo nocturno.
  • Crearemos un script para dibujar todo el cielo, con las estrellas y los planetas. Para ello usaremos la base de datos sky2000, ¡que contiene más de 300.000 estrellas!

 


1. Instalar la librería Ephem en Blender

Nota: este tutorial se ha empezado a escribir el día 26/9/2017, y actualmente la versión más reciente de ephem es la 3.7.6. Puede ser que en versiones futuras el método de instalación cambie.

Instalar la librería ephem en Blender me ha llevado muchos problemas, y la forma que os voy a explicar para conseguirlo quizá no es elegante… ¡pero es lo único que me ha funcionado!

Primero id a la web oficial de blender y bajad la última versión. Descomprimid el fichero.

Descargad ephem desde este enlace y descomprimidla en un directorio que recordéis. Os creará un directorio con un nombre parecido a ‘ephem-3.7.6.0’. Abrid esta carpeta e instalad la librería con el script setup.py. Si estáis en windows, abrid la consola dentro del directorio y escribid:

setup.py install

Y en Linux:

python setup.py install

Veréis también una carpeta llamada ‘ephem’ dentro del directorio. Tenéis que copiarla.

Ahora buscáis la carpeta de Blender que os habéis descargado. Dentro hay una sub-carpeta cuyo nombre es el número de la versión. Abridla -> python -> lib -> python3.5 y pegad dentro la carpeta ‘ephem’.

Dentro la carpeta del sistema dónde se haya instalado la librería y encontraréis un archivo terminado en .so (si estáis en Linux) o en .dll (si estáis en Windows). Copiad este archivo y pegadlo dentro de la carpeta ‘ephem’ que habéis copiado dentro del directorio de instalación de Blender.

¿Cómo puede comprobarse si la librería se ha instalado bien? Abrid blender y poned la interfaz en modo scripting:

En la consola, escribid ‘import ephem’. Si todo ha funcionado correctamente, no habrá ningún mensaje de error:


2. Preparar la escena de Blender

Para que no tengáis que buscar vuestras propias imágenes de estrellas y planetas, os he preparado algunos assets que podéis descargar desde aquí y utilizar en vuestro planetario.

Empezad por crear una nueva escena de blender. Lo primero será cambiar el motor de ‘Blender Render’ a ‘Blender Game’:

 

Ahora hay que añadir el “suelo” para tener una referencia de dónde está el horizonte. En este tutorial lo haremos simplemente añadiendo un círculo y escalándolo, pero si estáis inspirados podéis crear una pequeña escena como un bosque o un campo nocturno. También podéis extraer algun modelo de Blendswap como he hecho yo para mi escena:

Mi escena es un campo árido. He modificado también las intensidad de las lámparas para que parezca que sea medianoche.

Si queréis algo básico, añadid un círculo lleno en el centro y escaladlo a 50:

Ahora elevad el cubo un poco por encima del nivel del suelo (a Z = 1.5 estará bien):

Colocad la cámara justo encima del cubo (a Z = 2), y de tal forma que quede mirando en la dirección positiva del eje Y:

 

Emparentad la cámara con el cubo (seleccionad la cámara, después el cubo y pulsad Ctrl+P->Object).

Seleccionad la cámara y abrid las propiedades de cámara:

Debajo de ‘Lens’, cambiad el parámetro ‘End’ del ‘Clipping’ por 200:

También hay que cambiar el color de fondo del cielo a negro.

Ahora crearemos las mallas para los planetas y las estrellas. Cambiad la capa activa:

Para crear los cuerpos celestes utilizaremos un truquillo para que, cuándo tengamos que colocarlos en la bóveda celeste, no tengamos que hacer cálculos muy complicados de conversión a coordenadas esféricas. Sólo nos bastará con saber la Altitud y el Azimut del objeto celeste.

El centro de todos los objetos será un Empty colocado en las coordenadas [0,0,0]. Su malla estará en la posición [-150, 0, 0], emparentada a este Empty. Cuándo se cree una nueva estrella, el programa sabrá su Altitud (eje Y) y su Azimut (eje Z), así que rotará el Empty este mismo número de grados. La malla de la estrella también rotará y se moverá hasta su posición real en la bóveda celeste.

Empezad por añadir un Empty de tipo Plain Axes en el centro de la escena 3D.

 

Cambiad el nombre de este empty por ‘estrella’:

Ahora hay que importar la imagen de la estrella como un plano. Para simplificar este proceso, id a File->Use Preferences -> Add-ons y añadid el Add-on ‘Import-Export: Import Images as Planes’:

Ahora id a la escena 3D y pulsad Ctrl+A->Add->Images as Planes y buscad la imagen ‘estrella.png’. Veréis que ha aparecido un plano en la escena 3D:

 

Si cambiáis el modo de visualización de Solid a Textured, veréis que la textura que aparece sobre el plano es un poco extraña, y no se parece mucho a una estrella…

 

Con el objeto seleccionado, id a la pestaña de Materiales y cambiad sus propiedades de Shading y Transparency.

También, dentro de la pestaña ‘Texture’, hay que marcar la casilla ‘Use Alpha ‘ debajo de ‘Image’.

Ahora el plano parecerá una estrella.

Emparentadlo con el Empty, rotadlo 90 grados en el eje Y y desplazadlo a X = -150:

Repetid este proceso con el resto de imágenes de planetas y fases de la luna que tenéis en la carpeta de recursos que habéis descargado. Los nombres que tendréis que poner a cada empty son:

  • Sun
  • Mercury
  • Venus
  • Mars
  • Jupiter
  • Neptune
  • Uranus
  • MoonX (para cada una de las fases de la luna, y sustituyendo la X por un número, empezando por Moon0 que se corresponderá al Empty de la imagen ‘moon0_thumbnail.png’)

Una vez acabado, tendréis algo parecido a esto:

 

 


3. Scripting y Logic Bricks

¿Qué tenemos que programar? Lo primero que haremos será programar los Logic Bricks para poder mirar a nuestro alrededor con el ratón, como si de un juego en primera persona se tratase. Después escribiremos un script que leerá la base de datos de las estrellas y nos pintará el cielo copiando los objetos “modelo” de las estrellas y planetas que hemos creado en el paso 2.

Seleccionad el cubo y abrid el Logic Editor. Añadid un sensor de tipo ‘Mouse’ y cambiad el desplegable ‘Mouse Event’ a ‘Movement’:

 

Añadid un Actuador de tipo ‘Mouse’ y cambiad el Mode de ‘Visibility’ a ‘Look’:

 

Y conectad estos dos bloques:

 

Ahora seleccionad la cámara y cread un nuevo script con el editor de texto de Blender. Ponedle el nombre ‘main.py’. ¡La terminación .py es muy importante, no la olvidéis!

Antes de escribir el script, bajad la base de datos sky2000 y recordad su path.

Lo primero que hará este script es importar las librerías necesarias y guardar la escena actual:


import math
import bge
import datetime
import ephem
import time
import datetime
import Rasterizer
import random

scene = bge.logic.getCurrentScene()

Después se definirá la función para calcular la posición de las estrellas en el cielo.

def dibujar_estrellas(line,observatorio,data,epoca):
 #Funcion para calcular las estrellas.
 #Recibe como parametro una linia del archivo de estrellas,
 #las coordenadas del observador y la epoca
 #y devuelve su nombre, coordenadas y magnitud.
 star = ephem.readdb(line)
 star.compute(data,epoch=epoca)
 star.compute(observatorio)
 nombre = star.name
 azimut=star.az
 altitud=star.alt
 magnitud=star.mag
 return nombre, float(repr(azimut)), float(repr(altitud)), magnitud

Seguidamente, se crea una función que calculará el valor numérico de la escala de la malla de las estrellas en función de su magnitud. Los valores se han encontrado mediante interpolación simple:

def magnitudEstrella(cadena):
 #Recibe una cadena de texto, correspondiente a la magnitud de la estrella
 magnitud = float(cadena)
 mag = 0.0207875*magnitud*magnitud-0.490934*magnitud+2.93498
 return mag

La siguiente función calculará la posición del cada uno de los planetas en el cielo:

def Calculo_planeta(pl, o, d, e):
 #pl -> clase planeta
 #o -> observatorioo
 #d -> data
 #e -> epoca
 #Devuelve nombre del objeto, su altitud y azimut en grados (angulos Y y Z para rotar el objeto 3D)
 pl.compute(d, epoch = e)
 pl.compute(o)
 return pl.name, float(repr(pl.alt)), float(repr(pl.az))

La función para calcular la posición de la Luna es muy parecida a la del resto de planetas y cuerpos del sistema, pero además se calcula su fase:

def Calculo_luna(l, o, d, e):
 #l -> clase planeta (lluna)
 #o -> observatorioo
 #d -> data
 #e -> epoca
 #Devuelve: nombre del objeto, su altitud y azimut en grados (angulos Y y Z para rotar el objeto 3D) y la lunacion
 l.compute(d,epoch = e)
 l.compute(o)
 nnm=ephem.next_new_moon(d)
 pnm=ephem.previous_new_moon(d)
 lunation=(d-pnm)/(nnm-pnm)
 lunation=lunation*29.530588853
 return l.name, float(repr(l.alt)), float(repr(l.az)), int(lunation)

La función inicializa() combinará todas estas funciones para leer el fichero de estrellas y dibujarlas en el cielo. Se empieza por guardar el path al fichero de las estrellas. ¡Acordaos de cambiarlo por el vuestro!

def inicializa():
 
 
 #Cambiar el path por la localizacion del archivo sky2000.edb
 estrellas = open('/home/glare/Documents/planetario/dat/sky2000.edb', 'r')

Después se guardan los parámetros que definen la posición del observador sobre la tierra (Latitud y Longitud) así como la fecha:

 LAT = 40.0 #Latitud
 LON = 15.0 #Longitud
 elevacion=200.0 #Elevacion por encima del nivel del mar

 #Obtener la data. Puede entrarse a mano:
 dia=2
 mes=9
 year=2017
 hora=19
 minutos=16
 segundos=0
 epoca='2017'

Después habrá que escoger si queremos elegir la fecha actual del ordenador, o la que se ha definido mediante variables:

 #Comentar una de las dos lineas siguientes
 #data = ephem.Date(datetime.datetime.utcnow()) #Utilizar la data ACTUAL del ordendor
 timetuple=(year, mes, dia, hora, minutos, segundos) #Utilizar la fecha especificada con variables

Se calcula la posición del observador:

 #Calcular la posicion del observador
 observatorio = ephem.Observer()
 observatorio.lon=str(LON)
 observatorio.lat = str(LAT)
 observatorio.date = data # 12:22:56 EDT
 observatorio.elevation=elevacion

Y la posición de cada uno de los planetas, el Sol y la Luna con su fase. También se añaden sus respectivos objetos en el cielo.


 #Calculo de la posicion de los planetas en el cielo
 #Mercurio
 name, altm, azm = Calculo_planeta(ephem.Mercury(), observatorio, data, epoca)
 newobject = scene.addObject(name, name)
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,altm,-azm],False)

 #Venus
 name, altm, azm = Calculo_planeta(ephem.Venus(), observatorio, data, epoca)
 newobject = scene.addObject(name, name)
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,altm,-azm],False)

 #Marte
 name, altm, azm = Calculo_planeta(ephem.Mars(), observatorio, data, epoca)
 newobject = scene.addObject(name, name)
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,altm,-azm],False)

 #Jupiter
 name, altm, azm = Calculo_planeta(ephem.Jupiter(), observatorio, data, epoca)
 newobject = scene.addObject(name, name)
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,altm,-azm],False)

 #Saturno
 name, altm, azm = Calculo_planeta(ephem.Saturn(), observatorio, data, epoca)
 newobject = scene.addObject(name, name)
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,altm,-azm],False)

 #Urano
 name, altm, azm = Calculo_planeta(ephem.Uranus(), observatorio, data, epoca)
 newobject = scene.addObject(name, name)
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,altm,-azm],False)

 #Neptuno
 name, altm, azm = Calculo_planeta(ephem.Neptune(), observatorio, data, epoca)
 newobject = scene.addObject(name, name)
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,altm,-azm],False)

 #El Sol en realidad es una estrella, pero se calcula como si fuese un planeta mas
 name, altm, azm = Calculo_planeta(ephem.Sun(), observatorio, data, epoca)
 newobject = scene.addObject(name, name)
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,altm,-azm],False)

 #Calculo de la posicion de la Luna y su fase (lunacion)
 name, altm, azm, lunacion = Calculo_luna(ephem.Moon(), observatorio, data, epoca)
 nombre_luna = "Moon"+str(lunacion)
 newobject = scene.addObject(nombre_luna, nombre_luna)
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,altm,-azm],False)
 newobject.applyRotation([LAT,0,0], True)

Para terminar, se recorre el fichero de las estrellas y se van colocando en el cielo, modificando el tamaño de sus mallas en función de su magnitud:

 #Leer el fichero de las estrellas e ir colocandolas en el cielo
 for line in estrellas:
 nom, az, alt, mag = dibujar_estrellas(line, observatorio, data, epoca)
 az *= -1
 if True:
 newobject = scene.addObject("estrella", "estrella")
 newobject.worldPosition = [0,0,0]
 newobject.applyRotation([0,alt,az],False)
 newobject.children[0]['nom'] = nom
 escala = magnitudEstrella(mag)
 escala *= 2.5
 newobject.children[0].localScale = [escala, escala, escala]

¡Ya está! ¿Cómo debería quedar el código finalizado?

#
#Planetario 3D virutal
#Hecho con mucho amor por los robots de Robologs en beneficio de los seres humanos
#
#www.robologs.net



import math
import bge
import datetime
import ephem
import time
import datetime
import Rasterizer
import random

scene = bge.logic.getCurrentScene()


def dibujar_estrellas(line,observatorio,data,epoca):
    #Funcion para calcular las estrellas.
    #Recibe como parametro una linia del archivo de estrellas,
    #las coordenadas del observador y la epoca
    #y devuelve su nombre, coordenadas y magnitud.
    star = ephem.readdb(line)
    star.compute(data,epoch=epoca)
    star.compute(observatorio)
    nombre = star.name
    azimut=star.az
    altitud=star.alt
    magnitud=star.mag
    return nombre, float(repr(azimut)), float(repr(altitud)), magnitud

def magnitudEstrella(cadena):
        #Recibe una cadena de texto, correspondiente a la magnitud de la estrella
        magnitud = float(cadena)
        mag = 0.0207875*magnitud*magnitud-0.490934*magnitud+2.93498
        return mag

def Calculo_planeta(pl, o, d, e):
    #pl -> clase planeta
    #o -> observatorioo
    #d -> data
    #e -> epoca
    #Devuelve nombre del objeto, su altitud y azimut en grados (angulos Y y Z para rotar el objeto 3D)
    pl.compute(d, epoch = e)
    pl.compute(o)
    return pl.name, float(repr(pl.alt)), float(repr(pl.az))

def Calculo_luna(l, o, d, e):
    #l -> clase planeta (lluna)
    #o -> observatorioo
    #d -> data
    #e -> epoca
    #Devuelve: nombre del objeto, su altitud y azimut en grados (angulos Y y Z para rotar el objeto 3D) y la lunacion
	l.compute(d,epoch = e)
	l.compute(o)
	nnm=ephem.next_new_moon(d)
	pnm=ephem.previous_new_moon(d)
	lunation=(d-pnm)/(nnm-pnm)
	lunation=lunation*29.530588853
	return l.name, float(repr(l.alt)), float(repr(l.az)), int(lunation)

def inicializa():

    #Cambiar el path por la localizacion del archivo sky2000.edb
    estrellas = open('/home/glare/Documents/planetario/dat/sky2000.edb', 'r')

    LAT = 40.0 #Latitud
    LON = 15.0 #Longitud
    elevacion=200.0 #Elevacion por encima del nivel del mar

    #Obtener la data. Puede entrarse a mano:
    dia=2
    mes=9
    year=2017
    hora=19
    minutos=16
    segundos=0
    epoca='2017'
    #Comentar una de las dos lineas siguientes
    data = ephem.Date(datetime.datetime.utcnow()) #Utilizar la data ACTUAL del ordendor
    timetuple=(year, mes, dia, hora, minutos, segundos) #Utilizar la fecha especificada con variables

    #Calcular la posicion del observador
    observatorio = ephem.Observer()
    observatorio.lon=str(LON)
    observatorio.lat = str(LAT)
    observatorio.date = data   # 12:22:56 EDT
    observatorio.elevation=elevacion

    #Calculo de la posicion de los planetas en el cielo
    #Mercurio
    name, altm, azm = Calculo_planeta(ephem.Mercury(), observatorio, data, epoca)
    newobject = scene.addObject(name, name)
    newobject.worldPosition = [0,0,0]
    newobject.applyRotation([0,altm,-azm],False)
    
    #Venus
    name, altm, azm = Calculo_planeta(ephem.Venus(), observatorio, data, epoca)
    newobject = scene.addObject(name, name)
    newobject.worldPosition = [0,0,0]
    newobject.applyRotation([0,altm,-azm],False)

    #Marte
    name, altm, azm = Calculo_planeta(ephem.Mars(), observatorio, data, epoca)
    newobject = scene.addObject(name, name)
    newobject.worldPosition = [0,0,0]
    newobject.applyRotation([0,altm,-azm],False)

    #Jupiter
    name, altm, azm = Calculo_planeta(ephem.Jupiter(), observatorio, data, epoca)
    newobject = scene.addObject(name, name)
    newobject.worldPosition = [0,0,0]
    newobject.applyRotation([0,altm,-azm],False)

    #Saturno
    name, altm, azm = Calculo_planeta(ephem.Saturn(), observatorio, data, epoca)
    newobject = scene.addObject(name, name)
    newobject.worldPosition = [0,0,0]
    newobject.applyRotation([0,altm,-azm],False)

    #Urano
    name, altm, azm = Calculo_planeta(ephem.Uranus(), observatorio, data, epoca)
    newobject = scene.addObject(name, name)
    newobject.worldPosition = [0,0,0]
    newobject.applyRotation([0,altm,-azm],False)

    #Neptuno
    name, altm, azm = Calculo_planeta(ephem.Neptune(), observatorio, data, epoca)
    newobject = scene.addObject(name, name)
    newobject.worldPosition = [0,0,0]
    newobject.applyRotation([0,altm,-azm],False)

    #El Sol en realidad es una estrella, pero se calcula como si fuese un planeta mas
    name, altm, azm = Calculo_planeta(ephem.Sun(), observatorio, data, epoca)
    newobject = scene.addObject(name, name)
    newobject.worldPosition = [0,0,0]
    newobject.applyRotation([0,altm,-azm],False)

    #Calculo de la posicion de la Luna y su fase (lunacion)
    name, altm, azm, lunacion = Calculo_luna(ephem.Moon(), observatorio, data, epoca)
    nombre_luna = "Moon"+str(lunacion)
    newobject = scene.addObject(nombre_luna, nombre_luna)
    newobject.worldPosition = [0,0,0]
    newobject.applyRotation([0,altm,-azm],False)
    newobject.applyRotation([LAT,0,0], True)

    #Leer el fichero de las estrellas e ir colocandolas en el cielo
    for line in estrellas:
        nom, az, alt, mag = dibujar_estrellas(line, observatorio, data, epoca)
        az *= -1
        if True:
                newobject = scene.addObject("estrella", "estrella")
                newobject.worldPosition = [0,0,0]
                newobject.applyRotation([0,alt,az],False)
                newobject.children[0]['nom'] = nom
                escala = magnitudEstrella(mag)
                escala *= 2.5
                newobject.children[0].localScale = [escala, escala, escala]

Por último seleccionad la cámara y, dentro del Logic Editor, cread un sensor de tipo Always y un actuador de tipo python. Cambiad el tipo de script del controlador de ‘Script’ a ‘Module’ y escribid el nombre ‘main.inicializa’ en el cuadro de texto. Unid el sensor y el controlador.

¡Bien hecho! Si ahora váis a la vista de cámara y pulsáis ‘P’, veréis como aparece un cielo estrellado (puede que tarde un poco a cargar, la base de datos de estrellas es enorme).

¡Esto es todo por hoy! Pero hay muchas cosas más que podéis hacer para mejorar vuestro planetario: cambiar el color de las estrellas en función de su tipo, dibujar las líneas y el nombre de las constelaciones…

Un screenshot de la versión actual de mi planetario. Ampliando un poco el código, puede cambiarse el color de las estrellas y dibujar constelaciones.

 

Gl4r3

Brillante, luminosa y cegadora a veces, Glare es tan artista como técnica. Le encanta dar rienda suelta a sus módulos de imaginación y desdibujar los ya de por si delgados límites que separan el mundo de la electrónica y el arte. Su mayor creación hasta la fecha es un instrumento capaz de convertir los colores y la luz en música. Cuándo sus circuitos no están trabajando en una nueva obra electrónica, le gusta dedicar sus ciclos a la lectura o a documentar sus invenciones para beneficio de los humanos. Sus artilugios favoritos son aquellos que combinan una funcionalidad práctica con un diseño elegante y artístico.

Antes de comentar, por favor, lee las Normas

Ten el honor de decir "Primero!"

avatar
wpDiscuz