add wall.ipynb
This commit is contained in:
parent
143fad8669
commit
233704bc56
6 changed files with 805 additions and 18 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
__pycache__/
|
14
README.md
14
README.md
|
@ -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
251
memorylib.py
Normal 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)
|
||||||
|
|
35
shape.ipynb
35
shape.ipynb
|
@ -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
220
shape.py
Normal 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
302
wall.ipynb
Normal file
File diff suppressed because one or more lines are too long
Reference in a new issue