Line data Source code
1 : /**
2 : * @file normal.cpp
3 : * @brief cube geometry normal interpolation
4 : *
5 : * cube geometry in the libprimis engine is faceted, only allowing 8 ticks of
6 : * movement; as a result, normal vectors of geometry are not very smooth
7 : *
8 : * to resolve this, adjacent cube faces with low differences in their angle can
9 : * have their faces "merged" by interpolating the normal maps of their respective
10 : * faces
11 : *
12 : * this is controlled by the lerp variables and is generally uniformly done for
13 : * all geometry on the map; see `lerpangle` for the threshold variable
14 : */
15 : #include "../libprimis-headers/cube.h"
16 : #include "../../shared/geomexts.h"
17 :
18 : #include "octarender.h"
19 :
20 : #include "world/octaworld.h"
21 : #include "world/world.h"
22 :
23 : struct NormalKey final
24 : {
25 : vec pos;
26 : int smooth;
27 :
28 0 : bool operator==(const NormalKey &k) const
29 : {
30 0 : return k.pos == pos && smooth == k.smooth;
31 : }
32 : };
33 :
34 : template<>
35 : struct std::hash<NormalKey>
36 : {
37 0 : size_t operator()(const NormalKey &k) const
38 : {
39 : std::hash<vec> vechash = std::hash<vec>();
40 0 : return vechash(k.pos);
41 : }
42 : };
43 :
44 : namespace //internal functionality not seen by other files
45 : {
46 : struct NormalGroup final {
47 : vec pos;
48 : int smooth, flat, normals, tnormals;
49 :
50 : NormalGroup() : smooth(0), flat(0), normals(-1), tnormals(-1) {}
51 0 : NormalGroup(const NormalKey &key) : pos(key.pos), smooth(key.smooth), flat(0), normals(-1), tnormals(-1) {}
52 : };
53 :
54 : struct Normal final
55 : {
56 : int next;
57 : vec surface;
58 : };
59 :
60 : struct TNormal final
61 : {
62 : int next;
63 : float offset;
64 : std::array<int, 2> normals;
65 : std::array<const NormalGroup *, 2> groups;
66 : };
67 :
68 : std::unordered_map<NormalKey, NormalGroup> normalgroups;
69 : std::vector<Normal> normals;
70 : std::vector<TNormal> tnormals;
71 : std::vector<int> smoothgroups;
72 :
73 : VARR(lerpangle, 0, 44, 180); //max angle to merge octree faces' normals smoothly
74 :
75 : bool usetnormals = true;
76 :
77 : /**
78 : * @brief Adds normal to the normals vector
79 : *
80 : * Only adds value if the NormalKey defined by the pos and smooth values does
81 : * not correspond to an existing normalgroup in the normalgroups map. Adds a
82 : * value to normalgroups corresponding to the pos/smooth values and adds a value
83 : * to normals corresponding to the surface vector.
84 : *
85 : * @param pos location of the normal
86 : * @param smooth smooth value of the normal
87 : * @param surface surface vector of the normal to add
88 : *
89 : * @return size of normals vector minus one
90 : */
91 0 : int addnormal(const vec &pos, int smooth, const vec &surface)
92 : {
93 0 : NormalKey key = { pos, smooth };
94 0 : std::unordered_map<NormalKey, NormalGroup>::iterator itr = normalgroups.find(key);
95 0 : if(itr == normalgroups.end())
96 : {
97 0 : itr = normalgroups.insert( { key, NormalGroup(key) } ).first;
98 : }
99 0 : Normal n;
100 0 : n.next = (*itr).second.normals;
101 0 : n.surface = surface;
102 0 : normals.push_back(n);
103 0 : return (*itr).second.normals = normals.size()-1;
104 : }
105 :
106 0 : void addtnormal(const vec &pos, int smooth, float offset, int normal1, int normal2, const vec &pos1, const vec &pos2)
107 : {
108 0 : NormalKey key = { pos, smooth };
109 0 : std::unordered_map<NormalKey, NormalGroup>::iterator itr = normalgroups.find(key);
110 0 : if(itr == normalgroups.end())
111 : {
112 0 : itr = normalgroups.insert( { key, NormalGroup(key) } ).first;
113 : }
114 : TNormal n;
115 0 : n.next = (*itr).second.tnormals;
116 0 : n.offset = offset;
117 0 : n.normals[0] = normal1;
118 0 : n.normals[1] = normal2;
119 0 : NormalKey key1 = { pos1, smooth },
120 0 : key2 = { pos2, smooth };
121 0 : n.groups[0] = &((*normalgroups.find(key1)).second);
122 0 : n.groups[1] = &((*normalgroups.find(key2)).second);
123 0 : tnormals.push_back(n);
124 0 : (*itr).second.tnormals = tnormals.size()-1;
125 0 : }
126 :
127 0 : int addnormal(const vec &pos, int smooth, int axis)
128 : {
129 0 : NormalKey key = { pos, smooth };
130 0 : std::unordered_map<NormalKey, NormalGroup>::iterator itr = normalgroups.find(key);
131 0 : if(itr == normalgroups.end())
132 : {
133 0 : itr = normalgroups.insert( { key, NormalGroup(key) } ).first;
134 : }
135 0 : (*itr).second.flat += 1<<(4*axis);
136 0 : return axis - 6;
137 : }
138 :
139 0 : void findnormal(const NormalGroup &g, float lerpthreshold, const vec &surface, vec &v)
140 : {
141 0 : v = vec(0, 0, 0);
142 0 : int total = 0;
143 : //check if abs value of x component of the surface normal is greater than the lerp threshold
144 : //note the assignments to the int n are bitshifted to pack all three axes within a single int
145 0 : if(surface.x >= lerpthreshold)
146 : {
147 0 : const int n = (g.flat>>4)&0xF;
148 0 : v.x += n; total += n;
149 : }
150 0 : else if(surface.x <= -lerpthreshold)
151 : {
152 0 : const int n = g.flat&0xF;
153 0 : v.x -= n;
154 0 : total += n;
155 : }
156 : //ditto y component
157 0 : if(surface.y >= lerpthreshold)
158 : {
159 0 : const int n = (g.flat>>12)&0xF;
160 0 : v.y += n;
161 0 : total += n;
162 : }
163 0 : else if(surface.y <= -lerpthreshold)
164 : {
165 0 : const int n = (g.flat>>8)&0xF;
166 0 : v.y -= n;
167 0 : total += n;
168 : }
169 : //ditto z component
170 0 : if(surface.z >= lerpthreshold)
171 : {
172 0 : const int n = (g.flat>>20)&0xF;
173 0 : v.z += n;
174 0 : total += n;
175 : }
176 0 : else if(surface.z <= -lerpthreshold)
177 : {
178 0 : const int n = (g.flat>>16)&0xF;
179 0 : v.z -= n;
180 0 : total += n;
181 : }
182 :
183 0 : for(int cur = g.normals; cur >= 0;)
184 : {
185 0 : const Normal &o = normals[cur];
186 0 : if(o.surface.dot(surface) >= lerpthreshold)
187 : {
188 0 : v.add(o.surface);
189 0 : total++;
190 : }
191 0 : cur = o.next;
192 : }
193 0 : if(total > 1)
194 : {
195 0 : v.normalize();
196 : }
197 0 : else if(!total)
198 : {
199 0 : v = surface;
200 : }
201 0 : }
202 :
203 0 : bool findtnormal(const NormalGroup &g, float lerpthreshold, const vec &surface, vec &v)
204 : {
205 0 : float bestangle = lerpthreshold;
206 0 : const TNormal *bestnorm = nullptr;
207 0 : for(int cur = g.tnormals; cur >= 0;)
208 : {
209 0 : const TNormal &o = tnormals[cur];
210 : const std::array<vec, 6> flats = { vec(-1, 0, 0),
211 : vec( 1, 0, 0),
212 : vec( 0, -1, 0),
213 : vec( 0, 1, 0),
214 : vec( 0, 0, -1),
215 0 : vec( 0, 0, 1) };
216 0 : vec n1 = o.normals[0] < 0 ? flats[o.normals[0]+6] : normals[o.normals[0]].surface,
217 0 : n2 = o.normals[1] < 0 ? flats[o.normals[1]+6] : normals[o.normals[1]].surface,
218 0 : nt;
219 0 : nt.lerp(n1, n2, o.offset).normalize();
220 0 : float tangle = nt.dot(surface);
221 0 : if(tangle >= bestangle)
222 : {
223 0 : bestangle = tangle;
224 0 : bestnorm = &o;
225 : }
226 0 : cur = o.next;
227 : }
228 0 : if(!bestnorm)
229 : {
230 0 : return false;
231 : }
232 0 : vec n1, n2;
233 0 : findnormal(*bestnorm->groups[0], lerpthreshold, surface, n1);
234 0 : findnormal(*bestnorm->groups[1], lerpthreshold, surface, n2);
235 0 : v.lerp(n1, n2, bestnorm->offset).normalize();
236 0 : return true;
237 : }
238 :
239 : VARR(lerpsubdiv, 0, 2, 4); //Linear intERPolation SUBDIVisions
240 : VARR(lerpsubdivsize, 4, 4, 128);//Linear intERPolation SUBDIVision cube SIZE
241 :
242 0 : void addnormals(const cube &c, const ivec &o, int size)
243 : {
244 0 : if(c.children)
245 : {
246 0 : size >>= 1;
247 0 : for(size_t i = 0; i < c.children->size(); ++i)
248 : {
249 0 : addnormals((*c.children)[i], ivec(i, o, size), size);
250 : }
251 0 : return;
252 : }
253 0 : else if(c.isempty())
254 : {
255 0 : return;
256 : }
257 0 : std::array<vec, Face_MaxVerts> pos;
258 : std::array<int, Face_MaxVerts> norms;
259 0 : int tj = usetnormals && c.ext ? c.ext->tjoints : -1, vis;
260 0 : for(int i = 0; i < 6; ++i)
261 : {
262 0 : if((vis = visibletris(c, i, o, size)))
263 : {
264 0 : if(c.texture[i] == Default_Sky)
265 : {
266 0 : continue;
267 : }
268 :
269 0 : std::array<vec, 2> planes;
270 0 : int numverts = c.ext ? c.ext->surfaces[i].numverts&Face_MaxVerts : 0,
271 0 : convex = 0,
272 0 : numplanes = 0;
273 0 : if(numverts)
274 : {
275 0 : vertinfo *verts = c.ext->verts() + c.ext->surfaces[i].verts;
276 0 : const vec vo(static_cast<ivec>(o).mask(~0xFFF));
277 0 : for(int j = 0; j < numverts; ++j)
278 : {
279 0 : const vertinfo &v = verts[j];
280 0 : pos[j] = vec(v.x, v.y, v.z).mul(1.0f/8).add(vo);
281 : }
282 0 : if(!(c.merged&(1<<i)) && !flataxisface(c, i))
283 : {
284 0 : convex = faceconvexity(verts, numverts, size);
285 : }
286 : }
287 0 : else if(c.merged&(1<<i))
288 : {
289 0 : continue;
290 : }
291 : else
292 : {
293 0 : std::array<ivec, 4> v;
294 0 : genfaceverts(c, i, v);
295 0 : if(!flataxisface(c, i))
296 : {
297 0 : convex = faceconvexity(v);
298 : }
299 0 : const int order = vis&4 || convex < 0 ? 1 : 0;
300 0 : const vec vo(o);
301 0 : pos[numverts++] = static_cast<vec>(v[order]).mul(size/8.0f).add(vo);
302 0 : if(vis&1)
303 : {
304 0 : pos[numverts++] = static_cast<vec>(v[order+1]).mul(size/8.0f).add(vo);
305 : }
306 0 : pos[numverts++] = static_cast<vec>(v[order+2]).mul(size/8.0f).add(vo);
307 0 : if(vis&2)
308 : {
309 0 : pos[numverts++] = static_cast<vec>(v[(order+3)&3]).mul(size/8.0f).add(vo);
310 : }
311 : }
312 :
313 0 : if(!flataxisface(c, i))
314 : {
315 0 : planes[numplanes++].cross(pos[0], pos[1], pos[2]).normalize();
316 0 : if(convex)
317 : {
318 0 : planes[numplanes++].cross(pos[0], pos[2], pos[3]).normalize();
319 : }
320 : }
321 :
322 0 : const VSlot &vslot = lookupvslot(c.texture[i], false);
323 0 : const int smooth = vslot.slot->smooth;
324 :
325 0 : if(!numplanes)
326 : {
327 0 : for(int k = 0; k < numverts; ++k)
328 : {
329 0 : norms[k] = addnormal(pos[k], smooth, i);
330 : }
331 : }
332 0 : else if(numplanes==1)
333 : {
334 0 : for(int k = 0; k < numverts; ++k)
335 : {
336 0 : norms[k] = addnormal(pos[k], smooth, planes[0]);
337 : }
338 : }
339 : else
340 : {
341 0 : const vec avg = vec(planes[0]).add(planes[1]).normalize();
342 0 : norms[0] = addnormal(pos[0], smooth, avg);
343 0 : norms[1] = addnormal(pos[1], smooth, planes[0]);
344 0 : norms[2] = addnormal(pos[2], smooth, avg);
345 0 : for(int k = 3; k < numverts; k++)
346 : {
347 0 : norms[k] = addnormal(pos[k], smooth, planes[1]);
348 : }
349 : }
350 :
351 0 : while(tj >= 0 && tjoints[tj].edge < i*(Face_MaxVerts+1))
352 : {
353 0 : tj = tjoints[tj].next;
354 : }
355 0 : while(tj >= 0 && tjoints[tj].edge < (i+1)*(Face_MaxVerts+1))
356 : {
357 0 : const int edge = tjoints[tj].edge,
358 0 : e1 = edge%(Face_MaxVerts+1),
359 0 : e2 = (e1+1)%numverts;
360 0 : const vec &v1 = pos[e1],
361 0 : &v2 = pos[e2];
362 0 : ivec d(vec(v2).sub(v1).mul(8));
363 0 : int axis = std::abs(d.x) > std::abs(d.y) ? (std::abs(d.x) > std::abs(d.z) ? 0 : 2) : (std::abs(d.y) > std::abs(d.z) ? 1 : 2);
364 0 : if(d[axis] < 0)
365 : {
366 0 : d.neg();
367 : }
368 0 : reduceslope(d);
369 0 : const int origin = static_cast<int>(std::min(v1[axis], v2[axis])*8)&~0x7FFF,
370 0 : offset1 = (static_cast<int>(v1[axis]*8) - origin) / d[axis],
371 0 : offset2 = (static_cast<int>(v2[axis]*8) - origin) / d[axis];
372 0 : const vec o2 = vec(v1).sub(vec(d).mul(offset1/8.0f));
373 0 : vec n1, n2;
374 0 : const float doffset = 1.0f / (offset2 - offset1);
375 0 : while(tj >= 0)
376 : {
377 0 : const TJoint &t = tjoints[tj];
378 0 : if(t.edge != edge)
379 : {
380 0 : break;
381 : }
382 0 : const float offset = (t.offset - offset1) * doffset;
383 0 : const vec tpos = vec(d).mul(t.offset/8.0f).add(o2);
384 0 : addtnormal(tpos, smooth, offset, norms[e1], norms[e2], v1, v2);
385 0 : tj = t.next;
386 : }
387 : }
388 : }
389 : }
390 : }
391 : }
392 :
393 : /* externally relevant functionality */
394 : ///////////////////////////////////////
395 :
396 0 : void findnormal(const vec &pos, int smooth, const vec &surface, vec &v)
397 : {
398 0 : NormalKey key = { pos, smooth };
399 0 : std::unordered_map<NormalKey, NormalGroup>::iterator itr = normalgroups.find(key);
400 0 : if(smooth < 0)
401 : {
402 0 : smooth = 0;
403 : }
404 0 : bool usegroup = (static_cast<int>(smoothgroups.size()) > smooth) && smoothgroups[smooth] >= 0;
405 0 : if(itr != normalgroups.end())
406 : {
407 0 : int angle = usegroup ? smoothgroups[smooth] : lerpangle;
408 0 : float lerpthreshold = cos360(angle) - 1e-5f;
409 0 : if((*itr).second.tnormals < 0 || !findtnormal((*itr).second, lerpthreshold, surface, v))
410 : {
411 0 : findnormal((*itr).second, lerpthreshold, surface, v);
412 : }
413 : }
414 : else
415 : {
416 0 : v = surface;
417 : }
418 0 : }
419 :
420 0 : void cubeworld::calcnormals(bool lerptjoints)
421 : {
422 0 : usetnormals = lerptjoints;
423 0 : if(usetnormals)
424 : {
425 0 : findtjoints();
426 : }
427 0 : for(size_t i = 0; i < worldroot->size(); ++i)
428 : {
429 0 : addnormals((*worldroot)[i], ivec(i, ivec(0, 0, 0), mapsize()/2), mapsize()/2);
430 : }
431 0 : }
432 :
433 0 : void clearnormals()
434 : {
435 0 : normalgroups.clear();
436 0 : normals.clear();
437 0 : tnormals.clear();
438 0 : }
439 :
440 0 : void resetsmoothgroups()
441 : {
442 0 : smoothgroups.clear();
443 0 : }
444 :
445 : static constexpr int maxsmoothgroups = 10000;
446 :
447 1 : int smoothangle(int id, int angle)
448 : {
449 1 : if(id < 0)
450 : {
451 0 : id = static_cast<int>(smoothgroups.size());
452 : }
453 1 : if(id >= maxsmoothgroups)
454 : {
455 0 : return -1;
456 : }
457 2 : while(static_cast<int>(smoothgroups.size()) <= id)
458 : {
459 1 : smoothgroups.push_back(-1);
460 : }
461 1 : if(angle >= 0)
462 : {
463 0 : smoothgroups[id] = std::min(angle, 180);
464 : }
465 1 : return id;
466 : }
467 :
468 1 : void initnormalcmds()
469 : {
470 1 : addcommand("smoothangle", reinterpret_cast<identfun>(+[] (const int *id, const int *angle)
471 : {
472 1 : intret(smoothangle(*id, *angle));
473 1 : }), "ib", Id_Command);
474 1 : }
|