jupyter-widgets / pythreejs

A Jupyter - Three.js bridge
https://pythreejs.readthedocs.io
Other
943 stars 188 forks source link

PlainGeometry missing in 2.0.2 #254

Closed PBrockmann closed 5 years ago

PBrockmann commented 5 years ago

I cannot instantiate a PlainGeometry with last 2.0.2 release.

geom = PlainGeometry(vertices=[[0,0,0],[30,0,0],[30,30,0]],
faces=[[0,1,2]],
colors = ['red', 'blue', 'green'])
mesh = Mesh(geometry=geom, material=BasicMaterial(color='blue'))
scene = Scene(children=[mesh,AmbientLight(color='#cccccc')])
c = PerspectiveCamera(position=[0, 0, 100], fov = 40, aspect = 6/4, near = 1, far = 1000)
renderer = Renderer(camera=c, scene = scene, controls=[OrbitControls(controlling=c)])
display(renderer)

should work. I get NameError: name 'PlainGeometry' is not defined

vidartf commented 5 years ago

The Plain[Buffer]Geometry classes was renamed when going to 1.x, in order to more closely align with the three.js API: https://pythreejs.readthedocs.io/en/stable/installing.html#upgrading-to-1-x

vidartf commented 5 years ago
PBrockmann commented 5 years ago

An example with a face-colored geometry would help me a lot. As the cube in https://stemkoski.github.io/Three.js/Vertex-Colors.html

Here is what I have done using three.js, now I would like to do the same using pythreejs !

function meshCreate(grid, values) {                             // quad is made from 2 triangles
        var geometry = new THREE.Geometry;
        var id = 0;
        for (var i=0; i<grid.length; i++) {
                cell = grid[i];
                for (var j=0; j<cell.length; j++) { 
                         geometry.vertices.push(new THREE.Vector3(cell[j][0], cell[j][1], cell[j][2]) 
                }      
                // counter-clockwise
                face = new THREE.Face3(id, id+2, id+1);
                r = Math.floor(Math.random());
                g = Math.floor(Math.random());
                b = Math.floor(Math.random());      
                face.color.setRGB(r, g,b);
                geometry.faces.push(face);
                face = new THREE.Face3(id, id+3, id+2);
                face.color.setRGB(r, g,b);
                geometry.faces.push(face);
                id = id + 4;
        }
        return new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, wireframe: false}) );
vidartf commented 5 years ago

You could have a look at this example: https://render.githubusercontent.com/view/ipynb?commit=645ea6bea758555978f83bd0004ce561fa58d99c&enc_url=68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f6a7570797465722d776964676574732f707974687265656a732f363435656136626561373538353535393738663833626430303034636535363166613538643939632f6578616d706c65732f4578616d706c65732e6970796e62&nwo=jupyter-widgets%2Fpythreejs&path=examples%2FExamples.ipynb&repository_id=15400194&repository_type=Repository#Buffer-Geometries

vidartf commented 5 years ago

Or if you still want to use the old Geometry type, the previous paragraph to that example can be helpful (I would say the BufferGeometry code is cleaner though).

PBrockmann commented 5 years ago

Thanks @vidartf for your help on this issue. In fact I would like to handle faces of a geometry with colored flat faces as this example in three.js.

image

I would like to do the same in a python notebook.

<!DOCTYPE html>
<html>
<head>

<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

<script src="three.js/build/three.js"></script>
<script src="three.js/controls/OrbitControls.js"></script>
<script>

$( document ).ready(function() {

//#################################################

width = 500;
height = 500;

$('#div1').width(width);
$('#div1').height(height);

scene = new THREE.Scene();
scene.background = new THREE.Color( 0xeeeeee );

var VIEW_ANGLE = 45, ASPECT = width / height, NEAR = 0.1, FAR = 20000;
camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
camera.position.set(2,2,2);
camera.lookAt(scene.position);  
scene.add(camera);

renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(width, height);
$('#div1').append(renderer.domElement);

controls = new THREE.OrbitControls(camera, renderer.domElement);

vertices = [
    [0, 0, 0],
    [0, 0, 1],
    [0, 1, 0],
    [0, 1, 1],
    [1, 0, 0],
    [1, 0, 1],
    [1, 1, 0],
    [1, 1, 1]
];

faces = [
    [0, 1, 3],
    [0, 3, 2],
    [0, 2, 4],
    [2, 6, 4],
    [0, 4, 1],
    [1, 4, 5],
    [2, 3, 6],
    [3, 7, 6],
    [1, 5, 3],
    [3, 5, 7],
    [4, 6, 5],
    [5, 6, 7]
];

geometry = new THREE.Geometry();
for (var i=0; i<vertices.length; i++) {
    vector3 = new THREE.Vector3(vertices[i][0]-0.5, vertices[i][1]-0.5, vertices[i][2]-0.5)
    geometry.vertices.push(vector3);
}
for (var i=0; i<faces.length; i++) {
    face = new THREE.Face3(faces[i][0], faces[i][1], faces[i][2]);
    face.color.setRGB(Math.random(), Math.random(), Math.random());
    geometry.faces.push(face);
}

mesh = new THREE.Mesh(
    geometry,
    new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, wireframe: false})
);
scene.add(mesh);

//==============================================================================================
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
    controls.update();
}

animate();
//#################################################
});

</script>
</head>

<body>
<div id='div1'></div>
</body>
</html>
PBrockmann commented 5 years ago

This line is obscur. And I haven't been able to generate a facesColored list as an argument to Mesh. I would like to understand how to use a facescolors variable.

# Map the vertex colors into the 'color' slot of the faces
faces = [f + [None, [vertexcolors[i] for i in f], None] for f in faces]

image

vertices = [
    [0, 0, 0],
    [0, 0, 1],
    [0, 1, 0],
    [0, 1, 1],
    [1, 0, 0],
    [1, 0, 1],
    [1, 1, 0],
    [1, 1, 1]
]

faces = [
    [0, 1, 3],
    [0, 3, 2],
    [0, 2, 4],
    [2, 6, 4],
    [0, 4, 1],
    [1, 4, 5],
    [2, 3, 6],
    [3, 7, 6],
    [1, 5, 3],
    [3, 5, 7],
    [4, 6, 5],
    [5, 6, 7]
]

vertexcolors = ['#000000', '#0000ff', '#00ff00', '#ff0000',
                '#00ffff', '#ff00ff', '#ffff00', '#ffffff']

facescolors = ['#000000', '#0000ff', '#00ff00', '#ff0000',
               '#00ffff', '#ff00ff', '#ffff00', '#ffffff',
                '#00ffff', '#ff00ff', '#ffff00', '#ffffff']

# Map the vertex colors into the 'color' slot of the faces
faces = [f + [None, [vertexcolors[i] for i in f], None] for f in faces]

# Create the geometry:
cubeGeometry = Geometry(
    vertices=vertices,
    faces=faces
)

# Create a mesh. Note that the material need to be told to use the vertex colors.
mesh = Mesh(
    geometry=cubeGeometry,
    material=MeshBasicMaterial(vertexColors='FaceColors'),
    position=[-0.5, -0.5, -0.5],   # Center the cube
)

scene = Scene(children=[mesh], background="#cccccc")

c = PerspectiveCamera(position=[3, 3, 3])

rendererCube = Renderer(camera=c,
                        scene=scene, 
                        controls=[OrbitControls(controlling=c)])

display(rendererCube)
PBrockmann commented 5 years ago

Got it. image

The faces list should be as [0, 1, 3, None, '#a4fcdd', None]

So replace in the previous pythreejs python code: faces = [f + [None, [vertexcolors[i] for i in f], None] for f in faces] by faces = [faces[i] + [None, rand_color.generate()[0], None] for i in range(0,len(faces))]

So code becomes:

vertices = [
    [0, 0, 0],
    [0, 0, 1],
    [0, 1, 0],
    [0, 1, 1],
    [1, 0, 0],
    [1, 0, 1],
    [1, 1, 0],
    [1, 1, 1]
]

faces = [
    [0, 1, 3],
    [0, 3, 2],
    [0, 2, 4],
    [2, 6, 4],
    [0, 4, 1],
    [1, 4, 5],
    [2, 3, 6],
    [3, 7, 6],
    [1, 5, 3],
    [3, 5, 7],
    [4, 6, 5],
    [5, 6, 7]
]

faces = [faces[i] + [None, rand_color.generate()[0], None] for i in range(0,len(faces))]

# Create the geometry:
cubeGeometry = Geometry(
    vertices=vertices,
    faces=faces
)

# Create a mesh. Note that the material need to be told to use the vertex colors.
mesh = Mesh(
    geometry=cubeGeometry,
    material=MeshBasicMaterial(vertexColors='FaceColors'),
    position=[-0.5, -0.5, -0.5],   # Center the cube
)

scene = Scene(children=[mesh], background="#cccccc")

c = PerspectiveCamera(position=[3, 3, 3])

rendererCube = Renderer(camera=c,
                        scene=scene, 
                        controls=[OrbitControls(controlling=c)])

display(rendererCube)
PBrockmann commented 5 years ago

Is there a way to optimize this simple example by using BufferGeometry rather than Geometry ? In fact, my real project is to display and update more than 360x180 quads (made from 2 triangles).

As with this mesh (2 deg x 2 deg) image

Test https://gist.github.com/PBrockmann/ddd0d5ad4205f153e1287be3a9a68078#file-pythreejs_07-ipynb

BTW, a 1deg x 1deg mesh is not displayed (https://github.com/jupyter-widgets/pythreejs/issues/219)

vidartf commented 5 years ago

Using a BufferGeometry should normally be much more performant, both in sync and in rendering. It might be slightly more complex to understand how to set up the arrays correctly though. Hopefully, there is a relevant JS example out there.

PBrockmann commented 5 years ago

Could you point me this exemple, one with a FacesColor mode ?

PBrockmann commented 5 years ago

Ok, I have figured out how to instantiate a BufferGeometry by transforming a classic Geometry with the from_geometry() method and then checking needed attributes.

geometry = Geometry(vertices=vertices, faces=faces)
bufferGeometry = BufferGeometry()
a = bufferGeometry.from_geometry(geometry)
a.attributes

Cube with FacesColor and a BufferGeometry

vertices = [
    [0., 0., 0.],
    [0., 0., 1.],
    [0., 1., 0.],
    [0., 1., 1.],
    [1., 0., 0.],
    [1., 0., 1.],
    [1., 1., 0.],
    [1., 1., 1.]
]

faces = [
    [0, 1, 3],
    [0, 3, 2],
    [0, 2, 4],
    [2, 6, 4],
    [0, 4, 1],
    [1, 4, 5],
    [2, 3, 6],
    [3, 7, 6],
    [1, 5, 3],
    [3, 5, 7],
    [4, 6, 5],
    [5, 6, 7]
]

p = []; c = []
for f in faces:
    color = [random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1)]
    for v in f:
        p.append(vertices[v])
        c.append(color)

# Create the buffer geometry:
geometry = BufferGeometry(attributes=dict(
    position=BufferAttribute(p),
    color=BufferAttribute(c)
))

# Create a mesh. Note that the material need to be told to use the vertex colors.
mesh = Mesh(
    geometry=geometry,
    material=MeshBasicMaterial(vertexColors='FaceColors'),
    position=[-0.5, -0.5, -0.5],   # Center the cube
)

scene = Scene(children=[mesh], background="#cccccc")

c = PerspectiveCamera(position=[3, 3, 3])

rendererCube = Renderer(camera=c,
                        scene=scene, 
                        controls=[OrbitControls(controlling=c)])

display(rendererCube)
PBrockmann commented 5 years ago

Comparison between the 2 codes are available from https://gist.github.com/PBrockmann/ddd0d5ad4205f153e1287be3a9a68078

I suppose the BufferGeometry procures a better display. Note that I have to set 6 times each color values to display face colored quads. Quad primitives is there crudelly missing.

I still have the limitations of the number of vertex. 1deg x 1deg does not display anything.

PBrockmann commented 5 years ago

Also would need to know how to update colors ? WIth a Geometry(vertices=vertices,faces=changeFaces()) you can modify faces and the update of the object is done.

Now with a

BufferGeometry(attributes=dict(
        position = BufferAttribute(p),
        color = changeColors()
))

No changes are done.

So I am wondering how to update a BufferGeometry ?

PBrockmann commented 5 years ago

Thanks to @vidartf for the solution

geometry.attributes['color'] = new_cList should be geometry.attributes['color'].array = new_cList

Correct notebook is available from https://gist.github.com/PBrockmann/f155a779382e2b8dec7543f3466e0798

I think this issue can be closed.