Created
April 6, 2020 16:00
-
-
Save Epihaius/e5e75668cb78a60f23dea87d1b4569ae to your computer and use it in GitHub Desktop.
Procedural Catmull-Clark subdivision surfaces
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This module contains a Python adaptation of the Catmull-Clark subdivision | |
# surface algorithm implemented in JavaScript, as found here: | |
# https://github.com/Erkaman/gl-catmull-clark/blob/master/index.js | |
# Its license is included below: | |
''' | |
This software is released under the MIT license: | |
Permission is hereby granted, free of charge, to any person obtaining a copy of | |
this software and associated documentation files (the "Software"), to deal in | |
the Software without restriction, including without limitation the rights to | |
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |
the Software, and to permit persons to whom the Software is furnished to do so, | |
subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
''' | |
from panda3d.core import Point3, Vec3 | |
class Point: | |
__slots__ = ("_pos", "new_point", "index", "faces", "edges") | |
def __init__(self, x=0., y=0., z=0.): | |
self._pos = [x, y, z] | |
self.new_point = None | |
self.index = None | |
self.faces = [] | |
self.edges = set() | |
def __getitem__(self, index): | |
return self._pos[index] | |
def __setitem__(self, index, rhs): | |
self._pos[index] = rhs | |
def __add__(self, rhs): | |
x1, y1, z1 = self._pos | |
x2, y2, z2 = rhs | |
return Point(x1 + x2, y1 + y2, z1 + z2) | |
def __iadd__(self, rhs): | |
x1, y1, z1 = self._pos | |
x2, y2, z2 = rhs | |
self._pos = [x1 + x2, y1 + y2, z1 + z2] | |
return self | |
def __mul__(self, rhs): | |
x, y, z = self._pos | |
return Point(x * rhs, y * rhs, z * rhs) | |
def __imul__(self, rhs): | |
x, y, z = self._pos | |
self._pos = [x * rhs, y * rhs, z * rhs] | |
return self | |
def __div__(self, rhs): | |
x, y, z = self._pos | |
return Point(x / rhs, y / rhs, z / rhs) | |
def __idiv__(self, rhs): | |
x, y, z = self._pos | |
self._pos = [x / rhs, y / rhs, z / rhs] | |
return self | |
def __truediv__(self, rhs): | |
x, y, z = self._pos | |
return Point(x / rhs, y / rhs, z / rhs) | |
def __itruediv__(self, rhs): | |
x, y, z = self._pos | |
self._pos = [x / rhs, y / rhs, z / rhs] | |
return self | |
class UV: | |
__slots__ = ("_uv", "point_index", "index", "faces", "edges") | |
def __init__(self, u=0., v=0., point_index=0): | |
self._uv = [u, v] | |
# store the index of the associated point in world space | |
self.point_index = point_index | |
self.index = None | |
self.faces = [] | |
self.edges = set() | |
def __getitem__(self, index): | |
return self._uv[index] | |
def __setitem__(self, index, rhs): | |
self._uv[index] = rhs | |
def __add__(self, rhs): | |
u1, v1 = self._uv | |
u2, v2 = rhs | |
return UV(u1 + u2, v1 + v2) | |
def __iadd__(self, rhs): | |
u1, v1 = self._uv | |
u2, v2 = rhs | |
self._uv = [u1 + u2, v1 + v2] | |
return self | |
def __mul__(self, rhs): | |
u, v = self._uv | |
return UV(u * rhs, v * rhs) | |
def __imul__(self, rhs): | |
u, v = self._uv | |
self._uv = [u * rhs, v * rhs] | |
return self | |
def __div__(self, rhs): | |
u, v = self._uv | |
return UV(u / rhs, v / rhs) | |
def __idiv__(self, rhs): | |
u, v = self._uv | |
self._uv = [u / rhs, v / rhs] | |
return self | |
def __truediv__(self, rhs): | |
u, v = self._uv | |
return UV(u / rhs, v / rhs) | |
def __itruediv__(self, rhs): | |
u, v = self._uv | |
self._uv = [u / rhs, v / rhs] | |
return self | |
class Edge: | |
__slots__ = ("_points", "point", "mid_point", "faces") | |
def __init__(self, point1, point2): | |
self._points = [point1, point2] | |
self.point = None | |
self.mid_point = None | |
self.faces = [] | |
def __getitem__(self, index): | |
return self._points[index] | |
class Face: | |
__slots__ = ("_points", "point", "edges") | |
def __init__(self): | |
self._points = [] | |
self.point = None | |
self.edges = [] | |
def __getitem__(self, index): | |
return self._points[index] | |
def __len__(self): | |
return len(self._points) | |
def append(self, point): | |
self._points.append(point) | |
def _quads_to_tris(quads): | |
tris = [] | |
for quad in quads: | |
tris.append([quad[0], quad[1], quad[2]]) | |
tris.append([quad[0], quad[2], quad[3]]) | |
return tris | |
def _sort(edge): | |
''' | |
Example: | |
given (0, 4), return (0, 4); | |
given (2, 1), return (1, 2). | |
''' | |
return edge if edge[0] < edge[1] else (edge[1], edge[0]) | |
# Implement the Catmull-Clark subdivision for UVs | |
def _subdivide_uvs(uvs, old_uv_faces, quads): | |
# Original UVs, indexed by their indices. | |
# For every UV, we store adjacent UV faces and adjacent UV edges. | |
original_uvs = {} | |
# Original UV faces, in their original order. | |
# For every UV face, we store the UV edges, the UV points, and the UV face point. | |
uv_faces = {} | |
# Original UV edges, indexed by the sorted indices of their UVs. | |
# So the UV edge whose UVs have indices `6` and `2` will be | |
# indexed by the tuple (2, 6). | |
uv_edges = {} | |
# First we collect all the information that we need to run the algorithm. | |
# Each UV must know its adjacent edges and faces. | |
# Each face must know its edges and UVs. | |
# Each edge must know its adjacent faces and UVs. | |
# We collect all this information in the following loop. | |
for i in range(len(old_uv_faces)): | |
uv_indices = old_uv_faces[i] | |
# initialize: | |
uv_faces[i] = uv_face = Face() | |
# go through all of the UVs of the UV face | |
for uv_index in uv_indices: | |
if uv_index in original_uvs: | |
# use a previously created UV object | |
uv_obj = original_uvs[uv_index] | |
else: | |
# create a new UV object | |
(u, v), point_index = uvs[uv_index] | |
uv_obj = UV(u, v, point_index) | |
original_uvs[uv_index] = uv_obj | |
# every UV should hold a reference to its faces | |
uv_obj.faces.append(uv_face) | |
# every face should know its UVs | |
uv_face.append(uv_obj) | |
avg = UV() | |
# now compute the face point (see Wikipedia) | |
for uv in uv_face: | |
avg += uv | |
avg /= len(uv_face) | |
uv_face.point = avg | |
index_count = len(uv_indices) | |
# go through all of the edges of the face | |
for j in range(index_count): | |
i1 = j | |
i2 = 0 if i1 + 1 == index_count else i1 + 1 | |
edge = (uv_indices[i1], uv_indices[i2]) | |
# every edge is represented by the sorted indices of its UVs | |
# (the sorting ensures that (1, 2) and (2, 1) are considered to be | |
# the same edge, which they are) | |
edge = _sort(edge) | |
if edge in uv_edges: | |
# use a previously created edge object | |
edge_obj = uv_edges[edge] | |
else: | |
# create a new edge object | |
edge_obj = Edge(original_uvs[edge[0]], original_uvs[edge[1]]) | |
uv_edges[edge] = edge_obj | |
# every edge should know its adjacent faces | |
edge_obj.faces.append(uv_face) | |
# every UV should know its adjacent edges | |
edge_obj[0].edges.add(edge_obj) | |
edge_obj[1].edges.add(edge_obj) | |
# every face should know its edges | |
uv_face.edges.append(edge_obj) | |
# Compute the edge point and the midpoint of every edge. | |
for edge in uv_edges.values(): | |
# compute the midpoint | |
edge.mid_point = (edge[0] + edge[1]) * .5 | |
if len(edge.faces) == 1: | |
# the edge belongs to a hole border; | |
# the edge point is just the midpoint in this case | |
edge.point = edge.mid_point | |
continue | |
avg = UV() | |
count = 0 | |
# add face points of edge | |
for uv_face in edge.faces: | |
avg += uv_face.point | |
count += 1 | |
# sum together with the two endpoints | |
for uv in edge: | |
avg += uv | |
count += 1 | |
# finally, compute edge point | |
avg /= count | |
edge.point = avg | |
new_uvs = [] | |
new_uv_faces = [] | |
def get_index(): | |
index = 0 | |
while True: | |
yield index | |
index += 1 | |
index_generator = get_index() | |
# We create new indices using the following method. | |
# The index of every UV vertex is stored in the UV object, in an attribute named `index`. | |
def get_new_vertex_index(uv): | |
uv.index = next(index_generator) | |
new_uvs.append((uv[:], uv.point_index)) | |
return uv.index | |
# We go through all of the faces. | |
# We subdivide n-sided faces into n new quads. | |
for i in range(len(uv_faces)): | |
uv_face = uv_faces[i] | |
for j in range(len(uv_face)): | |
edge_count = len(uv_face.edges) | |
a_, b_, c_, d_ = quads.pop(0) | |
a = uv_face[j] | |
a.point_index = a_.index | |
b = uv_face.edges[j % edge_count].point | |
b.point_index = b_.index | |
c = uv_face.point | |
c.point_index = c_.index | |
d = uv_face.edges[(j + edge_count - 1) % edge_count].point | |
d.point_index = d_.index | |
ia = get_new_vertex_index(a) if a.index is None else a.index | |
ib = get_new_vertex_index(b) if b.index is None else b.index | |
ic = get_new_vertex_index(c) if c.index is None else c.index | |
id = get_new_vertex_index(d) if d.index is None else d.index | |
new_uv_faces.append([id, ia, ib, ic]) | |
return new_uvs, new_uv_faces | |
# Implement Catmull-Clark subdivision, as it is described on Wikipedia | |
def _subdivide(positions, uvs, old_faces, old_uv_faces): | |
# Original points, indexed by their indices. | |
# For every point, we store adjacent faces and adjacent edges. | |
original_points = {} | |
# Original faces, in their original order. | |
# For every face, we store the edges, the points, and the face point. | |
faces = {} | |
# Original edges, indexed by the sorted indices of their points. | |
# So the edge whose points have indices `6` and `2` will be | |
# indexed by the tuple (2, 6). | |
edges = {} | |
# First we collect all the information that we need to run the algorithm. | |
# Each point must know its adjacent edges and faces. | |
# Each face must know its edges and points. | |
# Each edge must know its adjacent faces and points. | |
# We collect all this information in the following loop. | |
for i in range(len(old_faces)): | |
point_indices = old_faces[i] | |
# initialize: | |
faces[i] = face = Face() | |
# go through all of the points of the face | |
for point_index in point_indices: | |
if point_index in original_points: | |
# use a previously created point object | |
point_obj = original_points[point_index] | |
else: | |
# create a new point object | |
point_obj = Point(*positions[point_index]) | |
original_points[point_index] = point_obj | |
# every point should have a reference to its faces | |
point_obj.faces.append(face) | |
# every face should know its points | |
face.append(point_obj) | |
avg = Point() | |
# now compute the face point (see Wikipedia) | |
for p in face: | |
avg += p | |
avg /= len(face) | |
face.point = avg | |
index_count = len(point_indices) | |
# go through all of the edges of the face | |
for j in range(index_count): | |
i1 = j | |
i2 = 0 if i1 + 1 == index_count else i1 + 1 | |
edge = (point_indices[i1], point_indices[i2]) | |
# every edge is represented by the sorted indices of its points | |
# (the sorting ensures that (1, 2) and (2, 1) are considered to be | |
# the same edge, which they are) | |
edge = _sort(edge) | |
if edge in edges: | |
# use a previously created edge object | |
edge_obj = edges[edge] | |
else: | |
# create a new edge object | |
edge_obj = Edge(original_points[edge[0]], original_points[edge[1]]) | |
edges[edge] = edge_obj | |
# every edge should know its adjacent faces | |
edge_obj.faces.append(face) | |
# every point should know its adjacent edges | |
edge_obj[0].edges.add(edge_obj) | |
edge_obj[1].edges.add(edge_obj) | |
# every face should know its edges | |
face.edges.append(edge_obj) | |
# Compute the edge point and the midpoint of every edge. | |
for edge in edges.values(): | |
# compute the midpoint | |
edge.mid_point = (edge[0] + edge[1]) * .5 | |
if len(edge.faces) == 1: | |
# the edge belongs to a hole border; | |
# the edge point is just the midpoint in this case | |
edge.point = edge.mid_point | |
continue | |
avg = Point() | |
count = 0 | |
# add face points of edge | |
for face in edge.faces: | |
avg += face.point | |
count += 1 | |
# sum together with the two endpoints | |
for point in edge: | |
avg += point | |
count += 1 | |
# finally, compute edge point | |
avg /= count | |
edge.point = avg | |
# Each original point is moved to the position (F + 2R + (n-3)P) / n. | |
# See the Wikipedia article for more details. | |
for i in range(len(positions)): | |
point = original_points[i] | |
new_point = Point() | |
n = len(point.faces) | |
if n != len(point.edges): | |
# the point lies on the border of a hole; | |
# the new point is the weighted average of the endpoints of both | |
# border edges connected at the original point and that point (p): | |
# new_point = 1/8 ep1 + 1/8 ep2 + 6/8 p | |
# where ep1 and ep2 are the two border points neighboring p; | |
# using the midpoints (mp1 and mp2) of those border edges, since: | |
# mp1 == 1/2 ep1 + 1/2 p | |
# and: | |
# mp2 == 1/2 ep2 + 1/2 p | |
# this comes down to: | |
# new_point = 1/4 mp1 + 1/4 mp2 + 1/2 p | |
for edge in point.edges: | |
if len(edge.faces) == 1: | |
new_point += edge.mid_point * .25 | |
new_point += point * .5 | |
point.new_point = new_point | |
continue | |
avg = Point() | |
for face in point.faces: | |
avg += face.point | |
avg /= n | |
new_point += avg | |
avg = Point() | |
for edge in point.edges: | |
avg += edge.mid_point | |
avg /= n | |
new_point += avg * 2. | |
new_point += point * (n - 3) | |
new_point /= n | |
point.new_point = new_point | |
new_positions = [] | |
new_faces = [] | |
quads = [] | |
def get_index(): | |
index = 0 | |
while True: | |
yield index | |
index += 1 | |
index_generator = get_index() | |
# We create new indices using the following method. | |
# The index of every vertex is stored in the Point object, in an attribute named `index`. | |
def get_new_vertex_index(p): | |
p.index = next(index_generator) | |
new_positions.append(p[:]) | |
return p.index | |
# We go through all of the faces. | |
# We subdivide n-sided faces into n new quads. | |
for i in range(len(faces)): | |
face = faces[i] | |
for j in range(len(face)): | |
point = face[j] | |
edge_count = len(face.edges) | |
a = point.new_point | |
b = face.edges[j % edge_count].point | |
c = face.point | |
d = face.edges[(j + edge_count - 1) % edge_count].point | |
ia = get_new_vertex_index(a) if a.index is None else a.index | |
ib = get_new_vertex_index(b) if b.index is None else b.index | |
ic = get_new_vertex_index(c) if c.index is None else c.index | |
id = get_new_vertex_index(d) if d.index is None else d.index | |
new_faces.append([id, ia, ib, ic]) | |
quads.append([a, b, c, d]) | |
new_uvs, new_uv_faces = _subdivide_uvs(uvs, old_uv_faces, quads) | |
return {"positions": new_positions, "uvs": new_uvs, | |
"faces": new_faces, "uv_faces": new_uv_faces} | |
def subdivide(positions, uvs, faces, uv_faces, subdivision_count): | |
if subdivision_count < 1: | |
raise RuntimeError("`subdivision_count` must be a positive number!") | |
data = {"positions": positions, "uvs": uvs, "faces": faces, "uv_faces": uv_faces} | |
for i in range(subdivision_count): | |
data = _subdivide(data["positions"], data["uvs"], data["faces"], data["uv_faces"]) | |
data["faces"] = _quads_to_tris(data["faces"]) | |
return data | |
def convert_data(subdiv_data): | |
positions = subdiv_data["positions"] | |
uvs = subdiv_data["uvs"] | |
subdiv_uv_faces = subdiv_data["uv_faces"] | |
geom_data = [] | |
processed_data = {} | |
processed_mvs = [] | |
processed_uv_mvs = [] | |
for face in subdiv_uv_faces: | |
for i in face: | |
vert_data = {} | |
(u, v), j = uvs[i] | |
vert_data["pos"] = Point3(*positions[j]) | |
vert_data["uv"] = (u, v) | |
if j in processed_mvs: | |
vert_data["pos_ind"] = processed_mvs.index(j) | |
else: | |
vert_data["pos_ind"] = len(processed_mvs) | |
processed_mvs.append(j) | |
if i in processed_uv_mvs: | |
vert_data["uv_ind"] = processed_uv_mvs.index(i) | |
else: | |
vert_data["uv_ind"] = len(processed_uv_mvs) | |
processed_uv_mvs.append(i) | |
processed_data[i] = vert_data | |
tris = [] | |
for indices in ((face[0], face[1], face[2]), (face[0], face[2], face[3])): | |
tri_data = [processed_data[i] for i in indices] | |
tris.append(tri_data) | |
poly_verts = [processed_data[i] for i in face] | |
poly_data = {"verts": poly_verts, "tris": tris} | |
geom_data.append(poly_data) | |
return geom_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This module contains code to compute data that describes the geometry of | |
# imported models as valid surfaces, suitable for Catmull-Clark subdivision. | |
# An algorithm to quadrangulate the surfaces is included. | |
from panda3d.core import * | |
class MergedVertex: | |
__slots__ = ("_ids",) | |
def __init__(self, vert_id=None): | |
self._ids = [] if vert_id is None else [vert_id] | |
def __getitem__(self, index): | |
return self._ids[index] | |
def __len__(self): | |
return len(self._ids) | |
def append(self, vert_id): | |
self._ids.append(vert_id) | |
def extend(self, vert_ids): | |
self._ids.extend(vert_ids) | |
def remove(self, vert_id): | |
self._ids.remove(vert_id) | |
class MergedUV: | |
__slots__ = ("merged_vert", "uv") | |
def __init__(self, merged_vert, u, v): | |
self.merged_vert = merged_vert | |
self.uv = [u, v] | |
def compute_indices(geom_data, verts, merged_verts): | |
merged_uvs = {} | |
processed_mvs = [] | |
processed_muvs = [] | |
for merged_vert in set(merged_verts.values()): | |
verts_by_uv = {} | |
for vert_id in merged_vert: | |
vert = verts[vert_id] | |
uv = vert["uv"] | |
if uv in verts_by_uv: | |
verts_by_uv[uv].append(vert_id) | |
else: | |
verts_by_uv[uv] = [vert_id] | |
for (u, v), vert_ids in verts_by_uv.items(): | |
merged_uv = MergedUV(merged_vert, u, v) | |
merged_uvs.update({v_id: merged_uv for v_id in vert_ids}) | |
for poly_data in geom_data: | |
for vert_data in poly_data["verts"]: | |
vert_id = id(vert_data) | |
merged_vertex = merged_verts[vert_id] | |
merged_uv = merged_uvs[vert_id] | |
if merged_vertex in processed_mvs: | |
vert_data["pos_ind"] = processed_mvs.index(merged_vertex) | |
else: | |
vert_data["pos_ind"] = len(processed_mvs) | |
processed_mvs.append(merged_vertex) | |
if merged_uv in processed_muvs: | |
vert_data["uv_ind"] = processed_muvs.index(merged_uv) | |
else: | |
vert_data["uv_ind"] = len(processed_muvs) | |
processed_muvs.append(merged_uv) | |
def _is_doublesided(edge_pos, poly_pos, other_poly_pos): | |
p1, p2 = edge_pos | |
i1 = poly_pos.index(p1) | |
p3 = poly_pos[i1-1] | |
i2 = poly_pos.index(p2) | |
p4 = poly_pos[0 if i2 == len(poly_pos) - 1 else i2+1] | |
i1 = other_poly_pos.index(p2) | |
p5 = other_poly_pos[i1-1] | |
i2 = other_poly_pos.index(p1) | |
p6 = other_poly_pos[0 if i2 == len(other_poly_pos) - 1 else i2+1] | |
return p3 in (p5, p6) or p4 in (p5, p6) | |
def _define_connectivity(geom_data): | |
connectivity = {} | |
merged_verts = {} | |
merged_edges = {} | |
poly_pos = {} | |
verts = {} | |
edges = {} | |
verts_by_pos = {} | |
edges_by_pos = {} | |
edge_ids_by_vert_id = {} | |
polys_by_edge = {} | |
ordered_poly_edges = [] | |
def check_valid_merge(): | |
mvs_to_check = [] | |
# Check how many edges are connected to both merged verts at the ends of a given edge. | |
# If there are more than two, break the merged verts and split all of the merged edges | |
# attached to them. | |
for edge in edges.values(): | |
merged_vert1 = merged_verts[edge[0]] | |
merged_vert2 = merged_verts[edge[1]] | |
edge_ids1 = {e_id for v_id in merged_vert1 for e_id in edge_ids_by_vert_id[v_id]} | |
edge_ids2 = {e_id for v_id in merged_vert2 for e_id in edge_ids_by_vert_id[v_id]} | |
edge_ids = edge_ids1 & edge_ids2 | |
if len(edge_ids) > 2: | |
mvs = (merged_vert1, merged_vert2) | |
if merged_vert1 in mvs_to_check: | |
mvs_to_check.remove(merged_vert1) | |
if merged_vert2 in mvs_to_check: | |
mvs_to_check.remove(merged_vert2) | |
for edge_id in edge_ids1 | edge_ids2: | |
mv1, mv2 = [merged_verts[v_id] for v_id in edges[edge_id]] | |
if mv1 not in mvs and mv1 not in mvs_to_check: | |
mvs_to_check.append(mv1) | |
if mv2 not in mvs and mv2 not in mvs_to_check: | |
mvs_to_check.append(mv2) | |
merged_edges[edge_id] = [edge_id] | |
for vert_id in merged_vert1[:] + merged_vert2[:]: | |
merged_verts[vert_id] = MergedVertex(vert_id) | |
# The above procedure might introduce a new issue: after splitting certain merged | |
# edges, the merged verts at their ends could end up connecting multiple border | |
# vertices. To fix this, start at any border edge connected to such a merged | |
# vertex and follow the adjacent edges connected to that merged vertex, removing | |
# from it vertices encountered along the way and adding them to new merged vertices. | |
for merged_vert in mvs_to_check: | |
edge_ids = {e_id for v_id in merged_vert for e_id in edge_ids_by_vert_id[v_id]} | |
border_edge_ids = [e_id for e_id in edge_ids if len(merged_edges[e_id]) == 1] | |
if len(border_edge_ids) > 2: | |
while border_edge_ids: | |
border_edge_id = border_edge_ids.pop() | |
edge1 = edges[border_edge_id] | |
new_merged_vert = MergedVertex() | |
while True: | |
v1_id, v2_id = edge1 | |
vert_id = v1_id if merged_verts[v1_id] is merged_vert else v2_id | |
merged_vert.remove(vert_id) | |
new_merged_vert.append(vert_id) | |
merged_verts[vert_id] = new_merged_vert | |
e1_id, e2_id = edge_ids_by_vert_id[vert_id] | |
edge2_id = e1_id if e2_id == id(edge1) else e2_id | |
merged_edge = merged_edges[edge2_id] | |
if len(merged_edge) == 1: | |
border_edge_ids.remove(merged_edge[0]) | |
break | |
else: | |
e1_id, e2_id = merged_edge | |
edge1 = edges[e1_id if e2_id == edge2_id else e2_id] | |
for poly_data in geom_data: | |
poly_id = id(poly_data) | |
poly_pos[poly_id] = [Point3(*v["pos"]) for v in poly_data["verts"]] | |
tmp_edges = [] | |
positions = {} | |
poly_verts_by_pos = {} | |
poly_edges_by_pos = {} | |
poly_edges_by_vert_id = {} | |
poly_edges = [] | |
poly_tris = [] | |
for tri_data in poly_data["tris"]: | |
tri_vert_ids = [] | |
for vert_data in tri_data: | |
pos = vert_data["pos"] | |
if pos in poly_verts_by_pos: | |
vert_id = poly_verts_by_pos[pos] | |
else: | |
vert_id = id(vert_data) | |
verts[vert_id] = vert_data | |
poly_verts_by_pos[pos] = vert_id | |
positions[vert_id] = pos | |
tri_vert_ids.append(vert_id) | |
poly_tris.append(tuple(tri_vert_ids)) | |
for i, j in ((0, 1), (1, 2), (2, 0)): | |
edge_vert_ids = (tri_vert_ids[i], tri_vert_ids[j]) | |
reversed_vert_ids = edge_vert_ids[::-1] | |
if reversed_vert_ids in tmp_edges: | |
# if the edge appears twice, it's actually a diagonal | |
tmp_edges.remove(reversed_vert_ids) | |
else: | |
tmp_edges.append(edge_vert_ids) | |
for edge_vert_ids in tmp_edges: | |
vert1_id, vert2_id = edge_vert_ids | |
poly_edges_by_vert_id[vert1_id] = edge_vert_ids | |
edge_id = id(edge_vert_ids) | |
edge_ids_by_vert_id.setdefault(vert1_id, []).append(edge_id) | |
edge_ids_by_vert_id.setdefault(vert2_id, []).append(edge_id) | |
# Define verts and edges in winding order | |
vert1_id, vert2_id = edge_vert_ids = poly_edges_by_vert_id[poly_tris[0][0]] | |
edge = edge_vert_ids | |
edge1_id = id(edge) | |
edges[edge1_id] = edge | |
poly_edges.append(edge1_id) | |
pos1 = positions[vert1_id] | |
pos2 = positions[vert2_id] | |
poly_edges_by_pos[(pos1, pos2)] = edge1_id | |
while vert2_id != vert1_id: | |
edge_vert_ids = poly_edges_by_vert_id[vert2_id] | |
vert2_id = edge_vert_ids[1] | |
edge = edge_vert_ids | |
edge_id = id(edge) | |
edges[edge_id] = edge | |
poly_edges.append(edge_id) | |
pos1 = pos2 | |
pos2 = positions[vert2_id] | |
poly_edges_by_pos[(pos1, pos2)] = edge_id | |
ordered_poly_edges.append(poly_edges) | |
verts_by_pos[poly_id] = poly_verts_by_pos | |
edges_by_pos[poly_id] = poly_edges_by_pos | |
neighbor_count = {} | |
poly_connections = {"neighbors": {}, "neighbor_count": neighbor_count} | |
for edge_pos in list(poly_edges_by_pos): | |
polys_by_edge.setdefault(edge_pos, []).append(poly_id) | |
poly_connections["neighbors"][edge_pos] = neighbors = [] | |
reversed_edge_pos = edge_pos[::-1] | |
if reversed_edge_pos in polys_by_edge: | |
# one or more other polys form a continuous surface with this one | |
for other_poly_id in polys_by_edge[reversed_edge_pos]: | |
other_connections = connectivity[other_poly_id] | |
other_neighbors = other_connections["neighbors"][reversed_edge_pos] | |
if not _is_doublesided(edge_pos, poly_pos[poly_id], poly_pos[other_poly_id]): | |
# the triangles of both polys connected by the current edge | |
# are not each other's inverse | |
neighbors.append(other_poly_id) | |
other_neighbors.append(poly_id) | |
neighbor_count.setdefault(other_poly_id, 0) | |
neighbor_count[other_poly_id] += 1 | |
other_neighbor_count = other_connections["neighbor_count"] | |
other_neighbor_count.setdefault(poly_id, 0) | |
other_neighbor_count[poly_id] += 1 | |
connectivity[poly_id] = poly_connections | |
for poly_id, connections in connectivity.items(): | |
for edge_pos, neighbors in connections["neighbors"].items(): | |
edge_id = edges_by_pos[poly_id][edge_pos] | |
if edge_id in merged_edges: | |
continue | |
merged_edge = [edge_id] | |
merged_edges[edge_id] = merged_edge | |
if neighbors: | |
neighbor_to_keep = neighbors[0] | |
if len(neighbors) > 1: | |
neighbor_count = connections["neighbor_count"] | |
for neighbor_id in neighbors: | |
if neighbor_count[neighbor_id] > neighbor_count[neighbor_to_keep]: | |
neighbor_to_keep = neighbor_id | |
neighbors.remove(neighbor_to_keep) | |
for neighbor_id in neighbors: | |
connectivity[neighbor_id]["neighbors"][edge_pos[::-1]].remove(poly_id) | |
connectivity[neighbor_id]["neighbor_count"][poly_id] -= 1 | |
neighbor_edge_id = edges_by_pos[neighbor_to_keep][edge_pos[::-1]] | |
if neighbor_edge_id not in merged_edges: | |
merged_edge.append(neighbor_edge_id) | |
merged_edges[neighbor_edge_id] = merged_edge | |
for poly_edges in ordered_poly_edges: | |
for edge_id in poly_edges: | |
merged_edge = merged_edges[edge_id] | |
vert1_id, vert2_id = edges[edge_id] | |
if vert1_id in merged_verts: | |
merged_vert1 = merged_verts[vert1_id] | |
else: | |
merged_vert1 = MergedVertex(vert1_id) | |
merged_verts[vert1_id] = merged_vert1 | |
if vert2_id in merged_verts: | |
merged_vert2 = merged_verts[vert2_id] | |
else: | |
merged_vert2 = MergedVertex(vert2_id) | |
merged_verts[vert2_id] = merged_vert2 | |
if len(merged_edge) > 1: | |
neighbor_edge_id = merged_edge[0 if merged_edge[1] == edge_id else 1] | |
neighbor_vert1_id, neighbor_vert2_id = edges[neighbor_edge_id] | |
if neighbor_vert1_id not in merged_vert2: | |
if neighbor_vert1_id in merged_verts: | |
merged_vert = merged_verts[neighbor_vert1_id] | |
for vert_id in merged_vert: | |
merged_vert2.append(vert_id) | |
merged_verts[vert_id] = merged_vert2 | |
else: | |
merged_vert2.append(neighbor_vert1_id) | |
merged_verts[neighbor_vert1_id] = merged_vert2 | |
if neighbor_vert2_id not in merged_vert1: | |
if neighbor_vert2_id in merged_verts: | |
merged_vert = merged_verts[neighbor_vert2_id] | |
for vert_id in merged_vert: | |
merged_vert1.append(vert_id) | |
merged_verts[vert_id] = merged_vert1 | |
else: | |
merged_vert1.append(neighbor_vert2_id) | |
merged_verts[neighbor_vert2_id] = merged_vert1 | |
check_valid_merge() | |
compute_indices(geom_data, verts, merged_verts) | |
def define_geom_data(geom, quadrangulate=False): | |
geom_data = [] | |
coords = [] | |
src_vert_data = geom.get_vertex_data() | |
dest_format = GeomVertexFormat.get_v3n3t2() | |
dest_vert_data = src_vert_data.convert_to(dest_format) | |
memview = memoryview(dest_vert_data.get_array(0)).cast("B").cast("f") | |
processed_data = {} | |
tris_by_edge = {} | |
# keep track of which triangles share edges, so they can be combined into quads | |
# afterwards | |
adjacent_tris = {} | |
indices = geom.get_primitive(0).get_vertex_list() | |
def get_quad_vert_index_list(quad): | |
tri1, tri2 = quad | |
index_list = list(tri1) | |
vi1, vi2, vi3 = tri2 | |
other_indices = (vi1, vi2, vi3, vi1) | |
new_index = set(tri2).difference(tri1).pop() | |
i = other_indices.index(new_index) + 1 | |
index_list.insert(index_list.index(other_indices[i]), new_index) | |
return index_list | |
def get_quad_score(quad): | |
vi1, vi2, vi3, vi4 = index_list = get_quad_vert_index_list(quad) | |
points = [Point3(*memview[vi*8:vi*8+3]) for vi in index_list] | |
points.append(points[0]) | |
vecs = [points[i + 1] - points[i] for i in range(4)] | |
lengths = [vec.length_squared() for vec in vecs] | |
plane = Plane(*points[:3]) | |
d = abs(plane.dist_to_plane(points[3])) | |
if d < .00001: | |
score = 10. | |
else: | |
q = d * d / max(.00001, max(lengths)) | |
score = 10. - (1./q if q > 1. else q) * 10. | |
for i, j in ((0, 1), (0, 2), (1, 3)): | |
q = max(.00001, lengths[i]) / max(.00001, lengths[j]) | |
score += min(q, 1./q) | |
for vec in vecs: | |
vec.normalize() | |
vecs.append(vecs[0]) | |
for i in range(4): | |
score += 1. - abs(vecs[i].dot(vecs[i + 1])) | |
score += vecs[i].cross(vecs[i + 1]).dot(plane.get_normal()) | |
return score | |
for tri_vert_ids in (indices[i:i+3] for i in range(0, len(indices), 3)): | |
vi1, vi2, vi3 = tri = tuple(tri_vert_ids) | |
if {tri, (vi2, vi3, vi1), (vi3, vi1, vi2)}.intersection(adjacent_tris): | |
continue | |
adjacent_tris[tri] = tris = [] | |
if quadrangulate: | |
edge_vert_ids = ((vi1, vi2), (vi2, vi3), (vi3, vi1)) | |
for vert_ids in edge_vert_ids: | |
if vert_ids[::-1] in tris_by_edge: | |
for other_tri in tris_by_edge[vert_ids[::-1]]: | |
if tris.count(other_tri) == 2: | |
tris.remove(other_tri) | |
tris.remove(other_tri) | |
adjacent_tris[other_tri].remove(tri) | |
adjacent_tris[other_tri].remove(tri) | |
else: | |
tris.append(other_tri) | |
adjacent_tris.setdefault(other_tri, []).append(tri) | |
tris_by_edge.setdefault(vert_ids, []).append(tri) | |
if quadrangulate: | |
quads = {} | |
quad_scores = {} | |
for tri, other_tris in adjacent_tris.items(): | |
quad_list = [] | |
for other_tri in other_tris: | |
quad = (tri, other_tri) | |
quad_list.append(quad) | |
quads[tri] = quad_list | |
for tri, quad_list in quads.items(): | |
for quad in quad_list: | |
if quad in quad_scores or quad[::-1] in quad_scores: | |
continue | |
quad_scores[quad] = get_quad_score(quad) | |
sorted_quads = sorted([(v, k) for k, v in quad_scores.items()]) | |
sorted_quads = [k for _, k in sorted_quads] | |
quads = [] | |
while sorted_quads: | |
quad = sorted_quads.pop() | |
quads.append(quad) | |
for tri in quad: | |
for other_tri in adjacent_tris.get(tri, []): | |
adjacent_tris[other_tri].remove(tri) | |
other_quad = (tri, other_tri) | |
if other_quad in sorted_quads: | |
sorted_quads.remove(other_quad) | |
elif other_quad[::-1] in sorted_quads: | |
sorted_quads.remove(other_quad[::-1]) | |
if tri in adjacent_tris: | |
del adjacent_tris[tri] | |
else: | |
quads = [] | |
for poly in quads + [(tri,) for tri in adjacent_tris]: | |
if len(poly) == 2: | |
index_list = get_quad_vert_index_list(poly) | |
else: | |
index_list = poly[0] | |
for i in index_list: | |
vert_data = {} | |
pos = Point3(*memview[i*8:i*8+3]) | |
for crd in coords: | |
if pos == crd: | |
pos = crd | |
break | |
else: | |
coords.append(pos) | |
vert_data["pos"] = pos | |
u, v = memview[i*8+6:i*8+8] | |
vert_data["uv"] = (u, v) | |
processed_data[i] = vert_data | |
poly_verts = [processed_data[i] for i in index_list] | |
tris = [[processed_data[i] for i in indices] for indices in poly] | |
poly_data = {"verts": poly_verts, "tris": tris} | |
geom_data.append(poly_data) | |
_define_connectivity(geom_data) | |
return geom_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from direct.showbase.ShowBase import ShowBase | |
from geom_data import * | |
import catmull_clark | |
from collections import deque | |
import struct | |
import array | |
# These are the shaders used to render the model as a wireframe. | |
VERT_SHADER = """ | |
#version 330 | |
// Vertex inputs | |
in vec4 p3d_Vertex; | |
in int sides; | |
uniform mat4 p3d_ModelMatrix; | |
out Vertex | |
{ | |
int side_gen; | |
} vertex; | |
void main() | |
{ | |
gl_Position = p3d_ModelMatrix * p3d_Vertex; | |
vertex.side_gen = sides; | |
} | |
""" | |
GEOM_SHADER = """ | |
#version 330 | |
layout(triangles) in; | |
// Three lines will be generated: 6 vertices | |
layout(line_strip, max_vertices=6) out; | |
uniform mat4 p3d_ViewProjectionMatrix; | |
uniform mat4 p3d_ViewMatrixInverse; | |
uniform mat4 p3d_ProjectionMatrix; | |
in Vertex | |
{ | |
int side_gen; | |
} vertex[]; | |
void main() | |
{ | |
int side_generation, S0, S1, S2; | |
int sides[3]; | |
// determine which sides should be generated (1) or not (0) | |
side_generation = vertex[0].side_gen; | |
S0 = side_generation >> 2; | |
S1 = (side_generation ^ (S0 << 2)) >> 1; | |
S2 = side_generation ^ (S0 << 2) ^ (S1 << 1); | |
sides = int[] (S0, S1, S2); | |
vec3 P0 = gl_in[0].gl_Position.xyz; | |
vec3 P1 = gl_in[1].gl_Position.xyz; | |
vec3 P2 = gl_in[2].gl_Position.xyz; | |
vec3 pos[4] = vec3[] (P0, P1, P2, P0); | |
vec3 V0 = P1 - P0; | |
vec3 V1 = P2 - P1; | |
vec3 face_normal = cross(V0, V1); | |
vec3 vec; | |
// a face whose normal points away from the camera is not rendered; | |
// this is determined by the dot product of the face normal and the | |
// following vector: | |
if (p3d_ProjectionMatrix[3].w == 1.) | |
// orthographic lens; | |
// use inverted camera direction vector | |
vec = p3d_ViewMatrixInverse[2].xyz; | |
else | |
// perspective lens; | |
// compute vector pointing from any point of triangle to camera origin | |
vec = p3d_ViewMatrixInverse[3].xyz - P0; | |
if (dot(vec, face_normal) >= 0.) { | |
// generate sides | |
for (int i = 0; i < 3; ++i) | |
{ | |
for (int j = 0; j < sides[i]; ++j) | |
{ | |
gl_Position = p3d_ViewProjectionMatrix * vec4(pos[i], 1.0); | |
EmitVertex(); | |
gl_Position = p3d_ViewProjectionMatrix * vec4(pos[i + 1], 1.0); | |
EmitVertex(); | |
EndPrimitive(); | |
} | |
} | |
} | |
} | |
""" | |
FRAG_SHADER = """ | |
#version 330 | |
layout(location = 0) out vec4 out_color; | |
void main() | |
{ | |
out_color = vec4(1., 1., 1., 1.); | |
} | |
""" | |
def create_uv_map(): | |
color = (0., 0., 0., 1.) | |
back_colors = ((1., 0., 0., 1.), (0., 1., 0., 1.), | |
(1., 1., 0., 1.), (1., 0., 1., 1.), | |
(0., 1., 1., 1.), (1., 1., 1., 1.), | |
(1., .5, 1., 1.), (.5, 1., 1., 1.)) | |
fill_brushes = deque([]) | |
pen = PNMBrush.make_spot(color, 3, False) | |
for back_color in back_colors: | |
fill_brushes.append(PNMBrush.make_pixel(back_color)) | |
d = 150 | |
image = PNMImage(d * 8, d * 8) | |
image.fill(1., 1., 1.) | |
painter = PNMPainter(image) | |
painter.set_pen(pen) | |
y = 0 | |
for i in range(8): | |
x = 0 | |
for brush in fill_brushes: | |
painter.set_fill(brush) | |
painter.draw_rectangle(x, y, x + d, y + d) | |
x += d | |
fill_brushes.rotate(1) | |
y += d | |
uv_map = Texture("uv_map") | |
uv_map.load(image) | |
return uv_map | |
def create_cap(): | |
from math import sin, cos, pi | |
geom_data = [] | |
vert_data = {} | |
vert_ids_by_pos = {} | |
def add_vertex_data(pos, uv, vertex_count): | |
p = Point3(*pos) | |
vd = {"pos": p, "uv": uv} | |
vert_data[vertex_count] = vd | |
vert_ids_by_pos.setdefault(p, []).append(id(vd)) | |
vert_data_array = array.array("f", []) | |
vert_index_array = array.array("H", []) | |
vertex_count = 0 | |
angle_h = pi * .4 | |
angle_v = pi * .175 | |
# main part of cap | |
for i in range(3): | |
z = sin(angle_v * i) | |
radius_h = cos(angle_v * i) | |
for j in range(6): | |
if j in (0, 5): | |
x = 0. | |
y = -radius_h | |
else: | |
x = radius_h * sin(angle_h * j) | |
y = radius_h * -cos(angle_h * j) | |
pos = (x, y, z) | |
normal = Vec3(*pos).normalized() | |
uv = (j / 5., i / 8.) | |
vert_data_array.extend(pos) | |
vert_data_array.extend(normal) | |
vert_data_array.extend(uv) | |
add_vertex_data(pos, uv, vertex_count) | |
vertex_count += 1 | |
# top of cap | |
for j in range(5): | |
x = radius_h * sin(angle_h * j) | |
y = radius_h * -cos(angle_h * j) | |
pos = (x, y, z) | |
normal = Vec3(*pos).normalized() | |
uv = (x * .15 / radius_h + .5, y * .15 / radius_h + .5) | |
vert_data_array.extend(pos) | |
vert_data_array.extend(normal) | |
vert_data_array.extend(uv) | |
add_vertex_data(pos, uv, vertex_count) | |
vertex_count += 1 | |
# peak of cap | |
normal = Vec3(0., 0., 1.) | |
x = sin(angle_h * 2) | |
y = -cos(angle_h * 2) | |
pos = (x, y, 0.) | |
uv = (0., .3) | |
vert_data_array.extend(pos) | |
vert_data_array.extend(normal) | |
vert_data_array.extend(uv) | |
add_vertex_data(pos, uv, vertex_count) | |
pos = (0., 2., 0.) | |
uv = (.15, 0.) | |
vert_data_array.extend(pos) | |
vert_data_array.extend(normal) | |
vert_data_array.extend(uv) | |
add_vertex_data(pos, uv, vertex_count + 1) | |
pos = (-x, y, 0.) | |
uv = (.3, .3) | |
vert_data_array.extend(pos) | |
vert_data_array.extend(normal) | |
vert_data_array.extend(uv) | |
add_vertex_data(pos, uv, vertex_count + 2) | |
vertex_count += 3 | |
verts = {id(vd): vd for vd in vert_data.values()} | |
merged_verts = {} | |
for vert_ids in vert_ids_by_pos.values(): | |
merged_vert = MergedVertex() | |
merged_vert.extend(vert_ids) | |
merged_verts.update({v_id: merged_vert for v_id in vert_ids}) | |
# quads for main part of cap | |
for i in range(2): | |
for j in range(5): | |
vi1 = i * 6 + j | |
vi2 = vi1 + 1 | |
vi3 = vi1 + 6 | |
vi4 = vi2 + 6 | |
vert_ids = (vi1, vi2, vi3, vi2, vi4, vi3) | |
vert_index_array.extend(vert_ids) | |
tri_data1 = [vert_data[vi] for vi in vert_ids[:3]] | |
tri_data2 = [vert_data[vi] for vi in vert_ids[3:]] | |
poly_verts = [vert_data[vi] for vi in (vi1, vi2, vi4, vi3)] | |
poly_data = {"verts": poly_verts, "tris": (tri_data1, tri_data2)} | |
geom_data.append(poly_data) | |
vi1 = vertex_count - 8 | |
tris = [] | |
# triangles for pentagonal top of cap | |
for j in range(3): | |
vi2 = vi1 + 1 + j | |
vi3 = vi2 + 1 | |
vert_ids = (vi1, vi2, vi3) | |
vert_index_array.extend(vert_ids) | |
tris.append([vert_data[vi] for vi in vert_ids]) | |
poly_verts = [vert_data[vi] for vi in (vi1, vi1+1, vi1+2, vi1+3, vi1+4)] | |
poly_data = {"verts": poly_verts, "tris": tuple(tris)} | |
geom_data.append(poly_data) | |
# triangle for peak of cap | |
vi1 = vertex_count - 3 | |
vert_ids = (vi1, vi1+1, vi1+2) | |
vert_index_array.extend(vert_ids) | |
tri_data = [vert_data[vi] for vi in vert_ids] | |
poly_verts = [vert_data[vi] for vi in vert_ids] | |
poly_data = {"verts": poly_verts, "tris": (tri_data,)} | |
geom_data.append(poly_data) | |
vertex_format = GeomVertexFormat.get_v3n3t2() | |
vertex_data = GeomVertexData("cap_data", vertex_format, Geom.UH_static) | |
vert_array = vertex_data.modify_array(0) | |
vert_array.set_num_rows(vertex_count) | |
memview = memoryview(vert_array).cast("B").cast("f") | |
memview[:] = vert_data_array | |
tris_prim = GeomTriangles(Geom.UH_static) | |
tris_array = tris_prim.modify_vertices() | |
tris_array.set_num_rows(10 * 6 + 3 * 4) | |
memview = memoryview(tris_array).cast("B").cast("H") | |
memview[:] = vert_index_array | |
geom = Geom(vertex_data) | |
geom.add_primitive(tris_prim) | |
node = GeomNode("cap") | |
node.add_geom(geom) | |
compute_indices(geom_data, verts, merged_verts) | |
return node, geom_data | |
def subdivide_surfaces(geom_data, subdivision_count=1): | |
positions = [] | |
faces = [] | |
uvs = [] | |
uv_faces = [] | |
for poly_data in geom_data: | |
face = [] | |
uv_face = [] | |
for vert_data in poly_data["verts"]: | |
index = vert_data["pos_ind"] | |
face.append(index) | |
if index == len(positions): | |
positions.append(list(vert_data["pos"])) | |
uv_index = vert_data["uv_ind"] | |
uv_face.append(uv_index) | |
if uv_index == len(uvs): | |
uvs.append((list(vert_data["uv"]), index)) | |
faces.append(face) | |
uv_faces.append(uv_face) | |
data = catmull_clark.subdivide(positions, uvs, faces, uv_faces, subdivision_count) | |
positions = data["positions"] | |
uvs = data["uvs"] | |
faces = data["faces"] | |
uv_faces = data["uv_faces"] | |
count = len(uvs) | |
vertex_format = GeomVertexFormat.get_v3n3t2() | |
vertex_data = GeomVertexData("vert_data", vertex_format, Geom.UH_static) | |
vertex_data.reserve_num_rows(count) | |
vertex_data.unclean_set_num_rows(count) | |
memview = memoryview(vertex_data.modify_array(0)).cast("B").cast("f") | |
data_array = array.array("f", []) | |
face_normals = {} | |
for face in faces: | |
edge_vec1 = Point3(*positions[face[1]]) - Point3(*positions[face[0]]) | |
edge_vec2 = Point3(*positions[face[2]]) - Point3(*positions[face[1]]) | |
normal = edge_vec1.cross(edge_vec2) | |
for index in face: | |
face_normals.setdefault(index, []).append(normal) | |
vert_normals = [sum(face_normals[i], Vec3()).normalized() for i in range(len(positions))] | |
new_geom_data = catmull_clark.convert_data(data) | |
for uv, i in uvs: | |
data_array.extend(positions[i]) | |
data_array.extend(vert_normals[i]) | |
data_array.extend(uv) | |
memview[:] = data_array | |
prim = GeomTriangles(Geom.UH_static) | |
prim_size = len(uv_faces) * 6 | |
int_format = "H" | |
if prim_size >= 2 ** 16: | |
prim.set_index_type(Geom.NT_uint32) | |
int_format = "I" | |
prim.reserve_num_vertices(prim_size) | |
prim_array = prim.modify_vertices() | |
prim_array.unclean_set_num_rows(prim_size) | |
memview = memoryview(prim_array).cast("B").cast(int_format) | |
data_array = array.array(int_format, []) | |
for (row0, row1, row2, row3) in uv_faces: | |
data_array.extend((row0, row1, row2, row0, row2, row3)) | |
memview[:] = data_array | |
geom = Geom(vertex_data) | |
geom.add_primitive(prim) | |
geom_node = GeomNode("subdiv_mesh") | |
geom_node.add_geom(geom) | |
return NodePath(geom_node), new_geom_data | |
def create_wireframe_geom(geom_data, vertex_format): | |
sides = {} | |
polys = [] | |
positions = {} | |
vert_count = 0 | |
for poly_data in geom_data: | |
poly = [] | |
poly_verts = set() | |
edges = [] | |
sides[id(poly)] = poly_sides = [] | |
diagonals = [] | |
for tri_data in poly_data["tris"]: | |
tri = [] | |
poly.append(tri) | |
vert_count += 3 | |
for vert_data in tri_data: | |
vert_id = id(vert_data) | |
positions[vert_id] = vert_data["pos"] | |
tri.append(vert_id) | |
poly_verts.add(vert_id) | |
for i, j in ((0, 1), (1, 2), (2, 0)): | |
edge = {id(tri_data[i]), id(tri_data[j])} | |
if edge in edges: | |
# if the edge appears twice, it's actually a diagonal | |
edges.remove(edge) | |
diagonals.append(edge) | |
else: | |
edges.append(edge) | |
for tri_data in poly_data["tris"]: | |
vi1, vi2, vi3 = [id(vert_data) for vert_data in tri_data] | |
# determine which triangle edges should be rendered (i.e. whether | |
# they are polygon edges); | |
# 1 indicates that an edge should be rendered, 0 that it shouldn't; | |
# pack these values into a single int for space-efficient storing in | |
# the "sides" vertex data column | |
s1 = 0 if {vi1, vi2} in diagonals else 1 | |
s2 = 0 if {vi2, vi3} in diagonals else 1 | |
s3 = 0 if {vi3, vi1} in diagonals else 1 | |
poly_sides.append(s1 << 2 | s2 << 1 | s3) | |
polys.append((poly, poly_verts)) | |
# Build wireframe geom. | |
vertex_data = GeomVertexData("wireframe_data", vertex_format, Geom.UH_static) | |
vertex_data.reserve_num_rows(vert_count) | |
vertex_data.unclean_set_num_rows(vert_count) | |
vertex_array = vertex_data.modify_array(0) | |
s = vertex_array.array_format.stride | |
memview = memoryview(vertex_array).cast("B") | |
prim = GeomTriangles(Geom.UH_static) | |
prim.reserve_num_vertices(vert_count) | |
row = 0 | |
for poly, _ in polys: | |
for tri, tri_sides in zip(poly, sides[id(poly)]): | |
for v_id in tri: | |
x, y, z = positions[v_id] | |
memview[s*row:s*(row+1)] = struct.pack("3fI", x, y, z, tri_sides) | |
row += 1 | |
prim.add_next_vertices(vert_count) | |
geom = Geom(vertex_data) | |
geom.add_primitive(prim) | |
geom_node = GeomNode("wireframe_geom") | |
geom_node.add_geom(geom) | |
wireframe_geom = NodePath(geom_node) | |
shader = Shader.make(Shader.SL_GLSL, VERT_SHADER, FRAG_SHADER, GEOM_SHADER) | |
wireframe_geom.set_shader(shader) | |
return wireframe_geom | |
class MyApp(ShowBase): | |
def __init__(self): | |
ShowBase.__init__(self) | |
# set up a light source | |
p_light = PointLight("point_light") | |
p_light.set_color((1., 1., 1., 1.)) | |
self.light = self.camera.attach_new_node(p_light) | |
self.light.set_pos(5., -10., 7.) | |
self.render.set_light(self.light) | |
self.render_modes = deque(["shaded+wire", "wire", "shaded"]) | |
self.uv_map = create_uv_map() | |
self.has_texture = False | |
node, self.geom_data = create_cap() | |
self.model = self.render.attach_new_node(node) | |
# if you want to load a model instead of using the procedurally | |
# generated cap model, comment out the two lines above and uncomment | |
# the lines below | |
# path_to_model = "" | |
# self.model = self.loader.load_model(path_to_model).find("**/+GeomNode") | |
# self.model.set_state(RenderState.make_empty()) | |
# self.model.reparent_to(self.render) | |
# geom = self.model.node().get_geom(0) | |
# self.geom_data = define_geom_data(geom, quadrangulate=True) | |
array_format = GeomVertexArrayFormat() | |
array_format.add_column(InternalName.make("vertex"), 3, Geom.NT_float32, Geom.C_point) | |
array_format.add_column(InternalName.make("sides"), 1, Geom.NT_int32, Geom.C_other) | |
vertex_format = GeomVertexFormat() | |
vertex_format.add_array(array_format) | |
self.wire_vertex_format = GeomVertexFormat.register_format(vertex_format) | |
self.wireframe_geom = create_wireframe_geom(self.geom_data, self.wire_vertex_format) | |
self.wireframe_geom.reparent_to(self.model) | |
self.accept("r", self.change_render_mode) | |
self.accept("s", self.subdivide_surfaces) | |
self.accept("t", self.toggle_texture) | |
def apply_render_mode(self): | |
render_mode = self.render_modes[0] | |
if "shaded" in render_mode: | |
self.model.show() | |
else: | |
self.model.hide() | |
if "wire" in render_mode: | |
self.wireframe_geom.show_through() | |
else: | |
self.wireframe_geom.hide() | |
def change_render_mode(self): | |
self.render_modes.rotate(1) | |
self.apply_render_mode() | |
def subdivide_surfaces(self): | |
self.model.detach_node() | |
self.model, self.geom_data = subdivide_surfaces(self.geom_data, subdivision_count=1) | |
self.model.reparent_to(self.render) | |
self.wireframe_geom = create_wireframe_geom(self.geom_data, self.wire_vertex_format) | |
self.wireframe_geom.reparent_to(self.model) | |
self.apply_render_mode() | |
if self.has_texture: | |
self.model.set_texture(self.uv_map, 1) | |
def toggle_texture(self): | |
self.has_texture = not self.has_texture | |
if self.has_texture: | |
self.model.set_texture(self.uv_map) | |
else: | |
self.model.clear_texture() | |
app = MyApp() | |
app.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment