]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/client/miscfunctions.qc
further optimizations... multi-cutting
[divverent/nexuiz.git] / data / qcsrc / client / miscfunctions.qc
1 var float(string text, float handleColors, vector fontSize) stringwidth;
2
3 entity players;
4 entity teams;
5
6 void restartAnnouncer_Think() {
7         float countdown_rounded, countdown;
8         countdown = getstatf(STAT_GAMESTARTTIME) - time;
9         countdown_rounded = floor(0.5 + countdown);
10         if(countdown <= 0) {
11                 if (!spectatee_status) //do cprint only for players
12                         centerprint("^1Begin!");
13
14                 sound(self, CHAN_VOICE, "announcer/robotic/begin.wav", VOL_BASEVOICE, ATTN_NONE);
15                 //reset maptime announcers now as well
16                 announcer_5min = announcer_1min = FALSE;
17
18                 remove(self);
19                 return;
20         }
21         else {
22                 if (!spectatee_status) //do cprint only for players
23                         centerprint(strcat("^1Game starts in ", ftos(countdown_rounded), " seconds"));
24
25                 if(countdown_rounded <= 3 && countdown_rounded >= 1) {
26                         sound(self, CHAN_VOICE, strcat("announcer/robotic/", ftos(countdown_rounded), ".wav"), VOL_BASEVOICE, ATTN_NONE);
27                 }
28
29                 self.nextthink = getstatf(STAT_GAMESTARTTIME) - (countdown - 1);
30         }
31 }
32
33 /**
34  * Plays the 1minute or 5 minutes (of maptime) remaining sound, if client wants it
35  */
36 void maptimeAnnouncer() {
37     float timelimit;
38     timelimit = getstatf(STAT_TIMELIMIT);
39     float timeleft;
40     timeleft = max(0, timelimit * 60 + getstatf(STAT_GAMESTARTTIME) - time);
41
42     float warmuplimit;
43     float warmuptimeleft;
44     if(warmup_stage) {
45         warmuplimit = cvar("g_warmup_limit");
46         if(warmuplimit > 0) {
47            warmuptimeleft = max(0, warmuplimit + getstatf(STAT_GAMESTARTTIME) - time); 
48         }
49     }
50
51     //5 minute check
52     if (cvar("cl_sound_maptime_warning") >= 2) {
53         //make sure that after connect (and e.g. 4 minutes left) we will not get a wrong sound
54         if(announcer_5min)
55         {
56                         if(((!warmup_stage || warmuplimit == 0) && timeleft > 300) || (warmup_stage && warmuplimit > 0 && warmuptimeleft > 300))
57                                 announcer_5min = FALSE;
58         }
59         else if (((!warmup_stage || warmuplimit == 0) && timelimit > 0 && timeleft < 300 && timeleft > 299) || (warmup_stage && warmuplimit > 0 && warmuptimeleft < 300 && warmuptimeleft > 299))
60         //if we're in warmup mode, check whether there's a warmup timelimit
61         if not (warmuplimit == -1 && warmup_stage) {
62                         announcer_5min = TRUE;
63                         //dprint("i will play the sound, I promise!\n");
64                         sound(self, CHAN_VOICE, "announcer/robotic/5minutesremain.wav", VOL_BASEVOICE, ATTN_NONE);
65                 }
66     }
67
68     //1 minute check
69     if (cvar("cl_sound_maptime_warning") == 1 || cvar("cl_sound_maptime_warning") == 3) {
70         if (announcer_1min)
71         {
72                         if(((!warmup_stage || warmuplimit == 0) && timeleft > 60) || (warmup_stage && warmuplimit > 0 && warmuptimeleft > 60))
73                                 announcer_1min = FALSE;
74         }
75         else if (((!warmup_stage || warmuplimit == 0) && timelimit > 0 && timeleft < 60) || (warmup_stage && warmuplimit > 0 && warmuptimeleft < 60))
76         //if we're in warmup mode, check whether there's a warmup timelimit
77         if not (warmuplimit == -1 && warmup_stage) {
78                         announcer_1min = TRUE;
79                         sound(self, CHAN_VOICE, "announcer/robotic/1minuteremains.wav", VOL_BASEVOICE, ATTN_NONE);
80         }
81         }
82 }
83
84  /**
85  * Announce carried items (e.g. flags in CTF).
86  */
87 float redflag_prev;
88 float blueflag_prev;
89 void carrierAnnouncer() {
90         float stat_items, redflag, blueflag;
91         float pickup;
92         string item;
93
94         if not(cvar("cl_notify_carried_items"))
95                 return;
96
97         stat_items = getstati(STAT_ITEMS);
98
99         redflag = (stat_items/IT_RED_FLAG_TAKEN) & 3;
100         blueflag = (stat_items/IT_BLUE_FLAG_TAKEN) & 3;
101
102         if (redflag == 3 && redflag != redflag_prev) {
103                 item = "^1RED^7 flag";
104                 pickup = (redflag_prev == 2);
105         }
106
107         if (blueflag == 3 && blueflag != blueflag_prev) {
108                 item = "^4BLUE^7 flag";
109                 pickup = (blueflag_prev == 2);
110         }
111
112         if (item)
113         {
114                 if (pickup) {
115                         if (cvar("cl_notify_carried_items") & 2)
116                                 centerprint(strcat("You picked up the ", item, "!"));
117                 }
118                 else {
119                         if (cvar("cl_notify_carried_items") & 1)
120                                 centerprint(strcat("You got the ", item, "!"));
121                 }
122         }
123
124         blueflag_prev = blueflag;
125         redflag_prev = redflag;
126 }
127
128 /**
129   * Add all future announcer sounds precaches here.
130   * TODO: make all announcer sound() calls client-side in the end, to allow queues etc.
131   */
132
133 /**
134  * Add all future announcer sounds precaches here.
135  * TODO: make all announcer sound() calls client-side in the end, to allow queues etc.
136  */
137 void Announcer_Precache () {
138     precache_sound ("announcer/robotic/1minuteremains.wav");
139         precache_sound ("announcer/robotic/5minutesremain.wav");
140 }
141
142 void AuditLists()
143 {
144         entity e;
145         entity prev;
146
147         prev = players;
148         for(e = prev.sort_next; e; prev = e, e = e.sort_next)
149         {
150                 if(prev != e.sort_prev)
151                         error(strcat("sort list chain error\nplease submit the output of 'prvm_edicts client' to the developers"));
152         }
153
154         prev = teams;
155         for(e = prev.sort_next; e; prev = e, e = e.sort_next)
156         {
157                 if(prev != e.sort_prev)
158                         error(strcat("sort list chain error\nplease submit the output of 'prvm_edicts client' to the developers"));
159         }
160 }
161
162
163 float RegisterPlayer(entity player)
164 {
165         entity pl;
166         AuditLists();
167         for(pl = players.sort_next; pl; pl = pl.sort_next)
168                 if(pl == player)
169                         error("Player already registered!");
170         player.sort_next = players.sort_next;
171         player.sort_prev = players;
172         if(players.sort_next)
173                 players.sort_next.sort_prev = player;
174         players.sort_next = player;
175         AuditLists();
176         return true;
177 }
178
179 void RemovePlayer(entity player)
180 {
181         entity pl, parent;
182         AuditLists();
183         parent = players;
184         for(pl = players.sort_next; pl && pl != player; pl = pl.sort_next)
185                 parent = pl;
186
187         if(!pl)
188         {
189                 error("Trying to remove a player which is not in the playerlist!");
190                 return;
191         }
192         parent.sort_next = player.sort_next;
193         if(player.sort_next)
194                 player.sort_next.sort_prev = parent;
195         AuditLists();
196 }
197
198 void MoveToLast(entity e)
199 {
200         AuditLists();
201         other = e.sort_next;
202         while(other)
203         {
204                 SORT_SWAP(other, e);
205                 other = e.sort_next;
206         }
207         AuditLists();
208 }
209
210 float RegisterTeam(entity Team)
211 {
212         entity tm;
213         AuditLists();
214         for(tm = teams.sort_next; tm; tm = tm.sort_next)
215                 if(tm == Team)
216                         error("Team already registered!");
217         Team.sort_next = teams.sort_next;
218         Team.sort_prev = teams;
219         if(teams.sort_next)
220                 teams.sort_next.sort_prev = Team;
221         teams.sort_next = Team;
222         AuditLists();
223         return true;
224 }
225
226 void RemoveTeam(entity Team)
227 {
228         entity tm, parent;
229         AuditLists();
230         parent = teams;
231         for(tm = teams.sort_next; tm && tm != Team; tm = tm.sort_next)
232                 parent = tm;
233
234         if(!tm)
235         {
236                 print("Trying to remove a team which is not in the teamlist!");
237                 return;
238         }
239         parent.sort_next = Team.sort_next;
240         if(Team.sort_next)
241                 Team.sort_next.sort_prev = parent;
242         AuditLists();
243 }
244
245 entity GetTeam(float Team, float add)
246 {
247         float num;
248         entity tm;
249         num = (Team == COLOR_SPECTATOR) ? 16 : Team;
250         if(teamslots[num])
251                 return teamslots[num];
252         if not(add)
253                 return NULL;
254         tm = spawn();
255         tm.team = Team;
256         teamslots[num] = tm;
257         RegisterTeam(tm);
258         return tm;
259 }
260
261 void CSQC_CheckEngine()
262 {
263         sbar_font = FONT_USER+1;
264         sbar_bigfont = FONT_USER+2;
265 }
266
267 vector Sbar_GetFontsize(string cvarname)
268 {
269         vector v;
270         v = stov(cvar_string(cvarname));
271         if(v_x == 0)
272                 v = '8 8 0';
273         if(v_y == 0)
274                 v_y = v_x;
275         v_z = 0;
276         return v;
277 }
278
279 float Sbar_GetWidth(float teamcolumnwidth)
280 {
281         float f;
282         f = cvar("sbar_width");
283         if(f == 0)
284                 f = 640;
285         if(f < 320)
286                 f = 320;
287         if(f > vid_conwidth - 2 * teamcolumnwidth)
288                 f = vid_conwidth - 2 * teamcolumnwidth;
289         return f;
290 }
291
292 float PreviewExists(string name)
293 {
294         float f;
295         string file;
296
297         if(cvar("cl_readpicture_force"))
298                 return false;
299
300         file = strcat(name, ".tga");
301         f = fopen(file, FILE_READ);
302         if(f >= 0)
303         {
304                 fclose(f);
305                 return true;
306         }
307         file = strcat(name, ".png");
308         f = fopen(file, FILE_READ);
309         if(f >= 0)
310         {
311                 fclose(f);
312                 return true;
313         }
314         file = strcat(name, ".jpg");
315         f = fopen(file, FILE_READ);
316         if(f >= 0)
317         {
318                 fclose(f);
319                 return true;
320         }
321         file = strcat(name, ".pcx");
322         f = fopen(file, FILE_READ);
323         if(f >= 0)
324         {
325                 fclose(f);
326                 return true;
327         }
328         return false;
329 }
330
331 vector rotate(vector v, float a)
332 {
333         vector w;
334         // FTEQCC SUCKS AGAIN
335         w_x =      v_x * cos(a) + v_y * sin(a);
336         w_y = -1 * v_x * sin(a) + v_y * cos(a);
337         return w;
338 }
339
340 float ColorTranslateMode;
341
342 string ColorTranslateRGB(string s)
343 {
344         if(ColorTranslateMode & 1)
345                 return strdecolorize(s);
346         else
347                 return s;
348 }
349
350 float cvar_or(string cv, float v)
351 {
352         string s;
353         s = cvar_string(cv);
354         if(s == "")
355                 return v;
356         else
357                 return stof(s);
358 }
359
360 vector project_3d_to_2d(vector vec)
361 {
362         vec = cs_project(vec);
363         if(cs_project_is_b0rked > 0)
364         {
365                 vec_x *= vid_conwidth / vid_width;
366                 vec_y *= vid_conheight / vid_height;
367         }
368         return vec;
369 }
370
371 void dummyfunction(float a1, float a2, float a3, float a4, float a5, float a6, float a7, float a8)
372 {
373 }
374
375 float expandingbox_sizefactor_from_fadelerp(float fadelerp)
376 {
377         return 1.2 / (1.2 - fadelerp);
378 }
379
380 vector expandingbox_resize_centered_box_offset(float sz, vector boxsize, float boxxsizefactor)
381 {
382         boxsize_x *= boxxsizefactor; // easier interface for text
383         return boxsize * (0.5 * (1 - sz));
384 }
385
386 void drawborderlines(float thickness, vector pos, vector dim, vector color, float alpha, float drawflag)
387 {
388         vector line_dim;
389
390         // left and right lines
391         pos_x -= thickness;
392         line_dim_x = thickness;
393         line_dim_y = dim_y;
394         drawfill(pos, line_dim, color, alpha, drawflag);
395         drawfill(pos + (dim_x + thickness) * '1 0 0', line_dim, color, alpha, drawflag);
396
397         // upper and lower lines
398         pos_y -= thickness;
399         line_dim_x = dim_x + thickness * 2; // make upper and lower lines longer
400         line_dim_y = thickness;
401         drawfill(pos, line_dim, color, alpha, drawflag);
402         drawfill(pos + (dim_y + thickness) * '0 1 0', line_dim, color, alpha, drawflag);
403 }
404
405 void drawpic_tiled(vector pos, string pic, vector sz, vector area, vector color, float alpha, float drawflag)
406 {
407         vector current_pos, end_pos, new_size, ratio;
408         end_pos = pos + area;
409
410         current_pos_y = pos_y;
411         while (current_pos_y < end_pos_y)
412         {
413                 current_pos_x = pos_x;
414                 while (current_pos_x < end_pos_x)
415                 {
416                         new_size_x = min(sz_x, end_pos_x - current_pos_x);
417                         new_size_y = min(sz_y, end_pos_y - current_pos_y);
418                         ratio_x = new_size_x / sz_x;
419                         ratio_y = new_size_y / sz_y;
420                         drawsubpic(current_pos, new_size, pic, '0 0 0', ratio, color, alpha, drawflag);
421                         current_pos_x += sz_x;
422                 }
423                 current_pos_y += sz_y;
424         }
425 }
426
427 void drawpic_expanding(vector position, string pic, vector scale, vector rgb, float alpha, float flag, float fadelerp)
428 {
429         float sz;
430         sz = expandingbox_sizefactor_from_fadelerp(fadelerp);
431
432         drawpic(position + expandingbox_resize_centered_box_offset(sz, scale, 1), pic, scale * sz, rgb, alpha * (1 - fadelerp), flag);
433 }
434
435 void drawpic_expanding_two(vector position, string pic, vector scale, vector rgb, float alpha, float flag, float fadelerp)
436 {
437         drawpic_expanding(position, pic, scale, rgb, alpha, flag, fadelerp);
438         drawpic(position, pic, scale, rgb, alpha * fadelerp, flag);
439 }
440
441 vector drawfontscale;
442 void drawstring_expanding(vector position, string text, vector scale, vector rgb, float alpha, float flag, float fadelerp)
443 {
444         float sz;
445         sz = expandingbox_sizefactor_from_fadelerp(fadelerp);
446
447         if(cvar("menu_font_size_snapping_fix"))
448                 drawfontscale = sz * '1 1 0';
449         else
450                 drawfontscale = '1 1 0';
451         dummyfunction(0, 0, 0, 0, 0, 0, 0, 0);
452         drawstring(position + expandingbox_resize_centered_box_offset(sz, scale, stringwidth(text, FALSE, scale * (sz / drawfontscale_x)) / (scale_x * sz)), text, scale * (sz / drawfontscale_x), rgb, alpha * (1 - fadelerp), flag);
453         // width parameter:
454         //    (scale_x * sz / drawfontscale_x) * drawfontscale_x * SIZE1 / (scale_x * sz)
455         //    SIZE1
456
457         if(cvar("menu_font_size_snapping_fix"))
458                 drawfontscale = '1 1 0';
459 }
460
461 void drawcolorcodedstring_expanding(vector position, string text, vector scale, float alpha, float flag, float fadelerp)
462 {
463         float sz;
464         sz = expandingbox_sizefactor_from_fadelerp(fadelerp);
465
466         if(cvar("menu_font_size_snapping_fix"))
467                 drawfontscale = sz * '1 1 0';
468         else
469                 drawfontscale = '1 1 0';
470         dummyfunction(0, 0, 0, 0, 0, 0, 0, 0);
471         drawcolorcodedstring(position + expandingbox_resize_centered_box_offset(sz, scale, stringwidth(text, TRUE, scale * (sz / drawfontscale_x)) / (scale_x * sz)), text, scale * (sz / drawfontscale_x), alpha * (1 - fadelerp), flag);
472
473         if(cvar("menu_font_size_snapping_fix"))
474                 drawfontscale = '1 1 0';
475 }
476
477 // this draws the triangles of a model DIRECTLY. Don't expect high performance, really...
478 void PolyDrawModel(entity e)
479 {
480         float i_s, i_t;
481         float n_t;
482         vector tri;
483         string tex;
484         for(i_s = 0; ; ++i_s)
485         {
486                 tex = getsurfacetexture(e, i_s);
487                 if not(tex)
488                         break; // this is beyond the last one
489                 n_t = getsurfacenumtriangles(e, i_s);
490                 for(i_t = 0; i_t < n_t; ++i_t)
491                 {
492                         tri = getsurfacetriangle(e, i_s, i_t);
493                         R_BeginPolygon(tex, 0);
494                         R_PolygonVertex(getsurfacepoint(e, i_s, tri_x), getsurfacepointattribute(e, i_s, tri_x, SPA_TEXCOORDS0), '1 1 1', 1);
495                         R_PolygonVertex(getsurfacepoint(e, i_s, tri_y), getsurfacepointattribute(e, i_s, tri_y, SPA_TEXCOORDS0), '1 1 1', 1);
496                         R_PolygonVertex(getsurfacepoint(e, i_s, tri_z), getsurfacepointattribute(e, i_s, tri_z, SPA_TEXCOORDS0), '1 1 1', 1);
497                         R_EndPolygon();
498                 }
499         }
500 }
501
502 .vector movedir; // for clipping planes
503 .vector texcoord;
504 entity winding_pool;
505 .entity aiment, enemy;
506 void Draw_CylindricLine(vector from, vector to, float thickness, string texture, float aspect, float shift, vector rgb, float alpha, float drawflag);
507 void PolyDrawModel_Point_Interpolate(entity w, entity dst, entity src0, entity src1, float lerp)
508 {
509         dst.texcoord = src0.texcoord * (1 - lerp) + src1.texcoord * lerp;
510 }
511 void PolyDrawCycles(entity edges, entity p, float epsilon, string tex, vector c, float a, float f)
512 {
513 }
514 #define EPSILON 0.5
515
516 vector PolyDraw_CutPlaneTransform(entity cutplanes, vector v)
517 {
518         return v + cutplanes.origin; // TODO rotation
519 }
520
521 void PolyDrawTriangle_Cut_Recursive(entity cutplanes, entity winding, string tex)
522 {
523         entity wf, wb, p, edge;
524         //           front               back
525         if(cutplanes.aiment || cutplanes.enemy) // this is a node
526         {
527                 ClipWindingEpsilon(winding, cutplanes.movedir, cutplanes.origin * cutplanes.movedir, EPSILON, (!(cutplanes.enemy)) - (!(cutplanes.aiment)));
528                 wf = clipwinding_front;
529                 wb = clipwinding_back;
530                 if(cutplanes.aiment)
531                 {
532                         cutplanes.aiment.sort_prev = cutplanes;
533                         if(wf.cnt >= 2)
534                                 PolyDrawTriangle_Cut_Recursive(cutplanes.aiment, wf, tex);
535                         if(wf != winding)
536                                 FreeWinding(wf);
537                 }
538                 if(cutplanes.enemy)
539                 {
540                         cutplanes.enemy.sort_prev = cutplanes;
541                         if(wb.cnt >= 2)
542                                 PolyDrawTriangle_Cut_Recursive(cutplanes.enemy, wb, tex);
543                         if(wb != winding)
544                                 FreeWinding(wb);
545                 }
546         }
547         else // this is a leaf
548         {
549                 if(winding.cnt >= 3)
550                 {
551                         R_BeginPolygon(tex, 0);
552                         for(p = winding.sort_next; p != winding; p = p.sort_next)
553                                 R_PolygonVertex(PolyDraw_CutPlaneTransform(cutplanes, p.origin), p.texcoord, '1 1 1', 1);
554                         R_EndPolygon();
555                 }
556                 for(p = winding.sort_next; p != winding; p = p.sort_next)
557                         if(p.winding_cutpoint)
558                         {
559                                 edge = Pool_spawn(winding_pool);
560                                 edge.classname = "winding";
561                                 edge.origin = p.origin;
562                                 edge.movedir = ((p.sort_next == winding) ? winding : p).sort_next.origin;
563                                 edge.sort_next = cutplanes.sort_next;
564                                 cutplanes.sort_next = edge;
565                         }
566         }
567 }
568 void PolyDrawTriangle_Cut_Finish_Recursive(entity cutplanes, string tex, vector c, float a, float f)
569 {
570         entity edge, en;
571         entity p, pp;
572         vector pc; float pcn;
573         vector o, t;
574         vector offset;
575         //           front               back
576         if(cutplanes.aiment || cutplanes.enemy) // this is a node
577         {
578                 if(cutplanes.aiment)
579                         PolyDrawTriangle_Cut_Finish_Recursive(cutplanes.aiment, tex, c, a, f);
580                 if(cutplanes.enemy)
581                         PolyDrawTriangle_Cut_Finish_Recursive(cutplanes.enemy, tex, c, a, f);
582         }
583         else // this is a leaf
584         {
585                 PolyDrawCycles(cutplanes.sort_next, cutplanes, EPSILON * 2, "", '1 0 0', 1, 0);
586
587                 for(pp = cutplanes; (p = pp.sort_prev); pp = p)
588                 {
589                         makevectors(p.angles);
590                         pc = '0 0 0';
591                         pcn = 0;
592                         offset = EPSILON * p.movedir;
593                         if(p.enemy == pp)
594                         {
595                                 offset = -1 * offset;
596                                 v_forward = -1 * v_forward;
597                                 v_right = -1 * v_right;
598                                 v_up = -1 * v_up;
599                         }
600                         for(edge = cutplanes.sort_next; edge; edge = edge.sort_next)
601                         {
602                                 if(fabs((edge.origin - p.origin) * p.movedir) <= EPSILON)
603                                 if(fabs((edge.movedir - p.origin) * p.movedir) <= EPSILON)
604                                 {
605                                         pcn += 2;
606                                         pc = pc + edge.origin + edge.movedir;
607                                 }
608                         }
609                         pc = pc * (1 / pcn);
610                         for(edge = cutplanes.sort_next; edge; edge = edge.sort_next)
611                         {
612                                 if(fabs((edge.origin - p.origin) * p.movedir) <= EPSILON)
613                                 if(fabs((edge.movedir - p.origin) * p.movedir) <= EPSILON)
614                                 {
615                                         R_BeginPolygon(tex, f);
616                                         o = edge.origin - offset;
617                                         t = '1 0 0' * (o * v_right) + '0 1 0' * (o * v_up);
618                                         R_PolygonVertex(PolyDraw_CutPlaneTransform(cutplanes, o), t, c, a);
619                                         o = edge.movedir - offset;
620                                         t = '1 0 0' * (o * v_right) + '0 1 0' * (o * v_up);
621                                         R_PolygonVertex(PolyDraw_CutPlaneTransform(cutplanes, o), t, c, a);
622                                         o = pc - offset;
623                                         t = '1 0 0' * (o * v_right) + '0 1 0' * (o * v_up);
624                                         R_PolygonVertex(PolyDraw_CutPlaneTransform(cutplanes, o), t, c, a);
625                                         R_EndPolygon();
626                                 }
627                         }
628                 }
629
630                 for(edge = cutplanes.sort_next; edge; )
631                 {
632                         en = edge.sort_next;
633                         Pool_remove(winding_pool, edge);
634                         edge = en;
635                 }
636                 cutplanes.sort_next = world;
637         }
638 }
639
640 void PolyDrawModel_Cut(entity e, entity cutplanes)
641 {
642         float i_s, i_t;
643         float n_t;
644         vector tri;
645         string tex;
646         entity w, p, edge, en;
647         entity edges;
648         w = AllocWinding(world);
649         w.winding_point_interpolate = PolyDrawModel_Point_Interpolate;
650
651         for(i_s = 0; ; ++i_s)
652         {
653                 tex = getsurfacetexture(e, i_s);
654                 if not(tex)
655                         break; // this is beyond the last one
656                 n_t = getsurfacenumtriangles(e, i_s);
657                 for(i_t = 0; i_t < n_t; ++i_t)
658                 {
659                         tri = getsurfacetriangle(e, i_s, i_t);
660                         ClearWinding(w); // do we NEED this?
661                         p = AllocPoint(w, getsurfacepoint(e, i_s, tri_x)); p.texcoord = getsurfacepointattribute(e, i_s, tri_x, SPA_TEXCOORDS0); InsertIntoWinding(w, p);
662                         p = AllocPoint(w, getsurfacepoint(e, i_s, tri_y)); p.texcoord = getsurfacepointattribute(e, i_s, tri_y, SPA_TEXCOORDS0); InsertIntoWinding(w, p);
663                         p = AllocPoint(w, getsurfacepoint(e, i_s, tri_z)); p.texcoord = getsurfacepointattribute(e, i_s, tri_z, SPA_TEXCOORDS0); InsertIntoWinding(w, p);
664                         PolyDrawTriangle_Cut_Recursive(cutplanes, w, tex);
665                         ClearWinding(w);
666                 }
667         }
668         PolyDrawTriangle_Cut_Finish_Recursive(cutplanes, "", '1 0 0', 1, 0);
669
670         FreeWinding(w);
671 }