From 467a34acd4d8e5c9d2dfcaba0792df6dac96068f Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Mon, 1 Oct 2018 15:10:23 +0200 Subject: [PATCH 1/7] Small cleanup --- examples/Shaders.ipynb | 3 +-- js/scripts/generate-shader-utils.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/examples/Shaders.ipynb b/examples/Shaders.ipynb index 874df4ba..2a06a311 100644 --- a/examples/Shaders.ipynb +++ b/examples/Shaders.ipynb @@ -71,17 +71,16 @@ "source": [ "material = ShaderMaterial(\n", " uniforms=dict(\n", + " **UniformsLib['common'],\n", " time=dict(value=0.0),\n", " resolution=dict(value=(1, 1)),\n", " user_color=dict(value='green'),\n", - " **UniformsLib['lights']\n", " ),\n", " defines=dict(\n", " FIX_COLOR=1,\n", " ),\n", " vertexShader=vertex_shader,\n", " fragmentShader=fragment_shader,\n", - " lights=True,\n", ")" ] }, diff --git a/js/scripts/generate-shader-utils.js b/js/scripts/generate-shader-utils.js index 955ae920..951b67a0 100644 --- a/js/scripts/generate-shader-utils.js +++ b/js/scripts/generate-shader-utils.js @@ -19,7 +19,7 @@ const JSON_AUTOGEN_EXT = '.' + AUTOGEN_EXT + '.json'; // We actually need access to THREE data here -var THREE = require('three'); +const THREE = require('three'); // @@ -28,13 +28,13 @@ var THREE = require('three'); function compileTemplate(templateName) { templateName = path.basename(templateName, '.mustache'); - var templatePath = path.resolve(templateDir, templateName + '.mustache'); + const templatePath = path.resolve(templateDir, templateName + '.mustache'); return Handlebars.compile(fse.readFileSync(templatePath, { encoding: 'utf-8' })); } -var pyWrapperTemplate = compileTemplate('py_shader_utils'); +const pyWrapperTemplate = compileTemplate('py_shader_utils'); // @@ -42,11 +42,11 @@ var pyWrapperTemplate = compileTemplate('py_shader_utils'); // function mapPromiseFnOverObject(object, mapFn) { - var promises = []; + let promises = []; Object.keys(object).forEach(function(key) { - var value = object[key]; - var result = mapFn(key, value); + const value = object[key]; + const result = mapFn(key, value); if (result instanceof Array) { promises = promises.concat(result); } else { @@ -97,8 +97,8 @@ function createPythonWrapper(name, relativePath) { function createPythonModuleInitFile(modulePath) { - var dirname = path.dirname(modulePath); - var pyInitFilePath = path.resolve(pySrcDir, dirname, '__init__.py'); + const dirname = path.dirname(modulePath); + const pyInitFilePath = path.resolve(pySrcDir, dirname, '__init__.py'); return fse.ensureFile(pyInitFilePath); } @@ -111,7 +111,7 @@ function createPythonFiles() { } return mapPromiseFnOverObject(shaderUtilsConfig, function(name, configObj) { - var relativePath = configObj.relativePath; + const relativePath = configObj.relativePath; return createPythonWrapper(name, relativePath).then(function() { // ensures each dir has empty __init__.py file for proper importing of sub dirs return createPythonModuleInitFile(relativePath); From 20fc323e6a0149419e92a437d863f0449c695614 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Fri, 28 Sep 2018 13:11:21 +0200 Subject: [PATCH 2/7] Add thick lines example Vendor the thick lines code from three.js (r97), and expose to kernel. --- examples/Geometries.ipynb | 2 +- examples/LineGeometry.ipynb | 368 ++++++++++++++++ js/scripts/generate-wrappers.js | 5 + js/scripts/prop-types.js | 51 ++- js/scripts/three-class-config.js | 52 +++ js/src/_base/Three.js | 10 +- js/src/examples/lines/Line2.js | 52 +++ js/src/examples/lines/LineGeometry.js | 105 +++++ js/src/examples/lines/LineMaterial.js | 402 ++++++++++++++++++ js/src/examples/lines/LineSegments2.js | 52 +++ js/src/examples/lines/LineSegmentsGeometry.js | 254 +++++++++++ js/src/geometries/LineGeometry.js | 24 ++ js/src/geometries/LineSegmentsGeometry.js | 56 +++ js/src/materials/LineMaterial.js | 30 ++ js/src/objects/Line2.js | 21 + js/src/objects/LineSegments2.js | 21 + pythreejs/traits.py | 2 +- 17 files changed, 1485 insertions(+), 22 deletions(-) create mode 100644 examples/LineGeometry.ipynb create mode 100644 js/src/examples/lines/Line2.js create mode 100644 js/src/examples/lines/LineGeometry.js create mode 100644 js/src/examples/lines/LineMaterial.js create mode 100644 js/src/examples/lines/LineSegments2.js create mode 100644 js/src/examples/lines/LineSegmentsGeometry.js create mode 100644 js/src/geometries/LineGeometry.js create mode 100644 js/src/geometries/LineSegmentsGeometry.js create mode 100644 js/src/materials/LineMaterial.js create mode 100644 js/src/objects/Line2.js create mode 100644 js/src/objects/LineSegments2.js diff --git a/examples/Geometries.ipynb b/examples/Geometries.ipynb index 3d375b62..91133b9c 100644 --- a/examples/Geometries.ipynb +++ b/examples/Geometries.ipynb @@ -1116,7 +1116,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.3" + "version": "3.6.6" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/examples/LineGeometry.ipynb b/examples/LineGeometry.ipynb new file mode 100644 index 00000000..492e75a7 --- /dev/null +++ b/examples/LineGeometry.ipynb @@ -0,0 +1,368 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Line Geometry" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Three.js has some example code for thick lines via an instance-based geometry. Since WebGL does not guarantee support for line thickness greater than 1 for GL lines, pytheejs includes these objects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pythreejs import *\n", + "from IPython.display import display\n", + "from ipywidgets import VBox, HBox, Checkbox, jslink\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g1 = BufferGeometry(\n", + " attributes={\n", + " 'position': BufferAttribute(np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [4, 4, 4]], dtype=np.float32), normalized=False),\n", + " 'color': BufferAttribute(np.array([[1, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype=np.float32), normalized=False),\n", + " },\n", + ")\n", + "m1 = LineBasicMaterial(vertexColors='VertexColors', linewidth=10)\n", + "line1 = LineSegments(g1, m1);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g2 = LineSegmentsGeometry(\n", + " positions=[\n", + " [[0, 0, 0], [1, 1, 1]],\n", + " [[2, 2, 2], [4, 4, 4]]\n", + " ],\n", + " colors=[\n", + " [[1, 0, 0], [1, 0, 0]],\n", + " [[0, 1, 0], [0, 0, 1]]\n", + " ],\n", + ")\n", + "m2 = LineMaterial(linewidth=10, vertexColors='VertexColors')\n", + "line2 = LineSegments2(g2, m2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "view_width = 600\n", + "view_height = 400\n", + "camera = PerspectiveCamera(position=[10, 0, 0], aspect=view_width/view_height)\n", + "key_light = DirectionalLight(position=[0, 10, 10])\n", + "ambient_light = AmbientLight()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "scene = Scene(children=[line1, line2, camera, key_light, ambient_light])\n", + "controller = OrbitControls(controlling=camera, screenSpacePanning=False)\n", + "renderer = Renderer(camera=camera, scene=scene, controls=[controller],\n", + " width=view_width, height=view_height)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "chks = [Checkbox(True, description='GL line'), Checkbox(True, description='Fat line')]\n", + "jslink((chks[0], 'value'), (line1, 'visible'))\n", + "jslink((chks[1], 'value'), (line2, 'visible'))\n", + "VBox([renderer, HBox(chks)])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "posInstBuffer = InstancedInterleavedBuffer( np.array([[0, 0, 0, 1, 1, 1], [2, 2, 2, 4, 4, 4]], dtype=np.float32))\n", + "colInstBuffer = InstancedInterleavedBuffer( np.array([[1, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1]], dtype=np.float32))\n", + "dbgG = InstancedBufferGeometry(attributes={\n", + " 'position': BufferAttribute(np.array([ [- 1, 2, 0], [1, 2, 0], [- 1, 1, 0], [1, 1, 0], [- 1, 0, 0], [1, 0, 0], [- 1, - 1, 0], [1, - 1, 0] ], dtype=np.float32)),\n", + " 'uv': BufferAttribute(np.array([ [- 1, 2], [1, 2], [- 1, 1], [1, 1], [- 1, - 1], [1, - 1], [- 1, - 2], [1, - 2] ], dtype=np.float32)),\n", + " 'index': BufferAttribute(np.array([ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ], dtype=np.uint8)),\n", + " 'instanceStart': InterleavedBufferAttribute(posInstBuffer, 3, 0),\n", + " 'instanceEnd': InterleavedBufferAttribute(posInstBuffer, 3, 3),\n", + " 'instanceColorStart': InterleavedBufferAttribute(colInstBuffer, 3, 0),\n", + " 'instanceColorEnd': InterleavedBufferAttribute(colInstBuffer, 3, 3),\n", + "})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = ShaderMaterial(\n", + " vertexShader='''\n", + "#include \n", + "#include \n", + "#include \n", + "#include \n", + "#include \n", + "\n", + "uniform float linewidth;\n", + "uniform vec2 resolution;\n", + "\n", + "attribute vec3 instanceStart;\n", + "attribute vec3 instanceEnd;\n", + "\n", + "attribute vec3 instanceColorStart;\n", + "attribute vec3 instanceColorEnd;\n", + "\n", + "varying vec2 vUv;\n", + "\n", + "void trimSegment( const in vec4 start, inout vec4 end ) {\n", + "\n", + " // trim end segment so it terminates between the camera plane and the near plane\n", + "\n", + " // conservative estimate of the near plane\n", + " float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column\n", + " float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column\n", + " float nearEstimate = - 0.5 * b / a;\n", + "\n", + " float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );\n", + "\n", + " end.xyz = mix( start.xyz, end.xyz, alpha );\n", + "\n", + "}\n", + "\n", + "void main() {\n", + "\n", + " #ifdef USE_COLOR\n", + "\n", + " vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;\n", + "\n", + " #endif\n", + " \n", + " float aspect = resolution.x / resolution.y;\n", + "\n", + " vUv = uv;\n", + " \n", + " // camera space\n", + " vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );\n", + " vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );\n", + "\n", + " // special case for perspective projection, and segments that terminate either in, or behind, the camera plane\n", + " // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space\n", + " // but we need to perform ndc-space calculations in the shader, so we must address this issue directly\n", + " // perhaps there is a more elegant solution -- WestLangley\n", + "\n", + " bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column\n", + "\n", + " if ( perspective ) {\n", + "\n", + " if ( start.z < 0.0 && end.z >= 0.0 ) {\n", + "\n", + " trimSegment( start, end );\n", + "\n", + " } else if ( end.z < 0.0 && start.z >= 0.0 ) {\n", + "\n", + " trimSegment( end, start );\n", + "\n", + " }\n", + "\n", + " }\n", + "\n", + " // clip space\n", + " vec4 clipStart = projectionMatrix * start;\n", + " vec4 clipEnd = projectionMatrix * end;\n", + "\n", + " // ndc space\n", + " vec2 ndcStart = clipStart.xy / clipStart.w;\n", + " vec2 ndcEnd = clipEnd.xy / clipEnd.w;\n", + "\n", + " // direction\n", + " vec2 dir = ndcEnd - ndcStart;\n", + "\n", + " // account for clip-space aspect ratio\n", + " dir.x *= aspect;\n", + " dir = normalize( dir );\n", + "\n", + " // perpendicular to dir\n", + " vec2 offset = vec2( dir.y, - dir.x );\n", + "\n", + " // undo aspect ratio adjustment\n", + " dir.x /= aspect;\n", + " offset.x /= aspect;\n", + "\n", + " // sign flip\n", + " if ( position.x < 0.0 ) offset *= - 1.0;\n", + "\n", + " // endcaps\n", + " if ( position.y < 0.0 ) {\n", + "\n", + " offset += - dir;\n", + "\n", + " } else if ( position.y > 1.0 ) {\n", + "\n", + " offset += dir;\n", + "\n", + " }\n", + "\n", + " // adjust for linewidth\n", + " offset *= linewidth;\n", + " \n", + " // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...\n", + " offset /= resolution.y;\n", + "\n", + " // select end\n", + " vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;\n", + "\n", + " // back to clip space\n", + " offset *= clip.w;\n", + "\n", + " clip.xy += offset;\n", + "\n", + " gl_Position = clip;\n", + " \n", + " //gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n", + " \n", + " //if ( instanceStart.x > 1.5) {\n", + " // gl_Position.x += 2.0;\n", + " //}\n", + " \n", + " vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation\n", + "\n", + " #include \n", + " #include \n", + " #include \n", + "}\n", + "''',\n", + " fragmentShader='''\n", + "uniform vec3 diffuse;\n", + "uniform float opacity;\n", + "\n", + "varying float vLineDistance;\n", + "\n", + "#include \n", + "#include \n", + "#include \n", + "#include \n", + "#include \n", + "\n", + "varying vec2 vUv;\n", + "\n", + "void main() {\n", + "\n", + " #include \n", + "\n", + "\n", + " if ( abs( vUv.y ) > 1.0 ) {\n", + "\n", + " float a = vUv.x;\n", + " float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;\n", + " float len2 = a * a + b * b;\n", + "\n", + " if ( len2 > 1.0 ) discard;\n", + "\n", + " }\n", + "\n", + " vec4 diffuseColor = vec4( diffuse, opacity );\n", + "\n", + " #include \n", + " #include \n", + "\n", + " gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a );\n", + "\n", + " #include \n", + " #include \n", + " #include \n", + " #include \n", + "\n", + "}\n", + "''',\n", + " vertexColors='VertexColors',\n", + " uniforms=dict(\n", + " **UniformsLib['common'],\n", + " linewidth={'value': 10.0},\n", + " resolution={'value': (100., 100.)},\n", + " )\n", + ")\n", + "Mesh(g2, m)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(repr(line2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/js/scripts/generate-wrappers.js b/js/scripts/generate-wrappers.js index 1485d146..155d7e9a 100644 --- a/js/scripts/generate-wrappers.js +++ b/js/scripts/generate-wrappers.js @@ -40,6 +40,11 @@ const CUSTOM_CLASSES = [ 'core/BaseBufferGeometry.js', 'objects/CloneArray.js', 'objects/Blackbox.js', + 'objects/Line2.js', + 'objects/LineSegments2.js', + 'geometries/LineGeometry.js', + 'geometries/LineSegmentsGeometry.js', + 'materials/LineMaterial.js', ]; const IGNORE_FILES = [ diff --git a/js/scripts/prop-types.js b/js/scripts/prop-types.js index 1859a92e..4ed6bb44 100644 --- a/js/scripts/prop-types.js +++ b/js/scripts/prop-types.js @@ -2,6 +2,21 @@ const JS_WIDGET_SERIALIZER = '{ deserialize: serializers.unpackThreeModel }'; + +function pythonify(value) { + if (value === false) { return 'False'; } + if (value === true) { return 'True'; } + if (value === Infinity) { return "float('inf')"; } + if (value === -Infinity) { return "-float('inf')"; } + if (value === undefined || value === null) { return 'None'; } + if (Array.isArray(value)) { + return `[${ + value.map(function(v) { return pythonify(v); }).join(', ') + }]`; + } + return JSON.stringify(value); +} + class BaseType { constructor(options) { options = options || {}; @@ -18,15 +33,7 @@ class BaseType { return null; } getPythonDefaultValue() { - if (this.defaultValue === false) { return 'False'; } - if (this.defaultValue === true) { return 'True'; } - if (this.defaultValue === 0) { return '0'; } - if (this.defaultValue === '') { return "''"; } - if (this.defaultValue === Infinity) { return "float('inf')"; } - if (this.defaultValue === -Infinity) { return "-float('inf')"; } - if (!this.defaultValue) { return 'None'; } - - return JSON.stringify(this.defaultValue); + return pythonify(this.defaultValue); } getPropertyConverterFn() { return null; @@ -382,16 +389,24 @@ class ArrayBufferType extends BaseType { super(options); this.arrayType = arrayType; this.shapeConstraint = shapeConstraint; - this.defaultValue = null; + this.defaultValue = options && options.nullable ? null : undefined; this.serializer = 'dataserializers.data_union_serialization'; } getTraitlet() { const args = []; + if (this.defaultValue !== undefined) { + args.push(pythonify(this.defaultValue)); + } if (this.arrayType) { - args.push(`dtype=${this.arrayType}`); + args.push(`dtype=${pythonify(this.arrayType)}`); } if (this.shapeConstraint) { - args.push(`shape_constraint=${this.shapeConstraint}`); + args.push(`shape_constraint=shape_constraints(${ + this.shapeConstraint.map(function(v) { return pythonify(v); }).join(', ') + })`); + } + if (this.nullable) { + args.push(this.getNullableStr()); } return `WebGLDataUnion(${args.join(', ')})${this.getTagString()}`; @@ -463,7 +478,7 @@ class Vector2 extends BaseType { } getTraitlet() { return `Vector2(default_value=${ - JSON.stringify(this.defaultValue)})${this.getTagString()}`; + pythonify(this.defaultValue)})${this.getTagString()}`; } getPropertyConverterFn() { return 'convertVector'; @@ -480,7 +495,7 @@ class Vector3 extends BaseType { } getTraitlet() { return `Vector3(default_value=${ - JSON.stringify(this.defaultValue)})${this.getTagString()}`; + pythonify(this.defaultValue)})${this.getTagString()}`; } getPropertyConverterFn() { return 'convertVector'; @@ -497,7 +512,7 @@ class Vector4 extends BaseType { } getTraitlet() { return `Vector4(default_value=${ - JSON.stringify(this.defaultValue)})${this.getTagString()}`; + pythonify(this.defaultValue)})${this.getTagString()}`; } getPropertyConverterFn() { return 'convertVector'; @@ -550,7 +565,7 @@ class Matrix3 extends BaseType { } getTraitlet() { return `Matrix3(default_value=${ - JSON.stringify(this.defaultValue)})${this.getTagString()}`; + pythonify(this.defaultValue)})${this.getTagString()}`; } getPropertyConverterFn() { return 'convertMatrix'; @@ -572,7 +587,7 @@ class Matrix4 extends BaseType { } getTraitlet() { return `Matrix4(default_value=${ - JSON.stringify(this.defaultValue)})${this.getTagString()}`; + pythonify(this.defaultValue)})${this.getTagString()}`; } getPropertyConverterFn() { return 'convertMatrix'; @@ -591,7 +606,7 @@ class Euler extends BaseType { getTraitlet() { return `Euler(default_value=${ - JSON.stringify(this.defaultValue)})${this.getTagString()}`; + pythonify(this.defaultValue)})${this.getTagString()}`; } getPropertyConverterFn() { return 'convertEuler'; diff --git a/js/scripts/three-class-config.js b/js/scripts/three-class-config.js index ad5e8062..d1946a76 100644 --- a/js/scripts/three-class-config.js +++ b/js/scripts/three-class-config.js @@ -653,6 +653,20 @@ module.exports = { }, constructorArgs: [ 'parameters' ], }, + LineMaterial: { + relativePath: './materials/LineMaterial', + superClass: 'Material', + properties: { + color: new Types.Color('#ffffff'), + fog: new Types.Bool(false), + lights: new Types.Bool(false), + linewidth: new Types.Float(1.0), + dashScale: new Types.Float(1.0), + dashSize: new Types.Float(1.0), + gapSize: new Types.Float(1.0), + }, + constructorArgs: [ 'parameters' ], + }, Material: { relativePath: './materials/Material', properties: { @@ -1154,6 +1168,26 @@ module.exports = { constructorArgs: [ 'original', 'positions', 'merge' ], // TODO: Add restriction: Source cannot use strip/fan draw modes }, + Line2: { + relativePath: './objects/Line2', + superClass: 'Mesh', + properties: { + material: new Types.InitializedThreeType('LineMaterial', {nullable: true}), + geometry: new Types.InitializedThreeType('LineGeometry', {nullable: true}), + }, + constructorArgs: [ 'geometry', 'material' ], + propsDefinedByThree: [ 'geometry', 'material' ], + }, + LineSegments2: { + relativePath: './objects/LineSegments2', + superClass: 'Mesh', + properties: { + material: new Types.InitializedThreeType('LineMaterial', {nullable: true}), + geometry: new Types.InitializedThreeType('LineSegmentsGeometry', {nullable: true}), + }, + constructorArgs: [ 'geometry', 'material' ], + propsDefinedByThree: [ 'geometry', 'material' ], + }, WebGLRenderTarget: { relativePath: './renderers/WebGLRenderTarget', }, @@ -1559,6 +1593,24 @@ module.exports = { phiLength: new Types.Float(Math.PI * 2.0), }, }, + LineGeometry: { + relativePath: './geometries/LineGeometry', + superClass: 'LineSegmentsGeometry', + constructorArgs: [], + properties: { + positions: new Types.ArrayBuffer('float32', [null, 3]), + colors: new Types.ArrayBuffer('float32', [null, 3], {nullable: true}), + }, + }, + LineSegmentsGeometry: { + relativePath: './geometries/LineSegmentsGeometry', + superClass: 'BaseBufferGeometry', + constructorArgs: [], + properties: { + positions: new Types.ArrayBuffer('float32', [null, 2, 3]), + colors: new Types.ArrayBuffer('float32', [null, 2, 3], {nullable: true}), + }, + }, OctahedronGeometry: { relativePath: './geometries/OctahedronGeometry', superClass: 'BaseGeometry', diff --git a/js/src/_base/Three.js b/js/src/_base/Three.js index ed258c60..9694cfd2 100644 --- a/js/src/_base/Three.js +++ b/js/src/_base/Three.js @@ -768,11 +768,17 @@ var ThreeModel = widgets.WidgetModel.extend({ return arr && arr.data; }, - convertArrayBufferThreeToModel: function(arrBuffer) { + convertArrayBufferThreeToModel: function(arrBuffer, propName) { if (arrBuffer === null) { return null; } - // Never back-convert to a new widget + var current = this.get(propName); + var currentArray = dataserializers.getArray(current); + if (currentArray && (currentArray.data === arrBuffer)) { + // Unchanged, do nothing + return current; + } + // Never create a new widget, even if current is one return ndarray(arrBuffer); }, diff --git a/js/src/examples/lines/Line2.js b/js/src/examples/lines/Line2.js new file mode 100644 index 00000000..d88b13f3 --- /dev/null +++ b/js/src/examples/lines/Line2.js @@ -0,0 +1,52 @@ +/** + * @author WestLangley / http://github.com/WestLangley + * + */ + +var THREE = require('three'); +var LineGeometry = require('./LineGeometry').LineGeometry; +var LineMaterial = require('./LineMaterial').LineMaterial; + + +var Line2 = function ( geometry, material ) { + + THREE.Mesh.call( this ); + + this.type = 'Line2'; + + this.geometry = geometry !== undefined ? geometry : new LineGeometry(); + this.material = material !== undefined ? material : new LineMaterial( { color: Math.random() * 0xffffff } ); + +}; + +Line2.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { + + constructor: Line2, + + isLine2: true, + + onBeforeRender: function( renderer, scene, camera, geometry, material, group ) { + + if ( material.isLineMaterial ) { + + var size = renderer.getSize(); + + material.resolution = new THREE.Vector2(size.width, size.height); + + } + + }, + + copy: function ( source ) { + + // todo + + return this; + + } + +} ); + +module.exports = { + Line2: Line2 +}; diff --git a/js/src/examples/lines/LineGeometry.js b/js/src/examples/lines/LineGeometry.js new file mode 100644 index 00000000..105d7735 --- /dev/null +++ b/js/src/examples/lines/LineGeometry.js @@ -0,0 +1,105 @@ +/** + * @author WestLangley / http://github.com/WestLangley + * + */ + +var LineSegmentsGeometry = require('./LineSegmentsGeometry').LineSegmentsGeometry; + + +var LineGeometry = function () { + + LineSegmentsGeometry.call( this ); + + this.type = 'LineGeometry'; + +}; + +LineGeometry.prototype = Object.assign( Object.create( LineSegmentsGeometry.prototype ), { + + constructor: LineGeometry, + + isLineGeometry: true, + + setPositions: function ( array ) { + + // converts [ x1, y1, z1, x2, y2, z2, ... ] to pairs format + + var length = array.length - 3; + var points = new Float32Array( 2 * length ); + + for ( var i = 0; i < length; i += 3 ) { + + points[ 2 * i ] = array[ i ]; + points[ 2 * i + 1 ] = array[ i + 1 ]; + points[ 2 * i + 2 ] = array[ i + 2 ]; + + points[ 2 * i + 3 ] = array[ i + 3 ]; + points[ 2 * i + 4 ] = array[ i + 4 ]; + points[ 2 * i + 5 ] = array[ i + 5 ]; + + } + + LineSegmentsGeometry.prototype.setPositions.call( this, points ); + + return this; + + }, + + setColors: function ( array ) { + + // converts [ r1, g1, b1, r2, g2, b2, ... ] to pairs format + + var length = array.length - 3; + var colors = new Float32Array( 2 * length ); + + for ( var i = 0; i < length; i += 3 ) { + + colors[ 2 * i ] = array[ i ]; + colors[ 2 * i + 1 ] = array[ i + 1 ]; + colors[ 2 * i + 2 ] = array[ i + 2 ]; + + colors[ 2 * i + 3 ] = array[ i + 3 ]; + colors[ 2 * i + 4 ] = array[ i + 4 ]; + colors[ 2 * i + 5 ] = array[ i + 5 ]; + + } + + LineSegmentsGeometry.prototype.setColors.call( this, colors ); + + return this; + + }, + + fromLine: function ( line ) { + + var geometry = line.geometry; + + if ( geometry.isGeometry ) { + + this.setPositions( geometry.vertices ); + + } else if ( geometry.isBufferGeometry ) { + + this.setPositions( geometry.position.array ); // assumes non-indexed + + } + + // set colors, maybe + + return this; + + }, + + copy: function ( source ) { + + // todo + + return this; + + } + +} ); + +module.exports = { + LineGeometry: LineGeometry +}; diff --git a/js/src/examples/lines/LineMaterial.js b/js/src/examples/lines/LineMaterial.js new file mode 100644 index 00000000..b797a022 --- /dev/null +++ b/js/src/examples/lines/LineMaterial.js @@ -0,0 +1,402 @@ +/** + * @author WestLangley / http://github.com/WestLangley + * + * parameters = { + * color: , + * linewidth: , + * dashed: , + * dashScale: , + * dashSize: , + * gapSize: , + * resolution: , // to be set by renderer + * } + */ + +var THREE = require('three'); + +lineUniforms = { + + linewidth: { value: 1 }, + resolution: { value: new THREE.Vector2( 1, 1 ) }, + dashScale: { value: 1 }, + dashSize: { value: 1 }, + gapSize: { value: 1 } // todo FIX - maybe change to totalSize + +}; + +lineShaders = { + + uniforms: THREE.UniformsUtils.merge( [ + THREE.UniformsLib.common, + THREE.UniformsLib.fog, + lineUniforms + ] ), + + vertexShader: + ` + #include + #include + #include + #include + #include + + uniform float linewidth; + uniform vec2 resolution; + + attribute vec3 instanceStart; + attribute vec3 instanceEnd; + + attribute vec3 instanceColorStart; + attribute vec3 instanceColorEnd; + + varying vec2 vUv; + + #ifdef USE_DASH + + uniform float dashScale; + attribute float instanceDistanceStart; + attribute float instanceDistanceEnd; + varying float vLineDistance; + + #endif + + void trimSegment( const in vec4 start, inout vec4 end ) { + + // trim end segment so it terminates between the camera plane and the near plane + + // conservative estimate of the near plane + float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column + float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column + float nearEstimate = - 0.5 * b / a; + + float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); + + end.xyz = mix( start.xyz, end.xyz, alpha ); + + } + + void main() { + + #ifdef USE_COLOR + + vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; + + #endif + + #ifdef USE_DASH + + vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; + + #endif + + float aspect = resolution.x / resolution.y; + + vUv = uv; + + // camera space + vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); + vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); + + // special case for perspective projection, and segments that terminate either in, or behind, the camera plane + // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space + // but we need to perform ndc-space calculations in the shader, so we must address this issue directly + // perhaps there is a more elegant solution -- WestLangley + + bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column + + if ( perspective ) { + + if ( start.z < 0.0 && end.z >= 0.0 ) { + + trimSegment( start, end ); + + } else if ( end.z < 0.0 && start.z >= 0.0 ) { + + trimSegment( end, start ); + + } + + } + + // clip space + vec4 clipStart = projectionMatrix * start; + vec4 clipEnd = projectionMatrix * end; + + // ndc space + vec2 ndcStart = clipStart.xy / clipStart.w; + vec2 ndcEnd = clipEnd.xy / clipEnd.w; + + // direction + vec2 dir = ndcEnd - ndcStart; + + // account for clip-space aspect ratio + dir.x *= aspect; + dir = normalize( dir ); + + // perpendicular to dir + vec2 offset = vec2( dir.y, - dir.x ); + + // undo aspect ratio adjustment + dir.x /= aspect; + offset.x /= aspect; + + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; + + // endcaps + if ( position.y < 0.0 ) { + + offset += - dir; + + } else if ( position.y > 1.0 ) { + + offset += dir; + + } + + // adjust for linewidth + offset *= linewidth; + + // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... + offset /= resolution.y; + + // select end + vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; + + // back to clip space + offset *= clip.w; + + clip.xy += offset; + + gl_Position = clip; + + vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation + + #include + #include + #include + + } + `, + + fragmentShader: + ` + uniform vec3 diffuse; + uniform float opacity; + + #ifdef USE_DASH + + uniform float dashSize; + uniform float gapSize; + + #endif + + varying float vLineDistance; + + #include + #include + #include + #include + #include + + varying vec2 vUv; + + void main() { + + #include + + #ifdef USE_DASH + + if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps + + if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX + + #endif + + if ( abs( vUv.y ) > 1.0 ) { + + float a = vUv.x; + float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; + float len2 = a * a + b * b; + + if ( len2 > 1.0 ) discard; + + } + + vec4 diffuseColor = vec4( diffuse, opacity ); + + #include + #include + + gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a ); + + #include + #include + #include + #include + + } + ` +}; + + +var LineMaterial = function ( parameters ) { + + THREE.ShaderMaterial.call( this, { + + type: 'LineMaterial', + + uniforms: THREE.UniformsUtils.clone( lineShaders.uniforms ), + + vertexShader: lineShaders.vertexShader, + fragmentShader: lineShaders.fragmentShader + + } ); + + this.dashed = false; + + Object.defineProperties( this, { + + color: { + + enumerable: true, + + get: function () { + + return this.uniforms.diffuse.value; + + }, + + set: function ( value ) { + + this.uniforms.diffuse.value = value; + + } + + }, + + linewidth: { + + enumerable: true, + + get: function () { + + return this.uniforms.linewidth.value; + + }, + + set: function ( value ) { + + this.uniforms.linewidth.value = value; + + } + + }, + + dashScale: { + + enumerable: true, + + get: function () { + + return this.uniforms.dashScale.value; + + }, + + set: function ( value ) { + + this.uniforms.dashScale.value = value; + + } + + }, + + dashSize: { + + enumerable: true, + + get: function () { + + return this.uniforms.dashSize.value; + + }, + + set: function ( value ) { + + this.uniforms.dashSize.value = value; + + } + + }, + + gapSize: { + + enumerable: true, + + get: function () { + + return this.uniforms.gapSize.value; + + }, + + set: function ( value ) { + + this.uniforms.gapSize.value = value; + + } + + }, + + resolution: { + + enumerable: true, + + get: function () { + + return this.uniforms.resolution.value; + + }, + + set: function ( value ) { + + this.uniforms.resolution.value.copy( value ); + + } + + } + + } ); + + this.setValues( parameters ); + +}; + +LineMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype ); +LineMaterial.prototype.constructor = LineMaterial; + +LineMaterial.prototype.isLineMaterial = true; + +LineMaterial.prototype.copy = function ( source ) { + + THREE.ShaderMaterial.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + + this.linewidth = source.linewidth; + + this.resolution = source.resolution; + + this.dashScale = source.dashScale; + + this.dashSize = source.dashSize; + + this.gapSize = source.gapSize; + + return this; + +}; + + +module.exports = { + LineMaterial: LineMaterial +}; diff --git a/js/src/examples/lines/LineSegments2.js b/js/src/examples/lines/LineSegments2.js new file mode 100644 index 00000000..7deee27c --- /dev/null +++ b/js/src/examples/lines/LineSegments2.js @@ -0,0 +1,52 @@ +/** + * @author WestLangley / http://github.com/WestLangley + * + */ + +var THREE = require('three'); +var LineSegmentsGeometry = require('./LineSegmentsGeometry').LineSegmentsGeometry; +var LineMaterial = require('./LineMaterial').LineMaterial; + + +var LineSegments2 = function ( geometry, material ) { + + THREE.Mesh.call( this ); + + this.type = 'LineSegments2'; + + this.geometry = geometry !== undefined ? geometry : new LineSegmentsGeometry(); + this.material = material !== undefined ? material : new LineMaterial( { color: Math.random() * 0xffffff } ); + +}; + +LineSegments2.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), { + + constructor: LineSegments2, + + isLineSegments2: true, + + onBeforeRender: function( renderer, scene, camera, geometry, material, group ) { + + if ( material.isLineMaterial ) { + + var size = renderer.getSize(); + + material.resolution = new THREE.Vector2(size.width, size.height); + + } + + }, + + copy: function ( source ) { + + // todo + + return this; + + }, + +} ); + +module.exports = { + LineSegments2: LineSegments2 +}; diff --git a/js/src/examples/lines/LineSegmentsGeometry.js b/js/src/examples/lines/LineSegmentsGeometry.js new file mode 100644 index 00000000..d57685ef --- /dev/null +++ b/js/src/examples/lines/LineSegmentsGeometry.js @@ -0,0 +1,254 @@ +/** + * @author WestLangley / http://github.com/WestLangley + * + */ + +var THREE = require('three'); + +var LineSegmentsGeometry = function () { + + THREE.InstancedBufferGeometry.call( this ); + + this.type = 'LineSegmentsGeometry'; + + var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ]; + var uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ]; + var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ]; + + this.setIndex( index ); + this.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) ); + this.addAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) ); + +}; + +LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.InstancedBufferGeometry.prototype ), { + + constructor: LineSegmentsGeometry, + + isLineSegmentsGeometry: true, + + applyMatrix: function ( matrix ) { + + var start = this.attributes.instanceStart; + var end = this.attributes.instanceEnd; + + if ( start !== undefined ) { + + matrix.applyToBufferAttribute( start ); + + matrix.applyToBufferAttribute( end ); + + start.data.needsUpdate = true; + + } + + if ( this.boundingBox !== null ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere !== null ) { + + this.computeBoundingSphere(); + + } + + return this; + + }, + + setPositions: function ( array ) { + + var lineSegments; + + if ( array instanceof Float32Array ) { + + lineSegments = array; + + } else if ( Array.isArray( array ) ) { + + lineSegments = new Float32Array( array ); + + } + + var instanceBuffer = new THREE.InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz + + this.addAttribute( 'instanceStart', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz + this.addAttribute( 'instanceEnd', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz + + // + + this.computeBoundingBox(); + this.computeBoundingSphere(); + + return this; + + }, + + setColors: function ( array ) { + + var colors; + + if ( array instanceof Float32Array ) { + + colors = array; + + } else if ( Array.isArray( array ) ) { + + colors = new Float32Array( array ); + + } + + var instanceColorBuffer = new THREE.InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb + + this.addAttribute( 'instanceColorStart', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb + this.addAttribute( 'instanceColorEnd', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb + + return this; + + }, + + fromWireframeGeometry: function ( geometry ) { + + this.setPositions( geometry.attributes.position.array ); + + return this; + + }, + + fromEdgesGeometry: function ( geometry ) { + + this.setPositions( geometry.attributes.position.array ); + + return this; + + }, + + fromLineSegements: function ( lineSegments ) { + + var geometry = lineSegments.geometry; + + if ( geometry.isGeometry ) { + + this.setPositions( geometry.vertices ); + + } else if ( geometry.isBufferGeometry ) { + + this.setPositions( geometry.position.array ); // assumes non-indexed + + } + + // set colors, maybe + + return this; + + }, + + computeBoundingBox: function () { + + var box = new THREE.Box3(); + + return function computeBoundingBox() { + + if ( this.boundingBox === null ) { + + this.boundingBox = new THREE.Box3(); + + } + + var start = this.attributes.instanceStart; + var end = this.attributes.instanceEnd; + + if ( start !== undefined && end !== undefined ) { + + this.boundingBox.setFromBufferAttribute( start ); + + box.setFromBufferAttribute( end ); + + this.boundingBox.union( box ); + + } + + }; + + }(), + + computeBoundingSphere: function () { + + var vector = new THREE.Vector3(); + + return function computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new THREE.Sphere(); + + } + + if ( this.boundingBox === null ) { + + this.computeBoundingBox(); + + } + + var start = this.attributes.instanceStart; + var end = this.attributes.instanceEnd; + + if ( start !== undefined && end !== undefined ) { + + var center = this.boundingSphere.center; + + this.boundingBox.getCenter( center ); + + var maxRadiusSq = 0; + + for ( var i = 0, il = start.count; i < il; i ++ ) { + + vector.fromBufferAttribute( start, i ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) ); + + vector.fromBufferAttribute( end, i ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) ); + + } + + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); + + if ( isNaN( this.boundingSphere.radius ) ) { + + console.error( 'LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this ); + + } + + } + + }; + + }(), + + toJSON: function () { + + // todo + + }, + + clone: function () { + + // todo + + }, + + copy: function ( source ) { + + // todo + + return this; + + } + +} ); + +module.exports = { + LineSegmentsGeometry: LineSegmentsGeometry +}; diff --git a/js/src/geometries/LineGeometry.js b/js/src/geometries/LineGeometry.js new file mode 100644 index 00000000..607888cc --- /dev/null +++ b/js/src/geometries/LineGeometry.js @@ -0,0 +1,24 @@ +var Promise = require('bluebird'); +var LineGeometry = require('../examples/lines/LineGeometry.js').LineGeometry; +var LineGeometryAutogen = require('./LineGeometry.autogen'); + +var utils = require('../_base/utils'); + + +var LineGeometryModel = LineGeometryAutogen.LineGeometryModel.extend({ + + + constructThreeObject: function() { + + var result = new LineGeometry(); + return Promise.resolve(result); + + }, + +}); + +utils.customModelsLut[LineGeometry.prototype.constructor.name] = 'LineGeometry'; + +module.exports = { + LineGeometryModel: LineGeometryModel, +}; diff --git a/js/src/geometries/LineSegmentsGeometry.js b/js/src/geometries/LineSegmentsGeometry.js new file mode 100644 index 00000000..ef60a347 --- /dev/null +++ b/js/src/geometries/LineSegmentsGeometry.js @@ -0,0 +1,56 @@ +var Promise = require('bluebird'); +var LineSegmentsGeometry = require('../examples/lines/LineSegmentsGeometry.js').LineSegmentsGeometry; +var LineSegmentsGeometryAutogen = require('./LineSegmentsGeometry.autogen').LineSegmentsGeometryModel; + +var utils = require('../_base/utils'); + + +var LineSegmentsGeometryModel = LineSegmentsGeometryAutogen.extend({ + + constructThreeObject: function() { + + var result = new LineSegmentsGeometry(); + return Promise.resolve(result); + + }, + + createPropertiesArrays: function() { + + LineSegmentsGeometryAutogen.prototype.createPropertiesArrays.call(this); + + this.property_assigners['positions'] = 'assignLineAttribute'; + this.property_assigners['colors'] = 'assignLineAttribute'; + + }, + + assignLineAttribute: function(obj, key, value) { + if (key === 'positions') { + obj.setPositions(value); + } else if (key === 'colors') { + obj.setColors(value); + } else { + throw new Error(`Unknown line attribute key: ${key}`); + } + }, + + convertArrayBufferThreeToModel: function(arrBuffer, propName) { + if (arrBuffer === null) { + return null; + } + var current = this.get(propName); + var currentArray = dataserializers.getArray(current); + if (currentArray && (currentArray.data === arrBuffer)) { + // Unchanged, do nothing + return current; + } + // Never create a new widget, even if current is one + return ndarray(arrBuffer, currentArray && currentArray.shape); + }, + +}); + +utils.customModelsLut[LineSegmentsGeometry.prototype.constructor.name] = 'LineSegmentsGeometry'; + +module.exports = { + LineSegmentsGeometryModel: LineSegmentsGeometryModel, +}; diff --git a/js/src/materials/LineMaterial.js b/js/src/materials/LineMaterial.js new file mode 100644 index 00000000..accd0557 --- /dev/null +++ b/js/src/materials/LineMaterial.js @@ -0,0 +1,30 @@ +var Promise = require('bluebird'); +var LineMaterial = require('../examples/lines/LineMaterial.js').LineMaterial; +var LineMaterialAutogen = require('./LineMaterial.autogen').LineMaterialModel; + +var utils = require('../_base/utils'); + + +var LineMaterialModel = LineMaterialAutogen.extend({ + + constructThreeObject: function() { + + var result = new LineMaterial({ + color: this.convertColorModelToThree(this.get('color'), 'color'), + dashScale: this.convertFloatModelToThree(this.get('dashScale'), 'dashScale'), + dashSize: this.convertFloatModelToThree(this.get('dashSize'), 'dashSize'), + gapSize: this.convertFloatModelToThree(this.get('gapSize'), 'gapSize'), + linewidth: this.convertFloatModelToThree(this.get('linewidth'), 'linewidth'), + type: this.get('type'), + }); + return Promise.resolve(result); + + }, + +}); + +utils.customModelsLut[LineMaterial.prototype.constructor.name] = 'LineMaterial'; + +module.exports = { + LineMaterialModel: LineMaterialModel, +}; diff --git a/js/src/objects/Line2.js b/js/src/objects/Line2.js new file mode 100644 index 00000000..c4fd5cfd --- /dev/null +++ b/js/src/objects/Line2.js @@ -0,0 +1,21 @@ +var Promise = require('bluebird'); +var Line2 = require('../examples/lines/Line2.js').Line2; +var Line2Autogen = require('./Line2.autogen').Line2Model; + +var Line2Model = Line2Autogen.extend({ + + constructThreeObject: function() { + + var result = new Line2( + this.convertThreeTypeModelToThree(this.get('geometry'), 'geometry'), + this.convertThreeTypeArrayModelToThree(this.get('material'), 'material') + ); + return Promise.resolve(result); + + }, + +}); + +module.exports = { + Line2Model: Line2Model, +}; diff --git a/js/src/objects/LineSegments2.js b/js/src/objects/LineSegments2.js new file mode 100644 index 00000000..a601334b --- /dev/null +++ b/js/src/objects/LineSegments2.js @@ -0,0 +1,21 @@ +var Promise = require('bluebird'); +var LineSegments2 = require('../examples/lines/LineSegments2.js').LineSegments2; +var LineSegments2Autogen = require('./LineSegments2.autogen').LineSegments2Model; + +var LineSegments2Model = LineSegments2Autogen.extend({ + + constructThreeObject: function() { + + var result = new LineSegments2( + this.convertThreeTypeModelToThree(this.get('geometry'), 'geometry'), + this.convertThreeTypeArrayModelToThree(this.get('material'), 'material') + ); + return Promise.resolve(result); + + }, + +}); + +module.exports = { + LineSegments2Model: LineSegments2Model, +}; diff --git a/pythreejs/traits.py b/pythreejs/traits.py index bb4d0745..29f0b0c1 100644 --- a/pythreejs/traits.py +++ b/pythreejs/traits.py @@ -13,7 +13,7 @@ from ipywidgets import widget_serialization -from ipydatawidgets import DataUnion, NDArrayWidget +from ipydatawidgets import DataUnion, NDArrayWidget, shape_constraints import numpy as np From 4799ad07e97d91ad1fa3b1366af582e5b7869a7c Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Tue, 2 Oct 2018 13:41:14 +0200 Subject: [PATCH 3/7] Fixes and tweaks for line geometry + example --- examples/LineGeometry.ipynb | 89 +++++++++++++++++------ js/scripts/prop-types.js | 5 +- js/src/geometries/LineSegmentsGeometry.js | 21 ++---- 3 files changed, 77 insertions(+), 38 deletions(-) diff --git a/examples/LineGeometry.ipynb b/examples/LineGeometry.ipynb index 492e75a7..97350bb9 100644 --- a/examples/LineGeometry.ipynb +++ b/examples/LineGeometry.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Three.js has some example code for thick lines via an instance-based geometry. Since WebGL does not guarantee support for line thickness greater than 1 for GL lines, pytheejs includes these objects." + "Three.js has some example code for thick lines via an instance-based geometry. Since WebGL does not guarantee support for line thickness greater than 1 for GL lines, pytheejs includes these objects by default." ] }, { @@ -26,6 +26,13 @@ "import numpy as np" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, let's set up a normal GL line for comparison. Depending on your OS/browser combination, this might not respect the `linewidth` argument. E.g. most browsers on Windows does not support linewidth greater than 1, due to lack of support in the ANGLE library that most browsers rely on." + ] + }, { "cell_type": "code", "execution_count": null, @@ -42,6 +49,13 @@ "line1 = LineSegments(g1, m1);" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we'll set up two variants of the instance geometry based lines. One with a single color, and one with vertex colors." + ] + }, { "cell_type": "code", "execution_count": null, @@ -53,13 +67,36 @@ " [[0, 0, 0], [1, 1, 1]],\n", " [[2, 2, 2], [4, 4, 4]]\n", " ],\n", + ")\n", + "m2 = LineMaterial(linewidth=10, color='cyan')\n", + "line2 = LineSegments2(g2, m2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g3 = LineSegmentsGeometry(\n", + " positions=[\n", + " [[0, 0, 0], [1, 1, 1]],\n", + " [[2, 2, 2], [4, 4, 4]]\n", + " ],\n", " colors=[\n", " [[1, 0, 0], [1, 0, 0]],\n", " [[0, 1, 0], [0, 0, 1]]\n", " ],\n", ")\n", - "m2 = LineMaterial(linewidth=10, vertexColors='VertexColors')\n", - "line2 = LineSegments2(g2, m2)" + "m3 = LineMaterial(linewidth=10, vertexColors='VertexColors')\n", + "line3 = LineSegments2(g3, m3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, let's set up a simple scenea and renderer, and add some checkboxes so we can toggle the visibility of the different lines." ] }, { @@ -81,7 +118,7 @@ "metadata": {}, "outputs": [], "source": [ - "scene = Scene(children=[line1, line2, camera, key_light, ambient_light])\n", + "scene = Scene(children=[line1, line2, line3, camera, key_light, ambient_light])\n", "controller = OrbitControls(controlling=camera, screenSpacePanning=False)\n", "renderer = Renderer(camera=camera, scene=scene, controls=[controller],\n", " width=view_width, height=view_height)" @@ -93,9 +130,14 @@ "metadata": {}, "outputs": [], "source": [ - "chks = [Checkbox(True, description='GL line'), Checkbox(True, description='Fat line')]\n", + "chks = [\n", + " Checkbox(True, description='GL line'),\n", + " Checkbox(True, description='Fat line (single color)'),\n", + " Checkbox(True, description='Fat line (vertex colors)'),\n", + "]\n", "jslink((chks[0], 'value'), (line1, 'visible'))\n", "jslink((chks[1], 'value'), (line2, 'visible'))\n", + "jslink((chks[2], 'value'), (line3, 'visible'))\n", "VBox([renderer, HBox(chks)])" ] }, @@ -106,18 +148,30 @@ "outputs": [], "source": [] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For reference, the code below shows how you would recreate the line geometry and material from the kernel. The only significant difference is that you need to declare the render view resolution on material creation, while the included `LineMaterial` automatically sets this." + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ + "# The line segment points and colors. Each array of six is one instance/segment [x1, y1, z1, x2, y2, z2]\n", "posInstBuffer = InstancedInterleavedBuffer( np.array([[0, 0, 0, 1, 1, 1], [2, 2, 2, 4, 4, 4]], dtype=np.float32))\n", "colInstBuffer = InstancedInterleavedBuffer( np.array([[1, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1]], dtype=np.float32))\n", - "dbgG = InstancedBufferGeometry(attributes={\n", - " 'position': BufferAttribute(np.array([ [- 1, 2, 0], [1, 2, 0], [- 1, 1, 0], [1, 1, 0], [- 1, 0, 0], [1, 0, 0], [- 1, - 1, 0], [1, - 1, 0] ], dtype=np.float32)),\n", + "\n", + "# This uses InstancedBufferGeometry, so that the geometry is reused for each line segment\n", + "lineGeo = InstancedBufferGeometry(attributes={\n", + " # Helper line geometry (2x4 grid), that is instanced\n", + " 'position': BufferAttribute(np.array([[- 1, 2, 0], [1, 2, 0], [- 1, 1, 0], [1, 1, 0], [- 1, 0, 0], [1, 0, 0], [- 1, - 1, 0], [1, - 1, 0] ], dtype=np.float32)),\n", " 'uv': BufferAttribute(np.array([ [- 1, 2], [1, 2], [- 1, 1], [1, 1], [- 1, - 1], [1, - 1], [- 1, - 2], [1, - 2] ], dtype=np.float32)),\n", " 'index': BufferAttribute(np.array([ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ], dtype=np.uint8)),\n", + " # The line segments are split into start/end for each instance:\n", " 'instanceStart': InterleavedBufferAttribute(posInstBuffer, 3, 0),\n", " 'instanceEnd': InterleavedBufferAttribute(posInstBuffer, 3, 3),\n", " 'instanceColorStart': InterleavedBufferAttribute(colInstBuffer, 3, 0),\n", @@ -131,7 +185,8 @@ "metadata": {}, "outputs": [], "source": [ - "m = ShaderMaterial(\n", + "# The line material shader:\n", + "lineMat = ShaderMaterial(\n", " vertexShader='''\n", "#include \n", "#include \n", @@ -254,12 +309,6 @@ "\n", " gl_Position = clip;\n", " \n", - " //gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n", - " \n", - " //if ( instanceStart.x > 1.5) {\n", - " // gl_Position.x += 2.0;\n", - " //}\n", - " \n", " vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation\n", "\n", " #include \n", @@ -316,24 +365,16 @@ " linewidth={'value': 10.0},\n", " resolution={'value': (100., 100.)},\n", " )\n", - ")\n", - "Mesh(g2, m)" + ")" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "print(repr(line2))" + "Mesh(lineGeo, lineMat)" ] }, { diff --git a/js/scripts/prop-types.js b/js/scripts/prop-types.js index 4ed6bb44..b43534d4 100644 --- a/js/scripts/prop-types.js +++ b/js/scripts/prop-types.js @@ -27,6 +27,9 @@ class BaseType { if (this.defaultValue === Infinity || this.defaultValue === -Infinity) { return this.defaultValue.toString(); } + if (this.defaultValue === undefined) { + return 'undefined'; + } return JSON.stringify(this.defaultValue); } getPropArrayName() { @@ -264,7 +267,7 @@ class Bool extends BaseType { class Int extends BaseType { constructor(defaultValue, options) { options = options || {}; - super(); + super(options); this.minValue = options.minValue; this.maxValue = options.maxValue; this.defaultValue = (defaultValue === null || defaultValue === undefined) && !this.nullable ? 0 : defaultValue ; diff --git a/js/src/geometries/LineSegmentsGeometry.js b/js/src/geometries/LineSegmentsGeometry.js index ef60a347..42beef3c 100644 --- a/js/src/geometries/LineSegmentsGeometry.js +++ b/js/src/geometries/LineSegmentsGeometry.js @@ -1,4 +1,6 @@ var Promise = require('bluebird'); +var dataserializers = require('jupyter-dataserializers'); +var ndarray = require('ndarray'); var LineSegmentsGeometry = require('../examples/lines/LineSegmentsGeometry.js').LineSegmentsGeometry; var LineSegmentsGeometryAutogen = require('./LineSegmentsGeometry.autogen').LineSegmentsGeometryModel; @@ -25,26 +27,19 @@ var LineSegmentsGeometryModel = LineSegmentsGeometryAutogen.extend({ assignLineAttribute: function(obj, key, value) { if (key === 'positions') { - obj.setPositions(value); + obj.setPositions(value || []); } else if (key === 'colors') { - obj.setColors(value); + if (value) { + obj.setColors(value); + } } else { throw new Error(`Unknown line attribute key: ${key}`); } }, convertArrayBufferThreeToModel: function(arrBuffer, propName) { - if (arrBuffer === null) { - return null; - } - var current = this.get(propName); - var currentArray = dataserializers.getArray(current); - if (currentArray && (currentArray.data === arrBuffer)) { - // Unchanged, do nothing - return current; - } - // Never create a new widget, even if current is one - return ndarray(arrBuffer, currentArray && currentArray.shape); + // This property is write-only, so always return current value. + return this.get(propName); }, }); From 9bdc184bc3ef0f6c42046b28e019d2e75ff20e1f Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Tue, 2 Oct 2018 13:41:48 +0200 Subject: [PATCH 4/7] Avoid deprecated prepublish script --- js/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/package.json b/js/package.json index e1caae4c..1f65ee3d 100644 --- a/js/package.json +++ b/js/package.json @@ -19,7 +19,8 @@ "build:bundles-prod": "webpack -p && node ./scripts/copy-files.js", "build:labextension": "rimraf lab-dist && mkdirp lab-dist && cd lab-dist && npm pack ..", "build:all": "npm run build:labextension", - "prepublish": "npm run autogen && npm run build:bundles-prod" + "prepare": "npm run autogen", + "prepack": "npm run build:bundles-prod" }, "devDependencies": { "eslint": "^5.6.0", From 58f6b8ef700558728784c2d4c529e57b553a9a90 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Tue, 2 Oct 2018 14:20:05 +0200 Subject: [PATCH 5/7] Use node 8 on travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ca42cb3c..ab139ac3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ cache: - ~/.npm # NPM cache before_install: - pip install -U pip setuptools - - nvm install 6 + - nvm install 8 install: - pip install --upgrade -e ".[test, examples, docs]" script: From 3d8ef24e3635daa2b51687c65049ed9eed8847b3 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Tue, 2 Oct 2018 15:13:43 +0200 Subject: [PATCH 6/7] Make line geometry example py2 compatible And compatible with py 3.4. --- examples/LineGeometry.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/LineGeometry.ipynb b/examples/LineGeometry.ipynb index 97350bb9..851264d6 100644 --- a/examples/LineGeometry.ipynb +++ b/examples/LineGeometry.ipynb @@ -361,9 +361,9 @@ "''',\n", " vertexColors='VertexColors',\n", " uniforms=dict(\n", - " **UniformsLib['common'],\n", " linewidth={'value': 10.0},\n", " resolution={'value': (100., 100.)},\n", + " **UniformsLib['common']\n", " )\n", ")" ] From b44ca4320f4d1693bd023f8697a5f42a288ac696 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Tue, 2 Oct 2018 15:22:39 +0200 Subject: [PATCH 7/7] Undo previous cleanup (py2 compat) --- examples/Shaders.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Shaders.ipynb b/examples/Shaders.ipynb index 2a06a311..40d978bd 100644 --- a/examples/Shaders.ipynb +++ b/examples/Shaders.ipynb @@ -71,10 +71,10 @@ "source": [ "material = ShaderMaterial(\n", " uniforms=dict(\n", - " **UniformsLib['common'],\n", " time=dict(value=0.0),\n", " resolution=dict(value=(1, 1)),\n", " user_color=dict(value='green'),\n", + " **UniformsLib['common']\n", " ),\n", " defines=dict(\n", " FIX_COLOR=1,\n",