3d model experiments

This commit is contained in:
Yonokid
2025-07-26 17:14:03 -04:00
parent fc985ca410
commit e6f2f19c1a
15 changed files with 16794 additions and 38 deletions

View File

@@ -2,6 +2,7 @@ import sqlite3
from pathlib import Path
import pyray as ray
from raylib import CAMERA_ORTHOGRAPHIC
from raylib.defines import (
RL_FUNC_ADD,
RL_ONE,
@@ -67,11 +68,18 @@ def main():
ray.set_config_flags(ray.ConfigFlags.FLAG_MSAA_4X_HINT)
ray.set_trace_log_level(ray.TraceLogLevel.LOG_WARNING)
camera = ray.Camera3D()
camera.position = ray.Vector3(0.0, 0.0, 10.0) # Camera position
camera.target = ray.Vector3(0.0, 0.0, 0.0) # Camera looking at point
camera.up = ray.Vector3(0.0, 1.0, 0.0) # Camera up vector
camera.fovy = screen_height # For orthographic, this acts as the view height
camera.projection = CAMERA_ORTHOGRAPHIC
ray.init_window(screen_width, screen_height, "PyTaiko")
if global_data.config["video"]["borderless"]:
ray.toggle_borderless_windowed()
if global_data.config["video"]["fullscreen"]:
ray.maximize_window()
ray.toggle_fullscreen()
current_screen = Screens.LOADING
@@ -104,18 +112,23 @@ def main():
ray.rl_set_blend_factors_separate(RL_SRC_ALPHA, RL_ONE_MINUS_SRC_ALPHA, RL_ONE, RL_ONE_MINUS_SRC_ALPHA, RL_FUNC_ADD, RL_FUNC_ADD)
ray.set_exit_key(ray.KeyboardKey.KEY_A)
global_data.textures = load_all_textures_from_zip(Path('Graphics/lumendata/intermission.zip'))
while not ray.window_should_close():
while not ray.window_should_close():
ray.begin_texture_mode(target)
ray.begin_blend_mode(ray.BlendMode.BLEND_CUSTOM_SEPARATE)
screen = screen_mapping[current_screen]
# Begin 3D mode with orthographic camera
if ray.is_key_pressed(ray.KeyboardKey.KEY_F11):
ray.toggle_fullscreen()
ray.toggle_borderless_windowed()
next_screen = screen.update()
ray.clear_background(ray.BLACK)
screen.draw()
ray.begin_mode_3d(camera)
screen.draw_3d()
ray.end_mode_3d()
if next_screen is not None:
current_screen = next_screen
@@ -129,7 +142,7 @@ def main():
ray.draw_texture_pro(
target.texture,
ray.Rectangle(0, 0, target.texture.width, -target.texture.height),
ray.Rectangle(0, 0, screen_width, screen_height),
ray.Rectangle(0, 0, ray.get_screen_width(), ray.get_screen_height()),
ray.Vector2(0,0),
0,
ray.WHITE

View File

@@ -11,14 +11,14 @@ class Background:
def __init__(self, screen_width: int, screen_height: int):
self.screen_width = screen_width
self.screen_height = screen_height
self.donbg = DonBG.create(self.screen_width, self.screen_height, random.randint(6, 6), 1)
self.donbg = DonBG.create(self.screen_width, self.screen_height, random.randint(1, 6), 1)
self.bg_normal = BGNormal.create(self.screen_width, self.screen_height, random.randint(1, 5))
self.bg_fever = BGFever.create(self.screen_width, self.screen_height, 4)
self.footer = Footer(self.screen_width, self.screen_height, random.randint(1, 3))
self.is_clear = False
def update(self, current_time_ms: float, is_clear: bool):
self.is_clear = is_clear
self.donbg.update(current_time_ms, is_clear)
self.donbg.update(current_time_ms, self.is_clear)
self.bg_normal.update(current_time_ms)
self.bg_fever.update(current_time_ms)
def draw(self):
@@ -38,7 +38,7 @@ class DonBG:
@staticmethod
def create(screen_width: int, screen_height: int, index: int, player_num: int):
map = [None, DonBG1, DonBG1, DonBG1, DonBG1, DonBG1, DonBG6]
map = [None, DonBG1, DonBG2, DonBG3, DonBG4, DonBG5, DonBG6]
selected_obj = map[index]
return selected_obj(index, screen_width, screen_height, player_num)
@@ -75,14 +75,123 @@ class DonBG1(DonBGBase):
def draw(self):
texture_index = 0
if self.is_clear:
texture_index = 2
texture_index = 3
top_texture = self.textures[self.name + f'_{self.player_num}p'][0 + texture_index]
for i in range(0, self.screen_width + top_texture.width, top_texture.width):
ray.draw_texture(top_texture, i + int(self.move.attribute), 0, ray.WHITE)
wave_texture = self.textures[self.name + f'_{self.player_num}p'][1 + texture_index]
for i in range(0, self.screen_width + (wave_texture.width+80), wave_texture.width+80):
ray.draw_texture(wave_texture, i + int(self.move.attribute * ((wave_texture.width+80)/top_texture.width)) + 100, int(self.overlay_move.attribute), ray.WHITE)
texture = self.textures[self.name + f'_{self.player_num}p'][2 + texture_index]
for i in range(0, self.screen_width + texture.width + texture.width*5, texture.width):
ray.draw_texture(texture, i + int(self.move.attribute * (texture.width/top_texture.width)*3), int(self.overlay_move.attribute) + 105, ray.WHITE)
class DonBG2(DonBGBase):
def __init__(self, index: int, screen_width: int, screen_height: int, player_num: int):
super().__init__(index, screen_width, screen_height, player_num)
self.overlay_move = Animation.create_move(1500, start_position=0, total_distance=20, reverse_delay=0)
def update(self, current_time_ms: float, is_clear: bool):
super().update(current_time_ms, is_clear)
self.overlay_move.update(current_time_ms)
if self.overlay_move.is_finished:
self.overlay_move.restart()
def draw(self):
texture_index = 0
if self.is_clear:
texture_index = 2
top_texture = self.textures[self.name + f'_{self.player_num}p'][texture_index]
for i in range(0, self.screen_width + top_texture.width, top_texture.width):
ray.draw_texture(top_texture, i + int(self.move.attribute), 0, ray.WHITE)
texture = self.textures[self.name + f'_{self.player_num}p'][1 + texture_index]
for i in range(0, self.screen_width + texture.width, texture.width):
ray.draw_texture(texture, i + int(self.move.attribute), int(self.overlay_move.attribute) - 50, ray.WHITE)
ray.draw_texture(texture, i + int(self.move.attribute), int(self.overlay_move.attribute) - 25, ray.WHITE)
class DonBG3(DonBGBase):
def __init__(self, index: int, screen_width: int, screen_height: int, player_num: int):
super().__init__(index, screen_width, screen_height, player_num)
duration = 266
bounce_distance = 40
self.bounce_up = Animation.create_move(duration, total_distance=-bounce_distance, ease_out='quadratic')
self.bounce_down = Animation.create_move(duration, total_distance=-bounce_distance, ease_in='quadratic', delay=self.bounce_up.duration)
self.overlay_move = Animation.create_move(duration*3, total_distance=20, reverse_delay=0, ease_in='quadratic', ease_out='quadratic', delay=self.bounce_up.duration+self.bounce_down.duration)
self.overlay_move_2 = Animation.create_move(duration*3, total_distance=20, reverse_delay=0, ease_in='quadratic', ease_out='quadratic', delay=self.bounce_up.duration+self.bounce_down.duration+self.overlay_move.duration)
def update(self, current_time_ms: float, is_clear: bool):
super().update(current_time_ms, is_clear)
self.bounce_up.update(current_time_ms)
self.bounce_down.update(current_time_ms)
self.overlay_move.update(current_time_ms)
self.overlay_move_2.update(current_time_ms)
if self.overlay_move_2.is_finished:
self.bounce_up.restart()
self.bounce_down.restart()
self.overlay_move.restart()
self.overlay_move_2.restart()
def draw(self):
texture_index = 0
if self.is_clear:
texture_index = 2
top_texture = self.textures[self.name + f'_{self.player_num}p'][0 + texture_index]
for i in range(0, self.screen_width + top_texture.width, top_texture.width):
ray.draw_texture(top_texture, i + int(self.move.attribute), 0, ray.WHITE)
texture = self.textures[self.name + f'_{self.player_num}p'][1 + texture_index]
for i in range(0, self.screen_width + texture.width, texture.width):
ray.draw_texture(texture, i + int(self.move.attribute*2), int(self.bounce_up.attribute) - int(self.bounce_down.attribute) - 25 + int(self.overlay_move.attribute) + int(self.overlay_move_2.attribute), ray.WHITE)
class DonBG4(DonBGBase):
def __init__(self, index: int, screen_width: int, screen_height: int, player_num: int):
super().__init__(index, screen_width, screen_height, player_num)
self.overlay_move = Animation.create_move(1500, start_position=0, total_distance=20, reverse_delay=0)
def update(self, current_time_ms: float, is_clear: bool):
super().update(current_time_ms, is_clear)
self.overlay_move.update(current_time_ms)
if self.overlay_move.is_finished:
self.overlay_move.restart()
def draw(self):
texture_index = 0
if self.is_clear:
texture_index = 2
top_texture = self.textures[self.name + f'_{self.player_num}p'][texture_index]
for i in range(0, self.screen_width + top_texture.width, top_texture.width):
ray.draw_texture(top_texture, i + int(self.move.attribute), 0, ray.WHITE)
texture = self.textures[self.name + f'_{self.player_num}p'][1 + texture_index]
for i in range(0, self.screen_width + texture.width, texture.width):
ray.draw_texture(texture, i + int(self.move.attribute), int(self.overlay_move.attribute) - 25, ray.WHITE)
class DonBG5(DonBGBase):
def __init__(self, index: int, screen_width: int, screen_height: int, player_num: int):
super().__init__(index, screen_width, screen_height, player_num)
duration = 266
bounce_distance = 40
self.bounce_up = Animation.create_move(duration, total_distance=-bounce_distance, ease_out='quadratic')
self.bounce_down = Animation.create_move(duration, total_distance=-bounce_distance, ease_in='quadratic', delay=self.bounce_up.duration)
self.adjust = Animation.create_move(1000, total_distance=10, reverse_delay=0, delay=self.bounce_up.duration+self.bounce_down.duration)
def update(self, current_time_ms: float, is_clear: bool):
super().update(current_time_ms, is_clear)
self.bounce_up.update(current_time_ms)
self.bounce_down.update(current_time_ms)
self.adjust.update(current_time_ms)
if self.adjust.is_finished:
self.bounce_up.restart()
self.bounce_down.restart()
self.adjust.restart()
def draw(self):
texture_index = 0
if self.is_clear:
texture_index = 2
top_texture = self.textures[self.name + f'_{self.player_num}p'][0 + texture_index]
for i in range(0, self.screen_width + top_texture.width, top_texture.width):
ray.draw_texture(top_texture, i + int(self.move.attribute), 0, ray.WHITE)
texture = self.textures[self.name + f'_{self.player_num}p'][1 + texture_index]
for i in range(0, self.screen_width + texture.width + texture.width*2, texture.width*2):
ray.draw_texture(texture, i + int((self.move.attribute * (texture.width/top_texture.width))*2), int(self.bounce_up.attribute) - int(self.bounce_down.attribute) - int(self.adjust.attribute), ray.WHITE)
class DonBG6(DonBGBase):
def __init__(self, index: int, screen_width: int, screen_height: int, player_num: int):

View File

@@ -169,6 +169,8 @@ def calculate_base_score(play_note_list: deque[Note | Drumroll | Balloon]) -> in
balloon_count += note.count
else:
total_notes += 1
if total_notes == 0:
return 0
total_score = (1000000 - (balloon_count * 100) - (drumroll_sec * 1692.0079999994086)) / total_notes
return math.ceil(total_score / 10) * 10

BIN
model/cos_100000_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
model/cos_100000_02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

76
model/mikudon.mtl Normal file
View File

@@ -0,0 +1,76 @@
# Blender 4.5.0 MTL File: 'None'
# www.blender.org
newmtl RGB_don_color_S_CUS_0x10000001_
Ns 360.000000
Ka 1.000000 1.000000 1.000000
Kd 0.968627 0.945098 0.882352
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl cos_100000_HEADBODY_01_color_S_CUS_0x10000000__AT_ZERO___CULLNO
Ns 360.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
map_Kd cos_100000_01.png
illum 2
newmtl cos_100000_HEADBODY_02_color_S_CUS_0x10000000__AT_ZERO___CULLNO
Ns 360.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
map_Kd cos_100000_02.png
illum 2
newmtl cos_100000_HEADBODY_03_color_S_CUS_0x10000000__A_AB_AA_ADD_AT_O
Ns 360.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
map_Kd cos_100000_01.png
illum 2
newmtl cos_100000_HEADBODY_04_color_S_CUS_0x10000000__A_AB_AA_ADD_AT_O
Ns 360.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
map_Kd cos_100000_02.png
illum 2
newmtl don_FACEHIP_color_S_CUS_0x10000001_
Ns 360.000000
Ka 1.000000 1.000000 1.000000
Kd 0.968627 0.945098 0.882352
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl face2_FACIAL_S_CUS_0x10000002_A_AB_AA_ADD
Ns 360.000000
Ka 1.000000 1.000000 1.000000
Kd 0.968627 0.286274 0.156862
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2

16482
model/mikudon.obj Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,16 @@
import pyray as ray
class DevScreen:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.screen_init = False
self.model = ray.load_model("model/mikudon.obj")
self.model_position = ray.Vector3(-450.0, 100.0, -180.0)
self.model_scale = 1000.0
self.model_rotation_y = 45.0 # Face towards camera (rotate 180 degrees on Y-axis)
self.model_rotation_x = 0.0 # No up/down tilt
self.model_rotation_z = 0.0 # No roll
def on_screen_start(self):
if not self.screen_init:
@@ -13,12 +18,26 @@ class DevScreen:
def on_screen_end(self, next_screen: str):
self.screen_init = False
ray.unload_model(self.model)
return next_screen
def update(self):
self.on_screen_start()
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
return self.on_screen_end('RESULT')
return self.on_screen_end('GAME')
def draw(self):
pass
def draw_3d(self):
# Method 1: Using draw_model_ex for full control over rotation
rotation_axis = ray.Vector3(0.0, 1.0, 0.0) # Y-axis for horizontal rotation
ray.draw_model_ex(
self.model,
self.model_position,
rotation_axis,
self.model_rotation_y,
ray.Vector3(self.model_scale, self.model_scale, self.model_scale),
ray.WHITE
)

View File

@@ -304,3 +304,6 @@ class EntryScreen:
src = ray.Rectangle(0, 0, self.texture_black.width, self.texture_black.height)
dest = ray.Rectangle(0, 0, self.width, self.height)
ray.draw_texture_pro(self.texture_black, src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
def draw_3d(self):
pass

View File

@@ -1,6 +1,7 @@
import bisect
import math
import sqlite3
from collections import deque
from pathlib import Path
from typing import Optional
@@ -103,22 +104,26 @@ class GameScreen:
self.textures['onp_renda_dai'][0], self.textures['onp_renda_dai'][1],
self.textures['onp_fusen'][0]]
self.tja = TJAParser(song, start_delay=self.start_delay, distance=self.width - GameScreen.JUDGE_X)
if self.tja.metadata.bgmovie != Path() and self.tja.metadata.bgmovie.exists():
self.movie = VideoPlayer(self.tja.metadata.bgmovie)
self.movie.set_volume(0.0)
if song == Path(''):
self.start_ms = get_current_ms()
self.tja = None
else:
self.movie = None
session_data.song_title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en'])
self.tja = TJAParser(song, start_delay=self.start_delay, distance=self.width - GameScreen.JUDGE_X)
if self.tja.metadata.bgmovie != Path() and self.tja.metadata.bgmovie.exists():
self.movie = VideoPlayer(self.tja.metadata.bgmovie)
self.movie.set_volume(0.0)
else:
self.movie = None
session_data.song_title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en'])
if not hasattr(self, 'song_music'):
if self.tja.metadata.wave.exists() and self.tja.metadata.wave.is_file():
self.song_music = audio.load_sound(self.tja.metadata.wave)
audio.normalize_sound(self.song_music, 0.1935)
else:
self.song_music = None
self.start_ms = (get_current_ms() - self.tja.metadata.offset*1000)
self.player_1 = Player(self, 1, difficulty)
if not hasattr(self, 'song_music'):
if self.tja.metadata.wave.exists() and self.tja.metadata.wave.is_file():
self.song_music = audio.load_sound(self.tja.metadata.wave)
audio.normalize_sound(self.song_music, 0.1935)
else:
self.song_music = None
self.start_ms = (get_current_ms() - self.tja.metadata.offset*1000)
def on_screen_start(self):
if not self.screen_init:
@@ -146,6 +151,8 @@ class GameScreen:
return next_screen
def write_score(self):
if self.tja is None:
return
if global_data.config['general']['autoplay']:
return
with sqlite3.connect('scores.db') as con:
@@ -173,14 +180,15 @@ class GameScreen:
if self.transition is not None:
self.transition.update(get_current_ms())
self.current_ms = get_current_ms() - self.start_ms
if (self.current_ms >= self.tja.metadata.offset*1000 + self.start_delay - global_data.config["general"]["judge_offset"]) and not self.song_started:
if self.song_music is not None:
if not audio.is_sound_playing(self.song_music):
audio.play_sound(self.song_music)
print(f"Song started at {self.current_ms}")
if self.movie is not None:
self.movie.start(get_current_ms())
self.song_started = True
if self.tja is not None:
if (self.current_ms >= self.tja.metadata.offset*1000 + self.start_delay - global_data.config["general"]["judge_offset"]) and not self.song_started:
if self.song_music is not None:
if not audio.is_sound_playing(self.song_music):
audio.play_sound(self.song_music)
print(f"Song started at {self.current_ms}")
if self.movie is not None:
self.movie.start(get_current_ms())
self.song_started = True
if self.movie is not None:
self.movie.update()
else:
@@ -228,6 +236,9 @@ class GameScreen:
if self.result_transition is not None:
self.result_transition.draw(self.width, self.height, global_data.textures['shutter'][0], global_data.textures['shutter'][1])
def draw_3d(self):
self.player_1.draw_3d(self)
class Player:
TIMING_GOOD = 25.0250015258789
TIMING_OK = 75.0750045776367
@@ -239,7 +250,10 @@ class Player:
self.difficulty = difficulty
self.visual_offset = global_data.config["general"]["visual_offset"]
self.play_notes, self.draw_note_list, self.draw_bar_list = game_screen.tja.notes_to_position(self.difficulty)
if game_screen.tja is not None:
self.play_notes, self.draw_note_list, self.draw_bar_list = game_screen.tja.notes_to_position(self.difficulty)
else:
self.play_notes, self.draw_note_list, self.draw_bar_list = deque(), deque(), deque()
self.total_notes = len([note for note in self.play_notes if 0 < note.type < 5])
self.base_score = calculate_base_score(self.play_notes)
@@ -275,12 +289,23 @@ class Player:
self.input_log: dict[float, tuple] = dict()
self.gauge = Gauge(self.difficulty, game_screen.tja.metadata.course_data[self.difficulty].level, self.total_notes)
if game_screen.tja is not None:
stars = game_screen.tja.metadata.course_data[self.difficulty].level
else:
stars = 0
self.gauge = Gauge(self.difficulty, stars, self.total_notes)
self.gauge_hit_effect: list[GaugeHitEffect] = []
self.autoplay_hit_side = 'L'
self.last_subdivision = -1
self.model = ray.load_model("model/mikudon.obj")
self.model_position = ray.Vector3(-475.0, 160.0, -180.0)
self.model_scale = 850.0
self.model_rotation_y = 30.0 # Face towards camera (rotate 180 degrees on Y-axis)
self.model_rotation_x = 0.0 # No up/down tilt
self.model_rotation_z = 0.0
def get_result_score(self):
return self.score, self.good_count, self.ok_count, self.bad_count, self.total_drumroll, self.max_combo
@@ -383,9 +408,10 @@ class Player:
else:
note_type = game_screen.note_type_list[note.type][0]
self.combo += 1
if self.combo > self.max_combo:
self.max_combo = self.combo
if note.type < 7:
self.combo += 1
if self.combo > self.max_combo:
self.max_combo = self.combo
self.draw_arc_list.append(NoteArc(note_type, get_current_ms(), self.player_number, note.type == 3 or note.type == 4) or note.type == 7)
#game_screen.background.chibis.append(game_screen.background.Chibi())
@@ -710,6 +736,20 @@ class Player:
anim.draw(game_screen)
#ray.draw_circle(game_screen.width//2, game_screen.height, 300, ray.ORANGE)
def draw_3d(self):
'''
rotation_axis = ray.Vector3(0.0, 1.0, 0.0) # Y-axis for horizontal rotation
ray.draw_model_ex(
self.model,
self.model_position,
rotation_axis,
self.model_rotation_y,
ray.Vector3(self.model_scale, self.model_scale, self.model_scale),
ray.WHITE
)
'''
pass
class Judgement:
def __init__(self, type: str, big: bool, ms_display: Optional[float]=None):
self.type = type

View File

@@ -112,3 +112,5 @@ class LoadScreen:
if self.fade_in is not None:
ray.draw_rectangle(0, 0, self.width, self.height, ray.fade(ray.WHITE, self.fade_in.attribute))
def draw_3d(self):
pass

View File

@@ -1,4 +1,3 @@
import math
from pathlib import Path
import pyray as ray
@@ -252,6 +251,9 @@ class ResultScreen:
if self.fade_out is not None:
self.fade_out.draw(self.width, self.height)
def draw_3d(self):
pass
class Crown:
def __init__(self):
duration = 466

View File

@@ -264,3 +264,5 @@ class SettingsScreen:
else:
# Draw exit instruction
ray.draw_text("Press Don to exit settings", 250, 100, 20, ray.GREEN)
def draw_3d(self):
pass

View File

@@ -314,6 +314,9 @@ class SongSelectScreen:
if self.game_transition is not None:
self.game_transition.draw(self.screen_height)
def draw_3d(self):
pass
class SongBox:
OUTLINE_MAP = {
555: ray.Color(0, 77, 104, 255),

View File

@@ -127,6 +127,9 @@ class TitleScreen:
dest = ray.Rectangle(0, 0, self.width, self.height)
ray.draw_texture_pro(self.texture_black, src, dest, ray.Vector2(0, 0), 0, ray.fade(ray.WHITE, self.fade_out.attribute))
def draw_3d(self):
pass
class WarningScreen:
class X:
DELAY = 4250