From 596b4ce786e14ffa437e35d1fb0c9c33af04bc69 Mon Sep 17 00:00:00 2001 From: Zeno Rogue Date: Sun, 21 Apr 2019 01:21:03 +0200 Subject: [PATCH] 3d:: converting the old vector graphics into 3D models --- 3d-models.cpp | 793 ++++++++++++++++++++++++++++++++++++++++++++++++ compileunits.h | 1 + earcut.hpp | 794 +++++++++++++++++++++++++++++++++++++++++++++++++ graph.cpp | 37 ++- polygons.cpp | 16 + 5 files changed, 1628 insertions(+), 13 deletions(-) create mode 100644 3d-models.cpp create mode 100644 earcut.hpp diff --git a/3d-models.cpp b/3d-models.cpp new file mode 100644 index 00000000..6166952f --- /dev/null +++ b/3d-models.cpp @@ -0,0 +1,793 @@ +// HyperRogue +// This file contains the routines to convert HyperRogue's old vector graphics into 3D models + +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +#include "earcut.hpp" + +namespace hr { + +#define S (scalefactor / 0.805578) +#define SH (scalefactor / 0.805578 * geom3::height_width / 1.5) + +hyperpoint shcenter; + +vector get_shape(hpcshape sh) { + vector res; + for(int i=sh.s; i& vh) { + hyperpoint h = Hypc; + using namespace hyperpoint_vec; + for(auto h1: vh) h = h + h1; + return normalize(h); + } + +ld zc(ld z) { return geom3::human_height * (z - 0.5); } + +transmatrix zpush(ld z) { + return cpush(2, z); + } + +void add_cone(ld z0, const vector& vh, ld z1) { + last->flags |= POLY_TRIANGLES; + for(int i=0; i vh0, ld z1, vector vh1) { + last->flags |= POLY_TRIANGLES; + for(int i=0; i vh0, ld z1, vector vh1) { + last->flags |= POLY_TRIANGLES; + + struct mixed { + ld angle; + int owner; + hyperpoint h; + mixed(ld a, int o, hyperpoint _h) : angle(a), owner(o), h(_h) {} + }; + + transmatrix T0 = gpushxto0(get_center(vh0)); + transmatrix T1 = gpushxto0(get_center(vh1)); + + vector pairs; + for(auto h: vh0) pairs.emplace_back(atan2(T0*h), 0, h); + for(auto h: vh1) pairs.emplace_back(atan2(T1*h), 1, h); + sort(pairs.begin(), pairs.end(), [&] (const mixed p, const mixed q) { return p.angle < q.angle; }); + + hyperpoint lasts[2]; + for(auto pp: pairs) lasts[pp.owner] = pp.h; + + for(auto pp: pairs) { + int id = pp.owner; + hpcpush(zpush(z0) * lasts[0]); + hpcpush(zpush(z1) * lasts[1]); + hpcpush(zpush(id == 0 ? z0 : z1) * pp.h); + lasts[id] = pp.h; + } + } + +void shift_last(ld z) { + for(int i=last->s; i scaleshape(const vector& vh, ld s) { + vector res; + using namespace hyperpoint_vec; + for(hyperpoint h: vh) res.push_back(normalize(h * s + shcenter * (1-s))); + return res; + } + +void make_ha_3d(hpcshape& sh, bool isarmor, ld scale) { + shcenter = C0; + + auto groin = get_shape(shHumanGroin); + auto body = get_shape(shPBodyOnly); + auto neck = get_shape(shHumanNeck); + auto hand = get_shape(shPBodyHand); + auto arm = get_shape(shPBodyArm); + groin = scaleshape(groin, scale); + neck = scaleshape(neck, scale); + + auto fullbody = get_shape(sh); + + auto body7 = body[7]; + auto body26 = body[26]; + body.clear(); + + bool foundplus = false, foundminus = false; + for(hyperpoint h: fullbody) { + if(h[1] > 0.14 * S) { + if(foundplus) ; + else foundplus = true, body.push_back(body7); + } + else if(h[1] < -0.14 * S) { + if(foundminus) ; + else foundminus = true, body.push_back(body26); + } + else body.push_back(h); + } + + auto arm8 = arm[8]; + bool armused = false; + arm.clear(); + for(hyperpoint h: fullbody) { + if(h[1] < 0.08 * S) ; + else if(h[0] > -0.03 * S) { + if(armused) ; + else armused = true, arm.push_back(arm8); + } + else arm.push_back(h); + } + + auto hand0 = hand[0]; + hand.clear(); + hand.push_back(hand0); + for(hyperpoint h: fullbody) { + if(h[1] + h[0] > 0.13 * S) hand.push_back(h); + } + + bshape(sh, PPR::MONSTER_BODY); + add_cone(zc(0.4), groin, zc(0.36)); + add_prism_sync(zc(0.4), groin, zc(0.6), groin); + add_prism(zc(0.6), groin, zc(0.7), body); + add_prism(zc(0.7), body, zc(0.8), neck); + + add_cone(zc(0.8), neck, zc(0.83)); + + int at0 = isize(hpc); + ld h = geom3::human_height; + + if(isize(arm) > 3) { + shcenter = get_center(arm); + int arm0 = isize(hpc); + add_prism_sync(geom3::BODY - h*.03, arm, geom3::BODY + h*.03, arm); + add_cone(geom3::BODY + h*.03, arm, geom3::BODY + h*.05); + add_cone(geom3::BODY - h*.03, arm, geom3::BODY - h*.05); + int arm1 = isize(hpc); + for(int i=arm0; i 3) { + shcenter = get_center(hand); + add_cone(geom3::BODY, hand, geom3::BODY + 0.05 * geom3::human_height); + add_cone(geom3::BODY, hand, geom3::BODY - 0.05 * geom3::human_height); + } + + int at1 = isize(hpc); + for(int i=at0; i hs, int kind) { + ld ds[3]; + ds[0] = hdist(hs[0], hs[1]); + ds[1] = hdist(hs[1], hs[2]); + ds[2] = hdist(hs[2], hs[0]); + ld maxds = 0; + for(int i=0; i<3; i++) maxds = max(ds[i], maxds) - 1e-3; + + if(maxds > 0.02*S) for(int i=0; i<3; i++) { + int j = (i+1) % 3; + int k = (j+1) % 3; + if(hdist(hs[i], hs[j]) > maxds) { + auto hm = mid(hs[i], hs[j]); + addtri(make_array(hm, hs[i], hs[k]), kind); + addtri(make_array(hm, hs[j], hs[k]), kind); + return; + } + } + + if(kind) { + hyperpoint ht[3]; + ld hsh[3]; + ld shi[3]; + bool ok = true; + for(int s=0; s<3; s++) { + hs[s] = normalize(hs[s]); + hyperpoint h = hs[s]; + ld zz = zc(0.78); + hsh[s] = abs(h[1]); + zz -= h[1] * h[1] / 0.14 / 0.14 * 0.01 / S / S * SH; + zz -= h[0] * h[0] / 0.10 / 0.10 * 0.01 / S / S * SH; + if(abs(h[1]) > 0.14*S) ok = false, zz -= (abs(h[1])/S - 0.14) * SH; + if(abs(h[0]) > 0.08*S) ok = false, zz -= (abs(h[0])/S - 0.08) * (abs(h[0])/S - 0.08) * 25 * SH; + hpcpush(ht[s] = zpush(zz) * h); + if(hsh[s] < 0.1*S) shi[s] = -0.5; + else if(hsh[s] < 0.12*S) shi[s] = -0.1 - 0.4 * (hsh[s]/S - 0.1) / (0.12 - 0.1); + else shi[s] = -0.1; + shi[s] *= geom3::human_height; + } + if(ok && kind == 1) for(int i=0; i<3; i++) { + int j = (i+1) % 3; + hpcpush(ht[i]); + hpcpush(ht[j]); + hpcpush(zpush(shi[i]) * ht[i]); + hpcpush(ht[j]); + hpcpush(zpush(shi[i]) * ht[i]); + hpcpush(zpush(shi[i]) * ht[j]); + } + } + else { + for(int s=0; s<3; s++) { + hyperpoint h = hs[s]; + ld zz = zc(0.925); + if(h[0] < -0.05*S) zz += (h[0]/S + 0.05) * SH; + if(hdist0(h) <= 0.0501*S) { + zz += sqrt(0.0026 - pow(hdist0(h)/S, 2)) * SH; + } + hpcpush(zpush(zz) * h); + } + } + } + +void make_armor_3d(hpcshape& sh, int kind = 1) { + + auto body = get_shape(sh); + vector >> pts(2); + + for(hyperpoint h: body) { + array p; + p[0] = h[0] / h[3]; + p[1] = h[1] / h[3]; + pts[0].emplace_back(p); + } + + bshape(sh, sh.prio); + + vector indices = mapbox::earcut (pts); + + last->flags |= POLY_TRIANGLES; + + last->flags |= POLY_TRIANGLES; + for(int k=0; ks; iflags |= POLY_TRIANGLES; + for(int d=0; d<360; d+=30) + for(int u=-90; u<=90; u+=30) { + addpt(d, u); + addpt(d+30, u); + addpt(d, u+30); + addpt(d+30, u+30); + addpt(d+30, u); + addpt(d, u+30); + } + + add_texture(shPHeadOnly); + shift_last(-geom3::HEAD - 0.01 * SH); + } + + +void make_head_3d(hpcshape& sh) { + auto head = get_shape(sh); + vector >> pts(2); + + for(hyperpoint h: head) { + array p; + p[0] = h[0] / h[3]; + p[1] = h[1] / h[3]; + pts[0].emplace_back(p); + } + + array zero = {0,0}; + pts[1].emplace_back(zero); + head.push_back(C0); + + bshape(sh, sh.prio); + + vector indices = mapbox::earcut (pts); + + last->flags |= POLY_TRIANGLES; + for(int k=0; k notail; + ld minx = 9; + for(hyperpoint h: body) minx = min(minx, h[0]); + for(hyperpoint h: body) if(h[0] >= minx + tail) notail.push_back(h); + + auto body8 = scaleshape(notail, 0.8); + + bshape(sh, PPR::MONSTER_BODY); + add_prism(zc(0.4), body8, zc(0.45), body); + add_prism(zc(0.45), body, zc(0.5), notail); + add_prism_sync(zc(0.6), body8, zc(0.5), notail); + add_cone(zc(0.4), body8, zc(0.36)); + add_cone(zc(0.6), body8, zc(0.64)); + add_texture(sh); + } + +void make_ahead_3d(hpcshape& sh) { + auto body = get_shape(sh); + shcenter = get_center(body); + auto body8 = scaleshape(body, 0.5); + + bshape(sh, PPR::MONSTER_BODY); + add_prism_sync(zc(0.4), body8, zc(0.5), body); + add_prism_sync(zc(0.6), body8, zc(0.5), body); + add_cone(zc(0.4), body8, zc(0.36)); + add_cone(zc(0.6), body8, zc(0.64)); + add_texture(sh); + } + +void make_skeletal(hpcshape& sh, ld push = 0) { + auto body = get_shape(sh); + shcenter = get_center(body); + + bshape(sh, PPR::MONSTER_BODY); + add_prism_sync(zc(0.48), body, zc(0.5), body); + add_prism_sync(zc(0.52), body, zc(0.5), body); + add_cone(zc(0.48), body, zc(0.47)); + add_cone(zc(0.52), body, zc(0.53)); + add_texture(sh); + shift_last(-push); + } + +void make_revolution(hpcshape& sh, int mx = 180, ld push = 0) { + auto body = get_shape(sh); + bshape(sh, PPR::MONSTER_BODY); + int step = (mx == 360 ? 24 : 10); + for(int i=0; iflags |= POLY_TRIANGLES; + add_texture(sh); + shift_last(-push); + } + +void make_revolution_cut(hpcshape &sh, int each = 180, ld push = 0, ld width = 99) { + auto body = get_shape(sh); + int n = isize(body) / 2; + + auto gbody = body; + + int it = 0; + + vector nextid(n); + vector lastid(n); + vector stillin(n, true); + for(int i=0; i gbody[lastid[i]][0] && gbody[i][0] > gbody[nextid[i]][0]) || abs(gbody[i][1]) > width) + if(abs(gbody[i][1]) > cv) + cand = i, cv = abs(gbody[i][1]); + } + if(cand == -1) break; + int i = cand; + lastid[nextid[i]] = lastid[i]; + nextid[lastid[i]] = nextid[i]; + stillin[i] = false; + } + + for(int i=n; i>=0; i--) if(!stillin[i] && !stillin[nextid[i]]) nextid[i] = nextid[nextid[i]]; + for(int i=0; iflags |= POLY_TRIANGLES; + add_texture(sh); + shift_last(-push); + } + +void disable(hpcshape& sh) { + sh.s = sh.e = 0; + } + +void make_3d_models() { + if(DIM == 2) return; + shcenter = C0; + + if(floor_textures) { + auto& utt = models_texture; + utt.tvertices.clear(); + utt.texture_id = floor_textures->renderedTexture; + } + + make_humanoid_3d(shPBody); + make_humanoid_3d(shYeti); + make_humanoid_3d(shFemaleBody); + make_humanoid_3d(shRaiderBody); + make_humanoid_3d(shSkeletonBody); + make_humanoid_3d(shFatBody); + make_humanoid_3d(shWaterElemental); + make_humanoid_3d(shJiangShi); + + // shFatBody = shPBody; + // shFemaleBody = shPBody; + // shRaiderBody = shPBody; + // shJiangShi = shPBody; + + make_head_3d(shFemaleHair); + make_head_3d(shPHead); + make_head_3d(shTurban1); + make_head_3d(shTurban2); + make_head_3d(shAztecHead); + make_head_3d(shAztecCap); + make_head_3d(shVikingHelmet); + make_head_3d(shRaiderHelmet); + make_head_3d(shWestHat1); + make_head_3d(shWestHat2); + make_head_3d(shWitchHair); + make_head_3d(shBeautyHair); + make_head_3d(shFlowerHair); + make_head_3d(shGolemhead); + make_head_3d(shPirateHood); + make_head_3d(shEyepatch); + make_head_3d(shSkull); + make_head_3d(shRatHead); + make_head_3d(shDemon); + make_head_3d(shGoatHead); + make_head_3d(shRatCape1); + make_head_3d(shJiangShiCap1); + make_head_3d(shJiangShiCap2); + make_head_3d(shTerraHead); + + make_armor_3d(shKnightArmor); + make_armor_3d(shKnightCloak, 2); + make_armor_3d(shPrinceDress); + make_armor_3d(shPrincessDress, 2); + make_armor_3d(shTerraArmor1); + make_armor_3d(shTerraArmor2); + make_armor_3d(shTerraArmor3); + make_armor_3d(shSuspenders); + make_armor_3d(shJiangShiDress); + make_armor_3d(shFemaleDress); + make_armor_3d(shWightCloak, 2); + make_armor_3d(shRaiderArmor); + make_armor_3d(shRaiderShirt); + make_armor_3d(shArmor); + make_armor_3d(shRatCape2, 2); + + make_armor_3d(shHood, 2); + + make_foot_3d(shHumanFoot); + make_foot_3d(shYetiFoot); + make_skeletal(shSkeletalFoot); + + make_paw_3d(shWolfFrontPaw, shWolfFrontLeg); + make_paw_3d(shWolfRearPaw, shWolfRearLeg); + make_paw_3d(shDogFrontPaw, shDogFrontLeg); + make_paw_3d(shDogRearPaw, shDogRearLeg); + + // make_abody_3d(shWolfBody, 0.01); + // make_ahead_3d(shWolfHead); + // make_ahead_3d(shFamiliarHead); + make_revolution_cut(shWolfBody, 30, 0, 0.01*S); + make_revolution_cut(shWolfHead, 180, geom3::AHEAD - geom3::ABODY); + make_revolution_cut(shFamiliarHead, 30, geom3::AHEAD - geom3::ABODY); + + // make_abody_3d(shDogTorso, 0.01); + make_revolution_cut(shDogTorso, 30); + make_revolution_cut(shDogHead, 180, geom3::AHEAD - geom3::ABODY); + // make_ahead_3d(shDogHead); + + // make_abody_3d(shCatBody, 0.05); + // make_ahead_3d(shCatHead); + make_revolution_cut(shCatBody, 30); + make_revolution_cut(shCatHead, 180, geom3::AHEAD - geom3::ABODY); + + make_paw_3d(shReptileFrontFoot, shReptileFrontLeg); + make_paw_3d(shReptileRearFoot, shReptileRearLeg); + make_abody_3d(shReptileBody, -1); + // make_ahead_3d(shReptileHead); + make_revolution_cut(shReptileHead, 180, geom3::AHEAD - geom3::ABODY); + + make_paw_3d(shBullFrontHoof, shBullFrontHoof); + make_paw_3d(shBullRearHoof, shBullRearHoof); + // make_abody_3d(shBullBody, 0.05); + // make_ahead_3d(shBullHead); + // make_ahead_3d(shBullHorn); + make_revolution_cut(shBullBody, 180); + make_revolution_cut(shBullHead, 60, geom3::AHEAD - geom3::ABODY); + shift_shape(shBullHorn, -(geom3::AHEAD - geom3::ABODY)); + // make_revolution_cut(shBullHorn, 180, geom3::AHEAD - geom3::ABODY); + + make_paw_3d(shTrylobiteFrontClaw, shTrylobiteFrontLeg); + make_paw_3d(shTrylobiteRearClaw, shTrylobiteRearLeg); + make_abody_3d(shTrylobiteBody, 0); + // make_ahead_3d(shTrylobiteHead); + make_revolution_cut(shTrylobiteHead, 180, geom3::AHEAD - geom3::ABODY); + + make_revolution_cut(shShark, 180); + + make_revolution_cut(shGhost, 60); + make_revolution_cut(shSlime, 60); + + make_revolution_cut(shEagle, 180, 0, 0.05*S); + make_revolution_cut(shHawk, 180, 0, 0.05*S); + + make_revolution_cut(shGargoyleWings, 180, 0, 0.05*S); + make_revolution_cut(shGargoyleBody, 180, 0, 0.05*S); + make_revolution_cut(shGadflyWing, 180, 0, 0.05*S); + make_revolution_cut(shBatWings, 180, 0, 0.05*S); + make_revolution_cut(shBatBody, 180, 0, 0.05*S); + + make_revolution_cut(shJelly, 60); + make_revolution(shFoxTail1); + make_revolution(shFoxTail2); + make_revolution(shGadflyBody); + for(int i=0; i<8; i++) + make_revolution(shAsteroid[i], 360); + + make_revolution_cut(shBugLeg, 60, geom3::ALEG0); + + make_revolution(shBugArmor, 180, geom3::ABODY); + make_revolution_cut(shBugAntenna, 90, geom3::ABODY); + + make_revolution_cut(shButterflyBody, 180); + + shift_shape(shWolf1, -0.088 * S); + shift_shape(shWolf2, -0.088 * S); + shift_shape(shWolf3, -0.098 * S); + shift_shape(shFamiliarEye, -0.088 * S); + shift_shape(shWolfEyes, (-0.088 - 0.01 * 0.9) * S); + + shift_shape(shEyes, (-3.3) * S / -20); + for(int i=shEyes.s; i +#include +#include +#include +#include + +namespace mapbox { + +namespace util { + +template struct nth { + inline static typename std::tuple_element::type + get(const T& t) { return std::get(t); }; +}; + +} + +namespace detail { + +template +class Earcut { +public: + std::vector indices; + std::size_t vertices = 0; + + template + void operator()(const Polygon& points); + +private: + struct Node { + Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {} + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; + Node(Node&&) = delete; + Node& operator=(Node&&) = delete; + + const N i; + const double x; + const double y; + + // previous and next vertice nodes in a polygon ring + Node* prev = nullptr; + Node* next = nullptr; + + // z-order curve value + int32_t z = 0; + + // previous and next nodes in z-order + Node* prevZ = nullptr; + Node* nextZ = nullptr; + + // indicates whether this is a steiner point + bool steiner = false; + }; + + template Node* linkedList(const Ring& points, const bool clockwise); + Node* filterPoints(Node* start, Node* end = nullptr); + void earcutLinked(Node* ear, int pass = 0); + bool isEar(Node* ear); + bool isEarHashed(Node* ear); + Node* cureLocalIntersections(Node* start); + void splitEarcut(Node* start); + template Node* eliminateHoles(const Polygon& points, Node* outerNode); + void eliminateHole(Node* hole, Node* outerNode); + Node* findHoleBridge(Node* hole, Node* outerNode); + void indexCurve(Node* start); + Node* sortLinked(Node* list); + int32_t zOrder(const double x_, const double y_); + Node* getLeftmost(Node* start); + bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const; + bool isValidDiagonal(Node* a, Node* b); + double area(const Node* p, const Node* q, const Node* r) const; + bool equals(const Node* p1, const Node* p2); + bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2); + bool intersectsPolygon(const Node* a, const Node* b); + bool locallyInside(const Node* a, const Node* b); + bool middleInside(const Node* a, const Node* b); + Node* splitPolygon(Node* a, Node* b); + template Node* insertNode(std::size_t i, const Point& p, Node* last); + void removeNode(Node* p); + + bool hashing; + double minX, maxX; + double minY, maxY; + double inv_size = 0; + + template > + class ObjectPool { + public: + ObjectPool() { } + ObjectPool(std::size_t blockSize_) { + reset(blockSize_); + } + ~ObjectPool() { + clear(); + } + template + T* construct(Args&&... args) { + if (currentIndex >= blockSize) { + currentBlock = alloc_traits::allocate(alloc, blockSize); + allocations.emplace_back(currentBlock); + currentIndex = 0; + } + T* object = ¤tBlock[currentIndex++]; + alloc_traits::construct(alloc, object, std::forward(args)...); + return object; + } + void reset(std::size_t newBlockSize) { + for (auto allocation : allocations) { + alloc_traits::deallocate(alloc, allocation, blockSize); + } + allocations.clear(); + blockSize = std::max(1, newBlockSize); + currentBlock = nullptr; + currentIndex = blockSize; + } + void clear() { reset(blockSize); } + private: + T* currentBlock = nullptr; + std::size_t currentIndex = 1; + std::size_t blockSize = 1; + std::vector allocations; + Alloc alloc; + typedef typename std::allocator_traits alloc_traits; + }; + ObjectPool nodes; +}; + +template template +void Earcut::operator()(const Polygon& points) { + // reset + indices.clear(); + vertices = 0; + + if (points.empty()) return; + + double x; + double y; + int threshold = 80; + std::size_t len = 0; + + for (size_t i = 0; threshold >= 0 && i < points.size(); i++) { + threshold -= static_cast(points[i].size()); + len += points[i].size(); + } + + //estimate size of nodes and indices + nodes.reset(len * 3 / 2); + indices.reserve(len + points[0].size()); + + Node* outerNode = linkedList(points[0], true); + if (!outerNode || outerNode->prev == outerNode->next) return; + + if (points.size() > 1) outerNode = eliminateHoles(points, outerNode); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + hashing = threshold < 0; + if (hashing) { + Node* p = outerNode->next; + minX = maxX = outerNode->x; + minY = maxY = outerNode->y; + do { + x = p->x; + y = p->y; + minX = std::min(minX, x); + minY = std::min(minY, y); + maxX = std::max(maxX, x); + maxY = std::max(maxY, y); + p = p->next; + } while (p != outerNode); + + // minX, minY and size are later used to transform coords into integers for z-order calculation + inv_size = std::max(maxX - minX, maxY - minY); + inv_size = inv_size != .0 ? (1. / inv_size) : .0; + } + + earcutLinked(outerNode); + + nodes.clear(); +} + +// create a circular doubly linked list from polygon points in the specified winding order +template template +typename Earcut::Node* +Earcut::linkedList(const Ring& points, const bool clockwise) { + using Point = typename Ring::value_type; + double sum = 0; + const std::size_t len = points.size(); + std::size_t i, j; + Node* last = nullptr; + + // calculate original winding order of a polygon ring + for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) { + const auto& p1 = points[i]; + const auto& p2 = points[j]; + const double p20 = util::nth<0, Point>::get(p2); + const double p10 = util::nth<0, Point>::get(p1); + const double p11 = util::nth<1, Point>::get(p1); + const double p21 = util::nth<1, Point>::get(p2); + sum += (p20 - p10) * (p11 + p21); + } + + // link points into circular doubly-linked list in the specified winding order + if (clockwise == (sum > 0)) { + for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last); + } else { + for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last); + } + + if (last && equals(last, last->next)) { + removeNode(last); + last = last->next; + } + + vertices += len; + + return last; +} + +// eliminate colinear or duplicate points +template +typename Earcut::Node* +Earcut::filterPoints(Node* start, Node* end) { + if (!end) end = start; + + Node* p = start; + bool again; + do { + again = false; + + if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) { + removeNode(p); + p = end = p->prev; + + if (p == p->next) break; + again = true; + + } else { + p = p->next; + } + } while (again || p != end); + + return end; +} + +// main ear slicing loop which triangulates a polygon (given as a linked list) +template +void Earcut::earcutLinked(Node* ear, int pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && hashing) indexCurve(ear); + + Node* stop = ear; + Node* prev; + Node* next; + + int iterations = 0; + + // iterate through ears, slicing them one by one + while (ear->prev != ear->next) { + iterations++; + prev = ear->prev; + next = ear->next; + + if (hashing ? isEarHashed(ear) : isEar(ear)) { + // cut off the triangle + indices.emplace_back(prev->i); + indices.emplace_back(ear->i); + indices.emplace_back(next->i); + + removeNode(ear); + + // skipping the next vertice leads to less sliver triangles + ear = next->next; + stop = next->next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear == stop) { + // try filtering points and slicing again + if (!pass) earcutLinked(filterPoints(ear), 1); + + // if this didn't work, try curing all small self-intersections locally + else if (pass == 1) { + ear = cureLocalIntersections(ear); + earcutLinked(ear, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass == 2) splitEarcut(ear); + + break; + } + } +} + +// check whether a polygon node forms a valid ear with adjacent nodes +template +bool Earcut::isEar(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + Node* p = ear->next->next; + + while (p != ear->prev) { + if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->next; + } + + return true; +} + +template +bool Earcut::isEarHashed(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + const double minTX = std::min(a->x, std::min(b->x, c->x)); + const double minTY = std::min(a->y, std::min(b->y, c->y)); + const double maxTX = std::max(a->x, std::max(b->x, c->x)); + const double maxTY = std::max(a->y, std::max(b->y, c->y)); + + // z-order range for the current triangle bbox; + const int32_t minZ = zOrder(minTX, minTY); + const int32_t maxZ = zOrder(maxTX, maxTY); + + // first look for points inside the triangle in increasing z-order + Node* p = ear->nextZ; + + while (p && p->z <= maxZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->nextZ; + } + + // then look for points in decreasing z-order + p = ear->prevZ; + + while (p && p->z >= minZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->prevZ; + } + + return true; +} + +// go through all polygon nodes and cure small local self-intersections +template +typename Earcut::Node* +Earcut::cureLocalIntersections(Node* start) { + Node* p = start; + do { + Node* a = p->prev; + Node* b = p->next->next; + + // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) + if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) { + indices.emplace_back(a->i); + indices.emplace_back(p->i); + indices.emplace_back(b->i); + + // remove two nodes involved + removeNode(p); + removeNode(p->next); + + p = start = b; + } + p = p->next; + } while (p != start); + + return p; +} + +// try splitting polygon into two and triangulate them independently +template +void Earcut::splitEarcut(Node* start) { + // look for a valid diagonal that divides the polygon into two + Node* a = start; + do { + Node* b = a->next->next; + while (b != a->prev) { + if (a->i != b->i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + Node* c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a->next); + c = filterPoints(c, c->next); + + // run earcut on each half + earcutLinked(a); + earcutLinked(c); + return; + } + b = b->next; + } + a = a->next; + } while (a != start); +} + +// link every hole into the outer loop, producing a single-ring polygon without holes +template template +typename Earcut::Node* +Earcut::eliminateHoles(const Polygon& points, Node* outerNode) { + const size_t len = points.size(); + + std::vector queue; + for (size_t i = 1; i < len; i++) { + Node* list = linkedList(points[i], false); + if (list) { + if (list == list->next) list->steiner = true; + queue.push_back(getLeftmost(list)); + } + } + std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) { + return a->x < b->x; + }); + + // process holes from left to right + for (size_t i = 0; i < queue.size(); i++) { + eliminateHole(queue[i], outerNode); + outerNode = filterPoints(outerNode, outerNode->next); + } + + return outerNode; +} + +// find a bridge between vertices that connects hole with an outer ring and and link it +template +void Earcut::eliminateHole(Node* hole, Node* outerNode) { + outerNode = findHoleBridge(hole, outerNode); + if (outerNode) { + Node* b = splitPolygon(outerNode, hole); + filterPoints(b, b->next); + } +} + +// David Eberly's algorithm for finding a bridge between hole and outer polygon +template +typename Earcut::Node* +Earcut::findHoleBridge(Node* hole, Node* outerNode) { + Node* p = outerNode; + double hx = hole->x; + double hy = hole->y; + double qx = -std::numeric_limits::infinity(); + Node* m = nullptr; + + // find a segment intersected by a ray from the hole's leftmost Vertex to the left; + // segment's endpoint with lesser x will be potential connection Vertex + do { + if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) { + double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y); + if (x <= hx && x > qx) { + qx = x; + if (x == hx) { + if (hy == p->y) return p; + if (hy == p->next->y) return p->next; + } + m = p->x < p->next->x ? p : p->next; + } + } + p = p->next; + } while (p != outerNode); + + if (!m) return 0; + + if (hx == qx) return m->prev; + + // look for points inside the triangle of hole Vertex, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the Vertex of the minimum angle with the ray as connection Vertex + + const Node* stop = m; + double tanMin = std::numeric_limits::infinity(); + double tanCur = 0; + + p = m->next; + double mx = m->x; + double my = m->y; + + while (p != stop) { + if (hx >= p->x && p->x >= mx && hx != p->x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) { + + tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential + + if ((tanCur < tanMin || (tanCur == tanMin && p->x > m->x)) && locallyInside(p, hole)) { + m = p; + tanMin = tanCur; + } + } + + p = p->next; + } + + return m; +} + +// interlink polygon nodes in z-order +template +void Earcut::indexCurve(Node* start) { + assert(start); + Node* p = start; + + do { + p->z = p->z ? p->z : zOrder(p->x, p->y); + p->prevZ = p->prev; + p->nextZ = p->next; + p = p->next; + } while (p != start); + + p->prevZ->nextZ = nullptr; + p->prevZ = nullptr; + + sortLinked(p); +} + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +template +typename Earcut::Node* +Earcut::sortLinked(Node* list) { + assert(list); + Node* p; + Node* q; + Node* e; + Node* tail; + int i, numMerges, pSize, qSize; + int inSize = 1; + + for (;;) { + p = list; + list = nullptr; + tail = nullptr; + numMerges = 0; + + while (p) { + numMerges++; + q = p; + pSize = 0; + for (i = 0; i < inSize; i++) { + pSize++; + q = q->nextZ; + if (!q) break; + } + + qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize == 0) { + e = q; + q = q->nextZ; + qSize--; + } else if (qSize == 0 || !q) { + e = p; + p = p->nextZ; + pSize--; + } else if (p->z <= q->z) { + e = p; + p = p->nextZ; + pSize--; + } else { + e = q; + q = q->nextZ; + qSize--; + } + + if (tail) tail->nextZ = e; + else list = e; + + e->prevZ = tail; + tail = e; + } + + p = q; + } + + tail->nextZ = nullptr; + + if (numMerges <= 1) return list; + + inSize *= 2; + } +} + +// z-order of a Vertex given coords and size of the data bounding box +template +int32_t Earcut::zOrder(const double x_, const double y_) { + // coords are transformed into non-negative 15-bit integer range + int32_t x = static_cast(32767.0 * (x_ - minX) * inv_size); + int32_t y = static_cast(32767.0 * (y_ - minY) * inv_size); + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} + +// find the leftmost node of a polygon ring +template +typename Earcut::Node* +Earcut::getLeftmost(Node* start) { + Node* p = start; + Node* leftmost = start; + do { + if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y)) + leftmost = p; + p = p->next; + } while (p != start); + + return leftmost; +} + +// check if a point lies within a convex triangle +template +bool Earcut::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const { + return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && + (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && + (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0; +} + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +template +bool Earcut::isValidDiagonal(Node* a, Node* b) { + return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && + locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b); +} + +// signed area of a triangle +template +double Earcut::area(const Node* p, const Node* q, const Node* r) const { + return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y); +} + +// check if two points are equal +template +bool Earcut::equals(const Node* p1, const Node* p2) { + return p1->x == p2->x && p1->y == p2->y; +} + +// check if two segments intersect +template +bool Earcut::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) { + if ((equals(p1, q1) && equals(p2, q2)) || + (equals(p1, q2) && equals(p2, q1))) return true; + return (area(p1, q1, p2) > 0) != (area(p1, q1, q2) > 0) && + (area(p2, q2, p1) > 0) != (area(p2, q2, q1) > 0); +} + +// check if a polygon diagonal intersects any polygon segments +template +bool Earcut::intersectsPolygon(const Node* a, const Node* b) { + const Node* p = a; + do { + if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i && + intersects(p, p->next, a, b)) return true; + p = p->next; + } while (p != a); + + return false; +} + +// check if a polygon diagonal is locally inside the polygon +template +bool Earcut::locallyInside(const Node* a, const Node* b) { + return area(a->prev, a, a->next) < 0 ? + area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 : + area(a, b, a->prev) < 0 || area(a, a->next, b) < 0; +} + +// check if the middle Vertex of a polygon diagonal is inside the polygon +template +bool Earcut::middleInside(const Node* a, const Node* b) { + const Node* p = a; + bool inside = false; + double px = (a->x + b->x) / 2; + double py = (a->y + b->y) / 2; + do { + if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y && + (px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x)) + inside = !inside; + p = p->next; + } while (p != a); + + return inside; +} + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits +// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a +// single ring +template +typename Earcut::Node* +Earcut::splitPolygon(Node* a, Node* b) { + Node* a2 = nodes.construct(a->i, a->x, a->y); + Node* b2 = nodes.construct(b->i, b->x, b->y); + Node* an = a->next; + Node* bp = b->prev; + + a->next = b; + b->prev = a; + + a2->next = an; + an->prev = a2; + + b2->next = a2; + a2->prev = b2; + + bp->next = b2; + b2->prev = bp; + + return b2; +} + +// create a node and util::optionally link it with previous one (in a circular doubly linked list) +template template +typename Earcut::Node* +Earcut::insertNode(std::size_t i, const Point& pt, Node* last) { + Node* p = nodes.construct(static_cast(i), util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt)); + + if (!last) { + p->prev = p; + p->next = p; + + } else { + assert(last); + p->next = last->next; + p->prev = last; + last->next->prev = p; + last->next = p; + } + return p; +} + +template +void Earcut::removeNode(Node* p) { + p->next->prev = p->prev; + p->prev->next = p->next; + + if (p->prevZ) p->prevZ->nextZ = p->nextZ; + if (p->nextZ) p->nextZ->prevZ = p->prevZ; +} +} + +template +std::vector earcut(const Polygon& poly) { + mapbox::detail::Earcut earcut; + earcut(poly); + return std::move(earcut.indices); +} +} diff --git a/graph.cpp b/graph.cpp index 46509ba4..4527d83f 100644 --- a/graph.cpp +++ b/graph.cpp @@ -580,43 +580,56 @@ transmatrix otherbodyparts(const transmatrix& V, color_t col, eMonster who, doub // todo - if(detaillevel >= 2) { + if(detaillevel >= 2 && DIM == 2) { transmatrix VL = mmscale(V, geom3::LEG1); queuepoly(VL * xpush(rightfoot*3/4), shHumanLeg, col); queuepoly(VL * Mirror * xpush(-rightfoot*3/4), shHumanLeg, col); } - if(true) { + if(DIM == 2) { transmatrix VL = mmscale(V, geom3::LEG); queuepoly(VL * xpush(rightfoot/2), shHumanLeg, col); queuepoly(VL * Mirror * xpush(-rightfoot/2), shHumanLeg, col); } - if(detaillevel >= 2) { + if(detaillevel >= 2 && DIM == 2) { transmatrix VL = mmscale(V, geom3::LEG3); queuepoly(VL * xpush(rightfoot/4), shHumanLeg, col); queuepoly(VL * Mirror * xpush(-rightfoot/4), shHumanLeg, col); } - if(who == moWaterElemental) { + transmatrix Tright, Tleft; + + if(DIM == 2) { + Tright = VFOOT * xpush(rightfoot); + Tleft = VFOOT * Mirror * xpush(-rightfoot); + } + else { + Tright = V * cspin(0, 2, rightfoot/SCALE * 3); + Tleft = V * Mirror * cspin(2, 0, rightfoot/SCALE * 3); + } + + if(who == moWaterElemental && DIM == 2) { double fishtail = footfun(footphase / .4) / 4 * 1.5; queuepoly(VFOOT * xpush(fishtail), shFishTail, watercolor(100)); } else if(who == moSkeleton) { - queuepoly(VFOOT * xpush(rightfoot), shSkeletalFoot, col); - queuepoly(VFOOT * Mirror * xpush(-rightfoot), shSkeletalFoot, col); + queuepoly(Tright, shSkeletalFoot, col); + queuepoly(Tleft, shSkeletalFoot, col); return spin(rightfoot * wobble); } else if(isTroll(who) || who == moMonkey || who == moYeti || who == moRatling || who == moRatlingAvenger || who == moGoblin) { - queuepoly(VFOOT * xpush(rightfoot), shYetiFoot, col); - queuepoly(VFOOT * Mirror * xpush(-rightfoot), shYetiFoot, col); + queuepoly(Tright, shYetiFoot, col); + queuepoly(Tleft, shYetiFoot, col); } else { - queuepoly(VFOOT * xpush(rightfoot), shHumanFoot, col); - queuepoly(VFOOT * Mirror * xpush(-rightfoot), shHumanFoot, col); + queuepoly(Tright, shHumanFoot, col); + queuepoly(Tleft, shHumanFoot, col); } - if(!mmspatial) return spin(rightfoot * wobble); + if(DIM == 3) queuepoly(VHEAD, shPHeadOnly, col); + + if(DIM == 3 || !mmspatial) return spin(rightfoot * wobble); if(detaillevel >= 2 && who != moZombie) queuepoly(mmscale(V, geom3::NECK1), shHumanNeck, col); @@ -630,8 +643,6 @@ transmatrix otherbodyparts(const transmatrix& V, color_t col, eMonster who, doub } return spin(rightfoot * wobble); - - return Id; } #endif diff --git a/polygons.cpp b/polygons.cpp index 88c49b0d..d53ad7eb 100644 --- a/polygons.cpp +++ b/polygons.cpp @@ -1714,6 +1714,8 @@ hpcshape shAsymmetric, + shPBodyOnly, shPBodyArm, shPBodyHand, shPHeadOnly, + shDodeca; vector shPlainWall3D, shWireframe3D, shWall3D, shMiniWall3D; @@ -2683,6 +2685,8 @@ void configure_floorshapes() { generate_floorshapes(); } +extern void make_3d_models(); + void buildpolys() { symmetriesAt.clear(); @@ -2923,6 +2927,11 @@ void buildpolys() { bshape(shFemaleDress, PPR::MONSTER_ARMOR0, scalefactor, 97); bshape(shDemon, PPR::MONSTER_HAIR, scalefactor, 98); + bshape(shPBodyOnly, PPR::MONSTER_BODY, scalefactor, 389); + bshape(shPBodyArm, PPR::MONSTER_BODY, scalefactor, 390); + bshape(shPBodyHand, PPR::MONSTER_BODY, scalefactor, 391); + bshape(shPHeadOnly, PPR::MONSTER_HEAD, scalefactor, 392); + bshape(shTrylobite, PPR::MONSTER_BODY, scalefactor, 99); bshape(shTrylobiteHead, PPR::MONSTER_HEAD, scalefactor, 100); bshape(shTrylobiteBody, PPR::MONSTER_BODY, scalefactor, 308); @@ -3093,6 +3102,8 @@ void buildpolys() { bshape(shBead1, PPR(20), 1, 251); bshape(shArrow, PPR::ARROW, 1, 252); + make_3d_models(); + bshapeend(); prehpc = isize(hpc); @@ -4094,6 +4105,11 @@ NEWSHAPE, 386, 3, 1, 0.173768,0.275379, 0.340287,0.116342, 0.229291,-0.115277, NEWSHAPE, 387, 7, 1, 0.315263,-0.310217, 0.085056,-0.287538, NEWSHAPE, 388, 1, 1, 0.046590,0.284199, 0.028110,0.325611, 0.098711,0.333738, 0.088761,0.294314, 0.090351,0.227036, 0.092387,0.196322, 0.129546,0.192006, 0.168982,0.166667, 0.173088,0.117700, 0.022882,0.091527, 0.004586,0.133004, 0.022981,0.160866, 0.052990,0.184313, 0.085413,0.193910, 0.055297,0.184324, +NEWSHAPE, 389, 1, 2, -0.127943,0.000000, -0.121732,0.008437, -0.120752,0.047093, -0.114785,0.065246, -0.096531,0.082051, -0.079664,0.100183, -0.087015,0.156872, -0.056388,0.171466, -0.021870,0.150662, -0.022997,0.136774, -0.004819,0.120485, 0.007204,0.104455, 0.016748,0.083741, 0.026225,0.054833, 0.033323,0.030943, 0.034483,0.001189, 0.034483,-0.001189, +NEWSHAPE, 390, 1, 1, -0.079664,0.100183, -0.087015,0.156872, -0.090442,0.188317, -0.085023,0.215058, -0.078296,0.241201, -0.070101,0.263835, -0.062700,0.273833, -0.053763,0.276497, -0.037212,0.273273, -0.026261,0.230095, -0.024880,0.217700, -0.022225,0.198787, -0.020850,0.180288, -0.021870,0.150662, -0.022997,0.136774, -0.036634,0.100744, +NEWSHAPE, 391, 1, 1, -0.063645,0.226806, -0.078296,0.241201, -0.070101,0.263835, -0.062700,0.273833, -0.053763,0.276497, -0.030638,0.274461, -0.015319,0.275737, 0.001277,0.277150, 0.020384,0.271369, 0.038101,0.262896, 0.045596,0.255842, 0.062388,0.263558, 0.085371,0.258660, 0.084235,0.228817, 0.071073,0.213220, 0.048603,0.218088, 0.042541,0.228972, 0.028749,0.228742, 0.011222,0.224439, -0.012498,0.229969, -0.026261,0.230095, +NEWSHAPE, 392, 1, 2, 0.060794,0.001192, 0.058426,0.023847, 0.050054,0.030986, 0.042896,0.038130, 0.044109,0.042917, 0.032180,0.050058, 0.017884,0.059612, 0.005963,0.064401, -0.009546,0.068015, -0.022689,0.070455, -0.053753,0.053753, -0.065710,0.040621, -0.074098,0.028683, -0.088611,0.020357, -0.087387,0.017956, + NEWSHAPE }; #endif