/*
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
 * It is copyright by its individual contributors, as recorded in the
 * project's Git history.  See COPYING.txt at the top level for license
 * terms and a link to the Git history.
 */

/*
 *
 * Polygon object interpreter
 *
 */

#include <stdexcept>
#include <stdlib.h>
#include "dxxsconf.h"
#include "dsx-ns.h"
#include "dxxerror.h"

#include "interp.h"
#include "console.h"
#include "common/3d/globvars.h"
#include "polyobj.h"
#include "byteutil.h"
#include "u_mem.h"

#include "compiler-range_for.h"
#include "d_range.h"
#include "d_zip.h"
#include "partial_range.h"

namespace dcx {

namespace {

constexpr std::integral_constant<unsigned, 0> OP_EOF{};   //eof
constexpr std::integral_constant<unsigned, 1> OP_DEFPOINTS{};   //defpoints
constexpr std::integral_constant<unsigned, 2> OP_FLATPOLY{};   //flat-shaded polygon
constexpr std::integral_constant<unsigned, 3> OP_TMAPPOLY{};   //texture-mapped polygon
constexpr std::integral_constant<unsigned, 4> OP_SORTNORM{};   //sort by normal
constexpr std::integral_constant<unsigned, 5> OP_RODBM{};   //rod bitmap
constexpr std::integral_constant<unsigned, 6> OP_SUBCALL{};   //call a subobject
constexpr std::integral_constant<unsigned, 7> OP_DEFP_START{};   //defpoints with start
constexpr std::integral_constant<unsigned, 8> OP_GLOW{};   //glow value for next poly

static inline int16_t *wp(uint8_t *p)
{
	return reinterpret_cast<int16_t *>(p);
}

static inline const int16_t *wp(const uint8_t *p)
{
	return reinterpret_cast<const int16_t *>(p);
}

static inline const vms_vector *vp(const uint8_t *p)
{
	return reinterpret_cast<const vms_vector *>(p);
}

static inline int16_t w(const uint8_t *p)
{
	return *wp(p);
}

constexpr vms_angvec zero_angles = {0,0,0};

class interpreter_ignore_op_defpoints
{
public:
	static void op_defpoints(const uint8_t *, uint16_t)
	{
	}
};

class interpreter_ignore_op_defp_start
{
public:
	static void op_defp_start(const uint8_t *, uint16_t)
	{
	}
};

class interpreter_ignore_op_flatpoly
{
public:
	static void op_flatpoly(const uint8_t *, uint16_t)
	{
	}
};

class interpreter_ignore_op_tmappoly
{
public:
	static void op_tmappoly(const uint8_t *, uint16_t)
	{
	}
};

class interpreter_ignore_op_rodbm
{
public:
	static void op_rodbm(const uint8_t *)
	{
	}
};

class interpreter_ignore_op_glow
{
public:
	static void op_glow(const uint8_t *)
	{
	}
};

}

}

class interpreter_track_model_extent
{
protected:
	const std::span<const uint8_t> model;
public:
	constexpr interpreter_track_model_extent(const std::span<const uint8_t> model) :
		model(model)
	{
	}
	uint8_t truncate_invalid_model(const unsigned line, uint8_t *const p, const std::ptrdiff_t d, const std::size_t size) const
	{
		const std::ptrdiff_t offset_from_base = p - model.data();
		const std::ptrdiff_t offset_of_value = offset_from_base + d;
		if (const auto model_length = model.size(); offset_of_value >= model_length || offset_of_value + size >= model_length)
		{
			auto &opref = *wp(p);
			const auto badop{opref};
			opref = OP_EOF;
			const unsigned long uml = model_length;
			const long ofb = offset_from_base;
			const long oov = offset_of_value;
			con_printf(CON_URGENT, "%s:%u: warning: invalid polymodel at %p with length %lu; opcode %u at offset %li references invalid offset %li; replacing invalid operation with EOF", __FILE__, line, model.data(), uml, badop, ofb, oov);
			return 1;
		}
		return 0;
	}
};

class interpreter_base
{
public:
	static uint16_t get_raw_opcode(const uint8_t *const p)
	{
		return w(p);
	}
	static uint_fast32_t translate_opcode(const uint8_t *, const uint16_t op)
	{
		return op;
	}
	static uint16_t get_op_subcount(const uint8_t *const p)
	{
		return w(p + 2);
	}
	[[noreturn]]
	static void op_default(const unsigned op, const uint8_t *const p)
	{
		char buf[64];
		snprintf(buf, sizeof(buf), "invalid polygon model opcode %u at %p", op, p);
		throw std::runtime_error(buf);
	}
};

namespace dsx {

namespace {

static int16_t init_model_sub(uint8_t *model_sub_ptr, const std::span<const uint8_t> model_base);
#if DXX_BUILD_DESCENT == 1
static void validate_model_sub(uint8_t *model_sub_ptr, const std::span<const uint8_t> model_base);
#endif

class g3_poly_get_color_state :
	public interpreter_ignore_op_defpoints,
	public interpreter_ignore_op_defp_start,
	public interpreter_ignore_op_tmappoly,
	public interpreter_ignore_op_rodbm,
	public interpreter_ignore_op_glow,
	public interpreter_base
{
public:
	int color{0};
	void op_flatpoly(const uint8_t *const p, const uint_fast32_t nv)
	{
		if (nv > MAX_POINTS_PER_POLY)
			return;
		if (g3_check_normal_facing(*vp(p+4),*vp(p+16)) > 0) {
#if DXX_BUILD_DESCENT == 1
			color = (w(p+28));
#elif DXX_BUILD_DESCENT == 2
			color = gr_find_closest_color_15bpp(packed_color_r5g5b5{w(p + 28)});
#endif
		}
	}
	void op_sortnorm(const uint8_t *const p)
	{
		const bool facing = g3_check_normal_facing(*vp(p+16),*vp(p+4)) > 0;
		color = g3_poly_get_color(facing ? p + w(p + 28) : p + w(p + 30));
	}
	void op_subcall(const uint8_t *const p)
	{
#if DXX_BUILD_DESCENT == 1
		color = g3_poly_get_color(p+w(p+16));
#elif DXX_BUILD_DESCENT == 2
		(void)p;
#endif
	}
};

class g3_interpreter_draw_base
{
protected:
	grs_bitmap *const *const model_bitmaps;
	polygon_model_points &Interp_point_list;
	grs_canvas &canvas;
	const tmap_drawer_type tmap_drawer_ptr;
	const submodel_angles anim_angles;
	const g3s_lrgb model_light;
private:
	void rotate(uint_fast32_t i, const vms_vector *const src, const uint_fast32_t n)
	{
		for (auto &&[dest, src] : zip(partial_range(Interp_point_list, i, i + n), unchecked_partial_range(src, n)))
			g3_rotate_point(dest, src);
	}
	void set_color_by_model_light(fix g3s_lrgb::*const c, g3s_lrgb &o, const fix color) const
	{
		o.*c = fixmul(color, model_light.*c);
	}
protected:
	template <std::size_t N>
		std::array<g3_draw_tmap_point *, N> prepare_point_list(const uint_fast32_t nv, const uint8_t *const p)
		{
			std::array<g3_draw_tmap_point *, N> point_list;
			for (uint_fast32_t i = 0; i < nv; ++i)
				point_list[i] = &Interp_point_list[wp(p + 30)[i]];
			return point_list;
		}
	g3s_lrgb get_noglow_light(const uint8_t *const p) const
	{
		g3s_lrgb light;
		const auto negdot = -vm_vec_build_dot(View_matrix.fvec, *vp(p + 16));
		const auto color = (f1_0 / 4) + ((negdot * 3) / 4);
		set_color_by_model_light(&g3s_lrgb::r, light, color);
		set_color_by_model_light(&g3s_lrgb::g, light, color);
		set_color_by_model_light(&g3s_lrgb::b, light, color);
		return light;
	}
	g3_interpreter_draw_base(grs_bitmap *const *const mbitmaps, polygon_model_points &plist, grs_canvas &ccanvas, const tmap_drawer_type tmap_drawer_ptr, const submodel_angles aangles, const g3s_lrgb &mlight) :
		model_bitmaps(mbitmaps), Interp_point_list(plist),
		canvas(ccanvas),
		tmap_drawer_ptr{tmap_drawer_ptr},
		anim_angles(aangles), model_light(mlight)
	{
	}
	void op_defpoints(const vms_vector *const src, const uint_fast32_t n)
	{
		rotate(0, src, n);
	}
	void op_defp_start(const uint8_t *const p, const vms_vector *const src, const uint_fast32_t n)
	{
		rotate(static_cast<int>(w(p + 4)), src, n);
	}
	static std::pair<uint16_t, uint16_t> get_sortnorm_offsets(const uint8_t *const p)
	{
		const uint16_t a = w(p + 30), b = w(p + 28);
		return (g3_check_normal_facing(*vp(p + 16), *vp(p + 4)) > 0)
			? std::make_pair(a, b)	//draw back then front
			: std::make_pair(b, a)	//not facing.  draw front then back
		;
	}
	void op_rodbm(const uint8_t *const p)
	{
		const g3_instance_context viewer_context{View_matrix, View_position};
		g3_draw_rod_tmap(canvas, *model_bitmaps[w(p + 2)], /* bot_point = */ g3_rotated_point{viewer_context, *vp(p + 20)}, w(p + 16), /* top_point = */ g3_rotated_point{viewer_context, *vp(p + 4)}, w(p + 32), g3s_lrgb{F1_0, F1_0, F1_0}, tmap_drawer_ptr);
	}
	void op_subcall(const uint8_t *const p, const glow_values_t *const glow_values)
	{
		auto &&ctx = g3_start_instance_angles(*vp(p + 4), anim_angles ? anim_angles[w(p + 2)] : zero_angles);
		g3_draw_polygon_model(model_bitmaps, Interp_point_list, canvas, tmap_drawer_ptr, anim_angles, model_light, glow_values, p + w(p + 16));
		g3_done_instance(ctx);
	}
};

class g3_draw_polygon_model_state :
	public interpreter_base,
	g3_interpreter_draw_base
{
	const glow_values_t *const glow_values;
	unsigned glow_num = ~0u;		//glow off by default
public:
	g3_draw_polygon_model_state(grs_bitmap *const *const mbitmaps, polygon_model_points &plist, grs_canvas &ccanvas, const tmap_drawer_type tmap_drawer_ptr, const submodel_angles aangles, const g3s_lrgb &mlight, const glow_values_t *const glvalues) :
		g3_interpreter_draw_base{mbitmaps, plist, ccanvas, tmap_drawer_ptr, aangles, mlight},
		glow_values(glvalues)
	{
	}
	void op_defpoints(const uint8_t *const p, const uint_fast32_t n)
	{
		g3_interpreter_draw_base::op_defpoints(vp(p + 4), n);
	}
	void op_defp_start(const uint8_t *const p, const uint_fast32_t n)
	{
		g3_interpreter_draw_base::op_defp_start(p, vp(p + 8), n);
	}
	void op_flatpoly(const uint8_t *const p, const uint_fast32_t nv)
	{
		if (nv > MAX_POINTS_PER_POLY)
			return;
#if DXX_BUILD_DESCENT == 2
		fix effective_glow_value;
		if (glow_values && glow_num < glow_values->size())
		{
			effective_glow_value = (*glow_values)[glow_num];
			if (effective_glow_value == -3)
				return;
		}
		else
			effective_glow_value = 0;
#endif
		if (g3_check_normal_facing(*vp(p+4),*vp(p+16)) > 0)
		{
#if DXX_BUILD_DESCENT == 1
				const uint8_t color = w(p + 28);
#elif DXX_BUILD_DESCENT == 2
				//					DPH: Now we treat this color as 15bpp
				const uint8_t color = effective_glow_value == -2
					? 255
					: gr_find_closest_color_15bpp(packed_color_r5g5b5{w(p + 28)});
#endif
				g3_draw_poly(canvas, nv, prepare_point_list<MAX_POINTS_PER_POLY>(nv, p), color);
		}
	}
	static g3s_lrgb get_glow_light(const fix c)
	{
		return {c, c, c};
	}
	void op_tmappoly(const uint8_t *const p, const uint_fast32_t nv)
	{
		if (nv > MAX_POINTS_PER_POLY)
			return;
		if (!(g3_check_normal_facing(*vp(p+4),*vp(p+16)) > 0))
			return;
		//calculate light from surface normal
		const auto &&light = (glow_values && glow_num < glow_values->size())
			? get_glow_light((*glow_values)[std::exchange(glow_num, -1)]) //yes glow
			: get_noglow_light(p); //no glow
		//now poke light into l values
		std::array<g3s_uvl, MAX_POINTS_PER_POLY> uvl_list;
		std::array<g3s_lrgb, MAX_POINTS_PER_POLY> lrgb_list;
		const fix average_light = (light.r + light.g + light.b) / 3;
		range_for (const uint_fast32_t i, xrange(nv))
		{
			lrgb_list[i] = light;
			uvl_list[i] = (reinterpret_cast<const g3s_uvl *>(p+30+((nv&~1)+1)*2))[i];
			uvl_list[i].l = average_light;
		}
		g3_draw_tmap(canvas, nv, prepare_point_list<MAX_POINTS_PER_POLY>(nv, p), uvl_list, lrgb_list, *model_bitmaps[w(p + 28)], tmap_drawer_ptr);
	}
	void op_sortnorm(const uint8_t *const p)
	{
		const auto &&offsets = get_sortnorm_offsets(p);
		const auto a{offsets.first};
		const auto b{offsets.second};
		g3_draw_polygon_model(model_bitmaps, Interp_point_list, canvas, tmap_drawer_ptr, anim_angles, model_light, glow_values, p + a);
		g3_draw_polygon_model(model_bitmaps, Interp_point_list, canvas, tmap_drawer_ptr, anim_angles, model_light, glow_values, p + b);
	}
	using g3_interpreter_draw_base::op_rodbm;
	void op_subcall(const uint8_t *const p)
	{
		g3_interpreter_draw_base::op_subcall(p, glow_values);
	}
	void op_glow(const uint8_t *const p)
	{
		glow_num = w(p+2);
	}
};

class g3_draw_morphing_model_state :
	public interpreter_ignore_op_glow,
	public interpreter_base,
	g3_interpreter_draw_base
{
	const vms_vector *const new_points;
	static constexpr const glow_values_t *glow_values = nullptr;
public:
	g3_draw_morphing_model_state(grs_bitmap *const *const mbitmaps, polygon_model_points &plist, grs_canvas &ccanvas, const tmap_drawer_type tmap_drawer_ptr, const submodel_angles aangles, const g3s_lrgb &mlight, const vms_vector *const npoints) :
		g3_interpreter_draw_base{mbitmaps, plist, ccanvas, tmap_drawer_ptr, aangles, mlight},
		new_points(npoints)
	{
	}
	void op_defpoints(const uint8_t *, const uint_fast32_t n)
	{
		g3_interpreter_draw_base::op_defpoints(new_points, n);
	}
	void op_defp_start(const uint8_t *const p, const uint_fast32_t n)
	{
		g3_interpreter_draw_base::op_defp_start(p, new_points, n);
	}
	void op_flatpoly(const uint8_t *const p, const uint_fast32_t nv)
	{
		int ntris;
		const uint8_t color = w(p+28);
		unsigned i;
		auto point_list = prepare_point_list<3>(i = 2, p);
		for (ntris=nv-2;ntris;ntris--) {
			point_list[2] = &Interp_point_list[wp(p+30)[i++]];
			g3_check_and_draw_poly(canvas, point_list, color);
			point_list[1] = point_list[2];
		}
	}
	void op_tmappoly(const uint8_t *const p, const uint_fast32_t nv)
	{
		if (nv > MAX_POINTS_PER_POLY)
			return;
		std::array<g3s_uvl, MAX_POINTS_PER_POLY> uvl_list;
		std::array<g3s_lrgb, MAX_POINTS_PER_POLY> lrgb_list;
		lrgb_list.fill(get_noglow_light(p));
		range_for (const uint_fast32_t i, xrange(nv))
			uvl_list[i] = (reinterpret_cast<const g3s_uvl *>(p+30+((nv&~1)+1)*2))[i];
		g3_draw_tmap(canvas, nv, prepare_point_list<MAX_POINTS_PER_POLY>(nv, p), uvl_list, lrgb_list, *model_bitmaps[w(p + 28)], tmap_drawer_ptr);
	}
	void op_sortnorm(const uint8_t *const p)
	{
		const auto &&offsets = get_sortnorm_offsets(p);
		auto &a = offsets.first;
		auto &b = offsets.second;
		g3_draw_morphing_model(canvas, tmap_drawer_ptr, p + a, model_bitmaps, anim_angles, model_light, new_points, Interp_point_list);
		g3_draw_morphing_model(canvas, tmap_drawer_ptr, p + b, model_bitmaps, anim_angles, model_light, new_points, Interp_point_list);
	}
	using g3_interpreter_draw_base::op_rodbm;
	void op_subcall(const uint8_t *const p)
	{
		g3_interpreter_draw_base::op_subcall(p, glow_values);
	}
};

template <typename T>
class model_load_state :
	public interpreter_track_model_extent,
	public interpreter_ignore_op_defpoints,
	public interpreter_ignore_op_defp_start,
	public interpreter_ignore_op_rodbm,
	public interpreter_ignore_op_glow,
	public interpreter_base
{
public:
	using interpreter_track_model_extent::interpreter_track_model_extent;
	int16_t init_bounded_model_sub(const unsigned line, uint8_t *const p, const std::ptrdiff_t d) const
	{
		if (truncate_invalid_model(line, p, d, sizeof(uint16_t)))
			return 0;
		return static_cast<const T *>(this)->init_sub_model(p + d);
	}
	void op_tmappoly(uint8_t *const p, const uint_fast32_t nv)
	{
		constexpr unsigned offset_texture = 28;
		(void)nv;
		Assert(nv > 2);		//must have 3 or more points
		if (truncate_invalid_model(__LINE__, p, offset_texture, sizeof(uint16_t)))
			return;
		static_cast<T *>(this)->update_texture(w(p + offset_texture));
	}
	void op_sortnorm(uint8_t *const p)
	{
		constexpr unsigned offset_submodel0 = 28;
		constexpr unsigned offset_submodel1 = offset_submodel0 + sizeof(uint16_t);
		if (truncate_invalid_model(__LINE__, p, offset_submodel1, sizeof(uint16_t)))
			return;
		const auto n0 = w(p + offset_submodel0);
		const auto h0 = init_bounded_model_sub(__LINE__, p, n0);
		const auto n1 = w(p + offset_submodel1);
		const auto h1 = init_bounded_model_sub(__LINE__, p, n1);
		const auto hm = std::max(h0, h1);
		static_cast<T *>(this)->update_texture(hm);
	}
	void op_subcall(uint8_t *const p)
	{
		constexpr unsigned offset_displacement = 16;
		if (truncate_invalid_model(__LINE__, p, offset_displacement, sizeof(uint16_t)))
			return;
		const auto n0 = w(p + offset_displacement);
		const auto h0 = init_bounded_model_sub(__LINE__, p, n0);
		static_cast<T *>(this)->update_texture(h0);
	}
};

class init_model_sub_state :
	public model_load_state<init_model_sub_state>
#if DXX_BUILD_DESCENT == 2
	, public interpreter_ignore_op_flatpoly
#endif
{
public:
	int16_t highest_texture_num = -1;
	using model_load_state::model_load_state;
	int16_t init_sub_model(uint8_t *const p) const
	{
		return init_model_sub(p, model);
	}
	void update_texture(const int16_t t)
	{
		if (highest_texture_num < t)
			highest_texture_num = t;
	}
#if DXX_BUILD_DESCENT == 1
	void op_flatpoly(uint8_t *const p, const uint_fast32_t nv) const
	{
		//must have 3 or more points
		if (nv <= 2)
			return;
		const auto p16 = wp(p + 28);
		*p16 = static_cast<short>(gr_find_closest_color_15bpp(packed_color_r5g5b5{*p16}));
	}
#endif
};

#if DXX_BUILD_DESCENT == 1
class validate_model_sub_state :
	public model_load_state<validate_model_sub_state>,
	public interpreter_ignore_op_flatpoly
{
public:
	using model_load_state::model_load_state;
	unsigned init_sub_model(uint8_t *const p) const
	{
		validate_model_sub(p, model);
		return 0;
	}
	void update_texture(int16_t)
	{
	}
};
#endif

template <typename State>
[[nodiscard]]
static std::size_t dispatch_polymodel_op(const auto p, State &state, const uint_fast32_t op)
{
	switch (op)
	{
		case OP_DEFPOINTS: {
			const auto n = state.get_op_subcount(p);
			const std::size_t record_size = n * sizeof(vms_vector) + 4;
			state.op_defpoints(p, n);
			return record_size;
		}
		case OP_DEFP_START: {
			const auto n = state.get_op_subcount(p);
			const std::size_t record_size = n * sizeof(vms_vector) + 8;
			state.op_defp_start(p, n);
			return record_size;
		}
		case OP_FLATPOLY: {
			const auto n = state.get_op_subcount(p);
			const std::size_t record_size = 30 + ((n & ~1) + 1) * 2;
			state.op_flatpoly(p, n);
			return record_size;
		}
		case OP_TMAPPOLY: {
			const auto n = state.get_op_subcount(p);
			const std::size_t record_size = 30 + ((n & ~1) + 1) * 2 + n * 12;
			state.op_tmappoly(p, n);
			return record_size;
		}
		case OP_SORTNORM: {
			constexpr std::size_t record_size{32};
			state.op_sortnorm(p);
			return record_size;
		}
		case OP_RODBM: {
			constexpr std::size_t record_size{36};
			state.op_rodbm(p);
			return record_size;
		}
		case OP_SUBCALL: {
			constexpr std::size_t record_size{20};
			state.op_subcall(p);
			return record_size;
		}
		case OP_GLOW: {
			constexpr std::size_t record_size{4};
			state.op_glow(p);
			return record_size;
		}
		default:
			state.op_default(op, p);
	}
}

static auto iterate_polymodel(auto p, auto &state)
{
	for (uint16_t op; (op = state.get_raw_opcode(p)) != OP_EOF;)
		p += dispatch_polymodel_op(p, state, state.translate_opcode(p, op));
	return p;
}

}

}

#if DXX_WORDS_BIGENDIAN
namespace dcx {

namespace {

static inline fix *fp(uint8_t *p)
{
	return reinterpret_cast<fix *>(p);
}

static inline vms_vector *vp(uint8_t *p)
{
	return reinterpret_cast<vms_vector *>(p);
}

static void short_swap(short *s)
{
	*s = SWAPSHORT(*s);
}

static void fix_swap(fix &f)
{
	f = SWAPINT(f);
}

static void fix_swap(fix *f)
{
	fix_swap(*f);
}

static void vms_vector_swap(vms_vector &v)
{
	fix_swap(v.x);
	fix_swap(v.y);
	fix_swap(v.z);
}

class swap_polygon_model_data_state : public interpreter_base
{
public:
	static uint_fast32_t translate_opcode(uint8_t *const p, const uint16_t op)
	{
		return *wp(p) = INTEL_SHORT(op);
	}
	static uint16_t get_op_subcount(const uint8_t *const p)
	{
		return SWAPSHORT(w(p + 2));
	}
	static void op_defpoints(uint8_t *const p, const uint_fast32_t n)
	{
		*wp(p + 2) = n;
		range_for (const uint_fast32_t i, xrange(n))
			vms_vector_swap(*vp((p + 4) + (i * sizeof(vms_vector))));
	}
	static void op_defp_start(uint8_t *const p, const uint_fast32_t n)
	{
		*wp(p + 2) = n;
		short_swap(wp(p + 4));
		range_for (const uint_fast32_t i, xrange(n))
			vms_vector_swap(*vp((p + 8) + (i * sizeof(vms_vector))));
	}
	static void op_flatpoly(uint8_t *const p, const uint_fast32_t n)
	{
		*wp(p + 2) = n;
		vms_vector_swap(*vp(p + 4));
		vms_vector_swap(*vp(p + 16));
		short_swap(wp(p+28));
		for (uint_fast32_t i = 0; i < n; ++i)
			short_swap(wp(p + 30 + (i * 2)));
	}
	static void op_tmappoly(uint8_t *const p, const uint_fast32_t n)
	{
		*wp(p + 2) = n;
		vms_vector_swap(*vp(p + 4));
		vms_vector_swap(*vp(p + 16));
		range_for (const uint_fast32_t i, xrange(n)) {
			const auto uvl_val = reinterpret_cast<g3s_uvl *>((p+30+((n&~1)+1)*2) + (i * sizeof(g3s_uvl)));
			fix_swap(&uvl_val->u);
			fix_swap(&uvl_val->v);
		}
		short_swap(wp(p+28));
		range_for (const uint_fast32_t i, xrange(n))
			short_swap(wp(p + 30 + (i * 2)));
	}
	void op_sortnorm(uint8_t *const p)
	{
		vms_vector_swap(*vp(p + 4));
		vms_vector_swap(*vp(p + 16));
		short_swap(wp(p + 28));
		short_swap(wp(p + 30));
		swap_polygon_model_data(p + w(p+28));
		swap_polygon_model_data(p + w(p+30));
	}
	static void op_rodbm(uint8_t *const p)
	{
		vms_vector_swap(*vp(p + 20));
		vms_vector_swap(*vp(p + 4));
		short_swap(wp(p+2));
		fix_swap(fp(p + 16));
		fix_swap(fp(p + 32));
	}
	void op_subcall(uint8_t *const p)
	{
		short_swap(wp(p+2));
		vms_vector_swap(*vp(p+4));
		short_swap(wp(p+16));
		swap_polygon_model_data(p + w(p+16));
	}
	static void op_glow(uint8_t *const p)
	{
		short_swap(wp(p + 2));
	}
};

}

void swap_polygon_model_data(uint8_t *const data)
{
	swap_polygon_model_data_state state;
	iterate_polymodel(data, state);
}

}
#endif

#if DXX_WORDS_NEED_ALIGNMENT

#define MAX_CHUNKS 100 // increase if insufficent
namespace dcx {

namespace {

/*
 * A chunk struct (as used for alignment) contains all relevant data
 * concerning a piece of data that may need to be aligned.
 * To align it, we need to copy it to an aligned position,
 * and update all pointers  to it.
 * (Those pointers are actually offsets
 * relative to start of model_data) to it.
 */
struct chunk
{
	const uint8_t *old_base; // where the offset sets off from (relative to beginning of model_data)
	uint8_t *new_base; // where the base is in the aligned structure
	short offset; // how much to add to base to get the address of the offset
	short correction; // how much the value of the offset must be shifted for alignment
};

static void add_chunk(const uint8_t *old_base, uint8_t *new_base, int offset,
	       chunk *chunk_list, int *no_chunks)
{
	Assert(*no_chunks + 1 < MAX_CHUNKS); //increase MAX_CHUNKS if you get this
	chunk_list[*no_chunks].old_base = old_base;
	chunk_list[*no_chunks].new_base = new_base;
	chunk_list[*no_chunks].offset = offset;
	chunk_list[*no_chunks].correction = 0;
	(*no_chunks)++;
}

class get_chunks_state :
	public interpreter_ignore_op_defpoints,
	public interpreter_ignore_op_defp_start,
	public interpreter_ignore_op_flatpoly,
	public interpreter_ignore_op_tmappoly,
	public interpreter_ignore_op_rodbm,
	public interpreter_ignore_op_glow,
	public interpreter_base
{
	const uint8_t *const data;
	uint8_t *const new_data;
	chunk *const list;
	int *const no;
public:
	get_chunks_state(const uint8_t *const p, uint8_t *const ndata, chunk *const l, int *const n) :
		data(p), new_data(ndata), list(l), no(n)
	{
	}
	static uint_fast32_t translate_opcode(const uint8_t *, const uint16_t op)
	{
		return INTEL_SHORT(op);
	}
	static uint16_t get_op_subcount(const uint8_t *const p)
	{
		return GET_INTEL_SHORT(p + 2);
	}
	void op_sortnorm(const uint8_t *const p)
	{
		add_chunk(p, p - data + new_data, 28, list, no);
		add_chunk(p, p - data + new_data, 30, list, no);
	}
	void op_subcall(const uint8_t *const p)
	{
		add_chunk(p, p - data + new_data, 16, list, no);
	}
};

/*
 * finds what chunks the data points to, adds them to the chunk_list, 
 * and returns the length of the current chunk
 */
int get_chunks(const uint8_t *data, uint8_t *new_data, chunk *list, int *no)
{
	get_chunks_state state(data, new_data, list, no);
	auto p = iterate_polymodel(data, state);
	return p + 2 - data;
}

static const uint8_t *old_dest(const chunk &o) // return where chunk is (in unaligned struct)
{
	return GET_INTEL_SHORT(&o.old_base[o.offset]) + o.old_base;
}
static uint8_t *new_dest(const chunk &o) // return where chunk is (in aligned struct)
{
	return GET_INTEL_SHORT(&o.old_base[o.offset]) + o.new_base + o.correction;
}

/*
 * find chunk with smallest address
 */
static int get_first_chunks_index(chunk *chunk_list, int no_chunks)
{
	int first_index{0};
	Assert(no_chunks >= 1);
	for (int i = 1; i < no_chunks; i++)
		if (old_dest(chunk_list[i]) < old_dest(chunk_list[first_index]))
			first_index = i;
	return first_index;
}

}

void align_polygon_model_data(polymodel *pm)
{
	int chunk_len;
	int total_correction{0};
	chunk cur_ch;
	chunk ch_list[MAX_CHUNKS];
	int no_chunks{0};
	constexpr unsigned SHIFT_SPACE = 500;	// increase if insufficient
	int tmp_size = pm->model_data_size + SHIFT_SPACE;
	// where we build the aligned version of pm->model_data
	const auto tmp = std::make_unique<uint8_t[]>(tmp_size);

	//start with first chunk (is always aligned!)
	const uint8_t *cur_old = pm->model_data.get();
	auto cur_new = tmp.get();
	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--;
		for (int i = first_index; i < no_chunks; i++)
			ch_list[i] = ch_list[i + 1];
		// if (new) address unaligned:
		const uintptr_t u = reinterpret_cast<uintptr_t>(new_dest(cur_ch));
		if (u % 4L != 0) {
			// calculate how much to move to be aligned
			short to_shift = 4 - u % 4L;
			// correct chunks' addresses
			cur_ch.correction += to_shift;
			for (int i = 0; i < no_chunks; i++)
				ch_list[i].correction += to_shift;
			total_correction += to_shift;
			Assert(reinterpret_cast<uintptr_t>(new_dest(cur_ch)) % 4L == 0);
			Assert(total_correction <= SHIFT_SPACE); // if you get this, increase SHIFT_SPACE
		}
		//write (corrected) chunk for current chunk:
		*(reinterpret_cast<short *>(cur_ch.new_base + cur_ch.offset))
			= INTEL_SHORT(static_cast<short>(cur_ch.correction + GET_INTEL_SHORT(cur_ch.old_base + cur_ch.offset)));
		//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
		for (auto &sp : pm->submodel_ptrs)
			if (&pm->model_data[sp] >= cur_old &&
				&pm->model_data[sp] < cur_old + chunk_len)
				sp += (cur_new - tmp.get()) - (cur_old - pm->model_data.get());
	}
	pm->model_data_size += total_correction;
	pm->model_data = std::make_unique<uint8_t[]>(pm->model_data_size);
	memcpy(pm->model_data.get(), tmp.get(), pm->model_data_size);
}

}
#endif //def WORDS_NEED_ALIGNMENT

namespace dsx {

// check a polymodel for it's color and return it
int g3_poly_get_color(const uint8_t *p)
{
	g3_poly_get_color_state state;
	iterate_polymodel(p, state);
	return state.color;
}

//calls the object interpreter to render an object.  The object renderer
//is really a separate pipeline.
void g3_draw_polygon_model(grs_bitmap *const *const model_bitmaps, polygon_model_points &Interp_point_list, grs_canvas &canvas, const tmap_drawer_type tmap_drawer_ptr, const submodel_angles anim_angles, const g3s_lrgb model_light, const glow_values_t *const glow_values, const uint8_t *const p)
{
	g3_draw_polygon_model_state state(model_bitmaps, Interp_point_list, canvas, tmap_drawer_ptr, anim_angles, model_light, glow_values);
	iterate_polymodel(p, state);
}

#ifndef NDEBUG
static int nest_count;
#endif

//alternate interpreter for morphing object
void g3_draw_morphing_model(grs_canvas &canvas, const tmap_drawer_type tmap_drawer_ptr, const uint8_t *const p, grs_bitmap *const *const model_bitmaps, const submodel_angles anim_angles, const g3s_lrgb model_light, const vms_vector *new_points, polygon_model_points &Interp_point_list)
{
	g3_draw_morphing_model_state state(model_bitmaps, Interp_point_list, canvas, tmap_drawer_ptr, anim_angles, model_light, new_points);
	iterate_polymodel(p, state);
}

namespace {

static int16_t init_model_sub(uint8_t *const model_sub_ptr, const std::span<const uint8_t> model_base)
{
	init_model_sub_state state(model_base);
	Assert(++nest_count < 1000);
	iterate_polymodel(model_sub_ptr, state);
	return state.highest_texture_num;
}

}

//init code for bitmap models
int16_t g3_init_polygon_model(const std::span<uint8_t> model)
{
	#ifndef NDEBUG
	nest_count = 0;
	#endif
	return init_model_sub(model.data(), model);
}

#if DXX_BUILD_DESCENT == 1
namespace {

static void validate_model_sub(uint8_t *const model_sub_ptr, const std::span<const uint8_t> model_base)
{
	validate_model_sub_state state(model_base);
	assert(++nest_count < 1000);
	iterate_polymodel(model_sub_ptr, state);
}

}

void g3_validate_polygon_model(const std::span<uint8_t> model)
{
#ifndef NDEBUG
	nest_count = 0;
#endif
	return validate_model_sub(model.data(), model);
}
#endif

}
