rev |
line source |
nuclear@9
|
1 #include "config.h"
|
nuclear@14
|
2 #include <stdio.h>
|
nuclear@19
|
3 #include <stdlib.h>
|
nuclear@7
|
4 #include <string.h>
|
nuclear@12
|
5 #include <math.h>
|
nuclear@7
|
6 #include "x3d.h"
|
nuclear@7
|
7 #include "fixed.h"
|
nuclear@7
|
8 #include "sincos.h"
|
nuclear@8
|
9 #include "logger.h"
|
nuclear@8
|
10 #include "polyfill.h"
|
nuclear@8
|
11 #include "gbasys.h"
|
nuclear@17
|
12 #include "x3d_impl.h"
|
nuclear@7
|
13
|
nuclear@14
|
14 int dbg_fill_dump;
|
nuclear@14
|
15
|
nuclear@7
|
16 #define MAT_STACK_SIZE 4
|
nuclear@7
|
17
|
nuclear@7
|
18 struct matrix {
|
nuclear@8
|
19 int32_t m[12];
|
nuclear@7
|
20 };
|
nuclear@7
|
21
|
nuclear@15
|
22 static void proc_vertex(const int32_t *vin, const int32_t *cin, const int32_t *tin,
|
nuclear@15
|
23 pvec3 *vout, pvec3 *cout, pvec2 *tout);
|
nuclear@14
|
24 static int dump_frame(struct pixel_buffer *frame);
|
nuclear@8
|
25
|
nuclear@8
|
26
|
nuclear@7
|
27 static int32_t proj_fov = M_PI_X16;
|
nuclear@7
|
28 static int32_t proj_aspect = 65536;
|
nuclear@13
|
29 static int32_t inv_proj_aspect = 65536;
|
nuclear@7
|
30 static int32_t proj_near = ftox16(0.5);
|
nuclear@7
|
31 static int32_t proj_far = 500 << 16;
|
nuclear@13
|
32 static int32_t inv_tan_half_xfov, inv_tan_half_yfov;
|
nuclear@7
|
33
|
nuclear@8
|
34 #define ID_INIT {65536, 0, 0, 0, 0, 65536, 0, 0, 0, 0, 65536, 0}
|
nuclear@8
|
35
|
nuclear@8
|
36 static struct matrix identity = { ID_INIT };
|
nuclear@7
|
37
|
nuclear@7
|
38 static short mtop;
|
nuclear@8
|
39 static struct matrix mstack[MAT_STACK_SIZE] = { {ID_INIT}, {ID_INIT} };
|
nuclear@8
|
40
|
nuclear@8
|
41 static const int32_t *vertex_array;
|
nuclear@8
|
42 static unsigned short vertex_count;
|
nuclear@8
|
43 static const int32_t *color_array;
|
nuclear@8
|
44 static unsigned short color_count;
|
nuclear@15
|
45 static const int32_t *texcoord_array;
|
nuclear@15
|
46 static unsigned short texcoord_count;
|
nuclear@8
|
47
|
nuclear@8
|
48 static int32_t im_color[3];
|
nuclear@15
|
49 static int32_t im_texcoord[2];
|
nuclear@9
|
50 static uint8_t im_color_index;
|
nuclear@7
|
51
|
nuclear@17
|
52 #define MAX_TEXTURES 64
|
nuclear@17
|
53 static struct texture textures[MAX_TEXTURES];
|
nuclear@17
|
54 static short cur_tex = -1;
|
nuclear@17
|
55
|
nuclear@17
|
56
|
nuclear@12
|
57 void x3d_projection(int fov, int32_t aspect, int32_t nearz, int32_t farz)
|
nuclear@7
|
58 {
|
nuclear@12
|
59 proj_fov = (M_PI_X16 * fov) / 180;
|
nuclear@7
|
60 proj_aspect = aspect;
|
nuclear@13
|
61 inv_proj_aspect = x16div(65536, proj_aspect);
|
nuclear@7
|
62 proj_near = nearz;
|
nuclear@7
|
63 proj_far = farz;
|
nuclear@12
|
64
|
nuclear@13
|
65 inv_tan_half_yfov = (int32_t)(65536.0 / tan(0.5 * proj_fov / 65536.0));
|
nuclear@13
|
66 inv_tan_half_xfov = x16mul(inv_tan_half_yfov, aspect);
|
nuclear@7
|
67 }
|
nuclear@7
|
68
|
nuclear@7
|
69 int x3d_push_matrix(void)
|
nuclear@7
|
70 {
|
nuclear@7
|
71 short newtop = mtop + 1;
|
nuclear@7
|
72 if(newtop >= MAT_STACK_SIZE) {
|
nuclear@7
|
73 return -1;
|
nuclear@7
|
74 }
|
nuclear@7
|
75 memcpy(mstack + newtop, mstack + mtop, sizeof *mstack);
|
nuclear@7
|
76 mtop = newtop;
|
nuclear@7
|
77 return 0;
|
nuclear@7
|
78 }
|
nuclear@7
|
79
|
nuclear@7
|
80 int x3d_pop_matrix(void)
|
nuclear@7
|
81 {
|
nuclear@7
|
82 if(mtop <= 0) {
|
nuclear@7
|
83 return -1;
|
nuclear@7
|
84 }
|
nuclear@7
|
85 --mtop;
|
nuclear@7
|
86 return 0;
|
nuclear@7
|
87 }
|
nuclear@7
|
88
|
nuclear@7
|
89 void x3d_load_matrix(int32_t *m)
|
nuclear@7
|
90 {
|
nuclear@8
|
91 memcpy(mstack[mtop].m, m, sizeof *mstack);
|
nuclear@7
|
92 }
|
nuclear@7
|
93
|
nuclear@7
|
94
|
nuclear@7
|
95 #define M(i,j) (((i) << 2) + (j))
|
nuclear@7
|
96 void x3d_mult_matrix(int32_t *m)
|
nuclear@7
|
97 {
|
nuclear@7
|
98 int i, j;
|
nuclear@7
|
99 struct matrix tmp;
|
nuclear@7
|
100
|
nuclear@8
|
101 memcpy(tmp.m, mstack[mtop].m, sizeof tmp);
|
nuclear@7
|
102
|
nuclear@7
|
103 for(i=0; i<3; i++) {
|
nuclear@7
|
104 for(j=0; j<4; j++) {
|
nuclear@8
|
105 mstack[mtop].m[M(i, j)] =
|
nuclear@14
|
106 x16mul(m[M(0, j)], tmp.m[M(i, 0)]) +
|
nuclear@14
|
107 x16mul(m[M(1, j)], tmp.m[M(i, 1)]) +
|
nuclear@14
|
108 x16mul(m[M(2, j)], tmp.m[M(i, 2)]);
|
nuclear@7
|
109 }
|
nuclear@14
|
110 mstack[mtop].m[M(i, 3)] += tmp.m[M(i, 3)];
|
nuclear@7
|
111 }
|
nuclear@7
|
112 }
|
nuclear@7
|
113
|
nuclear@7
|
114 void x3d_load_identity(void)
|
nuclear@7
|
115 {
|
nuclear@8
|
116 memcpy(mstack[mtop].m, identity.m, sizeof identity);
|
nuclear@7
|
117 }
|
nuclear@7
|
118
|
nuclear@8
|
119 void x3d_translate(int32_t x, int32_t y, int32_t z)
|
nuclear@8
|
120 {
|
nuclear@8
|
121 int32_t m[] = ID_INIT;
|
nuclear@8
|
122 m[3] = x;
|
nuclear@8
|
123 m[7] = y;
|
nuclear@8
|
124 m[11] = z;
|
nuclear@8
|
125
|
nuclear@8
|
126 x3d_mult_matrix(m);
|
nuclear@8
|
127 }
|
nuclear@8
|
128
|
nuclear@8
|
129 void x3d_rotate(int32_t deg, int32_t x, int32_t y, int32_t z)
|
nuclear@8
|
130 {
|
nuclear@8
|
131 int32_t xform[] = ID_INIT;
|
nuclear@8
|
132
|
nuclear@8
|
133 int32_t angle = x16mul(M_PI_X16, deg) / 180;
|
nuclear@8
|
134 int32_t sina = sin_x16(angle);
|
nuclear@8
|
135 int32_t cosa = cos_x16(angle);
|
nuclear@8
|
136 int32_t one_minus_cosa = 65536 - cosa;
|
nuclear@8
|
137 int32_t nxsq = x16sq(x);
|
nuclear@8
|
138 int32_t nysq = x16sq(y);
|
nuclear@8
|
139 int32_t nzsq = x16sq(z);
|
nuclear@8
|
140
|
nuclear@8
|
141 xform[0] = nxsq + x16mul(65536 - nxsq, cosa);
|
nuclear@8
|
142 xform[4] = x16mul(x16mul(x, y), one_minus_cosa) - x16mul(z, sina);
|
nuclear@8
|
143 xform[8] = x16mul(x16mul(x, z), one_minus_cosa) + x16mul(y, sina);
|
nuclear@8
|
144 xform[1] = x16mul(x16mul(x, y), one_minus_cosa) + x16mul(z, sina);
|
nuclear@8
|
145 xform[5] = nysq + x16mul(65536 - nysq, cosa);
|
nuclear@8
|
146 xform[9] = x16mul(x16mul(y, z), one_minus_cosa) - x16mul(x, sina);
|
nuclear@8
|
147 xform[2] = x16mul(x16mul(x, z), one_minus_cosa) - x16mul(y, sina);
|
nuclear@8
|
148 xform[6] = x16mul(x16mul(y, z), one_minus_cosa) + x16mul(x, sina);
|
nuclear@8
|
149 xform[10] = nzsq + x16mul(65536 - nzsq, cosa);
|
nuclear@8
|
150
|
nuclear@8
|
151 x3d_mult_matrix(xform);
|
nuclear@8
|
152 }
|
nuclear@8
|
153
|
nuclear@8
|
154 void x3d_scale(int32_t x, int32_t y, int32_t z)
|
nuclear@8
|
155 {
|
nuclear@8
|
156 int32_t m[] = ID_INIT;
|
nuclear@8
|
157
|
nuclear@8
|
158 m[0] = x;
|
nuclear@8
|
159 m[5] = y;
|
nuclear@8
|
160 m[10] = z;
|
nuclear@8
|
161
|
nuclear@8
|
162 x3d_mult_matrix(m);
|
nuclear@8
|
163 }
|
nuclear@8
|
164
|
nuclear@8
|
165 void x3d_vertex_array(int count, const int32_t *ptr)
|
nuclear@8
|
166 {
|
nuclear@8
|
167 vertex_array = ptr;
|
nuclear@8
|
168 vertex_count = count;
|
nuclear@8
|
169 }
|
nuclear@8
|
170
|
nuclear@8
|
171 void x3d_color_array(int count, const int32_t *ptr)
|
nuclear@8
|
172 {
|
nuclear@8
|
173 color_array = ptr;
|
nuclear@8
|
174 color_count = count;
|
nuclear@8
|
175 }
|
nuclear@8
|
176
|
nuclear@15
|
177 void x3d_texcoord_array(int count, const int32_t *ptr)
|
nuclear@15
|
178 {
|
nuclear@15
|
179 texcoord_array = ptr;
|
nuclear@15
|
180 texcoord_count = count;
|
nuclear@15
|
181 }
|
nuclear@15
|
182
|
nuclear@12
|
183 int x3d_draw(int prim, int vnum)
|
nuclear@8
|
184 {
|
nuclear@8
|
185 int i, j, pverts = prim;
|
nuclear@8
|
186 const int32_t *vptr = vertex_array;
|
nuclear@8
|
187 const int32_t *cptr = color_array;
|
nuclear@15
|
188 const int32_t *tptr = texcoord_array;
|
nuclear@9
|
189 #ifndef PALMODE
|
nuclear@8
|
190 short cr, cg, cb;
|
nuclear@9
|
191 #endif
|
nuclear@9
|
192 uint16_t color;
|
nuclear@8
|
193
|
nuclear@8
|
194 if(!vertex_array) return -1;
|
nuclear@8
|
195
|
nuclear@8
|
196 if(vnum > vertex_count) {
|
nuclear@8
|
197 logmsg(LOG_DBG, "%s called with vnum=%d, but current vertex array has %d vertices\n",
|
nuclear@8
|
198 __FUNCTION__, vnum, vertex_count);
|
nuclear@8
|
199 vnum = vertex_count;
|
nuclear@8
|
200 }
|
nuclear@8
|
201 if(color_array && vnum > color_count) {
|
nuclear@8
|
202 logmsg(LOG_DBG, "%s called with vnum=%d, but current color array has %d elements\n",
|
nuclear@8
|
203 __FUNCTION__, vnum, color_count);
|
nuclear@8
|
204 vnum = color_count;
|
nuclear@8
|
205 }
|
nuclear@15
|
206 if(texcoord_array && vnum > texcoord_count) {
|
nuclear@15
|
207 logmsg(LOG_DBG, "%s called with vnum=%d, but current texcoord array has %d elements\n",
|
nuclear@15
|
208 __FUNCTION__, vnum, texcoord_count);
|
nuclear@15
|
209 vnum = texcoord_count;
|
nuclear@15
|
210 }
|
nuclear@8
|
211
|
nuclear@8
|
212 for(i=0; i<vnum; i+=pverts) {
|
nuclear@8
|
213 /* process vertices */
|
nuclear@8
|
214 pvec3 vpos[4];
|
nuclear@8
|
215 pvec3 col[4];
|
nuclear@15
|
216 pvec2 tex[4];
|
nuclear@8
|
217
|
nuclear@8
|
218 for(j=0; j<pverts; j++) {
|
nuclear@15
|
219 proc_vertex(vptr, cptr, tptr, vpos + j, col + j, tex + j);
|
nuclear@12
|
220
|
nuclear@12
|
221 if(vpos[j].z <= proj_near) {
|
nuclear@12
|
222 goto skip_prim;
|
nuclear@12
|
223 }
|
nuclear@12
|
224
|
nuclear@8
|
225 vptr += 3;
|
nuclear@8
|
226 if(cptr) cptr += 3;
|
nuclear@15
|
227 if(tptr) tptr += 2;
|
nuclear@8
|
228 }
|
nuclear@8
|
229
|
nuclear@9
|
230 #ifdef PALMODE
|
nuclear@9
|
231 color = im_color_index;
|
nuclear@9
|
232 #else
|
nuclear@8
|
233 cr = col[0].x >> 8;
|
nuclear@8
|
234 cg = col[0].y >> 8;
|
nuclear@8
|
235 cb = col[0].z >> 8;
|
nuclear@8
|
236
|
nuclear@8
|
237 if(cr > 255) cr = 255;
|
nuclear@8
|
238 if(cg > 255) cg = 255;
|
nuclear@8
|
239 if(cb > 255) cb = 255;
|
nuclear@8
|
240
|
nuclear@9
|
241 color = RGB(cr, cg, cb);
|
nuclear@9
|
242 #endif
|
nuclear@9
|
243
|
nuclear@12
|
244 /* project & viewport */
|
nuclear@12
|
245 for(j=0; j<pverts; j++) {
|
nuclear@12
|
246 int32_t x, y;
|
nuclear@12
|
247
|
nuclear@13
|
248 x = x16mul(vpos[j].x, inv_tan_half_xfov);
|
nuclear@12
|
249 x = x16div(x, vpos[j].z);
|
nuclear@13
|
250 vpos[j].x = (x16mul(x, inv_proj_aspect) + 65536) * (WIDTH / 2);
|
nuclear@12
|
251
|
nuclear@13
|
252 y = x16mul(vpos[j].y, inv_tan_half_yfov);
|
nuclear@12
|
253 y = x16div(y, vpos[j].z);
|
nuclear@12
|
254 vpos[j].y = (65536 - y) * (HEIGHT / 2);
|
nuclear@12
|
255 }
|
nuclear@12
|
256
|
nuclear@8
|
257 switch(pverts) {
|
nuclear@8
|
258 case X3D_POINTS:
|
nuclear@9
|
259 draw_point(vpos, color);
|
nuclear@8
|
260 break;
|
nuclear@8
|
261
|
nuclear@8
|
262 case X3D_LINES:
|
nuclear@8
|
263 break;
|
nuclear@8
|
264
|
nuclear@8
|
265 case X3D_TRIANGLES:
|
nuclear@8
|
266 case X3D_QUADS:
|
nuclear@17
|
267 draw_poly(pverts, vpos, tex, color, cur_tex >= 0 ? textures + cur_tex : 0);
|
nuclear@14
|
268 if(dbg_fill_dump) {
|
nuclear@14
|
269 dump_frame(back_buffer);
|
nuclear@14
|
270 }
|
nuclear@8
|
271 break;
|
nuclear@8
|
272 }
|
nuclear@12
|
273 skip_prim: ;
|
nuclear@8
|
274 }
|
nuclear@14
|
275
|
nuclear@14
|
276 dbg_fill_dump = 0;
|
nuclear@8
|
277 return 0;
|
nuclear@8
|
278 }
|
nuclear@8
|
279
|
nuclear@15
|
280 static void proc_vertex(const int32_t *vin, const int32_t *cin, const int32_t *tin,
|
nuclear@15
|
281 pvec3 *vout, pvec3 *cout, pvec2 *tout)
|
nuclear@8
|
282 {
|
nuclear@8
|
283 int i;
|
nuclear@8
|
284 int32_t tvert[3];
|
nuclear@8
|
285 int32_t *mvmat = mstack[mtop].m;
|
nuclear@8
|
286
|
nuclear@8
|
287 /* transform vertex with current matrix */
|
nuclear@8
|
288 for(i=0; i<3; i++) {
|
nuclear@8
|
289 tvert[i] = x16mul(mvmat[0], vin[0]) +
|
nuclear@8
|
290 x16mul(mvmat[1], vin[1]) +
|
nuclear@8
|
291 x16mul(mvmat[2], vin[2]) +
|
nuclear@8
|
292 mvmat[3];
|
nuclear@8
|
293 mvmat += 4;
|
nuclear@8
|
294 }
|
nuclear@8
|
295
|
nuclear@8
|
296 vout->x = tvert[0];
|
nuclear@8
|
297 vout->y = tvert[1];
|
nuclear@8
|
298 vout->z = tvert[2];
|
nuclear@8
|
299 /*logmsg(LOG_DBG, "%s: (%g %g %g) -> (%g %g %g)\n", __FUNCTION__,
|
nuclear@8
|
300 x16tof(vin[0]), x16tof(vin[1]), x16tof(vin[2]),
|
nuclear@8
|
301 x16tof(vout->x), x16tof(vout->y), x16tof(vout->z));*/
|
nuclear@8
|
302
|
nuclear@8
|
303 if(color_array) {
|
nuclear@8
|
304 cout->x = cin[0];
|
nuclear@8
|
305 cout->y = cin[1];
|
nuclear@8
|
306 cout->z = cin[2];
|
nuclear@8
|
307 } else {
|
nuclear@8
|
308 cout->x = im_color[0];
|
nuclear@8
|
309 cout->y = im_color[1];
|
nuclear@8
|
310 cout->z = im_color[2];
|
nuclear@8
|
311 }
|
nuclear@15
|
312
|
nuclear@15
|
313 if(texcoord_array) {
|
nuclear@15
|
314 tout->x = tin[0];
|
nuclear@15
|
315 tout->y = tin[1];
|
nuclear@15
|
316 } else {
|
nuclear@15
|
317 tout->x = im_texcoord[0];
|
nuclear@15
|
318 tout->y = im_texcoord[1];
|
nuclear@15
|
319 }
|
nuclear@8
|
320 }
|
nuclear@8
|
321
|
nuclear@9
|
322 void x3d_color_index(int cidx)
|
nuclear@9
|
323 {
|
nuclear@9
|
324 im_color_index = cidx;
|
nuclear@9
|
325 }
|
nuclear@9
|
326
|
nuclear@8
|
327 void x3d_color(int32_t r, int32_t g, int32_t b)
|
nuclear@8
|
328 {
|
nuclear@8
|
329 im_color[0] = r;
|
nuclear@8
|
330 im_color[1] = g;
|
nuclear@8
|
331 im_color[2] = b;
|
nuclear@8
|
332 }
|
nuclear@14
|
333
|
nuclear@17
|
334 static int count_bits(int x)
|
nuclear@17
|
335 {
|
nuclear@17
|
336 int i, count = 0;
|
nuclear@17
|
337 for(i=0; i<32; i++) {
|
nuclear@17
|
338 if(x & 1) count++;
|
nuclear@17
|
339 x >>= 1;
|
nuclear@17
|
340 }
|
nuclear@17
|
341 return count;
|
nuclear@17
|
342 }
|
nuclear@17
|
343
|
nuclear@17
|
344 int x3d_create_texture_rgb(int xsz, int ysz, const uint16_t *pixels)
|
nuclear@17
|
345 {
|
nuclear@17
|
346 int i, j;
|
nuclear@17
|
347
|
nuclear@17
|
348 if(xsz == 0 || count_bits(xsz) > 1 || ysz == 0 || count_bits(ysz) > 1) {
|
nuclear@17
|
349 logmsg(LOG_DBG, "%s: texture size (%dx%d) not power of two!\n", __func__, xsz, ysz);
|
nuclear@17
|
350 return -1;
|
nuclear@17
|
351 }
|
nuclear@17
|
352
|
nuclear@17
|
353 for(i=0; i<MAX_TEXTURES; i++) {
|
nuclear@17
|
354 if(!textures[i].pixels) {
|
nuclear@19
|
355 /*textures[i].pixels = pixels;*/
|
nuclear@19
|
356 textures[i].pixels = malloc(xsz * ysz * 2);
|
nuclear@19
|
357 memcpy(textures[i].pixels, pixels, xsz * ysz * 2);
|
nuclear@17
|
358 textures[i].xsz = xsz;
|
nuclear@17
|
359 textures[i].ysz = ysz;
|
nuclear@17
|
360 textures[i].umask = xsz - 1;
|
nuclear@17
|
361 textures[i].vmask = ysz - 1;
|
nuclear@17
|
362
|
nuclear@17
|
363 for(j=0; j<32; j++) {
|
nuclear@17
|
364 if((1 << j) == xsz) {
|
nuclear@17
|
365 textures[i].ushift = j;
|
nuclear@17
|
366 }
|
nuclear@17
|
367 if((1 << j) == ysz) {
|
nuclear@17
|
368 textures[i].vshift = j;
|
nuclear@17
|
369 }
|
nuclear@17
|
370 }
|
nuclear@17
|
371
|
nuclear@19
|
372 logmsg(LOG_DBG, "create texture %dx%d: %p\n", xsz, ysz, pixels);
|
nuclear@19
|
373
|
nuclear@17
|
374 return i;
|
nuclear@17
|
375 }
|
nuclear@17
|
376 }
|
nuclear@17
|
377 return -1;
|
nuclear@17
|
378 }
|
nuclear@17
|
379
|
nuclear@17
|
380 void x3d_enable_texture(int texid)
|
nuclear@17
|
381 {
|
nuclear@17
|
382 cur_tex = texid;
|
nuclear@17
|
383 }
|
nuclear@17
|
384
|
nuclear@17
|
385 void x3d_disable_texture(void)
|
nuclear@17
|
386 {
|
nuclear@17
|
387 cur_tex = 0;
|
nuclear@17
|
388 }
|
nuclear@17
|
389
|
nuclear@17
|
390 int x3d_get_active_texture(void)
|
nuclear@17
|
391 {
|
nuclear@17
|
392 return cur_tex;
|
nuclear@17
|
393 }
|
nuclear@17
|
394
|
nuclear@14
|
395 static int dump_frame(struct pixel_buffer *frame)
|
nuclear@14
|
396 {
|
nuclear@14
|
397 static int frameno;
|
nuclear@14
|
398 char buf[128];
|
nuclear@14
|
399 FILE *fp;
|
nuclear@14
|
400 int i, npix;
|
nuclear@14
|
401 uint16_t *ptr = frame->pixels;
|
nuclear@14
|
402
|
nuclear@14
|
403 sprintf(buf, "dump%03d.ppm", ++frameno);
|
nuclear@14
|
404
|
nuclear@14
|
405 if(!(fp = fopen(buf, "wb"))) {
|
nuclear@14
|
406 fprintf(stderr, "failed to dump file: %s\n", buf);
|
nuclear@14
|
407 return -1;
|
nuclear@14
|
408 }
|
nuclear@14
|
409
|
nuclear@14
|
410 fprintf(fp, "P6\n%d %d\n255\n", frame->x, frame->y);
|
nuclear@14
|
411
|
nuclear@14
|
412 npix = frame->x * frame->y;
|
nuclear@14
|
413 for(i=0; i<npix; i++) {
|
nuclear@14
|
414 uint16_t pixel = *ptr++;
|
nuclear@14
|
415 fputc(GET_R(pixel), fp);
|
nuclear@14
|
416 fputc(GET_G(pixel), fp);
|
nuclear@14
|
417 fputc(GET_B(pixel), fp);
|
nuclear@14
|
418 }
|
nuclear@14
|
419 fclose(fp);
|
nuclear@14
|
420 return 0;
|
nuclear@14
|
421 }
|