commit f741a0c8e83ca7d5bcd70fb88de303ccc3ed5991 Author: wfz <1040079213@qq.com> Date: Sun Jul 26 14:41:36 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f00653 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +*.log +.backend.pid +.DS_Store +.env diff --git a/backend/dependency-reduced-pom.xml b/backend/dependency-reduced-pom.xml new file mode 100644 index 0000000..e6c7967 --- /dev/null +++ b/backend/dependency-reduced-pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + com.zombie + zombie-crisis-server + 1.0.0 + + + + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + org.projectlombok + lombok + 1.18.30 + + + + + + maven-jar-plugin + 3.3.0 + + + + com.zombie.game.GameServerMain + + + + + + maven-shade-plugin + 3.5.1 + + + package + + shade + + + + + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + + 17 + 17 + UTF-8 + + diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..f63a2f2 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + + com.zombie + zombie-crisis-server + 1.0.0 + jar + + + 17 + 17 + UTF-8 + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + org.java-websocket + Java-WebSocket + 1.5.4 + + + com.google.code.gson + gson + 2.10.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + org.slf4j + slf4j-simple + 2.0.9 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + org.projectlombok + lombok + 1.18.30 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + com.zombie.game.GameServerMain + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + + shade + + + + + + + diff --git a/backend/src/main/java/com/zombie/game/GameServerMain.java b/backend/src/main/java/com/zombie/game/GameServerMain.java new file mode 100644 index 0000000..b4b9baa --- /dev/null +++ b/backend/src/main/java/com/zombie/game/GameServerMain.java @@ -0,0 +1,34 @@ +package com.zombie.game; + +import com.zombie.game.server.GameWebSocketServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GameServerMain { + private static final Logger logger = LoggerFactory.getLogger(GameServerMain.class); + + public static void main(String[] args) throws Exception { + int port = 8080; + if (args.length > 0) { + try { + port = Integer.parseInt(args[0]); + } catch (NumberFormatException e) { + logger.warn("Invalid port number, using default: 8080"); + } + } + + GameWebSocketServer server = new GameWebSocketServer(port); + server.start(); + + logger.info("ZP2 Server started on port {}", port); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + logger.info("Shutting down server..."); + try { + server.stop(); + } catch (Exception e) { + logger.error("Error during shutdown", e); + } + })); + } +} diff --git a/backend/src/main/java/com/zombie/game/ecs/ECSWorld.java b/backend/src/main/java/com/zombie/game/ecs/ECSWorld.java new file mode 100644 index 0000000..58d5959 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/ECSWorld.java @@ -0,0 +1,133 @@ +package com.zombie.game.ecs; + +import com.zombie.game.ecs.components.*; +import com.zombie.game.model.Constants; +import com.zombie.game.model.GameMap; +import lombok.Getter; + +import java.util.*; + +/** ECS world that manages all entities, components, and systems / ECS世界,管理所有实体、组件和系统 */ +@Getter +public class ECSWorld { + private final Object lock = new Object(); + private final GameMap map; + private int nextEntityId; + private final Set entities = new LinkedHashSet<>(); + private final Set players = new LinkedHashSet<>(); + private final Set zombies = new LinkedHashSet<>(); + private final Map positions = new HashMap<>(); + private final Map healths = new HashMap<>(); + private final Map collisions = new HashMap<>(); + private final Map renderInfos = new HashMap<>(); + private final Map playerInputs = new HashMap<>(); + private final Map blocksMovements = new HashMap<>(); + private final Map movementCosts = new HashMap<>(); + private final Map zombieAIs = new HashMap<>(); + private final List systems = new ArrayList<>(); + private float gameTime; + private final Map playerIdToEntity = new HashMap<>(); + + public ECSWorld(String mapFilePath) { + this.map = new GameMap(mapFilePath); + this.nextEntityId = 0; + this.gameTime = 0; + + // Wire GameMap to ECS component maps + map.setComponentMaps(blocksMovements, movementCosts); + + // Create wall entities from map data + createWallsFromMapData(); + } + + /** Create static wall and nut wall entities from map JSON data */ + private void createWallsFromMapData() { + for (int[] pos : map.getStaticWallData()) { + createStaticWallEntity(pos[0], pos[1]); + } + for (int[] pos : map.getNutWallData()) { + createNutWallEntity(pos[0], pos[1]); + } + } + + /** Create a static wall entity (indestructible, blocks movement) */ + public int createStaticWallEntity(int gx, int gy) { + int entityId = createEntity(); + positions.put(entityId, new Position(gx + 0.5f, gy + 0.5f)); + blocksMovements.put(entityId, new BlocksMovement()); + map.registerWall(gx, gy, entityId); + return entityId; + } + + /** Create a nut wall entity (destructible, blocks movement with cost) */ + public int createNutWallEntity(int gx, int gy) { + int entityId = createEntity(); + positions.put(entityId, new Position(gx + 0.5f, gy + 0.5f)); + healths.put(entityId, new Health(Constants.NUT_WALL_MAX_HEALTH)); + blocksMovements.put(entityId, new BlocksMovement()); + movementCosts.put(entityId, new MovementCost(Constants.NUT_WALL_MOVEMENT_COST)); + map.registerWall(gx, gy, entityId); + return entityId; + } + + /** Create a zombie entity for testing pathfinding */ + public int createZombieEntity(float x, float y) { + int entityId = createEntity(); + positions.put(entityId, new Position(x, y)); + healths.put(entityId, new Health(100)); + collisions.put(entityId, new Collision(Constants.ZOMBIE_SIZE)); + renderInfos.put(entityId, new RenderInfo(RenderInfo.EntityType.ZOMBIE)); + zombieAIs.put(entityId, new ZombieAI()); + zombies.add(entityId); + return entityId; + } + + public int createEntity() { + int id = nextEntityId++; + entities.add(id); + return id; + } + + public void destroyEntity(int id) { + entities.remove(id); + players.remove(id); + zombies.remove(id); + positions.remove(id); + healths.remove(id); + collisions.remove(id); + renderInfos.remove(id); + playerInputs.remove(id); + blocksMovements.remove(id); + movementCosts.remove(id); + zombieAIs.remove(id); + } + + public int createPlayerEntity(String playerId, String name, float x, float y) { + int entityId = createEntity(); + positions.put(entityId, new Position(x, y)); + healths.put(entityId, new Health(100)); + collisions.put(entityId, new Collision(Constants.PLAYER_SIZE)); + renderInfos.put(entityId, new RenderInfo(RenderInfo.EntityType.PLAYER)); + playerInputs.put(entityId, new PlayerInput()); + players.add(entityId); + playerIdToEntity.put(playerId, entityId); + return entityId; + } + + public Integer getPlayerEntity(String playerId) { + return playerIdToEntity.get(playerId); + } + + public int[][] getMapData() { return map.getCells(); } + + public void update(float dt) { + gameTime += dt; + for (System system : systems) { + system.update(dt, this); + } + } + + public void addSystem(System system) { + systems.add(system); + } +} diff --git a/backend/src/main/java/com/zombie/game/ecs/System.java b/backend/src/main/java/com/zombie/game/ecs/System.java new file mode 100644 index 0000000..956ca0d --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/System.java @@ -0,0 +1,7 @@ +package com.zombie.game.ecs; + +/** Interface for ECS systems that process entities each frame / ECS系统接口,每帧处理实体 */ +public interface System { + // Updates the system with delta time and world reference / 使用增量时间和世界引用更新系统 + void update(float dt, ECSWorld world); +} diff --git a/backend/src/main/java/com/zombie/game/ecs/components/BlocksMovement.java b/backend/src/main/java/com/zombie/game/ecs/components/BlocksMovement.java new file mode 100644 index 0000000..4bf7b2c --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/components/BlocksMovement.java @@ -0,0 +1,5 @@ +package com.zombie.game.ecs.components; + +/** Tag component: entity blocks movement on its grid cell / 标记组件:实体在其网格格子上阻挡移动 */ +public class BlocksMovement { +} diff --git a/backend/src/main/java/com/zombie/game/ecs/components/Collision.java b/backend/src/main/java/com/zombie/game/ecs/components/Collision.java new file mode 100644 index 0000000..0698e0f --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/components/Collision.java @@ -0,0 +1,15 @@ +package com.zombie.game.ecs.components; + +import lombok.Data; + +/** Component storing collision size for entity hitboxes / 存储实体碰撞体大小的组件 */ +@Data +public class Collision { + // Collision radius/size for hitbox detection / 用于碰撞检测的碰撞体半径/大小 + private float size; + + // Constructor setting the collision size / 构造函数,设置碰撞体大小 + public Collision(float size) { + this.size = size; + } +} diff --git a/backend/src/main/java/com/zombie/game/ecs/components/Health.java b/backend/src/main/java/com/zombie/game/ecs/components/Health.java new file mode 100644 index 0000000..2b81970 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/components/Health.java @@ -0,0 +1,28 @@ +package com.zombie.game.ecs.components; + +import lombok.Data; + +/** Component storing entity health points / 存储实体生命值的组件 */ +@Data +public class Health { + // Current health value / 当前生命值 + private float health; + // Maximum health value / 最大生命值 + private float maxHealth; + + // Constructor setting initial and max health / 构造函数,设置初始和最大生命值 + public Health(float maxHealth) { + this.health = maxHealth; + this.maxHealth = maxHealth; + } + + // Checks if the entity is still alive / 检查实体是否仍然存活 + public boolean isAlive() { + return health > 0; + } + + // Resets health to maximum value / 将生命值重置为最大值 + public void reset() { + this.health = maxHealth; + } +} diff --git a/backend/src/main/java/com/zombie/game/ecs/components/MovementCost.java b/backend/src/main/java/com/zombie/game/ecs/components/MovementCost.java new file mode 100644 index 0000000..65fa344 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/components/MovementCost.java @@ -0,0 +1,14 @@ +package com.zombie.game.ecs.components; + +import lombok.Data; + +/** Component storing extra movement cost for flow field pathfinding / 存储流场寻路额外移动代价的组件 */ +@Data +public class MovementCost { + /** Extra cost added on top of base (1.0 for straight, 1.414 for diagonal) / 基础代价之外的额外代价 */ + private float cost; + + public MovementCost(float cost) { + this.cost = cost; + } +} diff --git a/backend/src/main/java/com/zombie/game/ecs/components/PlayerInput.java b/backend/src/main/java/com/zombie/game/ecs/components/PlayerInput.java new file mode 100644 index 0000000..5ce4523 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/components/PlayerInput.java @@ -0,0 +1,27 @@ +package com.zombie.game.ecs.components; + +import lombok.Data; + +/** Component storing player input state for movement and aiming / 存储玩家移动和瞄准输入状态的组件 */ +@Data +public class PlayerInput { + // Movement direction X component / 移动方向X分量 + private float dx; + // Movement direction Y component / 移动方向Y分量 + private float dy; + // Aim target X coordinate / 瞄准目标X坐标 + private float aimX; + // Aim target Y coordinate / 瞄准目标Y坐标 + private float aimY; + // Input sequence number for ordering / 用于排序的输入序列号 + private int seq; + + // Default constructor initializing all values to zero / 默认构造函数,将所有值初始化为零 + public PlayerInput() { + this.dx = 0; + this.dy = 0; + this.aimX = 0; + this.aimY = 0; + this.seq = 0; + } +} diff --git a/backend/src/main/java/com/zombie/game/ecs/components/Position.java b/backend/src/main/java/com/zombie/game/ecs/components/Position.java new file mode 100644 index 0000000..4c18b61 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/components/Position.java @@ -0,0 +1,42 @@ +package com.zombie.game.ecs.components; + +import lombok.Data; + +/** Component storing entity position and facing angle / 存储实体位置和朝向角度的组件 */ +@Data +public class Position { + // X coordinate / X坐标 + private float x; + // Y coordinate / Y坐标 + private float y; + // Facing angle in radians / 朝向角度(弧度) + private float angle; + + // Default constructor initializing to origin / 默认构造函数,初始化为原点 + public Position() { + this.x = 0; + this.y = 0; + this.angle = 0; + } + + // Constructor with position only / 仅带位置的构造函数 + public Position(float x, float y) { + this.x = x; + this.y = y; + this.angle = 0; + } + + // Constructor with position and angle / 带位置和角度的构造函数 + public Position(float x, float y, float angle) { + this.x = x; + this.y = y; + this.angle = angle; + } + + // Calculates Euclidean distance to a point / 计算到某点的欧几里得距离 + public float distanceTo(float px, float py) { + float dx = px - x; + float dy = py - y; + return (float) Math.sqrt(dx * dx + dy * dy); + } +} diff --git a/backend/src/main/java/com/zombie/game/ecs/components/RenderInfo.java b/backend/src/main/java/com/zombie/game/ecs/components/RenderInfo.java new file mode 100644 index 0000000..dd2813f --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/components/RenderInfo.java @@ -0,0 +1,21 @@ +package com.zombie.game.ecs.components; + +import lombok.Data; + +/** Component storing rendering information for an entity / 存储实体渲染信息的组件 */ +@Data +public class RenderInfo { + // Enum defining possible entity types / 定义可能实体类型的枚举 + public enum EntityType { + PLAYER, + ZOMBIE + } + + // The type of this entity / 此实体的类型 + private EntityType type; + + // Constructor setting the entity type / 构造函数,设置实体类型 + public RenderInfo(EntityType type) { + this.type = type; + } +} diff --git a/backend/src/main/java/com/zombie/game/ecs/components/ZombieAI.java b/backend/src/main/java/com/zombie/game/ecs/components/ZombieAI.java new file mode 100644 index 0000000..733359e --- /dev/null +++ b/backend/src/main/java/com/zombie/game/ecs/components/ZombieAI.java @@ -0,0 +1,26 @@ +package com.zombie.game.ecs.components; + +import lombok.Data; + +/** Zombie AI component storing pathfinding target and state / 僵尸AI组件,存储寻路目标和状态 */ +@Data +public class ZombieAI { + /** Current movement target X / 当前移动目标X */ + private float targetX; + /** Current movement target Y / 当前移动目标Y */ + private float targetY; + /** Whether zombie has an active target / 是否有活跃目标 */ + private boolean hasTarget; + /** Reserved grid X to prevent overlap / 预留网格X防止重叠 */ + private int reservedGridX = -1; + /** Reserved grid Y to prevent overlap / 预留网格Y防止重叠 */ + private int reservedGridY = -1; + /** Whether zombie holds a grid reservation / 是否持有网格预留 */ + private boolean reservation; + + public ZombieAI() { + this.targetX = 0; + this.targetY = 0; + this.hasTarget = false; + } +} diff --git a/backend/src/main/java/com/zombie/game/model/Constants.java b/backend/src/main/java/com/zombie/game/model/Constants.java new file mode 100644 index 0000000..42a1a91 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/model/Constants.java @@ -0,0 +1,56 @@ +package com.zombie.game.model; + +/** Game constants / 游戏常量 */ +public class Constants { + // Grid cell size in pixels / 网格单元格大小(像素) + public static final int GRID_SIZE = 32; + // Tick rate per second / 每秒逻辑帧率 + public static final int TICK_RATE = 30; + // Duration of one tick in seconds / 每帧持续时间(秒) + public static final float TICK_INTERVAL = 1.0f / TICK_RATE; + // Player collision size / 玩家碰撞体积 + public static final float PLAYER_SIZE = 0.8f; + // Player movement speed / 玩家移动速度 + public static final float PLAYER_SPEED = 5.0f; + // Zombie collision size / 僵尸碰撞体积 + public static final float ZOMBIE_SIZE = 0.8f; + // Zombie movement speed / 僵尸移动速度 + public static final float ZOMBIE_SPEED = 2.0f; + // Nut wall max health / 坚果墙最大生命值 + public static final float NUT_WALL_MAX_HEALTH = 500.0f; + // Nut wall extra movement cost for flow field / 坚果墙流场额外移动代价 + public static final float NUT_WALL_MOVEMENT_COST = 20.0f; + // Grid center threshold for retarget / 到达网格中心后重新选方向的阈值 + public static final float GRID_CENTER_THRESHOLD = 0.15f; + // Zombie spawn interval in seconds / 僵尸刷新间隔(秒) + public static final float ZOMBIE_SPAWN_INTERVAL = 3.0f; + // Max zombie count / 最大僵尸数量 + public static final int ZOMBIE_MAX_COUNT = 20; + + // Create room message type / 创建房间消息类型 + public static final String MSG_CREATE_ROOM = "create_room"; + // Join room message type / 加入房间消息类型 + public static final String MSG_JOIN_ROOM = "join_room"; + // Leave room message type / 离开房间消息类型 + public static final String MSG_LEAVE_ROOM = "leave_room"; + // Room list message type / 房间列表消息类型 + public static final String MSG_ROOM_LIST = "room_list"; + // Room state message type / 房间状态消息类型 + public static final String MSG_ROOM_STATE = "room_state"; + // Ready state message type / 准备状态消息类型 + public static final String MSG_READY = "ready"; + // Start game message type / 开始游戏消息类型 + public static final String MSG_START_GAME = "start_game"; + // Game started notification / 游戏已开始通知 + public static final String MSG_GAME_STARTED = "game_started"; + // Player input message type / 玩家输入消息类型 + public static final String MSG_PLAYER_INPUT = "player_input"; + // Game state broadcast / 游戏状态广播 + public static final String MSG_GAME_STATE = "game_state"; + // Player joined notification / 玩家加入通知 + public static final String MSG_PLAYER_JOIN = "player_join"; + // Player left notification / 玩家离开通知 + public static final String MSG_PLAYER_LEAVE = "player_leave"; + // Error message type / 错误消息类型 + public static final String MSG_ERROR = "error"; +} diff --git a/backend/src/main/java/com/zombie/game/model/FlowField.java b/backend/src/main/java/com/zombie/game/model/FlowField.java new file mode 100644 index 0000000..5216f60 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/model/FlowField.java @@ -0,0 +1,194 @@ +package com.zombie.game.model; + +import com.zombie.game.ecs.components.BlocksMovement; +import com.zombie.game.ecs.components.MovementCost; + +import java.util.*; + +/** Flow field navigation using Dijkstra with ECS component-based wall queries / 基于ECS组件查询的Dijkstra流场导航 */ +public class FlowField { + private final int width; + private final int height; + private float[][] distanceField; + private float[][] flowFieldX; + private float[][] flowFieldY; + private boolean valid; + + public FlowField(int width, int height) { + this.width = width; + this.height = height; + this.distanceField = new float[height][width]; + this.flowFieldX = new float[height][width]; + this.flowFieldY = new float[height][width]; + this.valid = false; + } + + /** + * Update flow field based on player positions and ECS wall components. + * @param wallGrid spatial index "x,y" -> entityId + * @param blocksMovements blocks-movement component map + * @param movementCosts movement-cost component map + * @param playerPositions list of [x, y] player positions + */ + public void update(Map wallGrid, + Map blocksMovements, + Map movementCosts, + List playerPositions) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + distanceField[y][x] = Float.MAX_VALUE; + } + } + + PriorityQueue queue = new PriorityQueue<>(Comparator.comparingDouble(n -> n.dist)); + + for (float[] pos : playerPositions) { + int px = (int) Math.floor(pos[0]); + int py = (int) Math.floor(pos[1]); + if (px >= 0 && px < width && py >= 0 && py < height) { + if (!isBlocked(wallGrid, blocksMovements, px, py)) { + distanceField[py][px] = 0; + queue.add(new Node(px, py, 0)); + } + } + } + + int[][] dirs = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {1, -1}, {-1, 1}, {1, 1}}; + + while (!queue.isEmpty()) { + Node current = queue.poll(); + int cx = current.x; + int cy = current.y; + float currentDist = current.dist; + + if (currentDist > distanceField[cy][cx]) continue; + + for (int[] dir : dirs) { + int nx = cx + dir[0]; + int ny = cy + dir[1]; + + if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; + + // Diagonal: check corner blocking + if (dir[0] != 0 && dir[1] != 0) { + if (isBlocked(wallGrid, blocksMovements, cx + dir[0], cy) || + isBlocked(wallGrid, blocksMovements, cx, cy + dir[1])) { + continue; + } + } + + float moveCost = getMovementCost(wallGrid, blocksMovements, movementCosts, nx, ny, dir); + if (moveCost == Float.MAX_VALUE) continue; + + float newDist = currentDist + moveCost; + + if (newDist < distanceField[ny][nx]) { + distanceField[ny][nx] = newDist; + queue.add(new Node(nx, ny, newDist)); + } + } + } + + // Compute flow directions + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (isBlocked(wallGrid, blocksMovements, x, y) || distanceField[y][x] == Float.MAX_VALUE) { + flowFieldX[y][x] = 0; + flowFieldY[y][x] = 0; + continue; + } + + float bestDist = distanceField[y][x]; + float bestDirX = 0; + float bestDirY = 0; + + for (int[] dir : dirs) { + int nx = x + dir[0]; + int ny = y + dir[1]; + + if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; + if (isBlocked(wallGrid, blocksMovements, nx, ny)) continue; + + if (dir[0] != 0 && dir[1] != 0) { + if (isBlocked(wallGrid, blocksMovements, x + dir[0], y) || + isBlocked(wallGrid, blocksMovements, x, y + dir[1])) { + continue; + } + } + + if (distanceField[ny][nx] < bestDist) { + bestDist = distanceField[ny][nx]; + float len = (float) Math.sqrt(dir[0] * dir[0] + dir[1] * dir[1]); + bestDirX = dir[0] / len; + bestDirY = dir[1] / len; + } + } + + flowFieldX[y][x] = bestDirX; + flowFieldY[y][x] = bestDirY; + } + } + + valid = true; + } + + /** Get flow direction at world position / 获取世界坐标处的流场方向 */ + public float[] getDirection(float wx, float wy) { + int gx = (int) Math.floor(wx); + int gy = (int) Math.floor(wy); + if (gx < 0 || gx >= width || gy < 0 || gy >= height) { + return new float[]{0, 0}; + } + return new float[]{flowFieldX[gy][gx], flowFieldY[gy][gx]}; + } + + /** Get distance to nearest player at world position / 获取世界坐标处到最近玩家的距离 */ + public float getDistance(float wx, float wy) { + int gx = (int) Math.floor(wx); + int gy = (int) Math.floor(wy); + if (gx < 0 || gx >= width || gy < 0 || gy >= height) { + return Float.MAX_VALUE; + } + return distanceField[gy][gx]; + } + + public boolean isValid() { return valid; } + public void invalidate() { valid = false; } + + private boolean isBlocked(Map wallGrid, + Map blocksMovements, + int gx, int gy) { + if (gx < 0 || gx >= width || gy < 0 || gy >= height) return true; + Integer entityId = wallGrid.get(gx + "," + gy); + if (entityId == null) return false; + return blocksMovements.containsKey(entityId); + } + + private float getMovementCost(Map wallGrid, + Map blocksMovements, + Map movementCosts, + int gx, int gy, int[] dir) { + float baseCost = (dir[0] != 0 && dir[1] != 0) ? 1.414f : 1.0f; + Integer entityId = wallGrid.get(gx + "," + gy); + if (entityId == null) return baseCost; + // Completely blocked (no MovementCost but has BlocksMovement) + if (blocksMovements.containsKey(entityId) && !movementCosts.containsKey(entityId)) { + return Float.MAX_VALUE; + } + MovementCost mc = movementCosts.get(entityId); + if (mc != null) { + return baseCost + mc.getCost(); + } + return baseCost; + } + + private static class Node { + int x, y; + float dist; + Node(int x, int y, float dist) { + this.x = x; + this.y = y; + this.dist = dist; + } + } +} diff --git a/backend/src/main/java/com/zombie/game/model/GameMap.java b/backend/src/main/java/com/zombie/game/model/GameMap.java new file mode 100644 index 0000000..342a910 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/model/GameMap.java @@ -0,0 +1,197 @@ +package com.zombie.game.model; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zombie.game.ecs.components.BlocksMovement; +import com.zombie.game.ecs.components.MovementCost; +import lombok.Getter; + +import java.io.File; +import java.util.*; + +/** Game map: spatial index for wall entities, flow field, and spawn points / 游戏地图:墙体实体空间索引、流场和出生点 */ +@Getter +public class GameMap { + private final int[][] cells; + private final int width; + private final int height; + /** Spatial index: "x,y" -> wall entity ID / 空间索引:"x,y" -> 墙体实体ID */ + private final Map wallGrid = new HashMap<>(); + private final List spawnPoints; + /** Raw static wall positions loaded from JSON / 从JSON加载的静态墙位置 */ + private final List staticWallData = new ArrayList<>(); + /** Raw nut wall positions loaded from JSON / 从JSON加载的坚果墙位置 */ + private final List nutWallData = new ArrayList<>(); + /** Flow field for zombie pathfinding / 僵尸寻路流场 */ + private final FlowField flowField; + + // References to ECS component maps, set by ECSWorld after construction + private Map blocksMovements; + private Map movementCosts; + + public GameMap(String mapFilePath) { + JsonNode root = loadMapFile(mapFilePath); + this.width = root.get("width").asInt(); + this.height = root.get("height").asInt(); + this.cells = new int[height][width]; + this.spawnPoints = new ArrayList<>(); + this.flowField = new FlowField(width, height); + loadFromJson(root); + } + + /** Set ECS component map references (called by ECSWorld) */ + public void setComponentMaps(Map bm, Map mc) { + this.blocksMovements = bm; + this.movementCosts = mc; + } + + /** Register a wall entity in the spatial index */ + public void registerWall(int gx, int gy, int entityId) { + wallGrid.put(key(gx, gy), entityId); + } + + /** Unregister a wall entity and invalidate flow field */ + public void unregisterWall(int gx, int gy) { + wallGrid.remove(key(gx, gy)); + flowField.invalidate(); + } + + /** Check if grid cell blocks movement / 检查网格格子是否阻挡移动 */ + public boolean isWall(int gx, int gy) { + if (gx < 0 || gx >= width || gy < 0 || gy >= height) return true; + Integer entityId = wallGrid.get(key(gx, gy)); + if (entityId == null) return false; + return blocksMovements != null && blocksMovements.containsKey(entityId); + } + + /** Check if world-space area is walkable / 检查世界坐标区域是否可行走 */ + public boolean isWalkable(float wx, float wy, float size) { + float half = size / 2; + float[][] corners = { + {wx - half, wy - half}, {wx + half, wy - half}, + {wx - half, wy + half}, {wx + half, wy + half} + }; + for (float[] c : corners) { + int gx = (int) Math.floor(c[0]); + int gy = (int) Math.floor(c[1]); + if (isWall(gx, gy)) return false; + } + return true; + } + + /** Update flow field with current player positions */ + public void updateFlowField(List playerPositions) { + flowField.update(wallGrid, blocksMovements, movementCosts, playerPositions); + } + + /** Get flow direction at world position */ + public float[] getFlowDirection(float wx, float wy) { + return flowField.getDirection(wx, wy); + } + + /** Check if flow field is valid */ + public boolean isFlowFieldValid() { + return flowField.isValid(); + } + + /** Get cell grid with wall info (for sync) / 获取包含墙壁信息的格子网格 */ + public int[][] getCells() { + int[][] result = new int[height][width]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + result[y][x] = cells[y][x]; + } + } + // Mark wall cells from spatial index + for (Map.Entry entry : wallGrid.entrySet()) { + String[] parts = entry.getKey().split(","); + int x = Integer.parseInt(parts[0]); + int y = Integer.parseInt(parts[1]); + if (x >= 0 && x < width && y >= 0 && y < height) { + Integer entityId = entry.getValue(); + if (blocksMovements != null && blocksMovements.containsKey(entityId)) { + // Check if it's a nut wall (has MovementCost) or static wall + if (movementCosts != null && movementCosts.containsKey(entityId)) { + result[y][x] = 4; // nut wall + } else { + result[y][x] = 1; // static wall + } + } + } + } + return result; + } + + /** Get player spawn points */ + public List getSpawnPoints() { + return spawnPoints; + } + + // --- Private --- + + private JsonNode loadMapFile(String mapFilePath) { + try { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(new File(mapFilePath)); + } catch (Exception e) { + throw new RuntimeException("Failed to load map file: " + mapFilePath, e); + } + } + + private void loadFromJson(JsonNode root) { + JsonNode wallsNode = root.get("walls"); + if (wallsNode != null && wallsNode.isArray()) { + for (JsonNode wallNode : wallsNode) { + int x = (int) wallNode.get("x").asDouble(); + int y = (int) wallNode.get("y").asDouble(); + String type = wallNode.get("type").asText(); + if (x < 0 || x >= width || y < 0 || y >= height) continue; + if ("static".equals(type)) { + staticWallData.add(new int[]{x, y}); + } else if ("nut".equals(type)) { + nutWallData.add(new int[]{x, y}); + } + } + } + + JsonNode playerSpawns = root.get("playerSpawns"); + if (playerSpawns != null && playerSpawns.isArray()) { + for (JsonNode spawn : playerSpawns) { + int x = spawn.get("x").asInt(); + int y = spawn.get("y").asInt(); + if (x >= 0 && x < width && y >= 0 && y < height) { + cells[y][x] = 2; + spawnPoints.add(new int[]{x, y}); + } + } + } + + JsonNode zombieSpawns = root.get("zombieSpawns"); + if (zombieSpawns != null && zombieSpawns.isArray()) { + for (JsonNode spawn : zombieSpawns) { + int x = spawn.get("x").asInt(); + int y = spawn.get("y").asInt(); + if (x >= 0 && x < width && y >= 0 && y < height) { + cells[y][x] = 3; + } + } + } + } + + /** Get zombie spawn points from cells / 从格子中获取僵尸出生点 */ + public List getZombieSpawnPoints() { + List points = new ArrayList<>(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (cells[y][x] == 3) { + points.add(new int[]{x, y}); + } + } + } + return points; + } + + private static String key(int x, int y) { + return x + "," + y; + } +} diff --git a/backend/src/main/java/com/zombie/game/model/MapData.java b/backend/src/main/java/com/zombie/game/model/MapData.java new file mode 100644 index 0000000..db5665d --- /dev/null +++ b/backend/src/main/java/com/zombie/game/model/MapData.java @@ -0,0 +1,144 @@ +package com.zombie.game.model; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; + +/** Serializable map data for storage or network transfer / 可序列化的地图数据,用于存储或网络传输 */ +public class MapData { + // Map unique identifier / 地图唯一标识 + private String id; + // Map display name / 地图显示名称 + private String name; + // Map width in grid units / 地图宽度(网格单位) + private int width; + // Map height in grid units / 地图高度(网格单位) + private int height; + // List of wall data maps / 墙壁数据映射列表 + private List> walls; + // List of player spawn coordinates / 玩家出生点坐标列表 + private List> playerSpawns; + // List of zombie spawn coordinates / 僵尸出生点坐标列表 + private List> zombieSpawns; + + // Default constructor / 默认构造函数 + public MapData() { + this.walls = new ArrayList<>(); + this.playerSpawns = new ArrayList<>(); + this.zombieSpawns = new ArrayList<>(); + } + + // Full constructor with all fields / 全字段构造函数 + public MapData(String id, String name, int width, int height, + List> walls, + List> playerSpawns, + List> zombieSpawns) { + this.id = id; + this.name = name; + this.width = width; + this.height = height; + this.walls = walls; + this.playerSpawns = playerSpawns; + this.zombieSpawns = zombieSpawns; + } + + // Get map ID / 获取地图ID + public String getId() { return id; } + // Set map ID / 设置地图ID + public void setId(String id) { this.id = id; } + // Get map name / 获取地图名称 + public String getName() { return name; } + // Set map name / 设置地图名称 + public void setName(String name) { this.name = name; } + // Get map width / 获取地图宽度 + public int getWidth() { return width; } + // Set map width / 设置地图宽度 + public void setWidth(int width) { this.width = width; } + // Get map height / 获取地图高度 + public int getHeight() { return height; } + // Set map height / 设置地图高度 + public void setHeight(int height) { this.height = height; } + // Get walls list / 获取墙壁列表 + public List> getWalls() { return walls; } + // Set walls list / 设置墙壁列表 + public void setWalls(List> walls) { this.walls = walls; } + // Get player spawns / 获取玩家出生点 + public List> getPlayerSpawns() { return playerSpawns; } + // Set player spawns / 设置玩家出生点 + public void setPlayerSpawns(List> playerSpawns) { this.playerSpawns = playerSpawns; } + // Get zombie spawns / 获取僵尸出生点 + public List> getZombieSpawns() { return zombieSpawns; } + // Set zombie spawns / 设置僵尸出生点 + public void setZombieSpawns(List> zombieSpawns) { this.zombieSpawns = zombieSpawns; } + + // Convert this map data to a 2D cell array / 将此地图数据转换为二维格子数组 + public int[][] toCells() { + int[][] cells = new int[height][width]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + cells[y][x] = 0; + } + } + // Mark wall cells / 标记墙壁格子 + for (Map wall : walls) { + int x = (Integer) wall.get("x"); + int y = (Integer) wall.get("y"); + if (x >= 0 && x < width && y >= 0 && y < height) { + cells[y][x] = 1; + } + } + // Mark player spawn cells / 标记玩家出生点格子 + for (Map spawn : playerSpawns) { + int x = spawn.get("x"); + int y = spawn.get("y"); + if (x >= 0 && x < width && y >= 0 && y < height) { + cells[y][x] = 2; + } + } + // Mark zombie spawn cells / 标记僵尸出生点格子 + for (Map spawn : zombieSpawns) { + int x = spawn.get("x"); + int y = spawn.get("y"); + if (x >= 0 && x < width && y >= 0 && y < height) { + cells[y][x] = 3; + } + } + return cells; + } + + // Create MapData from a GameMap instance / 从GameMap实例创建MapData + public static MapData fromGameMap(String id, String name, GameMap map) { + List> walls = new ArrayList<>(); + List> playerSpawns = new ArrayList<>(); + List> zombieSpawns = new ArrayList<>(); + + // Convert static walls from raw data + for (int[] pos : map.getStaticWallData()) { + Map wallData = new HashMap<>(); + wallData.put("x", pos[0]); + wallData.put("y", pos[1]); + wallData.put("type", "static"); + walls.add(wallData); + } + + // Convert nut walls from raw data + for (int[] pos : map.getNutWallData()) { + Map wallData = new HashMap<>(); + wallData.put("x", pos[0]); + wallData.put("y", pos[1]); + wallData.put("type", "nut"); + walls.add(wallData); + } + + // Convert player spawn points + for (int[] spawn : map.getSpawnPoints()) { + Map sp = new HashMap<>(); + sp.put("x", spawn[0]); + sp.put("y", spawn[1]); + playerSpawns.add(sp); + } + + return new MapData(id, name, map.getWidth(), map.getHeight(), walls, playerSpawns, zombieSpawns); + } +} diff --git a/backend/src/main/java/com/zombie/game/model/PlayerInfo.java b/backend/src/main/java/com/zombie/game/model/PlayerInfo.java new file mode 100644 index 0000000..4c098db --- /dev/null +++ b/backend/src/main/java/com/zombie/game/model/PlayerInfo.java @@ -0,0 +1,26 @@ +package com.zombie.game.model; + +import lombok.Getter; + +/** Player information within a room / 房间内的玩家信息 */ +@Getter +public class PlayerInfo { + // Player unique identifier / 玩家唯一标识 + private final String id; + // Player display name / 玩家显示名称 + private final String name; + // Whether the player is ready / 玩家是否已准备 + private boolean ready; + + // Create a new player info / 创建新的玩家信息 + public PlayerInfo(String id, String name) { + this.id = id; + this.name = name; + this.ready = false; + } + + // Set the ready state / 设置准备状态 + public void setReady(boolean ready) { + this.ready = ready; + } +} diff --git a/backend/src/main/java/com/zombie/game/model/Room.java b/backend/src/main/java/com/zombie/game/model/Room.java new file mode 100644 index 0000000..9ab3d3d --- /dev/null +++ b/backend/src/main/java/com/zombie/game/model/Room.java @@ -0,0 +1,106 @@ +package com.zombie.game.model; + +import lombok.Getter; +import java.util.*; + +import static com.zombie.game.model.Constants.*; + +/** Game room holding players and game state / 游戏房间,管理玩家和游戏状态 */ +@Getter +public class Room { + // Room unique identifier / 房间唯一标识 + private String id; + // Host player ID / 房主玩家ID + private String hostId; + // Map of playerId to PlayerInfo / 玩家ID到玩家信息的映射 + private Map players; + // Whether the game has started / 游戏是否已开始 + private boolean gameStarted; + // Maximum number of players allowed / 最大玩家数量 + private final int maxPlayers = 4; + + // Set game started flag / 设置游戏开始标志 + public void setGameStarted(boolean started) { this.gameStarted = started; } + + // Create a new room with the host player / 创建新房间并添加房主 + public Room(String id, String hostId, String hostName) { + this.id = id; + this.hostId = hostId; + this.players = new LinkedHashMap<>(); + players.put(hostId, new PlayerInfo(hostId, hostName)); + } + + // Add a player to the room / 添加玩家到房间 + public boolean addPlayer(String playerId, String playerName) { + if (players.size() >= maxPlayers) return false; + if (players.containsKey(playerId)) return false; + players.put(playerId, new PlayerInfo(playerId, playerName)); + return true; + } + + // Remove a player from the room / 从房间移除玩家 + public void removePlayer(String playerId) { + players.remove(playerId); + } + + // Get player by ID / 根据ID获取玩家 + public PlayerInfo getPlayer(String playerId) { + return players.get(playerId); + } + + // Get all players / 获取所有玩家 + public Collection getPlayers() { + return players.values(); + } + + // Get current player count / 获取当前玩家数量 + public int getPlayerCount() { + return players.size(); + } + + // Check if a player is the host / 检查玩家是否为房主 + public boolean isHost(String playerId) { + return hostId.equals(playerId); + } + + // Check if all non-host players are ready / 检查所有非房主玩家是否已准备 + public boolean allReady() { + for (PlayerInfo p : players.values()) { + if (!p.getId().equals(hostId) && !p.isReady()) return false; + } + return true; + } + + // Convert room state to a map for a specific player / 将房间状态转换为指定玩家的Map + public Map toStateMap(String playerId) { + Map map = new LinkedHashMap<>(); + map.put("roomId", id); + map.put("hostId", hostId); + map.put("isHost", hostId.equals(playerId)); + map.put("playerId", playerId); + + // Build player list with index / 构建带索引的玩家列表 + List> playerList = new ArrayList<>(); + int index = 0; + for (PlayerInfo p : players.values()) { + Map pm = new LinkedHashMap<>(); + pm.put("id", p.getId()); + pm.put("name", p.getName()); + pm.put("ready", p.isReady()); + pm.put("index", index++); + playerList.add(pm); + } + map.put("players", playerList); + return map; + } + + // Convert to a lightweight room list entry / 转换为轻量级房间列表条目 + public Map toRoomListMap() { + Map map = new LinkedHashMap<>(); + map.put("id", id); + PlayerInfo host = players.get(hostId); + map.put("hostName", host != null ? host.getName() : "Unknown"); + map.put("playerCount", players.size()); + return map; + } +} diff --git a/backend/src/main/java/com/zombie/game/server/GameLoop.java b/backend/src/main/java/com/zombie/game/server/GameLoop.java new file mode 100644 index 0000000..0b90e00 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/server/GameLoop.java @@ -0,0 +1,73 @@ +package com.zombie.game.server; + +import com.zombie.game.ecs.ECSWorld; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Game loop running at fixed tick rate for a single room / 以固定帧率运行的游戏循环,为单个房间服务 + */ +public class GameLoop { + // Room ID this loop serves / 该循环服务的房间ID + private String roomId; + // The ECS world being updated / 正在更新的ECS世界 + private ECSWorld world; + // Callback to broadcast state each tick / 每帧广播状态的回调 + private GameService.GameStateBroadcast broadcaster; + // Whether the loop is currently running / 循环当前是否正在运行 + private volatile boolean running; + // Target tick rate (30 ticks per second) / 目标帧率(每秒30帧) + private static final int TICK_RATE = 30; + // Interval between ticks in milliseconds / 帧间隔(毫秒) + private static final long TICK_INTERVAL_MS = 1000 / TICK_RATE; + // Interval between ticks in seconds / 帧间隔(秒) + private static final float TICK_INTERVAL_SEC = 1.0f / TICK_RATE; + // Scheduler for running the loop / 运行循环的调度器 + private ScheduledExecutorService scheduler; + + // Constructor: sets up the game loop for a room / 构造函数:为房间设置游戏循环 + public GameLoop(String roomId, ECSWorld world, GameService.GameStateBroadcast broadcaster) { + this.roomId = roomId; + this.world = world; + this.broadcaster = broadcaster; + this.running = true; + // Create single-threaded daemon scheduler / 创建单线程守护调度器 + this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "GameLoop-" + roomId); + t.setDaemon(true); + return t; + }); + } + + // Start the game loop / 启动游戏循环 + public void start() { + scheduler.scheduleAtFixedRate(this::tick, 0, TICK_INTERVAL_MS, TimeUnit.MILLISECONDS); + } + + // Single tick: update world and broadcast state / 单帧:更新世界并广播状态 + private void tick() { + if (!running) return; + synchronized (world.getLock()) { + world.update(TICK_INTERVAL_SEC); + } + broadcaster.broadcast(roomId, world); + } + + // Stop the game loop / 停止游戏循环 + public void stop() { + running = false; + if (scheduler != null && !scheduler.isShutdown()) { + scheduler.shutdown(); + try { + // Force shutdown if graceful shutdown fails / 优雅关闭失败则强制关闭 + if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + } + } + } +} diff --git a/backend/src/main/java/com/zombie/game/server/GameService.java b/backend/src/main/java/com/zombie/game/server/GameService.java new file mode 100644 index 0000000..ac43691 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/server/GameService.java @@ -0,0 +1,163 @@ +package com.zombie.game.server; + +import com.zombie.game.ecs.ECSWorld; +import com.zombie.game.ecs.components.*; +import com.zombie.game.model.*; +import com.zombie.game.systems.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Core game service managing game lifecycle and state / 核心游戏服务,管理游戏生命周期和状态 + */ +public class GameService { + // Logger for game events / 游戏事件日志记录器 + private static final Logger logger = LoggerFactory.getLogger(GameService.class); + + // Active game worlds mapped by room ID / 活跃的游戏世界,以房间ID映射 + private final Map activeGames = new ConcurrentHashMap<>(); + // Game loops mapped by room ID / 游戏循环,以房间ID映射 + private final Map gameLoops = new ConcurrentHashMap<>(); + // Callback for broadcasting game state / 广播游戏状态的回调 + private final GameStateBroadcast broadcaster; + + // Interface for broadcasting game state to clients / 向客户端广播游戏状态的接口 + public interface GameStateBroadcast { + void broadcast(String roomId, ECSWorld world); + } + + // Constructor with broadcast callback / 带广播回调的构造函数 + public GameService(GameStateBroadcast broadcaster) { + this.broadcaster = broadcaster; + } + + // Start a new game for the given room / 为指定房间启动新游戏 + public Map> startGame(Room room) { + // Load map from system property or default / 从系统属性或默认路径加载地图 + String mapPath = System.getProperty("map.path", "maps/d540209a.json"); + ECSWorld world = new ECSWorld(mapPath); + + // Add systems in order: input -> flow field -> zombie spawn -> zombie movement + world.addSystem(new PlayerInputSystem()); + world.addSystem(new FlowFieldUpdateSystem()); + world.addSystem(new ZombieSpawnSystem()); + world.addSystem(new ZombieMovementSystem()); + + // Spawn players at map spawn points / 在地图出生点生成玩家 + int index = 0; + List spawnPoints = world.getMap().getSpawnPoints(); + + for (PlayerInfo playerInfo : room.getPlayers()) { + int[] sp = spawnPoints.get(index % spawnPoints.size()); + float wx = sp[0] + 0.5f; + float wy = sp[1] + 0.5f; + world.createPlayerEntity(playerInfo.getId(), playerInfo.getName(), wx, wy); + index++; + } + + // Spawn test zombies at zombie spawn points / 在僵尸出生点生成测试僵尸 + List zombieSpawns = world.getMap().getZombieSpawnPoints(); + for (int[] zsp : zombieSpawns) { + int zombieId = world.createZombieEntity(zsp[0] + 0.5f, zsp[1] + 0.5f); + logger.info("Initial zombie created: id={} at ({}, {})", zombieId, zsp[0], zsp[1]); + } + + activeGames.put(room.getId(), world); + + // Build initial data for each player / 为每位玩家构建初始数据 + Map> playerInitData = new LinkedHashMap<>(); + for (PlayerInfo playerInfo : room.getPlayers()) { + Map data = new LinkedHashMap<>(); + data.put("playerId", playerInfo.getId()); + data.put("mapData", serializeMapData(world.getMapData())); + + // Build player list with positions / 构建包含位置的玩家列表 + List> playerList = new ArrayList<>(); + int idx = 0; + for (PlayerInfo p : room.getPlayers()) { + Integer entityId = world.getPlayerEntity(p.getId()); + Map pm = new LinkedHashMap<>(); + pm.put("id", p.getId()); + pm.put("name", p.getName()); + if (entityId != null) { + Position pos = world.getPositions().get(entityId); + if (pos != null) { + pm.put("x", pos.getX()); + pm.put("y", pos.getY()); + } + } + pm.put("index", idx++); + playerList.add(pm); + } + data.put("players", playerList); + playerInitData.put(playerInfo.getId(), data); + } + + // Create game loop (not started yet) / 创建游戏循环(尚未启动) + GameLoop loop = new GameLoop(room.getId(), world, broadcaster); + gameLoops.put(room.getId(), loop); + + logger.info("Game initialized for room: {}", room.getId()); + return playerInitData; + } + + // Start the game loop for a room (call after sending game_started) / 启动房间的游戏循环(在发送 game_started 之后调用) + public void startGameLoop(String roomId) { + GameLoop loop = gameLoops.get(roomId); + if (loop != null) { + loop.start(); + } + } + + // Stop the game for a room / 停止指定房间的游戏 + public void stopGame(String roomId) { + GameLoop loop = gameLoops.remove(roomId); + if (loop != null) { + loop.stop(); + } + activeGames.remove(roomId); + logger.info("Game stopped for room: {}", roomId); + } + + // Process player input for movement and aiming / 处理玩家输入(移动和瞄准) + public void processPlayerInput(String roomId, String playerId, + float dx, float dy, float aimX, float aimY, int seq) { + ECSWorld world = activeGames.get(roomId); + if (world == null) return; + + synchronized (world.getLock()) { + Integer entityId = world.getPlayerEntity(playerId); + if (entityId == null) return; + + // Skip if player is dead / 如果玩家已死亡则跳过 + Health health = world.getHealths().get(entityId); + if (health == null || !health.isAlive()) return; + + // Apply input to player entity / 将输入应用到玩家实体 + PlayerInput input = world.getPlayerInputs().get(entityId); + if (input == null) return; + + input.setDx(dx); + input.setDy(dy); + input.setAimX(aimX); + input.setAimY(aimY); + input.setSeq(seq); + } + } + + // Convert 2D int array to nested list for JSON serialization / 将二维整数数组转换为嵌套列表用于JSON序列化 + private List> serializeMapData(int[][] cells) { + List> result = new ArrayList<>(); + for (int[] row : cells) { + List rowList = new ArrayList<>(); + for (int cell : row) { + rowList.add(cell); + } + result.add(rowList); + } + return result; + } +} diff --git a/backend/src/main/java/com/zombie/game/server/GameWebSocketServer.java b/backend/src/main/java/com/zombie/game/server/GameWebSocketServer.java new file mode 100644 index 0000000..616fcdb --- /dev/null +++ b/backend/src/main/java/com/zombie/game/server/GameWebSocketServer.java @@ -0,0 +1,370 @@ +package com.zombie.game.server; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.zombie.game.ecs.ECSWorld; +import com.zombie.game.model.*; +import com.zombie.game.model.Constants; +import com.zombie.game.systems.StateSyncSystem; +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Main WebSocket server handling all client connections / 主WebSocket服务器,处理所有客户端连接 + * Manages game rooms, player connections, and message routing / 管理游戏房间、玩家连接和消息路由 + */ +public class GameWebSocketServer extends WebSocketServer { + // SLF4J logger for server events / SLF4J日志记录器,用于服务器事件 + private static final Logger logger = LoggerFactory.getLogger(GameWebSocketServer.class); + // JSON serializer/deserializer / JSON序列化/反序列化工具 + private Gson gson; + // Room manager for creating and tracking game rooms / 房间管理器,用于创建和追踪游戏房间 + private RoomManager roomManager; + // Game service for game logic / 游戏服务,用于游戏逻辑处理 + private GameService gameService; + // Map connection to player ID / 连接映射到玩家ID + private Map connectionToPlayer; + // Map player ID to connection / 玩家ID映射到连接 + private Map playerToConnection; + // Timer for broadcasting room list periodically / 定时器,用于定期广播房间列表 + private Timer roomListTimer; + + // Constructor: initializes server with given port / 构造函数:用指定端口初始化服务器 + public GameWebSocketServer(int port) { + super(new InetSocketAddress(port)); + this.gson = new Gson(); + this.roomManager = new RoomManager(); + this.connectionToPlayer = new ConcurrentHashMap<>(); + this.playerToConnection = new ConcurrentHashMap<>(); + this.gameService = new GameService(this::broadcastGameState); + } + + // Called when a new client connects / 新客户端连接时调用 + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + logger.info("New connection: {}", conn.getRemoteSocketAddress()); + } + + // Called when a client disconnects / 客户端断开连接时调用 + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + logger.info("Connection closed: {}", conn.getRemoteSocketAddress()); + // Remove player mapping and handle room leave / 移除玩家映射并处理离开房间 + String playerId = connectionToPlayer.remove(conn); + if (playerId != null) { + playerToConnection.remove(playerId); + handleLeaveRoomByPlayerId(playerId); + } + } + + // Called when a message is received from a client / 收到客户端消息时调用 + @Override + public void onMessage(WebSocket conn, String message) { + try { + // Parse message type and data / 解析消息类型和数据 + JsonObject msg = gson.fromJson(message, JsonObject.class); + String type = msg.get("type").getAsString(); + JsonObject data = msg.has("data") ? msg.getAsJsonObject("data") : new JsonObject(); + + // Route message to appropriate handler / 将消息路由到对应的处理函数 + switch (type) { + case Constants.MSG_CREATE_ROOM: + handleCreateRoom(conn, data); + break; + case Constants.MSG_JOIN_ROOM: + handleJoinRoom(conn, data); + break; + case Constants.MSG_LEAVE_ROOM: + handleLeaveRoomByConn(conn); + break; + case Constants.MSG_ROOM_LIST: + handleRoomList(conn); + break; + case Constants.MSG_READY: + handleReady(conn); + break; + case Constants.MSG_START_GAME: + handleStartGame(conn); + break; + case Constants.MSG_PLAYER_INPUT: + handlePlayerInput(conn, data); + break; + } + } catch (Exception e) { + logger.error("Error processing message from {}", conn.getRemoteSocketAddress(), e); + sendError(conn, "Invalid message format"); + } + } + + // Called when a WebSocket error occurs / WebSocket错误发生时调用 + @Override + public void onError(WebSocket conn, Exception ex) { + logger.error("WebSocket error on {}", conn != null ? conn.getRemoteSocketAddress() : "null", ex); + } + + // Called when the server starts successfully / 服务器成功启动时调用 + @Override + public void onStart() { + logger.info("Game WebSocket Server started on port {}", getPort()); + // Start periodic room list broadcast (every 2 seconds) / 启动定期房间列表广播(每2秒) + roomListTimer = new Timer(true); + roomListTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + broadcastRoomList(); + } + }, 0, 2000); + } + + // Handle create room request / 处理创建房间请求 + private void handleCreateRoom(WebSocket conn, JsonObject data) { + // Validate required fields / 验证必需字段 + if (!MessageUtils.hasRequired(data, "playerName")) { + sendError(conn, "Missing playerName"); + return; + } + String playerName = MessageUtils.getString(data, "playerName"); + // Generate unique IDs for player and room / 为玩家和房间生成唯一ID + String playerId = UUID.randomUUID().toString(); + String roomId = UUID.randomUUID().toString().substring(0, 8); + + // Track connection mappings / 记录连接映射 + connectionToPlayer.put(conn, playerId); + playerToConnection.put(playerId, conn); + + // Create new room with player as host / 创建新房间,玩家作为房主 + Room room = new Room(roomId, playerId, playerName); + roomManager.addRoom(roomId, room); + + // Send room state back to creator / 向创建者发送房间状态 + sendToConnection(conn, Constants.MSG_ROOM_STATE, room.toStateMap(playerId)); + logger.info("Room created: {} by {}", roomId, playerName); + } + + // Handle join room request / 处理加入房间请求 + private void handleJoinRoom(WebSocket conn, JsonObject data) { + // Validate required fields / 验证必需字段 + if (!MessageUtils.hasRequired(data, "roomId", "playerName")) { + sendError(conn, "Missing roomId or playerName"); + return; + } + String roomId = MessageUtils.getString(data, "roomId"); + String playerName = MessageUtils.getString(data, "playerName"); + + Room room = roomManager.getRoom(roomId); + if (room == null) { + sendError(conn, "Room not found"); + return; + } + + // Check if room is full (max 4 players) / 检查房间是否已满(最多4人) + if (room.getPlayerCount() >= 4) { + sendError(conn, "Room is full"); + return; + } + + // Generate player ID and track connection / 生成玩家ID并追踪连接 + String playerId = UUID.randomUUID().toString(); + connectionToPlayer.put(conn, playerId); + playerToConnection.put(playerId, conn); + + // Add player to room / 将玩家添加到房间 + room.addPlayer(playerId, playerName); + roomManager.joinRoom(roomId, playerId, playerName); + + // Notify all players of updated room state / 通知所有玩家房间状态已更新 + broadcastRoomState(room); + logger.info("Player {} joined room {}", playerName, roomId); + } + + // Handle leave room by connection / 通过连接处理离开房间 + private void handleLeaveRoomByConn(WebSocket conn) { + String playerId = connectionToPlayer.get(conn); + if (playerId == null) return; + handleLeaveRoomByPlayerId(playerId); + } + + // Handle leave room by player ID / 通过玩家ID处理离开房间 + private void handleLeaveRoomByPlayerId(String playerId) { + Room room = roomManager.leaveRoom(playerId); + if (room == null) return; + + Room updatedRoom = roomManager.getRoom(room.getId()); + if (updatedRoom == null) { + // Room was deleted, stop the game / 房间被删除,停止游戏 + gameService.stopGame(room.getId()); + } else { + // Notify remaining players / 通知剩余玩家 + broadcastRoomState(updatedRoom); + } + } + + // Handle room list request / 处理房间列表请求 + private void handleRoomList(WebSocket conn) { + List> roomList = new ArrayList<>(); + for (Room room : roomManager.getAvailableRooms()) { + roomList.add(room.toRoomListMap()); + } + Map data = new LinkedHashMap<>(); + data.put("rooms", roomList); + sendToConnection(conn, Constants.MSG_ROOM_LIST, data); + } + + // Handle player ready toggle / 处理玩家准备状态切换 + private void handleReady(WebSocket conn) { + String playerId = connectionToPlayer.get(conn); + if (playerId == null) return; + + Room room = roomManager.getRoomByPlayerId(playerId); + if (room == null) return; + + PlayerInfo player = room.getPlayer(playerId); + if (player != null) { + // Toggle ready state / 切换准备状态 + player.setReady(!player.isReady()); + broadcastRoomState(room); + } + } + + // Handle start game request (host only) / 处理开始游戏请求(仅房主) + private void handleStartGame(WebSocket conn) { + String playerId = connectionToPlayer.get(conn); + if (playerId == null) { + sendError(conn, "Not in a room / 未在房间中"); + return; + } + + Room room = roomManager.getRoomByPlayerId(playerId); + if (room == null) { + sendError(conn, "Room not found / 房间不存在"); + return; + } + + if (!room.isHost(playerId)) { + sendError(conn, "Only host can start game / 只有房主可以开始游戏"); + return; + } + if (room.isGameStarted()) { + sendError(conn, "Game already started / 游戏已经开始"); + return; + } + if (!room.allReady()) { + sendError(conn, "Not all players are ready / 不是所有玩家都已准备"); + return; + } + + // Set game started flag after startGame succeeds / startGame 成功后再设置标志 + try { + startGame(room); + room.setGameStarted(true); + } catch (Exception e) { + logger.error("Failed to start game for room {}", room.getId(), e); + sendError(conn, "Failed to start game: " + e.getMessage()); + } + } + + // Start the game for a room / 为房间启动游戏 + private void startGame(Room room) { + // Initialize game world and create player entities / 初始化游戏世界并创建玩家实体 + Map> playerInitData = gameService.startGame(room); + + // Send initial game data to each player / 向每位玩家发送初始游戏数据 + for (Map.Entry> entry : playerInitData.entrySet()) { + WebSocket pConn = playerToConnection.get(entry.getKey()); + if (pConn != null) { + sendToConnection(pConn, Constants.MSG_GAME_STARTED, entry.getValue()); + } + } + + // Start game loop after game_started is sent / game_started 发送完毕后再启动游戏循环 + gameService.startGameLoop(room.getId()); + } + + // Handle player input (movement, aim) / 处理玩家输入(移动、瞄准) + private void handlePlayerInput(WebSocket conn, JsonObject data) { + String playerId = connectionToPlayer.get(conn); + if (playerId == null) return; + + Room room = roomManager.getRoomByPlayerId(playerId); + if (room == null || !room.isGameStarted()) return; + + // Extract input data / 提取输入数据 + float dx = MessageUtils.getFloat(data, "dx", 0); + float dy = MessageUtils.getFloat(data, "dy", 0); + float aimX = MessageUtils.getFloat(data, "aimX", 0); + float aimY = MessageUtils.getFloat(data, "aimY", 0); + int seq = MessageUtils.getInt(data, "seq", 0); + + gameService.processPlayerInput(room.getId(), playerId, dx, dy, aimX, aimY, seq); + } + + // Broadcast game state to all players in a room / 向房间内所有玩家广播游戏状态 + private void broadcastGameState(String roomId, ECSWorld world) { + Room room = roomManager.getRoom(roomId); + if (room == null) return; + + StateSyncSystem syncSystem = new StateSyncSystem(); + + // Sync state under world lock / 在世界锁下同步状态 + synchronized (world.getLock()) { + Map state = syncSystem.buildGameState(world); + for (PlayerInfo playerInfo : room.getPlayers()) { + WebSocket pConn = playerToConnection.get(playerInfo.getId()); + if (pConn != null && pConn.isOpen()) { + sendToConnection(pConn, Constants.MSG_GAME_STATE, state); + } + } + } + } + + // Broadcast room state to all players in a room / 向房间内所有玩家广播房间状态 + private void broadcastRoomState(Room room) { + for (PlayerInfo player : room.getPlayers()) { + WebSocket pConn = playerToConnection.get(player.getId()); + if (pConn != null && pConn.isOpen()) { + sendToConnection(pConn, Constants.MSG_ROOM_STATE, room.toStateMap(player.getId())); + } + } + } + + // Broadcast room list to all unconnected clients / 向所有未连接的玩家广播房间列表 + private void broadcastRoomList() { + List> roomList = new ArrayList<>(); + for (Room room : roomManager.getAvailableRooms()) { + roomList.add(room.toRoomListMap()); + } + Map data = new LinkedHashMap<>(); + data.put("rooms", roomList); + + // Only send to clients not in a room / 只发送给不在房间中的客户端 + for (WebSocket conn : getConnections()) { + if (connectionToPlayer.get(conn) == null) { + sendToConnection(conn, Constants.MSG_ROOM_LIST, data); + } + } + } + + // Send a message to a specific connection / 向指定连接发送消息 + private void sendToConnection(WebSocket conn, String type, Object data) { + if (conn != null && conn.isOpen()) { + Map msg = new LinkedHashMap<>(); + msg.put("type", type); + msg.put("data", data); + conn.send(gson.toJson(msg)); + } + } + + // Send error message to a connection / 向连接发送错误消息 + private void sendError(WebSocket conn, String message) { + Map data = new LinkedHashMap<>(); + data.put("message", message); + sendToConnection(conn, Constants.MSG_ERROR, data); + } +} diff --git a/backend/src/main/java/com/zombie/game/server/MapStorage.java b/backend/src/main/java/com/zombie/game/server/MapStorage.java new file mode 100644 index 0000000..6f46d8b --- /dev/null +++ b/backend/src/main/java/com/zombie/game/server/MapStorage.java @@ -0,0 +1,136 @@ +package com.zombie.game.server; + +import com.google.gson.Gson; +import com.zombie.game.model.MapData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.file.*; +import java.util.*; + +/** + * Storage service for game maps (save, load, list, delete) / 地图存储服务(保存、加载、列出、删除) + */ +public class MapStorage { + // Logger for map operations / 地图操作日志记录器 + private static final Logger logger = LoggerFactory.getLogger(MapStorage.class); + // Directory where map files are stored / 地图文件存储目录 + private static final String MAPS_DIR = "maps"; + // JSON serializer/deserializer / JSON序列化/反序列化工具 + private final Gson gson; + // In-memory cache of loaded maps / 已加载地图的内存缓存 + private final Map cache; + + // Constructor: initializes storage and ensures maps directory exists / 构造函数:初始化存储并确保地图目录存在 + public MapStorage() { + this.gson = new Gson(); + this.cache = new HashMap<>(); + ensureMapsDir(); + } + + // Create maps directory if it doesn't exist / 如果地图目录不存在则创建 + private void ensureMapsDir() { + Path mapsPath = Paths.get(MAPS_DIR); + if (!Files.exists(mapsPath)) { + try { + Files.createDirectories(mapsPath); + } catch (IOException e) { + logger.error("Failed to create maps directory", e); + } + } + } + + // Save a map to disk and cache / 将地图保存到磁盘并缓存 + public MapData save(MapData mapData) { + // Assign ID if missing / 如果缺少ID则生成 + if (mapData.getId() == null || mapData.getId().isEmpty()) { + mapData.setId(UUID.randomUUID().toString().substring(0, 8)); + } + // Set default name if missing / 如果缺少名称则使用默认值 + if (mapData.getName() == null || mapData.getName().isEmpty()) { + mapData.setName("untitled"); + } + + String filename = mapData.getId() + ".json"; + Path filePath = Paths.get(MAPS_DIR, filename); + + try (Writer writer = Files.newBufferedWriter(filePath)) { + gson.toJson(mapData, writer); + cache.put(mapData.getId(), mapData); + logger.info("Map saved: {} ({})", mapData.getName(), mapData.getId()); + return mapData; + } catch (IOException e) { + logger.error("Failed to save map: {}", mapData.getId(), e); + throw new RuntimeException("Failed to save map", e); + } + } + + // Load a map from cache or disk / 从缓存或磁盘加载地图 + public MapData load(String id) { + // Check cache first / 先检查缓存 + MapData cached = cache.get(id); + if (cached != null) { + return cached; + } + + String filename = id + ".json"; + Path filePath = Paths.get(MAPS_DIR, filename); + + if (!Files.exists(filePath)) { + return null; + } + + try (Reader reader = Files.newBufferedReader(filePath)) { + MapData mapData = gson.fromJson(reader, MapData.class); + cache.put(id, mapData); + return mapData; + } catch (IOException e) { + logger.error("Failed to load map: {}", id, e); + return null; + } + } + + // List all maps from disk / 列出磁盘上的所有地图 + public List listMaps() { + List maps = new ArrayList<>(); + Path mapsPath = Paths.get(MAPS_DIR); + + if (!Files.exists(mapsPath)) { + return maps; + } + + try (DirectoryStream stream = Files.newDirectoryStream(mapsPath, "*.json")) { + for (Path file : stream) { + try (Reader reader = Files.newBufferedReader(file)) { + MapData mapData = gson.fromJson(reader, MapData.class); + maps.add(mapData); + } catch (IOException e) { + logger.warn("Failed to read map file: {}", file); + } + } + } catch (IOException e) { + logger.error("Failed to list maps", e); + } + + return maps; + } + + // Delete a map from disk and cache / 从磁盘和缓存中删除地图 + public boolean delete(String id) { + String filename = id + ".json"; + Path filePath = Paths.get(MAPS_DIR, filename); + + try { + boolean deleted = Files.deleteIfExists(filePath); + if (deleted) { + cache.remove(id); + logger.info("Map deleted: {}", id); + } + return deleted; + } catch (IOException e) { + logger.error("Failed to delete map: {}", id, e); + return false; + } + } +} diff --git a/backend/src/main/java/com/zombie/game/server/MessageUtils.java b/backend/src/main/java/com/zombie/game/server/MessageUtils.java new file mode 100644 index 0000000..2ea0096 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/server/MessageUtils.java @@ -0,0 +1,73 @@ +package com.zombie.game.server; + +import com.google.gson.JsonObject; + +/** + * Utility class for extracting values from JSON messages / 工具类,用于从JSON消息中提取值 + */ +public class MessageUtils { + + // Get string value from JSON with default / 从JSON获取字符串值,带默认值 + public static String getString(JsonObject data, String key, String defaultValue) { + if (data == null || !data.has(key) || data.get(key).isJsonNull()) { + return defaultValue; + } + try { + return data.get(key).getAsString(); + } catch (Exception e) { + return defaultValue; + } + } + + // Get string value from JSON (null default) / 从JSON获取字符串值(默认null) + public static String getString(JsonObject data, String key) { + return getString(data, key, null); + } + + // Get float value from JSON with default / 从JSON获取浮点数值,带默认值 + public static float getFloat(JsonObject data, String key, float defaultValue) { + if (data == null || !data.has(key) || data.get(key).isJsonNull()) { + return defaultValue; + } + try { + return data.get(key).getAsFloat(); + } catch (Exception e) { + return defaultValue; + } + } + + // Get int value from JSON with default / 从JSON获取整数值,带默认值 + public static int getInt(JsonObject data, String key, int defaultValue) { + if (data == null || !data.has(key) || data.get(key).isJsonNull()) { + return defaultValue; + } + try { + return data.get(key).getAsInt(); + } catch (Exception e) { + return defaultValue; + } + } + + // Get boolean value from JSON with default / 从JSON获取布尔值,带默认值 + public static boolean getBoolean(JsonObject data, String key, boolean defaultValue) { + if (data == null || !data.has(key) || data.get(key).isJsonNull()) { + return defaultValue; + } + try { + return data.get(key).getAsBoolean(); + } catch (Exception e) { + return defaultValue; + } + } + + // Check if all required keys are present and non-null / 检查所有必需键是否存在且非空 + public static boolean hasRequired(JsonObject data, String... keys) { + if (data == null) return false; + for (String key : keys) { + if (!data.has(key) || data.get(key).isJsonNull()) { + return false; + } + } + return true; + } +} diff --git a/backend/src/main/java/com/zombie/game/server/RoomManager.java b/backend/src/main/java/com/zombie/game/server/RoomManager.java new file mode 100644 index 0000000..b265f2e --- /dev/null +++ b/backend/src/main/java/com/zombie/game/server/RoomManager.java @@ -0,0 +1,103 @@ +package com.zombie.game.server; + +import com.zombie.game.model.PlayerInfo; +import com.zombie.game.model.Room; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Room manager for tracking game rooms and player-room assignments / 房间管理器,用于追踪游戏房间和玩家房间分配 + */ +public class RoomManager { + // All active rooms mapped by room ID / 所有活跃房间,以房间ID映射 + private final Map rooms = new ConcurrentHashMap<>(); + // Player ID to room ID mapping / 玩家ID到房间ID的映射 + private final Map playerToRoom = new ConcurrentHashMap<>(); + + // Add a new room to the manager / 向管理器添加新房间 + public void addRoom(String roomId, Room room) { + rooms.put(roomId, room); + // Map existing players in room to room ID / 将房间中现有玩家映射到房间ID + for (PlayerInfo player : room.getPlayers()) { + playerToRoom.put(player.getId(), roomId); + } + } + + // Add a player to a room / 将玩家添加到房间 + public boolean joinRoom(String roomId, String playerId, String playerName) { + Room room = rooms.get(roomId); + if (room == null) return false; + // Reject if room is full / 房间已满则拒绝 + if (room.getPlayerCount() >= 4) return false; + + boolean added = room.addPlayer(playerId, playerName); + if (added) { + playerToRoom.put(playerId, roomId); + } + return added; + } + + // Remove a player from their room / 从房间中移除玩家 + public Room leaveRoom(String playerId) { + String roomId = playerToRoom.remove(playerId); + if (roomId == null) return null; + + Room room = rooms.get(roomId); + if (room == null) return null; + + room.removePlayer(playerId); + // Delete room if empty / 如果房间为空则删除 + if (room.getPlayerCount() == 0) { + rooms.remove(roomId); + return null; + } + return room; + } + + // Get room by player ID / 通过玩家ID获取房间 + public Room getRoomByPlayerId(String playerId) { + String roomId = playerToRoom.get(playerId); + if (roomId == null) return null; + return rooms.get(roomId); + } + + // Get room by room ID / 通过房间ID获取房间 + public Room getRoom(String roomId) { + return rooms.get(roomId); + } + + // Get all rooms / 获取所有房间 + public Collection getAllRooms() { + return rooms.values(); + } + + // Get rooms that haven't started a game yet / 获取尚未开始游戏的房间 + public List getAvailableRooms() { + List available = new ArrayList<>(); + for (Room room : rooms.values()) { + if (!room.isGameStarted()) { + available.add(room); + } + } + return available; + } + + // Remove a room and all player mappings / 移除房间及其所有玩家映射 + public void removeRoom(String roomId) { + Room room = rooms.remove(roomId); + if (room != null) { + for (PlayerInfo player : room.getPlayers()) { + playerToRoom.remove(player.getId()); + } + } + } + + // Check if a player is in any room / 检查玩家是否在任意房间中 + public boolean isPlayerInRoom(String playerId) { + return playerToRoom.containsKey(playerId); + } +} diff --git a/backend/src/main/java/com/zombie/game/systems/FlowFieldUpdateSystem.java b/backend/src/main/java/com/zombie/game/systems/FlowFieldUpdateSystem.java new file mode 100644 index 0000000..816e068 --- /dev/null +++ b/backend/src/main/java/com/zombie/game/systems/FlowFieldUpdateSystem.java @@ -0,0 +1,30 @@ +package com.zombie.game.systems; + +import com.zombie.game.ecs.ECSWorld; +import com.zombie.game.ecs.System; +import com.zombie.game.ecs.components.Position; +import com.zombie.game.model.GameMap; + +import java.util.ArrayList; +import java.util.List; + +/** Updates flow field each frame based on player positions / 每帧根据玩家位置更新流场 */ +public class FlowFieldUpdateSystem implements System { + + @Override + public void update(float dt, ECSWorld world) { + GameMap map = world.getMap(); + + List playerPositions = new ArrayList<>(); + for (int entityId : world.getPlayers()) { + Position pos = world.getPositions().get(entityId); + if (pos != null) { + playerPositions.add(new float[]{pos.getX(), pos.getY()}); + } + } + + if (!playerPositions.isEmpty()) { + map.updateFlowField(playerPositions); + } + } +} diff --git a/backend/src/main/java/com/zombie/game/systems/PlayerInputSystem.java b/backend/src/main/java/com/zombie/game/systems/PlayerInputSystem.java new file mode 100644 index 0000000..680e92e --- /dev/null +++ b/backend/src/main/java/com/zombie/game/systems/PlayerInputSystem.java @@ -0,0 +1,46 @@ +package com.zombie.game.systems; + +import com.zombie.game.ecs.ECSWorld; +import com.zombie.game.ecs.System; +import com.zombie.game.ecs.components.*; + +import static com.zombie.game.model.Constants.*; + +/** + * Processes player input to update positions and aim angles. + * 处理玩家输入以更新位置和瞄准角度。 + */ +public class PlayerInputSystem implements System { + + @Override + public void update(float dt, ECSWorld world) { + // Iterate over all player entities / 遍历所有玩家实体 + for (int entityId : world.getPlayers()) { + // Retrieve components for this entity / 获取该实体的组件 + PlayerInput input = world.getPlayerInputs().get(entityId); + Position pos = world.getPositions().get(entityId); + Health health = world.getHealths().get(entityId); + + // Skip if any required component is missing or player is dead / 若缺少必要组件或玩家已死亡则跳过 + if (input == null || pos == null || health == null || !health.isAlive()) continue; + + // Calculate movement speed based on tick interval / 根据 tick 间隔计算移动速度 + float speed = PLAYER_SPEED * TICK_INTERVAL; + // Compute new position from input direction / 根据输入方向计算新位置 + float newX = pos.getX() + input.getDx() * speed; + float newY = pos.getY() + input.getDy() * speed; + + // Update X position if walkable, clamped to grid bounds / 若可行走则更新 X 位置,限制在网格边界内 + if (world.getMap().isWalkable(newX, pos.getY(), PLAYER_SIZE)) { + pos.setX(Math.max(0.5f, Math.min(GRID_SIZE - 0.5f, newX))); + } + // Update Y position if walkable, clamped to grid bounds / 若可行走则更新 Y 位置,限制在网格边界内 + if (world.getMap().isWalkable(pos.getX(), newY, PLAYER_SIZE)) { + pos.setY(Math.max(0.5f, Math.min(GRID_SIZE - 0.5f, newY))); + } + + // Update facing angle based on aim direction / 根据瞄准方向更新朝向角度 + pos.setAngle((float) Math.atan2(input.getAimX() - pos.getX(), input.getAimY() - pos.getY())); + } + } +} diff --git a/backend/src/main/java/com/zombie/game/systems/StateSyncSystem.java b/backend/src/main/java/com/zombie/game/systems/StateSyncSystem.java new file mode 100644 index 0000000..ffc14ba --- /dev/null +++ b/backend/src/main/java/com/zombie/game/systems/StateSyncSystem.java @@ -0,0 +1,108 @@ +package com.zombie.game.systems; + +import com.zombie.game.ecs.ECSWorld; +import com.zombie.game.ecs.System; +import com.zombie.game.ecs.components.*; + +import java.util.*; + +/** + * Collects and serializes game state for network synchronization. + * 收集并序列化游戏状态以进行网络同步。 + */ +public class StateSyncSystem implements System { + + /** + * Builds a map representing the current game state including all players and zombies. + */ + public Map buildGameState(ECSWorld world) { + Map state = new LinkedHashMap<>(); + + // Players + List> playerStates = new ArrayList<>(); + for (int entityId : world.getPlayers()) { + Map map = new LinkedHashMap<>(); + Position pos = world.getPositions().get(entityId); + Health health = world.getHealths().get(entityId); + + String playerId = null; + for (var entry : world.getPlayerIdToEntity().entrySet()) { + if (entry.getValue() == entityId) { + playerId = entry.getKey(); + break; + } + } + + map.put("id", playerId); + if (pos != null) { + map.put("x", pos.getX()); + map.put("y", pos.getY()); + map.put("angle", pos.getAngle()); + } + if (health != null) { + map.put("health", health.getHealth()); + } + + PlayerInput input = world.getPlayerInputs().get(entityId); + if (input != null) { + map.put("lastProcessedSeq", input.getSeq()); + } + + playerStates.add(map); + } + + // Zombies (only alive) + List> zombieStates = new ArrayList<>(); + for (int entityId : world.getZombies()) { + Health health = world.getHealths().get(entityId); + if (health == null || !health.isAlive()) continue; + + Map map = new LinkedHashMap<>(); + Position pos = world.getPositions().get(entityId); + + map.put("id", entityId); + if (pos != null) { + map.put("x", pos.getX()); + map.put("y", pos.getY()); + map.put("angle", pos.getAngle()); + } + map.put("health", health.getHealth()); + map.put("maxHealth", health.getMaxHealth()); + + zombieStates.add(map); + } + + // Nut walls + List> nutWallStates = new ArrayList<>(); + for (Map.Entry entry : world.getHealths().entrySet()) { + int entityId = entry.getKey(); + // Skip non-wall entities (players and zombies have health too) + if (world.getPlayers().contains(entityId) || world.getZombies().contains(entityId)) continue; + if (!world.getBlocksMovements().containsKey(entityId)) continue; + + Health health = entry.getValue(); + Position pos = world.getPositions().get(entityId); + if (pos == null) continue; + + Map wallState = new LinkedHashMap<>(); + wallState.put("id", entityId); + wallState.put("x", (int) Math.floor(pos.getX())); + wallState.put("y", (int) Math.floor(pos.getY())); + wallState.put("health", health.getHealth()); + wallState.put("maxHealth", health.getMaxHealth()); + nutWallStates.add(wallState); + } + + state.put("players", playerStates); + state.put("zombies", zombieStates); + state.put("nutWalls", nutWallStates); + state.put("gameTime", world.getGameTime()); + + return state; + } + + @Override + public void update(float dt, ECSWorld world) { + // No per-frame logic; state is built on demand + } +} diff --git a/backend/src/main/java/com/zombie/game/systems/ZombieMovementSystem.java b/backend/src/main/java/com/zombie/game/systems/ZombieMovementSystem.java new file mode 100644 index 0000000..cd32f3e --- /dev/null +++ b/backend/src/main/java/com/zombie/game/systems/ZombieMovementSystem.java @@ -0,0 +1,235 @@ +package com.zombie.game.systems; + +import com.zombie.game.ecs.ECSWorld; +import com.zombie.game.ecs.System; +import com.zombie.game.ecs.components.*; +import com.zombie.game.model.GameMap; + +import java.util.*; + +import static com.zombie.game.model.Constants.*; + +/** Zombie movement system using flow field pathfinding / 僵尸移动系统,使用流场寻路 */ +public class ZombieMovementSystem implements System { + + @Override + public void update(float dt, ECSWorld world) { + GameMap map = world.getMap(); + if (!map.isFlowFieldValid()) return; + + List sortedZombies = new ArrayList<>(world.getZombies()); + Collections.sort(sortedZombies); + + for (int entityId : sortedZombies) { + Position pos = world.getPositions().get(entityId); + Health health = world.getHealths().get(entityId); + ZombieAI ai = world.getZombieAIs().get(entityId); + + if (pos == null || health == null || !health.isAlive()) continue; + if (ai == null) continue; + + // Target selection: query flow field for next grid + float centerDist = Float.MAX_VALUE; + if (ai.isHasTarget()) { + float dx = ai.getTargetX() - pos.getX(); + float dy = ai.getTargetY() - pos.getY(); + centerDist = (float) Math.sqrt(dx * dx + dy * dy); + } + + if (!ai.isHasTarget() || centerDist < GRID_CENTER_THRESHOLD) { + float[] flowDir = map.getFlowDirection(pos.getX(), pos.getY()); + float dirX = flowDir[0]; + float dirY = flowDir[1]; + + if (dirX == 0 && dirY == 0) continue; + + int currentGridX = (int) Math.floor(pos.getX()); + int currentGridY = (int) Math.floor(pos.getY()); + int nextGridX = currentGridX + (int) Math.round(dirX); + int nextGridY = currentGridY + (int) Math.round(dirY); + + // If next grid is wall, try to slide along single axis + if (map.isWall(nextGridX, nextGridY)) { + nextGridX = currentGridX + (int) Math.signum(dirX); + nextGridY = currentGridY + (int) Math.signum(dirY); + if (map.isWall(nextGridX, nextGridY)) { + if (!map.isWall(currentGridX + (int) Math.signum(dirX), currentGridY)) { + nextGridX = currentGridX + (int) Math.signum(dirX); + nextGridY = currentGridY; + } else if (!map.isWall(currentGridX, currentGridY + (int) Math.signum(dirY))) { + nextGridX = currentGridX; + nextGridY = currentGridY + (int) Math.signum(dirY); + } else { + ai.setReservation(false); + continue; + } + } + } + + // Check grid occupation by other zombies + if (isGridOccupiedOrReserved(nextGridX, nextGridY, world, entityId)) { + int[] alt = findAlternativeDirection(currentGridX, currentGridY, dirX, dirY, map, world, entityId); + if (alt != null) { + nextGridX = alt[0]; + nextGridY = alt[1]; + } else { + ai.setReservation(false); + continue; + } + } + + ai.setReservedGridX(nextGridX); + ai.setReservedGridY(nextGridY); + ai.setReservation(true); + ai.setTargetX(nextGridX + 0.5f); + ai.setTargetY(nextGridY + 0.5f); + ai.setHasTarget(true); + } + + // Move toward target + float dx = ai.getTargetX() - pos.getX(); + float dy = ai.getTargetY() - pos.getY(); + float dist = (float) Math.sqrt(dx * dx + dy * dy); + + if (dist < 0.01f) { + ai.setHasTarget(false); + continue; + } + + float moveDirX = dx / dist; + float moveDirY = dy / dist; + float speed = ZOMBIE_SPEED; + + float moveX = moveDirX * speed * dt; + float moveY = moveDirY * speed * dt; + float newX = pos.getX() + moveX; + float newY = pos.getY() + moveY; + + boolean canMoveX = map.isWalkable(newX, pos.getY(), ZOMBIE_SIZE); + boolean canMoveY = map.isWalkable(pos.getX(), newY, ZOMBIE_SIZE); + + if (moveX != 0 && moveY != 0) { + // Diagonal: check corner blocking + int checkX = (int) Math.floor(newX); + int checkY = (int) Math.floor(newY); + int checkCurrentX = (int) Math.floor(pos.getX()); + int checkCurrentY = (int) Math.floor(pos.getY()); + + boolean blockedByCorner = false; + if (checkX != checkCurrentX && checkY != checkCurrentY) { + boolean wallInX = map.isWall(checkX, checkCurrentY); + boolean wallInY = map.isWall(checkCurrentX, checkY); + if (wallInX || wallInY) { + blockedByCorner = true; + if (!wallInX && canMoveX) { + pos.setX(newX); + } else if (!wallInY && canMoveY) { + pos.setY(newY); + } + } + } + + if (!blockedByCorner) { + boolean canMoveDiag = map.isWalkable(newX, newY, ZOMBIE_SIZE); + if (canMoveDiag) { + pos.setX(newX); + pos.setY(newY); + } else { + if (canMoveX) pos.setX(newX); + if (canMoveY) pos.setY(newY); + } + } + } else { + if (canMoveX) pos.setX(newX); + if (canMoveY) pos.setY(newY); + } + + // Zombie separation + for (int otherId : world.getZombies()) { + if (otherId == entityId) continue; + Health otherHealth = world.getHealths().get(otherId); + if (otherHealth == null || !otherHealth.isAlive()) continue; + Position otherPos = world.getPositions().get(otherId); + if (otherPos == null) continue; + + float sepDx = pos.getX() - otherPos.getX(); + float sepDy = pos.getY() - otherPos.getY(); + float sepDist = (float) Math.sqrt(sepDx * sepDx + sepDy * sepDy); + + if (sepDist < ZOMBIE_SIZE && sepDist > 0.01f) { + float overlap = ZOMBIE_SIZE - sepDist; + float pushX = (sepDx / sepDist) * overlap * 0.5f; + float pushY = (sepDy / sepDist) * overlap * 0.5f; + + if (map.isWalkable(pos.getX() + pushX, pos.getY() + pushY, ZOMBIE_SIZE)) { + pos.setX(pos.getX() + pushX); + pos.setY(pos.getY() + pushY); + } else { + if (map.isWalkable(pos.getX() + pushX, pos.getY(), ZOMBIE_SIZE)) { + pos.setX(pos.getX() + pushX); + } + if (map.isWalkable(pos.getX(), pos.getY() + pushY, ZOMBIE_SIZE)) { + pos.setY(pos.getY() + pushY); + } + } + } + } + + // Update facing angle + if (moveDirX != 0 || moveDirY != 0) { + pos.setAngle((float) Math.atan2(moveDirX, moveDirY)); + } + } + } + + private boolean isGridOccupiedOrReserved(int gridX, int gridY, ECSWorld world, int selfId) { + for (int otherId : world.getZombies()) { + if (otherId == selfId) continue; + Health otherHealth = world.getHealths().get(otherId); + if (otherHealth == null || !otherHealth.isAlive()) continue; + + Position otherPos = world.getPositions().get(otherId); + if (otherPos == null) continue; + + int otherGridX = (int) Math.floor(otherPos.getX()); + int otherGridY = (int) Math.floor(otherPos.getY()); + if (otherGridX == gridX && otherGridY == gridY) return true; + + ZombieAI otherAi = world.getZombieAIs().get(otherId); + if (otherAi != null && otherAi.isReservation() && + otherAi.getReservedGridX() == gridX && otherAi.getReservedGridY() == gridY) { + return true; + } + } + return false; + } + + private int[] findAlternativeDirection(int currentGridX, int currentGridY, float dirX, float dirY, + GameMap map, ECSWorld world, int selfId) { + int[][] allDirs = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {1, -1}, {-1, 1}, {1, 1}}; + List candidates = new ArrayList<>(); + + for (int[] dir : allDirs) { + int nx = currentGridX + dir[0]; + int ny = currentGridY + dir[1]; + + if (map.isWall(nx, ny)) continue; + + if (dir[0] != 0 && dir[1] != 0) { + if (map.isWall(currentGridX + dir[0], currentGridY) || + map.isWall(currentGridX, currentGridY + dir[1])) { + continue; + } + } + + if (isGridOccupiedOrReserved(nx, ny, world, selfId)) continue; + + float dotProduct = dir[0] * dirX + dir[1] * dirY; + candidates.add(new int[]{nx, ny, (int) (dotProduct * 1000)}); + } + + if (candidates.isEmpty()) return null; + candidates.sort((a, b) -> b[2] - a[2]); + return new int[]{candidates.get(0)[0], candidates.get(0)[1]}; + } +} diff --git a/backend/src/main/java/com/zombie/game/systems/ZombieSpawnSystem.java b/backend/src/main/java/com/zombie/game/systems/ZombieSpawnSystem.java new file mode 100644 index 0000000..eadc2ed --- /dev/null +++ b/backend/src/main/java/com/zombie/game/systems/ZombieSpawnSystem.java @@ -0,0 +1,47 @@ +package com.zombie.game.systems; + +import com.zombie.game.ecs.ECSWorld; +import com.zombie.game.ecs.System; +import com.zombie.game.ecs.components.Health; +import com.zombie.game.model.GameMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Random; + +import static com.zombie.game.model.Constants.*; + +/** Periodically spawns zombies at map spawn points / 定期在地图出生点生成僵尸 */ +public class ZombieSpawnSystem implements System { + private static final Logger logger = LoggerFactory.getLogger(ZombieSpawnSystem.class); + private final Random random = new Random(); + private float spawnTimer = 0; + + @Override + public void update(float dt, ECSWorld world) { + spawnTimer += dt; + if (spawnTimer < ZOMBIE_SPAWN_INTERVAL) return; + spawnTimer -= ZOMBIE_SPAWN_INTERVAL; + + // Count alive zombies + int aliveCount = 0; + for (int entityId : world.getZombies()) { + Health health = world.getHealths().get(entityId); + if (health != null && health.isAlive()) { + aliveCount++; + } + } + + if (aliveCount >= ZOMBIE_MAX_COUNT) return; + + // Pick a random spawn point + GameMap map = world.getMap(); + List spawns = map.getZombieSpawnPoints(); + if (spawns.isEmpty()) return; + + int[] sp = spawns.get(random.nextInt(spawns.size())); + int newZombie = world.createZombieEntity(sp[0] + 0.5f, sp[1] + 0.5f); + logger.info("Zombie spawned: id={} at ({}, {}), alive={}", newZombie, sp[0], sp[1], aliveCount + 1); + } +} diff --git a/backend/target/classes/com/zombie/game/GameServerMain.class b/backend/target/classes/com/zombie/game/GameServerMain.class new file mode 100644 index 0000000..fa15fe7 Binary files /dev/null and b/backend/target/classes/com/zombie/game/GameServerMain.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/ECSWorld.class b/backend/target/classes/com/zombie/game/ecs/ECSWorld.class new file mode 100644 index 0000000..8308ea6 Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/ECSWorld.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/System.class b/backend/target/classes/com/zombie/game/ecs/System.class new file mode 100644 index 0000000..4635d8c Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/System.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/components/BlocksMovement.class b/backend/target/classes/com/zombie/game/ecs/components/BlocksMovement.class new file mode 100644 index 0000000..70b7018 Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/components/BlocksMovement.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/components/Collision.class b/backend/target/classes/com/zombie/game/ecs/components/Collision.class new file mode 100644 index 0000000..e55f29c Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/components/Collision.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/components/Health.class b/backend/target/classes/com/zombie/game/ecs/components/Health.class new file mode 100644 index 0000000..2c099f8 Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/components/Health.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/components/MovementCost.class b/backend/target/classes/com/zombie/game/ecs/components/MovementCost.class new file mode 100644 index 0000000..26071d0 Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/components/MovementCost.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/components/PlayerInput.class b/backend/target/classes/com/zombie/game/ecs/components/PlayerInput.class new file mode 100644 index 0000000..f31e0ec Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/components/PlayerInput.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/components/Position.class b/backend/target/classes/com/zombie/game/ecs/components/Position.class new file mode 100644 index 0000000..0599019 Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/components/Position.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/components/RenderInfo$EntityType.class b/backend/target/classes/com/zombie/game/ecs/components/RenderInfo$EntityType.class new file mode 100644 index 0000000..dd3107f Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/components/RenderInfo$EntityType.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/components/RenderInfo.class b/backend/target/classes/com/zombie/game/ecs/components/RenderInfo.class new file mode 100644 index 0000000..486da73 Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/components/RenderInfo.class differ diff --git a/backend/target/classes/com/zombie/game/ecs/components/ZombieAI.class b/backend/target/classes/com/zombie/game/ecs/components/ZombieAI.class new file mode 100644 index 0000000..4c38ec4 Binary files /dev/null and b/backend/target/classes/com/zombie/game/ecs/components/ZombieAI.class differ diff --git a/backend/target/classes/com/zombie/game/model/Constants.class b/backend/target/classes/com/zombie/game/model/Constants.class new file mode 100644 index 0000000..54fc4d8 Binary files /dev/null and b/backend/target/classes/com/zombie/game/model/Constants.class differ diff --git a/backend/target/classes/com/zombie/game/model/FlowField$Node.class b/backend/target/classes/com/zombie/game/model/FlowField$Node.class new file mode 100644 index 0000000..78d40a9 Binary files /dev/null and b/backend/target/classes/com/zombie/game/model/FlowField$Node.class differ diff --git a/backend/target/classes/com/zombie/game/model/FlowField.class b/backend/target/classes/com/zombie/game/model/FlowField.class new file mode 100644 index 0000000..efa39cf Binary files /dev/null and b/backend/target/classes/com/zombie/game/model/FlowField.class differ diff --git a/backend/target/classes/com/zombie/game/model/GameMap.class b/backend/target/classes/com/zombie/game/model/GameMap.class new file mode 100644 index 0000000..18c518e Binary files /dev/null and b/backend/target/classes/com/zombie/game/model/GameMap.class differ diff --git a/backend/target/classes/com/zombie/game/model/MapData.class b/backend/target/classes/com/zombie/game/model/MapData.class new file mode 100644 index 0000000..33f1f21 Binary files /dev/null and b/backend/target/classes/com/zombie/game/model/MapData.class differ diff --git a/backend/target/classes/com/zombie/game/model/PlayerInfo.class b/backend/target/classes/com/zombie/game/model/PlayerInfo.class new file mode 100644 index 0000000..312b902 Binary files /dev/null and b/backend/target/classes/com/zombie/game/model/PlayerInfo.class differ diff --git a/backend/target/classes/com/zombie/game/model/Room.class b/backend/target/classes/com/zombie/game/model/Room.class new file mode 100644 index 0000000..238d9c1 Binary files /dev/null and b/backend/target/classes/com/zombie/game/model/Room.class differ diff --git a/backend/target/classes/com/zombie/game/server/GameLoop.class b/backend/target/classes/com/zombie/game/server/GameLoop.class new file mode 100644 index 0000000..f324954 Binary files /dev/null and b/backend/target/classes/com/zombie/game/server/GameLoop.class differ diff --git a/backend/target/classes/com/zombie/game/server/GameService$GameStateBroadcast.class b/backend/target/classes/com/zombie/game/server/GameService$GameStateBroadcast.class new file mode 100644 index 0000000..6349965 Binary files /dev/null and b/backend/target/classes/com/zombie/game/server/GameService$GameStateBroadcast.class differ diff --git a/backend/target/classes/com/zombie/game/server/GameService.class b/backend/target/classes/com/zombie/game/server/GameService.class new file mode 100644 index 0000000..1144e44 Binary files /dev/null and b/backend/target/classes/com/zombie/game/server/GameService.class differ diff --git a/backend/target/classes/com/zombie/game/server/GameWebSocketServer$1.class b/backend/target/classes/com/zombie/game/server/GameWebSocketServer$1.class new file mode 100644 index 0000000..134ec25 Binary files /dev/null and b/backend/target/classes/com/zombie/game/server/GameWebSocketServer$1.class differ diff --git a/backend/target/classes/com/zombie/game/server/GameWebSocketServer.class b/backend/target/classes/com/zombie/game/server/GameWebSocketServer.class new file mode 100644 index 0000000..4161c9e Binary files /dev/null and b/backend/target/classes/com/zombie/game/server/GameWebSocketServer.class differ diff --git a/backend/target/classes/com/zombie/game/server/MapStorage.class b/backend/target/classes/com/zombie/game/server/MapStorage.class new file mode 100644 index 0000000..a1e5785 Binary files /dev/null and b/backend/target/classes/com/zombie/game/server/MapStorage.class differ diff --git a/backend/target/classes/com/zombie/game/server/MessageUtils.class b/backend/target/classes/com/zombie/game/server/MessageUtils.class new file mode 100644 index 0000000..7362ac5 Binary files /dev/null and b/backend/target/classes/com/zombie/game/server/MessageUtils.class differ diff --git a/backend/target/classes/com/zombie/game/server/RoomManager.class b/backend/target/classes/com/zombie/game/server/RoomManager.class new file mode 100644 index 0000000..e0f6e08 Binary files /dev/null and b/backend/target/classes/com/zombie/game/server/RoomManager.class differ diff --git a/backend/target/classes/com/zombie/game/systems/FlowFieldUpdateSystem.class b/backend/target/classes/com/zombie/game/systems/FlowFieldUpdateSystem.class new file mode 100644 index 0000000..4478a8f Binary files /dev/null and b/backend/target/classes/com/zombie/game/systems/FlowFieldUpdateSystem.class differ diff --git a/backend/target/classes/com/zombie/game/systems/PlayerInputSystem.class b/backend/target/classes/com/zombie/game/systems/PlayerInputSystem.class new file mode 100644 index 0000000..8620aea Binary files /dev/null and b/backend/target/classes/com/zombie/game/systems/PlayerInputSystem.class differ diff --git a/backend/target/classes/com/zombie/game/systems/StateSyncSystem.class b/backend/target/classes/com/zombie/game/systems/StateSyncSystem.class new file mode 100644 index 0000000..e6e3b73 Binary files /dev/null and b/backend/target/classes/com/zombie/game/systems/StateSyncSystem.class differ diff --git a/backend/target/classes/com/zombie/game/systems/ZombieMovementSystem.class b/backend/target/classes/com/zombie/game/systems/ZombieMovementSystem.class new file mode 100644 index 0000000..c5a2383 Binary files /dev/null and b/backend/target/classes/com/zombie/game/systems/ZombieMovementSystem.class differ diff --git a/backend/target/classes/com/zombie/game/systems/ZombieSpawnSystem.class b/backend/target/classes/com/zombie/game/systems/ZombieSpawnSystem.class new file mode 100644 index 0000000..9d03271 Binary files /dev/null and b/backend/target/classes/com/zombie/game/systems/ZombieSpawnSystem.class differ diff --git a/backend/target/maven-archiver/pom.properties b/backend/target/maven-archiver/pom.properties new file mode 100644 index 0000000..b98cac7 --- /dev/null +++ b/backend/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=zombie-crisis-server +groupId=com.zombie +version=1.0.0 diff --git a/backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..410544a --- /dev/null +++ b/backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,24 @@ +com/zombie/game/ecs/components/PlayerInput.class +com/zombie/game/model/MapData.class +com/zombie/game/model/GameMap.class +com/zombie/game/ecs/components/Collision.class +com/zombie/game/ecs/ECSWorld.class +com/zombie/game/server/GameWebSocketServer$1.class +com/zombie/game/ecs/components/Position.class +com/zombie/game/GameServerMain.class +com/zombie/game/server/MessageUtils.class +com/zombie/game/model/PlayerInfo.class +com/zombie/game/server/GameService$GameStateBroadcast.class +com/zombie/game/server/GameService.class +com/zombie/game/server/GameWebSocketServer.class +com/zombie/game/ecs/components/RenderInfo$EntityType.class +com/zombie/game/ecs/System.class +com/zombie/game/model/Room.class +com/zombie/game/server/MapStorage.class +com/zombie/game/systems/PlayerInputSystem.class +com/zombie/game/systems/StateSyncSystem.class +com/zombie/game/ecs/components/Health.class +com/zombie/game/model/Constants.class +com/zombie/game/ecs/components/RenderInfo.class +com/zombie/game/server/GameLoop.class +com/zombie/game/server/RoomManager.class diff --git a/backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..4a79a4b --- /dev/null +++ b/backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,28 @@ +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/server/GameWebSocketServer.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/systems/ZombieSpawnSystem.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/ECSWorld.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/model/GameMap.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/systems/StateSyncSystem.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/server/GameLoop.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/components/ZombieAI.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/systems/PlayerInputSystem.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/components/Health.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/components/Collision.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/GameServerMain.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/model/Constants.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/server/RoomManager.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/server/GameService.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/model/MapData.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/systems/ZombieMovementSystem.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/components/BlocksMovement.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/server/MessageUtils.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/System.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/model/Room.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/server/MapStorage.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/components/PlayerInput.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/components/RenderInfo.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/model/PlayerInfo.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/components/MovementCost.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/ecs/components/Position.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/systems/FlowFieldUpdateSystem.java +/Users/wfz/workspace/zp2/backend/src/main/java/com/zombie/game/model/FlowField.java diff --git a/backend/target/original-zombie-crisis-server-1.0.0.jar b/backend/target/original-zombie-crisis-server-1.0.0.jar new file mode 100644 index 0000000..f888f98 Binary files /dev/null and b/backend/target/original-zombie-crisis-server-1.0.0.jar differ diff --git a/backend/target/zombie-crisis-server-1.0.0.jar b/backend/target/zombie-crisis-server-1.0.0.jar new file mode 100644 index 0000000..f888f98 Binary files /dev/null and b/backend/target/zombie-crisis-server-1.0.0.jar differ diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..393db8d --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,597 @@ +# ZP2 全局架构文档 + +## 1. 项目概述 + +ZP2 是一款多人在线俯视角 3D 游戏,玩家在 32x32 网格地图上移动、实时同步位置。本项目是 ZP1 的精简版,仅保留房间管理、地图加载、玩家移动和基础 3D 显示功能。 + +### 技术栈 + +| 层级 | 技术 | 版本 | +|------|------|------| +| 后端 | Java | 17 | +| 构建 | Maven | - | +| 通信 | Java-WebSocket | 1.5.4 | +| JSON | Gson + Jackson | 2.10.1 / 2.15.2 | +| 前端 | JavaScript (ES Module) | - | +| 渲染 | Three.js | ^0.170.0 | +| 构建 | Vite | ^6.0.0 | + +--- + +## 2. 系统架构总览 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Frontend (Browser) │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ main.js │───→│ engine.js│───→│ scene.js │ │ client.js │ │ +│ │ (App) │ │ (Loop/ │ │ (Three.js│ │(WebSocket)│ │ +│ │ │ │ Predict) │ │ Render) │ │ │ │ +│ └──────────┘ └──────────┘ └──────────┘ └─────┬────┘ │ +│ │ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ +│ │ lobby.js │ │ input.js │ │ grid.js │ │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ │ +└─────────────────────────────────────────────────────────┼────────┘ + │ WebSocket + │ JSON +┌─────────────────────────────────────────────────────────┼────────┐ +│ Backend (Java) │ │ +│ ┌──────────────────────────────────────────────────────┤ │ +│ │ GameWebSocketServer (8080) │←───────┘ +│ │ onMessage 消息路由 → 房间CRUD / 玩家输入 / 状态广播 │ +│ └──────┬───────────┬──────────────┬───────────────────┘ +│ │ │ │ +│ ┌──────┴───┐ ┌────┴─────┐ ┌────┴──────────┐ +│ │RoomMgr │ │GameService│ │ GameLoop │ +│ │(房间生命周期)│ │(游戏会话) │ │(30TPS固定帧) │ +│ └──────────┘ └────┬─────┘ └───────────────┘ +│ │ +│ ┌───────────────────┴────────────────────────────┐ +│ │ ECSWorld │ +│ │ 实体(Entities) + 组件(组件) + 系统(Systems) │ +│ │ ┌──────────────────────────────────────────┐ │ +│ │ │ PlayerInputSystem: 移动 + 碰撞 + 朝向 │ │ +│ │ │ StateSyncSystem: 构建状态快照 → 广播 │ │ +│ │ └──────────────────────────────────────────┘ │ +│ └────────────────────────────────────────────────┘ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 3. 后端架构 + +### 3.1 启动流程 + +``` +GameServerMain.main() + ├── 解析端口参数 (默认 8080) + ├── 创建 GameWebSocketServer + ├── server.start() 启动 WebSocket 监听 + └── 注册 shutdown hook(优雅关闭) +``` + +### 3.2 网络层 — GameWebSocketServer + +核心职责:管理 WebSocket 连接、路由消息、广播状态。 + +**连接管理:** +- `connectionToPlayer`: WebSocket → playerId 映射 +- `playerToConnection`: playerId → WebSocket 映射 + +**消息路由(onMessage):** + +| 消息类型 | 处理方法 | 说明 | +|----------|----------|------| +| `create_room` | `handleCreateRoom` | 创建房间,生成 playerId 和 roomId | +| `join_room` | `handleJoinRoom` | 加入房间,广播新状态 | +| `leave_room` | `handleLeaveRoomByConn` | 离开房间,清理游戏会话 | +| `room_list` | `handleRoomList` | 返回可用房间列表 | +| `ready` | `handleReady` | 切换玩家准备状态 | +| `start_game` | `handleStartGame` | 房主开始游戏(需全员准备) | +| `player_input` | `handlePlayerInput` | 转发玩家输入到 GameService | + +**定时任务:** +- 每 2 秒广播房间列表给未加入房间的玩家 + +### 3.3 房间管理 — RoomManager + +使用 `ConcurrentHashMap` 保证线程安全: + +- `rooms`: roomId → Room 映射 +- `playerToRoom`: playerId → roomId 映射 +- `getAvailableRooms()`: 返回未开始游戏的房间 + +**Room 实体:** +- `id`: 房间唯一标识(UUID 前8位) +- `hostId`: 房主 playerId +- `players`: LinkedHashMap 保持加入顺序(最多4人) +- `gameStarted`: 游戏是否已开始 + +### 3.4 游戏服务 — GameService + +**startGame(room) 流程:** +``` +1. 加载地图文件 (maps/d540209a.json) +2. 创建 ECSWorld +3. 注册 PlayerInputSystem +4. 为每个玩家创建 PlayerEntity(分配到出生点) +5. 创建并启动 GameLoop +6. 返回每个玩家的初始化数据(mapData + players) +``` + +**processPlayerInput:** +- 查找玩家对应的 ECS 实体 +- 更新 PlayerInput 组件(dx, dy, aimX, aimY, seq) +- 使用 `synchronized(world.lock)` 保证线程安全 + +### 3.5 游戏循环 — GameLoop + +固定时间步长循环(30 TPS): + +``` +ScheduledExecutorService + └── scheduleAtFixedRate(tick, 0, 33ms) + ├── synchronized(world.lock) { world.update(dt) } + └── broadcaster.broadcast(roomId, world) +``` + +- 每 33ms(1000/30)执行一次 tick +- tick 内更新所有 ECS 系统 +- 更新完成后广播游戏状态 + +### 3.6 ECS 架构 + +#### ECSWorld + +核心容器,管理所有实体和组件: + +| 组件映射 | 类型 | 说明 | +|----------|------|------| +| `entities` | `LinkedHashSet` | 所有活跃实体 | +| `players` | `LinkedHashSet` | 玩家实体集合 | +| `positions` | `Map` | 位置组件 | +| `healths` | `Map` | 生命值组件 | +| `collisions` | `Map` | 碰撞组件 | +| `renderInfos` | `Map` | 渲染组件 | +| `playerInputs` | `Map` | 输入组件 | +| `systems` | `List` | 系统列表 | +| `playerIdToEntity` | `Map` | 玩家ID到实体ID映射 | + +**createPlayerEntity(playerId, name, x, y):** +``` +1. createEntity() 获取新实体ID +2. 添加 Position(x, y) +3. 添加 Health(100) +4. 添加 Collision(PLAYER_SIZE=0.8) +5. 添加 RenderInfo(PLAYER) +6. 添加 PlayerInput() +7. 加入 players 集合 +8. 建立 playerId → entityId 映射 +``` + +#### System 接口 + +```java +public interface System { + void update(float dt, ECSWorld world); +} +``` + +#### PlayerInputSystem + +每帧处理所有玩家输入: + +``` +对于每个玩家实体: + 1. 获取 PlayerInput / Position / Health + 2. 跳过死亡或缺少组件的实体 + 3. 计算移动:newX = x + dx * PLAYER_SPEED * TICK_INTERVAL + 4. 逐轴碰撞检测:isWalkable(newX, y, PLAYER_SIZE) + 5. 限制在地图边界 [0.5, GRID_SIZE-0.5] + 6. 计算朝向:atan2(aimX - x, aimY - y) +``` + +#### StateSyncSystem + +构建游戏状态快照: + +```json +{ + "players": [ + { + "id": "player-uuid", + "x": 12.5, + "y": 8.3, + "angle": 1.57, + "health": 100, + "lastProcessedSeq": 42 + } + ], + "gameTime": 12.5 +} +``` + +### 3.7 地图系统 + +**GameMap** 从 JSON 文件加载: + +```json +{ + "width": 32, + "height": 32, + "walls": [{"x": 5, "y": 10, "type": "static"}], + "playerSpawns": [{"x": 2, "y": 2}], + "zombieSpawns": [{"x": 15, "y": 15}] +} +``` + +- `cells[y][x]`: 0=空地, 1=墙, 2=玩家出生点, 3=僵尸出生点 +- `isWalkable(wx, wy, size)`: 检查实体四个角是否均可通行 +- `isWall(gx, gy)`: 判断网格是否为墙或越界 + +**MapStorage**: 基于文件的地图持久化(save/load/list/delete) + +--- + +## 4. 前端架构 + +### 4.1 应用入口 — main.js + +``` +App 构造函数 + ├── 创建 lobbyEl 和 gameCanvasEl + ├── 实例化 LobbyUI + └── _setupLobby() 注册回调 +``` + +**视图切换:** +- 默认显示 lobby,隐藏 canvas +- `game_started` 消息后:隐藏 lobby,显示 canvas + +**连接管理(_ensureConnection):** +``` +if (!engine): + 创建 GameEngine → connect(WS_URL) → 注册网络处理器 +else if (!connected): + 重连 +``` + +### 4.2 游戏引擎 — engine.js + +#### 核心数据结构 + +```javascript +this.players = Map +this.pendingInputs = [] // 待确认输入队列 +``` + +#### 游戏循环(固定时间步长) + +``` +requestAnimationFrame(_loop) + delta = now - lastTick + accumulator += delta + while (accumulator >= TICK_INTERVAL): + _tick() + accumulator -= TICK_INTERVAL + updateCamera(localPlayer) + scene.render() +``` + +#### 客户端预测(_applyLocalPrediction) + +``` +1. 读取输入状态 (dx, dy) +2. 计算新位置: newX = x + dx * SPEED * dt +3. 逐轴碰撞检测 +4. 限制在地图范围内 [0.5, 31.5] +5. 计算朝向: atan2(aimX - x, aimY - y) +6. 更新场景中的玩家模型 +``` + +#### 服务器校正(_reconcileLocalPlayer) + +``` +1. 用服务器权威状态覆盖本地位置 +2. 移除 seq <= lastProcessedSeq 的输入(已确认) +3. 对剩余未确认输入重新执行预测 +4. 更新场景 +``` + +这是经典的 **客户端预测 + 服务器校正** 模式: +- 客户端立即响应输入(零延迟手感) +- 服务器每帧广播权威状态 +- 客户端用 seq 号匹配已处理输入,重新模拟未确认输入 + +### 4.3 3D 渲染 — scene.js + +**场景设置:** +- 透视相机 (FOV=45°, 偏移量 (0, 25, 18)) +- 环境光 + 方向光(带阴影)+ 点光源 +- 窗口大小自适应 + +**地图渲染(buildMap):** +- 地板: 32x32 Plane (深灰色) +- 墙壁: BoxGeometry(1, 1.5, 1) (蓝灰色) +- 出生点: BoxGeometry(1, 0.1, 1) (绿色半透明) + +**玩家模型(createPlayerModel):** +``` +Group + ├── 身体: Cylinder(半径 0.4, 高 0.8) + ├── 头部: Sphere(半径 0.2, 肤色) + └── 武器: Box(0.08, 0.08, 0.5, 深色) +``` + +**鼠标射线投射(getMouseGroundPos):** +``` +鼠标屏幕坐标 → NDC → Raycaster → 与 Y=0 平面相交 → 地面世界坐标 +``` + +### 4.4 网络客户端 — client.js + +WebSocket 封装,提供: +- 事件注册:`on(type, handler)` +- 消息发送:`send(type, data)` +- 便捷方法:`createRoom`, `joinRoom`, `sendInput` 等 + +消息格式: +```json +{ + "type": "player_input", + "data": {"dx": 1, "dy": 0, "aimX": 10, "aimY": 5, "seq": 42} +} +``` + +### 4.5 输入管理 — input.js + +**键盘输入:** +- WASD / 方向键 → 移动向量 +- 对角线归一化(×0.7071) + +**鼠标输入:** +- 位置追踪 (clientX, clientY) +- 左右键状态 + +**buildInputState:** +```javascript +{ + seq: sequenceNumber++, // 递增序列号 + dx: movement.dx, // 水平输入 [-1, 1] + dy: movement.dy, // 垂直输入 [-1, 1] + aimX: mouseGroundPos.x, // 瞄准点 X + aimY: mouseGroundPos.y // 瞄准点 Y +} +``` + +### 4.6 大厅 UI — lobby.js + +两个视图状态: + +**房间列表视图:** +- 玩家名称输入框 +- 创建房间 / 刷新按钮 +- 房间列表(点击 Join 加入) + +**房间视图:** +- 玩家列表(显示准备状态) +- Ready 按钮 +- Start Game 按钮(仅房主可见) +- Leave 按钮 + +### 4.7 网格碰撞 — grid.js + +**Grid 类:** +- `parseMap(mapData)`: 解析二维数组 +- `isWall(gx, gy)`: 越界返回 true,检查 cells == 1 +- `worldToGrid(wx, wy)`: 世界坐标 → 网格坐标 +- `isWalkable(wx, wy, size)`: 检查四个角是否可通行 + +**generateDefaultMap:** +- 边界墙(最外层) +- 预设墙体段(14段) +- 4个角落出生点(周围自动清理墙体) + +--- + +## 5. 网络协议 + +### 消息格式 + +所有消息使用统一的 `{type, data}` 信封格式: + +```json +{"type": "xxx", "data": {...}} +``` + +### 消息类型汇总 + +| 方向 | type | 说明 | +|------|------|------| +| C→S | `create_room` | 创建房间 `{playerName}` | +| C→S | `join_room` | 加入房间 `{roomId, playerName}` | +| C→S | `leave_room` | 离开房间 | +| C→S | `room_list` | 请求房间列表 | +| C→S | `ready` | 切换准备状态 | +| C→S | `start_game` | 开始游戏(仅房主) | +| C→S | `player_input` | 玩家输入 `{dx, dy, aimX, aimY, seq}` | +| S→C | `room_list` | 房间列表 `{rooms[]}` | +| S→C | `room_state` | 房间状态 `{roomId, hostId, isHost, playerId, players[]}` | +| S→C | `game_started` | 游戏开始 `{playerId, mapData, players[]}` | +| S→C | `game_state` | 游戏状态 `{players[{id, x, y, angle, health, lastProcessedSeq}], gameTime}` | +| S→C | `error` | 错误 `{message}` | + +### 通信时序 + +``` +Client A Server Client B + | | | + |── create_room ──────→| | + |←── room_state ──────| | + | |←── join_room ────────| + |←── room_state ──────|─── room_state ──────→| + | | | + |── ready ───────────→| | + |←── room_state ──────|─── room_state ──────→| + | | | + |── start_game ──────→| | + |←── game_started ────|─── game_started ────→| + | | | + |═══ player_input ═══→| | + | |═══ game_state ══════→| + |←══ game_state ══════| | +``` + +--- + +## 6. 数据流 + +### 6.1 玩家移动完整数据流 + +``` +InputManager (键盘/鼠标) + │ + ▼ +buildInputState() → {seq, dx, dy, aimX, aimY} + │ + ├──→ _applyLocalPrediction() [客户端预测] + │ ├── 新位置计算 + │ ├── 碰撞检测 + │ └── 更新 Three.js 模型 + │ + └──→ network.sendInput() ──→ WebSocket → Server + │ + ▼ + GameService.processPlayerInput() + │ + ▼ + ECSWorld.update(dt) + │ + ▼ + PlayerInputSystem.update() + │ + ├── 更新 Position 组件 + └── 更新朝向角度 + │ + ▼ + StateSyncSystem.buildGameState() + │ + ▼ + broadcast → WebSocket → Client + │ + ▼ + _processServerState() + │ + ├── 远程玩家: 直接更新 + └── 本地玩家: _reconcileLocalPlayer() + ├── 应用服务器状态 + ├── 移除已确认输入 + └── 重放未确认输入 +``` + +### 6.2 生命周期 + +``` +[创建房间] + Client → create_room → Server → RoomManager.addRoom() → Room 实例 + +[加入房间] + Client → join_room → Server → RoomManager.joinRoom() → 广播 room_state + +[开始游戏] + Client → start_game → Server → GameService.startGame() + ├── 加载地图 + ├── 创建 ECSWorld + PlayerInputSystem + ├── 创建 PlayerEntity(分配出生点) + ├── 启动 GameLoop (30 TPS) + └── 广播 game_started(含 mapData + players) + +[游戏进行中] + GameLoop.tick() → ECSWorld.update() → PlayerInputSystem → 广播 game_state + +[玩家断开] + onClose → RoomManager.leaveRoom() → 清理游戏会话(如房间为空) +``` + +--- + +## 7. 关键设计模式 + +### 7.1 ECS (Entity-Component-System) + +- **Entity**: 整数 ID,仅作为标识符 +- **Component**: 纯数据(Position, Health, PlayerInput 等) +- **System**: 处理逻辑(PlayerInputSystem, StateSyncSystem) + +优势:解耦数据与逻辑,便于扩展新实体类型。 + +### 7.2 客户端预测 + 服务器校正 + +- 客户端立即响应输入,不等待服务器 +- 服务器每帧广播权威状态 +- 客户端用序列号匹配已处理输入,重新模拟未确认输入 +- 结果:零延迟手感 + 服务器权威防作弊 + +### 7.3 固定时间步长 + +- 客户端和服务器均使用 30 TPS +- 渲染帧率与逻辑帧率解耦(渲染用 requestAnimationFrame) +- 保证确定性模拟 + +### 7.4 线程安全 + +- ECSWorld 使用 `synchronized(lock)` 保护 +- RoomManager 使用 `ConcurrentHashMap` +- GameLoop 使用单线程 ScheduledExecutorService + +--- + +## 8. 文件索引 + +### 后端 + +| 文件 | 职责 | +|------|------| +| `GameServerMain.java` | 服务器入口,启动 WebSocket | +| `model/Constants.java` | 游戏常量 + 消息类型 | +| `model/Room.java` | 房间实体 | +| `model/PlayerInfo.java` | 玩家信息 | +| `model/GameMap.java` | 地图加载 + 碰撞检测 | +| `model/MapData.java` | 地图序列化 DTO | +| `model/Wall.java` | 墙体抽象基类 | +| `model/StaticWall.java` | 不可破坏墙体 | +| `ecs/ECSWorld.java` | ECS 世界容器 | +| `ecs/System.java` | 系统接口 | +| `ecs/components/Position.java` | 位置组件 | +| `ecs/components/PlayerInput.java` | 输入组件 | +| `ecs/components/Health.java` | 生命值组件 | +| `ecs/components/Collision.java` | 碰撞组件 | +| `ecs/components/RenderInfo.java` | 渲染信息组件 | +| `server/GameWebSocketServer.java` | WebSocket 服务器 | +| `server/RoomManager.java` | 房间生命周期管理 | +| `server/GameService.java` | 游戏会话管理 | +| `server/GameLoop.java` | 30TPS 固定帧循环 | +| `server/MessageUtils.java` | JSON 安全提取工具 | +| `server/MapStorage.java` | 文件地图持久化 | +| `systems/PlayerInputSystem.java` | 移动 + 碰撞处理 | +| `systems/StateSyncSystem.java` | 状态快照构建 | + +### 前端 + +| 文件 | 职责 | +|------|------| +| `index.html` | HTML 外壳 | +| `src/main.js` | 应用入口,大厅/游戏切换 | +| `src/style.css` | 全局样式 | +| `src/game/engine.js` | 游戏引擎,预测 + 校正 | +| `src/game/scene.js` | Three.js 3D 渲染 | +| `src/network/client.js` | WebSocket 客户端 | +| `src/ui/lobby.js` | 大厅 UI | +| `src/utils/constants.js` | 游戏常量 | +| `src/utils/grid.js` | 网格碰撞 + 默认地图 | +| `src/utils/input.js` | 键盘/鼠标输入 | diff --git a/docs/migration-plan-zh.md b/docs/migration-plan-zh.md new file mode 100644 index 0000000..a7b4fad --- /dev/null +++ b/docs/migration-plan-zh.md @@ -0,0 +1,256 @@ +# ZP1 最小化迁移计划 + +## 目标 +将 zp1 重写为 zp2,仅保留:房间管理、地图加载、玩家移动、前端必要显示。技术栈不变(Java + Vite/Three.js),保留 ECS 架构和客户端预测。 + +## 范围 +- **保留**:房间系统、地图加载/渲染、玩家移动(WASD)、客户端预测、WebSocket 通信 +- **移除**:僵尸、武器/子弹、炮塔、火焰区域、掉落物、爆炸/特效、HUD、设置界面、地图编辑器、模板系统、对象池 + +--- + +## 阶段一:后端骨架 + +### 1.1 项目结构 +``` +zp2/backend/ +├── pom.xml +└── src/main/java/com/zombie/game/ + ├── GameServerMain.java # 入口,仅启动 WebSocket 服务器 + ├── model/ + │ ├── Constants.java # 精简为:GRID_SIZE, TICK_RATE, PLAYER_SIZE, PLAYER_SPEED, MSG_* + │ ├── Room.java # 保持原样 + │ ├── PlayerInfo.java # 保持原样 + │ ├── GameMap.java # 精简:移除流向场,仅保留 isWall/isWalkable + │ ├── MapData.java # 保持,用于地图反序列化 + │ └── StaticWall.java # 仅保留静态墙 + ├── server/ + │ ├── GameWebSocketServer.java # 精简:房间操作 + 玩家输入 + 状态广播 + │ ├── RoomManager.java # 保持原样 + │ ├── GameService.java # 精简:仅注册 PlayerInputSystem + StateSyncSystem + │ ├── GameLoop.java # 保持原样(30 TPS) + │ ├── MessageUtils.java # 保持原样 + │ └── MapStorage.java # 保留,用于加载地图文件 + ├── ecs/ + │ ├── ECSWorld.java # 精简:仅管理 players,移除僵尸/子弹/掉落物/炮塔/火焰区域 + │ ├── System.java # 保持接口 + │ └── components/ + │ ├── Position.java # 保持 + │ ├── PlayerInput.java # 精简:移除手雷相关字段 + │ ├── Health.java # 精简 + │ ├── Collision.java # 保持 + │ └── RenderInfo.java # 精简 + └── systems/ + ├── PlayerInputSystem.java # 保持:移动 + 碰撞 + 朝向 + └── StateSyncSystem.java # 精简:仅广播玩家位置 +``` + +### 1.2 关键变更 + +**Constants.java** +- 保留:`GRID_SIZE=32`, `TICK_RATE=30`, `PLAYER_SIZE=0.8f`, `PLAYER_SPEED=5.0f`, `MSG_*` 消息类型常量 +- 移除:`ZOMBIE_SIZE`, `LOOT_*`, 武器常量 + +**GameMap.java** +- 保留:`loadFromJson()`, `isWall()`, `isWalkable()`, `getCells()`, `getSpawnPoints()` +- 移除:整个 `FlowField`,`updateFlowField()`, `getFlowDirection()`, `addNutWall()`, 坚果墙逻辑 +- 仅保留 `StaticWall`(移除 `NutWall`, `TurretWall`) + +**ECSWorld.java** +- 保留:`entities`, `players`, `playerIdToEntity`, `map`, `systems`, `lock` +- 移除:`zombies`, `playerBullets`, `zombieBullets`, `loots`, `fireZones`, `turrets`, `wallEntities` +- 移除:所有对象池 +- 移除:`createZombieEntity()`, `createBulletEntity()`, `createGrenadeEntity()`, `createMolotovEntity()`, `createLootEntity()`, `createFireZoneEntity()`, `createTurretEntity()` +- 保留:`createPlayerEntity()`(精简版,无 WeaponState/RespawnState) +- 保留:`update(dt)` — 清理临时数据,遍历系统执行 + +**GameService.java** +- `startGame()`:仅注册 `PlayerInputSystem` 和 `StateSyncSystem`(移除其他10个系统) +- 保留:`processPlayerInput()`, `stopGame()` + +**PlayerInputSystem.java** +- 保留:移动(dx/dy * speed)、逐轴墙壁碰撞、朝向计算 +- 移除:武器射击逻辑(如有) + +**StateSyncSystem.java** +- 精简广播为:`{players: [{id, x, y, angle, health}], gameTime}` +- 移除:zombies, bullets, zombieBullets, loots, explosions, removedBullets, waveNumber, score + +**Room.java / RoomManager.java / GameLoop.java / MessageUtils.java** +- 保持原样(或微调清理) + +**GameWebSocketServer.java** +- 保留:`onMessage()` 分发、房间 CRUD 处理、`handlePlayerInput()`、`broadcastGameState()` +- 可选移除:房间列表广播定时器(或保留用于大厅刷新) + +### 1.3 地图数据 +- 保留 `maps/d540209a.json` 格式(walls + playerSpawns + zombieSpawns) +- 复制一份到 `zp2/maps/` + +--- + +## 阶段二:前端骨架 + +### 2.1 项目结构 +``` +zp2/frontend/ +├── package.json # 同样依赖(three, vite) +├── vite.config.js # 同样代理配置 +├── index.html # 同样最小化外壳 +└── src/ + ├── main.js # 精简:大厅 → 游戏切换 + ├── style.css # 仅大厅 + 房间 + 画布样式 + ├── game/ + │ ├── engine.js # 精简:仅玩家,无僵尸/子弹/战斗 + │ └── scene.js # 精简:仅地图 + 玩家,无特效 + ├── network/ + │ └── client.js # 保持原样 + ├── ui/ + │ └── lobby.js # 保持原样(房间管理 UI) + └── utils/ + ├── constants.js # 精简:仅地图 + 玩家 + 网络常量 + ├── grid.js # 精简:仅 Grid 类 + generateDefaultMap + └── input.js # 精简:仅移动 + 瞄准 +``` + +### 2.2 关键变更 + +**constants.js** +- 保留:`GRID_SIZE`, `CELL_SIZE`, `PLAYER_SIZE`, `TICK_RATE`, `TICK_INTERVAL` +- 保留:`PLAYER_CONFIG`(MAX_HEALTH, SPEED) +- 保留:`MSG_TYPE`(所有房间 + 游戏消息类型) +- 移除:`WEAPONS`, `WEAPON_CONFIG`, `ZOMBIE_CONFIG`, `ZOMBIE_SIZE` + +**grid.js** +- 保留:`Grid` 类的 `parseMap()`, `isWall()`, `worldToGrid()`, `gridToWorld()`, `isWalkable()` +- 保留:`generateDefaultMap()`(备用默认地图) +- 移除:`findPath()`, `getSpawnPoints()`, `isSpawnPoint()` + +**input.js** +- 保留:`attach()`, `detach()`, `getMovement()`, `buildInputState()`(仅移动 + 瞄准) +- 移除:`getSelectedWeapon()`, 武器快捷键绑定 +- 精简 `buildInputState()` 返回 `{seq, dx, dy, aimX, aimY}`(无 firing, weaponIndex, grenade 字段) + +**engine.js(约666→250行)** +- 保留:`connect()`, `start()`, `stop()`, `_loop()`, `_tick()` +- 保留:`_applyLocalPrediction()` — 带碰撞的移动预测 +- 保留:`_reconcileLocalPlayer()` — 服务器校正 +- 保留:`_initPlayers()`, `_addPlayer()`, `_removePlayer()` +- 精简 `_processServerState()`:仅同步玩家位置,移除所有僵尸/子弹/掉落物/炮塔/特效处理 +- 移除:`_handleGrenadeCharge()`, `_checkBulletHit()`, 武器状态, 手雷状态 +- 移除:`zombies`, `bullets`, `zombieBullets`, `loots`, `turrets` 映射表 + +**scene.js(约1331→250行)** +- 保留:构造函数(场景、相机、渲染器、灯光、窗口自适应) +- 保留:`buildMap(mapData)` — 地板 + 墙壁 +- 保留:`createPlayerModel()`, `addPlayer()`, `removePlayer()`, `updatePlayer()` +- 保留:`updateCamera()`, `getMouseGroundPos()`, `render()` +- 移除:所有僵尸渲染(约180行) +- 移除:所有子弹渲染(约230行) +- 移除:所有特效(约250行) +- 移除:炮塔、掉落物、坚果墙、手雷目标指示器(约215行) +- 移除:`updateEffects()`(或简化为空) + +**lobby.js** — 保持原样 + +**main.js** +- 保留:App 类、大厅绑定、游戏开始切换 +- 移除:HUD 初始化、设置界面初始化 +- 移除:`_updateHUD()` 调用 + +**style.css** +- 保留:大厅样式、房间样式、画布容器样式 +- 移除:HUD 样式、武器面板、手雷充能、击杀信息、设置弹窗 + +--- + +## 阶段三:清理与验证 + +### 3.1 消息协议验证 +确保以下消息端到端正常工作: +- `CREATE_ROOM` / `JOIN_ROOM` / `LEAVE_ROOM` / `READY` / `START_GAME` +- `ROOM_LIST` / `ROOM_STATE` / `GAME_STARTED` / `ERROR` +- `PLAYER_INPUT`(仅移动:dx, dy, aimX, aimY, seq) +- `GAME_STATE`(仅玩家数据) + +### 3.2 测试流程 +1. 启动后端:`mvn package && java -jar target/*.jar` +2. 启动前端:`npm run dev` +3. 打开两个浏览器标签页 +4. 标签页1:创建房间 → 标签页2:加入房间 → 标签页1:开始游戏 +5. 两个玩家应能在 32x32 地图上互相看到对方移动 +6. WASD 移动响应流畅(客户端预测) +7. 玩家与墙壁碰撞正确 + +--- + +## 待创建文件清单(共36个) + +### 后端(23个文件) +| 序号 | 文件 | 来源 | +|------|------|------| +| 1 | `backend/pom.xml` | 从 zp1 复制 | +| 2 | `GameServerMain.java` | 精简版 | +| 3 | `model/Constants.java` | 精简版 | +| 4 | `model/Room.java` | 基本保持 | +| 5 | `model/PlayerInfo.java` | 基本保持 | +| 6 | `model/GameMap.java` | 精简版(无流向场) | +| 7 | `model/MapData.java` | 基本保持 | +| 8 | `model/StaticWall.java` | 基本保持 | +| 9 | `ecs/System.java` | 保持 | +| 10 | `ecs/ECSWorld.java` | 精简版 | +| 11 | `ecs/components/Position.java` | 保持 | +| 12 | `ecs/components/PlayerInput.java` | 精简版 | +| 13 | `ecs/components/Health.java` | 精简版 | +| 14 | `ecs/components/Collision.java` | 保持 | +| 15 | `ecs/components/RenderInfo.java` | 精简版 | +| 16 | `systems/PlayerInputSystem.java` | 基本保持 | +| 17 | `systems/StateSyncSystem.java` | 精简版 | +| 18 | `server/GameWebSocketServer.java` | 精简版 | +| 19 | `server/RoomManager.java` | 基本保持 | +| 20 | `server/GameService.java` | 精简版 | +| 21 | `server/GameLoop.java` | 基本保持 | +| 22 | `server/MessageUtils.java` | 保持 | +| 23 | `server/MapStorage.java` | 基本保持 | + +### 前端(12个文件) +| 序号 | 文件 | 来源 | +|------|------|------| +| 24 | `frontend/package.json` | 保持 | +| 25 | `frontend/vite.config.js` | 保持 | +| 26 | `frontend/index.html` | 保持 | +| 27 | `frontend/src/main.js` | 精简版 | +| 28 | `frontend/src/style.css` | 精简版 | +| 29 | `frontend/src/game/engine.js` | 精简版 | +| 30 | `frontend/src/game/scene.js` | 精简版 | +| 31 | `frontend/src/network/client.js` | 基本保持 | +| 32 | `frontend/src/ui/lobby.js` | 保持 | +| 33 | `frontend/src/utils/constants.js` | 精简版 | +| 34 | `frontend/src/utils/grid.js` | 精简版 | +| 35 | `frontend/src/utils/input.js` | 精简版 | + +### 数据文件(1个) +| 序号 | 文件 | 来源 | +|------|------|------| +| 36 | `maps/d540209a.json` | 从 zp1 复制 | + +--- + +## 关键设计决策 + +| 决策 | 选择 | 原因 | +|------|------|------| +| 技术栈 | Java 17 + Vite + Three.js | 与 zp1 一致,迁移成本最低 | +| ECS 架构 | 保留 | 结构清晰,便于后续扩展 | +| 客户端预测 | 保留 | 移动手感更好,代码量不大 | +| 流向场寻路 | 移除 | 仅僵尸 AI 需要 | +| 地图格式 | 保持 JSON | 兼容现有地图文件 | +| WebSocket 协议 | 保持相同消息类型 | 前后端保持同步 | +| 大厅 UI | 保持原样 | 房间管理是核心功能 | +| HUD/设置 | 移除 | 不在本次范围内 | +| 地图编辑器 | 移除 | 不在本次范围内 | + +## 风险点 +- **StateSyncSystem**:精简时需小心处理消息格式,确保前端能正确解析 +- **ECSWorld**:移除组件映射表后,需检查所有引用这些组件的系统 +- **客户端预测**:engine.js 中的校正逻辑引用了武器状态,需验证精简版是否正常工作 diff --git a/docs/migration-plan.md b/docs/migration-plan.md new file mode 100644 index 0000000..fab4ed6 --- /dev/null +++ b/docs/migration-plan.md @@ -0,0 +1,143 @@ +# ZP1 Minimal Migration Plan + +## Goal +Rewrite zp1 into zp2, keeping only: room management, map loading, player movement, and minimal frontend display. Same tech stack (Java + Vite/Three.js), ECS architecture, client prediction. + +## Scope +- **Keep**: Room system, map loading/rendering, player movement (WASD), client prediction, WebSocket communication +- **Strip**: Zombies, weapons/bullets, turrets, fire zones, loots, explosions/effects, HUD, settings, map designer, template system, object pool + +--- + +## Phase 1: Backend Skeleton + +### 1.1 Project structure +``` +zp2/backend/ +├── pom.xml +└── src/main/java/com/zombie/game/ + ├── GameServerMain.java + ├── model/ + │ ├── Constants.java + │ ├── Room.java + │ ├── PlayerInfo.java + │ ├── GameMap.java + │ ├── MapData.java + │ └── StaticWall.java + ├── server/ + │ ├── GameWebSocketServer.java + │ ├── RoomManager.java + │ ├── GameService.java + │ ├── GameLoop.java + │ ├── MessageUtils.java + │ └── MapStorage.java + ├── ecs/ + │ ├── ECSWorld.java + │ ├── System.java + │ └── components/ + │ ├── Position.java + │ ├── PlayerInput.java + │ ├── Health.java + │ ├── Collision.java + │ └── RenderInfo.java + └── systems/ + ├── PlayerInputSystem.java + └── StateSyncSystem.java +``` + +### 1.2 Key changes + +**Constants.java** — Keep `GRID_SIZE`, `TICK_RATE`, `PLAYER_SIZE`, `PLAYER_SPEED`, `MSG_*`. Remove zombie/weapon/loot constants. + +**GameMap.java** — Keep `loadFromJson()`, `isWall()`, `isWalkable()`, `getCells()`, `getSpawnPoints()`. Remove `FlowField` entirely. + +**ECSWorld.java** — Keep `entities`, `players`, `playerIdToEntity`, `map`, `systems`, `lock`. Remove zombies/bullets/loots/turrets/fireZones. Remove object pools. Keep `createPlayerEntity()` only. + +**GameService.java** — Register only `PlayerInputSystem` + `StateSyncSystem`. + +**StateSyncSystem.java** — Broadcast `{players: [{id, x, y, angle, health}], gameTime}` only. + +**GameWebSocketServer.java** — Keep room CRUD + player input + state broadcast. + +### 1.3 Map data +- Copy `maps/d540209a.json` (walls + playerSpawns + zombieSpawns) + +--- + +## Phase 2: Frontend Skeleton + +### 2.1 Project structure +``` +zp2/frontend/ +├── package.json +├── vite.config.js +├── index.html +└── src/ + ├── main.js + ├── style.css + ├── game/ + │ ├── engine.js + │ └── scene.js + ├── network/ + │ └── client.js + ├── ui/ + │ └── lobby.js + └── utils/ + ├── constants.js + ├── grid.js + └── input.js +``` + +### 2.2 Key changes + +**constants.js** — Keep `GRID_SIZE`, `PLAYER_SIZE`, `TICK_RATE`, `TICK_INTERVAL`, `PLAYER_CONFIG`, `MSG_TYPE`. Remove `WEAPONS`, `WEAPON_CONFIG`, `ZOMBIE_CONFIG`. + +**grid.js** — Keep `Grid` class (`parseMap`, `isWall`, `worldToGrid`, `gridToWorld`, `isWalkable`). Remove `findPath`, `getSpawnPoints`. + +**input.js** — Keep `attach/detach`, `getMovement`, `buildInputState` (movement + aim only). Remove weapon bindings. + +**engine.js (~666→~250 lines)** — Keep connect, loop, tick, local prediction, reconciliation. Strip zombie/bullet/loot/turret processing. + +**scene.js (~1331→~250 lines)** — Keep constructor, lighting, `buildMap`, player rendering, camera, `getMouseGroundPos`. Remove zombie/bullet/effect/turret/loot rendering. + +**lobby.js** — Keep as-is. + +**main.js** — Keep lobby wiring + game transition. Remove HUD/settings. + +**style.css** — Keep lobby + room + canvas styles. Remove HUD/weapon/kill-feed styles. + +--- + +## Phase 3: Cleanup & Verification + +### 3.1 Message protocol +- `CREATE_ROOM` / `JOIN_ROOM` / `LEAVE_ROOM` / `READY` / `START_GAME` +- `ROOM_LIST` / `ROOM_STATE` / `GAME_STARTED` / `ERROR` +- `PLAYER_INPUT` (dx, dy, aimX, aimY, seq) +- `GAME_STATE` (players only) + +### 3.2 Test flow +1. `mvn package && java -jar target/*.jar` +2. `npm run dev` +3. Open two tabs → Create room → Join room → Start game +4. Both players see each other move, WASD responsive, wall collision works + +--- + +## Files to Create (36 total) + +| # | File | Source | +|---|------|--------| +| 1-23 | Backend Java files (see 1.1) | Simplified from zp1 | +| 24-35 | Frontend JS/HTML/CSS (see 2.1) | Simplified from zp1 | +| 36 | `maps/d540209a.json` | Copy from zp1 | + +## Key Decisions + +| Decision | Choice | Reason | +|----------|--------|--------| +| Tech stack | Java 17 + Vite + Three.js | Same as zp1 | +| ECS | Keep | Structure preserved | +| Client prediction | Keep | Better movement feel | +| Flow field | Remove | Zombie-only feature | +| HUD/Settings | Remove | Not in scope | diff --git a/frontend/dist/assets/index-BeHdn1hs.css b/frontend/dist/assets/index-BeHdn1hs.css new file mode 100644 index 0000000..ca5db79 --- /dev/null +++ b/frontend/dist/assets/index-BeHdn1hs.css @@ -0,0 +1 @@ +*{margin:0;padding:0;box-sizing:border-box}body{background:#1a1a2e;color:#e0e0e0;font-family:Segoe UI,system-ui,-apple-system,sans-serif;overflow:hidden}canvas{display:block;width:100vw;height:100vh}.lobby{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:2rem}.lobby h1{font-size:3rem;color:#48f;margin-bottom:2rem;text-shadow:0 0 20px rgba(68,136,255,.3)}.lobby h2{font-size:2rem;color:#48f;margin-bottom:1.5rem}.lobby-form{display:flex;flex-direction:column;gap:1rem;width:100%;max-width:400px}.lobby-form input{padding:.75rem 1rem;background:#16213e;border:1px solid #334466;border-radius:6px;color:#e0e0e0;font-size:1rem;outline:none}.lobby-form input:focus{border-color:#48f}.lobby-actions{display:flex;gap:.5rem}button{padding:.6rem 1.2rem;background:#48f;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:.9rem;font-weight:600;transition:background .2s}button:hover{background:#37e}button:active{background:#26d}.room-list{margin-top:1.5rem;width:100%;max-width:500px}.room-list .empty{text-align:center;color:#667;padding:2rem}.room-item{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;background:#16213e;border-radius:6px;margin-bottom:.5rem}.room-name{font-weight:600;flex:1}.room-players{color:#889;margin-right:1rem}.join-btn{padding:.4rem .8rem;font-size:.8rem}.room-view{max-width:500px;width:100%}.player-list{width:100%;margin-bottom:1.5rem}.player-item{display:flex;justify-content:space-between;align-items:center;padding:.6rem 1rem;background:#16213e;border-radius:6px;margin-bottom:.4rem}.player-item.ready{border-left:3px solid #44ff88}.ready-status{font-size:.8rem;font-weight:600;color:#667}.player-item.ready .ready-status{color:#4f8}.room-actions{display:flex;gap:.5rem;justify-content:center}#startBtn{background:#4a4}#startBtn:hover{background:#393}#leaveBtn{background:#a44}#leaveBtn:hover{background:#933} diff --git a/frontend/dist/assets/index-BpzQrr0d.js b/frontend/dist/assets/index-BpzQrr0d.js new file mode 100644 index 0000000..f156253 --- /dev/null +++ b/frontend/dist/assets/index-BpzQrr0d.js @@ -0,0 +1,3863 @@ +(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))n(r);new MutationObserver(r=>{for(const s of r)if(s.type==="childList")for(const a of s.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&n(a)}).observe(document,{childList:!0,subtree:!0});function t(r){const s={};return r.integrity&&(s.integrity=r.integrity),r.referrerPolicy&&(s.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?s.credentials="include":r.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function n(r){if(r.ep)return;r.ep=!0;const s=t(r);fetch(r.href,s)}})();const In=1,Js=.8,Go=30,Ci=1e3/Go,Pi={MAX_HEALTH:100,SPEED:5},dt={CREATE_ROOM:"create_room",JOIN_ROOM:"join_room",LEAVE_ROOM:"leave_room",ROOM_LIST:"room_list",ROOM_STATE:"room_state",READY:"ready",START_GAME:"start_game",GAME_STARTED:"game_started",PLAYER_INPUT:"player_input",GAME_STATE:"game_state",PLAYER_JOIN:"player_join",PLAYER_LEAVE:"player_leave",ERROR:"error"};class Vo{constructor(e){this.cells=[],this.width=0,this.height=0,this.parseMap(e)}parseMap(e){var t;this.height=e.length,this.width=((t=e[0])==null?void 0:t.length)||0,this.cells=[];for(let n=0;n=this.width||t<0||t>=this.height)return!0;const n=this.cells[t][e];return n===1||n===4}worldToGrid(e,t){return{x:Math.floor(e/In),y:Math.floor(t/In)}}gridToWorld(e,t){return{x:e*In+In/2,y:t*In+In/2}}isWalkable(e,t,n){const r=n/2,s=[{x:e-r,y:t-r},{x:e+r,y:t-r},{x:e-r,y:t+r},{x:e+r,y:t+r}];for(const a of s){const o=this.worldToGrid(a.x,a.y);if(this.isWall(o.x,o.y))return!1}return!0}}class ko{constructor(){this.keys={},this.mouse={x:0,y:0,left:!1,right:!1,groundX:0,groundY:0},this.keyBindings={moveUp:"KeyW",moveDown:"KeyS",moveLeft:"KeyA",moveRight:"KeyD"},this.sequenceNumber=0,this._onKeyDown=e=>{this.keys[e.code]=!0},this._onKeyUp=e=>{this.keys[e.code]=!1},this._onMouseMove=e=>{this.mouse.x=e.clientX,this.mouse.y=e.clientY},this._onMouseDown=e=>{e.button===0&&(this.mouse.left=!0),e.button===2&&(this.mouse.right=!0)},this._onMouseUp=e=>{e.button===0&&(this.mouse.left=!1),e.button===2&&(this.mouse.right=!1)},this._onContextMenu=e=>e.preventDefault()}attach(){window.addEventListener("keydown",this._onKeyDown),window.addEventListener("keyup",this._onKeyUp),window.addEventListener("mousemove",this._onMouseMove),window.addEventListener("mousedown",this._onMouseDown),window.addEventListener("mouseup",this._onMouseUp),window.addEventListener("contextmenu",this._onContextMenu)}detach(){window.removeEventListener("keydown",this._onKeyDown),window.removeEventListener("keyup",this._onKeyUp),window.removeEventListener("mousemove",this._onMouseMove),window.removeEventListener("mousedown",this._onMouseDown),window.removeEventListener("mouseup",this._onMouseUp),window.removeEventListener("contextmenu",this._onContextMenu)}getMovement(){let e=0,t=0;return(this.keys[this.keyBindings.moveUp]||this.keys.ArrowUp)&&(t-=1),(this.keys[this.keyBindings.moveDown]||this.keys.ArrowDown)&&(t+=1),(this.keys[this.keyBindings.moveLeft]||this.keys.ArrowLeft)&&(e-=1),(this.keys[this.keyBindings.moveRight]||this.keys.ArrowRight)&&(e+=1),e!==0&&t!==0&&(e*=.7071,t*=.7071),{dx:e,dy:t}}buildInputState(e){const t=this.getMovement();return this.sequenceNumber++,{seq:this.sequenceNumber,dx:t.dx,dy:t.dy,aimX:e.x,aimY:e.y}}}/** + * @license + * Copyright 2010-2024 Three.js Authors + * SPDX-License-Identifier: MIT + */const Ls="170",Wo=0,Qs=1,Xo=2,io=1,ro=2,jt=3,gn=0,vt=1,Jt=2,mn=0,jn=1,ea=2,ta=3,na=4,qo=5,wn=100,Yo=101,Ko=102,Zo=103,$o=104,jo=200,Jo=201,Qo=202,el=203,kr=204,Wr=205,tl=206,nl=207,il=208,rl=209,sl=210,al=211,ol=212,ll=213,cl=214,Xr=0,qr=1,Yr=2,ei=3,Kr=4,Zr=5,$r=6,jr=7,Ds=0,hl=1,ul=2,_n=0,dl=1,fl=2,pl=3,ml=4,_l=5,gl=6,vl=7,so=300,ti=301,ni=302,Jr=303,Qr=304,or=306,es=1e3,Cn=1001,ts=1002,Ot=1003,xl=1004,Li=1005,Gt=1006,ur=1007,Pn=1008,nn=1009,ao=1010,oo=1011,Mi=1012,Us=1013,Ln=1014,Qt=1015,Si=1016,Is=1017,Ns=1018,ii=1020,lo=35902,co=1021,ho=1022,Ft=1023,uo=1024,fo=1025,Jn=1026,ri=1027,po=1028,Fs=1029,mo=1030,Os=1031,Bs=1033,Ji=33776,Qi=33777,er=33778,tr=33779,ns=35840,is=35841,rs=35842,ss=35843,as=36196,os=37492,ls=37496,cs=37808,hs=37809,us=37810,ds=37811,fs=37812,ps=37813,ms=37814,_s=37815,gs=37816,vs=37817,xs=37818,Ms=37819,Ss=37820,Es=37821,nr=36492,ys=36494,Ts=36495,_o=36283,As=36284,bs=36285,ws=36286,Ml=3200,Sl=3201,go=0,El=1,pn="",Rt="srgb",ai="srgb-linear",lr="linear",qe="srgb",Nn=7680,ia=519,yl=512,Tl=513,Al=514,vo=515,bl=516,wl=517,Rl=518,Cl=519,ra=35044,sa="300 es",en=2e3,rr=2001;class oi{addEventListener(e,t){this._listeners===void 0&&(this._listeners={});const n=this._listeners;n[e]===void 0&&(n[e]=[]),n[e].indexOf(t)===-1&&n[e].push(t)}hasEventListener(e,t){if(this._listeners===void 0)return!1;const n=this._listeners;return n[e]!==void 0&&n[e].indexOf(t)!==-1}removeEventListener(e,t){if(this._listeners===void 0)return;const r=this._listeners[e];if(r!==void 0){const s=r.indexOf(t);s!==-1&&r.splice(s,1)}}dispatchEvent(e){if(this._listeners===void 0)return;const n=this._listeners[e.type];if(n!==void 0){e.target=this;const r=n.slice(0);for(let s=0,a=r.length;s>8&255]+ht[i>>16&255]+ht[i>>24&255]+"-"+ht[e&255]+ht[e>>8&255]+"-"+ht[e>>16&15|64]+ht[e>>24&255]+"-"+ht[t&63|128]+ht[t>>8&255]+"-"+ht[t>>16&255]+ht[t>>24&255]+ht[n&255]+ht[n>>8&255]+ht[n>>16&255]+ht[n>>24&255]).toLowerCase()}function gt(i,e,t){return Math.max(e,Math.min(t,i))}function Pl(i,e){return(i%e+e)%e}function fr(i,e,t){return(1-t)*i+t*e}function ui(i,e){switch(e.constructor){case Float32Array:return i;case Uint32Array:return i/4294967295;case Uint16Array:return i/65535;case Uint8Array:return i/255;case Int32Array:return Math.max(i/2147483647,-1);case Int16Array:return Math.max(i/32767,-1);case Int8Array:return Math.max(i/127,-1);default:throw new Error("Invalid component type.")}}function _t(i,e){switch(e.constructor){case Float32Array:return i;case Uint32Array:return Math.round(i*4294967295);case Uint16Array:return Math.round(i*65535);case Uint8Array:return Math.round(i*255);case Int32Array:return Math.round(i*2147483647);case Int16Array:return Math.round(i*32767);case Int8Array:return Math.round(i*127);default:throw new Error("Invalid component type.")}}class He{constructor(e=0,t=0){He.prototype.isVector2=!0,this.x=e,this.y=t}get width(){return this.x}set width(e){this.x=e}get height(){return this.y}set height(e){this.y=e}set(e,t){return this.x=e,this.y=t,this}setScalar(e){return this.x=e,this.y=e,this}setX(e){return this.x=e,this}setY(e){return this.y=e,this}setComponent(e,t){switch(e){case 0:this.x=t;break;case 1:this.y=t;break;default:throw new Error("index is out of range: "+e)}return this}getComponent(e){switch(e){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+e)}}clone(){return new this.constructor(this.x,this.y)}copy(e){return this.x=e.x,this.y=e.y,this}add(e){return this.x+=e.x,this.y+=e.y,this}addScalar(e){return this.x+=e,this.y+=e,this}addVectors(e,t){return this.x=e.x+t.x,this.y=e.y+t.y,this}addScaledVector(e,t){return this.x+=e.x*t,this.y+=e.y*t,this}sub(e){return this.x-=e.x,this.y-=e.y,this}subScalar(e){return this.x-=e,this.y-=e,this}subVectors(e,t){return this.x=e.x-t.x,this.y=e.y-t.y,this}multiply(e){return this.x*=e.x,this.y*=e.y,this}multiplyScalar(e){return this.x*=e,this.y*=e,this}divide(e){return this.x/=e.x,this.y/=e.y,this}divideScalar(e){return this.multiplyScalar(1/e)}applyMatrix3(e){const t=this.x,n=this.y,r=e.elements;return this.x=r[0]*t+r[3]*n+r[6],this.y=r[1]*t+r[4]*n+r[7],this}min(e){return this.x=Math.min(this.x,e.x),this.y=Math.min(this.y,e.y),this}max(e){return this.x=Math.max(this.x,e.x),this.y=Math.max(this.y,e.y),this}clamp(e,t){return this.x=Math.max(e.x,Math.min(t.x,this.x)),this.y=Math.max(e.y,Math.min(t.y,this.y)),this}clampScalar(e,t){return this.x=Math.max(e,Math.min(t,this.x)),this.y=Math.max(e,Math.min(t,this.y)),this}clampLength(e,t){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(e,Math.min(t,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(e){return this.x*e.x+this.y*e.y}cross(e){return this.x*e.y-this.y*e.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}angleTo(e){const t=Math.sqrt(this.lengthSq()*e.lengthSq());if(t===0)return Math.PI/2;const n=this.dot(e)/t;return Math.acos(gt(n,-1,1))}distanceTo(e){return Math.sqrt(this.distanceToSquared(e))}distanceToSquared(e){const t=this.x-e.x,n=this.y-e.y;return t*t+n*n}manhattanDistanceTo(e){return Math.abs(this.x-e.x)+Math.abs(this.y-e.y)}setLength(e){return this.normalize().multiplyScalar(e)}lerp(e,t){return this.x+=(e.x-this.x)*t,this.y+=(e.y-this.y)*t,this}lerpVectors(e,t,n){return this.x=e.x+(t.x-e.x)*n,this.y=e.y+(t.y-e.y)*n,this}equals(e){return e.x===this.x&&e.y===this.y}fromArray(e,t=0){return this.x=e[t],this.y=e[t+1],this}toArray(e=[],t=0){return e[t]=this.x,e[t+1]=this.y,e}fromBufferAttribute(e,t){return this.x=e.getX(t),this.y=e.getY(t),this}rotateAround(e,t){const n=Math.cos(t),r=Math.sin(t),s=this.x-e.x,a=this.y-e.y;return this.x=s*n-a*r+e.x,this.y=s*r+a*n+e.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class Ce{constructor(e,t,n,r,s,a,o,l,h){Ce.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1],e!==void 0&&this.set(e,t,n,r,s,a,o,l,h)}set(e,t,n,r,s,a,o,l,h){const u=this.elements;return u[0]=e,u[1]=r,u[2]=o,u[3]=t,u[4]=s,u[5]=l,u[6]=n,u[7]=a,u[8]=h,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(e){const t=this.elements,n=e.elements;return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],this}extractBasis(e,t,n){return e.setFromMatrix3Column(this,0),t.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(e){const t=e.elements;return this.set(t[0],t[4],t[8],t[1],t[5],t[9],t[2],t[6],t[10]),this}multiply(e){return this.multiplyMatrices(this,e)}premultiply(e){return this.multiplyMatrices(e,this)}multiplyMatrices(e,t){const n=e.elements,r=t.elements,s=this.elements,a=n[0],o=n[3],l=n[6],h=n[1],u=n[4],d=n[7],p=n[2],m=n[5],v=n[8],M=r[0],f=r[3],c=r[6],A=r[1],T=r[4],S=r[7],F=r[2],R=r[5],b=r[8];return s[0]=a*M+o*A+l*F,s[3]=a*f+o*T+l*R,s[6]=a*c+o*S+l*b,s[1]=h*M+u*A+d*F,s[4]=h*f+u*T+d*R,s[7]=h*c+u*S+d*b,s[2]=p*M+m*A+v*F,s[5]=p*f+m*T+v*R,s[8]=p*c+m*S+v*b,this}multiplyScalar(e){const t=this.elements;return t[0]*=e,t[3]*=e,t[6]*=e,t[1]*=e,t[4]*=e,t[7]*=e,t[2]*=e,t[5]*=e,t[8]*=e,this}determinant(){const e=this.elements,t=e[0],n=e[1],r=e[2],s=e[3],a=e[4],o=e[5],l=e[6],h=e[7],u=e[8];return t*a*u-t*o*h-n*s*u+n*o*l+r*s*h-r*a*l}invert(){const e=this.elements,t=e[0],n=e[1],r=e[2],s=e[3],a=e[4],o=e[5],l=e[6],h=e[7],u=e[8],d=u*a-o*h,p=o*l-u*s,m=h*s-a*l,v=t*d+n*p+r*m;if(v===0)return this.set(0,0,0,0,0,0,0,0,0);const M=1/v;return e[0]=d*M,e[1]=(r*h-u*n)*M,e[2]=(o*n-r*a)*M,e[3]=p*M,e[4]=(u*t-r*l)*M,e[5]=(r*s-o*t)*M,e[6]=m*M,e[7]=(n*l-h*t)*M,e[8]=(a*t-n*s)*M,this}transpose(){let e;const t=this.elements;return e=t[1],t[1]=t[3],t[3]=e,e=t[2],t[2]=t[6],t[6]=e,e=t[5],t[5]=t[7],t[7]=e,this}getNormalMatrix(e){return this.setFromMatrix4(e).invert().transpose()}transposeIntoArray(e){const t=this.elements;return e[0]=t[0],e[1]=t[3],e[2]=t[6],e[3]=t[1],e[4]=t[4],e[5]=t[7],e[6]=t[2],e[7]=t[5],e[8]=t[8],this}setUvTransform(e,t,n,r,s,a,o){const l=Math.cos(s),h=Math.sin(s);return this.set(n*l,n*h,-n*(l*a+h*o)+a+e,-r*h,r*l,-r*(-h*a+l*o)+o+t,0,0,1),this}scale(e,t){return this.premultiply(pr.makeScale(e,t)),this}rotate(e){return this.premultiply(pr.makeRotation(-e)),this}translate(e,t){return this.premultiply(pr.makeTranslation(e,t)),this}makeTranslation(e,t){return e.isVector2?this.set(1,0,e.x,0,1,e.y,0,0,1):this.set(1,0,e,0,1,t,0,0,1),this}makeRotation(e){const t=Math.cos(e),n=Math.sin(e);return this.set(t,-n,0,n,t,0,0,0,1),this}makeScale(e,t){return this.set(e,0,0,0,t,0,0,0,1),this}equals(e){const t=this.elements,n=e.elements;for(let r=0;r<9;r++)if(t[r]!==n[r])return!1;return!0}fromArray(e,t=0){for(let n=0;n<9;n++)this.elements[n]=e[n+t];return this}toArray(e=[],t=0){const n=this.elements;return e[t]=n[0],e[t+1]=n[1],e[t+2]=n[2],e[t+3]=n[3],e[t+4]=n[4],e[t+5]=n[5],e[t+6]=n[6],e[t+7]=n[7],e[t+8]=n[8],e}clone(){return new this.constructor().fromArray(this.elements)}}const pr=new Ce;function xo(i){for(let e=i.length-1;e>=0;--e)if(i[e]>=65535)return!0;return!1}function sr(i){return document.createElementNS("http://www.w3.org/1999/xhtml",i)}function Ll(){const i=sr("canvas");return i.style.display="block",i}const aa={};function gi(i){i in aa||(aa[i]=!0,console.warn(i))}function Dl(i,e,t){return new Promise(function(n,r){function s(){switch(i.clientWaitSync(e,i.SYNC_FLUSH_COMMANDS_BIT,0)){case i.WAIT_FAILED:r();break;case i.TIMEOUT_EXPIRED:setTimeout(s,t);break;default:n()}}setTimeout(s,t)})}function Ul(i){const e=i.elements;e[2]=.5*e[2]+.5*e[3],e[6]=.5*e[6]+.5*e[7],e[10]=.5*e[10]+.5*e[11],e[14]=.5*e[14]+.5*e[15]}function Il(i){const e=i.elements;e[11]===-1?(e[10]=-e[10]-1,e[14]=-e[14]):(e[10]=-e[10],e[14]=-e[14]+1)}const ze={enabled:!0,workingColorSpace:ai,spaces:{},convert:function(i,e,t){return this.enabled===!1||e===t||!e||!t||(this.spaces[e].transfer===qe&&(i.r=tn(i.r),i.g=tn(i.g),i.b=tn(i.b)),this.spaces[e].primaries!==this.spaces[t].primaries&&(i.applyMatrix3(this.spaces[e].toXYZ),i.applyMatrix3(this.spaces[t].fromXYZ)),this.spaces[t].transfer===qe&&(i.r=Qn(i.r),i.g=Qn(i.g),i.b=Qn(i.b))),i},fromWorkingColorSpace:function(i,e){return this.convert(i,this.workingColorSpace,e)},toWorkingColorSpace:function(i,e){return this.convert(i,e,this.workingColorSpace)},getPrimaries:function(i){return this.spaces[i].primaries},getTransfer:function(i){return i===pn?lr:this.spaces[i].transfer},getLuminanceCoefficients:function(i,e=this.workingColorSpace){return i.fromArray(this.spaces[e].luminanceCoefficients)},define:function(i){Object.assign(this.spaces,i)},_getMatrix:function(i,e,t){return i.copy(this.spaces[e].toXYZ).multiply(this.spaces[t].fromXYZ)},_getDrawingBufferColorSpace:function(i){return this.spaces[i].outputColorSpaceConfig.drawingBufferColorSpace},_getUnpackColorSpace:function(i=this.workingColorSpace){return this.spaces[i].workingColorSpaceConfig.unpackColorSpace}};function tn(i){return i<.04045?i*.0773993808:Math.pow(i*.9478672986+.0521327014,2.4)}function Qn(i){return i<.0031308?i*12.92:1.055*Math.pow(i,.41666)-.055}const oa=[.64,.33,.3,.6,.15,.06],la=[.2126,.7152,.0722],ca=[.3127,.329],ha=new Ce().set(.4123908,.3575843,.1804808,.212639,.7151687,.0721923,.0193308,.1191948,.9505322),ua=new Ce().set(3.2409699,-1.5373832,-.4986108,-.9692436,1.8759675,.0415551,.0556301,-.203977,1.0569715);ze.define({[ai]:{primaries:oa,whitePoint:ca,transfer:lr,toXYZ:ha,fromXYZ:ua,luminanceCoefficients:la,workingColorSpaceConfig:{unpackColorSpace:Rt},outputColorSpaceConfig:{drawingBufferColorSpace:Rt}},[Rt]:{primaries:oa,whitePoint:ca,transfer:qe,toXYZ:ha,fromXYZ:ua,luminanceCoefficients:la,outputColorSpaceConfig:{drawingBufferColorSpace:Rt}}});let Fn;class Nl{static getDataURL(e){if(/^data:/i.test(e.src)||typeof HTMLCanvasElement>"u")return e.src;let t;if(e instanceof HTMLCanvasElement)t=e;else{Fn===void 0&&(Fn=sr("canvas")),Fn.width=e.width,Fn.height=e.height;const n=Fn.getContext("2d");e instanceof ImageData?n.putImageData(e,0,0):n.drawImage(e,0,0,e.width,e.height),t=Fn}return t.width>2048||t.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",e),t.toDataURL("image/jpeg",.6)):t.toDataURL("image/png")}static sRGBToLinear(e){if(typeof HTMLImageElement<"u"&&e instanceof HTMLImageElement||typeof HTMLCanvasElement<"u"&&e instanceof HTMLCanvasElement||typeof ImageBitmap<"u"&&e instanceof ImageBitmap){const t=sr("canvas");t.width=e.width,t.height=e.height;const n=t.getContext("2d");n.drawImage(e,0,0,e.width,e.height);const r=n.getImageData(0,0,e.width,e.height),s=r.data;for(let a=0;a0&&(n.userData=this.userData),t||(e.textures[this.uuid]=n),n}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(e){if(this.mapping!==so)return e;if(e.applyMatrix3(this.matrix),e.x<0||e.x>1)switch(this.wrapS){case es:e.x=e.x-Math.floor(e.x);break;case Cn:e.x=e.x<0?0:1;break;case ts:Math.abs(Math.floor(e.x)%2)===1?e.x=Math.ceil(e.x)-e.x:e.x=e.x-Math.floor(e.x);break}if(e.y<0||e.y>1)switch(this.wrapT){case es:e.y=e.y-Math.floor(e.y);break;case Cn:e.y=e.y<0?0:1;break;case ts:Math.abs(Math.floor(e.y)%2)===1?e.y=Math.ceil(e.y)-e.y:e.y=e.y-Math.floor(e.y);break}return this.flipY&&(e.y=1-e.y),e}set needsUpdate(e){e===!0&&(this.version++,this.source.needsUpdate=!0)}set needsPMREMUpdate(e){e===!0&&this.pmremVersion++}}xt.DEFAULT_IMAGE=null;xt.DEFAULT_MAPPING=so;xt.DEFAULT_ANISOTROPY=1;class Ye{constructor(e=0,t=0,n=0,r=1){Ye.prototype.isVector4=!0,this.x=e,this.y=t,this.z=n,this.w=r}get width(){return this.z}set width(e){this.z=e}get height(){return this.w}set height(e){this.w=e}set(e,t,n,r){return this.x=e,this.y=t,this.z=n,this.w=r,this}setScalar(e){return this.x=e,this.y=e,this.z=e,this.w=e,this}setX(e){return this.x=e,this}setY(e){return this.y=e,this}setZ(e){return this.z=e,this}setW(e){return this.w=e,this}setComponent(e,t){switch(e){case 0:this.x=t;break;case 1:this.y=t;break;case 2:this.z=t;break;case 3:this.w=t;break;default:throw new Error("index is out of range: "+e)}return this}getComponent(e){switch(e){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+e)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(e){return this.x=e.x,this.y=e.y,this.z=e.z,this.w=e.w!==void 0?e.w:1,this}add(e){return this.x+=e.x,this.y+=e.y,this.z+=e.z,this.w+=e.w,this}addScalar(e){return this.x+=e,this.y+=e,this.z+=e,this.w+=e,this}addVectors(e,t){return this.x=e.x+t.x,this.y=e.y+t.y,this.z=e.z+t.z,this.w=e.w+t.w,this}addScaledVector(e,t){return this.x+=e.x*t,this.y+=e.y*t,this.z+=e.z*t,this.w+=e.w*t,this}sub(e){return this.x-=e.x,this.y-=e.y,this.z-=e.z,this.w-=e.w,this}subScalar(e){return this.x-=e,this.y-=e,this.z-=e,this.w-=e,this}subVectors(e,t){return this.x=e.x-t.x,this.y=e.y-t.y,this.z=e.z-t.z,this.w=e.w-t.w,this}multiply(e){return this.x*=e.x,this.y*=e.y,this.z*=e.z,this.w*=e.w,this}multiplyScalar(e){return this.x*=e,this.y*=e,this.z*=e,this.w*=e,this}applyMatrix4(e){const t=this.x,n=this.y,r=this.z,s=this.w,a=e.elements;return this.x=a[0]*t+a[4]*n+a[8]*r+a[12]*s,this.y=a[1]*t+a[5]*n+a[9]*r+a[13]*s,this.z=a[2]*t+a[6]*n+a[10]*r+a[14]*s,this.w=a[3]*t+a[7]*n+a[11]*r+a[15]*s,this}divide(e){return this.x/=e.x,this.y/=e.y,this.z/=e.z,this.w/=e.w,this}divideScalar(e){return this.multiplyScalar(1/e)}setAxisAngleFromQuaternion(e){this.w=2*Math.acos(e.w);const t=Math.sqrt(1-e.w*e.w);return t<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=e.x/t,this.y=e.y/t,this.z=e.z/t),this}setAxisAngleFromRotationMatrix(e){let t,n,r,s;const l=e.elements,h=l[0],u=l[4],d=l[8],p=l[1],m=l[5],v=l[9],M=l[2],f=l[6],c=l[10];if(Math.abs(u-p)<.01&&Math.abs(d-M)<.01&&Math.abs(v-f)<.01){if(Math.abs(u+p)<.1&&Math.abs(d+M)<.1&&Math.abs(v+f)<.1&&Math.abs(h+m+c-3)<.1)return this.set(1,0,0,0),this;t=Math.PI;const T=(h+1)/2,S=(m+1)/2,F=(c+1)/2,R=(u+p)/4,b=(d+M)/4,U=(v+f)/4;return T>S&&T>F?T<.01?(n=0,r=.707106781,s=.707106781):(n=Math.sqrt(T),r=R/n,s=b/n):S>F?S<.01?(n=.707106781,r=0,s=.707106781):(r=Math.sqrt(S),n=R/r,s=U/r):F<.01?(n=.707106781,r=.707106781,s=0):(s=Math.sqrt(F),n=b/s,r=U/s),this.set(n,r,s,t),this}let A=Math.sqrt((f-v)*(f-v)+(d-M)*(d-M)+(p-u)*(p-u));return Math.abs(A)<.001&&(A=1),this.x=(f-v)/A,this.y=(d-M)/A,this.z=(p-u)/A,this.w=Math.acos((h+m+c-1)/2),this}setFromMatrixPosition(e){const t=e.elements;return this.x=t[12],this.y=t[13],this.z=t[14],this.w=t[15],this}min(e){return this.x=Math.min(this.x,e.x),this.y=Math.min(this.y,e.y),this.z=Math.min(this.z,e.z),this.w=Math.min(this.w,e.w),this}max(e){return this.x=Math.max(this.x,e.x),this.y=Math.max(this.y,e.y),this.z=Math.max(this.z,e.z),this.w=Math.max(this.w,e.w),this}clamp(e,t){return this.x=Math.max(e.x,Math.min(t.x,this.x)),this.y=Math.max(e.y,Math.min(t.y,this.y)),this.z=Math.max(e.z,Math.min(t.z,this.z)),this.w=Math.max(e.w,Math.min(t.w,this.w)),this}clampScalar(e,t){return this.x=Math.max(e,Math.min(t,this.x)),this.y=Math.max(e,Math.min(t,this.y)),this.z=Math.max(e,Math.min(t,this.z)),this.w=Math.max(e,Math.min(t,this.w)),this}clampLength(e,t){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(e,Math.min(t,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this.w=Math.floor(this.w),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this.w=Math.ceil(this.w),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this.w=Math.round(this.w),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this.z=Math.trunc(this.z),this.w=Math.trunc(this.w),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this.w=-this.w,this}dot(e){return this.x*e.x+this.y*e.y+this.z*e.z+this.w*e.w}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)+Math.abs(this.w)}normalize(){return this.divideScalar(this.length()||1)}setLength(e){return this.normalize().multiplyScalar(e)}lerp(e,t){return this.x+=(e.x-this.x)*t,this.y+=(e.y-this.y)*t,this.z+=(e.z-this.z)*t,this.w+=(e.w-this.w)*t,this}lerpVectors(e,t,n){return this.x=e.x+(t.x-e.x)*n,this.y=e.y+(t.y-e.y)*n,this.z=e.z+(t.z-e.z)*n,this.w=e.w+(t.w-e.w)*n,this}equals(e){return e.x===this.x&&e.y===this.y&&e.z===this.z&&e.w===this.w}fromArray(e,t=0){return this.x=e[t],this.y=e[t+1],this.z=e[t+2],this.w=e[t+3],this}toArray(e=[],t=0){return e[t]=this.x,e[t+1]=this.y,e[t+2]=this.z,e[t+3]=this.w,e}fromBufferAttribute(e,t){return this.x=e.getX(t),this.y=e.getY(t),this.z=e.getZ(t),this.w=e.getW(t),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this.w=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z,yield this.w}}class Bl extends oi{constructor(e=1,t=1,n={}){super(),this.isRenderTarget=!0,this.width=e,this.height=t,this.depth=1,this.scissor=new Ye(0,0,e,t),this.scissorTest=!1,this.viewport=new Ye(0,0,e,t);const r={width:e,height:t,depth:1};n=Object.assign({generateMipmaps:!1,internalFormat:null,minFilter:Gt,depthBuffer:!0,stencilBuffer:!1,resolveDepthBuffer:!0,resolveStencilBuffer:!0,depthTexture:null,samples:0,count:1},n);const s=new xt(r,n.mapping,n.wrapS,n.wrapT,n.magFilter,n.minFilter,n.format,n.type,n.anisotropy,n.colorSpace);s.flipY=!1,s.generateMipmaps=n.generateMipmaps,s.internalFormat=n.internalFormat,this.textures=[];const a=n.count;for(let o=0;o=0?1:-1,T=1-c*c;if(T>Number.EPSILON){const F=Math.sqrt(T),R=Math.atan2(F,c*A);f=Math.sin(f*R)/F,o=Math.sin(o*R)/F}const S=o*A;if(l=l*f+p*S,h=h*f+m*S,u=u*f+v*S,d=d*f+M*S,f===1-o){const F=1/Math.sqrt(l*l+h*h+u*u+d*d);l*=F,h*=F,u*=F,d*=F}}e[t]=l,e[t+1]=h,e[t+2]=u,e[t+3]=d}static multiplyQuaternionsFlat(e,t,n,r,s,a){const o=n[r],l=n[r+1],h=n[r+2],u=n[r+3],d=s[a],p=s[a+1],m=s[a+2],v=s[a+3];return e[t]=o*v+u*d+l*m-h*p,e[t+1]=l*v+u*p+h*d-o*m,e[t+2]=h*v+u*m+o*p-l*d,e[t+3]=u*v-o*d-l*p-h*m,e}get x(){return this._x}set x(e){this._x=e,this._onChangeCallback()}get y(){return this._y}set y(e){this._y=e,this._onChangeCallback()}get z(){return this._z}set z(e){this._z=e,this._onChangeCallback()}get w(){return this._w}set w(e){this._w=e,this._onChangeCallback()}set(e,t,n,r){return this._x=e,this._y=t,this._z=n,this._w=r,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(e){return this._x=e.x,this._y=e.y,this._z=e.z,this._w=e.w,this._onChangeCallback(),this}setFromEuler(e,t=!0){const n=e._x,r=e._y,s=e._z,a=e._order,o=Math.cos,l=Math.sin,h=o(n/2),u=o(r/2),d=o(s/2),p=l(n/2),m=l(r/2),v=l(s/2);switch(a){case"XYZ":this._x=p*u*d+h*m*v,this._y=h*m*d-p*u*v,this._z=h*u*v+p*m*d,this._w=h*u*d-p*m*v;break;case"YXZ":this._x=p*u*d+h*m*v,this._y=h*m*d-p*u*v,this._z=h*u*v-p*m*d,this._w=h*u*d+p*m*v;break;case"ZXY":this._x=p*u*d-h*m*v,this._y=h*m*d+p*u*v,this._z=h*u*v+p*m*d,this._w=h*u*d-p*m*v;break;case"ZYX":this._x=p*u*d-h*m*v,this._y=h*m*d+p*u*v,this._z=h*u*v-p*m*d,this._w=h*u*d+p*m*v;break;case"YZX":this._x=p*u*d+h*m*v,this._y=h*m*d+p*u*v,this._z=h*u*v-p*m*d,this._w=h*u*d-p*m*v;break;case"XZY":this._x=p*u*d-h*m*v,this._y=h*m*d-p*u*v,this._z=h*u*v+p*m*d,this._w=h*u*d+p*m*v;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+a)}return t===!0&&this._onChangeCallback(),this}setFromAxisAngle(e,t){const n=t/2,r=Math.sin(n);return this._x=e.x*r,this._y=e.y*r,this._z=e.z*r,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(e){const t=e.elements,n=t[0],r=t[4],s=t[8],a=t[1],o=t[5],l=t[9],h=t[2],u=t[6],d=t[10],p=n+o+d;if(p>0){const m=.5/Math.sqrt(p+1);this._w=.25/m,this._x=(u-l)*m,this._y=(s-h)*m,this._z=(a-r)*m}else if(n>o&&n>d){const m=2*Math.sqrt(1+n-o-d);this._w=(u-l)/m,this._x=.25*m,this._y=(r+a)/m,this._z=(s+h)/m}else if(o>d){const m=2*Math.sqrt(1+o-n-d);this._w=(s-h)/m,this._x=(r+a)/m,this._y=.25*m,this._z=(l+u)/m}else{const m=2*Math.sqrt(1+d-n-o);this._w=(a-r)/m,this._x=(s+h)/m,this._y=(l+u)/m,this._z=.25*m}return this._onChangeCallback(),this}setFromUnitVectors(e,t){let n=e.dot(t)+1;return nMath.abs(e.z)?(this._x=-e.y,this._y=e.x,this._z=0,this._w=n):(this._x=0,this._y=-e.z,this._z=e.y,this._w=n)):(this._x=e.y*t.z-e.z*t.y,this._y=e.z*t.x-e.x*t.z,this._z=e.x*t.y-e.y*t.x,this._w=n),this.normalize()}angleTo(e){return 2*Math.acos(Math.abs(gt(this.dot(e),-1,1)))}rotateTowards(e,t){const n=this.angleTo(e);if(n===0)return this;const r=Math.min(1,t/n);return this.slerp(e,r),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(e){return this._x*e._x+this._y*e._y+this._z*e._z+this._w*e._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let e=this.length();return e===0?(this._x=0,this._y=0,this._z=0,this._w=1):(e=1/e,this._x=this._x*e,this._y=this._y*e,this._z=this._z*e,this._w=this._w*e),this._onChangeCallback(),this}multiply(e){return this.multiplyQuaternions(this,e)}premultiply(e){return this.multiplyQuaternions(e,this)}multiplyQuaternions(e,t){const n=e._x,r=e._y,s=e._z,a=e._w,o=t._x,l=t._y,h=t._z,u=t._w;return this._x=n*u+a*o+r*h-s*l,this._y=r*u+a*l+s*o-n*h,this._z=s*u+a*h+n*l-r*o,this._w=a*u-n*o-r*l-s*h,this._onChangeCallback(),this}slerp(e,t){if(t===0)return this;if(t===1)return this.copy(e);const n=this._x,r=this._y,s=this._z,a=this._w;let o=a*e._w+n*e._x+r*e._y+s*e._z;if(o<0?(this._w=-e._w,this._x=-e._x,this._y=-e._y,this._z=-e._z,o=-o):this.copy(e),o>=1)return this._w=a,this._x=n,this._y=r,this._z=s,this;const l=1-o*o;if(l<=Number.EPSILON){const m=1-t;return this._w=m*a+t*this._w,this._x=m*n+t*this._x,this._y=m*r+t*this._y,this._z=m*s+t*this._z,this.normalize(),this}const h=Math.sqrt(l),u=Math.atan2(h,o),d=Math.sin((1-t)*u)/h,p=Math.sin(t*u)/h;return this._w=a*d+this._w*p,this._x=n*d+this._x*p,this._y=r*d+this._y*p,this._z=s*d+this._z*p,this._onChangeCallback(),this}slerpQuaternions(e,t,n){return this.copy(e).slerp(t,n)}random(){const e=2*Math.PI*Math.random(),t=2*Math.PI*Math.random(),n=Math.random(),r=Math.sqrt(1-n),s=Math.sqrt(n);return this.set(r*Math.sin(e),r*Math.cos(e),s*Math.sin(t),s*Math.cos(t))}equals(e){return e._x===this._x&&e._y===this._y&&e._z===this._z&&e._w===this._w}fromArray(e,t=0){return this._x=e[t],this._y=e[t+1],this._z=e[t+2],this._w=e[t+3],this._onChangeCallback(),this}toArray(e=[],t=0){return e[t]=this._x,e[t+1]=this._y,e[t+2]=this._z,e[t+3]=this._w,e}fromBufferAttribute(e,t){return this._x=e.getX(t),this._y=e.getY(t),this._z=e.getZ(t),this._w=e.getW(t),this._onChangeCallback(),this}toJSON(){return this.toArray()}_onChange(e){return this._onChangeCallback=e,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class I{constructor(e=0,t=0,n=0){I.prototype.isVector3=!0,this.x=e,this.y=t,this.z=n}set(e,t,n){return n===void 0&&(n=this.z),this.x=e,this.y=t,this.z=n,this}setScalar(e){return this.x=e,this.y=e,this.z=e,this}setX(e){return this.x=e,this}setY(e){return this.y=e,this}setZ(e){return this.z=e,this}setComponent(e,t){switch(e){case 0:this.x=t;break;case 1:this.y=t;break;case 2:this.z=t;break;default:throw new Error("index is out of range: "+e)}return this}getComponent(e){switch(e){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+e)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(e){return this.x=e.x,this.y=e.y,this.z=e.z,this}add(e){return this.x+=e.x,this.y+=e.y,this.z+=e.z,this}addScalar(e){return this.x+=e,this.y+=e,this.z+=e,this}addVectors(e,t){return this.x=e.x+t.x,this.y=e.y+t.y,this.z=e.z+t.z,this}addScaledVector(e,t){return this.x+=e.x*t,this.y+=e.y*t,this.z+=e.z*t,this}sub(e){return this.x-=e.x,this.y-=e.y,this.z-=e.z,this}subScalar(e){return this.x-=e,this.y-=e,this.z-=e,this}subVectors(e,t){return this.x=e.x-t.x,this.y=e.y-t.y,this.z=e.z-t.z,this}multiply(e){return this.x*=e.x,this.y*=e.y,this.z*=e.z,this}multiplyScalar(e){return this.x*=e,this.y*=e,this.z*=e,this}multiplyVectors(e,t){return this.x=e.x*t.x,this.y=e.y*t.y,this.z=e.z*t.z,this}applyEuler(e){return this.applyQuaternion(da.setFromEuler(e))}applyAxisAngle(e,t){return this.applyQuaternion(da.setFromAxisAngle(e,t))}applyMatrix3(e){const t=this.x,n=this.y,r=this.z,s=e.elements;return this.x=s[0]*t+s[3]*n+s[6]*r,this.y=s[1]*t+s[4]*n+s[7]*r,this.z=s[2]*t+s[5]*n+s[8]*r,this}applyNormalMatrix(e){return this.applyMatrix3(e).normalize()}applyMatrix4(e){const t=this.x,n=this.y,r=this.z,s=e.elements,a=1/(s[3]*t+s[7]*n+s[11]*r+s[15]);return this.x=(s[0]*t+s[4]*n+s[8]*r+s[12])*a,this.y=(s[1]*t+s[5]*n+s[9]*r+s[13])*a,this.z=(s[2]*t+s[6]*n+s[10]*r+s[14])*a,this}applyQuaternion(e){const t=this.x,n=this.y,r=this.z,s=e.x,a=e.y,o=e.z,l=e.w,h=2*(a*r-o*n),u=2*(o*t-s*r),d=2*(s*n-a*t);return this.x=t+l*h+a*d-o*u,this.y=n+l*u+o*h-s*d,this.z=r+l*d+s*u-a*h,this}project(e){return this.applyMatrix4(e.matrixWorldInverse).applyMatrix4(e.projectionMatrix)}unproject(e){return this.applyMatrix4(e.projectionMatrixInverse).applyMatrix4(e.matrixWorld)}transformDirection(e){const t=this.x,n=this.y,r=this.z,s=e.elements;return this.x=s[0]*t+s[4]*n+s[8]*r,this.y=s[1]*t+s[5]*n+s[9]*r,this.z=s[2]*t+s[6]*n+s[10]*r,this.normalize()}divide(e){return this.x/=e.x,this.y/=e.y,this.z/=e.z,this}divideScalar(e){return this.multiplyScalar(1/e)}min(e){return this.x=Math.min(this.x,e.x),this.y=Math.min(this.y,e.y),this.z=Math.min(this.z,e.z),this}max(e){return this.x=Math.max(this.x,e.x),this.y=Math.max(this.y,e.y),this.z=Math.max(this.z,e.z),this}clamp(e,t){return this.x=Math.max(e.x,Math.min(t.x,this.x)),this.y=Math.max(e.y,Math.min(t.y,this.y)),this.z=Math.max(e.z,Math.min(t.z,this.z)),this}clampScalar(e,t){return this.x=Math.max(e,Math.min(t,this.x)),this.y=Math.max(e,Math.min(t,this.y)),this.z=Math.max(e,Math.min(t,this.z)),this}clampLength(e,t){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(e,Math.min(t,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this.z=Math.trunc(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(e){return this.x*e.x+this.y*e.y+this.z*e.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(e){return this.normalize().multiplyScalar(e)}lerp(e,t){return this.x+=(e.x-this.x)*t,this.y+=(e.y-this.y)*t,this.z+=(e.z-this.z)*t,this}lerpVectors(e,t,n){return this.x=e.x+(t.x-e.x)*n,this.y=e.y+(t.y-e.y)*n,this.z=e.z+(t.z-e.z)*n,this}cross(e){return this.crossVectors(this,e)}crossVectors(e,t){const n=e.x,r=e.y,s=e.z,a=t.x,o=t.y,l=t.z;return this.x=r*l-s*o,this.y=s*a-n*l,this.z=n*o-r*a,this}projectOnVector(e){const t=e.lengthSq();if(t===0)return this.set(0,0,0);const n=e.dot(this)/t;return this.copy(e).multiplyScalar(n)}projectOnPlane(e){return _r.copy(this).projectOnVector(e),this.sub(_r)}reflect(e){return this.sub(_r.copy(e).multiplyScalar(2*this.dot(e)))}angleTo(e){const t=Math.sqrt(this.lengthSq()*e.lengthSq());if(t===0)return Math.PI/2;const n=this.dot(e)/t;return Math.acos(gt(n,-1,1))}distanceTo(e){return Math.sqrt(this.distanceToSquared(e))}distanceToSquared(e){const t=this.x-e.x,n=this.y-e.y,r=this.z-e.z;return t*t+n*n+r*r}manhattanDistanceTo(e){return Math.abs(this.x-e.x)+Math.abs(this.y-e.y)+Math.abs(this.z-e.z)}setFromSpherical(e){return this.setFromSphericalCoords(e.radius,e.phi,e.theta)}setFromSphericalCoords(e,t,n){const r=Math.sin(t)*e;return this.x=r*Math.sin(n),this.y=Math.cos(t)*e,this.z=r*Math.cos(n),this}setFromCylindrical(e){return this.setFromCylindricalCoords(e.radius,e.theta,e.y)}setFromCylindricalCoords(e,t,n){return this.x=e*Math.sin(t),this.y=n,this.z=e*Math.cos(t),this}setFromMatrixPosition(e){const t=e.elements;return this.x=t[12],this.y=t[13],this.z=t[14],this}setFromMatrixScale(e){const t=this.setFromMatrixColumn(e,0).length(),n=this.setFromMatrixColumn(e,1).length(),r=this.setFromMatrixColumn(e,2).length();return this.x=t,this.y=n,this.z=r,this}setFromMatrixColumn(e,t){return this.fromArray(e.elements,t*4)}setFromMatrix3Column(e,t){return this.fromArray(e.elements,t*3)}setFromEuler(e){return this.x=e._x,this.y=e._y,this.z=e._z,this}setFromColor(e){return this.x=e.r,this.y=e.g,this.z=e.b,this}equals(e){return e.x===this.x&&e.y===this.y&&e.z===this.z}fromArray(e,t=0){return this.x=e[t],this.y=e[t+1],this.z=e[t+2],this}toArray(e=[],t=0){return e[t]=this.x,e[t+1]=this.y,e[t+2]=this.z,e}fromBufferAttribute(e,t){return this.x=e.getX(t),this.y=e.getY(t),this.z=e.getZ(t),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const e=Math.random()*Math.PI*2,t=Math.random()*2-1,n=Math.sqrt(1-t*t);return this.x=n*Math.cos(e),this.y=t,this.z=n*Math.sin(e),this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const _r=new I,da=new yi;class Ti{constructor(e=new I(1/0,1/0,1/0),t=new I(-1/0,-1/0,-1/0)){this.isBox3=!0,this.min=e,this.max=t}set(e,t){return this.min.copy(e),this.max.copy(t),this}setFromArray(e){this.makeEmpty();for(let t=0,n=e.length;t=this.min.x&&e.x<=this.max.x&&e.y>=this.min.y&&e.y<=this.max.y&&e.z>=this.min.z&&e.z<=this.max.z}containsBox(e){return this.min.x<=e.min.x&&e.max.x<=this.max.x&&this.min.y<=e.min.y&&e.max.y<=this.max.y&&this.min.z<=e.min.z&&e.max.z<=this.max.z}getParameter(e,t){return t.set((e.x-this.min.x)/(this.max.x-this.min.x),(e.y-this.min.y)/(this.max.y-this.min.y),(e.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(e){return e.max.x>=this.min.x&&e.min.x<=this.max.x&&e.max.y>=this.min.y&&e.min.y<=this.max.y&&e.max.z>=this.min.z&&e.min.z<=this.max.z}intersectsSphere(e){return this.clampPoint(e.center,Lt),Lt.distanceToSquared(e.center)<=e.radius*e.radius}intersectsPlane(e){let t,n;return e.normal.x>0?(t=e.normal.x*this.min.x,n=e.normal.x*this.max.x):(t=e.normal.x*this.max.x,n=e.normal.x*this.min.x),e.normal.y>0?(t+=e.normal.y*this.min.y,n+=e.normal.y*this.max.y):(t+=e.normal.y*this.max.y,n+=e.normal.y*this.min.y),e.normal.z>0?(t+=e.normal.z*this.min.z,n+=e.normal.z*this.max.z):(t+=e.normal.z*this.max.z,n+=e.normal.z*this.min.z),t<=-e.constant&&n>=-e.constant}intersectsTriangle(e){if(this.isEmpty())return!1;this.getCenter(di),Ui.subVectors(this.max,di),On.subVectors(e.a,di),Bn.subVectors(e.b,di),zn.subVectors(e.c,di),on.subVectors(Bn,On),ln.subVectors(zn,Bn),Mn.subVectors(On,zn);let t=[0,-on.z,on.y,0,-ln.z,ln.y,0,-Mn.z,Mn.y,on.z,0,-on.x,ln.z,0,-ln.x,Mn.z,0,-Mn.x,-on.y,on.x,0,-ln.y,ln.x,0,-Mn.y,Mn.x,0];return!gr(t,On,Bn,zn,Ui)||(t=[1,0,0,0,1,0,0,0,1],!gr(t,On,Bn,zn,Ui))?!1:(Ii.crossVectors(on,ln),t=[Ii.x,Ii.y,Ii.z],gr(t,On,Bn,zn,Ui))}clampPoint(e,t){return t.copy(e).clamp(this.min,this.max)}distanceToPoint(e){return this.clampPoint(e,Lt).distanceTo(e)}getBoundingSphere(e){return this.isEmpty()?e.makeEmpty():(this.getCenter(e.center),e.radius=this.getSize(Lt).length()*.5),e}intersect(e){return this.min.max(e.min),this.max.min(e.max),this.isEmpty()&&this.makeEmpty(),this}union(e){return this.min.min(e.min),this.max.max(e.max),this}applyMatrix4(e){return this.isEmpty()?this:(qt[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(e),qt[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(e),qt[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(e),qt[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(e),qt[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(e),qt[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(e),qt[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(e),qt[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(e),this.setFromPoints(qt),this)}translate(e){return this.min.add(e),this.max.add(e),this}equals(e){return e.min.equals(this.min)&&e.max.equals(this.max)}}const qt=[new I,new I,new I,new I,new I,new I,new I,new I],Lt=new I,Di=new Ti,On=new I,Bn=new I,zn=new I,on=new I,ln=new I,Mn=new I,di=new I,Ui=new I,Ii=new I,Sn=new I;function gr(i,e,t,n,r){for(let s=0,a=i.length-3;s<=a;s+=3){Sn.fromArray(i,s);const o=r.x*Math.abs(Sn.x)+r.y*Math.abs(Sn.y)+r.z*Math.abs(Sn.z),l=e.dot(Sn),h=t.dot(Sn),u=n.dot(Sn);if(Math.max(-Math.max(l,h,u),Math.min(l,h,u))>o)return!1}return!0}const Hl=new Ti,fi=new I,vr=new I;class zs{constructor(e=new I,t=-1){this.isSphere=!0,this.center=e,this.radius=t}set(e,t){return this.center.copy(e),this.radius=t,this}setFromPoints(e,t){const n=this.center;t!==void 0?n.copy(t):Hl.setFromPoints(e).getCenter(n);let r=0;for(let s=0,a=e.length;sthis.radius*this.radius&&(t.sub(this.center).normalize(),t.multiplyScalar(this.radius).add(this.center)),t}getBoundingBox(e){return this.isEmpty()?(e.makeEmpty(),e):(e.set(this.center,this.center),e.expandByScalar(this.radius),e)}applyMatrix4(e){return this.center.applyMatrix4(e),this.radius=this.radius*e.getMaxScaleOnAxis(),this}translate(e){return this.center.add(e),this}expandByPoint(e){if(this.isEmpty())return this.center.copy(e),this.radius=0,this;fi.subVectors(e,this.center);const t=fi.lengthSq();if(t>this.radius*this.radius){const n=Math.sqrt(t),r=(n-this.radius)*.5;this.center.addScaledVector(fi,r/n),this.radius+=r}return this}union(e){return e.isEmpty()?this:this.isEmpty()?(this.copy(e),this):(this.center.equals(e.center)===!0?this.radius=Math.max(this.radius,e.radius):(vr.subVectors(e.center,this.center).setLength(e.radius),this.expandByPoint(fi.copy(e.center).add(vr)),this.expandByPoint(fi.copy(e.center).sub(vr))),this)}equals(e){return e.center.equals(this.center)&&e.radius===this.radius}clone(){return new this.constructor().copy(this)}}const Yt=new I,xr=new I,Ni=new I,cn=new I,Mr=new I,Fi=new I,Sr=new I;class Eo{constructor(e=new I,t=new I(0,0,-1)){this.origin=e,this.direction=t}set(e,t){return this.origin.copy(e),this.direction.copy(t),this}copy(e){return this.origin.copy(e.origin),this.direction.copy(e.direction),this}at(e,t){return t.copy(this.origin).addScaledVector(this.direction,e)}lookAt(e){return this.direction.copy(e).sub(this.origin).normalize(),this}recast(e){return this.origin.copy(this.at(e,Yt)),this}closestPointToPoint(e,t){t.subVectors(e,this.origin);const n=t.dot(this.direction);return n<0?t.copy(this.origin):t.copy(this.origin).addScaledVector(this.direction,n)}distanceToPoint(e){return Math.sqrt(this.distanceSqToPoint(e))}distanceSqToPoint(e){const t=Yt.subVectors(e,this.origin).dot(this.direction);return t<0?this.origin.distanceToSquared(e):(Yt.copy(this.origin).addScaledVector(this.direction,t),Yt.distanceToSquared(e))}distanceSqToSegment(e,t,n,r){xr.copy(e).add(t).multiplyScalar(.5),Ni.copy(t).sub(e).normalize(),cn.copy(this.origin).sub(xr);const s=e.distanceTo(t)*.5,a=-this.direction.dot(Ni),o=cn.dot(this.direction),l=-cn.dot(Ni),h=cn.lengthSq(),u=Math.abs(1-a*a);let d,p,m,v;if(u>0)if(d=a*l-o,p=a*o-l,v=s*u,d>=0)if(p>=-v)if(p<=v){const M=1/u;d*=M,p*=M,m=d*(d+a*p+2*o)+p*(a*d+p+2*l)+h}else p=s,d=Math.max(0,-(a*p+o)),m=-d*d+p*(p+2*l)+h;else p=-s,d=Math.max(0,-(a*p+o)),m=-d*d+p*(p+2*l)+h;else p<=-v?(d=Math.max(0,-(-a*s+o)),p=d>0?-s:Math.min(Math.max(-s,-l),s),m=-d*d+p*(p+2*l)+h):p<=v?(d=0,p=Math.min(Math.max(-s,-l),s),m=p*(p+2*l)+h):(d=Math.max(0,-(a*s+o)),p=d>0?s:Math.min(Math.max(-s,-l),s),m=-d*d+p*(p+2*l)+h);else p=a>0?-s:s,d=Math.max(0,-(a*p+o)),m=-d*d+p*(p+2*l)+h;return n&&n.copy(this.origin).addScaledVector(this.direction,d),r&&r.copy(xr).addScaledVector(Ni,p),m}intersectSphere(e,t){Yt.subVectors(e.center,this.origin);const n=Yt.dot(this.direction),r=Yt.dot(Yt)-n*n,s=e.radius*e.radius;if(r>s)return null;const a=Math.sqrt(s-r),o=n-a,l=n+a;return l<0?null:o<0?this.at(l,t):this.at(o,t)}intersectsSphere(e){return this.distanceSqToPoint(e.center)<=e.radius*e.radius}distanceToPlane(e){const t=e.normal.dot(this.direction);if(t===0)return e.distanceToPoint(this.origin)===0?0:null;const n=-(this.origin.dot(e.normal)+e.constant)/t;return n>=0?n:null}intersectPlane(e,t){const n=this.distanceToPlane(e);return n===null?null:this.at(n,t)}intersectsPlane(e){const t=e.distanceToPoint(this.origin);return t===0||e.normal.dot(this.direction)*t<0}intersectBox(e,t){let n,r,s,a,o,l;const h=1/this.direction.x,u=1/this.direction.y,d=1/this.direction.z,p=this.origin;return h>=0?(n=(e.min.x-p.x)*h,r=(e.max.x-p.x)*h):(n=(e.max.x-p.x)*h,r=(e.min.x-p.x)*h),u>=0?(s=(e.min.y-p.y)*u,a=(e.max.y-p.y)*u):(s=(e.max.y-p.y)*u,a=(e.min.y-p.y)*u),n>a||s>r||((s>n||isNaN(n))&&(n=s),(a=0?(o=(e.min.z-p.z)*d,l=(e.max.z-p.z)*d):(o=(e.max.z-p.z)*d,l=(e.min.z-p.z)*d),n>l||o>r)||((o>n||n!==n)&&(n=o),(l=0?n:r,t)}intersectsBox(e){return this.intersectBox(e,Yt)!==null}intersectTriangle(e,t,n,r,s){Mr.subVectors(t,e),Fi.subVectors(n,e),Sr.crossVectors(Mr,Fi);let a=this.direction.dot(Sr),o;if(a>0){if(r)return null;o=1}else if(a<0)o=-1,a=-a;else return null;cn.subVectors(this.origin,e);const l=o*this.direction.dot(Fi.crossVectors(cn,Fi));if(l<0)return null;const h=o*this.direction.dot(Mr.cross(cn));if(h<0||l+h>a)return null;const u=-o*cn.dot(Sr);return u<0?null:this.at(u/a,s)}applyMatrix4(e){return this.origin.applyMatrix4(e),this.direction.transformDirection(e),this}equals(e){return e.origin.equals(this.origin)&&e.direction.equals(this.direction)}clone(){return new this.constructor().copy(this)}}class et{constructor(e,t,n,r,s,a,o,l,h,u,d,p,m,v,M,f){et.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],e!==void 0&&this.set(e,t,n,r,s,a,o,l,h,u,d,p,m,v,M,f)}set(e,t,n,r,s,a,o,l,h,u,d,p,m,v,M,f){const c=this.elements;return c[0]=e,c[4]=t,c[8]=n,c[12]=r,c[1]=s,c[5]=a,c[9]=o,c[13]=l,c[2]=h,c[6]=u,c[10]=d,c[14]=p,c[3]=m,c[7]=v,c[11]=M,c[15]=f,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return new et().fromArray(this.elements)}copy(e){const t=this.elements,n=e.elements;return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t[9]=n[9],t[10]=n[10],t[11]=n[11],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15],this}copyPosition(e){const t=this.elements,n=e.elements;return t[12]=n[12],t[13]=n[13],t[14]=n[14],this}setFromMatrix3(e){const t=e.elements;return this.set(t[0],t[3],t[6],0,t[1],t[4],t[7],0,t[2],t[5],t[8],0,0,0,0,1),this}extractBasis(e,t,n){return e.setFromMatrixColumn(this,0),t.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(e,t,n){return this.set(e.x,t.x,n.x,0,e.y,t.y,n.y,0,e.z,t.z,n.z,0,0,0,0,1),this}extractRotation(e){const t=this.elements,n=e.elements,r=1/Hn.setFromMatrixColumn(e,0).length(),s=1/Hn.setFromMatrixColumn(e,1).length(),a=1/Hn.setFromMatrixColumn(e,2).length();return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=0,t[4]=n[4]*s,t[5]=n[5]*s,t[6]=n[6]*s,t[7]=0,t[8]=n[8]*a,t[9]=n[9]*a,t[10]=n[10]*a,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,this}makeRotationFromEuler(e){const t=this.elements,n=e.x,r=e.y,s=e.z,a=Math.cos(n),o=Math.sin(n),l=Math.cos(r),h=Math.sin(r),u=Math.cos(s),d=Math.sin(s);if(e.order==="XYZ"){const p=a*u,m=a*d,v=o*u,M=o*d;t[0]=l*u,t[4]=-l*d,t[8]=h,t[1]=m+v*h,t[5]=p-M*h,t[9]=-o*l,t[2]=M-p*h,t[6]=v+m*h,t[10]=a*l}else if(e.order==="YXZ"){const p=l*u,m=l*d,v=h*u,M=h*d;t[0]=p+M*o,t[4]=v*o-m,t[8]=a*h,t[1]=a*d,t[5]=a*u,t[9]=-o,t[2]=m*o-v,t[6]=M+p*o,t[10]=a*l}else if(e.order==="ZXY"){const p=l*u,m=l*d,v=h*u,M=h*d;t[0]=p-M*o,t[4]=-a*d,t[8]=v+m*o,t[1]=m+v*o,t[5]=a*u,t[9]=M-p*o,t[2]=-a*h,t[6]=o,t[10]=a*l}else if(e.order==="ZYX"){const p=a*u,m=a*d,v=o*u,M=o*d;t[0]=l*u,t[4]=v*h-m,t[8]=p*h+M,t[1]=l*d,t[5]=M*h+p,t[9]=m*h-v,t[2]=-h,t[6]=o*l,t[10]=a*l}else if(e.order==="YZX"){const p=a*l,m=a*h,v=o*l,M=o*h;t[0]=l*u,t[4]=M-p*d,t[8]=v*d+m,t[1]=d,t[5]=a*u,t[9]=-o*u,t[2]=-h*u,t[6]=m*d+v,t[10]=p-M*d}else if(e.order==="XZY"){const p=a*l,m=a*h,v=o*l,M=o*h;t[0]=l*u,t[4]=-d,t[8]=h*u,t[1]=p*d+M,t[5]=a*u,t[9]=m*d-v,t[2]=v*d-m,t[6]=o*u,t[10]=M*d+p}return t[3]=0,t[7]=0,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,this}makeRotationFromQuaternion(e){return this.compose(Gl,e,Vl)}lookAt(e,t,n){const r=this.elements;return St.subVectors(e,t),St.lengthSq()===0&&(St.z=1),St.normalize(),hn.crossVectors(n,St),hn.lengthSq()===0&&(Math.abs(n.z)===1?St.x+=1e-4:St.z+=1e-4,St.normalize(),hn.crossVectors(n,St)),hn.normalize(),Oi.crossVectors(St,hn),r[0]=hn.x,r[4]=Oi.x,r[8]=St.x,r[1]=hn.y,r[5]=Oi.y,r[9]=St.y,r[2]=hn.z,r[6]=Oi.z,r[10]=St.z,this}multiply(e){return this.multiplyMatrices(this,e)}premultiply(e){return this.multiplyMatrices(e,this)}multiplyMatrices(e,t){const n=e.elements,r=t.elements,s=this.elements,a=n[0],o=n[4],l=n[8],h=n[12],u=n[1],d=n[5],p=n[9],m=n[13],v=n[2],M=n[6],f=n[10],c=n[14],A=n[3],T=n[7],S=n[11],F=n[15],R=r[0],b=r[4],U=r[8],E=r[12],x=r[1],w=r[5],H=r[9],z=r[13],W=r[2],Z=r[6],k=r[10],j=r[14],V=r[3],ie=r[7],ce=r[11],xe=r[15];return s[0]=a*R+o*x+l*W+h*V,s[4]=a*b+o*w+l*Z+h*ie,s[8]=a*U+o*H+l*k+h*ce,s[12]=a*E+o*z+l*j+h*xe,s[1]=u*R+d*x+p*W+m*V,s[5]=u*b+d*w+p*Z+m*ie,s[9]=u*U+d*H+p*k+m*ce,s[13]=u*E+d*z+p*j+m*xe,s[2]=v*R+M*x+f*W+c*V,s[6]=v*b+M*w+f*Z+c*ie,s[10]=v*U+M*H+f*k+c*ce,s[14]=v*E+M*z+f*j+c*xe,s[3]=A*R+T*x+S*W+F*V,s[7]=A*b+T*w+S*Z+F*ie,s[11]=A*U+T*H+S*k+F*ce,s[15]=A*E+T*z+S*j+F*xe,this}multiplyScalar(e){const t=this.elements;return t[0]*=e,t[4]*=e,t[8]*=e,t[12]*=e,t[1]*=e,t[5]*=e,t[9]*=e,t[13]*=e,t[2]*=e,t[6]*=e,t[10]*=e,t[14]*=e,t[3]*=e,t[7]*=e,t[11]*=e,t[15]*=e,this}determinant(){const e=this.elements,t=e[0],n=e[4],r=e[8],s=e[12],a=e[1],o=e[5],l=e[9],h=e[13],u=e[2],d=e[6],p=e[10],m=e[14],v=e[3],M=e[7],f=e[11],c=e[15];return v*(+s*l*d-r*h*d-s*o*p+n*h*p+r*o*m-n*l*m)+M*(+t*l*m-t*h*p+s*a*p-r*a*m+r*h*u-s*l*u)+f*(+t*h*d-t*o*m-s*a*d+n*a*m+s*o*u-n*h*u)+c*(-r*o*u-t*l*d+t*o*p+r*a*d-n*a*p+n*l*u)}transpose(){const e=this.elements;let t;return t=e[1],e[1]=e[4],e[4]=t,t=e[2],e[2]=e[8],e[8]=t,t=e[6],e[6]=e[9],e[9]=t,t=e[3],e[3]=e[12],e[12]=t,t=e[7],e[7]=e[13],e[13]=t,t=e[11],e[11]=e[14],e[14]=t,this}setPosition(e,t,n){const r=this.elements;return e.isVector3?(r[12]=e.x,r[13]=e.y,r[14]=e.z):(r[12]=e,r[13]=t,r[14]=n),this}invert(){const e=this.elements,t=e[0],n=e[1],r=e[2],s=e[3],a=e[4],o=e[5],l=e[6],h=e[7],u=e[8],d=e[9],p=e[10],m=e[11],v=e[12],M=e[13],f=e[14],c=e[15],A=d*f*h-M*p*h+M*l*m-o*f*m-d*l*c+o*p*c,T=v*p*h-u*f*h-v*l*m+a*f*m+u*l*c-a*p*c,S=u*M*h-v*d*h+v*o*m-a*M*m-u*o*c+a*d*c,F=v*d*l-u*M*l-v*o*p+a*M*p+u*o*f-a*d*f,R=t*A+n*T+r*S+s*F;if(R===0)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const b=1/R;return e[0]=A*b,e[1]=(M*p*s-d*f*s-M*r*m+n*f*m+d*r*c-n*p*c)*b,e[2]=(o*f*s-M*l*s+M*r*h-n*f*h-o*r*c+n*l*c)*b,e[3]=(d*l*s-o*p*s-d*r*h+n*p*h+o*r*m-n*l*m)*b,e[4]=T*b,e[5]=(u*f*s-v*p*s+v*r*m-t*f*m-u*r*c+t*p*c)*b,e[6]=(v*l*s-a*f*s-v*r*h+t*f*h+a*r*c-t*l*c)*b,e[7]=(a*p*s-u*l*s+u*r*h-t*p*h-a*r*m+t*l*m)*b,e[8]=S*b,e[9]=(v*d*s-u*M*s-v*n*m+t*M*m+u*n*c-t*d*c)*b,e[10]=(a*M*s-v*o*s+v*n*h-t*M*h-a*n*c+t*o*c)*b,e[11]=(u*o*s-a*d*s-u*n*h+t*d*h+a*n*m-t*o*m)*b,e[12]=F*b,e[13]=(u*M*r-v*d*r+v*n*p-t*M*p-u*n*f+t*d*f)*b,e[14]=(v*o*r-a*M*r-v*n*l+t*M*l+a*n*f-t*o*f)*b,e[15]=(a*d*r-u*o*r+u*n*l-t*d*l-a*n*p+t*o*p)*b,this}scale(e){const t=this.elements,n=e.x,r=e.y,s=e.z;return t[0]*=n,t[4]*=r,t[8]*=s,t[1]*=n,t[5]*=r,t[9]*=s,t[2]*=n,t[6]*=r,t[10]*=s,t[3]*=n,t[7]*=r,t[11]*=s,this}getMaxScaleOnAxis(){const e=this.elements,t=e[0]*e[0]+e[1]*e[1]+e[2]*e[2],n=e[4]*e[4]+e[5]*e[5]+e[6]*e[6],r=e[8]*e[8]+e[9]*e[9]+e[10]*e[10];return Math.sqrt(Math.max(t,n,r))}makeTranslation(e,t,n){return e.isVector3?this.set(1,0,0,e.x,0,1,0,e.y,0,0,1,e.z,0,0,0,1):this.set(1,0,0,e,0,1,0,t,0,0,1,n,0,0,0,1),this}makeRotationX(e){const t=Math.cos(e),n=Math.sin(e);return this.set(1,0,0,0,0,t,-n,0,0,n,t,0,0,0,0,1),this}makeRotationY(e){const t=Math.cos(e),n=Math.sin(e);return this.set(t,0,n,0,0,1,0,0,-n,0,t,0,0,0,0,1),this}makeRotationZ(e){const t=Math.cos(e),n=Math.sin(e);return this.set(t,-n,0,0,n,t,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(e,t){const n=Math.cos(t),r=Math.sin(t),s=1-n,a=e.x,o=e.y,l=e.z,h=s*a,u=s*o;return this.set(h*a+n,h*o-r*l,h*l+r*o,0,h*o+r*l,u*o+n,u*l-r*a,0,h*l-r*o,u*l+r*a,s*l*l+n,0,0,0,0,1),this}makeScale(e,t,n){return this.set(e,0,0,0,0,t,0,0,0,0,n,0,0,0,0,1),this}makeShear(e,t,n,r,s,a){return this.set(1,n,s,0,e,1,a,0,t,r,1,0,0,0,0,1),this}compose(e,t,n){const r=this.elements,s=t._x,a=t._y,o=t._z,l=t._w,h=s+s,u=a+a,d=o+o,p=s*h,m=s*u,v=s*d,M=a*u,f=a*d,c=o*d,A=l*h,T=l*u,S=l*d,F=n.x,R=n.y,b=n.z;return r[0]=(1-(M+c))*F,r[1]=(m+S)*F,r[2]=(v-T)*F,r[3]=0,r[4]=(m-S)*R,r[5]=(1-(p+c))*R,r[6]=(f+A)*R,r[7]=0,r[8]=(v+T)*b,r[9]=(f-A)*b,r[10]=(1-(p+M))*b,r[11]=0,r[12]=e.x,r[13]=e.y,r[14]=e.z,r[15]=1,this}decompose(e,t,n){const r=this.elements;let s=Hn.set(r[0],r[1],r[2]).length();const a=Hn.set(r[4],r[5],r[6]).length(),o=Hn.set(r[8],r[9],r[10]).length();this.determinant()<0&&(s=-s),e.x=r[12],e.y=r[13],e.z=r[14],Dt.copy(this);const h=1/s,u=1/a,d=1/o;return Dt.elements[0]*=h,Dt.elements[1]*=h,Dt.elements[2]*=h,Dt.elements[4]*=u,Dt.elements[5]*=u,Dt.elements[6]*=u,Dt.elements[8]*=d,Dt.elements[9]*=d,Dt.elements[10]*=d,t.setFromRotationMatrix(Dt),n.x=s,n.y=a,n.z=o,this}makePerspective(e,t,n,r,s,a,o=en){const l=this.elements,h=2*s/(t-e),u=2*s/(n-r),d=(t+e)/(t-e),p=(n+r)/(n-r);let m,v;if(o===en)m=-(a+s)/(a-s),v=-2*a*s/(a-s);else if(o===rr)m=-a/(a-s),v=-a*s/(a-s);else throw new Error("THREE.Matrix4.makePerspective(): Invalid coordinate system: "+o);return l[0]=h,l[4]=0,l[8]=d,l[12]=0,l[1]=0,l[5]=u,l[9]=p,l[13]=0,l[2]=0,l[6]=0,l[10]=m,l[14]=v,l[3]=0,l[7]=0,l[11]=-1,l[15]=0,this}makeOrthographic(e,t,n,r,s,a,o=en){const l=this.elements,h=1/(t-e),u=1/(n-r),d=1/(a-s),p=(t+e)*h,m=(n+r)*u;let v,M;if(o===en)v=(a+s)*d,M=-2*d;else if(o===rr)v=s*d,M=-1*d;else throw new Error("THREE.Matrix4.makeOrthographic(): Invalid coordinate system: "+o);return l[0]=2*h,l[4]=0,l[8]=0,l[12]=-p,l[1]=0,l[5]=2*u,l[9]=0,l[13]=-m,l[2]=0,l[6]=0,l[10]=M,l[14]=-v,l[3]=0,l[7]=0,l[11]=0,l[15]=1,this}equals(e){const t=this.elements,n=e.elements;for(let r=0;r<16;r++)if(t[r]!==n[r])return!1;return!0}fromArray(e,t=0){for(let n=0;n<16;n++)this.elements[n]=e[n+t];return this}toArray(e=[],t=0){const n=this.elements;return e[t]=n[0],e[t+1]=n[1],e[t+2]=n[2],e[t+3]=n[3],e[t+4]=n[4],e[t+5]=n[5],e[t+6]=n[6],e[t+7]=n[7],e[t+8]=n[8],e[t+9]=n[9],e[t+10]=n[10],e[t+11]=n[11],e[t+12]=n[12],e[t+13]=n[13],e[t+14]=n[14],e[t+15]=n[15],e}}const Hn=new I,Dt=new et,Gl=new I(0,0,0),Vl=new I(1,1,1),hn=new I,Oi=new I,St=new I,fa=new et,pa=new yi;class kt{constructor(e=0,t=0,n=0,r=kt.DEFAULT_ORDER){this.isEuler=!0,this._x=e,this._y=t,this._z=n,this._order=r}get x(){return this._x}set x(e){this._x=e,this._onChangeCallback()}get y(){return this._y}set y(e){this._y=e,this._onChangeCallback()}get z(){return this._z}set z(e){this._z=e,this._onChangeCallback()}get order(){return this._order}set order(e){this._order=e,this._onChangeCallback()}set(e,t,n,r=this._order){return this._x=e,this._y=t,this._z=n,this._order=r,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(e){return this._x=e._x,this._y=e._y,this._z=e._z,this._order=e._order,this._onChangeCallback(),this}setFromRotationMatrix(e,t=this._order,n=!0){const r=e.elements,s=r[0],a=r[4],o=r[8],l=r[1],h=r[5],u=r[9],d=r[2],p=r[6],m=r[10];switch(t){case"XYZ":this._y=Math.asin(gt(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-u,m),this._z=Math.atan2(-a,s)):(this._x=Math.atan2(p,h),this._z=0);break;case"YXZ":this._x=Math.asin(-gt(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(o,m),this._z=Math.atan2(l,h)):(this._y=Math.atan2(-d,s),this._z=0);break;case"ZXY":this._x=Math.asin(gt(p,-1,1)),Math.abs(p)<.9999999?(this._y=Math.atan2(-d,m),this._z=Math.atan2(-a,h)):(this._y=0,this._z=Math.atan2(l,s));break;case"ZYX":this._y=Math.asin(-gt(d,-1,1)),Math.abs(d)<.9999999?(this._x=Math.atan2(p,m),this._z=Math.atan2(l,s)):(this._x=0,this._z=Math.atan2(-a,h));break;case"YZX":this._z=Math.asin(gt(l,-1,1)),Math.abs(l)<.9999999?(this._x=Math.atan2(-u,h),this._y=Math.atan2(-d,s)):(this._x=0,this._y=Math.atan2(o,m));break;case"XZY":this._z=Math.asin(-gt(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(p,h),this._y=Math.atan2(o,s)):(this._x=Math.atan2(-u,m),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+t)}return this._order=t,n===!0&&this._onChangeCallback(),this}setFromQuaternion(e,t,n){return fa.makeRotationFromQuaternion(e),this.setFromRotationMatrix(fa,t,n)}setFromVector3(e,t=this._order){return this.set(e.x,e.y,e.z,t)}reorder(e){return pa.setFromEuler(this),this.setFromQuaternion(pa,e)}equals(e){return e._x===this._x&&e._y===this._y&&e._z===this._z&&e._order===this._order}fromArray(e){return this._x=e[0],this._y=e[1],this._z=e[2],e[3]!==void 0&&(this._order=e[3]),this._onChangeCallback(),this}toArray(e=[],t=0){return e[t]=this._x,e[t+1]=this._y,e[t+2]=this._z,e[t+3]=this._order,e}_onChange(e){return this._onChangeCallback=e,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._order}}kt.DEFAULT_ORDER="XYZ";class Hs{constructor(){this.mask=1}set(e){this.mask=(1<>>0}enable(e){this.mask|=1<1){for(let t=0;t1){for(let n=0;n0&&(r.userData=this.userData),r.layers=this.layers.mask,r.matrix=this.matrix.toArray(),r.up=this.up.toArray(),this.matrixAutoUpdate===!1&&(r.matrixAutoUpdate=!1),this.isInstancedMesh&&(r.type="InstancedMesh",r.count=this.count,r.instanceMatrix=this.instanceMatrix.toJSON(),this.instanceColor!==null&&(r.instanceColor=this.instanceColor.toJSON())),this.isBatchedMesh&&(r.type="BatchedMesh",r.perObjectFrustumCulled=this.perObjectFrustumCulled,r.sortObjects=this.sortObjects,r.drawRanges=this._drawRanges,r.reservedRanges=this._reservedRanges,r.visibility=this._visibility,r.active=this._active,r.bounds=this._bounds.map(o=>({boxInitialized:o.boxInitialized,boxMin:o.box.min.toArray(),boxMax:o.box.max.toArray(),sphereInitialized:o.sphereInitialized,sphereRadius:o.sphere.radius,sphereCenter:o.sphere.center.toArray()})),r.maxInstanceCount=this._maxInstanceCount,r.maxVertexCount=this._maxVertexCount,r.maxIndexCount=this._maxIndexCount,r.geometryInitialized=this._geometryInitialized,r.geometryCount=this._geometryCount,r.matricesTexture=this._matricesTexture.toJSON(e),this._colorsTexture!==null&&(r.colorsTexture=this._colorsTexture.toJSON(e)),this.boundingSphere!==null&&(r.boundingSphere={center:r.boundingSphere.center.toArray(),radius:r.boundingSphere.radius}),this.boundingBox!==null&&(r.boundingBox={min:r.boundingBox.min.toArray(),max:r.boundingBox.max.toArray()}));function s(o,l){return o[l.uuid]===void 0&&(o[l.uuid]=l.toJSON(e)),l.uuid}if(this.isScene)this.background&&(this.background.isColor?r.background=this.background.toJSON():this.background.isTexture&&(r.background=this.background.toJSON(e).uuid)),this.environment&&this.environment.isTexture&&this.environment.isRenderTargetTexture!==!0&&(r.environment=this.environment.toJSON(e).uuid);else if(this.isMesh||this.isLine||this.isPoints){r.geometry=s(e.geometries,this.geometry);const o=this.geometry.parameters;if(o!==void 0&&o.shapes!==void 0){const l=o.shapes;if(Array.isArray(l))for(let h=0,u=l.length;h0){r.children=[];for(let o=0;o0){r.animations=[];for(let o=0;o0&&(n.geometries=o),l.length>0&&(n.materials=l),h.length>0&&(n.textures=h),u.length>0&&(n.images=u),d.length>0&&(n.shapes=d),p.length>0&&(n.skeletons=p),m.length>0&&(n.animations=m),v.length>0&&(n.nodes=v)}return n.object=r,n;function a(o){const l=[];for(const h in o){const u=o[h];delete u.metadata,l.push(u)}return l}}clone(e){return new this.constructor().copy(this,e)}copy(e,t=!0){if(this.name=e.name,this.up.copy(e.up),this.position.copy(e.position),this.rotation.order=e.rotation.order,this.quaternion.copy(e.quaternion),this.scale.copy(e.scale),this.matrix.copy(e.matrix),this.matrixWorld.copy(e.matrixWorld),this.matrixAutoUpdate=e.matrixAutoUpdate,this.matrixWorldAutoUpdate=e.matrixWorldAutoUpdate,this.matrixWorldNeedsUpdate=e.matrixWorldNeedsUpdate,this.layers.mask=e.layers.mask,this.visible=e.visible,this.castShadow=e.castShadow,this.receiveShadow=e.receiveShadow,this.frustumCulled=e.frustumCulled,this.renderOrder=e.renderOrder,this.animations=e.animations.slice(),this.userData=JSON.parse(JSON.stringify(e.userData)),t===!0)for(let n=0;n0?r.multiplyScalar(1/Math.sqrt(s)):r.set(0,0,0)}static getBarycoord(e,t,n,r,s){Ut.subVectors(r,t),Zt.subVectors(n,t),yr.subVectors(e,t);const a=Ut.dot(Ut),o=Ut.dot(Zt),l=Ut.dot(yr),h=Zt.dot(Zt),u=Zt.dot(yr),d=a*h-o*o;if(d===0)return s.set(0,0,0),null;const p=1/d,m=(h*l-o*u)*p,v=(a*u-o*l)*p;return s.set(1-m-v,v,m)}static containsPoint(e,t,n,r){return this.getBarycoord(e,t,n,r,$t)===null?!1:$t.x>=0&&$t.y>=0&&$t.x+$t.y<=1}static getInterpolation(e,t,n,r,s,a,o,l){return this.getBarycoord(e,t,n,r,$t)===null?(l.x=0,l.y=0,"z"in l&&(l.z=0),"w"in l&&(l.w=0),null):(l.setScalar(0),l.addScaledVector(s,$t.x),l.addScaledVector(a,$t.y),l.addScaledVector(o,$t.z),l)}static getInterpolatedAttribute(e,t,n,r,s,a){return wr.setScalar(0),Rr.setScalar(0),Cr.setScalar(0),wr.fromBufferAttribute(e,t),Rr.fromBufferAttribute(e,n),Cr.fromBufferAttribute(e,r),a.setScalar(0),a.addScaledVector(wr,s.x),a.addScaledVector(Rr,s.y),a.addScaledVector(Cr,s.z),a}static isFrontFacing(e,t,n,r){return Ut.subVectors(n,t),Zt.subVectors(e,t),Ut.cross(Zt).dot(r)<0}set(e,t,n){return this.a.copy(e),this.b.copy(t),this.c.copy(n),this}setFromPointsAndIndices(e,t,n,r){return this.a.copy(e[t]),this.b.copy(e[n]),this.c.copy(e[r]),this}setFromAttributeAndIndices(e,t,n,r){return this.a.fromBufferAttribute(e,t),this.b.fromBufferAttribute(e,n),this.c.fromBufferAttribute(e,r),this}clone(){return new this.constructor().copy(this)}copy(e){return this.a.copy(e.a),this.b.copy(e.b),this.c.copy(e.c),this}getArea(){return Ut.subVectors(this.c,this.b),Zt.subVectors(this.a,this.b),Ut.cross(Zt).length()*.5}getMidpoint(e){return e.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(e){return Nt.getNormal(this.a,this.b,this.c,e)}getPlane(e){return e.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(e,t){return Nt.getBarycoord(e,this.a,this.b,this.c,t)}getInterpolation(e,t,n,r,s){return Nt.getInterpolation(e,this.a,this.b,this.c,t,n,r,s)}containsPoint(e){return Nt.containsPoint(e,this.a,this.b,this.c)}isFrontFacing(e){return Nt.isFrontFacing(this.a,this.b,this.c,e)}intersectsBox(e){return e.intersectsTriangle(this)}closestPointToPoint(e,t){const n=this.a,r=this.b,s=this.c;let a,o;kn.subVectors(r,n),Wn.subVectors(s,n),Tr.subVectors(e,n);const l=kn.dot(Tr),h=Wn.dot(Tr);if(l<=0&&h<=0)return t.copy(n);Ar.subVectors(e,r);const u=kn.dot(Ar),d=Wn.dot(Ar);if(u>=0&&d<=u)return t.copy(r);const p=l*d-u*h;if(p<=0&&l>=0&&u<=0)return a=l/(l-u),t.copy(n).addScaledVector(kn,a);br.subVectors(e,s);const m=kn.dot(br),v=Wn.dot(br);if(v>=0&&m<=v)return t.copy(s);const M=m*h-l*v;if(M<=0&&h>=0&&v<=0)return o=h/(h-v),t.copy(n).addScaledVector(Wn,o);const f=u*v-m*d;if(f<=0&&d-u>=0&&m-v>=0)return Ma.subVectors(s,r),o=(d-u)/(d-u+(m-v)),t.copy(r).addScaledVector(Ma,o);const c=1/(f+M+p);return a=M*c,o=p*c,t.copy(n).addScaledVector(kn,a).addScaledVector(Wn,o)}equals(e){return e.a.equals(this.a)&&e.b.equals(this.b)&&e.c.equals(this.c)}}const yo={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},un={h:0,s:0,l:0},zi={h:0,s:0,l:0};function Pr(i,e,t){return t<0&&(t+=1),t>1&&(t-=1),t<1/6?i+(e-i)*6*t:t<1/2?e:t<2/3?i+(e-i)*6*(2/3-t):i}class Ve{constructor(e,t,n){return this.isColor=!0,this.r=1,this.g=1,this.b=1,this.set(e,t,n)}set(e,t,n){if(t===void 0&&n===void 0){const r=e;r&&r.isColor?this.copy(r):typeof r=="number"?this.setHex(r):typeof r=="string"&&this.setStyle(r)}else this.setRGB(e,t,n);return this}setScalar(e){return this.r=e,this.g=e,this.b=e,this}setHex(e,t=Rt){return e=Math.floor(e),this.r=(e>>16&255)/255,this.g=(e>>8&255)/255,this.b=(e&255)/255,ze.toWorkingColorSpace(this,t),this}setRGB(e,t,n,r=ze.workingColorSpace){return this.r=e,this.g=t,this.b=n,ze.toWorkingColorSpace(this,r),this}setHSL(e,t,n,r=ze.workingColorSpace){if(e=Pl(e,1),t=gt(t,0,1),n=gt(n,0,1),t===0)this.r=this.g=this.b=n;else{const s=n<=.5?n*(1+t):n+t-n*t,a=2*n-s;this.r=Pr(a,s,e+1/3),this.g=Pr(a,s,e),this.b=Pr(a,s,e-1/3)}return ze.toWorkingColorSpace(this,r),this}setStyle(e,t=Rt){function n(s){s!==void 0&&parseFloat(s)<1&&console.warn("THREE.Color: Alpha component of "+e+" will be ignored.")}let r;if(r=/^(\w+)\(([^\)]*)\)/.exec(e)){let s;const a=r[1],o=r[2];switch(a){case"rgb":case"rgba":if(s=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(o))return n(s[4]),this.setRGB(Math.min(255,parseInt(s[1],10))/255,Math.min(255,parseInt(s[2],10))/255,Math.min(255,parseInt(s[3],10))/255,t);if(s=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(o))return n(s[4]),this.setRGB(Math.min(100,parseInt(s[1],10))/100,Math.min(100,parseInt(s[2],10))/100,Math.min(100,parseInt(s[3],10))/100,t);break;case"hsl":case"hsla":if(s=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(o))return n(s[4]),this.setHSL(parseFloat(s[1])/360,parseFloat(s[2])/100,parseFloat(s[3])/100,t);break;default:console.warn("THREE.Color: Unknown color model "+e)}}else if(r=/^\#([A-Fa-f\d]+)$/.exec(e)){const s=r[1],a=s.length;if(a===3)return this.setRGB(parseInt(s.charAt(0),16)/15,parseInt(s.charAt(1),16)/15,parseInt(s.charAt(2),16)/15,t);if(a===6)return this.setHex(parseInt(s,16),t);console.warn("THREE.Color: Invalid hex color "+e)}else if(e&&e.length>0)return this.setColorName(e,t);return this}setColorName(e,t=Rt){const n=yo[e.toLowerCase()];return n!==void 0?this.setHex(n,t):console.warn("THREE.Color: Unknown color "+e),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(e){return this.r=e.r,this.g=e.g,this.b=e.b,this}copySRGBToLinear(e){return this.r=tn(e.r),this.g=tn(e.g),this.b=tn(e.b),this}copyLinearToSRGB(e){return this.r=Qn(e.r),this.g=Qn(e.g),this.b=Qn(e.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(e=Rt){return ze.fromWorkingColorSpace(ut.copy(this),e),Math.round(gt(ut.r*255,0,255))*65536+Math.round(gt(ut.g*255,0,255))*256+Math.round(gt(ut.b*255,0,255))}getHexString(e=Rt){return("000000"+this.getHex(e).toString(16)).slice(-6)}getHSL(e,t=ze.workingColorSpace){ze.fromWorkingColorSpace(ut.copy(this),t);const n=ut.r,r=ut.g,s=ut.b,a=Math.max(n,r,s),o=Math.min(n,r,s);let l,h;const u=(o+a)/2;if(o===a)l=0,h=0;else{const d=a-o;switch(h=u<=.5?d/(a+o):d/(2-a-o),a){case n:l=(r-s)/d+(r0!=e>0&&this.version++,this._alphaTest=e}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(e){if(e!==void 0)for(const t in e){const n=e[t];if(n===void 0){console.warn(`THREE.Material: parameter '${t}' has value of undefined.`);continue}const r=this[t];if(r===void 0){console.warn(`THREE.Material: '${t}' is not a property of THREE.${this.type}.`);continue}r&&r.isColor?r.set(n):r&&r.isVector3&&n&&n.isVector3?r.copy(n):this[t]=n}}toJSON(e){const t=e===void 0||typeof e=="string";t&&(e={textures:{},images:{}});const n={metadata:{version:4.6,type:"Material",generator:"Material.toJSON"}};n.uuid=this.uuid,n.type=this.type,this.name!==""&&(n.name=this.name),this.color&&this.color.isColor&&(n.color=this.color.getHex()),this.roughness!==void 0&&(n.roughness=this.roughness),this.metalness!==void 0&&(n.metalness=this.metalness),this.sheen!==void 0&&(n.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(n.sheenColor=this.sheenColor.getHex()),this.sheenRoughness!==void 0&&(n.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),this.emissiveIntensity!==void 0&&this.emissiveIntensity!==1&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.getHex()),this.specularIntensity!==void 0&&(n.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(n.specularColor=this.specularColor.getHex()),this.shininess!==void 0&&(n.shininess=this.shininess),this.clearcoat!==void 0&&(n.clearcoat=this.clearcoat),this.clearcoatRoughness!==void 0&&(n.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(n.clearcoatMap=this.clearcoatMap.toJSON(e).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(e).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(e).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),this.dispersion!==void 0&&(n.dispersion=this.dispersion),this.iridescence!==void 0&&(n.iridescence=this.iridescence),this.iridescenceIOR!==void 0&&(n.iridescenceIOR=this.iridescenceIOR),this.iridescenceThicknessRange!==void 0&&(n.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(n.iridescenceMap=this.iridescenceMap.toJSON(e).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(n.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(e).uuid),this.anisotropy!==void 0&&(n.anisotropy=this.anisotropy),this.anisotropyRotation!==void 0&&(n.anisotropyRotation=this.anisotropyRotation),this.anisotropyMap&&this.anisotropyMap.isTexture&&(n.anisotropyMap=this.anisotropyMap.toJSON(e).uuid),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(e).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(e).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(e).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(e).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(e).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(e).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(e).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(e).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(e).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(e).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(e).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(e).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(n.specularIntensityMap=this.specularIntensityMap.toJSON(e).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(n.specularColorMap=this.specularColorMap.toJSON(e).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(e).uuid,this.combine!==void 0&&(n.combine=this.combine)),this.envMapRotation!==void 0&&(n.envMapRotation=this.envMapRotation.toArray()),this.envMapIntensity!==void 0&&(n.envMapIntensity=this.envMapIntensity),this.reflectivity!==void 0&&(n.reflectivity=this.reflectivity),this.refractionRatio!==void 0&&(n.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(e).uuid),this.transmission!==void 0&&(n.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(n.transmissionMap=this.transmissionMap.toJSON(e).uuid),this.thickness!==void 0&&(n.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(n.thicknessMap=this.thicknessMap.toJSON(e).uuid),this.attenuationDistance!==void 0&&this.attenuationDistance!==1/0&&(n.attenuationDistance=this.attenuationDistance),this.attenuationColor!==void 0&&(n.attenuationColor=this.attenuationColor.getHex()),this.size!==void 0&&(n.size=this.size),this.shadowSide!==null&&(n.shadowSide=this.shadowSide),this.sizeAttenuation!==void 0&&(n.sizeAttenuation=this.sizeAttenuation),this.blending!==jn&&(n.blending=this.blending),this.side!==gn&&(n.side=this.side),this.vertexColors===!0&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),this.transparent===!0&&(n.transparent=!0),this.blendSrc!==kr&&(n.blendSrc=this.blendSrc),this.blendDst!==Wr&&(n.blendDst=this.blendDst),this.blendEquation!==wn&&(n.blendEquation=this.blendEquation),this.blendSrcAlpha!==null&&(n.blendSrcAlpha=this.blendSrcAlpha),this.blendDstAlpha!==null&&(n.blendDstAlpha=this.blendDstAlpha),this.blendEquationAlpha!==null&&(n.blendEquationAlpha=this.blendEquationAlpha),this.blendColor&&this.blendColor.isColor&&(n.blendColor=this.blendColor.getHex()),this.blendAlpha!==0&&(n.blendAlpha=this.blendAlpha),this.depthFunc!==ei&&(n.depthFunc=this.depthFunc),this.depthTest===!1&&(n.depthTest=this.depthTest),this.depthWrite===!1&&(n.depthWrite=this.depthWrite),this.colorWrite===!1&&(n.colorWrite=this.colorWrite),this.stencilWriteMask!==255&&(n.stencilWriteMask=this.stencilWriteMask),this.stencilFunc!==ia&&(n.stencilFunc=this.stencilFunc),this.stencilRef!==0&&(n.stencilRef=this.stencilRef),this.stencilFuncMask!==255&&(n.stencilFuncMask=this.stencilFuncMask),this.stencilFail!==Nn&&(n.stencilFail=this.stencilFail),this.stencilZFail!==Nn&&(n.stencilZFail=this.stencilZFail),this.stencilZPass!==Nn&&(n.stencilZPass=this.stencilZPass),this.stencilWrite===!0&&(n.stencilWrite=this.stencilWrite),this.rotation!==void 0&&this.rotation!==0&&(n.rotation=this.rotation),this.polygonOffset===!0&&(n.polygonOffset=!0),this.polygonOffsetFactor!==0&&(n.polygonOffsetFactor=this.polygonOffsetFactor),this.polygonOffsetUnits!==0&&(n.polygonOffsetUnits=this.polygonOffsetUnits),this.linewidth!==void 0&&this.linewidth!==1&&(n.linewidth=this.linewidth),this.dashSize!==void 0&&(n.dashSize=this.dashSize),this.gapSize!==void 0&&(n.gapSize=this.gapSize),this.scale!==void 0&&(n.scale=this.scale),this.dithering===!0&&(n.dithering=!0),this.alphaTest>0&&(n.alphaTest=this.alphaTest),this.alphaHash===!0&&(n.alphaHash=!0),this.alphaToCoverage===!0&&(n.alphaToCoverage=!0),this.premultipliedAlpha===!0&&(n.premultipliedAlpha=!0),this.forceSinglePass===!0&&(n.forceSinglePass=!0),this.wireframe===!0&&(n.wireframe=!0),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),this.wireframeLinecap!=="round"&&(n.wireframeLinecap=this.wireframeLinecap),this.wireframeLinejoin!=="round"&&(n.wireframeLinejoin=this.wireframeLinejoin),this.flatShading===!0&&(n.flatShading=!0),this.visible===!1&&(n.visible=!1),this.toneMapped===!1&&(n.toneMapped=!1),this.fog===!1&&(n.fog=!1),Object.keys(this.userData).length>0&&(n.userData=this.userData);function r(s){const a=[];for(const o in s){const l=s[o];delete l.metadata,a.push(l)}return a}if(t){const s=r(e.textures),a=r(e.images);s.length>0&&(n.textures=s),a.length>0&&(n.images=a)}return n}clone(){return new this.constructor().copy(this)}copy(e){this.name=e.name,this.blending=e.blending,this.side=e.side,this.vertexColors=e.vertexColors,this.opacity=e.opacity,this.transparent=e.transparent,this.blendSrc=e.blendSrc,this.blendDst=e.blendDst,this.blendEquation=e.blendEquation,this.blendSrcAlpha=e.blendSrcAlpha,this.blendDstAlpha=e.blendDstAlpha,this.blendEquationAlpha=e.blendEquationAlpha,this.blendColor.copy(e.blendColor),this.blendAlpha=e.blendAlpha,this.depthFunc=e.depthFunc,this.depthTest=e.depthTest,this.depthWrite=e.depthWrite,this.stencilWriteMask=e.stencilWriteMask,this.stencilFunc=e.stencilFunc,this.stencilRef=e.stencilRef,this.stencilFuncMask=e.stencilFuncMask,this.stencilFail=e.stencilFail,this.stencilZFail=e.stencilZFail,this.stencilZPass=e.stencilZPass,this.stencilWrite=e.stencilWrite;const t=e.clippingPlanes;let n=null;if(t!==null){const r=t.length;n=new Array(r);for(let s=0;s!==r;++s)n[s]=t[s].clone()}return this.clippingPlanes=n,this.clipIntersection=e.clipIntersection,this.clipShadows=e.clipShadows,this.shadowSide=e.shadowSide,this.colorWrite=e.colorWrite,this.precision=e.precision,this.polygonOffset=e.polygonOffset,this.polygonOffsetFactor=e.polygonOffsetFactor,this.polygonOffsetUnits=e.polygonOffsetUnits,this.dithering=e.dithering,this.alphaTest=e.alphaTest,this.alphaHash=e.alphaHash,this.alphaToCoverage=e.alphaToCoverage,this.premultipliedAlpha=e.premultipliedAlpha,this.forceSinglePass=e.forceSinglePass,this.visible=e.visible,this.toneMapped=e.toneMapped,this.userData=JSON.parse(JSON.stringify(e.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(e){e===!0&&this.version++}onBuild(){console.warn("Material: onBuild() has been removed.")}}class To extends Ai{static get type(){return"MeshBasicMaterial"}constructor(e){super(),this.isMeshBasicMaterial=!0,this.color=new Ve(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new kt,this.combine=Ds,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(e)}copy(e){return super.copy(e),this.color.copy(e.color),this.map=e.map,this.lightMap=e.lightMap,this.lightMapIntensity=e.lightMapIntensity,this.aoMap=e.aoMap,this.aoMapIntensity=e.aoMapIntensity,this.specularMap=e.specularMap,this.alphaMap=e.alphaMap,this.envMap=e.envMap,this.envMapRotation.copy(e.envMapRotation),this.combine=e.combine,this.reflectivity=e.reflectivity,this.refractionRatio=e.refractionRatio,this.wireframe=e.wireframe,this.wireframeLinewidth=e.wireframeLinewidth,this.wireframeLinecap=e.wireframeLinecap,this.wireframeLinejoin=e.wireframeLinejoin,this.fog=e.fog,this}}const st=new I,Hi=new He;class Vt{constructor(e,t,n=!1){if(Array.isArray(e))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,this.name="",this.array=e,this.itemSize=t,this.count=e!==void 0?e.length/t:0,this.normalized=n,this.usage=ra,this.updateRanges=[],this.gpuType=Qt,this.version=0}onUploadCallback(){}set needsUpdate(e){e===!0&&this.version++}setUsage(e){return this.usage=e,this}addUpdateRange(e,t){this.updateRanges.push({start:e,count:t})}clearUpdateRanges(){this.updateRanges.length=0}copy(e){return this.name=e.name,this.array=new e.array.constructor(e.array),this.itemSize=e.itemSize,this.count=e.count,this.normalized=e.normalized,this.usage=e.usage,this.gpuType=e.gpuType,this}copyAt(e,t,n){e*=this.itemSize,n*=t.itemSize;for(let r=0,s=this.itemSize;rt.count&&console.warn("THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry."),t.needsUpdate=!0}return this}computeBoundingBox(){this.boundingBox===null&&(this.boundingBox=new Ti);const e=this.attributes.position,t=this.morphAttributes.position;if(e&&e.isGLBufferAttribute){console.error("THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.",this),this.boundingBox.set(new I(-1/0,-1/0,-1/0),new I(1/0,1/0,1/0));return}if(e!==void 0){if(this.boundingBox.setFromBufferAttribute(e),t)for(let n=0,r=t.length;n0&&(e.userData=this.userData),this.parameters!==void 0){const l=this.parameters;for(const h in l)l[h]!==void 0&&(e[h]=l[h]);return e}e.data={attributes:{}};const t=this.index;t!==null&&(e.data.index={type:t.array.constructor.name,array:Array.prototype.slice.call(t.array)});const n=this.attributes;for(const l in n){const h=n[l];e.data.attributes[l]=h.toJSON(e.data)}const r={};let s=!1;for(const l in this.morphAttributes){const h=this.morphAttributes[l],u=[];for(let d=0,p=h.length;d0&&(r[l]=u,s=!0)}s&&(e.data.morphAttributes=r,e.data.morphTargetsRelative=this.morphTargetsRelative);const a=this.groups;a.length>0&&(e.data.groups=JSON.parse(JSON.stringify(a)));const o=this.boundingSphere;return o!==null&&(e.data.boundingSphere={center:o.center.toArray(),radius:o.radius}),e}clone(){return new this.constructor().copy(this)}copy(e){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const t={};this.name=e.name;const n=e.index;n!==null&&this.setIndex(n.clone(t));const r=e.attributes;for(const h in r){const u=r[h];this.setAttribute(h,u.clone(t))}const s=e.morphAttributes;for(const h in s){const u=[],d=s[h];for(let p=0,m=d.length;p0){const r=t[n[0]];if(r!==void 0){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let s=0,a=r.length;s(e.far-e.near)**2))&&(Sa.copy(s).invert(),En.copy(e.ray).applyMatrix4(Sa),!(n.boundingBox!==null&&En.intersectsBox(n.boundingBox)===!1)&&this._computeIntersections(e,t,En)))}_computeIntersections(e,t,n){let r;const s=this.geometry,a=this.material,o=s.index,l=s.attributes.position,h=s.attributes.uv,u=s.attributes.uv1,d=s.attributes.normal,p=s.groups,m=s.drawRange;if(o!==null)if(Array.isArray(a))for(let v=0,M=p.length;vt.far?null:{distance:h,point:qi.clone(),object:i}}function Yi(i,e,t,n,r,s,a,o,l,h){i.getVertexPosition(o,Vi),i.getVertexPosition(l,ki),i.getVertexPosition(h,Wi);const u=Zl(i,e,t,n,Vi,ki,Wi,ya);if(u){const d=new I;Nt.getBarycoord(ya,Vi,ki,Wi,d),r&&(u.uv=Nt.getInterpolatedAttribute(r,o,l,h,d,new He)),s&&(u.uv1=Nt.getInterpolatedAttribute(s,o,l,h,d,new He)),a&&(u.normal=Nt.getInterpolatedAttribute(a,o,l,h,d,new I),u.normal.dot(n.direction)>0&&u.normal.multiplyScalar(-1));const p={a:o,b:l,c:h,normal:new I,materialIndex:0};Nt.getNormal(Vi,ki,Wi,p.normal),u.face=p,u.barycoord=d}return u}class Ht extends rn{constructor(e=1,t=1,n=1,r=1,s=1,a=1){super(),this.type="BoxGeometry",this.parameters={width:e,height:t,depth:n,widthSegments:r,heightSegments:s,depthSegments:a};const o=this;r=Math.floor(r),s=Math.floor(s),a=Math.floor(a);const l=[],h=[],u=[],d=[];let p=0,m=0;v("z","y","x",-1,-1,n,t,e,a,s,0),v("z","y","x",1,-1,n,t,-e,a,s,1),v("x","z","y",1,1,e,n,t,r,a,2),v("x","z","y",1,-1,e,n,-t,r,a,3),v("x","y","z",1,-1,e,t,n,r,s,4),v("x","y","z",-1,-1,e,t,-n,r,s,5),this.setIndex(l),this.setAttribute("position",new Tt(h,3)),this.setAttribute("normal",new Tt(u,3)),this.setAttribute("uv",new Tt(d,2));function v(M,f,c,A,T,S,F,R,b,U,E){const x=S/b,w=F/U,H=S/2,z=F/2,W=R/2,Z=b+1,k=U+1;let j=0,V=0;const ie=new I;for(let ce=0;ce0?1:-1,u.push(ie.x,ie.y,ie.z),d.push(De/b),d.push(1-ce/U),j+=1}}for(let ce=0;ce0&&(t.defines=this.defines),t.vertexShader=this.vertexShader,t.fragmentShader=this.fragmentShader,t.lights=this.lights,t.clipping=this.clipping;const n={};for(const r in this.extensions)this.extensions[r]===!0&&(n[r]=!0);return Object.keys(n).length>0&&(t.extensions=n),t}}class Ro extends ft{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new et,this.projectionMatrix=new et,this.projectionMatrixInverse=new et,this.coordinateSystem=en}copy(e,t){return super.copy(e,t),this.matrixWorldInverse.copy(e.matrixWorldInverse),this.projectionMatrix.copy(e.projectionMatrix),this.projectionMatrixInverse.copy(e.projectionMatrixInverse),this.coordinateSystem=e.coordinateSystem,this}getWorldDirection(e){return super.getWorldDirection(e).negate()}updateMatrixWorld(e){super.updateMatrixWorld(e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(e,t){super.updateWorldMatrix(e,t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return new this.constructor().copy(this)}}const dn=new I,Ta=new He,Aa=new He;class yt extends Ro{constructor(e=50,t=1,n=.1,r=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=e,this.zoom=1,this.near=n,this.far=r,this.focus=10,this.aspect=t,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(e,t){return super.copy(e,t),this.fov=e.fov,this.zoom=e.zoom,this.near=e.near,this.far=e.far,this.focus=e.focus,this.aspect=e.aspect,this.view=e.view===null?null:Object.assign({},e.view),this.filmGauge=e.filmGauge,this.filmOffset=e.filmOffset,this}setFocalLength(e){const t=.5*this.getFilmHeight()/e;this.fov=Rs*2*Math.atan(t),this.updateProjectionMatrix()}getFocalLength(){const e=Math.tan(dr*.5*this.fov);return .5*this.getFilmHeight()/e}getEffectiveFOV(){return Rs*2*Math.atan(Math.tan(dr*.5*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}getViewBounds(e,t,n){dn.set(-1,-1,.5).applyMatrix4(this.projectionMatrixInverse),t.set(dn.x,dn.y).multiplyScalar(-e/dn.z),dn.set(1,1,.5).applyMatrix4(this.projectionMatrixInverse),n.set(dn.x,dn.y).multiplyScalar(-e/dn.z)}getViewSize(e,t){return this.getViewBounds(e,Ta,Aa),t.subVectors(Aa,Ta)}setViewOffset(e,t,n,r,s,a){this.aspect=e/t,this.view===null&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=e,this.view.fullHeight=t,this.view.offsetX=n,this.view.offsetY=r,this.view.width=s,this.view.height=a,this.updateProjectionMatrix()}clearViewOffset(){this.view!==null&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const e=this.near;let t=e*Math.tan(dr*.5*this.fov)/this.zoom,n=2*t,r=this.aspect*n,s=-.5*r;const a=this.view;if(this.view!==null&&this.view.enabled){const l=a.fullWidth,h=a.fullHeight;s+=a.offsetX*r/l,t-=a.offsetY*n/h,r*=a.width/l,n*=a.height/h}const o=this.filmOffset;o!==0&&(s+=e*o/this.getFilmWidth()),this.projectionMatrix.makePerspective(s,s+r,t,t-n,e,this.far,this.coordinateSystem),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(e){const t=super.toJSON(e);return t.object.fov=this.fov,t.object.zoom=this.zoom,t.object.near=this.near,t.object.far=this.far,t.object.focus=this.focus,t.object.aspect=this.aspect,this.view!==null&&(t.object.view=Object.assign({},this.view)),t.object.filmGauge=this.filmGauge,t.object.filmOffset=this.filmOffset,t}}const qn=-90,Yn=1;class ec extends ft{constructor(e,t,n){super(),this.type="CubeCamera",this.renderTarget=n,this.coordinateSystem=null,this.activeMipmapLevel=0;const r=new yt(qn,Yn,e,t);r.layers=this.layers,this.add(r);const s=new yt(qn,Yn,e,t);s.layers=this.layers,this.add(s);const a=new yt(qn,Yn,e,t);a.layers=this.layers,this.add(a);const o=new yt(qn,Yn,e,t);o.layers=this.layers,this.add(o);const l=new yt(qn,Yn,e,t);l.layers=this.layers,this.add(l);const h=new yt(qn,Yn,e,t);h.layers=this.layers,this.add(h)}updateCoordinateSystem(){const e=this.coordinateSystem,t=this.children.concat(),[n,r,s,a,o,l]=t;for(const h of t)this.remove(h);if(e===en)n.up.set(0,1,0),n.lookAt(1,0,0),r.up.set(0,1,0),r.lookAt(-1,0,0),s.up.set(0,0,-1),s.lookAt(0,1,0),a.up.set(0,0,1),a.lookAt(0,-1,0),o.up.set(0,1,0),o.lookAt(0,0,1),l.up.set(0,1,0),l.lookAt(0,0,-1);else if(e===rr)n.up.set(0,-1,0),n.lookAt(-1,0,0),r.up.set(0,-1,0),r.lookAt(1,0,0),s.up.set(0,0,1),s.lookAt(0,1,0),a.up.set(0,0,-1),a.lookAt(0,-1,0),o.up.set(0,-1,0),o.lookAt(0,0,1),l.up.set(0,-1,0),l.lookAt(0,0,-1);else throw new Error("THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: "+e);for(const h of t)this.add(h),h.updateMatrixWorld()}update(e,t){this.parent===null&&this.updateMatrixWorld();const{renderTarget:n,activeMipmapLevel:r}=this;this.coordinateSystem!==e.coordinateSystem&&(this.coordinateSystem=e.coordinateSystem,this.updateCoordinateSystem());const[s,a,o,l,h,u]=this.children,d=e.getRenderTarget(),p=e.getActiveCubeFace(),m=e.getActiveMipmapLevel(),v=e.xr.enabled;e.xr.enabled=!1;const M=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,e.setRenderTarget(n,0,r),e.render(t,s),e.setRenderTarget(n,1,r),e.render(t,a),e.setRenderTarget(n,2,r),e.render(t,o),e.setRenderTarget(n,3,r),e.render(t,l),e.setRenderTarget(n,4,r),e.render(t,h),n.texture.generateMipmaps=M,e.setRenderTarget(n,5,r),e.render(t,u),e.setRenderTarget(d,p,m),e.xr.enabled=v,n.texture.needsPMREMUpdate=!0}}class Co extends xt{constructor(e,t,n,r,s,a,o,l,h,u){e=e!==void 0?e:[],t=t!==void 0?t:ti,super(e,t,n,r,s,a,o,l,h,u),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(e){this.image=e}}class tc extends Dn{constructor(e=1,t={}){super(e,e,t),this.isWebGLCubeRenderTarget=!0;const n={width:e,height:e,depth:1},r=[n,n,n,n,n,n];this.texture=new Co(r,t.mapping,t.wrapS,t.wrapT,t.magFilter,t.minFilter,t.format,t.type,t.anisotropy,t.colorSpace),this.texture.isRenderTargetTexture=!0,this.texture.generateMipmaps=t.generateMipmaps!==void 0?t.generateMipmaps:!1,this.texture.minFilter=t.minFilter!==void 0?t.minFilter:Gt}fromEquirectangularTexture(e,t){this.texture.type=t.type,this.texture.colorSpace=t.colorSpace,this.texture.generateMipmaps=t.generateMipmaps,this.texture.minFilter=t.minFilter,this.texture.magFilter=t.magFilter;const n={uniforms:{tEquirect:{value:null}},vertexShader:` + + varying vec3 vWorldDirection; + + vec3 transformDirection( in vec3 dir, in mat4 matrix ) { + + return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); + + } + + void main() { + + vWorldDirection = transformDirection( position, modelMatrix ); + + #include + #include + + } + `,fragmentShader:` + + uniform sampler2D tEquirect; + + varying vec3 vWorldDirection; + + #include + + void main() { + + vec3 direction = normalize( vWorldDirection ); + + vec2 sampleUV = equirectUv( direction ); + + gl_FragColor = texture2D( tEquirect, sampleUV ); + + } + `},r=new Ht(5,5,5),s=new vn({name:"CubemapFromEquirect",uniforms:si(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:vt,blending:mn});s.uniforms.tEquirect.value=t;const a=new rt(r,s),o=t.minFilter;return t.minFilter===Pn&&(t.minFilter=Gt),new ec(1,10,this).update(e,a),t.minFilter=o,a.geometry.dispose(),a.material.dispose(),this}clear(e,t,n,r){const s=e.getRenderTarget();for(let a=0;a<6;a++)e.setRenderTarget(this,a),e.clear(t,n,r);e.setRenderTarget(s)}}const Ur=new I,nc=new I,ic=new Ce;class fn{constructor(e=new I(1,0,0),t=0){this.isPlane=!0,this.normal=e,this.constant=t}set(e,t){return this.normal.copy(e),this.constant=t,this}setComponents(e,t,n,r){return this.normal.set(e,t,n),this.constant=r,this}setFromNormalAndCoplanarPoint(e,t){return this.normal.copy(e),this.constant=-t.dot(this.normal),this}setFromCoplanarPoints(e,t,n){const r=Ur.subVectors(n,t).cross(nc.subVectors(e,t)).normalize();return this.setFromNormalAndCoplanarPoint(r,e),this}copy(e){return this.normal.copy(e.normal),this.constant=e.constant,this}normalize(){const e=1/this.normal.length();return this.normal.multiplyScalar(e),this.constant*=e,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(e){return this.normal.dot(e)+this.constant}distanceToSphere(e){return this.distanceToPoint(e.center)-e.radius}projectPoint(e,t){return t.copy(e).addScaledVector(this.normal,-this.distanceToPoint(e))}intersectLine(e,t){const n=e.delta(Ur),r=this.normal.dot(n);if(r===0)return this.distanceToPoint(e.start)===0?t.copy(e.start):null;const s=-(e.start.dot(this.normal)+this.constant)/r;return s<0||s>1?null:t.copy(e.start).addScaledVector(n,s)}intersectsLine(e){const t=this.distanceToPoint(e.start),n=this.distanceToPoint(e.end);return t<0&&n>0||n<0&&t>0}intersectsBox(e){return e.intersectsPlane(this)}intersectsSphere(e){return e.intersectsPlane(this)}coplanarPoint(e){return e.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(e,t){const n=t||ic.getNormalMatrix(e),r=this.coplanarPoint(Ur).applyMatrix4(e),s=this.normal.applyMatrix3(n).normalize();return this.constant=-r.dot(s),this}translate(e){return this.constant-=e.dot(this.normal),this}equals(e){return e.normal.equals(this.normal)&&e.constant===this.constant}clone(){return new this.constructor().copy(this)}}const yn=new zs,Ki=new I;class Gs{constructor(e=new fn,t=new fn,n=new fn,r=new fn,s=new fn,a=new fn){this.planes=[e,t,n,r,s,a]}set(e,t,n,r,s,a){const o=this.planes;return o[0].copy(e),o[1].copy(t),o[2].copy(n),o[3].copy(r),o[4].copy(s),o[5].copy(a),this}copy(e){const t=this.planes;for(let n=0;n<6;n++)t[n].copy(e.planes[n]);return this}setFromProjectionMatrix(e,t=en){const n=this.planes,r=e.elements,s=r[0],a=r[1],o=r[2],l=r[3],h=r[4],u=r[5],d=r[6],p=r[7],m=r[8],v=r[9],M=r[10],f=r[11],c=r[12],A=r[13],T=r[14],S=r[15];if(n[0].setComponents(l-s,p-h,f-m,S-c).normalize(),n[1].setComponents(l+s,p+h,f+m,S+c).normalize(),n[2].setComponents(l+a,p+u,f+v,S+A).normalize(),n[3].setComponents(l-a,p-u,f-v,S-A).normalize(),n[4].setComponents(l-o,p-d,f-M,S-T).normalize(),t===en)n[5].setComponents(l+o,p+d,f+M,S+T).normalize();else if(t===rr)n[5].setComponents(o,d,M,T).normalize();else throw new Error("THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: "+t);return this}intersectsObject(e){if(e.boundingSphere!==void 0)e.boundingSphere===null&&e.computeBoundingSphere(),yn.copy(e.boundingSphere).applyMatrix4(e.matrixWorld);else{const t=e.geometry;t.boundingSphere===null&&t.computeBoundingSphere(),yn.copy(t.boundingSphere).applyMatrix4(e.matrixWorld)}return this.intersectsSphere(yn)}intersectsSprite(e){return yn.center.set(0,0,0),yn.radius=.7071067811865476,yn.applyMatrix4(e.matrixWorld),this.intersectsSphere(yn)}intersectsSphere(e){const t=this.planes,n=e.center,r=-e.radius;for(let s=0;s<6;s++)if(t[s].distanceToPoint(n)0?e.max.x:e.min.x,Ki.y=r.normal.y>0?e.max.y:e.min.y,Ki.z=r.normal.z>0?e.max.z:e.min.z,r.distanceToPoint(Ki)<0)return!1}return!0}containsPoint(e){const t=this.planes;for(let n=0;n<6;n++)if(t[n].distanceToPoint(e)<0)return!1;return!0}clone(){return new this.constructor().copy(this)}}function Po(){let i=null,e=!1,t=null,n=null;function r(s,a){t(s,a),n=i.requestAnimationFrame(r)}return{start:function(){e!==!0&&t!==null&&(n=i.requestAnimationFrame(r),e=!0)},stop:function(){i.cancelAnimationFrame(n),e=!1},setAnimationLoop:function(s){t=s},setContext:function(s){i=s}}}function rc(i){const e=new WeakMap;function t(o,l){const h=o.array,u=o.usage,d=h.byteLength,p=i.createBuffer();i.bindBuffer(l,p),i.bufferData(l,h,u),o.onUploadCallback();let m;if(h instanceof Float32Array)m=i.FLOAT;else if(h instanceof Uint16Array)o.isFloat16BufferAttribute?m=i.HALF_FLOAT:m=i.UNSIGNED_SHORT;else if(h instanceof Int16Array)m=i.SHORT;else if(h instanceof Uint32Array)m=i.UNSIGNED_INT;else if(h instanceof Int32Array)m=i.INT;else if(h instanceof Int8Array)m=i.BYTE;else if(h instanceof Uint8Array)m=i.UNSIGNED_BYTE;else if(h instanceof Uint8ClampedArray)m=i.UNSIGNED_BYTE;else throw new Error("THREE.WebGLAttributes: Unsupported buffer data format: "+h);return{buffer:p,type:m,bytesPerElement:h.BYTES_PER_ELEMENT,version:o.version,size:d}}function n(o,l,h){const u=l.array,d=l.updateRanges;if(i.bindBuffer(h,o),d.length===0)i.bufferSubData(h,0,u);else{d.sort((m,v)=>m.start-v.start);let p=0;for(let m=1;m 0 + vec4 plane; + #ifdef ALPHA_TO_COVERAGE + float distanceToPlane, distanceGradient; + float clipOpacity = 1.0; + #pragma unroll_loop_start + for ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) { + plane = clippingPlanes[ i ]; + distanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w; + distanceGradient = fwidth( distanceToPlane ) / 2.0; + clipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane ); + if ( clipOpacity == 0.0 ) discard; + } + #pragma unroll_loop_end + #if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES + float unionClipOpacity = 1.0; + #pragma unroll_loop_start + for ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) { + plane = clippingPlanes[ i ]; + distanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w; + distanceGradient = fwidth( distanceToPlane ) / 2.0; + unionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane ); + } + #pragma unroll_loop_end + clipOpacity *= 1.0 - unionClipOpacity; + #endif + diffuseColor.a *= clipOpacity; + if ( diffuseColor.a == 0.0 ) discard; + #else + #pragma unroll_loop_start + for ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) { + plane = clippingPlanes[ i ]; + if ( dot( vClipPosition, plane.xyz ) > plane.w ) discard; + } + #pragma unroll_loop_end + #if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES + bool clipped = true; + #pragma unroll_loop_start + for ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) { + plane = clippingPlanes[ i ]; + clipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped; + } + #pragma unroll_loop_end + if ( clipped ) discard; + #endif + #endif +#endif`,Sc=`#if NUM_CLIPPING_PLANES > 0 + varying vec3 vClipPosition; + uniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ]; +#endif`,Ec=`#if NUM_CLIPPING_PLANES > 0 + varying vec3 vClipPosition; +#endif`,yc=`#if NUM_CLIPPING_PLANES > 0 + vClipPosition = - mvPosition.xyz; +#endif`,Tc=`#if defined( USE_COLOR_ALPHA ) + diffuseColor *= vColor; +#elif defined( USE_COLOR ) + diffuseColor.rgb *= vColor; +#endif`,Ac=`#if defined( USE_COLOR_ALPHA ) + varying vec4 vColor; +#elif defined( USE_COLOR ) + varying vec3 vColor; +#endif`,bc=`#if defined( USE_COLOR_ALPHA ) + varying vec4 vColor; +#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR ) + varying vec3 vColor; +#endif`,wc=`#if defined( USE_COLOR_ALPHA ) + vColor = vec4( 1.0 ); +#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR ) + vColor = vec3( 1.0 ); +#endif +#ifdef USE_COLOR + vColor *= color; +#endif +#ifdef USE_INSTANCING_COLOR + vColor.xyz *= instanceColor.xyz; +#endif +#ifdef USE_BATCHING_COLOR + vec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) ); + vColor.xyz *= batchingColor.xyz; +#endif`,Rc=`#define PI 3.141592653589793 +#define PI2 6.283185307179586 +#define PI_HALF 1.5707963267948966 +#define RECIPROCAL_PI 0.3183098861837907 +#define RECIPROCAL_PI2 0.15915494309189535 +#define EPSILON 1e-6 +#ifndef saturate +#define saturate( a ) clamp( a, 0.0, 1.0 ) +#endif +#define whiteComplement( a ) ( 1.0 - saturate( a ) ) +float pow2( const in float x ) { return x*x; } +vec3 pow2( const in vec3 x ) { return x*x; } +float pow3( const in float x ) { return x*x*x; } +float pow4( const in float x ) { float x2 = x*x; return x2*x2; } +float max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); } +float average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); } +highp float rand( const in vec2 uv ) { + const highp float a = 12.9898, b = 78.233, c = 43758.5453; + highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI ); + return fract( sin( sn ) * c ); +} +#ifdef HIGH_PRECISION + float precisionSafeLength( vec3 v ) { return length( v ); } +#else + float precisionSafeLength( vec3 v ) { + float maxComponent = max3( abs( v ) ); + return length( v / maxComponent ) * maxComponent; + } +#endif +struct IncidentLight { + vec3 color; + vec3 direction; + bool visible; +}; +struct ReflectedLight { + vec3 directDiffuse; + vec3 directSpecular; + vec3 indirectDiffuse; + vec3 indirectSpecular; +}; +#ifdef USE_ALPHAHASH + varying vec3 vPosition; +#endif +vec3 transformDirection( in vec3 dir, in mat4 matrix ) { + return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); +} +vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) { + return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz ); +} +mat3 transposeMat3( const in mat3 m ) { + mat3 tmp; + tmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x ); + tmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y ); + tmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z ); + return tmp; +} +bool isPerspectiveMatrix( mat4 m ) { + return m[ 2 ][ 3 ] == - 1.0; +} +vec2 equirectUv( in vec3 dir ) { + float u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5; + float v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5; + return vec2( u, v ); +} +vec3 BRDF_Lambert( const in vec3 diffuseColor ) { + return RECIPROCAL_PI * diffuseColor; +} +vec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) { + float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH ); + return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel ); +} +float F_Schlick( const in float f0, const in float f90, const in float dotVH ) { + float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH ); + return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel ); +} // validated`,Cc=`#ifdef ENVMAP_TYPE_CUBE_UV + #define cubeUV_minMipLevel 4.0 + #define cubeUV_minTileSize 16.0 + float getFace( vec3 direction ) { + vec3 absDirection = abs( direction ); + float face = - 1.0; + if ( absDirection.x > absDirection.z ) { + if ( absDirection.x > absDirection.y ) + face = direction.x > 0.0 ? 0.0 : 3.0; + else + face = direction.y > 0.0 ? 1.0 : 4.0; + } else { + if ( absDirection.z > absDirection.y ) + face = direction.z > 0.0 ? 2.0 : 5.0; + else + face = direction.y > 0.0 ? 1.0 : 4.0; + } + return face; + } + vec2 getUV( vec3 direction, float face ) { + vec2 uv; + if ( face == 0.0 ) { + uv = vec2( direction.z, direction.y ) / abs( direction.x ); + } else if ( face == 1.0 ) { + uv = vec2( - direction.x, - direction.z ) / abs( direction.y ); + } else if ( face == 2.0 ) { + uv = vec2( - direction.x, direction.y ) / abs( direction.z ); + } else if ( face == 3.0 ) { + uv = vec2( - direction.z, direction.y ) / abs( direction.x ); + } else if ( face == 4.0 ) { + uv = vec2( - direction.x, direction.z ) / abs( direction.y ); + } else { + uv = vec2( direction.x, direction.y ) / abs( direction.z ); + } + return 0.5 * ( uv + 1.0 ); + } + vec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) { + float face = getFace( direction ); + float filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 ); + mipInt = max( mipInt, cubeUV_minMipLevel ); + float faceSize = exp2( mipInt ); + highp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0; + if ( face > 2.0 ) { + uv.y += faceSize; + face -= 3.0; + } + uv.x += face * faceSize; + uv.x += filterInt * 3.0 * cubeUV_minTileSize; + uv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize ); + uv.x *= CUBEUV_TEXEL_WIDTH; + uv.y *= CUBEUV_TEXEL_HEIGHT; + #ifdef texture2DGradEXT + return texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb; + #else + return texture2D( envMap, uv ).rgb; + #endif + } + #define cubeUV_r0 1.0 + #define cubeUV_m0 - 2.0 + #define cubeUV_r1 0.8 + #define cubeUV_m1 - 1.0 + #define cubeUV_r4 0.4 + #define cubeUV_m4 2.0 + #define cubeUV_r5 0.305 + #define cubeUV_m5 3.0 + #define cubeUV_r6 0.21 + #define cubeUV_m6 4.0 + float roughnessToMip( float roughness ) { + float mip = 0.0; + if ( roughness >= cubeUV_r1 ) { + mip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0; + } else if ( roughness >= cubeUV_r4 ) { + mip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1; + } else if ( roughness >= cubeUV_r5 ) { + mip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4; + } else if ( roughness >= cubeUV_r6 ) { + mip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5; + } else { + mip = - 2.0 * log2( 1.16 * roughness ); } + return mip; + } + vec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) { + float mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP ); + float mipF = fract( mip ); + float mipInt = floor( mip ); + vec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt ); + if ( mipF == 0.0 ) { + return vec4( color0, 1.0 ); + } else { + vec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 ); + return vec4( mix( color0, color1, mipF ), 1.0 ); + } + } +#endif`,Pc=`vec3 transformedNormal = objectNormal; +#ifdef USE_TANGENT + vec3 transformedTangent = objectTangent; +#endif +#ifdef USE_BATCHING + mat3 bm = mat3( batchingMatrix ); + transformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) ); + transformedNormal = bm * transformedNormal; + #ifdef USE_TANGENT + transformedTangent = bm * transformedTangent; + #endif +#endif +#ifdef USE_INSTANCING + mat3 im = mat3( instanceMatrix ); + transformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) ); + transformedNormal = im * transformedNormal; + #ifdef USE_TANGENT + transformedTangent = im * transformedTangent; + #endif +#endif +transformedNormal = normalMatrix * transformedNormal; +#ifdef FLIP_SIDED + transformedNormal = - transformedNormal; +#endif +#ifdef USE_TANGENT + transformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz; + #ifdef FLIP_SIDED + transformedTangent = - transformedTangent; + #endif +#endif`,Lc=`#ifdef USE_DISPLACEMENTMAP + uniform sampler2D displacementMap; + uniform float displacementScale; + uniform float displacementBias; +#endif`,Dc=`#ifdef USE_DISPLACEMENTMAP + transformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias ); +#endif`,Uc=`#ifdef USE_EMISSIVEMAP + vec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv ); + #ifdef DECODE_VIDEO_TEXTURE_EMISSIVE + emissiveColor = sRGBTransferEOTF( emissiveColor ); + #endif + totalEmissiveRadiance *= emissiveColor.rgb; +#endif`,Ic=`#ifdef USE_EMISSIVEMAP + uniform sampler2D emissiveMap; +#endif`,Nc="gl_FragColor = linearToOutputTexel( gl_FragColor );",Fc=`vec4 LinearTransferOETF( in vec4 value ) { + return value; +} +vec4 sRGBTransferEOTF( in vec4 value ) { + return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a ); +} +vec4 sRGBTransferOETF( in vec4 value ) { + return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a ); +}`,Oc=`#ifdef USE_ENVMAP + #ifdef ENV_WORLDPOS + vec3 cameraToFrag; + if ( isOrthographic ) { + cameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) ); + } else { + cameraToFrag = normalize( vWorldPosition - cameraPosition ); + } + vec3 worldNormal = inverseTransformDirection( normal, viewMatrix ); + #ifdef ENVMAP_MODE_REFLECTION + vec3 reflectVec = reflect( cameraToFrag, worldNormal ); + #else + vec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio ); + #endif + #else + vec3 reflectVec = vReflect; + #endif + #ifdef ENVMAP_TYPE_CUBE + vec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) ); + #else + vec4 envColor = vec4( 0.0 ); + #endif + #ifdef ENVMAP_BLENDING_MULTIPLY + outgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity ); + #elif defined( ENVMAP_BLENDING_MIX ) + outgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity ); + #elif defined( ENVMAP_BLENDING_ADD ) + outgoingLight += envColor.xyz * specularStrength * reflectivity; + #endif +#endif`,Bc=`#ifdef USE_ENVMAP + uniform float envMapIntensity; + uniform float flipEnvMap; + uniform mat3 envMapRotation; + #ifdef ENVMAP_TYPE_CUBE + uniform samplerCube envMap; + #else + uniform sampler2D envMap; + #endif + +#endif`,zc=`#ifdef USE_ENVMAP + uniform float reflectivity; + #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT ) + #define ENV_WORLDPOS + #endif + #ifdef ENV_WORLDPOS + varying vec3 vWorldPosition; + uniform float refractionRatio; + #else + varying vec3 vReflect; + #endif +#endif`,Hc=`#ifdef USE_ENVMAP + #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT ) + #define ENV_WORLDPOS + #endif + #ifdef ENV_WORLDPOS + + varying vec3 vWorldPosition; + #else + varying vec3 vReflect; + uniform float refractionRatio; + #endif +#endif`,Gc=`#ifdef USE_ENVMAP + #ifdef ENV_WORLDPOS + vWorldPosition = worldPosition.xyz; + #else + vec3 cameraToVertex; + if ( isOrthographic ) { + cameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) ); + } else { + cameraToVertex = normalize( worldPosition.xyz - cameraPosition ); + } + vec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix ); + #ifdef ENVMAP_MODE_REFLECTION + vReflect = reflect( cameraToVertex, worldNormal ); + #else + vReflect = refract( cameraToVertex, worldNormal, refractionRatio ); + #endif + #endif +#endif`,Vc=`#ifdef USE_FOG + vFogDepth = - mvPosition.z; +#endif`,kc=`#ifdef USE_FOG + varying float vFogDepth; +#endif`,Wc=`#ifdef USE_FOG + #ifdef FOG_EXP2 + float fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth ); + #else + float fogFactor = smoothstep( fogNear, fogFar, vFogDepth ); + #endif + gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor ); +#endif`,Xc=`#ifdef USE_FOG + uniform vec3 fogColor; + varying float vFogDepth; + #ifdef FOG_EXP2 + uniform float fogDensity; + #else + uniform float fogNear; + uniform float fogFar; + #endif +#endif`,qc=`#ifdef USE_GRADIENTMAP + uniform sampler2D gradientMap; +#endif +vec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) { + float dotNL = dot( normal, lightDirection ); + vec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 ); + #ifdef USE_GRADIENTMAP + return vec3( texture2D( gradientMap, coord ).r ); + #else + vec2 fw = fwidth( coord ) * 0.5; + return mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) ); + #endif +}`,Yc=`#ifdef USE_LIGHTMAP + uniform sampler2D lightMap; + uniform float lightMapIntensity; +#endif`,Kc=`LambertMaterial material; +material.diffuseColor = diffuseColor.rgb; +material.specularStrength = specularStrength;`,Zc=`varying vec3 vViewPosition; +struct LambertMaterial { + vec3 diffuseColor; + float specularStrength; +}; +void RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) { + float dotNL = saturate( dot( geometryNormal, directLight.direction ) ); + vec3 irradiance = dotNL * directLight.color; + reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); +} +void RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) { + reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); +} +#define RE_Direct RE_Direct_Lambert +#define RE_IndirectDiffuse RE_IndirectDiffuse_Lambert`,$c=`uniform bool receiveShadow; +uniform vec3 ambientLightColor; +#if defined( USE_LIGHT_PROBES ) + uniform vec3 lightProbe[ 9 ]; +#endif +vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) { + float x = normal.x, y = normal.y, z = normal.z; + vec3 result = shCoefficients[ 0 ] * 0.886227; + result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y; + result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z; + result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x; + result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y; + result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z; + result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 ); + result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z; + result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y ); + return result; +} +vec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) { + vec3 worldNormal = inverseTransformDirection( normal, viewMatrix ); + vec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe ); + return irradiance; +} +vec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) { + vec3 irradiance = ambientLightColor; + return irradiance; +} +float getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) { + float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 ); + if ( cutoffDistance > 0.0 ) { + distanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) ); + } + return distanceFalloff; +} +float getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) { + return smoothstep( coneCosine, penumbraCosine, angleCosine ); +} +#if NUM_DIR_LIGHTS > 0 + struct DirectionalLight { + vec3 direction; + vec3 color; + }; + uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ]; + void getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) { + light.color = directionalLight.color; + light.direction = directionalLight.direction; + light.visible = true; + } +#endif +#if NUM_POINT_LIGHTS > 0 + struct PointLight { + vec3 position; + vec3 color; + float distance; + float decay; + }; + uniform PointLight pointLights[ NUM_POINT_LIGHTS ]; + void getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) { + vec3 lVector = pointLight.position - geometryPosition; + light.direction = normalize( lVector ); + float lightDistance = length( lVector ); + light.color = pointLight.color; + light.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay ); + light.visible = ( light.color != vec3( 0.0 ) ); + } +#endif +#if NUM_SPOT_LIGHTS > 0 + struct SpotLight { + vec3 position; + vec3 direction; + vec3 color; + float distance; + float decay; + float coneCos; + float penumbraCos; + }; + uniform SpotLight spotLights[ NUM_SPOT_LIGHTS ]; + void getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) { + vec3 lVector = spotLight.position - geometryPosition; + light.direction = normalize( lVector ); + float angleCos = dot( light.direction, spotLight.direction ); + float spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos ); + if ( spotAttenuation > 0.0 ) { + float lightDistance = length( lVector ); + light.color = spotLight.color * spotAttenuation; + light.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay ); + light.visible = ( light.color != vec3( 0.0 ) ); + } else { + light.color = vec3( 0.0 ); + light.visible = false; + } + } +#endif +#if NUM_RECT_AREA_LIGHTS > 0 + struct RectAreaLight { + vec3 color; + vec3 position; + vec3 halfWidth; + vec3 halfHeight; + }; + uniform sampler2D ltc_1; uniform sampler2D ltc_2; + uniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ]; +#endif +#if NUM_HEMI_LIGHTS > 0 + struct HemisphereLight { + vec3 direction; + vec3 skyColor; + vec3 groundColor; + }; + uniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ]; + vec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) { + float dotNL = dot( normal, hemiLight.direction ); + float hemiDiffuseWeight = 0.5 * dotNL + 0.5; + vec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight ); + return irradiance; + } +#endif`,jc=`#ifdef USE_ENVMAP + vec3 getIBLIrradiance( const in vec3 normal ) { + #ifdef ENVMAP_TYPE_CUBE_UV + vec3 worldNormal = inverseTransformDirection( normal, viewMatrix ); + vec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 ); + return PI * envMapColor.rgb * envMapIntensity; + #else + return vec3( 0.0 ); + #endif + } + vec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) { + #ifdef ENVMAP_TYPE_CUBE_UV + vec3 reflectVec = reflect( - viewDir, normal ); + reflectVec = normalize( mix( reflectVec, normal, roughness * roughness) ); + reflectVec = inverseTransformDirection( reflectVec, viewMatrix ); + vec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness ); + return envMapColor.rgb * envMapIntensity; + #else + return vec3( 0.0 ); + #endif + } + #ifdef USE_ANISOTROPY + vec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) { + #ifdef ENVMAP_TYPE_CUBE_UV + vec3 bentNormal = cross( bitangent, viewDir ); + bentNormal = normalize( cross( bentNormal, bitangent ) ); + bentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) ); + return getIBLRadiance( viewDir, bentNormal, roughness ); + #else + return vec3( 0.0 ); + #endif + } + #endif +#endif`,Jc=`ToonMaterial material; +material.diffuseColor = diffuseColor.rgb;`,Qc=`varying vec3 vViewPosition; +struct ToonMaterial { + vec3 diffuseColor; +}; +void RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) { + vec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color; + reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); +} +void RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) { + reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); +} +#define RE_Direct RE_Direct_Toon +#define RE_IndirectDiffuse RE_IndirectDiffuse_Toon`,eh=`BlinnPhongMaterial material; +material.diffuseColor = diffuseColor.rgb; +material.specularColor = specular; +material.specularShininess = shininess; +material.specularStrength = specularStrength;`,th=`varying vec3 vViewPosition; +struct BlinnPhongMaterial { + vec3 diffuseColor; + vec3 specularColor; + float specularShininess; + float specularStrength; +}; +void RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) { + float dotNL = saturate( dot( geometryNormal, directLight.direction ) ); + vec3 irradiance = dotNL * directLight.color; + reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); + reflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength; +} +void RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) { + reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); +} +#define RE_Direct RE_Direct_BlinnPhong +#define RE_IndirectDiffuse RE_IndirectDiffuse_BlinnPhong`,nh=`PhysicalMaterial material; +material.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor ); +vec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) ); +float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z ); +material.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness; +material.roughness = min( material.roughness, 1.0 ); +#ifdef IOR + material.ior = ior; + #ifdef USE_SPECULAR + float specularIntensityFactor = specularIntensity; + vec3 specularColorFactor = specularColor; + #ifdef USE_SPECULAR_COLORMAP + specularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb; + #endif + #ifdef USE_SPECULAR_INTENSITYMAP + specularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a; + #endif + material.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor ); + #else + float specularIntensityFactor = 1.0; + vec3 specularColorFactor = vec3( 1.0 ); + material.specularF90 = 1.0; + #endif + material.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor ); +#else + material.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor ); + material.specularF90 = 1.0; +#endif +#ifdef USE_CLEARCOAT + material.clearcoat = clearcoat; + material.clearcoatRoughness = clearcoatRoughness; + material.clearcoatF0 = vec3( 0.04 ); + material.clearcoatF90 = 1.0; + #ifdef USE_CLEARCOATMAP + material.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x; + #endif + #ifdef USE_CLEARCOAT_ROUGHNESSMAP + material.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y; + #endif + material.clearcoat = saturate( material.clearcoat ); material.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 ); + material.clearcoatRoughness += geometryRoughness; + material.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 ); +#endif +#ifdef USE_DISPERSION + material.dispersion = dispersion; +#endif +#ifdef USE_IRIDESCENCE + material.iridescence = iridescence; + material.iridescenceIOR = iridescenceIOR; + #ifdef USE_IRIDESCENCEMAP + material.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r; + #endif + #ifdef USE_IRIDESCENCE_THICKNESSMAP + material.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum; + #else + material.iridescenceThickness = iridescenceThicknessMaximum; + #endif +#endif +#ifdef USE_SHEEN + material.sheenColor = sheenColor; + #ifdef USE_SHEEN_COLORMAP + material.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb; + #endif + material.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 ); + #ifdef USE_SHEEN_ROUGHNESSMAP + material.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a; + #endif +#endif +#ifdef USE_ANISOTROPY + #ifdef USE_ANISOTROPYMAP + mat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x ); + vec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb; + vec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b; + #else + vec2 anisotropyV = anisotropyVector; + #endif + material.anisotropy = length( anisotropyV ); + if( material.anisotropy == 0.0 ) { + anisotropyV = vec2( 1.0, 0.0 ); + } else { + anisotropyV /= material.anisotropy; + material.anisotropy = saturate( material.anisotropy ); + } + material.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) ); + material.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y; + material.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y; +#endif`,ih=`struct PhysicalMaterial { + vec3 diffuseColor; + float roughness; + vec3 specularColor; + float specularF90; + float dispersion; + #ifdef USE_CLEARCOAT + float clearcoat; + float clearcoatRoughness; + vec3 clearcoatF0; + float clearcoatF90; + #endif + #ifdef USE_IRIDESCENCE + float iridescence; + float iridescenceIOR; + float iridescenceThickness; + vec3 iridescenceFresnel; + vec3 iridescenceF0; + #endif + #ifdef USE_SHEEN + vec3 sheenColor; + float sheenRoughness; + #endif + #ifdef IOR + float ior; + #endif + #ifdef USE_TRANSMISSION + float transmission; + float transmissionAlpha; + float thickness; + float attenuationDistance; + vec3 attenuationColor; + #endif + #ifdef USE_ANISOTROPY + float anisotropy; + float alphaT; + vec3 anisotropyT; + vec3 anisotropyB; + #endif +}; +vec3 clearcoatSpecularDirect = vec3( 0.0 ); +vec3 clearcoatSpecularIndirect = vec3( 0.0 ); +vec3 sheenSpecularDirect = vec3( 0.0 ); +vec3 sheenSpecularIndirect = vec3(0.0 ); +vec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) { + float x = clamp( 1.0 - dotVH, 0.0, 1.0 ); + float x2 = x * x; + float x5 = clamp( x * x2 * x2, 0.0, 0.9999 ); + return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 ); +} +float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) { + float a2 = pow2( alpha ); + float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) ); + float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) ); + return 0.5 / max( gv + gl, EPSILON ); +} +float D_GGX( const in float alpha, const in float dotNH ) { + float a2 = pow2( alpha ); + float denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0; + return RECIPROCAL_PI * a2 / pow2( denom ); +} +#ifdef USE_ANISOTROPY + float V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) { + float gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) ); + float gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) ); + float v = 0.5 / ( gv + gl ); + return saturate(v); + } + float D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) { + float a2 = alphaT * alphaB; + highp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH ); + highp float v2 = dot( v, v ); + float w2 = a2 / v2; + return RECIPROCAL_PI * a2 * pow2 ( w2 ); + } +#endif +#ifdef USE_CLEARCOAT + vec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) { + vec3 f0 = material.clearcoatF0; + float f90 = material.clearcoatF90; + float roughness = material.clearcoatRoughness; + float alpha = pow2( roughness ); + vec3 halfDir = normalize( lightDir + viewDir ); + float dotNL = saturate( dot( normal, lightDir ) ); + float dotNV = saturate( dot( normal, viewDir ) ); + float dotNH = saturate( dot( normal, halfDir ) ); + float dotVH = saturate( dot( viewDir, halfDir ) ); + vec3 F = F_Schlick( f0, f90, dotVH ); + float V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV ); + float D = D_GGX( alpha, dotNH ); + return F * ( V * D ); + } +#endif +vec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) { + vec3 f0 = material.specularColor; + float f90 = material.specularF90; + float roughness = material.roughness; + float alpha = pow2( roughness ); + vec3 halfDir = normalize( lightDir + viewDir ); + float dotNL = saturate( dot( normal, lightDir ) ); + float dotNV = saturate( dot( normal, viewDir ) ); + float dotNH = saturate( dot( normal, halfDir ) ); + float dotVH = saturate( dot( viewDir, halfDir ) ); + vec3 F = F_Schlick( f0, f90, dotVH ); + #ifdef USE_IRIDESCENCE + F = mix( F, material.iridescenceFresnel, material.iridescence ); + #endif + #ifdef USE_ANISOTROPY + float dotTL = dot( material.anisotropyT, lightDir ); + float dotTV = dot( material.anisotropyT, viewDir ); + float dotTH = dot( material.anisotropyT, halfDir ); + float dotBL = dot( material.anisotropyB, lightDir ); + float dotBV = dot( material.anisotropyB, viewDir ); + float dotBH = dot( material.anisotropyB, halfDir ); + float V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL ); + float D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH ); + #else + float V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV ); + float D = D_GGX( alpha, dotNH ); + #endif + return F * ( V * D ); +} +vec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) { + const float LUT_SIZE = 64.0; + const float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE; + const float LUT_BIAS = 0.5 / LUT_SIZE; + float dotNV = saturate( dot( N, V ) ); + vec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) ); + uv = uv * LUT_SCALE + LUT_BIAS; + return uv; +} +float LTC_ClippedSphereFormFactor( const in vec3 f ) { + float l = length( f ); + return max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 ); +} +vec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) { + float x = dot( v1, v2 ); + float y = abs( x ); + float a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y; + float b = 3.4175940 + ( 4.1616724 + y ) * y; + float v = a / b; + float theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v; + return cross( v1, v2 ) * theta_sintheta; +} +vec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) { + vec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ]; + vec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ]; + vec3 lightNormal = cross( v1, v2 ); + if( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 ); + vec3 T1, T2; + T1 = normalize( V - N * dot( V, N ) ); + T2 = - cross( N, T1 ); + mat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) ); + vec3 coords[ 4 ]; + coords[ 0 ] = mat * ( rectCoords[ 0 ] - P ); + coords[ 1 ] = mat * ( rectCoords[ 1 ] - P ); + coords[ 2 ] = mat * ( rectCoords[ 2 ] - P ); + coords[ 3 ] = mat * ( rectCoords[ 3 ] - P ); + coords[ 0 ] = normalize( coords[ 0 ] ); + coords[ 1 ] = normalize( coords[ 1 ] ); + coords[ 2 ] = normalize( coords[ 2 ] ); + coords[ 3 ] = normalize( coords[ 3 ] ); + vec3 vectorFormFactor = vec3( 0.0 ); + vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] ); + vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] ); + vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] ); + vectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] ); + float result = LTC_ClippedSphereFormFactor( vectorFormFactor ); + return vec3( result ); +} +#if defined( USE_SHEEN ) +float D_Charlie( float roughness, float dotNH ) { + float alpha = pow2( roughness ); + float invAlpha = 1.0 / alpha; + float cos2h = dotNH * dotNH; + float sin2h = max( 1.0 - cos2h, 0.0078125 ); + return ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI ); +} +float V_Neubelt( float dotNV, float dotNL ) { + return saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) ); +} +vec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) { + vec3 halfDir = normalize( lightDir + viewDir ); + float dotNL = saturate( dot( normal, lightDir ) ); + float dotNV = saturate( dot( normal, viewDir ) ); + float dotNH = saturate( dot( normal, halfDir ) ); + float D = D_Charlie( sheenRoughness, dotNH ); + float V = V_Neubelt( dotNV, dotNL ); + return sheenColor * ( D * V ); +} +#endif +float IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) { + float dotNV = saturate( dot( normal, viewDir ) ); + float r2 = roughness * roughness; + float a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95; + float b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72; + float DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) ); + return saturate( DG * RECIPROCAL_PI ); +} +vec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) { + float dotNV = saturate( dot( normal, viewDir ) ); + const vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 ); + const vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 ); + vec4 r = roughness * c0 + c1; + float a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y; + vec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw; + return fab; +} +vec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) { + vec2 fab = DFGApprox( normal, viewDir, roughness ); + return specularColor * fab.x + specularF90 * fab.y; +} +#ifdef USE_IRIDESCENCE +void computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) { +#else +void computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) { +#endif + vec2 fab = DFGApprox( normal, viewDir, roughness ); + #ifdef USE_IRIDESCENCE + vec3 Fr = mix( specularColor, iridescenceF0, iridescence ); + #else + vec3 Fr = specularColor; + #endif + vec3 FssEss = Fr * fab.x + specularF90 * fab.y; + float Ess = fab.x + fab.y; + float Ems = 1.0 - Ess; + vec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619; vec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg ); + singleScatter += FssEss; + multiScatter += Fms * Ems; +} +#if NUM_RECT_AREA_LIGHTS > 0 + void RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) { + vec3 normal = geometryNormal; + vec3 viewDir = geometryViewDir; + vec3 position = geometryPosition; + vec3 lightPos = rectAreaLight.position; + vec3 halfWidth = rectAreaLight.halfWidth; + vec3 halfHeight = rectAreaLight.halfHeight; + vec3 lightColor = rectAreaLight.color; + float roughness = material.roughness; + vec3 rectCoords[ 4 ]; + rectCoords[ 0 ] = lightPos + halfWidth - halfHeight; rectCoords[ 1 ] = lightPos - halfWidth - halfHeight; + rectCoords[ 2 ] = lightPos - halfWidth + halfHeight; + rectCoords[ 3 ] = lightPos + halfWidth + halfHeight; + vec2 uv = LTC_Uv( normal, viewDir, roughness ); + vec4 t1 = texture2D( ltc_1, uv ); + vec4 t2 = texture2D( ltc_2, uv ); + mat3 mInv = mat3( + vec3( t1.x, 0, t1.y ), + vec3( 0, 1, 0 ), + vec3( t1.z, 0, t1.w ) + ); + vec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y ); + reflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords ); + reflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords ); + } +#endif +void RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) { + float dotNL = saturate( dot( geometryNormal, directLight.direction ) ); + vec3 irradiance = dotNL * directLight.color; + #ifdef USE_CLEARCOAT + float dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) ); + vec3 ccIrradiance = dotNLcc * directLight.color; + clearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material ); + #endif + #ifdef USE_SHEEN + sheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness ); + #endif + reflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material ); + reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); +} +void RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) { + reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); +} +void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) { + #ifdef USE_CLEARCOAT + clearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness ); + #endif + #ifdef USE_SHEEN + sheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness ); + #endif + vec3 singleScattering = vec3( 0.0 ); + vec3 multiScattering = vec3( 0.0 ); + vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI; + #ifdef USE_IRIDESCENCE + computeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering ); + #else + computeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering ); + #endif + vec3 totalScattering = singleScattering + multiScattering; + vec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) ); + reflectedLight.indirectSpecular += radiance * singleScattering; + reflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance; + reflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance; +} +#define RE_Direct RE_Direct_Physical +#define RE_Direct_RectArea RE_Direct_RectArea_Physical +#define RE_IndirectDiffuse RE_IndirectDiffuse_Physical +#define RE_IndirectSpecular RE_IndirectSpecular_Physical +float computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) { + return saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion ); +}`,rh=` +vec3 geometryPosition = - vViewPosition; +vec3 geometryNormal = normal; +vec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition ); +vec3 geometryClearcoatNormal = vec3( 0.0 ); +#ifdef USE_CLEARCOAT + geometryClearcoatNormal = clearcoatNormal; +#endif +#ifdef USE_IRIDESCENCE + float dotNVi = saturate( dot( normal, geometryViewDir ) ); + if ( material.iridescenceThickness == 0.0 ) { + material.iridescence = 0.0; + } else { + material.iridescence = saturate( material.iridescence ); + } + if ( material.iridescence > 0.0 ) { + material.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor ); + material.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi ); + } +#endif +IncidentLight directLight; +#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct ) + PointLight pointLight; + #if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0 + PointLightShadow pointLightShadow; + #endif + #pragma unroll_loop_start + for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) { + pointLight = pointLights[ i ]; + getPointLightInfo( pointLight, geometryPosition, directLight ); + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS ) + pointLightShadow = pointLightShadows[ i ]; + directLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0; + #endif + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); + } + #pragma unroll_loop_end +#endif +#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct ) + SpotLight spotLight; + vec4 spotColor; + vec3 spotLightCoord; + bool inSpotLightMap; + #if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0 + SpotLightShadow spotLightShadow; + #endif + #pragma unroll_loop_start + for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) { + spotLight = spotLights[ i ]; + getSpotLightInfo( spotLight, geometryPosition, directLight ); + #if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS ) + #define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX + #elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS ) + #define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS + #else + #define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS ) + #endif + #if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS ) + spotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w; + inSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) ); + spotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy ); + directLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color; + #endif + #undef SPOT_LIGHT_MAP_INDEX + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS ) + spotLightShadow = spotLightShadows[ i ]; + directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0; + #endif + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); + } + #pragma unroll_loop_end +#endif +#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) + DirectionalLight directionalLight; + #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0 + DirectionalLightShadow directionalLightShadow; + #endif + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { + directionalLight = directionalLights[ i ]; + getDirectionalLightInfo( directionalLight, directLight ); + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) + directionalLightShadow = directionalLightShadows[ i ]; + directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + #endif + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); + } + #pragma unroll_loop_end +#endif +#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea ) + RectAreaLight rectAreaLight; + #pragma unroll_loop_start + for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) { + rectAreaLight = rectAreaLights[ i ]; + RE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); + } + #pragma unroll_loop_end +#endif +#if defined( RE_IndirectDiffuse ) + vec3 iblIrradiance = vec3( 0.0 ); + vec3 irradiance = getAmbientLightIrradiance( ambientLightColor ); + #if defined( USE_LIGHT_PROBES ) + irradiance += getLightProbeIrradiance( lightProbe, geometryNormal ); + #endif + #if ( NUM_HEMI_LIGHTS > 0 ) + #pragma unroll_loop_start + for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) { + irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal ); + } + #pragma unroll_loop_end + #endif +#endif +#if defined( RE_IndirectSpecular ) + vec3 radiance = vec3( 0.0 ); + vec3 clearcoatRadiance = vec3( 0.0 ); +#endif`,sh=`#if defined( RE_IndirectDiffuse ) + #ifdef USE_LIGHTMAP + vec4 lightMapTexel = texture2D( lightMap, vLightMapUv ); + vec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity; + irradiance += lightMapIrradiance; + #endif + #if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV ) + iblIrradiance += getIBLIrradiance( geometryNormal ); + #endif +#endif +#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular ) + #ifdef USE_ANISOTROPY + radiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy ); + #else + radiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness ); + #endif + #ifdef USE_CLEARCOAT + clearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness ); + #endif +#endif`,ah=`#if defined( RE_IndirectDiffuse ) + RE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); +#endif +#if defined( RE_IndirectSpecular ) + RE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); +#endif`,oh=`#if defined( USE_LOGDEPTHBUF ) + gl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5; +#endif`,lh=`#if defined( USE_LOGDEPTHBUF ) + uniform float logDepthBufFC; + varying float vFragDepth; + varying float vIsPerspective; +#endif`,ch=`#ifdef USE_LOGDEPTHBUF + varying float vFragDepth; + varying float vIsPerspective; +#endif`,hh=`#ifdef USE_LOGDEPTHBUF + vFragDepth = 1.0 + gl_Position.w; + vIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) ); +#endif`,uh=`#ifdef USE_MAP + vec4 sampledDiffuseColor = texture2D( map, vMapUv ); + #ifdef DECODE_VIDEO_TEXTURE + sampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor ); + #endif + diffuseColor *= sampledDiffuseColor; +#endif`,dh=`#ifdef USE_MAP + uniform sampler2D map; +#endif`,fh=`#if defined( USE_MAP ) || defined( USE_ALPHAMAP ) + #if defined( USE_POINTS_UV ) + vec2 uv = vUv; + #else + vec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy; + #endif +#endif +#ifdef USE_MAP + diffuseColor *= texture2D( map, uv ); +#endif +#ifdef USE_ALPHAMAP + diffuseColor.a *= texture2D( alphaMap, uv ).g; +#endif`,ph=`#if defined( USE_POINTS_UV ) + varying vec2 vUv; +#else + #if defined( USE_MAP ) || defined( USE_ALPHAMAP ) + uniform mat3 uvTransform; + #endif +#endif +#ifdef USE_MAP + uniform sampler2D map; +#endif +#ifdef USE_ALPHAMAP + uniform sampler2D alphaMap; +#endif`,mh=`float metalnessFactor = metalness; +#ifdef USE_METALNESSMAP + vec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv ); + metalnessFactor *= texelMetalness.b; +#endif`,_h=`#ifdef USE_METALNESSMAP + uniform sampler2D metalnessMap; +#endif`,gh=`#ifdef USE_INSTANCING_MORPH + float morphTargetInfluences[ MORPHTARGETS_COUNT ]; + float morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r; + for ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) { + morphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r; + } +#endif`,vh=`#if defined( USE_MORPHCOLORS ) + vColor *= morphTargetBaseInfluence; + for ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) { + #if defined( USE_COLOR_ALPHA ) + if ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ]; + #elif defined( USE_COLOR ) + if ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ]; + #endif + } +#endif`,xh=`#ifdef USE_MORPHNORMALS + objectNormal *= morphTargetBaseInfluence; + for ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) { + if ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ]; + } +#endif`,Mh=`#ifdef USE_MORPHTARGETS + #ifndef USE_INSTANCING_MORPH + uniform float morphTargetBaseInfluence; + uniform float morphTargetInfluences[ MORPHTARGETS_COUNT ]; + #endif + uniform sampler2DArray morphTargetsTexture; + uniform ivec2 morphTargetsTextureSize; + vec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) { + int texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset; + int y = texelIndex / morphTargetsTextureSize.x; + int x = texelIndex - y * morphTargetsTextureSize.x; + ivec3 morphUV = ivec3( x, y, morphTargetIndex ); + return texelFetch( morphTargetsTexture, morphUV, 0 ); + } +#endif`,Sh=`#ifdef USE_MORPHTARGETS + transformed *= morphTargetBaseInfluence; + for ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) { + if ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ]; + } +#endif`,Eh=`float faceDirection = gl_FrontFacing ? 1.0 : - 1.0; +#ifdef FLAT_SHADED + vec3 fdx = dFdx( vViewPosition ); + vec3 fdy = dFdy( vViewPosition ); + vec3 normal = normalize( cross( fdx, fdy ) ); +#else + vec3 normal = normalize( vNormal ); + #ifdef DOUBLE_SIDED + normal *= faceDirection; + #endif +#endif +#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) + #ifdef USE_TANGENT + mat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal ); + #else + mat3 tbn = getTangentFrame( - vViewPosition, normal, + #if defined( USE_NORMALMAP ) + vNormalMapUv + #elif defined( USE_CLEARCOAT_NORMALMAP ) + vClearcoatNormalMapUv + #else + vUv + #endif + ); + #endif + #if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED ) + tbn[0] *= faceDirection; + tbn[1] *= faceDirection; + #endif +#endif +#ifdef USE_CLEARCOAT_NORMALMAP + #ifdef USE_TANGENT + mat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal ); + #else + mat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv ); + #endif + #if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED ) + tbn2[0] *= faceDirection; + tbn2[1] *= faceDirection; + #endif +#endif +vec3 nonPerturbedNormal = normal;`,yh=`#ifdef USE_NORMALMAP_OBJECTSPACE + normal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0; + #ifdef FLIP_SIDED + normal = - normal; + #endif + #ifdef DOUBLE_SIDED + normal = normal * faceDirection; + #endif + normal = normalize( normalMatrix * normal ); +#elif defined( USE_NORMALMAP_TANGENTSPACE ) + vec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0; + mapN.xy *= normalScale; + normal = normalize( tbn * mapN ); +#elif defined( USE_BUMPMAP ) + normal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection ); +#endif`,Th=`#ifndef FLAT_SHADED + varying vec3 vNormal; + #ifdef USE_TANGENT + varying vec3 vTangent; + varying vec3 vBitangent; + #endif +#endif`,Ah=`#ifndef FLAT_SHADED + varying vec3 vNormal; + #ifdef USE_TANGENT + varying vec3 vTangent; + varying vec3 vBitangent; + #endif +#endif`,bh=`#ifndef FLAT_SHADED + vNormal = normalize( transformedNormal ); + #ifdef USE_TANGENT + vTangent = normalize( transformedTangent ); + vBitangent = normalize( cross( vNormal, vTangent ) * tangent.w ); + #endif +#endif`,wh=`#ifdef USE_NORMALMAP + uniform sampler2D normalMap; + uniform vec2 normalScale; +#endif +#ifdef USE_NORMALMAP_OBJECTSPACE + uniform mat3 normalMatrix; +#endif +#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) ) + mat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) { + vec3 q0 = dFdx( eye_pos.xyz ); + vec3 q1 = dFdy( eye_pos.xyz ); + vec2 st0 = dFdx( uv.st ); + vec2 st1 = dFdy( uv.st ); + vec3 N = surf_norm; + vec3 q1perp = cross( q1, N ); + vec3 q0perp = cross( N, q0 ); + vec3 T = q1perp * st0.x + q0perp * st1.x; + vec3 B = q1perp * st0.y + q0perp * st1.y; + float det = max( dot( T, T ), dot( B, B ) ); + float scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det ); + return mat3( T * scale, B * scale, N ); + } +#endif`,Rh=`#ifdef USE_CLEARCOAT + vec3 clearcoatNormal = nonPerturbedNormal; +#endif`,Ch=`#ifdef USE_CLEARCOAT_NORMALMAP + vec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0; + clearcoatMapN.xy *= clearcoatNormalScale; + clearcoatNormal = normalize( tbn2 * clearcoatMapN ); +#endif`,Ph=`#ifdef USE_CLEARCOATMAP + uniform sampler2D clearcoatMap; +#endif +#ifdef USE_CLEARCOAT_NORMALMAP + uniform sampler2D clearcoatNormalMap; + uniform vec2 clearcoatNormalScale; +#endif +#ifdef USE_CLEARCOAT_ROUGHNESSMAP + uniform sampler2D clearcoatRoughnessMap; +#endif`,Lh=`#ifdef USE_IRIDESCENCEMAP + uniform sampler2D iridescenceMap; +#endif +#ifdef USE_IRIDESCENCE_THICKNESSMAP + uniform sampler2D iridescenceThicknessMap; +#endif`,Dh=`#ifdef OPAQUE +diffuseColor.a = 1.0; +#endif +#ifdef USE_TRANSMISSION +diffuseColor.a *= material.transmissionAlpha; +#endif +gl_FragColor = vec4( outgoingLight, diffuseColor.a );`,Uh=`vec3 packNormalToRGB( const in vec3 normal ) { + return normalize( normal ) * 0.5 + 0.5; +} +vec3 unpackRGBToNormal( const in vec3 rgb ) { + return 2.0 * rgb.xyz - 1.0; +} +const float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.; +const float Inv255 = 1. / 255.; +const vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 ); +const vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g ); +const vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b ); +const vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a ); +vec4 packDepthToRGBA( const in float v ) { + if( v <= 0.0 ) + return vec4( 0., 0., 0., 0. ); + if( v >= 1.0 ) + return vec4( 1., 1., 1., 1. ); + float vuf; + float af = modf( v * PackFactors.a, vuf ); + float bf = modf( vuf * ShiftRight8, vuf ); + float gf = modf( vuf * ShiftRight8, vuf ); + return vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af ); +} +vec3 packDepthToRGB( const in float v ) { + if( v <= 0.0 ) + return vec3( 0., 0., 0. ); + if( v >= 1.0 ) + return vec3( 1., 1., 1. ); + float vuf; + float bf = modf( v * PackFactors.b, vuf ); + float gf = modf( vuf * ShiftRight8, vuf ); + return vec3( vuf * Inv255, gf * PackUpscale, bf ); +} +vec2 packDepthToRG( const in float v ) { + if( v <= 0.0 ) + return vec2( 0., 0. ); + if( v >= 1.0 ) + return vec2( 1., 1. ); + float vuf; + float gf = modf( v * 256., vuf ); + return vec2( vuf * Inv255, gf ); +} +float unpackRGBAToDepth( const in vec4 v ) { + return dot( v, UnpackFactors4 ); +} +float unpackRGBToDepth( const in vec3 v ) { + return dot( v, UnpackFactors3 ); +} +float unpackRGToDepth( const in vec2 v ) { + return v.r * UnpackFactors2.r + v.g * UnpackFactors2.g; +} +vec4 pack2HalfToRGBA( const in vec2 v ) { + vec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) ); + return vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w ); +} +vec2 unpackRGBATo2Half( const in vec4 v ) { + return vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) ); +} +float viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) { + return ( viewZ + near ) / ( near - far ); +} +float orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) { + return depth * ( near - far ) - near; +} +float viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) { + return ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ ); +} +float perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) { + return ( near * far ) / ( ( far - near ) * depth - far ); +}`,Ih=`#ifdef PREMULTIPLIED_ALPHA + gl_FragColor.rgb *= gl_FragColor.a; +#endif`,Nh=`vec4 mvPosition = vec4( transformed, 1.0 ); +#ifdef USE_BATCHING + mvPosition = batchingMatrix * mvPosition; +#endif +#ifdef USE_INSTANCING + mvPosition = instanceMatrix * mvPosition; +#endif +mvPosition = modelViewMatrix * mvPosition; +gl_Position = projectionMatrix * mvPosition;`,Fh=`#ifdef DITHERING + gl_FragColor.rgb = dithering( gl_FragColor.rgb ); +#endif`,Oh=`#ifdef DITHERING + vec3 dithering( vec3 color ) { + float grid_position = rand( gl_FragCoord.xy ); + vec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 ); + dither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position ); + return color + dither_shift_RGB; + } +#endif`,Bh=`float roughnessFactor = roughness; +#ifdef USE_ROUGHNESSMAP + vec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv ); + roughnessFactor *= texelRoughness.g; +#endif`,zh=`#ifdef USE_ROUGHNESSMAP + uniform sampler2D roughnessMap; +#endif`,Hh=`#if NUM_SPOT_LIGHT_COORDS > 0 + varying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ]; +#endif +#if NUM_SPOT_LIGHT_MAPS > 0 + uniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ]; +#endif +#ifdef USE_SHADOWMAP + #if NUM_DIR_LIGHT_SHADOWS > 0 + uniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ]; + varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ]; + struct DirectionalLightShadow { + float shadowIntensity; + float shadowBias; + float shadowNormalBias; + float shadowRadius; + vec2 shadowMapSize; + }; + uniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ]; + #endif + #if NUM_SPOT_LIGHT_SHADOWS > 0 + uniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ]; + struct SpotLightShadow { + float shadowIntensity; + float shadowBias; + float shadowNormalBias; + float shadowRadius; + vec2 shadowMapSize; + }; + uniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ]; + #endif + #if NUM_POINT_LIGHT_SHADOWS > 0 + uniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ]; + varying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ]; + struct PointLightShadow { + float shadowIntensity; + float shadowBias; + float shadowNormalBias; + float shadowRadius; + vec2 shadowMapSize; + float shadowCameraNear; + float shadowCameraFar; + }; + uniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ]; + #endif + float texture2DCompare( sampler2D depths, vec2 uv, float compare ) { + return step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) ); + } + vec2 texture2DDistribution( sampler2D shadow, vec2 uv ) { + return unpackRGBATo2Half( texture2D( shadow, uv ) ); + } + float VSMShadow (sampler2D shadow, vec2 uv, float compare ){ + float occlusion = 1.0; + vec2 distribution = texture2DDistribution( shadow, uv ); + float hard_shadow = step( compare , distribution.x ); + if (hard_shadow != 1.0 ) { + float distance = compare - distribution.x ; + float variance = max( 0.00000, distribution.y * distribution.y ); + float softness_probability = variance / (variance + distance * distance ); softness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 ); occlusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 ); + } + return occlusion; + } + float getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) { + float shadow = 1.0; + shadowCoord.xyz /= shadowCoord.w; + shadowCoord.z += shadowBias; + bool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0; + bool frustumTest = inFrustum && shadowCoord.z <= 1.0; + if ( frustumTest ) { + #if defined( SHADOWMAP_TYPE_PCF ) + vec2 texelSize = vec2( 1.0 ) / shadowMapSize; + float dx0 = - texelSize.x * shadowRadius; + float dy0 = - texelSize.y * shadowRadius; + float dx1 = + texelSize.x * shadowRadius; + float dy1 = + texelSize.y * shadowRadius; + float dx2 = dx0 / 2.0; + float dy2 = dy0 / 2.0; + float dx3 = dx1 / 2.0; + float dy3 = dy1 / 2.0; + shadow = ( + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) + + texture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z ) + ) * ( 1.0 / 17.0 ); + #elif defined( SHADOWMAP_TYPE_PCF_SOFT ) + vec2 texelSize = vec2( 1.0 ) / shadowMapSize; + float dx = texelSize.x; + float dy = texelSize.y; + vec2 uv = shadowCoord.xy; + vec2 f = fract( uv * shadowMapSize + 0.5 ); + uv -= f * texelSize; + shadow = ( + texture2DCompare( shadowMap, uv, shadowCoord.z ) + + texture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) + + texture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) + + texture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) + + mix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ), + texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ), + f.x ) + + mix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ), + texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ), + f.x ) + + mix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ), + texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ), + f.y ) + + mix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ), + texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ), + f.y ) + + mix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ), + texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ), + f.x ), + mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ), + texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ), + f.x ), + f.y ) + ) * ( 1.0 / 9.0 ); + #elif defined( SHADOWMAP_TYPE_VSM ) + shadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z ); + #else + shadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ); + #endif + } + return mix( 1.0, shadow, shadowIntensity ); + } + vec2 cubeToUV( vec3 v, float texelSizeY ) { + vec3 absV = abs( v ); + float scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) ); + absV *= scaleToCube; + v *= scaleToCube * ( 1.0 - 2.0 * texelSizeY ); + vec2 planar = v.xy; + float almostATexel = 1.5 * texelSizeY; + float almostOne = 1.0 - almostATexel; + if ( absV.z >= almostOne ) { + if ( v.z > 0.0 ) + planar.x = 4.0 - v.x; + } else if ( absV.x >= almostOne ) { + float signX = sign( v.x ); + planar.x = v.z * signX + 2.0 * signX; + } else if ( absV.y >= almostOne ) { + float signY = sign( v.y ); + planar.x = v.x + 2.0 * signY + 2.0; + planar.y = v.z * signY - 2.0; + } + return vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 ); + } + float getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) { + float shadow = 1.0; + vec3 lightToPosition = shadowCoord.xyz; + + float lightToPositionLength = length( lightToPosition ); + if ( lightToPositionLength - shadowCameraFar <= 0.0 && lightToPositionLength - shadowCameraNear >= 0.0 ) { + float dp = ( lightToPositionLength - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear ); dp += shadowBias; + vec3 bd3D = normalize( lightToPosition ); + vec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) ); + #if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM ) + vec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y; + shadow = ( + texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) + + texture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp ) + ) * ( 1.0 / 9.0 ); + #else + shadow = texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ); + #endif + } + return mix( 1.0, shadow, shadowIntensity ); + } +#endif`,Gh=`#if NUM_SPOT_LIGHT_COORDS > 0 + uniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ]; + varying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ]; +#endif +#ifdef USE_SHADOWMAP + #if NUM_DIR_LIGHT_SHADOWS > 0 + uniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ]; + varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ]; + struct DirectionalLightShadow { + float shadowIntensity; + float shadowBias; + float shadowNormalBias; + float shadowRadius; + vec2 shadowMapSize; + }; + uniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ]; + #endif + #if NUM_SPOT_LIGHT_SHADOWS > 0 + struct SpotLightShadow { + float shadowIntensity; + float shadowBias; + float shadowNormalBias; + float shadowRadius; + vec2 shadowMapSize; + }; + uniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ]; + #endif + #if NUM_POINT_LIGHT_SHADOWS > 0 + uniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ]; + varying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ]; + struct PointLightShadow { + float shadowIntensity; + float shadowBias; + float shadowNormalBias; + float shadowRadius; + vec2 shadowMapSize; + float shadowCameraNear; + float shadowCameraFar; + }; + uniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ]; + #endif +#endif`,Vh=`#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 ) + vec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix ); + vec4 shadowWorldPosition; +#endif +#if defined( USE_SHADOWMAP ) + #if NUM_DIR_LIGHT_SHADOWS > 0 + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) { + shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 ); + vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition; + } + #pragma unroll_loop_end + #endif + #if NUM_POINT_LIGHT_SHADOWS > 0 + #pragma unroll_loop_start + for ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) { + shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 ); + vPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition; + } + #pragma unroll_loop_end + #endif +#endif +#if NUM_SPOT_LIGHT_COORDS > 0 + #pragma unroll_loop_start + for ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) { + shadowWorldPosition = worldPosition; + #if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS ) + shadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias; + #endif + vSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition; + } + #pragma unroll_loop_end +#endif`,kh=`float getShadowMask() { + float shadow = 1.0; + #ifdef USE_SHADOWMAP + #if NUM_DIR_LIGHT_SHADOWS > 0 + DirectionalLightShadow directionalLight; + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) { + directionalLight = directionalLightShadows[ i ]; + shadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + } + #pragma unroll_loop_end + #endif + #if NUM_SPOT_LIGHT_SHADOWS > 0 + SpotLightShadow spotLight; + #pragma unroll_loop_start + for ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) { + spotLight = spotLightShadows[ i ]; + shadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0; + } + #pragma unroll_loop_end + #endif + #if NUM_POINT_LIGHT_SHADOWS > 0 + PointLightShadow pointLight; + #pragma unroll_loop_start + for ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) { + pointLight = pointLightShadows[ i ]; + shadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0; + } + #pragma unroll_loop_end + #endif + #endif + return shadow; +}`,Wh=`#ifdef USE_SKINNING + mat4 boneMatX = getBoneMatrix( skinIndex.x ); + mat4 boneMatY = getBoneMatrix( skinIndex.y ); + mat4 boneMatZ = getBoneMatrix( skinIndex.z ); + mat4 boneMatW = getBoneMatrix( skinIndex.w ); +#endif`,Xh=`#ifdef USE_SKINNING + uniform mat4 bindMatrix; + uniform mat4 bindMatrixInverse; + uniform highp sampler2D boneTexture; + mat4 getBoneMatrix( const in float i ) { + int size = textureSize( boneTexture, 0 ).x; + int j = int( i ) * 4; + int x = j % size; + int y = j / size; + vec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 ); + vec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 ); + vec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 ); + vec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 ); + return mat4( v1, v2, v3, v4 ); + } +#endif`,qh=`#ifdef USE_SKINNING + vec4 skinVertex = bindMatrix * vec4( transformed, 1.0 ); + vec4 skinned = vec4( 0.0 ); + skinned += boneMatX * skinVertex * skinWeight.x; + skinned += boneMatY * skinVertex * skinWeight.y; + skinned += boneMatZ * skinVertex * skinWeight.z; + skinned += boneMatW * skinVertex * skinWeight.w; + transformed = ( bindMatrixInverse * skinned ).xyz; +#endif`,Yh=`#ifdef USE_SKINNING + mat4 skinMatrix = mat4( 0.0 ); + skinMatrix += skinWeight.x * boneMatX; + skinMatrix += skinWeight.y * boneMatY; + skinMatrix += skinWeight.z * boneMatZ; + skinMatrix += skinWeight.w * boneMatW; + skinMatrix = bindMatrixInverse * skinMatrix * bindMatrix; + objectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz; + #ifdef USE_TANGENT + objectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz; + #endif +#endif`,Kh=`float specularStrength; +#ifdef USE_SPECULARMAP + vec4 texelSpecular = texture2D( specularMap, vSpecularMapUv ); + specularStrength = texelSpecular.r; +#else + specularStrength = 1.0; +#endif`,Zh=`#ifdef USE_SPECULARMAP + uniform sampler2D specularMap; +#endif`,$h=`#if defined( TONE_MAPPING ) + gl_FragColor.rgb = toneMapping( gl_FragColor.rgb ); +#endif`,jh=`#ifndef saturate +#define saturate( a ) clamp( a, 0.0, 1.0 ) +#endif +uniform float toneMappingExposure; +vec3 LinearToneMapping( vec3 color ) { + return saturate( toneMappingExposure * color ); +} +vec3 ReinhardToneMapping( vec3 color ) { + color *= toneMappingExposure; + return saturate( color / ( vec3( 1.0 ) + color ) ); +} +vec3 CineonToneMapping( vec3 color ) { + color *= toneMappingExposure; + color = max( vec3( 0.0 ), color - 0.004 ); + return pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) ); +} +vec3 RRTAndODTFit( vec3 v ) { + vec3 a = v * ( v + 0.0245786 ) - 0.000090537; + vec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081; + return a / b; +} +vec3 ACESFilmicToneMapping( vec3 color ) { + const mat3 ACESInputMat = mat3( + vec3( 0.59719, 0.07600, 0.02840 ), vec3( 0.35458, 0.90834, 0.13383 ), + vec3( 0.04823, 0.01566, 0.83777 ) + ); + const mat3 ACESOutputMat = mat3( + vec3( 1.60475, -0.10208, -0.00327 ), vec3( -0.53108, 1.10813, -0.07276 ), + vec3( -0.07367, -0.00605, 1.07602 ) + ); + color *= toneMappingExposure / 0.6; + color = ACESInputMat * color; + color = RRTAndODTFit( color ); + color = ACESOutputMat * color; + return saturate( color ); +} +const mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3( + vec3( 1.6605, - 0.1246, - 0.0182 ), + vec3( - 0.5876, 1.1329, - 0.1006 ), + vec3( - 0.0728, - 0.0083, 1.1187 ) +); +const mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3( + vec3( 0.6274, 0.0691, 0.0164 ), + vec3( 0.3293, 0.9195, 0.0880 ), + vec3( 0.0433, 0.0113, 0.8956 ) +); +vec3 agxDefaultContrastApprox( vec3 x ) { + vec3 x2 = x * x; + vec3 x4 = x2 * x2; + return + 15.5 * x4 * x2 + - 40.14 * x4 * x + + 31.96 * x4 + - 6.868 * x2 * x + + 0.4298 * x2 + + 0.1191 * x + - 0.00232; +} +vec3 AgXToneMapping( vec3 color ) { + const mat3 AgXInsetMatrix = mat3( + vec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ), + vec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ), + vec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 ) + ); + const mat3 AgXOutsetMatrix = mat3( + vec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ), + vec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ), + vec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 ) + ); + const float AgxMinEv = - 12.47393; const float AgxMaxEv = 4.026069; + color *= toneMappingExposure; + color = LINEAR_SRGB_TO_LINEAR_REC2020 * color; + color = AgXInsetMatrix * color; + color = max( color, 1e-10 ); color = log2( color ); + color = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv ); + color = clamp( color, 0.0, 1.0 ); + color = agxDefaultContrastApprox( color ); + color = AgXOutsetMatrix * color; + color = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) ); + color = LINEAR_REC2020_TO_LINEAR_SRGB * color; + color = clamp( color, 0.0, 1.0 ); + return color; +} +vec3 NeutralToneMapping( vec3 color ) { + const float StartCompression = 0.8 - 0.04; + const float Desaturation = 0.15; + color *= toneMappingExposure; + float x = min( color.r, min( color.g, color.b ) ); + float offset = x < 0.08 ? x - 6.25 * x * x : 0.04; + color -= offset; + float peak = max( color.r, max( color.g, color.b ) ); + if ( peak < StartCompression ) return color; + float d = 1. - StartCompression; + float newPeak = 1. - d * d / ( peak + d - StartCompression ); + color *= newPeak / peak; + float g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. ); + return mix( color, vec3( newPeak ), g ); +} +vec3 CustomToneMapping( vec3 color ) { return color; }`,Jh=`#ifdef USE_TRANSMISSION + material.transmission = transmission; + material.transmissionAlpha = 1.0; + material.thickness = thickness; + material.attenuationDistance = attenuationDistance; + material.attenuationColor = attenuationColor; + #ifdef USE_TRANSMISSIONMAP + material.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r; + #endif + #ifdef USE_THICKNESSMAP + material.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g; + #endif + vec3 pos = vWorldPosition; + vec3 v = normalize( cameraPosition - pos ); + vec3 n = inverseTransformDirection( normal, viewMatrix ); + vec4 transmitted = getIBLVolumeRefraction( + n, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90, + pos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness, + material.attenuationColor, material.attenuationDistance ); + material.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission ); + totalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission ); +#endif`,Qh=`#ifdef USE_TRANSMISSION + uniform float transmission; + uniform float thickness; + uniform float attenuationDistance; + uniform vec3 attenuationColor; + #ifdef USE_TRANSMISSIONMAP + uniform sampler2D transmissionMap; + #endif + #ifdef USE_THICKNESSMAP + uniform sampler2D thicknessMap; + #endif + uniform vec2 transmissionSamplerSize; + uniform sampler2D transmissionSamplerMap; + uniform mat4 modelMatrix; + uniform mat4 projectionMatrix; + varying vec3 vWorldPosition; + float w0( float a ) { + return ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 ); + } + float w1( float a ) { + return ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 ); + } + float w2( float a ){ + return ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 ); + } + float w3( float a ) { + return ( 1.0 / 6.0 ) * ( a * a * a ); + } + float g0( float a ) { + return w0( a ) + w1( a ); + } + float g1( float a ) { + return w2( a ) + w3( a ); + } + float h0( float a ) { + return - 1.0 + w1( a ) / ( w0( a ) + w1( a ) ); + } + float h1( float a ) { + return 1.0 + w3( a ) / ( w2( a ) + w3( a ) ); + } + vec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) { + uv = uv * texelSize.zw + 0.5; + vec2 iuv = floor( uv ); + vec2 fuv = fract( uv ); + float g0x = g0( fuv.x ); + float g1x = g1( fuv.x ); + float h0x = h0( fuv.x ); + float h1x = h1( fuv.x ); + float h0y = h0( fuv.y ); + float h1y = h1( fuv.y ); + vec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy; + vec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy; + vec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy; + vec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy; + return g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) + + g1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) ); + } + vec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) { + vec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) ); + vec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) ); + vec2 fLodSizeInv = 1.0 / fLodSize; + vec2 cLodSizeInv = 1.0 / cLodSize; + vec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) ); + vec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) ); + return mix( fSample, cSample, fract( lod ) ); + } + vec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) { + vec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior ); + vec3 modelScale; + modelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) ); + modelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) ); + modelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) ); + return normalize( refractionVector ) * thickness * modelScale; + } + float applyIorToRoughness( const in float roughness, const in float ior ) { + return roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 ); + } + vec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) { + float lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior ); + return textureBicubic( transmissionSamplerMap, fragCoord.xy, lod ); + } + vec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) { + if ( isinf( attenuationDistance ) ) { + return vec3( 1.0 ); + } else { + vec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance; + vec3 transmittance = exp( - attenuationCoefficient * transmissionDistance ); return transmittance; + } + } + vec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor, + const in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix, + const in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness, + const in vec3 attenuationColor, const in float attenuationDistance ) { + vec4 transmittedLight; + vec3 transmittance; + #ifdef USE_DISPERSION + float halfSpread = ( ior - 1.0 ) * 0.025 * dispersion; + vec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread ); + for ( int i = 0; i < 3; i ++ ) { + vec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix ); + vec3 refractedRayExit = position + transmissionRay; + + vec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 ); + vec2 refractionCoords = ndcPos.xy / ndcPos.w; + refractionCoords += 1.0; + refractionCoords /= 2.0; + + vec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] ); + transmittedLight[ i ] = transmissionSample[ i ]; + transmittedLight.a += transmissionSample.a; + transmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ]; + } + transmittedLight.a /= 3.0; + + #else + + vec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix ); + vec3 refractedRayExit = position + transmissionRay; + vec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 ); + vec2 refractionCoords = ndcPos.xy / ndcPos.w; + refractionCoords += 1.0; + refractionCoords /= 2.0; + transmittedLight = getTransmissionSample( refractionCoords, roughness, ior ); + transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ); + + #endif + vec3 attenuatedColor = transmittance * transmittedLight.rgb; + vec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness ); + float transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0; + return vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor ); + } +#endif`,eu=`#if defined( USE_UV ) || defined( USE_ANISOTROPY ) + varying vec2 vUv; +#endif +#ifdef USE_MAP + varying vec2 vMapUv; +#endif +#ifdef USE_ALPHAMAP + varying vec2 vAlphaMapUv; +#endif +#ifdef USE_LIGHTMAP + varying vec2 vLightMapUv; +#endif +#ifdef USE_AOMAP + varying vec2 vAoMapUv; +#endif +#ifdef USE_BUMPMAP + varying vec2 vBumpMapUv; +#endif +#ifdef USE_NORMALMAP + varying vec2 vNormalMapUv; +#endif +#ifdef USE_EMISSIVEMAP + varying vec2 vEmissiveMapUv; +#endif +#ifdef USE_METALNESSMAP + varying vec2 vMetalnessMapUv; +#endif +#ifdef USE_ROUGHNESSMAP + varying vec2 vRoughnessMapUv; +#endif +#ifdef USE_ANISOTROPYMAP + varying vec2 vAnisotropyMapUv; +#endif +#ifdef USE_CLEARCOATMAP + varying vec2 vClearcoatMapUv; +#endif +#ifdef USE_CLEARCOAT_NORMALMAP + varying vec2 vClearcoatNormalMapUv; +#endif +#ifdef USE_CLEARCOAT_ROUGHNESSMAP + varying vec2 vClearcoatRoughnessMapUv; +#endif +#ifdef USE_IRIDESCENCEMAP + varying vec2 vIridescenceMapUv; +#endif +#ifdef USE_IRIDESCENCE_THICKNESSMAP + varying vec2 vIridescenceThicknessMapUv; +#endif +#ifdef USE_SHEEN_COLORMAP + varying vec2 vSheenColorMapUv; +#endif +#ifdef USE_SHEEN_ROUGHNESSMAP + varying vec2 vSheenRoughnessMapUv; +#endif +#ifdef USE_SPECULARMAP + varying vec2 vSpecularMapUv; +#endif +#ifdef USE_SPECULAR_COLORMAP + varying vec2 vSpecularColorMapUv; +#endif +#ifdef USE_SPECULAR_INTENSITYMAP + varying vec2 vSpecularIntensityMapUv; +#endif +#ifdef USE_TRANSMISSIONMAP + uniform mat3 transmissionMapTransform; + varying vec2 vTransmissionMapUv; +#endif +#ifdef USE_THICKNESSMAP + uniform mat3 thicknessMapTransform; + varying vec2 vThicknessMapUv; +#endif`,tu=`#if defined( USE_UV ) || defined( USE_ANISOTROPY ) + varying vec2 vUv; +#endif +#ifdef USE_MAP + uniform mat3 mapTransform; + varying vec2 vMapUv; +#endif +#ifdef USE_ALPHAMAP + uniform mat3 alphaMapTransform; + varying vec2 vAlphaMapUv; +#endif +#ifdef USE_LIGHTMAP + uniform mat3 lightMapTransform; + varying vec2 vLightMapUv; +#endif +#ifdef USE_AOMAP + uniform mat3 aoMapTransform; + varying vec2 vAoMapUv; +#endif +#ifdef USE_BUMPMAP + uniform mat3 bumpMapTransform; + varying vec2 vBumpMapUv; +#endif +#ifdef USE_NORMALMAP + uniform mat3 normalMapTransform; + varying vec2 vNormalMapUv; +#endif +#ifdef USE_DISPLACEMENTMAP + uniform mat3 displacementMapTransform; + varying vec2 vDisplacementMapUv; +#endif +#ifdef USE_EMISSIVEMAP + uniform mat3 emissiveMapTransform; + varying vec2 vEmissiveMapUv; +#endif +#ifdef USE_METALNESSMAP + uniform mat3 metalnessMapTransform; + varying vec2 vMetalnessMapUv; +#endif +#ifdef USE_ROUGHNESSMAP + uniform mat3 roughnessMapTransform; + varying vec2 vRoughnessMapUv; +#endif +#ifdef USE_ANISOTROPYMAP + uniform mat3 anisotropyMapTransform; + varying vec2 vAnisotropyMapUv; +#endif +#ifdef USE_CLEARCOATMAP + uniform mat3 clearcoatMapTransform; + varying vec2 vClearcoatMapUv; +#endif +#ifdef USE_CLEARCOAT_NORMALMAP + uniform mat3 clearcoatNormalMapTransform; + varying vec2 vClearcoatNormalMapUv; +#endif +#ifdef USE_CLEARCOAT_ROUGHNESSMAP + uniform mat3 clearcoatRoughnessMapTransform; + varying vec2 vClearcoatRoughnessMapUv; +#endif +#ifdef USE_SHEEN_COLORMAP + uniform mat3 sheenColorMapTransform; + varying vec2 vSheenColorMapUv; +#endif +#ifdef USE_SHEEN_ROUGHNESSMAP + uniform mat3 sheenRoughnessMapTransform; + varying vec2 vSheenRoughnessMapUv; +#endif +#ifdef USE_IRIDESCENCEMAP + uniform mat3 iridescenceMapTransform; + varying vec2 vIridescenceMapUv; +#endif +#ifdef USE_IRIDESCENCE_THICKNESSMAP + uniform mat3 iridescenceThicknessMapTransform; + varying vec2 vIridescenceThicknessMapUv; +#endif +#ifdef USE_SPECULARMAP + uniform mat3 specularMapTransform; + varying vec2 vSpecularMapUv; +#endif +#ifdef USE_SPECULAR_COLORMAP + uniform mat3 specularColorMapTransform; + varying vec2 vSpecularColorMapUv; +#endif +#ifdef USE_SPECULAR_INTENSITYMAP + uniform mat3 specularIntensityMapTransform; + varying vec2 vSpecularIntensityMapUv; +#endif +#ifdef USE_TRANSMISSIONMAP + uniform mat3 transmissionMapTransform; + varying vec2 vTransmissionMapUv; +#endif +#ifdef USE_THICKNESSMAP + uniform mat3 thicknessMapTransform; + varying vec2 vThicknessMapUv; +#endif`,nu=`#if defined( USE_UV ) || defined( USE_ANISOTROPY ) + vUv = vec3( uv, 1 ).xy; +#endif +#ifdef USE_MAP + vMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy; +#endif +#ifdef USE_ALPHAMAP + vAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_LIGHTMAP + vLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_AOMAP + vAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_BUMPMAP + vBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_NORMALMAP + vNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_DISPLACEMENTMAP + vDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_EMISSIVEMAP + vEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_METALNESSMAP + vMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_ROUGHNESSMAP + vRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_ANISOTROPYMAP + vAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_CLEARCOATMAP + vClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_CLEARCOAT_NORMALMAP + vClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_CLEARCOAT_ROUGHNESSMAP + vClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_IRIDESCENCEMAP + vIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_IRIDESCENCE_THICKNESSMAP + vIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_SHEEN_COLORMAP + vSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_SHEEN_ROUGHNESSMAP + vSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_SPECULARMAP + vSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_SPECULAR_COLORMAP + vSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_SPECULAR_INTENSITYMAP + vSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_TRANSMISSIONMAP + vTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy; +#endif +#ifdef USE_THICKNESSMAP + vThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy; +#endif`,iu=`#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0 + vec4 worldPosition = vec4( transformed, 1.0 ); + #ifdef USE_BATCHING + worldPosition = batchingMatrix * worldPosition; + #endif + #ifdef USE_INSTANCING + worldPosition = instanceMatrix * worldPosition; + #endif + worldPosition = modelMatrix * worldPosition; +#endif`;const ru=`varying vec2 vUv; +uniform mat3 uvTransform; +void main() { + vUv = ( uvTransform * vec3( uv, 1 ) ).xy; + gl_Position = vec4( position.xy, 1.0, 1.0 ); +}`,su=`uniform sampler2D t2D; +uniform float backgroundIntensity; +varying vec2 vUv; +void main() { + vec4 texColor = texture2D( t2D, vUv ); + #ifdef DECODE_VIDEO_TEXTURE + texColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w ); + #endif + texColor.rgb *= backgroundIntensity; + gl_FragColor = texColor; + #include + #include +}`,au=`varying vec3 vWorldDirection; +#include +void main() { + vWorldDirection = transformDirection( position, modelMatrix ); + #include + #include + gl_Position.z = gl_Position.w; +}`,ou=`#ifdef ENVMAP_TYPE_CUBE + uniform samplerCube envMap; +#elif defined( ENVMAP_TYPE_CUBE_UV ) + uniform sampler2D envMap; +#endif +uniform float flipEnvMap; +uniform float backgroundBlurriness; +uniform float backgroundIntensity; +uniform mat3 backgroundRotation; +varying vec3 vWorldDirection; +#include +void main() { + #ifdef ENVMAP_TYPE_CUBE + vec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) ); + #elif defined( ENVMAP_TYPE_CUBE_UV ) + vec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness ); + #else + vec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 ); + #endif + texColor.rgb *= backgroundIntensity; + gl_FragColor = texColor; + #include + #include +}`,lu=`varying vec3 vWorldDirection; +#include +void main() { + vWorldDirection = transformDirection( position, modelMatrix ); + #include + #include + gl_Position.z = gl_Position.w; +}`,cu=`uniform samplerCube tCube; +uniform float tFlip; +uniform float opacity; +varying vec3 vWorldDirection; +void main() { + vec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) ); + gl_FragColor = texColor; + gl_FragColor.a *= opacity; + #include + #include +}`,hu=`#include +#include +#include +#include +#include +#include +#include +#include +varying vec2 vHighPrecisionZW; +void main() { + #include + #include + #include + #include + #ifdef USE_DISPLACEMENTMAP + #include + #include + #include + #endif + #include + #include + #include + #include + #include + #include + #include + vHighPrecisionZW = gl_Position.zw; +}`,uu=`#if DEPTH_PACKING == 3200 + uniform float opacity; +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +varying vec2 vHighPrecisionZW; +void main() { + vec4 diffuseColor = vec4( 1.0 ); + #include + #if DEPTH_PACKING == 3200 + diffuseColor.a = opacity; + #endif + #include + #include + #include + #include + #include + float fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5; + #if DEPTH_PACKING == 3200 + gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity ); + #elif DEPTH_PACKING == 3201 + gl_FragColor = packDepthToRGBA( fragCoordZ ); + #elif DEPTH_PACKING == 3202 + gl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 ); + #elif DEPTH_PACKING == 3203 + gl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 ); + #endif +}`,du=`#define DISTANCE +varying vec3 vWorldPosition; +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + #ifdef USE_DISPLACEMENTMAP + #include + #include + #include + #endif + #include + #include + #include + #include + #include + #include + #include + vWorldPosition = worldPosition.xyz; +}`,fu=`#define DISTANCE +uniform vec3 referencePosition; +uniform float nearDistance; +uniform float farDistance; +varying vec3 vWorldPosition; +#include +#include +#include +#include +#include +#include +#include +#include +void main () { + vec4 diffuseColor = vec4( 1.0 ); + #include + #include + #include + #include + #include + float dist = length( vWorldPosition - referencePosition ); + dist = ( dist - nearDistance ) / ( farDistance - nearDistance ); + dist = saturate( dist ); + gl_FragColor = packDepthToRGBA( dist ); +}`,pu=`varying vec3 vWorldDirection; +#include +void main() { + vWorldDirection = transformDirection( position, modelMatrix ); + #include + #include +}`,mu=`uniform sampler2D tEquirect; +varying vec3 vWorldDirection; +#include +void main() { + vec3 direction = normalize( vWorldDirection ); + vec2 sampleUV = equirectUv( direction ); + gl_FragColor = texture2D( tEquirect, sampleUV ); + #include + #include +}`,_u=`uniform float scale; +attribute float lineDistance; +varying float vLineDistance; +#include +#include +#include +#include +#include +#include +#include +void main() { + vLineDistance = scale * lineDistance; + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +}`,gu=`uniform vec3 diffuse; +uniform float opacity; +uniform float dashSize; +uniform float totalSize; +varying float vLineDistance; +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + if ( mod( vLineDistance, totalSize ) > dashSize ) { + discard; + } + vec3 outgoingLight = vec3( 0.0 ); + #include + #include + #include + outgoingLight = diffuseColor.rgb; + #include + #include + #include + #include + #include +}`,vu=`#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + #include + #if defined ( USE_ENVMAP ) || defined ( USE_SKINNING ) + #include + #include + #include + #include + #include + #endif + #include + #include + #include + #include + #include + #include + #include + #include + #include +}`,xu=`uniform vec3 diffuse; +uniform float opacity; +#ifndef FLAT_SHADED + varying vec3 vNormal; +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + #include + #include + #include + #include + #include + #include + #include + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + #ifdef USE_LIGHTMAP + vec4 lightMapTexel = texture2D( lightMap, vLightMapUv ); + reflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI; + #else + reflectedLight.indirectDiffuse += vec3( 1.0 ); + #endif + #include + reflectedLight.indirectDiffuse *= diffuseColor.rgb; + vec3 outgoingLight = reflectedLight.indirectDiffuse; + #include + #include + #include + #include + #include + #include + #include +}`,Mu=`#define LAMBERT +varying vec3 vViewPosition; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + vViewPosition = - mvPosition.xyz; + #include + #include + #include + #include +}`,Su=`#define LAMBERT +uniform vec3 diffuse; +uniform vec3 emissive; +uniform float opacity; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + vec3 totalEmissiveRadiance = emissive; + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance; + #include + #include + #include + #include + #include + #include + #include +}`,Eu=`#define MATCAP +varying vec3 vViewPosition; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + vViewPosition = - mvPosition.xyz; +}`,yu=`#define MATCAP +uniform vec3 diffuse; +uniform float opacity; +uniform sampler2D matcap; +varying vec3 vViewPosition; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + #include + #include + #include + #include + #include + #include + #include + #include + vec3 viewDir = normalize( vViewPosition ); + vec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) ); + vec3 y = cross( viewDir, x ); + vec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5; + #ifdef USE_MATCAP + vec4 matcapColor = texture2D( matcap, uv ); + #else + vec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 ); + #endif + vec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb; + #include + #include + #include + #include + #include + #include +}`,Tu=`#define NORMAL +#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE ) + varying vec3 vViewPosition; +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE ) + vViewPosition = - mvPosition.xyz; +#endif +}`,Au=`#define NORMAL +uniform float opacity; +#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE ) + varying vec3 vViewPosition; +#endif +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity ); + #include + #include + #include + #include + gl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a ); + #ifdef OPAQUE + gl_FragColor.a = 1.0; + #endif +}`,bu=`#define PHONG +varying vec3 vViewPosition; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + vViewPosition = - mvPosition.xyz; + #include + #include + #include + #include +}`,wu=`#define PHONG +uniform vec3 diffuse; +uniform vec3 emissive; +uniform vec3 specular; +uniform float shininess; +uniform float opacity; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + vec3 totalEmissiveRadiance = emissive; + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance; + #include + #include + #include + #include + #include + #include + #include +}`,Ru=`#define STANDARD +varying vec3 vViewPosition; +#ifdef USE_TRANSMISSION + varying vec3 vWorldPosition; +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + vViewPosition = - mvPosition.xyz; + #include + #include + #include +#ifdef USE_TRANSMISSION + vWorldPosition = worldPosition.xyz; +#endif +}`,Cu=`#define STANDARD +#ifdef PHYSICAL + #define IOR + #define USE_SPECULAR +#endif +uniform vec3 diffuse; +uniform vec3 emissive; +uniform float roughness; +uniform float metalness; +uniform float opacity; +#ifdef IOR + uniform float ior; +#endif +#ifdef USE_SPECULAR + uniform float specularIntensity; + uniform vec3 specularColor; + #ifdef USE_SPECULAR_COLORMAP + uniform sampler2D specularColorMap; + #endif + #ifdef USE_SPECULAR_INTENSITYMAP + uniform sampler2D specularIntensityMap; + #endif +#endif +#ifdef USE_CLEARCOAT + uniform float clearcoat; + uniform float clearcoatRoughness; +#endif +#ifdef USE_DISPERSION + uniform float dispersion; +#endif +#ifdef USE_IRIDESCENCE + uniform float iridescence; + uniform float iridescenceIOR; + uniform float iridescenceThicknessMinimum; + uniform float iridescenceThicknessMaximum; +#endif +#ifdef USE_SHEEN + uniform vec3 sheenColor; + uniform float sheenRoughness; + #ifdef USE_SHEEN_COLORMAP + uniform sampler2D sheenColorMap; + #endif + #ifdef USE_SHEEN_ROUGHNESSMAP + uniform sampler2D sheenRoughnessMap; + #endif +#endif +#ifdef USE_ANISOTROPY + uniform vec2 anisotropyVector; + #ifdef USE_ANISOTROPYMAP + uniform sampler2D anisotropyMap; + #endif +#endif +varying vec3 vViewPosition; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + vec3 totalEmissiveRadiance = emissive; + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + vec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse; + vec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular; + #include + vec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance; + #ifdef USE_SHEEN + float sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor ); + outgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect; + #endif + #ifdef USE_CLEARCOAT + float dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) ); + vec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc ); + outgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat; + #endif + #include + #include + #include + #include + #include + #include +}`,Pu=`#define TOON +varying vec3 vViewPosition; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + vViewPosition = - mvPosition.xyz; + #include + #include + #include +}`,Lu=`#define TOON +uniform vec3 diffuse; +uniform vec3 emissive; +uniform float opacity; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + vec3 totalEmissiveRadiance = emissive; + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance; + #include + #include + #include + #include + #include + #include +}`,Du=`uniform float size; +uniform float scale; +#include +#include +#include +#include +#include +#include +#ifdef USE_POINTS_UV + varying vec2 vUv; + uniform mat3 uvTransform; +#endif +void main() { + #ifdef USE_POINTS_UV + vUv = ( uvTransform * vec3( uv, 1 ) ).xy; + #endif + #include + #include + #include + #include + #include + #include + gl_PointSize = size; + #ifdef USE_SIZEATTENUATION + bool isPerspective = isPerspectiveMatrix( projectionMatrix ); + if ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z ); + #endif + #include + #include + #include + #include +}`,Uu=`uniform vec3 diffuse; +uniform float opacity; +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + vec3 outgoingLight = vec3( 0.0 ); + #include + #include + #include + #include + #include + outgoingLight = diffuseColor.rgb; + #include + #include + #include + #include + #include +}`,Iu=`#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +}`,Nu=`uniform vec3 color; +uniform float opacity; +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + gl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) ); + #include + #include + #include +}`,Fu=`uniform float rotation; +uniform vec2 center; +#include +#include +#include +#include +#include +void main() { + #include + vec4 mvPosition = modelViewMatrix[ 3 ]; + vec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) ); + #ifndef USE_SIZEATTENUATION + bool isPerspective = isPerspectiveMatrix( projectionMatrix ); + if ( isPerspective ) scale *= - mvPosition.z; + #endif + vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale; + vec2 rotatedPosition; + rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y; + rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y; + mvPosition.xy += rotatedPosition; + gl_Position = projectionMatrix * mvPosition; + #include + #include + #include +}`,Ou=`uniform vec3 diffuse; +uniform float opacity; +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + vec3 outgoingLight = vec3( 0.0 ); + #include + #include + #include + #include + #include + outgoingLight = diffuseColor.rgb; + #include + #include + #include + #include +}`,Le={alphahash_fragment:sc,alphahash_pars_fragment:ac,alphamap_fragment:oc,alphamap_pars_fragment:lc,alphatest_fragment:cc,alphatest_pars_fragment:hc,aomap_fragment:uc,aomap_pars_fragment:dc,batching_pars_vertex:fc,batching_vertex:pc,begin_vertex:mc,beginnormal_vertex:_c,bsdfs:gc,iridescence_fragment:vc,bumpmap_pars_fragment:xc,clipping_planes_fragment:Mc,clipping_planes_pars_fragment:Sc,clipping_planes_pars_vertex:Ec,clipping_planes_vertex:yc,color_fragment:Tc,color_pars_fragment:Ac,color_pars_vertex:bc,color_vertex:wc,common:Rc,cube_uv_reflection_fragment:Cc,defaultnormal_vertex:Pc,displacementmap_pars_vertex:Lc,displacementmap_vertex:Dc,emissivemap_fragment:Uc,emissivemap_pars_fragment:Ic,colorspace_fragment:Nc,colorspace_pars_fragment:Fc,envmap_fragment:Oc,envmap_common_pars_fragment:Bc,envmap_pars_fragment:zc,envmap_pars_vertex:Hc,envmap_physical_pars_fragment:jc,envmap_vertex:Gc,fog_vertex:Vc,fog_pars_vertex:kc,fog_fragment:Wc,fog_pars_fragment:Xc,gradientmap_pars_fragment:qc,lightmap_pars_fragment:Yc,lights_lambert_fragment:Kc,lights_lambert_pars_fragment:Zc,lights_pars_begin:$c,lights_toon_fragment:Jc,lights_toon_pars_fragment:Qc,lights_phong_fragment:eh,lights_phong_pars_fragment:th,lights_physical_fragment:nh,lights_physical_pars_fragment:ih,lights_fragment_begin:rh,lights_fragment_maps:sh,lights_fragment_end:ah,logdepthbuf_fragment:oh,logdepthbuf_pars_fragment:lh,logdepthbuf_pars_vertex:ch,logdepthbuf_vertex:hh,map_fragment:uh,map_pars_fragment:dh,map_particle_fragment:fh,map_particle_pars_fragment:ph,metalnessmap_fragment:mh,metalnessmap_pars_fragment:_h,morphinstance_vertex:gh,morphcolor_vertex:vh,morphnormal_vertex:xh,morphtarget_pars_vertex:Mh,morphtarget_vertex:Sh,normal_fragment_begin:Eh,normal_fragment_maps:yh,normal_pars_fragment:Th,normal_pars_vertex:Ah,normal_vertex:bh,normalmap_pars_fragment:wh,clearcoat_normal_fragment_begin:Rh,clearcoat_normal_fragment_maps:Ch,clearcoat_pars_fragment:Ph,iridescence_pars_fragment:Lh,opaque_fragment:Dh,packing:Uh,premultiplied_alpha_fragment:Ih,project_vertex:Nh,dithering_fragment:Fh,dithering_pars_fragment:Oh,roughnessmap_fragment:Bh,roughnessmap_pars_fragment:zh,shadowmap_pars_fragment:Hh,shadowmap_pars_vertex:Gh,shadowmap_vertex:Vh,shadowmask_pars_fragment:kh,skinbase_vertex:Wh,skinning_pars_vertex:Xh,skinning_vertex:qh,skinnormal_vertex:Yh,specularmap_fragment:Kh,specularmap_pars_fragment:Zh,tonemapping_fragment:$h,tonemapping_pars_fragment:jh,transmission_fragment:Jh,transmission_pars_fragment:Qh,uv_pars_fragment:eu,uv_pars_vertex:tu,uv_vertex:nu,worldpos_vertex:iu,background_vert:ru,background_frag:su,backgroundCube_vert:au,backgroundCube_frag:ou,cube_vert:lu,cube_frag:cu,depth_vert:hu,depth_frag:uu,distanceRGBA_vert:du,distanceRGBA_frag:fu,equirect_vert:pu,equirect_frag:mu,linedashed_vert:_u,linedashed_frag:gu,meshbasic_vert:vu,meshbasic_frag:xu,meshlambert_vert:Mu,meshlambert_frag:Su,meshmatcap_vert:Eu,meshmatcap_frag:yu,meshnormal_vert:Tu,meshnormal_frag:Au,meshphong_vert:bu,meshphong_frag:wu,meshphysical_vert:Ru,meshphysical_frag:Cu,meshtoon_vert:Pu,meshtoon_frag:Lu,points_vert:Du,points_frag:Uu,shadow_vert:Iu,shadow_frag:Nu,sprite_vert:Fu,sprite_frag:Ou},te={common:{diffuse:{value:new Ve(16777215)},opacity:{value:1},map:{value:null},mapTransform:{value:new Ce},alphaMap:{value:null},alphaMapTransform:{value:new Ce},alphaTest:{value:0}},specularmap:{specularMap:{value:null},specularMapTransform:{value:new Ce}},envmap:{envMap:{value:null},envMapRotation:{value:new Ce},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1},aoMapTransform:{value:new Ce}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1},lightMapTransform:{value:new Ce}},bumpmap:{bumpMap:{value:null},bumpMapTransform:{value:new Ce},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalMapTransform:{value:new Ce},normalScale:{value:new He(1,1)}},displacementmap:{displacementMap:{value:null},displacementMapTransform:{value:new Ce},displacementScale:{value:1},displacementBias:{value:0}},emissivemap:{emissiveMap:{value:null},emissiveMapTransform:{value:new Ce}},metalnessmap:{metalnessMap:{value:null},metalnessMapTransform:{value:new Ce}},roughnessmap:{roughnessMap:{value:null},roughnessMapTransform:{value:new Ce}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new Ve(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new Ve(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaMapTransform:{value:new Ce},alphaTest:{value:0},uvTransform:{value:new Ce}},sprite:{diffuse:{value:new Ve(16777215)},opacity:{value:1},center:{value:new He(.5,.5)},rotation:{value:0},map:{value:null},mapTransform:{value:new Ce},alphaMap:{value:null},alphaMapTransform:{value:new Ce},alphaTest:{value:0}}},zt={basic:{uniforms:pt([te.common,te.specularmap,te.envmap,te.aomap,te.lightmap,te.fog]),vertexShader:Le.meshbasic_vert,fragmentShader:Le.meshbasic_frag},lambert:{uniforms:pt([te.common,te.specularmap,te.envmap,te.aomap,te.lightmap,te.emissivemap,te.bumpmap,te.normalmap,te.displacementmap,te.fog,te.lights,{emissive:{value:new Ve(0)}}]),vertexShader:Le.meshlambert_vert,fragmentShader:Le.meshlambert_frag},phong:{uniforms:pt([te.common,te.specularmap,te.envmap,te.aomap,te.lightmap,te.emissivemap,te.bumpmap,te.normalmap,te.displacementmap,te.fog,te.lights,{emissive:{value:new Ve(0)},specular:{value:new Ve(1118481)},shininess:{value:30}}]),vertexShader:Le.meshphong_vert,fragmentShader:Le.meshphong_frag},standard:{uniforms:pt([te.common,te.envmap,te.aomap,te.lightmap,te.emissivemap,te.bumpmap,te.normalmap,te.displacementmap,te.roughnessmap,te.metalnessmap,te.fog,te.lights,{emissive:{value:new Ve(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:Le.meshphysical_vert,fragmentShader:Le.meshphysical_frag},toon:{uniforms:pt([te.common,te.aomap,te.lightmap,te.emissivemap,te.bumpmap,te.normalmap,te.displacementmap,te.gradientmap,te.fog,te.lights,{emissive:{value:new Ve(0)}}]),vertexShader:Le.meshtoon_vert,fragmentShader:Le.meshtoon_frag},matcap:{uniforms:pt([te.common,te.bumpmap,te.normalmap,te.displacementmap,te.fog,{matcap:{value:null}}]),vertexShader:Le.meshmatcap_vert,fragmentShader:Le.meshmatcap_frag},points:{uniforms:pt([te.points,te.fog]),vertexShader:Le.points_vert,fragmentShader:Le.points_frag},dashed:{uniforms:pt([te.common,te.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:Le.linedashed_vert,fragmentShader:Le.linedashed_frag},depth:{uniforms:pt([te.common,te.displacementmap]),vertexShader:Le.depth_vert,fragmentShader:Le.depth_frag},normal:{uniforms:pt([te.common,te.bumpmap,te.normalmap,te.displacementmap,{opacity:{value:1}}]),vertexShader:Le.meshnormal_vert,fragmentShader:Le.meshnormal_frag},sprite:{uniforms:pt([te.sprite,te.fog]),vertexShader:Le.sprite_vert,fragmentShader:Le.sprite_frag},background:{uniforms:{uvTransform:{value:new Ce},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:Le.background_vert,fragmentShader:Le.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1},backgroundRotation:{value:new Ce}},vertexShader:Le.backgroundCube_vert,fragmentShader:Le.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:Le.cube_vert,fragmentShader:Le.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:Le.equirect_vert,fragmentShader:Le.equirect_frag},distanceRGBA:{uniforms:pt([te.common,te.displacementmap,{referencePosition:{value:new I},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:Le.distanceRGBA_vert,fragmentShader:Le.distanceRGBA_frag},shadow:{uniforms:pt([te.lights,te.fog,{color:{value:new Ve(0)},opacity:{value:1}}]),vertexShader:Le.shadow_vert,fragmentShader:Le.shadow_frag}};zt.physical={uniforms:pt([zt.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatMapTransform:{value:new Ce},clearcoatNormalMap:{value:null},clearcoatNormalMapTransform:{value:new Ce},clearcoatNormalScale:{value:new He(1,1)},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatRoughnessMapTransform:{value:new Ce},dispersion:{value:0},iridescence:{value:0},iridescenceMap:{value:null},iridescenceMapTransform:{value:new Ce},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},iridescenceThicknessMapTransform:{value:new Ce},sheen:{value:0},sheenColor:{value:new Ve(0)},sheenColorMap:{value:null},sheenColorMapTransform:{value:new Ce},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},sheenRoughnessMapTransform:{value:new Ce},transmission:{value:0},transmissionMap:{value:null},transmissionMapTransform:{value:new Ce},transmissionSamplerSize:{value:new He},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},thicknessMapTransform:{value:new Ce},attenuationDistance:{value:0},attenuationColor:{value:new Ve(0)},specularColor:{value:new Ve(1,1,1)},specularColorMap:{value:null},specularColorMapTransform:{value:new Ce},specularIntensity:{value:1},specularIntensityMap:{value:null},specularIntensityMapTransform:{value:new Ce},anisotropyVector:{value:new He},anisotropyMap:{value:null},anisotropyMapTransform:{value:new Ce}}]),vertexShader:Le.meshphysical_vert,fragmentShader:Le.meshphysical_frag};const Zi={r:0,b:0,g:0},Tn=new kt,Bu=new et;function zu(i,e,t,n,r,s,a){const o=new Ve(0);let l=s===!0?0:1,h,u,d=null,p=0,m=null;function v(A){let T=A.isScene===!0?A.background:null;return T&&T.isTexture&&(T=(A.backgroundBlurriness>0?t:e).get(T)),T}function M(A){let T=!1;const S=v(A);S===null?c(o,l):S&&S.isColor&&(c(S,1),T=!0);const F=i.xr.getEnvironmentBlendMode();F==="additive"?n.buffers.color.setClear(0,0,0,1,a):F==="alpha-blend"&&n.buffers.color.setClear(0,0,0,0,a),(i.autoClear||T)&&(n.buffers.depth.setTest(!0),n.buffers.depth.setMask(!0),n.buffers.color.setMask(!0),i.clear(i.autoClearColor,i.autoClearDepth,i.autoClearStencil))}function f(A,T){const S=v(T);S&&(S.isCubeTexture||S.mapping===or)?(u===void 0&&(u=new rt(new Ht(1,1,1),new vn({name:"BackgroundCubeMaterial",uniforms:si(zt.backgroundCube.uniforms),vertexShader:zt.backgroundCube.vertexShader,fragmentShader:zt.backgroundCube.fragmentShader,side:vt,depthTest:!1,depthWrite:!1,fog:!1})),u.geometry.deleteAttribute("normal"),u.geometry.deleteAttribute("uv"),u.onBeforeRender=function(F,R,b){this.matrixWorld.copyPosition(b.matrixWorld)},Object.defineProperty(u.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),r.update(u)),Tn.copy(T.backgroundRotation),Tn.x*=-1,Tn.y*=-1,Tn.z*=-1,S.isCubeTexture&&S.isRenderTargetTexture===!1&&(Tn.y*=-1,Tn.z*=-1),u.material.uniforms.envMap.value=S,u.material.uniforms.flipEnvMap.value=S.isCubeTexture&&S.isRenderTargetTexture===!1?-1:1,u.material.uniforms.backgroundBlurriness.value=T.backgroundBlurriness,u.material.uniforms.backgroundIntensity.value=T.backgroundIntensity,u.material.uniforms.backgroundRotation.value.setFromMatrix4(Bu.makeRotationFromEuler(Tn)),u.material.toneMapped=ze.getTransfer(S.colorSpace)!==qe,(d!==S||p!==S.version||m!==i.toneMapping)&&(u.material.needsUpdate=!0,d=S,p=S.version,m=i.toneMapping),u.layers.enableAll(),A.unshift(u,u.geometry,u.material,0,0,null)):S&&S.isTexture&&(h===void 0&&(h=new rt(new bi(2,2),new vn({name:"BackgroundMaterial",uniforms:si(zt.background.uniforms),vertexShader:zt.background.vertexShader,fragmentShader:zt.background.fragmentShader,side:gn,depthTest:!1,depthWrite:!1,fog:!1})),h.geometry.deleteAttribute("normal"),Object.defineProperty(h.material,"map",{get:function(){return this.uniforms.t2D.value}}),r.update(h)),h.material.uniforms.t2D.value=S,h.material.uniforms.backgroundIntensity.value=T.backgroundIntensity,h.material.toneMapped=ze.getTransfer(S.colorSpace)!==qe,S.matrixAutoUpdate===!0&&S.updateMatrix(),h.material.uniforms.uvTransform.value.copy(S.matrix),(d!==S||p!==S.version||m!==i.toneMapping)&&(h.material.needsUpdate=!0,d=S,p=S.version,m=i.toneMapping),h.layers.enableAll(),A.unshift(h,h.geometry,h.material,0,0,null))}function c(A,T){A.getRGB(Zi,wo(i)),n.buffers.color.setClear(Zi.r,Zi.g,Zi.b,T,a)}return{getClearColor:function(){return o},setClearColor:function(A,T=1){o.set(A),l=T,c(o,l)},getClearAlpha:function(){return l},setClearAlpha:function(A){l=A,c(o,l)},render:M,addToRenderList:f}}function Hu(i,e){const t=i.getParameter(i.MAX_VERTEX_ATTRIBS),n={},r=p(null);let s=r,a=!1;function o(x,w,H,z,W){let Z=!1;const k=d(z,H,w);s!==k&&(s=k,h(s.object)),Z=m(x,z,H,W),Z&&v(x,z,H,W),W!==null&&e.update(W,i.ELEMENT_ARRAY_BUFFER),(Z||a)&&(a=!1,S(x,w,H,z),W!==null&&i.bindBuffer(i.ELEMENT_ARRAY_BUFFER,e.get(W).buffer))}function l(){return i.createVertexArray()}function h(x){return i.bindVertexArray(x)}function u(x){return i.deleteVertexArray(x)}function d(x,w,H){const z=H.wireframe===!0;let W=n[x.id];W===void 0&&(W={},n[x.id]=W);let Z=W[w.id];Z===void 0&&(Z={},W[w.id]=Z);let k=Z[z];return k===void 0&&(k=p(l()),Z[z]=k),k}function p(x){const w=[],H=[],z=[];for(let W=0;W=0){const ce=W[V];let xe=Z[V];if(xe===void 0&&(V==="instanceMatrix"&&x.instanceMatrix&&(xe=x.instanceMatrix),V==="instanceColor"&&x.instanceColor&&(xe=x.instanceColor)),ce===void 0||ce.attribute!==xe||xe&&ce.data!==xe.data)return!0;k++}return s.attributesNum!==k||s.index!==z}function v(x,w,H,z){const W={},Z=w.attributes;let k=0;const j=H.getAttributes();for(const V in j)if(j[V].location>=0){let ce=Z[V];ce===void 0&&(V==="instanceMatrix"&&x.instanceMatrix&&(ce=x.instanceMatrix),V==="instanceColor"&&x.instanceColor&&(ce=x.instanceColor));const xe={};xe.attribute=ce,ce&&ce.data&&(xe.data=ce.data),W[V]=xe,k++}s.attributes=W,s.attributesNum=k,s.index=z}function M(){const x=s.newAttributes;for(let w=0,H=x.length;w=0){let ie=W[j];if(ie===void 0&&(j==="instanceMatrix"&&x.instanceMatrix&&(ie=x.instanceMatrix),j==="instanceColor"&&x.instanceColor&&(ie=x.instanceColor)),ie!==void 0){const ce=ie.normalized,xe=ie.itemSize,De=e.get(ie);if(De===void 0)continue;const Ke=De.buffer,q=De.type,ee=De.bytesPerElement,_e=q===i.INT||q===i.UNSIGNED_INT||ie.gpuType===Us;if(ie.isInterleavedBufferAttribute){const re=ie.data,ye=re.stride,be=ie.offset;if(re.isInstancedInterleavedBuffer){for(let Ue=0;Ue0&&i.getShaderPrecisionFormat(i.FRAGMENT_SHADER,i.HIGH_FLOAT).precision>0)return"highp";b="mediump"}return b==="mediump"&&i.getShaderPrecisionFormat(i.VERTEX_SHADER,i.MEDIUM_FLOAT).precision>0&&i.getShaderPrecisionFormat(i.FRAGMENT_SHADER,i.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}let h=t.precision!==void 0?t.precision:"highp";const u=l(h);u!==h&&(console.warn("THREE.WebGLRenderer:",h,"not supported, using",u,"instead."),h=u);const d=t.logarithmicDepthBuffer===!0,p=t.reverseDepthBuffer===!0&&e.has("EXT_clip_control"),m=i.getParameter(i.MAX_TEXTURE_IMAGE_UNITS),v=i.getParameter(i.MAX_VERTEX_TEXTURE_IMAGE_UNITS),M=i.getParameter(i.MAX_TEXTURE_SIZE),f=i.getParameter(i.MAX_CUBE_MAP_TEXTURE_SIZE),c=i.getParameter(i.MAX_VERTEX_ATTRIBS),A=i.getParameter(i.MAX_VERTEX_UNIFORM_VECTORS),T=i.getParameter(i.MAX_VARYING_VECTORS),S=i.getParameter(i.MAX_FRAGMENT_UNIFORM_VECTORS),F=v>0,R=i.getParameter(i.MAX_SAMPLES);return{isWebGL2:!0,getMaxAnisotropy:s,getMaxPrecision:l,textureFormatReadable:a,textureTypeReadable:o,precision:h,logarithmicDepthBuffer:d,reverseDepthBuffer:p,maxTextures:m,maxVertexTextures:v,maxTextureSize:M,maxCubemapSize:f,maxAttributes:c,maxVertexUniforms:A,maxVaryings:T,maxFragmentUniforms:S,vertexTextures:F,maxSamples:R}}function ku(i){const e=this;let t=null,n=0,r=!1,s=!1;const a=new fn,o=new Ce,l={value:null,needsUpdate:!1};this.uniform=l,this.numPlanes=0,this.numIntersection=0,this.init=function(d,p){const m=d.length!==0||p||n!==0||r;return r=p,n=d.length,m},this.beginShadows=function(){s=!0,u(null)},this.endShadows=function(){s=!1},this.setGlobalState=function(d,p){t=u(d,p,0)},this.setState=function(d,p,m){const v=d.clippingPlanes,M=d.clipIntersection,f=d.clipShadows,c=i.get(d);if(!r||v===null||v.length===0||s&&!f)s?u(null):h();else{const A=s?0:n,T=A*4;let S=c.clippingState||null;l.value=S,S=u(v,p,T,m);for(let F=0;F!==T;++F)S[F]=t[F];c.clippingState=S,this.numIntersection=M?this.numPlanes:0,this.numPlanes+=A}};function h(){l.value!==t&&(l.value=t,l.needsUpdate=n>0),e.numPlanes=n,e.numIntersection=0}function u(d,p,m,v){const M=d!==null?d.length:0;let f=null;if(M!==0){if(f=l.value,v!==!0||f===null){const c=m+M*4,A=p.matrixWorldInverse;o.getNormalMatrix(A),(f===null||f.length0){const h=new tc(l.height);return h.fromEquirectangularTexture(i,a),e.set(a,h),a.addEventListener("dispose",r),t(h.texture,a.mapping)}else return null}}return a}function r(a){const o=a.target;o.removeEventListener("dispose",r);const l=e.get(o);l!==void 0&&(e.delete(o),l.dispose())}function s(){e=new WeakMap}return{get:n,dispose:s}}class Lo extends Ro{constructor(e=-1,t=1,n=1,r=-1,s=.1,a=2e3){super(),this.isOrthographicCamera=!0,this.type="OrthographicCamera",this.zoom=1,this.view=null,this.left=e,this.right=t,this.top=n,this.bottom=r,this.near=s,this.far=a,this.updateProjectionMatrix()}copy(e,t){return super.copy(e,t),this.left=e.left,this.right=e.right,this.top=e.top,this.bottom=e.bottom,this.near=e.near,this.far=e.far,this.zoom=e.zoom,this.view=e.view===null?null:Object.assign({},e.view),this}setViewOffset(e,t,n,r,s,a){this.view===null&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=e,this.view.fullHeight=t,this.view.offsetX=n,this.view.offsetY=r,this.view.width=s,this.view.height=a,this.updateProjectionMatrix()}clearViewOffset(){this.view!==null&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const e=(this.right-this.left)/(2*this.zoom),t=(this.top-this.bottom)/(2*this.zoom),n=(this.right+this.left)/2,r=(this.top+this.bottom)/2;let s=n-e,a=n+e,o=r+t,l=r-t;if(this.view!==null&&this.view.enabled){const h=(this.right-this.left)/this.view.fullWidth/this.zoom,u=(this.top-this.bottom)/this.view.fullHeight/this.zoom;s+=h*this.view.offsetX,a=s+h*this.view.width,o-=u*this.view.offsetY,l=o-u*this.view.height}this.projectionMatrix.makeOrthographic(s,a,o,l,this.near,this.far,this.coordinateSystem),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(e){const t=super.toJSON(e);return t.object.zoom=this.zoom,t.object.left=this.left,t.object.right=this.right,t.object.top=this.top,t.object.bottom=this.bottom,t.object.near=this.near,t.object.far=this.far,this.view!==null&&(t.object.view=Object.assign({},this.view)),t}}const Zn=4,ba=[.125,.215,.35,.446,.526,.582],Rn=20,Ir=new Lo,wa=new Ve;let Nr=null,Fr=0,Or=0,Br=!1;const bn=(1+Math.sqrt(5))/2,Kn=1/bn,Ra=[new I(-bn,Kn,0),new I(bn,Kn,0),new I(-Kn,0,bn),new I(Kn,0,bn),new I(0,bn,-Kn),new I(0,bn,Kn),new I(-1,1,-1),new I(1,1,-1),new I(-1,1,1),new I(1,1,1)];class Ca{constructor(e){this._renderer=e,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(e,t=0,n=.1,r=100){Nr=this._renderer.getRenderTarget(),Fr=this._renderer.getActiveCubeFace(),Or=this._renderer.getActiveMipmapLevel(),Br=this._renderer.xr.enabled,this._renderer.xr.enabled=!1,this._setSize(256);const s=this._allocateTargets();return s.depthBuffer=!0,this._sceneToCubeUV(e,n,r,s),t>0&&this._blur(s,0,0,t),this._applyPMREM(s),this._cleanup(s),s}fromEquirectangular(e,t=null){return this._fromTexture(e,t)}fromCubemap(e,t=null){return this._fromTexture(e,t)}compileCubemapShader(){this._cubemapMaterial===null&&(this._cubemapMaterial=Da(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){this._equirectMaterial===null&&(this._equirectMaterial=La(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),this._cubemapMaterial!==null&&this._cubemapMaterial.dispose(),this._equirectMaterial!==null&&this._equirectMaterial.dispose()}_setSize(e){this._lodMax=Math.floor(Math.log2(e)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){this._blurMaterial!==null&&this._blurMaterial.dispose(),this._pingPongRenderTarget!==null&&this._pingPongRenderTarget.dispose();for(let e=0;e2?T:0,T,T),u.setRenderTarget(r),M&&u.render(v,o),u.render(e,o)}v.geometry.dispose(),v.material.dispose(),u.toneMapping=p,u.autoClear=d,e.background=f}_textureToCubeUV(e,t){const n=this._renderer,r=e.mapping===ti||e.mapping===ni;r?(this._cubemapMaterial===null&&(this._cubemapMaterial=Da()),this._cubemapMaterial.uniforms.flipEnvMap.value=e.isRenderTargetTexture===!1?-1:1):this._equirectMaterial===null&&(this._equirectMaterial=La());const s=r?this._cubemapMaterial:this._equirectMaterial,a=new rt(this._lodPlanes[0],s),o=s.uniforms;o.envMap.value=e;const l=this._cubeSize;$i(t,0,0,3*l,2*l),n.setRenderTarget(t),n.render(a,Ir)}_applyPMREM(e){const t=this._renderer,n=t.autoClear;t.autoClear=!1;const r=this._lodPlanes.length;for(let s=1;sRn&&console.warn(`sigmaRadians, ${s}, is too large and will clip, as it requested ${f} samples when the maximum is set to ${Rn}`);const c=[];let A=0;for(let b=0;bT-Zn?r-T+Zn:0),R=4*(this._cubeSize-S);$i(t,F,R,3*S,2*S),l.setRenderTarget(t),l.render(d,Ir)}}function Xu(i){const e=[],t=[],n=[];let r=i;const s=i-Zn+1+ba.length;for(let a=0;ai-Zn?l=ba[a-i+Zn-1]:a===0&&(l=0),n.push(l);const h=1/(o-2),u=-h,d=1+h,p=[u,u,d,u,d,d,u,u,d,d,u,d],m=6,v=6,M=3,f=2,c=1,A=new Float32Array(M*v*m),T=new Float32Array(f*v*m),S=new Float32Array(c*v*m);for(let R=0;R2?0:-1,E=[b,U,0,b+2/3,U,0,b+2/3,U+1,0,b,U,0,b+2/3,U+1,0,b,U+1,0];A.set(E,M*v*R),T.set(p,f*v*R);const x=[R,R,R,R,R,R];S.set(x,c*v*R)}const F=new rn;F.setAttribute("position",new Vt(A,M)),F.setAttribute("uv",new Vt(T,f)),F.setAttribute("faceIndex",new Vt(S,c)),e.push(F),r>Zn&&r--}return{lodPlanes:e,sizeLods:t,sigmas:n}}function Pa(i,e,t){const n=new Dn(i,e,t);return n.texture.mapping=or,n.texture.name="PMREM.cubeUv",n.scissorTest=!0,n}function $i(i,e,t,n,r){i.viewport.set(e,t,n,r),i.scissor.set(e,t,n,r)}function qu(i,e,t){const n=new Float32Array(Rn),r=new I(0,1,0);return new vn({name:"SphericalGaussianBlur",defines:{n:Rn,CUBEUV_TEXEL_WIDTH:1/e,CUBEUV_TEXEL_HEIGHT:1/t,CUBEUV_MAX_MIP:`${i}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:n},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:r}},vertexShader:Vs(),fragmentShader:` + + precision mediump float; + precision mediump int; + + varying vec3 vOutputDirection; + + uniform sampler2D envMap; + uniform int samples; + uniform float weights[ n ]; + uniform bool latitudinal; + uniform float dTheta; + uniform float mipInt; + uniform vec3 poleAxis; + + #define ENVMAP_TYPE_CUBE_UV + #include + + vec3 getSample( float theta, vec3 axis ) { + + float cosTheta = cos( theta ); + // Rodrigues' axis-angle rotation + vec3 sampleDirection = vOutputDirection * cosTheta + + cross( axis, vOutputDirection ) * sin( theta ) + + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); + + return bilinearCubeUV( envMap, sampleDirection, mipInt ); + + } + + void main() { + + vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); + + if ( all( equal( axis, vec3( 0.0 ) ) ) ) { + + axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); + + } + + axis = normalize( axis ); + + gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); + gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); + + for ( int i = 1; i < n; i++ ) { + + if ( i >= samples ) { + + break; + + } + + float theta = dTheta * float( i ); + gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); + gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); + + } + + } + `,blending:mn,depthTest:!1,depthWrite:!1})}function La(){return new vn({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:Vs(),fragmentShader:` + + precision mediump float; + precision mediump int; + + varying vec3 vOutputDirection; + + uniform sampler2D envMap; + + #include + + void main() { + + vec3 outputDirection = normalize( vOutputDirection ); + vec2 uv = equirectUv( outputDirection ); + + gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); + + } + `,blending:mn,depthTest:!1,depthWrite:!1})}function Da(){return new vn({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:Vs(),fragmentShader:` + + precision mediump float; + precision mediump int; + + uniform float flipEnvMap; + + varying vec3 vOutputDirection; + + uniform samplerCube envMap; + + void main() { + + gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); + + } + `,blending:mn,depthTest:!1,depthWrite:!1})}function Vs(){return` + + precision mediump float; + precision mediump int; + + attribute float faceIndex; + + varying vec3 vOutputDirection; + + // RH coordinate system; PMREM face-indexing convention + vec3 getDirection( vec2 uv, float face ) { + + uv = 2.0 * uv - 1.0; + + vec3 direction = vec3( uv, 1.0 ); + + if ( face == 0.0 ) { + + direction = direction.zyx; // ( 1, v, u ) pos x + + } else if ( face == 1.0 ) { + + direction = direction.xzy; + direction.xz *= -1.0; // ( -u, 1, -v ) pos y + + } else if ( face == 2.0 ) { + + direction.x *= -1.0; // ( -u, v, 1 ) pos z + + } else if ( face == 3.0 ) { + + direction = direction.zyx; + direction.xz *= -1.0; // ( -1, v, -u ) neg x + + } else if ( face == 4.0 ) { + + direction = direction.xzy; + direction.xy *= -1.0; // ( -u, -1, v ) neg y + + } else if ( face == 5.0 ) { + + direction.z *= -1.0; // ( u, v, -1 ) neg z + + } + + return direction; + + } + + void main() { + + vOutputDirection = getDirection( uv, faceIndex ); + gl_Position = vec4( position, 1.0 ); + + } + `}function Yu(i){let e=new WeakMap,t=null;function n(o){if(o&&o.isTexture){const l=o.mapping,h=l===Jr||l===Qr,u=l===ti||l===ni;if(h||u){let d=e.get(o);const p=d!==void 0?d.texture.pmremVersion:0;if(o.isRenderTargetTexture&&o.pmremVersion!==p)return t===null&&(t=new Ca(i)),d=h?t.fromEquirectangular(o,d):t.fromCubemap(o,d),d.texture.pmremVersion=o.pmremVersion,e.set(o,d),d.texture;if(d!==void 0)return d.texture;{const m=o.image;return h&&m&&m.height>0||u&&m&&r(m)?(t===null&&(t=new Ca(i)),d=h?t.fromEquirectangular(o):t.fromCubemap(o),d.texture.pmremVersion=o.pmremVersion,e.set(o,d),o.addEventListener("dispose",s),d.texture):null}}}return o}function r(o){let l=0;const h=6;for(let u=0;ue.maxTextureSize&&(R=Math.ceil(F/e.maxTextureSize),F=e.maxTextureSize);const b=new Float32Array(F*R*4*d),U=new So(b,F,R,d);U.type=Qt,U.needsUpdate=!0;const E=S*4;for(let w=0;w0)return i;const r=e*t;let s=Ia[r];if(s===void 0&&(s=new Float32Array(r),Ia[r]=s),e!==0){n.toArray(s,0);for(let a=1,o=0;a!==e;++a)o+=t,i[a].toArray(s,o)}return s}function at(i,e){if(i.length!==e.length)return!1;for(let t=0,n=i.length;t":" "} ${o}: ${t[a]}`)}return n.join(` +`)}const Ga=new Ce;function Yd(i){ze._getMatrix(Ga,ze.workingColorSpace,i);const e=`mat3( ${Ga.elements.map(t=>t.toFixed(4))} )`;switch(ze.getTransfer(i)){case lr:return[e,"LinearTransferOETF"];case qe:return[e,"sRGBTransferOETF"];default:return console.warn("THREE.WebGLProgram: Unsupported color space: ",i),[e,"LinearTransferOETF"]}}function Va(i,e,t){const n=i.getShaderParameter(e,i.COMPILE_STATUS),r=i.getShaderInfoLog(e).trim();if(n&&r==="")return"";const s=/ERROR: 0:(\d+)/.exec(r);if(s){const a=parseInt(s[1]);return t.toUpperCase()+` + +`+r+` + +`+qd(i.getShaderSource(e),a)}else return r}function Kd(i,e){const t=Yd(e);return[`vec4 ${i}( vec4 value ) {`,` return ${t[1]}( vec4( value.rgb * ${t[0]}, value.a ) );`,"}"].join(` +`)}function Zd(i,e){let t;switch(e){case dl:t="Linear";break;case fl:t="Reinhard";break;case pl:t="Cineon";break;case ml:t="ACESFilmic";break;case gl:t="AgX";break;case vl:t="Neutral";break;case _l:t="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",e),t="Linear"}return"vec3 "+i+"( vec3 color ) { return "+t+"ToneMapping( color ); }"}const ji=new I;function $d(){ze.getLuminanceCoefficients(ji);const i=ji.x.toFixed(4),e=ji.y.toFixed(4),t=ji.z.toFixed(4);return["float luminance( const in vec3 rgb ) {",` const vec3 weights = vec3( ${i}, ${e}, ${t} );`," return dot( weights, rgb );","}"].join(` +`)}function jd(i){return[i.extensionClipCullDistance?"#extension GL_ANGLE_clip_cull_distance : require":"",i.extensionMultiDraw?"#extension GL_ANGLE_multi_draw : require":""].filter(vi).join(` +`)}function Jd(i){const e=[];for(const t in i){const n=i[t];n!==!1&&e.push("#define "+t+" "+n)}return e.join(` +`)}function Qd(i,e){const t={},n=i.getProgramParameter(e,i.ACTIVE_ATTRIBUTES);for(let r=0;r/gm;function Cs(i){return i.replace(ef,nf)}const tf=new Map;function nf(i,e){let t=Le[e];if(t===void 0){const n=tf.get(e);if(n!==void 0)t=Le[n],console.warn('THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.',e,n);else throw new Error("Can not resolve #include <"+e+">")}return Cs(t)}const rf=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function Xa(i){return i.replace(rf,sf)}function sf(i,e,t,n){let r="";for(let s=parseInt(e);s0&&(f+=` +`),c=["#define SHADER_TYPE "+t.shaderType,"#define SHADER_NAME "+t.shaderName,v].filter(vi).join(` +`),c.length>0&&(c+=` +`)):(f=[qa(t),"#define SHADER_TYPE "+t.shaderType,"#define SHADER_NAME "+t.shaderName,v,t.extensionClipCullDistance?"#define USE_CLIP_DISTANCE":"",t.batching?"#define USE_BATCHING":"",t.batchingColor?"#define USE_BATCHING_COLOR":"",t.instancing?"#define USE_INSTANCING":"",t.instancingColor?"#define USE_INSTANCING_COLOR":"",t.instancingMorph?"#define USE_INSTANCING_MORPH":"",t.useFog&&t.fog?"#define USE_FOG":"",t.useFog&&t.fogExp2?"#define FOG_EXP2":"",t.map?"#define USE_MAP":"",t.envMap?"#define USE_ENVMAP":"",t.envMap?"#define "+u:"",t.lightMap?"#define USE_LIGHTMAP":"",t.aoMap?"#define USE_AOMAP":"",t.bumpMap?"#define USE_BUMPMAP":"",t.normalMap?"#define USE_NORMALMAP":"",t.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",t.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",t.displacementMap?"#define USE_DISPLACEMENTMAP":"",t.emissiveMap?"#define USE_EMISSIVEMAP":"",t.anisotropy?"#define USE_ANISOTROPY":"",t.anisotropyMap?"#define USE_ANISOTROPYMAP":"",t.clearcoatMap?"#define USE_CLEARCOATMAP":"",t.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",t.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",t.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",t.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",t.specularMap?"#define USE_SPECULARMAP":"",t.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",t.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",t.roughnessMap?"#define USE_ROUGHNESSMAP":"",t.metalnessMap?"#define USE_METALNESSMAP":"",t.alphaMap?"#define USE_ALPHAMAP":"",t.alphaHash?"#define USE_ALPHAHASH":"",t.transmission?"#define USE_TRANSMISSION":"",t.transmissionMap?"#define USE_TRANSMISSIONMAP":"",t.thicknessMap?"#define USE_THICKNESSMAP":"",t.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",t.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",t.mapUv?"#define MAP_UV "+t.mapUv:"",t.alphaMapUv?"#define ALPHAMAP_UV "+t.alphaMapUv:"",t.lightMapUv?"#define LIGHTMAP_UV "+t.lightMapUv:"",t.aoMapUv?"#define AOMAP_UV "+t.aoMapUv:"",t.emissiveMapUv?"#define EMISSIVEMAP_UV "+t.emissiveMapUv:"",t.bumpMapUv?"#define BUMPMAP_UV "+t.bumpMapUv:"",t.normalMapUv?"#define NORMALMAP_UV "+t.normalMapUv:"",t.displacementMapUv?"#define DISPLACEMENTMAP_UV "+t.displacementMapUv:"",t.metalnessMapUv?"#define METALNESSMAP_UV "+t.metalnessMapUv:"",t.roughnessMapUv?"#define ROUGHNESSMAP_UV "+t.roughnessMapUv:"",t.anisotropyMapUv?"#define ANISOTROPYMAP_UV "+t.anisotropyMapUv:"",t.clearcoatMapUv?"#define CLEARCOATMAP_UV "+t.clearcoatMapUv:"",t.clearcoatNormalMapUv?"#define CLEARCOAT_NORMALMAP_UV "+t.clearcoatNormalMapUv:"",t.clearcoatRoughnessMapUv?"#define CLEARCOAT_ROUGHNESSMAP_UV "+t.clearcoatRoughnessMapUv:"",t.iridescenceMapUv?"#define IRIDESCENCEMAP_UV "+t.iridescenceMapUv:"",t.iridescenceThicknessMapUv?"#define IRIDESCENCE_THICKNESSMAP_UV "+t.iridescenceThicknessMapUv:"",t.sheenColorMapUv?"#define SHEEN_COLORMAP_UV "+t.sheenColorMapUv:"",t.sheenRoughnessMapUv?"#define SHEEN_ROUGHNESSMAP_UV "+t.sheenRoughnessMapUv:"",t.specularMapUv?"#define SPECULARMAP_UV "+t.specularMapUv:"",t.specularColorMapUv?"#define SPECULAR_COLORMAP_UV "+t.specularColorMapUv:"",t.specularIntensityMapUv?"#define SPECULAR_INTENSITYMAP_UV "+t.specularIntensityMapUv:"",t.transmissionMapUv?"#define TRANSMISSIONMAP_UV "+t.transmissionMapUv:"",t.thicknessMapUv?"#define THICKNESSMAP_UV "+t.thicknessMapUv:"",t.vertexTangents&&t.flatShading===!1?"#define USE_TANGENT":"",t.vertexColors?"#define USE_COLOR":"",t.vertexAlphas?"#define USE_COLOR_ALPHA":"",t.vertexUv1s?"#define USE_UV1":"",t.vertexUv2s?"#define USE_UV2":"",t.vertexUv3s?"#define USE_UV3":"",t.pointsUvs?"#define USE_POINTS_UV":"",t.flatShading?"#define FLAT_SHADED":"",t.skinning?"#define USE_SKINNING":"",t.morphTargets?"#define USE_MORPHTARGETS":"",t.morphNormals&&t.flatShading===!1?"#define USE_MORPHNORMALS":"",t.morphColors?"#define USE_MORPHCOLORS":"",t.morphTargetsCount>0?"#define MORPHTARGETS_TEXTURE_STRIDE "+t.morphTextureStride:"",t.morphTargetsCount>0?"#define MORPHTARGETS_COUNT "+t.morphTargetsCount:"",t.doubleSided?"#define DOUBLE_SIDED":"",t.flipSided?"#define FLIP_SIDED":"",t.shadowMapEnabled?"#define USE_SHADOWMAP":"",t.shadowMapEnabled?"#define "+l:"",t.sizeAttenuation?"#define USE_SIZEATTENUATION":"",t.numLightProbes>0?"#define USE_LIGHT_PROBES":"",t.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",t.reverseDepthBuffer?"#define USE_REVERSEDEPTHBUF":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING"," attribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR"," attribute vec3 instanceColor;","#endif","#ifdef USE_INSTANCING_MORPH"," uniform sampler2D morphTexture;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_UV1"," attribute vec2 uv1;","#endif","#ifdef USE_UV2"," attribute vec2 uv2;","#endif","#ifdef USE_UV3"," attribute vec2 uv3;","#endif","#ifdef USE_TANGENT"," attribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )"," attribute vec4 color;","#elif defined( USE_COLOR )"," attribute vec3 color;","#endif","#ifdef USE_SKINNING"," attribute vec4 skinIndex;"," attribute vec4 skinWeight;","#endif",` +`].filter(vi).join(` +`),c=[qa(t),"#define SHADER_TYPE "+t.shaderType,"#define SHADER_NAME "+t.shaderName,v,t.useFog&&t.fog?"#define USE_FOG":"",t.useFog&&t.fogExp2?"#define FOG_EXP2":"",t.alphaToCoverage?"#define ALPHA_TO_COVERAGE":"",t.map?"#define USE_MAP":"",t.matcap?"#define USE_MATCAP":"",t.envMap?"#define USE_ENVMAP":"",t.envMap?"#define "+h:"",t.envMap?"#define "+u:"",t.envMap?"#define "+d:"",p?"#define CUBEUV_TEXEL_WIDTH "+p.texelWidth:"",p?"#define CUBEUV_TEXEL_HEIGHT "+p.texelHeight:"",p?"#define CUBEUV_MAX_MIP "+p.maxMip+".0":"",t.lightMap?"#define USE_LIGHTMAP":"",t.aoMap?"#define USE_AOMAP":"",t.bumpMap?"#define USE_BUMPMAP":"",t.normalMap?"#define USE_NORMALMAP":"",t.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",t.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",t.emissiveMap?"#define USE_EMISSIVEMAP":"",t.anisotropy?"#define USE_ANISOTROPY":"",t.anisotropyMap?"#define USE_ANISOTROPYMAP":"",t.clearcoat?"#define USE_CLEARCOAT":"",t.clearcoatMap?"#define USE_CLEARCOATMAP":"",t.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",t.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",t.dispersion?"#define USE_DISPERSION":"",t.iridescence?"#define USE_IRIDESCENCE":"",t.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",t.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",t.specularMap?"#define USE_SPECULARMAP":"",t.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",t.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",t.roughnessMap?"#define USE_ROUGHNESSMAP":"",t.metalnessMap?"#define USE_METALNESSMAP":"",t.alphaMap?"#define USE_ALPHAMAP":"",t.alphaTest?"#define USE_ALPHATEST":"",t.alphaHash?"#define USE_ALPHAHASH":"",t.sheen?"#define USE_SHEEN":"",t.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",t.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",t.transmission?"#define USE_TRANSMISSION":"",t.transmissionMap?"#define USE_TRANSMISSIONMAP":"",t.thicknessMap?"#define USE_THICKNESSMAP":"",t.vertexTangents&&t.flatShading===!1?"#define USE_TANGENT":"",t.vertexColors||t.instancingColor||t.batchingColor?"#define USE_COLOR":"",t.vertexAlphas?"#define USE_COLOR_ALPHA":"",t.vertexUv1s?"#define USE_UV1":"",t.vertexUv2s?"#define USE_UV2":"",t.vertexUv3s?"#define USE_UV3":"",t.pointsUvs?"#define USE_POINTS_UV":"",t.gradientMap?"#define USE_GRADIENTMAP":"",t.flatShading?"#define FLAT_SHADED":"",t.doubleSided?"#define DOUBLE_SIDED":"",t.flipSided?"#define FLIP_SIDED":"",t.shadowMapEnabled?"#define USE_SHADOWMAP":"",t.shadowMapEnabled?"#define "+l:"",t.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",t.numLightProbes>0?"#define USE_LIGHT_PROBES":"",t.decodeVideoTexture?"#define DECODE_VIDEO_TEXTURE":"",t.decodeVideoTextureEmissive?"#define DECODE_VIDEO_TEXTURE_EMISSIVE":"",t.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",t.reverseDepthBuffer?"#define USE_REVERSEDEPTHBUF":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",t.toneMapping!==_n?"#define TONE_MAPPING":"",t.toneMapping!==_n?Le.tonemapping_pars_fragment:"",t.toneMapping!==_n?Zd("toneMapping",t.toneMapping):"",t.dithering?"#define DITHERING":"",t.opaque?"#define OPAQUE":"",Le.colorspace_pars_fragment,Kd("linearToOutputTexel",t.outputColorSpace),$d(),t.useDepthPacking?"#define DEPTH_PACKING "+t.depthPacking:"",` +`].filter(vi).join(` +`)),a=Cs(a),a=ka(a,t),a=Wa(a,t),o=Cs(o),o=ka(o,t),o=Wa(o,t),a=Xa(a),o=Xa(o),t.isRawShaderMaterial!==!0&&(A=`#version 300 es +`,f=[m,"#define attribute in","#define varying out","#define texture2D texture"].join(` +`)+` +`+f,c=["#define varying in",t.glslVersion===sa?"":"layout(location = 0) out highp vec4 pc_fragColor;",t.glslVersion===sa?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join(` +`)+` +`+c);const T=A+f+a,S=A+c+o,F=Ha(r,r.VERTEX_SHADER,T),R=Ha(r,r.FRAGMENT_SHADER,S);r.attachShader(M,F),r.attachShader(M,R),t.index0AttributeName!==void 0?r.bindAttribLocation(M,0,t.index0AttributeName):t.morphTargets===!0&&r.bindAttribLocation(M,0,"position"),r.linkProgram(M);function b(w){if(i.debug.checkShaderErrors){const H=r.getProgramInfoLog(M).trim(),z=r.getShaderInfoLog(F).trim(),W=r.getShaderInfoLog(R).trim();let Z=!0,k=!0;if(r.getProgramParameter(M,r.LINK_STATUS)===!1)if(Z=!1,typeof i.debug.onShaderError=="function")i.debug.onShaderError(r,M,F,R);else{const j=Va(r,F,"vertex"),V=Va(r,R,"fragment");console.error("THREE.WebGLProgram: Shader Error "+r.getError()+" - VALIDATE_STATUS "+r.getProgramParameter(M,r.VALIDATE_STATUS)+` + +Material Name: `+w.name+` +Material Type: `+w.type+` + +Program Info Log: `+H+` +`+j+` +`+V)}else H!==""?console.warn("THREE.WebGLProgram: Program Info Log:",H):(z===""||W==="")&&(k=!1);k&&(w.diagnostics={runnable:Z,programLog:H,vertexShader:{log:z,prefix:f},fragmentShader:{log:W,prefix:c}})}r.deleteShader(F),r.deleteShader(R),U=new ir(r,M),E=Qd(r,M)}let U;this.getUniforms=function(){return U===void 0&&b(this),U};let E;this.getAttributes=function(){return E===void 0&&b(this),E};let x=t.rendererExtensionParallelShaderCompile===!1;return this.isReady=function(){return x===!1&&(x=r.getProgramParameter(M,Wd)),x},this.destroy=function(){n.releaseStatesOfProgram(this),r.deleteProgram(M),this.program=void 0},this.type=t.shaderType,this.name=t.shaderName,this.id=Xd++,this.cacheKey=e,this.usedTimes=1,this.program=M,this.vertexShader=F,this.fragmentShader=R,this}let df=0;class ff{constructor(){this.shaderCache=new Map,this.materialCache=new Map}update(e){const t=e.vertexShader,n=e.fragmentShader,r=this._getShaderStage(t),s=this._getShaderStage(n),a=this._getShaderCacheForMaterial(e);return a.has(r)===!1&&(a.add(r),r.usedTimes++),a.has(s)===!1&&(a.add(s),s.usedTimes++),this}remove(e){const t=this.materialCache.get(e);for(const n of t)n.usedTimes--,n.usedTimes===0&&this.shaderCache.delete(n.code);return this.materialCache.delete(e),this}getVertexShaderID(e){return this._getShaderStage(e.vertexShader).id}getFragmentShaderID(e){return this._getShaderStage(e.fragmentShader).id}dispose(){this.shaderCache.clear(),this.materialCache.clear()}_getShaderCacheForMaterial(e){const t=this.materialCache;let n=t.get(e);return n===void 0&&(n=new Set,t.set(e,n)),n}_getShaderStage(e){const t=this.shaderCache;let n=t.get(e);return n===void 0&&(n=new pf(e),t.set(e,n)),n}}class pf{constructor(e){this.id=df++,this.code=e,this.usedTimes=0}}function mf(i,e,t,n,r,s,a){const o=new Hs,l=new ff,h=new Set,u=[],d=r.logarithmicDepthBuffer,p=r.vertexTextures;let m=r.precision;const v={MeshDepthMaterial:"depth",MeshDistanceMaterial:"distanceRGBA",MeshNormalMaterial:"normal",MeshBasicMaterial:"basic",MeshLambertMaterial:"lambert",MeshPhongMaterial:"phong",MeshToonMaterial:"toon",MeshStandardMaterial:"physical",MeshPhysicalMaterial:"physical",MeshMatcapMaterial:"matcap",LineBasicMaterial:"basic",LineDashedMaterial:"dashed",PointsMaterial:"points",ShadowMaterial:"shadow",SpriteMaterial:"sprite"};function M(E){return h.add(E),E===0?"uv":`uv${E}`}function f(E,x,w,H,z){const W=H.fog,Z=z.geometry,k=E.isMeshStandardMaterial?H.environment:null,j=(E.isMeshStandardMaterial?t:e).get(E.envMap||k),V=j&&j.mapping===or?j.image.height:null,ie=v[E.type];E.precision!==null&&(m=r.getMaxPrecision(E.precision),m!==E.precision&&console.warn("THREE.WebGLProgram.getParameters:",E.precision,"not supported, using",m,"instead."));const ce=Z.morphAttributes.position||Z.morphAttributes.normal||Z.morphAttributes.color,xe=ce!==void 0?ce.length:0;let De=0;Z.morphAttributes.position!==void 0&&(De=1),Z.morphAttributes.normal!==void 0&&(De=2),Z.morphAttributes.color!==void 0&&(De=3);let Ke,q,ee,_e;if(ie){const Xe=zt[ie];Ke=Xe.vertexShader,q=Xe.fragmentShader}else Ke=E.vertexShader,q=E.fragmentShader,l.update(E),ee=l.getVertexShaderID(E),_e=l.getFragmentShaderID(E);const re=i.getRenderTarget(),ye=i.state.buffers.depth.getReversed(),be=z.isInstancedMesh===!0,Ue=z.isBatchedMesh===!0,tt=!!E.map,Oe=!!E.matcap,it=!!j,D=!!E.aoMap,At=!!E.lightMap,Ie=!!E.bumpMap,Ne=!!E.normalMap,Se=!!E.displacementMap,je=!!E.emissiveMap,Me=!!E.metalnessMap,y=!!E.roughnessMap,_=E.anisotropy>0,N=E.clearcoat>0,Y=E.dispersion>0,$=E.iridescence>0,X=E.sheen>0,ge=E.transmission>0,se=_&&!!E.anisotropyMap,he=N&&!!E.clearcoatMap,Be=N&&!!E.clearcoatNormalMap,J=N&&!!E.clearcoatRoughnessMap,ue=$&&!!E.iridescenceMap,Ee=$&&!!E.iridescenceThicknessMap,Te=X&&!!E.sheenColorMap,de=X&&!!E.sheenRoughnessMap,Fe=!!E.specularMap,Pe=!!E.specularColorMap,Ze=!!E.specularIntensityMap,C=ge&&!!E.transmissionMap,ne=ge&&!!E.thicknessMap,G=!!E.gradientMap,K=!!E.alphaMap,le=E.alphaTest>0,ae=!!E.alphaHash,we=!!E.extensions;let nt=_n;E.toneMapped&&(re===null||re.isXRRenderTarget===!0)&&(nt=i.toneMapping);const ct={shaderID:ie,shaderType:E.type,shaderName:E.name,vertexShader:Ke,fragmentShader:q,defines:E.defines,customVertexShaderID:ee,customFragmentShaderID:_e,isRawShaderMaterial:E.isRawShaderMaterial===!0,glslVersion:E.glslVersion,precision:m,batching:Ue,batchingColor:Ue&&z._colorsTexture!==null,instancing:be,instancingColor:be&&z.instanceColor!==null,instancingMorph:be&&z.morphTexture!==null,supportsVertexTextures:p,outputColorSpace:re===null?i.outputColorSpace:re.isXRRenderTarget===!0?re.texture.colorSpace:ai,alphaToCoverage:!!E.alphaToCoverage,map:tt,matcap:Oe,envMap:it,envMapMode:it&&j.mapping,envMapCubeUVHeight:V,aoMap:D,lightMap:At,bumpMap:Ie,normalMap:Ne,displacementMap:p&&Se,emissiveMap:je,normalMapObjectSpace:Ne&&E.normalMapType===El,normalMapTangentSpace:Ne&&E.normalMapType===go,metalnessMap:Me,roughnessMap:y,anisotropy:_,anisotropyMap:se,clearcoat:N,clearcoatMap:he,clearcoatNormalMap:Be,clearcoatRoughnessMap:J,dispersion:Y,iridescence:$,iridescenceMap:ue,iridescenceThicknessMap:Ee,sheen:X,sheenColorMap:Te,sheenRoughnessMap:de,specularMap:Fe,specularColorMap:Pe,specularIntensityMap:Ze,transmission:ge,transmissionMap:C,thicknessMap:ne,gradientMap:G,opaque:E.transparent===!1&&E.blending===jn&&E.alphaToCoverage===!1,alphaMap:K,alphaTest:le,alphaHash:ae,combine:E.combine,mapUv:tt&&M(E.map.channel),aoMapUv:D&&M(E.aoMap.channel),lightMapUv:At&&M(E.lightMap.channel),bumpMapUv:Ie&&M(E.bumpMap.channel),normalMapUv:Ne&&M(E.normalMap.channel),displacementMapUv:Se&&M(E.displacementMap.channel),emissiveMapUv:je&&M(E.emissiveMap.channel),metalnessMapUv:Me&&M(E.metalnessMap.channel),roughnessMapUv:y&&M(E.roughnessMap.channel),anisotropyMapUv:se&&M(E.anisotropyMap.channel),clearcoatMapUv:he&&M(E.clearcoatMap.channel),clearcoatNormalMapUv:Be&&M(E.clearcoatNormalMap.channel),clearcoatRoughnessMapUv:J&&M(E.clearcoatRoughnessMap.channel),iridescenceMapUv:ue&&M(E.iridescenceMap.channel),iridescenceThicknessMapUv:Ee&&M(E.iridescenceThicknessMap.channel),sheenColorMapUv:Te&&M(E.sheenColorMap.channel),sheenRoughnessMapUv:de&&M(E.sheenRoughnessMap.channel),specularMapUv:Fe&&M(E.specularMap.channel),specularColorMapUv:Pe&&M(E.specularColorMap.channel),specularIntensityMapUv:Ze&&M(E.specularIntensityMap.channel),transmissionMapUv:C&&M(E.transmissionMap.channel),thicknessMapUv:ne&&M(E.thicknessMap.channel),alphaMapUv:K&&M(E.alphaMap.channel),vertexTangents:!!Z.attributes.tangent&&(Ne||_),vertexColors:E.vertexColors,vertexAlphas:E.vertexColors===!0&&!!Z.attributes.color&&Z.attributes.color.itemSize===4,pointsUvs:z.isPoints===!0&&!!Z.attributes.uv&&(tt||K),fog:!!W,useFog:E.fog===!0,fogExp2:!!W&&W.isFogExp2,flatShading:E.flatShading===!0,sizeAttenuation:E.sizeAttenuation===!0,logarithmicDepthBuffer:d,reverseDepthBuffer:ye,skinning:z.isSkinnedMesh===!0,morphTargets:Z.morphAttributes.position!==void 0,morphNormals:Z.morphAttributes.normal!==void 0,morphColors:Z.morphAttributes.color!==void 0,morphTargetsCount:xe,morphTextureStride:De,numDirLights:x.directional.length,numPointLights:x.point.length,numSpotLights:x.spot.length,numSpotLightMaps:x.spotLightMap.length,numRectAreaLights:x.rectArea.length,numHemiLights:x.hemi.length,numDirLightShadows:x.directionalShadowMap.length,numPointLightShadows:x.pointShadowMap.length,numSpotLightShadows:x.spotShadowMap.length,numSpotLightShadowsWithMaps:x.numSpotLightShadowsWithMaps,numLightProbes:x.numLightProbes,numClippingPlanes:a.numPlanes,numClipIntersection:a.numIntersection,dithering:E.dithering,shadowMapEnabled:i.shadowMap.enabled&&w.length>0,shadowMapType:i.shadowMap.type,toneMapping:nt,decodeVideoTexture:tt&&E.map.isVideoTexture===!0&&ze.getTransfer(E.map.colorSpace)===qe,decodeVideoTextureEmissive:je&&E.emissiveMap.isVideoTexture===!0&&ze.getTransfer(E.emissiveMap.colorSpace)===qe,premultipliedAlpha:E.premultipliedAlpha,doubleSided:E.side===Jt,flipSided:E.side===vt,useDepthPacking:E.depthPacking>=0,depthPacking:E.depthPacking||0,index0AttributeName:E.index0AttributeName,extensionClipCullDistance:we&&E.extensions.clipCullDistance===!0&&n.has("WEBGL_clip_cull_distance"),extensionMultiDraw:(we&&E.extensions.multiDraw===!0||Ue)&&n.has("WEBGL_multi_draw"),rendererExtensionParallelShaderCompile:n.has("KHR_parallel_shader_compile"),customProgramCacheKey:E.customProgramCacheKey()};return ct.vertexUv1s=h.has(1),ct.vertexUv2s=h.has(2),ct.vertexUv3s=h.has(3),h.clear(),ct}function c(E){const x=[];if(E.shaderID?x.push(E.shaderID):(x.push(E.customVertexShaderID),x.push(E.customFragmentShaderID)),E.defines!==void 0)for(const w in E.defines)x.push(w),x.push(E.defines[w]);return E.isRawShaderMaterial===!1&&(A(x,E),T(x,E),x.push(i.outputColorSpace)),x.push(E.customProgramCacheKey),x.join()}function A(E,x){E.push(x.precision),E.push(x.outputColorSpace),E.push(x.envMapMode),E.push(x.envMapCubeUVHeight),E.push(x.mapUv),E.push(x.alphaMapUv),E.push(x.lightMapUv),E.push(x.aoMapUv),E.push(x.bumpMapUv),E.push(x.normalMapUv),E.push(x.displacementMapUv),E.push(x.emissiveMapUv),E.push(x.metalnessMapUv),E.push(x.roughnessMapUv),E.push(x.anisotropyMapUv),E.push(x.clearcoatMapUv),E.push(x.clearcoatNormalMapUv),E.push(x.clearcoatRoughnessMapUv),E.push(x.iridescenceMapUv),E.push(x.iridescenceThicknessMapUv),E.push(x.sheenColorMapUv),E.push(x.sheenRoughnessMapUv),E.push(x.specularMapUv),E.push(x.specularColorMapUv),E.push(x.specularIntensityMapUv),E.push(x.transmissionMapUv),E.push(x.thicknessMapUv),E.push(x.combine),E.push(x.fogExp2),E.push(x.sizeAttenuation),E.push(x.morphTargetsCount),E.push(x.morphAttributeCount),E.push(x.numDirLights),E.push(x.numPointLights),E.push(x.numSpotLights),E.push(x.numSpotLightMaps),E.push(x.numHemiLights),E.push(x.numRectAreaLights),E.push(x.numDirLightShadows),E.push(x.numPointLightShadows),E.push(x.numSpotLightShadows),E.push(x.numSpotLightShadowsWithMaps),E.push(x.numLightProbes),E.push(x.shadowMapType),E.push(x.toneMapping),E.push(x.numClippingPlanes),E.push(x.numClipIntersection),E.push(x.depthPacking)}function T(E,x){o.disableAll(),x.supportsVertexTextures&&o.enable(0),x.instancing&&o.enable(1),x.instancingColor&&o.enable(2),x.instancingMorph&&o.enable(3),x.matcap&&o.enable(4),x.envMap&&o.enable(5),x.normalMapObjectSpace&&o.enable(6),x.normalMapTangentSpace&&o.enable(7),x.clearcoat&&o.enable(8),x.iridescence&&o.enable(9),x.alphaTest&&o.enable(10),x.vertexColors&&o.enable(11),x.vertexAlphas&&o.enable(12),x.vertexUv1s&&o.enable(13),x.vertexUv2s&&o.enable(14),x.vertexUv3s&&o.enable(15),x.vertexTangents&&o.enable(16),x.anisotropy&&o.enable(17),x.alphaHash&&o.enable(18),x.batching&&o.enable(19),x.dispersion&&o.enable(20),x.batchingColor&&o.enable(21),E.push(o.mask),o.disableAll(),x.fog&&o.enable(0),x.useFog&&o.enable(1),x.flatShading&&o.enable(2),x.logarithmicDepthBuffer&&o.enable(3),x.reverseDepthBuffer&&o.enable(4),x.skinning&&o.enable(5),x.morphTargets&&o.enable(6),x.morphNormals&&o.enable(7),x.morphColors&&o.enable(8),x.premultipliedAlpha&&o.enable(9),x.shadowMapEnabled&&o.enable(10),x.doubleSided&&o.enable(11),x.flipSided&&o.enable(12),x.useDepthPacking&&o.enable(13),x.dithering&&o.enable(14),x.transmission&&o.enable(15),x.sheen&&o.enable(16),x.opaque&&o.enable(17),x.pointsUvs&&o.enable(18),x.decodeVideoTexture&&o.enable(19),x.decodeVideoTextureEmissive&&o.enable(20),x.alphaToCoverage&&o.enable(21),E.push(o.mask)}function S(E){const x=v[E.type];let w;if(x){const H=zt[x];w=jl.clone(H.uniforms)}else w=E.uniforms;return w}function F(E,x){let w;for(let H=0,z=u.length;H0?n.push(c):m.transparent===!0?r.push(c):t.push(c)}function l(d,p,m,v,M,f){const c=a(d,p,m,v,M,f);m.transmission>0?n.unshift(c):m.transparent===!0?r.unshift(c):t.unshift(c)}function h(d,p){t.length>1&&t.sort(d||gf),n.length>1&&n.sort(p||Ya),r.length>1&&r.sort(p||Ya)}function u(){for(let d=e,p=i.length;d=s.length?(a=new Ka,s.push(a)):a=s[r],a}function t(){i=new WeakMap}return{get:e,dispose:t}}function xf(){const i={};return{get:function(e){if(i[e.id]!==void 0)return i[e.id];let t;switch(e.type){case"DirectionalLight":t={direction:new I,color:new Ve};break;case"SpotLight":t={position:new I,direction:new I,color:new Ve,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":t={position:new I,color:new Ve,distance:0,decay:0};break;case"HemisphereLight":t={direction:new I,skyColor:new Ve,groundColor:new Ve};break;case"RectAreaLight":t={color:new Ve,position:new I,halfWidth:new I,halfHeight:new I};break}return i[e.id]=t,t}}}function Mf(){const i={};return{get:function(e){if(i[e.id]!==void 0)return i[e.id];let t;switch(e.type){case"DirectionalLight":t={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new He};break;case"SpotLight":t={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new He};break;case"PointLight":t={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new He,shadowCameraNear:1,shadowCameraFar:1e3};break}return i[e.id]=t,t}}}let Sf=0;function Ef(i,e){return(e.castShadow?2:0)-(i.castShadow?2:0)+(e.map?1:0)-(i.map?1:0)}function yf(i){const e=new xf,t=Mf(),n={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1,numLightProbes:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0,numLightProbes:0};for(let h=0;h<9;h++)n.probe.push(new I);const r=new I,s=new et,a=new et;function o(h){let u=0,d=0,p=0;for(let E=0;E<9;E++)n.probe[E].set(0,0,0);let m=0,v=0,M=0,f=0,c=0,A=0,T=0,S=0,F=0,R=0,b=0;h.sort(Ef);for(let E=0,x=h.length;E0&&(i.has("OES_texture_float_linear")===!0?(n.rectAreaLTC1=te.LTC_FLOAT_1,n.rectAreaLTC2=te.LTC_FLOAT_2):(n.rectAreaLTC1=te.LTC_HALF_1,n.rectAreaLTC2=te.LTC_HALF_2)),n.ambient[0]=u,n.ambient[1]=d,n.ambient[2]=p;const U=n.hash;(U.directionalLength!==m||U.pointLength!==v||U.spotLength!==M||U.rectAreaLength!==f||U.hemiLength!==c||U.numDirectionalShadows!==A||U.numPointShadows!==T||U.numSpotShadows!==S||U.numSpotMaps!==F||U.numLightProbes!==b)&&(n.directional.length=m,n.spot.length=M,n.rectArea.length=f,n.point.length=v,n.hemi.length=c,n.directionalShadow.length=A,n.directionalShadowMap.length=A,n.pointShadow.length=T,n.pointShadowMap.length=T,n.spotShadow.length=S,n.spotShadowMap.length=S,n.directionalShadowMatrix.length=A,n.pointShadowMatrix.length=T,n.spotLightMatrix.length=S+F-R,n.spotLightMap.length=F,n.numSpotLightShadowsWithMaps=R,n.numLightProbes=b,U.directionalLength=m,U.pointLength=v,U.spotLength=M,U.rectAreaLength=f,U.hemiLength=c,U.numDirectionalShadows=A,U.numPointShadows=T,U.numSpotShadows=S,U.numSpotMaps=F,U.numLightProbes=b,n.version=Sf++)}function l(h,u){let d=0,p=0,m=0,v=0,M=0;const f=u.matrixWorldInverse;for(let c=0,A=h.length;c=a.length?(o=new Za(i),a.push(o)):o=a[s],o}function n(){e=new WeakMap}return{get:t,dispose:n}}class Af extends Ai{static get type(){return"MeshDepthMaterial"}constructor(e){super(),this.isMeshDepthMaterial=!0,this.depthPacking=Ml,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(e)}copy(e){return super.copy(e),this.depthPacking=e.depthPacking,this.map=e.map,this.alphaMap=e.alphaMap,this.displacementMap=e.displacementMap,this.displacementScale=e.displacementScale,this.displacementBias=e.displacementBias,this.wireframe=e.wireframe,this.wireframeLinewidth=e.wireframeLinewidth,this}}class bf extends Ai{static get type(){return"MeshDistanceMaterial"}constructor(e){super(),this.isMeshDistanceMaterial=!0,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(e)}copy(e){return super.copy(e),this.map=e.map,this.alphaMap=e.alphaMap,this.displacementMap=e.displacementMap,this.displacementScale=e.displacementScale,this.displacementBias=e.displacementBias,this}}const wf=`void main() { + gl_Position = vec4( position, 1.0 ); +}`,Rf=`uniform sampler2D shadow_pass; +uniform vec2 resolution; +uniform float radius; +#include +void main() { + const float samples = float( VSM_SAMPLES ); + float mean = 0.0; + float squared_mean = 0.0; + float uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 ); + float uvStart = samples <= 1.0 ? 0.0 : - 1.0; + for ( float i = 0.0; i < samples; i ++ ) { + float uvOffset = uvStart + i * uvStride; + #ifdef HORIZONTAL_PASS + vec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) ); + mean += distribution.x; + squared_mean += distribution.y * distribution.y + distribution.x * distribution.x; + #else + float depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) ); + mean += depth; + squared_mean += depth * depth; + #endif + } + mean = mean / samples; + squared_mean = squared_mean / samples; + float std_dev = sqrt( squared_mean - mean * mean ); + gl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) ); +}`;function Cf(i,e,t){let n=new Gs;const r=new He,s=new He,a=new Ye,o=new Af({depthPacking:Sl}),l=new bf,h={},u=t.maxTextureSize,d={[gn]:vt,[vt]:gn,[Jt]:Jt},p=new vn({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new He},radius:{value:4}},vertexShader:wf,fragmentShader:Rf}),m=p.clone();m.defines.HORIZONTAL_PASS=1;const v=new rn;v.setAttribute("position",new Vt(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const M=new rt(v,p),f=this;this.enabled=!1,this.autoUpdate=!0,this.needsUpdate=!1,this.type=io;let c=this.type;this.render=function(R,b,U){if(f.enabled===!1||f.autoUpdate===!1&&f.needsUpdate===!1||R.length===0)return;const E=i.getRenderTarget(),x=i.getActiveCubeFace(),w=i.getActiveMipmapLevel(),H=i.state;H.setBlending(mn),H.buffers.color.setClear(1,1,1,1),H.buffers.depth.setTest(!0),H.setScissorTest(!1);const z=c!==jt&&this.type===jt,W=c===jt&&this.type!==jt;for(let Z=0,k=R.length;Zu||r.y>u)&&(r.x>u&&(s.x=Math.floor(u/ie.x),r.x=s.x*ie.x,V.mapSize.x=s.x),r.y>u&&(s.y=Math.floor(u/ie.y),r.y=s.y*ie.y,V.mapSize.y=s.y)),V.map===null||z===!0||W===!0){const xe=this.type!==jt?{minFilter:Ot,magFilter:Ot}:{};V.map!==null&&V.map.dispose(),V.map=new Dn(r.x,r.y,xe),V.map.texture.name=j.name+".shadowMap",V.camera.updateProjectionMatrix()}i.setRenderTarget(V.map),i.clear();const ce=V.getViewportCount();for(let xe=0;xe0||b.map&&b.alphaTest>0){const H=x.uuid,z=b.uuid;let W=h[H];W===void 0&&(W={},h[H]=W);let Z=W[z];Z===void 0&&(Z=x.clone(),W[z]=Z,b.addEventListener("dispose",F)),x=Z}if(x.visible=b.visible,x.wireframe=b.wireframe,E===jt?x.side=b.shadowSide!==null?b.shadowSide:b.side:x.side=b.shadowSide!==null?b.shadowSide:d[b.side],x.alphaMap=b.alphaMap,x.alphaTest=b.alphaTest,x.map=b.map,x.clipShadows=b.clipShadows,x.clippingPlanes=b.clippingPlanes,x.clipIntersection=b.clipIntersection,x.displacementMap=b.displacementMap,x.displacementScale=b.displacementScale,x.displacementBias=b.displacementBias,x.wireframeLinewidth=b.wireframeLinewidth,x.linewidth=b.linewidth,U.isPointLight===!0&&x.isMeshDistanceMaterial===!0){const H=i.properties.get(x);H.light=U}return x}function S(R,b,U,E,x){if(R.visible===!1)return;if(R.layers.test(b.layers)&&(R.isMesh||R.isLine||R.isPoints)&&(R.castShadow||R.receiveShadow&&x===jt)&&(!R.frustumCulled||n.intersectsObject(R))){R.modelViewMatrix.multiplyMatrices(U.matrixWorldInverse,R.matrixWorld);const z=e.update(R),W=R.material;if(Array.isArray(W)){const Z=z.groups;for(let k=0,j=Z.length;k=1):V.indexOf("OpenGL ES")!==-1&&(j=parseFloat(/^OpenGL ES (\d)/.exec(V)[1]),k=j>=2);let ie=null,ce={};const xe=i.getParameter(i.SCISSOR_BOX),De=i.getParameter(i.VIEWPORT),Ke=new Ye().fromArray(xe),q=new Ye().fromArray(De);function ee(C,ne,G,K){const le=new Uint8Array(4),ae=i.createTexture();i.bindTexture(C,ae),i.texParameteri(C,i.TEXTURE_MIN_FILTER,i.NEAREST),i.texParameteri(C,i.TEXTURE_MAG_FILTER,i.NEAREST);for(let we=0;we"u"?!1:/OculusBrowser/g.test(navigator.userAgent),h=new He,u=new WeakMap;let d;const p=new WeakMap;let m=!1;try{m=typeof OffscreenCanvas<"u"&&new OffscreenCanvas(1,1).getContext("2d")!==null}catch{}function v(y,_){return m?new OffscreenCanvas(y,_):sr("canvas")}function M(y,_,N){let Y=1;const $=Me(y);if(($.width>N||$.height>N)&&(Y=N/Math.max($.width,$.height)),Y<1)if(typeof HTMLImageElement<"u"&&y instanceof HTMLImageElement||typeof HTMLCanvasElement<"u"&&y instanceof HTMLCanvasElement||typeof ImageBitmap<"u"&&y instanceof ImageBitmap||typeof VideoFrame<"u"&&y instanceof VideoFrame){const X=Math.floor(Y*$.width),ge=Math.floor(Y*$.height);d===void 0&&(d=v(X,ge));const se=_?v(X,ge):d;return se.width=X,se.height=ge,se.getContext("2d").drawImage(y,0,0,X,ge),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+$.width+"x"+$.height+") to ("+X+"x"+ge+")."),se}else return"data"in y&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+$.width+"x"+$.height+")."),y;return y}function f(y){return y.generateMipmaps}function c(y){i.generateMipmap(y)}function A(y){return y.isWebGLCubeRenderTarget?i.TEXTURE_CUBE_MAP:y.isWebGL3DRenderTarget?i.TEXTURE_3D:y.isWebGLArrayRenderTarget||y.isCompressedArrayTexture?i.TEXTURE_2D_ARRAY:i.TEXTURE_2D}function T(y,_,N,Y,$=!1){if(y!==null){if(i[y]!==void 0)return i[y];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+y+"'")}let X=_;if(_===i.RED&&(N===i.FLOAT&&(X=i.R32F),N===i.HALF_FLOAT&&(X=i.R16F),N===i.UNSIGNED_BYTE&&(X=i.R8)),_===i.RED_INTEGER&&(N===i.UNSIGNED_BYTE&&(X=i.R8UI),N===i.UNSIGNED_SHORT&&(X=i.R16UI),N===i.UNSIGNED_INT&&(X=i.R32UI),N===i.BYTE&&(X=i.R8I),N===i.SHORT&&(X=i.R16I),N===i.INT&&(X=i.R32I)),_===i.RG&&(N===i.FLOAT&&(X=i.RG32F),N===i.HALF_FLOAT&&(X=i.RG16F),N===i.UNSIGNED_BYTE&&(X=i.RG8)),_===i.RG_INTEGER&&(N===i.UNSIGNED_BYTE&&(X=i.RG8UI),N===i.UNSIGNED_SHORT&&(X=i.RG16UI),N===i.UNSIGNED_INT&&(X=i.RG32UI),N===i.BYTE&&(X=i.RG8I),N===i.SHORT&&(X=i.RG16I),N===i.INT&&(X=i.RG32I)),_===i.RGB_INTEGER&&(N===i.UNSIGNED_BYTE&&(X=i.RGB8UI),N===i.UNSIGNED_SHORT&&(X=i.RGB16UI),N===i.UNSIGNED_INT&&(X=i.RGB32UI),N===i.BYTE&&(X=i.RGB8I),N===i.SHORT&&(X=i.RGB16I),N===i.INT&&(X=i.RGB32I)),_===i.RGBA_INTEGER&&(N===i.UNSIGNED_BYTE&&(X=i.RGBA8UI),N===i.UNSIGNED_SHORT&&(X=i.RGBA16UI),N===i.UNSIGNED_INT&&(X=i.RGBA32UI),N===i.BYTE&&(X=i.RGBA8I),N===i.SHORT&&(X=i.RGBA16I),N===i.INT&&(X=i.RGBA32I)),_===i.RGB&&N===i.UNSIGNED_INT_5_9_9_9_REV&&(X=i.RGB9_E5),_===i.RGBA){const ge=$?lr:ze.getTransfer(Y);N===i.FLOAT&&(X=i.RGBA32F),N===i.HALF_FLOAT&&(X=i.RGBA16F),N===i.UNSIGNED_BYTE&&(X=ge===qe?i.SRGB8_ALPHA8:i.RGBA8),N===i.UNSIGNED_SHORT_4_4_4_4&&(X=i.RGBA4),N===i.UNSIGNED_SHORT_5_5_5_1&&(X=i.RGB5_A1)}return(X===i.R16F||X===i.R32F||X===i.RG16F||X===i.RG32F||X===i.RGBA16F||X===i.RGBA32F)&&e.get("EXT_color_buffer_float"),X}function S(y,_){let N;return y?_===null||_===Ln||_===ii?N=i.DEPTH24_STENCIL8:_===Qt?N=i.DEPTH32F_STENCIL8:_===Mi&&(N=i.DEPTH24_STENCIL8,console.warn("DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.")):_===null||_===Ln||_===ii?N=i.DEPTH_COMPONENT24:_===Qt?N=i.DEPTH_COMPONENT32F:_===Mi&&(N=i.DEPTH_COMPONENT16),N}function F(y,_){return f(y)===!0||y.isFramebufferTexture&&y.minFilter!==Ot&&y.minFilter!==Gt?Math.log2(Math.max(_.width,_.height))+1:y.mipmaps!==void 0&&y.mipmaps.length>0?y.mipmaps.length:y.isCompressedTexture&&Array.isArray(y.image)?_.mipmaps.length:1}function R(y){const _=y.target;_.removeEventListener("dispose",R),U(_),_.isVideoTexture&&u.delete(_)}function b(y){const _=y.target;_.removeEventListener("dispose",b),x(_)}function U(y){const _=n.get(y);if(_.__webglInit===void 0)return;const N=y.source,Y=p.get(N);if(Y){const $=Y[_.__cacheKey];$.usedTimes--,$.usedTimes===0&&E(y),Object.keys(Y).length===0&&p.delete(N)}n.remove(y)}function E(y){const _=n.get(y);i.deleteTexture(_.__webglTexture);const N=y.source,Y=p.get(N);delete Y[_.__cacheKey],a.memory.textures--}function x(y){const _=n.get(y);if(y.depthTexture&&(y.depthTexture.dispose(),n.remove(y.depthTexture)),y.isWebGLCubeRenderTarget)for(let Y=0;Y<6;Y++){if(Array.isArray(_.__webglFramebuffer[Y]))for(let $=0;$<_.__webglFramebuffer[Y].length;$++)i.deleteFramebuffer(_.__webglFramebuffer[Y][$]);else i.deleteFramebuffer(_.__webglFramebuffer[Y]);_.__webglDepthbuffer&&i.deleteRenderbuffer(_.__webglDepthbuffer[Y])}else{if(Array.isArray(_.__webglFramebuffer))for(let Y=0;Y<_.__webglFramebuffer.length;Y++)i.deleteFramebuffer(_.__webglFramebuffer[Y]);else i.deleteFramebuffer(_.__webglFramebuffer);if(_.__webglDepthbuffer&&i.deleteRenderbuffer(_.__webglDepthbuffer),_.__webglMultisampledFramebuffer&&i.deleteFramebuffer(_.__webglMultisampledFramebuffer),_.__webglColorRenderbuffer)for(let Y=0;Y<_.__webglColorRenderbuffer.length;Y++)_.__webglColorRenderbuffer[Y]&&i.deleteRenderbuffer(_.__webglColorRenderbuffer[Y]);_.__webglDepthRenderbuffer&&i.deleteRenderbuffer(_.__webglDepthRenderbuffer)}const N=y.textures;for(let Y=0,$=N.length;Y<$;Y++){const X=n.get(N[Y]);X.__webglTexture&&(i.deleteTexture(X.__webglTexture),a.memory.textures--),n.remove(N[Y])}n.remove(y)}let w=0;function H(){w=0}function z(){const y=w;return y>=r.maxTextures&&console.warn("THREE.WebGLTextures: Trying to use "+y+" texture units while this GPU supports only "+r.maxTextures),w+=1,y}function W(y){const _=[];return _.push(y.wrapS),_.push(y.wrapT),_.push(y.wrapR||0),_.push(y.magFilter),_.push(y.minFilter),_.push(y.anisotropy),_.push(y.internalFormat),_.push(y.format),_.push(y.type),_.push(y.generateMipmaps),_.push(y.premultiplyAlpha),_.push(y.flipY),_.push(y.unpackAlignment),_.push(y.colorSpace),_.join()}function Z(y,_){const N=n.get(y);if(y.isVideoTexture&&Se(y),y.isRenderTargetTexture===!1&&y.version>0&&N.__version!==y.version){const Y=y.image;if(Y===null)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else if(Y.complete===!1)console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete");else{q(N,y,_);return}}t.bindTexture(i.TEXTURE_2D,N.__webglTexture,i.TEXTURE0+_)}function k(y,_){const N=n.get(y);if(y.version>0&&N.__version!==y.version){q(N,y,_);return}t.bindTexture(i.TEXTURE_2D_ARRAY,N.__webglTexture,i.TEXTURE0+_)}function j(y,_){const N=n.get(y);if(y.version>0&&N.__version!==y.version){q(N,y,_);return}t.bindTexture(i.TEXTURE_3D,N.__webglTexture,i.TEXTURE0+_)}function V(y,_){const N=n.get(y);if(y.version>0&&N.__version!==y.version){ee(N,y,_);return}t.bindTexture(i.TEXTURE_CUBE_MAP,N.__webglTexture,i.TEXTURE0+_)}const ie={[es]:i.REPEAT,[Cn]:i.CLAMP_TO_EDGE,[ts]:i.MIRRORED_REPEAT},ce={[Ot]:i.NEAREST,[xl]:i.NEAREST_MIPMAP_NEAREST,[Li]:i.NEAREST_MIPMAP_LINEAR,[Gt]:i.LINEAR,[ur]:i.LINEAR_MIPMAP_NEAREST,[Pn]:i.LINEAR_MIPMAP_LINEAR},xe={[yl]:i.NEVER,[Cl]:i.ALWAYS,[Tl]:i.LESS,[vo]:i.LEQUAL,[Al]:i.EQUAL,[Rl]:i.GEQUAL,[bl]:i.GREATER,[wl]:i.NOTEQUAL};function De(y,_){if(_.type===Qt&&e.has("OES_texture_float_linear")===!1&&(_.magFilter===Gt||_.magFilter===ur||_.magFilter===Li||_.magFilter===Pn||_.minFilter===Gt||_.minFilter===ur||_.minFilter===Li||_.minFilter===Pn)&&console.warn("THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device."),i.texParameteri(y,i.TEXTURE_WRAP_S,ie[_.wrapS]),i.texParameteri(y,i.TEXTURE_WRAP_T,ie[_.wrapT]),(y===i.TEXTURE_3D||y===i.TEXTURE_2D_ARRAY)&&i.texParameteri(y,i.TEXTURE_WRAP_R,ie[_.wrapR]),i.texParameteri(y,i.TEXTURE_MAG_FILTER,ce[_.magFilter]),i.texParameteri(y,i.TEXTURE_MIN_FILTER,ce[_.minFilter]),_.compareFunction&&(i.texParameteri(y,i.TEXTURE_COMPARE_MODE,i.COMPARE_REF_TO_TEXTURE),i.texParameteri(y,i.TEXTURE_COMPARE_FUNC,xe[_.compareFunction])),e.has("EXT_texture_filter_anisotropic")===!0){if(_.magFilter===Ot||_.minFilter!==Li&&_.minFilter!==Pn||_.type===Qt&&e.has("OES_texture_float_linear")===!1)return;if(_.anisotropy>1||n.get(_).__currentAnisotropy){const N=e.get("EXT_texture_filter_anisotropic");i.texParameterf(y,N.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(_.anisotropy,r.getMaxAnisotropy())),n.get(_).__currentAnisotropy=_.anisotropy}}}function Ke(y,_){let N=!1;y.__webglInit===void 0&&(y.__webglInit=!0,_.addEventListener("dispose",R));const Y=_.source;let $=p.get(Y);$===void 0&&($={},p.set(Y,$));const X=W(_);if(X!==y.__cacheKey){$[X]===void 0&&($[X]={texture:i.createTexture(),usedTimes:0},a.memory.textures++,N=!0),$[X].usedTimes++;const ge=$[y.__cacheKey];ge!==void 0&&($[y.__cacheKey].usedTimes--,ge.usedTimes===0&&E(_)),y.__cacheKey=X,y.__webglTexture=$[X].texture}return N}function q(y,_,N){let Y=i.TEXTURE_2D;(_.isDataArrayTexture||_.isCompressedArrayTexture)&&(Y=i.TEXTURE_2D_ARRAY),_.isData3DTexture&&(Y=i.TEXTURE_3D);const $=Ke(y,_),X=_.source;t.bindTexture(Y,y.__webglTexture,i.TEXTURE0+N);const ge=n.get(X);if(X.version!==ge.__version||$===!0){t.activeTexture(i.TEXTURE0+N);const se=ze.getPrimaries(ze.workingColorSpace),he=_.colorSpace===pn?null:ze.getPrimaries(_.colorSpace),Be=_.colorSpace===pn||se===he?i.NONE:i.BROWSER_DEFAULT_WEBGL;i.pixelStorei(i.UNPACK_FLIP_Y_WEBGL,_.flipY),i.pixelStorei(i.UNPACK_PREMULTIPLY_ALPHA_WEBGL,_.premultiplyAlpha),i.pixelStorei(i.UNPACK_ALIGNMENT,_.unpackAlignment),i.pixelStorei(i.UNPACK_COLORSPACE_CONVERSION_WEBGL,Be);let J=M(_.image,!1,r.maxTextureSize);J=je(_,J);const ue=s.convert(_.format,_.colorSpace),Ee=s.convert(_.type);let Te=T(_.internalFormat,ue,Ee,_.colorSpace,_.isVideoTexture);De(Y,_);let de;const Fe=_.mipmaps,Pe=_.isVideoTexture!==!0,Ze=ge.__version===void 0||$===!0,C=X.dataReady,ne=F(_,J);if(_.isDepthTexture)Te=S(_.format===ri,_.type),Ze&&(Pe?t.texStorage2D(i.TEXTURE_2D,1,Te,J.width,J.height):t.texImage2D(i.TEXTURE_2D,0,Te,J.width,J.height,0,ue,Ee,null));else if(_.isDataTexture)if(Fe.length>0){Pe&&Ze&&t.texStorage2D(i.TEXTURE_2D,ne,Te,Fe[0].width,Fe[0].height);for(let G=0,K=Fe.length;G0){const le=$a(de.width,de.height,_.format,_.type);for(const ae of _.layerUpdates){const we=de.data.subarray(ae*le/de.data.BYTES_PER_ELEMENT,(ae+1)*le/de.data.BYTES_PER_ELEMENT);t.compressedTexSubImage3D(i.TEXTURE_2D_ARRAY,G,0,0,ae,de.width,de.height,1,ue,we)}_.clearLayerUpdates()}else t.compressedTexSubImage3D(i.TEXTURE_2D_ARRAY,G,0,0,0,de.width,de.height,J.depth,ue,de.data)}else t.compressedTexImage3D(i.TEXTURE_2D_ARRAY,G,Te,de.width,de.height,J.depth,0,de.data,0,0);else console.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()");else Pe?C&&t.texSubImage3D(i.TEXTURE_2D_ARRAY,G,0,0,0,de.width,de.height,J.depth,ue,Ee,de.data):t.texImage3D(i.TEXTURE_2D_ARRAY,G,Te,de.width,de.height,J.depth,0,ue,Ee,de.data)}else{Pe&&Ze&&t.texStorage2D(i.TEXTURE_2D,ne,Te,Fe[0].width,Fe[0].height);for(let G=0,K=Fe.length;G0){const G=$a(J.width,J.height,_.format,_.type);for(const K of _.layerUpdates){const le=J.data.subarray(K*G/J.data.BYTES_PER_ELEMENT,(K+1)*G/J.data.BYTES_PER_ELEMENT);t.texSubImage3D(i.TEXTURE_2D_ARRAY,0,0,0,K,J.width,J.height,1,ue,Ee,le)}_.clearLayerUpdates()}else t.texSubImage3D(i.TEXTURE_2D_ARRAY,0,0,0,0,J.width,J.height,J.depth,ue,Ee,J.data)}else t.texImage3D(i.TEXTURE_2D_ARRAY,0,Te,J.width,J.height,J.depth,0,ue,Ee,J.data);else if(_.isData3DTexture)Pe?(Ze&&t.texStorage3D(i.TEXTURE_3D,ne,Te,J.width,J.height,J.depth),C&&t.texSubImage3D(i.TEXTURE_3D,0,0,0,0,J.width,J.height,J.depth,ue,Ee,J.data)):t.texImage3D(i.TEXTURE_3D,0,Te,J.width,J.height,J.depth,0,ue,Ee,J.data);else if(_.isFramebufferTexture){if(Ze)if(Pe)t.texStorage2D(i.TEXTURE_2D,ne,Te,J.width,J.height);else{let G=J.width,K=J.height;for(let le=0;le>=1,K>>=1}}else if(Fe.length>0){if(Pe&&Ze){const G=Me(Fe[0]);t.texStorage2D(i.TEXTURE_2D,ne,Te,G.width,G.height)}for(let G=0,K=Fe.length;G0&&ne++;const K=Me(ue[0]);t.texStorage2D(i.TEXTURE_CUBE_MAP,ne,Fe,K.width,K.height)}for(let K=0;K<6;K++)if(J){Pe?C&&t.texSubImage2D(i.TEXTURE_CUBE_MAP_POSITIVE_X+K,0,0,0,ue[K].width,ue[K].height,Te,de,ue[K].data):t.texImage2D(i.TEXTURE_CUBE_MAP_POSITIVE_X+K,0,Fe,ue[K].width,ue[K].height,0,Te,de,ue[K].data);for(let le=0;le>X),Ee=Math.max(1,_.height>>X);$===i.TEXTURE_3D||$===i.TEXTURE_2D_ARRAY?t.texImage3D($,X,he,ue,Ee,_.depth,0,ge,se,null):t.texImage2D($,X,he,ue,Ee,0,ge,se,null)}t.bindFramebuffer(i.FRAMEBUFFER,y),Ne(_)?o.framebufferTexture2DMultisampleEXT(i.FRAMEBUFFER,Y,$,J.__webglTexture,0,Ie(_)):($===i.TEXTURE_2D||$>=i.TEXTURE_CUBE_MAP_POSITIVE_X&&$<=i.TEXTURE_CUBE_MAP_NEGATIVE_Z)&&i.framebufferTexture2D(i.FRAMEBUFFER,Y,$,J.__webglTexture,X),t.bindFramebuffer(i.FRAMEBUFFER,null)}function re(y,_,N){if(i.bindRenderbuffer(i.RENDERBUFFER,y),_.depthBuffer){const Y=_.depthTexture,$=Y&&Y.isDepthTexture?Y.type:null,X=S(_.stencilBuffer,$),ge=_.stencilBuffer?i.DEPTH_STENCIL_ATTACHMENT:i.DEPTH_ATTACHMENT,se=Ie(_);Ne(_)?o.renderbufferStorageMultisampleEXT(i.RENDERBUFFER,se,X,_.width,_.height):N?i.renderbufferStorageMultisample(i.RENDERBUFFER,se,X,_.width,_.height):i.renderbufferStorage(i.RENDERBUFFER,X,_.width,_.height),i.framebufferRenderbuffer(i.FRAMEBUFFER,ge,i.RENDERBUFFER,y)}else{const Y=_.textures;for(let $=0;${delete _.__boundDepthTexture,delete _.__depthDisposeCallback,Y.removeEventListener("dispose",$)};Y.addEventListener("dispose",$),_.__depthDisposeCallback=$}_.__boundDepthTexture=Y}if(y.depthTexture&&!_.__autoAllocateDepthBuffer){if(N)throw new Error("target.depthTexture not supported in Cube render targets");ye(_.__webglFramebuffer,y)}else if(N){_.__webglDepthbuffer=[];for(let Y=0;Y<6;Y++)if(t.bindFramebuffer(i.FRAMEBUFFER,_.__webglFramebuffer[Y]),_.__webglDepthbuffer[Y]===void 0)_.__webglDepthbuffer[Y]=i.createRenderbuffer(),re(_.__webglDepthbuffer[Y],y,!1);else{const $=y.stencilBuffer?i.DEPTH_STENCIL_ATTACHMENT:i.DEPTH_ATTACHMENT,X=_.__webglDepthbuffer[Y];i.bindRenderbuffer(i.RENDERBUFFER,X),i.framebufferRenderbuffer(i.FRAMEBUFFER,$,i.RENDERBUFFER,X)}}else if(t.bindFramebuffer(i.FRAMEBUFFER,_.__webglFramebuffer),_.__webglDepthbuffer===void 0)_.__webglDepthbuffer=i.createRenderbuffer(),re(_.__webglDepthbuffer,y,!1);else{const Y=y.stencilBuffer?i.DEPTH_STENCIL_ATTACHMENT:i.DEPTH_ATTACHMENT,$=_.__webglDepthbuffer;i.bindRenderbuffer(i.RENDERBUFFER,$),i.framebufferRenderbuffer(i.FRAMEBUFFER,Y,i.RENDERBUFFER,$)}t.bindFramebuffer(i.FRAMEBUFFER,null)}function Ue(y,_,N){const Y=n.get(y);_!==void 0&&_e(Y.__webglFramebuffer,y,y.texture,i.COLOR_ATTACHMENT0,i.TEXTURE_2D,0),N!==void 0&&be(y)}function tt(y){const _=y.texture,N=n.get(y),Y=n.get(_);y.addEventListener("dispose",b);const $=y.textures,X=y.isWebGLCubeRenderTarget===!0,ge=$.length>1;if(ge||(Y.__webglTexture===void 0&&(Y.__webglTexture=i.createTexture()),Y.__version=_.version,a.memory.textures++),X){N.__webglFramebuffer=[];for(let se=0;se<6;se++)if(_.mipmaps&&_.mipmaps.length>0){N.__webglFramebuffer[se]=[];for(let he=0;he<_.mipmaps.length;he++)N.__webglFramebuffer[se][he]=i.createFramebuffer()}else N.__webglFramebuffer[se]=i.createFramebuffer()}else{if(_.mipmaps&&_.mipmaps.length>0){N.__webglFramebuffer=[];for(let se=0;se<_.mipmaps.length;se++)N.__webglFramebuffer[se]=i.createFramebuffer()}else N.__webglFramebuffer=i.createFramebuffer();if(ge)for(let se=0,he=$.length;se0&&Ne(y)===!1){N.__webglMultisampledFramebuffer=i.createFramebuffer(),N.__webglColorRenderbuffer=[],t.bindFramebuffer(i.FRAMEBUFFER,N.__webglMultisampledFramebuffer);for(let se=0;se<$.length;se++){const he=$[se];N.__webglColorRenderbuffer[se]=i.createRenderbuffer(),i.bindRenderbuffer(i.RENDERBUFFER,N.__webglColorRenderbuffer[se]);const Be=s.convert(he.format,he.colorSpace),J=s.convert(he.type),ue=T(he.internalFormat,Be,J,he.colorSpace,y.isXRRenderTarget===!0),Ee=Ie(y);i.renderbufferStorageMultisample(i.RENDERBUFFER,Ee,ue,y.width,y.height),i.framebufferRenderbuffer(i.FRAMEBUFFER,i.COLOR_ATTACHMENT0+se,i.RENDERBUFFER,N.__webglColorRenderbuffer[se])}i.bindRenderbuffer(i.RENDERBUFFER,null),y.depthBuffer&&(N.__webglDepthRenderbuffer=i.createRenderbuffer(),re(N.__webglDepthRenderbuffer,y,!0)),t.bindFramebuffer(i.FRAMEBUFFER,null)}}if(X){t.bindTexture(i.TEXTURE_CUBE_MAP,Y.__webglTexture),De(i.TEXTURE_CUBE_MAP,_);for(let se=0;se<6;se++)if(_.mipmaps&&_.mipmaps.length>0)for(let he=0;he<_.mipmaps.length;he++)_e(N.__webglFramebuffer[se][he],y,_,i.COLOR_ATTACHMENT0,i.TEXTURE_CUBE_MAP_POSITIVE_X+se,he);else _e(N.__webglFramebuffer[se],y,_,i.COLOR_ATTACHMENT0,i.TEXTURE_CUBE_MAP_POSITIVE_X+se,0);f(_)&&c(i.TEXTURE_CUBE_MAP),t.unbindTexture()}else if(ge){for(let se=0,he=$.length;se0)for(let he=0;he<_.mipmaps.length;he++)_e(N.__webglFramebuffer[he],y,_,i.COLOR_ATTACHMENT0,se,he);else _e(N.__webglFramebuffer,y,_,i.COLOR_ATTACHMENT0,se,0);f(_)&&c(se),t.unbindTexture()}y.depthBuffer&&be(y)}function Oe(y){const _=y.textures;for(let N=0,Y=_.length;N0){if(Ne(y)===!1){const _=y.textures,N=y.width,Y=y.height;let $=i.COLOR_BUFFER_BIT;const X=y.stencilBuffer?i.DEPTH_STENCIL_ATTACHMENT:i.DEPTH_ATTACHMENT,ge=n.get(y),se=_.length>1;if(se)for(let he=0;he<_.length;he++)t.bindFramebuffer(i.FRAMEBUFFER,ge.__webglMultisampledFramebuffer),i.framebufferRenderbuffer(i.FRAMEBUFFER,i.COLOR_ATTACHMENT0+he,i.RENDERBUFFER,null),t.bindFramebuffer(i.FRAMEBUFFER,ge.__webglFramebuffer),i.framebufferTexture2D(i.DRAW_FRAMEBUFFER,i.COLOR_ATTACHMENT0+he,i.TEXTURE_2D,null,0);t.bindFramebuffer(i.READ_FRAMEBUFFER,ge.__webglMultisampledFramebuffer),t.bindFramebuffer(i.DRAW_FRAMEBUFFER,ge.__webglFramebuffer);for(let he=0;he<_.length;he++){if(y.resolveDepthBuffer&&(y.depthBuffer&&($|=i.DEPTH_BUFFER_BIT),y.stencilBuffer&&y.resolveStencilBuffer&&($|=i.STENCIL_BUFFER_BIT)),se){i.framebufferRenderbuffer(i.READ_FRAMEBUFFER,i.COLOR_ATTACHMENT0,i.RENDERBUFFER,ge.__webglColorRenderbuffer[he]);const Be=n.get(_[he]).__webglTexture;i.framebufferTexture2D(i.DRAW_FRAMEBUFFER,i.COLOR_ATTACHMENT0,i.TEXTURE_2D,Be,0)}i.blitFramebuffer(0,0,N,Y,0,0,N,Y,$,i.NEAREST),l===!0&&(it.length=0,D.length=0,it.push(i.COLOR_ATTACHMENT0+he),y.depthBuffer&&y.resolveDepthBuffer===!1&&(it.push(X),D.push(X),i.invalidateFramebuffer(i.DRAW_FRAMEBUFFER,D)),i.invalidateFramebuffer(i.READ_FRAMEBUFFER,it))}if(t.bindFramebuffer(i.READ_FRAMEBUFFER,null),t.bindFramebuffer(i.DRAW_FRAMEBUFFER,null),se)for(let he=0;he<_.length;he++){t.bindFramebuffer(i.FRAMEBUFFER,ge.__webglMultisampledFramebuffer),i.framebufferRenderbuffer(i.FRAMEBUFFER,i.COLOR_ATTACHMENT0+he,i.RENDERBUFFER,ge.__webglColorRenderbuffer[he]);const Be=n.get(_[he]).__webglTexture;t.bindFramebuffer(i.FRAMEBUFFER,ge.__webglFramebuffer),i.framebufferTexture2D(i.DRAW_FRAMEBUFFER,i.COLOR_ATTACHMENT0+he,i.TEXTURE_2D,Be,0)}t.bindFramebuffer(i.DRAW_FRAMEBUFFER,ge.__webglMultisampledFramebuffer)}else if(y.depthBuffer&&y.resolveDepthBuffer===!1&&l){const _=y.stencilBuffer?i.DEPTH_STENCIL_ATTACHMENT:i.DEPTH_ATTACHMENT;i.invalidateFramebuffer(i.DRAW_FRAMEBUFFER,[_])}}}function Ie(y){return Math.min(r.maxSamples,y.samples)}function Ne(y){const _=n.get(y);return y.samples>0&&e.has("WEBGL_multisampled_render_to_texture")===!0&&_.__useRenderToTexture!==!1}function Se(y){const _=a.render.frame;u.get(y)!==_&&(u.set(y,_),y.update())}function je(y,_){const N=y.colorSpace,Y=y.format,$=y.type;return y.isCompressedTexture===!0||y.isVideoTexture===!0||N!==ai&&N!==pn&&(ze.getTransfer(N)===qe?(Y!==Ft||$!==nn)&&console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture color space:",N)),_}function Me(y){return typeof HTMLImageElement<"u"&&y instanceof HTMLImageElement?(h.width=y.naturalWidth||y.width,h.height=y.naturalHeight||y.height):typeof VideoFrame<"u"&&y instanceof VideoFrame?(h.width=y.displayWidth,h.height=y.displayHeight):(h.width=y.width,h.height=y.height),h}this.allocateTextureUnit=z,this.resetTextureUnits=H,this.setTexture2D=Z,this.setTexture2DArray=k,this.setTexture3D=j,this.setTextureCube=V,this.rebindTextures=Ue,this.setupRenderTarget=tt,this.updateRenderTargetMipmap=Oe,this.updateMultisampleRenderTarget=At,this.setupDepthRenderbuffer=be,this.setupFrameBufferTexture=_e,this.useMultisampledRTT=Ne}function If(i,e){function t(n,r=pn){let s;const a=ze.getTransfer(r);if(n===nn)return i.UNSIGNED_BYTE;if(n===Is)return i.UNSIGNED_SHORT_4_4_4_4;if(n===Ns)return i.UNSIGNED_SHORT_5_5_5_1;if(n===lo)return i.UNSIGNED_INT_5_9_9_9_REV;if(n===ao)return i.BYTE;if(n===oo)return i.SHORT;if(n===Mi)return i.UNSIGNED_SHORT;if(n===Us)return i.INT;if(n===Ln)return i.UNSIGNED_INT;if(n===Qt)return i.FLOAT;if(n===Si)return i.HALF_FLOAT;if(n===co)return i.ALPHA;if(n===ho)return i.RGB;if(n===Ft)return i.RGBA;if(n===uo)return i.LUMINANCE;if(n===fo)return i.LUMINANCE_ALPHA;if(n===Jn)return i.DEPTH_COMPONENT;if(n===ri)return i.DEPTH_STENCIL;if(n===po)return i.RED;if(n===Fs)return i.RED_INTEGER;if(n===mo)return i.RG;if(n===Os)return i.RG_INTEGER;if(n===Bs)return i.RGBA_INTEGER;if(n===Ji||n===Qi||n===er||n===tr)if(a===qe)if(s=e.get("WEBGL_compressed_texture_s3tc_srgb"),s!==null){if(n===Ji)return s.COMPRESSED_SRGB_S3TC_DXT1_EXT;if(n===Qi)return s.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;if(n===er)return s.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;if(n===tr)return s.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}else return null;else if(s=e.get("WEBGL_compressed_texture_s3tc"),s!==null){if(n===Ji)return s.COMPRESSED_RGB_S3TC_DXT1_EXT;if(n===Qi)return s.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(n===er)return s.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(n===tr)return s.COMPRESSED_RGBA_S3TC_DXT5_EXT}else return null;if(n===ns||n===is||n===rs||n===ss)if(s=e.get("WEBGL_compressed_texture_pvrtc"),s!==null){if(n===ns)return s.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;if(n===is)return s.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;if(n===rs)return s.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;if(n===ss)return s.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}else return null;if(n===as||n===os||n===ls)if(s=e.get("WEBGL_compressed_texture_etc"),s!==null){if(n===as||n===os)return a===qe?s.COMPRESSED_SRGB8_ETC2:s.COMPRESSED_RGB8_ETC2;if(n===ls)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:s.COMPRESSED_RGBA8_ETC2_EAC}else return null;if(n===cs||n===hs||n===us||n===ds||n===fs||n===ps||n===ms||n===_s||n===gs||n===vs||n===xs||n===Ms||n===Ss||n===Es)if(s=e.get("WEBGL_compressed_texture_astc"),s!==null){if(n===cs)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:s.COMPRESSED_RGBA_ASTC_4x4_KHR;if(n===hs)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:s.COMPRESSED_RGBA_ASTC_5x4_KHR;if(n===us)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:s.COMPRESSED_RGBA_ASTC_5x5_KHR;if(n===ds)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:s.COMPRESSED_RGBA_ASTC_6x5_KHR;if(n===fs)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:s.COMPRESSED_RGBA_ASTC_6x6_KHR;if(n===ps)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:s.COMPRESSED_RGBA_ASTC_8x5_KHR;if(n===ms)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:s.COMPRESSED_RGBA_ASTC_8x6_KHR;if(n===_s)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:s.COMPRESSED_RGBA_ASTC_8x8_KHR;if(n===gs)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:s.COMPRESSED_RGBA_ASTC_10x5_KHR;if(n===vs)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:s.COMPRESSED_RGBA_ASTC_10x6_KHR;if(n===xs)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:s.COMPRESSED_RGBA_ASTC_10x8_KHR;if(n===Ms)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:s.COMPRESSED_RGBA_ASTC_10x10_KHR;if(n===Ss)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:s.COMPRESSED_RGBA_ASTC_12x10_KHR;if(n===Es)return a===qe?s.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:s.COMPRESSED_RGBA_ASTC_12x12_KHR}else return null;if(n===nr||n===ys||n===Ts)if(s=e.get("EXT_texture_compression_bptc"),s!==null){if(n===nr)return a===qe?s.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT:s.COMPRESSED_RGBA_BPTC_UNORM_EXT;if(n===ys)return s.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT;if(n===Ts)return s.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT}else return null;if(n===_o||n===As||n===bs||n===ws)if(s=e.get("EXT_texture_compression_rgtc"),s!==null){if(n===nr)return s.COMPRESSED_RED_RGTC1_EXT;if(n===As)return s.COMPRESSED_SIGNED_RED_RGTC1_EXT;if(n===bs)return s.COMPRESSED_RED_GREEN_RGTC2_EXT;if(n===ws)return s.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT}else return null;return n===ii?i.UNSIGNED_INT_24_8:i[n]!==void 0?i[n]:null}return{convert:t}}class Nf extends yt{constructor(e=[]){super(),this.isArrayCamera=!0,this.cameras=e}}class $n extends ft{constructor(){super(),this.isGroup=!0,this.type="Group"}}const Ff={type:"move"};class Hr{constructor(){this._targetRay=null,this._grip=null,this._hand=null}getHandSpace(){return this._hand===null&&(this._hand=new $n,this._hand.matrixAutoUpdate=!1,this._hand.visible=!1,this._hand.joints={},this._hand.inputState={pinching:!1}),this._hand}getTargetRaySpace(){return this._targetRay===null&&(this._targetRay=new $n,this._targetRay.matrixAutoUpdate=!1,this._targetRay.visible=!1,this._targetRay.hasLinearVelocity=!1,this._targetRay.linearVelocity=new I,this._targetRay.hasAngularVelocity=!1,this._targetRay.angularVelocity=new I),this._targetRay}getGripSpace(){return this._grip===null&&(this._grip=new $n,this._grip.matrixAutoUpdate=!1,this._grip.visible=!1,this._grip.hasLinearVelocity=!1,this._grip.linearVelocity=new I,this._grip.hasAngularVelocity=!1,this._grip.angularVelocity=new I),this._grip}dispatchEvent(e){return this._targetRay!==null&&this._targetRay.dispatchEvent(e),this._grip!==null&&this._grip.dispatchEvent(e),this._hand!==null&&this._hand.dispatchEvent(e),this}connect(e){if(e&&e.hand){const t=this._hand;if(t)for(const n of e.hand.values())this._getHandJoint(t,n)}return this.dispatchEvent({type:"connected",data:e}),this}disconnect(e){return this.dispatchEvent({type:"disconnected",data:e}),this._targetRay!==null&&(this._targetRay.visible=!1),this._grip!==null&&(this._grip.visible=!1),this._hand!==null&&(this._hand.visible=!1),this}update(e,t,n){let r=null,s=null,a=null;const o=this._targetRay,l=this._grip,h=this._hand;if(e&&t.session.visibilityState!=="visible-blurred"){if(h&&e.hand){a=!0;for(const M of e.hand.values()){const f=t.getJointPose(M,n),c=this._getHandJoint(h,M);f!==null&&(c.matrix.fromArray(f.transform.matrix),c.matrix.decompose(c.position,c.rotation,c.scale),c.matrixWorldNeedsUpdate=!0,c.jointRadius=f.radius),c.visible=f!==null}const u=h.joints["index-finger-tip"],d=h.joints["thumb-tip"],p=u.position.distanceTo(d.position),m=.02,v=.005;h.inputState.pinching&&p>m+v?(h.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:e.handedness,target:this})):!h.inputState.pinching&&p<=m-v&&(h.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:e.handedness,target:this}))}else l!==null&&e.gripSpace&&(s=t.getPose(e.gripSpace,n),s!==null&&(l.matrix.fromArray(s.transform.matrix),l.matrix.decompose(l.position,l.rotation,l.scale),l.matrixWorldNeedsUpdate=!0,s.linearVelocity?(l.hasLinearVelocity=!0,l.linearVelocity.copy(s.linearVelocity)):l.hasLinearVelocity=!1,s.angularVelocity?(l.hasAngularVelocity=!0,l.angularVelocity.copy(s.angularVelocity)):l.hasAngularVelocity=!1));o!==null&&(r=t.getPose(e.targetRaySpace,n),r===null&&s!==null&&(r=s),r!==null&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),o.matrixWorldNeedsUpdate=!0,r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1,this.dispatchEvent(Ff)))}return o!==null&&(o.visible=r!==null),l!==null&&(l.visible=s!==null),h!==null&&(h.visible=a!==null),this}_getHandJoint(e,t){if(e.joints[t.jointName]===void 0){const n=new $n;n.matrixAutoUpdate=!1,n.visible=!1,e.joints[t.jointName]=n,e.add(n)}return e.joints[t.jointName]}}const Of=` +void main() { + + gl_Position = vec4( position, 1.0 ); + +}`,Bf=` +uniform sampler2DArray depthColor; +uniform float depthWidth; +uniform float depthHeight; + +void main() { + + vec2 coord = vec2( gl_FragCoord.x / depthWidth, gl_FragCoord.y / depthHeight ); + + if ( coord.x >= 1.0 ) { + + gl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r; + + } else { + + gl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r; + + } + +}`;class zf{constructor(){this.texture=null,this.mesh=null,this.depthNear=0,this.depthFar=0}init(e,t,n){if(this.texture===null){const r=new xt,s=e.properties.get(r);s.__webglTexture=t.texture,(t.depthNear!=n.depthNear||t.depthFar!=n.depthFar)&&(this.depthNear=t.depthNear,this.depthFar=t.depthFar),this.texture=r}}getMesh(e){if(this.texture!==null&&this.mesh===null){const t=e.cameras[0].viewport,n=new vn({vertexShader:Of,fragmentShader:Bf,uniforms:{depthColor:{value:this.texture},depthWidth:{value:t.z},depthHeight:{value:t.w}}});this.mesh=new rt(new bi(20,20),n)}return this.mesh}reset(){this.texture=null,this.mesh=null}getDepthTexture(){return this.texture}}class Hf extends oi{constructor(e,t){super();const n=this;let r=null,s=1,a=null,o="local-floor",l=1,h=null,u=null,d=null,p=null,m=null,v=null;const M=new zf,f=t.getContextAttributes();let c=null,A=null;const T=[],S=[],F=new He;let R=null;const b=new yt;b.viewport=new Ye;const U=new yt;U.viewport=new Ye;const E=[b,U],x=new Nf;let w=null,H=null;this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(q){let ee=T[q];return ee===void 0&&(ee=new Hr,T[q]=ee),ee.getTargetRaySpace()},this.getControllerGrip=function(q){let ee=T[q];return ee===void 0&&(ee=new Hr,T[q]=ee),ee.getGripSpace()},this.getHand=function(q){let ee=T[q];return ee===void 0&&(ee=new Hr,T[q]=ee),ee.getHandSpace()};function z(q){const ee=S.indexOf(q.inputSource);if(ee===-1)return;const _e=T[ee];_e!==void 0&&(_e.update(q.inputSource,q.frame,h||a),_e.dispatchEvent({type:q.type,data:q.inputSource}))}function W(){r.removeEventListener("select",z),r.removeEventListener("selectstart",z),r.removeEventListener("selectend",z),r.removeEventListener("squeeze",z),r.removeEventListener("squeezestart",z),r.removeEventListener("squeezeend",z),r.removeEventListener("end",W),r.removeEventListener("inputsourceschange",Z);for(let q=0;q=0&&(S[re]=null,T[re].disconnect(_e))}for(let ee=0;ee=S.length){S.push(_e),re=be;break}else if(S[be]===null){S[be]=_e,re=be;break}if(re===-1)break}const ye=T[re];ye&&ye.connect(_e)}}const k=new I,j=new I;function V(q,ee,_e){k.setFromMatrixPosition(ee.matrixWorld),j.setFromMatrixPosition(_e.matrixWorld);const re=k.distanceTo(j),ye=ee.projectionMatrix.elements,be=_e.projectionMatrix.elements,Ue=ye[14]/(ye[10]-1),tt=ye[14]/(ye[10]+1),Oe=(ye[9]+1)/ye[5],it=(ye[9]-1)/ye[5],D=(ye[8]-1)/ye[0],At=(be[8]+1)/be[0],Ie=Ue*D,Ne=Ue*At,Se=re/(-D+At),je=Se*-D;if(ee.matrixWorld.decompose(q.position,q.quaternion,q.scale),q.translateX(je),q.translateZ(Se),q.matrixWorld.compose(q.position,q.quaternion,q.scale),q.matrixWorldInverse.copy(q.matrixWorld).invert(),ye[10]===-1)q.projectionMatrix.copy(ee.projectionMatrix),q.projectionMatrixInverse.copy(ee.projectionMatrixInverse);else{const Me=Ue+Se,y=tt+Se,_=Ie-je,N=Ne+(re-je),Y=Oe*tt/y*Me,$=it*tt/y*Me;q.projectionMatrix.makePerspective(_,N,Y,$,Me,y),q.projectionMatrixInverse.copy(q.projectionMatrix).invert()}}function ie(q,ee){ee===null?q.matrixWorld.copy(q.matrix):q.matrixWorld.multiplyMatrices(ee.matrixWorld,q.matrix),q.matrixWorldInverse.copy(q.matrixWorld).invert()}this.updateCamera=function(q){if(r===null)return;let ee=q.near,_e=q.far;M.texture!==null&&(M.depthNear>0&&(ee=M.depthNear),M.depthFar>0&&(_e=M.depthFar)),x.near=U.near=b.near=ee,x.far=U.far=b.far=_e,(w!==x.near||H!==x.far)&&(r.updateRenderState({depthNear:x.near,depthFar:x.far}),w=x.near,H=x.far),b.layers.mask=q.layers.mask|2,U.layers.mask=q.layers.mask|4,x.layers.mask=b.layers.mask|U.layers.mask;const re=q.parent,ye=x.cameras;ie(x,re);for(let be=0;be0&&(f.alphaTest.value=c.alphaTest);const A=e.get(c),T=A.envMap,S=A.envMapRotation;T&&(f.envMap.value=T,An.copy(S),An.x*=-1,An.y*=-1,An.z*=-1,T.isCubeTexture&&T.isRenderTargetTexture===!1&&(An.y*=-1,An.z*=-1),f.envMapRotation.value.setFromMatrix4(Gf.makeRotationFromEuler(An)),f.flipEnvMap.value=T.isCubeTexture&&T.isRenderTargetTexture===!1?-1:1,f.reflectivity.value=c.reflectivity,f.ior.value=c.ior,f.refractionRatio.value=c.refractionRatio),c.lightMap&&(f.lightMap.value=c.lightMap,f.lightMapIntensity.value=c.lightMapIntensity,t(c.lightMap,f.lightMapTransform)),c.aoMap&&(f.aoMap.value=c.aoMap,f.aoMapIntensity.value=c.aoMapIntensity,t(c.aoMap,f.aoMapTransform))}function a(f,c){f.diffuse.value.copy(c.color),f.opacity.value=c.opacity,c.map&&(f.map.value=c.map,t(c.map,f.mapTransform))}function o(f,c){f.dashSize.value=c.dashSize,f.totalSize.value=c.dashSize+c.gapSize,f.scale.value=c.scale}function l(f,c,A,T){f.diffuse.value.copy(c.color),f.opacity.value=c.opacity,f.size.value=c.size*A,f.scale.value=T*.5,c.map&&(f.map.value=c.map,t(c.map,f.uvTransform)),c.alphaMap&&(f.alphaMap.value=c.alphaMap,t(c.alphaMap,f.alphaMapTransform)),c.alphaTest>0&&(f.alphaTest.value=c.alphaTest)}function h(f,c){f.diffuse.value.copy(c.color),f.opacity.value=c.opacity,f.rotation.value=c.rotation,c.map&&(f.map.value=c.map,t(c.map,f.mapTransform)),c.alphaMap&&(f.alphaMap.value=c.alphaMap,t(c.alphaMap,f.alphaMapTransform)),c.alphaTest>0&&(f.alphaTest.value=c.alphaTest)}function u(f,c){f.specular.value.copy(c.specular),f.shininess.value=Math.max(c.shininess,1e-4)}function d(f,c){c.gradientMap&&(f.gradientMap.value=c.gradientMap)}function p(f,c){f.metalness.value=c.metalness,c.metalnessMap&&(f.metalnessMap.value=c.metalnessMap,t(c.metalnessMap,f.metalnessMapTransform)),f.roughness.value=c.roughness,c.roughnessMap&&(f.roughnessMap.value=c.roughnessMap,t(c.roughnessMap,f.roughnessMapTransform)),c.envMap&&(f.envMapIntensity.value=c.envMapIntensity)}function m(f,c,A){f.ior.value=c.ior,c.sheen>0&&(f.sheenColor.value.copy(c.sheenColor).multiplyScalar(c.sheen),f.sheenRoughness.value=c.sheenRoughness,c.sheenColorMap&&(f.sheenColorMap.value=c.sheenColorMap,t(c.sheenColorMap,f.sheenColorMapTransform)),c.sheenRoughnessMap&&(f.sheenRoughnessMap.value=c.sheenRoughnessMap,t(c.sheenRoughnessMap,f.sheenRoughnessMapTransform))),c.clearcoat>0&&(f.clearcoat.value=c.clearcoat,f.clearcoatRoughness.value=c.clearcoatRoughness,c.clearcoatMap&&(f.clearcoatMap.value=c.clearcoatMap,t(c.clearcoatMap,f.clearcoatMapTransform)),c.clearcoatRoughnessMap&&(f.clearcoatRoughnessMap.value=c.clearcoatRoughnessMap,t(c.clearcoatRoughnessMap,f.clearcoatRoughnessMapTransform)),c.clearcoatNormalMap&&(f.clearcoatNormalMap.value=c.clearcoatNormalMap,t(c.clearcoatNormalMap,f.clearcoatNormalMapTransform),f.clearcoatNormalScale.value.copy(c.clearcoatNormalScale),c.side===vt&&f.clearcoatNormalScale.value.negate())),c.dispersion>0&&(f.dispersion.value=c.dispersion),c.iridescence>0&&(f.iridescence.value=c.iridescence,f.iridescenceIOR.value=c.iridescenceIOR,f.iridescenceThicknessMinimum.value=c.iridescenceThicknessRange[0],f.iridescenceThicknessMaximum.value=c.iridescenceThicknessRange[1],c.iridescenceMap&&(f.iridescenceMap.value=c.iridescenceMap,t(c.iridescenceMap,f.iridescenceMapTransform)),c.iridescenceThicknessMap&&(f.iridescenceThicknessMap.value=c.iridescenceThicknessMap,t(c.iridescenceThicknessMap,f.iridescenceThicknessMapTransform))),c.transmission>0&&(f.transmission.value=c.transmission,f.transmissionSamplerMap.value=A.texture,f.transmissionSamplerSize.value.set(A.width,A.height),c.transmissionMap&&(f.transmissionMap.value=c.transmissionMap,t(c.transmissionMap,f.transmissionMapTransform)),f.thickness.value=c.thickness,c.thicknessMap&&(f.thicknessMap.value=c.thicknessMap,t(c.thicknessMap,f.thicknessMapTransform)),f.attenuationDistance.value=c.attenuationDistance,f.attenuationColor.value.copy(c.attenuationColor)),c.anisotropy>0&&(f.anisotropyVector.value.set(c.anisotropy*Math.cos(c.anisotropyRotation),c.anisotropy*Math.sin(c.anisotropyRotation)),c.anisotropyMap&&(f.anisotropyMap.value=c.anisotropyMap,t(c.anisotropyMap,f.anisotropyMapTransform))),f.specularIntensity.value=c.specularIntensity,f.specularColor.value.copy(c.specularColor),c.specularColorMap&&(f.specularColorMap.value=c.specularColorMap,t(c.specularColorMap,f.specularColorMapTransform)),c.specularIntensityMap&&(f.specularIntensityMap.value=c.specularIntensityMap,t(c.specularIntensityMap,f.specularIntensityMapTransform))}function v(f,c){c.matcap&&(f.matcap.value=c.matcap)}function M(f,c){const A=e.get(c).light;f.referencePosition.value.setFromMatrixPosition(A.matrixWorld),f.nearDistance.value=A.shadow.camera.near,f.farDistance.value=A.shadow.camera.far}return{refreshFogUniforms:n,refreshMaterialUniforms:r}}function kf(i,e,t,n){let r={},s={},a=[];const o=i.getParameter(i.MAX_UNIFORM_BUFFER_BINDINGS);function l(A,T){const S=T.program;n.uniformBlockBinding(A,S)}function h(A,T){let S=r[A.id];S===void 0&&(v(A),S=u(A),r[A.id]=S,A.addEventListener("dispose",f));const F=T.program;n.updateUBOMapping(A,F);const R=e.render.frame;s[A.id]!==R&&(p(A),s[A.id]=R)}function u(A){const T=d();A.__bindingPointIndex=T;const S=i.createBuffer(),F=A.__size,R=A.usage;return i.bindBuffer(i.UNIFORM_BUFFER,S),i.bufferData(i.UNIFORM_BUFFER,F,R),i.bindBuffer(i.UNIFORM_BUFFER,null),i.bindBufferBase(i.UNIFORM_BUFFER,T,S),S}function d(){for(let A=0;A0&&(S+=F-R),A.__size=S,A.__cache={},this}function M(A){const T={boundary:0,storage:0};return typeof A=="number"||typeof A=="boolean"?(T.boundary=4,T.storage=4):A.isVector2?(T.boundary=8,T.storage=8):A.isVector3||A.isColor?(T.boundary=16,T.storage=12):A.isVector4?(T.boundary=16,T.storage=16):A.isMatrix3?(T.boundary=48,T.storage=48):A.isMatrix4?(T.boundary=64,T.storage=64):A.isTexture?console.warn("THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group."):console.warn("THREE.WebGLRenderer: Unsupported uniform value type.",A),T}function f(A){const T=A.target;T.removeEventListener("dispose",f);const S=a.indexOf(T.__bindingPointIndex);a.splice(S,1),i.deleteBuffer(r[T.id]),delete r[T.id],delete s[T.id]}function c(){for(const A in r)i.deleteBuffer(r[A]);a=[],r={},s={}}return{bind:l,update:h,dispose:c}}class Wf{constructor(e={}){const{canvas:t=Ll(),context:n=null,depth:r=!0,stencil:s=!1,alpha:a=!1,antialias:o=!1,premultipliedAlpha:l=!0,preserveDrawingBuffer:h=!1,powerPreference:u="default",failIfMajorPerformanceCaveat:d=!1,reverseDepthBuffer:p=!1}=e;this.isWebGLRenderer=!0;let m;if(n!==null){if(typeof WebGLRenderingContext<"u"&&n instanceof WebGLRenderingContext)throw new Error("THREE.WebGLRenderer: WebGL 1 is not supported since r163.");m=n.getContextAttributes().alpha}else m=a;const v=new Uint32Array(4),M=new Int32Array(4);let f=null,c=null;const A=[],T=[];this.domElement=t,this.debug={checkShaderErrors:!0,onShaderError:null},this.autoClear=!0,this.autoClearColor=!0,this.autoClearDepth=!0,this.autoClearStencil=!0,this.sortObjects=!0,this.clippingPlanes=[],this.localClippingEnabled=!1,this._outputColorSpace=Rt,this.toneMapping=_n,this.toneMappingExposure=1;const S=this;let F=!1,R=0,b=0,U=null,E=-1,x=null;const w=new Ye,H=new Ye;let z=null;const W=new Ve(0);let Z=0,k=t.width,j=t.height,V=1,ie=null,ce=null;const xe=new Ye(0,0,k,j),De=new Ye(0,0,k,j);let Ke=!1;const q=new Gs;let ee=!1,_e=!1;const re=new et,ye=new et,be=new I,Ue=new Ye,tt={background:null,fog:null,environment:null,overrideMaterial:null,isScene:!0};let Oe=!1;function it(){return U===null?V:1}let D=n;function At(g,P){return t.getContext(g,P)}try{const g={alpha:!0,depth:r,stencil:s,antialias:o,premultipliedAlpha:l,preserveDrawingBuffer:h,powerPreference:u,failIfMajorPerformanceCaveat:d};if("setAttribute"in t&&t.setAttribute("data-engine",`three.js r${Ls}`),t.addEventListener("webglcontextlost",K,!1),t.addEventListener("webglcontextrestored",le,!1),t.addEventListener("webglcontextcreationerror",ae,!1),D===null){const P="webgl2";if(D=At(P,g),D===null)throw At(P)?new Error("Error creating WebGL context with your selected attributes."):new Error("Error creating WebGL context.")}}catch(g){throw console.error("THREE.WebGLRenderer: "+g.message),g}let Ie,Ne,Se,je,Me,y,_,N,Y,$,X,ge,se,he,Be,J,ue,Ee,Te,de,Fe,Pe,Ze,C;function ne(){Ie=new Ku(D),Ie.init(),Pe=new If(D,Ie),Ne=new Vu(D,Ie,e,Pe),Se=new Lf(D,Ie),Ne.reverseDepthBuffer&&p&&Se.buffers.depth.setReversed(!0),je=new ju(D),Me=new _f,y=new Uf(D,Ie,Se,Me,Ne,Pe,je),_=new Wu(S),N=new Yu(S),Y=new rc(D),Ze=new Hu(D,Y),$=new Zu(D,Y,je,Ze),X=new Qu(D,$,Y,je),Te=new Ju(D,Ne,y),J=new ku(Me),ge=new mf(S,_,N,Ie,Ne,Ze,J),se=new Vf(S,Me),he=new vf,Be=new Tf(Ie),Ee=new zu(S,_,N,Se,X,m,l),ue=new Cf(S,X,Ne),C=new kf(D,je,Ne,Se),de=new Gu(D,Ie,je),Fe=new $u(D,Ie,je),je.programs=ge.programs,S.capabilities=Ne,S.extensions=Ie,S.properties=Me,S.renderLists=he,S.shadowMap=ue,S.state=Se,S.info=je}ne();const G=new Hf(S,D);this.xr=G,this.getContext=function(){return D},this.getContextAttributes=function(){return D.getContextAttributes()},this.forceContextLoss=function(){const g=Ie.get("WEBGL_lose_context");g&&g.loseContext()},this.forceContextRestore=function(){const g=Ie.get("WEBGL_lose_context");g&&g.restoreContext()},this.getPixelRatio=function(){return V},this.setPixelRatio=function(g){g!==void 0&&(V=g,this.setSize(k,j,!1))},this.getSize=function(g){return g.set(k,j)},this.setSize=function(g,P,O=!0){if(G.isPresenting){console.warn("THREE.WebGLRenderer: Can't change size while VR device is presenting.");return}k=g,j=P,t.width=Math.floor(g*V),t.height=Math.floor(P*V),O===!0&&(t.style.width=g+"px",t.style.height=P+"px"),this.setViewport(0,0,g,P)},this.getDrawingBufferSize=function(g){return g.set(k*V,j*V).floor()},this.setDrawingBufferSize=function(g,P,O){k=g,j=P,V=O,t.width=Math.floor(g*O),t.height=Math.floor(P*O),this.setViewport(0,0,g,P)},this.getCurrentViewport=function(g){return g.copy(w)},this.getViewport=function(g){return g.copy(xe)},this.setViewport=function(g,P,O,B){g.isVector4?xe.set(g.x,g.y,g.z,g.w):xe.set(g,P,O,B),Se.viewport(w.copy(xe).multiplyScalar(V).round())},this.getScissor=function(g){return g.copy(De)},this.setScissor=function(g,P,O,B){g.isVector4?De.set(g.x,g.y,g.z,g.w):De.set(g,P,O,B),Se.scissor(H.copy(De).multiplyScalar(V).round())},this.getScissorTest=function(){return Ke},this.setScissorTest=function(g){Se.setScissorTest(Ke=g)},this.setOpaqueSort=function(g){ie=g},this.setTransparentSort=function(g){ce=g},this.getClearColor=function(g){return g.copy(Ee.getClearColor())},this.setClearColor=function(){Ee.setClearColor.apply(Ee,arguments)},this.getClearAlpha=function(){return Ee.getClearAlpha()},this.setClearAlpha=function(){Ee.setClearAlpha.apply(Ee,arguments)},this.clear=function(g=!0,P=!0,O=!0){let B=0;if(g){let L=!1;if(U!==null){const Q=U.texture.format;L=Q===Bs||Q===Os||Q===Fs}if(L){const Q=U.texture.type,oe=Q===nn||Q===Ln||Q===Mi||Q===ii||Q===Is||Q===Ns,fe=Ee.getClearColor(),pe=Ee.getClearAlpha(),Ae=fe.r,Re=fe.g,me=fe.b;oe?(v[0]=Ae,v[1]=Re,v[2]=me,v[3]=pe,D.clearBufferuiv(D.COLOR,0,v)):(M[0]=Ae,M[1]=Re,M[2]=me,M[3]=pe,D.clearBufferiv(D.COLOR,0,M))}else B|=D.COLOR_BUFFER_BIT}P&&(B|=D.DEPTH_BUFFER_BIT),O&&(B|=D.STENCIL_BUFFER_BIT,this.state.buffers.stencil.setMask(4294967295)),D.clear(B)},this.clearColor=function(){this.clear(!0,!1,!1)},this.clearDepth=function(){this.clear(!1,!0,!1)},this.clearStencil=function(){this.clear(!1,!1,!0)},this.dispose=function(){t.removeEventListener("webglcontextlost",K,!1),t.removeEventListener("webglcontextrestored",le,!1),t.removeEventListener("webglcontextcreationerror",ae,!1),he.dispose(),Be.dispose(),Me.dispose(),_.dispose(),N.dispose(),X.dispose(),Ze.dispose(),C.dispose(),ge.dispose(),G.dispose(),G.removeEventListener("sessionstart",Ws),G.removeEventListener("sessionend",Xs),xn.stop()};function K(g){g.preventDefault(),console.log("THREE.WebGLRenderer: Context Lost."),F=!0}function le(){console.log("THREE.WebGLRenderer: Context Restored."),F=!1;const g=je.autoReset,P=ue.enabled,O=ue.autoUpdate,B=ue.needsUpdate,L=ue.type;ne(),je.autoReset=g,ue.enabled=P,ue.autoUpdate=O,ue.needsUpdate=B,ue.type=L}function ae(g){console.error("THREE.WebGLRenderer: A WebGL context could not be created. Reason: ",g.statusMessage)}function we(g){const P=g.target;P.removeEventListener("dispose",we),nt(P)}function nt(g){ct(g),Me.remove(g)}function ct(g){const P=Me.get(g).programs;P!==void 0&&(P.forEach(function(O){ge.releaseProgram(O)}),g.isShaderMaterial&&ge.releaseShaderCache(g))}this.renderBufferDirect=function(g,P,O,B,L,Q){P===null&&(P=tt);const oe=L.isMesh&&L.matrixWorld.determinant()<0,fe=Bo(g,P,O,B,L);Se.setMaterial(B,oe);let pe=O.index,Ae=1;if(B.wireframe===!0){if(pe=$.getWireframeAttribute(O),pe===void 0)return;Ae=2}const Re=O.drawRange,me=O.attributes.position;let Ge=Re.start*Ae,$e=(Re.start+Re.count)*Ae;Q!==null&&(Ge=Math.max(Ge,Q.start*Ae),$e=Math.min($e,(Q.start+Q.count)*Ae)),pe!==null?(Ge=Math.max(Ge,0),$e=Math.min($e,pe.count)):me!=null&&(Ge=Math.max(Ge,0),$e=Math.min($e,me.count));const Je=$e-Ge;if(Je<0||Je===1/0)return;Ze.setup(L,B,fe,O,pe);let mt,ke=de;if(pe!==null&&(mt=Y.get(pe),ke=Fe,ke.setIndex(mt)),L.isMesh)B.wireframe===!0?(Se.setLineWidth(B.wireframeLinewidth*it()),ke.setMode(D.LINES)):ke.setMode(D.TRIANGLES);else if(L.isLine){let ve=B.linewidth;ve===void 0&&(ve=1),Se.setLineWidth(ve*it()),L.isLineSegments?ke.setMode(D.LINES):L.isLineLoop?ke.setMode(D.LINE_LOOP):ke.setMode(D.LINE_STRIP)}else L.isPoints?ke.setMode(D.POINTS):L.isSprite&&ke.setMode(D.TRIANGLES);if(L.isBatchedMesh)if(L._multiDrawInstances!==null)ke.renderMultiDrawInstances(L._multiDrawStarts,L._multiDrawCounts,L._multiDrawCount,L._multiDrawInstances);else if(Ie.get("WEBGL_multi_draw"))ke.renderMultiDraw(L._multiDrawStarts,L._multiDrawCounts,L._multiDrawCount);else{const ve=L._multiDrawStarts,Xt=L._multiDrawCounts,We=L._multiDrawCount,Pt=pe?Y.get(pe).bytesPerElement:1,Un=Me.get(B).currentProgram.getUniforms();for(let Mt=0;Mt{function Q(){if(B.forEach(function(oe){Me.get(oe).currentProgram.isReady()&&B.delete(oe)}),B.size===0){L(g);return}setTimeout(Q,10)}Ie.get("KHR_parallel_shader_compile")!==null?Q():setTimeout(Q,10)})};let Ct=null;function Wt(g){Ct&&Ct(g)}function Ws(){xn.stop()}function Xs(){xn.start()}const xn=new Po;xn.setAnimationLoop(Wt),typeof self<"u"&&xn.setContext(self),this.setAnimationLoop=function(g){Ct=g,G.setAnimationLoop(g),g===null?xn.stop():xn.start()},G.addEventListener("sessionstart",Ws),G.addEventListener("sessionend",Xs),this.render=function(g,P){if(P!==void 0&&P.isCamera!==!0){console.error("THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.");return}if(F===!0)return;if(g.matrixWorldAutoUpdate===!0&&g.updateMatrixWorld(),P.parent===null&&P.matrixWorldAutoUpdate===!0&&P.updateMatrixWorld(),G.enabled===!0&&G.isPresenting===!0&&(G.cameraAutoUpdate===!0&&G.updateCamera(P),P=G.getCamera()),g.isScene===!0&&g.onBeforeRender(S,g,P,U),c=Be.get(g,T.length),c.init(P),T.push(c),ye.multiplyMatrices(P.projectionMatrix,P.matrixWorldInverse),q.setFromProjectionMatrix(ye),_e=this.localClippingEnabled,ee=J.init(this.clippingPlanes,_e),f=he.get(g,A.length),f.init(),A.push(f),G.enabled===!0&&G.isPresenting===!0){const Q=S.xr.getDepthSensingMesh();Q!==null&&hr(Q,P,-1/0,S.sortObjects)}hr(g,P,0,S.sortObjects),f.finish(),S.sortObjects===!0&&f.sort(ie,ce),Oe=G.enabled===!1||G.isPresenting===!1||G.hasDepthSensing()===!1,Oe&&Ee.addToRenderList(f,g),this.info.render.frame++,ee===!0&&J.beginShadows();const O=c.state.shadowsArray;ue.render(O,g,P),ee===!0&&J.endShadows(),this.info.autoReset===!0&&this.info.reset();const B=f.opaque,L=f.transmissive;if(c.setupLights(),P.isArrayCamera){const Q=P.cameras;if(L.length>0)for(let oe=0,fe=Q.length;oe0&&Ys(B,L,g,P),Oe&&Ee.render(g),qs(f,g,P);U!==null&&(y.updateMultisampleRenderTarget(U),y.updateRenderTargetMipmap(U)),g.isScene===!0&&g.onAfterRender(S,g,P),Ze.resetDefaultState(),E=-1,x=null,T.pop(),T.length>0?(c=T[T.length-1],ee===!0&&J.setGlobalState(S.clippingPlanes,c.state.camera)):c=null,A.pop(),A.length>0?f=A[A.length-1]:f=null};function hr(g,P,O,B){if(g.visible===!1)return;if(g.layers.test(P.layers)){if(g.isGroup)O=g.renderOrder;else if(g.isLOD)g.autoUpdate===!0&&g.update(P);else if(g.isLight)c.pushLight(g),g.castShadow&&c.pushShadow(g);else if(g.isSprite){if(!g.frustumCulled||q.intersectsSprite(g)){B&&Ue.setFromMatrixPosition(g.matrixWorld).applyMatrix4(ye);const oe=X.update(g),fe=g.material;fe.visible&&f.push(g,oe,fe,O,Ue.z,null)}}else if((g.isMesh||g.isLine||g.isPoints)&&(!g.frustumCulled||q.intersectsObject(g))){const oe=X.update(g),fe=g.material;if(B&&(g.boundingSphere!==void 0?(g.boundingSphere===null&&g.computeBoundingSphere(),Ue.copy(g.boundingSphere.center)):(oe.boundingSphere===null&&oe.computeBoundingSphere(),Ue.copy(oe.boundingSphere.center)),Ue.applyMatrix4(g.matrixWorld).applyMatrix4(ye)),Array.isArray(fe)){const pe=oe.groups;for(let Ae=0,Re=pe.length;Ae0&&wi(L,P,O),Q.length>0&&wi(Q,P,O),oe.length>0&&wi(oe,P,O),Se.buffers.depth.setTest(!0),Se.buffers.depth.setMask(!0),Se.buffers.color.setMask(!0),Se.setPolygonOffset(!1)}function Ys(g,P,O,B){if((O.isScene===!0?O.overrideMaterial:null)!==null)return;c.state.transmissionRenderTarget[B.id]===void 0&&(c.state.transmissionRenderTarget[B.id]=new Dn(1,1,{generateMipmaps:!0,type:Ie.has("EXT_color_buffer_half_float")||Ie.has("EXT_color_buffer_float")?Si:nn,minFilter:Pn,samples:4,stencilBuffer:s,resolveDepthBuffer:!1,resolveStencilBuffer:!1,colorSpace:ze.workingColorSpace}));const Q=c.state.transmissionRenderTarget[B.id],oe=B.viewport||w;Q.setSize(oe.z,oe.w);const fe=S.getRenderTarget();S.setRenderTarget(Q),S.getClearColor(W),Z=S.getClearAlpha(),Z<1&&S.setClearColor(16777215,.5),S.clear(),Oe&&Ee.render(O);const pe=S.toneMapping;S.toneMapping=_n;const Ae=B.viewport;if(B.viewport!==void 0&&(B.viewport=void 0),c.setupLightsView(B),ee===!0&&J.setGlobalState(S.clippingPlanes,B),wi(g,O,B),y.updateMultisampleRenderTarget(Q),y.updateRenderTargetMipmap(Q),Ie.has("WEBGL_multisampled_render_to_texture")===!1){let Re=!1;for(let me=0,Ge=P.length;me0),me=!!O.morphAttributes.position,Ge=!!O.morphAttributes.normal,$e=!!O.morphAttributes.color;let Je=_n;B.toneMapped&&(U===null||U.isXRRenderTarget===!0)&&(Je=S.toneMapping);const mt=O.morphAttributes.position||O.morphAttributes.normal||O.morphAttributes.color,ke=mt!==void 0?mt.length:0,ve=Me.get(B),Xt=c.state.lights;if(ee===!0&&(_e===!0||g!==x)){const bt=g===x&&B.id===E;J.setState(B,g,bt)}let We=!1;B.version===ve.__version?(ve.needsLights&&ve.lightsStateVersion!==Xt.state.version||ve.outputColorSpace!==fe||L.isBatchedMesh&&ve.batching===!1||!L.isBatchedMesh&&ve.batching===!0||L.isBatchedMesh&&ve.batchingColor===!0&&L.colorTexture===null||L.isBatchedMesh&&ve.batchingColor===!1&&L.colorTexture!==null||L.isInstancedMesh&&ve.instancing===!1||!L.isInstancedMesh&&ve.instancing===!0||L.isSkinnedMesh&&ve.skinning===!1||!L.isSkinnedMesh&&ve.skinning===!0||L.isInstancedMesh&&ve.instancingColor===!0&&L.instanceColor===null||L.isInstancedMesh&&ve.instancingColor===!1&&L.instanceColor!==null||L.isInstancedMesh&&ve.instancingMorph===!0&&L.morphTexture===null||L.isInstancedMesh&&ve.instancingMorph===!1&&L.morphTexture!==null||ve.envMap!==pe||B.fog===!0&&ve.fog!==Q||ve.numClippingPlanes!==void 0&&(ve.numClippingPlanes!==J.numPlanes||ve.numIntersection!==J.numIntersection)||ve.vertexAlphas!==Ae||ve.vertexTangents!==Re||ve.morphTargets!==me||ve.morphNormals!==Ge||ve.morphColors!==$e||ve.toneMapping!==Je||ve.morphTargetsCount!==ke)&&(We=!0):(We=!0,ve.__version=B.version);let Pt=ve.currentProgram;We===!0&&(Pt=Ri(B,P,L));let Un=!1,Mt=!1,ci=!1;const Qe=Pt.getUniforms(),Bt=ve.uniforms;if(Se.useProgram(Pt.program)&&(Un=!0,Mt=!0,ci=!0),B.id!==E&&(E=B.id,Mt=!0),Un||x!==g){Se.buffers.depth.getReversed()?(re.copy(g.projectionMatrix),Ul(re),Il(re),Qe.setValue(D,"projectionMatrix",re)):Qe.setValue(D,"projectionMatrix",g.projectionMatrix),Qe.setValue(D,"viewMatrix",g.matrixWorldInverse);const sn=Qe.map.cameraPosition;sn!==void 0&&sn.setValue(D,be.setFromMatrixPosition(g.matrixWorld)),Ne.logarithmicDepthBuffer&&Qe.setValue(D,"logDepthBufFC",2/(Math.log(g.far+1)/Math.LN2)),(B.isMeshPhongMaterial||B.isMeshToonMaterial||B.isMeshLambertMaterial||B.isMeshBasicMaterial||B.isMeshStandardMaterial||B.isShaderMaterial)&&Qe.setValue(D,"isOrthographic",g.isOrthographicCamera===!0),x!==g&&(x=g,Mt=!0,ci=!0)}if(L.isSkinnedMesh){Qe.setOptional(D,L,"bindMatrix"),Qe.setOptional(D,L,"bindMatrixInverse");const bt=L.skeleton;bt&&(bt.boneTexture===null&&bt.computeBoneTexture(),Qe.setValue(D,"boneTexture",bt.boneTexture,y))}L.isBatchedMesh&&(Qe.setOptional(D,L,"batchingTexture"),Qe.setValue(D,"batchingTexture",L._matricesTexture,y),Qe.setOptional(D,L,"batchingIdTexture"),Qe.setValue(D,"batchingIdTexture",L._indirectTexture,y),Qe.setOptional(D,L,"batchingColorTexture"),L._colorsTexture!==null&&Qe.setValue(D,"batchingColorTexture",L._colorsTexture,y));const hi=O.morphAttributes;if((hi.position!==void 0||hi.normal!==void 0||hi.color!==void 0)&&Te.update(L,O,Pt),(Mt||ve.receiveShadow!==L.receiveShadow)&&(ve.receiveShadow=L.receiveShadow,Qe.setValue(D,"receiveShadow",L.receiveShadow)),B.isMeshGouraudMaterial&&B.envMap!==null&&(Bt.envMap.value=pe,Bt.flipEnvMap.value=pe.isCubeTexture&&pe.isRenderTargetTexture===!1?-1:1),B.isMeshStandardMaterial&&B.envMap===null&&P.environment!==null&&(Bt.envMapIntensity.value=P.environmentIntensity),Mt&&(Qe.setValue(D,"toneMappingExposure",S.toneMappingExposure),ve.needsLights&&zo(Bt,ci),Q&&B.fog===!0&&se.refreshFogUniforms(Bt,Q),se.refreshMaterialUniforms(Bt,B,V,j,c.state.transmissionRenderTarget[g.id]),ir.upload(D,Zs(ve),Bt,y)),B.isShaderMaterial&&B.uniformsNeedUpdate===!0&&(ir.upload(D,Zs(ve),Bt,y),B.uniformsNeedUpdate=!1),B.isSpriteMaterial&&Qe.setValue(D,"center",L.center),Qe.setValue(D,"modelViewMatrix",L.modelViewMatrix),Qe.setValue(D,"normalMatrix",L.normalMatrix),Qe.setValue(D,"modelMatrix",L.matrixWorld),B.isShaderMaterial||B.isRawShaderMaterial){const bt=B.uniformsGroups;for(let sn=0,an=bt.length;sn0&&y.useMultisampledRTT(g)===!1?L=Me.get(g).__webglMultisampledFramebuffer:Array.isArray(Re)?L=Re[O]:L=Re,w.copy(g.viewport),H.copy(g.scissor),z=g.scissorTest}else w.copy(xe).multiplyScalar(V).floor(),H.copy(De).multiplyScalar(V).floor(),z=Ke;if(Se.bindFramebuffer(D.FRAMEBUFFER,L)&&B&&Se.drawBuffers(g,L),Se.viewport(w),Se.scissor(H),Se.setScissorTest(z),Q){const pe=Me.get(g.texture);D.framebufferTexture2D(D.FRAMEBUFFER,D.COLOR_ATTACHMENT0,D.TEXTURE_CUBE_MAP_POSITIVE_X+P,pe.__webglTexture,O)}else if(oe){const pe=Me.get(g.texture),Ae=P||0;D.framebufferTextureLayer(D.FRAMEBUFFER,D.COLOR_ATTACHMENT0,pe.__webglTexture,O||0,Ae)}E=-1},this.readRenderTargetPixels=function(g,P,O,B,L,Q,oe){if(!(g&&g.isWebGLRenderTarget)){console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");return}let fe=Me.get(g).__webglFramebuffer;if(g.isWebGLCubeRenderTarget&&oe!==void 0&&(fe=fe[oe]),fe){Se.bindFramebuffer(D.FRAMEBUFFER,fe);try{const pe=g.texture,Ae=pe.format,Re=pe.type;if(!Ne.textureFormatReadable(Ae)){console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");return}if(!Ne.textureTypeReadable(Re)){console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");return}P>=0&&P<=g.width-B&&O>=0&&O<=g.height-L&&D.readPixels(P,O,B,L,Pe.convert(Ae),Pe.convert(Re),Q)}finally{const pe=U!==null?Me.get(U).__webglFramebuffer:null;Se.bindFramebuffer(D.FRAMEBUFFER,pe)}}},this.readRenderTargetPixelsAsync=async function(g,P,O,B,L,Q,oe){if(!(g&&g.isWebGLRenderTarget))throw new Error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let fe=Me.get(g).__webglFramebuffer;if(g.isWebGLCubeRenderTarget&&oe!==void 0&&(fe=fe[oe]),fe){const pe=g.texture,Ae=pe.format,Re=pe.type;if(!Ne.textureFormatReadable(Ae))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.");if(!Ne.textureTypeReadable(Re))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.");if(P>=0&&P<=g.width-B&&O>=0&&O<=g.height-L){Se.bindFramebuffer(D.FRAMEBUFFER,fe);const me=D.createBuffer();D.bindBuffer(D.PIXEL_PACK_BUFFER,me),D.bufferData(D.PIXEL_PACK_BUFFER,Q.byteLength,D.STREAM_READ),D.readPixels(P,O,B,L,Pe.convert(Ae),Pe.convert(Re),0);const Ge=U!==null?Me.get(U).__webglFramebuffer:null;Se.bindFramebuffer(D.FRAMEBUFFER,Ge);const $e=D.fenceSync(D.SYNC_GPU_COMMANDS_COMPLETE,0);return D.flush(),await Dl(D,$e,4),D.bindBuffer(D.PIXEL_PACK_BUFFER,me),D.getBufferSubData(D.PIXEL_PACK_BUFFER,0,Q),D.deleteBuffer(me),D.deleteSync($e),Q}else throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.")}},this.copyFramebufferToTexture=function(g,P=null,O=0){g.isTexture!==!0&&(gi("WebGLRenderer: copyFramebufferToTexture function signature has changed."),P=arguments[0]||null,g=arguments[1]);const B=Math.pow(2,-O),L=Math.floor(g.image.width*B),Q=Math.floor(g.image.height*B),oe=P!==null?P.x:0,fe=P!==null?P.y:0;y.setTexture2D(g,0),D.copyTexSubImage2D(D.TEXTURE_2D,O,0,0,oe,fe,L,Q),Se.unbindTexture()},this.copyTextureToTexture=function(g,P,O=null,B=null,L=0){g.isTexture!==!0&&(gi("WebGLRenderer: copyTextureToTexture function signature has changed."),B=arguments[0]||null,g=arguments[1],P=arguments[2],L=arguments[3]||0,O=null);let Q,oe,fe,pe,Ae,Re,me,Ge,$e;const Je=g.isCompressedTexture?g.mipmaps[L]:g.image;O!==null?(Q=O.max.x-O.min.x,oe=O.max.y-O.min.y,fe=O.isBox3?O.max.z-O.min.z:1,pe=O.min.x,Ae=O.min.y,Re=O.isBox3?O.min.z:0):(Q=Je.width,oe=Je.height,fe=Je.depth||1,pe=0,Ae=0,Re=0),B!==null?(me=B.x,Ge=B.y,$e=B.z):(me=0,Ge=0,$e=0);const mt=Pe.convert(P.format),ke=Pe.convert(P.type);let ve;P.isData3DTexture?(y.setTexture3D(P,0),ve=D.TEXTURE_3D):P.isDataArrayTexture||P.isCompressedArrayTexture?(y.setTexture2DArray(P,0),ve=D.TEXTURE_2D_ARRAY):(y.setTexture2D(P,0),ve=D.TEXTURE_2D),D.pixelStorei(D.UNPACK_FLIP_Y_WEBGL,P.flipY),D.pixelStorei(D.UNPACK_PREMULTIPLY_ALPHA_WEBGL,P.premultiplyAlpha),D.pixelStorei(D.UNPACK_ALIGNMENT,P.unpackAlignment);const Xt=D.getParameter(D.UNPACK_ROW_LENGTH),We=D.getParameter(D.UNPACK_IMAGE_HEIGHT),Pt=D.getParameter(D.UNPACK_SKIP_PIXELS),Un=D.getParameter(D.UNPACK_SKIP_ROWS),Mt=D.getParameter(D.UNPACK_SKIP_IMAGES);D.pixelStorei(D.UNPACK_ROW_LENGTH,Je.width),D.pixelStorei(D.UNPACK_IMAGE_HEIGHT,Je.height),D.pixelStorei(D.UNPACK_SKIP_PIXELS,pe),D.pixelStorei(D.UNPACK_SKIP_ROWS,Ae),D.pixelStorei(D.UNPACK_SKIP_IMAGES,Re);const ci=g.isDataArrayTexture||g.isData3DTexture,Qe=P.isDataArrayTexture||P.isData3DTexture;if(g.isRenderTargetTexture||g.isDepthTexture){const Bt=Me.get(g),hi=Me.get(P),bt=Me.get(Bt.__renderTarget),sn=Me.get(hi.__renderTarget);Se.bindFramebuffer(D.READ_FRAMEBUFFER,bt.__webglFramebuffer),Se.bindFramebuffer(D.DRAW_FRAMEBUFFER,sn.__webglFramebuffer);for(let an=0;an0&&(t.object.backgroundBlurriness=this.backgroundBlurriness),this.backgroundIntensity!==1&&(t.object.backgroundIntensity=this.backgroundIntensity),t.object.backgroundRotation=this.backgroundRotation.toArray(),this.environmentIntensity!==1&&(t.object.environmentIntensity=this.environmentIntensity),t.object.environmentRotation=this.environmentRotation.toArray(),t}}class xi extends rn{constructor(e=1,t=1,n=1,r=32,s=1,a=!1,o=0,l=Math.PI*2){super(),this.type="CylinderGeometry",this.parameters={radiusTop:e,radiusBottom:t,height:n,radialSegments:r,heightSegments:s,openEnded:a,thetaStart:o,thetaLength:l};const h=this;r=Math.floor(r),s=Math.floor(s);const u=[],d=[],p=[],m=[];let v=0;const M=[],f=n/2;let c=0;A(),a===!1&&(e>0&&T(!0),t>0&&T(!1)),this.setIndex(u),this.setAttribute("position",new Tt(d,3)),this.setAttribute("normal",new Tt(p,3)),this.setAttribute("uv",new Tt(m,2));function A(){const S=new I,F=new I;let R=0;const b=(t-e)/n;for(let U=0;U<=s;U++){const E=[],x=U/s,w=x*(t-e)+e;for(let H=0;H<=r;H++){const z=H/r,W=z*l+o,Z=Math.sin(W),k=Math.cos(W);F.x=w*Z,F.y=-x*n+f,F.z=w*k,d.push(F.x,F.y,F.z),S.set(Z,b,k).normalize(),p.push(S.x,S.y,S.z),m.push(z,1-x),E.push(v++)}M.push(E)}for(let U=0;U0||E!==0)&&(u.push(x,w,z),R+=3),(t>0||E!==s-1)&&(u.push(w,H,z),R+=3)}h.addGroup(c,R,0),c+=R}function T(S){const F=v,R=new He,b=new I;let U=0;const E=S===!0?e:t,x=S===!0?1:-1;for(let H=1;H<=r;H++)d.push(0,f*x,0),p.push(0,x,0),m.push(.5,.5),v++;const w=v;for(let H=0;H<=r;H++){const W=H/r*l+o,Z=Math.cos(W),k=Math.sin(W);b.x=E*k,b.y=f*x,b.z=E*Z,d.push(b.x,b.y,b.z),p.push(0,x,0),R.x=Z*.5+.5,R.y=k*.5*x+.5,m.push(R.x,R.y),v++}for(let H=0;H0)&&m.push(T,S,R),(c!==n-1||l{this.camera.aspect=window.innerWidth/window.innerHeight,this.camera.updateProjectionMatrix(),this.renderer.setSize(window.innerWidth,window.innerHeight)})}buildMap(e){var M;for(const f of this.wallMeshes)this.scene.remove(f),f.geometry.dispose(),f.material.dispose();this.wallMeshes=[];for(const[,f]of this.nutWallMeshes)this.scene.remove(f.mesh),f.mesh.geometry.dispose(),f.mesh.material.dispose();this.nutWallMeshes.clear();const t=e.length,n=((M=e[0])==null?void 0:M.length)||0,r=new bi(n,t),s=new It({color:2763322}),a=new rt(r,s);a.rotation.x=-Math.PI/2,a.position.set(n/2,0,t/2),a.receiveShadow=!0,this.scene.add(a),this.wallMeshes.push(a);const o=new Ht(1,1.5,1),l=new It({color:5592439}),h=new Ht(1,1.2,1),u=new It({color:9136404}),d=new Ht(1,.1,1),p=new It({color:65416,transparent:!0,opacity:.5}),m=new Ht(1,.1,1),v=new It({color:16729156,transparent:!0,opacity:.4});for(let f=0;f{e.geometry&&e.geometry.dispose(),e.material&&(Array.isArray(e.material)?e.material.forEach(t=>t.dispose()):e.material.dispose())})}}class Qf{constructor(){this.ws=null,this.connected=!1,this.handlers=new Map}async connect(e){return new Promise((t,n)=>{this.ws=new WebSocket(e),this.ws.onopen=()=>{this.connected=!0,t()},this.ws.onerror=r=>{n(r)},this.ws.onclose=()=>{this.connected=!1},this.ws.onmessage=r=>{try{const s=JSON.parse(r.data),a=this.handlers.get(s.type);if(a)for(const o of a)o(s.data||{})}catch(s){console.error("Failed to parse message:",s)}}})}disconnect(){this.ws&&(this.ws.close(),this.ws=null,this.connected=!1)}on(e,t){this.handlers.has(e)||this.handlers.set(e,[]),this.handlers.get(e).push(t)}send(e,t={}){this.ws&&this.connected&&this.ws.send(JSON.stringify({type:e,data:t}))}createRoom(e){this.send(dt.CREATE_ROOM,{playerName:e})}joinRoom(e,t){this.send(dt.JOIN_ROOM,{roomId:e,playerName:t})}leaveRoom(){this.send(dt.LEAVE_ROOM)}requestRoomList(){this.send(dt.ROOM_LIST)}ready(){this.send(dt.READY)}startGame(){this.send(dt.START_GAME)}sendInput(e){this.send(dt.PLAYER_INPUT,e)}}class ep{constructor(e){this.canvas=e,this.scene=new Jf(e),this.input=new ko,this.network=new Qf,this.grid=null,this.mapData=null,this.localPlayerId=null,this.players=new Map,this.zombies=new Map,this.running=!1,this.lastTick=0,this.accumulator=0,this.pendingInputs=[],this.gameTime=0,this.onStateUpdate=null}async connect(e){await this.network.connect(e),this._setupNetworkHandlers()}_setupNetworkHandlers(){this.network.on(dt.GAME_STARTED,e=>{if(!e.mapData){console.error("Server did not provide map data / 服务器未提供地图数据"),alert("Failed to load map data from server. / 无法从服务器加载地图数据。");return}this.localPlayerId=e.playerId,this.mapData=e.mapData,this.grid=new Vo(this.mapData),this.scene.buildMap(this.mapData),this._initPlayers(e.players),this.start(),this.onStateUpdate&&this.onStateUpdate("game_started",e)}),this.network.on(dt.GAME_STATE,e=>{this._processServerState(e)}),this.network.on(dt.PLAYER_JOIN,e=>{this._addPlayer(e)}),this.network.on(dt.PLAYER_LEAVE,e=>{this._removePlayer(e)}),this.network.on(dt.ERROR,e=>{console.error("Server error:",e.message),alert(e.message)})}_initPlayers(e){const t=[4491519,16729156,4521796,16777028];for(const n of e){const r=n.id===this.localPlayerId,s=t[n.index%t.length];this.players.set(n.id,{id:n.id,name:n.name,x:n.x,y:n.y,angle:0,health:Pi.MAX_HEALTH,color:s,isLocal:r}),this.scene.addPlayer(n.id,n.x,n.y,s,r)}}_addPlayer(e){const t=[4491519,16729156,4521796,16777028],n=e.id===this.localPlayerId,r=t[e.index%t.length];this.players.set(e.id,{id:e.id,name:e.name,x:e.x,y:e.y,angle:0,health:Pi.MAX_HEALTH,color:r,isLocal:n}),this.scene.addPlayer(e.id,e.x,e.y,r,n)}_removePlayer(e){this.players.delete(e.id),this.scene.removePlayer(e.id)}start(){this.running||(this.running=!0,this.input.attach(),this.lastTick=performance.now(),this._loop())}stop(){this.running=!1,this.input.detach()}_loop(){if(!this.running)return;requestAnimationFrame(()=>this._loop());const e=performance.now(),t=e-this.lastTick;for(this.lastTick=e,this.accumulator+=t;this.accumulator>=Ci;)this._tick(),this.accumulator-=Ci;const n=this.players.get(this.localPlayerId);n&&this.scene.updateCamera(n.x,n.y),this.scene.render()}_tick(){if(!this.localPlayerId)return;const e=this.players.get(this.localPlayerId);if(!e||e.health<=0)return;const t=this.scene.getMouseGroundPos(this.input.mouse.x,this.input.mouse.y);this.input.mouse.groundX=t.x,this.input.mouse.groundY=t.y;const n=this.input.buildInputState(t);this._applyLocalPrediction(n),this.pendingInputs.push(n),this.pendingInputs.length>60&&this.pendingInputs.splice(0,this.pendingInputs.length-60),this.network.sendInput(n)}_applyLocalPrediction(e){const t=this.players.get(this.localPlayerId);if(!t)return;const n=Pi.SPEED,r=Ci/1e3;let s=t.x+e.dx*n*r,a=t.y+e.dy*n*r;this.grid&&(this.grid.isWalkable(s,t.y,.8)||(s=t.x),this.grid.isWalkable(t.x,a,.8)||(a=t.y));const o=this.grid.width-.5,l=this.grid.height-.5;s=Math.max(.5,Math.min(o,s)),a=Math.max(.5,Math.min(l,a)),t.x=s,t.y=a;const h=e.aimX-t.x,u=e.aimY-t.y;t.angle=Math.atan2(h,u),this.scene.updatePlayer(t.id,t.x,t.y,t.angle,t.health)}_processServerState(e){if(e.players)for(const t of e.players){const n=this.players.get(t.id);n&&(t.id===this.localPlayerId?this._reconcileLocalPlayer(t):(n.x=t.x,n.y=t.y,n.angle=t.angle,n.health=t.health,this.scene.updatePlayer(t.id,t.x,t.y,t.angle,t.health)))}if(e.zombies){const t=new Set;for(const n of e.zombies){const r=n.id;if(t.add(r),!this.zombies.has(r))this.zombies.set(r,{id:r,x:n.x,y:n.y}),this.scene.addZombie(r,n.x,n.y);else{const s=this.zombies.get(r);s.x=n.x,s.y=n.y,this.scene.updateZombie(r,n.x,n.y,n.angle)}}for(const[n]of this.zombies)t.has(n)||(this.zombies.delete(n),this.scene.removeZombie(n))}if(e.nutWalls)for(const t of e.nutWalls)this.scene.updateNutWall(t.x,t.y,t.health,t.maxHealth);e.gameTime!==void 0&&(this.gameTime=e.gameTime),this.onStateUpdate&&this.onStateUpdate("state",e)}_reconcileLocalPlayer(e){const t=this.players.get(this.localPlayerId);if(!t)return;let n=e.lastProcessedSeq||0;if(t.x=e.x,t.y=e.y,t.angle=e.angle,t.health=e.health,this.pendingInputs=this.pendingInputs.filter(r=>r.seq>n),t.health>0){const r=Pi.SPEED,s=Ci/1e3;for(const a of this.pendingInputs){let o=t.x+a.dx*r*s,l=t.y+a.dy*r*s;this.grid&&(this.grid.isWalkable(o,t.y,.8)||(o=t.x),this.grid.isWalkable(t.x,l,.8)||(l=t.y));const h=this.grid.width-.5,u=this.grid.height-.5;o=Math.max(.5,Math.min(h,o)),l=Math.max(.5,Math.min(u,l)),t.x=o,t.y=l;const d=a.aimX-t.x,p=a.aimY-t.y;t.angle=Math.atan2(d,p)}}this.scene.updatePlayer(t.id,t.x,t.y,t.angle,t.health)}destroy(){this.stop(),this.network.disconnect(),this.scene.destroy()}}class tp{constructor(e){this.container=e,this.onCreateRoom=null,this.onJoinRoom=null,this.onRefreshRooms=null,this.onReady=null,this.onStartGame=null,this.onLeaveRoom=null,this.render()}render(){this.container.innerHTML=` +
+

ZP2

+
+ +
+ + +
+
+
+
+ `,this.container.querySelector("#createRoom").onclick=()=>{const e=this.container.querySelector("#playerName").value.trim()||"Player";this.onCreateRoom&&this.onCreateRoom(e)},this.container.querySelector("#refreshRooms").onclick=()=>{this.onRefreshRooms&&this.onRefreshRooms()},this.container.style.display="block"}updateRoomList(e){const t=this.container.querySelector("#roomList");if(!e||e.length===0){t.innerHTML='

No rooms available

';return}t.innerHTML=e.map(n=>` +
+ ${n.hostName||n.id}'s Room + ${n.playerCount}/4 + +
+ `).join(""),t.querySelectorAll(".join-btn").forEach(n=>{n.onclick=()=>{const r=this.container.querySelector("#playerName").value.trim()||"Player";this.onJoinRoom&&this.onJoinRoom(n.dataset.id,r)}})}showRoom(e,t,n){const r=e.players||[];this.container.innerHTML=` +
+

${e.roomName||e.roomId}

+
+ ${r.map(a=>` +
+ ${a.name}${a.id===n?" (you)":""} + ${a.ready?"READY":"NOT READY"} +
+ `).join("")} +
+
+ + ${t?'':""} + +
+
+ `,this.container.querySelector("#readyBtn").onclick=()=>{this.onReady&&this.onReady()};const s=this.container.querySelector("#startBtn");s&&(s.onclick=()=>{this.onStartGame&&this.onStartGame()}),this.container.querySelector("#leaveBtn").onclick=()=>{this.onLeaveRoom&&this.onLeaveRoom()}}}const no=`ws://${window.location.hostname}:8080/ws`;class np{constructor(){this.appEl=document.getElementById("app"),this.lobbyEl=document.createElement("div"),this.gameCanvasEl=document.createElement("canvas"),this.appEl.appendChild(this.lobbyEl),this.appEl.appendChild(this.gameCanvasEl),this.gameCanvasEl.style.display="none",this.engine=null,this.lobby=new tp(this.lobbyEl),this.playerId=null,this.roomId=null,this.isHost=!1,this.playerName="",this._setupLobby()}_setupLobby(){this.lobby.onCreateRoom=e=>{this.playerName=e,this._ensureConnection().then(()=>{this.engine.network.createRoom(e)})},this.lobby.onJoinRoom=(e,t)=>{this.playerName=t,this._ensureConnection().then(()=>{this.engine.network.joinRoom(e,t)})},this.lobby.onRefreshRooms=()=>{this._ensureConnection().then(()=>{this.engine.network.requestRoomList()})},this.lobby.onReady=()=>{this.engine&&this.engine.network.ready()},this.lobby.onStartGame=()=>{this.engine&&this.engine.network.startGame()},this.lobby.onLeaveRoom=()=>{this.engine&&(this.engine.network.leaveRoom(),this.roomId=null,this.isHost=!1,this.lobby.render())}}async _ensureConnection(){if(this.engine){if(!this.engine.network.connected)try{await this.engine.connect(no),this._setupNetworkHandlers()}catch(e){console.error("Failed to reconnect:",e)}}else{this.engine=new ep(this.gameCanvasEl);try{await this.engine.connect(no),this._setupNetworkHandlers()}catch(e){console.error("Failed to connect:",e),alert("Failed to connect to server. Make sure the server is running on port 8080.")}}}_setupNetworkHandlers(){const e=this.engine.network;e.on(dt.ROOM_LIST,t=>{this.lobby.updateRoomList(t.rooms)}),e.on(dt.ROOM_STATE,t=>{t.playerId?this.playerId=t.playerId:this.playerId||console.warn("ROOM_STATE missing playerId / ROOM_STATE 缺少 playerId"),this.roomId=t.roomId,this.isHost=t.isHost||!1,this.lobby.showRoom(t,this.isHost,this.playerId)}),e.on(dt.GAME_STARTED,t=>{this.playerId=t.playerId,this.lobbyEl.style.display="none",this.gameCanvasEl.style.display="block"}),e.on(dt.ERROR,t=>{console.error("Error:",t.message),alert(t.message)})}}new np; diff --git a/frontend/dist/index.html b/frontend/dist/index.html new file mode 100644 index 0000000..6a18b38 --- /dev/null +++ b/frontend/dist/index.html @@ -0,0 +1,13 @@ + + + + + + ZP2 + + + + +
+ + diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..ab55ddd --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + ZP2 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..0625e3d --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1152 @@ +{ + "name": "zp2-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "zp2-frontend", + "version": "1.0.0", + "dependencies": { + "three": "^0.170.0" + }, + "devDependencies": { + "vite": "^6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/three": { + "version": "0.170.0", + "resolved": "https://registry.npmmirror.com/three/-/three-0.170.0.tgz", + "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..aef350b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,17 @@ +{ + "name": "zp2-frontend", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "three": "^0.170.0" + }, + "devDependencies": { + "vite": "^6.0.0" + } +} diff --git a/frontend/src/game/engine.js b/frontend/src/game/engine.js new file mode 100644 index 0000000..8615b2d --- /dev/null +++ b/frontend/src/game/engine.js @@ -0,0 +1,337 @@ +// Game engine core logic / 游戏引擎核心逻辑 +import { TICK_RATE, TICK_INTERVAL, MSG_TYPE, PLAYER_CONFIG } from '../utils/constants.js' +import { Grid } from '../utils/grid.js' +import { InputManager } from '../utils/input.js' +import { GameScene } from './scene.js' +import { NetworkClient } from '../network/client.js' + +/** + * Main game engine that handles game loop, player prediction, and state reconciliation / 主游戏引擎,处理游戏循环、玩家预测和状态同步 + */ +export class GameEngine { + constructor(canvas) { + this.canvas = canvas + this.scene = new GameScene(canvas) + this.input = new InputManager() + this.network = new NetworkClient() + this.grid = null + this.mapData = null + + // Local player identifier / 本地玩家标识 + this.localPlayerId = null + // Map of all players by ID / 所有玩家的 ID 映射 + this.players = new Map() + // Map of all zombies by ID / 所有僵尸的 ID 映射 + this.zombies = new Map() + + // Game loop state / 游戏循环状态 + this.running = false + this.lastTick = 0 + this.accumulator = 0 + + // Pending input buffer for reconciliation / 待处理的输入缓冲,用于状态同步 + this.pendingInputs = [] + + // Current game time from server / 来自服务器的当前游戏时间 + this.gameTime = 0 + + // External state update callback / 外部状态更新回调 + this.onStateUpdate = null + } + + // Connect to game server / 连接游戏服务器 + async connect(url) { + await this.network.connect(url) + this._setupNetworkHandlers() + } + + // Register network event handlers / 注册网络事件处理器 + _setupNetworkHandlers() { + // Handle game start from server / 处理服务器发送的游戏开始信号 + this.network.on(MSG_TYPE.GAME_STARTED, (data) => { + if (!data.mapData) { + console.error('Server did not provide map data / 服务器未提供地图数据') + alert('Failed to load map data from server. / 无法从服务器加载地图数据。') + return + } + this.localPlayerId = data.playerId + this.mapData = data.mapData + this.grid = new Grid(this.mapData) + this.scene.buildMap(this.mapData) + this._initPlayers(data.players) + this.start() + if (this.onStateUpdate) this.onStateUpdate('game_started', data) + }) + + // Handle periodic game state updates / 处理定期游戏状态更新 + this.network.on(MSG_TYPE.GAME_STATE, (data) => { + this._processServerState(data) + }) + + // Handle new player joining / 处理新玩家加入 + this.network.on(MSG_TYPE.PLAYER_JOIN, (data) => { + this._addPlayer(data) + }) + + // Handle player leaving / 处理玩家离开 + this.network.on(MSG_TYPE.PLAYER_LEAVE, (data) => { + this._removePlayer(data) + }) + + // Handle server errors / 处理服务器错误 + this.network.on(MSG_TYPE.ERROR, (data) => { + console.error('Server error:', data.message) + alert(data.message) + }) + } + + // Initialize all players from server data / 根据服务器数据初始化所有玩家 + _initPlayers(playersData) { + const colors = [0x4488ff, 0xff4444, 0x44ff44, 0xffff44] + for (const p of playersData) { + const isLocal = p.id === this.localPlayerId + const color = colors[p.index % colors.length] + this.players.set(p.id, { + id: p.id, + name: p.name, + x: p.x, + y: p.y, + angle: 0, + health: PLAYER_CONFIG.MAX_HEALTH, + color, + isLocal + }) + this.scene.addPlayer(p.id, p.x, p.y, color, isLocal) + } + } + + // Add a new player to the game / 向游戏中添加新玩家 + _addPlayer(data) { + const colors = [0x4488ff, 0xff4444, 0x44ff44, 0xffff44] + const isLocal = data.id === this.localPlayerId + const color = colors[data.index % colors.length] + this.players.set(data.id, { + id: data.id, + name: data.name, + x: data.x, + y: data.y, + angle: 0, + health: PLAYER_CONFIG.MAX_HEALTH, + color, + isLocal + }) + this.scene.addPlayer(data.id, data.x, data.y, color, isLocal) + } + + // Remove a player from the game / 从游戏中移除玩家 + _removePlayer(data) { + this.players.delete(data.id) + this.scene.removePlayer(data.id) + } + + // Start the game loop / 启动游戏循环 + start() { + if (this.running) return + this.running = true + this.input.attach() + this.lastTick = performance.now() + this._loop() + } + + // Stop the game loop / 停止游戏循环 + stop() { + this.running = false + this.input.detach() + } + + // Main game loop with fixed timestep / 固定时间步长的主游戏循环 + _loop() { + if (!this.running) return + requestAnimationFrame(() => this._loop()) + + const now = performance.now() + const delta = now - this.lastTick + this.lastTick = now + + this.accumulator += delta + // Run fixed ticks / 执行固定时间步长更新 + while (this.accumulator >= TICK_INTERVAL) { + this._tick() + this.accumulator -= TICK_INTERVAL + } + + // Update camera to follow local player / 更新相机跟随本地玩家 + const localPlayer = this.players.get(this.localPlayerId) + if (localPlayer) { + this.scene.updateCamera(localPlayer.x, localPlayer.y) + } + this.scene.render() + } + + // Single tick update: input, prediction, and network send / 单次tick更新:输入处理、预测和网络发送 + _tick() { + if (!this.localPlayerId) return + + const localPlayer = this.players.get(this.localPlayerId) + if (!localPlayer || localPlayer.health <= 0) return + + // Get mouse position on ground plane / 获取鼠标在地面上的位置 + const mouseGroundPos = this.scene.getMouseGroundPos(this.input.mouse.x, this.input.mouse.y) + this.input.mouse.groundX = mouseGroundPos.x + this.input.mouse.groundY = mouseGroundPos.y + + const inputState = this.input.buildInputState(mouseGroundPos) + + // Apply client-side prediction / 应用客户端预测 + this._applyLocalPrediction(inputState) + + // Buffer input for reconciliation / 缓存输入用于状态同步 + this.pendingInputs.push(inputState) + if (this.pendingInputs.length > 60) { + this.pendingInputs.splice(0, this.pendingInputs.length - 60) + } + + this.network.sendInput(inputState) + } + + // Apply local movement prediction / 应用本地移动预测 + _applyLocalPrediction(inputState) { + const player = this.players.get(this.localPlayerId) + if (!player) return + + const speed = PLAYER_CONFIG.SPEED + const dt = TICK_INTERVAL / 1000 + let newX = player.x + inputState.dx * speed * dt + let newY = player.y + inputState.dy * speed * dt + + // Check wall collisions / 检查墙壁碰撞 + if (this.grid) { + if (!this.grid.isWalkable(newX, player.y, 0.8)) newX = player.x + if (!this.grid.isWalkable(player.x, newY, 0.8)) newY = player.y + } + + // Clamp to map bounds / 限制在地图范围内 + const maxX = this.grid.width - 0.5 + const maxY = this.grid.height - 0.5 + newX = Math.max(0.5, Math.min(maxX, newX)) + newY = Math.max(0.5, Math.min(maxY, newY)) + + player.x = newX + player.y = newY + + // Calculate facing angle toward aim point / 计算朝向瞄准点的角度 + const dx = inputState.aimX - player.x + const dy = inputState.aimY - player.y + player.angle = Math.atan2(dx, dy) + + this.scene.updatePlayer(player.id, player.x, player.y, player.angle, player.health) + } + + // Process server state update / 处理服务器状态更新 + _processServerState(state) { + if (state.players) { + for (const ps of state.players) { + const player = this.players.get(ps.id) + if (player) { + if (ps.id === this.localPlayerId) { + this._reconcileLocalPlayer(ps) + } else { + player.x = ps.x + player.y = ps.y + player.angle = ps.angle + player.health = ps.health + this.scene.updatePlayer(ps.id, ps.x, ps.y, ps.angle, ps.health) + } + } + } + } + + // Sync zombies + if (state.zombies) { + const serverZombieIds = new Set() + for (const zs of state.zombies) { + const id = zs.id + serverZombieIds.add(id) + if (!this.zombies.has(id)) { + this.zombies.set(id, { id, x: zs.x, y: zs.y }) + this.scene.addZombie(id, zs.x, zs.y) + } else { + const zombie = this.zombies.get(id) + zombie.x = zs.x + zombie.y = zs.y + this.scene.updateZombie(id, zs.x, zs.y, zs.angle) + } + } + for (const [id] of this.zombies) { + if (!serverZombieIds.has(id)) { + this.zombies.delete(id) + this.scene.removeZombie(id) + } + } + } + + // Sync nut walls + if (state.nutWalls) { + for (const nw of state.nutWalls) { + this.scene.updateNutWall(nw.x, nw.y, nw.health, nw.maxHealth) + } + } + + if (state.gameTime !== undefined) this.gameTime = state.gameTime + + if (this.onStateUpdate) this.onStateUpdate('state', state) + } + + // Reconcile local player state with server authority / 用服务器权威数据同步本地玩家状态 + _reconcileLocalPlayer(serverState) { + const player = this.players.get(this.localPlayerId) + if (!player) return + + let lastProcessedSeq = serverState.lastProcessedSeq || 0 + + // Apply server state / 应用服务器状态 + player.x = serverState.x + player.y = serverState.y + player.angle = serverState.angle + player.health = serverState.health + + // Remove acknowledged inputs / 移除已确认的输入 + this.pendingInputs = this.pendingInputs.filter(input => input.seq > lastProcessedSeq) + + // Re-apply unacknowledged inputs / 重新应用未确认的输入 + if (player.health > 0) { + const speed = PLAYER_CONFIG.SPEED + const dt = TICK_INTERVAL / 1000 + for (const input of this.pendingInputs) { + let newX = player.x + input.dx * speed * dt + let newY = player.y + input.dy * speed * dt + + if (this.grid) { + if (!this.grid.isWalkable(newX, player.y, 0.8)) newX = player.x + if (!this.grid.isWalkable(player.x, newY, 0.8)) newY = player.y + } + + const maxX = this.grid.width - 0.5 + const maxY = this.grid.height - 0.5 + newX = Math.max(0.5, Math.min(maxX, newX)) + newY = Math.max(0.5, Math.min(maxY, newY)) + + player.x = newX + player.y = newY + + const dx = input.aimX - player.x + const dy = input.aimY - player.y + player.angle = Math.atan2(dx, dy) + } + } + + this.scene.updatePlayer(player.id, player.x, player.y, player.angle, player.health) + } + + // Clean up resources / 清理资源 + destroy() { + this.stop() + this.network.disconnect() + this.scene.destroy() + } +} diff --git a/frontend/src/game/scene.js b/frontend/src/game/scene.js new file mode 100644 index 0000000..8527a89 --- /dev/null +++ b/frontend/src/game/scene.js @@ -0,0 +1,302 @@ +// 3D scene rendering with Three.js / 使用 Three.js 进行3D场景渲染 +import * as THREE from 'three' +import { PLAYER_SIZE } from '../utils/constants.js' + +/** + * Manages the 3D scene, camera, lighting, player models, zombie models, and wall meshes + */ +export class GameScene { + constructor(canvas) { + this.canvas = canvas + + this.scene = new THREE.Scene() + this.scene.background = new THREE.Color(0x1a1a2e) + + this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200) + this.cameraOffset = new THREE.Vector3(0, 25, 18) + + this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true }) + this.renderer.setSize(window.innerWidth, window.innerHeight) + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)) + this.renderer.shadowMap.enabled = true + this.renderer.shadowMap.type = THREE.PCFSoftShadowMap + + this.players = new Map() + this.zombies = new Map() + this.wallMeshes = [] + this.nutWallMeshes = new Map() + + this._setupLighting() + this._setupResize() + } + + _setupLighting() { + const ambient = new THREE.AmbientLight(0x404060, 0.6) + this.scene.add(ambient) + + const dirLight = new THREE.DirectionalLight(0xffeedd, 0.8) + dirLight.position.set(16, 30, 16) + dirLight.castShadow = true + dirLight.shadow.mapSize.width = 2048 + dirLight.shadow.mapSize.height = 2048 + dirLight.shadow.camera.near = 0.5 + dirLight.shadow.camera.far = 80 + dirLight.shadow.camera.left = -20 + dirLight.shadow.camera.right = 20 + dirLight.shadow.camera.top = 20 + dirLight.shadow.camera.bottom = -20 + this.scene.add(dirLight) + + const pointLight = new THREE.PointLight(0xff4400, 0.3, 50) + pointLight.position.set(16, 10, 16) + this.scene.add(pointLight) + } + + _setupResize() { + window.addEventListener('resize', () => { + this.camera.aspect = window.innerWidth / window.innerHeight + this.camera.updateProjectionMatrix() + this.renderer.setSize(window.innerWidth, window.innerHeight) + }) + } + + // Build map: floor, static walls (1), player spawns (2), nut walls (4) + buildMap(mapData) { + for (const mesh of this.wallMeshes) { + this.scene.remove(mesh) + mesh.geometry.dispose() + mesh.material.dispose() + } + this.wallMeshes = [] + for (const [, entry] of this.nutWallMeshes) { + this.scene.remove(entry.mesh) + entry.mesh.geometry.dispose() + entry.mesh.material.dispose() + } + this.nutWallMeshes.clear() + + const mapHeight = mapData.length + const mapWidth = mapData[0]?.length || 0 + + const floorGeo = new THREE.PlaneGeometry(mapWidth, mapHeight) + const floorMat = new THREE.MeshLambertMaterial({ color: 0x2a2a3a }) + const floor = new THREE.Mesh(floorGeo, floorMat) + floor.rotation.x = -Math.PI / 2 + floor.position.set(mapWidth / 2, 0, mapHeight / 2) + floor.receiveShadow = true + this.scene.add(floor) + this.wallMeshes.push(floor) + + const wallGeo = new THREE.BoxGeometry(1, 1.5, 1) + const wallMat = new THREE.MeshLambertMaterial({ color: 0x555577 }) + const nutWallGeo = new THREE.BoxGeometry(1, 1.2, 1) + const nutWallMat = new THREE.MeshLambertMaterial({ color: 0x8B6914 }) + const spawnGeo = new THREE.BoxGeometry(1, 0.1, 1) + const spawnMat = new THREE.MeshLambertMaterial({ color: 0x00ff88, transparent: true, opacity: 0.5 }) + const zombieSpawnGeo = new THREE.BoxGeometry(1, 0.1, 1) + const zombieSpawnMat = new THREE.MeshLambertMaterial({ color: 0xff4444, transparent: true, opacity: 0.4 }) + + for (let y = 0; y < mapHeight; y++) { + for (let x = 0; x < mapWidth; x++) { + const cell = mapData[y] && mapData[y][x] + if (cell === 1) { + const wall = new THREE.Mesh(wallGeo, wallMat) + wall.position.set(x + 0.5, 0.75, y + 0.5) + wall.castShadow = true + wall.receiveShadow = true + this.scene.add(wall) + this.wallMeshes.push(wall) + } else if (cell === 2) { + const spawn = new THREE.Mesh(spawnGeo, spawnMat) + spawn.position.set(x + 0.5, 0.05, y + 0.5) + this.scene.add(spawn) + this.wallMeshes.push(spawn) + } else if (cell === 3) { + const zsp = new THREE.Mesh(zombieSpawnGeo, zombieSpawnMat) + zsp.position.set(x + 0.5, 0.05, y + 0.5) + this.scene.add(zsp) + this.wallMeshes.push(zsp) + } else if (cell === 4) { + const nutWall = new THREE.Mesh(nutWallGeo, nutWallMat.clone()) + nutWall.position.set(x + 0.5, 0.6, y + 0.5) + nutWall.castShadow = true + nutWall.receiveShadow = true + this.scene.add(nutWall) + this.wallMeshes.push(nutWall) + const key = x + ',' + y + this.nutWallMeshes.set(key, { mesh: nutWall, x, y, maxHealth: 500 }) + } + } + } + } + + // Update nut wall appearance based on health + updateNutWall(x, y, health, maxHealth) { + const key = x + ',' + y + const entry = this.nutWallMeshes.get(key) + if (!entry) return + const ratio = health / maxHealth + const r = 0.55 + (1 - ratio) * 0.45 + const g = 0.41 * ratio + const b = 0.08 * ratio + entry.mesh.material.color.setRGB(r, g, b) + if (health <= 0) { + this.scene.remove(entry.mesh) + entry.mesh.geometry.dispose() + entry.mesh.material.dispose() + this.nutWallMeshes.delete(key) + } + } + + createPlayerModel(color = 0x4488ff) { + const group = new THREE.Group() + + const bodyGeo = new THREE.CylinderGeometry(PLAYER_SIZE / 2, PLAYER_SIZE / 2, 0.8, 8) + const bodyMat = new THREE.MeshLambertMaterial({ color }) + const body = new THREE.Mesh(bodyGeo, bodyMat) + body.position.y = 0.4 + body.castShadow = true + group.add(body) + + const headGeo = new THREE.SphereGeometry(0.2, 8, 8) + const headMat = new THREE.MeshLambertMaterial({ color: 0xffcc99 }) + const head = new THREE.Mesh(headGeo, headMat) + head.position.y = 1.0 + head.castShadow = true + group.add(head) + + const gunGeo = new THREE.BoxGeometry(0.08, 0.08, 0.5) + const gunMat = new THREE.MeshLambertMaterial({ color: 0x333333 }) + const gun = new THREE.Mesh(gunGeo, gunMat) + gun.position.set(0, 0.5, 0.4) + group.add(gun) + + return group + } + + addPlayer(id, x, y, color, isLocal = false) { + const model = this.createPlayerModel(color) + model.position.set(x, 0, y) + this.scene.add(model) + this.players.set(id, { model, isLocal }) + } + + removePlayer(id) { + const player = this.players.get(id) + if (player) { + this.scene.remove(player.model) + this.players.delete(id) + } + } + + updatePlayer(id, x, y, angle, health) { + const player = this.players.get(id) + if (player) { + player.model.position.x = x + player.model.position.z = y + player.model.rotation.y = angle + } + } + + // --- Zombie rendering --- + + createZombieModel() { + const group = new THREE.Group() + + // Body - dark green + const bodyGeo = new THREE.CylinderGeometry(0.35, 0.4, 0.9, 8) + const bodyMat = new THREE.MeshLambertMaterial({ color: 0x3a5a2a }) + const body = new THREE.Mesh(bodyGeo, bodyMat) + body.position.y = 0.45 + body.castShadow = true + group.add(body) + + // Head - slightly green + const headGeo = new THREE.SphereGeometry(0.22, 8, 8) + const headMat = new THREE.MeshLambertMaterial({ color: 0x6b8e4e }) + const head = new THREE.Mesh(headGeo, headMat) + head.position.y = 1.1 + head.castShadow = true + group.add(head) + + // Arms - thin cylinders pointing forward + const armGeo = new THREE.CylinderGeometry(0.06, 0.06, 0.6, 6) + const armMat = new THREE.MeshLambertMaterial({ color: 0x3a5a2a }) + const leftArm = new THREE.Mesh(armGeo, armMat) + leftArm.position.set(-0.3, 0.7, 0.3) + leftArm.rotation.x = -Math.PI / 3 + group.add(leftArm) + const rightArm = new THREE.Mesh(armGeo, armMat) + rightArm.position.set(0.3, 0.7, 0.3) + rightArm.rotation.x = -Math.PI / 3 + group.add(rightArm) + + return group + } + + addZombie(id, x, y) { + const model = this.createZombieModel() + model.position.set(x, 0, y) + this.scene.add(model) + this.zombies.set(id, { model }) + } + + removeZombie(id) { + const zombie = this.zombies.get(id) + if (zombie) { + this.scene.remove(zombie.model) + this.zombies.delete(id) + } + } + + updateZombie(id, x, y, angle) { + const zombie = this.zombies.get(id) + if (zombie) { + zombie.model.position.x = x + zombie.model.position.z = y + zombie.model.rotation.y = angle + } + } + + // Camera follows target + updateCamera(targetX, targetY) { + const target = new THREE.Vector3(targetX, 0, targetY) + const desiredPos = target.clone().add(this.cameraOffset) + this.camera.position.lerp(desiredPos, 0.1) + this.camera.lookAt(target) + } + + getMouseGroundPos(mouseX, mouseY) { + const mouse = new THREE.Vector2( + (mouseX / window.innerWidth) * 2 - 1, + -(mouseY / window.innerHeight) * 2 + 1 + ) + const raycaster = new THREE.Raycaster() + raycaster.setFromCamera(mouse, this.camera) + const groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0) + const intersection = new THREE.Vector3() + raycaster.ray.intersectPlane(groundPlane, intersection) + if (intersection) { + return { x: intersection.x, y: intersection.z } + } + return { x: 0, y: 0 } + } + + render() { + this.renderer.render(this.scene, this.camera) + } + + destroy() { + this.renderer.dispose() + this.scene.traverse(child => { + if (child.geometry) child.geometry.dispose() + if (child.material) { + if (Array.isArray(child.material)) { + child.material.forEach(m => m.dispose()) + } else { + child.material.dispose() + } + } + }) + } +} diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..761382d --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,141 @@ +// Application entry point / 应用入口 +import { MSG_TYPE } from './utils/constants.js' +import { GameEngine } from './game/engine.js' +import { LobbyUI } from './ui/lobby.js' +import './style.css' + +// WebSocket server URL / WebSocket 服务器地址 +const WS_URL = `ws://${window.location.hostname}:8080/ws` + +/** + * Main application class that manages lobby and game transitions / 主应用类,管理大厅和游戏之间的切换 + */ +class App { + constructor() { + // Root DOM element / 根 DOM 元素 + this.appEl = document.getElementById('app') + + // Lobby container element / 大厅容器元素 + this.lobbyEl = document.createElement('div') + // Game canvas element / 游戏画布元素 + this.gameCanvasEl = document.createElement('canvas') + + this.appEl.appendChild(this.lobbyEl) + this.appEl.appendChild(this.gameCanvasEl) + + // Hide game canvas initially / 初始时隐藏游戏画布 + this.gameCanvasEl.style.display = 'none' + + // Game engine instance / 游戏引擎实例 + this.engine = null + // Lobby UI instance / 大厅 UI 实例 + this.lobby = new LobbyUI(this.lobbyEl) + + // Player session info / 玩家会话信息 + this.playerId = null + this.roomId = null + this.isHost = false + this.playerName = '' + + this._setupLobby() + } + + // Set up lobby event callbacks / 设置大厅事件回调 + _setupLobby() { + this.lobby.onCreateRoom = (name) => { + this.playerName = name + this._ensureConnection().then(() => { + this.engine.network.createRoom(name) + }) + } + + this.lobby.onJoinRoom = (roomId, name) => { + this.playerName = name + this._ensureConnection().then(() => { + this.engine.network.joinRoom(roomId, name) + }) + } + + this.lobby.onRefreshRooms = () => { + this._ensureConnection().then(() => { + this.engine.network.requestRoomList() + }) + } + + this.lobby.onReady = () => { + if (this.engine) this.engine.network.ready() + } + + this.lobby.onStartGame = () => { + if (this.engine) this.engine.network.startGame() + } + + this.lobby.onLeaveRoom = () => { + if (this.engine) { + this.engine.network.leaveRoom() + this.roomId = null + this.isHost = false + this.lobby.render() + } + } + } + + // Ensure WebSocket connection is established / 确保 WebSocket 连接已建立 + async _ensureConnection() { + if (!this.engine) { + this.engine = new GameEngine(this.gameCanvasEl) + try { + await this.engine.connect(WS_URL) + this._setupNetworkHandlers() + } catch (e) { + console.error('Failed to connect:', e) + alert('Failed to connect to server. Make sure the server is running on port 8080.') + } + } else if (!this.engine.network.connected) { + try { + await this.engine.connect(WS_URL) + this._setupNetworkHandlers() + } catch (e) { + console.error('Failed to reconnect:', e) + } + } + } + + // Register network message handlers / 注册网络消息处理器 + _setupNetworkHandlers() { + const net = this.engine.network + + // Handle room list updates / 处理房间列表更新 + net.on(MSG_TYPE.ROOM_LIST, (data) => { + this.lobby.updateRoomList(data.rooms) + }) + + // Handle room state updates / 处理房间状态更新 + net.on(MSG_TYPE.ROOM_STATE, (data) => { + if (data.playerId) { + this.playerId = data.playerId + } else if (!this.playerId) { + console.warn('ROOM_STATE missing playerId / ROOM_STATE 缺少 playerId') + } + this.roomId = data.roomId + this.isHost = data.isHost || false + this.lobby.showRoom(data, this.isHost, this.playerId) + }) + + // Handle game start event / 处理游戏开始事件 + net.on(MSG_TYPE.GAME_STARTED, (data) => { + this.playerId = data.playerId + this.lobbyEl.style.display = 'none' + this.gameCanvasEl.style.display = 'block' + }) + + // Handle error messages / 处理错误消息 + net.on(MSG_TYPE.ERROR, (data) => { + console.error('Error:', data.message) + alert(data.message) + }) + } +} + +// Initialize the application / 初始化应用 +const app = new App() diff --git a/frontend/src/network/client.js b/frontend/src/network/client.js new file mode 100644 index 0000000..03de8c4 --- /dev/null +++ b/frontend/src/network/client.js @@ -0,0 +1,107 @@ +// Network client for WebSocket communication / WebSocket 通信网络客户端 +import { MSG_TYPE } from '../utils/constants.js' + +/** + * WebSocket client that handles connection and message routing / WebSocket 客户端,处理连接和消息路由 + */ +export class NetworkClient { + constructor() { + this.ws = null + this.connected = false + // Message handlers by type / 按类型存储的消息处理器 + this.handlers = new Map() + } + + // Connect to WebSocket server / 连接 WebSocket 服务器 + async connect(url) { + return new Promise((resolve, reject) => { + this.ws = new WebSocket(url) + + this.ws.onopen = () => { + this.connected = true + resolve() + } + + this.ws.onerror = (e) => { + reject(e) + } + + this.ws.onclose = () => { + this.connected = false + } + + this.ws.onmessage = (e) => { + try { + const msg = JSON.parse(e.data) + const handlers = this.handlers.get(msg.type) + if (handlers) { + for (const h of handlers) { + h(msg.data || {}) + } + } + } catch (err) { + console.error('Failed to parse message:', err) + } + } + }) + } + + // Disconnect from server / 断开服务器连接 + disconnect() { + if (this.ws) { + this.ws.close() + this.ws = null + this.connected = false + } + } + + // Register a message handler for a specific type / 为特定类型注册消息处理器 + on(type, handler) { + if (!this.handlers.has(type)) { + this.handlers.set(type, []) + } + this.handlers.get(type).push(handler) + } + + // Send a message to the server / 向服务器发送消息 + send(type, data = {}) { + if (this.ws && this.connected) { + this.ws.send(JSON.stringify({ type, data })) + } + } + + // Create a new room / 创建新房间 + createRoom(playerName) { + this.send(MSG_TYPE.CREATE_ROOM, { playerName }) + } + + // Join an existing room / 加入已有房间 + joinRoom(roomId, playerName) { + this.send(MSG_TYPE.JOIN_ROOM, { roomId, playerName }) + } + + // Leave current room / 离开当前房间 + leaveRoom() { + this.send(MSG_TYPE.LEAVE_ROOM) + } + + // Request room list / 请求房间列表 + requestRoomList() { + this.send(MSG_TYPE.ROOM_LIST) + } + + // Signal ready state / 发送准备状态 + ready() { + this.send(MSG_TYPE.READY) + } + + // Start the game (host only) / 开始游戏(仅房主) + startGame() { + this.send(MSG_TYPE.START_GAME) + } + + // Send player input to server / 向服务器发送玩家输入 + sendInput(inputState) { + this.send(MSG_TYPE.PLAYER_INPUT, inputState) + } +} diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..1c00df6 --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,180 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background: #1a1a2e; + color: #e0e0e0; + font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; + overflow: hidden; +} + +canvas { + display: block; + width: 100vw; + height: 100vh; +} + +.lobby { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: 2rem; +} + +.lobby h1 { + font-size: 3rem; + color: #4488ff; + margin-bottom: 2rem; + text-shadow: 0 0 20px rgba(68, 136, 255, 0.3); +} + +.lobby h2 { + font-size: 2rem; + color: #4488ff; + margin-bottom: 1.5rem; +} + +.lobby-form { + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; + max-width: 400px; +} + +.lobby-form input { + padding: 0.75rem 1rem; + background: #16213e; + border: 1px solid #334466; + border-radius: 6px; + color: #e0e0e0; + font-size: 1rem; + outline: none; +} + +.lobby-form input:focus { + border-color: #4488ff; +} + +.lobby-actions { + display: flex; + gap: 0.5rem; +} + +button { + padding: 0.6rem 1.2rem; + background: #4488ff; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 600; + transition: background 0.2s; +} + +button:hover { + background: #3377ee; +} + +button:active { + background: #2266dd; +} + +.room-list { + margin-top: 1.5rem; + width: 100%; + max-width: 500px; +} + +.room-list .empty { + text-align: center; + color: #667; + padding: 2rem; +} + +.room-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem 1rem; + background: #16213e; + border-radius: 6px; + margin-bottom: 0.5rem; +} + +.room-name { + font-weight: 600; + flex: 1; +} + +.room-players { + color: #889; + margin-right: 1rem; +} + +.join-btn { + padding: 0.4rem 0.8rem; + font-size: 0.8rem; +} + +.room-view { + max-width: 500px; + width: 100%; +} + +.player-list { + width: 100%; + margin-bottom: 1.5rem; +} + +.player-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.6rem 1rem; + background: #16213e; + border-radius: 6px; + margin-bottom: 0.4rem; +} + +.player-item.ready { + border-left: 3px solid #44ff88; +} + +.ready-status { + font-size: 0.8rem; + font-weight: 600; + color: #667; +} + +.player-item.ready .ready-status { + color: #44ff88; +} + +.room-actions { + display: flex; + gap: 0.5rem; + justify-content: center; +} + +#startBtn { + background: #44aa44; +} + +#startBtn:hover { + background: #339933; +} + +#leaveBtn { + background: #aa4444; +} + +#leaveBtn:hover { + background: #993333; +} diff --git a/frontend/src/ui/lobby.js b/frontend/src/ui/lobby.js new file mode 100644 index 0000000..194671f --- /dev/null +++ b/frontend/src/ui/lobby.js @@ -0,0 +1,114 @@ +// Lobby UI for room management / 房间管理大厅 UI + +/** + * Renders and manages the lobby interface for room creation and joining / 渲染和管理用于创建和加入房间的大厅界面 + */ +export class LobbyUI { + constructor(container) { + this.container = container + // Event callbacks / 事件回调 + this.onCreateRoom = null + this.onJoinRoom = null + this.onRefreshRooms = null + this.onReady = null + this.onStartGame = null + this.onLeaveRoom = null + this.render() + } + + // Render the initial lobby view / 渲染初始大厅视图 + render() { + this.container.innerHTML = ` +
+

ZP2

+
+ +
+ + +
+
+
+
+ ` + + // Bind create room button / 绑定创建房间按钮 + this.container.querySelector('#createRoom').onclick = () => { + const name = this.container.querySelector('#playerName').value.trim() || 'Player' + if (this.onCreateRoom) this.onCreateRoom(name) + } + + // Bind refresh rooms button / 绑定刷新房间按钮 + this.container.querySelector('#refreshRooms').onclick = () => { + if (this.onRefreshRooms) this.onRefreshRooms() + } + + this.container.style.display = 'block' + } + + // Update the room list display / 更新房间列表显示 + updateRoomList(rooms) { + const listEl = this.container.querySelector('#roomList') + if (!rooms || rooms.length === 0) { + listEl.innerHTML = '

No rooms available

' + return + } + + listEl.innerHTML = rooms.map(r => ` +
+ ${r.hostName || r.id}'s Room + ${r.playerCount}/4 + +
+ `).join('') + + // Bind join buttons / 绑定加入按钮 + listEl.querySelectorAll('.join-btn').forEach(btn => { + btn.onclick = () => { + const name = this.container.querySelector('#playerName').value.trim() || 'Player' + if (this.onJoinRoom) this.onJoinRoom(btn.dataset.id, name) + } + }) + } + + // Show the room view with player list / 显示带有玩家列表的房间视图 + showRoom(data, isHost, playerId) { + const players = data.players || [] + this.container.innerHTML = ` +
+

${data.roomName || data.roomId}

+
+ ${players.map(p => ` +
+ ${p.name}${p.id === playerId ? ' (you)' : ''} + ${p.ready ? 'READY' : 'NOT READY'} +
+ `).join('')} +
+
+ + ${isHost ? '' : ''} + +
+
+ ` + + // Bind ready button / 绑定准备按钮 + this.container.querySelector('#readyBtn').onclick = () => { + if (this.onReady) this.onReady() + } + + // Bind start game button (host only) / 绑定开始游戏按钮(仅房主) + const startBtn = this.container.querySelector('#startBtn') + if (startBtn) { + startBtn.onclick = () => { + if (this.onStartGame) this.onStartGame() + } + } + + // Bind leave button / 绑定离开按钮 + this.container.querySelector('#leaveBtn').onclick = () => { + if (this.onLeaveRoom) this.onLeaveRoom() + } + } +} diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js new file mode 100644 index 0000000..5ef59c8 --- /dev/null +++ b/frontend/src/utils/constants.js @@ -0,0 +1,40 @@ +// Game configuration constants / 游戏配置常量 + +// Size of each cell in world units / 每个格子的世界单位大小 +export const CELL_SIZE = 1 +// Player model size / 玩家模型大小 +export const PLAYER_SIZE = 0.8 + +// Network tick rate / 网络tick速率 +export const TICK_RATE = 30 +// Time between ticks in ms / 每次tick之间的时间(毫秒) +export const TICK_INTERVAL = 1000 / TICK_RATE + +// Player configuration / 玩家配置 +export const PLAYER_CONFIG = { + MAX_HEALTH: 100, // Maximum player health / 玩家最大生命值 + SPEED: 5 // Player movement speed / 玩家移动速度 +} + +// Zombie configuration / 僵尸配置 +export const ZOMBIE_CONFIG = { + SIZE: 0.8, // Zombie collision size / 僵尸碰撞体积 + SPEED: 2 // Zombie movement speed / 僵尸移动速度 +} + +// Message types for network protocol / 网络协议的消息类型 +export const MSG_TYPE = { + CREATE_ROOM: 'create_room', + JOIN_ROOM: 'join_room', + LEAVE_ROOM: 'leave_room', + ROOM_LIST: 'room_list', + ROOM_STATE: 'room_state', + READY: 'ready', + START_GAME: 'start_game', + GAME_STARTED: 'game_started', + PLAYER_INPUT: 'player_input', + GAME_STATE: 'game_state', + PLAYER_JOIN: 'player_join', + PLAYER_LEAVE: 'player_leave', + ERROR: 'error' +} diff --git a/frontend/src/utils/grid.js b/frontend/src/utils/grid.js new file mode 100644 index 0000000..1394acd --- /dev/null +++ b/frontend/src/utils/grid.js @@ -0,0 +1,68 @@ +// Grid system for collision detection / 用于碰撞检测的网格系统 +import { CELL_SIZE } from './constants.js' + +/** + * Grid class for map parsing and collision queries / 网格类,用于地图解析和碰撞查询 + */ +export class Grid { + constructor(mapData) { + this.cells = [] + this.width = 0 + this.height = 0 + this.parseMap(mapData) + } + + // Parse map data into internal grid / 将地图数据解析为内部网格 + parseMap(mapData) { + this.height = mapData.length + this.width = mapData[0]?.length || 0 + this.cells = [] + for (let y = 0; y < this.height; y++) { + this.cells[y] = [] + for (let x = 0; x < this.width; x++) { + this.cells[y][x] = mapData[y][x] || 0 + } + } + } + + // Check if a grid cell is a wall (static=1 or nut=4) / 检查网格单元格是否为墙 + isWall(gridX, gridY) { + if (gridX < 0 || gridX >= this.width || gridY < 0 || gridY >= this.height) return true + const cell = this.cells[gridY][gridX] + return cell === 1 || cell === 4 + } + + // Convert world coordinates to grid coordinates / 将世界坐标转换为网格坐标 + worldToGrid(wx, wy) { + return { + x: Math.floor(wx / CELL_SIZE), + y: Math.floor(wy / CELL_SIZE) + } + } + + // Convert grid coordinates to world coordinates / 将网格坐标转换为世界坐标 + gridToWorld(gx, gy) { + return { + x: gx * CELL_SIZE + CELL_SIZE / 2, + y: gy * CELL_SIZE + CELL_SIZE / 2 + } + } + + // Check if a world position is walkable (no wall collisions) / 检查世界位置是否可行走(无墙壁碰撞) + isWalkable(wx, wy, size) { + const half = size / 2 + const corners = [ + { x: wx - half, y: wy - half }, + { x: wx + half, y: wy - half }, + { x: wx - half, y: wy + half }, + { x: wx + half, y: wy + half } + ] + for (const corner of corners) { + const g = this.worldToGrid(corner.x, corner.y) + if (this.isWall(g.x, g.y)) return false + } + return true + } +} + + diff --git a/frontend/src/utils/input.js b/frontend/src/utils/input.js new file mode 100644 index 0000000..f463e87 --- /dev/null +++ b/frontend/src/utils/input.js @@ -0,0 +1,94 @@ +// Input manager for keyboard and mouse / 键盘和鼠标输入管理器 + +/** + * Tracks keyboard and mouse state for player input / 追踪键盘和鼠标状态用于玩家输入 + */ +export class InputManager { + constructor() { + // Currently pressed keys / 当前按下的按键 + this.keys = {} + // Mouse state / 鼠标状态 + this.mouse = { x: 0, y: 0, left: false, right: false, groundX: 0, groundY: 0 } + + // Key bindings for movement / 移动按键绑定 + this.keyBindings = { + moveUp: 'KeyW', + moveDown: 'KeyS', + moveLeft: 'KeyA', + moveRight: 'KeyD' + } + + // Input sequence number for reconciliation / 用于同步的输入序列号 + this.sequenceNumber = 0 + + // Event handler bound references (for attach/detach) / 事件处理器绑定引用(用于挂载/卸载) + this._onKeyDown = (e) => { + this.keys[e.code] = true + } + this._onKeyUp = (e) => { + this.keys[e.code] = false + } + this._onMouseMove = (e) => { + this.mouse.x = e.clientX + this.mouse.y = e.clientY + } + this._onMouseDown = (e) => { + if (e.button === 0) this.mouse.left = true + if (e.button === 2) this.mouse.right = true + } + this._onMouseUp = (e) => { + if (e.button === 0) this.mouse.left = false + if (e.button === 2) this.mouse.right = false + } + // Prevent context menu on right click / 阻止右键菜单 + this._onContextMenu = (e) => e.preventDefault() + } + + // Attach event listeners / 挂载事件监听器 + attach() { + window.addEventListener('keydown', this._onKeyDown) + window.addEventListener('keyup', this._onKeyUp) + window.addEventListener('mousemove', this._onMouseMove) + window.addEventListener('mousedown', this._onMouseDown) + window.addEventListener('mouseup', this._onMouseUp) + window.addEventListener('contextmenu', this._onContextMenu) + } + + // Remove event listeners / 移除事件监听器 + detach() { + window.removeEventListener('keydown', this._onKeyDown) + window.removeEventListener('keyup', this._onKeyUp) + window.removeEventListener('mousemove', this._onMouseMove) + window.removeEventListener('mousedown', this._onMouseDown) + window.removeEventListener('mouseup', this._onMouseUp) + window.removeEventListener('contextmenu', this._onContextMenu) + } + + // Get normalized movement direction / 获取归一化的移动方向 + getMovement() { + let dx = 0, dy = 0 + if (this.keys[this.keyBindings.moveUp] || this.keys['ArrowUp']) dy -= 1 + if (this.keys[this.keyBindings.moveDown] || this.keys['ArrowDown']) dy += 1 + if (this.keys[this.keyBindings.moveLeft] || this.keys['ArrowLeft']) dx -= 1 + if (this.keys[this.keyBindings.moveRight] || this.keys['ArrowRight']) dx += 1 + // Normalize diagonal movement / 对角线移动归一化 + if (dx !== 0 && dy !== 0) { + dx *= 0.7071 + dy *= 0.7071 + } + return { dx, dy } + } + + // Build complete input state for sending to server / 构建完整的输入状态用于发送给服务器 + buildInputState(mouseGroundPos) { + const movement = this.getMovement() + this.sequenceNumber++ + return { + seq: this.sequenceNumber, + dx: movement.dx, + dy: movement.dy, + aimX: mouseGroundPos.x, + aimY: mouseGroundPos.y + } + } +} diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..dbb7301 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + server: { + host: '0.0.0.0', + port: 3000, + proxy: { + '/ws': { + target: 'ws://localhost:8080', + ws: true + } + } + } +}) diff --git a/maps/d540209a.json b/maps/d540209a.json new file mode 100644 index 0000000..2c12a98 --- /dev/null +++ b/maps/d540209a.json @@ -0,0 +1,464 @@ +{ + "id": "d540209a", + "name": "my-map", + "width": 32, + "height": 32, + "walls": [ + { + "x": 0.0, + "y": 0.0, + "type": "static" + }, + { + "x": 0.0, + "y": 1.0, + "type": "static" + }, + { + "x": 0.0, + "y": 2.0, + "type": "static" + }, + { + "x": 0.0, + "y": 3.0, + "type": "static" + }, + { + "x": 0.0, + "y": 4.0, + "type": "static" + }, + { + "x": 0.0, + "y": 5.0, + "type": "static" + }, + { + "x": 0.0, + "y": 6.0, + "type": "static" + }, + { + "x": 0.0, + "y": 7.0, + "type": "static" + }, + { + "x": 23.0, + "y": 7.0, + "type": "static" + }, + { + "x": 0.0, + "y": 8.0, + "type": "static" + }, + { + "x": 23.0, + "y": 8.0, + "type": "static" + }, + { + "x": 24.0, + "y": 8.0, + "type": "static" + }, + { + "x": 0.0, + "y": 9.0, + "type": "static" + }, + { + "x": 24.0, + "y": 9.0, + "type": "static" + }, + { + "x": 0.0, + "y": 10.0, + "type": "static" + }, + { + "x": 24.0, + "y": 10.0, + "type": "static" + }, + { + "x": 29.0, + "y": 10.0, + "type": "static" + }, + { + "x": 0.0, + "y": 11.0, + "type": "static" + }, + { + "x": 24.0, + "y": 11.0, + "type": "static" + }, + { + "x": 29.0, + "y": 11.0, + "type": "static" + }, + { + "x": 0.0, + "y": 12.0, + "type": "static" + }, + { + "x": 24.0, + "y": 12.0, + "type": "static" + }, + { + "x": 0.0, + "y": 13.0, + "type": "static" + }, + { + "x": 24.0, + "y": 13.0, + "type": "static" + }, + { + "x": 29.0, + "y": 13.0, + "type": "static" + }, + { + "x": 0.0, + "y": 14.0, + "type": "static" + }, + { + "x": 24.0, + "y": 14.0, + "type": "static" + }, + { + "x": 29.0, + "y": 14.0, + "type": "static" + }, + { + "x": 24.0, + "y": 15.0, + "type": "static" + }, + { + "x": 29.0, + "y": 15.0, + "type": "static" + }, + { + "x": 24.0, + "y": 16.0, + "type": "static" + }, + { + "x": 0.0, + "y": 17.0, + "type": "static" + }, + { + "x": 1.0, + "y": 17.0, + "type": "static" + }, + { + "x": 2.0, + "y": 17.0, + "type": "static" + }, + { + "x": 3.0, + "y": 17.0, + "type": "static" + }, + { + "x": 6.0, + "y": 17.0, + "type": "static" + }, + { + "x": 7.0, + "y": 17.0, + "type": "static" + }, + { + "x": 10.0, + "y": 17.0, + "type": "static" + }, + { + "x": 11.0, + "y": 17.0, + "type": "static" + }, + { + "x": 12.0, + "y": 17.0, + "type": "static" + }, + { + "x": 13.0, + "y": 17.0, + "type": "static" + }, + { + "x": 14.0, + "y": 17.0, + "type": "static" + }, + { + "x": 15.0, + "y": 17.0, + "type": "static" + }, + { + "x": 16.0, + "y": 17.0, + "type": "static" + }, + { + "x": 17.0, + "y": 17.0, + "type": "static" + }, + { + "x": 18.0, + "y": 17.0, + "type": "static" + }, + { + "x": 19.0, + "y": 17.0, + "type": "static" + }, + { + "x": 20.0, + "y": 17.0, + "type": "static" + }, + { + "x": 21.0, + "y": 17.0, + "type": "static" + }, + { + "x": 22.0, + "y": 17.0, + "type": "static" + }, + { + "x": 23.0, + "y": 17.0, + "type": "static" + }, + { + "x": 24.0, + "y": 17.0, + "type": "static" + }, + { + "x": 29.0, + "y": 17.0, + "type": "static" + }, + { + "x": 29.0, + "y": 19.0, + "type": "static" + }, + { + "x": 29.0, + "y": 20.0, + "type": "static" + }, + { + "x": 29.0, + "y": 21.0, + "type": "static" + }, + { + "x": 29.0, + "y": 22.0, + "type": "static" + }, + { + "x": 28.0, + "y": 23.0, + "type": "static" + }, + { + "x": 29.0, + "y": 23.0, + "type": "static" + }, + { + "x": 28.0, + "y": 24.0, + "type": "static" + }, + { + "x": 9.0, + "y": 25.0, + "type": "static" + }, + { + "x": 10.0, + "y": 25.0, + "type": "static" + }, + { + "x": 11.0, + "y": 25.0, + "type": "static" + }, + { + "x": 12.0, + "y": 25.0, + "type": "static" + }, + { + "x": 13.0, + "y": 25.0, + "type": "static" + }, + { + "x": 14.0, + "y": 25.0, + "type": "static" + }, + { + "x": 15.0, + "y": 25.0, + "type": "static" + }, + { + "x": 28.0, + "y": 25.0, + "type": "static" + }, + { + "x": 15.0, + "y": 26.0, + "type": "static" + }, + { + "x": 17.0, + "y": 26.0, + "type": "static" + }, + { + "x": 18.0, + "y": 26.0, + "type": "static" + }, + { + "x": 20.0, + "y": 26.0, + "type": "static" + }, + { + "x": 22.0, + "y": 26.0, + "type": "static" + }, + { + "x": 24.0, + "y": 26.0, + "type": "static" + }, + { + "x": 25.0, + "y": 26.0, + "type": "static" + }, + { + "x": 26.0, + "y": 26.0, + "type": "static" + }, + { + "x": 27.0, + "y": 26.0, + "type": "static" + }, + { + "x": 28.0, + "y": 26.0, + "type": "static" + }, + { + "x": 10.0, + "y": 10.0, + "type": "nut" + }, + { + "x": 11.0, + "y": 10.0, + "type": "nut" + }, + { + "x": 16.0, + "y": 12.0, + "type": "nut" + }, + { + "x": 17.0, + "y": 12.0, + "type": "nut" + }, + { + "x": 8.0, + "y": 20.0, + "type": "nut" + }, + { + "x": 9.0, + "y": 20.0, + "type": "nut" + } + ], + "playerSpawns": [ + { + "x": 4, + "y": 1 + }, + { + "x": 9, + "y": 2 + }, + { + "x": 14, + "y": 2 + }, + { + "x": 24, + "y": 2 + } + ], + "zombieSpawns": [ + { + "x": 23, + "y": 15 + }, + { + "x": 6, + "y": 21 + }, + { + "x": 15, + "y": 5 + }, + { + "x": 10, + "y": 28 + } + ] +} \ No newline at end of file diff --git a/script/start.bat b/script/start.bat new file mode 100644 index 0000000..96b0230 --- /dev/null +++ b/script/start.bat @@ -0,0 +1,81 @@ +@echo off +REM ZP2 Startup Script for Windows / ZP2 Windows 启动脚本 + +setlocal EnableDelayedExpansion + +set "SCRIPT_DIR=%~dp0" +set "PROJECT_DIR=%SCRIPT_DIR%.." +set "BACKEND_DIR=%PROJECT_DIR%\backend" +set "FRONTEND_DIR=%PROJECT_DIR%\frontend" + +echo ================================ +echo ZP2 Game Server Launcher +echo ZP2 游戏服务器启动器 +echo ================================ +echo. + +REM Check prerequisites / 检查依赖 +echo [INFO] Checking prerequisites / 检查依赖... + +where java >nul 2>nul +if %ERRORLEVEL% neq 0 ( + echo [ERROR] Java not found. Please install Java 17+. / 未找到 Java,请安装 Java 17+。 + exit /b 1 +) + +for /f "tokens=3" %%g in ('java -version 2^>^1 ^| findstr /i "version"') do ( + set "JAVA_VER=%%g" + set "JAVA_VER=!JAVA_VER:"=!" +) +echo [INFO] Java !JAVA_VER! found / 找到 Java !JAVA_VER! + +where mvn >nul 2>nul +if %ERRORLEVEL% neq 0 ( + echo [ERROR] Maven not found. Please install Maven. / 未找到 Maven,请安装 Maven。 + exit /b 1 +) +echo [INFO] Maven found / 找到 Maven + +where node >nul 2>nul +if %ERRORLEVEL% neq 0 ( + echo [ERROR] Node.js not found. Please install Node.js. / 未找到 Node.js,请安装 Node.js。 + exit /b 1 +) +for /f "tokens=*" %%v in ('node -v') do echo [INFO] %%v found / 找到 %%v + +echo. + +REM Build backend / 构建后端 +echo [INFO] Building backend / 构建后端... +cd /d "%BACKEND_DIR%" +call mvn package -q +if %ERRORLEVEL% neq 0 ( + echo [ERROR] Backend build failed / 后端构建失败 + exit /b 1 +) +echo [INFO] Backend built successfully / 后端构建成功 +echo. + +REM Start backend / 启动后端 +echo [INFO] Starting backend server on port 8080 / 启动后端服务器 (端口 8080)... +cd /d "%PROJECT_DIR%" +start "ZP2 Backend" java -jar backend\target\zombie-crisis-server-1.0.0.jar +echo [INFO] Backend started in new window / 后端已在新窗口中启动 +echo. + +REM Start frontend / 启动前端 +echo [INFO] Starting frontend dev server / 启动前端开发服务器... +cd /d "%FRONTEND_DIR%" + +if not exist "node_modules\" ( + echo [INFO] Installing frontend dependencies / 安装前端依赖... + call npm install +) + +echo [INFO] Frontend dev server starting at http://localhost:3000 / 前端开发服务器地址: http://localhost:3000 +echo. +echo Press Ctrl+C to stop the frontend. Backend runs in a separate window. +echo 按 Ctrl+C 停止前端,后端运行在独立窗口中。 +echo. + +call npm run dev diff --git a/script/start.sh b/script/start.sh new file mode 100755 index 0000000..4ceb776 --- /dev/null +++ b/script/start.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# ZP2 Startup Script for macOS/Linux / ZP2 macOS/Linux 启动脚本 + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +BACKEND_DIR="$PROJECT_DIR/backend" +FRONTEND_DIR="$PROJECT_DIR/frontend" + +# Colors / 颜色 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# Check prerequisites / 检查依赖 +check_prereqs() { + info "Checking prerequisites / 检查依赖..." + + if ! command -v java &>/dev/null; then + error "Java not found. Please install Java 17+. / 未找到 Java,请安装 Java 17+。" + exit 1 + fi + + JAVA_VER=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | cut -d'.' -f1) + if [ "$JAVA_VER" -lt 17 ]; then + error "Java 17+ required, found Java $JAVA_VER / 需要 Java 17+,当前为 Java $JAVA_VER" + exit 1 + fi + info "Java $JAVA_VER OK" + + if ! command -v mvn &>/dev/null; then + error "Maven not found. Please install Maven. / 未找到 Maven,请安装 Maven。" + exit 1 + fi + info "Maven OK" + + if ! command -v node &>/dev/null; then + error "Node.js not found. Please install Node.js. / 未找到 Node.js,请安装 Node.js。" + exit 1 + fi + info "Node.js $(node -v) OK" +} + +# Build backend / 构建后端 +build_backend() { + info "Building backend / 构建后端..." + cd "$BACKEND_DIR" + mvn package -q + info "Backend built successfully / 后端构建成功" +} + +# Start backend / 启动后端 +start_backend() { + info "Starting backend server on port 8080 / 启动后端服务器 (端口 8080)..." + cd "$PROJECT_DIR" + nohup java -jar backend/target/zombie-crisis-server-1.0.0.jar > "$PROJECT_DIR/backend.log" 2>&1 & + BACKEND_PID=$! + echo "$BACKEND_PID" > "$PROJECT_DIR/.backend.pid" + info "Backend started (PID: $BACKEND_PID) / 后端已启动 (PID: $BACKEND_PID)" +} + +# Start frontend / 启动前端 +start_frontend() { + info "Starting frontend dev server / 启动前端开发服务器..." + cd "$FRONTEND_DIR" + + if [ ! -d "node_modules" ]; then + info "Installing frontend dependencies / 安装前端依赖..." + npm install + fi + + info "Frontend dev server starting at http://localhost:3000 / 前端开发服务器地址: http://localhost:3000" + npm run dev +} + +# Handle Ctrl+C / 处理 Ctrl+C +cleanup() { + if [ -f "$PROJECT_DIR/.backend.pid" ]; then + PID=$(cat "$PROJECT_DIR/.backend.pid") + info "Stopping backend (PID: $PID) / 停止后端 (PID: $PID)..." + kill "$PID" 2>/dev/null || true + rm -f "$PROJECT_DIR/.backend.pid" + fi + exit 0 +} + +trap cleanup INT TERM + +# Main / 主流程 +echo "==================================" +echo " ZP2 Game Server Launcher" +echo " ZP2 游戏服务器启动器" +echo "==================================" +echo "" + +check_prereqs +build_backend +start_backend +start_frontend