diff --git a/RELEASE.md b/RELEASE.md index 874d52ed..a1b58bd3 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,8 +3,10 @@ git add and git commit rm -rf build rm -rf dist -python setup.py sdist upload -python setup.py bdist_wheel upload +pip install -ve . +python setup.py sdist +python setup.py bdist_wheel +twine upload dist/* cd js grunt build npm publish diff --git a/build.cmd b/build.cmd index 44cad20c..4d8023c0 100644 --- a/build.cmd +++ b/build.cmd @@ -1,5 +1,5 @@ pip install -ve . jupyter nbextension install --py --user k3d jupyter nbextension enable --py --user k3d -python setup.py sdist -python setup.py bdist_wheel \ No newline at end of file +@REM python setup.py sdist +@REM python setup.py bdist_wheel \ No newline at end of file diff --git a/docs/source/basic_functionality/Camera.rst b/docs/source/basic_functionality/Camera.rst index c6f0c5c7..1aafb32c 100644 --- a/docs/source/basic_functionality/Camera.rst +++ b/docs/source/basic_functionality/Camera.rst @@ -1,7 +1,7 @@ Camera ====== -Camera is 9-th vector: +The camera state is determined by a 9-th degree vector: .. code:: @@ -12,8 +12,8 @@ Camera is 9-th vector: ] -Is is synchronized between frontend and backend automatically. -Below there is an example of camera manipulation in Python backend. +It is synchronized between the frontend and backend automatically. +There is an example below of camera manipulation using the Python backend. .. code:: @@ -31,7 +31,7 @@ Below there is an example of camera manipulation in Python backend. .. k3d_plot :: :filename: camera/camera01.py -Look at bigger icosahedron from above (z>0) and first quarter of xy plane: +Look at the bigger icosahedron from above (z>0) and from the first quarter of xy plane: .. code:: @@ -42,7 +42,7 @@ Look at bigger icosahedron from above (z>0) and first quarter of xy plane: .. k3d_plot :: :filename: camera/camera02.py -Look at smaller icosahedron from above (z>0) +Look at the smaller icosahedron from above (z>0) .. code:: @@ -53,7 +53,7 @@ Look at smaller icosahedron from above (z>0) .. k3d_plot :: :filename: camera/camera03.py -Look at larger icosahedron from a point above its center orienting camera to have y-axis up. +Look at the larger icosahedron from a point above its center, orienting camera to have y-axis up. .. code:: diff --git a/docs/source/basic_functionality/Implicit_plot.rst b/docs/source/basic_functionality/Implicit_plot.rst index 916f017e..acde18d1 100644 --- a/docs/source/basic_functionality/Implicit_plot.rst +++ b/docs/source/basic_functionality/Implicit_plot.rst @@ -6,10 +6,10 @@ In this case a function of three variables is sampled on 3d equidistant grid and an `k3d.marching_cubes` object which will do the visualization. Note that: - - data exchanged between frontend and backend is big - - browser javascript does mesh computation + - the amount of data exchanged between the frontend and backend is big + - the browser javascript does the mesh computations - `level` is a single scalar parameter which can be passed to the frontend for data exploration - - it is possible to use [jslink](https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#Linking-widgets-attributes-from-the-client-side) for interaction without a Python kernel]. + - it is possible to use `jslink `_ for interaction without a Python kernel. .. code:: @@ -37,7 +37,6 @@ Note that: zmin=zmin, zmax=zmax, level=0.0, flat_shading=False) plot += plt_iso - plot += plt_iso plot.display() .. k3d_plot :: diff --git a/docs/source/basic_functionality/Interaction_and_timeseries.rst b/docs/source/basic_functionality/Interaction_and_timeseries.rst index 7a74c622..6503e579 100644 --- a/docs/source/basic_functionality/Interaction_and_timeseries.rst +++ b/docs/source/basic_functionality/Interaction_and_timeseries.rst @@ -21,7 +21,7 @@ There are two ways of changing data in the plot: .. k3d_plot :: :filename: interaction/Interaction01.py -Using backend to send a data at each timestep +Using the backend to send data at each timestep --------------------------------------------- The Python backend can update attribute of any plot object in K3D-jupyter. @@ -36,7 +36,7 @@ The Python backend can update attribute of any plot object in K3D-jupyter. Sending a dictionary of all timesteps ------------------------------------- -In this case it is possible to play an animation using only frontend. +In this case it is possible to play an animation using only the frontend. Time is a string denoting wall time. .. code:: @@ -46,7 +46,7 @@ Time is a string denoting wall time. .. k3d_plot :: :filename: interaction/Interaction02.py -The animation can be controlled from GUI or by several attributes: +The animation can be controlled from the GUI or by several attributes: .. code:: @@ -58,7 +58,7 @@ The number of frames which are played can be inspected or set with plot.fps attr plot.fps -One can programatically change or read the time in the animation using: +One can programmatically change or read the time in the animation using: .. code:: diff --git a/docs/source/basic_functionality/Line.rst b/docs/source/basic_functionality/Line.rst index 4c8573c7..268dec01 100644 --- a/docs/source/basic_functionality/Line.rst +++ b/docs/source/basic_functionality/Line.rst @@ -1,10 +1,9 @@ Line ==== -Let us draw a trajectory of `N` steps of an approximation to the -[Wiener Process](https://en.wikipedia.org/wiki/Wiener_process) in three dimensions. +Let us draw a trajectory of `N` steps of an approximation to the `Wiener Process `_ in three dimensions. -Below, a blue thin line is a trajectory and the total displacement is shown with red thick line. +Below, a blue thin line is a trajectory and the total displacement is shown with a red thick line. Line takes data as an array of coordinates `[number_of_points,3]`. diff --git a/docs/source/basic_functionality/Mesh.rst b/docs/source/basic_functionality/Mesh.rst index ce51ee23..d5c4f3fa 100644 --- a/docs/source/basic_functionality/Mesh.rst +++ b/docs/source/basic_functionality/Mesh.rst @@ -1,7 +1,7 @@ Mesh ==== -Mesh is an object which displays triangles in 3d. An scalar can be displayed on the mesh +Mesh is an object that displays triangles in 3d. A scalar can be displayed on the mesh using color map. .. code:: @@ -46,7 +46,7 @@ Scalars can be updated interactively using ipywidgets communication: .. k3d_plot :: :filename: mesh/Mesh02.py -It is possible to send time series of an attribute values: +It is possible to send a time series consisting of attribute values: .. code:: diff --git a/docs/source/basic_functionality/Points.rst b/docs/source/basic_functionality/Points.rst index a7144b54..7abc1f4d 100644 --- a/docs/source/basic_functionality/Points.rst +++ b/docs/source/basic_functionality/Points.rst @@ -1,7 +1,7 @@ Points ====== -To draw points one needs to prepare data in the array of coordinates `[number_of_points, 3]`. +To draw points one needs to prepare data as an array of coordinates `[number_of_points, 3]`. Colors for all the points can either be the same or have an individual value (`colors` attribute). When the number of points is larger than $10^3$ it is recommended to use fast shaders: `flat`, ` diff --git a/docs/source/basic_functionality/Surface.rst b/docs/source/basic_functionality/Surface.rst index 861addb1..0f91c04a 100644 --- a/docs/source/basic_functionality/Surface.rst +++ b/docs/source/basic_functionality/Surface.rst @@ -1,11 +1,11 @@ Surface ======= -`k3d.surface` is an easy way to produce plot of an explicit function of two variables +`k3d.surface` is an easy way to produce a plot of an explicit function of two variables f(x,y) on a rectangular domain. It takes a table of values, i.e. `f(x_i,y_i) = f[j,i]`. Proper scaling of x and y -axes can by done by specifying `xmin/xmax` parameters. +axes can be done by specifying `xmin/xmax` parameters. .. code:: diff --git a/docs/source/basic_plotting.rst b/docs/source/basic_plotting.rst index 2590d616..9ca17b39 100644 --- a/docs/source/basic_plotting.rst +++ b/docs/source/basic_plotting.rst @@ -1,4 +1,4 @@ -Basic plottting +Basic plottting =============== Creating and displaying a plot @@ -15,10 +15,10 @@ To show the plot below a Jupyter cell, we call its ``display()`` method. import k3d plot = k3d.plot() - + # here you would normally create objects to display # and add them to the plot - + plot.display() If K3D-jupyter is installed properly, after executing the above snippet you @@ -47,7 +47,7 @@ In the next example we will learn how to display objects on the plot. .. code:: ipython3 import k3d - + k3d.points([0, 0, 0]) This is, however not a good practice, because a ``Plot`` object is created @@ -60,7 +60,7 @@ Adding objects to plot ---------------------- The main idea is that plot objects (e.g. lines, meshed wtc) are -interactively added to the plot. +interactively added to the plot. To draw the triangle we will use the ``mesh()`` method from the ``k3d`` module. This method creates a ``Mesh`` object, which can be added to a @@ -70,15 +70,15 @@ K3D ``Plot``. import k3d plot = k3d.plot() - - + + vertices = [[0, 0, 0], [1, 0, 0], [0, 0, 1]] indices = [[0, 1, 2]] - + mesh = k3d.mesh(vertices, indices) - + plot += mesh - + plot.display() .. k3d_plot :: @@ -105,14 +105,14 @@ variables: .. code:: ipython3 - plot += k3d.mesh([0, 1, 1, - 1, 1, 0, + plot += k3d.mesh([0, 1, 1, + 1, 1, 0, 1, 1, 1, - + 1, 2, 2, 1, 1, 1, 2, 1, 1], [0, 1, 2, 3, 4, 5], color=0x00ff00) - + plot .. k3d_plot :: @@ -126,7 +126,7 @@ variables: :figclass: align-center One blue and two green triangles - + This is a plot of two meshes. Please note – in the second case we didn’t @@ -160,7 +160,7 @@ place, where having named our objects beforehand comes in handy: Having variables is also convenient when we want to modify the objects -already shown. +already shown. @@ -187,8 +187,8 @@ with wheel / both mose buttons: zooms in or out (only vertical) To return to the default camera position, press the “Camera reset” icon from the top-right toolbar -Fullscreen mode and detachted mode -++++++++++++++++++++++++++++++++++ +Fullscreen mode and detached mode ++++++++++++++++++++++++++++++++++ It is possible to switch the plot to fullscreen mode using the “Fullscreen” icon from the toolbar. To exit fullscreen mode press the @@ -200,21 +200,21 @@ widget” icon. .. _snapshots: - + Screenshots and snapshots ------------------------- -To save a screenshot of the current view, press the “Save screenshot” -icon from the toolbar. It provides better resolution, which can be -controlled by `plot.screenshot_scale` parameter. +To save a screenshot of the current view, press the “Screenshot” +button in the Controls section of the toolbar. It provides better resolution, which can be +controlled by the `plot.screenshot_scale` parameter. The filename will be generated as “K3D-”, then a string of digits (technically: decimal timestamp) and then “.png”. .. note: If the `plot.name` is set it will be used as a name of the screenshot. - -Screenshots can be made programatically by: + +Screenshots can be made programmatically by: .. code:: ipython3 @@ -222,27 +222,27 @@ Screenshots can be made programatically by: The ".png" file is contained in the `plot.screenshot` attribute, however its synchronization might be a little bit delayed (it relies -on asynchronous traitlets mechanism internally) +on asynchronous traitlets mechanism internally). + +Snapshot is a "live" version of a screen in the form of standalone +html file. Similarly to snapshots, it can be done programmatically via: -Snapshot is a "live" version of a screne in the form of stand-alone -html file. Similarily to snapshots it can be done programatically via: - - on the javascript side `plot.fetch_snapshot()`, note that fetching - might take some time, and `plot.snapshot` + might take some time, and `plot.snapshot` won't be set until it finishes - on the python side `plot.get_snapshot()` - + In this case one has to write HTML code to a file: - + .. code:: - + with open('../_static/points.html','w') as fp: fp.write(plot.snapshot) - + diff --git a/examples/custom_data.ipynb b/examples/custom_data.ipynb new file mode 100644 index 00000000..d2e70e75 --- /dev/null +++ b/examples/custom_data.ipynb @@ -0,0 +1,81 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "14ce67c3", + "metadata": {}, + "outputs": [], + "source": [ + "import k3d\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a26ad80", + "metadata": {}, + "outputs": [], + "source": [ + "N = 1000\n", + "line = k3d.line(np.cumsum(np.random.randn(N,3).astype(np.float32),axis=0), \n", + " shader='mesh',\n", + " width = 0.2,\n", + " custom_data = {\"test\": 123})\n", + "\n", + "plot = k3d.plot(custom_data={\"test\": 321})\n", + "plot += line\n", + "plot.display()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e63e0bc1", + "metadata": {}, + "outputs": [], + "source": [ + "code = [\n", + " \"console.log(K3DInstance.getWorld().ObjectsListJson[%s].custom_data);\" % (line.id),\n", + " \"console.log(K3DInstance.parameters.customData);\"\n", + "]\n", + "with open(\"./custom_data.html\", \"w\") as f:\n", + " f.write(\n", + " plot.get_snapshot(\n", + " additional_js_code=\"\\n\".join(code)\n", + " )\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99eae867", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/lines.ipynb b/examples/lines.ipynb new file mode 100644 index 00000000..bb192c67 --- /dev/null +++ b/examples/lines.ipynb @@ -0,0 +1,84 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "9a3b909b", + "metadata": {}, + "outputs": [], + "source": [ + "import k3d\n", + "import numpy as np\n", + "\n", + "N = 100\n", + "\n", + "theta = np.linspace(0, 2.0 * np.pi, N)\n", + "phi = np.linspace(0, 2.0 * np.pi, N)\n", + "theta, phi = np.meshgrid(theta, phi)\n", + "\n", + "c, a = 2, 1\n", + "x = (c + a * np.cos(theta)) * np.cos(phi)\n", + "y = (c + a * np.cos(theta)) * np.sin(phi)\n", + "z = a * np.sin(theta)\n", + "\n", + "vertices = np.dstack([x, y, z]).astype(np.float32)\n", + "indices = (np.stack([\n", + " np.arange(N*N - N - 1) + 0, np.arange(N*N - N - 1) + N, np.arange(N*N - N - 1) + N + 1,\n", + " np.arange(N*N - N - 1) + 0, np.arange(N*N - N - 1) + N + 1, np.arange(N*N - N - 1) + 1\n", + "]).T).astype(np.uint32)\n", + "\n", + "plot = k3d.plot()\n", + "\n", + "points = k3d.points(vertices, point_size=0.05, shader='3d', color=0)\n", + "\n", + "\n", + "mesh = k3d.mesh(vertices, indices, flat_shading=False, \n", + " shader='mesh',\n", + " attribute=phi, \n", + " color_map=k3d.matplotlib_color_maps.twilight,\n", + " color=0x00ff00)\n", + "\n", + "wire = k3d.lines(vertices, indices, flat_shading=False, \n", + " shader='thick', width=0.003,\n", + " indices_type='triangle',\n", + " color=0)\n", + "\n", + "\n", + "plot += mesh\n", + "plot += wire\n", + "plot += points\n", + "\n", + "plot.display()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6c7ada5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/mesh_custom.ipynb b/examples/mesh_custom.ipynb index 126ff482..19ef5081 100644 --- a/examples/mesh_custom.ipynb +++ b/examples/mesh_custom.ipynb @@ -22,22 +22,43 @@ "\n", "vertices = np.dstack([x, y, z]).astype(np.float32)\n", "indices = (np.stack([\n", - " np.arange(N*N) + 0, np.arange(N*N) + N, np.arange(N*N) + N + 1,\n", - " np.arange(N*N) + 0, np.arange(N*N) + N + 1, np.arange(N*N) + 1\n", - "]).T % (N * N)).astype(np.uint32)\n", + " np.arange(N*N - N - 1) + 0, np.arange(N*N - N - 1) + N, np.arange(N*N - N - 1) + N + 1,\n", + " np.arange(N*N - N - 1) + 0, np.arange(N*N - N - 1) + N + 1, np.arange(N*N - N - 1) + 1\n", + "]).T).astype(np.uint32)\n", "\n", "plot = k3d.plot()\n", - "plot += k3d.points(vertices, point_size=0.05, shader='3d', color=0)\n", + "\n", + "points = k3d.points(vertices, point_size=0.05, shader='3d', color=0)\n", + "\n", "\n", "mesh = k3d.mesh(vertices, indices, flat_shading=False, \n", - " attribute=phi,\n", - " color_map=k3d.matplotlib_color_maps.twilight)\n", + " shader='mesh',\n", + " attribute=phi, \n", + " color_map=k3d.matplotlib_color_maps.twilight,\n", + " color=0x00ff00)\n", + "\n", + "wire = k3d.lines(vertices, indices, flat_shading=False, \n", + " shader='mesh', width=0.003,\n", + " indices_type='segment',\n", + " color=0)\n", + "\n", "\n", "plot += mesh\n", + "plot += wire\n", + "plot += points\n", "\n", "plot.display()" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plot.camera_reset(0.8)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -66,7 +87,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -80,7 +101,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.8.12" } }, "nbformat": 4, diff --git a/examples/platonic.ipynb b/examples/platonic.ipynb index b28d14fe..533acd60 100644 --- a/examples/platonic.ipynb +++ b/examples/platonic.ipynb @@ -27,22 +27,67 @@ " radius = 3.5\n", " obj.transform.translation = [math.sin(rad) * radius, math.cos(rad) * radius, 0]\n", " obj.color = colors[i]\n", - " plot += obj\n", + " plot += obj \n", "\n", "plot.display()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Platonic with custom menu" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "import k3d\n", + "import k3d.platonic as platonic\n", + "import math\n", + "from ipywidgets import widgets, link\n", + "\n", + "plot2 = k3d.plot(menu_visibility=False)\n", + "\n", + "meshes = [\n", + " platonic.Dodecahedron().mesh,\n", + " platonic.Cube().mesh,\n", + " platonic.Icosahedron().mesh,\n", + " platonic.Octahedron().mesh,\n", + " platonic.Tetrahedron().mesh\n", + "]\n", + "\n", + "colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff]\n", + "\n", + "menu = []\n", + "\n", + "for i, obj in enumerate(meshes): \n", + " rad = math.radians(i / len(meshes) * 360)\n", + " radius = 3.5\n", + " obj.transform.translation = [math.sin(rad) * radius, math.cos(rad) * radius, 0]\n", + " obj.color = colors[i]\n", + " plot2 += obj\n", + " \n", + " w = widgets.Checkbox(\n", + " value=True,\n", + " description='Object #' + str(i),\n", + " disabled=False,\n", + " indent=False\n", + " )\n", + " link((w, 'value'), (obj, 'visible'))\n", + " menu.append(w)\n", + "\n", + "display(widgets.HBox(menu))\n", + "plot2.display()" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -56,7 +101,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.8.12" } }, "nbformat": 4, diff --git a/examples/snapshots_in_js.ipynb b/examples/snapshots_in_js.ipynb new file mode 100644 index 00000000..2ee94fa7 --- /dev/null +++ b/examples/snapshots_in_js.ipynb @@ -0,0 +1,165 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import k3d\n", + "import base64" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Produce a plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "iteration = 4\n", + "size = 3**iteration\n", + "\n", + "voxels = np.ones((size, size, size));\n", + "\n", + "def iterate(length, x, y, z):\n", + "\n", + " nl = length // 3 \n", + "\n", + " if nl < 1:\n", + " return\n", + "\n", + " margin = (nl-1) // 2\n", + "\n", + " voxels[z-margin:z+margin+1, y-margin:y+margin+1, :] = 0\n", + " voxels[z-margin:z+margin+1, :, x-margin:x+margin+1] = 0\n", + " voxels[:, y-margin:y+margin+1, x-margin:x+margin+1] = 0 \n", + "\n", + " for ix,iy,iz in np.ndindex((3,3,3)):\n", + " if (1 if ix !=1 else 0) + (1 if iy != 1 else 0) + (1 if iz != 1 else 0) !=2:\n", + " iterate(nl, x + (ix-1) * nl, y + (iy-1) * nl , z + (iz-1) * nl)\n", + "\n", + "iterate(size, size//2, size//2, size//2)\n", + "\n", + "plot = k3d.plot()\n", + "plot += k3d.voxels(voxels.astype(np.uint8), compression_level=9)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create and save to *.k3d file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "template = \"\"\"\n", + "\n", + "\n", + "\n", + " \n", + " K3D in js\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "
\n", + "\n", + "\n", + "\n", + "\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = plot.get_binary_snapshot()\n", + "\n", + "with open(\"k3d_in_javascript.html\", \"w\") as f: \n", + " # base64 is only for embeding data in html normally recommeded way is to load data via ajax\n", + " f.write(template.replace(\"[DATA]\", base64.b64encode(data).decode(\"utf-8\")))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/js/package.json b/js/package.json index 0e1c2c7d..fa902b07 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "k3d", - "version": "2.12.0", + "version": "2.13.0", "description": "3D visualization library", "author": "k3d team", "main": "src/index.js", @@ -68,7 +68,7 @@ "screenfull": "^5.1.0", "stats.js": "^0.17.0", "style-loader": "^2.0.0", - "three": "^0.137.4", + "three": "^0.138.2", "three-mesh-bvh": "^0.3.7" } } diff --git a/js/src/core/Core.js b/js/src/core/Core.js index 8419232c..58109b5a 100644 --- a/js/src/core/Core.js +++ b/js/src/core/Core.js @@ -296,7 +296,9 @@ function K3D(provider, targetDOMNode, parameters) { cameraAnimation: {}, autoRendering: true, axesHelper: 1.0, + axesHelperColors: [0xff0000, 0x00ff00, 0x0000ff], snapshotType: 'full', + customData: null, guiVersion: require('../../package.json').version, }, parameters || {}, @@ -391,7 +393,7 @@ function K3D(provider, targetDOMNode, parameters) { this.getFullscreen = function () { return fullscreen.screenfull.isFullscreen; - } + }; this.setDirectionalLightingIntensity = function (value) { self.parameters.lighting = Math.min(Math.max(value, 0.0), 4.0); @@ -626,6 +628,19 @@ function K3D(provider, targetDOMNode, parameters) { }); }; + /** + * Set axes helper of plot + * @memberof K3D.Core + * @param {Number} size + */ + this.setAxesHelperColors = function (colors) { + self.parameters.axesHelperColors = colors; + + self.rebuildSceneData(true).then(() => { + self.render(); + }); + }; + /** * Set grid auto fit mode of K3D * @memberof K3D.Core diff --git a/js/src/core/lib/helpers/serialize.js b/js/src/core/lib/helpers/serialize.js index e2a0f312..4acdfb30 100644 --- a/js/src/core/lib/helpers/serialize.js +++ b/js/src/core/lib/helpers/serialize.js @@ -105,8 +105,7 @@ function serialize(obj) { } if (obj !== null) { - if (typeof (obj.data) !== 'undefined' && typeof (obj.shape) !== 'undefined' - && typeof (obj.data) !== 'undefined') { + if (typeof (obj.data) !== 'undefined' && typeof (obj.shape) !== 'undefined') { // plain data return serializeArray(obj); } diff --git a/js/src/core/lib/objectsGUIprovider.js b/js/src/core/lib/objectsGUIprovider.js index 45dde068..ec1b062c 100644 --- a/js/src/core/lib/objectsGUIprovider.js +++ b/js/src/core/lib/objectsGUIprovider.js @@ -20,7 +20,7 @@ function update(K3D, json, GUI, changes) { function moveToGroup(json) { let parent; - if (json.group !== null) { + if (json.group != null) { if (typeof (K3D.gui_groups[json.group]) === 'undefined') { K3D.gui_groups[json.group] = GUI.addFolder(`${json.group}`).close(); } @@ -54,7 +54,7 @@ function update(K3D, json, GUI, changes) { K3D.gui_groups[group].destroy(); delete K3D.gui_groups[group]; } - }) + }); } function addController(folder, obj, param, options1, options2, options3) { @@ -282,6 +282,13 @@ function update(K3D, json, GUI, changes) { changeParameter.bind(this, K3D, json, param), ); } + + if (json.type === 'Lines' || json.type === 'Line') { + addController(K3D.gui_map[json.id], json, param, + ['simple', 'thick', 'mesh']).onChange( + changeParameter.bind(this, K3D, json, param), + ); + } break; case 'mode': if (json.type === 'Label') { @@ -311,18 +318,16 @@ function update(K3D, json, GUI, changes) { } break; case 'width': - if (json.type === 'Line' && json.shader === 'mesh') { + if ((json.type === 'Line' || json.type === 'Lines') && json.shader === 'mesh') { addController(K3D.gui_map[json.id], json, param).onChange( changeParameter.bind(this, K3D, json, param), ); } break; case 'radial_segments': - if (json.shader === 'mesh') { - addController(K3D.gui_map[json.id], json, param, 0, 64, 1).name('radialSeg').onChange( - changeParameter.bind(this, K3D, json, param), - ); - } + addController(K3D.gui_map[json.id], json, param, 0, 64, 1).name('radialSeg').onChange( + changeParameter.bind(this, K3D, json, param), + ); break; case 'mesh_detail': if (json.shader === 'mesh') { diff --git a/js/src/extension.js b/js/src/extension.js index 76044116..1d6793de 100644 --- a/js/src/extension.js +++ b/js/src/extension.js @@ -19,7 +19,7 @@ window.__webpack_public_path__ = `${document.querySelector('body').getAttribute( require('katex/dist/katex.min.css'); require('lil-gui/dist/lil-gui.css'); -// Export the required load_ipython_extention +// Export the required load_ipython_extension module.exports = { load_ipython_extension() { }, diff --git a/js/src/k3d.js b/js/src/k3d.js index 196a3b12..3ba008cf 100644 --- a/js/src/k3d.js +++ b/js/src/k3d.js @@ -224,19 +224,19 @@ const PlotView = widgets.DOMWidgetView.extend({ this.model.lastCameraSync = (new Date()).getTime(); this.model.on('msg:custom', function (obj) { - const {model} = this; + const { model } = this; if (obj.msg_type === 'fetch_screenshot') { this.K3DInstance.getScreenshot(this.K3DInstance.parameters.screenshotScale, obj.only_canvas) .then((canvas) => { const data = canvas.toDataURL().split(',')[1]; - model.save('screenshot', data, {patch: true}); + model.save('screenshot', data, { patch: true }); }); } if (obj.msg_type === 'fetch_snapshot') { - model.save('snapshot', this.K3DInstance.getHTMLSnapshot(obj.compression_level), {patch: true}); + model.save('snapshot', this.K3DInstance.getHTMLSnapshot(obj.compression_level), { patch: true }); } if (obj.msg_type === 'start_auto_play') { @@ -298,6 +298,7 @@ const PlotView = widgets.DOMWidgetView.extend({ this.model.on('change:camera_fov', this._setCameraFOV, this); this.model.on('change:camera_damping_factor', this._setCameraDampingFactor, this); this.model.on('change:axes_helper', this._setAxesHelper, this); + this.model.on('change:axes_helper_colors', this._setAxesHelperColors, this); this.model.on('change:snapshot_type', this._setSnapshotType, this); this.model.on('change:name', this._setName, this); this.model.on('change:mode', this._setViewMode, this); @@ -353,14 +354,14 @@ const PlotView = widgets.DOMWidgetView.extend({ this._setVoxelPaintColor(); this.model.get('object_ids').forEach(function (id) { - this.renderPromises.push(this.K3DInstance.load({objects: [objectsList[id].attributes]})); + this.renderPromises.push(this.K3DInstance.load({ objects: [objectsList[id].attributes] })); }, this); this.cameraChangeId = this.K3DInstance.on(this.K3DInstance.events.CAMERA_CHANGE, (control) => { if (self.model._comm_live) { if ((new Date()).getTime() - self.model.lastCameraSync > 200) { self.model.lastCameraSync = (new Date()).getTime(); - self.model.save('camera', control, {patch: true}); + self.model.save('camera', control, { patch: true }); } } }); @@ -372,18 +373,18 @@ const PlotView = widgets.DOMWidgetView.extend({ } if (objectsList[change.id]) { - objectsList[change.id].save(change.key, change.value, {patch: true}); + objectsList[change.id].save(change.key, change.value, { patch: true }); } } }); this.GUIParametersChanges = this.K3DInstance.on(this.K3DInstance.events.PARAMETERS_CHANGE, (change) => { - self.model.save(change.key, change.value, {patch: true}); + self.model.save(change.key, change.value, { patch: true }); }); this.voxelsCallback = this.K3DInstance.on(this.K3DInstance.events.VOXELS_CALLBACK, (param) => { if (objectsList[param.object.K3DIdentifier]) { - objectsList[param.object.K3DIdentifier].send({msg_type: 'click_callback', coord: param.coord}); + objectsList[param.object.K3DIdentifier].send({ msg_type: 'click_callback', coord: param.coord }); } }); @@ -522,6 +523,10 @@ const PlotView = widgets.DOMWidgetView.extend({ this.K3DInstance.setAxesHelper(this.model.get('axes_helper')); }, + _setAxesHelperColors() { + this.K3DInstance.setAxesHelperColors(this.model.get('axes_helper_colors')); + }, + _setSnapshotType() { this.K3DInstance.setSnapshotType(this.model.get('snapshot_type')); }, @@ -563,7 +568,7 @@ const PlotView = widgets.DOMWidgetView.extend({ }, this); _.difference(newObjectId, oldObjectId).forEach(function (id) { - this.renderPromises.push(this.K3DInstance.load({objects: [objectsList[id].attributes]})); + this.renderPromises.push(this.K3DInstance.load({ objects: [objectsList[id].attributes] })); }, this); }, diff --git a/js/src/providers/threejs/helpers/THREE.TrackballControls.js b/js/src/providers/threejs/helpers/THREE.TrackballControls.js index 2b5511ad..01569832 100644 --- a/js/src/providers/threejs/helpers/THREE.TrackballControls.js +++ b/js/src/providers/threejs/helpers/THREE.TrackballControls.js @@ -118,10 +118,10 @@ module.exports = function (THREE) { const getMouseOnScreen = (function () { const vector = new THREE.Vector2(); - return function (pageX, pageY) { + return function (x, y) { vector.set( - (pageX - scope.screen.left) / scope.screen.width, - (pageY - scope.screen.top) / scope.screen.height, + x / scope.screen.width, + y / scope.screen.height, ); return vector; @@ -131,10 +131,10 @@ module.exports = function (THREE) { const getMouseOnCircle = (function () { const vector = new THREE.Vector2(); - return function (pageX, pageY) { + return function (x, y) { vector.set( - ((pageX - scope.screen.width * 0.5 - scope.screen.left) / (scope.screen.width * 0.5)), - ((scope.screen.height + 2 * (scope.screen.top - pageY)) / scope.screen.width), + ((x - scope.screen.width * 0.5) / (scope.screen.width * 0.5)), + ((scope.screen.height - 2 * y) / scope.screen.width), ); return vector; @@ -457,13 +457,13 @@ module.exports = function (THREE) { const state = (_keyState !== STATE.NONE) ? _keyState : _state; if (state === STATE.ROTATE && !scope.noRotate) { - _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); + _moveCurr.copy(getMouseOnCircle(event.offsetX, event.offsetY)); _movePrev.copy(_moveCurr); } else if (state === STATE.ZOOM && !scope.noZoom) { - _zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY)); + _zoomStart.copy(getMouseOnScreen(event.offsetX, event.offsetY)); _zoomEnd.copy(_zoomStart); } else if (state === STATE.PAN && !scope.noPan) { - _panStart.copy(getMouseOnScreen(event.pageX, event.pageY)); + _panStart.copy(getMouseOnScreen(event.offsetX, event.offsetY)); _panEnd.copy(_panStart); } @@ -477,11 +477,11 @@ module.exports = function (THREE) { if (state === STATE.ROTATE && !scope.noRotate) { _movePrev.copy(_moveCurr); - _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); + _moveCurr.copy(getMouseOnCircle(event.offsetX, event.offsetY)); } else if (state === STATE.ZOOM && !scope.noZoom) { - _zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); + _zoomEnd.copy(getMouseOnScreen(event.offsetX, event.offsetY)); } else if (state === STATE.PAN && !scope.noPan) { - _panEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); + _panEnd.copy(getMouseOnScreen(event.offsetX, event.offsetY)); } } @@ -552,18 +552,18 @@ module.exports = function (THREE) { break; case 1: _movePrev.copy(_moveCurr); - _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); + _moveCurr.copy(getMouseOnCircle(event.offsetX, event.offsetY)); break; default: // 2 or more const position = getSecondPointerPosition(event); - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; + const dx = event.offsetX - position.x; + const dy = event.offsetY - position.y; _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy); - const x = (event.pageX + position.x) / 2; - const y = (event.pageY + position.y) / 2; + const x = (event.offsetX + position.x) / 2; + const y = (event.offsetY + position.y) / 2; _panEnd.copy(getMouseOnScreen(x, y)); break; } @@ -577,13 +577,13 @@ module.exports = function (THREE) { case 1: _state = STATE.TOUCH_ROTATE; - _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); + _moveCurr.copy(getMouseOnCircle(event.offsetX, event.offsetY)); _movePrev.copy(_moveCurr); break; case 2: _state = STATE.TOUCH_ZOOM_PAN; - _moveCurr.copy(getMouseOnCircle(event.pageX - _movePrev.pageX, event.pageY - _movePrev.pageY)); + _moveCurr.copy(getMouseOnCircle(event.offsetX - _movePrev.pageX, event.offsetY - _movePrev.pageY)); _movePrev.copy(_moveCurr); break; } @@ -620,7 +620,7 @@ module.exports = function (THREE) { scope._pointerPositions[event.pointerId] = position; } - position.set(event.pageX, event.pageY); + position.set(event.offsetX, event.offsetY); } function getSecondPointerPosition(event) { diff --git a/js/src/providers/threejs/helpers/TransformControls.js b/js/src/providers/threejs/helpers/TransformControls.js index daaf89f7..bf0a5cf3 100644 --- a/js/src/providers/threejs/helpers/TransformControls.js +++ b/js/src/providers/threejs/helpers/TransformControls.js @@ -1,30 +1,104 @@ -// jshint ignore: start -// jscs:disable +import { + BoxGeometry, + BufferGeometry, + CylinderGeometry, + DoubleSide, + Euler, + Float32BufferAttribute, + Line, + LineBasicMaterial, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + OctahedronGeometry, + PlaneGeometry, + Quaternion, + Raycaster, + SphereGeometry, + TorusGeometry, + Vector3 +} from 'three'; + +const _raycaster = new Raycaster(); + +const _tempVector = new Vector3(); +const _tempVector2 = new Vector3(); +const _tempQuaternion = new Quaternion(); +const _unit = { + X: new Vector3(1, 0, 0), + Y: new Vector3(0, 1, 0), + Z: new Vector3(0, 0, 1) +}; + +const _changeEvent = { type: 'change' }; +const _mouseDownEvent = { type: 'mouseDown' }; +const _mouseUpEvent = { type: 'mouseUp', mode: null }; +const _objectChangeEvent = { type: 'objectChange' }; + +class TransformControls extends Object3D { -module.exports = function (THREE) { - /** - * @author arodic / https://github.com/arodic - */ + constructor(camera, domElement) { + + super(); - THREE.TransformControls = function (camera, domElement) { if (domElement === undefined) { + console.warn('THREE.TransformControls: The second parameter "domElement" is now mandatory.'); domElement = document; - } - THREE.Object3D.call(this); + } this.visible = false; this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll - const _gizmo = new THREE.TransformControlsGizmo(); + const _gizmo = new TransformControlsGizmo(); + this._gizmo = _gizmo; this.add(_gizmo); - const _plane = new THREE.TransformControlsPlane(); + const _plane = new TransformControlsPlane(); + this._plane = _plane; this.add(_plane); const scope = this; + // Defined getter, setter and store for a property + function defineProperty(propName, defaultValue) { + + let propValue = defaultValue; + + Object.defineProperty(scope, propName, { + + get: function () { + + return propValue !== undefined ? propValue : defaultValue; + + }, + + set: function (value) { + + if (propValue !== value) { + + propValue = value; + _plane[propName] = value; + _gizmo[propName] = value; + + scope.dispatchEvent({ type: propName + '-changed', value: value }); + scope.dispatchEvent(_changeEvent); + + } + + } + + }); + + scope[propName] = defaultValue; + _plane[propName] = defaultValue; + _gizmo[propName] = defaultValue; + + } + // Define properties with getters/setter // Setting the defined property will automatically trigger change event // Defined properties are passed down to gizmo and plane @@ -44,55 +118,19 @@ module.exports = function (THREE) { defineProperty('showY', true); defineProperty('showZ', true); - const changeEvent = { type: 'change' }; - const mouseDownEvent = { type: 'mouseDown' }; - const mouseUpEvent = { type: 'mouseUp', mode: scope.mode }; - const objectChangeEvent = { type: 'objectChange' }; - // Reusable utility variables - const ray = new THREE.Raycaster(); - - const _tempVector = new THREE.Vector3(); - const _tempVector2 = new THREE.Vector3(); - const _tempQuaternion = new THREE.Quaternion(); - const _unit = { - X: new THREE.Vector3(1, 0, 0), - Y: new THREE.Vector3(0, 1, 0), - Z: new THREE.Vector3(0, 0, 1), - }; - - const pointStart = new THREE.Vector3(); - const pointEnd = new THREE.Vector3(); - const offset = new THREE.Vector3(); - const rotationAxis = new THREE.Vector3(); - const startNorm = new THREE.Vector3(); - const endNorm = new THREE.Vector3(); - let rotationAngle = 0; - - const cameraPosition = new THREE.Vector3(); - const cameraQuaternion = new THREE.Quaternion(); - const cameraScale = new THREE.Vector3(); - - const parentPosition = new THREE.Vector3(); - const parentQuaternion = new THREE.Quaternion(); - const parentQuaternionInv = new THREE.Quaternion(); - const parentScale = new THREE.Vector3(); - - const worldPositionStart = new THREE.Vector3(); - const worldQuaternionStart = new THREE.Quaternion(); - const worldScaleStart = new THREE.Vector3(); - - const worldPosition = new THREE.Vector3(); - const worldQuaternion = new THREE.Quaternion(); - const worldQuaternionInv = new THREE.Quaternion(); - const worldScale = new THREE.Vector3(); - - const eye = new THREE.Vector3(); - - const positionStart = new THREE.Vector3(); - const quaternionStart = new THREE.Quaternion(); - const scaleStart = new THREE.Vector3(); + const worldPosition = new Vector3(); + const worldPositionStart = new Vector3(); + const worldQuaternion = new Quaternion(); + const worldQuaternionStart = new Quaternion(); + const cameraPosition = new Vector3(); + const cameraQuaternion = new Quaternion(); + const pointStart = new Vector3(); + const pointEnd = new Vector3(); + const rotationAxis = new Vector3(); + const rotationAngle = 0; + const eye = new Vector3(); // TODO: remove properties unused in plane and gizmo @@ -108,487 +146,651 @@ module.exports = function (THREE) { defineProperty('rotationAngle', rotationAngle); defineProperty('eye', eye); - domElement.addEventListener('mousedown', onPointerDown, false); - domElement.addEventListener('touchstart', onPointerDown, false); - domElement.addEventListener('mousemove', onPointerHover, false); - domElement.addEventListener('touchmove', onPointerHover, false); - domElement.addEventListener('touchmove', onPointerMove, false); - document.addEventListener('mouseup', onPointerUp, false); - domElement.addEventListener('touchend', onPointerUp, false); - domElement.addEventListener('touchcancel', onPointerUp, false); - domElement.addEventListener('touchleave', onPointerUp, false); - - this.dispose = function () { - domElement.removeEventListener('mousedown', onPointerDown); - domElement.removeEventListener('touchstart', onPointerDown); - domElement.removeEventListener('mousemove', onPointerHover); - document.removeEventListener('mousemove', onPointerMove); - domElement.removeEventListener('touchmove', onPointerHover); - domElement.removeEventListener('touchmove', onPointerMove); - document.removeEventListener('mouseup', onPointerUp); - domElement.removeEventListener('touchend', onPointerUp); - domElement.removeEventListener('touchcancel', onPointerUp); - domElement.removeEventListener('touchleave', onPointerUp); - - this.traverse((child) => { - if (child.geometry) child.geometry.dispose(); - if (child.material) child.material.dispose(); - }); - }; + this._offset = new Vector3(); + this._startNorm = new Vector3(); + this._endNorm = new Vector3(); + this._cameraScale = new Vector3(); - // Set current object - this.attach = function (object) { - this.object = object; - this.visible = true; + this._parentPosition = new Vector3(); + this._parentQuaternion = new Quaternion(); + this._parentQuaternionInv = new Quaternion(); + this._parentScale = new Vector3(); - return this; - }; + this._worldScaleStart = new Vector3(); + this._worldQuaternionInv = new Quaternion(); + this._worldScale = new Vector3(); - // Detatch from object - this.detach = function () { - this.object = undefined; - this.visible = false; - this.axis = null; + this._positionStart = new Vector3(); + this._quaternionStart = new Quaternion(); + this._scaleStart = new Vector3(); - return this; - }; + this._getPointer = getPointer.bind(this); + this._onPointerDown = onPointerDown.bind(this); + this._onPointerHover = onPointerHover.bind(this); + this._onPointerMove = onPointerMove.bind(this); + this._onPointerUp = onPointerUp.bind(this); - // Defined getter, setter and store for a property - function defineProperty(propName, defaultValue) { - let propValue = defaultValue; + this.domElement.addEventListener('pointerdown', this._onPointerDown); + this.domElement.addEventListener('pointermove', this._onPointerHover); + this.domElement.addEventListener('pointerup', this._onPointerUp); - Object.defineProperty(scope, propName, { + } - get() { - return propValue !== undefined ? propValue : defaultValue; - }, + // updateMatrixWorld updates key transformation variables + updateMatrixWorld() { - set(value) { - if (propValue !== value) { - propValue = value; - _plane[propName] = value; - _gizmo[propName] = value; + if (this.object !== undefined) { - scope.dispatchEvent({ type: `${propName}-changed`, value }); - scope.dispatchEvent(changeEvent); - } - }, + this.object.updateMatrixWorld(); - }); + if (this.object.parent === null) { - scope[propName] = defaultValue; - _plane[propName] = defaultValue; - _gizmo[propName] = defaultValue; - } + console.error('TransformControls: The attached 3D object must be a part of the scene graph.'); - // updateMatrixWorld updates key transformation variables - this.updateMatrixWorld = function () { - if (this.object !== undefined) { - this.object.updateMatrixWorld(); - this.object.parent.matrixWorld.decompose(parentPosition, parentQuaternion, parentScale); - this.object.matrixWorld.decompose(worldPosition, worldQuaternion, worldScale); + } else { + + this.object.parent.matrixWorld.decompose(this._parentPosition, + this._parentQuaternion, + this._parentScale); - parentQuaternionInv.copy(parentQuaternion).inverse(); - worldQuaternionInv.copy(worldQuaternion).inverse(); } - this.camera.updateMatrixWorld(); - this.camera.matrixWorld.decompose(cameraPosition, cameraQuaternion, cameraScale); + this.object.matrixWorld.decompose(this.worldPosition, this.worldQuaternion, this._worldScale); - eye.copy(cameraPosition).sub(worldPosition).normalize(); + this._parentQuaternionInv.copy(this._parentQuaternion).invert(); + this._worldQuaternionInv.copy(this.worldQuaternion).invert(); - THREE.Object3D.prototype.updateMatrixWorld.call(this); - }; + } - this.pointerHover = function (pointer) { - if (this.object === undefined || this.dragging === true - || (pointer.button !== undefined && pointer.button !== 0)) return; + this.camera.updateMatrixWorld(); + this.camera.matrixWorld.decompose(this.cameraPosition, this.cameraQuaternion, this._cameraScale); - ray.setFromCamera(pointer, this.camera); + this.eye.copy(this.cameraPosition).sub(this.worldPosition).normalize(); - const intersect = ray.intersectObjects(_gizmo.picker[this.mode].children, true)[0] || false; + super.updateMatrixWorld(this); - if (intersect) { - this.axis = intersect.object.name; - } else { - this.axis = null; - } - }; + } - this.pointerDown = function (pointer) { - if (this.object === undefined || this.dragging === true - || (pointer.button !== undefined && pointer.button !== 0)) return; + pointerHover(pointer) { - if ((pointer.button === 0 || pointer.button === undefined) && this.axis !== null) { - ray.setFromCamera(pointer, this.camera); + if (this.object === undefined || this.dragging === true) return; - const planeIntersect = ray.intersectObjects([_plane], true)[0] || false; + _raycaster.setFromCamera(pointer, this.camera); - if (planeIntersect) { - let { space } = this; + const intersect = intersectObjectWithRay(this._gizmo.picker[this.mode], _raycaster); - if (this.mode === 'scale') { - space = 'local'; - } else if (this.axis === 'E' || this.axis === 'XYZE' || this.axis === 'XYZ') { - space = 'world'; - } + if (intersect) { - if (space === 'local' && this.mode === 'rotate') { - const snap = this.rotationSnap; + this.axis = intersect.object.name; - if (this.axis === 'X' && snap) { - this.object.rotation.x = Math.round(this.object.rotation.x / snap) * snap; - } - if (this.axis === 'Y' && snap) { - this.object.rotation.y = Math.round(this.object.rotation.y / snap) * snap; - } - if (this.axis === 'Z' && snap) { - this.object.rotation.z = Math.round(this.object.rotation.z / snap) * snap; - } - } + } else { - this.object.updateMatrixWorld(); - this.object.parent.updateMatrixWorld(); + this.axis = null; - positionStart.copy(this.object.position); - quaternionStart.copy(this.object.quaternion); - scaleStart.copy(this.object.scale); + } - this.object.matrixWorld.decompose(worldPositionStart, worldQuaternionStart, worldScaleStart); + } - pointStart.copy(planeIntersect.point).sub(worldPositionStart); - } + pointerDown(pointer) { - this.dragging = true; - mouseDownEvent.mode = this.mode; - this.dispatchEvent(mouseDownEvent); - } - }; + if (this.object === undefined || this.dragging === true || pointer.button !== 0) return; - this.pointerMove = function (pointer) { - const { axis } = this; - const { mode } = this; - const { object } = this; - let { space } = this; + if (this.axis !== null) { + + _raycaster.setFromCamera(pointer, this.camera); + + const planeIntersect = intersectObjectWithRay(this._plane, _raycaster, true); + + if (planeIntersect) { + + this.object.updateMatrixWorld(); + this.object.parent.updateMatrixWorld(); + + this._positionStart.copy(this.object.position); + this._quaternionStart.copy(this.object.quaternion); + this._scaleStart.copy(this.object.scale); + + this.object.matrixWorld.decompose(this.worldPositionStart, + this.worldQuaternionStart, + this._worldScaleStart); + + this.pointStart.copy(planeIntersect.point).sub(this.worldPositionStart); - if (mode === 'scale') { - space = 'local'; - } else if (axis === 'E' || axis === 'XYZE' || axis === 'XYZ') { - space = 'world'; } - if (object === undefined || axis === null || this.dragging === false - || (pointer.button !== undefined && pointer.button !== 0)) return; + this.dragging = true; + _mouseDownEvent.mode = this.mode; + this.dispatchEvent(_mouseDownEvent); - ray.setFromCamera(pointer, this.camera); + } - const planeIntersect = ray.intersectObjects([_plane], true)[0] || false; + } - if (planeIntersect === false) return; + pointerMove(pointer) { - pointEnd.copy(planeIntersect.point).sub(worldPositionStart); + const axis = this.axis; + const mode = this.mode; + const object = this.object; + let space = this.space; - if (mode === 'translate') { - // Apply translate + if (mode === 'scale') { - offset.copy(pointEnd).sub(pointStart); + space = 'local'; - if (space === 'local' && axis !== 'XYZ') { - offset.applyQuaternion(worldQuaternionInv); - } + } else if (axis === 'E' || axis === 'XYZE' || axis === 'XYZ') { - if (axis.indexOf('X') === -1) offset.x = 0; - if (axis.indexOf('Y') === -1) offset.y = 0; - if (axis.indexOf('Z') === -1) offset.z = 0; + space = 'world'; - if (space === 'local' && axis !== 'XYZ') { - offset.applyQuaternion(quaternionStart).divide(parentScale); - } else { - offset.applyQuaternion(parentQuaternionInv).divide(parentScale); - } + } - object.position.copy(offset).add(positionStart); + if (object === undefined || axis === null || this.dragging === false || pointer.button !== -1) return; - // Apply translation snap + _raycaster.setFromCamera(pointer, this.camera); - if (this.translationSnap) { - if (space === 'local') { - object.position.applyQuaternion(_tempQuaternion.copy(quaternionStart).inverse()); + const planeIntersect = intersectObjectWithRay(this._plane, _raycaster, true); - if (axis.search('X') !== -1) { - object.position.x = Math.round(object.position.x / this.translationSnap) - * this.translationSnap; - } + if (!planeIntersect) return; - if (axis.search('Y') !== -1) { - object.position.y = Math.round(object.position.y / this.translationSnap) - * this.translationSnap; - } + this.pointEnd.copy(planeIntersect.point).sub(this.worldPositionStart); - if (axis.search('Z') !== -1) { - object.position.z = Math.round(object.position.z / this.translationSnap) - * this.translationSnap; - } + if (mode === 'translate') { - object.position.applyQuaternion(quaternionStart); - } + // Apply translate - if (space === 'world') { - if (object.parent) { - object.position.add(_tempVector.setFromMatrixPosition(object.parent.matrixWorld)); - } + this._offset.copy(this.pointEnd).sub(this.pointStart); - if (axis.search('X') !== -1) { - object.position.x = Math.round(object.position.x / this.translationSnap) - * this.translationSnap; - } + if (space === 'local' && axis !== 'XYZ') { - if (axis.search('Y') !== -1) { - object.position.y = Math.round(object.position.y / this.translationSnap) - * this.translationSnap; - } + this._offset.applyQuaternion(this._worldQuaternionInv); - if (axis.search('Z') !== -1) { - object.position.z = Math.round(object.position.z / this.translationSnap) - * this.translationSnap; - } + } - if (object.parent) { - object.position.sub(_tempVector.setFromMatrixPosition(object.parent.matrixWorld)); - } - } - } - } else if (mode === 'scale') { - if (axis.search('XYZ') !== -1) { - let d = pointEnd.length() / pointStart.length(); + if (axis.indexOf('X') === -1) this._offset.x = 0; + if (axis.indexOf('Y') === -1) this._offset.y = 0; + if (axis.indexOf('Z') === -1) this._offset.z = 0; - if (pointEnd.dot(pointStart) < 0) d *= -1; + if (space === 'local' && axis !== 'XYZ') { - _tempVector2.set(d, d, d); - } else { - _tempVector.copy(pointStart); - _tempVector2.copy(pointEnd); + this._offset.applyQuaternion(this._quaternionStart).divide(this._parentScale); + + } else { + + this._offset.applyQuaternion(this._parentQuaternionInv).divide(this._parentScale); + + } + + object.position.copy(this._offset).add(this._positionStart); - _tempVector.applyQuaternion(worldQuaternionInv); - _tempVector2.applyQuaternion(worldQuaternionInv); + // Apply translation snap - _tempVector2.divide(_tempVector); + if (this.translationSnap) { + + if (space === 'local') { + + object.position.applyQuaternion(_tempQuaternion.copy(this._quaternionStart).invert()); + + if (axis.search('X') !== -1) { + + object.position.x = Math.round(object.position.x / this.translationSnap) * this.translationSnap; - if (axis.search('X') === -1) { - _tempVector2.x = 1; } - if (axis.search('Y') === -1) { - _tempVector2.y = 1; + + if (axis.search('Y') !== -1) { + + object.position.y = Math.round(object.position.y / this.translationSnap) * this.translationSnap; + } - if (axis.search('Z') === -1) { - _tempVector2.z = 1; + + if (axis.search('Z') !== -1) { + + object.position.z = Math.round(object.position.z / this.translationSnap) * this.translationSnap; + } + + object.position.applyQuaternion(this._quaternionStart); + } - // Apply scale + if (space === 'world') { + + if (object.parent) { - object.scale.copy(scaleStart).multiply(_tempVector2); + object.position.add(_tempVector.setFromMatrixPosition(object.parent.matrixWorld)); + + } - if (this.scaleSnap) { if (axis.search('X') !== -1) { - object.scale.x = Math.round(object.scale.x / this.scaleSnap) * this.scaleSnap || this.scaleSnap; + + object.position.x = Math.round(object.position.x / this.translationSnap) * this.translationSnap; + } if (axis.search('Y') !== -1) { - object.scale.y = Math.round(object.scale.y / this.scaleSnap) * this.scaleSnap || this.scaleSnap; + + object.position.y = Math.round(object.position.y / this.translationSnap) * this.translationSnap; + } if (axis.search('Z') !== -1) { - object.scale.z = Math.round(object.scale.z / this.scaleSnap) * this.scaleSnap || this.scaleSnap; + + object.position.z = Math.round(object.position.z / this.translationSnap) * this.translationSnap; + } + + if (object.parent) { + + object.position.sub(_tempVector.setFromMatrixPosition(object.parent.matrixWorld)); + + } + } - } else if (mode === 'rotate') { - offset.copy(pointEnd).sub(pointStart); - const ROTATION_SPEED = 20 / worldPosition.distanceTo( - _tempVector.setFromMatrixPosition(this.camera.matrixWorld), - ); + } - if (axis === 'E') { - rotationAxis.copy(eye); - rotationAngle = pointEnd.angleTo(pointStart); + } else if (mode === 'scale') { - startNorm.copy(pointStart).normalize(); - endNorm.copy(pointEnd).normalize(); + if (axis.search('XYZ') !== -1) { - rotationAngle *= (endNorm.cross(startNorm).dot(eye) < 0 ? 1 : -1); - } else if (axis === 'XYZE') { - rotationAxis.copy(offset).cross(eye).normalize(); - rotationAngle = offset.dot(_tempVector.copy(rotationAxis).cross(this.eye)) * ROTATION_SPEED; - } else if (axis === 'X' || axis === 'Y' || axis === 'Z') { - rotationAxis.copy(_unit[axis]); + let d = this.pointEnd.length() / this.pointStart.length(); - _tempVector.copy(_unit[axis]); + if (this.pointEnd.dot(this.pointStart) < 0) d *= -1; - if (space === 'local') { - _tempVector.applyQuaternion(worldQuaternion); - } + _tempVector2.set(d, d, d); + + } else { + + _tempVector.copy(this.pointStart); + _tempVector2.copy(this.pointEnd); + + _tempVector.applyQuaternion(this._worldQuaternionInv); + _tempVector2.applyQuaternion(this._worldQuaternionInv); + + _tempVector2.divide(_tempVector); + + if (axis.search('X') === -1) { + + _tempVector2.x = 1; + + } + + if (axis.search('Y') === -1) { + + _tempVector2.y = 1; + + } + + if (axis.search('Z') === -1) { + + _tempVector2.z = 1; + + } + + } + + // Apply scale + + object.scale.copy(this._scaleStart).multiply(_tempVector2); + + if (this.scaleSnap) { + + if (axis.search('X') !== -1) { + + object.scale.x = Math.round(object.scale.x / this.scaleSnap) * this.scaleSnap || this.scaleSnap; + + } + + if (axis.search('Y') !== -1) { + + object.scale.y = Math.round(object.scale.y / this.scaleSnap) * this.scaleSnap || this.scaleSnap; + + } + + if (axis.search('Z') !== -1) { + + object.scale.z = Math.round(object.scale.z / this.scaleSnap) * this.scaleSnap || this.scaleSnap; + + } + + } + + } else if (mode === 'rotate') { + + this._offset.copy(this.pointEnd).sub(this.pointStart); + + const ROTATION_SPEED = 20 / this.worldPosition.distanceTo(_tempVector.setFromMatrixPosition(this.camera.matrixWorld)); + + if (axis === 'E') { + + this.rotationAxis.copy(this.eye); + this.rotationAngle = this.pointEnd.angleTo(this.pointStart); + + this._startNorm.copy(this.pointStart).normalize(); + this._endNorm.copy(this.pointEnd).normalize(); + + this.rotationAngle *= (this._endNorm.cross(this._startNorm).dot(this.eye) < 0 ? 1 : -1); + + } else if (axis === 'XYZE') { + + this.rotationAxis.copy(this._offset).cross(this.eye).normalize(); + this.rotationAngle = this._offset.dot(_tempVector.copy(this.rotationAxis) + .cross(this.eye)) * ROTATION_SPEED; + + } else if (axis === 'X' || axis === 'Y' || axis === 'Z') { + + this.rotationAxis.copy(_unit[axis]); + + _tempVector.copy(_unit[axis]); + + if (space === 'local') { + + _tempVector.applyQuaternion(this.worldQuaternion); + + } + + this.rotationAngle = this._offset.dot(_tempVector.cross(this.eye).normalize()) * ROTATION_SPEED; + + } + + // Apply rotation snap + + if (this.rotationSnap) this.rotationAngle = Math.round(this.rotationAngle / this.rotationSnap) * this.rotationSnap; + + // Apply rotate + if (space === 'local' && axis !== 'E' && axis !== 'XYZE') { + + object.quaternion.copy(this._quaternionStart); + object.quaternion.multiply(_tempQuaternion.setFromAxisAngle(this.rotationAxis, this.rotationAngle)) + .normalize(); + + } else { + + this.rotationAxis.applyQuaternion(this._parentQuaternionInv); + object.quaternion.copy(_tempQuaternion.setFromAxisAngle(this.rotationAxis, this.rotationAngle)); + object.quaternion.multiply(this._quaternionStart).normalize(); + + } + + } + + this.dispatchEvent(_changeEvent); + this.dispatchEvent(_objectChangeEvent); + + } + + pointerUp(pointer) { + + if (pointer.button !== 0) return; + + if (this.dragging && (this.axis !== null)) { + + _mouseUpEvent.mode = this.mode; + this.dispatchEvent(_mouseUpEvent); + + } + + this.dragging = false; + this.axis = null; + + } + + dispose() { + + this.domElement.removeEventListener('pointerdown', this._onPointerDown); + this.domElement.removeEventListener('pointermove', this._onPointerHover); + this.domElement.removeEventListener('pointermove', this._onPointerMove); + this.domElement.removeEventListener('pointerup', this._onPointerUp); + + this.traverse(function (child) { + + if (child.geometry) child.geometry.dispose(); + if (child.material) child.material.dispose(); + + }); + + } + + // Set current object + attach(object) { + + this.object = object; + this.visible = true; + + return this; + + } + + // Detatch from object + detach() { + + this.object = undefined; + this.visible = false; + this.axis = null; + + return this; + + } + + reset() { + + if (!this.enabled) return; + + if (this.dragging) { + + this.object.position.copy(this._positionStart); + this.object.quaternion.copy(this._quaternionStart); + this.object.scale.copy(this._scaleStart); + + this.dispatchEvent(_changeEvent); + this.dispatchEvent(_objectChangeEvent); + + this.pointStart.copy(this.pointEnd); + + } + + } + + getRaycaster() { + + return _raycaster; + + } + + // TODO: deprecate + + getMode() { + + return this.mode; + + } + + setMode(mode) { + + this.mode = mode; + + } + + setTranslationSnap(translationSnap) { + + this.translationSnap = translationSnap; + + } + + setRotationSnap(rotationSnap) { + + this.rotationSnap = rotationSnap; + + } + + setScaleSnap(scaleSnap) { + + this.scaleSnap = scaleSnap; + + } + + setSize(size) { + + this.size = size; + + } + + setSpace(space) { + + this.space = space; + + } + + update() { + + console.warn( + 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.'); + + } + +} + +TransformControls.prototype.isTransformControls = true; + +// mouse / touch event handlers + +function getPointer(event) { + + if (this.domElement.ownerDocument.pointerLockElement) { + + return { + x: 0, + y: 0, + button: event.button + }; + + } else { + + const rect = this.domElement.getBoundingClientRect(); + + return { + x: (event.clientX - rect.left) / rect.width * 2 - 1, + y: -(event.clientY - rect.top) / rect.height * 2 + 1, + button: event.button + }; + + } + +} + +function onPointerHover(event) { - rotationAngle = offset.dot(_tempVector.cross(eye).normalize()) * ROTATION_SPEED; - } + if (!this.enabled) return; - // Apply rotation snap + switch (event.pointerType) { - if (this.rotationSnap) { - rotationAngle = Math.round(rotationAngle / this.rotationSnap) - * this.rotationSnap; - } + case 'mouse': + case 'pen': + this.pointerHover(this._getPointer(event)); + break; - this.rotationAngle = rotationAngle; + } - // Apply rotate - if (space === 'local' && axis !== 'E' && axis !== 'XYZE') { - object.quaternion.copy(quaternionStart); - object.quaternion.multiply(_tempQuaternion.setFromAxisAngle(rotationAxis, rotationAngle)) - .normalize(); - } else { - rotationAxis.applyQuaternion(parentQuaternionInv); - object.quaternion.copy(_tempQuaternion.setFromAxisAngle(rotationAxis, rotationAngle)); - object.quaternion.multiply(quaternionStart).normalize(); - } - } +} - this.dispatchEvent(changeEvent); - this.dispatchEvent(objectChangeEvent); - }; +function onPointerDown(event) { - this.pointerUp = function (pointer) { - if (pointer.button !== undefined && pointer.button !== 0) return; + if (!this.enabled) return; - if (this.dragging && (this.axis !== null)) { - mouseUpEvent.mode = this.mode; - this.dispatchEvent(mouseUpEvent); - } + if (!document.pointerLockElement) { - this.dragging = false; + this.domElement.setPointerCapture(event.pointerId); - if (pointer.button === undefined) this.axis = null; - }; + } - // normalize mouse / touch pointer and remap {x,y} to view space. + this.domElement.addEventListener('pointermove', this._onPointerMove); - function getPointer(event) { - if (document.pointerLockElement) { - return { - x: 0, - y: 0, - button: event.button, - }; - } + this.pointerHover(this._getPointer(event)); + this.pointerDown(this._getPointer(event)); - const pointer = event.changedTouches ? event.changedTouches[0] : event; +} - const rect = domElement.getBoundingClientRect(); +function onPointerMove(event) { - return { - x: ((pointer.clientX - rect.left) / rect.width) * 2 - 1, - y: (-(pointer.clientY - rect.top) / rect.height) * 2 + 1, - button: event.button, - }; - } + if (!this.enabled) return; - // mouse / touch event handlers + this.pointerMove(this._getPointer(event)); - function onPointerHover(event) { - if (!scope.enabled) return; +} - scope.pointerHover(getPointer(event)); - } +function onPointerUp(event) { - function onPointerDown(event) { - if (!scope.enabled) return; + if (!this.enabled) return; - document.addEventListener('mousemove', onPointerMove, false); + this.domElement.releasePointerCapture(event.pointerId); - scope.pointerHover(getPointer(event)); - scope.pointerDown(getPointer(event)); - } + this.domElement.removeEventListener('pointermove', this._onPointerMove); - function onPointerMove(event) { - if (!scope.enabled) return; + this.pointerUp(this._getPointer(event)); - scope.pointerMove(getPointer(event)); - } +} - function onPointerUp(event) { - if (!scope.enabled) return; +function intersectObjectWithRay(object, raycaster, includeInvisible) { - document.removeEventListener('mousemove', onPointerMove, false); + const allIntersections = raycaster.intersectObject(object, true); - scope.pointerUp(getPointer(event)); - } + for (let i = 0; i < allIntersections.length; i++) { - // TODO: deprecate + if (allIntersections[i].object.visible || includeInvisible) { - this.getMode = function () { - return scope.mode; - }; + return allIntersections[i]; - this.setMode = function (mode) { - scope.mode = mode; - }; + } - this.setTranslationSnap = function (translationSnap) { - scope.translationSnap = translationSnap; - }; + } - this.setRotationSnap = function (rotationSnap) { - scope.rotationSnap = rotationSnap; - }; + return false; - this.setScaleSnap = function (scaleSnap) { - scope.scaleSnap = scaleSnap; - }; +} - this.setSize = function (size) { - scope.size = size; - }; +// - this.setSpace = function (space) { - scope.space = space; - }; +// Reusable utility variables - this.update = function () { - console.warn( - 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.', - ); - }; - }; +const _tempEuler = new Euler(); +const _alignVector = new Vector3(0, 1, 0); +const _zeroVector = new Vector3(0, 0, 0); +const _lookAtMatrix = new Matrix4(); +const _tempQuaternion2 = new Quaternion(); +const _identityQuaternion = new Quaternion(); +const _dirVector = new Vector3(); +const _tempMatrix = new Matrix4(); - THREE.TransformControls.prototype = Object.assign(Object.create(THREE.Object3D.prototype), { +const _unitX = new Vector3(1, 0, 0); +const _unitY = new Vector3(0, 1, 0); +const _unitZ = new Vector3(0, 0, 1); - constructor: THREE.TransformControls, +const _v1 = new Vector3(); +const _v2 = new Vector3(); +const _v3 = new Vector3(); - isTransformControls: true, +class TransformControlsGizmo extends Object3D { - }); + constructor() { - THREE.TransformControlsGizmo = function () { - THREE.Object3D.call(this); + super(); this.type = 'TransformControlsGizmo'; // shared materials - const gizmoMaterial = new THREE.MeshBasicMaterial({ + const gizmoMaterial = new MeshBasicMaterial({ depthTest: false, depthWrite: false, - transparent: true, - side: THREE.DoubleSide, fog: false, + toneMapped: false, + transparent: true }); - const gizmoLineMaterial = new THREE.LineBasicMaterial({ + const gizmoLineMaterial = new LineBasicMaterial({ depthTest: false, depthWrite: false, - transparent: true, - linewidth: 1, fog: false, + toneMapped: false, + transparent: true }); // Make unique material for each axis/color @@ -596,333 +798,283 @@ module.exports = function (THREE) { const matInvisible = gizmoMaterial.clone(); matInvisible.opacity = 0.15; - const matHelper = gizmoMaterial.clone(); - matHelper.opacity = 0.33; + const matHelper = gizmoLineMaterial.clone(); + matHelper.opacity = 0.5; const matRed = gizmoMaterial.clone(); - matRed.color.set(0xff0000); + matRed.color.setHex(0xff0000); const matGreen = gizmoMaterial.clone(); - matGreen.color.set(0x00ff00); + matGreen.color.setHex(0x00ff00); const matBlue = gizmoMaterial.clone(); - matBlue.color.set(0x0000ff); - - const matWhiteTransparent = gizmoMaterial.clone(); - matWhiteTransparent.opacity = 0.25; - - const matYellowTransparent = matWhiteTransparent.clone(); - matYellowTransparent.color.set(0xffff00); - - const matCyanTransparent = matWhiteTransparent.clone(); - matCyanTransparent.color.set(0x00ffff); - - const matMagentaTransparent = matWhiteTransparent.clone(); - matMagentaTransparent.color.set(0xff00ff); - - const matYellow = gizmoMaterial.clone(); - matYellow.color.set(0xffff00); - - const matLineRed = gizmoLineMaterial.clone(); - matLineRed.color.set(0xff0000); + matBlue.color.setHex(0x0000ff); - const matLineGreen = gizmoLineMaterial.clone(); - matLineGreen.color.set(0x00ff00); + const matRedTransparent = gizmoMaterial.clone(); + matRedTransparent.color.setHex(0xff0000); + matRedTransparent.opacity = 0.5; - const matLineBlue = gizmoLineMaterial.clone(); - matLineBlue.color.set(0x0000ff); + const matGreenTransparent = gizmoMaterial.clone(); + matGreenTransparent.color.setHex(0x00ff00); + matGreenTransparent.opacity = 0.5; - const matLineCyan = gizmoLineMaterial.clone(); - matLineCyan.color.set(0x00ffff); + const matBlueTransparent = gizmoMaterial.clone(); + matBlueTransparent.color.setHex(0x0000ff); + matBlueTransparent.opacity = 0.5; - const matLineMagenta = gizmoLineMaterial.clone(); - matLineMagenta.color.set(0xff00ff); + const matWhiteTransparent = gizmoMaterial.clone(); + matWhiteTransparent.opacity = 0.25; - const matLineYellow = gizmoLineMaterial.clone(); - matLineYellow.color.set(0xffff00); + const matYellowTransparent = gizmoMaterial.clone(); + matYellowTransparent.color.setHex(0xffff00); + matYellowTransparent.opacity = 0.25; - const matLineGray = gizmoLineMaterial.clone(); - matLineGray.color.set(0x787878); + const matYellow = gizmoMaterial.clone(); + matYellow.color.setHex(0xffff00); - const matLineYellowTransparent = matLineYellow.clone(); - matLineYellowTransparent.opacity = 0.25; + const matGray = gizmoMaterial.clone(); + matGray.color.setHex(0x787878); // reusable geometry - const arrowGeometry = new THREE.CylinderBufferGeometry(0, 0.05, 0.2, 12, 1, false); - - const scaleHandleGeometry = new THREE.BoxBufferGeometry(0.125, 0.125, 0.125); + const arrowGeometry = new CylinderGeometry(0, 0.04, 0.1, 12); + arrowGeometry.translate(0, 0.05, 0); - const lineGeometry = new THREE.BufferGeometry(); - lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 1, 0, 0], 3)); + const scaleHandleGeometry = new BoxGeometry(0.08, 0.08, 0.08); + scaleHandleGeometry.translate(0, 0.04, 0); - const CircleGeometry = function (radius, arc) { - const geometry = new THREE.BufferGeometry(); - const vertices = []; + const lineGeometry = new BufferGeometry(); + lineGeometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0, 1, 0, 0], 3)); - for (let i = 0; i <= 64 * arc; ++i) { - vertices.push(0, Math.cos((i / 32) * Math.PI) * radius, Math.sin((i / 32) * Math.PI) * radius); - } + const lineGeometry2 = new CylinderGeometry(0.0075, 0.0075, 0.5, 3); + lineGeometry2.translate(0, 0.25, 0); - geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); + function CircleGeometry(radius, arc) { + const geometry = new TorusGeometry(radius, 0.0075, 3, 64, arc * Math.PI * 2); + geometry.rotateY(Math.PI / 2); + geometry.rotateX(Math.PI / 2); return geometry; - }; + + } // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position - const TranslateHelperGeometry = function () { - const geometry = new THREE.BufferGeometry(); + function TranslateHelperGeometry() { - geometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 1, 1, 1], 3)); + const geometry = new BufferGeometry(); + + geometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0, 1, 1, 1], 3)); return geometry; - }; + + } // Gizmo definitions - custom hierarchy definitions for setupGizmo() function const gizmoTranslate = { X: [ - [new THREE.Mesh(arrowGeometry, matRed), [1, 0, 0], [0, 0, -Math.PI / 2], null, 'fwd'], - [new THREE.Mesh(arrowGeometry, matRed), [1, 0, 0], [0, 0, Math.PI / 2], null, 'bwd'], - [new THREE.Line(lineGeometry, matLineRed)], + [new Mesh(arrowGeometry, matRed), [0.5, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(arrowGeometry, matRed), [-0.5, 0, 0], [0, 0, Math.PI / 2]], + [new Mesh(lineGeometry2, matRed), [0, 0, 0], [0, 0, -Math.PI / 2]] ], Y: [ - [new THREE.Mesh(arrowGeometry, matGreen), [0, 1, 0], null, null, 'fwd'], - [new THREE.Mesh(arrowGeometry, matGreen), [0, 1, 0], [Math.PI, 0, 0], null, 'bwd'], - [new THREE.Line(lineGeometry, matLineGreen), null, [0, 0, Math.PI / 2]], + [new Mesh(arrowGeometry, matGreen), [0, 0.5, 0]], + [new Mesh(arrowGeometry, matGreen), [0, -0.5, 0], [Math.PI, 0, 0]], + [new Mesh(lineGeometry2, matGreen)] ], Z: [ - [new THREE.Mesh(arrowGeometry, matBlue), [0, 0, 1], [Math.PI / 2, 0, 0], null, 'fwd'], - [new THREE.Mesh(arrowGeometry, matBlue), [0, 0, 1], [-Math.PI / 2, 0, 0], null, 'bwd'], - [new THREE.Line(lineGeometry, matLineBlue), null, [0, -Math.PI / 2, 0]], + [new Mesh(arrowGeometry, matBlue), [0, 0, 0.5], [Math.PI / 2, 0, 0]], + [new Mesh(arrowGeometry, matBlue), [0, 0, -0.5], [-Math.PI / 2, 0, 0]], + [new Mesh(lineGeometry2, matBlue), null, [Math.PI / 2, 0, 0]] ], XYZ: [ - [new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.1, 0), - matWhiteTransparent.clone()), [0, 0, 0], [0, 0, 0]], + [new Mesh(new OctahedronGeometry(0.1, 0), matWhiteTransparent.clone()), [0, 0, 0]] ], XY: [ - [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.295, 0.295), - matYellowTransparent.clone()), [0.15, 0.15, 0]], - [new THREE.Line(lineGeometry, matLineYellow), [0.18, 0.3, 0], null, [0.125, 1, 1]], - [new THREE.Line(lineGeometry, matLineYellow), [0.3, 0.18, 0], [0, 0, Math.PI / 2], [0.125, 1, 1]], + [new Mesh(new BoxGeometry(0.15, 0.15, 0.01), matBlueTransparent.clone()), [0.15, 0.15, 0]] ], YZ: [ - [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.295, 0.295), - matCyanTransparent.clone()), [0, 0.15, 0.15], [0, Math.PI / 2, 0]], - [new THREE.Line(lineGeometry, matLineCyan), [0, 0.18, 0.3], [0, 0, Math.PI / 2], [0.125, 1, 1]], - [new THREE.Line(lineGeometry, matLineCyan), [0, 0.3, 0.18], [0, -Math.PI / 2, 0], [0.125, 1, 1]], + [new Mesh(new BoxGeometry(0.15, 0.15, 0.01), + matRedTransparent.clone()), [0, 0.15, 0.15], [0, Math.PI / 2, 0]] ], XZ: [ - [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.295, 0.295), - matMagentaTransparent.clone()), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]], - [new THREE.Line(lineGeometry, matLineMagenta), [0.18, 0, 0.3], null, [0.125, 1, 1]], - [new THREE.Line(lineGeometry, matLineMagenta), [0.3, 0, 0.18], [0, -Math.PI / 2, 0], [0.125, 1, 1]], - ], + [new Mesh(new BoxGeometry(0.15, 0.15, 0.01), + matGreenTransparent.clone()), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]] + ] }; const pickerTranslate = { X: [ - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), - matInvisible), [0.6, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0.3, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [-0.3, 0, 0], [0, 0, Math.PI / 2]] ], Y: [ - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), matInvisible), [0, 0.6, 0]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0.3, 0]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, -0.3, 0], [0, 0, Math.PI]] ], Z: [ - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 1, 4, 1, false), - matInvisible), [0, 0, 0.6], [Math.PI / 2, 0, 0]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0, 0.3], [Math.PI / 2, 0, 0]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0, -0.3], [-Math.PI / 2, 0, 0]] ], XYZ: [ - [new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.2, 0), matInvisible)], + [new Mesh(new OctahedronGeometry(0.2, 0), matInvisible)] ], XY: [ - [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.4, 0.4), matInvisible), [0.2, 0.2, 0]], + [new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0.15, 0.15, 0]] ], YZ: [ - [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.4, 0.4), - matInvisible), [0, 0.2, 0.2], [0, Math.PI / 2, 0]], + [new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0, 0.15, 0.15], [0, Math.PI / 2, 0]] ], XZ: [ - [new THREE.Mesh(new THREE.PlaneBufferGeometry(0.4, 0.4), - matInvisible), [0.2, 0, 0.2], [-Math.PI / 2, 0, 0]], - ], + [new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]] + ] }; const helperTranslate = { START: [ - [new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.01, 2), matHelper), null, null, null, 'helper'], + [new Mesh(new OctahedronGeometry(0.01, 2), matHelper), null, null, null, 'helper'] ], END: [ - [new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.01, 2), matHelper), null, null, null, 'helper'], + [new Mesh(new OctahedronGeometry(0.01, 2), matHelper), null, null, null, 'helper'] ], DELTA: [ - [new THREE.Line(TranslateHelperGeometry(), matHelper), null, null, null, 'helper'], + [new Line(TranslateHelperGeometry(), matHelper), null, null, null, 'helper'] ], X: [ - [new THREE.Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper'], + [new Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper'] ], Y: [ - [new THREE.Line(lineGeometry, - matHelper.clone()), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], 'helper'], + [new Line(lineGeometry, matHelper.clone()), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], 'helper'] ], Z: [ - [new THREE.Line(lineGeometry, - matHelper.clone()), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], 'helper'], - ], + [new Line(lineGeometry, matHelper.clone()), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], 'helper'] + ] }; const gizmoRotate = { + XYZE: [ + [new Mesh(CircleGeometry(0.5, 1), matGray), null, [0, Math.PI / 2, 0]] + ], X: [ - [new THREE.Line(CircleGeometry(1, 0.5), matLineRed)], - [new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.04, 0), matRed), [0, 0, 0.99], null, [1, 3, 1]], + [new Mesh(CircleGeometry(0.5, 0.5), matRed)] ], Y: [ - [new THREE.Line(CircleGeometry(1, 0.5), matLineGreen), null, [0, 0, -Math.PI / 2]], - [new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.04, 0), matGreen), [0, 0, 0.99], null, [3, 1, 1]], + [new Mesh(CircleGeometry(0.5, 0.5), matGreen), null, [0, 0, -Math.PI / 2]] ], Z: [ - [new THREE.Line(CircleGeometry(1, 0.5), matLineBlue), null, [0, Math.PI / 2, 0]], - [new THREE.Mesh(new THREE.OctahedronBufferGeometry(0.04, 0), matBlue), [0.99, 0, 0], null, [1, 3, 1]], + [new Mesh(CircleGeometry(0.5, 0.5), matBlue), null, [0, Math.PI / 2, 0]] ], E: [ - [new THREE.Line(CircleGeometry(1.25, 1), matLineYellowTransparent), null, [0, Math.PI / 2, 0]], - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.03, 0, 0.15, 4, 1, false), - matLineYellowTransparent), [1.17, 0, 0], [0, 0, -Math.PI / 2], [1, 1, 0.001]], - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.03, 0, 0.15, 4, 1, false), - matLineYellowTransparent), [-1.17, 0, 0], [0, 0, Math.PI / 2], [1, 1, 0.001]], - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.03, 0, 0.15, 4, 1, false), - matLineYellowTransparent), [0, -1.17, 0], [Math.PI, 0, 0], [1, 1, 0.001]], - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.03, 0, 0.15, 4, 1, false), - matLineYellowTransparent), [0, 1.17, 0], [0, 0, 0], [1, 1, 0.001]], - ], - XYZE: [ - [new THREE.Line(CircleGeometry(1, 1), matLineGray), null, [0, Math.PI / 2, 0]], - ], + [new Mesh(CircleGeometry(0.75, 1), matYellowTransparent), null, [0, Math.PI / 2, 0]] + ] }; const helperRotate = { AXIS: [ - [new THREE.Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper'], - ], + [new Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper'] + ] }; const pickerRotate = { + XYZE: [ + [new Mesh(new SphereGeometry(0.25, 10, 8), matInvisible)] + ], X: [ - [new THREE.Mesh(new THREE.TorusBufferGeometry(1, 0.1, 4, 24), + [new Mesh(new TorusGeometry(0.5, 0.1, 4, 24), matInvisible), [0, 0, 0], [0, -Math.PI / 2, -Math.PI / 2]], ], Y: [ - [new THREE.Mesh(new THREE.TorusBufferGeometry(1, 0.1, 4, 24), - matInvisible), [0, 0, 0], [Math.PI / 2, 0, 0]], + [new Mesh(new TorusGeometry(0.5, 0.1, 4, 24), matInvisible), [0, 0, 0], [Math.PI / 2, 0, 0]], ], Z: [ - [new THREE.Mesh(new THREE.TorusBufferGeometry(1, 0.1, 4, 24), - matInvisible), [0, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(new TorusGeometry(0.5, 0.1, 4, 24), matInvisible), [0, 0, 0], [0, 0, -Math.PI / 2]], ], E: [ - [new THREE.Mesh(new THREE.TorusBufferGeometry(1.25, 0.1, 2, 24), matInvisible)], - ], - XYZE: [ - [new THREE.Mesh(new THREE.SphereBufferGeometry(0.7, 10, 8), matInvisible)], - ], + [new Mesh(new TorusGeometry(0.75, 0.1, 2, 24), matInvisible)] + ] }; const gizmoScale = { X: [ - [new THREE.Mesh(scaleHandleGeometry, matRed), [0.8, 0, 0], [0, 0, -Math.PI / 2]], - [new THREE.Line(lineGeometry, matLineRed), null, null, [0.8, 1, 1]], + [new Mesh(scaleHandleGeometry, matRed), [0.5, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(lineGeometry2, matRed), [0, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(scaleHandleGeometry, matRed), [-0.5, 0, 0], [0, 0, Math.PI / 2]], ], Y: [ - [new THREE.Mesh(scaleHandleGeometry, matGreen), [0, 0.8, 0]], - [new THREE.Line(lineGeometry, matLineGreen), null, [0, 0, Math.PI / 2], [0.8, 1, 1]], + [new Mesh(scaleHandleGeometry, matGreen), [0, 0.5, 0]], + [new Mesh(lineGeometry2, matGreen)], + [new Mesh(scaleHandleGeometry, matGreen), [0, -0.5, 0], [0, 0, Math.PI]], ], Z: [ - [new THREE.Mesh(scaleHandleGeometry, matBlue), [0, 0, 0.8], [Math.PI / 2, 0, 0]], - [new THREE.Line(lineGeometry, matLineBlue), null, [0, -Math.PI / 2, 0], [0.8, 1, 1]], + [new Mesh(scaleHandleGeometry, matBlue), [0, 0, 0.5], [Math.PI / 2, 0, 0]], + [new Mesh(lineGeometry2, matBlue), [0, 0, 0], [Math.PI / 2, 0, 0]], + [new Mesh(scaleHandleGeometry, matBlue), [0, 0, -0.5], [-Math.PI / 2, 0, 0]] ], XY: [ - [new THREE.Mesh(scaleHandleGeometry, matYellowTransparent), [0.85, 0.85, 0], null, [2, 2, 0.2]], - [new THREE.Line(lineGeometry, matLineYellow), [0.855, 0.98, 0], null, [0.125, 1, 1]], - [new THREE.Line(lineGeometry, matLineYellow), [0.98, 0.855, 0], [0, 0, Math.PI / 2], [0.125, 1, 1]], + [new Mesh(new BoxGeometry(0.15, 0.15, 0.01), matBlueTransparent), [0.15, 0.15, 0]] ], YZ: [ - [new THREE.Mesh(scaleHandleGeometry, matCyanTransparent), [0, 0.85, 0.85], null, [0.2, 2, 2]], - [new THREE.Line(lineGeometry, matLineCyan), [0, 0.855, 0.98], [0, 0, Math.PI / 2], [0.125, 1, 1]], - [new THREE.Line(lineGeometry, matLineCyan), [0, 0.98, 0.855], [0, -Math.PI / 2, 0], [0.125, 1, 1]], + [new Mesh(new BoxGeometry(0.15, 0.15, 0.01), matRedTransparent), [0, 0.15, 0.15], [0, Math.PI / 2, 0]] ], XZ: [ - [new THREE.Mesh(scaleHandleGeometry, matMagentaTransparent), [0.85, 0, 0.85], null, [2, 0.2, 2]], - [new THREE.Line(lineGeometry, matLineMagenta), [0.855, 0, 0.98], null, [0.125, 1, 1]], - [new THREE.Line(lineGeometry, matLineMagenta), [0.98, 0, 0.855], [0, -Math.PI / 2, 0], [0.125, 1, 1]], - ], - XYZX: [ - [new THREE.Mesh(new THREE.BoxBufferGeometry(0.125, 0.125, 0.125), - matWhiteTransparent.clone()), [1.1, 0, 0]], - ], - XYZY: [ - [new THREE.Mesh(new THREE.BoxBufferGeometry(0.125, 0.125, 0.125), - matWhiteTransparent.clone()), [0, 1.1, 0]], - ], - XYZZ: [ - [new THREE.Mesh(new THREE.BoxBufferGeometry(0.125, 0.125, 0.125), - matWhiteTransparent.clone()), [0, 0, 1.1]], + [new Mesh(new BoxGeometry(0.15, 0.15, 0.01), + matGreenTransparent), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]] ], + XYZ: [ + [new Mesh(new BoxGeometry(0.1, 0.1, 0.1), matWhiteTransparent.clone())], + ] }; const pickerScale = { X: [ - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 0.8, 4, 1, false), - matInvisible), [0.5, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0.3, 0, 0], [0, 0, -Math.PI / 2]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [-0.3, 0, 0], [0, 0, Math.PI / 2]] ], Y: [ - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 0.8, 4, 1, false), matInvisible), [0, 0.5, 0]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0.3, 0]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, -0.3, 0], [0, 0, Math.PI]] ], Z: [ - [new THREE.Mesh(new THREE.CylinderBufferGeometry(0.2, 0, 0.8, 4, 1, false), - matInvisible), [0, 0, 0.5], [Math.PI / 2, 0, 0]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0, 0.3], [Math.PI / 2, 0, 0]], + [new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4), matInvisible), [0, 0, -0.3], [-Math.PI / 2, 0, 0]] ], XY: [ - [new THREE.Mesh(scaleHandleGeometry, matInvisible), [0.85, 0.85, 0], null, [3, 3, 0.2]], + [new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0.15, 0.15, 0]], ], YZ: [ - [new THREE.Mesh(scaleHandleGeometry, matInvisible), [0, 0.85, 0.85], null, [0.2, 3, 3]], + [new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0, 0.15, 0.15], [0, Math.PI / 2, 0]], ], XZ: [ - [new THREE.Mesh(scaleHandleGeometry, matInvisible), [0.85, 0, 0.85], null, [3, 0.2, 3]], - ], - XYZX: [ - [new THREE.Mesh(new THREE.BoxBufferGeometry(0.2, 0.2, 0.2), matInvisible), [1.1, 0, 0]], - ], - XYZY: [ - [new THREE.Mesh(new THREE.BoxBufferGeometry(0.2, 0.2, 0.2), matInvisible), [0, 1.1, 0]], - ], - XYZZ: [ - [new THREE.Mesh(new THREE.BoxBufferGeometry(0.2, 0.2, 0.2), matInvisible), [0, 0, 1.1]], + [new Mesh(new BoxGeometry(0.2, 0.2, 0.01), matInvisible), [0.15, 0, 0.15], [-Math.PI / 2, 0, 0]], ], + XYZ: [ + [new Mesh(new BoxGeometry(0.2, 0.2, 0.2), matInvisible), [0, 0, 0]], + ] }; const helperScale = { X: [ - [new THREE.Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper'], + [new Line(lineGeometry, matHelper.clone()), [-1e3, 0, 0], null, [1e6, 1, 1], 'helper'] ], Y: [ - [new THREE.Line(lineGeometry, - matHelper.clone()), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], 'helper'], + [new Line(lineGeometry, matHelper.clone()), [0, -1e3, 0], [0, 0, Math.PI / 2], [1e6, 1, 1], 'helper'] ], Z: [ - [new THREE.Line(lineGeometry, - matHelper.clone()), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], 'helper'], - ], + [new Line(lineGeometry, matHelper.clone()), [0, 0, -1e3], [0, -Math.PI / 2, 0], [1e6, 1, 1], 'helper'] + ] }; // Creates an Object3D with gizmos described in custom hierarchy definition. - const setupGizmo = function (gizmoMap) { - const gizmo = new THREE.Object3D(); + function setupGizmo(gizmoMap) { + + const gizmo = new Object3D(); + + for (const name in gizmoMap) { - Object.keys(gizmoMap).forEach((name) => { for (let i = gizmoMap[name].length; i--;) { + const object = gizmoMap[name][i][0].clone(); const position = gizmoMap[name][i][1]; const rotation = gizmoMap[name][i][2]; @@ -934,13 +1086,21 @@ module.exports = function (THREE) { object.tag = tag; if (position) { + object.position.set(position[0], position[1], position[2]); + } + if (rotation) { + object.rotation.set(rotation[0], rotation[1], rotation[2]); + } + if (scale) { + object.scale.set(scale[0], scale[1], scale[2]); + } object.updateMatrix(); @@ -955,26 +1115,14 @@ module.exports = function (THREE) { object.scale.set(1, 1, 1); gizmo.add(object); - } - }); - return gizmo; - }; + } - // Reusable utility variables + } - const tempVector = new THREE.Vector3(0, 0, 0); - const tempEuler = new THREE.Euler(); - const alignVector = new THREE.Vector3(0, 1, 0); - const zeroVector = new THREE.Vector3(0, 0, 0); - const lookAtMatrix = new THREE.Matrix4(); - const tempQuaternion = new THREE.Quaternion(); - const tempQuaternion2 = new THREE.Quaternion(); - const identityQuaternion = new THREE.Quaternion(); + return gizmo; - const unitX = new THREE.Vector3(1, 0, 0); - const unitY = new THREE.Vector3(0, 1, 0); - const unitZ = new THREE.Vector3(0, 0, 1); + } // Gizmo creation @@ -982,399 +1130,455 @@ module.exports = function (THREE) { this.picker = {}; this.helper = {}; - this.add(this.gizmo.translate = setupGizmo(gizmoTranslate)); - this.add(this.gizmo.rotate = setupGizmo(gizmoRotate)); - this.add(this.gizmo.scale = setupGizmo(gizmoScale)); - this.add(this.picker.translate = setupGizmo(pickerTranslate)); - this.add(this.picker.rotate = setupGizmo(pickerRotate)); - this.add(this.picker.scale = setupGizmo(pickerScale)); - this.add(this.helper.translate = setupGizmo(helperTranslate)); - this.add(this.helper.rotate = setupGizmo(helperRotate)); - this.add(this.helper.scale = setupGizmo(helperScale)); + this.add(this.gizmo['translate'] = setupGizmo(gizmoTranslate)); + this.add(this.gizmo['rotate'] = setupGizmo(gizmoRotate)); + this.add(this.gizmo['scale'] = setupGizmo(gizmoScale)); + this.add(this.picker['translate'] = setupGizmo(pickerTranslate)); + this.add(this.picker['rotate'] = setupGizmo(pickerRotate)); + this.add(this.picker['scale'] = setupGizmo(pickerScale)); + this.add(this.helper['translate'] = setupGizmo(helperTranslate)); + this.add(this.helper['rotate'] = setupGizmo(helperRotate)); + this.add(this.helper['scale'] = setupGizmo(helperScale)); // Pickers should be hidden always - this.picker.translate.visible = false; - this.picker.rotate.visible = false; - this.picker.scale.visible = false; - - // updateMatrixWorld will update transformations and appearance of individual handles + this.picker['translate'].visible = false; + this.picker['rotate'].visible = false; + this.picker['scale'].visible = false; - this.updateMatrixWorld = function () { - let { space } = this; + } - if (this.mode === 'scale') space = 'local'; // scale always oriented to local rotation + // updateMatrixWorld will update transformations and appearance of individual handles - const quaternion = space === 'local' ? this.worldQuaternion : identityQuaternion; + updateMatrixWorld(force) { - // Show only gizmos for current transform mode + const space = (this.mode === 'scale') ? 'local' : this.space; // scale always oriented to local rotation - this.gizmo.translate.visible = this.mode === 'translate'; - this.gizmo.rotate.visible = this.mode === 'rotate'; - this.gizmo.scale.visible = this.mode === 'scale'; + const quaternion = (space === 'local') ? this.worldQuaternion : _identityQuaternion; - this.helper.translate.visible = this.mode === 'translate'; - this.helper.rotate.visible = this.mode === 'rotate'; - this.helper.scale.visible = this.mode === 'scale'; + // Show only gizmos for current transform mode - let handles = []; - handles = handles.concat(this.picker[this.mode].children); - handles = handles.concat(this.gizmo[this.mode].children); - handles = handles.concat(this.helper[this.mode].children); + this.gizmo['translate'].visible = this.mode === 'translate'; + this.gizmo['rotate'].visible = this.mode === 'rotate'; + this.gizmo['scale'].visible = this.mode === 'scale'; - for (let i = 0; i < handles.length; i++) { - const handle = handles[i]; + this.helper['translate'].visible = this.mode === 'translate'; + this.helper['rotate'].visible = this.mode === 'rotate'; + this.helper['scale'].visible = this.mode === 'scale'; - // hide aligned to camera - handle.visible = true; - handle.rotation.set(0, 0, 0); - handle.position.copy(this.worldPosition); + let handles = []; + handles = handles.concat(this.picker[this.mode].children); + handles = handles.concat(this.gizmo[this.mode].children); + handles = handles.concat(this.helper[this.mode].children); - const eyeDistance = this.worldPosition.distanceTo(this.cameraPosition); - handle.scale.set(1, 1, 1).multiplyScalar((eyeDistance * this.size) / 7); + for (let i = 0; i < handles.length; i++) { - // TODO: simplify helpers and consider decoupling from gizmo + const handle = handles[i]; - if (handle.tag === 'helper') { - handle.visible = false; + // hide aligned to camera - if (handle.name === 'AXIS') { - handle.position.copy(this.worldPositionStart); - handle.visible = !!this.axis; + handle.visible = true; + handle.rotation.set(0, 0, 0); + handle.position.copy(this.worldPosition); - if (this.axis === 'X') { - tempQuaternion.setFromEuler(tempEuler.set(0, 0, 0)); - handle.quaternion.copy(quaternion).multiply(tempQuaternion); + let factor; - if (Math.abs(alignVector.copy(unitX).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { - handle.visible = false; - } - } + if (this.camera.isOrthographicCamera) { - if (this.axis === 'Y') { - tempQuaternion.setFromEuler(tempEuler.set(0, 0, Math.PI / 2)); - handle.quaternion.copy(quaternion).multiply(tempQuaternion); + factor = (this.camera.top - this.camera.bottom) / this.camera.zoom; - if (Math.abs(alignVector.copy(unitY).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { - handle.visible = false; - } - } + } else { - if (this.axis === 'Z') { - tempQuaternion.setFromEuler(tempEuler.set(0, Math.PI / 2, 0)); - handle.quaternion.copy(quaternion).multiply(tempQuaternion); + factor = this.worldPosition.distanceTo(this.cameraPosition) * Math.min(1.9 * Math.tan(Math.PI * this.camera.fov / 360) / this.camera.zoom, + 7); - if (Math.abs(alignVector.copy(unitZ).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { - handle.visible = false; - } - } + } - if (this.axis === 'XYZE') { - tempQuaternion.setFromEuler(tempEuler.set(0, Math.PI / 2, 0)); - alignVector.copy(this.rotationAxis); - handle.quaternion.setFromRotationMatrix(lookAtMatrix.lookAt(zeroVector, - alignVector, - unitY)); - handle.quaternion.multiply(tempQuaternion); - handle.visible = this.dragging; - } + handle.scale.set(1, 1, 1).multiplyScalar(factor * this.size / 4); - if (this.axis === 'E') { - handle.visible = false; - } - } else if (handle.name === 'START') { - handle.position.copy(this.worldPositionStart); - handle.visible = this.dragging; - } else if (handle.name === 'END') { - handle.position.copy(this.worldPosition); - handle.visible = this.dragging; - } else if (handle.name === 'DELTA') { - handle.position.copy(this.worldPositionStart); - handle.quaternion.copy(this.worldQuaternionStart); - tempVector.set(1e-10, 1e-10, 1e-10).add(this.worldPositionStart).sub(this.worldPosition) - .multiplyScalar(-1); - tempVector.applyQuaternion(this.worldQuaternionStart.clone().inverse()); - handle.scale.copy(tempVector); - handle.visible = this.dragging; - } else { - handle.quaternion.copy(quaternion); + // TODO: simplify helpers and consider decoupling from gizmo - if (this.dragging) { - handle.position.copy(this.worldPositionStart); - } else { - handle.position.copy(this.worldPosition); - } + if (handle.tag === 'helper') { - if (this.axis) { - handle.visible = this.axis.search(handle.name) !== -1; - } - } + handle.visible = false; - // If updating helper, skip rest of the loop - continue; - } + if (handle.name === 'AXIS') { - // Align handles to current local or world rotation + handle.position.copy(this.worldPositionStart); + handle.visible = !!this.axis; - handle.quaternion.copy(quaternion); + if (this.axis === 'X') { - if (this.mode === 'translate' || this.mode === 'scale') { - // Hide translate and scale axis facing the camera + _tempQuaternion.setFromEuler(_tempEuler.set(0, 0, 0)); + handle.quaternion.copy(quaternion).multiply(_tempQuaternion); - const AXIS_HIDE_TRESHOLD = 0.99; - const PLANE_HIDE_TRESHOLD = 0.2; - const AXIS_FLIP_TRESHOLD = 0.0; + if (Math.abs(_alignVector.copy(_unitX).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { - if (handle.name === 'X' || handle.name === 'XYZX') { - if (Math.abs(alignVector.copy(unitX).applyQuaternion(quaternion) - .dot(this.eye)) > AXIS_HIDE_TRESHOLD) { - handle.scale.set(1e-10, 1e-10, 1e-10); - handle.visible = false; - } - } - if (handle.name === 'Y' || handle.name === 'XYZY') { - if (Math.abs(alignVector.copy(unitY).applyQuaternion(quaternion) - .dot(this.eye)) > AXIS_HIDE_TRESHOLD) { - handle.scale.set(1e-10, 1e-10, 1e-10); handle.visible = false; + } + } - if (handle.name === 'Z' || handle.name === 'XYZZ') { - if (Math.abs(alignVector.copy(unitZ).applyQuaternion(quaternion) - .dot(this.eye)) > AXIS_HIDE_TRESHOLD) { - handle.scale.set(1e-10, 1e-10, 1e-10); + + if (this.axis === 'Y') { + + _tempQuaternion.setFromEuler(_tempEuler.set(0, 0, Math.PI / 2)); + handle.quaternion.copy(quaternion).multiply(_tempQuaternion); + + if (Math.abs(_alignVector.copy(_unitY).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { + handle.visible = false; + } + } - if (handle.name === 'XY') { - if (Math.abs(alignVector.copy(unitZ).applyQuaternion(quaternion) - .dot(this.eye)) < PLANE_HIDE_TRESHOLD) { - handle.scale.set(1e-10, 1e-10, 1e-10); + + if (this.axis === 'Z') { + + _tempQuaternion.setFromEuler(_tempEuler.set(0, Math.PI / 2, 0)); + handle.quaternion.copy(quaternion).multiply(_tempQuaternion); + + if (Math.abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion).dot(this.eye)) > 0.9) { + handle.visible = false; + } + } - if (handle.name === 'YZ') { - if (Math.abs(alignVector.copy(unitX).applyQuaternion(quaternion) - .dot(this.eye)) < PLANE_HIDE_TRESHOLD) { - handle.scale.set(1e-10, 1e-10, 1e-10); - handle.visible = false; - } + + if (this.axis === 'XYZE') { + + _tempQuaternion.setFromEuler(_tempEuler.set(0, Math.PI / 2, 0)); + _alignVector.copy(this.rotationAxis); + handle.quaternion.setFromRotationMatrix(_lookAtMatrix.lookAt(_zeroVector, + _alignVector, + _unitY)); + handle.quaternion.multiply(_tempQuaternion); + handle.visible = this.dragging; + } - if (handle.name === 'XZ') { - if (Math.abs(alignVector.copy(unitY).applyQuaternion(quaternion) - .dot(this.eye)) < PLANE_HIDE_TRESHOLD) { - handle.scale.set(1e-10, 1e-10, 1e-10); - handle.visible = false; - } + + if (this.axis === 'E') { + + handle.visible = false; + } - // Flip translate and scale axis ocluded behind another axis - if (handle.name.search('X') !== -1) { - if (alignVector.copy(unitX).applyQuaternion(quaternion).dot(this.eye) < AXIS_FLIP_TRESHOLD) { - if (handle.tag === 'fwd') { - handle.visible = false; - } else { - handle.scale.x *= -1; - } - } else if (handle.tag === 'bwd') { - handle.visible = false; - } + } else if (handle.name === 'START') { + + handle.position.copy(this.worldPositionStart); + handle.visible = this.dragging; + + } else if (handle.name === 'END') { + + handle.position.copy(this.worldPosition); + handle.visible = this.dragging; + + } else if (handle.name === 'DELTA') { + + handle.position.copy(this.worldPositionStart); + handle.quaternion.copy(this.worldQuaternionStart); + _tempVector.set(1e-10, 1e-10, 1e-10).add(this.worldPositionStart).sub(this.worldPosition) + .multiplyScalar(-1); + _tempVector.applyQuaternion(this.worldQuaternionStart.clone().invert()); + handle.scale.copy(_tempVector); + handle.visible = this.dragging; + + } else { + + handle.quaternion.copy(quaternion); + + if (this.dragging) { + + handle.position.copy(this.worldPositionStart); + + } else { + + handle.position.copy(this.worldPosition); + } - if (handle.name.search('Y') !== -1) { - if (alignVector.copy(unitY).applyQuaternion(quaternion).dot(this.eye) < AXIS_FLIP_TRESHOLD) { - if (handle.tag === 'fwd') { - handle.visible = false; - } else { - handle.scale.y *= -1; - } - } else if (handle.tag === 'bwd') { - handle.visible = false; - } + if (this.axis) { + + handle.visible = this.axis.search(handle.name) !== -1; + } - if (handle.name.search('Z') !== -1) { - if (alignVector.copy(unitZ).applyQuaternion(quaternion).dot(this.eye) < AXIS_FLIP_TRESHOLD) { - if (handle.tag === 'fwd') { - handle.visible = false; - } else { - handle.scale.z *= -1; - } - } else if (handle.tag === 'bwd') { - handle.visible = false; - } + } + + // If updating helper, skip rest of the loop + continue; + + } + + // Align handles to current local or world rotation + + handle.quaternion.copy(quaternion); + + if (this.mode === 'translate' || this.mode === 'scale') { + + // Hide translate and scale axis facing the camera + + const AXIS_HIDE_TRESHOLD = 0.99; + const PLANE_HIDE_TRESHOLD = 0.2; + + if (handle.name === 'X') { + + if (Math.abs(_alignVector.copy(_unitX).applyQuaternion(quaternion) + .dot(this.eye)) > AXIS_HIDE_TRESHOLD) { + + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } - } else if (this.mode === 'rotate') { - // Align handles to current local or world rotation - tempQuaternion2.copy(quaternion); - alignVector.copy(this.eye).applyQuaternion(tempQuaternion.copy(quaternion).inverse()); + } + + if (handle.name === 'Y') { + + if (Math.abs(_alignVector.copy(_unitY).applyQuaternion(quaternion) + .dot(this.eye)) > AXIS_HIDE_TRESHOLD) { + + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; - if (handle.name.search('E') !== -1) { - handle.quaternion.setFromRotationMatrix(lookAtMatrix.lookAt(this.eye, zeroVector, unitY)); } - if (handle.name === 'X') { - tempQuaternion.setFromAxisAngle(unitX, Math.atan2(-alignVector.y, alignVector.z)); - tempQuaternion.multiplyQuaternions(tempQuaternion2, tempQuaternion); - handle.quaternion.copy(tempQuaternion); + } + + if (handle.name === 'Z') { + + if (Math.abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion) + .dot(this.eye)) > AXIS_HIDE_TRESHOLD) { + + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } - if (handle.name === 'Y') { - tempQuaternion.setFromAxisAngle(unitY, Math.atan2(alignVector.x, alignVector.z)); - tempQuaternion.multiplyQuaternions(tempQuaternion2, tempQuaternion); - handle.quaternion.copy(tempQuaternion); + } + + if (handle.name === 'XY') { + + if (Math.abs(_alignVector.copy(_unitZ).applyQuaternion(quaternion) + .dot(this.eye)) < PLANE_HIDE_TRESHOLD) { + + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } - if (handle.name === 'Z') { - tempQuaternion.setFromAxisAngle(unitZ, Math.atan2(alignVector.y, alignVector.x)); - tempQuaternion.multiplyQuaternions(tempQuaternion2, tempQuaternion); - handle.quaternion.copy(tempQuaternion); + } + + if (handle.name === 'YZ') { + + if (Math.abs(_alignVector.copy(_unitX).applyQuaternion(quaternion) + .dot(this.eye)) < PLANE_HIDE_TRESHOLD) { + + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } + } - // Hide disabled axes - handle.visible = handle.visible && (handle.name.indexOf('X') === -1 || this.showX); - handle.visible = handle.visible && (handle.name.indexOf('Y') === -1 || this.showY); - handle.visible = handle.visible && (handle.name.indexOf('Z') === -1 || this.showZ); - handle.visible = handle.visible && (handle.name.indexOf('E') === -1 - || (this.showX && this.showY && this.showZ)); - - // highlight selected axis - - handle.material._opacity = handle.material._opacity || handle.material.opacity; - handle.material._color = handle.material._color || handle.material.color.clone(); - - handle.material.color.copy(handle.material._color); - handle.material.opacity = handle.material._opacity; - - if (!this.enabled) { - handle.material.opacity *= 0.5; - handle.material.color.lerp(new THREE.Color(1, 1, 1), 0.5); - } else if (this.axis) { - if (handle.name === this.axis) { - handle.material.opacity = 1.0; - handle.material.color.lerp(new THREE.Color(1, 1, 1), 0.5); - } else if (this.axis.split('').some((a) => handle.name === a)) { - handle.material.opacity = 1.0; - handle.material.color.lerp(new THREE.Color(1, 1, 1), 0.5); - } else { - handle.material.opacity *= 0.25; - handle.material.color.lerp(new THREE.Color(1, 1, 1), 0.5); + if (handle.name === 'XZ') { + + if (Math.abs(_alignVector.copy(_unitY).applyQuaternion(quaternion) + .dot(this.eye)) < PLANE_HIDE_TRESHOLD) { + + handle.scale.set(1e-10, 1e-10, 1e-10); + handle.visible = false; + } + + } + + } else if (this.mode === 'rotate') { + + // Align handles to current local or world rotation + + _tempQuaternion2.copy(quaternion); + _alignVector.copy(this.eye).applyQuaternion(_tempQuaternion.copy(quaternion).invert()); + + if (handle.name.search('E') !== -1) { + + handle.quaternion.setFromRotationMatrix(_lookAtMatrix.lookAt(this.eye, _zeroVector, _unitY)); + + } + + if (handle.name === 'X') { + + _tempQuaternion.setFromAxisAngle(_unitX, Math.atan2(-_alignVector.y, _alignVector.z)); + _tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion); + handle.quaternion.copy(_tempQuaternion); + + } + + if (handle.name === 'Y') { + + _tempQuaternion.setFromAxisAngle(_unitY, Math.atan2(_alignVector.x, _alignVector.z)); + _tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion); + handle.quaternion.copy(_tempQuaternion); + + } + + if (handle.name === 'Z') { + + _tempQuaternion.setFromAxisAngle(_unitZ, Math.atan2(_alignVector.y, _alignVector.x)); + _tempQuaternion.multiplyQuaternions(_tempQuaternion2, _tempQuaternion); + handle.quaternion.copy(_tempQuaternion); + } + } - THREE.Object3D.prototype.updateMatrixWorld.call(this); - }; - }; + // Hide disabled axes + handle.visible = handle.visible && (handle.name.indexOf('X') === -1 || this.showX); + handle.visible = handle.visible && (handle.name.indexOf('Y') === -1 || this.showY); + handle.visible = handle.visible && (handle.name.indexOf('Z') === -1 || this.showZ); + handle.visible = handle.visible && (handle.name.indexOf('E') === -1 || (this.showX && this.showY && this.showZ)); + + // highlight selected axis + + handle.material._color = handle.material._color || handle.material.color.clone(); + handle.material._opacity = handle.material._opacity || handle.material.opacity; + + handle.material.color.copy(handle.material._color); + handle.material.opacity = handle.material._opacity; - THREE.TransformControlsGizmo.prototype = Object.assign(Object.create(THREE.Object3D.prototype), { + if (this.enabled && this.axis) { - constructor: THREE.TransformControlsGizmo, + if (handle.name === this.axis) { - isTransformControlsGizmo: true, + handle.material.color.setHex(0xffff00); + handle.material.opacity = 1.0; - }); + } else if (this.axis.split('').some(function (a) { - THREE.TransformControlsPlane = function () { - THREE.Mesh.call(this, - new THREE.PlaneBufferGeometry(100000, 100000, 2, 2), - new THREE.MeshBasicMaterial({ + return handle.name === a; + + })) { + + handle.material.color.setHex(0xffff00); + handle.material.opacity = 1.0; + + } + + } + + } + + super.updateMatrixWorld(force); + + } + +} + +TransformControlsGizmo.prototype.isTransformControlsGizmo = true; + +// + +class TransformControlsPlane extends Mesh { + + constructor() { + + super( + new PlaneGeometry(100000, 100000, 2, 2), + new MeshBasicMaterial({ visible: false, wireframe: true, - side: THREE.DoubleSide, + side: DoubleSide, transparent: true, opacity: 0.1, - })); + toneMapped: false + }) + ); this.type = 'TransformControlsPlane'; - const unitX = new THREE.Vector3(1, 0, 0); - const unitY = new THREE.Vector3(0, 1, 0); - const unitZ = new THREE.Vector3(0, 0, 1); - - const tempVector = new THREE.Vector3(); - const dirVector = new THREE.Vector3(); - const alignVector = new THREE.Vector3(); - const tempMatrix = new THREE.Matrix4(); - const identityQuaternion = new THREE.Quaternion(); - - this.updateMatrixWorld = function () { - let { space } = this; - - this.position.copy(this.worldPosition); - - if (this.mode === 'scale') space = 'local'; // scale always oriented to local rotation - - unitX.set(1, 0, 0).applyQuaternion(space === 'local' ? this.worldQuaternion : identityQuaternion); - unitY.set(0, 1, 0).applyQuaternion(space === 'local' ? this.worldQuaternion : identityQuaternion); - unitZ.set(0, 0, 1).applyQuaternion(space === 'local' ? this.worldQuaternion : identityQuaternion); - - // Align the plane for current transform mode, axis and space. - - alignVector.copy(unitY); - - switch (this.mode) { - case 'translate': - case 'scale': - switch (this.axis) { - case 'X': - alignVector.copy(this.eye).cross(unitX); - dirVector.copy(unitX).cross(alignVector); - break; - case 'Y': - alignVector.copy(this.eye).cross(unitY); - dirVector.copy(unitY).cross(alignVector); - break; - case 'Z': - alignVector.copy(this.eye).cross(unitZ); - dirVector.copy(unitZ).cross(alignVector); - break; - case 'XY': - dirVector.copy(unitZ); - break; - case 'YZ': - dirVector.copy(unitX); - break; - case 'XZ': - alignVector.copy(unitZ); - dirVector.copy(unitY); - break; - case 'XYZ': - case 'E': - dirVector.set(0, 0, 0); - break; - default: - break; - } - break; - case 'rotate': - default: - // special case for rotate - dirVector.set(0, 0, 0); - } + } + + updateMatrixWorld(force) { + + let space = this.space; + + this.position.copy(this.worldPosition); + + if (this.mode === 'scale') space = 'local'; // scale always oriented to local rotation + + _v1.copy(_unitX).applyQuaternion(space === 'local' ? this.worldQuaternion : _identityQuaternion); + _v2.copy(_unitY).applyQuaternion(space === 'local' ? this.worldQuaternion : _identityQuaternion); + _v3.copy(_unitZ).applyQuaternion(space === 'local' ? this.worldQuaternion : _identityQuaternion); + + // Align the plane for current transform mode, axis and space. + + _alignVector.copy(_v2); + + switch (this.mode) { + + case 'translate': + case 'scale': + switch (this.axis) { + + case 'X': + _alignVector.copy(this.eye).cross(_v1); + _dirVector.copy(_v1).cross(_alignVector); + break; + case 'Y': + _alignVector.copy(this.eye).cross(_v2); + _dirVector.copy(_v2).cross(_alignVector); + break; + case 'Z': + _alignVector.copy(this.eye).cross(_v3); + _dirVector.copy(_v3).cross(_alignVector); + break; + case 'XY': + _dirVector.copy(_v3); + break; + case 'YZ': + _dirVector.copy(_v1); + break; + case 'XZ': + _alignVector.copy(_v3); + _dirVector.copy(_v2); + break; + case 'XYZ': + case 'E': + _dirVector.set(0, 0, 0); + break; - if (dirVector.length() === 0) { - // If in rotate mode, make the plane parallel to camera - this.quaternion.copy(this.cameraQuaternion); - } else { - tempMatrix.lookAt(tempVector.set(0, 0, 0), dirVector, alignVector); + } - this.quaternion.setFromRotationMatrix(tempMatrix); - } + break; + case 'rotate': + default: + // special case for rotate + _dirVector.set(0, 0, 0); - THREE.Object3D.prototype.updateMatrixWorld.call(this); - }; - }; + } - THREE.TransformControlsPlane.prototype = Object.assign(Object.create(THREE.Mesh.prototype), { + if (_dirVector.length() === 0) { - constructor: THREE.TransformControlsPlane, + // If in rotate mode, make the plane parallel to camera + this.quaternion.copy(this.cameraQuaternion); - isTransformControlsPlane: true, + } else { - }); -}; + _tempMatrix.lookAt(_tempVector.set(0, 0, 0), _dirVector, _alignVector); + + this.quaternion.setFromRotationMatrix(_tempMatrix); + + } + + super.updateMatrixWorld(force); + + } + +} + +TransformControlsPlane.prototype.isTransformControlsPlane = true; + +export { TransformControls, TransformControlsGizmo, TransformControlsPlane }; \ No newline at end of file diff --git a/js/src/providers/threejs/initializers/Manipulate.js b/js/src/providers/threejs/initializers/Manipulate.js index a76360c0..61c94f87 100644 --- a/js/src/providers/threejs/initializers/Manipulate.js +++ b/js/src/providers/threejs/initializers/Manipulate.js @@ -6,8 +6,8 @@ const { viewModes } = require('../../../core/lib/viewMode'); * @memberof K3D.Providers.ThreeJS.Initializers */ module.exports = function (K3D) { - const world = K3D.getWorld(); let - draggingState = false; + const world = K3D.getWorld(); + let draggingState = false; K3D.on(K3D.events.VIEW_MODE_CHANGE, (mode) => { if (mode === viewModes.manipulate) { diff --git a/js/src/providers/threejs/initializers/Renderer.js b/js/src/providers/threejs/initializers/Renderer.js index 6abaf9a2..abf2c6a2 100644 --- a/js/src/providers/threejs/initializers/Renderer.js +++ b/js/src/providers/threejs/initializers/Renderer.js @@ -32,12 +32,14 @@ module.exports = function (K3D) { antialias: K3D.parameters.antialias > 0, preserveDrawingBuffer: true, alpha: true, + stencil: true, powerPreference: 'high-performance', }); self.renderer = new THREE.WebGLRenderer({ alpha: true, precision: "highp", + premultipliedAlpha: true, antialias: K3D.parameters.antialias > 0, logarithmicDepthBuffer: K3D.parameters.logarithmicDepthBuffer, canvas, @@ -75,6 +77,7 @@ module.exports = function (K3D) { console.log('K3D: (UNMASKED_VENDOR_WEBGL)', gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL)); console.log('K3D: (UNMASKED_RENDERER_WEBGL)', gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)); console.log('K3D: (depth bits)', gl.getParameter(gl.DEPTH_BITS)); + console.log('K3D: (stencil bits)', gl.getParameter(gl.STENCIL_BITS)); function standardRender(scene, camera, rt) { if (typeof (rt) === 'undefined') { @@ -279,7 +282,7 @@ module.exports = function (K3D) { rt, width, height, chunkHeights, aaLevel, currentRenderMethod).then((scene) => { rt.dispose(); - return [axesHelper, grid, scene]; + return [grid, scene, axesHelper]; }); }); }); diff --git a/js/src/providers/threejs/initializers/Scene.js b/js/src/providers/threejs/initializers/Scene.js index 68002d09..36948819 100644 --- a/js/src/providers/threejs/initializers/Scene.js +++ b/js/src/providers/threejs/initializers/Scene.js @@ -10,11 +10,7 @@ let rebuildSceneDataPromises = null; function generateAxesHelper(K3D, axesHelper) { const promises = []; - const colors = { - x: 0xff0000, - y: 0x0000ff, - z: 0x00ff00, - }; + const colors = K3D.parameters.axesHelperColors; const directions = { x: [1, 0, 0], y: [0, 1, 0], @@ -27,7 +23,8 @@ function generateAxesHelper(K3D, axesHelper) { }; const labelColor = new THREE.Color(K3D.parameters.labelColor); - ['x', 'y', 'z'].forEach((axis) => { + + ['x', 'y', 'z'].forEach((axis, i) => { const label = Text.create({ position: new THREE.Vector3().fromArray(directions[axis]).multiplyScalar(1.1).toArray(), reference_point: 'cc', @@ -38,12 +35,13 @@ function generateAxesHelper(K3D, axesHelper) { promises.push(label.then((obj) => { axesHelper[axis] = obj; + axesHelper[axis].color = colors[i]; axesHelper.scene.add(obj); })); }); const arrows = Vectors.create({ - colors: { data: [colors.x, colors.x, colors.y, colors.y, colors.z, colors.z] }, + colors: { data: [colors[0], colors[0], colors[1], colors[1], colors[2], colors[2]] }, origins: { data: [0, 0, 0, 0, 0, 0, 0, 0, 0] }, vectors: { data: [].concat(directions.x, directions.y, directions.z) }, line_width: 0.05, @@ -58,25 +56,11 @@ function generateAxesHelper(K3D, axesHelper) { return promises; } -function ensureTwoTicksOnGrids(sceneBoundingBox, majorScale) { - const size = sceneBoundingBox.getSize(new THREE.Vector3()); - - ['x', 'y', 'z'].forEach((axis) => { - const dist = size[axis] / majorScale; - - if (dist <= 2.0) { - sceneBoundingBox.min[axis] -= (1.0 - dist / 2.0 + 0.0001) * majorScale; - sceneBoundingBox.max[axis] += (1.0 - dist / 2.0 + 0.0001) * majorScale; - } - }); -} - function getSceneBoundingBox() { /* jshint validthis:true */ const sceneBoundingBox = new THREE.Box3(); let objectBoundingBox; - this.K3DObjects.traverse((object) => { let isK3DObject = false; let ref = object; @@ -92,6 +76,7 @@ function getSceneBoundingBox() { if (isK3DObject && typeof (object.position.z) !== 'undefined' + && object.visible && (object.geometry || object.boundingBox)) { if (object.geometry && object.geometry.boundingBox) { objectBoundingBox = object.geometry.boundingBox.clone(); @@ -212,9 +197,15 @@ function rebuildSceneData(K3D, grids, axesHelper, force) { updateAxesHelper = !K3D.parameters.axesHelper || (K3D.parameters.axesHelper && !axesHelper.x); if (axesHelper.x && !updateAxesHelper) { + // has axes labels changed? updateAxesHelper |= K3D.parameters.axes[0] !== axesHelper.x.text || K3D.parameters.axes[1] !== axesHelper.y.text || K3D.parameters.axes[2] !== axesHelper.z.text; + + // has axes colors changed? + updateAxesHelper |= K3D.parameters.axesHelperColors[0] !== axesHelper.x.color + || K3D.parameters.axesHelperColors[1] !== axesHelper.y.color + || K3D.parameters.axesHelperColors[2] !== axesHelper.z.color; } if (updateAxesHelper) { @@ -239,7 +230,7 @@ function rebuildSceneData(K3D, grids, axesHelper, force) { axesHelper.width = 100; axesHelper.height = 100; } - + if (updateAxesHelper) { if (K3D.parameters.axesHelper > 0) { generateAxesHelper(K3D, axesHelper).forEach((p) => { @@ -261,7 +252,13 @@ function rebuildSceneData(K3D, grids, axesHelper, force) { majorScale = pow10ceil(Math.max(size.x, size.y, size.z)) / 10.0; minorScale = majorScale / 10.0; - ensureTwoTicksOnGrids(sceneBoundingBox, majorScale); + ['x', 'y', 'z'].forEach(function (axis) { + if (sceneBoundingBox.min[axis] === sceneBoundingBox.max[axis]) { + sceneBoundingBox.min[axis] -= majorScale / 2.0; + sceneBoundingBox.max[axis] += majorScale / 2.0; + } + }); + size = sceneBoundingBox.getSize(new THREE.Vector3()); sceneBoundingBox.min = new THREE.Vector3( Math.floor(sceneBoundingBox.min.x / majorScale) * majorScale, @@ -329,9 +326,10 @@ function rebuildSceneData(K3D, grids, axesHelper, force) { // create labels Object.keys(grids.labelsOnEdges).forEach((key) => { const iterateAxis = _.difference(['x', 'y', 'z'], key.replace(/[^xyz]/g, '').split(''))[0]; - const iterationCount = size[iterateAxis] / majorScale; - const deltaValue = unitVectors[iterateAxis].clone().multiplyScalar(majorScale); - const deltaPosition = unitVectors[iterateAxis].clone().multiplyScalar( + let iterationCount = size[iterateAxis] / majorScale; + + let deltaValue = unitVectors[iterateAxis].clone().multiplyScalar(majorScale); + let deltaPosition = unitVectors[iterateAxis].clone().multiplyScalar( grids.labelsOnEdges[key].p[0].distanceTo(grids.labelsOnEdges[key].p[1]) / iterationCount, ); let j; @@ -339,6 +337,17 @@ function rebuildSceneData(K3D, grids, axesHelper, force) { let p; let label; + if (iterationCount <= 2) { + let originalIterationCount = iterationCount; + + iterationCount = originalIterationCount * 5; + deltaValue = unitVectors[iterateAxis].clone() + .multiplyScalar(originalIterationCount * majorScale / iterationCount); + deltaPosition = unitVectors[iterateAxis].clone().multiplyScalar( + grids.labelsOnEdges[key].p[0].distanceTo(grids.labelsOnEdges[key].p[1]) / iterationCount, + ); + } + for (j = 1; j <= iterationCount - 1; j++) { v = grids.labelsOnEdges[key].v[0].clone().add(deltaValue.clone().multiplyScalar(j)); p = grids.labelsOnEdges[key].p[0].clone().add(deltaPosition.clone().multiplyScalar(j)); @@ -408,8 +417,7 @@ function rebuildSceneData(K3D, grids, axesHelper, force) { const delta = unitVectors[iterateAxis].clone().multiplyScalar(minorScale); let p1; let p2; - let - j; + let j; for (j = 0; j <= size[iterateAxis] / minorScale; j++) { p1 = plane.p1.clone().add(delta.clone().multiplyScalar(j)); diff --git a/js/src/providers/threejs/objects/Lines.js b/js/src/providers/threejs/objects/Lines.js new file mode 100644 index 00000000..8b3e45cb --- /dev/null +++ b/js/src/providers/threejs/objects/Lines.js @@ -0,0 +1,34 @@ +const LinesSimple = require('./LinesSimple'); +const LinesMesh = require('./LinesMesh'); +const LinesThick = require('./LinesThick'); + +/** + * Loader strategy to handle Lines object + * @method Line + * @memberof K3D.Providers.ThreeJS.Objects + * @param {Object} config all configurations params from JSON + * @return {Object} 3D object ready to render + */ +module.exports = { + create(config, K3D) { + config.visible = typeof (config.visible) !== 'undefined' ? config.visible : true; + config.color = typeof (config.color) !== 'undefined' ? config.color : 0xff00; + config.shader = typeof (config.shader) !== 'undefined' ? config.shader : 'simple'; + + if (config.shader === 'mesh') { + return LinesMesh.create(config, K3D); + } if (config.shader === 'simple') { + return LinesSimple.create(config, K3D); + } + return LinesThick.create(config, K3D); + }, + + update(config, changes, obj, K3D) { + if (config.shader === 'mesh') { + return LinesMesh.update(config, changes, obj, K3D); + } if (config.shader === 'simple') { + return LinesSimple.update(config, changes, obj, K3D); + } + return LinesThick.update(config, changes, obj, K3D); + }, +}; diff --git a/js/src/providers/threejs/objects/LinesMesh.js b/js/src/providers/threejs/objects/LinesMesh.js new file mode 100644 index 00000000..4fcceda9 --- /dev/null +++ b/js/src/providers/threejs/objects/LinesMesh.js @@ -0,0 +1,129 @@ +const THREE = require('three'); +const Fn = require('../helpers/Fn'); + +const { areAllChangesResolve } = Fn; +const { commonUpdate } = Fn; +const { colorsToFloat32Array } = require('../../../core/lib/helpers/buffer'); +const streamLine = require('../helpers/Streamline'); +const BufferGeometryUtils = require('three/examples/jsm/utils/BufferGeometryUtils'); + +const { handleColorMap } = Fn; + +/** + * Loader strategy to handle Lines object + * @method Line + * @memberof K3D.Providers.ThreeJS.Objects + * @param {Object} config all configurations params from JSON + * @return {Object} 3D object ready to render + */ +module.exports = { + create(config) { + config.radial_segments = typeof (config.radial_segments) !== 'undefined' ? config.radial_segments : 8; + config.width = typeof (config.width) !== 'undefined' ? config.width : 0.1; + + const material = new THREE.MeshPhongMaterial({ + emissive: 0, + shininess: 50, + specular: 0x111111, + side: THREE.DoubleSide, + wireframe: false, + }); + const radialSegments = config.radial_segments; + const { width } = config; + let verticesColors = (config.colors && config.colors.data) || null; + const color = new THREE.Color(config.color); + const colorRange = config.color_range; + const colorMap = (config.color_map && config.color_map.data) || null; + const attribute = (config.attribute && config.attribute.data) || []; + const modelMatrix = new THREE.Matrix4(); + const position = config.vertices.data; + const indices = config.indices.data; + const edges = new Set(); + let jump = config.indices_type == 'segment' ? 2 : 3; + + if (verticesColors && verticesColors.length === position.length / 3) { + verticesColors = colorsToFloat32Array(verticesColors); + } + + let g = []; + let verticesCount = position.length / 3; + let offsets + + for (let i = 0; i < indices.length; i += jump) { + if (jump === 3) { + offsets = [ + [indices[i], indices[i + 1]], + [indices[i + 1], indices[i + 2]], + [indices[i + 2], indices[i]], + ]; + } else { + offsets = [ + [indices[i], indices[i + 1]] + ]; + } + + for (let j = 0; j < offsets.length; j++) { + let hash = offsets[j][0] > offsets[j][1] + ? offsets[j][0] + offsets[j][1] * verticesCount + : offsets[j][1] + offsets[j][0] * verticesCount; + + if (!edges.has(hash)) { + edges.add(hash); + + let o1 = offsets[j][0] * 3; + let o2 = offsets[j][1] * 3; + + g.push( + streamLine( + [ + position[o1], position[o1 + 1], position[o1 + 2], + position[o2], position[o2 + 1], position[o2 + 2] + ], + attribute.length > 0 ? [attribute[offsets[j][0]], attribute[offsets[j][1]]] : null, + width, + radialSegments, + color, + verticesColors.length > 0 + ? [verticesColors[o1], verticesColors[o1 + 1], verticesColors[o1 + 2], + verticesColors[o2], verticesColors[o2 + 1], verticesColors[o2 + 2]] + : null, + colorRange + ) + ); + } + } + } + + let geometry = BufferGeometryUtils.mergeBufferGeometries(g); + + if (attribute && colorRange && colorMap && attribute.length > 0 && colorRange.length > 0 + && colorMap.length > 0) { + handleColorMap(geometry, colorMap, colorRange, null, material); + } else { + material.setValues({ vertexColors: THREE.VertexColors }); + } + + geometry.computeBoundingSphere(); + geometry.computeBoundingBox(); + + modelMatrix.set.apply(modelMatrix, config.model_matrix.data); + + const object = new THREE.Mesh(geometry, material); + object.applyMatrix4(modelMatrix); + + object.updateMatrixWorld(); + + return Promise.resolve(object); + }, + + update(config, changes, obj) { + const resolvedChanges = {}; + + commonUpdate(config, changes, resolvedChanges, obj); + + if (areAllChangesResolve(changes, resolvedChanges)) { + return Promise.resolve({ json: config, obj }); + } + return false; + }, +}; diff --git a/js/src/providers/threejs/objects/LinesSimple.js b/js/src/providers/threejs/objects/LinesSimple.js new file mode 100644 index 00000000..ba6de690 --- /dev/null +++ b/js/src/providers/threejs/objects/LinesSimple.js @@ -0,0 +1,144 @@ +const THREE = require('three'); +const { colorsToFloat32Array } = require('../../../core/lib/helpers/buffer'); +const Fn = require('../helpers/Fn'); +const { commonUpdate } = Fn; +const { areAllChangesResolve } = Fn; +const { getColorsArray } = Fn; +const { handleColorMap } = Fn; + +/** + * Loader strategy to handle Lines object + * @method Line + * @memberof K3D.Providers.ThreeJS.Objects + * @param {Object} config all configurations params from JSON + * @return {Object} 3D object ready to render + */ +module.exports = { + create(config) { + const geometry = new THREE.BufferGeometry(); + const material = new THREE.MeshBasicMaterial(); + let verticesColors = (config.colors && config.colors.data) || null; + const color = new THREE.Color(config.color); + const colorRange = config.color_range; + const colorMap = (config.color_map && config.color_map.data) || null; + const attr = (config.attribute && config.attribute.data) || null; + const object = new THREE.Line(geometry, material); + const modelMatrix = new THREE.Matrix4(); + const vertices = config.vertices.data; + const indices = config.indices.data; + const edges = new Set(); + + let positions = []; + let attribute = []; + let colors = []; + let jump = config.indices_type == 'segment' ? 2 : 3; + let offsets; + + let verticesCount = vertices.length / 3; + + verticesColors = (verticesColors && verticesColors.length === vertices.length / 3 + ? colorsToFloat32Array(verticesColors) : getColorsArray(color, vertices.length / 3) + ); + + for (let i = 0; i < indices.length; i += jump) { + if (jump === 3) { + offsets = [ + [indices[i], indices[i + 1]], + [indices[i + 1], indices[i + 2]], + [indices[i + 2], indices[i]], + ]; + } else { + offsets = [ + [indices[i], indices[i + 1]] + ]; + } + + for (let j = 0; j < offsets.length; j++) { + let hash = offsets[j][0] > offsets[j][1] + ? offsets[j][0] + offsets[j][1] * verticesCount + : offsets[j][1] + offsets[j][0] * verticesCount; + + if (!edges.has(hash)) { + edges.add(hash); + + let o1 = offsets[j][0] * 3; + let o2 = offsets[j][1] * 3; + + positions.push( + vertices[o1], vertices[o1 + 1], vertices[o1 + 2], + vertices[o2], vertices[o2 + 1], vertices[o2 + 2] + ); + + if (verticesColors && verticesColors.length > 0) { + colors.push( + verticesColors[o1], verticesColors[o1 + 1], verticesColors[o1 + 2], + verticesColors[o2], verticesColors[o2 + 1], verticesColors[o2 + 2] + ); + } + + if (attr && attr.length > 0) { + attribute.push(attr[offsets[j][0]], attr[offsets[j][1]]); + } + } + } + } + + positions = new Float32Array(positions); + attribute = new Float32Array(attribute); + colors = new Float32Array(colors); + + if (colorRange && colorMap && attribute.length > 0 && colorRange.length > 0 + && colorMap.length > 0) { + handleColorMap(geometry, colorMap, colorRange, attribute, material); + } else { + material.setValues({ vertexColors: THREE.VertexColors }); + geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); + } + + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + + geometry.computeBoundingSphere(); + geometry.computeBoundingBox(); + + modelMatrix.set.apply(modelMatrix, config.model_matrix.data); + object.applyMatrix4(modelMatrix); + + object.updateMatrixWorld(); + + return Promise.resolve(object); + }, + + update(config, changes, obj) { + const resolvedChanges = {}; + + if (typeof (obj.geometry.attributes.uv) !== 'undefined') { + if (typeof (changes.color_range) !== 'undefined' && !changes.color_range.timeSeries) { + obj.material.uniforms.low.value = changes.color_range[0]; + obj.material.uniforms.high.value = changes.color_range[1]; + + resolvedChanges.color_range = null; + } + + if (typeof (changes.attribute) !== 'undefined' && !changes.attribute.timeSeries + && changes.attribute.data.length === obj.geometry.attributes.uv.array.length) { + const data = obj.geometry.attributes.uv.array; + + for (let i = 0; i < data.length; i++) { + data[i] = (changes.attribute.data[i] - config.color_range[0]) + / (config.color_range[1] - config.color_range[0]); + } + + obj.geometry.attributes.uv.needsUpdate = true; + resolvedChanges.attribute = null; + } + } + + commonUpdate(config, changes, resolvedChanges, obj); + + if (areAllChangesResolve(changes, resolvedChanges)) { + return Promise.resolve({ json: config, obj }); + } + + return false; + }, +}; diff --git a/js/src/providers/threejs/objects/LinesThick.js b/js/src/providers/threejs/objects/LinesThick.js new file mode 100644 index 00000000..e8e30977 --- /dev/null +++ b/js/src/providers/threejs/objects/LinesThick.js @@ -0,0 +1,209 @@ +const THREE = require('three'); +const { colorsToFloat32Array } = require('../../../core/lib/helpers/buffer'); +const MeshLine = require('../helpers/THREE.MeshLine')(THREE); +const Fn = require('../helpers/Fn'); + +const { commonUpdate } = Fn; +const { areAllChangesResolve } = Fn; +const colorMapHelper = require('../../../core/lib/helpers/colorMap'); + +const { getColorsArray } = Fn; + +/** + * Loader strategy to handle Lines object + * @method Line + * @memberof K3D.Providers.ThreeJS.Objects + * @param {Object} config all configurations params from JSON + * @return {Object} 3D object ready to render + */ +function create(config, K3D) { + config.width = typeof (config.width) !== 'undefined' ? config.width : 0.1; + + const material = new MeshLine.MeshLineMaterial({ + color: new THREE.Color(1, 1, 1), + opacity: 1.0, + sizeAttenuation: true, + transparent: true, + lineWidth: config.width, + resolution: new THREE.Vector2(K3D.getWorld().width, K3D.getWorld().height), + side: THREE.DoubleSide, + }); + let verticesColors = (config.colors && config.colors.data) || null; + const color = new THREE.Color(config.color); + let uvs = null; + const colorRange = config.color_range; + const colorMap = (config.color_map && config.color_map.data) || null; + const attr = (config.attribute && config.attribute.data) || null; + const modelMatrix = new THREE.Matrix4(); + const vertices = config.vertices.data; + const indices = config.indices.data; + const edges = new Set(); + let jump = config.indices_type == 'segment' ? 2 : 3; + let offsets; + + let positions = []; + let attribute = []; + let colors = []; + + let verticesCount = vertices.length / 3; + + verticesColors = (verticesColors && verticesColors.length === vertices.length / 3 + ? colorsToFloat32Array(verticesColors) : getColorsArray(color, vertices.length / 3) + ); + + for (let i = 0; i < indices.length; i += jump) { + if (jump === 3) { + offsets = [ + [indices[i], indices[i + 1]], + [indices[i + 1], indices[i + 2]], + [indices[i + 2], indices[i]], + ]; + } else { + offsets = [ + [indices[i], indices[i + 1]] + ]; + } + + for (let j = 0; j < offsets.length; j++) { + let hash = offsets[j][0] > offsets[j][1] + ? offsets[j][0] + offsets[j][1] * verticesCount + : offsets[j][1] + offsets[j][0] * verticesCount; + + if (!edges.has(hash)) { + edges.add(hash); + + let o1 = offsets[j][0] * 3; + let o2 = offsets[j][1] * 3; + + positions.push( + vertices[o1], vertices[o1 + 1], vertices[o1 + 2], + vertices[o2], vertices[o2 + 1], vertices[o2 + 2] + ); + + if (verticesColors && verticesColors.length > 0) { + colors.push( + verticesColors[o1], verticesColors[o1 + 1], verticesColors[o1 + 2], + verticesColors[o2], verticesColors[o2 + 1], verticesColors[o2 + 2] + ); + } + + if (attr && attr.length > 0) { + attribute.push(attr[offsets[j][0]], attr[offsets[j][1]]); + } + } + } + } + + positions = new Float32Array(positions); + attribute = new Float32Array(attribute); + + if (colorRange && colorMap && attribute.length > 0 && colorRange.length > 0 && colorMap.length > 0) { + const canvas = colorMapHelper.createCanvasGradient(colorMap, 1024); + const texture = new THREE.CanvasTexture(canvas, THREE.UVMapping, THREE.ClampToEdgeWrapping, + THREE.ClampToEdgeWrapping, THREE.NearestFilter, THREE.NearestFilter); + texture.needsUpdate = true; + + material.uniforms.useMap.value = 1.0; + material.uniforms.map.value = texture; + + uvs = new Float32Array(attribute.length); + + for (let i = 0; i < attribute.length; i++) { + uvs[i] = (attribute[i] - colorRange[0]) / (colorRange[1] - colorRange[0]); + } + + colors = null; + } + + const line = new MeshLine.MeshLine(); + + line.setGeometry(positions, true, null, colors, uvs); + line.geometry.computeBoundingSphere(); + line.geometry.computeBoundingBox(); + + const object = new THREE.Mesh(line.geometry, material); + object.userData.meshLine = line; + object.userData.lastPositions = new Float32Array(positions); + object.userData.lastUVs = uvs; + object.userData.lastColors = verticesColors; + + modelMatrix.set.apply(modelMatrix, config.model_matrix.data); + object.applyMatrix4(modelMatrix); + + object.updateMatrixWorld(); + + const resizelistenerId = K3D.on(K3D.events.RESIZED, () => { + // update outlines + object.material.uniforms.resolution.value.x = K3D.getWorld().width; + object.material.uniforms.resolution.value.y = K3D.getWorld().height; + }); + + object.onRemove = function () { + K3D.off(K3D.events.RESIZED, resizelistenerId); + if (object.material.uniforms.map.value) { + object.material.uniforms.map.value.dispose(); + object.material.uniforms.map.value = undefined; + } + }; + + return Promise.resolve(object); +} + +function update(config, changes, obj) { + let uvs = obj.userData.lastUVs; + let positions = obj.userData.lastPositions; + const colors = obj.userData.lastColors; + const resolvedChanges = {}; + + if (typeof (obj.geometry.attributes.uv) !== 'undefined') { + if (typeof (changes.color_range) !== 'undefined' && !changes.color_range.timeSeries) { + obj.material.uniforms.low.value = changes.color_range[0]; + obj.material.uniforms.high.value = changes.color_range[1]; + + resolvedChanges.color_range = null; + } + + if (typeof (changes.attribute) !== 'undefined' && !changes.attribute.timeSeries) { + if (changes.attribute.data.length !== obj.geometry.attributes.uv.array.length) { + return false; + } + + uvs = new Float32Array(changes.attribute.data.length); + + for (let i = 0; i < uvs.length; i++) { + uvs[i] = (changes.attribute.data[i] - config.color_range[0]) + / (config.color_range[1] - config.color_range[0]); + } + + obj.userData.lastUVs = uvs; + } + } + + if (typeof (changes.vertices) !== 'undefined' && !changes.vertices.timeSeries) { + if (changes.vertices.data.length !== positions.length) { + return false; + } + + positions = changes.vertices.data; + obj.userData.lastPositions = positions; + } + + if (typeof (changes.attribute) !== 'undefined' || typeof (changes.vertices) !== 'undefined') { + obj.userData.meshLine.setGeometry(positions, false, null, colors, uvs); + + resolvedChanges.attribute = null; + resolvedChanges.vertices = null; + } + + commonUpdate(config, changes, resolvedChanges, obj); + + if (areAllChangesResolve(changes, resolvedChanges)) { + return Promise.resolve({ json: config, obj }); + } + return false; +} + +module.exports = { + create, + update, +}; diff --git a/js/src/providers/threejs/objects/MIP.js b/js/src/providers/threejs/objects/MIP.js index 039c6232..05ba35e5 100644 --- a/js/src/providers/threejs/objects/MIP.js +++ b/js/src/providers/threejs/objects/MIP.js @@ -39,7 +39,7 @@ module.exports = { modelMatrix.set.apply(modelMatrix, config.model_matrix.data); modelMatrix.decompose(translation, rotation, scale); - const texture = new THREE.DataTexture3D( + const texture = new THREE.Data3DTexture( config.volume.data, config.volume.shape[2], config.volume.shape[1], diff --git a/js/src/providers/threejs/objects/MeshVolume.js b/js/src/providers/threejs/objects/MeshVolume.js index 4a2bb7ce..1b7aa0c6 100644 --- a/js/src/providers/threejs/objects/MeshVolume.js +++ b/js/src/providers/threejs/objects/MeshVolume.js @@ -38,7 +38,7 @@ module.exports = { geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3)); geometry.setIndex(new THREE.BufferAttribute(indices, 1)); - const texture = new THREE.DataTexture3D( + const texture = new THREE.Data3DTexture( config.volume.data, config.volume.shape[2], config.volume.shape[1], diff --git a/js/src/providers/threejs/objects/TextureData.js b/js/src/providers/threejs/objects/TextureData.js index e4a4e203..97dbc2d8 100644 --- a/js/src/providers/threejs/objects/TextureData.js +++ b/js/src/providers/threejs/objects/TextureData.js @@ -137,9 +137,9 @@ module.exports = { } if (typeof (changes.attribute) !== 'undefined' && !changes.attribute.timeSeries) { - if (obj.material.uniforms.volumeTexture.value.image.data.constructor === changes.attribute.data.constructor - && obj.material.uniforms.volumeTexture.value.image.width === changes.attribute.shape[1] - && obj.material.uniforms.volumeTexture.value.image.height === changes.attribute.shape[0]) { + if (obj.material.uniforms.map.value.image.data.constructor === changes.attribute.data.constructor + && obj.material.uniforms.map.value.image.width === changes.attribute.shape[1] + && obj.material.uniforms.map.value.image.height === changes.attribute.shape[0]) { obj.material.uniforms.map.value.image.data = changes.attribute.data; obj.material.uniforms.map.value.needsUpdate = true; diff --git a/js/src/providers/threejs/objects/Volume.js b/js/src/providers/threejs/objects/Volume.js index b9a4e947..32f2ca04 100644 --- a/js/src/providers/threejs/objects/Volume.js +++ b/js/src/providers/threejs/objects/Volume.js @@ -66,7 +66,7 @@ module.exports = { modelMatrix.set.apply(modelMatrix, config.model_matrix.data); modelMatrix.decompose(translation, rotation, scale); - const texture = new THREE.DataTexture3D( + const texture = new THREE.Data3DTexture( config.volume.data, config.volume.shape[2], config.volume.shape[1], diff --git a/js/src/providers/threejs/objects/shaders/MeshVolume.vertex.glsl b/js/src/providers/threejs/objects/shaders/MeshVolume.vertex.glsl index 6a51a80f..cda731f2 100644 --- a/js/src/providers/threejs/objects/shaders/MeshVolume.vertex.glsl +++ b/js/src/providers/threejs/objects/shaders/MeshVolume.vertex.glsl @@ -8,8 +8,12 @@ void main() { vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); worldPosition = modelMatrix * vec4( position, 1.0 ); + #include + + gl_Position = projectionMatrix * mvPosition; + gl_Position = projectionMatrix * mvPosition; #include #include -} \ No newline at end of file +} diff --git a/js/src/providers/threejs/provider.js b/js/src/providers/threejs/provider.js index 069b9635..03f48ea1 100644 --- a/js/src/providers/threejs/provider.js +++ b/js/src/providers/threejs/provider.js @@ -5,10 +5,11 @@ require('./helpers/THREE.STLLoader')(THREE); require('./helpers/THREE.CopyShader')(THREE); require('./helpers/THREE.TrackballControls')(THREE); require('./helpers/THREE.OrbitControls')(THREE); -require('./helpers/TransformControls')(THREE); +THREE.TransformControls = require('./helpers/TransformControls').TransformControls; THREE.Mesh.prototype.raycast = threeMeshBVH.acceleratedRaycast; + /** * K3D ThreeJS Provider namespace * @alias module:ThreeJS @@ -46,6 +47,7 @@ module.exports = { */ Objects: { Line: require('./objects/Line'), + Lines: require('./objects/Lines'), MarchingCubes: require('./objects/MarchingCubes'), Mesh: require('./objects/Mesh'), Points: require('./objects/Points'), diff --git a/k3d/__init__.py b/k3d/__init__.py index 134eb97a..dd17082f 100644 --- a/k3d/__init__.py +++ b/k3d/__init__.py @@ -10,6 +10,7 @@ from .factory import (plot, nice_colors, line, + lines, marching_cubes, mesh, points, diff --git a/k3d/factory.py b/k3d/factory.py index 420ce5c9..b8aea6e0 100644 --- a/k3d/factory.py +++ b/k3d/factory.py @@ -16,6 +16,7 @@ from .helpers import check_attribute_range from .objects import ( Line, + Lines, MarchingCubes, Mesh, Points, @@ -67,6 +68,104 @@ default_colormap = matplotlib_color_maps.Inferno +def lines( + vertices, + indices, + indices_type="triangle", + color=_default_color, + colors=[], # lgtm [py/similar-function] + attribute=[], + color_map=None, + color_range=[], + width=0.01, + shader="thick", + radial_segments=8, + name=None, + group=None, + custom_data=None, + compression_level=0, + **kwargs +): + """Create a Line drawable for plotting segments and polylines. + + Arguments: + vertices: `array_like`. + Array with (x, y, z) coordinates of segment endpoints. + indices: `array_like`. + Array of vertex indices: int pair of indices from vertices array. + indices_type: `str`. + Interpretation of indices array + Legal values are: + + :`segment`: indices contains pair of values, + + :`triangle`: indices contains triple of values + color: `int`. + Packed RGB color of the lines (0xff0000 is red, 0xff is blue) when `colors` is empty. + colors: `array_like`. + Array of int: packed RGB colors (0xff0000 is red, 0xff is blue) when attribute, + color_map and color_range are empty. + attribute: `array_like`. + Array of float attribute for the color mapping, coresponding to each vertex. + color_map: `list`. + A list of float quadruplets (attribute value, R, G, B), sorted by attribute value. The first + quadruplet should have value 0.0, the last 1.0; R, G, B are RGB color components in the range 0.0 to 1.0. + color_range: `list`. + A pair [min_value, max_value], which determines the levels of color attribute mapped + to 0 and 1 in the color map respectively. + shader: `str`. + Display style (name of the shader used) of the lines. + Legal values are: + + :`simple`: simple lines, + + :`thick`: thick lines, + + :`mesh`: high precision triangle mesh of segments (high quality and GPU load). + radial_segments: 'int'. + Number of segmented faces around the circumference of the tube + width: `float`. + Thickness of the lines. + name: `string`. + A name of a object + group: `string`. + A name of a group + custom_data: `dict`. + A object with custom data attached to object. + """ + if color_map is None: + color_map = default_colormap + + color_map = ( + np.array(color_map, np.float32) if type(color_map) is not dict else color_map + ) + attribute = ( + np.array(attribute, np.float32) if type(attribute) is not dict else attribute + ) + color_range = check_attribute_range(attribute, color_range) + + return process_transform_arguments( + Lines( + vertices=vertices, + indices=indices, + indices_type=indices_type, + color=color, + width=width, + shader=shader, + radial_segments=radial_segments, + colors=colors, + attribute=attribute, + color_map=color_map, + color_range=color_range, + name=name, + group=group, + custom_data=custom_data, + compression_level=compression_level, + ), + **kwargs + ) + + def line( vertices, color=_default_color, @@ -79,6 +178,7 @@ def line( radial_segments=8, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -116,9 +216,12 @@ def line( name: `string`. A name of a object group: `string`. - A name of a group""" + A name of a group + custom_data: `dict`. + A object with custom data attached to object.""" if color_map is None: color_map = default_colormap + color_map = ( np.array(color_map, np.float32) if type(color_map) is not dict else color_map ) @@ -140,6 +243,7 @@ def line( color_range=color_range, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -158,6 +262,7 @@ def marching_cubes( spacings_z=[], name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -196,7 +301,9 @@ def marching_cubes( name: `string`. A name of a object group: `string`. - A name of a group + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" return process_transform_arguments( @@ -212,6 +319,7 @@ def marching_cubes( opacity=opacity, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -239,6 +347,7 @@ def mesh( uvs=None, name=None, group=None, + custom_data=None, compression_level=0, triangles_attribute=[], **kwargs @@ -279,10 +388,6 @@ def mesh( typles should have value 0.0, the last 1.0; opacity is in the range 0.0 to 1.0. side: `string`. Control over which side to render for a mesh. Legal values are `front`, `back`, `double`. - name: `string`. - A name of a object - group: `string`. - A name of a group texture: `bytes`. Image data in a specific format. texture_file_format: `str`. @@ -290,6 +395,12 @@ def mesh( for example 'jpeg', 'png', 'gif', 'tiff'. uvs: `array_like`. Array of float uvs for the texturing, coresponding to each vertex. + name: `string`. + A name of a object + group: `string`. + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" if color_map is None: @@ -338,11 +449,12 @@ def mesh( volume_bounds=volume_bounds, opacity_function=opacity_function, side=side, - name=name, - group=group, texture=texture, uvs=uvs, texture_file_format=texture_file_format, + name=name, + group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -364,6 +476,7 @@ def points( opacity_function=[], name=None, group=None, + custom_data=None, compression_level=0, mesh_detail=2, **kwargs @@ -416,7 +529,9 @@ def points( name: `string`. A name of a object group: `string`. - A name of a group""" + A name of a group + custom_data: `dict`. + A object with custom data attached to object.""" if color_map is None: color_map = default_colormap @@ -436,12 +551,13 @@ def points( opacity=opacity, opacities=opacities, mesh_detail=mesh_detail, - name=name, - group=group, attribute=attribute, color_map=color_map, color_range=color_range, opacity_function=opacity_function, + name=name, + group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -456,6 +572,7 @@ def stl( flat_shading=True, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -473,7 +590,9 @@ def stl( name: `string`. A name of a object group: `string`. - A name of a group""" + A name of a group + custom_data: `dict`. + A object with custom data attached to object.""" plain = isinstance(stl, six.string_types) return process_transform_arguments( @@ -485,6 +604,7 @@ def stl( flat_shading=flat_shading, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -501,6 +621,7 @@ def surface( color_range=[], name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -535,7 +656,9 @@ def surface( name: `string`. A name of a object group: `string`. - A name of a group + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" @@ -556,6 +679,7 @@ def surface( color_range=color_range, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -574,6 +698,7 @@ def text( is_html=False, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -597,12 +722,14 @@ def text( First letter: 'l', 'c' or 'r': left, center or right Second letter: 't', 'c' or 'b': top, center or bottom. + size: `float`. + Font size in 'em' HTML units. name: `string`. A name of a object group: `string`. - A name of a group - size: `float`. - Font size in 'em' HTML units. + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" @@ -618,6 +745,7 @@ def text( label_box=label_box, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -632,9 +760,10 @@ def text2d( size=1.0, reference_point="lt", label_box=True, + is_html=False, name=None, group=None, - is_html=False, + custom_data=None, compression_level=0, ): """Create a Text2d drawable for 2D-positioned (viewport bound, OSD) labels. @@ -656,12 +785,14 @@ def text2d( Second letter: 't', 'c' or 'b': top, center or bottom. label_box: `Boolean`. Label background box. + size: `float`. + Font size in 'em' HTML units. name: `string`. A name of a object group: `string`. - A name of a group - size: `float`. - Font size in 'em' HTML units.""" + A name of a group + custom_data: `dict`. + A object with custom data attached to object.""" return Text2d( position=position, @@ -673,6 +804,7 @@ def text2d( label_box=label_box, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ) @@ -684,12 +816,13 @@ def label( color=_default_color, on_top=True, size=1.0, - name=None, - group=None, max_length=0.8, mode="dynamic", is_html=False, label_box=True, + name=None, + group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -710,14 +843,16 @@ def label( Maximum length of line in % of half screen size (only for mode='dynamic'). mode: `str`. Label node. Can be 'dynamic', 'local' or 'side'. - name: `string`. - A name of a object - group: `string`. - A name of a group is_html: `Boolean`. Whether text should be interpreted as HTML insted of KaTeX. size: `float`. Font size in 'em' HTML units. + name: `string`. + A name of a object + group: `string`. + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" @@ -734,6 +869,7 @@ def label( label_box=label_box, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -751,6 +887,7 @@ def texture( interpolation=True, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -789,13 +926,15 @@ def texture( to 0 and 1 in the color map respectively. interpolation: `bool`. Whether data should be interpolatedor not. - name: `string`. - A name of a object - group: `string`. - A name of a group puv: `list`. A list of float triplets (x,y,z). The first triplet mean a position of left-bottom corner of texture. Second and third triplets means a base of coordinate system for texture. + name: `string`. + A name of a object + group: `string`. + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" if color_map is None: @@ -813,9 +952,10 @@ def texture( attribute=attribute, opacity_function=opacity_function, puv=puv, + interpolation=interpolation, name=name, group=group, - interpolation=interpolation, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -833,6 +973,7 @@ def texture_text( size=1.0, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -856,13 +997,15 @@ def texture_text( font_weight: `int`. Thickness of the characters in HTML-like units from the range (100, 900), where 400 is normal and 600 is bold font. + font_size: `int`. + The font size inside the sprite texture in px units. This does not affect the size of the + text in the scene, only the accuracy and raster size of the texture. name: `string`. A name of a object group: `string`. - A name of a group - font_size: `int`. - The font size inside the sprite texture in px units. This does not affect the size of the - text in the scene, only the accuracy and raster size of the texture.""" + A name of a group + custom_data: `dict`. + A object with custom data attached to object.""" return process_transform_arguments( TextureText( text=text, @@ -874,6 +1017,7 @@ def texture_text( font_weight=font_weight, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -893,6 +1037,7 @@ def vector_field( line_width=0.01, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -935,7 +1080,9 @@ def vector_field( name: `string`. A name of a object group: `string`. - A name of a group + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" return process_transform_arguments( @@ -950,6 +1097,7 @@ def vector_field( scale=scale, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -971,6 +1119,7 @@ def vectors( line_width=0.01, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -1012,8 +1161,8 @@ def vectors( A name of a object group: `string`. A name of a group - group: `string`. - A name of a group""" + custom_data: `dict`. + A object with custom data attached to object.""" return process_transform_arguments( Vectors( vectors=vectors if vectors is not None else origins, @@ -1028,6 +1177,7 @@ def vectors( line_width=line_width, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -1042,10 +1192,11 @@ def voxels( outlines=True, outlines_color=0, opacity=1.0, + bounds=None, name=None, group=None, + custom_data=None, compression_level=0, - bounds=None, **kwargs ): """Create a Voxels drawable for 3D volumetric data. @@ -1087,7 +1238,9 @@ def voxels( name: `string`. A name of a object group: `string`. - A name of a group + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" if color_map is None: @@ -1108,6 +1261,7 @@ def voxels( opacity=opacity, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -1123,10 +1277,11 @@ def sparse_voxels( outlines=True, outlines_color=0, opacity=1.0, + bounds=None, name=None, group=None, + custom_data=None, compression_level=0, - bounds=None, **kwargs ): """Create a Voxels drawable for 3D volumetric data. @@ -1159,7 +1314,9 @@ def sparse_voxels( name: `string`. A name of a object group: `string`. - A name of a group + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" if color_map is None: @@ -1188,6 +1345,7 @@ def sparse_voxels( opacity=opacity, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -1206,6 +1364,7 @@ def voxels_group( opacity=1.0, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -1241,7 +1400,9 @@ def voxels_group( name: `string`. A name of a object group: `string`. - A name of a group + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" if color_map is None: @@ -1266,6 +1427,7 @@ def voxels_group( opacity=opacity, name=name, group=group, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -1290,6 +1452,7 @@ def volume( ray_samples_count=16, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -1346,7 +1509,9 @@ def volume( name: `string`. A name of a object group: `string`. - A name of a group + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" @@ -1380,6 +1545,7 @@ def volume( focal_length=focal_length, name=name, group=group, + custom_data=custom_data, ray_samples_count=ray_samples_count, ), **kwargs @@ -1396,6 +1562,7 @@ def mip( gradient_step=0.005, name=None, group=None, + custom_data=None, compression_level=0, **kwargs ): @@ -1430,7 +1597,9 @@ def mip( name: `string`. A name of a object group: `string`. - A name of a group + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" if color_map is None: @@ -1451,11 +1620,12 @@ def mip( color_map=color_map, opacity_function=opacity_function, color_range=color_range, - compression_level=compression_level, samples=samples, gradient_step=gradient_step, name=name, group=group, + custom_data=custom_data, + compression_level=compression_level, ), **kwargs ) @@ -1473,11 +1643,12 @@ def vtk_poly_data( flat_shading=True, volume_bounds=[], opacity_function=[], + color_range=[], + cell_color_attribute=None, name=None, group=None, - color_range=[], + custom_data=None, compression_level=0, - cell_color_attribute=None, **kwargs ): """Create a Mesh drawable from given vtkPolyData. @@ -1525,7 +1696,9 @@ def vtk_poly_data( name: `string`. A name of a object group: `string`. - A name of a group + A name of a group + custom_data: `dict`. + A object with custom data attached to object. kwargs: `dict`. Dictionary arguments to configure transform and model_matrix.""" if color_map is None: @@ -1581,9 +1754,10 @@ def vtk_poly_data( texture=None, opacity_function=opacity_function, side=side, + flat_shading=flat_shading, name=name, group=group, - flat_shading=flat_shading, + custom_data=custom_data, compression_level=compression_level, ), **kwargs @@ -1630,8 +1804,7 @@ def plot( time=0.0, axes=["x", "y", "z"], axes_helper=1.0, - name=None, - group=None, + axes_helper_colors=[0xff0000, 0x00ff00, 0x0000ff], camera_mode="trackball", snapshot_type='full', auto_rendering=True, @@ -1643,6 +1816,8 @@ def plot( camera_pan_speed=0.3, camera_damping_factor=0.0, fps=25.0, + name=None, + custom_data=None ): """Create a K3D Plot widget. @@ -1699,12 +1874,12 @@ def plot( Axes labels for plot. axes_helper: `Float`. Axes helper size. + axes_helper_colors: `List`. + List of triple packed RGB color of the axes helper (0xff0000 is red, 0xff is blue). time: `list`. Time value (used in TimeSeries) name: `string`. Name of the plot. Used to filenames of snapshot/screenshot etc. - group: `string`. - A name of a group camera_mode: `str`. Mode of camera. @@ -1720,7 +1895,9 @@ def plot( fps: `Float`. Fps of animation. grid: `array_like`. - 6-element tuple specifying the bounds of the plot grid (x0, y0, z0, x1, y1, z1).""" + 6-element tuple specifying the bounds of the plot grid (x0, y0, z0, x1, y1, z1). + custom_data: `dict`. + A object with custom data attached to object.""" return Plot( antialias=antialias, @@ -1740,10 +1917,10 @@ def plot( grid=grid, axes=axes, axes_helper=axes_helper, + axes_helper_colors=axes_helper_colors, screenshot_scale=screenshot_scale, camera_fov=camera_fov, name=name, - group=group, camera_mode=camera_mode, snapshot_type=snapshot_type, camera_no_zoom=camera_no_zoom, @@ -1755,4 +1932,5 @@ def plot( camera_pan_speed=camera_pan_speed, auto_rendering=auto_rendering, fps=fps, + custom_data=custom_data ) diff --git a/k3d/objects.py b/k3d/objects.py index 86f36461..da739ecb 100644 --- a/k3d/objects.py +++ b/k3d/objects.py @@ -92,6 +92,7 @@ class Drawable(widgets.Widget): id = Integer().tag(sync=True) name = Unicode(default_value=None, allow_none=True).tag(sync=True) group = Unicode(default_value=None, allow_none=True).tag(sync=True) + custom_data = Dict(default_value=None, allow_none=True).tag(sync=True) visible = TimeSeries(Bool(True)).tag(sync=True) compression_level = Integer().tag(sync=True) @@ -318,6 +319,100 @@ def _validate_colors(self, proposal): return proposal["value"] +class Lines(Drawable): + """ + A set of line (polyline) made up of indices. + + Attributes: + vertices: `array_like`. + An array with (x, y, z) coordinates of segment endpoints. + indices: `array_like`. + Array of vertex indices: int pair of indices from vertices array. + indices_type: `str`. + Interpretation of indices array + Legal values are: + + :`segment`: indices contains pair of values, + + :`triangle`: indices contains triple of values + colors: `array_like`. + Same-length array of (`int`) packed RGB color of the points (0xff0000 is red, 0xff is blue). + color: `int`. + Packed RGB color of the lines (0xff0000 is red, 0xff is blue) when `colors` is empty. + attribute: `array_like`. + Array of float attribute for the color mapping, coresponding to each vertex. + color_map: `list`. + A list of float quadruplets (attribute value, R, G, B), sorted by attribute value. The first + quadruplet should have value 0.0, the last 1.0; R, G, B are RGB color components in the range 0.0 to 1.0. + color_range: `list`. + A pair [min_value, max_value], which determines the levels of color attribute mapped + to 0 and 1 in the color map respectively. + width: `float`. + The thickness of the lines. + shader: `str`. + Display style (name of the shader used) of the lines. + Legal values are: + + :`simple`: simple lines, + + :`thick`: thick lines, + + :`mesh`: high precision triangle mesh of segments (high quality and GPU load). + radial_segments: 'int': + Number of segmented faces around the circumference of the tube. + model_matrix: `array_like`. + 4x4 model transform matrix. + """ + + type = Unicode(read_only=True).tag(sync=True) + + vertices = TimeSeries(Array(dtype=np.float32)).tag( + sync=True, **array_serialization_wrap("vertices") + ) + indices = Array(dtype=np.float32).tag(sync=True, **array_serialization_wrap("indices")) + indices_type = TimeSeries(Unicode()).tag(sync=True) + colors = TimeSeries(Array(dtype=np.uint32)).tag( + sync=True, **array_serialization_wrap("colors") + ) + color = TimeSeries(Int(min=0, max=0xFFFFFF)).tag(sync=True) + width = TimeSeries(Float(min=EPSILON, default_value=0.01)).tag(sync=True) + attribute = TimeSeries(Array(dtype=np.float32)).tag( + sync=True, **array_serialization_wrap("attribute") + ) + color_map = TimeSeries(Array(dtype=np.float32)).tag( + sync=True, **array_serialization_wrap("color_map") + ) + color_range = TimeSeries(ListOrArray(minlen=2, maxlen=2, empty_ok=True)).tag( + sync=True + ) + shader = TimeSeries(Unicode()).tag(sync=True) + radial_segments = TimeSeries(Int()).tag(sync=True) + model_matrix = TimeSeries(Array(dtype=np.float32)).tag( + sync=True, **array_serialization_wrap("model_matrix") + ) + + def __init__(self, **kwargs): + super(Lines, self).__init__(**kwargs) + + self.set_trait("type", "Lines") + + def get_bounding_box(self): + return get_bounding_box_points(self.vertices, self.model_matrix) + + @validate("colors") + def _validate_colors(self, proposal): + if type(proposal["value"]) is dict or type(self.vertices) is dict: + return proposal["value"] + + required = self.vertices.size // 3 # (x, y, z) triplet per 1 color + actual = proposal["value"].size + if actual != 0 and required != actual: + raise TraitError( + "colors has wrong size: %s (%s required)" % (actual, required) + ) + return proposal["value"] + + class MarchingCubes(DrawableWithCallback): """ An isosurface in a scalar field obtained through Marching Cubes algorithm. diff --git a/k3d/plot.py b/k3d/plot.py index e7fcfba1..15199e01 100644 --- a/k3d/plot.py +++ b/k3d/plot.py @@ -4,12 +4,12 @@ import ipywidgets as widgets from IPython.display import display from functools import wraps -from traitlets import Unicode, Bool, Int, List, Float +from traitlets import Unicode, Bool, Int, List, Float, Dict from ._version import __version__ as version from .objects import (Line, Label, MIP, MarchingCubes, Mesh, Points, STL, SparseVoxels, Surface, Text, Text2d, Texture, TextureText, VectorField, Vectors, Volume, Voxels, - VoxelsGroup, ListOrArray, Drawable, TimeSeries) + VoxelsGroup, ListOrArray, Drawable, TimeSeries, VoxelChunk) objects_map = { 'Line': Line, @@ -87,6 +87,10 @@ class Plot(widgets.DOMWidget): Can be 'full', 'online' or 'inline'. axes: `list`. Axes labels for plot. + axes_helper: `Float`. + Axes helper size. + axes_helper_colors: `List`. + List of triple packed RGB color of the axes helper (0xff0000 is red, 0xff is blue). time: `list`. Time value (used in TimeSeries) name: `string`. @@ -186,9 +190,12 @@ class Plot(widgets.DOMWidget): name = Unicode(default_value=None, allow_none=True).tag(sync=True) axes = List(minlen=3, maxlen=3, default_value=["x", "y", "z"]).tag(sync=True) axes_helper = Float().tag(sync=True) + axes_helper_colors = List(minlen=3, maxlen=3, + default_value=[0xff0000, 0x00ff00, 0x0000ff]).tag(sync=True) mode = Unicode().tag(sync=True) camera_mode = Unicode().tag(sync=True) manipulate_mode = Unicode().tag(sync=True) + custom_data = Dict(default_value=None, allow_none=True).tag(sync=True) objects = [] @@ -221,6 +228,7 @@ def __init__( camera_fov=45.0, camera_damping_factor=0.0, axes_helper=1.0, + axes_helper_colors=[0xff0000, 0x00ff00, 0x0000ff], name=None, mode="view", camera_mode="trackball", @@ -229,6 +237,7 @@ def __init__( fps=25.0, grid_color=0xE6E6E6, label_color=0x444444, + custom_data=None, *args, **kwargs ): @@ -263,6 +272,7 @@ def __init__( self.camera_fov = camera_fov self.axes = axes self.axes_helper = axes_helper + self.axes_helper_colors = axes_helper_colors self.name = name self.mode = mode self.snapshot_type = snapshot_type @@ -270,6 +280,7 @@ def __init__( self.manipulate_mode = manipulate_mode self.auto_rendering = auto_rendering self.camera = [] + self.custom_data = custom_data self.object_ids = [] self.objects = [] @@ -457,6 +468,11 @@ def load_binary_snapshot(self, data): k: from_json(o[k]) for k in o.keys() if k != 'type' } + # force to use current version + attributes['_model_module'] = 'k3d' + attributes['_model_module_version'] = version + attributes['_view_module_version'] = version + if name == 'objects': o = objects_map[o['type']](**attributes) self += o @@ -514,7 +530,9 @@ def get_plot_params(self): "height": self.height, "cameraFov": self.camera_fov, "axesHelper": self.axes_helper, + "axesHelperColors": self.axes_helper_colors, "cameraAnimation": self.camera_animation, + "customData": self.custom_data, "fps": self.fps, } diff --git a/k3d/test/plot_compare.py b/k3d/test/plot_compare.py index 74470732..1a6bad01 100644 --- a/k3d/test/plot_compare.py +++ b/k3d/test/plot_compare.py @@ -18,7 +18,10 @@ def prepare(): def compare(name, only_canvas=True, threshold=0.2, camera_factor=1.0): pytest.headless.sync(hold_until_refreshed=True) - pytest.headless.camera_reset(camera_factor) + + if camera_factor is not None: + pytest.headless.camera_reset(camera_factor) + screenshot = pytest.headless.get_screenshot(only_canvas) result = Image.open(BytesIO(screenshot)) diff --git a/k3d/test/references/line_advanced.png b/k3d/test/references/line_advanced.png index 3035ec71..01a148ae 100644 Binary files a/k3d/test/references/line_advanced.png and b/k3d/test/references/line_advanced.png differ diff --git a/k3d/test/references/line_mesh.png b/k3d/test/references/line_mesh.png index 27633715..97827172 100644 Binary files a/k3d/test/references/line_mesh.png and b/k3d/test/references/line_mesh.png differ diff --git a/k3d/test/references/line_mesh_clipping_plane.png b/k3d/test/references/line_mesh_clipping_plane.png index 7afb9c96..1fcd9fe6 100644 Binary files a/k3d/test/references/line_mesh_clipping_plane.png and b/k3d/test/references/line_mesh_clipping_plane.png differ diff --git a/k3d/test/references/line_simple.png b/k3d/test/references/line_simple.png index 7f6943ed..604a56f0 100644 Binary files a/k3d/test/references/line_simple.png and b/k3d/test/references/line_simple.png differ diff --git a/k3d/test/references/line_simple_clipping_plane.png b/k3d/test/references/line_simple_clipping_plane.png index 58e35521..858a245b 100644 Binary files a/k3d/test/references/line_simple_clipping_plane.png and b/k3d/test/references/line_simple_clipping_plane.png differ diff --git a/k3d/test/references/line_simple_red.png b/k3d/test/references/line_simple_red.png index fe1577de..05b78cb9 100644 Binary files a/k3d/test/references/line_simple_red.png and b/k3d/test/references/line_simple_red.png differ diff --git a/k3d/test/references/line_simplified_mesh.png b/k3d/test/references/line_simplified_mesh.png index 7d4ecd82..e3e2cc96 100644 Binary files a/k3d/test/references/line_simplified_mesh.png and b/k3d/test/references/line_simplified_mesh.png differ diff --git a/k3d/test/references/line_thick.png b/k3d/test/references/line_thick.png index bd4befa0..2cc7cf0e 100644 Binary files a/k3d/test/references/line_thick.png and b/k3d/test/references/line_thick.png differ diff --git a/k3d/test/references/line_thick_clipping_plane.png b/k3d/test/references/line_thick_clipping_plane.png index dc600324..2111772f 100644 Binary files a/k3d/test/references/line_thick_clipping_plane.png and b/k3d/test/references/line_thick_clipping_plane.png differ diff --git a/k3d/test/references/lines_mesh.png b/k3d/test/references/lines_mesh.png new file mode 100644 index 00000000..1a4eb886 Binary files /dev/null and b/k3d/test/references/lines_mesh.png differ diff --git a/k3d/test/references/lines_mesh_attribute.png b/k3d/test/references/lines_mesh_attribute.png new file mode 100644 index 00000000..09905ac7 Binary files /dev/null and b/k3d/test/references/lines_mesh_attribute.png differ diff --git a/k3d/test/references/lines_mesh_attribute_segment.png b/k3d/test/references/lines_mesh_attribute_segment.png new file mode 100644 index 00000000..798d5d8d Binary files /dev/null and b/k3d/test/references/lines_mesh_attribute_segment.png differ diff --git a/k3d/test/references/lines_mesh_colors.png b/k3d/test/references/lines_mesh_colors.png new file mode 100644 index 00000000..ca362235 Binary files /dev/null and b/k3d/test/references/lines_mesh_colors.png differ diff --git a/k3d/test/references/lines_simple.png b/k3d/test/references/lines_simple.png new file mode 100644 index 00000000..f024bdfb Binary files /dev/null and b/k3d/test/references/lines_simple.png differ diff --git a/k3d/test/references/lines_simple_attribute.png b/k3d/test/references/lines_simple_attribute.png new file mode 100644 index 00000000..2c4f8353 Binary files /dev/null and b/k3d/test/references/lines_simple_attribute.png differ diff --git a/k3d/test/references/lines_simple_attribute_segment.png b/k3d/test/references/lines_simple_attribute_segment.png new file mode 100644 index 00000000..a5a874b3 Binary files /dev/null and b/k3d/test/references/lines_simple_attribute_segment.png differ diff --git a/k3d/test/references/lines_simple_colors.png b/k3d/test/references/lines_simple_colors.png new file mode 100644 index 00000000..bfbfdbb1 Binary files /dev/null and b/k3d/test/references/lines_simple_colors.png differ diff --git a/k3d/test/references/lines_thick.png b/k3d/test/references/lines_thick.png new file mode 100644 index 00000000..708eebed Binary files /dev/null and b/k3d/test/references/lines_thick.png differ diff --git a/k3d/test/references/lines_thick_attribute.png b/k3d/test/references/lines_thick_attribute.png new file mode 100644 index 00000000..4b52837e Binary files /dev/null and b/k3d/test/references/lines_thick_attribute.png differ diff --git a/k3d/test/references/lines_thick_attribute_segment.png b/k3d/test/references/lines_thick_attribute_segment.png new file mode 100644 index 00000000..64670a06 Binary files /dev/null and b/k3d/test/references/lines_thick_attribute_segment.png differ diff --git a/k3d/test/references/lines_thick_colors.png b/k3d/test/references/lines_thick_colors.png new file mode 100644 index 00000000..7149e84e Binary files /dev/null and b/k3d/test/references/lines_thick_colors.png differ diff --git a/k3d/test/references/linux/labels.png b/k3d/test/references/linux/labels.png index d49540ca..df1976be 100644 Binary files a/k3d/test/references/linux/labels.png and b/k3d/test/references/linux/labels.png differ diff --git a/k3d/test/references/linux/labels_without_box.png b/k3d/test/references/linux/labels_without_box.png index f6b7058d..e9970f8d 100644 Binary files a/k3d/test/references/linux/labels_without_box.png and b/k3d/test/references/linux/labels_without_box.png differ diff --git a/k3d/test/references/linux/text.png b/k3d/test/references/linux/text.png index 967c6ec2..8c028fde 100644 Binary files a/k3d/test/references/linux/text.png and b/k3d/test/references/linux/text.png differ diff --git a/k3d/test/references/linux/text2d.png b/k3d/test/references/linux/text2d.png index a7a4db89..bb9782ea 100644 Binary files a/k3d/test/references/linux/text2d.png and b/k3d/test/references/linux/text2d.png differ diff --git a/k3d/test/references/linux/text2d_without_box.png b/k3d/test/references/linux/text2d_without_box.png index 6ad524ad..c24eb2fe 100644 Binary files a/k3d/test/references/linux/text2d_without_box.png and b/k3d/test/references/linux/text2d_without_box.png differ diff --git a/k3d/test/references/linux/text_without_box.png b/k3d/test/references/linux/text_without_box.png index 3e209cb2..5eb76407 100644 Binary files a/k3d/test/references/linux/text_without_box.png and b/k3d/test/references/linux/text_without_box.png differ diff --git a/k3d/test/references/linux/texture_text.png b/k3d/test/references/linux/texture_text.png index 300ae9f0..6c8e323a 100644 Binary files a/k3d/test/references/linux/texture_text.png and b/k3d/test/references/linux/texture_text.png differ diff --git a/k3d/test/references/linux/vectors_labels.png b/k3d/test/references/linux/vectors_labels.png index bbea60d2..061846d2 100644 Binary files a/k3d/test/references/linux/vectors_labels.png and b/k3d/test/references/linux/vectors_labels.png differ diff --git a/k3d/test/references/linux/vectors_labels_dynamic_head_size.png b/k3d/test/references/linux/vectors_labels_dynamic_head_size.png index 1c9e1235..7a58d909 100644 Binary files a/k3d/test/references/linux/vectors_labels_dynamic_head_size.png and b/k3d/test/references/linux/vectors_labels_dynamic_head_size.png differ diff --git a/k3d/test/references/marching_cubes.png b/k3d/test/references/marching_cubes.png index 96ce7762..90fc1745 100644 Binary files a/k3d/test/references/marching_cubes.png and b/k3d/test/references/marching_cubes.png differ diff --git a/k3d/test/references/marching_cubes_dynamic_level.png b/k3d/test/references/marching_cubes_dynamic_level.png index f72b4401..4d0833e2 100644 Binary files a/k3d/test/references/marching_cubes_dynamic_level.png and b/k3d/test/references/marching_cubes_dynamic_level.png differ diff --git a/k3d/test/references/marching_cubes_non_uniformly_spaced.png b/k3d/test/references/marching_cubes_non_uniformly_spaced.png index 81d93c52..9739fa73 100644 Binary files a/k3d/test/references/marching_cubes_non_uniformly_spaced.png and b/k3d/test/references/marching_cubes_non_uniformly_spaced.png differ diff --git a/k3d/test/references/marching_cubes_smoothed.png b/k3d/test/references/marching_cubes_smoothed.png index c2e96bb3..9c75e754 100644 Binary files a/k3d/test/references/marching_cubes_smoothed.png and b/k3d/test/references/marching_cubes_smoothed.png differ diff --git a/k3d/test/references/marching_cubes_wireframe.png b/k3d/test/references/marching_cubes_wireframe.png index 30bdc2c4..0dbd54f4 100644 Binary files a/k3d/test/references/marching_cubes_wireframe.png and b/k3d/test/references/marching_cubes_wireframe.png differ diff --git a/k3d/test/references/mesh.png b/k3d/test/references/mesh.png index 0e94fb80..287b6a98 100644 Binary files a/k3d/test/references/mesh.png and b/k3d/test/references/mesh.png differ diff --git a/k3d/test/references/mesh_advanced.png b/k3d/test/references/mesh_advanced.png index bbe6b87d..8dbb42f5 100644 Binary files a/k3d/test/references/mesh_advanced.png and b/k3d/test/references/mesh_advanced.png differ diff --git a/k3d/test/references/mesh_advanced_opacity.png b/k3d/test/references/mesh_advanced_opacity.png index e90f7ac1..cf2d79c8 100644 Binary files a/k3d/test/references/mesh_advanced_opacity.png and b/k3d/test/references/mesh_advanced_opacity.png differ diff --git a/k3d/test/references/mesh_advanced_smoothed.png b/k3d/test/references/mesh_advanced_smoothed.png index d3f633e5..7a24a4d7 100644 Binary files a/k3d/test/references/mesh_advanced_smoothed.png and b/k3d/test/references/mesh_advanced_smoothed.png differ diff --git a/k3d/test/references/mesh_advanced_wireframe.png b/k3d/test/references/mesh_advanced_wireframe.png index c77d8eba..5b59d45c 100644 Binary files a/k3d/test/references/mesh_advanced_wireframe.png and b/k3d/test/references/mesh_advanced_wireframe.png differ diff --git a/k3d/test/references/mesh_attribute.png b/k3d/test/references/mesh_attribute.png index 42f559af..94dd5e81 100644 Binary files a/k3d/test/references/mesh_attribute.png and b/k3d/test/references/mesh_attribute.png differ diff --git a/k3d/test/references/mesh_attribute_advanced.png b/k3d/test/references/mesh_attribute_advanced.png index 9fed88ae..933271a5 100644 Binary files a/k3d/test/references/mesh_attribute_advanced.png and b/k3d/test/references/mesh_attribute_advanced.png differ diff --git a/k3d/test/references/mesh_attribute_advanced_clipping_planes.png b/k3d/test/references/mesh_attribute_advanced_clipping_planes.png index 4a1ed782..abf55a09 100644 Binary files a/k3d/test/references/mesh_attribute_advanced_clipping_planes.png and b/k3d/test/references/mesh_attribute_advanced_clipping_planes.png differ diff --git a/k3d/test/references/mesh_triangle_attribute.png b/k3d/test/references/mesh_triangle_attribute.png index 90b9432f..113127b1 100644 Binary files a/k3d/test/references/mesh_triangle_attribute.png and b/k3d/test/references/mesh_triangle_attribute.png differ diff --git a/k3d/test/references/mesh_volume_data.png b/k3d/test/references/mesh_volume_data.png index f1b61c38..5081de23 100644 Binary files a/k3d/test/references/mesh_volume_data.png and b/k3d/test/references/mesh_volume_data.png differ diff --git a/k3d/test/references/mip.png b/k3d/test/references/mip.png index 5b462698..9e12aacc 100644 Binary files a/k3d/test/references/mip.png and b/k3d/test/references/mip.png differ diff --git a/k3d/test/references/mip_opacity_function.png b/k3d/test/references/mip_opacity_function.png index 6c4317a9..546ad1bd 100644 Binary files a/k3d/test/references/mip_opacity_function.png and b/k3d/test/references/mip_opacity_function.png differ diff --git a/k3d/test/references/points_3d.png b/k3d/test/references/points_3d.png index 00f011c0..b165efd8 100644 Binary files a/k3d/test/references/points_3d.png and b/k3d/test/references/points_3d.png differ diff --git a/k3d/test/references/points_3dSpecular.png b/k3d/test/references/points_3dSpecular.png index 98ebee06..518191a7 100644 Binary files a/k3d/test/references/points_3dSpecular.png and b/k3d/test/references/points_3dSpecular.png differ diff --git a/k3d/test/references/points_3dSpecular_sizes.png b/k3d/test/references/points_3dSpecular_sizes.png index 7151d436..27c88cd6 100644 Binary files a/k3d/test/references/points_3dSpecular_sizes.png and b/k3d/test/references/points_3dSpecular_sizes.png differ diff --git a/k3d/test/references/points_3d_clipping_plane.png b/k3d/test/references/points_3d_clipping_plane.png index 1581dbf5..a65d0d80 100644 Binary files a/k3d/test/references/points_3d_clipping_plane.png and b/k3d/test/references/points_3d_clipping_plane.png differ diff --git a/k3d/test/references/points_3d_no_opacity.png b/k3d/test/references/points_3d_no_opacity.png index 1266e4de..2de23e70 100644 Binary files a/k3d/test/references/points_3d_no_opacity.png and b/k3d/test/references/points_3d_no_opacity.png differ diff --git a/k3d/test/references/points_3d_no_opacity_with_plane.png b/k3d/test/references/points_3d_no_opacity_with_plane.png index 09447f1a..a1b84ebb 100644 Binary files a/k3d/test/references/points_3d_no_opacity_with_plane.png and b/k3d/test/references/points_3d_no_opacity_with_plane.png differ diff --git a/k3d/test/references/points_dot.png b/k3d/test/references/points_dot.png index 5485445f..558d88f6 100644 Binary files a/k3d/test/references/points_dot.png and b/k3d/test/references/points_dot.png differ diff --git a/k3d/test/references/points_flat.png b/k3d/test/references/points_flat.png index 1b534557..a55a521d 100644 Binary files a/k3d/test/references/points_flat.png and b/k3d/test/references/points_flat.png differ diff --git a/k3d/test/references/points_mesh.png b/k3d/test/references/points_mesh.png index fdc68b74..ade9d9ea 100644 Binary files a/k3d/test/references/points_mesh.png and b/k3d/test/references/points_mesh.png differ diff --git a/k3d/test/references/points_mesh_clipping_plane.png b/k3d/test/references/points_mesh_clipping_plane.png index 57506afa..cc94cef1 100644 Binary files a/k3d/test/references/points_mesh_clipping_plane.png and b/k3d/test/references/points_mesh_clipping_plane.png differ diff --git a/k3d/test/references/points_mesh_high_detail.png b/k3d/test/references/points_mesh_high_detail.png index 2dc21d93..0774bad2 100644 Binary files a/k3d/test/references/points_mesh_high_detail.png and b/k3d/test/references/points_mesh_high_detail.png differ diff --git a/k3d/test/references/points_mesh_low_detail.png b/k3d/test/references/points_mesh_low_detail.png index 17eecc92..270a47c6 100644 Binary files a/k3d/test/references/points_mesh_low_detail.png and b/k3d/test/references/points_mesh_low_detail.png differ diff --git a/k3d/test/references/points_mesh_sizes.png b/k3d/test/references/points_mesh_sizes.png index 5a2b5d9a..9771d192 100644 Binary files a/k3d/test/references/points_mesh_sizes.png and b/k3d/test/references/points_mesh_sizes.png differ diff --git a/k3d/test/references/stl.png b/k3d/test/references/stl.png index 4a46994e..f015029a 100644 Binary files a/k3d/test/references/stl.png and b/k3d/test/references/stl.png differ diff --git a/k3d/test/references/stl_color.png b/k3d/test/references/stl_color.png index 2c8ba1e6..ca2e2017 100644 Binary files a/k3d/test/references/stl_color.png and b/k3d/test/references/stl_color.png differ diff --git a/k3d/test/references/stl_smooth.png b/k3d/test/references/stl_smooth.png index 3be31ee4..f7ae0247 100644 Binary files a/k3d/test/references/stl_smooth.png and b/k3d/test/references/stl_smooth.png differ diff --git a/k3d/test/references/stl_wireframe.png b/k3d/test/references/stl_wireframe.png index 00d21deb..4f178004 100644 Binary files a/k3d/test/references/stl_wireframe.png and b/k3d/test/references/stl_wireframe.png differ diff --git a/k3d/test/references/surface.png b/k3d/test/references/surface.png index bfc67704..b4dde69f 100644 Binary files a/k3d/test/references/surface.png and b/k3d/test/references/surface.png differ diff --git a/k3d/test/references/surface_attribute.png b/k3d/test/references/surface_attribute.png index ca8586ce..bf6375e3 100644 Binary files a/k3d/test/references/surface_attribute.png and b/k3d/test/references/surface_attribute.png differ diff --git a/k3d/test/references/surface_attribute_low.png b/k3d/test/references/surface_attribute_low.png index ff945a1a..80f476e1 100644 Binary files a/k3d/test/references/surface_attribute_low.png and b/k3d/test/references/surface_attribute_low.png differ diff --git a/k3d/test/references/surface_attribute_low_dynamic_smooth.png b/k3d/test/references/surface_attribute_low_dynamic_smooth.png index 434d0add..6f2678e9 100644 Binary files a/k3d/test/references/surface_attribute_low_dynamic_smooth.png and b/k3d/test/references/surface_attribute_low_dynamic_smooth.png differ diff --git a/k3d/test/references/surface_attribute_low_dynamic_wireframe.png b/k3d/test/references/surface_attribute_low_dynamic_wireframe.png index 17961bb6..9ccd8119 100644 Binary files a/k3d/test/references/surface_attribute_low_dynamic_wireframe.png and b/k3d/test/references/surface_attribute_low_dynamic_wireframe.png differ diff --git a/k3d/test/references/surface_dynamic_color.png b/k3d/test/references/surface_dynamic_color.png index 50f83a8a..31dec7d9 100644 Binary files a/k3d/test/references/surface_dynamic_color.png and b/k3d/test/references/surface_dynamic_color.png differ diff --git a/k3d/test/references/texture.png b/k3d/test/references/texture.png index 7c380d86..0d409de7 100644 Binary files a/k3d/test/references/texture.png and b/k3d/test/references/texture.png differ diff --git a/k3d/test/references/texture_attribute.png b/k3d/test/references/texture_attribute.png index 5e5ce401..218a9e73 100644 Binary files a/k3d/test/references/texture_attribute.png and b/k3d/test/references/texture_attribute.png differ diff --git a/k3d/test/references/texture_attribute_dynamic_interpolation.png b/k3d/test/references/texture_attribute_dynamic_interpolation.png index fe2c2582..9f48116c 100644 Binary files a/k3d/test/references/texture_attribute_dynamic_interpolation.png and b/k3d/test/references/texture_attribute_dynamic_interpolation.png differ diff --git a/k3d/test/references/texture_dynamic_change.png b/k3d/test/references/texture_dynamic_change.png index e7ab7300..0be6cee2 100644 Binary files a/k3d/test/references/texture_dynamic_change.png and b/k3d/test/references/texture_dynamic_change.png differ diff --git a/k3d/test/references/vector_field.png b/k3d/test/references/vector_field.png index 313eaead..e217f17d 100644 Binary files a/k3d/test/references/vector_field.png and b/k3d/test/references/vector_field.png differ diff --git a/k3d/test/references/vector_field_3d.png b/k3d/test/references/vector_field_3d.png index 97a3f7db..8443c520 100644 Binary files a/k3d/test/references/vector_field_3d.png and b/k3d/test/references/vector_field_3d.png differ diff --git a/k3d/test/references/vector_field_3d_no_head.png b/k3d/test/references/vector_field_3d_no_head.png index 342f2f8c..14443558 100644 Binary files a/k3d/test/references/vector_field_3d_no_head.png and b/k3d/test/references/vector_field_3d_no_head.png differ diff --git a/k3d/test/references/vector_field_3d_scale.png b/k3d/test/references/vector_field_3d_scale.png index 2f01c192..c16a3001 100644 Binary files a/k3d/test/references/vector_field_3d_scale.png and b/k3d/test/references/vector_field_3d_scale.png differ diff --git a/k3d/test/references/vector_field_no_head.png b/k3d/test/references/vector_field_no_head.png index f1d9195e..e532742a 100644 Binary files a/k3d/test/references/vector_field_no_head.png and b/k3d/test/references/vector_field_no_head.png differ diff --git a/k3d/test/references/vector_field_scale.png b/k3d/test/references/vector_field_scale.png index 92994c7c..3285d40e 100644 Binary files a/k3d/test/references/vector_field_scale.png and b/k3d/test/references/vector_field_scale.png differ diff --git a/k3d/test/references/vectors.png b/k3d/test/references/vectors.png index 0e7f9983..63e3a89e 100644 Binary files a/k3d/test/references/vectors.png and b/k3d/test/references/vectors.png differ diff --git a/k3d/test/references/vectors_advance.png b/k3d/test/references/vectors_advance.png index cab21907..e9975eb0 100644 Binary files a/k3d/test/references/vectors_advance.png and b/k3d/test/references/vectors_advance.png differ diff --git a/k3d/test/references/volume.png b/k3d/test/references/volume.png index c46b122d..087a490c 100644 Binary files a/k3d/test/references/volume.png and b/k3d/test/references/volume.png differ diff --git a/k3d/test/references/volume_alpha_coef.png b/k3d/test/references/volume_alpha_coef.png index cc3277db..2fcd5500 100644 Binary files a/k3d/test/references/volume_alpha_coef.png and b/k3d/test/references/volume_alpha_coef.png differ diff --git a/k3d/test/references/volume_opacity_function.png b/k3d/test/references/volume_opacity_function.png index 5d7e0db0..d86ab2ce 100644 Binary files a/k3d/test/references/volume_opacity_function.png and b/k3d/test/references/volume_opacity_function.png differ diff --git a/k3d/test/references/voxels.png b/k3d/test/references/voxels.png index e481fabf..eceb50c7 100644 Binary files a/k3d/test/references/voxels.png and b/k3d/test/references/voxels.png differ diff --git a/k3d/test/references/voxels_box.png b/k3d/test/references/voxels_box.png index b5a3bf81..29640039 100644 Binary files a/k3d/test/references/voxels_box.png and b/k3d/test/references/voxels_box.png differ diff --git a/k3d/test/references/voxels_group.png b/k3d/test/references/voxels_group.png index 7df3c04b..83f0d038 100644 Binary files a/k3d/test/references/voxels_group.png and b/k3d/test/references/voxels_group.png differ diff --git a/k3d/test/references/voxels_group_opacity.png b/k3d/test/references/voxels_group_opacity.png index 0a4fff6b..423913c2 100644 Binary files a/k3d/test/references/voxels_group_opacity.png and b/k3d/test/references/voxels_group_opacity.png differ diff --git a/k3d/test/references/voxels_group_wireframe.png b/k3d/test/references/voxels_group_wireframe.png index 7f3bf100..bdf6da2d 100644 Binary files a/k3d/test/references/voxels_group_wireframe.png and b/k3d/test/references/voxels_group_wireframe.png differ diff --git a/k3d/test/references/voxels_outline.png b/k3d/test/references/voxels_outline.png index cde9a0d4..2f95a12d 100644 Binary files a/k3d/test/references/voxels_outline.png and b/k3d/test/references/voxels_outline.png differ diff --git a/k3d/test/references/voxels_outline_clipping_plane.png b/k3d/test/references/voxels_outline_clipping_plane.png index 219c4602..8fa60f84 100644 Binary files a/k3d/test/references/voxels_outline_clipping_plane.png and b/k3d/test/references/voxels_outline_clipping_plane.png differ diff --git a/k3d/test/references/voxels_outline_dynamic_outlines_color.png b/k3d/test/references/voxels_outline_dynamic_outlines_color.png index 945f1832..c47ce460 100644 Binary files a/k3d/test/references/voxels_outline_dynamic_outlines_color.png and b/k3d/test/references/voxels_outline_dynamic_outlines_color.png differ diff --git a/k3d/test/references/voxels_outline_opacity.png b/k3d/test/references/voxels_outline_opacity.png index 88d3f572..f5b6e395 100644 Binary files a/k3d/test/references/voxels_outline_opacity.png and b/k3d/test/references/voxels_outline_opacity.png differ diff --git a/k3d/test/references/voxels_sparse.png b/k3d/test/references/voxels_sparse.png index a6511214..03eb1b17 100644 Binary files a/k3d/test/references/voxels_sparse.png and b/k3d/test/references/voxels_sparse.png differ diff --git a/k3d/test/references/voxels_wireframe.png b/k3d/test/references/voxels_wireframe.png index fc097cc0..47f55903 100644 Binary files a/k3d/test/references/voxels_wireframe.png and b/k3d/test/references/voxels_wireframe.png differ diff --git a/k3d/test/references/win32/labels.png b/k3d/test/references/win32/labels.png index 9774aa32..3f5b2efc 100644 Binary files a/k3d/test/references/win32/labels.png and b/k3d/test/references/win32/labels.png differ diff --git a/k3d/test/references/win32/labels_without_box.png b/k3d/test/references/win32/labels_without_box.png index f9c15426..36a26ca2 100644 Binary files a/k3d/test/references/win32/labels_without_box.png and b/k3d/test/references/win32/labels_without_box.png differ diff --git a/k3d/test/references/win32/text.png b/k3d/test/references/win32/text.png index 7b7a6116..77f30682 100644 Binary files a/k3d/test/references/win32/text.png and b/k3d/test/references/win32/text.png differ diff --git a/k3d/test/references/win32/text2d.png b/k3d/test/references/win32/text2d.png index 6acae046..42184864 100644 Binary files a/k3d/test/references/win32/text2d.png and b/k3d/test/references/win32/text2d.png differ diff --git a/k3d/test/references/win32/text2d_without_box.png b/k3d/test/references/win32/text2d_without_box.png index 946efeea..66244309 100644 Binary files a/k3d/test/references/win32/text2d_without_box.png and b/k3d/test/references/win32/text2d_without_box.png differ diff --git a/k3d/test/references/win32/text_without_box.png b/k3d/test/references/win32/text_without_box.png index d062e844..e9a7b474 100644 Binary files a/k3d/test/references/win32/text_without_box.png and b/k3d/test/references/win32/text_without_box.png differ diff --git a/k3d/test/references/win32/texture_text.png b/k3d/test/references/win32/texture_text.png index ed425f41..dc07041c 100644 Binary files a/k3d/test/references/win32/texture_text.png and b/k3d/test/references/win32/texture_text.png differ diff --git a/k3d/test/references/win32/vectors_labels.png b/k3d/test/references/win32/vectors_labels.png index 1ecffddf..99345354 100644 Binary files a/k3d/test/references/win32/vectors_labels.png and b/k3d/test/references/win32/vectors_labels.png differ diff --git a/k3d/test/references/win32/vectors_labels_dynamic_head_size.png b/k3d/test/references/win32/vectors_labels_dynamic_head_size.png index c7842b67..b9df36f1 100644 Binary files a/k3d/test/references/win32/vectors_labels_dynamic_head_size.png and b/k3d/test/references/win32/vectors_labels_dynamic_head_size.png differ diff --git a/k3d/test/test_visual_lines.py b/k3d/test/test_visual_lines.py new file mode 100644 index 00000000..99dd9fde --- /dev/null +++ b/k3d/test/test_visual_lines.py @@ -0,0 +1,199 @@ +import k3d +import numpy as np +import pytest +from .plot_compare import * + +N = 100 + +theta = np.linspace(0, 2.0 * np.pi, N) +phi = np.linspace(0, 2.0 * np.pi, N) +theta, phi = np.meshgrid(theta, phi) + +c, a = 2, 1 +x = (c + a * np.cos(theta)) * np.cos(phi) +y = (c + a * np.cos(theta)) * np.sin(phi) +z = a * np.sin(theta) + +vertices = np.dstack([x, y, z]).astype(np.float32) +indices = (np.stack([ + np.arange(N * N - N - 1) + 0, np.arange(N * N - N - 1) + N, np.arange(N * N - N - 1) + N + 1, + np.arange(N * N - N - 1) + 0, np.arange(N * N - N - 1) + N + 1, np.arange(N * N - N - 1) + 1 +]).T).astype(np.uint32) + +colors = np.linspace(0, 0xffffff, N * N).astype(np.uint32) + + +def test_lines_simple(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='simple', + color=0xff) + pytest.plot += lines + + compare('lines_simple', camera_factor=0.5) + + +def test_lines_simple_colors(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='simple', + colors=colors) + pytest.plot += lines + + compare('lines_simple_colors', camera_factor=0.5) + + +def test_lines_simple_attribute(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='simple', + attribute=phi, + color_map=k3d.matplotlib_color_maps.twilight) + pytest.plot += lines + + compare('lines_simple_attribute', camera_factor=0.5) + + +def test_lines_simple_attribute_segment(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='simple', + attribute=phi, + indices_type='segment', + color_map=k3d.matplotlib_color_maps.twilight) + pytest.plot += lines + + compare('lines_simple_attribute_segment', camera_factor=0.5) + + +def test_lines_thick(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='thick', + width=0.003, + color=0xff) + pytest.plot += lines + + compare('lines_thick', camera_factor=0.5) + + +def test_lines_thick_colors(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='thick', + width=0.003, + colors=colors) + pytest.plot += lines + + compare('lines_thick_colors', camera_factor=0.5) + + +def test_lines_thick_attribute(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='thick', + width=0.003, + attribute=phi, + color_map=k3d.matplotlib_color_maps.twilight) + pytest.plot += lines + + compare('lines_thick_attribute', camera_factor=0.5) + + +def test_lines_thick_attribute_segment(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='thick', + width=0.003, + attribute=phi, + indices_type='segment', + color_map=k3d.matplotlib_color_maps.twilight) + pytest.plot += lines + + compare('lines_thick_attribute_segment', camera_factor=0.5) + + +def test_lines_mesh(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='mesh', + width=0.003, + color=0xff) + pytest.plot += lines + + compare('lines_mesh', camera_factor=0.5) + + +def test_lines_mesh_colors(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='mesh', + width=0.003, + color=0xff, + colors=colors) + pytest.plot += lines + + compare('lines_mesh_colors', camera_factor=0.5) + + +def test_lines_mesh_attribute(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='mesh', + width=0.003, + color=0xff, + attribute=phi, + color_map=k3d.matplotlib_color_maps.twilight) + pytest.plot += lines + + compare('lines_mesh_attribute', camera_factor=0.5) + + +def test_lines_mesh_attribute_segment(): + global vertices, indices + + prepare() + + lines = k3d.lines(vertices, indices, flat_shading=False, + shader='mesh', + width=0.003, + color=0xff, + attribute=phi, + indices_type='segment', + color_map=k3d.matplotlib_color_maps.twilight) + pytest.plot += lines + + compare('lines_mesh_attribute_segment', camera_factor=0.5) diff --git a/package.json b/package.json index 6daa68b8..9e9c85de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "k3d", - "version": "2.12.0", + "version": "2.13.0", "description": "3D visualization library", "keywords": [ "jupyter", @@ -39,12 +39,12 @@ "watch:labextension": "jupyter labextension watch ." }, "dependencies": { - "@jupyter-widgets/base": "^4.0.0", - "lil-gui": "^0.16.0", + "@jupyter-widgets/base": "^4.1.0", "es6-promise": "^4.2.8", "fflate": "^0.7.1", "file-saver": "^2.0.5", "katex": "^0.13.19", + "lil-gui": "^0.16.0", "lodash": "^4.17.21", "msgpack-lite": "^0.1.26", "rasterizehtml": "^1.3.0", @@ -52,11 +52,11 @@ "screenfull": "^5.1.0", "stats.js": "^0.17.0", "style-loader": "^2.0.0", - "three": "^0.137.4", + "three": "^0.138.2", "three-mesh-bvh": "^0.3.7" }, "devDependencies": { - "@jupyterlab/builder": "^3.2.1", + "@jupyterlab/builder": "^3.4.1", "prettier": "^2.4.1", "rimraf": "^3.0.2" },