diff --git a/ipyvolume/__init__.py b/ipyvolume/__init__.py index c17f8832..4deb0bcc 100644 --- a/ipyvolume/__init__.py +++ b/ipyvolume/__init__.py @@ -9,7 +9,6 @@ from ipyvolume.transferfunction import * # noqa: F401, F403 from ipyvolume.pylab import * # noqa: F401, F403 - def _jupyter_nbextension_paths(): return [{ 'section': 'notebook', diff --git a/ipyvolume/pylab.py b/ipyvolume/pylab.py old mode 100644 new mode 100755 index af971ec6..621f53f2 --- a/ipyvolume/pylab.py +++ b/ipyvolume/pylab.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from __future__ import division +import pythreejs __all__ = [ 'current', @@ -40,6 +41,14 @@ 'style', 'plot_plane', 'selector_default', + 'ambient_light', + 'directional_light', + 'spot_light', + 'point_light', + 'hemisphere_light', + 'setup_light_widgets', + 'setup_material_widgets', + 'show_lighting_widgets', ] import os @@ -75,7 +84,7 @@ import ipyvolume as ipv import ipyvolume.embed from ipyvolume import utils - +import math _last_figure = None @@ -124,6 +133,7 @@ def _docsubst(f): _doc_snippets["z2d"] = "idem for z" _doc_snippets["texture"] = "PIL.Image object or ipywebrtc.MediaStream (can be a seqence)" +emissive_intensity_default = 0.2 class current: figure = None @@ -309,7 +319,26 @@ def squarelim(): @_docsubst -def plot_trisurf(x, y, z, triangles=None, lines=None, color=default_color, u=None, v=None, texture=None): +def plot_trisurf( + x, + y, + z, + triangles=None, + lines=None, + color=default_color, + u=None, + v=None, + texture=None, + lighting_model='DEFAULT', + opacity=1, + emissive_intensity=emissive_intensity_default, + specular_color='white', + shininess=1, + roughness=0, + metalness=0, + cast_shadow=True, + receive_shadow=True, + flat_shading=True): """Draw a polygon/triangle mesh defined by a coordinate and triangle indices. The following example plots a rectangle in the z==2 plane, consisting of 2 triangles: @@ -333,10 +362,20 @@ def plot_trisurf(x, y, z, triangles=None, lines=None, color=default_color, u=Non :param z: {z} :param triangles: numpy array with indices referring to the vertices, defining the triangles, with shape (M, 3) :param lines: numpy array with indices referring to the vertices, defining the lines, with shape (K, 2) - :param color: {color} + :param color: {color} Color of the material, essentially a solid color unaffected by other lighting. Default is 'red' :param u: {u} :param v: {v} :param texture: {texture} + :param lighting_model: The lighting model used to calculate the final color of the mesh. Can be 'DEFAULT', 'LAMBERT', 'PHONG', 'PHYSICAL'. implicit 'DEFAULT'. Will be automatically updated to 'PHYSICAL' if a light is added to figure + :param opacity: (Non-Default) 0 - Mesh is fully transparent; 1 - Mesh is fully opaque + :param emissive_intensity: (Non-Default) Factor multiplied with color. Takes values between 0 and 1. Default is 0.2 + :param specular_color: {color} (Phong Only) Color of the specular tint. Default 'white'. + :param shininess: (Phong Only) Specular intensity. Default is 1 + :param roughness: (Physical Only) How rough the material appears. 0.0 means a smooth mirror reflection, 1.0 means fully diffuse. Default is 1 + :param metalness: (Physical Only) How much the material is like a metal. Non-metallic materials such as wood or stone use 0.0, metallic use 1.0, with nothing (usually) in between + :param cast_shadow: (Non-Default) Property of a mesh to cast shadows. Default False. Works only with Directional, Point and Spot lights + :param receive_shadow: (Non-Default) Property of a mesh to receive shadows. Default False. Works only with Directional, Point and Spot lights + :param flat_shading: (Physical, Phong) A technique for color computing where all polygons reflect as a flat surface. Default True :return: :any:`Mesh` """ fig = gcf() @@ -344,25 +383,89 @@ def plot_trisurf(x, y, z, triangles=None, lines=None, color=default_color, u=Non triangles = np.array(triangles).astype(dtype=np.uint32) if lines is not None: lines = np.array(lines).astype(dtype=np.uint32) - mesh = ipv.Mesh(x=x, y=y, z=z, triangles=triangles, lines=lines, color=color, u=u, v=v, texture=texture) + mesh = ipv.Mesh( + x=x, + y=y, + z=z, + triangles=triangles, + lines=lines, + color=color, + u=u, v=v, + texture=texture, + lighting_model=lighting_model, + opacity=opacity, + emissive_intensity=emissive_intensity, + specular_color=specular_color, + shininess=shininess, + roughness=roughness, + metalness=metalness, + cast_shadow=cast_shadow, + receive_shadow=receive_shadow, + flat_shading=flat_shading + ) _grow_limits(np.array(x).reshape(-1), np.array(y).reshape(-1), np.array(z).reshape(-1)) fig.meshes = fig.meshes + [mesh] + return mesh @_docsubst -def plot_surface(x, y, z, color=default_color, wrapx=False, wrapy=False): +def plot_surface( + x, + y, + z, + color=default_color, + wrapx=False, + wrapy=False, + lighting_model='DEFAULT', + opacity=1, + emissive_intensity=emissive_intensity_default, + specular_color='white', + shininess=1, + roughness=0, + metalness=0, + cast_shadow=True, + receive_shadow=True, + flat_shading=True + ): """Draws a 2d surface in 3d, defined by the 2d ordered arrays x,y,z. :param x: {x2d} :param y: {y2d} :param z: {z2d} - :param color: {color2d} + :param color: {color2d} Color of the material, essentially a solid color unaffected by other lighting. Default is 'red' :param bool wrapx: when True, the x direction is assumed to wrap, and polygons are drawn between the end end begin points :param bool wrapy: simular for the y coordinate + :param lighting_model: The lighting model used to calculate the final color of the mesh. Can be 'DEFAULT', 'LAMBERT', 'PHONG', 'PHYSICAL'. implicit 'DEFAULT'. Will be automatically updated to 'PHYSICAL' if a light is added to figure + :param opacity: (Non-Default) 0 - Mesh is fully transparent; 1 - Mesh is fully opaque + :param emissive_intensity: (Non-Default) Factor multiplied with color. Takes values between 0 and 1. Default is 0.2 + :param specular_color: {color} (Phong Only) Color of the specular tint. Default 'white'. + :param shininess: (Phong Only) Specular intensity. Default is 1 + :param roughness: (Physical Only) How rough the material appears. 0.0 means a smooth mirror reflection, 1.0 means fully diffuse. Default is 1 + :param metalness: (Physical Only) How much the material is like a metal. Non-metallic materials such as wood or stone use 0.0, metallic use 1.0, with nothing (usually) in between + :param cast_shadow: (Non-Default) Property of a mesh to cast shadows. Default False. Works only with Directional, Point and Spot lights + :param receive_shadow: (Non-Default) Property of a mesh to receive shadows. Default False. Works only with Directional, Point and Spot lights + :param flat_shading: (Physical, Phong) A technique for color computing where all polygons reflect as a flat surface. Default True :return: :any:`Mesh` """ - return plot_mesh(x, y, z, color=color, wrapx=wrapx, wrapy=wrapy, wireframe=False) + return plot_mesh( + x, + y, + z, + color=color, + wrapx=wrapx, + wrapy=wrapy, + wireframe=False, + lighting_model=lighting_model, + opacity=opacity, + emissive_intensity=emissive_intensity, + specular_color=specular_color, + shininess=shininess, + roughness=roughness, + metalness=metalness, + cast_shadow=cast_shadow, + receive_shadow=receive_shadow, + flat_shading=flat_shading) @_docsubst @@ -383,21 +486,51 @@ def plot_wireframe(x, y, z, color=default_color, wrapx=False, wrapy=False): def plot_mesh( - x, y, z, color=default_color, wireframe=True, surface=True, wrapx=False, wrapy=False, u=None, v=None, texture=None + x, + y, + z, + color=default_color, + wireframe=True, + surface=True, + wrapx=False, + wrapy=False, + u=None, + v=None, + texture=None, + lighting_model='DEFAULT', + opacity=1, + emissive_intensity=emissive_intensity_default, + specular_color='white', + shininess=1, + roughness=0, + metalness=0, + cast_shadow=True, + receive_shadow=True, + flat_shading=True ): """Draws a 2d wireframe+surface in 3d: generalization of :any:`plot_wireframe` and :any:`plot_surface`. :param x: {x2d} :param y: {y2d} :param z: {z2d} - :param color: {color2d} + :param color: {color2d} Color of the material, essentially a solid color unaffected by other lighting. Default is 'red' :param bool wireframe: draw lines between the vertices :param bool surface: draw faces/triangles between the vertices :param bool wrapx: when True, the x direction is assumed to wrap, and polygons are drawn between the begin and end points - :param boool wrapy: idem for y + :param bool wrapy: idem for y :param u: {u} :param v: {v} :param texture: {texture} + :param lighting_model: The lighting model used to calculate the final color of the mesh. Can be 'DEFAULT', 'LAMBERT', 'PHONG', 'PHYSICAL'. implicit 'DEFAULT'. Will be automatically updated to 'PHYSICAL' if a light is added to figure + :param opacity: (Non-Default) 0 - Mesh is fully transparent; 1 - Mesh is fully opaque + :param emissive_intensity: (Non-Default) Factor multiplied with color. Takes values between 0 and 1. Default is 0.2 + :param specular_color: {color} (Phong Only) Color of the specular tint. Default 'white'. + :param shininess: (Phong Only) Specular intensity. Default is 1 + :param roughness: (Physical Only) How rough the material appears. 0.0 means a smooth mirror reflection, 1.0 means fully diffuse. Default is 1 + :param metalness: (Physical Only) How much the material is like a metal. Non-metallic materials such as wood or stone use 0.0, metallic use 1.0, with nothing (usually) in between + :param cast_shadow: (Non-Default) Property of a mesh to cast shadows. Default False. Works only with Directional, Point and Spot lights + :param receive_shadow: (Non-Default) Property of a mesh to receive shadows. Default False. Works only with Directional, Point and Spot lights + :param flat_shading: (Physical, Phong) A technique for color computing where all polygons reflect as a flat surface. Default True :return: :any:`Mesh` """ fig = gcf() @@ -471,13 +604,28 @@ def reshape_color(ar): u=u, v=v, texture=texture, + lighting_model=lighting_model, + opacity=opacity, + emissive_intensity=emissive_intensity, + specular_color=specular_color, + shininess=shininess, + roughness=roughness, + metalness=metalness, + cast_shadow=cast_shadow, + receive_shadow=receive_shadow, + flat_shading=flat_shading ) fig.meshes = fig.meshes + [mesh] return mesh @_docsubst -def plot(x, y, z, color=default_color, **kwargs): +def plot( + x, + y, + z, + color=default_color, + **kwargs): """Plot a line in 3d. :param x: {x} @@ -493,7 +641,12 @@ def plot(x, y, z, color=default_color, **kwargs): visible_lines=True, color_selected=None, size_selected=1, size=1, connected=True, visible_markers=False ) kwargs = dict(defaults, **kwargs) - s = ipv.Scatter(x=x, y=y, z=z, color=color, **kwargs) + s = ipv.Scatter( + x=x, + y=y, + z=z, + color=color, + **kwargs) s.material.visible = False fig.scatters = fig.scatters + [s] return s @@ -511,20 +664,31 @@ def scatter( marker="diamond", selection=None, grow_limits=True, + lighting_model='DEFAULT', + opacity=1, + emissive_intensity=emissive_intensity_default, + roughness=0, + metalness=0, **kwargs ): """Plot many markers/symbols in 3d. - + Due to certain shader limitations, should not use with Spot Lights and Point Lights. + Does not support shadow mapping. :param x: {x} :param y: {y} :param z: {z} - :param color: {color} + :param color: {color} Color of the material, essentially a solid color unaffected by other lighting. Default is 'red' :param size: {size} :param size_selected: like size, but for selected glyphs :param color_selected: like color, but for selected glyphs :param marker: {marker} :param selection: numpy array of shape (N,) or (S, N) with indices of x,y,z arrays of the selected markers, which can have a different size and color + :param lighting_model: The lighting model used to calculate the final color of the mesh. Can be 'DEFAULT', 'PHYSICAL'. implicit 'DEFAULT'. Will be automatically updated to 'PHYSICAL' if a light is added to figure + :param opacity: (Physical Only) 0 - Mesh is fully transparent; 1 - Mesh is fully opaque + :param emissive_intensity: (Physical Only) Factor multiplied with color. Takes values between 0 and 1. Default is 0.2 + :param roughness: (Physical Only) How rough the material appears. 0.0 means a smooth mirror reflection, 1.0 means fully diffuse. Default is 1 + :param metalness: (Physical Only) How much the material is like a metal. Non-metallic materials such as wood or stone use 0.0, metallic use 1.0, with nothing (usually) in between :param kwargs: :return: :any:`Scatter` """ @@ -541,6 +705,11 @@ def scatter( size_selected=size_selected, geo=marker, selection=selection, + lighting_model=lighting_model, + opacity=opacity, + emissive_intensity=emissive_intensity, + roughness=roughness, + metalness=metalness, **kwargs ) fig.scatters = fig.scatters + [s] @@ -1511,3 +1680,467 @@ def _make_triangles_lines(shape, wrapx=False, wrapy=False): lines[3::4, 0], lines[3::4, 1] = t1[1], t2[1] return triangles, lines + +def ambient_light( + light_color=default_color_selected, + intensity = 1): + """Create a new Ambient Light + An Ambient Light source represents an omni-directional, fixed-intensity and fixed-color light source that affects all objects in the scene equally (is omni-present). + This light cannot be used to cast shadows. + :param light_color: {color} Color of the Ambient Light. Default 'white' + :param intensity: Factor used to increase or decrease the Ambient Light intensity. Default is 1 + :return: :any:`pythreejs.AmbientLight` + """ + + light = pythreejs.AmbientLight(color=light_color, intensity=intensity) + + fig = gcf() + fig.lights = fig.lights + [light] + + return light + +def hemisphere_light( + light_color=default_color_selected, + light_color2=default_color, + intensity = 1, + position=[0, 1, 0]): + """Create a new Hemisphere Light + A light source positioned directly above the scene, with color fading from the sky color to the ground color. + This light cannot be used to cast shadows. + :param light_color: {color} Sky color. Default 'white' + :param light_color2: {color} Ground color. Default 'red' + :param intensity: Factor used to increase or decrease the Hemisphere Light intensity. Default is 1 + :param position: 3-element array (x y z) which describes the position of the Hemisphere Light. Default [0, 1, 0] + :return: :any:`pythreejs.HemisphereLight` + """ + + light = pythreejs.HemisphereLight(color=light_color, groundColor=light_color2, intensity=intensity, position=position) + + fig = gcf() + fig.lights = fig.lights + [light] + + return light + +def directional_light( + light_color=default_color_selected, + intensity = 1, + position=[10, 10, 10], + target=[0, 0, 0], + cast_shadow=True): + """Create a new Directional Light + A Directional Light source illuminates all objects equally from a given direction. + This light can be used to cast shadows. + :param light_color: {color} Color of the Directional Light. Default 'white' + :param intensity: Factor used to increase or decrease the Directional Light intensity. Default is 1 + :param position: 3-element array (x y z) which describes the position of the Directional Light. Default [10, 10, 10] + :param target: 3-element array (x y z) which describes the target of the Directional Light. Default [0, 0, 0] + :param cast_shadow: Property of a Directional Light to cast shadows. Default True + :return: :any:`pythreejs.DirectionalLight` + """ + near=0.5 + far=5000 + shadow_map_size=1024 + shadow_bias=-0.0008 + shadow_radius=1 + shadow_camera_orthographic_size=256 + + # Shadow params + camera = pythreejs.OrthographicCamera( + near=near, + far=far, + left=-shadow_camera_orthographic_size/2, + right=shadow_camera_orthographic_size/2, + top=shadow_camera_orthographic_size/2, + bottom=-shadow_camera_orthographic_size/2 + ) + shadow = pythreejs.DirectionalLightShadow( + mapSize=(shadow_map_size, shadow_map_size), + radius=shadow_radius, + bias=shadow_bias, + camera=camera + ) + # Light params + target = pythreejs.Object3D(position=target) + light = pythreejs.DirectionalLight( + color=light_color, + intensity=intensity, + position=position, + target=target, + castShadow=cast_shadow, + shadow=shadow + ) + + fig = gcf() + + fig.lights = fig.lights + [light] + + return light + +def spot_light( + light_color=default_color_selected, + intensity = 1, + position=[10, 10, 10], + target=[0, 0, 0], + cast_shadow=True): + """Create a new Spot Light + A Spot Light produces a directed cone of light. The light becomes more intense closer to the spotlight source and to the center of the light cone. + This light can be used to cast shadows. + :param light_color: {color} Color of the Spot Light. Default 'white' + :param intensity: Factor used to increase or decrease the Spot Light intensity. Default is 1 + :param position: 3-element array (x y z) which describes the position of the Spot Light. Default [0 1 0] + :param target: 3-element array (x y z) which describes the target of the Spot Light. Default [0 0 0] + :param cast_shadow: Property of a Spot Light to cast shadows. Default False + :return: :any:`pythreejs.SpotLight` + """ + + angle=0.8 + penumbra=0 + distance=0 + decay=1 + shadow_map_size=1024 + shadow_bias=-0.0008 + shadow_radius=1 + near=0.5 + far=5000 + fov=90 + aspect=1 + + # Shadow params + camera = pythreejs.PerspectiveCamera( + near=near, + far=far, + fov=fov, + aspect=aspect + ) + shadow = pythreejs.LightShadow( + mapSize=(shadow_map_size,shadow_map_size), + radius=shadow_radius, + bias=shadow_bias, + camera=camera + ) + # Light params + target = pythreejs.Object3D(position=target) + light = pythreejs.SpotLight( + color=light_color, + intensity=intensity, + position=position, + target=target, + angle=angle, + distance=distance, + decay=decay, + penumbra=penumbra, + castShadow=cast_shadow, + shadow=shadow + ) + + fig = gcf() + + fig.lights = fig.lights + [light] + + return light + +def point_light( + light_color=default_color_selected, + intensity = 1, + position=[10, 10, 10], + cast_shadow=True): + """Create a new Point Light + A Point Light originates from a single point and spreads outward in all directions. + This light can be used to cast shadows. + :param light_color: {color} Color of the Point Light. Default 'white' + :param intensity: Factor used to increase or decrease the Point Light intensity. Default is 1 + :param position: 3-element array (x y z) which describes the position of the Point Light. Default [0 1 0] + :param cast_shadow: Property of a Point Light to cast shadows. Default False + :return: :any:`PointLight` + """ + near=0.5 + far=5000 + fov=90 + aspect=1 + distance=0 + decay=1 + shadow_map_size=1024 + shadow_bias=-0.0008 + shadow_radius=1 + + # Shadow params + camera = pythreejs.PerspectiveCamera( + near=near, + far=far, + fov=fov, + aspect=aspect + ) + shadow = pythreejs.LightShadow( + mapSize=(shadow_map_size,shadow_map_size), + radius=shadow_radius, + bias=shadow_bias, + camera=camera + ) + # Light params + light = pythreejs.PointLight( + color=light_color, + intensity=intensity, + position=position, + distance=distance, + decay=decay, + castShadow=cast_shadow, + shadow=shadow + ) + + + fig = gcf() + + fig.lights = fig.lights + [light] + + return light + +def setup_material_widgets(mesh=None, tab=None, index=0): + """Set up customization widgets for Mesh object materials inside an ipywidget.Tab + For more accessibility call show_lighting_widgets() instead + :param mesh: {Mesh} Mesh object + :param tab: {ipywidgets.Tab} Parent ipywidget.Tab + :param index: Index of current ipywidget.Tab + """ + if tab == None or mesh == None: + return None + style = {'description_width': '200px'} + layout = {'width': '450px'} + + surf_color = ipywidgets.ColorPicker(description='Color:', value=str(mesh.color), continuous_update=True, style=style, layout=layout) + mlm = 'PHYSICAL' if len(gcf().lights) > 0 and mesh.lighting_model=='DEFAULT' else mesh.lighting_model + surf_lighting_model = ipywidgets.Dropdown(options=['DEFAULT','LAMBERT','PHONG','PHYSICAL'],value=mlm, description='Lighting Model:',style=style, layout=layout) + surf_opacity = ipywidgets.FloatSlider(description='Opacity (Non-Default):',value=mesh.opacity, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + surf_emissive_intensity = ipywidgets.FloatSlider(description='Emissive Intensity (Non-Default):',value=mesh.emissive_intensity, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + surf_specular_color = ipywidgets.ColorPicker(description='Specular Color (Phong):',value=str(mesh.specular_color), continuous_update=True, style=style, layout=layout) + surf_shininess = ipywidgets.FloatSlider(description='Shininess (Phong):',value=mesh.shininess, min=0.01, max=100.0, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + surf_roughness = ipywidgets.FloatSlider(description='Roughness (Physical):',value=mesh.roughness, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal',readout=True, style=style, layout=layout) + surf_metalness = ipywidgets.FloatSlider(description='Metalness (Physical):',value=mesh.metalness, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + surf_cast_shadow = ipywidgets.widgets.Checkbox(value=mesh.cast_shadow, description='Cast Shadow (Non-Default)', style=style, layout=layout) + surf_receive_shadow = ipywidgets.widgets.Checkbox(value=mesh.cast_shadow, description='Receive Shadow (Non-Default)', style=style, layout=layout) + surf_flat_shading = ipywidgets.widgets.Checkbox(value=mesh.flat_shading, description='Flat Shading (Physical, Phong)', style=style, layout=layout) + + def set_params(color, + lighting_model, + opacity, + emissive_intensity, + specular_color, + shininess, + roughness, + metalness, + cast_shadow, + receive_shadow, + flat_shading): + mesh.color = color + mesh.lighting_model = lighting_model + mesh.opacity = opacity + mesh.emissive_intensity = emissive_intensity + mesh.specular_color = specular_color + mesh.shininess = shininess + mesh.roughness = roughness + mesh.metalness = metalness + mesh.cast_shadow = cast_shadow + mesh.receive_shadow = receive_shadow + mesh.flat_shading = flat_shading + + interactables = ipywidgets.interactive(set_params, + color=surf_color, + lighting_model=surf_lighting_model, + opacity=surf_opacity, + emissive_intensity = surf_emissive_intensity, + specular_color = surf_specular_color, + shininess = surf_shininess, + roughness = surf_roughness, + metalness = surf_metalness, + cast_shadow = surf_cast_shadow, + receive_shadow = surf_receive_shadow, + flat_shading = surf_flat_shading) + + box = ipywidgets.VBox(children = interactables.children) + tab.children += (box,) + tab.set_title(index, "Mesh "+str(index)) + + + +def setup_light_widgets(light=None, tab=None, index=0): + """Set up customization widgets for pythreejs.Light objects inside an ipywidget.Tab + For more accessibility call show_lighting_widgets() instead + :param mesh: {pythreejs.Light} Light object. Can be AmbientLight, HemisphereLight, DirectionalLight, SpotLight or PointLight + :param tab: {ipywidgets.Tab} Parent ipywidget.Tab + :param index: Index of current ipywidget.Tab + """ + if tab == None or light == None: + return None + interactables = None + style = {'description_width': '200px'} + layout = {'width': '450px'} + if light.type == 'AmbientLight': + light_color = ipywidgets.ColorPicker(description='Light Color:', value=str(light.color), continuous_update=True, style=style, layout=layout) + light_intensity = ipywidgets.FloatSlider(description='Intensity:', value=light.intensity, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + + def set_params_ambiental(color, intensity): + light.color = color + light.intensity = intensity + + interactables = ipywidgets.interactive(set_params_ambiental, + color=light_color, + intensity=light_intensity) + elif light.type == 'HemisphereLight': + light_color = ipywidgets.ColorPicker(description='Light Color:', value=str(light.color), continuous_update=True, style=style, layout=layout) + light_color2 = ipywidgets.ColorPicker(description='Light Color2:', value=str(light.groundColor), continuous_update=True, style=style, layout=layout) + light_intensity = ipywidgets.FloatSlider(description='Intensity:', value=light.intensity, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + position_x = ipywidgets.FloatText(description='Position X:', value=light.position[0], style=style, layout=layout) + position_y = ipywidgets.FloatText(description='Position Y:', value=light.position[1], style=style, layout=layout) + position_z = ipywidgets.FloatText(description='Position Z:', value=light.position[2], style=style, layout=layout) + def set_params_hemisphere(color, color2, intensity, pos_x, pos_y, pos_z): + light.color = color + light.groundColor = color2 + light.intensity = intensity + light.position = (pos_x, pos_y, pos_z) + + interactables = ipywidgets.interactive(set_params_hemisphere, + color=light_color, + color2=light_color2, + intensity=light_intensity, + pos_x=position_x, + pos_y=position_y, + pos_z=position_z) + + elif light.type == 'DirectionalLight': + light_color = ipywidgets.ColorPicker(description='Light Color:', value=str(light.color), continuous_update=True, style=style, layout=layout) + light_intensity = ipywidgets.FloatSlider(description='Intensity:', value=light.intensity, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + position_x = ipywidgets.FloatText(description='Position X:',value=light.position[0], style=style, layout=layout) + position_y = ipywidgets.FloatText(description='Position Y:',value=light.position[1], style=style, layout=layout) + position_z = ipywidgets.FloatText(description='Position Z:',value=light.position[2], style=style, layout=layout) + target_x = ipywidgets.FloatText(description='Target X:', value=light.target.position[0], style=style, layout=layout) + target_y = ipywidgets.FloatText(description='Target Y:', value=light.target.position[1], style=style, layout=layout) + target_z = ipywidgets.FloatText(description='Target Z:', value=light.target.position[2], style=style, layout=layout) + cast_shadow = ipywidgets.Checkbox(value=light.castShadow, description='Cast Shadow', style=style, layout=layout) + shadow_bias = ipywidgets.FloatSlider(description='Shadow Bias:', value=light.shadow.bias, min=-0.001, max=0.001, step=0.00001, continuous_update=True, orientation='horizontal',readout=True, readout_format='.7f', style=style, layout=layout) + shadow_radius = ipywidgets.FloatSlider(description='Shadow Radius:', value=light.shadow.radius, min=0, max=10, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + shadow_camera_orthographic_size = ipywidgets.FloatSlider(description='Shadow Cam Ortho Size:', value=light.shadow.camera.right * 2, min=0, max=1024, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + + def set_params_directional(color, intensity, pos_x, pos_y, pos_z, tar_x, tar_y, tar_z, cast_shadow, bias, radius, ortho_size): + light.color = color + light.intensity = intensity + light.position = (pos_x, pos_y, pos_z) + light.target.position = (tar_x, tar_y, tar_z) + light.castShadow = cast_shadow + light.shadow.bias = bias + light.shadow.radius = radius + light.shadow.camera.left = -ortho_size/2 + light.shadow.camera.right = ortho_size/2 + light.shadow.camera.top = ortho_size/2 + light.shadow.camera.bottom = -ortho_size/2 + + + interactables = ipywidgets.interactive(set_params_directional, + color=light_color, + intensity=light_intensity, + pos_x=position_x, + pos_y=position_y, + pos_z=position_z, + tar_x=target_x, + tar_y=target_y, + tar_z=target_z, + cast_shadow=cast_shadow, + bias=shadow_bias, + radius=shadow_radius, + ortho_size=shadow_camera_orthographic_size) + + elif light.type == 'SpotLight': + light_color = ipywidgets.ColorPicker(description='Light Color:', value=str(light.color), continuous_update=True, style=style, layout=layout) + light_intensity = ipywidgets.FloatSlider(description='Intensity:',value=light.intensity, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal',readout=True, style=style, layout=layout) + position_x = ipywidgets.FloatText(description='Position X:',value=light.position[0], style=style, layout=layout) + position_y = ipywidgets.FloatText(description='Position Y:',value=light.position[1], style=style, layout=layout) + position_z = ipywidgets.FloatText(description='Position Z:',value=light.position[2], style=style, layout=layout) + target_x = ipywidgets.FloatText(description='Target X:', value=light.target.position[0], style=style, layout=layout) + target_y = ipywidgets.FloatText(description='Target Y:', value=light.target.position[1], style=style, layout=layout) + target_z = ipywidgets.FloatText(description='Target Z:', value=light.target.position[2], style=style, layout=layout) + angle = ipywidgets.FloatSlider(description='Angle:', value=light.angle, min=math.pi/100, max=math.pi/2, step=0.001, continuous_update=True, orientation='horizontal', readout=True, readout_format='.07f', style=style, layout=layout) + penumbra = ipywidgets.FloatSlider(description='Penumbra:', value=light.penumbra, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + cast_shadow = ipywidgets.Checkbox(value=light.castShadow, description='Cast Shadow', style=style, layout=layout) + shadow_bias = ipywidgets.FloatSlider(description='Shadow Bias:', value=light.shadow.bias, min=-0.001, max=0.001, step=0.00001, continuous_update=True, orientation='horizontal', readout=True, readout_format='.7f', style=style, layout=layout) + shadow_radius = ipywidgets.FloatSlider(description='Shadow Radius:', value=light.shadow.radius, min=0, max=10, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + + def set_params_spot(color, intensity, pos_x, pos_y, pos_z, tar_x, tar_y, tar_z, angle, penumbra, cast_shadow, bias, radius): + light.color = color + light.intensity = intensity + light.position = (pos_x, pos_y, pos_z) + light.target.position = (tar_x, tar_y, tar_z) + light.angle = angle + light.penumbra = penumbra + light.castShadow = cast_shadow + light.shadow.bias = bias + light.shadow.radius = radius + + interactables = ipywidgets.interactive(set_params_spot, + color=light_color, + intensity=light_intensity, + pos_x=position_x, + pos_y=position_y, + pos_z=position_z, + tar_x=target_x, + tar_y=target_y, + tar_z=target_z, + angle = angle, + penumbra = penumbra, + cast_shadow=cast_shadow, + bias=shadow_bias, + radius=shadow_radius) + + elif light.type == 'PointLight': + light_color = ipywidgets.ColorPicker(description='Light Color:', value=str(light.color), continuous_update=True, style=style, layout=layout) + light_intensity = ipywidgets.FloatSlider(description='Intensity:',value=light.intensity, min=0.0, max=1.0, step=0.01, continuous_update=True, orientation='horizontal',readout=True, style=style, layout=layout) + position_x = ipywidgets.FloatText(description='Position X:',value=light.position[0], style=style, layout=layout) + position_y = ipywidgets.FloatText(description='Position Y:',value=light.position[1], style=style, layout=layout) + position_z = ipywidgets.FloatText(description='Position Z:',value=light.position[2], style=style, layout=layout) + distance = ipywidgets.FloatSlider(description='Max Distance:', value=light.distance, min=0, max=1000, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + cast_shadow = ipywidgets.Checkbox(value=light.castShadow, description='Cast Shadow', style=style, layout=layout) + shadow_bias = ipywidgets.FloatSlider(description='Shadow Bias:', value=light.shadow.bias, min=-0.001, max=0.001, step=0.00001, continuous_update=True, orientation='horizontal', readout=True, readout_format='.7f', style=style, layout=layout) + shadow_radius = ipywidgets.FloatSlider(description='Shadow Radius:', value=light.shadow.radius, min=0, max=10, step=0.01, continuous_update=True, orientation='horizontal', readout=True, style=style, layout=layout) + + def set_params_point(color, intensity, pos_x, pos_y, pos_z, distance, cast_shadow, bias, radius): + light.color = color + light.intensity = intensity + light.position = (pos_x, pos_y, pos_z) + light.distance = distance + light.castShadow = cast_shadow + light.shadow.bias = bias + light.shadow.radius = radius + + + interactables = ipywidgets.interactive(set_params_point, + color=light_color, + intensity=light_intensity, + pos_x=position_x, + pos_y=position_y, + pos_z=position_z, + distance=distance, + cast_shadow=cast_shadow, + bias=shadow_bias, + radius=shadow_radius) + + + if interactables: + box = ipywidgets.VBox(children = interactables.children) + tab.children += (box,) + tab.set_title(index, light.type + " " + str(index)) + + + +def show_lighting_widgets(): + """Set up and show customization widgets for pythreejs.Light objects and Mesh object materials inside an ipywidget.Tab + Function will parse all lights and meshes from the current figure and will generate and display tabs for each object + """ + tab = ipywidgets.Tab() + fig = gcf() + index = 0 + for l in fig.lights: + setup_light_widgets(light=l, tab=tab, index=index) + index+=1 + for m in fig.meshes: + setup_material_widgets(mesh=m, tab=tab, index=index) + index+=1 + display(tab) + + diff --git a/ipyvolume/test_all.py b/ipyvolume/test_all.py old mode 100644 new mode 100755 index eb09e770..853dea9b --- a/ipyvolume/test_all.py +++ b/ipyvolume/test_all.py @@ -4,6 +4,7 @@ import shutil import json import contextlib +import math import numpy as np import pytest @@ -449,3 +450,140 @@ def test_datasets(): ipyvolume.datasets.aquariusA2.fetch() ipyvolume.datasets.hdz2000.fetch() ipyvolume.datasets.zeldovich.fetch() + + +def test_mesh_material(): + def test_material_components(mesh=None, is_scatter=False): + assert mesh.lighting_model == 'DEFAULT' + assert mesh.opacity == 1 + assert mesh.specular_color == 'white' + assert mesh.shininess == 1 + assert mesh.color == 'red' + assert mesh.emissive_intensity == 0.2 + assert mesh.roughness == 0 + assert mesh.metalness == 0 + if is_scatter == False: + assert mesh.cast_shadow == True + assert mesh.receive_shadow == True + + mesh.lighting_model = 'PHYSICAL' + mesh.opacity = 0 + mesh.specular_color = 'blue' + mesh.shininess = 10 + mesh.color = 'red' + mesh.emissive_intensity = 2 + mesh.roughness = 1 + mesh.metalness = 5 + if is_scatter == False: + mesh.cast_shadow = True + mesh.receive_shadow = True + + assert mesh.lighting_model == 'PHYSICAL' + assert mesh.opacity == 0 + assert mesh.specular_color == 'blue' + assert mesh.shininess == 10 + assert mesh.color == 'red' + assert mesh.emissive_intensity == 2 + assert mesh.roughness == 1 + assert mesh.metalness == 5 + if is_scatter == False: + assert mesh.cast_shadow == True + assert mesh.receive_shadow == True + + x, y, z, u, v = ipyvolume.examples.klein_bottle(draw=False) + + ipyvolume.figure() + mesh = ipyvolume.plot_mesh( x, y, z) + test_material_components(mesh) + + k = 20 + h = -15 + tx = np.array([k, -k, -k, k]) + tz = np.array([k, k, -k, -k]) + ty = np.array([h, h, h, h]) + + tri = [(0, 1, 2), (0, 2, 3)] + trisurf = ipyvolume.plot_trisurf(tx, ty, tz, triangles=tri) + test_material_components(trisurf) + + X = np.arange(-10, 10, 0.25*1)-10 + Y = np.arange(-10, 10, 0.25*1) + X, Y = np.meshgrid(X, Y) + R = np.sqrt(X**2 + Y**2) + Z = np.sin(R) + + surf = ipyvolume.plot_surface(X, Z, Y) + test_material_components(surf) + + x, y, z = np.random.random((3, 10000)) + scatter = ipyvolume.scatter(x, y, z, size=1, marker="sphere") + test_material_components(scatter, True) + + +def test_light_components(): + ambient = ipyvolume.ambient_light() + assert ambient.type == 'AmbientLight' + assert ambient.color == 'white' + assert ambient.intensity == 1 + + hemisphere = ipyvolume.hemisphere_light() + assert hemisphere.type == 'HemisphereLight' + assert hemisphere.color == '#ffffff' + assert hemisphere.groundColor == 'red' + assert hemisphere.intensity == 1 + assert hemisphere.position[0] == 0 + assert hemisphere.position[1] == 1 + assert hemisphere.position[2] == 0 + + directional = ipyvolume.directional_light() + assert directional.color == 'white' + assert directional.intensity == 1 + assert directional.position[0] == 10 + assert directional.position[1] == 10 + assert directional.position[2] == 10 + assert directional.target.position[0] == 0 + assert directional.target.position[1] == 0 + assert directional.target.position[2] == 0 + assert directional.castShadow==True + assert directional.shadow.bias==-0.0008 + assert directional.shadow.radius==1 + assert directional.shadow.camera.near==0.5 + assert directional.shadow.camera.far==5000 + assert directional.shadow.mapSize[0]==1024 + assert directional.shadow.camera.left==-256/2 + assert directional.shadow.camera.right==256/2 + assert directional.shadow.camera.top==256/2 + assert directional.shadow.camera.bottom==-256/2 + + spot = ipyvolume.spot_light() + assert spot.color == 'white' + assert spot.intensity == 1 + assert spot.position[0] == 10 + assert spot.position[1] == 10 + assert spot.position[2] == 10 + assert spot.target.position[0] == 0 + assert spot.target.position[1] == 0 + assert spot.target.position[2] == 0 + assert spot.shadow.camera.fov==90 + assert spot.castShadow==True + assert spot.shadow.mapSize[0]==1024 + assert spot.shadow.bias==-0.0008 + assert spot.shadow.radius==1 + assert spot.shadow.camera.near==0.5 + assert spot.shadow.camera.far==5000 + + point = ipyvolume.point_light() + assert point.color == 'white' + assert point.intensity == 1 + assert point.position[0] == 10 + assert point.position[1] == 10 + assert point.position[2] == 10 + assert point.shadow.camera.fov==90 + assert point.castShadow==True + assert point.distance==0 + assert point.decay==1 + assert point.shadow.mapSize[0]==1024 + assert point.shadow.bias==-0.0008 + assert point.shadow.radius==1 + assert point.shadow.camera.near==0.5 + assert point.shadow.camera.far==5000 \ No newline at end of file diff --git a/ipyvolume/widgets.py b/ipyvolume/widgets.py old mode 100644 new mode 100755 index 39f2e267..ea672ee9 --- a/ipyvolume/widgets.py +++ b/ipyvolume/widgets.py @@ -28,7 +28,7 @@ ) from ipyvolume.transferfunction import TransferFunction from ipyvolume.utils import debounced, grid_slice, reduce_size - +import math _last_figure = None logger = logging.getLogger("ipyvolume") @@ -64,6 +64,17 @@ class Mesh(widgets.Widget): color = Array(default_value="red", allow_none=True).tag(sync=True, **color_serialization) visible = traitlets.CBool(default_value=True).tag(sync=True) + lighting_model = traitlets.Enum(values=['DEFAULT', 'LAMBERT', 'PHONG', 'PHYSICAL'], default_value='DEFAULT').tag(sync=True) + opacity = traitlets.CFloat(1).tag(sync=True) + emissive_intensity = traitlets.CFloat(1).tag(sync=True) + specular_color = Array(default_value="white", allow_none=True).tag(sync=True, **color_serialization) + shininess = traitlets.CFloat(1).tag(sync=True) + roughness = traitlets.CFloat(0).tag(sync=True) + metalness = traitlets.CFloat(0).tag(sync=True) + cast_shadow = traitlets.CBool(default_value=True).tag(sync=True) + receive_shadow = traitlets.CBool(default_value=True).tag(sync=True) + flat_shading = traitlets.CBool(default_value=True).tag(sync=True) + material = traitlets.Instance( pythreejs.ShaderMaterial, help='A :any:`pythreejs.ShaderMaterial` that is used for the mesh' ).tag(sync=True, **widgets.widget_serialization) @@ -80,7 +91,6 @@ def _default_material(self): def _default_line_material(self): return pythreejs.ShaderMaterial() - @widgets.register class Scatter(widgets.Widget): _view_name = Unicode('ScatterView').tag(sync=True) @@ -120,6 +130,16 @@ class Scatter(widgets.Widget): connected = traitlets.CBool(default_value=False).tag(sync=True) visible = traitlets.CBool(default_value=True).tag(sync=True) + lighting_model = traitlets.Enum(values=['DEFAULT', 'PHYSICAL'], default_value='DEFAULT').tag(sync=True) + opacity = traitlets.CFloat(1).tag(sync=True) + specular_color = Array(default_value="white", allow_none=True).tag(sync=True, **color_serialization) + shininess = traitlets.CFloat(1).tag(sync=True) + emissive_intensity = traitlets.CFloat(1).tag(sync=True) + roughness = traitlets.CFloat(0).tag(sync=True) + metalness = traitlets.CFloat(0).tag(sync=True) + cast_shadow = traitlets.CBool(default_value=False).tag(sync=True) + receive_shadow = traitlets.CBool(default_value=False).tag(sync=True) + texture = traitlets.Union( [ traitlets.Instance(ipywebrtc.MediaStream), @@ -217,7 +237,7 @@ def _update_data(self): self.data = np.array(data_view) self.extent = extent - + @widgets.register class Figure(ipywebrtc.MediaStream): """Widget class representing a volume (rendering) using three.js.""" @@ -240,6 +260,13 @@ class Figure(ipywebrtc.MediaStream): volumes = traitlets.List(traitlets.Instance(Volume), [], allow_none=False).tag( sync=True, **widgets.widget_serialization ) + + #lights = traitlets.List(traitlets.Instance(Light), [], allow_none=False).tag( + # sync=True, **widgets.widget_serialization + #) + lights = traitlets.List(traitlets.Instance(pythreejs.Light), [], allow_none=False).tag( + sync=True, **widgets.widget_serialization + ) animation = traitlets.Float(1000.0).tag(sync=True) animation_exponent = traitlets.Float(1.0).tag(sync=True) @@ -260,6 +287,8 @@ class Figure(ipywebrtc.MediaStream): pythreejs.Camera, allow_none=True, help='A :any:`pythreejs.Camera` instance to control the camera' ).tag(sync=True, **widgets.widget_serialization) + enable_shadows = traitlets.Bool(False).tag(sync=True) + @traitlets.default('camera') def _default_camera(self): # see https://github.com/maartenbreddels/ipyvolume/pull/40 for an explanation diff --git a/js/glsl/mesh-fragment.glsl b/js/glsl/mesh-fragment.glsl index 55a663de..b2ae6c4a 100644 --- a/js/glsl/mesh-fragment.glsl +++ b/js/glsl/mesh-fragment.glsl @@ -1,3 +1,121 @@ +#ifdef LAMBERT_SHADING + #define LAMBERT + uniform vec3 diffuse; + uniform vec3 emissive; + uniform float emissiveIntensity; + uniform float opacity; + + varying vec3 vLightFront; + + #ifdef DOUBLE_SIDED + varying vec3 vLightBack; + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif //LAMBERT_SHADING + +#ifdef PHONG_SHADING + #define PHONG + + uniform vec3 diffuse; + uniform vec3 emissive; + uniform vec3 specular; + + uniform float shininess; + uniform float opacity; + uniform float emissiveIntensity; + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif //PHONG_SHADING + +#ifdef PHYSICAL_SHADING + #define PHYSICAL + uniform vec3 diffuse; + uniform vec3 emissive; + uniform float roughness; + uniform float metalness; + uniform float opacity; + uniform float emissiveIntensity; + + #ifndef STANDARD + uniform float clearCoat; + uniform float clearCoatRoughness; + #endif + + varying vec3 vViewPosition; + + #ifndef FLAT_SHADED + varying vec3 vNormal; + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif //PHYSICAL_SHADING + varying vec4 vertex_color; varying vec3 vertex_position; varying vec2 vertex_uv; @@ -9,24 +127,151 @@ varying vec2 vertex_uv; #endif -void main(void) { -#ifdef USE_RGB - gl_FragColor = vec4(vertex_color.rgb, 1.0); -#else -#ifdef AS_LINE - gl_FragColor = vec4(vertex_color.rgb, vertex_color.a); -#else - vec3 fdx = dFdx( vertex_position ); - vec3 fdy = dFdy( vertex_position ); - vec3 normal = normalize( cross( fdx, fdy ) ); - float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) ); +void main(void) +{ +#ifdef DEFAULT_SHADING + #ifdef USE_RGB + gl_FragColor = vec4(vertex_color.rgb, 1.0); + #else + #ifdef AS_LINE + gl_FragColor = vec4(vertex_color.rgb, vertex_color.a); + #else + vec3 fdx = dFdx( vertex_position ); + vec3 fdy = dFdy( vertex_position ); + vec3 normal = normalize( cross( fdx, fdy ) ); + float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) ); -#ifdef USE_TEXTURE - vec4 sample = mix(texture2D(texture_previous, vertex_uv), texture2D(texture, vertex_uv), animation_time_texture); - gl_FragColor = vec4(clamp(diffuse, 0.2, 1.) * sample.rgb, 1.0); -#else - gl_FragColor = vec4(clamp(diffuse, 0.2, 1.) * vertex_color.rgb, vertex_color.a); -#endif // USE_TEXTURE -#endif // AS_LINE -#endif // USE_RGB + #ifdef USE_TEXTURE + vec4 sample = mix(texture2D(texture_previous, vertex_uv), texture2D(texture, vertex_uv), animation_time_texture); + gl_FragColor = vec4(clamp(diffuse, 0.2, 1.) * sample.rgb, 1.0); + #else + gl_FragColor = vec4(clamp(diffuse, 0.2, 1.) * vertex_color.rgb, vertex_color.a); + #endif // USE_TEXTURE + #endif // AS_LINE + #endif // USE_RGB +#endif //DEFAULT_SHADING + +#ifdef LAMBERT_SHADING + #include + + vec4 diffuseColor = vec4( vec3(1,1,1), opacity ); + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + vec3 totalEmissiveRadiance = emissive * emissiveIntensity; + + #include + #include + //#include + #include + #include + #include + #include + + // accumulation + reflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor ); + + #include + + reflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ); + + #ifdef DOUBLE_SIDED + reflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack; + #else + reflectedLight.directDiffuse = vLightFront; + #endif + + reflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask(); + + // modulation + #include + + vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance; + + #include + + gl_FragColor = vec4( outgoingLight, diffuseColor.a ); + + #include + #include + #include + #include + #include + +#endif //LAMBERT_SHADING + +#ifdef PHONG_SHADING + #include + + vec4 diffuseColor = vec4( vec3(1,1,1), opacity ); + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + vec3 totalEmissiveRadiance = emissive * emissiveIntensity; + + #include + #include + //#include + #include + #include + #include + #include + #include + //normal = -normal; + #include + + // accumulation + #include + #include + #include + #include + + // modulation + #include + + vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance; + + #include + + gl_FragColor = vec4( outgoingLight, diffuseColor.a ); + + #include + #include + #include + #include + #include +#endif //PHONG_SHADING + +#ifdef PHYSICAL_SHADING + #include + + vec4 diffuseColor = vec4( vec3(1,1,1), opacity ); + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + vec3 totalEmissiveRadiance = emissive * emissiveIntensity; + + #include + #include + //#include + #include + #include + #include + #include + #include + #include + #include + + // accumulation + #include + #include + #include + #include + + // modulation + #include + + vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance; + gl_FragColor = vec4( outgoingLight, diffuseColor.a ); + + #include + #include + #include + #include + #include +#endif //PHYSICAL_SHADING } diff --git a/js/glsl/mesh-vertex.glsl b/js/glsl/mesh-vertex.glsl index e2e674c5..89a6f4aa 100644 --- a/js/glsl/mesh-vertex.glsl +++ b/js/glsl/mesh-vertex.glsl @@ -1,4 +1,4 @@ - // for animation, all between 0 and 1 +// for animation, all between 0 and 1 uniform float animation_time_x; uniform float animation_time_y; uniform float animation_time_z; @@ -23,12 +23,164 @@ attribute vec3 position_previous; varying vec2 vertex_uv; #endif -attribute vec4 color; +attribute vec4 color_current; attribute vec4 color_previous; +#ifdef LAMBERT_SHADING + #define LAMBERT + varying vec3 vLightFront; + #ifdef DOUBLE_SIDED + varying vec3 vLightBack; + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif //LAMBERT_SHADING + +#ifdef PHONG_SHADING + #define PHONG + varying vec3 vViewPosition; + + #ifndef FLAT_SHADED + varying vec3 vNormal; + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif //PHONG_SHADING + +#ifdef PHYSICAL_SHADING + #define PHYSICAL + varying vec3 vViewPosition; + + #ifndef FLAT_SHADED + varying vec3 vNormal; + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif //PHYSICAL_SHADING + +void main(void) +{ + +#ifdef LAMBERT_SHADING + #include + #include + #include + + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include +#endif //LAMBERT_SHADING + +#ifdef PHONG_SHADING + #include + #include + #include + + #include + #include + #include + #include + #include + + #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED + vNormal = normalize( transformedNormal ); + #endif + + #include + #include + #include + #include + #include + #include + #include + + vViewPosition = - mvPosition.xyz; + + #include + #include + #include + #include +#endif //PHONG_SHADING + +#ifdef PHYSICAL_SHADING + #include + #include + #include + + #include + #include + #include + #include + #include + + #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED + vNormal = normalize( transformedNormal ); + #endif + + #include + #include + #include + #include + #include + #include + #include + + vViewPosition = - mvPosition.xyz; + + #include + #include + #include +#endif //PHYSICAL_SHADING -void main(void) { vec3 origin = vec3(xlim.x, ylim.x, zlim.x); vec3 size_viewport = vec3(xlim.y, ylim.y, zlim.y) - origin; @@ -46,6 +198,6 @@ void main(void) { #ifdef USE_RGB vertex_color = vec4(pos + vec3(0.5, 0.5, 0.5), 1.0); #else - vertex_color = mix(color_previous, color, animation_time_color); + vertex_color = mix(color_previous, color_current, animation_time_color); #endif } diff --git a/js/glsl/scatter-fragment.glsl b/js/glsl/scatter-fragment.glsl index 6b9ddf14..86b8edd1 100644 --- a/js/glsl/scatter-fragment.glsl +++ b/js/glsl/scatter-fragment.glsl @@ -1,38 +1,132 @@ -#include +#ifdef DEFAULT_SHADING + #include + varying vec4 vertex_color; + varying vec3 vertex_position; + varying vec2 vertex_uv; -varying vec4 vertex_color; -varying vec3 vertex_position; -varying vec2 vertex_uv; + #ifdef USE_TEXTURE + uniform sampler2D texture; + uniform sampler2D texture_previous; + uniform float animation_time_texture; + #endif +#endif //DEFAULT_SHADING -#ifdef USE_TEXTURE -uniform sampler2D texture; -uniform sampler2D texture_previous; -uniform float animation_time_texture; -#endif +#ifdef PHYSICAL_SHADING + //#extension GL_OES_standard_derivatives : enable + #define DEPTH_PACKING 3201 + #define PHYSICAL + uniform vec3 diffuse; + uniform vec3 emissive; + uniform float roughness; + uniform float metalness; + uniform float opacity; + uniform float emissiveIntensity; -void main(void) { -#ifdef USE_RGB - gl_FragColor = vertex_color; -#else - #ifdef AS_LINE - gl_FragColor = vertex_color; - #else - #ifdef USE_SPRITE - #ifdef USE_TEXTURE - gl_FragColor = mix(texture2D(texture_previous, vertex_uv), texture2D(texture, vertex_uv), animation_time_texture); - #else - gl_FragColor = vertex_color; - #endif - #else - vec3 fdx = dFdx( vertex_position ); - vec3 fdy = dFdy( vertex_position ); - vec3 normal = normalize( cross( fdx, fdy ) ); - float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) ); + #ifndef STANDARD + uniform float clearCoat; + uniform float clearCoatRoughness; + #endif + + varying vec3 vViewPosition; - gl_FragColor = vec4(clamp(diffuse, 0.2, 1.) * vertex_color.rgb, vertex_color.a); + #ifndef FLAT_SHADED + varying vec3 vNormal; #endif - #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include #endif + +void main(void) +{ +#ifdef DEFAULT_SHADING + #ifdef USE_RGB + gl_FragColor = vertex_color; + #else + #ifdef AS_LINE + gl_FragColor = vertex_color; + #else + #ifdef USE_SPRITE + #ifdef USE_TEXTURE + gl_FragColor = mix(texture2D(texture_previous, vertex_uv), texture2D(texture, vertex_uv), animation_time_texture); + #else + gl_FragColor = vertex_color; + #endif + #else + vec3 fdx = dFdx( vertex_position ); + vec3 fdy = dFdy( vertex_position ); + vec3 normal = normalize( cross( fdx, fdy ) ); + float diffuse = dot( normal, vec3( 0.0, 0.0, 1.0 ) ); + + gl_FragColor = vec4(clamp(diffuse, 0.2, 1.) * vertex_color.rgb, vertex_color.a); + #endif + #endif + #endif + #include + +#endif //DEFAULT_SHADING + +#ifdef PHYSICAL_SHADING + #include + + vec4 diffuseColor = vec4( 1.0, 1.0, 1.0, opacity ); + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + vec3 totalEmissiveRadiance = emissive * emissiveIntensity; + + #include + #include + //#include + + #include + #include + #include + #include + #include + + #include + #include + + // accumulation + #include + #include + #include + #include + + // modulation + #include + + vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance; + gl_FragColor = vec4( outgoingLight, diffuseColor.a ); + + #include + #include #include + #include + #include + +#endif } diff --git a/js/glsl/scatter-vertex.glsl b/js/glsl/scatter-vertex.glsl index 4dc262a7..f4e5dd2b 100644 --- a/js/glsl/scatter-vertex.glsl +++ b/js/glsl/scatter-vertex.glsl @@ -1,4 +1,6 @@ -#include +#ifdef DEFAULT_SHADING + #include +#endif //DEFAULT_SHADING // for animation, all between 0 and 1 uniform float animation_time_x; @@ -19,29 +21,81 @@ varying vec3 vertex_position; varying vec2 vertex_uv; #ifdef AS_LINE -attribute vec3 position_previous; + attribute vec3 position_previous; #else -attribute float x; -attribute float x_previous; -attribute float y; -attribute float y_previous; -attribute float z; -attribute float z_previous; + attribute float x; + attribute float x_previous; + attribute float y; + attribute float y_previous; + attribute float z; + attribute float z_previous; -attribute vec3 v; -attribute vec3 v_previous; + attribute vec3 v; + attribute vec3 v_previous; -attribute float size; -attribute float size_previous; + attribute float size; + attribute float size_previous; #endif -attribute vec4 color; +attribute vec4 color_current; attribute vec4 color_previous; +#ifdef PHYSICAL_SHADING + //#extension GL_OES_standard_derivatives : enable + #define DEPTH_PACKING 3201 + #define PHYSICAL + varying vec3 vViewPosition; + #ifndef FLAT_SHADED + varying vec3 vNormal; + #endif + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif //PHYSICAL_SHADING void main(void) { + +#ifdef PHYSICAL_SHADING + #include + #include + #include + + #include + #include + #include + #include + #include + + #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED + vNormal = normalize( transformedNormal ); + #endif + + #include + #include + #include + #include + #include + #include + #include + + vViewPosition = - mvPosition.xyz; + + #include + #include + #include +#endif //PHYSICAL_SHADING + vec3 origin = vec3(xlim.x, ylim.x, zlim.x); vec3 size_viewport = vec3(xlim.y, ylim.y, zlim.y) - origin; vec3 animation_time = vec3(animation_time_x, animation_time_y, animation_time_z); @@ -81,7 +135,7 @@ void main(void) { vec4 view_pos = modelViewMatrix * vec4(model_pos, 1.0); #endif #endif - vec4 mvPosition = view_pos; + vec4 mvPosition_new = view_pos;// gl_Position = projectionMatrix * view_pos; vec3 positionEye = ( modelViewMatrix * vec4( model_pos, 1.0 ) ).xyz; vertex_position = positionEye; @@ -89,8 +143,11 @@ void main(void) { #ifdef USE_RGB vertex_color = vec4(model_pos + vec3(0.5, 0.5, 0.5), 1.0); #else - vertex_color = mix(color_previous, color, animation_time_color); + vertex_color = mix(color_previous, color_current, animation_time_color); #endif - #include +//#include +#ifdef USE_FOG + fogDepth = -mvPosition_new.z; +#endif } diff --git a/js/src/figure.ts b/js/src/figure.ts old mode 100644 new mode 100755 index fda432aa..417b7e99 --- a/js/src/figure.ts +++ b/js/src/figure.ts @@ -30,6 +30,7 @@ import "./three/OrbitControls.js"; import "./three/StereoEffect.js"; import "./three/THREEx.FullScreen.js"; import "./three/TrackballControls.js"; +import { timeThursday, thresholdScott } from "d3"; const shaders = { screen_fragment: require("raw-loader!../glsl/screen-fragment.glsl"), @@ -103,6 +104,7 @@ class FigureModel extends widgets.DOMWidgetModel { static serializers = {...widgets.DOMWidgetModel.serializers, scatters: { deserialize: widgets.unpack_models }, meshes: { deserialize: widgets.unpack_models }, + lights: { deserialize: widgets.unpack_models }, volumes: { deserialize: widgets.unpack_models }, camera: { deserialize: widgets.unpack_models }, scene: { deserialize: widgets.unpack_models }, @@ -130,6 +132,7 @@ class FigureModel extends widgets.DOMWidgetModel { displayscale: 1, scatters: null, meshes: null, + lights: null, volumes: null, show: "Volume", xlim: [0., 1.], @@ -208,6 +211,7 @@ class FigureView extends widgets.DOMWidgetView { mesh_views: { [key: string]: MeshView }; scatter_views: { [key: string]: ScatterView }; volume_views: { [key: string]: VolumeView }; + lights: { [key: string]: THREE.Light }; volume_back_target: THREE.WebGLRenderTarget; geometry_depth_target: THREE.WebGLRenderTarget; color_pass_target: THREE.WebGLRenderTarget; @@ -859,6 +863,8 @@ class FigureView extends widgets.DOMWidgetView { this.update_meshes(); this.model.on("change:volumes", this.update_volumes, this); this.update_volumes(); + this.model.on("change:lights", this.update_lights, this); + this.update_lights(); this.update_size(); @@ -1509,6 +1515,74 @@ class FigureView extends widgets.DOMWidgetView { } } + async update_lights() { + + // Initialize lights if this is the first pass + if (!this.lights) { + this.lights = {} + } + + const lights = this.model.get("lights"); + if (lights.length !== 0) { // So now check if list has length 0 + // Change mesh lighting model + for (let mesh_key in this.mesh_views) { + this.mesh_views[mesh_key].force_lighting_model(); + } + for (let scatter_key in this.scatter_views) { + this.scatter_views[scatter_key].force_lighting_model(); + } + + const current_light_cids = []; + lights.forEach(async (light_model) => { + if (!(light_model.cid in this.lights)) { + const light = light_model.obj; + if (light.castShadow) { + this.update_shadows() + } + + const on_light_change = () => { + if (light.castShadow) { + this.update_shadows() + } + + this.update(); + } + + light_model.on("change", on_light_change); + light_model.on("childchange", on_light_change); + + this.lights[light_model.cid] = light; + + if (light.target) { + this.scene_scatter.add(light.target); + } + this.scene_scatter.add(light); + } + + // Do not delete current lights + current_light_cids.push(light_model.cid); + }); + + // Remove previous lights + for (const cid of Object.keys(this.lights)) { + const light = this.lights[cid]; + + if (current_light_cids.indexOf(cid) === -1) { + this.scene_scatter.remove(light); + delete this.lights[cid]; + } + } + + this.update(); + } + } + + update_shadows() { + // Activate shadow mapping + this.renderer.shadowMap.enabled = true + this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; + } + transition(f, on_done, context) { const that = this; const exp = that.model.get("animation_exponent"); diff --git a/js/src/mesh.ts b/js/src/mesh.ts index 09fcb4af..80111e7c 100644 --- a/js/src/mesh.ts +++ b/js/src/mesh.ts @@ -26,9 +26,33 @@ class MeshView extends widgets.WidgetView { texture_loader: any; textures: any; texture_video: any; + + LIGHTING_MODELS: any; + + lighting_model: any; + diffuse_color : any; + opacity : any; + specular_color : any; + shininess : any; + color : any; + emissive_intensity : any; + roughness : any; + metalness : any; + cast_shadow : any; + receive_shadow : any; + flat_shading : any; + render() { // console.log("created mesh view, parent is") // console.log(this.options.parent) + + this.LIGHTING_MODELS = { + DEFAULT: 'DEFAULT', + LAMBERT: 'LAMBERT', + PHONG: 'PHONG', + PHYSICAL : 'PHYSICAL' + }; + this.renderer = this.options.parent; this.previous_values = {}; this.attributes_changed = {}; @@ -40,7 +64,8 @@ class MeshView extends widgets.WidgetView { this._load_textures(); } - this.uniforms = { + this.uniforms = THREE.UniformsUtils.merge( [ + { xlim : { type: "2f", value: [0., 1.] }, ylim : { type: "2f", value: [0., 1.] }, zlim : { type: "2f", value: [0., 1.] }, @@ -54,13 +79,28 @@ class MeshView extends widgets.WidgetView { animation_time_texture : { type: "f", value: 1. }, texture: { type: "t", value: null }, texture_previous: { type: "t", value: null }, - }; + }, + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "lights" ], + { + emissive: { value: new THREE.Color( 0x000000 ) }, + emissiveIntensity: { value: 1 }, + specular: { value: new THREE.Color( 0xffffff ) }, + shininess: { value: 0 }, + roughness: { value: 0.0 }, + metalness: { value: 0.0 }, + }, + + ] ); + const get_material = (name) => { if (this.model.get(name)) { return this.model.get(name).obj.clone(); } else { const mat = new THREE.ShaderMaterial(); mat.side = THREE.DoubleSide; + mat.needsUpdate = true; + return mat; } @@ -78,28 +118,30 @@ class MeshView extends widgets.WidgetView { if (this.model.get("material")) { this.model.get("material").on("change", () => { this._update_materials(); - this.renderer.update(); }); } if (this.model.get("line_material")) { this.model.get("line_material").on("change", () => { this._update_materials(); - this.renderer.update(); }); } this.create_mesh(); this.add_to_scene(); + this.model.on("change:color change:sequence_index change:x change:y change:z change:v change:u change:triangles change:lines", this.on_change, this); this.model.on("change:geo change:connected", this.update_, this); this.model.on("change:texture", this._load_textures, this); - this.model.on("change:visible", this.update_visibility, this); + this.model.on("change:visible change:lighting_model change:opacity change:specular_color change:shininess change:emissive_intensity change:roughness change:metalness change:cast_shadow change:receive_shadow change:flat_shading", + this._update_materials, this); } - public update_visibility() { - this._update_materials(); - this.renderer.update(); + public force_lighting_model() { + if(this.lighting_model === this.LIGHTING_MODELS.DEFAULT){ + this.model.set("lighting_model", this.LIGHTING_MODELS.PHYSICAL); + this._update_materials(); + } } public _load_textures() { @@ -116,7 +158,7 @@ class MeshView extends widgets.WidgetView { texture.minFilter = THREE.LinearFilter; // texture.wrapT = THREE.RepeatWrapping; this.textures = [texture]; - this._update_materials(); + //this._update_materials(); this.update_(); }); } else { @@ -124,7 +166,7 @@ class MeshView extends widgets.WidgetView { this.texture_loader.load(texture_url, (threejs_texture) => { threejs_texture.wrapS = THREE.RepeatWrapping; threejs_texture.wrapT = THREE.RepeatWrapping; - this._update_materials(); + //this._update_materials(); this.update_(); }), ); @@ -138,11 +180,25 @@ class MeshView extends widgets.WidgetView { } add_to_scene() { + this.cast_shadow = this.model.get("cast_shadow"); + this.receive_shadow = this.model.get("receive_shadow"); + this.meshes.forEach((mesh) => { + mesh.castShadow = this.cast_shadow; + mesh.receiveShadow = this.receive_shadow; this.renderer.scene_scatter.add(mesh); }); } + update_shadow() { + this.cast_shadow = this.model.get("cast_shadow"); + this.receive_shadow = this.model.get("receive_shadow"); + this.meshes.forEach((mesh) => { + mesh.castShadow = this.cast_shadow; + mesh.receiveShadow = this.receive_shadow; + }); + } + remove_from_scene() { this.meshes.forEach((mesh) => { this.renderer.scene_scatter.remove(mesh); @@ -151,37 +207,44 @@ class MeshView extends widgets.WidgetView { } on_change(attribute) { - for (const key of this.model.changedAttributes()) { - // console.log("changed " +key) - this.previous_values[key] = this.model.previous(key); - // attributes_changed keys will say what needs to be animated, it's values are the properties in - // this.previous_values that need to be removed when the animation is done - // we treat changes in _selected attributes the same - const key_animation = key.replace("_selected", ""); - if (key_animation === "sequence_index") { - const animated_by_sequence = ["x", "y", "z", "u", "v", "color"]; - animated_by_sequence.forEach((name) => { - if (isArray(this.model.get(name)) && this.model.get(name).length > 1) { - this.attributes_changed[name] = [name, "sequence_index"]; + + try { + for (const key of this.model.changedAttributes()) { + + // console.log("changed " +key) + this.previous_values[key] = this.model.previous(key); + // attributes_changed keys will say what needs to be animated, it's values are the properties in + // this.previous_values that need to be removed when the animation is done + // we treat changes in _selected attributes the same + const key_animation = key.replace("_selected", ""); + if (key_animation === "sequence_index") { + const animated_by_sequence = ["x", "y", "z", "u", "v", "color"]; + animated_by_sequence.forEach((name) => { + if (isArray(this.model.get(name)) && this.model.get(name).length > 1) { + this.attributes_changed[name] = [name, "sequence_index"]; + } + }); + this.attributes_changed.texture = ["texture", "sequence_index"]; + } else if (key_animation === "triangles") { + // direct change, no animation + } else if (key_animation === "lines") { + // direct change, no animation + } else if (key_animation === "selected") { // and no explicit animation on this one + this.attributes_changed.color = [key]; + } else { + this.attributes_changed[key_animation] = [key]; + // animate the size as well on x y z changes + if (["x", "y", "z", "u", "v", "color"].indexOf(key_animation) !== -1) { + // console.log("adding size to list of changed attributes") + // this.attributes_changed["size"] = [] } - }); - this.attributes_changed.texture = ["texture", "sequence_index"]; - } else if (key_animation === "triangles") { - // direct change, no animation - } else if (key_animation === "lines") { - // direct change, no animation - } else if (key_animation === "selected") { // and no explicit animation on this one - this.attributes_changed.color = [key]; - } else { - this.attributes_changed[key_animation] = [key]; - // animate the size as well on x y z changes - if (["x", "y", "z", "u", "v", "color"].indexOf(key_animation) !== -1) { - // console.log("adding size to list of changed attributes") - // this.attributes_changed["size"] = [] - } + } } } + catch(err) { + console.log("ERROR: Error setting state: this.model.changedAttributes is not a function or its return value is not iterable"); + } this.update_(); } @@ -189,7 +252,7 @@ class MeshView extends widgets.WidgetView { this.remove_from_scene(); this.create_mesh(); this.add_to_scene(); - this.renderer.update(); + this._update_materials(); } _get_value(value, index, default_value) { @@ -266,23 +329,70 @@ class MeshView extends widgets.WidgetView { if (this.model.get("line_material")) { this.line_material_rgb.copy(this.model.get("line_material").obj); } - this.material_rgb.defines = {USE_RGB: true}; - this.line_material.defines = {AS_LINE: true}; - this.line_material_rgb.defines = {AS_LINE: true, USE_RGB: true}; + + // update material defines in order to run correct shader code + this.material.defines = {USE_COLOR: true, DEFAULT_SHADING:true, LAMBERT_SHADING:false, PHONG_SHADING:false, PHYSICAL_SHADING:false}; + this.material_rgb.defines = {USE_RGB: true, USE_COLOR: true, DEFAULT_SHADING:true, LAMBERT_SHADING:false, PHONG_SHADING:false, PHYSICAL_SHADING:false}; + this.line_material.defines = {AS_LINE: true, DEFAULT_SHADING:true, LAMBERT_SHADING:false, PHONG_SHADING:false, PHYSICAL_SHADING:false}; + this.line_material_rgb.defines = {AS_LINE: true, USE_RGB: true, USE_COLOR: true, DEFAULT_SHADING:true, LAMBERT_SHADING:false, PHONG_SHADING:false, PHYSICAL_SHADING:false}; this.material.extensions = {derivatives: true}; + // locally and the visible with this object's visible trait this.material.visible = this.material.visible && this.model.get("visible"); this.material_rgb.visible = this.material.visible && this.model.get("visible"); this.line_material.visible = this.line_material.visible && this.model.get("visible"); this.line_material_rgb.visible = this.line_material.visible && this.model.get("visible"); + + this.lighting_model = this.model.get("lighting_model"); this.materials.forEach((material) => { + material.uniforms = this.uniforms; material.vertexShader = require("raw-loader!../glsl/mesh-vertex.glsl"); material.fragmentShader = require("raw-loader!../glsl/mesh-fragment.glsl"); - material.uniforms = this.uniforms; + material.defines.DEFAULT_SHADING = false; + material.defines.LAMBERT_SHADING = false; + material.defines.PHONG_SHADING = false; + material.defines.PHYSICAL_SHADING = false; + + if(this.lighting_model === this.LIGHTING_MODELS.DEFAULT) { + material.defines.DEFAULT_SHADING = true; + } + else if(this.lighting_model === this.LIGHTING_MODELS.LAMBERT) { + material.defines.LAMBERT_SHADING = true; + } + else if(this.lighting_model === this.LIGHTING_MODELS.PHONG) { + material.defines.PHONG_SHADING = true; + } + else if(this.lighting_model === this.LIGHTING_MODELS.PHYSICAL) { + material.defines.PHYSICAL_SHADING = true; + } material.depthWrite = true; - material.transparant = true; + material.transparent = true; material.depthTest = true; + // use lighting + material.lights = true; + material.flatShading = this.model.get("flat_shading"); }); + + this.diffuse_color = this.model.get("diffuse_color"); + this.opacity = this.model.get("opacity"); + this.specular_color = this.model.get("specular_color"); + this.shininess = this.model.get("shininess"); + this.color = this.model.get("color"); + this.emissive_intensity = this.model.get("emissive_intensity"); + this.roughness = this.model.get("roughness"); + this.metalness = this.model.get("metalness"); + + this.material.uniforms.diffuse.value = new THREE.Color(1, 1, 1);// keep hardcoded + this.material.uniforms.opacity.value = this.opacity; + this.material.uniforms.specular.value = new THREE.Color(this.specular_color); + this.material.uniforms.shininess.value = this.shininess; + this.material.uniforms.emissive.value = new THREE.Color(this.color); + this.material.uniforms.emissiveIntensity.value = this.emissive_intensity; + this.material.uniforms.roughness.value = this.roughness; + this.material.uniforms.metalness.value = this.metalness; + + this.update_shadow(); + const texture = this.model.get("texture"); if (texture && this.textures) { this.material.defines.USE_TEXTURE = true; @@ -291,6 +401,8 @@ class MeshView extends widgets.WidgetView { this.material_rgb.needsUpdate = true; this.line_material.needsUpdate = true; this.line_material_rgb.needsUpdate = true; + + this.renderer.update(); } create_mesh() { @@ -396,7 +508,7 @@ class MeshView extends widgets.WidgetView { const geometry = new THREE.BufferGeometry(); geometry.addAttribute("position", new THREE.BufferAttribute(current.array_vec3.vertices, 3)); geometry.addAttribute("position_previous", new THREE.BufferAttribute(previous.array_vec3.vertices, 3)); - geometry.addAttribute("color", new THREE.BufferAttribute(current.array_vec4.color, 4)); + geometry.addAttribute("color_current", new THREE.BufferAttribute(current.array_vec4.color, 4)); geometry.addAttribute("color_previous", new THREE.BufferAttribute(previous.array_vec4.color, 4)); geometry.setIndex(new THREE.BufferAttribute(triangles, 1)); const texture = this.model.get("texture"); @@ -414,13 +526,16 @@ class MeshView extends widgets.WidgetView { geometry.addAttribute("u_previous", new THREE.BufferAttribute(u_previous, 1)); geometry.addAttribute("v_previous", new THREE.BufferAttribute(v_previous, 1)); } + geometry.computeVertexNormals(); this.surface_mesh = new THREE.Mesh(geometry, this.material); // BUG? because of our custom shader threejs thinks our object if out // of the frustum + this.surface_mesh.frustumCulled = false; this.surface_mesh.material_rgb = this.material_rgb; this.surface_mesh.material_normal = this.material; + this.meshes.push(this.surface_mesh); } @@ -432,7 +547,7 @@ class MeshView extends widgets.WidgetView { geometry.addAttribute("position_previous", new THREE.BufferAttribute(previous.array_vec3.vertices, 3)); const color = new THREE.BufferAttribute(current.array_vec4.color, 4); color.normalized = true; - geometry.addAttribute("color", color); + geometry.addAttribute("color_current", color); const color_previous = new THREE.BufferAttribute(previous.array_vec4.color, 4); color_previous.normalized = true; geometry.addAttribute("color_previous", color_previous); @@ -483,6 +598,13 @@ class MeshModel extends widgets.WidgetModel { texture: serialize.texture, material: { deserialize: widgets.unpack_models }, line_material: { deserialize: widgets.unpack_models }, + diffuse_color : serialize.color_or_json, + opacity : serialize.array_or_json, + specular_color : serialize.color_or_json, + shininess : serialize.array_or_json, + emissive_intensity : serialize.array_or_json, + roughness : serialize.array_or_json, + metalness : serialize.array_or_json, }; defaults() { return { @@ -492,13 +614,24 @@ class MeshModel extends widgets.WidgetModel { _model_module : "ipyvolume", _view_module : "ipyvolume", _model_module_version: semver_range, - _view_module_version: semver_range, + _view_module_version: semver_range, color: "red", sequence_index: 0, connected: false, visible: true, visible_lines: true, visible_faces: true, + lighting_model: "DEFAULT", + diffuse_color : "white", + opacity : 1, + specular_color : "white", + shininess : 1, + emissive_intensity : 1, + roughness : 0, + metalness : 0, + cast_shadow : false, + receive_shadow : false, + flat_shading : true }; } } diff --git a/js/src/scatter.ts b/js/src/scatter.ts index c7af4edb..58237fbe 100644 --- a/js/src/scatter.ts +++ b/js/src/scatter.ts @@ -26,7 +26,28 @@ class ScatterView extends widgets.WidgetView { texture_video: HTMLVideoElement; line_segments: any; mesh: any; + + LIGHTING_MODELS: any; + + lighting_model: any; + diffuse_color : any; + opacity : any; + color : any; + specular_color : any; + shininess : any; + emissive_intensity : any; + roughness : any; + metalness : any; + cast_shadow : any; + receive_shadow : any; + render() { + + this.LIGHTING_MODELS = { + DEFAULT: 'DEFAULT', + PHYSICAL : 'PHYSICAL' + }; + this.renderer = this.options.parent; this.previous_values = {}; this.attributes_changed = {}; @@ -85,7 +106,8 @@ class ScatterView extends widgets.WidgetView { triangle_2d: geo_triangle_2d, }; - this.uniforms = { + this.uniforms = THREE.UniformsUtils.merge( [ + { xlim : { type: "2f", value: [0., 1.] }, ylim : { type: "2f", value: [0., 1.] }, zlim : { type: "2f", value: [0., 1.] }, @@ -99,7 +121,19 @@ class ScatterView extends widgets.WidgetView { animation_time_color : { type: "f", value: 1. }, texture: { type: "t", value: null }, texture_previous: { type: "t", value: null }, - }; + }, + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "lights" ], + { + emissive: { value: new THREE.Color( 0x000000 ) }, + emissiveIntensity: { value: 1 }, + specular: { value: new THREE.Color( 0xffffff ) }, + shininess: { value: 0 }, + roughness: { value: 0.0 }, + metalness: { value: 0.0 }, + }, + + ] ); const get_material = (name) => { if (this.model.get(name)) { return this.model.get(name).obj.clone(); @@ -112,17 +146,16 @@ class ScatterView extends widgets.WidgetView { this.line_material = get_material("line_material"); this.line_material_rgb = get_material("line_material"); this.materials = [this.material, this.material_rgb, this.line_material, this.line_material_rgb]; + this._update_materials(); if (this.model.get("material")) { this.model.get("material").on("change", () => { this._update_materials(); - this.renderer.update(); }); } if (this.model.get("line_material")) { this.model.get("line_material").on("change", () => { this._update_materials(); - this.renderer.update(); }); } @@ -132,12 +165,15 @@ class ScatterView extends widgets.WidgetView { this.on_change, this); this.model.on("change:geo change:connected", this.update_, this); this.model.on("change:texture", this._load_textures, this); - this.model.on("change:visible", this.update_visibility, this); + this.model.on("change:visible", this._update_materials, this); this.model.on("change:geo", () => { this._update_materials(); - this.renderer.update(); }); + + this.model.on("change:lighting_model change:color change:opacity change:specular_color change:shininess change:emissive_intensity change:roughness change:metalness change:cast_shadow change:receive_shadow", + this._update_materials, this); } + _load_textures() { const texture = this.model.get("texture"); if (texture.stream) { // instanceof media.MediaStreamModel) { @@ -163,19 +199,32 @@ class ScatterView extends widgets.WidgetView { ); } } - update_visibility() { - this._update_materials(); - this.renderer.update(); + + public force_lighting_model() { + if(this.lighting_model === this.LIGHTING_MODELS.DEFAULT){ + this.model.set("lighting_model", this.LIGHTING_MODELS.PHYSICAL); + this._update_materials(); + } } + set_limits(limits) { for (const key of Object.keys(limits)) { this.material.uniforms[key].value = limits[key]; } } add_to_scene() { + + //currently, no shadow support because of InstancedBufferGeometry + this.cast_shadow = this.model.get("cast_shadow"); + this.receive_shadow = this.model.get("receive_shadow"); + this.mesh.castShadow = false; + this.mesh.receiveShadow = false; + this.renderer.scene_scatter.add(this.mesh); if (this.line_segments) { this.renderer.scene_scatter.add(this.line_segments); + this.line_segments.castShadow = false; + this.line_segments.receiveShadow = false; } } remove_from_scene() { @@ -275,25 +324,65 @@ class ScatterView extends widgets.WidgetView { // not present on .copy.. bug? this.line_material_rgb.linewidth = this.line_material.linewidth = this.model.get("line_material").obj.linewidth; } + + this.material.defines = {USE_COLOR: true, DEFAULT_SHADING:true, PHYSICAL_SHADING:false}; this.material.extensions = {derivatives: true}; - this.material_rgb.defines = {USE_RGB: true}; + this.material_rgb.defines = {USE_RGB: true, USE_COLOR: true, DEFAULT_SHADING:true, PHYSICAL_SHADING:false}; this.material_rgb.extensions = {derivatives: true}; - this.line_material.defines = {AS_LINE: true}; - this.line_material_rgb.defines = {USE_RGB: true, AS_LINE: true}; + this.line_material.defines = {AS_LINE: true, DEFAULT_SHADING:true, PHYSICAL_SHADING:false}; + this.line_material_rgb.defines = {USE_RGB: true, AS_LINE: true, USE_COLOR: true, DEFAULT_SHADING:true, PHYSICAL_SHADING:false}; // locally and the visible with this object's visible trait this.material.visible = this.material.visible && this.model.get("visible"); this.material_rgb.visible = this.material.visible && this.model.get("visible"); this.line_material.visible = this.line_material.visible && this.model.get("visible"); this.line_material_rgb.visible = this.line_material.visible && this.model.get("visible"); + + this.lighting_model = this.model.get("lighting_model"); + this.materials.forEach((material) => { material.vertexShader = require("raw-loader!../glsl/scatter-vertex.glsl"); material.fragmentShader = require("raw-loader!../glsl/scatter-fragment.glsl"); + material.defines.DEFAULT_SHADING = false; + material.defines.PHYSICAL_SHADING = false; + + if(this.lighting_model === this.LIGHTING_MODELS.DEFAULT) { + material.defines.DEFAULT_SHADING = true; + } + else { + //Does not support shadows because on three.js r97 the shadow mapping is not working correctly for InstancedBufferGeometry + //Should not use with Spot Lights and Point Lights because lighting color is the same for InstancedBufferGeometry instances + material.defines.PHYSICAL_SHADING = true; + } + material.uniforms = {...material.uniforms, ...this.uniforms}; + material.lights = true; + material.flatShading = true; + + this.diffuse_color = this.model.get("diffuse_color"); + this.opacity = this.model.get("opacity"); + this.specular_color = this.model.get("specular_color"); + this.shininess = this.model.get("shininess"); + this.color = this.model.get("color"); + this.emissive_intensity = this.model.get("emissive_intensity"); + this.roughness = this.model.get("roughness"); + this.metalness = this.model.get("metalness"); + + material.uniforms.diffuse.value = new THREE.Color(1, 1, 1);// keep hardcoded + material.uniforms.opacity.value = this.opacity; + material.uniforms.specular.value = new THREE.Color(this.specular_color); + material.uniforms.shininess.value = this.shininess; + material.uniforms.emissive.value = new THREE.Color(this.color); + material.uniforms.emissiveIntensity.value = this.emissive_intensity; + material.uniforms.roughness.value = this.roughness; + material.uniforms.metalness.value = this.metalness; + material.depthWrite = true; - material.transparant = true; + material.transparent = true; material.depthTest = true; material.needsUpdate = true; + }); + const geo = this.model.get("geo"); const sprite = geo.endsWith("2d"); if (sprite) { @@ -306,10 +395,14 @@ class ScatterView extends widgets.WidgetView { this.material.defines.USE_TEXTURE = true; } } + this.material.lights = true; + this.material.flatShading = true; this.material.needsUpdate = true; this.material_rgb.needsUpdate = true; this.line_material.needsUpdate = true; this.line_material_rgb.needsUpdate = true; + + this.renderer.update(); } create_mesh() { let geo = this.model.get("geo"); @@ -319,11 +412,12 @@ class ScatterView extends widgets.WidgetView { } const sprite = geo.endsWith("2d"); const buffer_geo = new THREE.BufferGeometry().fromGeometry(this.geos[geo]); + //buffer_geo.computeVertexNormals(); const instanced_geo = new THREE.InstancedBufferGeometry(); const vertices = (buffer_geo.attributes.position as any).clone(); instanced_geo.addAttribute("position", vertices); - + const sequence_index = this.model.get("sequence_index"); let sequence_index_previous = this.previous_values.sequence_index; if (typeof sequence_index_previous === "undefined") { @@ -335,6 +429,11 @@ class ScatterView extends widgets.WidgetView { const current = new values.Values(scalar_names, [], this.get_current.bind(this), sequence_index, vector4_names); const previous = new values.Values(scalar_names, [], this.get_previous.bind(this), sequence_index_previous, vector4_names); + //Fix for Uncaught TypeError: Cannot read property 'BYTES_PER_ELEMENT' of undefined + current.ensure_array(["color"]); + // Workaround for shader issue - Threejs already uses the name color + instanced_geo.addAttribute("color_current", new THREE.BufferAttribute(current.array_vec4.color, 4)); + const length = Math.max(current.length, previous.length); if (length === 0) { console.error("no single member is an array, not supported (yet?)"); @@ -384,6 +483,7 @@ class ScatterView extends widgets.WidgetView { this.material.uniforms.texture_previous.value = this.textures[sequence_index_previous % this.textures.length]; } } + instanced_geo.computeVertexNormals(); this.mesh = new THREE.Mesh(instanced_geo, this.material); this.mesh.material_rgb = this.material_rgb; this.mesh.material_normal = this.material; @@ -398,8 +498,9 @@ class ScatterView extends widgets.WidgetView { current.ensure_array(["color"]); previous.ensure_array(["color"]); - geometry.addAttribute("color", new THREE.BufferAttribute(current.array_vec4.color, 4)); + geometry.addAttribute("color_current", new THREE.BufferAttribute(current.array_vec4.color, 4)); geometry.addAttribute("color_previous", new THREE.BufferAttribute(previous.array_vec4.color, 4)); + geometry.computeVertexNormals(); this.line_segments = new THREE.Line(geometry, this.line_material); this.line_segments.frustumCulled = false; @@ -444,6 +545,13 @@ class ScatterModel extends widgets.WidgetModel { texture: serialize.texture, material: { deserialize: widgets.unpack_models }, line_material: { deserialize: widgets.unpack_models }, + diffuse_color : serialize.color_or_json, + opacity : serialize.array_or_json, + specular_color : serialize.color_or_json, + shininess : serialize.array_or_json, + emissive_intensity : serialize.array_or_json, + roughness : serialize.array_or_json, + metalness : serialize.array_or_json, }; defaults() { @@ -463,6 +571,16 @@ class ScatterModel extends widgets.WidgetModel { connected: false, visible: true, selected: null, + lighting_model: "DEFAULT", + diffuse_color : "white", + opacity : 1, + specular_color : "white", + shininess : 1, + emissive_intensity : 1, + roughness : 0, + metalness : 0, + cast_shadow : false, + receive_shadow : false, }; } } diff --git a/notebooks/lighting_demo.ipynb b/notebooks/lighting_demo.ipynb new file mode 100644 index 00000000..003a8a10 --- /dev/null +++ b/notebooks/lighting_demo.ipynb @@ -0,0 +1,537 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "import numpy as np\n", + "from numpy import cos, sin, pi\n", + "from ipyvolume import pylab as p3\n", + "import math\n", + "import ipywidgets as widgets\n", + "try:\n", + " import scipy.ndimage\n", + " import scipy.special\n", + "except:\n", + " pass\n", + " \n", + "def klein_bottle(\n", + " endpoint=True,\n", + " tab=None,\n", + " index=0,\n", + " color='red',\n", + " flat_shading=True):\n", + " # http://paulbourke.net/geometry/klein/\n", + " u = np.linspace(0, 2 * pi, num=50, endpoint=endpoint)\n", + " v = np.linspace(0, 2 * pi, num=50, endpoint=endpoint)\n", + " u, v = np.meshgrid(u, v)\n", + " r = 4 * (1 - cos(u) / 2)\n", + " x = 6 * cos(u) * (1 + sin(u)) + r * cos(u) * cos(v) * (u < pi) + r * cos(v + pi) * (u >= pi)\n", + " y = 16 * sin(u) + r * sin(u) * cos(v) * (u < pi)\n", + " z = r * sin(v)\n", + " mesh = p3.plot_mesh(x=x, y=y, z=z, wireframe=False, wrapx=not endpoint, wrapy=not endpoint,\n", + " u=u / (2 * np.pi), v=v / (2 * np.pi), color=color, flat_shading=flat_shading)\n", + " p3.setup_material_widgets(mesh=mesh, tab=tab, index=index)\n", + " return mesh\n", + " \n", + "def worldplane(\n", + " tab=None,\n", + " index=0,\n", + " color='red',\n", + " flat_shading=True):\n", + " k = 20\n", + " h = -15\n", + " tx = np.array([k, -k, -k, k])\n", + " tz = np.array([k, k, -k, -k])\n", + " ty = np.array([h, h, h, h])\n", + " tri = [(0, 1, 2), (0, 2, 3)]\n", + " p = p3.plot_trisurf(x=tx, y=ty, z=tz, triangles=tri, color=color, flat_shading=flat_shading)\n", + " p3.setup_material_widgets(mesh=p, tab=tab, index=index)\n", + " return p\n", + " \n", + "def test_surface(\n", + " tab=None,\n", + " index=0,\n", + " color='red',\n", + " flat_shading=True):\n", + " X = np.arange(-10, 10, 0.25*1)-10\n", + " Y = np.arange(-10, 10, 0.25*1)\n", + " X, Y = np.meshgrid(X, Y)\n", + " R = np.sqrt(X**2 + Y**2)\n", + " Z = np.sin(R)\n", + " surf = p3.plot_surface(x=X+10, y=Z-10, z=Y+5, color=color, flat_shading=flat_shading)\n", + " p3.setup_material_widgets(mesh=surf, tab=tab, index=index)\n", + " return surf\n", + "\n", + "def plot_all(kb=False, \n", + " ts=False, \n", + " wp=True, \n", + " color='red', \n", + " flat_shading=True, \n", + " show_ui=True):\n", + " k=None\n", + " w=None\n", + " s=None\n", + " p3.clear()\n", + " p3.figure()\n", + " tab = widgets.Tab() if show_ui == True else None\n", + " index = 0\n", + "\n", + " if kb:\n", + " k=klein_bottle(tab=tab, index=index, color=color, flat_shading=flat_shading)\n", + " index+=1\n", + " if ts:\n", + " s=test_surface(tab=tab, index=index, color=color, flat_shading=flat_shading)\n", + " index+=1\n", + " if wp:\n", + " w=worldplane(tab=tab, index=index, color=color, flat_shading=flat_shading)\n", + " p3.show()\n", + " display(tab)\n", + " return [k,w,s] \n", + "\n", + "def lights_dir_spot():\n", + " dir1=p3.directional_light(light_color='blue', \n", + " intensity=1.0,\n", + " position=[30,30,30],\n", + " target=[0,20,30])\n", + " sp1=p3.spot_light(light_color='yellow',\n", + " intensity=1,\n", + " position=[20,30,20],\n", + " target=[-20,-20,-20])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Ambient Light" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "cbb4274be08641bb9f63ad021df82c32", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Figure(camera=PerspectiveCamera(fov=45.0, position=(0.0, 0.0, 2.0), quaternion=(0.0, 0.0, 0.0, …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "None" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "197f42cf389647268f807c9c3e508c6c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tab(children=(VBox(children=(ColorPicker(value='green', description='Light Color:', layout=Layout(width='450px…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_all(kb=True, ts=False, wp=True, show_ui=False)\n", + "tab = widgets.Tab()\n", + "light = p3.ambient_light(light_color=\"green\")\n", + "p3.setup_light_widgets(light=light, tab=tab, index=0)\n", + "display(tab)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Hemisphere Light" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fcdd16451c66442d89d1975efa7fa61c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Figure(camera=PerspectiveCamera(fov=45.0, position=(0.0, 0.0, 2.0), quaternion=(0.0, 0.0, 0.0, …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "None" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "48998a3df5514e27a68e02b230fa3b1e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tab(children=(VBox(children=(ColorPicker(value='#ffffff', description='Light Color:', layout=Layout(width='450…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_all(kb=True, ts=False, wp=True, show_ui=False)\n", + "tab = widgets.Tab()\n", + "light = p3.hemisphere_light()\n", + "p3.setup_light_widgets(light=light, tab=tab, index=0)\n", + "display(tab)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Directional Light" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "a6aad210ef2944b5a580fdd1dc1b2a32", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Figure(camera=PerspectiveCamera(fov=45.0, position=(0.0, 0.0, 2.0), quaternion=(0.0, 0.0, 0.0, …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "None" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3a4df04c44e14a5c992045caaf3dde00", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tab(children=(VBox(children=(ColorPicker(value='orange', description='Light Color:', layout=Layout(width='450p…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_all(kb=True, ts=False, wp=True, show_ui=False)\n", + "tab = widgets.Tab()\n", + "light_d = p3.directional_light(light_color=\"orange\")\n", + "p3.setup_light_widgets(light=light_d, tab=tab, index=0)\n", + "display(tab)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Spot Light" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "04bf2c54cac347afaec147ffc38a2d98", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Figure(camera=PerspectiveCamera(fov=45.0, position=(0.0, 0.0, 2.0), quaternion=(0.0, 0.0, 0.0, …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "None" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c81ea556148a48a78c62adc5b73be823", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tab(children=(VBox(children=(ColorPicker(value='blue', description='Light Color:', layout=Layout(width='450px'…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_all(kb=True, ts=False, wp=True, show_ui=False)\n", + "tab = widgets.Tab()\n", + "light_s = p3.spot_light(light_color=\"blue\")\n", + "p3.setup_light_widgets(light=light_s, tab=tab, index=0)\n", + "display(tab)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Point Light" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2643b94fd3164f528a8cd69a3fadafc6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Figure(camera=PerspectiveCamera(fov=45.0, position=(0.0, 0.0, 2.0), quaternion=(0.0, 0.0, 0.0, …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "None" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "79dd9e39a491415f84e04cf60af998ba", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tab(children=(VBox(children=(ColorPicker(value='yellow', description='Light Color:', layout=Layout(width='450p…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_all(kb=True, ts=False, wp=True, show_ui=False)\n", + "tab = widgets.Tab()\n", + "light_p = p3.point_light(light_color=\"yellow\")\n", + "p3.setup_light_widgets(light=light_p, tab=tab, index=0)\n", + "display(tab)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Material properties" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6ad41a5b0ecb495cab7c8fb8552ec587", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Figure(camera=PerspectiveCamera(fov=45.0, position=(0.0, 0.0, 2.0), quaternion=(0.0, 0.0, 0.0, …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "None" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "acffd8582fd7450dae83d35990829a64", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tab(children=(VBox(children=(ColorPicker(value='blue', description='Light Color:', layout=Layout(width='450px'…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "[bottle, plane, surf] = plot_all(kb=True, ts=True, wp=True, show_ui=False)\n", + "lights_dir_spot()\n", + "p3.show_lighting_widgets()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Animated Light" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ea82d58c0fde4da9b4db496ba6e4d816", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(Figure(camera=PerspectiveCamera(fov=45.0, position=(0.0, 0.0, 2.0), quaternion=(0.0, 0.0, 0.0, …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "None" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "[b1, p1, s1] = plot_all(kb=True, wp=True, ts=False, show_ui=False, color='white', flat_shading=False)\n", + "point1=p3.point_light()\n", + "point1.intensity=0.8" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "# Generating gif with animated light position\n", + "fps=20\n", + "frames=40\n", + "def set_pos(fig, i, fraction):\n", + " point1.position = [np.sin(2*np.pi*fraction) * fps, point1.position[1], np.cos(2*np.pi*fraction) * fps]\n", + "#movie = p3.movie('rotatelight.gif', set_pos, fps=fps, frames=frames, endpoint=False, gif_loop=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/rotatelight.gif b/notebooks/rotatelight.gif new file mode 100644 index 00000000..9011ef3e Binary files /dev/null and b/notebooks/rotatelight.gif differ diff --git a/notebooks/test.ipynb b/notebooks/test.ipynb index 7542ed4f..79dd1d09 100644 --- a/notebooks/test.ipynb +++ b/notebooks/test.ipynb @@ -899,6 +899,14 @@ "s.color_selected = \"brown\"" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Lighting test\n", + "Lighting test" + ] + }, { "cell_type": "code", "execution_count": null, @@ -924,7 +932,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.7.4" } }, "nbformat": 4,