You've already forked noclip.website
mirror of
https://github.com/encounter/noclip.website.git
synced 2026-03-30 11:31:37 -07:00
GrandTheftAuto3: water and sky (#148)
* GrandTheftAuto3: water * GrandTheftAuto3: ocean plane * GrandTheftAuto3: sky rendering * GrandTheftAuto3: fetch IMG for complete map
This commit is contained in:
committed by
Jasper St. Pierre
parent
6ee20affdf
commit
4ebdc794ad
+1
-1
@@ -19,7 +19,7 @@
|
||||
"@sentry/browser": "^5.1.1",
|
||||
"crc-32": "^1.2.0",
|
||||
"gl-matrix": "^3.0.0",
|
||||
"librw": "^0.1.0",
|
||||
"librw": "^0.2.0",
|
||||
"pako": "^1.0.7"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -29,7 +29,7 @@ export enum ObjectFlags {
|
||||
}
|
||||
|
||||
export interface ObjectDefinition {
|
||||
id: number;
|
||||
id?: number;
|
||||
modelName: string;
|
||||
txdName: string;
|
||||
drawDistance: number;
|
||||
@@ -37,6 +37,7 @@ export interface ObjectDefinition {
|
||||
tobj: boolean;
|
||||
timeOn?: number;
|
||||
timeOff?: number;
|
||||
dynamic: boolean;
|
||||
}
|
||||
|
||||
function parseObjectDefinition(row: string[], tobj: boolean): ObjectDefinition {
|
||||
@@ -46,7 +47,8 @@ function parseObjectDefinition(row: string[], tobj: boolean): ObjectDefinition {
|
||||
txdName: row[2],
|
||||
drawDistance: Number((row.length > 5) ? row[4] : row[3]),
|
||||
flags: Number(tobj ? row[row.length - 3] : row[row.length - 1]),
|
||||
tobj
|
||||
tobj,
|
||||
dynamic: false
|
||||
};
|
||||
if (tobj) {
|
||||
def.timeOn = Number(row[row.length - 2]);
|
||||
@@ -70,7 +72,7 @@ export function parseItemDefinition(text: string): ItemDefinition {
|
||||
}
|
||||
|
||||
export interface ItemInstance {
|
||||
id: number;
|
||||
id?: number;
|
||||
modelName: string;
|
||||
translation: vec3;
|
||||
scale: vec3;
|
||||
|
||||
@@ -1,24 +1,40 @@
|
||||
|
||||
precision mediump float; precision lowp sampler2DArray;
|
||||
|
||||
// Expected to be constant across the entire scene.
|
||||
layout(row_major, std140) uniform ub_SceneParams {
|
||||
Mat4x4 u_Projection;
|
||||
vec4 u_AmbientColor;
|
||||
};
|
||||
|
||||
layout(row_major, std140) uniform ub_MeshFragParams {
|
||||
Mat4x3 u_ViewMatrix;
|
||||
vec4 u_AmbientColor;
|
||||
#ifdef SKY
|
||||
Mat4x3 u_WorldMatrix;
|
||||
vec4 u_Frustum;
|
||||
vec4 u_SkyTopColor;
|
||||
vec4 u_SkyBotColor;
|
||||
#else
|
||||
float alphaThreshold;
|
||||
#endif
|
||||
};
|
||||
|
||||
uniform sampler2DArray u_Texture;
|
||||
|
||||
#ifdef SKY
|
||||
varying vec3 v_Position;
|
||||
#else
|
||||
varying vec4 v_Color;
|
||||
varying vec3 v_TexCoord;
|
||||
#endif
|
||||
|
||||
#ifdef VERT
|
||||
layout(location = 0) in vec3 a_Position;
|
||||
#ifdef SKY
|
||||
void main() {
|
||||
gl_Position = vec4(a_Position, 1.0);
|
||||
v_Position = a_Position;
|
||||
}
|
||||
#else
|
||||
layout(location = 1) in vec4 a_Color;
|
||||
layout(location = 2) in vec3 a_TexCoord;
|
||||
|
||||
@@ -28,8 +44,29 @@ void main() {
|
||||
v_TexCoord = a_TexCoord;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef FRAG
|
||||
#ifdef SKY
|
||||
void main() {
|
||||
vec3 nearPlane = v_Position * u_Frustum.xyz;
|
||||
vec3 cameraRay = Mul(u_WorldMatrix, vec4(nearPlane, 0.0));
|
||||
vec3 cameraPos = Mul(u_WorldMatrix, vec4(vec3(0.0), 1.0));
|
||||
float t = -cameraPos.y / cameraRay.y;
|
||||
vec3 oceanPlane = cameraPos + t * cameraRay;
|
||||
|
||||
if (t > 0.0 && (abs(oceanPlane.z) > 2000.0 || abs(oceanPlane.x) > 2000.0)) {
|
||||
vec2 uv = fract(oceanPlane.zx / 32.0);
|
||||
vec4 t_Color = vec4(0,0,0,1);
|
||||
t_Color.rgb += u_AmbientColor.rgb;
|
||||
t_Color *= texture(u_Texture, vec3(uv, 0));
|
||||
gl_FragColor = t_Color;
|
||||
} else {
|
||||
float elevation = atan(cameraRay.y, length(cameraRay.zx)) * 180.0 / radians(180.0);
|
||||
gl_FragColor = mix(u_SkyBotColor, u_SkyTopColor, clamp(abs(elevation / 45.0), 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
#else
|
||||
void main() {
|
||||
vec4 t_Color = v_Color;
|
||||
t_Color.rgb += u_AmbientColor.rgb;
|
||||
@@ -39,3 +76,4 @@ void main() {
|
||||
gl_FragColor = t_Color;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
+155
-85
@@ -14,11 +14,11 @@ import { mat4, quat, vec3, vec2 } from "gl-matrix";
|
||||
import { computeViewSpaceDepthFromWorldSpaceAABB } from "../Camera";
|
||||
import { GfxRenderHelper } from "../gfx/render/GfxRenderGraph";
|
||||
import { assert } from "../util";
|
||||
import { BasicRenderTarget, makeClearRenderPassDescriptor } from "../gfx/helpers/RenderTargetHelpers";
|
||||
import { GfxRenderInstManager, GfxRendererLayer, makeSortKey, setSortKeyDepth } from "../gfx/render/GfxRenderer";
|
||||
import { BasicRenderTarget, standardFullClearRenderPassDescriptor } from "../gfx/helpers/RenderTargetHelpers";
|
||||
import { GfxRenderInstManager, GfxRendererLayer, makeSortKey, setSortKeyDepth, GfxRenderInst } from "../gfx/render/GfxRenderer";
|
||||
import { ItemInstance, ObjectDefinition } from "./item";
|
||||
import { colorNew, White, colorNewCopy, colorLerp, colorMult } from "../Color";
|
||||
import { ColorSet } from "./time";
|
||||
import { colorNew, White, colorNewCopy, colorMult, Color } from "../Color";
|
||||
import { ColorSet, emptyColorSet, lerpColorSet } from "./time";
|
||||
import { AABB } from "../Geometry";
|
||||
|
||||
const TIME_FACTOR = 2500; // one day cycle per minute
|
||||
@@ -60,7 +60,7 @@ function halve(pixels: Uint8Array, width: number, height: number): Uint8Array {
|
||||
return halved;
|
||||
}
|
||||
|
||||
export class TextureAtlas extends TextureMapping {
|
||||
export class TextureArray extends TextureMapping {
|
||||
public subimages = new Map<string, number>();
|
||||
|
||||
constructor(device: GfxDevice, textures: Texture[]) {
|
||||
@@ -145,9 +145,105 @@ class GTA3Program extends DeviceProgram {
|
||||
public both = GTA3Program.program;
|
||||
}
|
||||
|
||||
const program = new GTA3Program();
|
||||
const mainProgram = new GTA3Program();
|
||||
const skyProgram = new GTA3Program();
|
||||
skyProgram.defines.set('SKY', '1');
|
||||
|
||||
class MeshFragData {
|
||||
class Renderer {
|
||||
protected vertexBuffer: GfxBuffer;
|
||||
protected indexBuffer: GfxBuffer;
|
||||
protected inputLayout: GfxInputLayout;
|
||||
protected inputState: GfxInputState;
|
||||
protected megaStateFlags: Partial<GfxMegaStateDescriptor>;
|
||||
protected gfxProgram?: GfxProgram;
|
||||
|
||||
protected indices: number;
|
||||
|
||||
constructor(protected program: DeviceProgram, protected atlas?: TextureArray) {}
|
||||
|
||||
public prepareToRender(device: GfxDevice, renderInstManager: GfxRenderInstManager, viewerInput: Viewer.ViewerRenderInput, colorSet: ColorSet): GfxRenderInst | undefined {
|
||||
const renderInst = renderInstManager.pushRenderInst();
|
||||
renderInst.setInputLayoutAndState(this.inputLayout, this.inputState);
|
||||
renderInst.drawIndexes(this.indices);
|
||||
|
||||
if (this.gfxProgram === undefined)
|
||||
this.gfxProgram = renderInstManager.gfxRenderCache.createProgram(device, this.program);
|
||||
|
||||
renderInst.setGfxProgram(this.gfxProgram);
|
||||
renderInst.setMegaStateFlags(this.megaStateFlags);
|
||||
if (this.atlas !== undefined)
|
||||
renderInst.setSamplerBindingsFromTextureMappings([this.atlas]);
|
||||
return renderInst;
|
||||
}
|
||||
|
||||
public destroy(device: GfxDevice): void {
|
||||
device.destroyBuffer(this.indexBuffer);
|
||||
device.destroyBuffer(this.vertexBuffer);
|
||||
device.destroyInputLayout(this.inputLayout);
|
||||
device.destroyInputState(this.inputState);
|
||||
if (this.gfxProgram !== undefined)
|
||||
device.destroyProgram(this.gfxProgram);
|
||||
if (this.atlas !== undefined)
|
||||
this.atlas.destroy(device);
|
||||
}
|
||||
}
|
||||
|
||||
export class SkyRenderer extends Renderer {
|
||||
constructor(device: GfxDevice, atlas?: TextureArray) {
|
||||
super(skyProgram, atlas);
|
||||
// fullscreen quad
|
||||
const vbuf = new Float32Array([
|
||||
-1, -1, 1,
|
||||
-1, 1, 1,
|
||||
1, 1, 1,
|
||||
1, -1, 1,
|
||||
]);
|
||||
const ibuf = new Uint16Array([0,1,2,0,2,3]);
|
||||
this.vertexBuffer = makeStaticDataBuffer(device, GfxBufferUsage.VERTEX, vbuf.buffer);
|
||||
this.indexBuffer = makeStaticDataBuffer(device, GfxBufferUsage.INDEX, ibuf.buffer);
|
||||
this.indices = ibuf.length;
|
||||
const vertexAttributeDescriptors: GfxVertexAttributeDescriptor[] = [
|
||||
{ location: GTA3Program.a_Position, bufferIndex: 0, format: GfxFormat.F32_RGB, bufferByteOffset: 0 * 0x04, frequency: GfxVertexAttributeFrequency.PER_VERTEX },
|
||||
];
|
||||
this.inputLayout = device.createInputLayout({ indexBufferFormat: GfxFormat.U16_R, vertexAttributeDescriptors });
|
||||
const buffers = [{ buffer: this.vertexBuffer, byteOffset: 0, byteStride: 3 * 0x04}];
|
||||
const indexBuffer = { buffer: this.indexBuffer, byteOffset: 0, byteStride: 0 };
|
||||
this.inputState = device.createInputState(this.inputLayout, buffers, indexBuffer);
|
||||
this.megaStateFlags = { depthWrite: false };
|
||||
}
|
||||
|
||||
public prepareToRender(device: GfxDevice, renderInstManager: GfxRenderInstManager, viewerInput: Viewer.ViewerRenderInput, colorSet: ColorSet): undefined {
|
||||
if (viewerInput.camera.isOrthographic) return;
|
||||
const renderInst = super.prepareToRender(device, renderInstManager, viewerInput, colorSet)!;
|
||||
renderInst.sortKey = makeSortKey(GfxRendererLayer.BACKGROUND);
|
||||
let offs = renderInst.allocateUniformBuffer(GTA3Program.ub_MeshFragParams, 12 + 4 + 12 + 4 + 4 + 4);
|
||||
const mapped = renderInst.mapUniformBufferF32(GTA3Program.ub_MeshFragParams);
|
||||
offs += fillMatrix4x3(mapped, offs, viewerInput.camera.viewMatrix);
|
||||
mapped[offs++] = colorSet.amb.r + colorSet.dir.r;
|
||||
mapped[offs++] = colorSet.amb.g + colorSet.dir.g;
|
||||
mapped[offs++] = colorSet.amb.b + colorSet.dir.b;
|
||||
mapped[offs++] = colorSet.amb.a + colorSet.dir.a;
|
||||
offs += fillMatrix4x3(mapped, offs, viewerInput.camera.worldMatrix);
|
||||
mapped[offs++] = viewerInput.camera.frustum.right;
|
||||
mapped[offs++] = viewerInput.camera.frustum.top;
|
||||
mapped[offs++] = viewerInput.camera.frustum.near;
|
||||
mapped[offs++] = viewerInput.camera.frustum.far;
|
||||
offs += fillColor(mapped, offs, colorSet.skyTop);
|
||||
offs += fillColor(mapped, offs, colorSet.skyBot);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export interface MeshFragData {
|
||||
indices: Uint16Array;
|
||||
vertices: number;
|
||||
texName?: string;
|
||||
position(vertex: number): vec3;
|
||||
color(vertex: number): Color;
|
||||
texCoord(vertex: number): vec2;
|
||||
}
|
||||
|
||||
class RWMeshFragData implements MeshFragData {
|
||||
public indices: Uint16Array;
|
||||
public texName?: string;
|
||||
|
||||
@@ -171,7 +267,7 @@ class MeshFragData {
|
||||
mesh.indices!.map(index => this.indexMap.indexOf(index))));
|
||||
}
|
||||
|
||||
public vertices() {
|
||||
public get vertices() {
|
||||
return this.indexMap.length;
|
||||
}
|
||||
|
||||
@@ -199,26 +295,22 @@ class MeshFragData {
|
||||
}
|
||||
}
|
||||
|
||||
class MeshData {
|
||||
public meshFragData: MeshFragData[] = [];
|
||||
export class ModelCache {
|
||||
public meshData = new Map<string, MeshFragData[]>();
|
||||
|
||||
constructor(atomic: rw.Atomic, public obj: ObjectDefinition) {
|
||||
private addAtomic(atomic: rw.Atomic, obj: ObjectDefinition) {
|
||||
const geom = atomic.geometry;
|
||||
|
||||
const positions = geom.morphTarget(0).vertices!.slice();
|
||||
const texCoords = (geom.numTexCoordSets > 0) ? geom.texCoords(0)!.slice() : null;
|
||||
const colors = (geom.colors !== null) ? geom.colors.slice() : null;
|
||||
|
||||
let h = geom.meshHeader;
|
||||
const h = geom.meshHeader;
|
||||
const frags: MeshFragData[] = [];
|
||||
for (let i = 0; i < h.numMeshes; i++) {
|
||||
const frag = new MeshFragData(h.mesh(i), h.tristrip, obj.txdName, positions, texCoords, colors);
|
||||
this.meshFragData.push(frag);
|
||||
const frag = new RWMeshFragData(h.mesh(i), h.tristrip, obj.txdName, positions, texCoords, colors);
|
||||
frags.push(frag);
|
||||
}
|
||||
this.meshData.set(obj.modelName, frags);
|
||||
}
|
||||
}
|
||||
|
||||
export class ModelCache {
|
||||
public meshData = new Map<string, MeshData>();
|
||||
|
||||
public addModel(model: rw.Clump, obj: ObjectDefinition) {
|
||||
let node: rw.Atomic | null = null;
|
||||
@@ -231,14 +323,14 @@ export class ModelCache {
|
||||
}
|
||||
}
|
||||
if (node !== null)
|
||||
this.meshData.set(obj.modelName, new MeshData(node, obj));
|
||||
this.addAtomic(node, obj);
|
||||
}
|
||||
}
|
||||
|
||||
export class MeshInstance {
|
||||
public modelMatrix = mat4.create();
|
||||
|
||||
constructor(public meshData: MeshData, public item: ItemInstance) {
|
||||
constructor(public frags: MeshFragData[], public item: ItemInstance) {
|
||||
mat4.fromRotationTranslationScale(this.modelMatrix, this.item.rotation, this.item.translation, this.item.scale);
|
||||
// convert Z-up to Y-up
|
||||
mat4.multiply(this.modelMatrix, mat4.fromQuat(mat4.create(), quat.fromValues(0.5, 0.5, 0.5, -0.5)), this.modelMatrix);
|
||||
@@ -250,6 +342,7 @@ export class DrawKey {
|
||||
public drawDistance?: number;
|
||||
public timeOn?: number;
|
||||
public timeOff?: number;
|
||||
public dynamic: boolean;
|
||||
|
||||
constructor(obj: ObjectDefinition, public zone: string) {
|
||||
if (obj.drawDistance < 99) {
|
||||
@@ -259,49 +352,38 @@ export class DrawKey {
|
||||
this.timeOn = obj.timeOn;
|
||||
this.timeOff = obj.timeOff;
|
||||
}
|
||||
this.dynamic = obj.dynamic;
|
||||
}
|
||||
}
|
||||
|
||||
export class SceneRenderer {
|
||||
export class SceneRenderer extends Renderer {
|
||||
public bbox = new AABB();
|
||||
|
||||
private vertexBuffer: GfxBuffer;
|
||||
private indexBuffer: GfxBuffer;
|
||||
private inputLayout: GfxInputLayout;
|
||||
private inputState: GfxInputState;
|
||||
private gfxProgram?: GfxProgram;
|
||||
|
||||
private megaStateFlags: Partial<GfxMegaStateDescriptor> = {
|
||||
blendMode: GfxBlendMode.ADD,
|
||||
blendDstFactor: GfxBlendFactor.ONE_MINUS_SRC_ALPHA,
|
||||
blendSrcFactor: GfxBlendFactor.SRC_ALPHA,
|
||||
};
|
||||
|
||||
private vertices = 0;
|
||||
private indices = 0;
|
||||
|
||||
constructor(device: GfxDevice, public key: DrawKey, meshes: MeshInstance[], private atlas?: TextureAtlas) {
|
||||
constructor(device: GfxDevice, public key: DrawKey, meshes: MeshInstance[], atlas?: TextureArray) {
|
||||
super(mainProgram, atlas);
|
||||
const skipFrag = (frag: MeshFragData) =>
|
||||
atlas !== undefined && frag.texName !== undefined && !atlas.subimages.has(frag.texName);
|
||||
|
||||
let vertices = 0;
|
||||
this.indices = 0;
|
||||
for (const inst of meshes) {
|
||||
for (const frag of inst.meshData.meshFragData) {
|
||||
for (const frag of inst.frags) {
|
||||
if (skipFrag(frag)) continue;
|
||||
this.vertices += frag.vertices();
|
||||
vertices += frag.vertices;
|
||||
this.indices += frag.indices.length;
|
||||
}
|
||||
}
|
||||
|
||||
const points = [] as vec3[];
|
||||
const vbuf = new Float32Array(this.vertices * 10);
|
||||
const vbuf = new Float32Array(vertices * 10);
|
||||
const ibuf = new Uint32Array(this.indices);
|
||||
let voffs = 0;
|
||||
let ioffs = 0;
|
||||
let lastIndex = 0;
|
||||
for (const inst of meshes) {
|
||||
for (const frag of inst.meshData.meshFragData) {
|
||||
for (const frag of inst.frags) {
|
||||
if (skipFrag(frag)) continue;
|
||||
const n = frag.vertices();
|
||||
const n = frag.vertices;
|
||||
const texLayer = (frag.texName === undefined || atlas === undefined) ? undefined : atlas.subimages.get(frag.texName);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const pos = vec3.transformMat4(vec3.create(), frag.position(i), inst.modelMatrix);
|
||||
@@ -339,9 +421,14 @@ export class SceneRenderer {
|
||||
const buffers = [{ buffer: this.vertexBuffer, byteOffset: 0, byteStride: 10 * 0x04}];
|
||||
const indexBuffer = { buffer: this.indexBuffer, byteOffset: 0, byteStride: 0 };
|
||||
this.inputState = device.createInputState(this.inputLayout, buffers, indexBuffer);
|
||||
this.megaStateFlags = {
|
||||
blendMode: GfxBlendMode.ADD,
|
||||
blendDstFactor: GfxBlendFactor.ONE_MINUS_SRC_ALPHA,
|
||||
blendSrcFactor: GfxBlendFactor.SRC_ALPHA,
|
||||
};
|
||||
}
|
||||
|
||||
public prepareToRender(device: GfxDevice, renderInstManager: GfxRenderInstManager, viewerInput: Viewer.ViewerRenderInput, dual: boolean): void {
|
||||
public prepareToRender(device: GfxDevice, renderInstManager: GfxRenderInstManager, viewerInput: Viewer.ViewerRenderInput, colorSet: ColorSet, dual = false): undefined {
|
||||
const hour = Math.floor(viewerInput.time / TIME_FACTOR) % 24;
|
||||
const { timeOn, timeOff } = this.key;
|
||||
let renderLayer = this.key.renderLayer;
|
||||
@@ -358,36 +445,29 @@ export class SceneRenderer {
|
||||
if (this.key.drawDistance !== undefined && depth > this.bbox.boundingSphereRadius() + 3 * this.key.drawDistance)
|
||||
return;
|
||||
|
||||
const renderInst = renderInstManager.pushRenderInst();
|
||||
renderInst.setInputLayoutAndState(this.inputLayout, this.inputState);
|
||||
renderInst.drawIndexes(this.indices);
|
||||
|
||||
if (this.gfxProgram === undefined)
|
||||
this.gfxProgram = renderInstManager.gfxRenderCache.createProgram(device, program);
|
||||
|
||||
renderInst.setGfxProgram(this.gfxProgram);
|
||||
renderInst.setMegaStateFlags(this.megaStateFlags);
|
||||
if (dual)
|
||||
renderInst.setMegaStateFlags({ depthWrite: false });
|
||||
if (this.atlas !== undefined)
|
||||
renderInst.setSamplerBindingsFromTextureMappings([this.atlas]);
|
||||
const renderInst = super.prepareToRender(device, renderInstManager, viewerInput, colorSet)!;
|
||||
renderInst.sortKey = setSortKeyDepth(makeSortKey(renderLayer), depth);
|
||||
|
||||
let offs = renderInst.allocateUniformBuffer(GTA3Program.ub_MeshFragParams, 12 + 4);
|
||||
let offs = renderInst.allocateUniformBuffer(GTA3Program.ub_MeshFragParams, 12 + 4 + 4);
|
||||
const mapped = renderInst.mapUniformBufferF32(GTA3Program.ub_MeshFragParams);
|
||||
offs += fillMatrix4x3(mapped, offs, viewerInput.camera.viewMatrix);
|
||||
if (this.key.dynamic) {
|
||||
mapped[offs++] = colorSet.amb.r + colorSet.dir.r;
|
||||
mapped[offs++] = colorSet.amb.g + colorSet.dir.g;
|
||||
mapped[offs++] = colorSet.amb.b + colorSet.dir.b;
|
||||
mapped[offs++] = colorSet.amb.a + colorSet.dir.a;
|
||||
} else {
|
||||
offs += fillColor(mapped, offs, colorSet.amb);
|
||||
}
|
||||
mapped[offs++] = !(renderLayer & GfxRendererLayer.TRANSLUCENT) ? 0.01 : dual ? -0.9 : 0.9;
|
||||
}
|
||||
|
||||
public destroy(device: GfxDevice): void {
|
||||
device.destroyBuffer(this.indexBuffer);
|
||||
device.destroyBuffer(this.vertexBuffer);
|
||||
device.destroyInputLayout(this.inputLayout);
|
||||
device.destroyInputState(this.inputState);
|
||||
if (this.gfxProgram !== undefined)
|
||||
device.destroyProgram(this.gfxProgram);
|
||||
if (this.atlas !== undefined)
|
||||
this.atlas.destroy(device);
|
||||
// PS2 alpha test emulation, see http://skygfx.rockstarvision.com/skygfx.html
|
||||
if (dual) {
|
||||
renderInst.setMegaStateFlags({ depthWrite: false });
|
||||
} else if (!!(this.key.renderLayer & GfxRendererLayer.TRANSLUCENT)) {
|
||||
this.prepareToRender(device, renderInstManager, viewerInput, colorSet, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,12 +476,12 @@ const bindingLayouts: GfxBindingLayoutDescriptor[] = [
|
||||
];
|
||||
|
||||
export class GTA3Renderer implements Viewer.SceneGfx {
|
||||
public sceneRenderers: SceneRenderer[] = [];
|
||||
public sceneRenderers: Renderer[] = [];
|
||||
public onstatechanged!: () => void;
|
||||
|
||||
private renderTarget = new BasicRenderTarget();
|
||||
private clearRenderPassDescriptor = makeClearRenderPassDescriptor(true, colorNewCopy(White));
|
||||
private ambient = colorNewCopy(White);
|
||||
private clearRenderPassDescriptor = standardFullClearRenderPassDescriptor;
|
||||
private currentColors = emptyColorSet();
|
||||
private renderHelper: GfxRenderHelper;
|
||||
private weather = 0;
|
||||
private scenarioSelect: UI.SingleSelect;
|
||||
@@ -414,30 +494,20 @@ export class GTA3Renderer implements Viewer.SceneGfx {
|
||||
const t = viewerInput.time / TIME_FACTOR;
|
||||
const cs1 = this.colorSets[Math.floor(t) % 24 + 24 * this.weather];
|
||||
const cs2 = this.colorSets[Math.floor(t+1) % 24 + 24 * this.weather];
|
||||
const skyTop = colorNewCopy(White);
|
||||
const skyBot = colorNewCopy(White);
|
||||
colorLerp(this.ambient, cs1.amb, cs2.amb, t % 1);
|
||||
colorLerp(skyTop, cs1.skyTop, cs2.skyTop, t % 1);
|
||||
colorLerp(skyBot, cs1.skyBot, cs2.skyBot, t % 1);
|
||||
colorLerp(this.clearRenderPassDescriptor.colorClearColor, skyTop, skyBot, 0.67); // fog
|
||||
lerpColorSet(this.currentColors, cs1, cs2, t % 1);
|
||||
|
||||
viewerInput.camera.setClipPlanes(1);
|
||||
this.renderHelper.pushTemplateRenderInst();
|
||||
const template = this.renderHelper.renderInstManager.pushTemplateRenderInst();
|
||||
template.setBindingLayouts(bindingLayouts);
|
||||
|
||||
let offs = template.allocateUniformBuffer(GTA3Program.ub_SceneParams, 16 + 4);
|
||||
let offs = template.allocateUniformBuffer(GTA3Program.ub_SceneParams, 16);
|
||||
const sceneParamsMapped = template.mapUniformBufferF32(GTA3Program.ub_SceneParams);
|
||||
offs += fillMatrix4x4(sceneParamsMapped, offs, viewerInput.camera.projectionMatrix);
|
||||
offs += fillColor(sceneParamsMapped, offs, this.ambient);
|
||||
|
||||
for (let i = 0; i < this.sceneRenderers.length; i++) {
|
||||
const sceneRenderer = this.sceneRenderers[i];
|
||||
sceneRenderer.prepareToRender(device, this.renderHelper.renderInstManager, viewerInput, false);
|
||||
if (!!(sceneRenderer.key.renderLayer & GfxRendererLayer.TRANSLUCENT)) {
|
||||
// PS2 alpha test emulation, see http://skygfx.rockstarvision.com/skygfx.html
|
||||
sceneRenderer.prepareToRender(device, this.renderHelper.renderInstManager, viewerInput, true);
|
||||
}
|
||||
sceneRenderer.prepareToRender(device, this.renderHelper.renderInstManager, viewerInput, this.currentColors);
|
||||
}
|
||||
|
||||
this.renderHelper.renderInstManager.popTemplateRenderInst();
|
||||
|
||||
+143
-81
@@ -3,23 +3,33 @@ import * as Viewer from '../viewer';
|
||||
import * as rw from 'librw';
|
||||
import { GfxDevice } from '../gfx/platform/GfxPlatform';
|
||||
import { DataFetcher } from '../DataFetcher';
|
||||
import { GTA3Renderer, SceneRenderer, DrawKey, Texture, TextureAtlas, MeshInstance, ModelCache } from './render';
|
||||
import { GTA3Renderer, SceneRenderer, DrawKey, Texture, TextureArray, MeshInstance, ModelCache, SkyRenderer } from './render';
|
||||
import { SceneContext } from '../SceneBase';
|
||||
import { getTextDecoder, assert } from '../util';
|
||||
import { parseItemPlacement, ItemPlacement, parseItemDefinition, ItemDefinition, ObjectDefinition, ItemInstance, parseZones } from './item';
|
||||
import { parseTimeCycle, ColorSet } from './time';
|
||||
import { parseWaterPro, waterMeshFragData, waterDefinition } from './water';
|
||||
import { quat, vec3 } from 'gl-matrix';
|
||||
import { AABB } from '../Geometry';
|
||||
import { GfxRendererLayer } from '../gfx/render/GfxRenderer';
|
||||
import ArrayBufferSlice from '../ArrayBufferSlice';
|
||||
|
||||
const pathBase = `GrandTheftAuto3`;
|
||||
|
||||
function UTF8ToString(array: Uint8Array) {
|
||||
let length = 0; while (length < array.length && array[length]) length++;
|
||||
return getTextDecoder('utf8')!.decode(array.subarray(0, length));
|
||||
}
|
||||
|
||||
class GTA3SceneDesc implements Viewer.SceneDesc {
|
||||
private static initialised = false;
|
||||
private complete: boolean;
|
||||
private assets = new Map<string, ArrayBufferSlice>();
|
||||
private ids: string[];
|
||||
|
||||
constructor(public id: string, public name: string) {
|
||||
if (this.id === 'all') {
|
||||
this.complete = (this.id === 'all');
|
||||
if (this.complete) {
|
||||
this.ids = [
|
||||
"comntop/comNtop",
|
||||
"comnbtm/comNbtm",
|
||||
@@ -49,13 +59,37 @@ class GTA3SceneDesc implements Viewer.SceneDesc {
|
||||
this.initialised = true;
|
||||
}
|
||||
|
||||
private async fetchIDE(id: string, dataFetcher: DataFetcher): Promise<ItemDefinition> {
|
||||
const buffer = await dataFetcher.fetchData(`${pathBase}/data/maps/${id}.ide`);
|
||||
const text = getTextDecoder('utf8')!.decode(buffer.arrayBuffer);
|
||||
private async fetchIMG(dataFetcher: DataFetcher): Promise<void> {
|
||||
const [bufferDIR, bufferIMG] = await Promise.all([
|
||||
dataFetcher.fetchData(`${pathBase}/models/gta3.dir`),
|
||||
dataFetcher.fetchData(`${pathBase}/models/gta3.img`),
|
||||
]);
|
||||
const view = bufferDIR.createDataView();
|
||||
for (let i = 0; i < view.byteLength; i += 32) {
|
||||
const offset = view.getUint32(i + 0, true);
|
||||
const size = view.getUint32(i + 4, true);
|
||||
const name = UTF8ToString(bufferDIR.subarray(i + 8, 24).createTypedArray(Uint8Array)).toLowerCase();
|
||||
const data = bufferIMG.subarray(2048 * offset, 2048 * size);
|
||||
this.assets.set(`${pathBase}/models/gta3/${name}`, data);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetch(dataFetcher: DataFetcher, path: string): Promise<ArrayBufferSlice> {
|
||||
let buffer = this.assets.get(path);
|
||||
if (buffer === undefined) {
|
||||
buffer = await dataFetcher.fetchData(path);
|
||||
this.assets.set(path, buffer);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private async fetchIDE(dataFetcher: DataFetcher, id: string): Promise<ItemDefinition> {
|
||||
const buffer = await this.fetch(dataFetcher, `${pathBase}/data/maps/${id}.ide`);
|
||||
const text = getTextDecoder('utf8')!.decode(buffer.createDataView());
|
||||
return parseItemDefinition(text);
|
||||
}
|
||||
|
||||
private async fetchIPL(id: string, dataFetcher: DataFetcher): Promise<ItemPlacement> {
|
||||
private async fetchIPL(dataFetcher: DataFetcher, id: string): Promise<ItemPlacement> {
|
||||
if (id === 'test') return {
|
||||
instances: [{
|
||||
id: 0,
|
||||
@@ -65,23 +99,59 @@ class GTA3SceneDesc implements Viewer.SceneDesc {
|
||||
scale: vec3.fromValues(10,10,10),
|
||||
}]
|
||||
};
|
||||
const buffer = await dataFetcher.fetchData((id === 'props') ? `${pathBase}/data/maps/props.IPL` : `${pathBase}/data/maps/${id}.ipl`);
|
||||
const text = getTextDecoder('utf8')!.decode(buffer.arrayBuffer);
|
||||
const buffer = await this.fetch(dataFetcher, (id === 'props') ? `${pathBase}/data/maps/props.IPL` : `${pathBase}/data/maps/${id}.ipl`);
|
||||
const text = getTextDecoder('utf8')!.decode(buffer.createDataView());
|
||||
return parseItemPlacement(text);
|
||||
}
|
||||
|
||||
private async fetchTimeCycle(dataFetcher: DataFetcher): Promise<ColorSet[]> {
|
||||
const buffer = await dataFetcher.fetchData(`${pathBase}/data/timecyc.dat`);
|
||||
const text = getTextDecoder('utf8')!.decode(buffer.arrayBuffer);
|
||||
const buffer = await this.fetch(dataFetcher, `${pathBase}/data/timecyc.dat`);
|
||||
const text = getTextDecoder('utf8')!.decode(buffer.createDataView());
|
||||
return parseTimeCycle(text);
|
||||
}
|
||||
|
||||
private async fetchZones(dataFetcher: DataFetcher): Promise<Map<string, AABB>> {
|
||||
const buffer = await dataFetcher.fetchData(`${pathBase}/data/gta3.zon`);
|
||||
const text = getTextDecoder('utf8')!.decode(buffer.arrayBuffer);
|
||||
const buffer = await this.fetch(dataFetcher, `${pathBase}/data/gta3.zon`);
|
||||
const text = getTextDecoder('utf8')!.decode(buffer.createDataView());
|
||||
return parseZones(text);
|
||||
}
|
||||
|
||||
private async fetchWater(dataFetcher: DataFetcher): Promise<ItemPlacement> {
|
||||
const buffer = await this.fetch(dataFetcher, `${pathBase}/data/waterpro.dat`);
|
||||
return parseWaterPro(buffer.createDataView());
|
||||
}
|
||||
|
||||
private async fetchTXD(dataFetcher: DataFetcher, txdName: string, textures: Map<string, Texture>): Promise<void> {
|
||||
const txdPath = (txdName === 'generic' || txdName === 'particle')
|
||||
? `${pathBase}/models/${txdName}.txd`
|
||||
: `${pathBase}/models/gta3/${txdName}.txd`;
|
||||
const buffer = await this.fetch(dataFetcher, txdPath);
|
||||
const stream = new rw.StreamMemory(buffer.createTypedArray(Uint8Array));
|
||||
const header = new rw.ChunkHeaderInfo(stream);
|
||||
assert(header.type === rw.PluginID.ID_TEXDICTIONARY);
|
||||
const txd = new rw.TexDictionary(stream);
|
||||
header.delete();
|
||||
stream.delete();
|
||||
for (let lnk = txd.textures.begin; !lnk.is(txd.textures.end); lnk = lnk.next) {
|
||||
const texture = new Texture(rw.Texture.fromDict(lnk), txdName);
|
||||
textures.set(texture.name, texture);
|
||||
}
|
||||
txd.delete();
|
||||
}
|
||||
|
||||
private async fetchDFF(dataFetcher: DataFetcher, modelName: string, cb: (clump: rw.Clump) => void): Promise<void> {
|
||||
const dffPath = `${pathBase}/models/gta3/${modelName}.dff`;
|
||||
const buffer = await this.fetch(dataFetcher, dffPath);
|
||||
const stream = new rw.StreamMemory(buffer.createTypedArray(Uint8Array));
|
||||
const header = new rw.ChunkHeaderInfo(stream);
|
||||
assert(header.type === rw.PluginID.ID_CLUMP);
|
||||
const clump = rw.Clump.streamRead(stream);
|
||||
header.delete();
|
||||
stream.delete();
|
||||
cb(clump);
|
||||
clump.delete();
|
||||
}
|
||||
|
||||
public async createScene(device: GfxDevice, context: SceneContext): Promise<Viewer.SceneGfx> {
|
||||
await GTA3SceneDesc.initialise();
|
||||
const dataFetcher = context.dataFetcher;
|
||||
@@ -90,11 +160,13 @@ class GTA3SceneDesc implements Viewer.SceneDesc {
|
||||
const ideids = ['generic', 'temppart/temppart', 'comroad/comroad', 'indroads/indroads', 'making/making', 'subroads/subroads'];
|
||||
for (const id of this.ids)
|
||||
if (id.match(/\//)) ideids.push(id.toLowerCase());
|
||||
const ides = await Promise.all(ideids.map(id => this.fetchIDE(id, dataFetcher)));
|
||||
const ides = await Promise.all(ideids.map(id => this.fetchIDE(dataFetcher, id)));
|
||||
for (const ide of ides) for (const obj of ide.objects) objects.set(obj.modelName, obj);
|
||||
objects.set('water', waterDefinition);
|
||||
|
||||
const ipls = await Promise.all(this.ids.map(id => this.fetchIPL(id, dataFetcher)));
|
||||
const [colorSets, zones] = await Promise.all([this.fetchTimeCycle(dataFetcher), this.fetchZones(dataFetcher)]);
|
||||
const ipls = await Promise.all(this.ids.map(id => this.fetchIPL(dataFetcher, id)));
|
||||
const [colorSets, zones, water] = await Promise.all([this.fetchTimeCycle(dataFetcher), this.fetchZones(dataFetcher), this.fetchWater(dataFetcher)]);
|
||||
ipls.push(water);
|
||||
|
||||
const drawKeys = new Map<string, DrawKey>();
|
||||
const layers = new Map<DrawKey, [ItemInstance, ObjectDefinition][]>();
|
||||
@@ -105,7 +177,7 @@ class GTA3SceneDesc implements Viewer.SceneDesc {
|
||||
console.warn('No definition for object', name);
|
||||
continue;
|
||||
}
|
||||
if (name.startsWith('lod') || name.startsWith('islandlod')) continue; // ignore LOD objects
|
||||
if ((name.startsWith('lod') && name !== 'lodistancoast01') || name.startsWith('islandlod')) continue; // ignore LOD objects
|
||||
|
||||
let zone = 'cityzon';
|
||||
for (const [name, bb] of zones) {
|
||||
@@ -123,81 +195,71 @@ class GTA3SceneDesc implements Viewer.SceneDesc {
|
||||
layers.get(drawKey)!.push([item, obj]);
|
||||
}
|
||||
|
||||
if (this.complete)
|
||||
await this.fetchIMG(dataFetcher);
|
||||
|
||||
const renderer = new GTA3Renderer(device, colorSets);
|
||||
const loadedTXD = new Map<string, Promise<void>>();
|
||||
const loadedDFF = new Map<string, Promise<void>>();
|
||||
const textures = new Map<string, Texture>();
|
||||
const modelCache = new ModelCache();
|
||||
for (const [drawKey, items] of layers) (async () => {
|
||||
|
||||
loadedTXD.set('particle', this.fetchTXD(dataFetcher, 'particle', textures));
|
||||
loadedDFF.set('water', (async () => {})());
|
||||
modelCache.meshData.set('water', [waterMeshFragData]);
|
||||
|
||||
loadedTXD.get('particle')!.then(() =>
|
||||
renderer.sceneRenderers.push(new SkyRenderer(device,
|
||||
new TextureArray(device, [textures.get('particle/water_old')!]))));
|
||||
|
||||
for (const [drawKey, items] of layers) {
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const [item, obj] of items) {
|
||||
if (!loadedTXD.has(obj.txdName)) {
|
||||
const txdPath = (obj.txdName === 'generic') ? `${pathBase}/models/generic.txd` : `${pathBase}/models/gta3/${obj.txdName}.txd`;
|
||||
loadedTXD.set(obj.txdName, dataFetcher.fetchData(txdPath).then(buffer => {
|
||||
const stream = new rw.StreamMemory(buffer.arrayBuffer);
|
||||
const header = new rw.ChunkHeaderInfo(stream);
|
||||
assert(header.type === rw.PluginID.ID_TEXDICTIONARY);
|
||||
const txd = new rw.TexDictionary(stream);
|
||||
header.delete();
|
||||
stream.delete();
|
||||
for (let lnk = txd.textures.begin; !lnk.is(txd.textures.end); lnk = lnk.next) {
|
||||
const texture = new Texture(rw.Texture.fromDict(lnk), obj.txdName);
|
||||
textures.set(texture.name, texture);
|
||||
}
|
||||
txd.delete();
|
||||
}));
|
||||
}
|
||||
promises.push(loadedTXD.get(obj.txdName)!);
|
||||
|
||||
if (!loadedDFF.has(obj.modelName)) {
|
||||
const dffPath = `${pathBase}/models/gta3/${obj.modelName}.dff`;
|
||||
loadedDFF.set(obj.modelName, dataFetcher.fetchData(dffPath).then(async buffer => {
|
||||
const stream = new rw.StreamMemory(buffer.arrayBuffer);
|
||||
const header = new rw.ChunkHeaderInfo(stream);
|
||||
assert(header.type === rw.PluginID.ID_CLUMP);
|
||||
const clump = rw.Clump.streamRead(stream);
|
||||
header.delete();
|
||||
stream.delete();
|
||||
modelCache.addModel(clump, obj);
|
||||
clump.delete();
|
||||
}));
|
||||
}
|
||||
promises.push(loadedDFF.get(obj.modelName)!);
|
||||
if (!loadedTXD.has(obj.txdName))
|
||||
loadedTXD.set(obj.txdName, this.fetchTXD(dataFetcher, obj.txdName, textures));
|
||||
if (!loadedDFF.has(obj.modelName))
|
||||
loadedDFF.set(obj.modelName, this.fetchDFF(dataFetcher, obj.modelName, clump => modelCache.addModel(clump, obj)));
|
||||
promises.push(loadedTXD.get(obj.txdName)!, loadedDFF.get(obj.modelName)!);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
const layerTextures = new Map<string, Texture[]>();
|
||||
const layerMeshes: MeshInstance[] = [];
|
||||
for (const [item, obj] of items) {
|
||||
const model = modelCache.meshData.get(item.modelName);
|
||||
if (model === undefined) {
|
||||
console.warn('Missing model', item.modelName);
|
||||
continue;
|
||||
}
|
||||
for (const frag of model.meshFragData) {
|
||||
if (frag.texName === undefined) continue;
|
||||
const texture = textures.get(frag.texName);
|
||||
if (texture === undefined) {
|
||||
console.warn('Missing texture', frag.texName, 'for', item.modelName);
|
||||
} else {
|
||||
let res = texture.width + 'x' + texture.height;
|
||||
if (rw.Raster.formatHasAlpha(texture.format))
|
||||
res += 'alpha';
|
||||
if (!layerTextures.has(res)) layerTextures.set(res, []);
|
||||
layerTextures.get(res)!.push(texture);
|
||||
const promise = Promise.all(promises).then(() => {
|
||||
const layerTextures = new Map<string, Set<Texture>>();
|
||||
const layerMeshes: MeshInstance[] = [];
|
||||
for (const [item, obj] of items) {
|
||||
const model = modelCache.meshData.get(item.modelName);
|
||||
if (model === undefined) {
|
||||
console.warn('Missing model', item.modelName);
|
||||
continue;
|
||||
}
|
||||
for (const frag of model) {
|
||||
if (frag.texName === undefined) continue;
|
||||
const texture = textures.get(frag.texName);
|
||||
if (texture === undefined) {
|
||||
console.warn('Missing texture', frag.texName, 'for', item.modelName);
|
||||
} else {
|
||||
let res = texture.width + 'x' + texture.height;
|
||||
if (rw.Raster.formatHasAlpha(texture.format))
|
||||
res += 'alpha';
|
||||
if (!layerTextures.has(res)) layerTextures.set(res, new Set());
|
||||
layerTextures.get(res)!.add(texture);
|
||||
}
|
||||
}
|
||||
layerMeshes.push(new MeshInstance(model, item));
|
||||
}
|
||||
layerMeshes.push(new MeshInstance(model, item));
|
||||
}
|
||||
for (const [res, textures] of layerTextures) {
|
||||
const key = Object.assign({}, drawKey);
|
||||
if (res.endsWith('alpha'))
|
||||
key.renderLayer = GfxRendererLayer.TRANSLUCENT;
|
||||
const atlas = (textures.length > 0) ? new TextureAtlas(device, textures) : undefined;
|
||||
const sceneRenderer = new SceneRenderer(device, key, layerMeshes, atlas);
|
||||
renderer.sceneRenderers.push(sceneRenderer);
|
||||
}
|
||||
})();
|
||||
for (const [res, textures] of layerTextures) {
|
||||
const key = Object.assign({}, drawKey);
|
||||
if (res.endsWith('alpha'))
|
||||
key.renderLayer = GfxRendererLayer.TRANSLUCENT;
|
||||
const atlas = (textures.size > 0) ? new TextureArray(device, Array.from(textures)) : undefined;
|
||||
const sceneRenderer = new SceneRenderer(device, key, layerMeshes, atlas);
|
||||
renderer.sceneRenderers.push(sceneRenderer);
|
||||
}
|
||||
});
|
||||
if (this.complete)
|
||||
await promise;
|
||||
}
|
||||
|
||||
if (this.complete)
|
||||
this.assets.clear();
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Color, colorNew } from '../Color';
|
||||
import { Color, colorNew, colorLerp, colorNewCopy, White } from '../Color';
|
||||
import { lerp } from '../MathHelpers';
|
||||
|
||||
function colorNorm(r: number, g: number, b: number, a: number = 255.0): Color {
|
||||
return colorNew(r/255.0, g/255.0, b/255.0, a/255.0);
|
||||
@@ -71,3 +72,54 @@ export async function parseTimeCycle(text: string) {
|
||||
}
|
||||
return sets;
|
||||
}
|
||||
|
||||
export function emptyColorSet(): ColorSet {
|
||||
return {
|
||||
amb: colorNewCopy(White),
|
||||
dir: colorNewCopy(White),
|
||||
skyTop: colorNewCopy(White),
|
||||
skyBot: colorNewCopy(White),
|
||||
|
||||
sunCore: colorNewCopy(White),
|
||||
sunCorona: colorNewCopy(White),
|
||||
sunSz: 0,
|
||||
sprSz: 0,
|
||||
sprBght: 0,
|
||||
shad: 0,
|
||||
lightShad: 0,
|
||||
treeShad: 0,
|
||||
farClp: 0,
|
||||
fogSt: 0,
|
||||
lightGnd: 0,
|
||||
|
||||
cloud: colorNewCopy(White),
|
||||
fluffyTop: colorNewCopy(White),
|
||||
fluffyBot: colorNewCopy(White),
|
||||
blur: colorNewCopy(White),
|
||||
};
|
||||
}
|
||||
|
||||
export function lerpColorSet(dst: ColorSet, a: ColorSet, b: ColorSet, t: number) {
|
||||
colorLerp(dst.amb, a.amb, b.amb, t);
|
||||
colorLerp(dst.dir, a.dir, b.dir, t);
|
||||
colorLerp(dst.skyTop, a.skyTop, b.skyTop, t);
|
||||
colorLerp(dst.skyBot, a.skyBot, b.skyBot, t);
|
||||
|
||||
colorLerp(dst.sunCore, a.sunCore, b.sunCore, t);
|
||||
colorLerp(dst.sunCorona, a.sunCorona, b.sunCorona, t);
|
||||
|
||||
dst.sunSz = lerp(a.sunSz, b.sunSz, t);
|
||||
dst.sprSz = lerp(a.sprSz, b.sprSz, t);
|
||||
dst.sprBght = lerp(a.sprBght, b.sprBght, t);
|
||||
dst.shad = lerp(a.shad, b.shad, t);
|
||||
dst.lightShad = lerp(a.lightShad, b.lightShad, t);
|
||||
dst.treeShad = lerp(a.treeShad, b.treeShad, t);
|
||||
dst.farClp = lerp(a.farClp, b.farClp, t);
|
||||
dst.fogSt = lerp(a.fogSt, b.fogSt, t);
|
||||
dst.lightGnd = lerp(a.lightGnd, b.lightGnd, t);
|
||||
|
||||
colorLerp(dst.cloud, a.cloud, b.cloud, t);
|
||||
colorLerp(dst.fluffyTop, a.fluffyTop, b.fluffyTop, t);
|
||||
colorLerp(dst.fluffyBot, a.fluffyBot, b.fluffyBot, t);
|
||||
colorLerp(dst.blur, a.blur, b.blur, t);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
import { vec3, vec2, vec4, quat } from 'gl-matrix';
|
||||
import { OpaqueBlack } from '../Color';
|
||||
import { ItemPlacement, ItemInstance, ObjectDefinition } from './item';
|
||||
|
||||
export function parseWaterPro(view: DataView, bounds = vec4.fromValues(-2048, -2048, 2048, 2048)): ItemPlacement {
|
||||
const numLevels = view.getInt32(0, true);
|
||||
const heights: number[] = [];
|
||||
for (let i = 0; i < numLevels; i++) {
|
||||
heights.push(view.getFloat32(0x4 + i * 0x4, true));
|
||||
}
|
||||
const instances: ItemInstance[] = [];
|
||||
const offs = 0x4 + 48 * 0x4 + 48 * 0x10 + 64 * 64;
|
||||
const width = (bounds[2] - bounds[0]) / 128;
|
||||
const height = (bounds[3] - bounds[1]) / 128;
|
||||
const scale = vec3.fromValues(width, height, 1);
|
||||
const rotation = quat.identity(quat.create());
|
||||
for (let i = 0; i < 128; i++) {
|
||||
for (let j = 0; j < 128; j++) {
|
||||
const level = view.getUint8(offs + 128 * i + j);
|
||||
if (level & 0x80) continue;
|
||||
instances.push({
|
||||
modelName: 'water',
|
||||
translation: vec3.fromValues(
|
||||
i * width + bounds[0],
|
||||
j * height + bounds[1],
|
||||
heights[level]
|
||||
),
|
||||
scale, rotation
|
||||
});
|
||||
}
|
||||
}
|
||||
return { instances };
|
||||
}
|
||||
|
||||
export const waterDefinition: ObjectDefinition = {
|
||||
modelName: 'water',
|
||||
txdName: 'particle',
|
||||
drawDistance: 1000,
|
||||
flags: 0,
|
||||
tobj: false,
|
||||
dynamic: true
|
||||
};
|
||||
|
||||
const squarePositions = [
|
||||
vec3.fromValues(0,0,0),
|
||||
vec3.fromValues(0,1,0),
|
||||
vec3.fromValues(1,1,0),
|
||||
vec3.fromValues(1,0,0),
|
||||
];
|
||||
|
||||
const squareTexCoords = [
|
||||
vec2.fromValues(0,0),
|
||||
vec2.fromValues(0,1),
|
||||
vec2.fromValues(1,1),
|
||||
vec2.fromValues(1,0),
|
||||
];
|
||||
|
||||
export const waterMeshFragData = {
|
||||
texName: 'particle/water_old',
|
||||
indices: new Uint16Array([0,1,2,0,2,3]),
|
||||
vertices: 4,
|
||||
position: (i: number) => squarePositions[i],
|
||||
texCoord: (i: number) => squareTexCoords[i],
|
||||
color: (i: number) => OpaqueBlack,
|
||||
};
|
||||
@@ -3166,10 +3166,10 @@ levn@~0.3.0:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
librw@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/librw/-/librw-0.1.0.tgz#d1838acdfe55dc15e68a950f3d0fd252b47fa8b6"
|
||||
integrity sha512-y9NeVRs9uDgJtZ/mvduN0iWPbznmWjHe21/0VQnqigwcR3XWA0+eqtaFpvTXzjepy07Up9YkKpHxgMPh7T/bOQ==
|
||||
librw@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/librw/-/librw-0.2.0.tgz#179cd8b9d731f4e578cae8c1e16978740ec825a5"
|
||||
integrity sha512-PF5s1PqoeTZiYn7d9tXQFcdEqg2RByOkY9uR0mmXcRtdxzn6SyWJEB/0gtE0JFHUcS96cyRcSd64zxwEVEZUUQ==
|
||||
|
||||
lodash.clone@^4.5.0:
|
||||
version "4.5.0"
|
||||
|
||||
Reference in New Issue
Block a user