Skip to content

Commit

Permalink
Wind Waker: Particle system improvements (#739)
Browse files Browse the repository at this point in the history
* Wind Waker: Split dPa_control_c init into common and scene

* Wind Waker: Replace Particle.ts EffectDrawGroup with ParticleGroup

* JPA: Fix misplaced `workData.volumeEmitCount = this.emitCount`

Now `workData.volumeEmitCount` is always valid when it should be

* Wind Waker: Introducing "simple" emitters

The game uses these to consolidate commonly used emitters, such as flames.

This fixes a big long-standing TODO in d_particle.ts, but the main goal is to fix the HACK related to indirect/projection particles

* Wind Waker: "Simple" particles can now access the framebuffer

This is done almost the same as the game, but we modify the TEV settings at emitter creation time rather than at draw time (because generating the material is expensive)

* Wind Waker: Fix common particles spawning once on scene creation

* Formatting improvements

* Wind Waker: d_a_ep now uses simple particles

* Wind Waker: Small note regarding simple particles ignoring emitter translation

* Wind Waker: Remove indirect particle hack

Particles now keep the groupID that was assigned to them at creation.  Projection particles are determined at simple emitter creation time, or if ParticleGroup.Projection is specified manually.

* Wind Waker: Simple particle emitters are cleared every frame

Actors such as d_a_ep must call `setSimple()` each frame

* Wind Waker: Fixup d_a_ep (torch) actor. The default state now matches the game.

* Wind Waker: Allow type 2 d_a_ep to render flames

These should be handled by the d_a_lamp actor (e.g. in Beedle's ship shop), but that is not yet implemented. For now, let's handle them in d_a_ep

* Wind Waker: Explicit return types for d_particle functions

* Wind Waker: Fixed TODO - d_a_obj_flame now uses simple particle emitters

* Wind Waker: Remove old `patchResData` indirect particles hack

This TEV patching is now done in a more proper place, inside the the simple particle callback

* Wind Waker: Add frustum culling for simple emitters

* Wind Waker: Don't distance cull 2D/UI particle emitters

* J2D: Add `J2DGrafContext.getFrustumForView()`

Given a view matrix, return a Frustum which can be used for culling. In the original game, frustums are generated from proj matrices alone. Points were transformed by the view matrix before being tested against the frustum. But noclip typically expects viewProj frustums.

* Wind Waker: Draw 2D/UI particle groups with ortho view/proj matrices

* Wind Waker: Implement first 2D emitter for d_a_title

* Wind Waker: Add sparkle emitter to d_a_title

* Wind Waker: Add new drawlists for fore/background 2D particles

This aligns with the game and fixes sorting issues with the title screen emitters

* Wind Waker: Don't frustum cull 2D particles

* Wind Waker: Use computeModelMatrixT to avoid list creation
  • Loading branch information
themikelester authored Jan 4, 2025
1 parent b18dc38 commit ab10965
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 103 deletions.
3 changes: 2 additions & 1 deletion src/Common/JSYSTEM/J2Dv1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { BTIData } from "./JUTTexture.js";
import { GXMaterialBuilder } from "../../gx/GXMaterialBuilder.js";
import { mat4, vec2, vec4 } from "gl-matrix";
import { GfxRenderCache } from "../../gfx/render/GfxRenderCache.js";
import { Frustum } from "../../Geometry.js";

const materialParams = new MaterialParams();
const drawParams = new DrawParams();
Expand Down Expand Up @@ -83,7 +84,6 @@ export class J2DGrafContext {
public sceneParams = new SceneParams();
public viewport = vec4.create();
public ortho = vec4.create();

public near: number;
public far: number;

Expand Down Expand Up @@ -118,6 +118,7 @@ export class J2DGrafContext {

projectionMatrixForCuboid(this.sceneParams.u_Projection, left, right, bottom, top, this.near, this.far);
projectionMatrixConvertClipSpaceNearZ(this.sceneParams.u_Projection, this.clipSpaceNearZ, GfxClipSpaceNearZ.NegativeOne);

}

public setOnRenderInst(renderInst: GfxRenderInst): void {
Expand Down
5 changes: 3 additions & 2 deletions src/Common/JSYSTEM/JPA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1711,7 +1711,7 @@ export class JPABaseEmitter {
throw "whoops";
}

private createParticle(): JPABaseParticle | null {
public createParticle(): JPABaseParticle | null {
if (this.emitterManager.deadParticlePool.length === 0)
return null;

Expand All @@ -1736,7 +1736,6 @@ export class JPABaseEmitter {
this.emitCount = bem1.divNumber * bem1.divNumber * 4 + 2;
else
this.emitCount = bem1.divNumber;
workData.volumeEmitCount = this.emitCount;
workData.volumeEmitIdx = 0;
} else {
// Rate
Expand All @@ -1748,6 +1747,8 @@ export class JPABaseEmitter {
this.emitCount = 1;
}

workData.volumeEmitCount = this.emitCount;

if (!!(this.status & JPAEmitterStatus.STOP_CREATE_PARTICLE))
this.emitCount = 0;

Expand Down
42 changes: 30 additions & 12 deletions src/ZeldaWindWaker/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { J3DModelInstance } from '../Common/JSYSTEM/J3D/J3DGraphBase.js';
import * as JPA from '../Common/JSYSTEM/JPA.js';
import { BTIData } from '../Common/JSYSTEM/JUTTexture.js';
import { dfRange } from '../DebugFloaters.js';
import { range } from '../MathHelpers.js';
import { computeModelMatrixT, range } from '../MathHelpers.js';
import { SceneContext } from '../SceneBase.js';
import { TextureMapping } from '../TextureHolder.js';
import { setBackbufferDescSimple, standardFullClearRenderPassDescriptor } from '../gfx/helpers/RenderGraphHelpers.js';
Expand All @@ -35,7 +35,7 @@ import { EDemoMode, dDemo_manager_c } from './d_demo.js';
import { dDlst_list_Set, dDlst_list_c } from './d_drawlist.js';
import { dKankyo_create, dKy__RegisterConstructors, dKy_setLight, dScnKy_env_light_c } from './d_kankyo.js';
import { dKyw__RegisterConstructors } from './d_kankyo_wether.js';
import { dPa_control_c } from './d_particle.js';
import { dPa_control_c, ParticleGroup } from './d_particle.js';
import { Placename, PlacenameState, dPn__update, d_pn__RegisterConstructors } from './d_place_name.js';
import { dProcName_e } from './d_procname.js';
import { ResType, dRes_control_c } from './d_resorce.js';
Expand Down Expand Up @@ -403,18 +403,37 @@ export class WindWakerRenderer implements Viewer.SceneGfx {
{
globals.particleCtrl.calc(globals, viewerInput);

for (let group = EffectDrawGroup.Main; group <= EffectDrawGroup.Indirect; group++) {
for (let group = ParticleGroup.Normal; group <= ParticleGroup.Wind; group++) {
let texPrjMtx: mat4 | null = null;

if (group === EffectDrawGroup.Indirect) {
if (group === ParticleGroup.Projection) {
texPrjMtx = scratchMatrix;
texProjCameraSceneTex(texPrjMtx, globals.camera.clipFromViewMatrix, 1);
}

globals.particleCtrl.setDrawInfo(globals.camera.viewFromWorldMatrix, globals.camera.clipFromViewMatrix, texPrjMtx, globals.camera.frustum);
renderInstManager.setCurrentList(dlst.effect[group]);
renderInstManager.setCurrentList(dlst.effect[group == ParticleGroup.Projection ? EffectDrawGroup.Indirect : EffectDrawGroup.Main]);
globals.particleCtrl.draw(device, this.renderHelper.renderInstManager, group);
}

// From mDoGph_Painter(). Draw the 2D particle groups with different view/proj matrices.
{
const orthoCtx = this.globals.scnPlay.currentGrafPort;
const template = renderInstManager.pushTemplate();
orthoCtx.setOnRenderInst(template);

const viewMtx = scratchMatrix;
computeModelMatrixT(viewMtx, orthoCtx.aspectRatioCorrection * 320, 240, 0);
globals.particleCtrl.setDrawInfo(viewMtx, orthoCtx.sceneParams.u_Projection, null, null);

renderInstManager.setCurrentList(dlst.particle2DBack);
globals.particleCtrl.draw(device, this.renderHelper.renderInstManager, ParticleGroup.TwoDback);
globals.particleCtrl.draw(device, this.renderHelper.renderInstManager, ParticleGroup.TwoDmenuBack);

renderInstManager.setCurrentList(dlst.particle2DFore);
globals.particleCtrl.draw(device, this.renderHelper.renderInstManager, ParticleGroup.TwoDfore);
globals.particleCtrl.draw(device, this.renderHelper.renderInstManager, ParticleGroup.TwoDmenuFore);
}
}

this.renderHelper.renderInstManager.popTemplate();
Expand Down Expand Up @@ -481,9 +500,11 @@ export class WindWakerRenderer implements Viewer.SceneGfx {

this.executeList(passRenderer, dlst.effect[EffectDrawGroup.Main]);
this.executeList(passRenderer, dlst.wetherEffect);
this.executeListSet(passRenderer, dlst.ui);

this.executeList(passRenderer, dlst.particle2DBack);
this.executeListSet(passRenderer, dlst.ui);
this.executeListSet(passRenderer, dlst.ui2D);
this.executeList(passRenderer, dlst.particle2DFore);
});
});

Expand Down Expand Up @@ -823,12 +844,9 @@ class SceneDesc {

renderer.extraTextures = new ZWWExtraTextures(device, ZAtoon, ZBtoonEX);

const jpac: JPA.JPAC[] = [];
for (let i = 0; i < particleArchives.length; i++) {
const jpacData = modelCache.getFileData(particleArchives[i]);
jpac.push(JPA.parse(jpacData));
}
globals.particleCtrl = new dPa_control_c(renderer.renderCache, jpac);
globals.particleCtrl = new dPa_control_c(renderer.renderCache);
globals.particleCtrl.createCommon(globals, JPA.parse(modelCache.getFileData(particleArchives[0])));
globals.particleCtrl.createRoomScene(globals, JPA.parse(modelCache.getFileData(particleArchives[1])));

// dStage_Create
dKankyo_create(globals);
Expand Down
Loading

0 comments on commit ab10965

Please sign in to comment.