mirror of
https://gitlab.com/skmp/dca3-game.git
synced 2025-04-28 13:07:59 +03:00
Merge branch 'skmp/anim-compress' into 'main'
Anim Dleta Compression See merge request skmp/dca3-game!34
This commit is contained in:
commit
3073b24cd9
27 changed files with 2578 additions and 708 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -374,6 +374,7 @@ liberty/*.iso
|
|||
liberty/dca-liberty.elf
|
||||
liberty/dca-liberty.elf.bin
|
||||
liberty/dca-liberty-sim.elf
|
||||
liberty/animtool*
|
||||
liberty/texconv*
|
||||
liberty/imgtool*
|
||||
liberty/extract-sfx*
|
||||
|
@ -391,6 +392,7 @@ miami/*.iso
|
|||
miami/dca-miami.elf
|
||||
miami/dca-miami.elf.bin
|
||||
miami/dca-miami-sim.elf
|
||||
miami/animtool*
|
||||
miami/texconv*
|
||||
miami/imgtool*
|
||||
miami/extract-sfx*
|
||||
|
|
|
@ -18,6 +18,7 @@ MOD_NAME?=
|
|||
GTA_DIR?=../../gta3
|
||||
GTA_MOD_DIR?=../../gta3_mod$(MOD_NAME)
|
||||
GTA_MOD_IMG_DIR?=$(GTA_MOD_DIR)/img
|
||||
GTA_MOD_CUTS_DIR=$(GTA_MOD_DIR)/cuts
|
||||
GTA_MOD_SFX_DIR?=$(GTA_MOD_DIR)/sfx
|
||||
GTA_MOD_LOOSE_DIR?=$(GTA_MOD_DIR)/loose
|
||||
|
||||
|
@ -28,6 +29,8 @@ REPACK_IMG_DC_DIR?=$(REPACK_DIR)/img-dc
|
|||
REPACK_SFX_ORIG_DIR?=$(REPACK_DIR)/sfx-orig
|
||||
REPACK_SFX_DC_DIR?=$(REPACK_DIR)/sfx-dc
|
||||
REPACK_STREAM_DECODED_DIR?=$(REPACK_DIR)/stream-decoded
|
||||
REPACK_CUTS_ORIG_DIR?=$(REPACK_DIR)/cuts-orig
|
||||
REPACK_CUTS_DC_DIR?=$(REPACK_DIR)/cuts-dc
|
||||
|
||||
LIBS :=
|
||||
TEXCONV_FLAGS :=
|
||||
|
@ -287,6 +290,9 @@ texconv: $(OBJS_TEXCONV) | pvrtex # You'll have to rebuild pvrtex manually if yo
|
|||
%.texconv.o: %.cpp
|
||||
$(CXX) -std=c++2a -c -O3 -g -MMD -MP -o $@ -I../vendor/koshle -I../vendor/librw/src $(INCLUDE) -I../vendor/emu -I../vendor/crypto -I../vendor/TriStripper/include $(DEFINES) -DDC_TEXCONV -DDC_SIM -D_INC_WINDOWS $(TEXCONV_FLAGS) $<
|
||||
|
||||
animtool: ../src/tools/animtool.cpp
|
||||
$(CXX) -std=c++17 -o $@ -g -O0 $<
|
||||
|
||||
-include $(DEPS)
|
||||
|
||||
#### Repacking ####
|
||||
|
@ -355,10 +361,16 @@ TEXTURE_DOWNSAMPLE_IMG ?= HALF
|
|||
-include sfxlooplist.mk
|
||||
-include wavlist.mk
|
||||
-include mp3list.mk
|
||||
-include cuts.mk
|
||||
|
||||
IMG_TEXTURES_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_TEXTURES))
|
||||
IMG_MODELS_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_MODELS))
|
||||
|
||||
CUTS_IFP_DC = $(addprefix $(REPACK_CUTS_DC_DIR)/, $(CUTS_IFP))
|
||||
CUTS_MISC_DC = $(addprefix $(REPACK_CUTS_DC_DIR)/, $(CUTS_MISC))
|
||||
|
||||
LOOSE_FILES_DC = $(addprefix $(REPACK_GTA_DIR)/, $(MISC_FILES))
|
||||
|
||||
SFX_DC_DIR = $(REPACK_GTA_DIR)/sfx
|
||||
SFX_DC_RAW = $(SFX_DC_DIR)/sfx_all.raw
|
||||
SFX_DC_DSC = $(SFX_DC_DIR)/sfx_all.dsc
|
||||
|
@ -368,6 +380,9 @@ STREAM_ADPCM_DC = $(addprefix $(REPACK_GTA_DIR)/stream/, $(STREAM_WAV:.wav=.APM)
|
|||
IMG_TEXTURES_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_TEXTURES))
|
||||
IMG_MODELS_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_MODELS))
|
||||
|
||||
CUTS_IFP_ORIG = $(addprefix $(REPACK_CUTS_ORIG_DIR)/, $(CUTS_IFP))
|
||||
CUTS_MISC_ORIG = $(addprefix $(REPACK_CUTS_ORIG_DIR)/, $(CUTS_MISC))
|
||||
|
||||
SFX_ORIG = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_WAV))
|
||||
SFX_ORIG_LOOP = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_LOOP_WAV))
|
||||
SFX_REPACK_DC_WAV = $(addprefix $(REPACK_SFX_DC_DIR)/, $(SFX_WAV) $(SFX_LOOP_WAV))
|
||||
|
@ -391,6 +406,15 @@ $(REPACK_DIR)/unpacked: imgtool $(GTA_DIR)/models/gta3.img $(GTA_DIR)/models/gta
|
|||
$(IMG_TEXTURES_ORIG) $(IMG_MODELS_ORIG): $(REPACK_DIR)/unpacked
|
||||
@touch $@
|
||||
|
||||
$(REPACK_DIR)/unpacked-cuts: imgtool $(GTA_DIR)/anim/cuts.img $(GTA_DIR)/anim/cuts.dir
|
||||
mkdir -p $(@D)
|
||||
./imgtool unpack "$(GTA_DIR)/anim/cuts" "$(REPACK_CUTS_ORIG_DIR)"
|
||||
@touch $@
|
||||
|
||||
$(CUTS_IFP_ORIG) $(CUTS_MISC_ORIG): $(REPACK_DIR)/unpacked-cuts
|
||||
@test -f $@
|
||||
@touch $@
|
||||
|
||||
# First try the mods img directory
|
||||
$(REPACK_IMG_DC_DIR)/%.dff: $(GTA_MOD_IMG_DIR)/%.dff texconv
|
||||
@mkdir -p $(@D)
|
||||
|
@ -423,6 +447,30 @@ $(REPACK_IMG_DC_DIR)/%.TXD: $(REPACK_IMG_ORIG_DIR)/%.TXD texconv
|
|||
@mkdir -p $(@D)
|
||||
./texconv $< $@ $(DEFAULT_RES) $(DEFAULT_RES) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_IMG)
|
||||
|
||||
|
||||
### cuts
|
||||
# First try the mods img directory
|
||||
$(REPACK_CUTS_DC_DIR)/%.dat: $(GTA_MOD_CUTS_DIR)/%.dat
|
||||
@mkdir -p $(@D)
|
||||
cp $< $@
|
||||
$(REPACK_CUTS_DC_DIR)/%.anm: $(GTA_MOD_CUTS_DIR)/%.anm
|
||||
@mkdir -p $(@D)
|
||||
cp $< $@
|
||||
$(REPACK_CUTS_DC_DIR)/%.ifp: $(GTA_MOD_CUTS_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
|
||||
# if not, the extracted img directory
|
||||
$(REPACK_CUTS_DC_DIR)/%.dat: $(REPACK_CUTS_ORIG_DIR)/%.dat
|
||||
@mkdir -p $(@D)
|
||||
cp $< $@
|
||||
$(REPACK_CUTS_DC_DIR)/%.anm: $(REPACK_CUTS_ORIG_DIR)/%.anm
|
||||
@mkdir -p $(@D)
|
||||
cp $< $@
|
||||
$(REPACK_CUTS_DC_DIR)/%.ifp: $(REPACK_CUTS_ORIG_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
|
||||
# first try the mods loose directory
|
||||
$(REPACK_GTA_DIR)/%.dff: $(GTA_MOD_LOOSE_DIR)/%.dff texconv
|
||||
@mkdir -p $(@D)
|
||||
|
@ -456,6 +504,22 @@ $(REPACK_GTA_DIR)/%.TXD: $(GTA_DIR)/%.TXD texconv
|
|||
@mkdir -p $(@D)
|
||||
./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD)
|
||||
|
||||
# first try the mods loose directory
|
||||
$(REPACK_GTA_DIR)/%.ifp: $(GTA_MOD_LOOSE_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
$(REPACK_GTA_DIR)/%.IFP: $(GTA_MOD_LOOSE_DIR)/%.IFP animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
|
||||
#if not, the original files
|
||||
$(REPACK_GTA_DIR)/%.ifp: $(GTA_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
$(REPACK_GTA_DIR)/%.IFP: $(GTA_DIR)/%.IFP animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
|
||||
$(REPACK_DIR)/packed: $(IMG_TEXTURES_DC) $(IMG_MODELS_DC)
|
||||
mkdir -p $(@D)
|
||||
mkdir -p "$(REPACK_GTA_DIR)/models/gta3"
|
||||
|
@ -465,6 +529,15 @@ $(REPACK_DIR)/packed: $(IMG_TEXTURES_DC) $(IMG_MODELS_DC)
|
|||
$(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir: $(REPACK_DIR)/packed
|
||||
@touch $@
|
||||
|
||||
$(REPACK_DIR)/packed-cuts: $(CUTS_IFP_DC) $(CUTS_MISC_DC)
|
||||
mkdir -p $(@D)
|
||||
mkdir -p "$(REPACK_GTA_DIR)/anim/cuts"
|
||||
./imgtool pack "$(REPACK_GTA_DIR)/anim/cuts" "$(REPACK_CUTS_DC_DIR)"
|
||||
@touch $@
|
||||
|
||||
$(REPACK_GTA_DIR)/anim/cuts.img $(REPACK_GTA_DIR)/anim/cuts.dir: $(REPACK_DIR)/packed-cuts
|
||||
@touch $@
|
||||
|
||||
# sfx processing
|
||||
$(REPACK_DIR)/unpacked-sfx: extract-sfx $(GTA_DIR)/audio/sfx.SDT $(GTA_DIR)/audio/sfx.RAW
|
||||
mkdir -p $(@D)
|
||||
|
|
286
liberty/cuts.mk
Normal file
286
liberty/cuts.mk
Normal file
|
@ -0,0 +1,286 @@
|
|||
CUTS_IFP = \
|
||||
A1_ss0.ifp \
|
||||
A2_pp.ifp \
|
||||
A3_ss.ifp \
|
||||
A4_pdr.ifp \
|
||||
A5_k2ft.ifp \
|
||||
A6_BAIT.ifp \
|
||||
A7_ETG.ifp \
|
||||
A8_PS.ifp \
|
||||
A9_ASD.ifp \
|
||||
BET.ifp \
|
||||
C1_TEX.ifp \
|
||||
D1_STOG.ifp \
|
||||
D2_KK.ifp \
|
||||
D3_ADO.ifp \
|
||||
D4_GTA2.ifp \
|
||||
D4_GTA.ifp \
|
||||
D5_ES.ifp \
|
||||
D6_STS.ifp \
|
||||
D7_MLD.ifp \
|
||||
el_ph1.ifp \
|
||||
el_ph2.ifp \
|
||||
el_ph3.ifp \
|
||||
el_ph4.ifp \
|
||||
END.ifp \
|
||||
hd_ph1.ifp \
|
||||
hd_ph2.ifp \
|
||||
hd_ph3.ifp \
|
||||
hd_ph4.ifp \
|
||||
hd_ph5.ifp \
|
||||
J0_dm2.ifp \
|
||||
J1_lfl.ifp \
|
||||
J2_kcl.ifp \
|
||||
J3_vh.ifp \
|
||||
J4_ETH.ifp \
|
||||
J5_dst.ifp \
|
||||
J6_tbj.ifp \
|
||||
JB.ifp \
|
||||
k1_kbo.ifp \
|
||||
k2_gis.ifp \
|
||||
k3_ds.ifp \
|
||||
k4_shi2.ifp \
|
||||
k4_shi.ifp \
|
||||
k5_sd.ifp \
|
||||
l1_lg.ifp \
|
||||
l2_dsb.ifp \
|
||||
l3_dm.ifp \
|
||||
l4_pap.ifp \
|
||||
l5_tfb.ifp \
|
||||
mt_ph1.ifp \
|
||||
mt_ph2.ifp \
|
||||
mt_ph3.ifp \
|
||||
mt_ph4.ifp \
|
||||
R0_PDR2.ifp \
|
||||
R1_SW.ifp \
|
||||
R2_AP.ifp \
|
||||
R3_ED.ifp \
|
||||
R4_GF.ifp \
|
||||
R5_PB.ifp \
|
||||
R6_MM.ifp \
|
||||
S0_MAS.ifp \
|
||||
S1_PF.ifp \
|
||||
S2_CTG2.ifp \
|
||||
S2_CTG.ifp \
|
||||
S3_RTC.ifp \
|
||||
S4_BDBA.ifp \
|
||||
S4_BDBB.ifp \
|
||||
S4_BDBD.ifp \
|
||||
S5_LRQb.ifp \
|
||||
S5_LRQc.ifp \
|
||||
S5_LRQ.ifp \
|
||||
t1_tol.ifp \
|
||||
t2_tpu.ifp \
|
||||
t3_mas.ifp \
|
||||
t4_tat.ifp \
|
||||
t5_bf.ifp \
|
||||
yd_ph1.ifp \
|
||||
yd_ph2.ifp \
|
||||
yd_ph3.ifp \
|
||||
yd_ph4.ifp
|
||||
|
||||
CUTS_MISC = \
|
||||
a1_ss0_asuka.anm \
|
||||
a1_ss0.dat \
|
||||
a1_ss0_maria.anm \
|
||||
a1_ss0_player.anm \
|
||||
a2_pp.dat \
|
||||
a2_pp_player.anm \
|
||||
a3_ss_asuka.anm \
|
||||
a3_ss.dat \
|
||||
a3_ss_kenji.anm \
|
||||
a3_ss_player.anm \
|
||||
a4_pdr_asuka.anm \
|
||||
a4_pdr.dat \
|
||||
a4_pdr_player.anm \
|
||||
a5_k2ft.dat \
|
||||
a5_k2ft_player.anm \
|
||||
a6_bait_asuka.anm \
|
||||
a6_bait.dat \
|
||||
a6_bait_player.anm \
|
||||
a7_etg_asuka.anm \
|
||||
a7_etg.dat \
|
||||
a7_etg_player.anm \
|
||||
a8_ps_asuka.anm \
|
||||
A8_PS.dat \
|
||||
a8_ps_maria.anm \
|
||||
a8_ps_player.anm \
|
||||
a9_asd.dat \
|
||||
a9_asd_player.anm \
|
||||
bet_cat.anm \
|
||||
bet.dat \
|
||||
c1_tex_cat.anm \
|
||||
c1_tex.dat \
|
||||
d1_stog.dat \
|
||||
d1_stog_love.anm \
|
||||
d1_stog_player.anm \
|
||||
D2_KK.dat \
|
||||
d2_kk_love.anm \
|
||||
d2_kk_player.anm \
|
||||
d3_ado.dat \
|
||||
d3_ado_love.anm \
|
||||
d3_ado_player.anm \
|
||||
d4_gta2_asuka.anm \
|
||||
d4_gta2_cat.anm \
|
||||
d4_gta2.dat \
|
||||
d4_gta2_miguel.anm \
|
||||
D4_GTA.dat \
|
||||
d4_gta_love.anm \
|
||||
d4_gta_player.anm \
|
||||
d5_es.dat \
|
||||
d5_es_love.anm \
|
||||
d5_es_player.anm \
|
||||
d6_sts.dat \
|
||||
d6_sts_love.anm \
|
||||
d6_sts_player.anm \
|
||||
d7_mld.dat \
|
||||
el_ph1.dat \
|
||||
el_ph2.dat \
|
||||
el_ph3.dat \
|
||||
el_ph4.dat \
|
||||
end.dat \
|
||||
hd_ph1.dat \
|
||||
hd_ph2.dat \
|
||||
hd_ph3.dat \
|
||||
hd_ph4.dat \
|
||||
hd_ph5.dat \
|
||||
intro.dat \
|
||||
J0_dm2.dat \
|
||||
j0_dm2_joey.anm \
|
||||
j0_dm2_misty.anm \
|
||||
j0_dm2_player.anm \
|
||||
J1_lfl.dat \
|
||||
j1_lfl_joey.anm \
|
||||
j1_lfl_misty.anm \
|
||||
j1_lfl_player.anm \
|
||||
J2_KCL.dat \
|
||||
j2_kcl_joey.anm \
|
||||
j2_kcl_player.anm \
|
||||
j3_vh.dat \
|
||||
j3_vh_joey.anm \
|
||||
j3_vh_player.anm \
|
||||
J4_ETH.dat \
|
||||
j4_eth_joey.anm \
|
||||
j4_eth_player.anm \
|
||||
j4_eth_tony.anm \
|
||||
J5_dst.dat \
|
||||
j5_dst_joey.anm \
|
||||
j5_dst_player.anm \
|
||||
j6_tbj.dat \
|
||||
j6_tbj_joey.anm \
|
||||
j6_tbj_player.anm \
|
||||
jb_col1.anm \
|
||||
jb.dat \
|
||||
K1_KBO.dat \
|
||||
k1_kbo_kenji.anm \
|
||||
k1_kbo_player.anm \
|
||||
K2_GIS.dat \
|
||||
k2_gis_kenji.anm \
|
||||
k2_gis_player.anm \
|
||||
k3_ds.dat \
|
||||
k3_ds_kenji.anm \
|
||||
k3_ds_player.anm \
|
||||
k4_shi2.dat \
|
||||
k4_shi2_keeper.anm \
|
||||
k4_shi2_player.anm \
|
||||
k4_shi.dat \
|
||||
k4_shi_kenji.anm \
|
||||
k4_shi_player.anm \
|
||||
K5_SD.dat \
|
||||
k5_sd_kenji.anm \
|
||||
k5_sd_player.anm \
|
||||
L1_LG.dat \
|
||||
l1_lg_eight.anm \
|
||||
l1_lg_luigi.anm \
|
||||
l1_lg_micky.anm \
|
||||
l1_lg_player.anm \
|
||||
l2_dsb.dat \
|
||||
l2_DSB_micky.anm \
|
||||
l2_dsb_player.anm \
|
||||
L3_DM.dat \
|
||||
l3_dm_luigi.anm \
|
||||
l3_dm_micky.anm \
|
||||
l3_dm_player.anm \
|
||||
l4_pap.dat \
|
||||
l4_pap_luigi.anm \
|
||||
l4_pap_player.anm \
|
||||
L5_TFB.dat \
|
||||
l5_tfb_luigi.anm \
|
||||
l5_tfb_micky.anm \
|
||||
l5_tfb_player.anm \
|
||||
mt_ph1.dat \
|
||||
mt_ph2.dat \
|
||||
mt_ph3.dat \
|
||||
mt_ph4.dat \
|
||||
R0_PDR2.dat \
|
||||
R0_pdr2_player.anm \
|
||||
R0_pdr2_ray.anm \
|
||||
R1_SW.dat \
|
||||
R1_sw_player.anm \
|
||||
R1_sw_ray.anm \
|
||||
R2_AP.dat \
|
||||
R2_ap_player.anm \
|
||||
R2_ap_ray.anm \
|
||||
r3_ed.dat \
|
||||
R3_ed_player.anm \
|
||||
R3_ed_ray.anm \
|
||||
R4_GF.dat \
|
||||
R4_gf_player.anm \
|
||||
R4_gf_ray.anm \
|
||||
R5_PB.dat \
|
||||
R5_pb_player.anm \
|
||||
R5_pb_ray.anm \
|
||||
R6_MM.dat \
|
||||
R6_mm_player.anm \
|
||||
R6_mm_ray.anm \
|
||||
S0_MAS.dat \
|
||||
S0_mas_frank.anm \
|
||||
s0_mas_joey.anm \
|
||||
s0_mas_luigi.anm \
|
||||
s0_mas_player.anm \
|
||||
S0_mas_tony.anm \
|
||||
S1_PF.dat \
|
||||
S1_pf_frank.anm \
|
||||
S1_pf_maria.anm \
|
||||
S1_pf_player.anm \
|
||||
S2_ctg2_cat.anm \
|
||||
S2_ctg2_curly.anm \
|
||||
s2_ctg2.dat \
|
||||
S2_ctg2_miguel.anm \
|
||||
S2_CTG.dat \
|
||||
s2_ctg_frank.anm \
|
||||
S3_rtc.dat \
|
||||
S3_rtc_frank.anm \
|
||||
S4_BDBA.dat \
|
||||
S4_BDBA_eight.anm \
|
||||
S4_BDBA_player.anm \
|
||||
S4_BDBB.dat \
|
||||
S4_BDBB_eight.anm \
|
||||
S4_BDBB_player.anm \
|
||||
S4_BDBD.dat \
|
||||
S5_lrqb_asuka.anm \
|
||||
s5_lrqb.dat \
|
||||
S5_lrqb_maria.anm \
|
||||
S5_lrqc_asuka.anm \
|
||||
S5_LRQC.dat \
|
||||
S5_lrqc_maria.anm \
|
||||
S5_lrqc_player.anm \
|
||||
S5_LRQ.dat \
|
||||
S5_lrq_frank.anm \
|
||||
S5_lrq_player.anm \
|
||||
T1_TOL.dat \
|
||||
t1_tol_player.anm \
|
||||
t1_tol_tony.anm \
|
||||
T2_TPU.dat \
|
||||
t2_tpu_player.anm \
|
||||
T3_MAS.dat \
|
||||
t3_mas_player.anm \
|
||||
T4_TAT.dat \
|
||||
t4_tat_player.anm \
|
||||
T5_BF.dat \
|
||||
t5_bf_player.anm \
|
||||
t5_bf_tony.anm \
|
||||
yd_ph1.dat \
|
||||
yd_ph2.dat \
|
||||
yd_ph3.dat \
|
||||
yd_ph4.dat
|
|
@ -18,6 +18,7 @@ MOD_NAME?=
|
|||
GTA_DIR?=../../miami
|
||||
GTA_MOD_DIR?=../../miami_mod$(MOD_NAME)
|
||||
GTA_MOD_IMG_DIR?=$(GTA_MOD_DIR)/img
|
||||
GTA_MOD_CUTS_DIR=$(GTA_MOD_DIR)/cuts
|
||||
GTA_MOD_SFX_DIR?=$(GTA_MOD_DIR)/sfx
|
||||
GTA_MOD_LOOSE_DIR?=$(GTA_MOD_DIR)/loose
|
||||
|
||||
|
@ -28,6 +29,8 @@ REPACK_IMG_DC_DIR?=$(REPACK_DIR)/miami-img-dc
|
|||
REPACK_SFX_ORIG_DIR?=$(REPACK_DIR)/miami-sfx-orig
|
||||
REPACK_SFX_DC_DIR?=$(REPACK_DIR)/miami-sfx-dc
|
||||
REPACK_STREAM_DECODED_DIR?=$(REPACK_DIR)/miami-stream-decoded
|
||||
REPACK_CUTS_ORIG_DIR?=$(REPACK_DIR)/miami-cuts-orig
|
||||
REPACK_CUTS_DC_DIR?=$(REPACK_DIR)/miami-cuts-dc
|
||||
|
||||
LIBS :=
|
||||
TEXCONV_FLAGS :=
|
||||
|
@ -293,6 +296,9 @@ texconv: $(OBJS_TEXCONV) | pvrtex # You'll have to rebuild pvrtex manually if yo
|
|||
%.texconv.o: %.cpp
|
||||
$(CXX) -std=c++2a -c -O0 -g -MMD -MP -o $@ -I../vendor/koshle -I../vendor/librw/src $(INCLUDE) -I../vendor/emu -I../vendor/crypto -I../vendor/TriStripper/include $(DEFINES) -DDC_TEXCONV -DDC_SIM $(TEXCONV_FLAGS) $<
|
||||
|
||||
animtool: ../src/tools/animtool.cpp
|
||||
$(CXX) -std=c++17 -o $@ -g -O0 $<
|
||||
|
||||
-include $(DEPS)
|
||||
|
||||
#### Repacking ####
|
||||
|
@ -344,11 +350,18 @@ TEXTURE_DOWNSAMPLE_IMG ?= HALF
|
|||
-include sfxlooplist.mk
|
||||
-include wavlist.mk
|
||||
-include mp3list.mk
|
||||
-include cuts.mk
|
||||
|
||||
IMG_TEXTURES_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_TEXTURES))
|
||||
IMG_MODELS_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_MODELS))
|
||||
IMG_IFP_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_IFP))
|
||||
IMG_MISC_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_MISC))
|
||||
|
||||
CUTS_IFP_DC = $(addprefix $(REPACK_CUTS_DC_DIR)/, $(CUTS_IFP))
|
||||
CUTS_MISC_DC = $(addprefix $(REPACK_CUTS_DC_DIR)/, $(CUTS_MISC))
|
||||
|
||||
LOOSE_FILES_DC = $(addprefix $(REPACK_GTA_DIR)/, $(MISC_FILES))
|
||||
|
||||
SFX_DC_DIR = $(REPACK_GTA_DIR)/sfx
|
||||
SFX_DC_RAW = $(SFX_DC_DIR)/sfx_all.raw
|
||||
SFX_DC_DSC = $(SFX_DC_DIR)/sfx_all.dsc
|
||||
|
@ -358,8 +371,12 @@ STREAM_ADPCM_DC = $(addprefix $(REPACK_GTA_DIR)/stream/, $(STREAM_WAV:.wav=.APM)
|
|||
|
||||
IMG_TEXTURES_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_TEXTURES))
|
||||
IMG_MODELS_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_MODELS))
|
||||
IMG_IFP_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_IFP))
|
||||
IMG_MISC_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_MISC))
|
||||
|
||||
CUTS_IFP_ORIG = $(addprefix $(REPACK_CUTS_ORIG_DIR)/, $(CUTS_IFP))
|
||||
CUTS_MISC_ORIG = $(addprefix $(REPACK_CUTS_ORIG_DIR)/, $(CUTS_MISC))
|
||||
|
||||
SFX_ORIG = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_WAV))
|
||||
SFX_ORIG_LOOP = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_LOOP_WAV))
|
||||
SFX_REPACK_DC_WAV = $(addprefix $(REPACK_SFX_DC_DIR)/, $(SFX_WAV) $(SFX_LOOP_WAV))
|
||||
|
@ -368,7 +385,7 @@ STREAM_ADF_DECODED = $(addprefix $(REPACK_STREAM_DECODED_DIR)/, $(STREAM_ADF:.ad
|
|||
|
||||
.PRECIOUS: $(SFX_ORIG) $(SFX_REPACK_DC) $(STREAM_ADF_DECODED)
|
||||
|
||||
$(REPACK_DIR)/repacked: $(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir $(LOOSE_FILES_DC) $(STREAM_ADPCM_DC) $(SFX_DC_RAW) $(SFX_DC_DSC)
|
||||
$(REPACK_DIR)/repacked: $(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir $(REPACK_GTA_DIR)/anim/cuts.img $(REPACK_GTA_DIR)/anim/cuts.dir $(LOOSE_FILES_DC) $(STREAM_ADPCM_DC) $(SFX_DC_RAW) $(SFX_DC_DSC)
|
||||
mkdir -p $(@D)
|
||||
@git archive --format zip --output "$(REPACK_GTA_DIR)/DCA3-$(GIT_VERSION).zip" HEAD
|
||||
@touch $@
|
||||
|
@ -379,7 +396,16 @@ $(REPACK_DIR)/unpacked: imgtool $(GTA_DIR)/models/gta3.img $(GTA_DIR)/models/gta
|
|||
./imgtool unpack "$(GTA_DIR)/models/gta3" "$(REPACK_IMG_ORIG_DIR)"
|
||||
@touch $@
|
||||
|
||||
$(IMG_TEXTURES_ORIG) $(IMG_MODELS_ORIG) $(IMG_MISC_ORIG): $(REPACK_DIR)/unpacked
|
||||
$(IMG_TEXTURES_ORIG) $(IMG_MODELS_ORIG) $(IMG_IFP_ORIG) $(IMG_MISC_ORIG): $(REPACK_DIR)/unpacked
|
||||
@test -f $@
|
||||
@touch $@
|
||||
|
||||
$(REPACK_DIR)/unpacked-cuts: imgtool $(GTA_DIR)/anim/cuts.img $(GTA_DIR)/anim/cuts.dir
|
||||
mkdir -p $(@D)
|
||||
./imgtool unpack "$(GTA_DIR)/anim/cuts" "$(REPACK_CUTS_ORIG_DIR)"
|
||||
@touch $@
|
||||
|
||||
$(CUTS_IFP_ORIG) $(CUTS_MISC_ORIG): $(REPACK_DIR)/unpacked-cuts
|
||||
@test -f $@
|
||||
@touch $@
|
||||
|
||||
|
@ -419,17 +445,35 @@ $(REPACK_IMG_DC_DIR)/%.TXD: $(REPACK_IMG_ORIG_DIR)/%.TXD texconv
|
|||
$(REPACK_IMG_DC_DIR)/%.col: $(GTA_MOD_IMG_DIR)/%.col
|
||||
@mkdir -p $(@D)
|
||||
cp $< $@
|
||||
$(REPACK_IMG_DC_DIR)/%.ifp: $(GTA_MOD_IMG_DIR)/%.ifp
|
||||
$(REPACK_IMG_DC_DIR)/%.ifp: $(GTA_MOD_IMG_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
cp $< $@
|
||||
./animtool $< $@
|
||||
|
||||
# if not, the extracted img directory
|
||||
$(REPACK_IMG_DC_DIR)/%.col: $(REPACK_IMG_ORIG_DIR)/%.col
|
||||
@mkdir -p $(@D)
|
||||
cp $< $@
|
||||
$(REPACK_IMG_DC_DIR)/%.ifp: $(REPACK_IMG_ORIG_DIR)/%.ifp
|
||||
$(REPACK_IMG_DC_DIR)/%.ifp: $(REPACK_IMG_ORIG_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
|
||||
### cuts
|
||||
# First try the mods img directory
|
||||
$(REPACK_CUTS_DC_DIR)/%.dat: $(GTA_MOD_CUTS_DIR)/%.dat
|
||||
@mkdir -p $(@D)
|
||||
cp $< $@
|
||||
$(REPACK_CUTS_DC_DIR)/%.ifp: $(GTA_MOD_CUTS_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
|
||||
# if not, the extracted img directory
|
||||
$(REPACK_CUTS_DC_DIR)/%.dat: $(REPACK_CUTS_ORIG_DIR)/%.dat
|
||||
@mkdir -p $(@D)
|
||||
cp $< $@
|
||||
$(REPACK_CUTS_DC_DIR)/%.ifp: $(REPACK_CUTS_ORIG_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
|
||||
|
||||
# first try the mods loose directory
|
||||
$(REPACK_GTA_DIR)/%.dff: $(GTA_MOD_LOOSE_DIR)/%.dff texconv
|
||||
|
@ -464,7 +508,23 @@ $(REPACK_GTA_DIR)/%.TXD: $(GTA_DIR)/%.TXD texconv
|
|||
@mkdir -p $(@D)
|
||||
./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD)
|
||||
|
||||
$(REPACK_DIR)/packed: $(IMG_TEXTURES_DC) $(IMG_MODELS_DC) $(IMG_MISC_DC)
|
||||
# first try the mods loose directory
|
||||
$(REPACK_GTA_DIR)/%.ifp: $(GTA_MOD_LOOSE_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
$(REPACK_GTA_DIR)/%.IFP: $(GTA_MOD_LOOSE_DIR)/%.IFP animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
|
||||
#if not, the original files
|
||||
$(REPACK_GTA_DIR)/%.ifp: $(GTA_DIR)/%.ifp animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
$(REPACK_GTA_DIR)/%.IFP: $(GTA_DIR)/%.IFP animtool
|
||||
@mkdir -p $(@D)
|
||||
./animtool $< $@
|
||||
|
||||
$(REPACK_DIR)/packed: $(IMG_TEXTURES_DC) $(IMG_MODELS_DC) $(IMG_IFP_DC) $(IMG_MISC_DC)
|
||||
mkdir -p $(@D)
|
||||
mkdir -p "$(REPACK_GTA_DIR)/models/gta3"
|
||||
./imgtool pack "$(REPACK_GTA_DIR)/models/gta3" "$(REPACK_IMG_DC_DIR)"
|
||||
|
@ -473,6 +533,15 @@ $(REPACK_DIR)/packed: $(IMG_TEXTURES_DC) $(IMG_MODELS_DC) $(IMG_MISC_DC)
|
|||
$(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir: $(REPACK_DIR)/packed
|
||||
@touch $@
|
||||
|
||||
$(REPACK_DIR)/packed-cuts: $(CUTS_IFP_DC) $(CUTS_MISC_DC)
|
||||
mkdir -p $(@D)
|
||||
mkdir -p "$(REPACK_GTA_DIR)/anim/cuts"
|
||||
./imgtool pack "$(REPACK_GTA_DIR)/anim/cuts" "$(REPACK_CUTS_DC_DIR)"
|
||||
@touch $@
|
||||
|
||||
$(REPACK_GTA_DIR)/anim/cuts.img $(REPACK_GTA_DIR)/anim/cuts.dir: $(REPACK_DIR)/packed-cuts
|
||||
@touch $@
|
||||
|
||||
# sfx processing
|
||||
$(REPACK_DIR)/unpacked-sfx: extract-sfx $(GTA_DIR)/Audio/sfx.SDT $(GTA_DIR)/Audio/sfx.RAW
|
||||
mkdir -p $(@D)
|
||||
|
|
151
miami/cuts.mk
Normal file
151
miami/cuts.mk
Normal file
|
@ -0,0 +1,151 @@
|
|||
CUTS_IFP = \
|
||||
ass_1.ifp \
|
||||
ass_2.ifp \
|
||||
bank_1.ifp \
|
||||
bank_2a.ifp \
|
||||
bank_2b.ifp \
|
||||
bank_3a.ifp \
|
||||
bank_3b.ifp \
|
||||
bank_4.ifp \
|
||||
bike_1.ifp \
|
||||
bike_2.ifp \
|
||||
bike_3.ifp \
|
||||
bud_1.ifp \
|
||||
bud_2.ifp \
|
||||
bud_3.ifp \
|
||||
cap_1.ifp \
|
||||
car_1.ifp \
|
||||
cnt_1a.ifp \
|
||||
cnt_1b.ifp \
|
||||
cnt_2.ifp \
|
||||
cok_1.ifp \
|
||||
cok_2a.ifp \
|
||||
cok_2b.ifp \
|
||||
cok_3.ifp \
|
||||
cok_4a.ifp \
|
||||
cok_4a2.ifp \
|
||||
cok_4b.ifp \
|
||||
col_1.ifp \
|
||||
col_2.ifp \
|
||||
col_3a.ifp \
|
||||
col_4a.ifp \
|
||||
col_4b.ifp \
|
||||
col_5a.ifp \
|
||||
col_5b.ifp \
|
||||
cub_1.ifp \
|
||||
cub_2.ifp \
|
||||
cub_3.ifp \
|
||||
cub_4.ifp \
|
||||
drug_1.ifp \
|
||||
fin.ifp \
|
||||
fin_2.ifp \
|
||||
finale.ifp \
|
||||
hat_1.ifp \
|
||||
hat_2.ifp \
|
||||
hat_3.ifp \
|
||||
ice_1.ifp \
|
||||
int_a.ifp \
|
||||
int_b.ifp \
|
||||
int_d.ifp \
|
||||
int_m.ifp \
|
||||
int_m2.ifp \
|
||||
law_1a.ifp \
|
||||
law_1b.ifp \
|
||||
law_2a.ifp \
|
||||
law_2b.ifp \
|
||||
law_2c.ifp \
|
||||
law_3.ifp \
|
||||
law_4.ifp \
|
||||
phil_1.ifp \
|
||||
phil_2.ifp \
|
||||
porn_1.ifp \
|
||||
porn_2.ifp \
|
||||
porn_3.ifp \
|
||||
porn_4.ifp \
|
||||
resc_1a.ifp \
|
||||
rok_1.ifp \
|
||||
rok_2.ifp \
|
||||
rok_3a.ifp \
|
||||
rok_4a.ifp \
|
||||
rok_4b.ifp \
|
||||
rok_4c.ifp \
|
||||
rok_4d.ifp \
|
||||
stripa.ifp \
|
||||
tax_1.ifp \
|
||||
tex_1.ifp \
|
||||
tex_2.ifp \
|
||||
tex_3.ifp
|
||||
|
||||
CUTS_MISC = \
|
||||
ass_1.dat \
|
||||
ass_2.dat \
|
||||
bank_1.dat \
|
||||
bank_2a.dat \
|
||||
bank_2b.dat \
|
||||
bank_3a.dat \
|
||||
bank_3b.dat \
|
||||
bank_4.dat \
|
||||
bike_1.dat \
|
||||
bike_2.dat \
|
||||
bike_3.dat \
|
||||
bud_1.dat \
|
||||
bud_2.dat \
|
||||
bud_3.dat \
|
||||
cap_1.dat \
|
||||
car_1.dat \
|
||||
cnt_1a.dat \
|
||||
cnt_1b.dat \
|
||||
cnt_2.dat \
|
||||
cok_1.dat \
|
||||
cok_2a.dat \
|
||||
cok_2b.dat \
|
||||
cok_3.dat \
|
||||
cok_4a.dat \
|
||||
cok_4a2.dat \
|
||||
cok_4b.dat \
|
||||
col_1.dat \
|
||||
col_2.dat \
|
||||
col_3a.dat \
|
||||
col_4a.dat \
|
||||
col_5a.dat \
|
||||
col_5b.dat \
|
||||
cub_1.dat \
|
||||
cub_2.dat \
|
||||
cub_3.dat \
|
||||
cub_4.dat \
|
||||
drug_1.dat \
|
||||
fin.dat \
|
||||
fin_2.dat \
|
||||
finale.dat \
|
||||
hat_1.dat \
|
||||
hat_2.dat \
|
||||
hat_3.dat \
|
||||
ice_1.dat \
|
||||
int_a.dat \
|
||||
int_b.dat \
|
||||
int_d.dat \
|
||||
int_m.dat \
|
||||
int_m2.dat \
|
||||
law_1a.dat \
|
||||
law_1b.dat \
|
||||
law_2a.dat \
|
||||
law_2b.dat \
|
||||
law_2c.dat \
|
||||
law_3.dat \
|
||||
law_4.dat \
|
||||
phil_1.dat \
|
||||
phil_2.dat \
|
||||
porn_1.dat \
|
||||
porn_2.dat \
|
||||
porn_3.dat \
|
||||
porn_4.dat \
|
||||
resc_1a.dat \
|
||||
rok_1.dat \
|
||||
rok_2.dat \
|
||||
rok_3a.dat \
|
||||
rok_4a.dat \
|
||||
stripa.dat \
|
||||
tax_1.dat \
|
||||
tex_1.dat \
|
||||
tex_2.dat \
|
||||
tex_3.dat
|
|
@ -45,8 +45,6 @@ MISC_FILES = \
|
|||
models/generic/zonecylb.DFF \
|
||||
models/gta3.img \
|
||||
\
|
||||
anim/cuts.img \
|
||||
anim/cuts.dir \
|
||||
anim/ped.ifp \
|
||||
\
|
||||
Icons/app.ico \
|
||||
|
|
|
@ -2,42 +2,50 @@ IMG_MISC = \
|
|||
airport.col \
|
||||
airportN.col \
|
||||
bank.col \
|
||||
baseball.ifp \
|
||||
biked.ifp \
|
||||
bikeh.ifp \
|
||||
bikes.ifp \
|
||||
bikev.ifp \
|
||||
bridge.col \
|
||||
buddy.ifp \
|
||||
chainsaw.ifp \
|
||||
cisland.col \
|
||||
CLUB.col \
|
||||
coach.ifp \
|
||||
colt45.ifp \
|
||||
concerth.col \
|
||||
docks.col \
|
||||
downtown.col \
|
||||
downtows.col \
|
||||
flame.ifp \
|
||||
golf.col \
|
||||
grenade.ifp \
|
||||
haiti.col \
|
||||
haitin.col \
|
||||
hotel.col \
|
||||
islandsf.col \
|
||||
knife.ifp \
|
||||
lance.ifp \
|
||||
lawyers.col \
|
||||
littleha.col \
|
||||
m60.ifp \
|
||||
mall.col \
|
||||
mansion.col \
|
||||
medic.ifp \
|
||||
nbeachbt.col \
|
||||
nbeach.col \
|
||||
nbeachw.col \
|
||||
oceandN.col \
|
||||
oceandrv.col \
|
||||
stadint.col \
|
||||
starisl.col \
|
||||
stripclb.col \
|
||||
washintn.col \
|
||||
washints.col \
|
||||
yacht.col
|
||||
|
||||
IMG_IFP = \
|
||||
baseball.ifp \
|
||||
biked.ifp \
|
||||
bikeh.ifp \
|
||||
bikes.ifp \
|
||||
bikev.ifp \
|
||||
buddy.ifp \
|
||||
chainsaw.ifp \
|
||||
coach.ifp \
|
||||
colt45.ifp \
|
||||
flame.ifp \
|
||||
grenade.ifp \
|
||||
knife.ifp \
|
||||
lance.ifp \
|
||||
m60.ifp \
|
||||
medic.ifp \
|
||||
playidles.ifp \
|
||||
python.ifp \
|
||||
rifle.ifp \
|
||||
|
@ -45,15 +53,9 @@ IMG_MISC = \
|
|||
shotgun.ifp \
|
||||
skate.ifp \
|
||||
sniper.ifp \
|
||||
stadint.col \
|
||||
starisl.col \
|
||||
stripclb.col \
|
||||
strip.ifp \
|
||||
sunbathe.ifp \
|
||||
sword.ifp \
|
||||
tec.ifp \
|
||||
uzi.ifp \
|
||||
van.ifp \
|
||||
washintn.col \
|
||||
washints.col \
|
||||
yacht.col
|
||||
van.ifp
|
|
@ -55,6 +55,8 @@ void
|
|||
CAnimBlendAssociation::FreeAnimBlendNodeArray(void)
|
||||
{
|
||||
assert(nodes != nil);
|
||||
for(unsigned i = 0; i < numNodes; i++)
|
||||
nodes[i].Destroy();
|
||||
RwFreeAlign(nodes);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,22 +29,15 @@ CAnimBlendHierarchy::CalcTotalTime(void)
|
|||
totalLength = 0.0f;
|
||||
|
||||
for(i = 0; i < numSequences; i++){
|
||||
float seqTime = 0.0f;
|
||||
for(j = 0; j < sequences[i].numFrames; j++)
|
||||
seqTime += sequences[i].GetDeltaTime(j);
|
||||
#ifdef FIX_BUGS
|
||||
if(sequences[i].numFrames == 0)
|
||||
continue;
|
||||
#endif
|
||||
float seqTime = sequences[i].GetEndTime();
|
||||
totalLength = Max(totalLength, seqTime);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CAnimBlendHierarchy::RemoveQuaternionFlips(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < numSequences; i++)
|
||||
sequences[i].RemoveQuaternionFlips();
|
||||
}
|
||||
|
||||
void
|
||||
CAnimBlendHierarchy::RemoveAnimSequences(void)
|
||||
{
|
||||
|
|
|
@ -7,15 +7,24 @@ void
|
|||
CAnimBlendNode::Init(void)
|
||||
{
|
||||
frameA = -1;
|
||||
frameB = -1;
|
||||
remainingTime = 0.0f;
|
||||
sequence = nil;
|
||||
association = nil;
|
||||
player = nil;
|
||||
}
|
||||
|
||||
void CAnimBlendNode::Destroy(void) {
|
||||
if (player) {
|
||||
delete player;
|
||||
player = nil;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight)
|
||||
{
|
||||
assert (player && player->keyFrames == sequence->keyFrames);
|
||||
|
||||
bool looped = false;
|
||||
|
||||
trans = CVector(0.0f, 0.0f, 0.0f);
|
||||
|
@ -29,17 +38,17 @@ CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight)
|
|||
|
||||
float blend = association->GetBlendAmount(weight);
|
||||
if(blend > 0.0f){
|
||||
float kfAdt = sequence->GetDeltaTime(frameA);
|
||||
float kfAdt = player->GetNextTimeDelta();
|
||||
float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt;
|
||||
if(sequence->type & CAnimBlendSequence::KF_TRANS){
|
||||
auto kfAt = sequence->GetTranslation(frameA);
|
||||
auto kfBt = sequence->GetTranslation(frameB);
|
||||
trans = kfBt + t*(kfAt - kfBt);
|
||||
if(player->type & CAnimBlendSequence::KF_TRANS){
|
||||
auto kfdAt = player->GetNextTranslationDelta();
|
||||
auto kfBt = player->GetPrevTranslation();
|
||||
trans = kfBt + t*kfdAt;
|
||||
trans *= blend;
|
||||
}
|
||||
if(sequence->type & CAnimBlendSequence::KF_ROT){
|
||||
auto kfAr = sequence->GetRotation(frameA);
|
||||
auto kfBr = sequence->GetRotation(frameB);
|
||||
if(player->type & CAnimBlendSequence::KF_ROT){
|
||||
auto kfAr = player->GetNextRotation();
|
||||
auto kfBr = player->GetPrevRotation();
|
||||
rot.Slerp(kfBr, kfAr, theta, invSin, t);
|
||||
rot *= blend;
|
||||
}
|
||||
|
@ -51,36 +60,35 @@ CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight)
|
|||
bool
|
||||
CAnimBlendNode::NextKeyFrame(void)
|
||||
{
|
||||
assert(player != nil);
|
||||
bool looped;
|
||||
|
||||
if(sequence->numFrames <= 1)
|
||||
if(player->numFrames <= 1)
|
||||
return false;
|
||||
|
||||
looped = false;
|
||||
frameB = frameA;
|
||||
|
||||
// Advance as long as we have to
|
||||
while(remainingTime <= 0.0f){
|
||||
frameA++;
|
||||
|
||||
if(frameA >= sequence->numFrames){
|
||||
if(frameA >= player->numFrames){
|
||||
// reached end of animation
|
||||
if(!association->IsRepeating()){
|
||||
frameA--;
|
||||
remainingTime = 0.0f;
|
||||
assert(frameA == player->curFrame);
|
||||
CalcDeltas();
|
||||
return false;
|
||||
}
|
||||
looped = true;
|
||||
frameA = 0;
|
||||
}
|
||||
|
||||
remainingTime += sequence->GetDeltaTime(frameA);
|
||||
player->AdvanceFrame();
|
||||
remainingTime += player->GetNextTimeDelta();
|
||||
}
|
||||
|
||||
frameB = frameA - 1;
|
||||
if(frameB < 0)
|
||||
frameB += sequence->numFrames;
|
||||
|
||||
assert(frameA == player->curFrame);
|
||||
CalcDeltas();
|
||||
return looped;
|
||||
}
|
||||
|
@ -89,31 +97,47 @@ CAnimBlendNode::NextKeyFrame(void)
|
|||
bool
|
||||
CAnimBlendNode::FindKeyFrame(float t)
|
||||
{
|
||||
if(sequence->numFrames < 1)
|
||||
if (player == nil) {
|
||||
player = new CAnimBlendPlayer();
|
||||
player->Init(sequence->keyFrames, sequence->type, sequence->numFrames);
|
||||
}
|
||||
if(player->numFrames < 1)
|
||||
return false;
|
||||
|
||||
frameA = 0;
|
||||
frameB = frameA;
|
||||
player->SeekToStart();
|
||||
|
||||
if(sequence->numFrames >= 2){
|
||||
frameA++;
|
||||
assert (player->keyFrames == sequence->keyFrames);
|
||||
|
||||
if(player->numFrames == 1){
|
||||
remainingTime = 0.0f;
|
||||
}else{
|
||||
// advance until t is between frameB and frameA
|
||||
while(t > sequence->GetDeltaTime(frameA)){
|
||||
t -= sequence->GetDeltaTime(frameA);
|
||||
frameB = frameA++;
|
||||
if(frameA >= sequence->numFrames){
|
||||
frameA++;
|
||||
player->AdvanceFrame();
|
||||
while (t > player->GetNextTimeDelta()) {
|
||||
t -= player->GetNextTimeDelta();
|
||||
if (frameA + 1 >= player->numFrames) {
|
||||
// reached end of animation
|
||||
if(!association->IsRepeating())
|
||||
if (!association->IsRepeating()) {
|
||||
assert(frameA == player->curFrame);
|
||||
CalcDeltas();
|
||||
remainingTime = 0.0f;
|
||||
return false;
|
||||
}
|
||||
// Frame 0 is effectively skipped here
|
||||
// Looks like an re3 / game bug?
|
||||
frameA = 0;
|
||||
frameB = 0;
|
||||
player->SeekToStart();
|
||||
}
|
||||
frameA++;
|
||||
player->AdvanceFrame();
|
||||
}
|
||||
|
||||
remainingTime = sequence->GetDeltaTime(frameA) - t;
|
||||
remainingTime = player->GetNextTimeDelta() - t;
|
||||
}
|
||||
|
||||
assert(frameA == player->curFrame);
|
||||
CalcDeltas();
|
||||
return true;
|
||||
}
|
||||
|
@ -121,10 +145,10 @@ CAnimBlendNode::FindKeyFrame(float t)
|
|||
void
|
||||
CAnimBlendNode::CalcDeltas(void)
|
||||
{
|
||||
if((sequence->type & CAnimBlendSequence::KF_ROT) == 0)
|
||||
if((player->type & CAnimBlendSequence::KF_ROT) == 0)
|
||||
return;
|
||||
auto kfAr = sequence->GetRotation(frameA);
|
||||
auto kfBr = sequence->GetRotation(frameB);
|
||||
auto kfAr = player->GetNextRotation();
|
||||
auto kfBr = player->GetPrevRotation();
|
||||
float cos = DotProduct(kfAr, kfBr);
|
||||
if(cos > 1.0f)
|
||||
cos = 1.0f;
|
||||
|
@ -139,12 +163,12 @@ CAnimBlendNode::GetCurrentTranslation(CVector &trans, float weight)
|
|||
|
||||
float blend = association->GetBlendAmount(weight);
|
||||
if(blend > 0.0f){
|
||||
auto kfAdt = sequence->GetDeltaTime(frameA);
|
||||
auto kfAdt = player->GetNextTimeDelta();
|
||||
float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt;
|
||||
if(sequence->type & CAnimBlendSequence::KF_TRANS){
|
||||
auto kfAt = sequence->GetTranslation(frameA);
|
||||
auto kfBt = sequence->GetTranslation(frameB);
|
||||
trans = kfBt + t*(kfAt - kfBt);
|
||||
if(player->type & CAnimBlendSequence::KF_TRANS){
|
||||
auto kfdAt = player->GetNextTranslationDelta();
|
||||
auto kfBt = player->GetPrevTranslation();
|
||||
trans = kfBt + t*kfdAt;
|
||||
trans *= blend;
|
||||
}
|
||||
}
|
||||
|
@ -157,7 +181,9 @@ CAnimBlendNode::GetEndTranslation(CVector &trans, float weight)
|
|||
|
||||
float blend = association->GetBlendAmount(weight);
|
||||
if(blend > 0.0f){
|
||||
if(sequence->type & CAnimBlendSequence::KF_TRANS)
|
||||
trans = sequence->GetTranslation(sequence->numFrames-1) * blend;
|
||||
if(player->type & CAnimBlendSequence::KF_TRANS){
|
||||
CVector pos = sequence->GetEndTranslation();
|
||||
trans = pos * blend;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,13 @@ public:
|
|||
float invSin; // 1/Sin(theta)
|
||||
// indices into array in sequence
|
||||
int32 frameA; // next key frame
|
||||
int32 frameB; // previous key frame
|
||||
float remainingTime; // time until frames have to advance
|
||||
CAnimBlendPlayer* player;
|
||||
CAnimBlendSequence *sequence;
|
||||
CAnimBlendAssociation *association;
|
||||
|
||||
void Init(void);
|
||||
void Destroy(void);
|
||||
bool Update(CVector &trans, CQuaternion &rot, float weight);
|
||||
bool NextKeyFrame(void);
|
||||
bool FindKeyFrame(float t);
|
||||
|
|
|
@ -25,45 +25,6 @@ CAnimBlendSequence::SetName(char *name)
|
|||
strncpy(this->name, name, 24);
|
||||
}
|
||||
|
||||
void
|
||||
CAnimBlendSequence::SetNumFrames(int numFrames, bool translation, bool compress)
|
||||
{
|
||||
int sz;
|
||||
|
||||
if(translation){
|
||||
type |= KF_ROT | KF_TRANS;
|
||||
if (compress) {
|
||||
type |= KF_COMPRESSED;
|
||||
sz = sizeof(KeyFrameTransCompressed);
|
||||
} else {
|
||||
sz = sizeof(KeyFrameTransUncompressed);
|
||||
}
|
||||
}else{
|
||||
sz = sizeof(KeyFrame);
|
||||
type |= KF_ROT;
|
||||
}
|
||||
keyFrames = RwMalloc(sz * numFrames);
|
||||
this->numFrames = numFrames;
|
||||
}
|
||||
|
||||
void
|
||||
CAnimBlendSequence::RemoveQuaternionFlips(void)
|
||||
{
|
||||
int i;
|
||||
CQuaternion last;
|
||||
|
||||
if(numFrames < 2)
|
||||
return;
|
||||
|
||||
last = GetRotation(0);
|
||||
for(i = 1; i < numFrames; i++){
|
||||
auto KFr = GetRotation(i);
|
||||
if(DotProduct(last, KFr) < 0.0f)
|
||||
SetRotation(i, -KFr);
|
||||
last = GetRotation(i);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_CUSTOM_ALLOCATOR
|
||||
bool
|
||||
CAnimBlendSequence::MoveMemory(void)
|
||||
|
|
|
@ -5,67 +5,297 @@
|
|||
#ifdef MoveMemory
|
||||
#undef MoveMemory // windows shit
|
||||
#endif
|
||||
struct CAnimBlendPlayer {
|
||||
enum {
|
||||
KF_ROT = 1,
|
||||
KF_TRANS = 2,
|
||||
|
||||
FLAGS_HAS_ROT_Y = 1 << 8,
|
||||
FLAGS_HAS_ROT_P = 1 << 9,
|
||||
FLAGS_HAS_ROT_R = 1 << 10,
|
||||
|
||||
// TODO: put them somewhere else?
|
||||
static int16 checked_f2i16(float f) {
|
||||
assert(f >= -32768 && f <= 32767);
|
||||
return f;
|
||||
}
|
||||
FLAGS_HAS_TRANS_X = 1 << 11,
|
||||
FLAGS_HAS_TRANS_Y = 1 << 12,
|
||||
FLAGS_HAS_TRANS_Z = 1 << 13,
|
||||
FLAGS_HAS_TRANS_ANY = 7 << 11,
|
||||
FLAGS_HAS_TRANS_LARGE = 1 << 14,
|
||||
FLAGS_QUAT0_NEG = 1 << 15
|
||||
};
|
||||
|
||||
static uint16 checked_f2u16(float f) {
|
||||
assert(f >= 0 && f <= 65535);
|
||||
return f;
|
||||
}
|
||||
int32 type;
|
||||
void* keyFrames;
|
||||
int32 curFrame;
|
||||
int32 numFrames;
|
||||
CQuaternion currentRotation;
|
||||
CQuaternion nextRotation;
|
||||
CVector currentTranslation;
|
||||
CVector nextTranslation;
|
||||
|
||||
#define KF_MINDELTA (1/256.f)
|
||||
unsigned readOffset;
|
||||
unsigned readOffset_initial;
|
||||
|
||||
struct KeyFrame {
|
||||
int16 rot[4]; // 4096
|
||||
uint16 dltTime; // 256
|
||||
|
||||
CQuaternion rotation_() {
|
||||
return { rot[0] * (1/4096.f), rot[1] * (1/4096.f), rot[2] * (1/4096.f), rot[3] * (1/4096.f) };
|
||||
uint16_t predicted_y, predicted_p, predicted_r;
|
||||
float predicted_tx = 0, predicted_ty = 0, predicted_tz = 0;
|
||||
float nextDeltaTime;
|
||||
|
||||
template <typename T>
|
||||
T read_unaligned(uint32_t ro) {
|
||||
T rv;
|
||||
for (unsigned i = 0; i < sizeof(T); i++) {
|
||||
((uint8_t*)&rv)[i] = ((uint8_t*)keyFrames)[ro];
|
||||
ro++;
|
||||
}
|
||||
readOffset = ro;
|
||||
return rv;
|
||||
}
|
||||
template <typename T>
|
||||
__always_inline T read() {
|
||||
if (!(readOffset & (sizeof(T) -1))) {
|
||||
return read_aligned<T>();
|
||||
} else {
|
||||
return read_unaligned<T>(readOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void rotation_(const CQuaternion& q) {
|
||||
rot[0] = checked_f2i16(q.x * 4096.0f);
|
||||
rot[1] = checked_f2i16(q.y * 4096.0f);
|
||||
rot[2] = checked_f2i16(q.z * 4096.0f);
|
||||
rot[3] = checked_f2i16(q.w * 4096.0f);
|
||||
template <typename T>
|
||||
__always_inline T read_aligned() {
|
||||
T rv;
|
||||
rv = *(T*)((uint8_t*)keyFrames + readOffset);
|
||||
readOffset += sizeof(T);
|
||||
return rv;
|
||||
}
|
||||
|
||||
float deltaTime_() {
|
||||
return dltTime * (1/256.0f);
|
||||
__always_inline CQuaternion fromSphericalFixed(uint16_t y, uint16_t p, uint16_t r) {
|
||||
CQuaternion q;
|
||||
#if !defined(DC_SH4)
|
||||
q.w = cos((y / 65536.0f) * 2 * M_PI) * cos((p / 65536.0f) * 2 * M_PI);
|
||||
q.x = cos((y / 65536.0f) * 2 * M_PI) * sin((p / 65536.0f) * 2 * M_PI);
|
||||
q.y = sin((y / 65536.0f) * 2 * M_PI) * cos((r / 65536.0f) * 2 * M_PI);
|
||||
q.z = sin((y / 65536.0f) * 2 * M_PI) * sin((r / 65536.0f) * 2 * M_PI);
|
||||
#else
|
||||
register float __ys __asm__("fr0");
|
||||
register float __yc __asm__("fr1");
|
||||
register float __ps __asm__("fr2");
|
||||
register float __pc __asm__("fr3");
|
||||
register float __rs __asm__("fr4");
|
||||
register float __rc __asm__("fr5");
|
||||
|
||||
__asm__ __volatile__(
|
||||
R"(
|
||||
lds %[y],fpul
|
||||
fsca fpul, dr0
|
||||
lds %[p],fpul
|
||||
fsca fpul, dr2
|
||||
lds %[r],fpul
|
||||
fsca fpul, dr4
|
||||
)"
|
||||
: "=f" (__ys), "=f" (__yc), "=f" (__ps), "=f" (__pc), "=f" (__rs), "=f" (__rc)
|
||||
: "0" (__ys), "1" (__yc), "2" (__ps), "3" (__pc), "4" (__rs), "5" (__rc), [y]"r"(y), [p]"r"(p), [r]"r"(r));
|
||||
|
||||
q.w = __yc * __pc;
|
||||
q.x = __yc * __ps;
|
||||
q.y = __ys * __rc;
|
||||
q.z = __ys * __rs;
|
||||
#endif
|
||||
return q;
|
||||
}
|
||||
|
||||
void deltaTime_(float t) {
|
||||
dltTime = checked_f2u16(t * 256); // always round down
|
||||
}
|
||||
};
|
||||
void AdvanceFrame() {
|
||||
if (++curFrame == numFrames){
|
||||
currentRotation = nextRotation;
|
||||
currentTranslation = nextTranslation;
|
||||
SeekToStart();
|
||||
return;
|
||||
}
|
||||
|
||||
struct KeyFrameTransUncompressed : KeyFrame {
|
||||
// Some animations use bigger range, eg during the intro
|
||||
CVector trans;
|
||||
CVector translation_() {
|
||||
return trans;
|
||||
// rotation
|
||||
{
|
||||
currentRotation = nextRotation;
|
||||
|
||||
// For rotation Y:
|
||||
if (type & FLAGS_HAS_ROT_Y) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
predicted_y = read<uint16_t>();
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_y += diff * 8;
|
||||
}
|
||||
}
|
||||
// For rotation P:
|
||||
if (type & FLAGS_HAS_ROT_P) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
predicted_p = read<uint16_t>();
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_p += diff * 8;
|
||||
}
|
||||
}
|
||||
// For rotation R:
|
||||
if (type & FLAGS_HAS_ROT_R) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
predicted_r = read<uint16_t>();
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_r += diff * 8;
|
||||
}
|
||||
}
|
||||
|
||||
nextRotation = fromSphericalFixed(predicted_y, predicted_p, predicted_r);
|
||||
}
|
||||
|
||||
// translation
|
||||
if (type & KF_TRANS) {
|
||||
currentTranslation = nextTranslation;
|
||||
if (type & FLAGS_HAS_TRANS_X) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
uint16_t diff = read<uint16_t>();
|
||||
if (diff != 32768) {
|
||||
predicted_tx += static_cast<int16_t>(diff) / 128.f;
|
||||
} else {
|
||||
predicted_tx = read<float>();
|
||||
}
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_tx += diff / 127.f;
|
||||
}
|
||||
}
|
||||
// Translation Y:
|
||||
if (type & FLAGS_HAS_TRANS_Y) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
uint16_t diff = read<uint16_t>();
|
||||
if (diff != 32768) {
|
||||
predicted_ty += static_cast<int16_t>(diff) / 128.f;
|
||||
} else {
|
||||
predicted_ty = read<float>();
|
||||
}
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_ty += diff / 127.f;
|
||||
}
|
||||
}
|
||||
// Translation Z:
|
||||
if (type & FLAGS_HAS_TRANS_Z) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
uint16_t diff = read<uint16_t>();
|
||||
if (diff != 32768) {
|
||||
predicted_tz += static_cast<int16_t>(diff) / 128.f;
|
||||
} else {
|
||||
predicted_tz = read<float>();
|
||||
}
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_tz += diff / 127.f;
|
||||
}
|
||||
}
|
||||
|
||||
nextTranslation = { predicted_tx, predicted_ty, predicted_tz };
|
||||
}
|
||||
|
||||
// time delta + quaternion flips
|
||||
{
|
||||
uint8_t byteValPacked = read<uint8_t>();
|
||||
uint8_t byteVal = byteValPacked & 127;
|
||||
float diff;
|
||||
if (byteVal == 127) {
|
||||
uint16_t fixed_diff = read<uint16_t>();
|
||||
diff = fixed_diff / 256.f;
|
||||
} else {
|
||||
diff = byteVal / 256.f;
|
||||
}
|
||||
nextDeltaTime = diff;
|
||||
|
||||
if (byteValPacked & 128) {
|
||||
nextRotation = -nextRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void translation_(const CVector &v) {
|
||||
trans = v;
|
||||
CQuaternion GetPrevRotation() {
|
||||
return currentRotation;
|
||||
}
|
||||
};
|
||||
|
||||
struct KeyFrameTransCompressed : KeyFrame {
|
||||
int16 trans[3]; // 128
|
||||
|
||||
CVector translation_() {
|
||||
return { trans[0] * (1/128.f), trans[1] * (1/128.f), trans[2] * (1/128.f)};
|
||||
CQuaternion GetNextRotation() {
|
||||
return nextRotation;
|
||||
}
|
||||
float GetNextTimeDelta() {
|
||||
return nextDeltaTime;
|
||||
}
|
||||
|
||||
void translation_(const CVector &v) {
|
||||
trans[0] = checked_f2i16(v.x * 128.f);
|
||||
trans[1] = checked_f2i16(v.y * 128.f);
|
||||
trans[2] = checked_f2i16(v.z * 128.f);
|
||||
CVector GetPrevTranslation() {
|
||||
return currentTranslation;
|
||||
}
|
||||
CVector GetNextTranslationDelta() {
|
||||
return nextTranslation - currentTranslation;
|
||||
}
|
||||
|
||||
void Init(void* kf, int32 tp, int nF) {
|
||||
keyFrames = kf;
|
||||
type = tp;
|
||||
numFrames = nF;
|
||||
|
||||
SeekToStart();
|
||||
currentTranslation = nextTranslation;
|
||||
currentRotation = nextRotation;
|
||||
}
|
||||
|
||||
void SeekToStart() {
|
||||
readOffset = 0;
|
||||
float startTime = read_aligned<float>();
|
||||
float endTime = read_aligned<float>();
|
||||
|
||||
if (type & KF_TRANS) {
|
||||
CVector startTranslation;
|
||||
if (type & FLAGS_HAS_TRANS_LARGE) {
|
||||
startTranslation.x = read_aligned<float>();
|
||||
startTranslation.y = read_aligned<float>();
|
||||
startTranslation.z = read_aligned<float>();
|
||||
predicted_tx = startTranslation.x;
|
||||
predicted_ty = startTranslation.y;
|
||||
predicted_tz = startTranslation.z;
|
||||
|
||||
CVector endTranslation;
|
||||
// Read final translation (may be used for verification or ignored)
|
||||
endTranslation.x = read_aligned<float>();
|
||||
endTranslation.y = read_aligned<float>();
|
||||
endTranslation.z = read_aligned<float>();
|
||||
} else {
|
||||
startTranslation.x = read_aligned<int16_t>() / 128.f;
|
||||
startTranslation.y = read_aligned<int16_t>() / 128.f;
|
||||
startTranslation.z = read_aligned<int16_t>() / 128.f;
|
||||
predicted_tx = startTranslation.x;
|
||||
predicted_ty = startTranslation.y;
|
||||
predicted_tz = startTranslation.z;
|
||||
|
||||
CVector endTranslation;
|
||||
// Read final translation (for completeness)
|
||||
endTranslation.x = read_aligned<int16_t>() / 128.f;
|
||||
endTranslation.y = read_aligned<int16_t>() / 128.f;
|
||||
endTranslation.z = read_aligned<int16_t>() / 128.f;
|
||||
}
|
||||
|
||||
nextTranslation = startTranslation;
|
||||
} else {
|
||||
CVector startTranslation = { 0, 0, 0 };
|
||||
CVector endTranslation = { 0, 0, 0 };
|
||||
nextTranslation = startTranslation;
|
||||
}
|
||||
|
||||
predicted_y = read_aligned<uint16_t>();
|
||||
predicted_p = read_aligned<uint16_t>();
|
||||
predicted_r = read_aligned<uint16_t>();
|
||||
nextRotation = fromSphericalFixed(predicted_y, predicted_p, predicted_r);
|
||||
|
||||
if (type & FLAGS_QUAT0_NEG) {
|
||||
nextRotation = -nextRotation;
|
||||
}
|
||||
|
||||
nextDeltaTime = startTime;
|
||||
curFrame = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -73,11 +303,28 @@ struct KeyFrameTransCompressed : KeyFrame {
|
|||
// The sequence of key frames of one animated node
|
||||
class CAnimBlendSequence
|
||||
{
|
||||
template <typename T>
|
||||
__always_inline T read_aligned(uint32_t &readOffset) {
|
||||
T rv;
|
||||
rv = *(T*)((uint8_t*)keyFrames + readOffset);
|
||||
readOffset += sizeof(T);
|
||||
return rv;
|
||||
}
|
||||
public:
|
||||
enum {
|
||||
KF_ROT = 1,
|
||||
KF_TRANS = 2,
|
||||
KF_COMPRESSED = 4, // only applicable for KF_TRANS
|
||||
|
||||
FLAGS_HAS_ROT_Y = 1 << 8,
|
||||
FLAGS_HAS_ROT_P = 1 << 9,
|
||||
FLAGS_HAS_ROT_R = 1 << 10,
|
||||
|
||||
FLAGS_HAS_TRANS_X = 1 << 11,
|
||||
FLAGS_HAS_TRANS_Y = 1 << 12,
|
||||
FLAGS_HAS_TRANS_Z = 1 << 13,
|
||||
FLAGS_HAS_TRANS_ANY = 7 << 11,
|
||||
FLAGS_HAS_TRANS_LARGE = 1 << 14,
|
||||
FLAGS_QUAT0_NEG = 1 << 15
|
||||
};
|
||||
int32 type;
|
||||
char name[24];
|
||||
|
@ -87,72 +334,61 @@ public:
|
|||
#endif
|
||||
void *keyFrames;
|
||||
|
||||
|
||||
struct InitData {
|
||||
CVector startTranslation, endTranslation;
|
||||
float endTime;
|
||||
};
|
||||
|
||||
__always_inline InitData GetInitData() {
|
||||
InitData rv;
|
||||
uint32_t readOffset = 0;
|
||||
|
||||
float startTime = read_aligned<float>(readOffset);
|
||||
rv.endTime = read_aligned<float>(readOffset);
|
||||
|
||||
if (type & KF_TRANS) {
|
||||
if (type & FLAGS_HAS_TRANS_LARGE) {
|
||||
rv.startTranslation.x = read_aligned<float>(readOffset);
|
||||
rv.startTranslation.y = read_aligned<float>(readOffset);
|
||||
rv.startTranslation.z = read_aligned<float>(readOffset);
|
||||
|
||||
// Read final translation (may be used for verification or ignored)
|
||||
rv.endTranslation.x = read_aligned<float>(readOffset);
|
||||
rv.endTranslation.y = read_aligned<float>(readOffset);
|
||||
rv.endTranslation.z = read_aligned<float>(readOffset);
|
||||
} else {
|
||||
rv.startTranslation.x = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
rv.startTranslation.y = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
rv.startTranslation.z = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
|
||||
// Read final translation (for completeness)
|
||||
rv.endTranslation.x = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
rv.endTranslation.y = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
rv.endTranslation.z = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
}
|
||||
} else {
|
||||
rv.startTranslation = { 0, 0, 0 };
|
||||
rv.endTranslation = { 0, 0, 0 };
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
CVector GetStartTranslation() {
|
||||
return GetInitData().startTranslation;
|
||||
}
|
||||
float GetEndTime() {
|
||||
return GetInitData().endTime;
|
||||
}
|
||||
CVector GetEndTranslation() {
|
||||
return GetInitData().endTranslation;
|
||||
}
|
||||
|
||||
CAnimBlendSequence(void);
|
||||
virtual ~CAnimBlendSequence(void);
|
||||
void SetName(char *name);
|
||||
void SetNumFrames(int numFrames, bool translation, bool compress);
|
||||
void RemoveQuaternionFlips(void);
|
||||
|
||||
|
||||
void SetTranslation(int n, const CVector &v) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
((KeyFrameTransCompressed*)keyFrames)[n].translation_(v);
|
||||
} else if (type & KF_TRANS) {
|
||||
((KeyFrameTransUncompressed*)keyFrames)[n].translation_(v);
|
||||
} else {
|
||||
assert(false && "SetTranslation called on sequence without translation");
|
||||
}
|
||||
}
|
||||
|
||||
CVector GetTranslation(int n) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
return ((KeyFrameTransCompressed*)keyFrames)[n].translation_();
|
||||
} else if (type & KF_TRANS) {
|
||||
return ((KeyFrameTransUncompressed*)keyFrames)[n].translation_();
|
||||
} else {
|
||||
assert(false && "GetTranslation called on sequence without translation");
|
||||
}
|
||||
}
|
||||
|
||||
void SetRotation(int n, const CQuaternion &q) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
((KeyFrameTransCompressed*)keyFrames)[n].rotation_(q);
|
||||
} else if (type & KF_TRANS) {
|
||||
((KeyFrameTransUncompressed*)keyFrames)[n].rotation_(q);
|
||||
} else {
|
||||
((KeyFrame*)keyFrames)[n].rotation_(q);
|
||||
}
|
||||
}
|
||||
|
||||
CQuaternion GetRotation(int n) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
return ((KeyFrameTransCompressed*)keyFrames)[n].rotation_();
|
||||
} else if (type & KF_TRANS) {
|
||||
return ((KeyFrameTransUncompressed*)keyFrames)[n].rotation_();
|
||||
} else {
|
||||
return ((KeyFrame*)keyFrames)[n].rotation_();
|
||||
}
|
||||
}
|
||||
|
||||
void SetDeltaTime(int n, float t) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
((KeyFrameTransCompressed*)keyFrames)[n].deltaTime_(t);
|
||||
} else if (type & KF_TRANS) {
|
||||
((KeyFrameTransUncompressed*)keyFrames)[n].deltaTime_(t);
|
||||
} else {
|
||||
((KeyFrame*)keyFrames)[n].deltaTime_(t);
|
||||
}
|
||||
}
|
||||
|
||||
float GetDeltaTime(int n) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
return ((KeyFrameTransCompressed*)keyFrames)[n].deltaTime_();
|
||||
} else if (type & KF_TRANS) {
|
||||
return ((KeyFrameTransUncompressed*)keyFrames)[n].deltaTime_();
|
||||
} else {
|
||||
return ((KeyFrame*)keyFrames)[n].deltaTime_();
|
||||
}
|
||||
}
|
||||
|
||||
bool HasTranslation(void) { return !!(type & KF_TRANS); }
|
||||
bool MoveMemory(void);
|
||||
|
|
|
@ -780,134 +780,70 @@ CAnimManager::LoadAnimFile(int fd, bool compress)
|
|||
char ident[4];
|
||||
uint32 size;
|
||||
};
|
||||
IfpHeader anpk, info, name, dgan, cpan, anim;
|
||||
int numANPK;
|
||||
IfpHeader anpv;
|
||||
char buf[256];
|
||||
int i, j, k, l;
|
||||
int j, k, l;
|
||||
float *fbuf = (float*)buf;
|
||||
|
||||
CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader));
|
||||
if(!CGeneral::faststrncmp(anpk.ident, "ANLF", 4)) {
|
||||
ROUNDSIZE(anpk.size);
|
||||
CFileMgr::Read(fd, buf, anpk.size);
|
||||
numANPK = *(int*)buf;
|
||||
} else if(!CGeneral::faststrncmp(anpk.ident, "ANPK", 4)) {
|
||||
CFileMgr::Seek(fd, -8, 1);
|
||||
numANPK = 1;
|
||||
}
|
||||
CFileMgr::Read(fd, (char*)&anpv, sizeof(IfpHeader));
|
||||
assert(memcmp(anpv.ident, "ANPV", 4) == 0);
|
||||
|
||||
// block name
|
||||
CFileMgr::Read(fd, buf, anpv.size);
|
||||
CAnimBlock *animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++];
|
||||
strncpy(animBlock->name, buf, 24);
|
||||
int32_t numAnims;
|
||||
CFileMgr::Read(fd, (char*)&numAnims, sizeof(numAnims));
|
||||
animBlock->numAnims = numAnims;
|
||||
|
||||
for(i = 0; i < numANPK; i++){
|
||||
// block name
|
||||
CFileMgr::Read(fd, (char*)&anpk, sizeof(IfpHeader));
|
||||
ROUNDSIZE(anpk.size);
|
||||
CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader));
|
||||
ROUNDSIZE(info.size);
|
||||
CFileMgr::Read(fd, buf, info.size);
|
||||
CAnimBlock *animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++];
|
||||
strncpy(animBlock->name, buf+4, 24);
|
||||
animBlock->numAnims = *(int*)buf;
|
||||
animBlock->firstIndex = ms_numAnimations;
|
||||
|
||||
animBlock->firstIndex = ms_numAnimations;
|
||||
for(j = 0; j < animBlock->numAnims; j++){
|
||||
CAnimBlendHierarchy *hier = &ms_aAnimations[ms_numAnimations++];
|
||||
|
||||
for(j = 0; j < animBlock->numAnims; j++){
|
||||
CAnimBlendHierarchy *hier = &ms_aAnimations[ms_numAnimations++];
|
||||
// animation name
|
||||
int32_t animNameLength;
|
||||
CFileMgr::Read(fd, (char*)&animNameLength, sizeof(animNameLength));
|
||||
CFileMgr::Read(fd, buf, animNameLength);
|
||||
hier->SetName(buf);
|
||||
|
||||
// animation name
|
||||
CFileMgr::Read(fd, (char*)&name, sizeof(IfpHeader));
|
||||
ROUNDSIZE(name.size);
|
||||
CFileMgr::Read(fd, buf, name.size);
|
||||
hier->SetName(buf);
|
||||
int32_t numSeqs;
|
||||
CFileMgr::Read(fd, (char*)&numSeqs, sizeof(animNameLength));
|
||||
hier->numSequences = numSeqs;
|
||||
hier->sequences = new CAnimBlendSequence[hier->numSequences];
|
||||
|
||||
// DG info has number of nodes/sequences
|
||||
CFileMgr::Read(fd, (char*)&dgan, sizeof(IfpHeader));
|
||||
ROUNDSIZE(dgan.size);
|
||||
CFileMgr::Read(fd, (char*)&info, sizeof(IfpHeader));
|
||||
ROUNDSIZE(info.size);
|
||||
CFileMgr::Read(fd, buf, info.size);
|
||||
hier->numSequences = *(int*)buf;
|
||||
hier->sequences = new CAnimBlendSequence[hier->numSequences];
|
||||
CAnimBlendSequence *seq = hier->sequences;
|
||||
for(k = 0; k < hier->numSequences; k++, seq++){
|
||||
// Each node has a name and key frames
|
||||
int32_t seqNameLength;
|
||||
CFileMgr::Read(fd, (char*)&seqNameLength, sizeof(seqNameLength));
|
||||
CFileMgr::Read(fd, buf, seqNameLength);
|
||||
seq->SetName(buf);
|
||||
|
||||
CAnimBlendSequence *seq = hier->sequences;
|
||||
for(k = 0; k < hier->numSequences; k++, seq++){
|
||||
// Each node has a name and key frames
|
||||
CFileMgr::Read(fd, (char*)&cpan, sizeof(IfpHeader));
|
||||
ROUNDSIZE(dgan.size);
|
||||
CFileMgr::Read(fd, (char*)&anim, sizeof(IfpHeader));
|
||||
ROUNDSIZE(anim.size);
|
||||
CFileMgr::Read(fd, buf, anim.size);
|
||||
int numFrames = *(int*)(buf+28);
|
||||
seq->SetName(buf);
|
||||
int32_t numFrames;
|
||||
CFileMgr::Read(fd, (char*)&numFrames, sizeof(numFrames));
|
||||
seq->numFrames = numFrames;
|
||||
int32_t boneTag;
|
||||
CFileMgr::Read(fd, (char*)&boneTag, sizeof(boneTag));
|
||||
|
||||
#ifdef PED_SKIN
|
||||
if(anim.size == 44)
|
||||
seq->SetBoneTag(*(int*)(buf+40));
|
||||
seq->SetBoneTag(boneTag);
|
||||
#endif
|
||||
if(numFrames == 0)
|
||||
continue;
|
||||
if(numFrames == 0)
|
||||
continue;
|
||||
|
||||
bool hasScale = false;
|
||||
bool hasTranslation = false;
|
||||
CFileMgr::Read(fd, (char*)&info, sizeof(info));
|
||||
if(!CGeneral::faststrncmp(info.ident, "KRTS", 4)) {
|
||||
hasScale = true;
|
||||
seq->SetNumFrames(numFrames, true, compress);
|
||||
}else if(!CGeneral::faststrncmp(info.ident, "KRT0", 4)) {
|
||||
hasTranslation = true;
|
||||
seq->SetNumFrames(numFrames, true, compress);
|
||||
}else if(!CGeneral::faststrncmp(info.ident, "KR00", 4)){
|
||||
seq->SetNumFrames(numFrames, false, compress);
|
||||
}
|
||||
uint32_t dataSize;
|
||||
CFileMgr::Read(fd, (char*)&dataSize, sizeof(dataSize));
|
||||
uint16_t flags;
|
||||
CFileMgr::Read(fd, (char*)&flags, sizeof(flags));
|
||||
|
||||
float *frameTimes = (float*)RwMalloc(sizeof(float) * numFrames);
|
||||
|
||||
for(l = 0; l < numFrames; l++){
|
||||
if(hasScale){
|
||||
CFileMgr::Read(fd, buf, 0x2C);
|
||||
CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
|
||||
rot.Invert();
|
||||
CVector trans(fbuf[4], fbuf[5], fbuf[6]);
|
||||
|
||||
seq->SetRotation(l, rot);
|
||||
seq->SetTranslation(l, trans);
|
||||
// scaling ignored
|
||||
frameTimes[l] = fbuf[10]; // absolute time here
|
||||
}else if(hasTranslation){
|
||||
CFileMgr::Read(fd, buf, 0x20);
|
||||
CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
|
||||
rot.Invert();
|
||||
CVector trans(fbuf[4], fbuf[5], fbuf[6]);
|
||||
|
||||
seq->SetRotation(l, rot);
|
||||
seq->SetTranslation(l, trans);
|
||||
frameTimes[l] = fbuf[7]; // absolute time here
|
||||
}else{
|
||||
CFileMgr::Read(fd, buf, 0x14);
|
||||
CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
|
||||
rot.Invert();
|
||||
|
||||
seq->SetRotation(l, rot);
|
||||
frameTimes[l] = fbuf[4]; // absolute time here
|
||||
}
|
||||
}
|
||||
|
||||
// convert absolute time to deltas
|
||||
float running_sum = 0.0f;
|
||||
for (l = 0; l < numFrames; l++) {
|
||||
auto dt = frameTimes[l] - running_sum;
|
||||
seq->SetDeltaTime(l, dt);
|
||||
assert(seq->GetDeltaTime(l) <= dt);
|
||||
running_sum += seq->GetDeltaTime(l);
|
||||
// if (seq->GetDeltaTime(l) == 0.0f && dt != 0.0f) {
|
||||
// seq->SetDeltaTime(l, KF_MINDELTA);
|
||||
// }
|
||||
|
||||
// assert(seq->GetDeltaTime(l) != 0.0f || dt == 0.0f);
|
||||
}
|
||||
RwFree(frameTimes);
|
||||
}
|
||||
|
||||
hier->RemoveQuaternionFlips();
|
||||
hier->CalcTotalTime();
|
||||
seq->keyFrames = RwMalloc(dataSize);
|
||||
assert(seq->keyFrames);
|
||||
CFileMgr::Read(fd, (char*)seq->keyFrames, dataSize - sizeof(flags));
|
||||
seq->type = flags;
|
||||
}
|
||||
|
||||
hier->CalcTotalTime();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -267,7 +267,7 @@ CCutsceneMgr::SetupCutsceneToStart(void)
|
|||
assert(RwObjectGetType(ms_pCutsceneObjects[i]->m_rwObject) == rpCLUMP);
|
||||
if (CAnimBlendAssociation *pAnimBlendAssoc = RpAnimBlendClumpGetFirstAssociation((RpClump*)ms_pCutsceneObjects[i]->m_rwObject)) {
|
||||
assert(pAnimBlendAssoc->hierarchy->sequences[0].HasTranslation());
|
||||
ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + pAnimBlendAssoc->hierarchy->sequences[0].GetTranslation(0));
|
||||
ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + pAnimBlendAssoc->hierarchy->sequences[0].GetStartTranslation());
|
||||
CWorld::Add(ms_pCutsceneObjects[i]);
|
||||
pAnimBlendAssoc->SetRun();
|
||||
} else {
|
||||
|
|
|
@ -4249,7 +4249,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedDraggedOutCarAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedDraggedOutCarAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4260,7 +4260,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedCarDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedCarDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedCarDoorAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4271,7 +4271,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedCarDoorLoAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedCarDoorLoAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedCarDoorLoAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4282,7 +4282,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedQuickDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedQuickDraggedOutCarAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedQuickDraggedOutCarAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4293,7 +4293,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedVanRearDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedVanRearDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedVanRearDoorAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4304,7 +4304,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedTrainDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedTrainDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedTrainDoorAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ CAnimBlendAssociation::~CAnimBlendAssociation(void)
|
|||
link.Remove();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CAnimBlendAssociation::AllocateAnimBlendNodeArray(int n)
|
||||
{
|
||||
|
@ -55,8 +54,11 @@ CAnimBlendAssociation::AllocateAnimBlendNodeArray(int n)
|
|||
void
|
||||
CAnimBlendAssociation::FreeAnimBlendNodeArray(void)
|
||||
{
|
||||
if(nodes)
|
||||
if(nodes) {
|
||||
for(unsigned i = 0; i < numNodes; i++)
|
||||
nodes[i].Destroy();
|
||||
RwFreeAlign(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -80,8 +82,9 @@ CAnimBlendAssociation::Init(RpClump *clump, CAnimBlendHierarchy *hier)
|
|||
frame = RpAnimBlendClumpFindFrame(clump, seq->name);
|
||||
else
|
||||
frame = RpAnimBlendClumpFindBone(clump, seq->boneTag);
|
||||
if(frame && seq->numFrames > 0)
|
||||
if(frame && seq->numFrames > 0) {
|
||||
nodes[frame - clumpData->frames].sequence = seq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,22 +35,11 @@ CAnimBlendHierarchy::CalcTotalTime(void)
|
|||
if(sequences[i].numFrames == 0)
|
||||
continue;
|
||||
#endif
|
||||
float seqTime = 0.0f;
|
||||
for(j = 0; j < sequences[i].numFrames; j++)
|
||||
seqTime += sequences[i].GetDeltaTime(j);
|
||||
float seqTime = sequences[i].GetEndTime();
|
||||
totalLength = Max(totalLength, seqTime);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CAnimBlendHierarchy::RemoveQuaternionFlips(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < numSequences; i++)
|
||||
sequences[i].RemoveQuaternionFlips();
|
||||
}
|
||||
|
||||
void
|
||||
CAnimBlendHierarchy::RemoveAnimSequences(void)
|
||||
{
|
||||
|
|
|
@ -21,7 +21,6 @@ public:
|
|||
void Shutdown(void);
|
||||
void SetName(char *name);
|
||||
void CalcTotalTime(void);
|
||||
void RemoveQuaternionFlips(void);
|
||||
void RemoveAnimSequences(void);
|
||||
void Uncompress(void);
|
||||
void RemoveUncompressedData(void);
|
||||
|
|
|
@ -7,15 +7,24 @@ void
|
|||
CAnimBlendNode::Init(void)
|
||||
{
|
||||
frameA = -1;
|
||||
frameB = -1;
|
||||
remainingTime = 0.0f;
|
||||
sequence = nil;
|
||||
association = nil;
|
||||
player = nil;
|
||||
}
|
||||
|
||||
void CAnimBlendNode::Destroy(void) {
|
||||
if (player) {
|
||||
delete player;
|
||||
player = nil;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight)
|
||||
{
|
||||
assert (player && player->keyFrames == sequence->keyFrames);
|
||||
|
||||
bool looped = false;
|
||||
|
||||
trans = CVector(0.0f, 0.0f, 0.0f);
|
||||
|
@ -29,17 +38,17 @@ CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight)
|
|||
|
||||
float blend = association->GetBlendAmount(weight);
|
||||
if(blend > 0.0f){
|
||||
float kfAdt = sequence->GetDeltaTime(frameA);
|
||||
float kfAdt = player->GetNextTimeDelta();
|
||||
float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt;
|
||||
if(sequence->type & CAnimBlendSequence::KF_TRANS){
|
||||
auto kfAt = sequence->GetTranslation(frameA);
|
||||
auto kfBt = sequence->GetTranslation(frameB);
|
||||
trans = kfBt + t*(kfAt - kfBt);
|
||||
if(player->type & CAnimBlendSequence::KF_TRANS){
|
||||
auto kfdAt = player->GetNextTranslationDelta();
|
||||
auto kfBt = player->GetPrevTranslation();
|
||||
trans = kfBt + t*kfdAt;
|
||||
trans *= blend;
|
||||
}
|
||||
if(sequence->type & CAnimBlendSequence::KF_ROT){
|
||||
auto kfAr = sequence->GetRotation(frameA);
|
||||
auto kfBr = sequence->GetRotation(frameB);
|
||||
if(player->type & CAnimBlendSequence::KF_ROT){
|
||||
auto kfAr = player->GetNextRotation();
|
||||
auto kfBr = player->GetPrevRotation();
|
||||
rot.Slerp(kfBr, kfAr, theta, invSin, t);
|
||||
rot *= blend;
|
||||
}
|
||||
|
@ -51,108 +60,84 @@ CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight)
|
|||
bool
|
||||
CAnimBlendNode::NextKeyFrame(void)
|
||||
{
|
||||
assert(player != nil);
|
||||
bool looped;
|
||||
|
||||
if(sequence->numFrames <= 1)
|
||||
if(player->numFrames <= 1)
|
||||
return false;
|
||||
|
||||
looped = false;
|
||||
frameB = frameA;
|
||||
|
||||
// Advance as long as we have to
|
||||
while(remainingTime <= 0.0f){
|
||||
frameA++;
|
||||
|
||||
if(frameA >= sequence->numFrames){
|
||||
if(frameA >= player->numFrames){
|
||||
// reached end of animation
|
||||
if(!association->IsRepeating()){
|
||||
frameA--;
|
||||
remainingTime = 0.0f;
|
||||
assert(frameA == player->curFrame);
|
||||
CalcDeltas();
|
||||
return false;
|
||||
}
|
||||
looped = true;
|
||||
frameA = 0;
|
||||
}
|
||||
|
||||
remainingTime += sequence->GetDeltaTime(frameA);
|
||||
player->AdvanceFrame();
|
||||
remainingTime += player->GetNextTimeDelta();
|
||||
}
|
||||
|
||||
frameB = frameA - 1;
|
||||
if(frameB < 0)
|
||||
frameB += sequence->numFrames;
|
||||
|
||||
assert(frameA == player->curFrame);
|
||||
CalcDeltas();
|
||||
return looped;
|
||||
}
|
||||
|
||||
// bool
|
||||
// CAnimBlendNode::NextKeyFrameCompressed(void)
|
||||
// {
|
||||
// bool looped;
|
||||
|
||||
// if(sequence->numFrames <= 1)
|
||||
// return false;
|
||||
|
||||
// looped = false;
|
||||
// frameB = frameA;
|
||||
|
||||
// // Advance as long as we have to
|
||||
// while(remainingTime <= 0.0f){
|
||||
// frameA++;
|
||||
|
||||
// if(frameA >= sequence->numFrames){
|
||||
// // reached end of animation
|
||||
// if(!association->IsRepeating()){
|
||||
// frameA--;
|
||||
// remainingTime = 0.0f;
|
||||
// return false;
|
||||
// }
|
||||
// looped = true;
|
||||
// frameA = 0;
|
||||
// }
|
||||
|
||||
// remainingTime += sequence->GetKeyFrameCompressed(frameA)->GetDeltaTime();
|
||||
// }
|
||||
|
||||
// frameB = frameA - 1;
|
||||
// if(frameB < 0)
|
||||
// frameB += sequence->numFrames;
|
||||
|
||||
// CalcDeltasCompressed();
|
||||
// return looped;
|
||||
// }
|
||||
|
||||
// Set animation to time t
|
||||
bool
|
||||
CAnimBlendNode::FindKeyFrame(float t)
|
||||
{
|
||||
if(sequence->numFrames < 1)
|
||||
if (player == nil) {
|
||||
player = new CAnimBlendPlayer();
|
||||
player->Init(sequence->keyFrames, sequence->type, sequence->numFrames);
|
||||
}
|
||||
if(player->numFrames < 1)
|
||||
return false;
|
||||
|
||||
frameA = 0;
|
||||
frameB = frameA;
|
||||
player->SeekToStart();
|
||||
|
||||
if(sequence->numFrames == 1){
|
||||
assert (player->keyFrames == sequence->keyFrames);
|
||||
|
||||
if(player->numFrames == 1){
|
||||
remainingTime = 0.0f;
|
||||
}else{
|
||||
// advance until t is between frameB and frameA
|
||||
while (t > sequence->GetDeltaTime(++frameA)) {
|
||||
t -= sequence->GetDeltaTime(frameA);
|
||||
if (frameA + 1 >= sequence->numFrames) {
|
||||
frameA++;
|
||||
player->AdvanceFrame();
|
||||
while (t > player->GetNextTimeDelta()) {
|
||||
t -= player->GetNextTimeDelta();
|
||||
if (frameA + 1 >= player->numFrames) {
|
||||
// reached end of animation
|
||||
if (!association->IsRepeating()) {
|
||||
assert(frameA == player->curFrame);
|
||||
CalcDeltas();
|
||||
remainingTime = 0.0f;
|
||||
return false;
|
||||
}
|
||||
// Frame 0 is effectively skipped here
|
||||
// Looks like an re3 / game bug?
|
||||
frameA = 0;
|
||||
player->SeekToStart();
|
||||
}
|
||||
frameB = frameA;
|
||||
frameA++;
|
||||
player->AdvanceFrame();
|
||||
}
|
||||
|
||||
remainingTime = sequence->GetDeltaTime(frameA) - t;
|
||||
remainingTime = player->GetNextTimeDelta() - t;
|
||||
}
|
||||
|
||||
assert(frameA == player->curFrame);
|
||||
CalcDeltas();
|
||||
return true;
|
||||
}
|
||||
|
@ -160,10 +145,10 @@ CAnimBlendNode::FindKeyFrame(float t)
|
|||
void
|
||||
CAnimBlendNode::CalcDeltas(void)
|
||||
{
|
||||
if((sequence->type & CAnimBlendSequence::KF_ROT) == 0)
|
||||
if((player->type & CAnimBlendSequence::KF_ROT) == 0)
|
||||
return;
|
||||
auto kfAr = sequence->GetRotation(frameA);
|
||||
auto kfBr = sequence->GetRotation(frameB);
|
||||
auto kfAr = player->GetNextRotation();
|
||||
auto kfBr = player->GetPrevRotation();
|
||||
float cos = DotProduct(kfAr, kfBr);
|
||||
if(cos > 1.0f)
|
||||
cos = 1.0f;
|
||||
|
@ -178,12 +163,12 @@ CAnimBlendNode::GetCurrentTranslation(CVector &trans, float weight)
|
|||
|
||||
float blend = association->GetBlendAmount(weight);
|
||||
if(blend > 0.0f){
|
||||
auto kfAdt = sequence->GetDeltaTime(frameA);
|
||||
auto kfAdt = player->GetNextTimeDelta();
|
||||
float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt;
|
||||
if(sequence->type & CAnimBlendSequence::KF_TRANS){
|
||||
auto kfAt = sequence->GetTranslation(frameA);
|
||||
auto kfBt = sequence->GetTranslation(frameB);
|
||||
trans = kfBt + t*(kfAt - kfBt);
|
||||
if(player->type & CAnimBlendSequence::KF_TRANS){
|
||||
auto kfdAt = player->GetNextTranslationDelta();
|
||||
auto kfBt = player->GetPrevTranslation();
|
||||
trans = kfBt + t*kfdAt;
|
||||
trans *= blend;
|
||||
}
|
||||
}
|
||||
|
@ -197,8 +182,8 @@ CAnimBlendNode::GetEndTranslation(CVector &trans, float weight)
|
|||
|
||||
float blend = association->GetBlendAmount(weight);
|
||||
if(blend > 0.0f){
|
||||
if(sequence->type & CAnimBlendSequence::KF_TRANS){
|
||||
CVector pos = sequence->GetTranslation(sequence->numFrames-1);
|
||||
if(player->type & CAnimBlendSequence::KF_TRANS){
|
||||
CVector pos = sequence->GetEndTranslation();
|
||||
trans = pos * blend;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,16 +13,15 @@ public:
|
|||
float invSin; // 1/Sin(theta)
|
||||
// indices into array in sequence
|
||||
int32 frameA; // next key frame
|
||||
int32 frameB; // previous key frame
|
||||
float remainingTime; // time until frames have to advance
|
||||
CAnimBlendPlayer* player;
|
||||
CAnimBlendSequence *sequence;
|
||||
CAnimBlendAssociation *association;
|
||||
|
||||
void Init(void);
|
||||
void Destroy(void);
|
||||
bool Update(CVector &trans, CQuaternion &rot, float weight);
|
||||
bool UpdateCompressed(CVector &trans, CQuaternion &rot, float weight);
|
||||
bool NextKeyFrame(void);
|
||||
bool NextKeyFrameCompressed(void);
|
||||
bool FindKeyFrame(float t);
|
||||
void CalcDeltas(void);
|
||||
void GetCurrentTranslation(CVector &trans, float weight);
|
||||
|
|
|
@ -23,45 +23,6 @@ CAnimBlendSequence::SetName(char *name)
|
|||
strncpy(this->name, name, 24);
|
||||
}
|
||||
|
||||
void
|
||||
CAnimBlendSequence::SetNumFrames(int numFrames, bool translation, bool compress)
|
||||
{
|
||||
int sz;
|
||||
|
||||
if(translation){
|
||||
type |= KF_ROT | KF_TRANS;
|
||||
if (compress) {
|
||||
type |= KF_COMPRESSED;
|
||||
sz = sizeof(KeyFrameTransCompressed);
|
||||
} else {
|
||||
sz = sizeof(KeyFrameTransUncompressed);
|
||||
}
|
||||
}else{
|
||||
sz = sizeof(KeyFrame);
|
||||
type |= KF_ROT;
|
||||
}
|
||||
keyFrames = RwMalloc(sz * numFrames);
|
||||
this->numFrames = numFrames;
|
||||
}
|
||||
|
||||
void
|
||||
CAnimBlendSequence::RemoveQuaternionFlips(void)
|
||||
{
|
||||
int i;
|
||||
CQuaternion last;
|
||||
|
||||
if(numFrames < 2)
|
||||
return;
|
||||
|
||||
last = GetRotation(0);
|
||||
for(i = 1; i < numFrames; i++){
|
||||
auto KFr = GetRotation(i);
|
||||
if(DotProduct(last, KFr) < 0.0f)
|
||||
SetRotation(i, -KFr);
|
||||
last = GetRotation(i);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_CUSTOM_ALLOCATOR
|
||||
bool
|
||||
CAnimBlendSequence::MoveMemory(void)
|
||||
|
|
|
@ -1,82 +1,333 @@
|
|||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "Quaternion.h"
|
||||
|
||||
#ifdef MoveMemory
|
||||
#undef MoveMemory // windows shit
|
||||
#endif
|
||||
|
||||
// TODO: put them somewhere else?
|
||||
static int16 checked_f2i16(float f) {
|
||||
assert(f >= -32768 && f <= 32767);
|
||||
return f;
|
||||
}
|
||||
struct CAnimBlendPlayer {
|
||||
enum {
|
||||
KF_ROT = 1,
|
||||
KF_TRANS = 2,
|
||||
|
||||
FLAGS_HAS_ROT_Y = 1 << 8,
|
||||
FLAGS_HAS_ROT_P = 1 << 9,
|
||||
FLAGS_HAS_ROT_R = 1 << 10,
|
||||
|
||||
static uint16 checked_f2u16(float f) {
|
||||
assert(f >= 0 && f <= 65535);
|
||||
return f;
|
||||
}
|
||||
FLAGS_HAS_TRANS_X = 1 << 11,
|
||||
FLAGS_HAS_TRANS_Y = 1 << 12,
|
||||
FLAGS_HAS_TRANS_Z = 1 << 13,
|
||||
FLAGS_HAS_TRANS_ANY = 7 << 11,
|
||||
FLAGS_HAS_TRANS_LARGE = 1 << 14,
|
||||
FLAGS_QUAT0_NEG = 1 << 15
|
||||
};
|
||||
|
||||
#define KF_MINDELTA (1/256.f)
|
||||
int32 type;
|
||||
void* keyFrames;
|
||||
int32 curFrame;
|
||||
int32 numFrames;
|
||||
CQuaternion currentRotation;
|
||||
CQuaternion nextRotation;
|
||||
CVector currentTranslation;
|
||||
CVector nextTranslation;
|
||||
|
||||
struct KeyFrame {
|
||||
int16 rot[4]; // 4096
|
||||
uint16 dltTime; // 256
|
||||
unsigned readOffset;
|
||||
unsigned readOffset_initial;
|
||||
|
||||
CQuaternion rotation_() {
|
||||
return { rot[0] * (1/4096.f), rot[1] * (1/4096.f), rot[2] * (1/4096.f), rot[3] * (1/4096.f) };
|
||||
uint16_t predicted_y, predicted_p, predicted_r;
|
||||
float predicted_tx = 0, predicted_ty = 0, predicted_tz = 0;
|
||||
float nextDeltaTime;
|
||||
|
||||
template <typename T>
|
||||
T read_unaligned(uint32_t ro) {
|
||||
T rv;
|
||||
for (unsigned i = 0; i < sizeof(T); i++) {
|
||||
((uint8_t*)&rv)[i] = ((uint8_t*)keyFrames)[ro];
|
||||
ro++;
|
||||
}
|
||||
readOffset = ro;
|
||||
return rv;
|
||||
}
|
||||
template <typename T>
|
||||
__always_inline T read() {
|
||||
if (!(readOffset & (sizeof(T) -1))) {
|
||||
return read_aligned<T>();
|
||||
} else {
|
||||
return read_unaligned<T>(readOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void rotation_(const CQuaternion& q) {
|
||||
rot[0] = checked_f2i16(q.x * 4096.0f);
|
||||
rot[1] = checked_f2i16(q.y * 4096.0f);
|
||||
rot[2] = checked_f2i16(q.z * 4096.0f);
|
||||
rot[3] = checked_f2i16(q.w * 4096.0f);
|
||||
template <typename T>
|
||||
__always_inline T read_aligned() {
|
||||
T rv;
|
||||
rv = *(T*)((uint8_t*)keyFrames + readOffset);
|
||||
readOffset += sizeof(T);
|
||||
return rv;
|
||||
}
|
||||
|
||||
float deltaTime_() {
|
||||
return dltTime * (1/256.0f);
|
||||
__always_inline CQuaternion fromSphericalFixed(uint16_t y, uint16_t p, uint16_t r) {
|
||||
CQuaternion q;
|
||||
#if !defined(DC_SH4)
|
||||
q.w = cos((y / 65536.0f) * 2 * M_PI) * cos((p / 65536.0f) * 2 * M_PI);
|
||||
q.x = cos((y / 65536.0f) * 2 * M_PI) * sin((p / 65536.0f) * 2 * M_PI);
|
||||
q.y = sin((y / 65536.0f) * 2 * M_PI) * cos((r / 65536.0f) * 2 * M_PI);
|
||||
q.z = sin((y / 65536.0f) * 2 * M_PI) * sin((r / 65536.0f) * 2 * M_PI);
|
||||
#else
|
||||
register float __ys __asm__("fr0");
|
||||
register float __yc __asm__("fr1");
|
||||
register float __ps __asm__("fr2");
|
||||
register float __pc __asm__("fr3");
|
||||
register float __rs __asm__("fr4");
|
||||
register float __rc __asm__("fr5");
|
||||
|
||||
__asm__ __volatile__(
|
||||
R"(
|
||||
lds %[y],fpul
|
||||
fsca fpul, dr0
|
||||
lds %[p],fpul
|
||||
fsca fpul, dr2
|
||||
lds %[r],fpul
|
||||
fsca fpul, dr4
|
||||
)"
|
||||
: "=f" (__ys), "=f" (__yc), "=f" (__ps), "=f" (__pc), "=f" (__rs), "=f" (__rc)
|
||||
: "0" (__ys), "1" (__yc), "2" (__ps), "3" (__pc), "4" (__rs), "5" (__rc), [y]"r"(y), [p]"r"(p), [r]"r"(r));
|
||||
|
||||
q.w = __yc * __pc;
|
||||
q.x = __yc * __ps;
|
||||
q.y = __ys * __rc;
|
||||
q.z = __ys * __rs;
|
||||
#endif
|
||||
return q;
|
||||
}
|
||||
|
||||
void deltaTime_(float t) {
|
||||
dltTime = checked_f2u16(t * 256); // always round down
|
||||
}
|
||||
};
|
||||
void AdvanceFrame() {
|
||||
if (++curFrame == numFrames){
|
||||
currentRotation = nextRotation;
|
||||
currentTranslation = nextTranslation;
|
||||
SeekToStart();
|
||||
return;
|
||||
}
|
||||
|
||||
struct KeyFrameTransUncompressed : KeyFrame {
|
||||
// Some animations use bigger range, eg during the intro
|
||||
CVector trans;
|
||||
CVector translation_() {
|
||||
return trans;
|
||||
// rotation
|
||||
{
|
||||
currentRotation = nextRotation;
|
||||
|
||||
// For rotation Y:
|
||||
if (type & FLAGS_HAS_ROT_Y) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
predicted_y = read<uint16_t>();
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_y += diff * 8;
|
||||
}
|
||||
}
|
||||
// For rotation P:
|
||||
if (type & FLAGS_HAS_ROT_P) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
predicted_p = read<uint16_t>();
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_p += diff * 8;
|
||||
}
|
||||
}
|
||||
// For rotation R:
|
||||
if (type & FLAGS_HAS_ROT_R) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
predicted_r = read<uint16_t>();
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_r += diff * 8;
|
||||
}
|
||||
}
|
||||
|
||||
nextRotation = fromSphericalFixed(predicted_y, predicted_p, predicted_r);
|
||||
}
|
||||
|
||||
// translation
|
||||
if (type & KF_TRANS) {
|
||||
currentTranslation = nextTranslation;
|
||||
if (type & FLAGS_HAS_TRANS_X) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
uint16_t diff = read<uint16_t>();
|
||||
if (diff != 32768) {
|
||||
predicted_tx += static_cast<int16_t>(diff) / 128.f;
|
||||
} else {
|
||||
predicted_tx = read<float>();
|
||||
}
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_tx += diff / 127.f;
|
||||
}
|
||||
}
|
||||
// Translation Y:
|
||||
if (type & FLAGS_HAS_TRANS_Y) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
uint16_t diff = read<uint16_t>();
|
||||
if (diff != 32768) {
|
||||
predicted_ty += static_cast<int16_t>(diff) / 128.f;
|
||||
} else {
|
||||
predicted_ty = read<float>();
|
||||
}
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_ty += diff / 127.f;
|
||||
}
|
||||
}
|
||||
// Translation Z:
|
||||
if (type & FLAGS_HAS_TRANS_Z) {
|
||||
uint8_t byteVal = read<uint8_t>();
|
||||
if (byteVal == 128) {
|
||||
uint16_t diff = read<uint16_t>();
|
||||
if (diff != 32768) {
|
||||
predicted_tz += static_cast<int16_t>(diff) / 128.f;
|
||||
} else {
|
||||
predicted_tz = read<float>();
|
||||
}
|
||||
} else {
|
||||
int8_t diff = static_cast<int8_t>(byteVal);
|
||||
predicted_tz += diff / 127.f;
|
||||
}
|
||||
}
|
||||
|
||||
nextTranslation = { predicted_tx, predicted_ty, predicted_tz };
|
||||
}
|
||||
|
||||
// time delta + quaternion flips
|
||||
{
|
||||
uint8_t byteValPacked = read<uint8_t>();
|
||||
uint8_t byteVal = byteValPacked & 127;
|
||||
float diff;
|
||||
if (byteVal == 127) {
|
||||
uint16_t fixed_diff = read<uint16_t>();
|
||||
diff = fixed_diff / 256.f;
|
||||
} else {
|
||||
diff = byteVal / 256.f;
|
||||
}
|
||||
nextDeltaTime = diff;
|
||||
|
||||
if (byteValPacked & 128) {
|
||||
nextRotation = -nextRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void translation_(const CVector &v) {
|
||||
trans = v;
|
||||
CQuaternion GetPrevRotation() {
|
||||
return currentRotation;
|
||||
}
|
||||
};
|
||||
|
||||
struct KeyFrameTransCompressed : KeyFrame {
|
||||
int16 trans[3]; // 128
|
||||
|
||||
CVector translation_() {
|
||||
return { trans[0] * (1/128.f), trans[1] * (1/128.f), trans[2] * (1/128.f)};
|
||||
CQuaternion GetNextRotation() {
|
||||
return nextRotation;
|
||||
}
|
||||
float GetNextTimeDelta() {
|
||||
return nextDeltaTime;
|
||||
}
|
||||
|
||||
void translation_(const CVector &v) {
|
||||
trans[0] = checked_f2i16(v.x * 128.f);
|
||||
trans[1] = checked_f2i16(v.y * 128.f);
|
||||
trans[2] = checked_f2i16(v.z * 128.f);
|
||||
CVector GetPrevTranslation() {
|
||||
return currentTranslation;
|
||||
}
|
||||
CVector GetNextTranslationDelta() {
|
||||
return nextTranslation - currentTranslation;
|
||||
}
|
||||
|
||||
void Init(void* kf, int32 tp, int nF) {
|
||||
keyFrames = kf;
|
||||
type = tp;
|
||||
numFrames = nF;
|
||||
|
||||
SeekToStart();
|
||||
currentTranslation = nextTranslation;
|
||||
currentRotation = nextRotation;
|
||||
}
|
||||
|
||||
void SeekToStart() {
|
||||
readOffset = 0;
|
||||
float startTime = read_aligned<float>();
|
||||
float endTime = read_aligned<float>();
|
||||
|
||||
if (type & KF_TRANS) {
|
||||
CVector startTranslation;
|
||||
if (type & FLAGS_HAS_TRANS_LARGE) {
|
||||
startTranslation.x = read_aligned<float>();
|
||||
startTranslation.y = read_aligned<float>();
|
||||
startTranslation.z = read_aligned<float>();
|
||||
predicted_tx = startTranslation.x;
|
||||
predicted_ty = startTranslation.y;
|
||||
predicted_tz = startTranslation.z;
|
||||
|
||||
CVector endTranslation;
|
||||
// Read final translation (may be used for verification or ignored)
|
||||
endTranslation.x = read_aligned<float>();
|
||||
endTranslation.y = read_aligned<float>();
|
||||
endTranslation.z = read_aligned<float>();
|
||||
} else {
|
||||
startTranslation.x = read_aligned<int16_t>() / 128.f;
|
||||
startTranslation.y = read_aligned<int16_t>() / 128.f;
|
||||
startTranslation.z = read_aligned<int16_t>() / 128.f;
|
||||
predicted_tx = startTranslation.x;
|
||||
predicted_ty = startTranslation.y;
|
||||
predicted_tz = startTranslation.z;
|
||||
|
||||
CVector endTranslation;
|
||||
// Read final translation (for completeness)
|
||||
endTranslation.x = read_aligned<int16_t>() / 128.f;
|
||||
endTranslation.y = read_aligned<int16_t>() / 128.f;
|
||||
endTranslation.z = read_aligned<int16_t>() / 128.f;
|
||||
}
|
||||
|
||||
nextTranslation = startTranslation;
|
||||
} else {
|
||||
CVector startTranslation = { 0, 0, 0 };
|
||||
CVector endTranslation = { 0, 0, 0 };
|
||||
nextTranslation = startTranslation;
|
||||
}
|
||||
|
||||
predicted_y = read_aligned<uint16_t>();
|
||||
predicted_p = read_aligned<uint16_t>();
|
||||
predicted_r = read_aligned<uint16_t>();
|
||||
nextRotation = fromSphericalFixed(predicted_y, predicted_p, predicted_r);
|
||||
|
||||
if (type & FLAGS_QUAT0_NEG) {
|
||||
nextRotation = -nextRotation;
|
||||
}
|
||||
|
||||
nextDeltaTime = startTime;
|
||||
curFrame = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// The sequence of key frames of one animated node
|
||||
class CAnimBlendSequence
|
||||
{
|
||||
template <typename T>
|
||||
__always_inline T read_aligned(uint32_t &readOffset) {
|
||||
T rv;
|
||||
rv = *(T*)((uint8_t*)keyFrames + readOffset);
|
||||
readOffset += sizeof(T);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public:
|
||||
enum {
|
||||
KF_ROT = 1,
|
||||
KF_TRANS = 2,
|
||||
KF_COMPRESSED = 4, // only applicable for KF_TRANS
|
||||
|
||||
FLAGS_HAS_ROT_Y = 1 << 8,
|
||||
FLAGS_HAS_ROT_P = 1 << 9,
|
||||
FLAGS_HAS_ROT_R = 1 << 10,
|
||||
|
||||
FLAGS_HAS_TRANS_X = 1 << 11,
|
||||
FLAGS_HAS_TRANS_Y = 1 << 12,
|
||||
FLAGS_HAS_TRANS_Z = 1 << 13,
|
||||
FLAGS_HAS_TRANS_ANY = 7 << 11,
|
||||
FLAGS_HAS_TRANS_LARGE = 1 << 14,
|
||||
FLAGS_QUAT0_NEG = 1 << 15
|
||||
};
|
||||
int32 type;
|
||||
char name[24];
|
||||
|
@ -84,71 +335,60 @@ public:
|
|||
int16 boneTag;
|
||||
void *keyFrames;
|
||||
|
||||
struct InitData {
|
||||
CVector startTranslation, endTranslation;
|
||||
float endTime;
|
||||
};
|
||||
|
||||
__always_inline InitData GetInitData() {
|
||||
InitData rv;
|
||||
uint32_t readOffset = 0;
|
||||
|
||||
float startTime = read_aligned<float>(readOffset);
|
||||
rv.endTime = read_aligned<float>(readOffset);
|
||||
|
||||
if (type & KF_TRANS) {
|
||||
if (type & FLAGS_HAS_TRANS_LARGE) {
|
||||
rv.startTranslation.x = read_aligned<float>(readOffset);
|
||||
rv.startTranslation.y = read_aligned<float>(readOffset);
|
||||
rv.startTranslation.z = read_aligned<float>(readOffset);
|
||||
|
||||
// Read final translation (may be used for verification or ignored)
|
||||
rv.endTranslation.x = read_aligned<float>(readOffset);
|
||||
rv.endTranslation.y = read_aligned<float>(readOffset);
|
||||
rv.endTranslation.z = read_aligned<float>(readOffset);
|
||||
} else {
|
||||
rv.startTranslation.x = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
rv.startTranslation.y = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
rv.startTranslation.z = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
|
||||
// Read final translation (for completeness)
|
||||
rv.endTranslation.x = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
rv.endTranslation.y = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
rv.endTranslation.z = read_aligned<int16_t>(readOffset) / 128.f;
|
||||
}
|
||||
} else {
|
||||
rv.startTranslation = { 0, 0, 0 };
|
||||
rv.endTranslation = { 0, 0, 0 };
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
CVector GetStartTranslation() {
|
||||
return GetInitData().startTranslation;
|
||||
}
|
||||
float GetEndTime() {
|
||||
return GetInitData().endTime;
|
||||
}
|
||||
CVector GetEndTranslation() {
|
||||
return GetInitData().endTranslation;
|
||||
}
|
||||
|
||||
CAnimBlendSequence(void);
|
||||
virtual ~CAnimBlendSequence(void);
|
||||
void SetName(char *name);
|
||||
void SetNumFrames(int numFrames, bool translation, bool compressed);
|
||||
void RemoveQuaternionFlips(void);
|
||||
|
||||
void SetTranslation(int n, const CVector &v) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
((KeyFrameTransCompressed*)keyFrames)[n].translation_(v);
|
||||
} else if (type & KF_TRANS) {
|
||||
((KeyFrameTransUncompressed*)keyFrames)[n].translation_(v);
|
||||
} else {
|
||||
assert(false && "SetTranslation called on sequence without translation");
|
||||
}
|
||||
}
|
||||
|
||||
CVector GetTranslation(int n) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
return ((KeyFrameTransCompressed*)keyFrames)[n].translation_();
|
||||
} else if (type & KF_TRANS) {
|
||||
return ((KeyFrameTransUncompressed*)keyFrames)[n].translation_();
|
||||
} else {
|
||||
assert(false && "GetTranslation called on sequence without translation");
|
||||
}
|
||||
}
|
||||
|
||||
void SetRotation(int n, const CQuaternion &q) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
((KeyFrameTransCompressed*)keyFrames)[n].rotation_(q);
|
||||
} else if (type & KF_TRANS) {
|
||||
((KeyFrameTransUncompressed*)keyFrames)[n].rotation_(q);
|
||||
} else {
|
||||
((KeyFrame*)keyFrames)[n].rotation_(q);
|
||||
}
|
||||
}
|
||||
|
||||
CQuaternion GetRotation(int n) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
return ((KeyFrameTransCompressed*)keyFrames)[n].rotation_();
|
||||
} else if (type & KF_TRANS) {
|
||||
return ((KeyFrameTransUncompressed*)keyFrames)[n].rotation_();
|
||||
} else {
|
||||
return ((KeyFrame*)keyFrames)[n].rotation_();
|
||||
}
|
||||
}
|
||||
|
||||
void SetDeltaTime(int n, float t) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
((KeyFrameTransCompressed*)keyFrames)[n].deltaTime_(t);
|
||||
} else if (type & KF_TRANS) {
|
||||
((KeyFrameTransUncompressed*)keyFrames)[n].deltaTime_(t);
|
||||
} else {
|
||||
((KeyFrame*)keyFrames)[n].deltaTime_(t);
|
||||
}
|
||||
}
|
||||
|
||||
float GetDeltaTime(int n) {
|
||||
if (type & KF_COMPRESSED) {
|
||||
return ((KeyFrameTransCompressed*)keyFrames)[n].deltaTime_();
|
||||
} else if (type & KF_TRANS) {
|
||||
return ((KeyFrameTransUncompressed*)keyFrames)[n].deltaTime_();
|
||||
} else {
|
||||
return ((KeyFrame*)keyFrames)[n].deltaTime_();
|
||||
}
|
||||
}
|
||||
|
||||
bool HasTranslation(void) { return !!(type & KF_TRANS); }
|
||||
bool MoveMemory(void);
|
||||
|
|
|
@ -1244,34 +1244,33 @@ CAnimManager::LoadAnimFile(RwStream *stream, bool compress, char (*uncompressedA
|
|||
char ident[4];
|
||||
uint32 size;
|
||||
};
|
||||
IfpHeader anpk, info, name, dgan, cpan, anim;
|
||||
IfpHeader anpv;
|
||||
char buf[256];
|
||||
int j, k, l;
|
||||
float *fbuf = (float*)buf;
|
||||
|
||||
RwStreamRead(stream, &anpv, sizeof(IfpHeader));
|
||||
assert(memcmp(anpv.ident, "ANPV", 4) == 0);
|
||||
|
||||
// block name
|
||||
RwStreamRead(stream, &anpk, sizeof(IfpHeader));
|
||||
ROUNDSIZE(anpk.size);
|
||||
RwStreamRead(stream, &info, sizeof(IfpHeader));
|
||||
ROUNDSIZE(info.size);
|
||||
RwStreamRead(stream, buf, info.size);
|
||||
CAnimBlock *animBlock = GetAnimationBlock(buf+4);
|
||||
RwStreamRead(stream, buf, anpv.size);
|
||||
int32_t numAnims;
|
||||
RwStreamRead(stream, &numAnims, sizeof(numAnims));
|
||||
CAnimBlock *animBlock = GetAnimationBlock(buf);
|
||||
if(animBlock){
|
||||
if(animBlock->numAnims == 0){
|
||||
animBlock->numAnims = *(int*)buf;
|
||||
animBlock->numAnims = numAnims;
|
||||
animBlock->firstIndex = ms_numAnimations;
|
||||
}
|
||||
}else{
|
||||
animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++];
|
||||
strncpy(animBlock->name, buf+4, MAX_ANIMBLOCK_NAME);
|
||||
animBlock->numAnims = *(int*)buf;
|
||||
strncpy(animBlock->name, buf, MAX_ANIMBLOCK_NAME);
|
||||
animBlock->numAnims = numAnims;
|
||||
animBlock->firstIndex = ms_numAnimations;
|
||||
}
|
||||
|
||||
debug("Loading ANIMS %s\n", animBlock->name);
|
||||
|
||||
bool stub_out = strcmp(animBlock->name, "law_1b") == 0;
|
||||
|
||||
animBlock->isLoaded = true;
|
||||
|
||||
int animIndex = animBlock->firstIndex;
|
||||
|
@ -1279,116 +1278,48 @@ CAnimManager::LoadAnimFile(RwStream *stream, bool compress, char (*uncompressedA
|
|||
assert(animIndex < ARRAY_SIZE(ms_aAnimations));
|
||||
CAnimBlendHierarchy *hier = &ms_aAnimations[animIndex++];
|
||||
|
||||
int32_t animNameLength;
|
||||
// animation name
|
||||
RwStreamRead(stream, &name, sizeof(IfpHeader));
|
||||
ROUNDSIZE(name.size);
|
||||
RwStreamRead(stream, buf, name.size);
|
||||
RwStreamRead(stream, &animNameLength, sizeof(animNameLength));
|
||||
RwStreamRead(stream, buf, animNameLength);
|
||||
hier->SetName(buf);
|
||||
|
||||
bool compressHier = compress;
|
||||
|
||||
if (uncompressedAnims) {
|
||||
for (int i = 0; uncompressedAnims[i][0]; i++) {
|
||||
if (!CGeneral::faststricmp(uncompressedAnims[i], hier->name)){
|
||||
debug("Loading %s uncompressed\n", hier->name);
|
||||
compressHier = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// DG info has number of nodes/sequences
|
||||
RwStreamRead(stream, (char*)&dgan, sizeof(IfpHeader));
|
||||
ROUNDSIZE(dgan.size);
|
||||
RwStreamRead(stream, (char*)&info, sizeof(IfpHeader));
|
||||
ROUNDSIZE(info.size);
|
||||
RwStreamRead(stream, buf, info.size);
|
||||
hier->numSequences = *(int*)buf;
|
||||
int32_t numSeqs;
|
||||
RwStreamRead(stream, &numSeqs, sizeof(numSeqs));
|
||||
hier->numSequences = numSeqs;
|
||||
hier->sequences = new CAnimBlendSequence[hier->numSequences];
|
||||
|
||||
CAnimBlendSequence *seq = hier->sequences;
|
||||
for(k = 0; k < hier->numSequences; k++, seq++){
|
||||
// Each node has a name and key frames
|
||||
RwStreamRead(stream, &cpan, sizeof(IfpHeader));
|
||||
ROUNDSIZE(dgan.size);
|
||||
RwStreamRead(stream, &anim, sizeof(IfpHeader));
|
||||
ROUNDSIZE(anim.size);
|
||||
RwStreamRead(stream, buf, anim.size);
|
||||
int numFrames = *(int*)(buf+28);
|
||||
int32_t seqNameLength;
|
||||
RwStreamRead(stream, &seqNameLength, sizeof(seqNameLength));
|
||||
RwStreamRead(stream, buf, seqNameLength);
|
||||
seq->SetName(buf);
|
||||
if(anim.size == 44)
|
||||
seq->SetBoneTag(*(int*)(buf+40));
|
||||
|
||||
int32_t numFrames;
|
||||
RwStreamRead(stream, &numFrames, sizeof(numFrames));
|
||||
seq->numFrames = numFrames;
|
||||
int32_t boneTag;
|
||||
RwStreamRead(stream, &boneTag, sizeof(boneTag));
|
||||
seq->SetBoneTag(boneTag);
|
||||
|
||||
if(numFrames == 0)
|
||||
continue;
|
||||
|
||||
bool hasScale = false;
|
||||
bool hasTranslation = false;
|
||||
RwStreamRead(stream, &info, sizeof(info));
|
||||
if(strncmp(info.ident, "KRTS", 4) == 0){
|
||||
hasScale = true;
|
||||
seq->SetNumFrames(stub_out ? 1 : numFrames, true, compressHier);
|
||||
}else if(strncmp(info.ident, "KRT0", 4) == 0){
|
||||
hasTranslation = true;
|
||||
seq->SetNumFrames(stub_out ? 1 : numFrames, true, compressHier);
|
||||
}else if(strncmp(info.ident, "KR00", 4) == 0){
|
||||
seq->SetNumFrames(stub_out ? 1 : numFrames, false, compressHier);
|
||||
}
|
||||
uint32_t dataSize;
|
||||
RwStreamRead(stream, &dataSize, sizeof(dataSize));
|
||||
uint16_t flags;
|
||||
RwStreamRead(stream, &flags, sizeof(flags));
|
||||
|
||||
seq->keyFrames = RwMalloc(dataSize);
|
||||
assert(seq->keyFrames);
|
||||
RwStreamRead(stream, seq->keyFrames, dataSize - sizeof(flags));
|
||||
seq->type = flags;
|
||||
|
||||
if(strstr(seq->name, "L Toe"))
|
||||
debug("anim %s has toe keyframes\n", hier->name); // BUG: seq->name
|
||||
|
||||
float *frameTimes = (float*)RwMalloc(sizeof(float) * numFrames);
|
||||
|
||||
for(l = 0; l < numFrames; l++){
|
||||
if(hasScale){
|
||||
RwStreamRead(stream, buf, 0x2C);
|
||||
CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
|
||||
rot.Invert();
|
||||
CVector trans(fbuf[4], fbuf[5], fbuf[6]);
|
||||
if (!stub_out || l ==0) {
|
||||
seq->SetRotation(l, rot);
|
||||
seq->SetTranslation(l, trans);
|
||||
}
|
||||
// scaling ignored
|
||||
frameTimes[l] = fbuf[10]; // absolute time here
|
||||
}else if(hasTranslation){
|
||||
RwStreamRead(stream, buf, 0x20);
|
||||
CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
|
||||
rot.Invert();
|
||||
CVector trans(fbuf[4], fbuf[5], fbuf[6]);
|
||||
|
||||
if (!stub_out || l ==0) {
|
||||
seq->SetRotation(l, rot);
|
||||
seq->SetTranslation(l, trans);
|
||||
}
|
||||
frameTimes[l] = fbuf[7]; // absolute time here
|
||||
}else{
|
||||
RwStreamRead(stream, buf, 0x14);
|
||||
CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]);
|
||||
rot.Invert();
|
||||
if (!stub_out || l ==0) {
|
||||
seq->SetRotation(l, rot);
|
||||
}
|
||||
frameTimes[l] = fbuf[4]; // absolute time here
|
||||
}
|
||||
}
|
||||
|
||||
// convert absolute time to deltas
|
||||
float running_sum = 0.0f;
|
||||
for (l = 0; l < numFrames; l++) {
|
||||
auto dt = frameTimes[l] - running_sum;
|
||||
if (!stub_out || l ==0) {
|
||||
seq->SetDeltaTime(l, dt);
|
||||
assert(seq->GetDeltaTime(l) <= dt);
|
||||
running_sum += seq->GetDeltaTime(l);
|
||||
}
|
||||
// if (seq->GetDeltaTime(l) == 0.0f && dt != 0.0f) {
|
||||
// seq->SetDeltaTime(l, KF_MINDELTA);
|
||||
// }
|
||||
|
||||
// assert(seq->GetDeltaTime(l) != 0.0f || dt == 0.0f);
|
||||
}
|
||||
RwFree(frameTimes);
|
||||
}
|
||||
|
||||
hier->RemoveQuaternionFlips();
|
||||
hier->CalcTotalTime();
|
||||
}
|
||||
if(animIndex > ms_numAnimations)
|
||||
|
|
|
@ -289,7 +289,7 @@ CCutsceneMgr::SetupCutsceneToStart(void)
|
|||
if (ms_pCutsceneObjects[i]->m_pAttachTo != nil) {
|
||||
pAnimBlendAssoc->flags &= (~ASSOC_HAS_TRANSLATION);
|
||||
} else {
|
||||
ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + pAnimBlendAssoc->hierarchy->sequences[0].GetTranslation(0));
|
||||
ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + pAnimBlendAssoc->hierarchy->sequences[0].GetStartTranslation());
|
||||
}
|
||||
pAnimBlendAssoc->SetRun();
|
||||
} else {
|
||||
|
|
|
@ -4894,7 +4894,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedDraggedOutCarAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedDraggedOutCarAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4904,7 +4904,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedCarDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedCarDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedCarDoorAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4914,7 +4914,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedCarDoorLoAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedCarDoorLoAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedCarDoorLoAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4924,7 +4924,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedQuickDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedQuickDraggedOutCarAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedQuickDraggedOutCarAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4934,7 +4934,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedVanRearDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedVanRearDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedVanRearDoorAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4944,7 +4944,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedTrainDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedTrainDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedTrainDoorAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4954,7 +4954,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedStdBikeJumpRhsAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedStdBikeJumpRhsAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedStdBikeJumpRhsAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4964,7 +4964,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedVespaBikeJumpRhsAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedVespaBikeJumpRhsAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedVespaBikeJumpRhsAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4974,7 +4974,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedHarleyBikeJumpRhsAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedHarleyBikeJumpRhsAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedHarleyBikeJumpRhsAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4984,7 +4984,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedDirtBikeJumpRhsAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedDirtBikeJumpRhsAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedDirtBikeJumpRhsAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4994,7 +4994,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void)
|
|||
if (!seq->HasTranslation())
|
||||
vecPedBikeKickAnimOffset = CVector(0.0f, 0.0f, 0.0f);
|
||||
else {
|
||||
vecPedBikeKickAnimOffset = seq->GetTranslation(seq->numFrames - 1);
|
||||
vecPedBikeKickAnimOffset = seq->GetEndTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
1027
src/tools/animtool.cpp
Normal file
1027
src/tools/animtool.cpp
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue