Archived
1
0
Fork 0
This repository has been archived on 2024-02-06. You can view files and clone it, but cannot push or open issues or pull requests.
SMS-Geography/hitbox.ipynb

529 lines
457 KiB
Text
Raw Permalink Normal View History

2022-03-12 14:28:24 +09:00
{
"cells": [
{
"cell_type": "markdown",
"id": "952650b7-fd36-4f91-af81-9ebb0b7d0706",
"metadata": {},
"source": [
"# SMS Hitbox\n",
"[Grounded/Airborne Wall/Ground/Roof hitbox](https://twitter.com/naosan_RTA2/status/1490198481860186113) (made by naosan\\[なおさん\\])"
]
},
{
"cell_type": "markdown",
"id": "899b3030-4722-4d82-9d40-bccbfc7468bc",
"metadata": {},
"source": [
"## Import Libraries"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "3bb5cd79-48af-47ba-b0c7-6ddb645fdead",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import struct\n",
"from memorylib import Dolphin\n",
"import matplotlib.pyplot as plt\n",
"from matplotlib import patches\n",
"import itertools\n",
"from collections import defaultdict, Counter\n",
"from shape import Polygon, Polyhedron\n",
"array = np.array\n",
"normalize = lambda x: x/np.linalg.norm(x)\n",
"read_struct = lambda addr, fmt: struct.unpack(fmt, d.read_ram(addr-0x80000000, struct.calcsize(fmt)))\n",
"\n",
"def hook():\n",
" global d, dolphin\n",
" d = dolphin = Dolphin()\n",
" assert dolphin.find_dolphin(), 'Dolphin not found'\n",
" assert dolphin.init_shared_memory(), 'MEM1 not found'\n",
" assert dolphin.read_ram(0, 3).tobytes() == b'GMS', 'Current game is not Sunshine'"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "b2dd3225-7f18-45b8-87f6-bb42279fb2e7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2690796364432 0x2727ff62290\n"
]
}
],
"source": [
"hook()"
]
},
{
"cell_type": "markdown",
"id": "194bce4c-80df-41ab-afa5-90616c2b0ca3",
"metadata": {},
"source": [
"## Classes and Functions"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "2f71120c-696f-4eec-b7cc-10f30f1547d1",
"metadata": {},
"outputs": [],
"source": [
"class Surface:\n",
" def __init__(self, surtype, surpara, trntype, unk7, verts, vidxs=None, n=None, c=None):\n",
" self.surtype, self.surpara, self.trntype, self.unk7 = \\\n",
" surtype, surpara, trntype, unk7\n",
" self.vidxs = vidxs\n",
" self.verts = verts\n",
" self.minY = verts[:,1].min()\n",
" self.maxY = verts[:,1].max()\n",
" self.n = normalize(np.cross(verts[1]-verts[0], verts[2]-verts[1])) if n is None else n\n",
" self.c = -np.dot(verts[0], self.n) if c is None else c\n",
" def __repr__(self):\n",
" return 'minY=%.0f maxY=%.0f n=(%5.2f, %5.2f, %5.2f)'%(\n",
" self.minY, self.maxY, *self.n,\n",
" )\n",
" \n",
"def checkList2list(ptr):\n",
" ans = []\n",
" while True:\n",
" ptr, data = read_struct(ptr+4, '>II')\n",
" ans.append(Surface(\n",
" *read_struct(data, '>HHBB'),\n",
" np.array(read_struct(data+0x10, '>9f'), 'f').reshape(3, 3),\n",
" n=np.array(read_struct(data+0x34, '>3f'), 'f'),\n",
" c=d.read_float(data+0x40),\n",
" ))\n",
" if ptr == 0: return ans"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "2c901433-7ee7-4771-9e89-1adc634ea889",
"metadata": {},
"outputs": [],
"source": [
"makeTriPrism = lambda tri0, tri1: Polyhedron([\n",
" *tri0,\n",
" *tri1,\n",
"], [e for i in range(3) for e in [(i, (i+1)%3), (i, i+3), (i+3, (i+1)%3+3)]])\n",
"\n",
"def makeGround(tri, hG=0):\n",
" poly = makeTriPrism(tri.verts+(0,hG,0), tri.verts-(0,108,0))\n",
" poly.clipPlane(p=(0, tri.minY-30, 0), n=(0, 1, 0))\n",
" return poly\n",
"def makeRoof(tri, hR=82):\n",
" poly = makeTriPrism(tri.verts-(0,hR,0), tri.verts-(0,160,0))\n",
" return poly\n",
"def makeWall(tri, rW=50, dy=30):\n",
" verts = tri.verts - (0, dy, 0)\n",
" n = tri.n\n",
" off = (2*rW,0,0) if abs(n[0])>0.707 else (0,0,2*rW)\n",
" poly = makeTriPrism(verts-off, verts+off)\n",
" poly.clipPlane(p=verts[0], n=n, c=-rW)\n",
" poly.clipPlane(p=verts[0], n=-n, c=-rW)\n",
" return poly\n",
"\n",
"# make collision: Polyhedron[]\n",
"def makeCol(tri, airborne=True):\n",
" ny = tri.n[1]\n",
" if ny > 0.2:\n",
" return [makeGround(tri, 0 if airborne else 100)]\n",
" elif ny < -0.2:\n",
" return [makeRoof(tri, 82 if airborne else 2)]\n",
" else:\n",
" return [\n",
" makeWall(tri, 50, 150),\n",
" makeWall(tri, 50, 30),\n",
" ] if airborne else [\n",
" makeWall(tri, 25, 30),\n",
" makeWall(tri, 50, 60),\n",
" ]"
]
},
{
"cell_type": "markdown",
"id": "cbb5f4b3-c5c8-4b95-ba11-5de3b8e05f1c",
"metadata": {},
"source": [
"## Draw wall hitboxs"
]
},
{
"cell_type": "markdown",
"id": "aa449e85-b4f3-48e1-b76d-f805a2a04f4b",
"metadata": {},
"source": [
"### Get data from Dolphin"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "99428fd2-b323-4d2d-b591-48714934d7a4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(-4762.12353515625, 230.0, 13479.6826171875)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# mario\n",
"gpMarioOriginal = 0x8040A378\n",
"ptrMario = d.read_uint32(gpMarioOriginal)\n",
"x, y, z = read_struct(ptrMario+0x10, '>3f')\n",
"\n",
"# get collision data (static collision)\n",
"gpMap = 0x8040A570\n",
"ptrMap = d.read_uint32(gpMap)\n",
"ptrCol = d.read_uint32(ptrMap+0x10)\n",
"xLimit, zLimit, xBlockCount, ptrStCLR = read_struct(ptrCol, '>ffI4x4xI')\n",
"## TBGCheckListRoot[zBlockCount][xBlockCount]\n",
"colOff = int((z+zLimit)//1024*xBlockCount + (x+xLimit)//1024)*36\n",
"## root->ground(12*2).next(4)\n",
"stWalls = checkList2list(d.read_uint32(ptrStCLR+colOff+4+12*2))\n",
"\n",
"x, y, z"
]
},
{
"cell_type": "markdown",
"id": "d28f4c61-0673-4909-ae1f-320624b2d99a",
"metadata": {},
"source": [
"### plot"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "5a6ef0d3-76e0-427d-9fb0-c3e5360c9199",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 864x864 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"yy, airborne = 183, False\n",
"#yy, airborne = y, True\n",
"\n",
"# paras\n",
"margin = 0.05\n",
"arsize0 = array([20, 10]) # arrow size (base)\n",
"arcnt = 5\n",
"\n",
"# vars\n",
"fig, ax = plt.subplots(figsize=(12,12))\n",
"xyMax = np.full(2, -np.inf)\n",
"xyMin = np.full(2, np.inf)\n",
"\n",
"# draw 2 hitboxs (draw in reverse order)\n",
"axes = [0, 2]\n",
"for ii in [1, 0]:\n",
" polys = [\n",
" (c, wall.n)\n",
" for wall in stWalls\n",
" for c in [makeCol(wall, airborne=airborne)[ii]]\n",
" # FIXME: filtering walls\n",
" if c.verts[:,2].max()<14200\n",
" ]\n",
" # arrow size scale (0.5 if radius is 25)\n",
" awmul = 1.0 if airborne or ii == 1 else 0.5\n",
" # draw wall hitboxs (draw in reverse order)\n",
" for poly, n in polys[::-1]:\n",
" # clip at y=yy and take (x, z) coordinate\n",
" verts = poly.slicePlane((0, yy, 0), (0, 1, 0))[:,axes]\n",
" p2 = Polygon(verts)\n",
" path = p2.path # path to plot\n",
" if path is None: continue # skip if no intersection\n",
" # plot hitbox area\n",
" patch = patches.PathPatch(path, facecolor='#88ff88e8', lw=1)\n",
" ax.add_patch(patch)\n",
" ## update x, y range\n",
" xyMax = array([xyMax, *verts]).max(axis=0)\n",
" xyMin = array([xyMin, *verts]).min(axis=0)\n",
" # plot arrow (TODO: can be improved)\n",
" ## center of the clipped polygon\n",
" vc = np.mean(verts, axis=0)\n",
" ## normal of the wall\n",
" n2 = normalize(n[axes])\n",
" ## direction vector (perpendicular to normal vector)\n",
" l2 = np.array([-n2[1], n2[0]])\n",
" loff = np.matmul(verts-vc, [n2[1], -n2[0]])\n",
" loffp = loff[loff>0].min() if len(loff[loff>0]) else 0\n",
" loffm = loff[loff<0].max() if len(loff[loff<0]) else 0\n",
" loffdis = loffp-loffm\n",
" larsize = np.dot(np.abs(l2), arsize0)\n",
" arsize = ( # FIXME: there should be better way to determine arrow size\n",
" loffdis/2 if loffdis<=larsize*2 else\n",
" loffdis/4 if loffdis<=larsize*4 else\n",
" larsize if loffdis<=larsize*arcnt else loffdis/arcnt\n",
" )*awmul\n",
" #if loffdis <= arsize: continue # if too small\n",
" ## offset parallel to direction vector\n",
" for loff in np.linspace(loffm+arsize*0.64, loffp-arsize*0.64, int(loffdis//arsize)) if loffdis>=arsize*2 else [0]:\n",
" ## offset parallel to normal vector\n",
" for off, l in [(-40, 37.5), (5.0, 37.5)]:\n",
" plt.arrow(\n",
" *vc+loff*l2+n2*off*awmul, *n2*l*awmul,\n",
" width=arsize*0.27, head_width=arsize*0.72, head_length=15*awmul,\n",
" length_includes_head=True, color='#080'\n",
" )\n",
"\n",
"# set x, y range of the figure\n",
"xMg, yMg = (xyMax-xyMin)*margin\n",
"xMax, yMax = xyMax\n",
"xMin, yMin = xyMin\n",
"ax.set_xlim(xMin-xMg, xMax+xMg)\n",
"ax.set_ylim(yMin-yMg, yMax+yMg)\n",
"\n",
"# if needed\n",
"ax.invert_xaxis()\n",
"#ax.invert_yaxis()\n",
"ax.grid()\n",
"\n",
"plt.title(f'y = %s (%s)'%(yy, 'airborne' if airborne else 'grounded'))\n",
"#fig.patch.set_facecolor('white')\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "5b9da0f5-f409-42ca-a851-aa779af128a0",
"metadata": {},
"source": [
"## Draw all static triangle's hitbox"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "03ac8d93-cfa3-4bb3-814f-cb8a797b9664",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(-4645.99853515625, 864.3046264648438, 12823.865234375)"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# mario\n",
"gpMarioOriginal = 0x8040A378\n",
"ptrMario = d.read_uint32(gpMarioOriginal)\n",
"x, y, z = read_struct(ptrMario+0x10, '>3f')\n",
"\n",
"# get collision data (static collision)\n",
"gpMap = 0x8040A570\n",
"ptrMap = d.read_uint32(gpMap)\n",
"ptrCol = d.read_uint32(ptrMap+0x10)\n",
"xLimit, zLimit, xBlockCount, ptrStCLR = read_struct(ptrCol, '>ffI4x4xI')\n",
"\n",
"## TBGCheckListRoot[zBlockCount][xBlockCount]\n",
"colOff = int((z+zLimit)//1024*xBlockCount + (x+xLimit)//1024)*36\n",
"## stGnds, stRoofs, stWalls\n",
"data1 = [\n",
" checkList2list(d.read_uint32(ptrStCLR+colOff+4+12*j))\n",
" for j in range(3)\n",
"]\n",
"\n",
"## next z block\n",
"colOff = int(((z+zLimit)//1024+1)*xBlockCount + (x+xLimit)//1024)*36\n",
"data2 = [\n",
" checkList2list(d.read_uint32(ptrStCLR+colOff+4+12*j))\n",
" for j in range(3)\n",
"]\n",
"\n",
"x, y, z"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "b80c9f55-8e6c-4a30-8738-add604e8c8e8",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 648x432 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"p0, pn = (x, y, z), (1, 0, 0)\n",
"airborne = True\n",
"axes = [2, 1] # (z, y)\n",
"\n",
"# paras\n",
"margin = 0.01\n",
"arsize0 = array([30, 30]) # arrow size\n",
"arcnt = 30 # max arrow count\n",
"\n",
"# vars\n",
"fig, ax = plt.subplots(figsize=(9, 6))\n",
"xyMax = np.full(2, -np.inf)\n",
"xyMin = np.full(2, np.inf)\n",
"\n",
"for stGnds, stRoofs, stWalls in (data1, data2):\n",
" for polys, awmul, alen, facecolor, arcolor in [\n",
" # ceiling\n",
" ([\n",
" (c, array((0, -1, 0)))\n",
" for tri in stRoofs\n",
" for c in makeCol(tri, airborne=airborne)\n",
" if tri.maxY >= 400\n",
" ##], 1.0, 78.0 if airborne else 158.0, '#f88e', '#800'),\n",
" ## FIXME: draw arrow\n",
" ], 1.0, 0, '#f88e', '#800'),\n",
" ([\n",
" (c, array((0, 1, 0)))\n",
" for tri in stGnds # TODO grounded hitbox\n",
" for c in makeCol(tri, airborne=airborne)\n",
" if tri.maxY >= 400\n",
" ## FIXME: draw arrow\n",
" ], 1.0, 0, '#88fe', '#008'), # TODO\n",
" *(\n",
" ([\n",
" (c, tri.n)\n",
" for tri in stWalls\n",
" for c in [makeCol(tri, airborne=airborne)[ii]]\n",
" if tri.maxY >= 400\n",
" # arrow size scale (0.5 if radius is 25)\n",
" ], 1.0 if airborne or ii == 1 else 0.5, 50.0, '#8f8e', '#080')\n",
" for ii in (1, 0)\n",
" ),\n",
" ]:\n",
" # draw wall hitboxs (draw in reverse order)\n",
" for poly, n in polys[::-1]:\n",
" # clip at y=yy and take (x, z) coordinate\n",
" verts = poly.slicePlane(p0, pn)[:,axes]\n",
" p2 = Polygon(verts)\n",
" path = p2.path # path to plot\n",
" if path is None: continue # skip if no intersection\n",
" # plot hitbox area\n",
" patch = patches.PathPatch(path, facecolor=facecolor, lw=1)\n",
" ax.add_patch(patch)\n",
" ## update x, y range\n",
" xyMax = array([xyMax, *verts]).max(axis=0)\n",
" xyMin = array([xyMin, *verts]).min(axis=0)\n",
" # plot arrow (TODO: can be improved)\n",
" ## center of the clipped polygon\n",
" vc = np.mean(verts, axis=0)\n",
" ## normal of the wall\n",
" n2 = normalize(n[axes])\n",
" ## direction vector (perpendicular to normal vector)\n",
" l2 = np.array([-n2[1], n2[0]])\n",
" loff = np.matmul(verts-vc, [n2[1], -n2[0]])\n",
" loffp = loff[loff>0].min() if len(loff[loff>0]) else 0\n",
" loffm = loff[loff<0].max() if len(loff[loff<0]) else 0\n",
" loffdis = loffp-loffm\n",
" larsize = np.dot(np.abs(l2), arsize0)\n",
" arsize = ( # FIXME: there should be better way to determine arrow size\n",
" loffdis/2 if loffdis<=larsize*2 else\n",
" loffdis/4 if loffdis<=larsize*4 else\n",
" larsize if loffdis<=larsize*arcnt else loffdis/arcnt\n",
" )*awmul\n",
" #if loffdis <= arsize: continue # if too small\n",
" ## offset parallel to direction vector\n",
" for loff in np.linspace(loffm+arsize*0.64, loffp-arsize*0.64, int(loffdis//arsize)) if loffdis>=arsize*2 else [0]:\n",
" ## offset parallel to normal vector\n",
" for off, l in [(alen*-0.8, alen*0.75), (alen*0.1, alen*0.75)]:\n",
" plt.arrow(\n",
" *vc+loff*l2+n2*off*awmul, *n2*l*awmul,\n",
" width=arsize*0.27, head_width=arsize*0.72, head_length=15*awmul,\n",
" length_includes_head=True, color=arcolor,\n",
" )\n",
"\n",
"# set x, y range of the figure\n",
"xMg, yMg = (xyMax-xyMin)*margin\n",
"xMax, yMax = xyMax\n",
"xMin, yMin = xyMin\n",
"ax.set_xlim(xMin-xMg, xMax+xMg)\n",
"ax.set_ylim(yMin-yMg, yMax+yMg)\n",
"\n",
"# if needed\n",
"ax.set_xlim(xMin-xMg, 13850)\n",
"ax.set_ylim(450, yMax+yMg)\n",
"#ax.invert_xaxis()\n",
"#ax.invert_yaxis()\n",
"ax.grid()\n",
"\n",
"#plt.title(f'z = %s (%s)'%(z, 'airborne' if airborne else 'grounded'))\n",
"plt.title('GBS')\n",
"fig.patch.set_facecolor('white')\n",
"plt.xlabel('z')\n",
"plt.ylabel('y')\n",
"\n",
"#plt.savefig('GBS.jpg')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a6decf0-0b20-42fa-af00-3230e520087d",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.1"
}
},
"nbformat": 4,
"nbformat_minor": 5
}