Game-specific .xsys4rc files are loaded from the game directory, so
relative paths in that file are most useful when interpreted from the
same directory. This allows patches that ship external fonts to
reference them with relative paths.
In both TapirEngine and SealEngine an instance is outlined only when the
global edge draw option and its per-instance edge flag are both on. The
per-instance flag is exposed via Set/GetInstanceDrawParam, whose edge
index differs by engine (Tapir 0, Seal 2).
For the global option the engine only distinguishes on/off, so drop the
RE_draw_edge_mode enum.
In SealEngine the outline length and color are plugin-wide values set
via SetEdgeLength/SetEdgeColor, not the per-mesh .opr parameters used by
TapirEngine.
Only resolve the method when the function's signature matches the
delegate type, returning 0 otherwise.
Also, DG_SET / DG_NEW_FROM_METHOD / DG_ADD should not add function 0.
This implements the sky/middle/ground hemisphere ambient driven by
light_params[8..19].
SealEngine has no separate specular-light instance; its specular instead
shares the hemisphere light vector.
SealEngine manages a 41-float light parameter set (serialized as .lit
file) for tone mapping, hemisphere lighting, and light scattering. This
adds the storage and HLL plumbing, without wiring values into shaders.
- A DIRECTIONAL_LIGHT instance owns a parameter set, filled by loading a
.lit file (RE_instance_load).
- SealEngine.StoreInstanceLightParam() reflects that presets into the
plugin's master buffer, which is used by the renderer.
- SealEngine.LoadInstanceLightParam() copies the master back into an
instance, and Reset()/Set()/Get() operate directly on the master.
Some games (e.g. Rance 01, Rance 9) only export GetFlag and SetFlag, so
Init() is never called. Initialize from _PreLink with a default save
file name in that case.
In TapirEngine, CalcIntersectEyeVec() only worked on models with a
"collision" mesh. In SealEngine, a raycast collider is built from the
union of meshes flagged HeightDetection=true in .opr.
Also return the nearest intersection instead of the first.
SetInstanceDiffuse has two roles depending on the instance type: for
directional lights it is the light color, and for meshes it is a
multiplier on the mesh color. xsystem4 honored only the former.
Treat inst->diffuse as color multiplier, via the diffuse_mod uniform.
This adds a helper that resets every per-draw uniform to a neutral
default and call it at the start of the billboard, .pae and .3de paths.
This fixes uv_scroll and use_blend_texture leaks from a previously
rendered model.
KeepPreviousView and its mode are purely a rendering performance knob
(incremental rendering optimization). Toggling the mode never affects
rendering correctness, so leaving these unimplemented is harmless and
warning about them is just noise.
PE_Reset() zeroed the controller stack, leaving it with no default
controller. Since PE_Reset() does not clear parts_engine_initialized,
the subsequent PE_Init() returns early so the stack stays empty.
Reset the stack with ctrl_stack_init() instead, which re-creates the
default controller. ctrl_stack_fini() is now unused and removed.
Save a reduced copy of the main surface to a file. The image is written
as QNT under the save directory.
To avoid aliasing at large reductions, the surface is downscaled by
repeatedly halving before the final stretch.
At spawn, init_particle() samples its fixed properties from a
deterministic PRNG into s3de_particle, so playback is reproducible.
Each frame, update_object_states() interpolates the emitter's animated
properties into s3de_object_state.
To draw, render_s3de_effect() combines the two: for each live particle
s3de_billboard_world_transform() or s3de_mesh_world_transform() derives
a world matrix from the lifetime state computed by eval_particle(),
together with a posture basis that orients the quad or mesh. Objects are
drawn sorted by distance from the camera.
.3de is the particle/effect format used by SealEngine. Like the
ReignEngine's .pae, it is a text format describing an effect as a set of
emitters, but the two differ enough in design that .3de lives in its own
file (s3de.c) rather than being folded into particle.c.
This commit lays the groundwork:
- Parse .3de text.
- Integrate effect loading, playback, and caching into the existing
infrastructure.
- Apply the camera object's per-frame override to RE_camera.
- Play the companion sound asset.
Billboard and polygon-object rendering will be added separately.
The original {Reign,Tapir,Seal}Engine apply motion bones to model bones
purely by array position, never consulting bone name or bone_id.
This fixes bone animation of "Shizuka & Nagi" model in Rance 9.
After loading a save, the game script may give a negative passed_time
because it computes the delta time using an absolute system.GetTime()
value stored in the save data.
This fixes hang on load in Rance 9.
This also removes the equivalent workaround in parts_render_update for
Rance 01, since the new clamp covers that case as well.
.mpr is an optional sidecar to .mot used by SealEngine, replacing .txa
used in ReignEngine / TapirEngine. While .mot drives bone poses, .mpr
drives material properties on a per-frame timeline.
Supported keys, all keyed by frame number:
Object{MulAlpha,MulDiffuse,AddAmbient} : applied to the whole model
Mesh{MulAlpha,MulDiffuse,AddAmbient} : applied per named mesh
MeshTextureAnime : swap diffuse texture index
Colors and alpha use linear interpolation between keys. Out-of-range
frames hold the nearest endpoint.
Mesh* tracks act as per-mesh tints and do not leak to other meshes
that happen to share a material. MeshTextureAnime is the exception:
it rewrites the shared material's diffuse slot, so all meshes using
that material animate in sync (e.g. in Rance 9, hanny_black_move.mpr
lists only hibana_R but hibana_L animates together).
The camera view direction was computed incorrectly in two ways:
1. glm_euler (XYZ order) applies pitch around world X. The original
engine uses YXZ where pitch is around the camera's local X after yaw.
2. SetCameraAngle negated the input yaw.
This implements rendering of FLAT_LIB_EMITTER libraries: timed spawning,
per-particle motion, fade, and 3-axis rotation.
For each visible emitter, render_flat_emitter() builds an emitter base
matrix from the ancestor key stack so particles follow their parent
timelines. parent_key_mode chooses between filtering ancestor properties
via the emitter's inherit_* flags (SELECTIVE) and using ancestor poses
wholesale (ANCESTOR_CURRENT / ANCESTOR_AT_BIRTH). For each birth frame
in the lifetime window, parts_flat_foreach_emitter_particle() reseeds
the RNG from (rand_seed, birth_frame), making particles deterministic
across re-renders, and yields each particle's pose, which is composed
with the emitter transform and submitted as a quad through the standard
parts texture pipeline.
3DLayer and Movie parts use a SACT sprite as their render target (for
SealEngine output or movie frames). That sprite was registered into
the scene at z=0, so the content was drawn at z=0 instead of (or in
addition to) the parts engine's render path at the parts' configured Z.
Hide the bound SACT sprite from the scene so the parts engine is the
sole renderer of its texture, and add a parts_render case for 3DLayer
that composites the bound sprite's texture through parts_render_cg.
The original engine implements proper perspective projection, but we
use simpler orthographic foreshortening. In Rance 9, the visual
difference is barely noticeable.
area_x/area_y select a sub-rectangle of the texture, but should not
shift the on-screen position. Translate the quad by (-area_x, -area_y)
to cancel the offset the sub-rect's top-left would otherwise introduce.
A parent key's draw_filter had no effect on CG/STOP_MOTION leaves
inside descendant TIMELINE libraries. Resolve the effective filter
at each key (own if non-NORMAL, otherwise inherited) and thread it
through the render chain.
FLAT_LIB_STOP_MOTION represents a sprite animation as a sequence of CG
frames. A stop_motion library specifies a base CG file (e.g. "foo.png");
at load time, parts_flat_load() discovers numbered frame variants
("foo_01.png", "foo_02.png", ...) and builds a frame list.
At render time, the active frame is resolved from the local timeline
clock using three parameters:
- span: number of timeline ticks each frame is displayed
- loop_type: 0 = play once and stop at last frame,
1 = loop forward,
2 = ping-pong (forward then backward)
- local: the timeline's current local time
The multiply and screen blend functions used in render_flat_item
ignored the source alpha, so transparent pixels still modulated the
destination color instead of leaving it untouched.
Add a draw_filter uniform to the parts fragment shader and have it
output an alpha-baked source color, so that the framebuffer result
after the GL blend equation interpolates correctly in alpha:
- multiply: shader outputs src = mix(vec3(1.0), mod_color, alpha)
blend (GL_DST_COLOR, GL_ZERO) gives src*dst,
i.e. mix(dst, mod_color*dst, alpha)
- screen: shader outputs src = mod_color * alpha
blend (GL_ONE, GL_ONE_MINUS_SRC_COLOR) gives src + (1-src)*dst,
i.e. mod_color*alpha + (1 - mod_color*alpha)*dst
In both cases alpha=0 leaves the destination untouched, alpha=1
gives the full multiply/screen result.
Also switch the surface-area mask from multiplication to discard,
so pixels outside the rect leave the destination intact under
multiply blending (where multiplying src by 0 would otherwise
zero out dst).
This adds a key handler facility to input.c so that the InputString
can intercept the Backspace key while the IME is open, and remove the
last character from the result string. Backspace is ignored while the
IME has composing text, so it does not interfere with IME editing.
The newer version (from Boku Dake no Hokenshitsu onwards) of
InputString.GetResultString() does not clear its internal string.
This fixes the issue in Rance 02 where the text always remained empty
when editing save comments.
This fixes heap use-after-free in the music mode in Rance 9.
Hold a reference to the struct page (`this` pointer) for the duration
of a method call. Without this, if the sole external reference to an
object is dropped during one of its method calls (e.g. an event handler
that destroys its own parent), the object and its members are freed
immediately, causing use-after-free when the VM tries to access them
afterward.
This matches the behavior of the original VM, which was introduced in
Rance9.exe and later titles.
The fix is gated on AIN v6.1+ (AIN v6 with a MSG1 section) to match
the original. Applying it unconditionally would change the heap refcount
state visible to resume saves, potentially breaking existing saves for
older games.
line_space was stored but never used during rendering or size
calculation. Apply it as inter-line spacing in both the size calculation
and the render path. Also initialize empty lines with the current text
style height so that newline-only lines occupy the correct space.
Change line_space from unsigned to int because negative values can be
set.
This fixes the rendering of the sample message in the "Message Color"
configuration of Rance 9.
PE_SetPartsFont* and PE_SetText*Space functions only modified the text
style struct without re-rendering existing character textures. Since
character glyphs are baked into textures at append time, subsequent
style changes had no visible effect.