529 lines
457 KiB
Text
529 lines
457 KiB
Text
|
{
|
||
|
"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": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGDCAYAAAAxsvoUAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8rg+JYAAAACXBIWXMAAAsTAAALEwEAmpwYAADNgElEQVR4nOzdd1hU19bA4d90mgKCoIKdYo+9Y40aFWuMvcTeBRWVaJpJTCyxxd5rYokNe4kGBQXsvWEXxU5HmHr/ALkalQ4DuN/n+Z7vZjyzz5oBZtY5e++1JAaDwYAgCIIgCEIuJDV2AIIgCIIgCOklEhlBEARBEHItkcgIgiAIgpBriURGEARBEIRcSyQygiAIgiDkWiKREQRBEAQh1xKJjCAIgiAIuZZIZARByJE2btxIrVq1MDc3x87Ojlq1arFw4UIMBgNff/01SqUSCwsL8uXLR7Vq1Th69GjSc0NCQvjyyy+xtbXF0tKSChUqsHr1auO9GEEQsoxIZARByHFmzpyJh4cH48aN48mTJzx9+pTFixdz/Phx1Go1AOPHjyc6OprIyEiGDh1Kx44d0el0APTq1YuiRYty//59Xr58ybp167C3tzfmSxIEIYtIRGVfQRBykoiICIoUKcLatWv58ssvP3jM119/jaOjI7/88gsAsbGxmJub8+jRI4oUKYKFhQX+/v5Urlw5GyMXBMEYxB0ZQRBylICAAOLj42nXrl2qjtfpdKxdu5aSJUsm3XWpXbs2w4cPZ+PGjTx48CArwxUEwchEIiMIQo7y4sULbG1tkcvlSY/VrVsXKysrTE1NOXbsGAC///47VlZWWFhY4Onpyc8//4xMJgPg77//xs3NjZ9//pmSJUtSuXJlTp06ZZTXIwhC1hKJjCAIOYqNjQ0vXrxAq9UmPXbixAnCw8OxsbFBr9cD4OXlRXh4OLGxsZw+fZpx48axb98+AKytrZk6dSpXrlzh6dOnVK5cmfbt2yNm0gUh7xGJjCAIOUqdOnVQqVT4+Pik6niJREKFChWoV68ee/bsee/fbW1t8fLy4vHjx7x69SqzwxUEwchEIiMIQo5iZWXFDz/8wLBhw9iyZQtRUVHo9XrOnz9PTEzMB59z/fp1/P39KV++PAATJkzg8uXLaLVaoqKiWLRoEU5OTtjY2GTnSxEEIRuIREYQhBxn/PjxzJo1i+nTp2Nvb4+9vT2DBw9m2rRp1K1bF4Dp06djYWGBubk5zZs3p2/fvgwePBhI2MXUoUMHrKysKFWqFPfv32fnzp3GfEmCIGQRsf1aEARBEIRcS9yREQRBEAQh1xKJjCAIgiAIuZZIZARBEARByLVEIiMIgiAIQq4lEhlBEARBEHItecqH5D62traUKFHC2GFkupiYGMzNzY0dRp4i3tOsId7XzCfe06wh3tfMl1Xv6b1793jx4sV7j+fJRKZEiRKcPn3a2GFkOl9fXxo1amTsMPIU8Z5mDfG+Zj7xnmYN8b5mvqx6T6tXr/7Bx8XUkiAIgiAIuZZIZARBEARByLWyLJHp168fdnZ2VKhQIemxv//+m/LlyyOVSt+b+vntt99wcnLC1dWVAwcOJD2+f/9+XF1dcXJyYurUqVkVriAIQq4yZcoU7OyKMmKEB/v27SM2NtbYIQlCmo0ePS7DS0GyLJH5+uuv2b9//zuPVahQgW3bttGgQYN3Hr969SobN27kypUr7N+/n2HDhqHT6dDpdAwfPpx9+/Zx9epVNmzYwNWrV7MqZEEQhFxh7NhxfP/9ZCIiwrh/356xY3+jYEF7mjT5gtmz53D9+nVE9xkhp4qJieGff/5h4sRvWbx4GfXrN+bIkSPpHi/LEpkGDRpQoECBdx4rW7Ysrq6u7x3r4+ND165dUalUlCxZEicnJ06ePMnJkydxcnKiVKlSKJVKunbtio+PT1aFLAiCkOP17NmbhQuX0bbtXGQyJZ9/PpHBg4/x3XchlCgxiO3br+Lm1gxHx5IMGDCEHTt2EBkZaeywhU/Y24lLzZr1KVjQnhEjfuTsWejUaSXx8dF8+WVXtm3blq7xc8SupUePHlG7du2k/3Z0dOTRo0cAFC1a9J3Hg4KCPjjG0qVLWbp0KQAhISH4+vpmXcBGEh0dnSdflzGJ9zRriPc180VHR7Ns2XKqVavOF190R6/X0qDB9xQu7Jt0TKlSBYDuQHc0mjji4iJ4/Pg5K1aswNTUHCur/FhaWmJqamqsl5HjiN/VzBcVFcWuXbuIjIwiKiqK169jUSrNKFmyHCNG1EKlMkciSbiPotXqqFz5d+zty/HwYTBbtmzB1tY2TefLEYlMZhg0aBCDBg0CErZo5cXtdGKbYOYT72nWEO9r5tJqtSxbtpxvv53O4MHHUKsduX/fn9Wrf+K3316l+HyNJoZr13wJDt7PjRv70WiiadHiC9q0+YJmzZq9d/f8UyJ+VzMuJiaGgIAAjhzx5Z9/fOnW7UuWLNlKiRKNKFWqESVK1EEqNUerhf8u5Xr16hU//fQ5c+YYeP7ciV9/bY6n5xC8vccjkUhSdf4ckcg4ODjw8OHDpP8OCQnBwcEB4KOPC4IgfApiY2OpVKkao0aNZPjwk5ibp+1qFUClMqd8+daUL98agOfPb3H9+gGmTl1Pv34DKVOmPO7uX9Cq1RdUr14dmUyW2S9DyEMMBgMXLlxg//4D7N59gDNnTlO8eCVKlGhE9eo/UqSIlKFDR6d53IIFnRk2zJ9Fi1rw7NlzZs6cjlSa8gqYHJHItG3blu7duzNmzBgeP35McHAwNWvWxGAwEBwczN27d3FwcGDjxo389ddfxg5XEAQhW7x69Yry5StjalqSggXL8Pp12pOYDylY0ImCBZ1wcxuOVhvPnTv+HD++nzVrBhAREUrTps1o27YlzZs3p1ChQplyTiF3e/bsGYcOHWL37gMcOnQQlSo/Li4tKFduDMePt2HoUP+kYyUS33Sfx8rKgSFDjrF6tTvPn/dj9erlyOXJpypZlsh069YNX19fXrx4gaOjI5MnT6ZAgQKMHDmS58+f07p1aypXrsyBAwcoX748nTt3ply5csjlchYsWJB0RTB//nxatGiBTqejX79+lC9fPqtCFgRByDEePHjAZ5/VwMGhLt27b0IiOZEl55HLVbi4NMXFpSkwg/DwEK5d28+8eTsZPtyDwoWLULNmNQYNGkCdOnVQKBRZEoeQs2g0GgICAti7dz979hzg3r3buLg0pnTpFgwbNhkbm5JZdm5z8wIMGHCI9es70a7dl2zZsjHZdV1Zlshs2LDhg4936NDhg49PmjSJSZMmvfd4q1ataNWqVabGJgiCkJNdvHiRunUbUa5cRzp0WIJUmn1TPVZWjtSpM4A6dQag02lYuLA5//xzmsDAqzx9eouGDZvQtu0XtGjRguLFi2dbXELWu3PnDgcOHGDXrgP4+flib++Mk1MLGjeeQ4kStZHJsi+JVanM6dPHh82bv6Zp0y/Yt2/nR4/NEVNLgiAIQoJjx47RooU7deqMpHnzn5N2dxiDTKbAxCQ/9vZl6dx5IVFRz7hx4yCrVu1nwoRvsbGx5csvO/Dbbz+nai2DkLNER0fz77//smfPAfbvP0BUVDSurs1xcuqMt/cyLCwKGjU+uVxJ167r2bnTg3r1GmFi8uGEXiQygiAIOcTWrVvp3r0PLVpMoV49jyw7T3h4OHq9PlXHajRqNBo1r169AuSUKtWKUqVa0ayZntDQC8ya5c706b9mWaxZ7ffff6dx48YolWao1bm7OnJaX4NMpsTOrhxly7ahRYsV2NuXT9oppFaT+DNPnbePLVhQx6tXr5BKpVhZWaV6jA+RSqW0a/cHhw79xNOnuz54jEhkBEEQcoBFixbh4eFFx45LqFKlZ5aeS6/XY2aWui3XMpkSmUz5weOdnJpiamqFl9dZLC0LZ3aY2aJwYV/mzDHg6SlhzpzcXQ05ra/B01OCh8e5TDn3278fUqkMM7MCxMamPhFKjkQioXnzH1i3TiQygiAIOdLkyZP59dcZ9OjxN2XKiDWBgpAWIpERBEEwoqFDh7Fq1Z/067efEiXqGzscQch1RCIjCIJgJB0
|
||
|
"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
|
||
|
}
|