2006-03-20 17:12:09 +00:00
|
|
|
/*
|
2014-06-01 17:55:23 +00:00
|
|
|
* Portions of this file are copyright Rebirth contributors and licensed as
|
|
|
|
* described in COPYING.txt.
|
|
|
|
* Portions of this file are copyright Parallax Software and licensed
|
|
|
|
* according to the Parallax license below.
|
|
|
|
* See COPYING.txt for license details.
|
|
|
|
|
2006-03-20 17:12:09 +00:00
|
|
|
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
|
|
|
|
SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
|
|
|
|
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
|
|
|
|
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
|
|
|
|
IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
|
|
|
|
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
|
|
|
|
FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
|
|
|
|
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
|
|
|
|
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
|
|
|
|
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
*
|
|
|
|
* Hacked-in polygon objects
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "inferno.h"
|
2015-04-02 02:36:57 +00:00
|
|
|
#include "robot.h"
|
2006-03-20 17:12:09 +00:00
|
|
|
#include "vecmat.h"
|
2015-07-25 23:10:48 +00:00
|
|
|
#include "cntrlcen.h"
|
2006-03-20 17:12:09 +00:00
|
|
|
#include "interp.h"
|
2012-07-07 18:35:06 +00:00
|
|
|
#include "dxxerror.h"
|
2006-03-20 17:12:09 +00:00
|
|
|
#include "u_mem.h"
|
|
|
|
#include "args.h"
|
2014-07-24 03:25:00 +00:00
|
|
|
#include "physfs-serial.h"
|
2014-07-20 01:09:55 +00:00
|
|
|
#include "physfsx.h"
|
2006-03-20 17:12:09 +00:00
|
|
|
#ifndef DRIVE
|
|
|
|
#include "texmap.h"
|
|
|
|
#include "bm.h"
|
|
|
|
#include "textures.h"
|
|
|
|
#include "object.h"
|
|
|
|
#include "lighting.h"
|
|
|
|
#include "piggy.h"
|
|
|
|
#endif
|
2008-04-06 20:23:28 +00:00
|
|
|
#include "render.h"
|
2006-03-20 17:12:09 +00:00
|
|
|
#ifdef OGL
|
|
|
|
#include "ogl_init.h"
|
|
|
|
#endif
|
|
|
|
|
2014-08-16 04:15:16 +00:00
|
|
|
#include "compiler-make_unique.h"
|
2014-09-20 23:47:27 +00:00
|
|
|
#include "partial_range.h"
|
2014-08-16 04:15:16 +00:00
|
|
|
|
2014-02-09 18:09:54 +00:00
|
|
|
unsigned N_polygon_models = 0;
|
|
|
|
array<polymodel, MAX_POLYGON_MODELS> Polygon_models; // = {&bot11,&bot17,&robot_s2,&robot_b2,&bot11,&bot17,&robot_s2,&robot_b2};
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
#define PM_COMPATIBLE_VERSION 6
|
|
|
|
#define PM_OBJFILE_VERSION 8
|
|
|
|
|
2014-09-20 23:47:27 +00:00
|
|
|
static unsigned Pof_file_end;
|
|
|
|
static unsigned Pof_addr;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
#define MODEL_BUF_SIZE 32768
|
|
|
|
|
2013-10-27 22:00:14 +00:00
|
|
|
static void _pof_cfseek(int len,int type)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case SEEK_SET: Pof_addr = len; break;
|
|
|
|
case SEEK_CUR: Pof_addr += len; break;
|
|
|
|
case SEEK_END:
|
|
|
|
Assert(len <= 0); // seeking from end, better be moving back.
|
|
|
|
Pof_addr = Pof_file_end + len;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Pof_addr > MODEL_BUF_SIZE)
|
|
|
|
Int3();
|
|
|
|
}
|
|
|
|
|
|
|
|
#define pof_cfseek(_buf,_len,_type) _pof_cfseek((_len),(_type))
|
|
|
|
|
2013-10-27 22:00:14 +00:00
|
|
|
static int pof_read_int(ubyte *bufp)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
i = *((int *) &bufp[Pof_addr]);
|
|
|
|
Pof_addr += 4;
|
|
|
|
return INTEL_INT(i);
|
|
|
|
|
2011-06-01 07:59:55 +00:00
|
|
|
// if (PHYSFS_read(f,&i,sizeof(i),1) != 1)
|
2006-03-20 17:12:09 +00:00
|
|
|
// Error("Unexpected end-of-file while reading object");
|
|
|
|
//
|
|
|
|
// return i;
|
|
|
|
}
|
|
|
|
|
2013-10-27 22:00:14 +00:00
|
|
|
static size_t pof_cfread(void *dst, size_t elsize, size_t nelem, ubyte *bufp)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
|
|
|
if (Pof_addr + nelem*elsize > Pof_file_end)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
memcpy(dst, &bufp[Pof_addr], elsize*nelem);
|
|
|
|
|
|
|
|
Pof_addr += elsize*nelem;
|
|
|
|
|
|
|
|
if (Pof_addr > MODEL_BUF_SIZE)
|
|
|
|
Int3();
|
|
|
|
|
|
|
|
return nelem;
|
|
|
|
}
|
|
|
|
|
2011-06-01 07:59:55 +00:00
|
|
|
// #define new_read_int(i,f) PHYSFS_read((f),&(i),sizeof(i),1)
|
2006-03-20 17:12:09 +00:00
|
|
|
#define new_pof_read_int(i,f) pof_cfread(&(i),sizeof(i),1,(f))
|
|
|
|
|
2013-10-27 22:00:14 +00:00
|
|
|
static short pof_read_short(ubyte *bufp)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
|
|
|
short s;
|
|
|
|
|
|
|
|
s = *((short *) &bufp[Pof_addr]);
|
|
|
|
Pof_addr += 2;
|
|
|
|
return INTEL_SHORT(s);
|
2011-06-01 07:59:55 +00:00
|
|
|
// if (PHYSFS_read(f,&s,sizeof(s),1) != 1)
|
2006-03-20 17:12:09 +00:00
|
|
|
// Error("Unexpected end-of-file while reading object");
|
|
|
|
//
|
|
|
|
// return s;
|
|
|
|
}
|
|
|
|
|
2013-10-27 22:00:14 +00:00
|
|
|
static void pof_read_string(char *buf,int max_char, ubyte *bufp)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i=0; i<max_char; i++) {
|
2006-03-20 17:12:09 +00:00
|
|
|
if ((*buf++ = bufp[Pof_addr++]) == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-06-01 07:59:55 +00:00
|
|
|
// while (max_char-- && (*buf=PHYSFSX_fgetc(f)) != 0) buf++;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-10-27 22:00:14 +00:00
|
|
|
static void pof_read_vecs(vms_vector *vecs,int n,ubyte *bufp)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2011-06-01 07:59:55 +00:00
|
|
|
// PHYSFS_read(f,vecs,sizeof(vms_vector),n);
|
2013-06-23 18:23:11 +00:00
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
vecs[i].x = pof_read_int(bufp);
|
|
|
|
vecs[i].y = pof_read_int(bufp);
|
|
|
|
vecs[i].z = pof_read_int(bufp);
|
|
|
|
}
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
if (Pof_addr > MODEL_BUF_SIZE)
|
|
|
|
Int3();
|
|
|
|
}
|
|
|
|
|
2013-10-27 22:00:14 +00:00
|
|
|
static void pof_read_angs(vms_angvec *angs,int n,ubyte *bufp)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2013-06-23 18:23:11 +00:00
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
angs[i].p = pof_read_short(bufp);
|
|
|
|
angs[i].b = pof_read_short(bufp);
|
|
|
|
angs[i].h = pof_read_short(bufp);
|
|
|
|
}
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
if (Pof_addr > MODEL_BUF_SIZE)
|
|
|
|
Int3();
|
|
|
|
}
|
|
|
|
|
|
|
|
#define ID_OHDR 0x5244484f // 'RDHO' //Object header
|
|
|
|
#define ID_SOBJ 0x4a424f53 // 'JBOS' //Subobject header
|
|
|
|
#define ID_GUNS 0x534e5547 // 'SNUG' //List of guns on this object
|
|
|
|
#define ID_ANIM 0x4d494e41 // 'MINA' //Animation data
|
|
|
|
#define ID_IDTA 0x41544449 // 'ATDI' //Interpreter data
|
|
|
|
#define ID_TXTR 0x52545854 // 'RTXT' //Texture filename list
|
|
|
|
|
2015-04-02 02:36:52 +00:00
|
|
|
static array<array<vms_angvec, MAX_SUBMODELS>, N_ANIM_STATES> anim_angs;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
//set the animation angles for this robot. Gun fields of robot info must
|
|
|
|
//be filled in.
|
|
|
|
#ifdef WORDS_NEED_ALIGNMENT
|
2015-02-22 01:29:43 +00:00
|
|
|
static const uint8_t *old_dest(const chunk &o) // return where chunk is (in unaligned struct)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2015-02-22 01:29:43 +00:00
|
|
|
return GET_INTEL_SHORT(&o.old_base[o.offset]) + o.old_base;
|
2006-03-20 17:12:09 +00:00
|
|
|
}
|
2014-12-22 04:35:49 +00:00
|
|
|
static uint8_t *new_dest(const chunk &o) // return where chunk is (in aligned struct)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2015-02-22 01:29:43 +00:00
|
|
|
return GET_INTEL_SHORT(&o.old_base[o.offset]) + o.new_base + o.correction;
|
2006-03-20 17:12:09 +00:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* find chunk with smallest address
|
|
|
|
*/
|
2014-12-22 04:35:49 +00:00
|
|
|
static int get_first_chunks_index(chunk *chunk_list, int no_chunks)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2014-09-26 02:42:10 +00:00
|
|
|
int first_index = 0;
|
2006-03-20 17:12:09 +00:00
|
|
|
Assert(no_chunks >= 1);
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i = 1; i < no_chunks; i++)
|
2006-03-20 17:12:09 +00:00
|
|
|
if (old_dest(chunk_list[i]) < old_dest(chunk_list[first_index]))
|
|
|
|
first_index = i;
|
|
|
|
return first_index;
|
|
|
|
}
|
|
|
|
#define SHIFT_SPACE 500 // increase if insufficent
|
|
|
|
|
2014-12-22 04:35:49 +00:00
|
|
|
static void align_polygon_model_data(polymodel *pm)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2014-09-26 02:42:10 +00:00
|
|
|
int chunk_len;
|
2006-03-20 17:12:09 +00:00
|
|
|
int total_correction = 0;
|
|
|
|
chunk cur_ch;
|
|
|
|
chunk ch_list[MAX_CHUNKS];
|
|
|
|
int no_chunks = 0;
|
|
|
|
int tmp_size = pm->model_data_size + SHIFT_SPACE;
|
2015-01-28 03:42:53 +00:00
|
|
|
RAIIdmem<uint8_t[]> tmp;
|
|
|
|
MALLOC(tmp, uint8_t[], tmp_size); // where we build the aligned version of pm->model_data
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
Assert(tmp != NULL);
|
|
|
|
//start with first chunk (is always aligned!)
|
2015-02-22 01:29:43 +00:00
|
|
|
const uint8_t *cur_old = pm->model_data.get();
|
2015-02-22 01:29:43 +00:00
|
|
|
auto cur_new = tmp.get();
|
2006-03-20 17:12:09 +00:00
|
|
|
chunk_len = get_chunks(cur_old, cur_new, ch_list, &no_chunks);
|
|
|
|
memcpy(cur_new, cur_old, chunk_len);
|
|
|
|
while (no_chunks > 0) {
|
|
|
|
int first_index = get_first_chunks_index(ch_list, no_chunks);
|
|
|
|
cur_ch = ch_list[first_index];
|
|
|
|
// remove first chunk from array:
|
|
|
|
no_chunks--;
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i = first_index; i < no_chunks; i++)
|
2006-03-20 17:12:09 +00:00
|
|
|
ch_list[i] = ch_list[i + 1];
|
|
|
|
// if (new) address unaligned:
|
2014-12-22 04:35:49 +00:00
|
|
|
const uintptr_t u = reinterpret_cast<uintptr_t>(new_dest(cur_ch));
|
|
|
|
if (u % 4L != 0) {
|
2006-03-20 17:12:09 +00:00
|
|
|
// calculate how much to move to be aligned
|
2014-12-22 04:35:49 +00:00
|
|
|
short to_shift = 4 - u % 4L;
|
2006-03-20 17:12:09 +00:00
|
|
|
// correct chunks' addresses
|
|
|
|
cur_ch.correction += to_shift;
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i = 0; i < no_chunks; i++)
|
2006-03-20 17:12:09 +00:00
|
|
|
ch_list[i].correction += to_shift;
|
|
|
|
total_correction += to_shift;
|
2015-02-22 01:29:43 +00:00
|
|
|
Assert(reinterpret_cast<uintptr_t>(new_dest(cur_ch)) % 4L == 0);
|
2006-03-20 17:12:09 +00:00
|
|
|
Assert(total_correction <= SHIFT_SPACE); // if you get this, increase SHIFT_SPACE
|
|
|
|
}
|
|
|
|
//write (corrected) chunk for current chunk:
|
|
|
|
*((short *)(cur_ch.new_base + cur_ch.offset))
|
2015-04-04 17:23:47 +00:00
|
|
|
= INTEL_SHORT(static_cast<short>(cur_ch.correction + GET_INTEL_SHORT(cur_ch.old_base + cur_ch.offset)));
|
2006-03-20 17:12:09 +00:00
|
|
|
//write (correctly aligned) chunk:
|
|
|
|
cur_old = old_dest(cur_ch);
|
|
|
|
cur_new = new_dest(cur_ch);
|
|
|
|
chunk_len = get_chunks(cur_old, cur_new, ch_list, &no_chunks);
|
|
|
|
memcpy(cur_new, cur_old, chunk_len);
|
|
|
|
//correct submodel_ptr's for pm, too
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i = 0; i < MAX_SUBMODELS; i++)
|
2014-07-24 03:12:57 +00:00
|
|
|
if (&pm->model_data[pm->submodel_ptrs[i]] >= cur_old
|
|
|
|
&& &pm->model_data[pm->submodel_ptrs[i]] < cur_old + chunk_len)
|
2015-02-22 01:29:43 +00:00
|
|
|
pm->submodel_ptrs[i] += (cur_new - tmp.get()) - (cur_old - pm->model_data.get());
|
2006-03-20 17:12:09 +00:00
|
|
|
}
|
|
|
|
pm->model_data_size += total_correction;
|
2014-08-16 04:15:16 +00:00
|
|
|
pm->model_data = make_unique<ubyte[]>(pm->model_data_size);
|
2006-03-20 17:12:09 +00:00
|
|
|
Assert(pm->model_data != NULL);
|
2015-02-22 01:29:43 +00:00
|
|
|
memcpy(pm->model_data.get(), tmp.get(), pm->model_data_size);
|
2006-03-20 17:12:09 +00:00
|
|
|
}
|
|
|
|
#endif //def WORDS_NEED_ALIGNMENT
|
|
|
|
|
|
|
|
//reads a binary file containing a 3d model
|
2013-06-08 22:24:17 +00:00
|
|
|
static polymodel *read_model_file(polymodel *pm,const char *filename,robot_info *r)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
|
|
|
short version;
|
|
|
|
int id,len, next_chunk;
|
2013-06-23 18:25:36 +00:00
|
|
|
ubyte model_buf[MODEL_BUF_SIZE];
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2015-01-17 18:31:42 +00:00
|
|
|
auto ifile = PHYSFSX_openReadBuffered(filename);
|
2015-01-17 18:31:42 +00:00
|
|
|
if (!ifile)
|
2006-03-20 17:12:09 +00:00
|
|
|
Error("Can't open file <%s>",filename);
|
|
|
|
|
2011-06-01 07:59:55 +00:00
|
|
|
Assert(PHYSFS_fileLength(ifile) <= MODEL_BUF_SIZE);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
Pof_addr = 0;
|
2011-06-01 07:59:55 +00:00
|
|
|
Pof_file_end = PHYSFS_read(ifile, model_buf, 1, PHYSFS_fileLength(ifile));
|
2015-01-17 18:31:42 +00:00
|
|
|
ifile.reset();
|
2006-03-20 17:12:09 +00:00
|
|
|
id = pof_read_int(model_buf);
|
|
|
|
|
|
|
|
if (id!=0x4f505350) /* 'OPSP' */
|
|
|
|
Error("Bad ID in model file <%s>",filename);
|
|
|
|
|
|
|
|
version = pof_read_short(model_buf);
|
|
|
|
|
|
|
|
if (version < PM_COMPATIBLE_VERSION || version > PM_OBJFILE_VERSION)
|
|
|
|
Error("Bad version (%d) in model file <%s>",version,filename);
|
|
|
|
|
|
|
|
while (new_pof_read_int(id,model_buf) == 1) {
|
|
|
|
id = INTEL_INT(id);
|
|
|
|
//id = pof_read_int(model_buf);
|
|
|
|
len = pof_read_int(model_buf);
|
|
|
|
next_chunk = Pof_addr + len;
|
|
|
|
|
|
|
|
switch (id) {
|
|
|
|
|
|
|
|
case ID_OHDR: { //Object header
|
|
|
|
vms_vector pmmin,pmmax;
|
|
|
|
|
|
|
|
pm->n_models = pof_read_int(model_buf);
|
|
|
|
pm->rad = pof_read_int(model_buf);
|
|
|
|
|
|
|
|
Assert(pm->n_models <= MAX_SUBMODELS);
|
|
|
|
|
|
|
|
pof_read_vecs(&pmmin,1,model_buf);
|
|
|
|
pof_read_vecs(&pmmax,1,model_buf);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ID_SOBJ: { //Subobject header
|
|
|
|
int n;
|
|
|
|
|
|
|
|
n = pof_read_short(model_buf);
|
|
|
|
|
|
|
|
Assert(n < MAX_SUBMODELS);
|
|
|
|
|
|
|
|
pm->submodel_parents[n] = pof_read_short(model_buf);
|
|
|
|
|
|
|
|
pof_read_vecs(&pm->submodel_norms[n],1,model_buf);
|
|
|
|
pof_read_vecs(&pm->submodel_pnts[n],1,model_buf);
|
|
|
|
pof_read_vecs(&pm->submodel_offsets[n],1,model_buf);
|
|
|
|
|
|
|
|
pm->submodel_rads[n] = pof_read_int(model_buf); //radius
|
|
|
|
|
|
|
|
pm->submodel_ptrs[n] = pof_read_int(model_buf); //offset
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef DRIVE
|
|
|
|
case ID_GUNS: { //List of guns on this object
|
|
|
|
|
|
|
|
if (r) {
|
|
|
|
vms_vector gun_dir;
|
|
|
|
|
|
|
|
r->n_guns = pof_read_int(model_buf);
|
|
|
|
|
|
|
|
Assert(r->n_guns <= MAX_GUNS);
|
|
|
|
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i=0;i<r->n_guns;i++) {
|
2014-09-20 23:47:27 +00:00
|
|
|
uint_fast32_t id;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
id = pof_read_short(model_buf);
|
2014-08-24 18:30:25 +00:00
|
|
|
/*
|
|
|
|
* D1 v1.0 boss02.pof has id=4 and r->n_guns==4.
|
|
|
|
* Relax the assert to check only for memory
|
|
|
|
* corruption.
|
|
|
|
*/
|
|
|
|
Assert(id < sizeof(r->gun_submodels) / sizeof(r->gun_submodels[0]));
|
2006-03-20 17:12:09 +00:00
|
|
|
r->gun_submodels[id] = pof_read_short(model_buf);
|
|
|
|
Assert(r->gun_submodels[id] != 0xff);
|
|
|
|
pof_read_vecs(&r->gun_points[id],1,model_buf);
|
|
|
|
|
|
|
|
if (version >= 7)
|
|
|
|
pof_read_vecs(&gun_dir,1,model_buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
pof_cfseek(model_buf,len,SEEK_CUR);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ID_ANIM: //Animation data
|
|
|
|
if (r) {
|
2014-10-26 22:51:27 +00:00
|
|
|
unsigned n_frames;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
n_frames = pof_read_short(model_buf);
|
|
|
|
|
|
|
|
Assert(n_frames == N_ANIM_STATES);
|
|
|
|
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int m=0;m<pm->n_models;m++)
|
2015-04-02 02:36:52 +00:00
|
|
|
range_for (auto &f, partial_range(anim_angs, n_frames))
|
|
|
|
pof_read_angs(&f[m], 1, model_buf);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
robot_set_angles(r,pm,anim_angs);
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
pof_cfseek(model_buf,len,SEEK_CUR);
|
|
|
|
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
case ID_TXTR: { //Texture filename list
|
|
|
|
int n;
|
|
|
|
char name_buf[128];
|
|
|
|
|
|
|
|
n = pof_read_short(model_buf);
|
|
|
|
while (n--) {
|
|
|
|
pof_read_string(name_buf,128,model_buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ID_IDTA: //Interpreter data
|
|
|
|
pm->model_data_size = len;
|
2014-08-16 04:15:16 +00:00
|
|
|
pm->model_data = make_unique<ubyte[]>(pm->model_data_size);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2014-07-24 03:12:57 +00:00
|
|
|
pof_cfread(pm->model_data.get(),1,len,model_buf);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
pof_cfseek(model_buf,len,SEEK_CUR);
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
if ( version >= 8 ) // Version 8 needs 4-byte alignment!!!
|
|
|
|
pof_cfseek(model_buf,next_chunk,SEEK_SET);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef WORDS_NEED_ALIGNMENT
|
|
|
|
align_polygon_model_data(pm);
|
|
|
|
#endif
|
2015-06-13 22:42:21 +00:00
|
|
|
if (words_bigendian)
|
2014-07-24 03:12:57 +00:00
|
|
|
swap_polygon_model_data(pm->model_data.get());
|
2006-03-20 17:12:09 +00:00
|
|
|
return pm;
|
|
|
|
}
|
|
|
|
|
|
|
|
//reads the gun information for a model
|
|
|
|
//fills in arrays gun_points & gun_dirs, returns the number of guns read
|
2015-07-25 23:10:48 +00:00
|
|
|
void read_model_guns(const char *filename, reactor &r)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2015-07-25 23:10:48 +00:00
|
|
|
auto &gun_points = r.gun_points;
|
|
|
|
auto &gun_dirs = r.gun_dirs;
|
2006-03-20 17:12:09 +00:00
|
|
|
short version;
|
|
|
|
int id,len;
|
|
|
|
int n_guns=0;
|
2013-06-23 18:25:36 +00:00
|
|
|
ubyte model_buf[MODEL_BUF_SIZE];
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2015-01-17 18:31:42 +00:00
|
|
|
auto ifile = PHYSFSX_openReadBuffered(filename);
|
2015-01-17 18:31:42 +00:00
|
|
|
if (!ifile)
|
2006-03-20 17:12:09 +00:00
|
|
|
Error("Can't open file <%s>",filename);
|
|
|
|
|
2011-06-01 07:59:55 +00:00
|
|
|
Assert(PHYSFS_fileLength(ifile) <= MODEL_BUF_SIZE);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
Pof_addr = 0;
|
2011-06-01 07:59:55 +00:00
|
|
|
Pof_file_end = PHYSFS_read(ifile, model_buf, 1, PHYSFS_fileLength(ifile));
|
2015-01-17 18:31:42 +00:00
|
|
|
ifile.reset();
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
id = pof_read_int(model_buf);
|
|
|
|
|
|
|
|
if (id!=0x4f505350) /* 'OPSP' */
|
|
|
|
Error("Bad ID in model file <%s>",filename);
|
|
|
|
|
|
|
|
version = pof_read_short(model_buf);
|
|
|
|
|
|
|
|
Assert(version >= 7); //must be 7 or higher for this data
|
|
|
|
|
|
|
|
if (version < PM_COMPATIBLE_VERSION || version > PM_OBJFILE_VERSION)
|
|
|
|
Error("Bad version (%d) in model file <%s>",version,filename);
|
|
|
|
|
|
|
|
while (new_pof_read_int(id,model_buf) == 1) {
|
|
|
|
id = INTEL_INT(id);
|
|
|
|
//id = pof_read_int(model_buf);
|
|
|
|
len = pof_read_int(model_buf);
|
|
|
|
|
|
|
|
if (id == ID_GUNS) { //List of guns on this object
|
|
|
|
n_guns = pof_read_int(model_buf);
|
|
|
|
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i=0;i<n_guns;i++) {
|
2006-03-20 17:12:09 +00:00
|
|
|
int id,sm;
|
|
|
|
|
|
|
|
id = pof_read_short(model_buf);
|
|
|
|
sm = pof_read_short(model_buf);
|
2014-11-01 03:08:27 +00:00
|
|
|
if (sm!=0)
|
2006-03-20 17:12:09 +00:00
|
|
|
Error("Invalid gun submodel in file <%s>",filename);
|
|
|
|
pof_read_vecs(&gun_points[id],1,model_buf);
|
|
|
|
|
|
|
|
pof_read_vecs(&gun_dirs[id],1,model_buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
pof_cfseek(model_buf,len,SEEK_CUR);
|
|
|
|
|
|
|
|
}
|
2015-07-25 23:10:48 +00:00
|
|
|
r.n_guns = n_guns;
|
2006-03-20 17:12:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//free up a model, getting rid of all its memory
|
2013-10-26 03:52:09 +00:00
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
|
|
static
|
|
|
|
#endif
|
2006-03-20 17:12:09 +00:00
|
|
|
void free_model(polymodel *po)
|
|
|
|
{
|
2014-07-24 03:12:57 +00:00
|
|
|
po->model_data.reset();
|
2006-03-20 17:12:09 +00:00
|
|
|
}
|
|
|
|
|
2014-08-16 22:18:14 +00:00
|
|
|
array<grs_bitmap *, MAX_POLYOBJ_TEXTURES> texture_list;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
//draw a polygon model
|
|
|
|
|
2015-02-14 22:48:29 +00:00
|
|
|
void draw_polygon_model(const vms_vector &pos,const vms_matrix *orient,const submodel_angles anim_angles,int model_num,int flags,g3s_lrgb light,glow_values_t *glow_values, alternate_textures alt_textures)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
|
|
|
polymodel *po;
|
2012-07-24 10:01:14 +00:00
|
|
|
if (model_num < 0)
|
|
|
|
return;
|
|
|
|
|
2006-03-20 17:12:09 +00:00
|
|
|
Assert(model_num < N_polygon_models);
|
|
|
|
|
|
|
|
po=&Polygon_models[model_num];
|
|
|
|
|
|
|
|
//check if should use simple model
|
|
|
|
if (po->simpler_model ) //must have a simpler model
|
|
|
|
if (flags==0) //can't switch if this is debris
|
|
|
|
//alt textures might not match, but in the one case we're using this
|
|
|
|
//for on 11/14/94, they do match. So we leave it in.
|
|
|
|
{
|
|
|
|
int cnt=1;
|
2014-10-26 22:01:04 +00:00
|
|
|
const auto depth = g3_calc_point_depth(pos); //gets 3d depth
|
2006-03-20 17:12:09 +00:00
|
|
|
while (po->simpler_model && depth > cnt++ * Simple_model_threshhold_scale * po->rad)
|
|
|
|
po = &Polygon_models[po->simpler_model-1];
|
|
|
|
}
|
|
|
|
|
2015-10-09 02:46:09 +00:00
|
|
|
array<bitmap_index, MAX_POLYOBJ_TEXTURES> texture_list_index;
|
2006-03-20 17:12:09 +00:00
|
|
|
if (alt_textures)
|
|
|
|
{
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i=0;i<po->n_textures;i++) {
|
2006-03-20 17:12:09 +00:00
|
|
|
texture_list_index[i] = alt_textures[i];
|
|
|
|
texture_list[i] = &GameBitmaps[alt_textures[i].index];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i=0;i<po->n_textures;i++) {
|
2006-03-20 17:12:09 +00:00
|
|
|
texture_list_index[i] = ObjBitmaps[ObjBitmapPtrs[po->first_texture+i]];
|
|
|
|
texture_list[i] = &GameBitmaps[ObjBitmaps[ObjBitmapPtrs[po->first_texture+i]].index];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure the textures for this object are paged in...
|
|
|
|
piggy_page_flushed = 0;
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i=0;i<po->n_textures;i++)
|
2006-03-20 17:12:09 +00:00
|
|
|
PIGGY_PAGE_IN( texture_list_index[i] );
|
|
|
|
// Hmmm... cache got flushed in the middle of paging all these in,
|
|
|
|
// so we need to reread them all in.
|
|
|
|
if (piggy_page_flushed) {
|
|
|
|
piggy_page_flushed = 0;
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i=0;i<po->n_textures;i++)
|
2006-03-20 17:12:09 +00:00
|
|
|
PIGGY_PAGE_IN( texture_list_index[i] );
|
|
|
|
}
|
|
|
|
// Make sure that they can all fit in memory.
|
|
|
|
Assert( piggy_page_flushed == 0 );
|
|
|
|
|
2014-10-02 03:02:35 +00:00
|
|
|
g3_start_instance_matrix(pos,orient);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2015-02-05 03:03:50 +00:00
|
|
|
polygon_model_points robot_points;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
if (flags == 0) //draw entire object
|
|
|
|
|
2015-02-05 03:03:50 +00:00
|
|
|
g3_draw_polygon_model(po->model_data.get(),&texture_list[0],anim_angles,light,glow_values, robot_points);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
else {
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int i=0;flags;flags>>=1,i++)
|
2006-03-20 17:12:09 +00:00
|
|
|
if (flags & 1) {
|
|
|
|
Assert(i < po->n_models);
|
|
|
|
|
|
|
|
//if submodel, rotate around its center point, not pivot point
|
|
|
|
|
2014-10-30 03:18:38 +00:00
|
|
|
const auto ofs = vm_vec_negated(vm_vec_avg(po->submodel_mins[i],po->submodel_maxs[i]));
|
2014-10-02 03:02:35 +00:00
|
|
|
g3_start_instance_matrix(ofs,NULL);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2015-02-05 03:03:50 +00:00
|
|
|
g3_draw_polygon_model(&po->model_data[po->submodel_ptrs[i]],&texture_list[0],anim_angles,light,glow_values, robot_points);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
g3_done_instance();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g3_done_instance();
|
|
|
|
}
|
|
|
|
|
|
|
|
void free_polygon_models()
|
|
|
|
{
|
2014-09-20 23:47:27 +00:00
|
|
|
range_for (auto &i, partial_range(Polygon_models, N_polygon_models))
|
|
|
|
free_model(&i);
|
2006-03-20 17:12:09 +00:00
|
|
|
}
|
|
|
|
|
2014-11-02 03:43:17 +00:00
|
|
|
static void assign_max(fix &a, const fix &b)
|
|
|
|
{
|
|
|
|
a = std::max(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void assign_min(fix &a, const fix &b)
|
|
|
|
{
|
|
|
|
a = std::min(a, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <fix vms_vector::*p>
|
|
|
|
static void update_bounds(vms_vector &minv, vms_vector &maxv, const vms_vector &vp)
|
|
|
|
{
|
2014-11-04 03:21:23 +00:00
|
|
|
auto &mx = maxv.*p;
|
|
|
|
assign_max(mx, vp.*p);
|
|
|
|
auto &mn = minv.*p;
|
|
|
|
assign_min(mn, vp.*p);
|
2014-11-02 03:43:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void assign_minmax(vms_vector &minv, vms_vector &maxv, const vms_vector &v)
|
|
|
|
{
|
|
|
|
update_bounds<&vms_vector::x>(minv, maxv, v);
|
|
|
|
update_bounds<&vms_vector::y>(minv, maxv, v);
|
|
|
|
update_bounds<&vms_vector::z>(minv, maxv, v);
|
|
|
|
}
|
|
|
|
|
2013-10-27 22:00:14 +00:00
|
|
|
static void polyobj_find_min_max(polymodel *pm)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
|
|
|
ushort nverts;
|
|
|
|
ushort *data,type;
|
2014-11-02 03:43:17 +00:00
|
|
|
auto &big_mn = pm->mins;
|
|
|
|
auto &big_mx = pm->maxs;
|
2014-09-26 02:42:10 +00:00
|
|
|
for (int m=0;m<pm->n_models;m++) {
|
2014-11-02 03:43:17 +00:00
|
|
|
auto &mn = pm->submodel_mins[m];
|
|
|
|
auto &mx = pm->submodel_maxs[m];
|
|
|
|
const auto &ofs = pm->submodel_offsets[m];
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
data = (ushort *)&pm->model_data[pm->submodel_ptrs[m]];
|
|
|
|
|
|
|
|
type = *data++;
|
|
|
|
|
|
|
|
Assert(type == 7 || type == 1);
|
|
|
|
|
|
|
|
nverts = *data++;
|
|
|
|
|
|
|
|
if (type==7)
|
|
|
|
data+=2; //skip start & pad
|
|
|
|
|
2014-11-02 03:43:17 +00:00
|
|
|
auto vp = reinterpret_cast<const vms_vector *>(data);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2014-11-02 03:43:17 +00:00
|
|
|
mn = mx = *vp++;
|
|
|
|
nverts--;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
if (m==0)
|
2014-11-02 03:43:17 +00:00
|
|
|
big_mn = big_mx = mn;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
while (nverts--) {
|
2014-11-02 03:43:17 +00:00
|
|
|
assign_minmax(mn, mx, *vp);
|
|
|
|
assign_minmax(big_mn, big_mx, vm_vec_add(*vp, ofs));
|
2006-03-20 17:12:09 +00:00
|
|
|
vp++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-12 03:11:46 +00:00
|
|
|
array<char[FILENAME_LEN], MAX_POLYGON_MODELS> Pof_names;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
//returns the number of this model
|
2013-06-08 22:24:17 +00:00
|
|
|
int load_polygon_model(const char *filename,int n_textures,int first_texture,robot_info *r)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
|
|
|
Assert(N_polygon_models < MAX_POLYGON_MODELS);
|
|
|
|
Assert(n_textures < MAX_POLYOBJ_TEXTURES);
|
|
|
|
|
|
|
|
Assert(strlen(filename) <= 12);
|
2015-02-28 22:34:07 +00:00
|
|
|
const auto n_models = N_polygon_models;
|
|
|
|
strcpy(Pof_names[n_models], filename);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2015-02-28 22:34:07 +00:00
|
|
|
auto &model = Polygon_models[n_models];
|
|
|
|
read_model_file(&model, filename, r);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2015-02-28 22:34:07 +00:00
|
|
|
polyobj_find_min_max(&model);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2015-02-28 22:34:07 +00:00
|
|
|
const auto highest_texture_num = g3_init_polygon_model(model.model_data.get());
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
if (highest_texture_num+1 != n_textures)
|
|
|
|
Error("Model <%s> references %d textures but specifies %d.",filename,highest_texture_num+1,n_textures);
|
|
|
|
|
2015-02-28 22:34:07 +00:00
|
|
|
model.n_textures = n_textures;
|
|
|
|
model.first_texture = first_texture;
|
|
|
|
model.simpler_model = 0;
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
// Assert(polygon_models[N_polygon_models]!=NULL);
|
|
|
|
|
|
|
|
N_polygon_models++;
|
|
|
|
|
|
|
|
return N_polygon_models-1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void init_polygon_models()
|
|
|
|
{
|
|
|
|
N_polygon_models = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//compare against this size when figuring how far to place eye for picture
|
|
|
|
#define BASE_MODEL_SIZE 0x28000
|
|
|
|
|
|
|
|
#define DEFAULT_VIEW_DIST 0x60000
|
|
|
|
|
|
|
|
//draws the given model in the current canvas. The distance is set to
|
|
|
|
//more-or-less fill the canvas. Note that this routine actually renders
|
|
|
|
//into an off-screen canvas that it creates, then copies to the current
|
|
|
|
//canvas.
|
2014-09-20 23:47:27 +00:00
|
|
|
void draw_model_picture(uint_fast32_t mn,vms_angvec *orient_angles)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2011-04-07 20:32:51 +00:00
|
|
|
g3s_lrgb lrgb = { f1_0, f1_0, f1_0 };
|
2007-08-08 12:38:13 +00:00
|
|
|
|
2014-09-20 23:47:27 +00:00
|
|
|
Assert(mn<N_polygon_models);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
gr_clear_canvas( BM_XRGB(0,0,0) );
|
|
|
|
g3_start_frame();
|
2015-08-13 03:15:53 +00:00
|
|
|
vms_vector temp_pos{};
|
2015-02-05 03:03:51 +00:00
|
|
|
g3_set_view_matrix(temp_pos,vmd_identity_matrix,0x9000);
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
if (Polygon_models[mn].rad != 0)
|
|
|
|
temp_pos.z = fixmuldiv(DEFAULT_VIEW_DIST,Polygon_models[mn].rad,BASE_MODEL_SIZE);
|
|
|
|
else
|
|
|
|
temp_pos.z = DEFAULT_VIEW_DIST;
|
|
|
|
|
2015-02-05 03:03:51 +00:00
|
|
|
const auto &&temp_orient = vm_angles_2_matrix(*orient_angles);
|
2014-10-02 03:02:35 +00:00
|
|
|
draw_polygon_model(temp_pos,&temp_orient,NULL,mn,0,lrgb,NULL,NULL);
|
2006-03-20 17:12:09 +00:00
|
|
|
g3_end_frame();
|
|
|
|
}
|
|
|
|
|
2014-07-24 03:25:00 +00:00
|
|
|
DEFINE_SERIAL_VMS_VECTOR_TO_MESSAGE();
|
|
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(polymodel, p, (p.n_models, p.model_data_size, serial::pad<4>(), p.submodel_ptrs, p.submodel_offsets, p.submodel_norms, p.submodel_pnts, p.submodel_rads, p.submodel_parents, p.submodel_mins, p.submodel_maxs, p.mins, p.maxs, p.rad, p.n_textures, p.first_texture, p.simpler_model));
|
|
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(polymodel, 12 + (10 * 4) + (10 * 3 * sizeof(vms_vector)) + (10 * sizeof(fix)) + 10 + (10 * 2 * sizeof(vms_vector)) + (2 * sizeof(vms_vector)) + 8);
|
|
|
|
|
2006-03-20 17:12:09 +00:00
|
|
|
/*
|
2011-06-01 07:59:55 +00:00
|
|
|
* reads a polymodel structure from a PHYSFS_file
|
2006-03-20 17:12:09 +00:00
|
|
|
*/
|
2013-03-03 01:03:33 +00:00
|
|
|
void polymodel_read(polymodel *pm, PHYSFS_file *fp)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2014-07-24 03:25:00 +00:00
|
|
|
pm->model_data.reset();
|
|
|
|
PHYSFSX_serialize_read(fp, *pm);
|
|
|
|
}
|
2006-03-20 17:12:09 +00:00
|
|
|
|
2015-12-04 03:36:31 +00:00
|
|
|
#if 0
|
2014-07-24 03:25:00 +00:00
|
|
|
void polymodel_write(PHYSFS_file *fp, const polymodel &pm)
|
|
|
|
{
|
|
|
|
PHYSFSX_serialize_write(fp, pm);
|
2006-03-20 17:12:09 +00:00
|
|
|
}
|
2015-12-04 03:36:31 +00:00
|
|
|
#endif
|
2006-03-20 17:12:09 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* routine which allocates, reads, and inits a polymodel's model_data
|
|
|
|
*/
|
2011-06-01 07:59:55 +00:00
|
|
|
void polygon_model_data_read(polymodel *pm, PHYSFS_file *fp)
|
2006-03-20 17:12:09 +00:00
|
|
|
{
|
2014-08-16 04:15:16 +00:00
|
|
|
pm->model_data = make_unique<ubyte[]>(pm->model_data_size);
|
2011-06-01 07:59:55 +00:00
|
|
|
PHYSFS_read(fp, pm->model_data, sizeof(ubyte), pm->model_data_size);
|
2006-03-20 17:12:09 +00:00
|
|
|
#ifdef WORDS_NEED_ALIGNMENT
|
|
|
|
align_polygon_model_data(pm);
|
|
|
|
#endif
|
2015-06-13 22:42:21 +00:00
|
|
|
if (words_bigendian)
|
2014-07-24 03:12:57 +00:00
|
|
|
swap_polygon_model_data(pm->model_data.get());
|
2013-03-03 01:03:33 +00:00
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
2014-07-24 03:12:57 +00:00
|
|
|
g3_init_polygon_model(pm->model_data.get());
|
2013-03-03 01:03:33 +00:00
|
|
|
#endif
|
2006-03-20 17:12:09 +00:00
|
|
|
}
|