1 | import math |
||
2 | from gem import vector |
||
3 | from gem import plane |
||
4 | |||
5 | class CSG(object): |
||
6 | def __init__(self): |
||
7 | self.polygons = [] |
||
8 | |||
9 | def clone(self): |
||
10 | newCSG = CSG() |
||
11 | for i in range(len(self.polygons)): |
||
12 | newCSG.polygons.append(self.polygons[i].clone()) |
||
13 | return newCSG |
||
14 | |||
15 | def fromPolygons(self, polygons): |
||
16 | newCSG = CSG() |
||
17 | newCSG.polygons = polygons |
||
18 | return newCSG |
||
19 | |||
20 | def toPolygons(self): |
||
21 | return self.polygons |
||
22 | |||
23 | def setColor(self, r, g, b): |
||
24 | for i in range(len(self.polygons)): |
||
25 | self.polygons[i].shared = [r, g, b] |
||
26 | |||
27 | def union(self, otherCSG): |
||
28 | csgA = csgNode(self.clone().polygons) |
||
29 | csgB = csgNode(otherCSG.clone().polygons) |
||
30 | csgA.clipTo(csgB) |
||
31 | csgB.clipTo(csgA) |
||
32 | csgB.invert() |
||
33 | csgB.clipTo(csgA) |
||
34 | csgB.invert() |
||
35 | csgA.build(csgB.allPolygons()) |
||
36 | |||
37 | return self.fromPolygons(csgA.allPolygons()) |
||
38 | |||
39 | View Code Duplication | def subtract(self, otherCSG): |
|
0 ignored issues
–
show
Duplication
introduced
by
![]() |
|||
40 | csgA = csgNode(self.clone().polygons) |
||
41 | csgB = csgNode(otherCSG.clone().polygons) |
||
42 | |||
43 | csgA.invert() |
||
44 | csgA.clipTo(csgB) |
||
45 | csgB.clipTo(csgA) |
||
46 | csgB.invert() |
||
47 | csgB.clipTo(csgA) |
||
48 | csgB.invert() |
||
49 | csgA.build(csgB.allPolygons()) |
||
50 | csgA.invert() |
||
51 | return self.fromPolygons(csgA.allPolygons()) |
||
52 | |||
53 | View Code Duplication | def intersect(self, otherCSG): |
|
0 ignored issues
–
show
|
|||
54 | csgA = csgNode(self.clone().polygons) |
||
55 | csgB = csgNode(otherCSG.clone().polygons) |
||
56 | |||
57 | csgA.invert() |
||
58 | csgB.clipTo(csgA) |
||
59 | csgB.invert() |
||
60 | csgA.clipTo(csgB) |
||
61 | csgB.clipTo(csgA) |
||
62 | csgA.build(csgB.allPolygons()) |
||
63 | csgA.invert() |
||
64 | |||
65 | |||
66 | return self.fromPolygons(csgA.allPolygons()) |
||
67 | |||
68 | def inverse(self): |
||
69 | newCSG = self.clone() |
||
70 | for i in range(len(self.polygons)): |
||
71 | newCSG.polygons[i] = self.polygons[i].flip() |
||
72 | return newCSG |
||
73 | |||
74 | def cube(self, center, radius): |
||
75 | c = center |
||
76 | r = radius |
||
77 | |||
78 | indices = [[0, 4, 6, 2], |
||
79 | [1, 3, 7, 5], |
||
80 | [0, 1, 5, 4], |
||
81 | [2, 6, 7, 3], |
||
82 | [0, 2, 3, 1], |
||
83 | [4, 5, 7, 6]] |
||
84 | |||
85 | normals = [[-1, 0, 0], |
||
86 | [ 1, 0, 0], |
||
87 | [ 0,-1, 0], |
||
88 | [ 0, 1, 0], |
||
89 | [ 0, 0,-1], |
||
90 | [ 0, 0, 1]] |
||
91 | |||
92 | finalPolygons = [] |
||
93 | invidiualPolygonVertices = [] |
||
94 | |||
95 | for i in range(6): |
||
96 | invidiualPolygonVertices = [] |
||
97 | for j in range(4): |
||
98 | pos = vector.Vector(3, |
||
99 | data = [c[0] + r[0] * (2 * int(bool(indices[i][j] & 1)) - 1), |
||
100 | c[1] + r[1] * (2 * int(bool(indices[i][j] & 2)) - 1), |
||
101 | c[2] + r[2] * (2 * int(bool(indices[i][j] & 4)) - 1)]) |
||
102 | |||
103 | invidiualPolygonVertices.append(csgVertex(pos, vector.Vector(3, normals[i]))) |
||
104 | finalPolygons.append(csgPolygon(invidiualPolygonVertices, [1, 1, 1])) |
||
105 | |||
106 | return self.fromPolygons(finalPolygons) |
||
107 | |||
108 | def sphere(self, center, radius, slices, stacks): |
||
109 | c = vector.Vector(3, data=center) |
||
110 | r = radius |
||
111 | sl = slices |
||
112 | st = stacks |
||
113 | |||
114 | polygons = [] |
||
115 | vertices = [] |
||
116 | |||
117 | #vertex = lambda theta, phi: vertices.append(csgVertex(c + (vector.Vector(3, data=[math.cos(theta) * math.sin(phi), math.cos(phi), math.sin(theta) * math.sin(phi)]) * r), vector.Vector(3, data=[math.cos(theta) * math.sin(phi), math.cos(phi), math.sin(theta) * math.sin(phi)]))) |
||
118 | def vertex(theta, phi): |
||
119 | t = theta * math.pi * 2 |
||
120 | p = phi * math.pi |
||
121 | cosT = math.cos(t) |
||
122 | cosP = math.cos(p) |
||
123 | sinT = math.sin(t) |
||
124 | sinP = math.sin(p) |
||
125 | |||
126 | direction = vector.Vector(3, data=[cosT * sinP, cosP, sinT * sinP]) |
||
127 | |||
128 | vertices.append(csgVertex(c + (direction * r), direction)) |
||
129 | |||
130 | for i in range(sl): |
||
131 | for j in range(st): |
||
132 | |||
133 | i = float(i) |
||
134 | j = float(j) |
||
135 | |||
136 | vertices = [] |
||
137 | |||
138 | vertex(i / sl, j / st) |
||
139 | |||
140 | if j > 0: |
||
141 | vertex((i + 1) / sl, j / st) |
||
142 | |||
143 | if j < (stacks - 1): |
||
144 | vertex((i + 1) / sl, (j + 1) / st) |
||
145 | |||
146 | vertex(i / sl, (j + 1) / st) |
||
147 | |||
148 | polygons.append(csgPolygon(vertices, [1, 1, 1])) |
||
149 | |||
150 | return self.fromPolygons(polygons) |
||
151 | |||
152 | def cylinder(self, start, end, radius, slices): |
||
153 | s = vector.Vector(3, data=start) |
||
154 | e = vector.Vector(3, data=end) |
||
155 | r = radius |
||
156 | slices = slices |
||
157 | |||
158 | ray = e - s |
||
159 | |||
160 | axisZ = ray.normalize() |
||
161 | isY = abs(axisZ.vector[1]) > 0.5 |
||
162 | axisX = vector.cross(vector.Vector(3, data=[isY, int(not isY), 0.0]), axisZ).normalize() |
||
163 | axisY = vector.cross(axisX, axisZ).normalize() |
||
164 | |||
165 | start = csgVertex(s, -axisZ) |
||
166 | end = csgVertex(e, axisZ.normalize()) |
||
167 | |||
168 | polygons = [] |
||
169 | |||
170 | def point(stack, sliceC, normalBlend): |
||
171 | angle = sliceC * math.pi * 2 |
||
172 | out = axisX * math.cos(angle) + axisY * math.sin(angle) |
||
173 | pos = s + ray * stack + out * r |
||
174 | normal = out * (1 - abs(normalBlend)) + axisZ * normalBlend |
||
175 | return csgVertex(pos, normal) |
||
176 | |||
177 | for i in range(slices): |
||
178 | t0 = i / slices |
||
179 | t1 = (i + 1) / slices |
||
180 | |||
181 | polygons.append(csgPolygon([start, point(0, t0, -1), point(0, t1, -1)], [1, 1, 1])) |
||
182 | polygons.append(csgPolygon([point(0, t1, 0), point(0, t0, 0), point(1, t0, 0), point(1, t1, 0)], [1, 1, 1])) |
||
183 | polygons.append(csgPolygon([end, point(1, t1, 1), point(1, t0, 1)], [1, 1, 1])) |
||
184 | |||
185 | return self.fromPolygons(polygons) |
||
186 | |||
187 | |||
188 | class csgVertex(object): |
||
189 | def __init__(self, pos, normal): |
||
190 | self.pos = pos |
||
191 | self.normal = normal |
||
192 | self.color = [1, 0, 0] |
||
193 | |||
194 | def __repr__(self): |
||
195 | return 'csgVertex: pos:{} , normal:{}, color:{}'.format(self.pos, self.normal, self.color) |
||
196 | |||
197 | def clone(self): |
||
198 | return csgVertex(self.pos.clone(), self.normal.clone()) |
||
199 | |||
200 | def flip(self): |
||
201 | self.normal = -self.normal |
||
202 | |||
203 | def interpolate(self, vert, t): |
||
204 | pos = vector.lerp(self.pos, vert.pos, t) |
||
205 | normal = vector.lerp(self.normal, vert.normal, t) |
||
206 | return csgVertex(pos, normal) |
||
207 | |||
208 | class csgPlane(plane.Plane): |
||
209 | def __init__(self, normal, d): |
||
210 | super(csgPlane, self).__init__() |
||
211 | self.normal = normal |
||
212 | self.d = d |
||
213 | self.epsilon = 1e-5 |
||
214 | |||
215 | def __repr__(self): |
||
216 | return 'csgPlane: normal:{} , d:{}'.format(self.normal, self.d) |
||
217 | |||
218 | def clone(self): |
||
219 | return csgPlane(self.normal.clone(), self.d) |
||
220 | |||
221 | def splitPolygon(self, polygon, cFront, cBack, f1, b1): |
||
222 | coplanarFront = cFront |
||
223 | coplanarBack = cBack |
||
224 | front = f1 |
||
225 | back = b1 |
||
226 | |||
227 | COPLANAR = 0 |
||
228 | FRONT = 1 |
||
229 | BACK = 2 |
||
230 | SPANNING = 3 |
||
231 | |||
232 | polygonType = 0 |
||
233 | types = [] |
||
234 | |||
235 | for i in range(len(polygon.vertices)): |
||
236 | t = self.normal.dot(polygon.vertices[i].pos) - self.d |
||
237 | typeP = 0 |
||
238 | if t < -self.epsilon: |
||
239 | typeP = BACK |
||
240 | if t > self.epsilon: |
||
241 | typeP = FRONT |
||
242 | if t > -self.epsilon and t < self.epsilon: |
||
243 | typeP = COPLANAR |
||
244 | |||
245 | polygonType |= typeP |
||
246 | types.append(typeP) |
||
247 | |||
248 | |||
249 | if polygonType == COPLANAR: |
||
250 | if self.normal.dot(polygon.plane.normal) > 0: |
||
251 | coplanarFront.append(polygon) |
||
252 | else: |
||
253 | coplanarBack.append(polygon) |
||
254 | if polygonType == FRONT: |
||
255 | front.append(polygon) |
||
256 | if polygonType == BACK: |
||
257 | back.append(polygon) |
||
258 | if polygonType == SPANNING: |
||
259 | f = [] |
||
260 | b = [] |
||
261 | |||
262 | for i in range(len(polygon.vertices)): |
||
263 | j = (i + 1) % len(polygon.vertices) |
||
264 | ti = types[i] |
||
265 | tj = types[j] |
||
266 | |||
267 | vi = polygon.vertices[i] |
||
268 | vj = polygon.vertices[j] |
||
269 | |||
270 | if (ti != BACK): |
||
271 | f.append(vi) |
||
272 | if (ti != FRONT): |
||
273 | if ti != BACK: |
||
274 | b.append(vi.clone()) |
||
275 | else: |
||
276 | b.append(vi) |
||
277 | |||
278 | if ((ti | tj) == SPANNING): |
||
279 | t = (self.d - self.normal.dot(vi.pos)) / self.normal.dot(vj.pos - vi.pos) |
||
280 | v = vi.interpolate(vj, t) |
||
281 | f.append(v) |
||
282 | b.append(v.clone()) |
||
283 | |||
284 | if (len(f) >= 3): |
||
285 | front.append(csgPolygon(f, polygon.shared)) |
||
286 | if (len(b) >= 3): |
||
287 | back.append(csgPolygon(b, polygon.shared)) |
||
288 | |||
289 | return coplanarFront, coplanarBack, front, back |
||
290 | |||
291 | class csgPolygon(object): |
||
292 | def __init__(self, vertices, shared): |
||
293 | self.vertices = vertices |
||
294 | self.shared = shared |
||
295 | self.plane = csgPlane(vector.Vector(3), 0) |
||
296 | self.plane.fromPoints(vertices[0].pos, vertices[1].pos, vertices[2].pos) |
||
297 | |||
298 | def __repr__(self): |
||
299 | return 'csgPolygon: vertices:{} , shared:{}, plane:{}'.format(self.vertices, self.shared, self.plane) |
||
300 | |||
301 | def clone(self): |
||
302 | return csgPolygon(self.vertices, self.shared) |
||
303 | |||
304 | def flip(self): |
||
305 | vertices = self.vertices[::-1] |
||
306 | for i in range(len(self.vertices)): |
||
307 | if self.vertices[i] == None: |
||
308 | vertices.append(self.vertices[i].flip()) |
||
309 | |||
310 | self.vertices = vertices |
||
311 | self.plane.flip() |
||
312 | |||
313 | class csgNode(CSG): |
||
314 | def __init__(self, polygons): |
||
315 | super(csgNode, self).__init__() |
||
316 | self.plane = None |
||
317 | self.front = None |
||
318 | self.back = None |
||
319 | self.polygons = [] |
||
320 | |||
321 | if polygons: |
||
322 | self.build(polygons) |
||
323 | |||
324 | def clone(self): |
||
325 | newNode = csgNode([]) |
||
326 | newNode.plane = self.plane and self.plane.clone() |
||
327 | newNode.front = self.front and self.front.clone() |
||
328 | newNode.back = self.back and self.back.clone() |
||
329 | newNode.polygons = self.polygons |
||
330 | return newNode |
||
331 | |||
332 | def invert(self): |
||
333 | for i in range(len(self.polygons)): |
||
334 | self.polygons[i].flip() |
||
335 | |||
336 | self.plane.flip() |
||
337 | |||
338 | if (self.front): |
||
339 | self.front.invert() |
||
340 | |||
341 | if (self.back): |
||
342 | self.back.invert() |
||
343 | |||
344 | temp = self.front |
||
345 | self.front = self.back |
||
346 | self.back = temp |
||
347 | |||
348 | def clipPolygons(self, polygons): |
||
349 | if (self.plane == 0): |
||
350 | return polygons |
||
351 | |||
352 | front = [] |
||
353 | back = [] |
||
354 | |||
355 | for i in range(len(polygons)): |
||
356 | front, back, front, back = self.plane.splitPolygon(polygons[i], front, back, front, back) |
||
357 | |||
358 | if self.front: |
||
359 | front = self.front.clipPolygons(front) |
||
360 | |||
361 | if self.back: |
||
362 | back = self.back.clipPolygons(back) |
||
363 | else: |
||
364 | back = [] |
||
365 | |||
366 | return front + back |
||
367 | |||
368 | def clipTo(self, bsp): |
||
369 | self.polygons = bsp.clipPolygons(self.polygons) |
||
370 | |||
371 | if self.front: |
||
372 | self.front.clipTo(bsp) |
||
373 | if self.back: |
||
374 | self.back.clipTo(bsp) |
||
375 | |||
376 | def allPolygons(self): |
||
377 | polygons = self.polygons |
||
378 | |||
379 | if self.front: |
||
380 | polygons = polygons + (self.front.allPolygons()) |
||
381 | if self.back: |
||
382 | polygons = polygons + (self.back.allPolygons()) |
||
383 | return polygons |
||
384 | |||
385 | def build(self, polygons): |
||
386 | if (len(polygons) == 0): |
||
387 | return |
||
388 | |||
389 | if (self.plane is None): |
||
390 | self.plane = polygons[0].plane.clone() |
||
391 | |||
392 | front = [] |
||
393 | back = [] |
||
394 | |||
395 | for i in range(len(polygons)): |
||
396 | self.polygons, self.polygons, front, back = self.plane.splitPolygon(polygons[i], self.polygons, self.polygons, front, back) |
||
397 | |||
398 | if len(front): |
||
399 | if (self.front is None): |
||
400 | self.front = csgNode([]) |
||
401 | self.front.build(front) |
||
402 | |||
403 | if len(back): |
||
404 | if(self.back is None): |
||
405 | self.back = csgNode([]) |
||
406 | self.back.build(back) |
||
407 |