Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * Portions of this file are copyright Rebirth contributors and licensed as
3
 * described in COPYING.txt.
4
 * Portions of this file are copyright Parallax Software and licensed
5
 * according to the Parallax license below.
6
 * See COPYING.txt for license details.
7
 
8
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
10
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
16
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17
COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
18
*/
19
 
20
/*
21
 *
22
 * Morphing code
23
 *
24
 */
25
 
26
#include <algorithm>
27
#include <stdio.h>
28
#include <stdlib.h>
29
#include <string.h>
30
 
31
#include "texmap.h"
32
#include "dxxerror.h"
33
#include "inferno.h"
34
#include "polyobj.h"
35
#include "game.h"
36
#include "lighting.h"
37
#include "newdemo.h"
38
#include "piggy.h"
39
#include "bm.h"
40
#include "interp.h"
41
#include "render.h"
42
#include "object.h"
43
 
44
#include "compiler-poison.h"
45
#include "compiler-range_for.h"
46
#include "d_enumerate.h"
47
#include "d_range.h"
48
#include "d_zip.h"
49
#include "partial_range.h"
50
 
51
using std::max;
52
 
53
namespace dcx {
54
 
55
namespace {
56
 
57
class invalid_morph_model_type : public std::runtime_error
58
{
59
        __attribute_cold
60
        static std::string prepare_message(const unsigned type)
61
        {
62
                char buf[32 + sizeof("4294967295")];
63
                const auto len = std::snprintf(buf, sizeof buf, "invalid morph model type: %u", type);
64
                return std::string(buf, len);
65
        }
66
public:
67
        invalid_morph_model_type(const unsigned type) :
68
                runtime_error(prepare_message(type))
69
        {
70
        }
71
};
72
 
73
class invalid_morph_model_vertex_count : public std::runtime_error
74
{
75
        __attribute_cold
76
        static std::string prepare_message(const unsigned count, const morph_data::polymodel_idx idx, const unsigned submodel_num)
77
        {
78
                char buf[68 + 3 * sizeof("4294967295")];
79
                const unsigned uidx = idx.idx;
80
                const auto len = std::snprintf(buf, sizeof buf, "too many vertices in morph model: found %u in model %u, submodel %u", count, uidx, submodel_num);
81
                return std::string(buf, len);
82
        }
83
public:
84
        invalid_morph_model_vertex_count(const unsigned count, const morph_data::polymodel_idx idx, const unsigned submodel_num) :
85
                runtime_error(prepare_message(count, idx, submodel_num))
86
        {
87
        }
88
};
89
 
90
struct submodel_data
91
{
92
        const uint16_t *body;
93
        const unsigned type;
94
        const unsigned nverts;
95
        const unsigned startpoint;
96
};
97
 
98
submodel_data parse_model_data_header(const polymodel &pm, const unsigned submodel_num)
99
{
100
        auto data = reinterpret_cast<const uint16_t *>(&pm.model_data[pm.submodel_ptrs[submodel_num]]);
101
        const auto ptype = data++;
102
 
103
        const uint16_t type = *ptype;
104
        const auto pnverts = data++;
105
 
106
        const uint16_t startpoint = (type == 7)
107
                ? *std::exchange(data, data + 2)                //get start point number, skip pad
108
                : (type == 1)
109
                ? 0                             //start at zero
110
                : throw invalid_morph_model_type(type);
111
        const uint16_t nverts = *pnverts;
112
        return {data, type, nverts, startpoint};
113
}
114
 
115
std::size_t count_submodel_points(const polymodel &pm, const morph_data::polymodel_idx model_idx, const unsigned submodel_num)
116
{
117
        /* Return the minimum array size that will not cause this submodel
118
         * to index past the end of the array.
119
         */
120
        const auto &&sd = parse_model_data_header(pm, submodel_num);
121
        const std::size_t count = sd.startpoint + sd.nverts;
122
        if (count > morph_data::MAX_VECS)
123
                throw invalid_morph_model_vertex_count(count, model_idx, submodel_num);
124
        return count;
125
}
126
 
127
std::size_t count_model_points(const polymodel &pm, const morph_data::polymodel_idx model_idx)
128
{
129
        /* Return the minimum array size that will not cause any used
130
         * submodel of this model to index past the end of the array.
131
         *
132
         * Unused submodels are not considered.  A submodel is used if:
133
         * - its index is not above pm.n_models
134
         * - its parent index is valid
135
         * - its parent index is a used submodel
136
         *
137
         * Submodel 0 is always used.
138
         */
139
        auto count = count_submodel_points(pm, model_idx, 0);
140
        unsigned visited_submodels = 1;
141
        const unsigned mask_all_enabled_models = (1 << pm.n_models) - 1;
142
        const auto &&submodel_parents = enumerate(partial_range(pm.submodel_parents, pm.n_models));
143
        for (;;)
144
        {
145
                if (visited_submodels == mask_all_enabled_models)
146
                        /* Every submodel has been checked, so the next pass through
147
                         * the loop will ignore every element.  Break out early to
148
                         * avoid the extra iteration.
149
                         */
150
                        break;
151
                const auto previous_visited_submodels = visited_submodels;
152
                range_for (auto &&e, submodel_parents)
153
                {
154
                        const unsigned mask_this_submodel = 1 << e.idx;
155
                        if (mask_this_submodel & visited_submodels)
156
                                /* Already tested on a prior iteration */
157
                                continue;
158
                        if (e.value >= pm.submodel_parents.size())
159
                                /* Ignore submodels with out-of-range parents.  This
160
                                 * avoids undefined behavior in the shift, since some
161
                                 * submodels have a parent of 0xff.
162
                                 */
163
                                continue;
164
                        const unsigned mask_parent_submodel = 1 << e.value;
165
                        if (mask_parent_submodel & visited_submodels)
166
                        {
167
                                visited_submodels |= mask_this_submodel;
168
                                /* Parent is in use, so this submodel is also in use. */
169
                                const auto subcount = count_submodel_points(pm, model_idx, e.idx);
170
                                count = std::max(count, subcount);
171
                        }
172
                }
173
                if (previous_visited_submodels == visited_submodels)
174
                        /* No changes on the most recent pass, so no changes will
175
                         * occur on any subsequent pass.  Break out.
176
                         */
177
                        break;
178
        }
179
        return count;
180
}
181
 
182
}
183
 
184
void *morph_data::operator new(std::size_t, const max_vectors max_vecs)
185
{
186
        return ::operator new(sizeof(morph_data) + (max_vecs.count * (sizeof(fix) + sizeof(vms_vector) + sizeof(vms_vector))));
187
}
188
 
189
morph_data::ptr morph_data::create(object_base &o, const polymodel &pm, const polymodel_idx model_idx)
190
{
191
        const max_vectors m{count_model_points(pm, model_idx)};
192
        /* This is an unusual form of `new` overload.  Although arguments to
193
         * `new` are typically considered a use of `placement new`, this is
194
         * not used to place the object.  Instead, it is used to pass extra
195
         * information to `operator new` so that the count of allocated
196
         * bytes can be adjusted.
197
         */
198
        return ptr(new(m) morph_data(o, m));
199
}
200
 
201
morph_data::morph_data(object_base &o, const max_vectors m) :
202
        obj(&o), Morph_sig(o.signature), max_vecs(m)
203
{
204
        DXX_POISON_VAR(submodel_active, 0xcc);
205
        const auto morph_times = get_morph_times();
206
        DXX_POISON_MEMORY(morph_times.begin(), morph_times.end(), 0xcc);
207
        const auto morph_vecs = get_morph_times();
208
        DXX_POISON_MEMORY(morph_vecs.begin(), morph_vecs.end(), 0xcc);
209
        const auto morph_deltas = get_morph_times();
210
        DXX_POISON_MEMORY(morph_deltas.begin(), morph_deltas.end(), 0xcc);
211
        DXX_POISON_VAR(n_morphing_points, 0xcc);
212
        DXX_POISON_VAR(submodel_startpoints, 0xcc);
213
}
214
 
215
span<fix> morph_data::get_morph_times()
216
{
217
        return {reinterpret_cast<fix *>(this + 1), max_vecs.count};
218
}
219
 
220
span<vms_vector> morph_data::get_morph_vecs()
221
{
222
        return {reinterpret_cast<vms_vector *>(get_morph_times().end()), max_vecs.count};
223
}
224
 
225
span<vms_vector> morph_data::get_morph_deltas()
226
{
227
        return {get_morph_vecs().end(), max_vecs.count};
228
}
229
 
230
d_level_unique_morph_object_state::~d_level_unique_morph_object_state() = default;
231
 
232
//returns ptr to data for this object, or NULL if none
233
morph_data::ptr *find_morph_data(d_level_unique_morph_object_state &LevelUniqueMorphObjectState, object_base &obj)
234
{
235
        auto &morph_objects = LevelUniqueMorphObjectState.morph_objects;
236
        if (Newdemo_state == ND_STATE_PLAYBACK) {
237
                return nullptr;
238
        }
239
 
240
        range_for (auto &i, morph_objects)
241
                if (i && i->obj == &obj)
242
                        return &i;
243
        return nullptr;
244
}
245
 
246
}
247
 
248
static void assign_max(fix &a, const fix &b)
249
{
250
        a = std::max(a, b);
251
}
252
 
253
static void assign_min(fix &a, const fix &b)
254
{
255
        a = std::min(a, b);
256
}
257
 
258
static void update_bounds(vms_vector &minv, vms_vector &maxv, const vms_vector &v, fix vms_vector::*const p)
259
{
260
        auto &mx = maxv.*p;
261
        assign_max(mx, v.*p);
262
        auto &mn = minv.*p;
263
        assign_min(mn, v.*p);
264
}
265
 
266
//takes pm, fills in min & max
267
static void find_min_max(const polymodel &pm, const unsigned submodel_num, vms_vector &minv, vms_vector &maxv)
268
{
269
        const auto &&sd = parse_model_data_header(pm, submodel_num);
270
        const unsigned nverts = sd.nverts;
271
        if (!nverts)
272
        {
273
                minv = maxv = {};
274
                return;
275
        }
276
        const auto vp = reinterpret_cast<const vms_vector *>(sd.body);
277
 
278
        minv = maxv = *vp;
279
 
280
        range_for (auto &v, unchecked_partial_range(vp + 1, nverts - 1))
281
        {
282
                update_bounds(minv, maxv, v, &vms_vector::x);
283
                update_bounds(minv, maxv, v, &vms_vector::y);
284
                update_bounds(minv, maxv, v, &vms_vector::z);
285
        }
286
}
287
 
288
#define MORPH_RATE (f1_0*3)
289
 
290
constexpr fix morph_rate = MORPH_RATE;
291
 
292
static fix update_bounding_box_extent(const vms_vector &vp, const vms_vector &box_size, fix vms_vector::*const p, const fix entry_extent)
293
{
294
        if (!(vp.*p))
295
                return entry_extent;
296
        const auto box_size_p = box_size.*p;
297
        const auto abs_vp_p = abs(vp.*p);
298
        if (f2i(box_size_p) >= abs_vp_p / 2)
299
                return entry_extent;
300
        const fix t = fixdiv(box_size_p, abs_vp_p);
301
        return std::min(entry_extent, t);
302
}
303
 
304
static fix compute_bounding_box_extents(const vms_vector &vp, const vms_vector &box_size)
305
{
306
        fix k = INT32_MAX;
307
 
308
        k = update_bounding_box_extent(vp, box_size, &vms_vector::x, k);
309
        k = update_bounding_box_extent(vp, box_size, &vms_vector::y, k);
310
        k = update_bounding_box_extent(vp, box_size, &vms_vector::z, k);
311
 
312
        return k;
313
}
314
 
315
static void init_points(const polymodel &pm, const vms_vector *const box_size, const unsigned submodel_num, morph_data *const md)
316
{
317
        const auto &&sd = parse_model_data_header(pm, submodel_num);
318
        const unsigned startpoint = sd.startpoint;
319
        const unsigned endpoint = sd.startpoint + sd.nverts;
320
 
321
        md->submodel_active[submodel_num] = morph_data::submodel_state::animating;
322
        md->n_morphing_points[submodel_num] = 0;
323
        md->submodel_startpoints[submodel_num] = startpoint;
324
 
325
        const auto morph_times = md->get_morph_times();
326
        const auto morph_vecs = md->get_morph_vecs();
327
        const auto morph_deltas = md->get_morph_deltas();
328
        auto &&zr = zip(
329
                unchecked_partial_range(reinterpret_cast<const vms_vector *>(sd.body), sd.nverts),
330
                partial_range(morph_vecs, startpoint, endpoint),
331
                partial_range(morph_deltas, startpoint, endpoint),
332
                partial_range(morph_times, startpoint, endpoint)
333
        );
334
        range_for (auto &&z, zr)
335
        {
336
                const auto vp = &std::get<0>(z);
337
                auto &morph_vec = std::get<1>(z);
338
                auto &morph_delta = std::get<2>(z);
339
                auto &morph_time = std::get<3>(z);
340
                fix k;
341
 
342
                if (box_size && (k = compute_bounding_box_extents(*vp, *box_size) != INT32_MAX))
343
                        vm_vec_copy_scale(morph_vec, *vp, k);
344
                else
345
                        morph_vec = {};
346
 
347
                const fix dist = vm_vec_normalized_dir_quick(morph_delta, *vp, morph_vec);
348
                morph_time = fixdiv(dist, morph_rate);
349
 
350
                if (morph_time != 0)
351
                        md->n_morphing_points[submodel_num]++;
352
 
353
                vm_vec_scale(morph_delta, morph_rate);
354
        }
355
}
356
 
357
static void update_points(const polymodel &pm, const unsigned submodel_num, morph_data *const md)
358
{
359
        const auto &&sd = parse_model_data_header(pm, submodel_num);
360
        const unsigned startpoint = sd.startpoint;
361
        const unsigned endpoint = startpoint + sd.nverts;
362
 
363
        const auto morph_times = md->get_morph_times();
364
        const auto morph_vecs = md->get_morph_vecs();
365
        const auto morph_deltas = md->get_morph_deltas();
366
        auto &&zr = zip(
367
                unchecked_partial_range(reinterpret_cast<const vms_vector *>(sd.body), sd.nverts),
368
                partial_range(morph_vecs, startpoint, endpoint),
369
                partial_range(morph_deltas, startpoint, endpoint),
370
                partial_range(morph_times, startpoint, endpoint)
371
        );
372
        range_for (auto &&z, zr)
373
        {
374
                const auto vp = &std::get<0>(z);
375
                auto &morph_vec = std::get<1>(z);
376
                auto &morph_delta = std::get<2>(z);
377
                auto &morph_time = std::get<3>(z);
378
                if (morph_time)         //not done yet
379
                {
380
                        if ((morph_time -= FrameTime) <= 0) {
381
                                morph_vec = *vp;
382
                                morph_time = 0;
383
                                md->n_morphing_points[submodel_num]--;
384
                        }
385
                        else
386
                                vm_vec_scale_add2(morph_vec, morph_delta, FrameTime);
387
                }
388
        }
389
}
390
 
391
//process the morphing object for one frame
392
void do_morph_frame(object &obj)
393
{
394
        auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
395
        const auto umd = find_morph_data(LevelUniqueMorphObjectState, obj);
396
 
397
        if (!umd) {                                     //maybe loaded half-morphed from disk
398
                obj.flags |= OF_SHOULD_BE_DEAD;         //..so kill it
399
                return;
400
        }
401
        const auto md = umd->get();
402
        assert(md->obj == &obj);
403
 
404
        auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
405
        const polymodel &pm = Polygon_models[obj.rtype.pobj_info.model_num];
406
 
407
        const auto n_models = pm.n_models;
408
        range_for (const auto &&zi, zip(xrange(n_models), md->submodel_active, md->n_morphing_points))
409
        {
410
                const unsigned i = std::get<0>(zi);
411
                auto &submodel_active = std::get<1>(zi);
412
                if (submodel_active == morph_data::submodel_state::animating)
413
                {
414
                        update_points(pm,i,md);
415
                        const auto &n_morphing_points = std::get<2>(zi);
416
                        if (n_morphing_points == 0) {           //maybe start submodel
417
                                submodel_active = morph_data::submodel_state::visible;          //not animating, just visible
418
                                md->n_submodels_active--;               //this one done animating
419
                                range_for (const auto &&zt, zip(xrange(n_models), pm.submodel_parents))
420
                                {
421
                                        auto &submodel_parents = std::get<1>(zt);
422
                                        if (submodel_parents == i)
423
                                        {               //start this one
424
                                                const auto t = std::get<0>(zt);
425
                                                init_points(pm,nullptr,t,md);
426
                                                md->n_submodels_active++;
427
                                        }
428
                                }
429
                        }
430
                }
431
        }
432
 
433
        if (!md->n_submodels_active) {                  //done morphing!
434
 
435
                obj.control_type = md->morph_save_control_type;
436
                set_object_movement_type(obj, md->morph_save_movement_type);
437
 
438
                obj.render_type = RT_POLYOBJ;
439
 
440
                obj.mtype.phys_info = md->morph_save_phys_info;
441
                umd->reset();
442
        }
443
}
444
 
445
constexpr vms_vector morph_rotvel{0x4000,0x2000,0x1000};
446
 
447
void init_morphs()
448
{
449
        auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
450
        auto &morph_objects = LevelUniqueMorphObjectState.morph_objects;
451
        morph_objects = {};
452
}
453
 
454
//make the object morph
455
void morph_start(d_level_unique_morph_object_state &LevelUniqueMorphObjectState, d_level_shared_polygon_model_state &LevelSharedPolygonModelState, object_base &obj)
456
{
457
        vms_vector pmmin,pmmax;
458
        vms_vector box_size;
459
 
460
        auto &morph_objects = LevelUniqueMorphObjectState.morph_objects;
461
        const auto mob = morph_objects.begin();
462
        const auto moe = morph_objects.end();
463
        const auto mop = [](const morph_data::ptr &pmo) {
464
                if (!pmo)
465
                        return true;
466
                auto &mo = *pmo.get();
467
                return mo.obj->type == OBJ_NONE || mo.obj->signature != mo.Morph_sig;
468
        };
469
        const auto moi = std::find_if(mob, moe, mop);
470
 
471
        if (moi == moe)         //no free slots
472
                return;
473
 
474
        auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
475
        const morph_data::polymodel_idx pmi(obj.rtype.pobj_info.model_num);
476
        auto &pm = Polygon_models[pmi.idx];
477
 
478
        *moi = morph_data::create(obj, pm, pmi);
479
        morph_data *const md = moi->get();
480
 
481
        assert(obj.render_type == RT_POLYOBJ);
482
 
483
        md->morph_save_control_type = obj.control_type;
484
        md->morph_save_movement_type = obj.movement_type;
485
        md->morph_save_phys_info = obj.mtype.phys_info;
486
 
487
        assert(obj.control_type == CT_AI);              //morph objects are also AI objects
488
 
489
        obj.control_type = CT_MORPH;
490
        obj.render_type = RT_MORPH;
491
        obj.movement_type = MT_PHYSICS;         //RT_NONE;
492
 
493
        obj.mtype.phys_info.rotvel = morph_rotvel;
494
 
495
        find_min_max(pm,0,pmmin,pmmax);
496
 
497
        box_size.x = max(-pmmin.x,pmmax.x) / 2;
498
        box_size.y = max(-pmmin.y,pmmax.y) / 2;
499
        box_size.z = max(-pmmin.z,pmmax.z) / 2;
500
 
501
        //clear all points
502
        const auto morph_times = md->get_morph_times();
503
        std::fill(morph_times.begin(), morph_times.end(), fix());
504
        //clear all parts
505
        md->submodel_active = {};
506
 
507
        md->n_submodels_active = 1;
508
 
509
        //now, project points onto surface of box
510
 
511
        init_points(pm,&box_size,0,md);
512
}
513
 
514
static void draw_model(grs_canvas &canvas, polygon_model_points &robot_points, polymodel *const pm, const unsigned submodel_num, const submodel_angles anim_angles, g3s_lrgb light, morph_data *const md)
515
{
516
        std::array<unsigned, MAX_SUBMODELS> sort_list;
517
        unsigned sort_n;
518
 
519
 
520
        //first, sort the submodels
521
 
522
        sort_list[0] = submodel_num;
523
        sort_n = 1;
524
 
525
        const uint_fast32_t n_models = pm->n_models;
526
        range_for (const uint_fast32_t i, xrange(n_models))
527
                if (md->submodel_active[i] != morph_data::submodel_state::invisible && pm->submodel_parents[i] == submodel_num)
528
                {
529
                        const auto facing = g3_check_normal_facing(pm->submodel_pnts[i],pm->submodel_norms[i]);
530
                        if (!facing)
531
                                sort_list[sort_n] = i;
532
                        else {          //put at start
533
                                const auto b = sort_list.begin();
534
                                const auto e = std::next(b, sort_n);
535
                                std::move_backward(b, e, std::next(e));
536
                                sort_list[0] = i;
537
                        }
538
                        ++sort_n;
539
                }
540
 
541
        //now draw everything
542
 
543
        range_for (const auto mn, partial_const_range(sort_list, sort_n))
544
        {
545
                if (mn == submodel_num) {
546
                        std::array<bitmap_index, MAX_POLYOBJ_TEXTURES> texture_list_index;
547
                        std::array<grs_bitmap *, MAX_POLYOBJ_TEXTURES> texture_list;
548
                        for (unsigned i = 0; i < pm->n_textures; ++i)
549
                        {
550
                                const auto ptr = ObjBitmapPtrs[pm->first_texture + i];
551
                                const auto &bmp = ObjBitmaps[ptr];
552
                                texture_list_index[i] = bmp;
553
                                texture_list[i] = &GameBitmaps[bmp.index];
554
                        }
555
 
556
                        // Make sure the textures for this object are paged in...
557
                        range_for (auto &j, partial_const_range(texture_list_index, pm->n_textures))
558
                                PIGGY_PAGE_IN(j);
559
                        // Hmmm... cache got flushed in the middle of paging all these in,
560
                        // so we need to reread them all in.
561
                        // Make sure that they can all fit in memory.
562
                        const auto morph_vecs = md->get_morph_vecs();
563
                        g3_draw_morphing_model(canvas, &pm->model_data[pm->submodel_ptrs[submodel_num]], &texture_list[0], anim_angles, light, &morph_vecs[md->submodel_startpoints[submodel_num]], robot_points);
564
                }
565
                else {
566
                        const auto &&orient = vm_angles_2_matrix(anim_angles[mn]);
567
                        g3_start_instance_matrix(pm->submodel_offsets[mn], orient);
568
                        draw_model(canvas, robot_points,pm,mn,anim_angles,light,md);
569
                        g3_done_instance();
570
                }
571
        }
572
 
573
}
574
 
575
namespace dsx {
576
 
577
void draw_morph_object(grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const vmobjptridx_t obj)
578
{
579
        if (Newdemo_state == ND_STATE_PLAYBACK)
580
                return;
581
        polymodel *po;
582
 
583
        auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
584
        const auto umd = find_morph_data(LevelUniqueMorphObjectState, obj);
585
        if (!umd)
586
                throw std::runtime_error("missing morph data");
587
        const auto md = umd->get();
588
 
589
        auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
590
        po=&Polygon_models[obj->rtype.pobj_info.model_num];
591
 
592
        const auto light = compute_object_light(LevelUniqueLightState, obj);
593
 
594
        g3_start_instance_matrix(obj->pos, obj->orient);
595
        polygon_model_points robot_points;
596
        draw_model(canvas, robot_points, po, 0, obj->rtype.pobj_info.anim_angles, light, md);
597
 
598
        g3_done_instance();
599
 
600
        if (Newdemo_state == ND_STATE_RECORDING)
601
                newdemo_record_morph_frame(obj);
602
}
603
 
604
}