<jupyter_start><jupyter_text>Voxel painter in pythreejsBased on: [voxel painter](http://threejs.org/examples/webgl_interactive_voxelpainter.html) ([source](https://github.com/mrdoob/three.js/blob/master/examples/webgl_interactive_voxelpainter.html))TODO:     - Have rollOver helper snap to other voxel face    - Delete voxel when shift-clicked<jupyter_code>from __future__ import division, print_function
from pythreejs import *
from IPython.display import display
from ipywidgets import HTML
from traitlets import link
import random
import math
import numpy as np<jupyter_output><empty_output><jupyter_text>Geometry definitions<jupyter_code>csize = 50 # size of voxel
stepx, stepy = csize, csize
sizex, sizey = 10*csize, 10*csize<jupyter_output><empty_output><jupyter_text>Helper functions<jupyter_code>def normalize(list):
    """ 
    Normalize vector list 
    """
    return [x/sum(list) for x in list]

def rotation_matrix(angle, axis='x'):
    """ 
    Return rotation matrix as list of rows used in the
    Object3d.quaternion_from_rotation() class method
    """
    if axis in ['x','y','z']:
        sin = math.sin(angle)
        cos = math.cos(angle)
        # counter-clockwise rotation in yz-plane
        if axis is 'x':
            return [1, 0, 0, 0, cos, -sin, 0, sin, cos]
        # counter-clockwise rotation in xz-plane
        elif axis is 'y':
            return [cos, 0, sin, 0, 1, 0, -sin, 0, cos]
        # counter-clockwise rotation in xy-plane
        elif axis is 'z':
            return [cos, -sin, 0, sin, cos, 0, 0, 0, 1]
    else:
        raise ValueError('Cannot rotate about %s axis' % axis)<jupyter_output><empty_output><jupyter_text>Scene objects:  Voxels<jupyter_code>cube_tex = ImageTexture(
    imageuri = 'textures/square-outline-textured.png'
)

cube_geo = BoxGeometry(
    width = csize,
    height = csize,
    depth = csize
)

cube_mat = LambertMaterial(
    color = 0xfeb74c,
    shading = 'FlatShading',
    map = cube_tex
)<jupyter_output><empty_output><jupyter_text>Roll-over helper<jupyter_code>rollOver_geo = cube_geo

rollOver_mat = BasicMaterial(
    color = 0xff0000,
    opacity = 0.5,
    transparent = True
)

rollOver_point = Mesh(
    geometry = rollOver_geo,
    material = rollOver_mat
)<jupyter_output><empty_output><jupyter_text>Surface<jupyter_code>surf_geo = SurfaceGeometry(
    z = [0]*sizex*sizey,
    width = 2*sizex,
    height = 2*sizey,
    width_segments = 2*sizex//stepx,
    height_segments = 2*sizey//stepy,
)

surf_grid = SurfaceGrid(
    geometry = surf_geo,
    material = LineBasicMaterial(
        color = 0x000000,
        opacity = 0.2,
        transparant = True
    ),
)

surface = Mesh(
    geometry = surf_geo,
    material = BasicMaterial(
        color = 'red',
        opacity = 0.2,
    ),
    visible = False
)

m = rotation_matrix(-math.pi/2)
surface.quaternion_from_rotation(m)
surf_grid.quaternion_from_rotation(m)<jupyter_output><empty_output><jupyter_text>Pickers (raycasting)<jupyter_code>click_picker = Picker(
    root = surface,
    event = 'click'
)
mousemove_picker = Picker(
    root = surface,
    event = 'mousemove'
)
def map_to_grid(value):
    """
    Convert continous to discrete coordinates
    """
    # limit position to positive y-axis
    if value[1] < 0: 
        value[1] = float(0)
        
    # limit to discrete steps based on cube size
    pos = [int(x//csize*csize+csize/2) for x in value]

    # if block already exist at this position, shift up
    while tuple(pos) in objects.keys():
        pos[1] += csize
    return pos
objects = {} # contains all voxels added to the scene
def on_click(name, value):
    """
    Create new object when mouse is clicked
    TODO: delete when shift-clicked
    """
    # convert position to discrete coordinates
    pos = map_to_grid(value)
        
    # create new object
    point = Mesh(
        geometry = cube_geo,
        material = cube_mat,
        position = pos
    )
    
    # add new object to scene and object list
    scene.children = scene.children + [point] #works
    #scene.children.append(point) # doesnt work (see: https://github.com/jovyan/pythreejs/blob/master/pythreejs/pythreejs.py#L134)
    objects[tuple(map(int,point.position))] = point
html = HTML()
def on_mousemove(name, value):
    """
    Show rollOver helper on mousemove
    TODO: Snap rollOver helper to existing voxel
    """
    # convert to discrete coordinates
    pos = map_to_grid(value)
    
    # update rollOver helper position
    rollOver_point.position = pos
    
    # write coordinates to html container
    html.value = "Coords: (%d, %d, %d)" % tuple(pos)    

# initialize with starting position
on_mousemove(None, rollOver_point.position)
click_picker.on_trait_change(on_click, 'point')
link((rollOver_point, 'position'), (mousemove_picker, 'point'))
mousemove_picker.on_trait_change(on_mousemove, 'point')<jupyter_output><empty_output><jupyter_text>Camera and scene<jupyter_code>camera = PerspectiveCamera(
    position = [500, 800, 1300],
    fov = 35, 
    aspect = 16/10,
    near = 1,
    far = 10000
)
camera.look_at(camera.position, [0,0,0])
scene = Scene(
    children = [
        surface,
        surf_grid,
        rollOver_point,
        AmbientLight(
            color = 0x606060
        ),
        DirectionalLight(
            color = 0xffffff,
            position = normalize([1, 0.75, 0.5]),
            intensity = 0.5
        )
    ]
)<jupyter_output><empty_output><jupyter_text>Render the scene<jupyter_code>renderer = Renderer(
    camera = camera,
    scene = scene,
    controls = [
        OrbitControls(
            controlling = camera
        ),
        click_picker,
        mousemove_picker
    ],
    background = 0xf0f0f0,
    antialias = True,
    renderer_type = 'auto' #'auto', 'canvas', 'webgl'
)
display(html, renderer)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10735042735042735, 'fraction_numerical': 0.017606837606837608, 'mean_word_length': 3.1088483146067416, 'pattern_counts': {'":': 0, '<': 38, '<?xml version=': 0, '>': 37, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '285449', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 2054, 'n_tokens_neox': 1946, 'n_words': 468, 'path': 'examples/threejs/voxel_painter.ipynb', 'repo_name': 'minrk/pythreejs', 'size': '5850'}