nx_mesh_sculpt.nx source
↩ module page · 124 lines · 5019 B
1// nx_mesh_sculpt.nx -- ZBrush-class curved primitive + sculpt brush, sovereign.
2//
3// Closes the "curved geometry + sculpting" gap on the mesh kernel. Builds an
4// icosphere by subdividing an octahedron and projecting every vertex onto the
5// sphere with integer nx_isqrt (NO trig, NO f64 -- both unreliable here), then
6// a radial "inflate/draw" brush that pushes verts within a brush radius outward
7// along their normal with a smooth (linear) falloff. For an origin-centred
8// primitive the outward radial direction IS the surface normal, so this is a
9// correct draw brush on the sphere; general per-vertex normals are a follow-up.
10// Reuses nx_mesh_subdivide (nx_mesh_edit) + nx_isqrt. No dup: grepped, none
11// existed. Output is pipeline-native Q14-mm -> flows to nx_stl_write_mesh.
12// license_tier: ORIGINAL.
13
14import "nx_syscalls.nx"
15import "nx_mesh.nx"
16import "nx_mesh_edit.nx"
17import "nx_isqrt.nx"
18
19// distance of vertex `idx` from the origin (Q14), = |pos|.
20func nx_mesh_vert_radius(m: *NxMesh, idx: i64) -> i64 {
21 let x: i64 = nx_mesh_get_vertex_x(m, idx)
22 let y: i64 = nx_mesh_get_vertex_y(m, idx)
23 let z: i64 = nx_mesh_get_vertex_z(m, idx)
24 return nx_isqrt(x * x + y * y + z * z)
25}
26
27// ===== factory: octahedron =======================================
28// 6 verts on the axes at +/-radius, 8 outward-wound triangles.
29
30func nx_mesh_make_octahedron(radius_q14: i64, color: i64) -> *NxMesh {
31 let m: *NxMesh = nx_mesh_alloc(6, 8, 0)
32 if (m as i64) == 0 { return m }
33 let r: i64 = radius_q14
34 let nr: i64 = 0 - radius_q14
35 nx_mesh_set_vertex(m, 0, r, 0, 0, color) // +x
36 nx_mesh_set_vertex(m, 1, nr, 0, 0, color) // -x
37 nx_mesh_set_vertex(m, 2, 0, r, 0, color) // +y
38 nx_mesh_set_vertex(m, 3, 0, nr, 0, color) // -y
39 nx_mesh_set_vertex(m, 4, 0, 0, r, color) // +z (north)
40 nx_mesh_set_vertex(m, 5, 0, 0, nr, color) // -z (south)
41 // top hemisphere (apex 4), CCW outward
42 nx_mesh_set_triangle(m, 0, 4, 0, 2)
43 nx_mesh_set_triangle(m, 1, 4, 2, 1)
44 nx_mesh_set_triangle(m, 2, 4, 1, 3)
45 nx_mesh_set_triangle(m, 3, 4, 3, 0)
46 // bottom hemisphere (apex 5)
47 nx_mesh_set_triangle(m, 4, 5, 2, 0)
48 nx_mesh_set_triangle(m, 5, 5, 1, 2)
49 nx_mesh_set_triangle(m, 6, 5, 3, 1)
50 nx_mesh_set_triangle(m, 7, 5, 0, 3)
51 return m
52}
53
54// ===== project every vertex onto the sphere of given radius ======
55// In-place. v' = v * R / |v|, integer-rounded via nx_isqrt. Axis
56// verts already at radius R are exactly preserved.
57
58func nx_mesh_project_to_sphere(m: *NxMesh, radius_q14: i64) -> i64 {
59 var i: i64 = 0
60 while i < m.n_verts {
61 let base: i64 = i * NX_MESH_VTX_STRIDE
62 let x: i64 = m.verts[base + 0]
63 let y: i64 = m.verts[base + 1]
64 let z: i64 = m.verts[base + 2]
65 let len: i64 = nx_isqrt(x * x + y * y + z * z)
66 if len > 0 {
67 m.verts[base + 0] = x * radius_q14 / len
68 m.verts[base + 1] = y * radius_q14 / len
69 m.verts[base + 2] = z * radius_q14 / len
70 }
71 i = i + 1
72 }
73 return NX_MESH_OK
74}
75
76// ===== factory: icosphere ========================================
77// Octahedron subdivided `subdiv` times, each level projected to the
78// sphere. subdiv=0 -> octahedron; each level: tris*4, verts+tris*3.
79
80func nx_mesh_make_icosphere(radius_q14: i64, subdiv: i64, color: i64) -> *NxMesh {
81 var m: *NxMesh = nx_mesh_make_octahedron(radius_q14, color)
82 var k: i64 = 0
83 while k < subdiv {
84 m = nx_mesh_subdivide(m)
85 nx_mesh_project_to_sphere(m, radius_q14)
86 k = k + 1
87 }
88 return m
89}
90
91// ===== sculpt: radial inflate/draw brush =========================
92//
93// Pushes every vertex within `radius_q14` of the brush centre outward along
94// its radial (= normal, for origin-centred meshes) direction by
95// disp = strength_q14 * (radius - dist_to_centre) / radius (linear falloff)
96// New position = v * (|v| + disp) / |v|. strength>0 raises a bump, <0 carves.
97// Topology (vert/tri counts) is preserved. In-place.
98
99func nx_mesh_sculpt_brush(m: *NxMesh, cx: i64, cy: i64, cz: i64,
100 radius_q14: i64, strength_q14: i64) -> i64 {
101 if radius_q14 <= 0 { return NX_MESH_ERR_BAD_DIMS }
102 var i: i64 = 0
103 while i < m.n_verts {
104 let base: i64 = i * NX_MESH_VTX_STRIDE
105 let x: i64 = m.verts[base + 0]
106 let y: i64 = m.verts[base + 1]
107 let z: i64 = m.verts[base + 2]
108 let dx: i64 = x - cx
109 let dy: i64 = y - cy
110 let dz: i64 = z - cz
111 let d: i64 = nx_isqrt(dx * dx + dy * dy + dz * dz)
112 if d < radius_q14 {
113 let disp: i64 = strength_q14 * (radius_q14 - d) / radius_q14
114 let len: i64 = nx_isqrt(x * x + y * y + z * z)
115 if len > 0 {
116 m.verts[base + 0] = x * (len + disp) / len
117 m.verts[base + 1] = y * (len + disp) / len
118 m.verts[base + 2] = z * (len + disp) / len
119 }
120 }
121 i = i + 1
122 }
123 return NX_MESH_OK
124}