Starshatter_Open
Open source Starshatter engine
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
ModelFileMAG.cpp
Go to the documentation of this file.
1 /* Project Magic 2.0
2  Destroyer Studios LLC
3  Copyright © 1997-2004. All Rights Reserved.
4 
5  SUBSYSTEM: Magic.exe
6  FILE: ModelFileMAG.cpp
7  AUTHOR: John DiCamillo
8 
9 
10  OVERVIEW
11  ========
12  File loader for MAG format models
13 */
14 
15 #include "stdafx.h"
16 #include "Magic.h"
17 #include "MagicDoc.h"
18 #include "ModelFileMAG.h"
19 
20 #include "Bitmap.h"
21 #include "Polygon.h"
22 #include "List.h"
23 
24 // +--------------------------------------------------------------------+
25 
26 struct MaterialMag6 {
29  float power; // highlight sharpness (big=shiny)
30  float brilliance; // diffuse power function
31  float bump; // bump level (0=none)
32  DWORD blend; // alpha blend type
33  bool shadow; // material casts shadow
34  bool luminous; // verts have their own lighting
35 
40 
45 
50 };
51 
52 // +--------------------------------------------------------------------+
53 
54 ModelFileMAG::ModelFileMAG(const char* fname)
55  : ModelFile(fname)
56 {
57 }
58 
60 {
61 }
62 
63 // +--------------------------------------------------------------------+
64 
65 bool
66 ModelFileMAG::Load(Model* m, double scale)
67 {
68  if (m && scale > 0 && strlen(filename) > 0) {
69  ModelFile::Load(m, scale);
70 
71  bool result = false;
72  FILE* fp = fopen(filename, "rb");
73 
74  // check MAG file:
75  if (!fp) {
76  ::MessageBox(0, "File Open Failed:\nMagic could not open the requested file.", "ERROR", MB_OK);
77  return result;
78  }
79 
80  ZeroMemory(pname, 64);
81  strncpy(pname, filename, 63);
82 
83  char file_id[5];
84  fread(file_id, 4, 1, fp);
85  file_id[4] = '\0';
86  int version = 1;
87 
88  if (!strcmp(file_id, "MAG6")) {
89  version = 6;
90  }
91  else if (!strcmp(file_id, "MAG5")) {
92  version = 5;
93  }
94  else if (!strcmp(file_id, "MAG4")) {
95  version = 4;
96  }
97  else {
98  ::MessageBox(0, "File Open Failed:\nThe requested file uses an invalid format.", "ERROR", MB_OK);
99  fclose(fp);
100  return result;
101  }
102 
103  // get ready to load, delete existing model:
104  m->GetSurfaces().destroy();
105  m->GetMaterials().destroy();
106  *pnverts = 0;
107  *pnpolys = 0;
108 
109  // now load the model:
110  switch (version) {
111  case 4:
112  case 5:
113  result = LoadMag5(fp, m, scale);
114  break;
115 
116  case 6:
117  result = LoadMag6(fp, m, scale);
118  break;
119 
120  default:
121  break;
122  }
123 
124  fclose(fp);
125  return true;
126  }
127 
128  return false;
129 }
130 
131 // +--------------------------------------------------------------------+
132 
133 bool
135 {
136  if (m) {
137  ModelFile::Save(m);
138 
139  FILE* fp = fopen(filename, "wb");
140  if (!fp) {
141  ::MessageBox(0, "Save Failed:\nMagic could not open the file for writing.", "ERROR", MB_OK);
142  return FALSE;
143  }
144 
145  fwrite("MAG6", 4, 1, fp);
146 
147  int i = 0;
148  int ntex = 0;
149  int nmtls = 0;
150  int nsurfs = m->NumSurfaces();
151  List<Bitmap> textures;
152 
153  ListIter<Material> m_iter = m->GetMaterials();
154  while (++m_iter) {
155  Material* mtl = m_iter.value();
156  Bitmap* bmp = mtl->tex_diffuse;
157 
158  if (bmp && !textures.contains(bmp)) {
159  textures.append(bmp);
160  }
161 
162  bmp = mtl->tex_specular;
163 
164  if (bmp && !textures.contains(bmp)) {
165  textures.append(bmp);
166  }
167 
168  bmp = mtl->tex_emissive;
169 
170  if (bmp && !textures.contains(bmp)) {
171  textures.append(bmp);
172  }
173 
174  bmp = mtl->tex_bumpmap;
175 
176  if (bmp && !textures.contains(bmp)) {
177  textures.append(bmp);
178  }
179 
180  nmtls++;
181  }
182 
183  ListIter<Bitmap> t_iter = textures;
184  while (++t_iter) {
185  Bitmap* bmp = t_iter.value();
186  ntex += strlen(bmp->GetFilename()) + 1;
187  }
188 
189  nsurfs = m->GetSurfaces().size();
190 
191  fwrite(&ntex, 4, 1, fp);
192  fwrite(&nmtls, 4, 1, fp);
193  fwrite(&nsurfs, 4, 1, fp);
194 
195  if (ntex) {
196  t_iter.reset();
197  while (++t_iter) {
198  Bitmap* bmp = t_iter.value();
199 
200  fwrite(bmp->GetFilename(),
201  strlen(bmp->GetFilename()) + 1,
202  1,
203  fp);
204  }
205  }
206 
207  if (nmtls) {
208  m_iter.reset();
209  while (++m_iter) {
210  Material* mtl = m_iter.value();
211  MaterialMag6 m6;
212 
213  ZeroMemory(&m6, sizeof(m6));
214 
215  CopyMemory(m6.name, mtl->name, Material::NAMELEN);
216  CopyMemory(m6.shader, mtl->shader, Material::NAMELEN);
217 
218  m6.ambient_value = mtl->ambient_value;
219  m6.ambient_color = mtl->ambient_color;
220  m6.diffuse_value = mtl->diffuse_value;
221  m6.diffuse_color = mtl->diffuse_color;
222  m6.specular_value = mtl->specular_value;
223  m6.specular_color = mtl->specular_color;
224  m6.emissive_value = mtl->emissive_value;
225  m6.emissive_color = mtl->emissive_color;
226 
227  m6.power = mtl->power;
228  m6.brilliance = mtl->brilliance;
229  m6.bump = mtl->bump;
230  m6.blend = mtl->blend;
231  m6.shadow = mtl->shadow;
232  m6.luminous = mtl->luminous;
233 
234  if (mtl->tex_diffuse)
235  m6.tex_diffuse = textures.index(mtl->tex_diffuse) + 1;
236 
237  if (mtl->tex_specular)
238  m6.tex_specular = textures.index(mtl->tex_specular) + 1;
239 
240  if (mtl->tex_emissive)
241  m6.tex_emissive = textures.index(mtl->tex_emissive) + 1;
242 
243  if (mtl->tex_bumpmap)
244  m6.tex_bumpmap = textures.index(mtl->tex_bumpmap) + 1;
245 
246  fwrite(&m6, sizeof(m6), 1, fp);
247  }
248  }
249 
250  ListIter<Surface> s_iter = m->GetSurfaces();
251  while (++s_iter) {
252  Surface* s = s_iter.value();
253 
254  int nverts = s->NumVerts();
255  int npolys = s->NumPolys();
256  BYTE namelen = strlen(s->Name()) + 1;
257 
258  fwrite(&nverts, 4, 1, fp);
259  fwrite(&npolys, 4, 1, fp);
260  fwrite(&namelen, 1, 1, fp);
261  fwrite(s->Name(), 1, namelen, fp);
262 
263  VertexSet* vset = s->GetVertexSet();
264  Poly* polys = s->GetPolys();
265 
266  // write vertex set:
267  for (int v = 0; v < nverts; v++) {
268  fwrite(&vset->loc[v], sizeof(float), 3, fp);
269  fwrite(&vset->nrm[v], sizeof(float), 3, fp);
270  fwrite(&vset->tu[v], sizeof(float), 1, fp);
271  fwrite(&vset->tv[v], sizeof(float), 1, fp);
272  }
273 
274  // write polys:
275  for (int n = 0; n < npolys; n++) {
276  Poly& poly = polys[n];
277  BYTE poly_nverts = (BYTE) poly.nverts;
278  BYTE material_index = 0;
279  WORD poly_verts[8];
280 
281  m_iter.reset();
282  while (++m_iter && !material_index) {
283  if (poly.material == m_iter.value())
284  material_index = m_iter.index() + 1;
285  }
286 
287  for (int i = 0; i < poly_nverts; i++) {
288  poly_verts[i] = poly.verts[i];
289  }
290 
291  fwrite(&poly_nverts, sizeof(BYTE), 1, fp);
292  fwrite(&material_index, sizeof(BYTE), 1, fp);
293  fwrite(&poly_verts[0], sizeof(WORD), poly_nverts, fp);
294  }
295  }
296 
297  return true;
298  }
299 
300  return false;
301 }
302 
303 // +--------------------------------------------------------------------+
304 
306 {
307  double distance;
308  double normal_x;
309  double normal_y;
310  double normal_z;
311  double normal_w;
312 };
313 
314 static void LoadPlane(Plane& p, FILE* fp)
315 {
316  HomogenousPlane tmp;
317  fread(&tmp, sizeof(HomogenousPlane), 1, fp);
318 }
319 
320 static void LoadFlags(LPDWORD flags, FILE* fp)
321 {
322  DWORD magic_flags;
323  fread(&magic_flags, sizeof(DWORD), 1, fp);
324 
336  const DWORD magic_mask = 0x0fc3;
337 
338  *flags = magic_flags & magic_mask;
339 }
340 
341 // +--------------------------------------------------------------------+
342 
343 static int mcomp(const void* a, const void* b)
344 {
345  Poly* pa = (Poly*) a;
346  Poly* pb = (Poly*) b;
347 
348  if (pa->sortval == pb->sortval)
349  return 0;
350 
351  if (pa->sortval < pb->sortval)
352  return -1;
353 
354  return 1;
355 }
356 
357 bool
358 ModelFileMAG::LoadMag5(FILE* fp, Model* m, double scale)
359 {
360  bool result = false;
361  int ntex = 0;
362  int nsurfs = 0;
363  double radius = 0;
364 
365  fread(&ntex, sizeof(ntex), 1, fp);
366  fread(&nsurfs, sizeof(nsurfs), 1, fp);
367 
368  // create a default gray material:
369  Material* mtl = new Material;
370 
371  if (mtl) {
372  mtl->Ka = Color::DarkGray;
373  mtl->Kd = Color::LightGray;
374  mtl->Ks = ColorValue(0.1f,0.1f,0.1f);
375  mtl->power = 10.0f;
376 
377  mtl->ambient_value = 0.2f;
379  mtl->diffuse_value = 0.8f;
381  mtl->specular_value = 0.5f;
383  strcpy_s(mtl->name, "(default)");
384 
385  m->GetMaterials().append(mtl);
386  }
387 
388  // read texture list:
389  for (int i = 0; i < ntex; i++) {
390  Material* mtl = new Material;
391  char tname[32];
392 
393  if (mtl) {
394  mtl->Ka = ColorValue(0.5f,0.5f,0.5f);
395  mtl->Kd = ColorValue(1.0f,1.0f,1.0f);
396  mtl->Ks = ColorValue(0.2f,0.2f,0.2f);
397  mtl->power = 20.0f;
398 
399  mtl->ambient_value = 1.0f;
400  mtl->ambient_color = Color::Gray;
401  mtl->diffuse_value = 1.0f;
403  mtl->specular_value = 0.2f;
405 
406  fread(tname, 32, 1, fp);
408  strcpy_s(mtl->name, tname);
409 
410  char* dot = strrchr(mtl->name, '.');
411  if (dot)
412  *dot = 0;
413 
414  char* plus = strrchr(mtl->name, '+');
415  if (plus)
416  *plus = 0;
417 
418  m->GetMaterials().append(mtl);
419  }
420  }
421 
422  int nverts = 0;
423  int npolys = 0;
424 
425  fread(&nverts, 4, 1, fp);
426  fread(&npolys, 4, 1, fp);
427 
428  // plan on creating four verts per poly:
429  int mag_nverts = nverts;
430  int next_vert = nverts;
431 
432  nverts = npolys * 4;
433  Surface* s = new Surface;
434  VertexSet* vset = 0;
435  Poly* polys = 0;
436 
437  if (s) {
438  s->SetName("default");
439  s->CreateVerts(nverts);
440  s->CreatePolys(npolys);
441 
442  vset = s->GetVertexSet();
443  polys = s->GetPolys();
444 
445  ZeroMemory(vset->loc, nverts * sizeof(Vec3));
446  ZeroMemory(vset->diffuse, nverts * sizeof(DWORD));
447  ZeroMemory(vset->specular, nverts * sizeof(DWORD));
448  ZeroMemory(vset->tu, nverts * sizeof(float));
449  ZeroMemory(vset->tv, nverts * sizeof(float));
450  ZeroMemory(vset->rw, nverts * sizeof(float));
451 
452  // read vertex set:
453  int v;
454  for (v = 0; v < mag_nverts; v++) {
455  Vec3 vert, norm;
456  DWORD vstate;
457 
458  fread(&vert, sizeof(Vec3), 1, fp);
459  fread(&norm, sizeof(Vec3), 1, fp);
460  fread(&vstate, sizeof(DWORD), 1, fp);
461 
462  vert.SwapYZ();
463  vert *= (float) scale;
464 
465  norm.SwapYZ();
466 
467  vset->loc[v] = vert;
468  vset->nrm[v] = norm;
469 
470  double d = vert.length();
471  if (d > radius)
472  radius = (float) d;
473  }
474 
475  while (v < nverts)
476  vset->nrm[v++] = Vec3(1,0,0);
477 
478  // read polys:
479  Vec3 dummy_center;
480  DWORD dummy_flags;
481  DWORD dummy_color;
482  int texture_num;
483  int poly_nverts;
484  int vert_index_buffer[32];
485  float texture_index_buffer[32];
486 
487  for (int n = 0; n < npolys; n++) {
488  Poly& poly = polys[n];
489  poly.vertex_set = vset;
490 
491  fread(&dummy_flags, sizeof(DWORD), 1, fp);
492  fread(&dummy_center, sizeof(Vec3), 1, fp);
493  LoadPlane(poly.plane, fp);
494  fread(&dummy_color, sizeof(DWORD), 1, fp);
495  fread(&texture_num, sizeof(int), 1, fp);
496 
497  if (texture_num >= 0 && texture_num < ntex) {
498  texture_num++;
499 
500  poly.material = m->GetMaterials()[texture_num];
501  poly.sortval = texture_num;
502 
503  if (dummy_flags & 2) { // luminous
504  Material* mtl = m->GetMaterials()[texture_num];
505 
506  mtl->ambient_value = 0.0f;
508  mtl->diffuse_value = 0.0f;
510  mtl->specular_value = 0.0f;
512  mtl->emissive_value = 1.0f;
514 
515  mtl->Ka = ColorValue(0,0,0,0);
516  mtl->Kd = ColorValue(0,0,0,0);
517  mtl->Ks = ColorValue(0,0,0,0);
518  mtl->Ke = ColorValue(1,1,1,1);
519 
520  mtl->tex_emissive = mtl->tex_diffuse;
521  }
522  }
523  else {
524  poly.material = m->GetMaterials().first(); // default material
525  poly.sortval = 1000;
526  }
527 
528  // hack: store flat shaded flag in unused visible byte
529  poly.visible = (BYTE) (dummy_flags & 1);
530 
531  fread(&poly_nverts, sizeof(int), 1, fp);
532  fread(vert_index_buffer, sizeof(int), poly_nverts, fp);
533 
534  if (poly_nverts == 3)
535  s->AddIndices(3);
536 
537  else if (poly_nverts == 4)
538  s->AddIndices(6);
539 
540  poly.nverts = poly_nverts;
541  for (int vi = 0; vi < poly_nverts; vi++) {
542  int v = vert_index_buffer[vi];
543 
544  if (vset->rw[v] > 0) {
545  vset->CopyVertex(next_vert, v);
546  v = next_vert++;
547  }
548 
549  vset->rw[v] = 1;
550  poly.verts[vi] = v;
551  }
552 
553  fread(texture_index_buffer, sizeof(float), poly_nverts, fp); // tu's
554  for (int vi = 0; vi < poly_nverts; vi++) {
555  int v = poly.verts[vi];
556  vset->tu[v] = texture_index_buffer[vi];
557  }
558 
559  fread(texture_index_buffer, sizeof(float), poly_nverts, fp); // tv's
560  for (int vi = 0; vi < poly_nverts; vi++) {
561  int v = poly.verts[vi];
562  vset->tv[v] = texture_index_buffer[vi];
563  }
564 
565  DWORD unused[32];
566  fread(unused, 16, 1, fp);
567  }
568 
569  // pass 2 (adjust vertex normals for flat polys):
570  for (int n = 0; n < npolys; n++) {
571  Poly& poly = polys[n];
572 
573  poly.plane = Plane(vset->loc[poly.verts[0]],
574  vset->loc[poly.verts[2]],
575  vset->loc[poly.verts[1]]);
576 
577  // hack: retrieve flat shaded flag from unused visible byte
578  if (poly.visible) {
579  int poly_nverts = poly.nverts;
580 
581  for (int vi = 0; vi < poly_nverts; vi++) {
582  int v = poly.verts[vi];
583  vset->nrm[v] = poly.plane.normal;
584  }
585  }
586  }
587 
588  // sort the polys by material index:
589  qsort((void*) polys, npolys, sizeof(Poly), mcomp);
590 
591  // then assign them to cohesive segments:
592  Segment* segment = 0;
593 
594  for (int n = 0; n < npolys; n++) {
595  if (segment && segment->material == polys[n].material) {
596  segment->npolys++;
597  }
598  else {
599  segment = 0;
600  }
601 
602  if (!segment) {
603  segment = new Segment;
604 
605  segment->npolys = 1;
606  segment->polys = &polys[n];
607  segment->material = segment->polys->material;
608 
609  s->GetSegments().append(segment);
610  }
611  }
612 
613  s->BuildHull();
614  m->GetSurfaces().append(s);
615 
616  *pnverts = nverts;
617  *pnpolys = npolys;
618  *pradius = (float) radius;
619 
620  result = nverts && npolys;
621  }
622 
623  return result;
624 }
625 
626 // +--------------------------------------------------------------------+
627 
628 bool
629 ModelFileMAG::LoadMag6(FILE* fp, Model* m, double scale)
630 {
631  bool result = false;
632  int i = 0;
633  int ntex = 0;
634  int nmtls = 0;
635  int nsurfs = 0;
636  double radius = 0;
637  List<Bitmap> textures;
638 
639  fread(&ntex, sizeof(ntex), 1, fp); // size of texture block
640  fread(&nmtls, sizeof(nmtls), 1, fp); // number of materials
641  fread(&nsurfs, sizeof(nsurfs), 1, fp); // number of surfaces
642 
643  // read texture list:
644  if (ntex) {
645  char* buffer = new char[ntex];
646  char* p = buffer;
647  Bitmap* bmp = 0;
648 
649  fread(buffer, ntex, 1, fp);
650 
651  while (p < buffer + ntex) {
653  textures.append(bmp);
654 
655  p += strlen(p) + 1;
656  }
657 
658  delete [] buffer;
659  }
660 
661  for (i = 0; i < nmtls; i++) {
662  MaterialMag6 m6;
663  Material* mtl = new Material;
664 
665  fread(&m6, sizeof(m6), 1, fp);
666 
667  if (mtl) {
668  CopyMemory(mtl->name, m6.name, Material::NAMELEN);
669  CopyMemory(mtl->shader, m6.shader, Material::NAMELEN);
670 
671  mtl->ambient_value = m6.ambient_value;
672  mtl->ambient_color = m6.ambient_color;
673  mtl->diffuse_value = m6.diffuse_value;
674  mtl->diffuse_color = m6.diffuse_color;
675  mtl->specular_value = m6.specular_value;
676  mtl->specular_color = m6.specular_color;
677  mtl->emissive_value = m6.emissive_value;
678  mtl->emissive_color = m6.emissive_color;
679 
680  mtl->Ka = ColorValue(mtl->ambient_color) * mtl->ambient_value;
681  mtl->Kd = ColorValue(mtl->diffuse_color) * mtl->diffuse_value;
682  mtl->Ks = ColorValue(mtl->specular_color) * mtl->specular_value;
683  mtl->Ke = ColorValue(mtl->emissive_color) * mtl->emissive_value;
684 
685  mtl->power = m6.power;
686  mtl->brilliance = m6.brilliance;
687  mtl->bump = m6.bump;
688  mtl->blend = m6.blend;
689  mtl->shadow = m6.shadow;
690  mtl->luminous = m6.luminous;
691 
692  if (m6.tex_diffuse && m6.tex_diffuse <= textures.size())
693  mtl->tex_diffuse = textures[m6.tex_diffuse - 1];
694 
695  if (m6.tex_specular && m6.tex_specular <= textures.size())
696  mtl->tex_specular = textures[m6.tex_specular - 1];
697 
698  if (m6.tex_emissive && m6.tex_emissive <= textures.size())
699  mtl->tex_emissive = textures[m6.tex_emissive - 1];
700 
701  if (m6.tex_bumpmap && m6.tex_bumpmap <= textures.size())
702  mtl->tex_bumpmap = textures[m6.tex_bumpmap - 1];
703 
704  m->GetMaterials().append(mtl);
705  }
706  }
707 
708  for (i = 0; i < nsurfs; i++) {
709  int nverts = 0;
710  int npolys = 0;
711  BYTE namelen = 0;
712  char name[128];
713 
714  fread(&nverts, 4, 1, fp);
715  fread(&npolys, 4, 1, fp);
716  fread(&namelen, 1, 1, fp);
717  fread(name, 1, namelen, fp);
718 
719  Surface* surface = new Surface;
720  surface->SetName(name);
721  surface->CreateVerts(nverts);
722  surface->CreatePolys(npolys);
723 
724  VertexSet* vset = surface->GetVertexSet();
725  Poly* polys = surface->GetPolys();
726 
727  ZeroMemory(polys, sizeof(Poly) * npolys);
728 
729  // read vertex set:
730  for (int v = 0; v < nverts; v++) {
731  fread(&vset->loc[v], sizeof(float), 3, fp);
732  fread(&vset->nrm[v], sizeof(float), 3, fp);
733  fread(&vset->tu[v], sizeof(float), 1, fp);
734  fread(&vset->tv[v], sizeof(float), 1, fp);
735 
736  double d = vset->loc[v].length();
737  if (d > radius)
738  radius = d;
739  }
740 
741  // read polys:
742  for (int n = 0; n < npolys; n++) {
743  Poly& poly = polys[n];
744  BYTE poly_nverts = 0;
745  BYTE material_index = 0;
746  WORD poly_verts[8];
747 
748  fread(&poly_nverts, sizeof(BYTE), 1, fp);
749  fread(&material_index, sizeof(BYTE), 1, fp);
750  fread(&poly_verts[0], sizeof(WORD), poly_nverts, fp);
751 
752  if (poly_nverts >= 3) {
753  poly.nverts = poly_nverts;
754 
755  for (int i = 0; i < poly_nverts; i++) {
756  poly.verts[i] = poly_verts[i];
757  }
758  }
759  else {
760  poly.sortval = 666;
761  }
762 
763  if (material_index > 0) {
764  poly.material = m->GetMaterials()[material_index-1];
765  poly.sortval = material_index;
766  }
767  else if (m->NumMaterials()) {
768  poly.material = m->GetMaterials().first();
769  poly.sortval = 1;
770  }
771  else {
772  poly.sortval = 1000;
773  }
774 
775  if (poly.nverts == 3)
776  surface->AddIndices(3);
777 
778  else if (poly.nverts == 4)
779  surface->AddIndices(6);
780 
781  poly.vertex_set = vset;
782  poly.plane = Plane(vset->loc[poly.verts[0]],
783  vset->loc[poly.verts[2]],
784  vset->loc[poly.verts[1]]);
785  }
786 
787  // sort the polys by material index:
788  qsort((void*) polys, npolys, sizeof(Poly), mcomp);
789 
790  // then assign them to cohesive segments:
791  Segment* segment = 0;
792 
793  for (int n = 0; n < npolys; n++) {
794  if (segment && segment->material == polys[n].material) {
795  segment->npolys++;
796  }
797  else {
798  segment = 0;
799  }
800 
801  if (!segment) {
802  segment = new Segment;
803 
804  segment->npolys = 1;
805  segment->polys = &polys[n];
806  segment->material = segment->polys->material;
807 
808  surface->GetSegments().append(segment);
809  }
810  }
811 
812  surface->ComputeTangents();
813  surface->BuildHull();
814  m->GetSurfaces().append(surface);
815 
816  *pnverts = nverts;
817  *pnpolys = npolys;
818  *pradius = (float) radius;
819 
820  result = nverts && npolys;
821  }
822 
823  return result;
824 }
825