{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pythreejs import *\n", "import numpy as np\n", "from IPython.display import display\n", "from ipywidgets import HTML, Text, Output, VBox\n", "from traitlets import link, dlink" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Simple sphere and text" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ball = Mesh(geometry=SphereGeometry(radius=1, widthSegments=32, heightSegments=24), \n", " material=MeshLambertMaterial(color='red'),\n", " position=[2, 1, 0])\n", "\n", "c = PerspectiveCamera(position=[0, 5, 5], up=[0, 1, 0],\n", " children=[DirectionalLight(color='white', position=[3, 5, 1], intensity=0.5)])\n", "\n", "scene = Scene(children=[ball, c, AmbientLight(color='#777777')])\n", "\n", "renderer = Renderer(camera=c, \n", " scene=scene, \n", " controls=[OrbitControls(controlling=c)])\n", "display(renderer)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ball.scale = (0.5,) * 3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import time, math\n", "ball.material.color = '#4400dd'\n", "for i in range(1, 150, 2):\n", " ball.scale = (i / 100.,) * 3\n", " ball.position = [math.cos(i / 10.), math.sin(i / 50.), i / 100.]\n", " time.sleep(.05)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Clickable Surface\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Generate surface data:\n", "view_width = 600\n", "view_height = 400\n", "nx, ny = (20, 20)\n", "xmax=1\n", "x = np.linspace(-xmax, xmax, nx)\n", "y = np.linspace(-xmax, xmax, ny)\n", "xx, yy = np.meshgrid(x, y)\n", "z = xx ** 2 - yy ** 2\n", "#z[6,1] = float('nan')\n", "\n", "\n", "# Generate scene objects from data:\n", "surf_g = SurfaceGeometry(z=list(z[::-1].flat), \n", " width=2 * xmax,\n", " height=2 * xmax,\n", " width_segments=nx - 1,\n", " height_segments=ny - 1)\n", "\n", "surf = Mesh(geometry=surf_g,\n", " material=MeshLambertMaterial(map=height_texture(z[::-1], 'YlGnBu_r')))\n", "\n", "surfgrid = SurfaceGrid(geometry=surf_g, material=LineBasicMaterial(color='black'),\n", " position=[0, 0, 1e-2]) # Avoid overlap by lifting grid slightly\n", "\n", "# Set up picking bojects:\n", "hover_point = Mesh(geometry=SphereGeometry(radius=0.05),\n", " material=MeshLambertMaterial(color='hotpink'))\n", "\n", "click_picker = Picker(controlling=surf, event='dblclick')\n", "hover_picker = Picker(controlling=surf, event='mousemove')\n", "\n", "# Set up scene:\n", "key_light = DirectionalLight(color='white', position=[3, 5, 1], intensity=0.4)\n", "c = PerspectiveCamera(position=[0, 3, 3], up=[0, 0, 1], aspect=view_width / view_height,\n", " children=[key_light])\n", "\n", "scene = Scene(children=[surf, c, surfgrid, hover_point, AmbientLight(intensity=0.8)])\n", "\n", "renderer = Renderer(camera=c, scene=scene,\n", " width=view_width, height=view_height,\n", " controls=[OrbitControls(controlling=c), click_picker, hover_picker])\n", "\n", "\n", "# Set up picking responses:\n", "# Add a new marker when double-clicking:\n", "out = Output()\n", "def f(change):\n", " value = change['new']\n", " with out:\n", " print('Clicked on %s' % (value,))\n", " point = Mesh(geometry=SphereGeometry(radius=0.05), \n", " material=MeshLambertMaterial(color='red'),\n", " position=value)\n", " scene.add(point)\n", "\n", "click_picker.observe(f, names=['point'])\n", "\n", "# Have marker follow picker point:\n", "link((hover_point, 'position'), (hover_picker, 'point'))\n", "\n", "# Show picker point coordinates as a label:\n", "h = HTML()\n", "def g(change):\n", " h.value = 'Green point at (%.3f, %.3f, %.3f)' % tuple(change['new'])\n", "g({'new': hover_point.position})\n", "hover_picker.observe(g, names=['point'])\n", "\n", "display(VBox([h, renderer, out]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "surf_g.z = list((-z[::-1]).flat)\n", "surf.material.map = height_texture(-z[::-1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Design our own texture" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from scipy import ndimage\n", "import matplotlib\n", "import matplotlib.pyplot as plt\n", "from skimage import img_as_ubyte \n", "\n", "jet = matplotlib.cm.get_cmap('jet')\n", "\n", "np.random.seed(int(1)) # start random number generator\n", "n = int(5) # starting points\n", "size = int(32) # size of image\n", "im = np.zeros((size,size)) # create zero image\n", "points = size*np.random.random((2, n**2)) # locations of seed values\n", "im[(points[0]).astype(np.int), (points[1]).astype(np.int)] = size # seed high values\n", "im = ndimage.gaussian_filter(im, sigma=size/(float(4)*n)) # smooth high values into surrounding areas\n", "im *= 1/np.max(im)# rescale to be in the range [0,1]\n", "rgba_im = img_as_ubyte(jet(im)) # convert the values to rgba image using the jet colormap\n", "\n", "t = DataTexture(data=rgba_im, format='RGBAFormat', width=size, height=size)\n", "\n", "geometry = SphereGeometry(radius=1, widthSegments=16, heightSegments=10)#TorusKnotGeometry(radius=2, radialSegments=200)\n", "material = MeshLambertMaterial(map=t)\n", "\n", "myobject = Mesh(geometry=geometry, material=material)\n", "c = PerspectiveCamera(position=[0, 3, 3], fov=40,\n", " children=[DirectionalLight(color='#ffffff', position=[3, 5, 1], intensity=0.5)])\n", "scene = Scene(children=[myobject, c, AmbientLight(color='#777777')])\n", "\n", "renderer = Renderer(camera=c, scene = scene, controls=[OrbitControls(controlling=c)], width=400, height=400)\n", "display(renderer)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Lines" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# On windows, linewidth of the material has no effect\n", "size = 4\n", "linesgeom = Geometry(vertices=[[0, 0, 0],\n", " [size, 0, 0],\n", " [0, 0, 0],\n", " [0, size, 0],\n", " [0, 0, 0],\n", " [0, 0, size]],\n", " colors = ['red', 'red', 'green', 'green', 'white', 'orange'])\n", "lines = Line(geometry=linesgeom, \n", " material=LineBasicMaterial(linewidth=5, vertexColors='VertexColors'), \n", " type='LinePieces',\n", " )\n", "scene = Scene(children=[\n", " lines,\n", " DirectionalLight(color='#ccaabb', position=[0,10,0]),\n", " AmbientLight(color='#cccccc'),\n", " ])\n", "c = PerspectiveCamera(position=[10, 10, 10])\n", "renderer = Renderer(camera=c, background='black', background_opacity=1, scene=scene, controls=[OrbitControls(controlling=c)],\n", " width=400, height=400)\n", "display(renderer)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Parametric Functions\n", "\n", "\n", "To use the ParametricGeometry class, you need to specify a javascript function as a string. The function should take two parameters that vary between 0 and 1, and a `THREE.Vector3(x,y,z)` that should be modified in place.\n", "\n", "If you want to build the surface in Python, you'll need to explicitly construct the vertices and faces and build a basic geometry from the vertices and faces." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f = \"\"\"\n", "function f(origu, origv, out) {\n", " // scale u and v to the ranges I want: [0, 2*pi]\n", " var u = 2*Math.PI*origu;\n", " var v = 2*Math.PI*origv;\n", " \n", " var x = Math.sin(u);\n", " var y = Math.cos(v);\n", " var z = Math.cos(u+v);\n", " \n", " out.set(x,y,z)\n", "}\n", "\"\"\"\n", "surf_g = ParametricGeometry(func=f, slices=16, stacks=16);\n", "\n", "surf = Mesh(geometry=surf_g, material=MeshLambertMaterial(color='green', side='FrontSide'))\n", "surf2 = Mesh(geometry=surf_g, material=MeshLambertMaterial(color='yellow', side='BackSide'))\n", "c = PerspectiveCamera(position=[5, 5, 3], up=[0, 0, 1],\n", " children=[DirectionalLight(color='white',\n", " position=[3, 5, 1],\n", " intensity=0.6)])\n", "scene = Scene(children=[surf, surf2, c, AmbientLight(intensity=0.5)])\n", "renderer = Renderer(camera=c, scene=scene, controls=[OrbitControls(controlling=c)], width=400, height=400)\n", "display(renderer)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Indexed Geometries\n", "\n", "The PlainGeometry lets you specify vertices and faces for a surface." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pythreejs import *\n", "from IPython.display import display\n", "\n", "vertices = [\n", " [0, 0, 0],\n", " [0, 0, 1],\n", " [0, 1, 0],\n", " [0, 1, 1],\n", " [1, 0, 0],\n", " [1, 0, 1],\n", " [1, 1, 0],\n", " [1, 1, 1]\n", "]\n", "\n", "faces = [\n", " [0, 1, 3],\n", " [0, 3, 2],\n", " [0, 2, 4],\n", " [2, 6, 4],\n", " [0, 4, 1],\n", " [1, 4, 5],\n", " [2, 3, 6],\n", " [3, 7, 6],\n", " [1, 5, 3],\n", " [3, 5, 7],\n", " [4, 6, 5],\n", " [5, 6, 7]\n", "]\n", "\n", "vertexcolors = ['#000000', '#0000ff', '#00ff00', '#ff0000',\n", " '#00ffff', '#ff00ff', '#ffff00', '#ffffff']\n", "\n", "# Map the vertex colors into the 'color' slot of the faces\n", "faces = [f + [None, [vertexcolors[i] for i in f], None] for f in faces]\n", "\n", "# Create the geometry:\n", "cubeGeometry = Geometry(vertices=vertices,\n", " faces=faces,\n", " colors=vertexcolors)\n", "# Calculate normals per face, for nice crisp edges:\n", "cubeGeometry.exec_three_obj_method('computeFaceNormals')\n", "\n", "# Create a mesh. Note that the material need to be told to use the vertex colors.\n", "myobjectCube = Mesh(\n", " geometry=cubeGeometry,\n", " material=MeshLambertMaterial(vertexColors='VertexColors'),\n", " position=[-0.5, -0.5, -0.5], # Center the cube\n", ")\n", "\n", "# Set up a scene and render it:\n", "cCube = PerspectiveCamera(position=[3, 3, 3], fov=20,\n", " children=[DirectionalLight(color='#ffffff', position=[-3, 5, 1], intensity=0.5)])\n", "sceneCube = Scene(children=[myobjectCube, cCube, AmbientLight(color='#dddddd')])\n", "\n", "rendererCube = Renderer(camera=cCube, background='black', background_opacity=1,\n", " scene=sceneCube, controls=[OrbitControls(controlling=cCube)])\n", "\n", "display(rendererCube)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Buffer Geometries\n", "\n", "The PlainBufferGeometry object uses several tricks to speed up both the transfer of data and the rendering of the data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pythreejs import *\n", "import numpy as np\n", "from IPython.display import display\n", "\n", "vertices = np.asarray([\n", " [0, 0, 0],\n", " [0, 0, 1],\n", " [0, 1, 0],\n", " [0, 1, 1],\n", " [1, 0, 0],\n", " [1, 0, 1],\n", " [1, 1, 0],\n", " [1, 1, 1]\n", "], dtype='float32')\n", "\n", "faces = np.asarray([\n", " [0, 1, 3],\n", " [0, 3, 2],\n", " [0, 2, 4],\n", " [2, 6, 4],\n", " [0, 4, 1],\n", " [1, 4, 5],\n", " [2, 3, 6],\n", " [3, 7, 6],\n", " [1, 5, 3],\n", " [3, 5, 7],\n", " [4, 6, 5],\n", " [5, 6, 7]\n", "], dtype='uint16').ravel() # We need to flatten index array\n", "\n", "\n", "vertexcolors = np.asarray([(0,0,0), (0,0,1), (0,1,0), (1,0,0),\n", " (0,1,1), (1,0,1), (1,1,0), (1,1,1)], dtype='float32')\n", "\n", "cubeGeometry = BufferGeometry(attributes=dict(\n", " position=BufferAttribute(vertices, normalized=False),\n", " index=BufferAttribute(faces, normalized=False),\n", " color=BufferAttribute(vertexcolors),\n", "))\n", "\n", "myobjectCube = Mesh(\n", " geometry=cubeGeometry,\n", " material=MeshLambertMaterial(vertexColors='VertexColors'),\n", " position=[-0.5, -0.5, -0.5] # Center the cube\n", ")\n", "cCube = PerspectiveCamera(\n", " position=[3, 3, 3], fov=20,\n", " children=[DirectionalLight(color='#ffffff', position=[-3, 5, 1], intensity=0.5)])\n", "sceneCube = Scene(children=[myobjectCube, cCube, AmbientLight(color='#dddddd')])\n", "\n", "rendererCube = Renderer(camera=cCube, background='black', background_opacity=1,\n", " scene = sceneCube, controls=[OrbitControls(controlling=cCube)])\n", "\n", "display(rendererCube)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that there are no face normals logic for buffer geometries, as the attributes are *vertex* attributes. If you want to add sharp edges for a BufferGeometry, you then have to duplicate the vertices (i.e., don't use an index attribute), and calculate the normals yourself." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Examples to do\n", "\n", "- image texture (with webcam picture!)\n", "- scaled object: point that doesn't change size\n", "- vertex shader\n", "- switch between phong, lambert, depth, and wireframe materials, normalmaterial" ] } ], "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.5.4" } }, "nbformat": 4, "nbformat_minor": 2 }