Skip to content

Commit

Permalink
Ocarina of Time 3D: Rewrite texture loading to remove TextureHolder a…
Browse files Browse the repository at this point in the history
…nd remove the name-based binding
  • Loading branch information
magcius committed Oct 16, 2024
1 parent 897a61e commit ca86b15
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 244 deletions.
19 changes: 11 additions & 8 deletions src/OcarinaOfTime3D/cmab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import { Color, colorFromRGBA } from "../Color.js";
import { Texture, TextureLevel, Version, calcTexMtx } from "./cmb.js";
import { decodeTexture, computeTextureByteSize, getTextureFormatFromGLFormat } from "./pica_texture.js";
import { getPointHermite } from "../Spline.js";
import { TextureMapping } from "../TextureHolder.js";
import { CtrTextureHolder } from "./render.js";
import { CmabData } from "./render.js";
import { lerp } from "../MathHelpers.js";
import { GfxTextureDimension } from "../gfx/platform/GfxPlatform.js";
import { GfxTexture, GfxTextureDimension } from "../gfx/platform/GfxPlatform.js";

// CMAB (CTR Material Animation Binary)
// Seems to be inspired by the .cmata file format. Perhaps an earlier version of NW4C used it?
Expand Down Expand Up @@ -493,13 +492,17 @@ export class ColorAnimator {
}

export class TexturePaletteAnimator {
constructor(public animationController: AnimationController, public cmab: CMAB, public animEntry: AnimationEntry) {
constructor(public animationController: AnimationController, public cmabData: CmabData, public animEntry: AnimationEntry) {
assert(animEntry.animationType === AnimationType.TexturePalette);
}

public fillTextureMapping(textureHolder: CtrTextureHolder, textureMapping: TextureMapping): void {
const animFrame = getAnimFrame(this.cmab, this.animationController.getTimeInFrames());
const textureIndex = sampleAnimationTrack(this.animEntry.tracks[0], animFrame);
textureHolder.fillTextureMapping(textureMapping, this.cmab.textures[textureIndex].name);
private getTextureIndex(): number {
const animFrame = getAnimFrame(this.cmabData.cmab, this.animationController.getTimeInFrames());
return sampleAnimationTrack(this.animEntry.tracks[0], animFrame);
}

public getTexture(): GfxTexture {
const textureIdx = this.getTextureIndex();
return this.cmabData.textureData[textureIdx].gfxTexture;
}
}
8 changes: 3 additions & 5 deletions src/OcarinaOfTime3D/cmb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ export interface Texture {
levels: TextureLevel[];
}

export function parseTexChunk(buffer: ArrayBufferSlice, texData: ArrayBufferSlice | null, cmbName: string = ''): Texture[] {
export function parseTexChunk(buffer: ArrayBufferSlice, texData: ArrayBufferSlice | null): Texture[] {
const view = buffer.createDataView();

assert(readString(buffer, 0x00, 0x04) === 'tex ');
Expand All @@ -663,9 +663,7 @@ export function parseTexChunk(buffer: ArrayBufferSlice, texData: ArrayBufferSlic
const glFormat = view.getUint32(offs + 0x0C, true);
let dataOffs = view.getUint32(offs + 0x10, true);
let dataEnd = dataOffs + size;
const texName = readString(buffer, offs + 0x14, 0x10);
// TODO(jstpierre): Maybe find another way to dedupe? Name seems inconsistent.
const name = `${cmbName}/${i}/${texName}`;
const name = readString(buffer, offs + 0x14, 0x10);
offs += 0x24;

const levels: TextureLevel[] = [];
Expand Down Expand Up @@ -722,7 +720,7 @@ export function parseTexChunk(buffer: ArrayBufferSlice, texData: ArrayBufferSlic
}

function readTexChunk(cmb: CMB, buffer: ArrayBufferSlice, texData: ArrayBufferSlice | null): void {
cmb.textures = parseTexChunk(buffer, texData, cmb.name);
cmb.textures = parseTexChunk(buffer, texData);
}

function readLutsChunk(cmb: CMB, buffer: ArrayBufferSlice): void {
Expand Down
2 changes: 1 addition & 1 deletion src/OcarinaOfTime3D/ctxb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export function parse(buffer: ArrayBufferSlice): CTXB {

const texChunkOffs = view.getUint32(0x10, true);
const texDataOffs = view.getUint32(0x14, true);
const textures = parseTexChunk(buffer.slice(texChunkOffs), buffer.slice(texDataOffs), 'ctxb');
const textures = parseTexChunk(buffer.slice(texChunkOffs), buffer.slice(texDataOffs));
return { textures };
}
53 changes: 29 additions & 24 deletions src/OcarinaOfTime3D/lm3d_scenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import * as CTXB from './ctxb.js';

import * as Viewer from '../viewer.js';

import { CmbInstance, CmbData } from './render.js';
import { CmbInstance, CmbData, CmabData } from './render.js';
import { SceneGroup } from '../viewer.js';
import { leftPad, assertExists, nArray, assert } from '../util.js';
import { leftPad, assertExists, assert } from '../util.js';
import { GfxDevice } from '../gfx/platform/GfxPlatform.js';
import { GrezzoTextureHolder, MultiCmbScene } from './scenes.js';
import { computeModelMatrixSRT, scaleMatrix } from '../MathHelpers.js';
import { MultiCmbScene } from './scenes.js';
import { computeModelMatrixSRT } from '../MathHelpers.js';
import { SceneContext } from '../SceneBase.js';
import { ZSIEnvironmentSettings } from './zsi.js';
import { colorFromRGBA } from '../Color.js';
Expand All @@ -32,7 +32,7 @@ function bcsvHashLM(str: string): number {
const r0 = ((((hash - r6) / 2) + r6) >> 24);
hash -= (r0 * 33554393);
}

return hash;
}

Expand Down Expand Up @@ -63,21 +63,19 @@ class SceneDesc implements Viewer.SceneDesc {
const path_gar = `${pathBase}/map/map${leftPad(''+this.mapNumber, 2, '0')}.gar`;
const models_path = `${pathBase}/mapmdl/map${this.mapNumber}`;

const textureHolder = new GrezzoTextureHolder();
const dataFetcher = context.dataFetcher;

const spawnVrbox = (renderer: MultiCmbScene, cache: GfxRenderCache, garName: string) => {
dataFetcher.fetchData(`${pathBase}/vrbox/${garName}`).then((garBuffer) => {

const vrGar = ZAR.parse(garBuffer);
const firstCMB = assertExists(vrGar.files.find((file) => file.name.endsWith('.cmb')));

const cmb = CMB.parse(firstCMB.buffer);
const cmbData = new CmbData(cache, cmb);
textureHolder.addCMB(device, cmb);
renderer.cmbData.push(cmbData);

const cmbRenderer = new CmbInstance(cache, textureHolder, cmbData, cmb.name);
const cmbRenderer = new CmbInstance(cache, cmbData, cmb.name);
cmbRenderer.isSkybox = true;
renderer.skyRenderers.push(cmbRenderer);

Expand All @@ -86,12 +84,13 @@ class SceneDesc implements Viewer.SceneDesc {

for (let i = 0; i < cmabFiles.length; i++) {
const cmab = CMAB.parse(CMB.Version.LuigisMansion, cmabFiles[i].buffer);
textureHolder.addTextures(device, cmab.textures);
cmbRenderer.bindCMAB(cmab);
const cmabData = new CmabData(cache, cmab);

cmbRenderer.bindCMAB(cmabData);
}

const csabFile = vrGar.files.find((file) => file.name === `${cmbBasename}.csab`);
if (csabFile){
if (csabFile !== undefined) {
cmbRenderer.bindCSAB(CSAB.parse(CMB.Version.LuigisMansion, csabFile.buffer));
}
});
Expand All @@ -113,7 +112,7 @@ class SceneDesc implements Viewer.SceneDesc {

const modelCache = new Map<string, CmbData>();

const renderer = new MultiCmbScene(device, textureHolder);
const renderer = new MultiCmbScene(device);
const cache = renderer.getRenderCache();
const promises: Promise<void>[] = [];
const envSettingsMap: Map<number, ZSIEnvironmentSettings> = new Map<number, ZSIEnvironmentSettings>();
Expand All @@ -127,7 +126,6 @@ class SceneDesc implements Viewer.SceneDesc {
default: spawnVrbox(renderer, cache, "vrball_m.gar"); break;
}


//TODO(M-1): This isn't right but it works for now
for (let i = 0; i < lightInfo.records.length; i++) {
const record = lightInfo.records[i];
Expand All @@ -143,7 +141,7 @@ class SceneDesc implements Viewer.SceneDesc {
let envSettings = envSettingsMap.get(roomNum) as ZSIEnvironmentSettings;

const light = envSettings.lights[index];

const diffuseR = assertExists(getField<number>(lightInfo, record, "diffuse_x")) / 0xFF;
const diffuseG = assertExists(getField<number>(lightInfo, record, "diffuse_y")) / 0xFF;
const diffuseB = assertExists(getField<number>(lightInfo, record, "diffuse_z")) / 0xFF;
Expand Down Expand Up @@ -187,36 +185,44 @@ class SceneDesc implements Viewer.SceneDesc {
//const skyboxType = assertExists(getField<number>(roomInfo, roomInfo.records[i], "VRboxType"));
//assert(skyboxType === 0);

// TODO(jstpierre): How does the engine know which CMB file to spawn?
const firstCMB = assertExists(roomGar.files.find((file) => file.name.endsWith('.cmb')));
const cmb = CMB.parse(firstCMB.buffer);
const ctxbFiles = roomGar.files.filter((file) => file.name.endsWith('.ctxb'));

// Patch textures into in CMB file.
for (let i = 0; i < ctxbFiles.length; i++) {
const ctxb = CTXB.parse(ctxbFiles[i].buffer);
textureHolder.addCTXB(device, ctxb);
for (let j = 0; j < ctxb.textures.length; j++) {
const texture = ctxb.textures[j];

const cmbTexture = cmb.textures.find((tex) => tex.name === texture.name);
if (cmbTexture === undefined)
continue;

assert(cmbTexture.levels.length === 0);
cmbTexture.levels = texture.levels;
}
}

const cmbData = new CmbData(cache, cmb);
textureHolder.addCMB(device, cmb);
renderer.cmbData.push(cmbData);

let envSettings = envSettingsMap.get(i) as ZSIEnvironmentSettings;

if(envSettings === undefined){
envSettings = envSettingsMap.get(0) as ZSIEnvironmentSettings;
}
const cmbRenderer = new CmbInstance(cache, textureHolder, cmbData, cmb.name);

const cmbRenderer = new CmbInstance(cache, cmbData, cmb.name);
cmbRenderer.setEnvironmentSettings(envSettings);
renderer.cmbRenderers.push(cmbRenderer);

const cmbBasename = firstCMB.name.split('.')[0];
const cmabFile = roomGar.files.find((file) => file.name === `${cmbBasename}.cmab`);
if (cmabFile) {
const cmab = CMAB.parse(CMB.Version.LuigisMansion, cmabFile.buffer);
textureHolder.addTextures(device, cmab.textures);
cmbRenderer.bindCMAB(cmab);
const cmabData = new CmabData(cache, cmab);
cmbRenderer.bindCMAB(cmabData);
}

const roomFurnitureEntries: BCSV.Bcsv = getEntriesWithField(furnitureInfo, "room_no", i);
Expand All @@ -234,12 +240,11 @@ class SceneDesc implements Viewer.SceneDesc {
if (cmbData === undefined) {
const cmb = CMB.parse(cmbFile.buffer);
cmbData = new CmbData(cache, cmb);
textureHolder.addTextures(device, cmb.textures);
renderer.cmbData.push(cmbData);
modelCache.set(cmbFilename, cmbData);
}

const cmbRenderer = new CmbInstance(cache, textureHolder, cmbData, cmb.name);
const cmbRenderer = new CmbInstance(cache, cmbData, cmb.name);
cmbRenderer.setEnvironmentSettings(envSettings);

const rotationX = assertExists(getField<number>(roomFurnitureEntries, record, "dir_x")) / 180 * Math.PI;
Expand Down
41 changes: 20 additions & 21 deletions src/OcarinaOfTime3D/mm3d_scenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import * as ZSI from './zsi.js';

import * as Viewer from '../viewer.js';

import { RoomRenderer, CtrTextureHolder, CmbInstance } from './render.js';
import { RoomRenderer, CmbInstance, CmabData } from './render.js';
import { SceneGroup } from '../viewer.js';
import { assert, assertExists, hexzero } from '../util.js';
import { GfxDevice } from '../gfx/platform/GfxPlatform.js';
import { OoT3DRenderer, ModelCache } from './oot3d_scenes.js';
import { TransparentBlack, colorFromRGBA, colorNewFromRGBA } from '../Color.js';
import { OoT3DRenderer, ModelCache, DataHolder } from './oot3d_scenes.js';
import { TransparentBlack } from '../Color.js';
import { mat4 } from 'gl-matrix';
import AnimationController from '../AnimationController.js';
import { SceneContext } from '../SceneBase.js';
Expand Down Expand Up @@ -601,16 +601,14 @@ class SceneDesc implements Viewer.SceneDesc {
this.id = id;
}

private async spawnActorForRoom(renderer: OoT3DRenderer, roomRenderer: RoomRenderer, actor: ZSI.Actor, j: number): Promise<void> {
const device = renderer.getRenderCache().device;

private async spawnActorForRoom(renderer: OoT3DRenderer, dataHolder: DataHolder, roomRenderer: RoomRenderer, actor: ZSI.Actor, j: number): Promise<void> {
function fetchArchive(archivePath: string): Promise<ZAR.ZAR> {
return renderer.modelCache.fetchArchive(`${pathBase}/actors/${archivePath}`);
return dataHolder.modelCache.fetchArchive(`${pathBase}/actors/${archivePath}`);
}

function buildModel(gar: ZAR.ZAR, modelPath: string, scale: number = 0.01): CmbInstance {
const cmbData = renderer.modelCache.getModel(renderer, gar, modelPath);
const cmbRenderer = new CmbInstance(renderer.getRenderCache(), renderer.textureHolder, cmbData);
const cmbData = dataHolder.modelCache.getModel(renderer, gar, modelPath);
const cmbRenderer = new CmbInstance(renderer.getRenderCache(), cmbData);
cmbRenderer.animationController.fps = 20;
cmbRenderer.setConstantColor(1, TransparentBlack);
cmbRenderer.name = `${hexzero(actor.actorId, 4)} / ${hexzero(actor.variable, 4)} / ${modelPath}`;
Expand All @@ -625,8 +623,9 @@ class SceneDesc implements Viewer.SceneDesc {

function parseCMAB(gar: ZAR.ZAR, filename: string) {
const cmab = CMAB.parse(CMB.Version.Majora, assertExists(ZAR.findFileData(gar, filename)));
renderer.textureHolder.addTextures(device, cmab.textures);
return cmab;
const cmabData = new CmabData(renderer.getRenderCache(), cmab);
dataHolder.cmabData.push(cmabData);
return cmabData;
}

function animFrame(frame: number): AnimationController {
Expand Down Expand Up @@ -882,11 +881,10 @@ class SceneDesc implements Viewer.SceneDesc {
dataFetcher.fetchData(path_info_zsi),
]);

const textureHolder = new CtrTextureHolder();
context.destroyablePool.push(textureHolder);

const modelCache = new ModelCache(device, dataFetcher);
context.destroyablePool.push(modelCache);

const dataHolder = new DataHolder(modelCache);
context.destroyablePool.push(dataHolder);

const gar = ZAR.parse(maybeDecompress(zarBuffer));

Expand All @@ -897,7 +895,7 @@ class SceneDesc implements Viewer.SceneDesc {
zsi.environmentSettings.push(new ZSIEnvironmentSettings());
}

const renderer = new OoT3DRenderer(device, textureHolder, zsi, modelCache);
const renderer = new OoT3DRenderer(device, zsi);
const cache = renderer.getRenderCache();
context.destroyablePool.push(renderer);

Expand All @@ -917,21 +915,22 @@ class SceneDesc implements Viewer.SceneDesc {

assert(roomSetup.mesh !== null);
const filename = roomZSINames[i].split('/').pop()!;
const roomRenderer = new RoomRenderer(cache, ZSI.Version.Majora, textureHolder, roomSetup.mesh, filename);
const roomRenderer = new RoomRenderer(cache, ZSI.Version.Majora, roomSetup.mesh, filename);
roomRenderer.roomSetups = roomSetups;
if (gar !== null) {
const cmabFile = gar.files.find((file) => file.name.startsWith(`ROOM${i}/`) && file.name.endsWith('.cmab') && !file.name.endsWith('_t.cmab'));
if (cmabFile) {
const cmab = CMAB.parse(CMB.Version.Majora, cmabFile.buffer);
textureHolder.addTextures(device, cmab.textures);
roomRenderer.bindCMAB(cmab);
const cmab = CMAB.parse(CMB.Version.Ocarina, cmabFile.buffer);
const cmabData = new CmabData(cache, cmab);
dataHolder.cmabData.push(cmabData);
roomRenderer.bindCMAB(cmabData);
}
}

renderer.roomRenderers.push(roomRenderer);

for (let j = 0; j < roomSetup.actors.length; j++)
this.spawnActorForRoom(renderer, roomRenderer, roomSetup.actors[j], j);
this.spawnActorForRoom(renderer, dataHolder, roomRenderer, roomSetup.actors[j], j);

if (this.disabledRooms.includes(i))
roomRenderer.setVisible(false);
Expand Down
Loading

0 comments on commit ca86b15

Please sign in to comment.