from dash import Dash, Output, Input, html, dcc, callback
import base64
from PIL import Image
import vnoise
import numpy as np
import random
from voxel_world import Volume
app = Dash(__name__)
# Using base64 encoding and decoding
def b64_image(image_bytes):
return 'data:image/png;base64,' + base64.b64encode(image_bytes).decode('utf-8')
style=dict(background='black', color='white', fontFamily='sans-serif', fontSize='12px')
# Layout
app.layout = html.Div(style=style, children=[
html.Div([
html.Label('Theme'),
dcc.Dropdown(
id='theme-dropdown',
options=[{'label': theme, 'value': theme} for theme in Volume.themes.keys()],
value='Lilac'
),
html.Label('Resolution'),
dcc.Slider(id='resolution-slider', min=1, max=20, step=1, value=10, marks={i: str(i) for i in range(1, 21)[0::5]}),
html.Label('Viewing Angle X'),
dcc.Slider(id='angle-x-slider', min=0, max=90, step=1, value=45, marks={i: str(i) for i in range(0, 91, 10)[0::10]}),
html.Label('Viewing Angle Y'),
dcc.Slider(id='angle-y-slider', min=0, max=90, step=1, value=30, marks={i: str(i) for i in range(0, 91, 10)[0::10]}),
html.Label('Zoom'),
dcc.Slider(id='zoom-slider', min=0.1, max=5.0, step=0.1, value=2.0, marks={i/10: str(i/10) for i in range(1, 51, 5)[0::10]}),
html.Label('Voxel Color'),
dcc.Input(id='voxel-color', type='color', value='#FFFFFF'),
html.Label('Specularity'),
dcc.Slider(id='specularity-slider', min=0, max=1, step=0.01, value=0.5, marks={i/10: str(i/10) for i in range(0, 11)[0::2]}),
html.Label('Perlin Noise Scale'),
dcc.Slider(id='noise-scale-slider', min=1, max=50, step=1, value=10, marks={i: str(i) for i in range(1, 51, 5)[0::5]}),
html.Label('Perlin Noise Octaves'),
dcc.Slider(id='noise-octaves-slider', min=1, max=10, step=1, value=4, marks={i: str(i) for i in range(1, 11)[0::2]}),
html.Label('Perlin Noise Persistence'),
dcc.Slider(id='noise-persistence-slider', min=0.1, max=1.0, step=0.1, value=0.5, marks={i/10: str(i/10) for i in range(1, 11)[0::2]}),
html.Label('Perlin Noise Lacunarity'),
dcc.Slider(id='noise-lacunarity-slider', min=1.0, max=4.0, step=0.1, value=2.0, marks={i/10: str(i/10) for i in range(10, 41, 5)}),
html.Button('Generate world', id='submit-val'),
], style={'width': '30%', 'display': 'inline-block', 'vertical-align': 'top'}),
html.Div([
html.Img(id='voxel-world', style={'width':'100%'})
], style={'width': '70%', 'display': 'inline-block'}),
])
# Callback
@callback(
Output('voxel-world', 'src'),
Input('submit-val', 'n_clicks'),
Input('theme-dropdown', 'value'),
Input('resolution-slider', 'value'),
Input('angle-x-slider', 'value'),
Input('angle-y-slider', 'value'),
Input('zoom-slider', 'value'),
Input('voxel-color', 'value'),
Input('specularity-slider', 'value'),
Input('noise-scale-slider', 'value'),
Input('noise-octaves-slider', 'value'),
Input('noise-persistence-slider', 'value'),
Input('noise-lacunarity-slider', 'value'),
)
def update_output(n_clicks, theme, resolution, angle_x, angle_y, zoom, voxel_color, specularity, noise_scale, noise_octaves, noise_persistence, noise_lacunarity):
""" Generate voxel world """
noise = vnoise.Noise()
size = 16
voxel_matrix = np.array([[[1 if noise.noise3(x / noise_scale, y / noise_scale, z / noise_scale) > random.uniform(-0.2, 0.2) else 0 for z in range(size)] for y in range(size)] for x in range(size)], dtype=np.uint8)
# Create color matrix with the selected voxel color
r, g, b = tuple(int(voxel_color[i:i+2], 16) for i in (1, 3, 5))
color_matrix = np.zeros((size, size, size, 3), dtype=np.uint8)
color_matrix[voxel_matrix > 0] = [r, g, b]
# Create specularity matrix
specularity_matrix = np.full((size, size, size), specularity, dtype=np.float32)
img_bytes = Volume(
voxel_matrix,
theme=theme,
resolution=resolution,
viewing_angle=(angle_x, angle_y),
zoom=zoom,
dark_bg=True,
color_matrix=color_matrix,
specularity_matrix=specularity_matrix
).byte_stream().getvalue()
return b64_image(img_bytes)
if __name__ == '__main__':
app.run(debug=True)