Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Metadata } from 'next';
import './globals.css';
import { Providers } from '@/components/Providers';

export const metadata: Metadata = {
title: 'VOIDSTRIKE - Browser-Based RTS',
Expand All @@ -18,15 +17,13 @@
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link

Check warning on line 20 in src/app/layout.tsx

View workflow job for this annotation

GitHub Actions / lint

Custom fonts not added in `pages/_document.js` will only load for a single page. This is discouraged. See: https://nextjs.org/docs/messages/no-page-custom-font
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Orbitron:wght@400;500;600;700;800;900&display=swap"
rel="stylesheet"
/>
</head>
<body className="font-sans bg-black text-white antialiased">
<Providers>
{children}
</Providers>
{children}
</body>
</html>
);
Expand Down
11 changes: 0 additions & 11 deletions src/components/Providers.tsx

This file was deleted.

60 changes: 2 additions & 58 deletions src/components/game/LoadingScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { NOISE_LIBRARY } from '@/rendering/shaders/noise.glsl';

interface LoadingScreenProps {
progress: number;
Expand Down Expand Up @@ -40,64 +41,7 @@ const VoidNebulaShader = {
uniform vec2 uMouse;
varying vec2 vUv;

vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }

float snoise(vec3 v) {
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v - i + dot(i, C.xxx);
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy;
vec3 x3 = x0 - D.yyy;
i = mod289(i);
vec4 p = permute(permute(permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
float n_ = 0.142857142857;
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_);
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
vec3 p0 = vec3(a0.xy, h.x);
vec3 p1 = vec3(a0.zw, h.y);
vec3 p2 = vec3(a1.xy, h.z);
vec3 p3 = vec3(a1.zw, h.w);
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
}

float fbm(vec3 p) {
float value = 0.0;
float amplitude = 0.5;
for (int i = 0; i < 6; i++) {
value += amplitude * snoise(p);
amplitude *= 0.5;
p *= 2.0;
}
return value;
}
${NOISE_LIBRARY}

void main() {
vec2 uv = vUv;
Expand Down
65 changes: 2 additions & 63 deletions src/components/home/HomeBackground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { NOISE_LIBRARY } from '@/rendering/shaders/noise.glsl';

// Custom void nebula shader
const VoidNebulaShader = {
Expand Down Expand Up @@ -33,69 +34,7 @@ const VoidNebulaShader = {
uniform vec3 uColor3;
varying vec2 vUv;

// Simplex noise functions
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }

float snoise(vec3 v) {
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v - i + dot(i, C.xxx);
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy;
vec3 x3 = x0 - D.yyy;
i = mod289(i);
vec4 p = permute(permute(permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
float n_ = 0.142857142857;
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_);
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
vec3 p0 = vec3(a0.xy, h.x);
vec3 p1 = vec3(a0.zw, h.y);
vec3 p2 = vec3(a1.xy, h.z);
vec3 p3 = vec3(a1.zw, h.w);
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
}

float fbm(vec3 p) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < 6; i++) {
value += amplitude * snoise(p * frequency);
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
${NOISE_LIBRARY}

void main() {
vec2 uv = vUv;
Expand Down
4 changes: 0 additions & 4 deletions src/engine/ai/FormationControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ export class FormationControl {
const arcSlots = this.calculateArcPositions(
group.center,
facing,
enemyCenter,
meleeUnits.length,
4 // Distance from center to front
);
Expand All @@ -238,7 +237,6 @@ export class FormationControl {
const rangedArcSlots = this.calculateArcPositions(
group.center,
facing,
enemyCenter,
rangedUnits.length,
-this.config.rangedOffset // Behind center
);
Expand All @@ -257,7 +255,6 @@ export class FormationControl {
const siegeSlots = this.calculateArcPositions(
group.center,
facing,
enemyCenter,
siegeUnits.length,
-this.config.rangedOffset * 2
);
Expand Down Expand Up @@ -301,7 +298,6 @@ export class FormationControl {
private calculateArcPositions(
center: { x: number; y: number },
facing: { x: number; y: number },
enemyCenter: { x: number; y: number },
count: number,
distanceOffset: number
): Array<{ x: number; y: number }> {
Expand Down
8 changes: 1 addition & 7 deletions src/engine/ai/InfluenceMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,17 +313,11 @@ export class InfluenceMap {
if (players.length < 2) return;

for (let i = 0; i < this.threatMap.length; i++) {
let maxInfluence = 0;
let totalInfluence = 0;
let _dominantPlayer: string | null = null;

for (const [playerId, data] of players) {
for (const [, data] of players) {
const influence = data.grid[i];
totalInfluence += influence;
if (influence > maxInfluence) {
maxInfluence = influence;
_dominantPlayer = playerId;
}
}

this.threatMap[i] = totalInfluence;
Expand Down
91 changes: 1 addition & 90 deletions src/rendering/InstancedDecorations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { MapData, MapDecoration } from '@/data/maps';
import AssetManager from '@/assets/AssetManager';
import { DECORATIONS } from '@/data/rendering.config';
import { TransformUtils } from './shared/InstancedMeshPool';
import { cloneGeometryForGPU } from './utils/geometryUtils';

// PERF: Shared transform utilities for all decoration classes (avoids duplicate temp objects)
const _transformUtils = new TransformUtils();
Expand Down Expand Up @@ -33,96 +34,6 @@ const GEOMETRY_QUARANTINE_FRAMES = 4;
// Shared frame counter for quarantine timing (updated by updateDecorationFrustum)
let _frameCount = 0;

/**
* Clone geometry with completely fresh GPU buffers for WebGPU.
* Creates new TypedArrays for all attributes and index to ensure zero shared state
* with the source geometry. This prevents "setIndexBuffer" crashes when source
* geometry is disposed while clones are still being rendered.
* Also ensures required attributes (like UVs) exist to prevent "Vertex buffer slot" errors.
*/
function cloneGeometryForGPU(source: THREE.BufferGeometry): THREE.BufferGeometry {
const cloned = new THREE.BufferGeometry();

// Copy all attributes with fresh TypedArrays (no shared references)
// Skip color attributes - AI models bake AO into vertex colors causing artifacts
for (const name of Object.keys(source.attributes)) {
if (name === 'color' || name.match(/^_?color_?\d+$/i)) {
continue; // Skip vertex colors entirely
}
const srcAttr = source.attributes[name];
// Create a completely new TypedArray by slicing (creates a copy)
const newArray = srcAttr.array.slice(0);
const newAttr = new THREE.BufferAttribute(newArray, srcAttr.itemSize, srcAttr.normalized);
newAttr.needsUpdate = true;
cloned.setAttribute(name, newAttr);
}

// Copy index with fresh TypedArray if present
if (source.index) {
const srcIndex = source.index;
const newIndexArray = srcIndex.array.slice(0);
const newIndex = new THREE.BufferAttribute(newIndexArray, srcIndex.itemSize, srcIndex.normalized);
newIndex.needsUpdate = true;
cloned.setIndex(newIndex);
}

// Copy morph attributes if present
if (source.morphAttributes) {
for (const name of Object.keys(source.morphAttributes)) {
const srcMorphArray = source.morphAttributes[name];
cloned.morphAttributes[name] = srcMorphArray.map(srcAttr => {
const newArray = srcAttr.array.slice(0);
const newAttr = new THREE.BufferAttribute(newArray, srcAttr.itemSize, srcAttr.normalized);
newAttr.needsUpdate = true;
return newAttr;
});
}
}

// Copy bounding volumes if computed
if (source.boundingBox) {
cloned.boundingBox = source.boundingBox.clone();
}
if (source.boundingSphere) {
cloned.boundingSphere = source.boundingSphere.clone();
}

// Copy groups
for (const group of source.groups) {
cloned.addGroup(group.start, group.count, group.materialIndex);
}

// Ensure UV coordinates exist - required by many shaders (slot 1)
// Some models from Tripo/Meshy AI lack UVs, causing "Vertex buffer slot 1" errors
if (!cloned.attributes.uv && cloned.attributes.position) {
const posCount = cloned.attributes.position.count;
const uvArray = new Float32Array(posCount * 2);
// Generate basic UV coords based on position (simple projection)
const pos = cloned.attributes.position;
for (let i = 0; i < posCount; i++) {
uvArray[i * 2] = pos.getX(i) * 0.5 + 0.5;
uvArray[i * 2 + 1] = pos.getZ(i) * 0.5 + 0.5;
}
cloned.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2));
}

// ALWAYS recompute smooth vertex normals - AI-generated models often have
// faceted/flat normals that cause triangular artifacts visible at higher resolutions.
// The original GLTF normals may look fine at low res but show per-triangle
// variations at native resolution.
if (cloned.attributes.position) {
// Delete existing normals so computeVertexNormals generates fresh smooth ones
cloned.deleteAttribute('normal');
cloned.computeVertexNormals();
}
// Ensure normals are uploaded to GPU
if (cloned.attributes.normal) {
cloned.attributes.normal.needsUpdate = true;
}

return cloned;
}

/**
* Update the shared frustum from camera matrices.
* Also stores camera position for distance-based culling.
Expand Down
Loading
Loading