{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Dual Contouring\n",
    "\n",
    "Dual Contouring extracts a **triangle mesh** with vertices placed optimally within each cell. Unlike Marching Cubes, it can preserve sharp features when given accurate surface normals.\n",
    "\n",
    "## Simple Usage\n",
    "\n",
    "For most cases, you can use the simple one-liner interface:\n",
    "\n",
    "```python\n",
    "vertices, faces = isoext.dual_contouring(grid, level=0.0)\n",
    "```\n",
    "\n",
    "This automatically computes intersection points and normals from the grid values.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:23.328082Z",
     "iopub.status.busy": "2026-07-05T04:06:23.327997Z",
     "iopub.status.idle": "2026-07-05T04:06:23.956373Z",
     "shell.execute_reply": "2026-07-05T04:06:23.955624Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "import isoext\n",
    "from isoext.sdf import CuboidSDF, get_sdf_normal\n",
    "from isoext import viewer\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:23.958005Z",
     "iopub.status.busy": "2026-07-05T04:06:23.957883Z",
     "iopub.status.idle": "2026-07-05T04:06:24.266261Z",
     "shell.execute_reply": "2026-07-05T04:06:24.265723Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vertices: torch.Size([6146, 3])\n",
      "Faces: torch.Size([12288, 3])\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"100%\"\n",
       "            height=\"420\"\n",
       "            src=\"_static/viser/index.html?playbackPath=../scenes/12fca5502d9cf198.viser\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "            style=\"border: 1px solid #8888; border-radius: 4px;\"\n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x78358d395f70>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Simple usage - just call dual_contouring directly\n",
    "grid = isoext.UniformGrid([64, 64, 64])\n",
    "cube = CuboidSDF(size=[1.0, 1.0, 1.0])\n",
    "grid.set_values(cube(grid.get_points()))\n",
    "\n",
    "# One-liner: normals computed automatically from grid values\n",
    "vertices, faces = isoext.dual_contouring(grid, level=0.0)\n",
    "\n",
    "print(f\"Vertices: {vertices.shape}\")\n",
    "print(f\"Faces: {faces.shape}\")\n",
    "\n",
    "viewer.embed(vertices, faces, color=\"lightblue\", flat_shading=True)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Custom Normals for Sharp Features\n",
    "\n",
    "For shapes with sharp features (like cubes), you can provide custom normals computed from the SDF gradient for better results:\n",
    "\n",
    "1. **Get intersections** — Call `get_intersection()` to get intersection points\n",
    "2. **Compute normals** — Use SDF gradient for accurate surface normals\n",
    "3. **Run dual contouring** — Pass the intersection with custom normals\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:24.282380Z",
     "iopub.status.busy": "2026-07-05T04:06:24.282229Z",
     "iopub.status.idle": "2026-07-05T04:06:24.284407Z",
     "shell.execute_reply": "2026-07-05T04:06:24.284207Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Intersection shape: torch.Size([24576, 3])\n"
     ]
    }
   ],
   "source": [
    "# Step 1: Find edge-surface intersections\n",
    "intersection = isoext.get_intersection(grid, level=0.0)\n",
    "points = intersection.get_points()\n",
    "print(f\"Intersection shape: {points.shape}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:24.285693Z",
     "iopub.status.busy": "2026-07-05T04:06:24.285624Z",
     "iopub.status.idle": "2026-07-05T04:06:24.520765Z",
     "shell.execute_reply": "2026-07-05T04:06:24.519681Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Normals shape: torch.Size([24576, 3])\n"
     ]
    }
   ],
   "source": [
    "# Step 2: Compute accurate normals using the SDF gradient\n",
    "normals = get_sdf_normal(cube, points)\n",
    "intersection.set_normals(normals)\n",
    "\n",
    "print(f\"Normals shape: {normals.shape}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:24.522482Z",
     "iopub.status.busy": "2026-07-05T04:06:24.522332Z",
     "iopub.status.idle": "2026-07-05T04:06:24.532114Z",
     "shell.execute_reply": "2026-07-05T04:06:24.531948Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vertices: torch.Size([6146, 3])\n",
      "Faces: torch.Size([12288, 3])\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"100%\"\n",
       "            height=\"420\"\n",
       "            src=\"_static/viser/index.html?playbackPath=../scenes/addd76f03945e2c6.viser\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "            style=\"border: 1px solid #8888; border-radius: 4px;\"\n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x78358c205f70>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Step 3: Run dual contouring with custom normals\n",
    "vertices, faces = isoext.dual_contouring(grid, level=0.0, intersection=intersection)\n",
    "\n",
    "print(f\"Vertices: {vertices.shape}\")  # (N, 3) float32\n",
    "print(f\"Faces: {faces.shape}\")        # (M, 3) int32\n",
    "\n",
    "viewer.embed(vertices, faces, color=\"salmon\", flat_shading=True)\n"
   ]
  },
  {
   "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.12.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
