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.
*