/* Copyright 2022 flyinghead This file is part of Flycast. Flycast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Flycast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ #pragma once #include "types.h" namespace elan { union PCW { enum Command { null = 0, unk_1, // instance matrix continuation? matrix = 2, projMatrix = 3, instance = 4, gmp = 5, ich = 7, model = 8, registerWait = 0xe, link = 0xf }; struct { // Obj Control u32 uv16bit : 1; u32 gouraud : 1; u32 offset : 1; u32 texture : 1; u32 colType : 2; u32 volume : 1; u32 shadow : 1; // Naomi 2 u32 n2Command : 4; u32 reserved : 4; // Group Control u32 userClip : 2; u32 stripLen : 2; u32 parallelLight:1; u32 res_2 : 2; u32 groupEnable : 1; // Para Control u32 listType : 3; u32 naomi2 : 1; u32 endOfStrip : 1; u32 paraType : 3; }; u32 full; }; struct ElanBase { PCW pcw; }; struct Model : public ElanBase { // 08000800 u32 id1; // 18000000 u32 _res; u32 zero; // 0 u32 offset; u32 one; // 1 u32 size; u32 _res0; // 0 }; static_assert(sizeof(Model) % 32 == 0, "Invalid size for Model"); struct Instance : public ElanBase { // 08000400 u32 id1; // f u32 id2; // 7f u32 _res; u32 offset; u32 one; // 1 u32 size; u32 _res0; bool isModelInstance() const { return id1 == 0xf && id2 == 0x7f && one == 1; } }; static_assert(sizeof(Instance) % 32 == 0, "Invalid size for Instance"); struct Matrix : public ElanBase { // 08000200 float proj7; // env map U offset float lm00; float lm01; float lm02; float lm10; float lm11; float lm12; float tm20; float tm21; float tm22; float proj8; // env map V offset float _res[4]; u32 contCmd; float proj4; // near? float tm00; float tm10; float mfr2; float tm01; float tm11; float mfr6; float tm02; float tm12; float mfr10; float mat03; float mat13; float mat23; float proj5; // far? float mproj6; }; static_assert(sizeof(Matrix) % 32 == 0, "Invalid size for Matrix"); struct ProjMatrix : public ElanBase { // 08000300 u32 _res; float fx, tx; float fy, ty; u32 _res0[2]; }; static_assert(sizeof(ProjMatrix) % 32 == 0, "Invalid size for ProjMatrix"); // Global Model Parameters struct GMP : public ElanBase { // 08000500 struct { u32 frac0:5; u32 exp0:3; u32 frac1:5; u32 exp1:3; float getCoef0() { return pow(2.f, exp0 - 1.f) * (1 + (float)frac0 / 32); } float getCoef1() { return pow(2.f, exp1 - 1.f) * (1 + (float)frac1 / 32); } } gloss; union { struct { u32 d0:1; // diffuse u32 s0:1; // specular u32 a0:1; // ambient? alpha? u32 f0:1; // fog u32 d1:1; u32 s1:1; u32 a1:1; u32 f1:1; u32 vol1UsesVol0UV:1; u32 b0:1; // bump mapping?? TODO check u32 b1:1; u32 e0:1; // environmental mapping u32 e1:1; u32 _res:19; }; u32 full; } paramSelect; // (e=env) // ee000 1111 1111 specular, lambert // ee110 0000 0000 vertex color // ee110 1111 1111 constant // 00000 1111 1111 bump shading? // seen: 00110 1111 1111 (b0 and b1 set) // seen: 11000 1111 1111 (e0 and e1 set, followed by vtx type2 (vtx only)) // seen: 11110 1111 1111 (everything! except v1uv0, rt66, vtx type2 (vtx only)) // seen: 00110 0000 0000 (b0 and b1, vf4) // seen: 00000 1010 1010 specular and fog? soul surfer u32 diffuse0; u32 specular0; u32 diffuse1; u32 specular1; u32 _reserved; u32 _reserved0; u32 tex0; u32 _reserved1; u32 pal0; u32 _reserved2; u32 tex1; u32 _reserved3; u32 pal1; }; static_assert(sizeof(GMP) % 32 == 0, "Invalid size for GMP"); union HeaderAndNormal { struct { u32 nx:8; u32 ny:8; u32 nz:8; u32 _res:5; u32 strip:1; u32 fan:1; u32 endOfStrip:1; }; u32 full; bool isFirstOrSecond() const { return strip == 0 && fan == 0; } bool isThird() const { return strip == 1 && fan == 1; } bool isFan() const { return strip == 0 && fan == 1; } bool isStrip() const { return strip == 1 && fan == 0; } }; struct Vertex { HeaderAndNormal header; float x; float y; float z; }; struct Normal { float nx; float ny; float nz; u32 _reserved; }; struct UnpackedUV { float u; float v; }; struct PackedUV { u32 uv0; u32 uv1; }; struct PackedRGB { u32 argb0; u32 argb1; }; struct BumpMap { struct { u8 bumpDegree; u8 fixedOffset; u8 _res[2]; } scaleFactor; // Normal vectors struct { int8_t x; int8_t y; int8_t z; u8 _res; } v0; struct { int8_t x; int8_t y; int8_t z; u8 _res; } v1; u32 _res; }; // // textured, 1 or 2 para // struct N2_VERTEX_VU : public Vertex { UnpackedUV uv; }; // // textured, 1 or 2 para with unpacked normal // struct N2_VERTEX_VNU : public Vertex { Normal normal; UnpackedUV uv; }; // // for colored vertex, 1 para // struct N2_VERTEX_VUR : public Vertex { UnpackedUV uv; PackedRGB rgb; }; // // for bumpmapped, 1 para // struct N2_VERTEX_VUB : public Vertex { UnpackedUV uv; BumpMap bump; }; struct N2_VERTEX_VR : public Vertex { PackedRGB rgb; }; struct ICHList : public ElanBase { enum { VTX_TYPE_V = 0x00000002, VTX_TYPE_VU = 0x0000000A, VTX_TYPE_VU1 = 0x0000001A, // TODO correct? VTX_TYPE_VNU = 0x0000000E, VTX_TYPE_VR = 0X00000042, VTX_TYPE_VUR = 0x0000004A, VTX_TYPE_VUR1 = 0x000000CA, // TODO correct? VTX_TYPE_VUB = 0x0000010A, VTX_TYPE_VUB1 = 0x0000030A, // TODO correct? }; // 08000700 ISP_TSP isp; TSP tsp0; TCW tcw0; TSP tsp1; TSP tcw1; u32 flags; u32 vtxCount; u32 vertexSize() const { switch (flags) { case VTX_TYPE_V: return sizeof(Vertex); case VTX_TYPE_VU: return sizeof(N2_VERTEX_VU); case VTX_TYPE_VNU: return sizeof(N2_VERTEX_VNU); case VTX_TYPE_VR: return sizeof(N2_VERTEX_VR); case VTX_TYPE_VUR: return sizeof(N2_VERTEX_VUR); case VTX_TYPE_VUB: return sizeof(N2_VERTEX_VUB); default: return 0; } } }; static_assert(sizeof(ICHList) % 32 == 0, "Invalid size for ICHList"); struct RegisterWait : public ElanBase { // 08000e00 u32 offset; // -1 u32 _res; // d, or 1080000d when waiting for ta reg u32 mask; // 0 u32 _res0[4]; }; static_assert(sizeof(RegisterWait) % 32 == 0, "Invalid size for RegisterWait"); struct Link : public ElanBase { // 08000f00 u32 offset; u32 _res; // 09000000 u32 size; u32 _res0[4]; }; static_assert(sizeof(Link) % 32 == 0, "Invalid size for Link"); constexpr size_t MAX_LIGHTS = 16; struct LightModel : public ElanBase { // 08000400 struct { u32 zero:4; u32 lightFlag:1; u32 useAmbientBase0:1; // When on, ambient color is multiplied with vertex color. u32 useAmbientOffset0:1;// Otherwise it's added to the diffuse/offset color as is. u32 useAmbientBase1:1; // Same for volume1 u32 useAmbientOffset1:1; u32 useBaseOver:1; // diffusion saturated light: overflow ambient+diffuse into specular u32 _res:2; u32 bumpId1:4; u32 bumpId2:4; u32 _res0:12; }; u16 diffuseMask0; u16 specularMask0; u32 ambientBase0; u32 ambientOffset0; u16 diffuseMask1; u16 specularMask1; u32 ambientBase1; u32 ambientOffset1; bool isDiffuse(int lightId) const { return (diffuseMask0 & (1 << lightId)) != 0; } bool isSpecular(int lightId) const { return (specularMask0 & (1 << lightId)) != 0; } }; // dmode, smode enum { // diffuse and specular N2_LMETHOD_SINGLE_SIDED, N2_LMETHOD_DOUBLE_SIDED, N2_LMETHOD_DOUBLE_SIDED_WITH_TOLERANCE, N2_LMETHOD_SPECIAL_EFFECT, // diffuse only N2_LMETHOD_THIN_SURFACE, N2_LMETHOD_BUMP_MAP }; // routing enum { N2_LFUNC_BASEDIFF_BASESPEC_ADD, N2_LFUNC_BASEDIFF_OFFSSPEC_ADD, N2_LFUNC_OFFSDIFF_BASESPEC_ADD, N2_LFUNC_OFFSDIFF_OFFSSPEC_ADD, N2_LFUNC_ALPHADIFF_ADD, N2_LFUNC_ALPHAATTEN_ADD, N2_LFUNC_FOGDIFF_ADD, N2_LFUNC_FOGATTENUATION_ADD, N2_LFUNC_BASEDIFF_BASESPEC_SUB, N2_LFUNC_BASEDIFF_OFFSSPEC_SUB, N2_LFUNC_OFFSDIFF_BASESPEC_SUB, N2_LFUNC_OFFSDIFF_OFFSSPEC_SUB, N2_LFUNC_ALPHADIFF_SUB, N2_LFUNC_ALPHAATTEN_SUB, N2_LFUNC_FOGDIFF_SUB, N2_LFUNC_FOGATTEN_SUB, }; struct ParallelLight : public ElanBase { // 08100400 struct { u32 lightId:4; u32 _res:4; u32 blue:8; u32 green:8; u32 red:8; }; struct { u32 dirZ:8; u32 dirY:8; u32 dirX:8; u32 routing:4; u32 dmode:2; u32 _res1:2; }; u32 _res2[5]; }; struct PointLight : public ElanBase { // 08000400 struct { u32 lightId:4; u32 _res:1; u32 dmode:3; u32 blue:8; u32 green:8; u32 red:8; }; struct { u32 dirZ:8; u32 dirY:8; u32 dirX:8; u32 routing:4; u32 smode:2; u32 one:1; u32 dattenmode:1; }; float posX; float posY; float posZ; u16 distA; u16 distB; u16 angleA; u16 angleB; float attnMinDistance() const { float a = 0; *((u16 *)&a + 1) = distA; float b = 0; *((u16 *)&b + 1) = distB; return -b / (a - 1); } float attnMaxDistance() const { float a = 0; *((u16 *)&a + 1) = distA; float b = 0; *((u16 *)&b + 1) = distB; return -b / a; } float attnDist(float dist) const { float a = 0; *((u16 *)&a + 1) = distA; float b = 0; *((u16 *)&b + 1) = distB; float rv; if (dattenmode) rv = b * dist + a; else rv = b / dist + a; return std::max(0.f, std::min(1.f, rv)); } bool isAttnDist() const { return distA != 1 && distB != 0; } float attnMinAngle() const { float a = 0; *((u16 *)&a + 1) = angleA; float b = 0; *((u16 *)&b + 1) = angleB; return acosf((1 - a) / b); } float attnMaxAngle() const { float a = 0; *((u16 *)&a + 1) = angleA; float b = 0; *((u16 *)&b + 1) = angleB; return acosf(-a / b); } float attnAngle(float angleCos) const { float a = 0; *((u16 *)&a + 1) = angleA; float b = 0; *((u16 *)&b + 1) = angleB; return std::max(0.f, std::min(1.f, angleCos * b + a)); } bool isAttnAngle() const { return angleA != 1 && angleB != 0; } }; }