Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Lights and Shadows Merge #341

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion ipyvolume/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
661 changes: 647 additions & 14 deletions ipyvolume/pylab.py
100644 → 100755

Large diffs are not rendered by default.

138 changes: 138 additions & 0 deletions ipyvolume/test_all.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import shutil
import json
import contextlib
import math

import numpy as np
import pytest
Expand Down Expand Up @@ -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
35 changes: 32 additions & 3 deletions ipyvolume/widgets.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)
from ipyvolume.transferfunction import TransferFunction
from ipyvolume.utils import debounced, grid_slice, reduce_size

import math

_last_figure = None
logger = logging.getLogger("ipyvolume")
Expand Down Expand Up @@ -78,6 +78,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)
Expand All @@ -94,7 +105,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)
Expand Down Expand Up @@ -147,6 +157,16 @@ class Scatter(widgets.Widget):
visible = traitlets.CBool(default_value=True).tag(sync=True)
shader_snippets = traitlets.Dict({'size': '\n'}).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),
Expand Down Expand Up @@ -244,7 +264,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."""
Expand All @@ -267,6 +287,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)
Expand All @@ -287,6 +314,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
Expand Down
Loading