itch.io is community of indie game creators and players

Devlogs

Initial Implementations

MeshPainter
A browser tool made in HTML5

# Mesh Painter — Devlog

> `*` = paid-only feature

## Session 1 — Initial build, export system, OBJ import, UI, controls

Initial OBJ mesh loading with loading overlay, progress bar, and batch updates. Drawing optimized with 3D sphere pre-filter and spatial grid for 60+ FPS on 250k-vertex meshes. Mesh rebuild only on dirty surfaces.

\* Export system added — OBJ export with vertex colors in `v x y z r g b` format, baked 1024×1024 texture PNG + MTL. GLB export via custom binary glTF 2.0 writer (positions, normals, UVs, vertex colors, 16-bit indices). Exports use counter-clockwise winding to match standard tools. HTML5 shows "not available in browser" notice for export.

OBJ import preserves vertex colors by parsing extended `v x y z r g b` lines. ObjLoader.gd supports both `load_from_file` and `load_from_text` static methods. Surface format checked for COLOR flag (`1 << 4`). Mesh-loading logic extracted into shared `_on_mesh_loaded` helper.

UI fixes — mouse click/paint passthrough guard that recursively checks all visible Controls under CanvasLayer and FileDialogs. Orbit/paint release fires before UI guard (releasing anywhere stops rotation/painting). Ghost UI controls fixed by only recursing into visible Controls. OS.alert() replaced with in-game AcceptDialog. `Image.set_pixel()` crash fixed with proper lock/unlock.

Top buttons act as tabs — clicking one hides other panels. Controls panel added with "LMB hold — Paint" instruction. Options panel with Zoom Step and Brush Step sliders. Clear paint shows ConfirmationDialog (undo still works). Scroll direction reversed: Shift+Wheel Up = bigger brush, Shift+Wheel Down = smaller.

Demo OBJ files synced to `user://demo_meshes/` on startup with stale file cleanup. `subdivide_mesh.gd` tool script for editor subdivision. FileDialog uses `ACCESS_USERDATA` for consistent behavior on desktop and HTML5. GitHub repo created with README.

## Session 2 — Auth, per-user files, save/load dialogs, free/paid gating, wireframe, brush system

Supabase authentication with credential persistence — email and password saved to `user://auth.cred`. AuthUI pre-fills login form on show. CloudAPI with paid recognition (`is_paid()`, `check_export_capability()`). Status label shows green "PAID VERSION", orange "FREE VERSION — BETA", or gray "FREE VERSION".

Main scene reorganized — AppRoot (Spatial) wraps 3D+dialogs, AppUI (Control) under CanvasLayer. Ready hides both before auth check, shows them on app init, hides on logout. Full signal cleanup on logout with safe disconnect wrapper. Collision shape cleaned up before recreation.

Per-user file isolation via email-derived slug (`email_at_domain_com`). All config and autosave paths use slug: `config_{slug}.cfg`, `autosave_{slug}.mp`. `Session.cfg` (SupabaseAuth) separated from per-user config (Main.gd) to avoid circular dependency on slug for auth restore.

Save/load dialog system — save/load `.mp` project files, save `.obj` with vertex colors. Exit flow: main screen shows save dialog (if savable) or silent autosave then logout; login screen quits immediately; window close triggers silent autosave only. Exit button uses re-entrancy guard instead of disconnect/reconnect. Demo mesh paths are protected from save/export/exit-dialog-save with "Save As Required" notification. Original file missing shows "File Not Found" before falling back to fresh cube.

\* Save As dialog filter — free users see only `*.mp`; paid users see `*.mp` + `*.obj`. Mesh info label refreshes after save.

Wireframe overlay with child MeshInstance, `PRIMITIVE_LINES`, unshaded blue material, Y toggle, X X-ray toggle (no depth test). Controls panel updated with wireframe controls.

Planar disc brush replaces spherical brush + angle culling — vertices projected onto hit surface plane, depth limit 0.3×radius, falloff uses planar distance. No vertex normals needed. Falloff changed from hard/soft dropdown to float slider (0.0–1.0). Radius range narrowed to [0.01, 1.0] with step 0.01.

Tier naming uses "BETA" instead of "DEMO" — implies active development. Version labeled "Beta 1.0.0". Backward-compatible `is_demo` field read for existing accounts.

## Session 3 — Eye dropper, controls/help UI, paid gating, bug fixes

\* Eye dropper (Ctrl+LMB on mesh) picks nearest vertex color via spatial grid — fast lookup, no barycentric interpolation. Sets brush color and updates params panel. Shows 64×64 circle swatch near cursor that fades out over 1.0s. Custom eyedropper cursor (`res://cursors/eyedropper.png`) shown while Ctrl held. Swatch and cursor cleaned up on logout. Cursor resets when mouse over UI while eye dropper active.

Controls button changed from "?" to "Controls" — opens side panel with all controls listed. Control list expanded with Shift+Scroll (brush size), Y (wireframe), X (X-ray), \*Ctrl+LMB (eye dropper). New "?" Help button opens centered AcceptDialog with version, credits, all controls, and itch.io link.

Free version grays out paid-only controls with "★" suffix to encourage upgrade. ControlsPanel height extended from 344px to 412px for all 15 labels. Help dialog control list kept exactly in sync.

Bug fixes — logout no longer deadlocks input (deactivates camera, resets mouse mode to VISIBLE, hides LoadingOverlay, exit button reordered to topmost). Exit button guard replaced disconnect/reconnect with permanent connection + `_exiting` re-entrancy flag. Exit-save on demo mesh no longer softlocks login (`_show_exit_save_dialog` returns `_save_to_path` result directly; `_logout` hides notification before hiding AppRoot). Demo meshes always load with full white vertex colors.

## Session 4 — 2D brush profile preview in ParamsPanel

Brush preview added to ParamsPanel — three live previews below the brush controls and above the color button:

- **FalloffPreview** — smoothstep-based falloff curve (white line, 50 steps) with cyan fill at 15% alpha over gray background

- **OpacityPreview** — level bar filled from bottom to current opacity height

- **RadiusPreview** — level bar filled from bottom to current radius height

All three update live from their respective sliders (radius, opacity, falloff) and redraw on any parameter change. Brush previews are available to all users (free feature).

BrushCursor.gd reverted to outline ring only — no inner disc.

## Session 5 — Autosave timer + progress bar

\* Autosave timer — silently saves after 3 minutes of inactivity (default, configurable 1–10 min via Options panel slider step 1). Timer resets on every brush stroke; only fires when there are unsaved changes and not currently painting.

Progress bar in ParamsPanel shows countdown (`M:SS / Xm`) and fills as time elapses. After save, shows `✓ Autosaved: filename.mp` for 2 seconds, then returns to countdown display. Config saved/loaded per-user.

Timer stopped on logout, signals disconnected on app shutdown (\* paid gating). Free users see grayed toggle with "★", non-editable slider, grayed labels, timer stopped, progress zeroed, label shows "★ Paid feature". Collapsible "▶ Autosave" section at bottom of Brush params.

## Session 6 — Threaded mesh loading, activity indicator, performance optimization

Threaded mesh loading infrastructure — `MeshWorker.gd` reusable thread wrapper with `call_deferred` callback and mutex-guarded state machine (IDLE/RUNNING/DONE/FAILED). ObjLoader.gd refactored with `_parse_file_to_arrays()` returning plain-Array dicts (thread-safe) and `_arrays_to_mesh()` for main-thread mesh creation.

File > Open `.obj` now parses on a background thread — file I/O and text parsing offloaded, main thread handles `ArrayMesh.add_surface_from_arrays` and scene tree updates. `.mp` loading kept synchronous (`File.get_var()` not thread-safe in Godot 3.x).

Activity indicator (spinning radial arc) in bottom-right of AppUI, shown/hidden based on active worker count. Grid rebuild optimized — `_build_vertex_grid` replaced MeshDataTool with `surface_get_arrays` direct iteration for faster rebuild.

Bug fix — `.mp` vertex colors no longer wiped on reload: `_init_white_colors` removed from `_on_mesh_loaded` (was overwriting saved colors); moved to `_load_fresh_cube` only where it belongs.

---

`*` = paid-only feature

Files

  • MeshPainter_html5 28 MB
    1 day ago
  • MeshPainter_Linux.zip 36 MB
    1 day ago
  • MeshPainter_MacOSX.zip 44 MB
    1 day ago
  • MeshPainter_Windows.zip 35 MB
    1 day ago
Download MeshPainter