From fec4fde8ed8a9ae8fd8c059a30cb9a86c833b8aa Mon Sep 17 00:00:00 2001 From: Chubbygummibear Date: Fri, 3 Nov 2023 11:44:52 -0700 Subject: [PATCH 1/3] this alone should do it --- src/misc/appearance.ts | 46 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/misc/appearance.ts b/src/misc/appearance.ts index 304618a..b2cfcb3 100644 --- a/src/misc/appearance.ts +++ b/src/misc/appearance.ts @@ -195,8 +195,21 @@ export namespace Appearance { appearances.push(...get_appearance_parts(underlay)); } } - appearances.push(appearance) - for(let overlay of [...appearance.overlays].sort((a,b) => { + appearances.push(appearance); + + /** + This is monster's original sort method which works on some browsers, but not all because it doesn't have all 3 cases required to uphold symmetry. + The cases required for a complete custom sort are + + | compareFn(a, b) | return value | sort order + | | > 0 | sort a after b, e.g. [b, a] + | | < 0 | sort a before b, e.g. [a, b] + | | === 0 | keep original order of a and b + + So the issue is that monster's only ever returned negatives or 0, so the lacking positive result would break some browser's js engine like firefox. + Whereas chromium based browsers (i.e. Google Chrome, Microsoft Edge) would function as intended. + + const appearances_to_sort = [...appearance.overlays].sort((a,b) => { let a_layer = a.layer < 0 ? appearance.layer : a.layer; let b_layer = b.layer < 0 ? appearance.layer : b.layer; if(a_layer < b_layer) @@ -206,7 +219,31 @@ export namespace Appearance { if(a_float_layer < b_float_layer) return a_float_layer - b_float_layer; return 0; - })) { + }) + + */ + + //To circumvent the sorting issues, we're going to split the possible layers into 2 arrays. + //1 for regular layers which have positive values and 1 for float layers which have negative values + const regular_layers = appearance.overlays.filter((overlay) => overlay.layer >= 0); + const float_layers = appearance.overlays.filter((overlay) => overlay.layer < 0); + + //sort by descending order for regular layers + regular_layers.sort((a,b) => { + return a.layer - b.layer; + }); + + //sort by ascending order for float layers + float_layers.sort((a,b) => { + return a.layer - b.layer; + }); + + //now combine the arrays with regular layers first. + const appearances_to_sort = regular_layers.concat(float_layers); + + //Resume monster's code + + for(let overlay of appearances_to_sort) { overlay = overlay_inherit(appearance, overlay); if(resolve_plane(overlay.plane, appearance.plane) != resolve_plane(appearance.plane)) { float_appearances.push(overlay); @@ -216,6 +253,9 @@ export namespace Appearance { } appearance.sorted_appearances = appearances; appearance.floating_appearances = float_appearances.length ? float_appearances : empty_arr; + if(appearance.name == "Dravak Sathune"){ + console.log("Dravak", appearance) + } return appearances; } From 8a67ad2629af086ac01d6c28919e1b3656635485 Mon Sep 17 00:00:00 2001 From: Chubbygummibear Date: Fri, 3 Nov 2023 11:45:36 -0700 Subject: [PATCH 2/3] kills my lizard --- src/misc/appearance.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/misc/appearance.ts b/src/misc/appearance.ts index b2cfcb3..326fd4c 100644 --- a/src/misc/appearance.ts +++ b/src/misc/appearance.ts @@ -253,9 +253,6 @@ export namespace Appearance { } appearance.sorted_appearances = appearances; appearance.floating_appearances = float_appearances.length ? float_appearances : empty_arr; - if(appearance.name == "Dravak Sathune"){ - console.log("Dravak", appearance) - } return appearances; } From 37f43f9d2cfbc4b2319be833e71c98cd25f3e631 Mon Sep 17 00:00:00 2001 From: Chubbygummibear Date: Sun, 18 Feb 2024 11:42:43 -0800 Subject: [PATCH 3/3] all --- src/main/menu.ts | 4 + src/main/rendering/gl_holder.ts | 76 ++++++++++++----- src/main/ui.ts | 3 + src/misc/appearance.ts | 19 +++-- src/misc/constants.ts | 141 ++++++++++++++++++++++++++++--- src/parser/binary_parser.ts | 8 +- src/parser/text_parser.ts | 29 +++++-- src/player/player.ts | 30 ++++--- src/player/rendering/atlas.ts | 4 +- src/player/rendering/buffer.ts | 98 +++++++++++---------- src/player/rendering/commands.ts | 3 +- 11 files changed, 303 insertions(+), 112 deletions(-) diff --git a/src/main/menu.ts b/src/main/menu.ts index c9a1f47..35391a5 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -82,6 +82,10 @@ export class MainMenu extends Menu { this.ui.player.toggle_darkness(); this.close(); }); + this.add_basic_button("Dump Textures", null, () => { + this.ui.player.dump_textures(); + this.close(); + }); } } diff --git a/src/main/rendering/gl_holder.ts b/src/main/rendering/gl_holder.ts index e1facdb..259a195 100644 --- a/src/main/rendering/gl_holder.ts +++ b/src/main/rendering/gl_holder.ts @@ -4,6 +4,7 @@ import { CopyShader, get_copy_shader, get_icon_shader, IconShader, ShaderHolder import { RenderingCmd } from "../../player/rendering/commands"; import { ViewportElement } from "../viewport"; import { render_maptext } from "./maptext"; +import { BlendMode } from "../../misc/constants"; export class DemoPlayerGlHolder { gl : WebGLRenderingContext; @@ -16,6 +17,7 @@ export class DemoPlayerGlHolder { max_texture_size : number; copy_framebuffer : WebGLFramebuffer; white_texture : WebGLTexture; + canvas_copy : WebGLTexture; shader : IconShader; shader_matrix : IconShader; @@ -27,7 +29,7 @@ export class DemoPlayerGlHolder { if(this.gl2) { this.gl = this.gl2; } else { - let gl = canvas.getContext("webgl", {desynchronized: true}); + let gl = canvas.getContext("webgl", {desynchronized: true, alpha: false}); if(!gl) throw new Error("Could not initialize WebGL"); this.gl = gl; } @@ -49,11 +51,15 @@ export class DemoPlayerGlHolder { this.white_texture = not_null(gl.createTexture()); gl.bindTexture(gl.TEXTURE_2D, this.white_texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255,255,255,255])); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0,0,0,0])); gl.bindTexture(gl.TEXTURE_2D, null); this.copy_framebuffer = not_null(gl.createFramebuffer()); this.max_texture_size = Math.min(gl.getParameter(gl.MAX_TEXTURE_SIZE), 32768); + this.canvas_copy = not_null(gl.createTexture()); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.canvas_copy); + this.square_buffer = not_null(gl.createBuffer()); gl.bindBuffer(gl.ARRAY_BUFFER, this.square_buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1]), gl.STATIC_DRAW); @@ -169,7 +175,7 @@ export class DemoPlayerGlHolder { } } gl.bindTexture(gl.TEXTURE_2D, null); - } else if(cmd.cmd == "atlestexcopywithin") { + } else if(cmd.cmd == "atlastexcopywithin") { let tex = not_null(this.atlas_textures[cmd.index]); let shader = this.shader_copy; this.set_shader(shader); @@ -257,6 +263,8 @@ export class DemoPlayerGlHolder { ia.vertexAttribDivisorANGLE(shader.a_layer, 1); ia.drawArraysInstancedANGLE(gl.TRIANGLES, 0, 6, cmd.num_elements); gl.deleteBuffer(buf); + // gl.activeTexture(gl.TEXTURE0 + 1); + // gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, gl.canvas); } else if(cmd.cmd == "copytoviewport") { let this_viewport_pixel = curr_viewport_pixel; let this_viewport = curr_viewport; @@ -338,26 +346,54 @@ export class DemoPlayerGlHolder { // this one was fun - I made test cases in BYOND and took screenshots and tried to reverse-engineer the blending equations from that. // fun fact BYOND uses premultiplied alpha. However, when you set_blend_mode(blend_mode : number) : void{ - if(blend_mode == 0) blend_mode = 1; + //if(blend_mode == 0) blend_mode = 1; if(blend_mode == this.curr_blend_mode) return; this.curr_blend_mode = blend_mode; const gl = this.gl; - if(blend_mode == 2) { // BLEND_ADD - gl.blendEquation(gl.FUNC_ADD); - gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - } else if(blend_mode == 3) { // BLEND_SUBTRACT - gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT); - gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE); - } else if(blend_mode == 4) { // BLEND_MULTIPLY - gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA); // fun fact if you do the math everything cancels out so that the destination alpha doesn't change at all. - } else if(blend_mode == 5) { // BLEND_INSET_OVERLAY - // TODO figure out if this is actually right - gl.blendEquation(gl.FUNC_ADD); - gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE) - } else { // BLEND_OVERLAY or BLEND_DEFAULT - gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + switch(blend_mode){ + case BlendMode.DEFAULT: { + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + break; + } + case BlendMode.ADD: { + gl.blendEquation(gl.FUNC_ADD); + gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + break; + } + case BlendMode.SUBTRACT: { + gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT); + gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE); + break; + } + case BlendMode.MULTIPLY: { + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA); // fun fact if you do the math everything cancels out so that the destination alpha doesn't change at all. + break; + } + case BlendMode.INSET_OVERLAY: { + // TODO figure out if this is actually right + gl.blendEquation(gl.FUNC_ADD); + gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE) + break; + } + case BlendMode.ALPHA: { + gl.blendEquation(gl.FUNC_ADD); + //gl.blendFuncSeparate(gl.DST_COLOR, gl.ZERO, gl.DST_ALPHA, gl.ZERO) + gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA); + break; + } + case BlendMode.ALPHA_INVERTED: { + gl.blendEquation(gl.FUNC_ADD); + gl.blendFuncSeparate(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE) + break; + } + //Just in case there's a weird value we'll use the default + default: { + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + break; + } } } diff --git a/src/main/ui.ts b/src/main/ui.ts index 997eedf..8bf69dc 100644 --- a/src/main/ui.ts +++ b/src/main/ui.ts @@ -193,4 +193,7 @@ export class DemoPlayerUi { handle_sounds(sounds : DemoSound[]) : void { this.sound_player.handle_sounds(sounds); } + async dump_textures() { + this.gl_holder.dump_textures(); + } } \ No newline at end of file diff --git a/src/misc/appearance.ts b/src/misc/appearance.ts index f044192..ff15c29 100644 --- a/src/misc/appearance.ts +++ b/src/misc/appearance.ts @@ -1,5 +1,5 @@ import { IconStateDir } from "../player/rendering/icon"; -import { Planes, RESET_ALPHA, RESET_COLOR, RESET_TRANSFORM } from "./constants"; +import { BlendMode, Planes, RESET_ALPHA, RESET_COLOR, RESET_TRANSFORM } from "./constants"; import { Matrix, matrix_invert, matrix_is_identity, matrix_multiply } from "./matrix"; export enum FilterType { @@ -175,7 +175,7 @@ export type TransitionalAppearance = BaseAppearance Planes.HIGHEST_EVER_PLANE) parent_plane = resolve_plane(parent_plane); if(plane < Planes.LOWEST_EVER_PLANE || plane > Planes.HIGHEST_EVER_PLANE) { plane = ((parent_plane + plane + 32767) << 16) >> 16; @@ -190,7 +190,10 @@ export namespace Appearance { * @returns TRUE if the plane value falls within the range of Byond lighting planes, FALSE if the plane is anything else */ export function is_lighting_plane(plane : number): boolean { - return (plane >= Planes.EMISSIVE_BLOCKER_PLANE && plane <= Planes.O_LIGHTING_VISUAL_PLANE) + if(plane < Planes.LOWEST_EVER_PLANE || plane > Planes.HIGHEST_EVER_PLANE) { + plane = plane % (Planes.HIGHEST_EVER_PLANE - Planes.LOWEST_EVER_PLANE); + } + return (plane >= Planes.LIGHTING_PLANE && plane <= Planes.LIGHT_MASK_PLANE) } export function get_appearance_parts(appearance : Appearance) { @@ -304,10 +307,10 @@ export namespace Appearance { } overlay.color_alpha = color_alpha; } - if(overlay.blend_mode == 0 && appearance.blend_mode > 0) { - clone(); - overlay.blend_mode = appearance.blend_mode; - } + // if(appearance.blend_mode == BlendMode.DEFAULT && overlay.blend_mode != BlendMode.DEFAULT) { + // clone(); + + // } if(overlay.plane < Planes.LOWEST_EVER_PLANE || overlay.plane > Planes.HIGHEST_EVER_PLANE) { clone(); overlay.plane = resolve_plane(overlay.plane, appearance.plane); @@ -442,7 +445,7 @@ export namespace ReaderAppearance { pixel_y: 0, pixel_z: 0, pixel_w: 0, - blend_mode: 0, + blend_mode: BlendMode.DEFAULT, glide_size: 8, screen_loc: null, transform: [1,0,0,0,1,0], diff --git a/src/misc/constants.ts b/src/misc/constants.ts index 04b2fa8..f0c58a5 100644 --- a/src/misc/constants.ts +++ b/src/misc/constants.ts @@ -36,24 +36,111 @@ export const SOUND_UPDATE = 16; export const MAX_LAYER : number = 32; +export const enum AppearanceAttributeIndex{ + TRANSFORMATION_MATRIX_3x3_A, + TRANSFORMATION_MATRIX_3x3_B, + TRANSFORMATION_MATRIX_3x3_C, + TRANSFORMATION_MATRIX_3x3_D, + TRANSFORMATION_MATRIX_3x3_E, + TRANSFORMATION_MATRIX_3x3_F, + + ICON_BOUND_X_1, + ICON_BOUND_Y_1, + ICON_BOUND_X_2, + ICON_BOUND_Y_2, + + ICON_LAYER, + //ICON_PLANE, + + COLOR_MATRIX_RED, + COLOR_MATRIX_RED_GREEN, + COLOR_MATRIX_RED_BLUE, + COLOR_MATRIX_RED_ALPHA, + + COLOR_MATRIX_GREEN_RED, + COLOR_MATRIX_GREEN, + COLOR_MATRIX_GREEN_BLUE, + COLOR_MATRIX_GREEN_ALPHA, + + COLOR_MATRIX_BLUE_RED, + COLOR_MATRIX_BLUE_GREEN, + COLOR_MATRIX_BLUE, + COLOR_MATRIX_BLUE_ALPHA, + + COLOR_MATRIX_ALPHA_RED, + COLOR_MATRIX_ALPHA_GREEN, + COLOR_MATRIX_ALPHA_BLUE, + COLOR_MATRIX_ALPHA_ALPHA, + + COLOR_MATRIX_COMPONENT_RED, + COLOR_MATRIX_COMPONENT_GREEN, + COLOR_MATRIX_COMPONENT_BLUE, + COLOR_MATRIX_COMPONENT_ALPHA, + + COLOR_RGBA_RED = 11, + COLOR_RGBA_GREEN, + COLOR_RGBA_BLUE, + COLOR_RGBA_ALPHA, +} ///All relevant planes used by Yogstation categorized into an enum of their values export const enum Planes{ - LOWEST_EVER_PLANE = -10000, - CLICKCATCHER_PLANE = -99, - SPACE_PLANE = -95, - FLOOR_PLANE = -2, - GAME_PLANE = -1, - BLACKNESS_PLANE = 0, - EMISSIVE_BLOCKER_PLANE = 12, + LOWEST_EVER_PLANE = -50, + + FIELD_OF_VISION_BLOCKER_PLANE = -45, + + CLICKCATCHER_PLANE = -40, + + PLANE_SPACE = -21, + PLANE_SPACE_PARALLAX = -20, + + GRAVITY_PULSE_PLANE = -12, + + RENDER_PLANE_TRANSPARENT = -11, + + TRANSPARENT_FLOOR_PLANE = -10, + + FLOOR_PLANE = -6, + WALL_PLANE = -5, + GAME_PLANE = -4, + ABOVE_GAME_PLANE = -3, + + SEETHROUGH_PLANE = -2, + RENDER_PLANE_GAME_WORLD = -1, + DEFAULT_PLANE = 0, + + AREA_PLANE = 2, + MASSIVE_OBJ_PLANE = 3, + GHOST_PLANE = 4, + POINT_PLANE = 5, + + LIGHTING_PLANE = 10, + O_LIGHTING_VISUAL_PLANE = 11, EMISSIVE_PLANE = 13, - EMISSIVE_UNBLOCKABLE_PLANE = 14, - LIGHTING_PLANE = 15, - O_LIGHTING_VISUAL_PLANE = 16, - FLOOR_OPENSPACE_PLANE = 17, - BYOND_LIGHTING_PLANE = 18, - CAMERA_STATIC_PLANE = 19, - HIGHEST_EVER_PLANE = 10000, + RENDER_PLANE_LIGHTING = 15, + LIGHT_MASK_PLANE = 16, + ABOVE_LIGHTING_PLANE = 17, + PIPECRAWL_IMAGES_PLANE = 20, + CAMERA_STATIC_PLANE = 21, + HIGH_GAME_PLANE = 22, + FULLSCREEN_PLANE = 23, + RUNECHAT_PLANE = 30, + BALLOON_CHAT_PLANE = 31, + HUD_PLANE = 35, + ABOVE_HUD_PLANE = 36, + SPLASHSCREEN_PLANE = 37, + RENDER_PLANE_GAME = 40, + RENDER_PLANE_GAME_MASKED = 41, + RENDER_PLANE_GAME_UNMASKED = 42, + RENDER_PLANE_NON_GAME = 45, + ESCAPE_MENU_PLANE = 46, + RENDER_PLANE_MASTER = 50, + HIGHEST_EVER_PLANE = RENDER_PLANE_MASTER, + + EMISSIVE_Z_BELOW_LAYER = 1, + EMISSIVE_FLOOR_LAYER = 2, + EMISSIVE_SPACE_LAYER = 3, + EMISSIVE_WALL_LAYER = 4, } export const enum SeeInvisibility{ @@ -62,4 +149,30 @@ export const enum SeeInvisibility{ SEE_INVISIBLE_OBSERVER = 60, INVISIBILITY_MAXIMUM = 100, INVISIBILITY_ABSTRACT = 101, +} + +export const enum BlendMode { + /** + * Also called BLEND_OVERLAY by Byond + */ + DEFAULT = 1, + ADD, + SUBTRACT, + MULTIPLY, + INSET_OVERLAY, + ALPHA, + ALPHA_INVERTED, +} + +export const enum RenderPlates{ + CANVAS, + GAME_PLATE, + UNMASKED_GAME_PLATE, + MASKED_GAME_PLATE, + TRANSPARENT_PLATE, + GAME_WORLD_PLATE, + LIGHTING_PLATE, + EMISSIVE_SLATE, + LIGHT_MASK_PLATE, + NON_GAME_PLATE, } \ No newline at end of file diff --git a/src/parser/binary_parser.ts b/src/parser/binary_parser.ts index 4805cae..821106a 100644 --- a/src/parser/binary_parser.ts +++ b/src/parser/binary_parser.ts @@ -1,5 +1,5 @@ -import { Filter, FilterType, ReaderAppearance } from "../misc/appearance"; -import { Planes, SOUND_MUTE, SOUND_PAUSED, SOUND_STREAM, SOUND_UPDATE } from "../misc/constants"; +import { Appearance, Filter, FilterType, ReaderAppearance } from "../misc/appearance"; +import { BlendMode, Planes, SOUND_MUTE, SOUND_PAUSED, SOUND_STREAM, SOUND_UPDATE } from "../misc/constants"; import { Matrix } from "../misc/matrix"; import { DemoParser, ReaderDemoAnimation, ReaderDemoAnimationFrame } from "./base_parser"; import { RevData } from "./interface"; @@ -542,14 +542,14 @@ export class DemoParserBinary extends DemoParser { if(filter) filters.push(filter); } // Uncomment this once filters are actually being used somewhere - //if(filters.length) appearance.filters = filters; + if(filters.length) appearance.filters = filters; } appearance.override = !!(daf & 0x40000000); if(daf & 0x80000000) { appearance.vis_flags = p.read_uint8(); } - if(appearance.plane == Planes.LIGHTING_PLANE && !appearance.screen_loc) appearance.blend_mode = 4; // This only exists because I CBA to implement plane masters right now + if(Appearance.is_lighting_plane(appearance.plane) && !appearance.screen_loc) appearance.blend_mode = BlendMode.MULTIPLY; // This only exists because I CBA to implement plane masters right now return this.appearance_refs[appearance_ref] = this.appearance_id(appearance); } else { if(appearance_ref == 0xFFFF) return null; diff --git a/src/parser/text_parser.ts b/src/parser/text_parser.ts index fe3491b..81eb91b 100644 --- a/src/parser/text_parser.ts +++ b/src/parser/text_parser.ts @@ -1,7 +1,7 @@ import { normalize_ref } from "../misc/ref"; -import { ReaderAppearance } from "../misc/appearance"; +import { Appearance, ReaderAppearance } from "../misc/appearance"; import { DemoParser } from "./base_parser"; -import { Planes } from "../misc/constants"; +import { BlendMode, Planes, SeeInvisibility } from "../misc/constants"; const MIN_VERSION = 1; const MAX_VERSION = 1; @@ -248,7 +248,7 @@ export class DemoParserText extends DemoParser { icon_state: `${(((x + y) ^ ~(x * y) + z) % 25 + 25) % 25}`, name: 'space', layer: 1.8, - plane: Planes.SPACE_PLANE, + plane: Planes.PLANE_SPACE, }; } @@ -268,7 +268,7 @@ export class DemoParserText extends DemoParser { icon_state: '1', name: 'space', layer: 1.8, - plane: Planes.SPACE_PLANE + plane: Planes.PLANE_SPACE }; if(p.curr() == 't') { p.idx++; @@ -357,8 +357,25 @@ export class DemoParserText extends DemoParser { if(p.read_next_or_end()) return appearance; if(p.curr() != ';') { appearance.plane = p.read_number(); - if(appearance.plane == Planes.LIGHTING_PLANE) appearance.blend_mode = 4; - else appearance.blend_mode = 0; + switch(appearance.plane){ + case Planes.EMISSIVE_PLANE: { + appearance.blend_mode = BlendMode.DEFAULT; + appearance.invisibility = SeeInvisibility.INVISIBILITY_ABSTRACT + break; + } + case Planes.LIGHTING_PLANE: { + appearance.blend_mode = BlendMode.MULTIPLY; + break; + } + case Planes.O_LIGHTING_VISUAL_PLANE: { + appearance.blend_mode = BlendMode.MULTIPLY; + break; + } + default: { + appearance.blend_mode = BlendMode.DEFAULT; + break; + } + } } if(p.read_next_or_end()) return appearance; if(p.curr() != ';') appearance.dir = p.read_number(); diff --git a/src/player/player.ts b/src/player/player.ts index 7e43e7f..73c869a 100644 --- a/src/player/player.ts +++ b/src/player/player.ts @@ -9,11 +9,12 @@ import { AtlasNode, DmiAtlas } from "./rendering/atlas"; import { IconState, IconStateDir } from "./rendering/icon"; import { CmdViewport, FollowDesc, RenderingCmd } from "./rendering/commands"; import { DrawBuffer } from "./rendering/buffer"; -import { LONG_GLIDE, Planes, RESET_ALPHA, RESET_COLOR, RESET_TRANSFORM, SEE_MOBS, SEE_OBJS, SEE_THRU, SEE_TURFS, SeeInvisibility } from "../misc/constants"; +import { BlendMode, LONG_GLIDE, Planes, RESET_ALPHA, RESET_COLOR, RESET_TRANSFORM, SEE_MOBS, SEE_OBJS, SEE_THRU, SEE_TURFS, SeeInvisibility } from "../misc/constants"; import { matrix_is_identity, matrix_multiply } from "../misc/matrix"; import { despam_promise } from "../misc/promise_despammer"; import { view_turfs } from "./view"; import { animate_appearance, appearance_interpolate } from "./rendering/animation"; +import { not_null } from "../misc/gl_util"; const empty_arr : [] = []; @@ -409,7 +410,7 @@ export class DemoPlayer { height: atom_bounds.height / 32, } }); - this.draw_object_list(drawing_commands, [atom], 101); + this.draw_object_list(drawing_commands, [atom], SeeInvisibility.INVISIBILITY_ABSTRACT); drawing_commands.push({cmd: "copytocanvas", canvas_index: i}); } drawing_commands.push({cmd: "flush"}); @@ -427,9 +428,10 @@ export class DemoPlayer { } draw_buffer = new DrawBuffer(); - draw_object_list(commands : RenderingCmd[], objects : Renderable[], see_invisible = 60, followview_window? : {x:number,y:number,width:number,height:number}|undefined) { + draw_object_list(commands : RenderingCmd[], objects : Renderable[], see_invisible = SeeInvisibility.SEE_INVISIBLE_OBSERVER, followview_window? : {x:number,y:number,width:number,height:number}|undefined) { for(let i = 0; i < objects.length; i++) { - objects.push(...objects[i].get_floating_overlays(this, see_invisible)) + let overlays_to_push = objects[i].get_floating_overlays(this, see_invisible); + objects.push(...overlays_to_push); } let buffer = this.draw_buffer; let buffer_index = 0; @@ -443,7 +445,6 @@ export class DemoPlayer { let b_layer = b_appearance?.layer ?? 0; return a_layer - b_layer; }); - for(let thing of objects) { let [x,y] = thing.get_offset(this); if(thing.is_screen_obj()) { @@ -462,13 +463,14 @@ export class DemoPlayer { } } if(appearance.icon_state_dir?.atlas_node) { - if(buffer.atlas != appearance.icon_state_dir.atlas_node?.atlas || (buffer.blend_mode || 1) != (appearance.blend_mode || 1) || buffer.uses_color_matrices != !!appearance.color_matrix) { + if(buffer.atlas != appearance.icon_state_dir.atlas_node?.atlas || Appearance.is_lighting_plane(buffer.plane) != Appearance.is_lighting_plane(appearance.plane) || buffer.uses_color_matrices != !!appearance.color_matrix) { if(buffer_index) { buffer.add_draw(commands, 0, buffer_index); buffer_index = 0; } buffer.atlas = appearance.icon_state_dir.atlas_node?.atlas as DmiAtlas; - buffer.blend_mode = appearance.blend_mode || 1; + buffer.blend_mode = appearance.blend_mode; + buffer.plane = appearance.plane; buffer.uses_color_matrices = !!appearance.color_matrix; } appearance.icon_state_dir.atlas_node.use_index = this.use_index; @@ -477,13 +479,14 @@ export class DemoPlayer { } if(appearance.maptext && appearance.maptext.maptext) { let node = this.get_maptext(appearance.maptext); - if(buffer.atlas != node.atlas || (buffer.blend_mode || 1) != (appearance.blend_mode || 1) || buffer.uses_color_matrices != !!appearance.color_matrix) { + if(buffer.atlas != node.atlas || Appearance.is_lighting_plane(buffer.plane) != Appearance.is_lighting_plane(appearance.plane) || buffer.uses_color_matrices != !!appearance.color_matrix) { if(buffer_index) { buffer.add_draw(commands, 0, buffer_index); buffer_index = 0; } buffer.atlas = node.atlas as DmiAtlas; - buffer.blend_mode = appearance.blend_mode || 1; + buffer.blend_mode = appearance.blend_mode; + buffer.plane = appearance.plane; buffer.uses_color_matrices = !!appearance.color_matrix; } node.use_index = this.use_index; @@ -976,6 +979,11 @@ export class DemoPlayer { this.see_invisible = vision_setting; this.change_counter++; } + + dump_textures() { + this.ui?.dump_textures(); + this.change_counter++; + } } export abstract class Renderable { @@ -1041,7 +1049,7 @@ export class Atom extends Renderable { get_click_target() : Atom|null {return this;} - get_appearance(player: DemoPlayer, see_invisible = 101, vis_contents_depth = 100): Appearance | null { + get_appearance(player: DemoPlayer, see_invisible = SeeInvisibility.INVISIBILITY_ABSTRACT, vis_contents_depth = 100): Appearance | null { if(vis_contents_depth <= 0) { console.warn(`Deep (possibly looping) vis_contents/images detected at [0x${this.ref.toString(16)}]. Pruning.`); this.vis_contents = empty_arr; @@ -1166,7 +1174,7 @@ export class OverlayProxy extends Renderable { get_offset(player:DemoPlayer) : [number,number] { return this.parent.get_offset(player); } - get_appearance(player: DemoPlayer, see_invisible = 101): Appearance | null { + get_appearance(player: DemoPlayer, see_invisible = SeeInvisibility.INVISIBILITY_ABSTRACT): Appearance | null { return this.appearance; } is_screen_obj(): boolean { diff --git a/src/player/rendering/atlas.ts b/src/player/rendering/atlas.ts index 58886ce..4d2da92 100644 --- a/src/player/rendering/atlas.ts +++ b/src/player/rendering/atlas.ts @@ -18,7 +18,7 @@ export class Atlas { return root.alloc(width, height); } expand() : void { - console.log("Resizing atlas to ", 1, this.size << 1); + console.log("Resizing atlas to ", this.size << 1); this.size_index++; let oldroot = this.root; let newroot = new AtlasNode(this, undefined, 0, 0, this.size_index); @@ -92,7 +92,7 @@ export class DmiAtlas extends Atlas { if(!from_node || !to_node) continue; if(!this.copy_within_command) { this.copy_within_command = { - cmd: "atlestexcopywithin", + cmd: "atlastexcopywithin", index: this.tex_index, parts: [] }; diff --git a/src/player/rendering/buffer.ts b/src/player/rendering/buffer.ts index 8fe780b..ddf51f6 100644 --- a/src/player/rendering/buffer.ts +++ b/src/player/rendering/buffer.ts @@ -1,5 +1,5 @@ import { Appearance } from "../../misc/appearance"; -import { MAX_LAYER } from "../../misc/constants"; +import { AppearanceAttributeIndex, BlendMode, MAX_LAYER, Planes } from "../../misc/constants"; import { Matrix, matrix_multiply, matrix_translate } from "../../misc/matrix"; import { DmiAtlas } from "./atlas"; import { RenderingCmd } from "./commands"; @@ -19,22 +19,25 @@ type AppearanceLike = { export class DrawBuffer { uses_color_matrices : boolean = false; - blend_mode : number = 1; + blend_mode : number = BlendMode.DEFAULT; atlas : DmiAtlas|undefined; + plane : number = Planes.GAME_PLANE; float_attribs : Float32Array = new Float32Array(1024 * this.get_stride()); write_appearance(index : number, appearance : AppearanceLike, step_x : number = 0, step_y : number = 0, node = appearance.icon_state_dir?.atlas_node) { let stride = this.get_stride(); + let off = index * stride; let fa = this.float_attribs; - fa[off] = 1; - fa[off+1] = 0; - fa[off+2] = 0; - fa[off+3] = 0; - fa[off+4] = 1; - fa[off+5] = 0; - let matrix_view = fa.subarray(off,off+6); + //https://www.byond.com/docs/ref/#/matrix + fa[off+AppearanceAttributeIndex.TRANSFORMATION_MATRIX_3x3_A] = 1; + fa[off+AppearanceAttributeIndex.TRANSFORMATION_MATRIX_3x3_B] = 0; + fa[off+AppearanceAttributeIndex.TRANSFORMATION_MATRIX_3x3_C] = 0; + fa[off+AppearanceAttributeIndex.TRANSFORMATION_MATRIX_3x3_D] = 0; + fa[off+AppearanceAttributeIndex.TRANSFORMATION_MATRIX_3x3_E] = 1; + fa[off+AppearanceAttributeIndex.TRANSFORMATION_MATRIX_3x3_F] = 0; + let matrix_view = fa.subarray(off,off+AppearanceAttributeIndex.ICON_BOUND_X_1); let icon_width = node?.width ?? 32; let icon_height = node?.height ?? 32; matrix_translate(matrix_view, -icon_width/2, -icon_height/2); @@ -42,52 +45,53 @@ export class DrawBuffer { matrix_translate(matrix_view, icon_width/2, icon_height/2); matrix_translate(matrix_view, step_x + (appearance?.pixel_x??0) + (appearance?.pixel_w??0), step_y + (appearance?.pixel_y??0) + (appearance?.pixel_z??0)); if(node) { - fa[off+6] = node.x; - fa[off+7] = node.y; - fa[off+8] = node.x+node.width; - fa[off+9] = node.y+node.height; + fa[off+AppearanceAttributeIndex.ICON_BOUND_X_1] = node.x; + fa[off+AppearanceAttributeIndex.ICON_BOUND_Y_1] = node.y; + fa[off+AppearanceAttributeIndex.ICON_BOUND_X_2] = node.x+node.width; + fa[off+AppearanceAttributeIndex.ICON_BOUND_Y_2] = node.y+node.height; } else { - fa[off+6] = 0; - fa[off+7] = 0; - fa[off+8] = 32; - fa[off+9] = 32; + fa[off+AppearanceAttributeIndex.ICON_BOUND_X_1] = 0; + fa[off+AppearanceAttributeIndex.ICON_BOUND_Y_1] = 0; + fa[off+AppearanceAttributeIndex.ICON_BOUND_X_2] = 32; + fa[off+AppearanceAttributeIndex.ICON_BOUND_Y_2] = 32; } - fa[off+10] = 1-Math.max(Math.min(appearance.layer / MAX_LAYER, 1), 0); + fa[off+AppearanceAttributeIndex.ICON_LAYER] = 1-Math.max(Math.min(appearance.layer / MAX_LAYER, 1), 0); let color_alpha = appearance.color_alpha ?? -1; if(this.uses_color_matrices) { if(appearance.color_matrix) { - fa.set(appearance.color_matrix, off+11); + fa.set(appearance.color_matrix, off+AppearanceAttributeIndex.COLOR_MATRIX_RED); } else { - fa[off+11] = (color_alpha & 0xFF) / 0xFF; - fa[off+12] = 0; - fa[off+13] = 0; - fa[off+14] = 0; + //https://www.byond.com/docs/ref/#/{notes}/color-matrix + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_RED ] = (color_alpha & 0xFF) / 0xFF; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_RED_GREEN ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_RED_BLUE ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_RED_ALPHA ] = 0; - fa[off+15] = 0; - fa[off+16] = ((color_alpha >> 8) & 0xFF) / 0xFF; - fa[off+17] = 0; - fa[off+18] = 0; - - fa[off+19] = 0; - fa[off+20] = 0; - fa[off+21] = ((color_alpha >> 16) & 0xFF) / 0xFF; - fa[off+22] = 0; - - fa[off+23] = 0; - fa[off+24] = 0; - fa[off+25] = 0; - fa[off+26] = ((color_alpha >> 24) & 0xFF) / 0xFF; - - fa[off+27] = 0; - fa[off+28] = 0; - fa[off+29] = 0; - fa[off+30] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_GREEN_RED ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_GREEN ] = ((color_alpha >> 8) & 0xFF) / 0xFF; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_GREEN_BLUE ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_GREEN_ALPHA ] = 0; + + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_BLUE_RED ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_BLUE_GREEN ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_BLUE ] = ((color_alpha >> 16) & 0xFF) / 0xFF; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_BLUE_ALPHA ] = 0; + + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_ALPHA_RED ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_ALPHA_GREEN ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_ALPHA_BLUE ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_ALPHA_ALPHA ] = ((color_alpha >> 24) & 0xFF) / 0xFF; + + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_COMPONENT_RED ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_COMPONENT_GREEN ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_COMPONENT_BLUE ] = 0; + fa[off + AppearanceAttributeIndex.COLOR_MATRIX_COMPONENT_ALPHA ] = 0; } } else { - fa[off+11] = (color_alpha & 0xFF) / 0xFF; - fa[off+12] = ((color_alpha >> 8) & 0xFF) / 0xFF; - fa[off+13] = ((color_alpha >> 16) & 0xFF) / 0xFF; - fa[off+14] = ((color_alpha >> 24) & 0xFF) / 0xFF; + fa[off+AppearanceAttributeIndex.COLOR_RGBA_RED ] = (color_alpha & 0xFF) / 0xFF; + fa[off+AppearanceAttributeIndex.COLOR_RGBA_GREEN] = ((color_alpha >> 8) & 0xFF) / 0xFF; + fa[off+AppearanceAttributeIndex.COLOR_RGBA_BLUE ] = ((color_alpha >> 16) & 0xFF) / 0xFF; + fa[off+AppearanceAttributeIndex.COLOR_RGBA_ALPHA] = ((color_alpha >> 24) & 0xFF) / 0xFF; } } @@ -107,10 +111,12 @@ export class DrawBuffer { if(start >= end) return; let stride = this.get_stride(); let slice = this.float_attribs.slice(start * stride, end * stride); + //console.log("draw buffer", this) out.push({ cmd: "batchdraw", atlas_index: this.atlas?.tex_index ?? 0, blend_mode: this.blend_mode, + plane: this.plane, use_color_matrix: this.uses_color_matrices, data: slice, transferables: [slice.buffer], diff --git a/src/player/rendering/commands.ts b/src/player/rendering/commands.ts index b0ba0ae..1bf109e 100644 --- a/src/player/rendering/commands.ts +++ b/src/player/rendering/commands.ts @@ -52,7 +52,7 @@ export interface AtlasTexMaptext extends BaseRenderingCmd { }[]; } export interface AtlasTexCopyWithin extends BaseRenderingCmd { - cmd: "atlestexcopywithin"; + cmd: "atlastexcopywithin"; index:number; parts: { x1: number; @@ -67,6 +67,7 @@ export interface CmdBatchDraw extends BaseRenderingCmd { cmd: "batchdraw"; atlas_index: number; blend_mode: number; + plane: number; use_color_matrix : boolean; data: Float32Array; num_elements: number;