diff --git a/src/viser/_message_api.py b/src/viser/_message_api.py index dc18f0357..193bd9e12 100644 --- a/src/viser/_message_api.py +++ b/src/viser/_message_api.py @@ -452,6 +452,7 @@ def add_mesh_simple( color: RgbTupleOrArray = (90, 200, 255), wireframe: bool = False, opacity: Optional[float] = None, + material: Literal["standard", "toon3", "toon5"] = "standard", side: Literal["front", "back", "double"] = "front", wxyz: Tuple[float, float, float, float] | onp.ndarray = (1.0, 0.0, 0.0, 0.0), position: Tuple[float, float, float] | onp.ndarray = (0.0, 0.0, 0.0), @@ -469,6 +470,7 @@ def add_mesh_simple( wireframe=wireframe, opacity=opacity, side=side, + material=material, ) ) node_handle = MeshHandle._make(self, name, wxyz, position, visible) diff --git a/src/viser/_messages.py b/src/viser/_messages.py index 26397ba71..eeac88a6d 100644 --- a/src/viser/_messages.py +++ b/src/viser/_messages.py @@ -185,7 +185,8 @@ class MeshMessage(Message): wireframe: bool opacity: Optional[float] - side: Literal["front", "back", "double"] = "front" + side: Literal["front", "back", "double"] + material: Literal["standard", "toon3", "toon5"] def __post_init__(self): # Check shapes. diff --git a/src/viser/client/src/App.tsx b/src/viser/client/src/App.tsx index 5b3f69b4b..cb91fe1bb 100644 --- a/src/viser/client/src/App.tsx +++ b/src/viser/client/src/App.tsx @@ -268,6 +268,12 @@ function ViewerCanvas({ children }: { children: React.ReactNode }) { + + ); } diff --git a/src/viser/client/src/WebsocketInterface.tsx b/src/viser/client/src/WebsocketInterface.tsx index 5469311d7..0390d648d 100644 --- a/src/viser/client/src/WebsocketInterface.tsx +++ b/src/viser/client/src/WebsocketInterface.tsx @@ -236,18 +236,51 @@ function useMessageHandler() { // Add mesh case "MeshMessage": { const geometry = new THREE.BufferGeometry(); - const material = new THREE.MeshStandardMaterial({ + + const generateGradientMap = (shades: 3 | 5) => { + const texture = new THREE.DataTexture( + Uint8Array.from( + shades == 3 + ? [0, 0, 0, 255, 128, 128, 128, 255, 255, 255, 255, 255] + : [ + 0, 0, 0, 255, 64, 64, 64, 255, 128, 128, 128, 255, 192, 192, + 192, 255, 255, 255, 255, 255, + ], + ), + shades, + 1, + THREE.RGBAFormat, + ); + + texture.needsUpdate = true; + return texture; + }; + const standardArgs = { color: message.color || undefined, vertexColors: message.vertex_colors !== null, wireframe: message.wireframe, transparent: message.opacity !== null, - opacity: message.opacity ?? undefined, + opacity: message.opacity ?? 1.0, side: { front: THREE.FrontSide, back: THREE.BackSide, double: THREE.DoubleSide, }[message.side], - }); + }; + const material = + message.material == "standard" + ? new THREE.MeshStandardMaterial(standardArgs) + : message.material == "toon3" + ? new THREE.MeshToonMaterial({ + gradientMap: generateGradientMap(3), + ...standardArgs, + }) + : message.material == "toon5" + ? new THREE.MeshToonMaterial({ + gradientMap: generateGradientMap(5), + ...standardArgs, + }) + : undefined; geometry.setAttribute( "position", new THREE.Float32BufferAttribute( diff --git a/src/viser/client/src/WebsocketMessages.tsx b/src/viser/client/src/WebsocketMessages.tsx index 28e782a02..86af61b18 100644 --- a/src/viser/client/src/WebsocketMessages.tsx +++ b/src/viser/client/src/WebsocketMessages.tsx @@ -145,6 +145,7 @@ export interface MeshMessage { wireframe: boolean; opacity: number | null; side: "front" | "back" | "double"; + material: "standard" | "toon3" | "toon5"; } /** Message for transform gizmos. *