Archived
1
0
Fork 0

add wall.ipynb

This commit is contained in:
sup39 2022-03-11 21:31:38 +09:00
parent 143fad8669
commit 233704bc56
6 changed files with 805 additions and 18 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
__pycache__/

View file

@ -1,6 +1,10 @@
# SMS-Shape-Notebook # SMS-Geography サンシャイン地理学
多角形と多面体のクラスを実装したJupyter Notebookです。これを使って斜面をすり抜ける範囲を描画することができます。今後Dolphinと連携するといった機能を追加する予定です。 ## Jupyter Notebooks
- [shape.ipynb](shape.ipynb): This notebook implements Polygon and Polyhedron class. You can use it to plot the area that you can clip through a slope.
- [wall.ipynb](wall.ipynb): This notebook gives a demonstration to read collision data from Dolphin and draw wall hitboxs with the shape library made by sup39.
This is a Jupyter Notebook that implements Polygon and Polyhedron class. - [shape.ipynb](shape.ipynb): 多角形と多面体のクラスを実装したJupyter Notebookです。これを使って斜面をすり抜ける範囲を描画することができます。
You can use this notebook to plot the area that you can clip through a slope. - [wall.ipynb](wall.ipynb): Dolphinから壁のデータを取得し、サポミクが実装した図形のライブラリを使って壁の判定を描画する方法を紹介します。
I will add some functions in the future such as accessing Dolphin's memory.
## Dependencies
These notebooks use [dolphin-memory-lib](https://github.com/RenolY2/dolphin-memory-lib/blob/main/memorylib.py) made by RenolY2.

251
memorylib.py Normal file
View file

@ -0,0 +1,251 @@
## This library is made by RenolY2
## https://github.com/RenolY2/dolphin-memory-lib/blob/main/memorylib.py
import ctypes
import struct
from struct import pack, unpack
from ctypes import wintypes, sizeof, addressof, POINTER, pointer
from ctypes.wintypes import DWORD, ULONG, LONG, WORD
from multiprocessing import shared_memory
# Various Windows structs/enums needed for operation
NULL = 0
TH32CS_SNAPHEAPLIST = 0x00000001
TH32CS_SNAPPROCESS = 0x00000002
TH32CS_SNAPTHREAD = 0x00000004
TH32CS_SNAPMODULE = 0x00000008
TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE
assert TH32CS_SNAPALL == 0xF
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_OPERATION = 0x0008
PROCESS_VM_READ = 0x0010
PROCESS_VM_WRITE = 0x0020
MEM_MAPPED = 0x40000
ULONG_PTR = ctypes.c_ulonglong
class PROCESSENTRY32(ctypes.Structure):
_fields_ = [ ( 'dwSize' , DWORD ) ,
( 'cntUsage' , DWORD) ,
( 'th32ProcessID' , DWORD) ,
( 'th32DefaultHeapID' , ctypes.POINTER(ULONG)) ,
( 'th32ModuleID' , DWORD) ,
( 'cntThreads' , DWORD) ,
( 'th32ParentProcessID' , DWORD) ,
( 'pcPriClassBase' , LONG) ,
( 'dwFlags' , DWORD) ,
( 'szExeFile' , ctypes.c_char * 260 ) ]
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [ ( 'BaseAddress' , ctypes.c_void_p),
( 'AllocationBase' , ctypes.c_void_p),
( 'AllocationProtect' , DWORD),
( 'PartitionID' , WORD),
( 'RegionSize' , ctypes.c_size_t),
( 'State' , DWORD),
( 'Protect' , DWORD),
( 'Type' , DWORD)]
class PSAPI_WORKING_SET_EX_BLOCK(ctypes.Structure):
_fields_ = [ ( 'Flags', ULONG_PTR),
( 'Valid', ULONG_PTR),
( 'ShareCount', ULONG_PTR),
( 'Win32Protection', ULONG_PTR),
( 'Shared', ULONG_PTR),
( 'Node', ULONG_PTR),
( 'Locked', ULONG_PTR),
( 'LargePage', ULONG_PTR),
( 'Reserved', ULONG_PTR),
( 'Bad', ULONG_PTR),
( 'ReservedUlong', ULONG_PTR)]
#class PSAPI_WORKING_SET_EX_INFORMATION(ctypes.Structure):
# _fields_ = [ ( 'VirtualAddress' , ctypes.c_void_p),
# ( 'VirtualAttributes' , PSAPI_WORKING_SET_EX_BLOCK)]
class PSAPI_WORKING_SET_EX_INFORMATION(ctypes.Structure):
_fields_ = [ ( 'VirtualAddress' , ctypes.c_void_p),
#( 'Flags', ULONG_PTR),
( 'Valid', ULONG_PTR, 1)]
#( 'ShareCount', ULONG_PTR),
#( 'Win32Protection', ULONG_PTR),
#( 'Shared', ULONG_PTR),
#( 'Node', ULONG_PTR),
#( 'Locked', ULONG_PTR),
#( 'LargePage', ULONG_PTR),
#( 'Reserved', ULONG_PTR),
#( 'Bad', ULONG_PTR),
#( 'ReservedUlong', ULONG_PTR)]
#def print_values(self):
# for i,v in self._fields_:
# print(i, getattr(self, i))
# The find_dolphin function is based on WindowsDolphinProcess::findPID() from
# aldelaro5's Dolphin memory engine
# https://github.com/aldelaro5/Dolphin-memory-engine
"""
MIT License
Copyright (c) 2017 aldelaro5
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE."""
class Dolphin(object):
def __init__(self):
self.pid = -1
self.memory = None
def reset(self):
self.pid = -1
self.memory = None
def find_dolphin(self, skip_pids=[]):
entry = PROCESSENTRY32()
entry.dwSize = sizeof(PROCESSENTRY32)
snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL)
print(addressof(entry), hex(addressof(entry)))
a = ULONG(addressof(entry))
self.pid = -1
if ctypes.windll.kernel32.Process32First(snapshot, pointer(entry)):
if entry.th32ProcessID not in skip_pids and entry.szExeFile in (b"Dolphin.exe", b"DolphinQt2.exe", b"DolphinWx.exe"):
self.pid = entry.th32ProcessID
else:
while ctypes.windll.kernel32.Process32Next(snapshot, pointer(entry)):
if entry.th32ProcessID in skip_pids:
continue
if entry.szExeFile in (b"Dolphin.exe", b"DolphinQt2.exe", b"DolphinWx.exe"):
self.pid = entry.th32ProcessID
ctypes.windll.kernel32.CloseHandle(snapshot)
if self.pid == -1:
return False
return True
def init_shared_memory(self):
try:
self.memory = shared_memory.SharedMemory('dolphin-emu.'+str(self.pid))
return True
except FileNotFoundError:
return False
def read_ram(self, offset, size):
return self.memory.buf[offset:offset+size]
def write_ram(self, offset, data):
self.memory.buf[offset:offset+len(data)] = data
def read_uint8(self, addr):
assert addr >= 0x80000000
value = self.read_ram(addr-0x80000000, 1)
return unpack(">B", value)[0]
def write_uint8(self, addr, val):
assert addr >= 0x80000000
return self.write_ram(addr - 0x80000000, pack(">B", val))
def read_uint16(self, addr):
assert addr >= 0x80000000
value = self.read_ram(addr-0x80000000, 2)
return unpack(">H", value)[0]
def write_uint16(self, addr, val):
assert addr >= 0x80000000
return self.write_ram(addr - 0x80000000, pack(">H", val))
def read_uint32(self, addr):
assert addr >= 0x80000000
value = self.read_ram(addr-0x80000000, 4)
return unpack(">I", value)[0]
def write_uint32(self, addr, val):
assert addr >= 0x80000000
return self.write_ram(addr - 0x80000000, pack(">I", val))
def read_float(self, addr):
assert addr >= 0x80000000
value = self.read_ram(addr - 0x80000000, 4)
return unpack(">f", value)[0]
def write_float(self, addr, val):
assert addr >= 0x80000000
return self.write_ram(addr - 0x80000000, pack(">f", val))
"""with open("ctypes.txt", "w") as f:
for a in ctypes.__dict__:
f.write(str(a))
f.write("\n")"""
if __name__ == "__main__":
dolphin = Dolphin()
import multiprocessing
if dolphin.find_dolphin():
print("Found Dolphin!")
else:
print("Didn't find Dolphin")
print(dolphin.pid)
dolphin.init_shared_memory()
if dolphin.init_shared_memory():
print("We found MEM1 and/or MEM2!")
else:
print("We didn't find it...")
import random
randint = random.randint
from timeit import default_timer
start = default_timer()
print("Testing Shared Memory Method")
start = default_timer()
count = 500000
for i in range(count):
value = randint(0, 2**32-1)
dolphin.write_uint32(0x80000000, value)
result = dolphin.read_uint32(0x80000000)
assert result == value
diff = default_timer()-start
print(count/diff, "per sec")
print("time: ", diff)

View file

@ -30,7 +30,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": 2,
"id": "bb2b194e-b451-4a35-ba17-f74619d658c5", "id": "bb2b194e-b451-4a35-ba17-f74619d658c5",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@ -88,7 +88,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 3,
"id": "21603f5b-59ad-45d3-945e-58978b8b3099", "id": "21603f5b-59ad-45d3-945e-58978b8b3099",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@ -293,7 +293,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 4,
"id": "a2316c48-2614-4f93-b152-f0d8ab379c9b", "id": "a2316c48-2614-4f93-b152-f0d8ab379c9b",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@ -384,7 +384,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": 23,
"id": "f0c3daf3-4b26-4a4e-a7bf-ad6a7e7e89a0", "id": "f0c3daf3-4b26-4a4e-a7bf-ad6a7e7e89a0",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@ -396,14 +396,14 @@
" [-174.36700439, 2357.66601562, 1443.62805176]])" " [-174.36700439, 2357.66601562, 1443.62805176]])"
] ]
}, },
"execution_count": 4, "execution_count": 23,
"metadata": {}, "metadata": {},
"output_type": "execute_result" "output_type": "execute_result"
} }
], ],
"source": [ "source": [
"raw = 'C3 A5 92 F2 45 01 08 7F 44 DB A6 14 43 2E 5D F4 45 13 5A A8 44 B4 74 19 C3 2E 5D F4 45 13 5A A8 44 B4 74 19'\n", "raw = 'C3 A5 92 F2 45 01 08 7F 44 DB A6 14 43 2E 5D F4 45 13 5A A8 44 B4 74 19 C3 2E 5D F4 45 13 5A A8 44 B4 74 19'\n",
"tri = np.frombuffer(bytes.fromhex(raw), '>f').astype('d').reshape(3, 3)\n", "tri = np.frombuffer(bytes.fromhex(raw), '>f').astype('d').reshape(-1, 3)[:3, :]\n",
"tri" "tri"
] ]
}, },
@ -431,7 +431,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 5, "execution_count": 6,
"id": "4a7cc39b-1506-46ba-a6d9-2f9714d6b747", "id": "4a7cc39b-1506-46ba-a6d9-2f9714d6b747",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
@ -502,7 +502,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": 24,
"id": "2f14f7cf-0187-4fdb-9d18-0e9ef08ff423", "id": "2f14f7cf-0187-4fdb-9d18-0e9ef08ff423",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@ -528,7 +528,8 @@
" # 境界上の一点 a point on the border\n", " # 境界上の一点 a point on the border\n",
" B = tri[1],\n", " B = tri[1],\n",
" # 境界の法線ベクトル normal vector of the border\n", " # 境界の法線ベクトル normal vector of the border\n",
" nB = array([0, 0, -1]),\n", " # -(n.x, 0, n.z); n = cross(P1-P0, P2-P1) in game\n",
" nB = np.cross(tri[0]-tri[1], tri[2]-tri[1])*(1, 0, 1),\n",
" # すり抜ける地面の高さ\n", " # すり抜ける地面の高さ\n",
" # height of the triangle that Mario clips through\n", " # height of the triangle that Mario clips through\n",
" yB = tri[1][1],\n", " yB = tri[1][1],\n",
@ -552,7 +553,7 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 8, "execution_count": 25,
"id": "0b64860d-b36b-4b5c-bb03-3e00daca9014", "id": "0b64860d-b36b-4b5c-bb03-3e00daca9014",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@ -590,8 +591,8 @@
"# ラベルのフォーマットを設定(xは目盛の値、iは番号)\n", "# ラベルのフォーマットを設定(xは目盛の値、iは番号)\n",
"# set the format of the labels\n", "# set the format of the labels\n",
"# (x is the value of the tick, and i is the index)\n", "# (x is the value of the tick, and i is the index)\n",
"ax.xaxis.set_major_formatter(lambda x, i: '%.2f'%x)\n", "ax.xaxis.set_major_formatter(plticker.FormatStrFormatter('%.2f'))\n",
"ax.yaxis.set_major_formatter(lambda x, i: '%.2f'%x)\n", "ax.yaxis.set_major_formatter(plticker.FormatStrFormatter('%.2f'))\n",
"\n", "\n",
"# x軸のラベルを30度回転\n", "# x軸のラベルを30度回転\n",
"# rotate x label 30 deg\n", "# rotate x label 30 deg\n",
@ -606,7 +607,7 @@
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "efec5e1f-16d2-4d66-b596-11016f934f1d", "id": "9163f1a0-9c6b-45ea-a856-ba98a6c9303c",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": []
@ -1094,6 +1095,14 @@
"OTHER DEALINGS IN THE SOFTWARE." "OTHER DEALINGS IN THE SOFTWARE."
] ]
}, },
{
"cell_type": "raw",
"id": "0412e7fe-ec24-43d4-bac0-ff42ab11bcd0",
"metadata": {},
"source": [
"なおさん(@naosan_RTA2)が一部の数式を追加し、Formatterのバグを修正してくださいました。ありがとうございます"
]
},
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,

220
shape.py Normal file
View file

@ -0,0 +1,220 @@
'''
MIT License
Copyright (c) 2022 sup39[サポミク]
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
'''
import numpy as np
array = np.array
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib import patches
normalize = lambda x: x/np.linalg.norm(x)
# 多角形
class Polygon():
def __init__(self, verts):
'''
* self.verts:
頂点の配列
Array of vertices
'''
self.verts = np.array(verts)
def clipLine(self, p, n, c):
'''
半平面 (x-p)n >= c との共通部分を取る
Take intersection with half plane (x-p)n >= c
* p:
直線上の一点
Any point on the line
* n:
直線の法線ベクトル
Normal vector of the line
* c:
定数
Constant
'''
p, n, c = map(array, (p, n, c))
r = np.dot(self.verts-p, n)-c
verts = []
for i in range(len(r)):
v0, r0 = self.verts[i-1], r[i-1]
v1, r1 = self.verts[i], r[i]
if r1 >= 0:
if r0 < 0: # (- +)
verts.append((r1*v0-r0*v1)/(-r0+r1))
verts.append(v1)
elif r0 >= 0: # (+ -)
verts.append((-r1*v0+r0*v1)/(r0-r1))
self.verts = np.array(verts) if len(verts) else self.verts[:0]
@property # getter
def path(self):
if self.verts.shape[0] == 0: return None
verts = self.verts
assert verts.shape[-1] == 2, 'verts should be 2D'
return Path([*verts, (0, 0)], [
Path.MOVETO,
*(Path.LINETO for _ in range(1, verts.shape[0])),
Path.CLOSEPOLY,
])
def plot(self, margin=0.05, facecolor='#2ee5b8', lw=1):
'''
この多角形を描画しfigとaxを返す
Plot this polygon and return ``fig'' and ``ax''
* margin:
Set margin of the plot
* facecolor:
面の色
Face color
* lw:
線の太さ
Line width
'''
fig, ax = plt.subplots()
if self.verts.shape[0] == 0: return ax
# path
path = self.path
patch = patches.PathPatch(path, facecolor=facecolor, lw=lw)
ax.add_patch(patch)
verts = self.verts
xMax, yMax = verts.max(axis=0)
xMin, yMin = verts.min(axis=0)
xMg, yMg = verts.ptp(axis=0)*margin
ax.set_xlim(xMin-xMg, xMax+xMg)
ax.set_ylim(yMin-yMg, yMax+yMg)
return fig, ax
def __repr__(self):
return 'Polygon with %d vertices:\n%s'%(
len(self.verts),
array(self.verts, 'f'),
)
# 多面体
class Polyhedron:
def __init__(self, verts, edges):
'''
* self.verts:
頂点の配列
Array of vertices
* self.edges:
(2頂点の番号)の配列
Array of edges (indices of 2 vertices)
'''
self.verts = np.array(verts)
self.edges = edges
def clipPlane(self, p, n, c=0):
'''
半空間 (x-p)n >= c との共通部分を取る
Take intersection with half space (x-p)n >= c
* p:
平面上の一点
Any point on the plane
* n:
直線の法線ベクトル
Normal vector of the plane
* c:
定数
Constant
'''
p, n, c = map(array, (p, n, c))
r = np.dot(self.verts-p, n)-c
rb = [s>=0 for s in r]
# map vertex indices old to new
io2n = {
iO: iN
for iN, iO in enumerate(iO for iO, sb in enumerate(rb) if sb)
}
# handle old vert
verts = [v for v, sb in zip(self.verts, rb) if sb]
edges = []
for i0, i1 in self.edges:
if rb[i0] and rb[i1]:
# remain
edges.append((io2n[i0], io2n[i1]))
elif rb[i0] or rb[i1]:
# new vert
v0, r0 = self.verts[i0], abs(r[i0])
v1, r1 = self.verts[i1], abs(r[i1])
vN = (r1*v0+r0*v1)/(r0+r1)
edges.append((io2n[i0 if rb[i0] else i1], len(verts)))
verts.append(vN)
# else drop edge
# add new face
nOld = len(io2n)
vNews = verts[nOld:]
if len(vNews):
assert len(vNews) >= 3
p0, p1 = vNews[:2]
# choose p1-p0 as e1
e1 = normalize(p1-p0)
# choose e2 that ⊥ n, e1
e2 = normalize(np.cross(n, e1))
# set (p0+p1)/2 as new origin, and use {e1, e2} as new basis
cNews = np.dot(vNews-(p0+p1)/2, array([e1, e2]).transpose())
# indices of new verts CCW
jNews = nOld+np.arctan2(cNews[:,0], cNews[:,1]).argsort()
# add to edge
for i in range(len(vNews)):
edges.append((jNews[i-1], jNews[i]))
# final
self.verts = array(verts)
self.edges = edges
def slicePlane(self, p, n):
'''
平面 (x-p)n=0 との共通部分(多角形)の頂点を返す
Return vertices of intersection(polygon) with plane (x-p)n=0
* p:
平面上の一点
Any point on the plane
* n:
平面の法線ベクトル
Normal vector of the plane
'''
p, n = map(array, (p, n))
r = np.dot(self.verts-p, n)
vNews = []
# handle old verts
for i0, i1 in self.edges:
# two vertices on other side of the plane
if np.sign(r[i0]) != np.sign(r[i1]):
v0, r0 = self.verts[i0], abs(r[i0])
v1, r1 = self.verts[i1], abs(r[i1])
vN = (r1*v0+r0*v1)/(r0+r1)
vNews.append(vN)
# new verts
if len(vNews):
assert len(vNews) >= 2
p0, p1 = vNews[:2]
e1 = normalize(p1-p0)
e2 = normalize(np.cross(n, e1))
cNews = np.dot(vNews-(p0+p1)/2, array([e1, e2]).transpose())
jNews = np.arctan2(cNews[:,0], cNews[:,1]).argsort()
return array([vNews[j] for j in jNews])
else:
return self.verts[:0]
def __repr__(self):
return 'Polyhedron with %d vertices and %d edges:\n%s'%(
len(self.verts), len(self.edges),
array([self.verts[[i0, i1]] for i0, i1 in self.edges], 'f')
)

302
wall.ipynb Normal file

File diff suppressed because one or more lines are too long