Initial commit: LynkedUp Fusion site

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Goutam
2026-07-17 14:51:13 +05:30
commit 74e52e9dcd
53 changed files with 9703 additions and 0 deletions
+256
View File
@@ -0,0 +1,256 @@
import * as THREE from "three";
function canvasTex(size, draw, repeatX = 1, repeatY = 1) {
const c = document.createElement("canvas");
c.width = c.height = size;
draw(c.getContext("2d"), size);
const t = new THREE.CanvasTexture(c);
t.wrapS = t.wrapT = THREE.RepeatWrapping;
t.repeat.set(repeatX, repeatY);
t.colorSpace = THREE.SRGBColorSpace;
t.anisotropy = 8;
return t;
}
function rand(seed) {
let s = seed;
return () => {
s = (s * 1103515245 + 12345) & 0x7fffffff;
return s / 0x7fffffff;
};
}
/* asphalt shingle courses with staggered tabs */
function drawShingles(g, S, base, seed = 7, weather = 0) {
const r = rand(seed);
g.fillStyle = base;
g.fillRect(0, 0, S, S);
const course = S / 10;
for (let row = 0; row < 10; row++) {
const y = row * course;
const off = (row % 2) * (course * 1.1);
for (let x = -1; x < 8; x++) {
const w = course * 2.2;
const px = x * w + off;
const shade = 0.82 + r() * 0.3 - weather * r() * 0.25;
g.fillStyle = `rgba(0,0,0,${Math.max(0, 1 - shade) * 0.9})`;
g.fillRect(px, y, w - 1.5, course - 1.5);
if (r() < 0.12 * (1 + weather * 3)) {
g.fillStyle = `rgba(255,255,255,${0.04 + r() * 0.05})`;
g.fillRect(px, y, w - 1.5, course - 1.5);
}
}
g.fillStyle = "rgba(0,0,0,.5)";
g.fillRect(0, y + course - 1.5, S, 1.5);
}
}
/* hail damage overlay: bruise blotches + torn patches */
function drawStormShingles(g, S) {
drawShingles(g, S, "#4c4a45", 11, 1);
const r = rand(41);
for (let i = 0; i < 7; i++) {
const x = r() * S, y = r() * S, rad = 18 + r() * 46;
const gr = g.createRadialGradient(x, y, 2, x, y, rad);
gr.addColorStop(0, "rgba(140,60,20,.5)");
gr.addColorStop(0.6, "rgba(120,50,25,.22)");
gr.addColorStop(1, "rgba(0,0,0,0)");
g.fillStyle = gr;
g.fillRect(x - rad, y - rad, rad * 2, rad * 2);
}
for (let i = 0; i < 3; i++) {
const x = r() * S * 0.8, y = r() * S * 0.8;
g.fillStyle = "rgba(15,14,12,.85)";
g.fillRect(x, y, 26 + r() * 30, 12 + r() * 10);
}
}
function drawClapboard(g, S, base, shadow) {
const board = S / 12;
for (let i = 0; i < 12; i++) {
const y = i * board;
const gr = g.createLinearGradient(0, y, 0, y + board);
gr.addColorStop(0, base);
gr.addColorStop(0.85, base);
gr.addColorStop(1, shadow);
g.fillStyle = gr;
g.fillRect(0, y, S, board);
}
const r = rand(5);
g.fillStyle = "rgba(0,0,0,.05)";
for (let i = 0; i < 40; i++) g.fillRect(r() * S, r() * S, 1.5, 4 + r() * 8);
}
function drawBrick(g, S) {
g.fillStyle = "#8a8078";
g.fillRect(0, 0, S, S);
const bh = S / 10, bw = S / 4;
const r = rand(23);
for (let row = 0; row < 10; row++) {
const off = (row % 2) * bw * 0.5;
for (let col = -1; col < 5; col++) {
const tone = 96 + r() * 46;
g.fillStyle = `rgb(${tone + 20},${tone * 0.62},${tone * 0.5})`;
g.fillRect(col * bw + off + 2, row * bh + 2, bw - 4, bh - 4);
}
}
}
function drawConcrete(g, S, tone = 168) {
g.fillStyle = `rgb(${tone},${tone},${tone - 4})`;
g.fillRect(0, 0, S, S);
const r = rand(3);
for (let i = 0; i < 900; i++) {
const v = r();
g.fillStyle = v > 0.5 ? "rgba(255,255,255,.05)" : "rgba(0,0,0,.06)";
g.fillRect(r() * S, r() * S, 1 + r() * 2, 1 + r() * 2);
}
}
function drawAsphalt(g, S) {
g.fillStyle = "#292b2a";
g.fillRect(0, 0, S, S);
const r = rand(17);
for (let i = 0; i < 1200; i++) {
const v = r();
g.fillStyle = v > 0.55 ? "rgba(255,255,255,.05)" : "rgba(0,0,0,.12)";
g.fillRect(r() * S, r() * S, 1 + r() * 2, 1 + r() * 2);
}
}
/* grass detail and the radial alpha falloff baked into ONE texture (repeat 1x1),
because standard materials share the map's UV transform across texture slots */
function drawGrassDisc(g, S) {
g.fillStyle = "#242d1b";
g.fillRect(0, 0, S, S);
const r = rand(9);
for (let i = 0; i < 26000; i++) {
const v = r();
const c = v > 0.66 ? "rgba(58,74,40,.5)" : v > 0.33 ? "rgba(34,44,26,.55)" : "rgba(20,26,16,.5)";
g.fillStyle = c;
g.fillRect(r() * S, r() * S, 1 + r() * 2.4, 1 + r() * 2);
}
/* mow lines */
g.fillStyle = "rgba(255,255,255,.016)";
for (let i = 0; i < 14; i++) g.fillRect(0, i * (S / 14), S, S / 28);
/* carve the radial fade into the alpha channel */
g.globalCompositeOperation = "destination-in";
const gr = g.createRadialGradient(S / 2, S / 2, S * 0.18, S / 2, S / 2, S * 0.5);
gr.addColorStop(0, "rgba(0,0,0,1)");
gr.addColorStop(0.5, "rgba(0,0,0,1)");
gr.addColorStop(0.78, "rgba(0,0,0,.45)");
gr.addColorStop(0.98, "rgba(0,0,0,0)");
g.fillStyle = gr;
g.fillRect(0, 0, S, S);
g.globalCompositeOperation = "source-over";
}
/* street strip: asphalt with lane dashes, alpha-faded at both ends */
export function drawRoad(g, S) {
drawAsphalt(g, S);
g.fillStyle = "rgba(210,200,160,.5)";
for (let i = 0; i < 9; i++) g.fillRect(i * (S / 9) + S / 36, S * 0.485, S / 18, S * 0.03);
g.fillStyle = "rgba(220,220,220,.28)";
g.fillRect(0, S * 0.06, S, S * 0.016);
g.fillRect(0, S * 0.924, S, S * 0.016);
g.globalCompositeOperation = "destination-in";
const gr = g.createLinearGradient(0, 0, S, 0);
gr.addColorStop(0, "rgba(0,0,0,0)");
gr.addColorStop(0.18, "rgba(0,0,0,1)");
gr.addColorStop(0.82, "rgba(0,0,0,1)");
gr.addColorStop(1, "rgba(0,0,0,0)");
g.fillStyle = gr;
g.fillRect(0, 0, S, S);
g.globalCompositeOperation = "source-over";
}
/* soft dark contact blob that grounds whatever stands on it */
export function drawContact(g, S) {
const gr = g.createRadialGradient(S / 2, S / 2, S * 0.05, S / 2, S / 2, S * 0.5);
gr.addColorStop(0, "rgba(0,0,0,.62)");
gr.addColorStop(0.55, "rgba(0,0,0,.34)");
gr.addColorStop(1, "rgba(0,0,0,0)");
g.fillStyle = gr;
g.fillRect(0, 0, S, S);
}
function drawGaragePanels(g, S) {
g.fillStyle = "#d8d6d0";
g.fillRect(0, 0, S, S);
const ph = S / 4;
for (let i = 0; i < 4; i++) {
const y = i * ph;
const gr = g.createLinearGradient(0, y, 0, y + ph);
gr.addColorStop(0, "rgba(0,0,0,.22)");
gr.addColorStop(0.14, "rgba(255,255,255,.16)");
gr.addColorStop(0.85, "rgba(0,0,0,.05)");
gr.addColorStop(1, "rgba(0,0,0,.34)");
g.fillStyle = gr;
g.fillRect(0, y, S, ph);
}
}
/* soft elliptical glow for the LiDAR sheet: fades before every plane edge */
function drawScanBand(g, S) {
const gr = g.createRadialGradient(S / 2, S / 2, S * 0.05, S / 2, S / 2, S * 0.5);
gr.addColorStop(0, "rgba(255,214,90,.85)");
gr.addColorStop(0.45, "rgba(255,184,0,.34)");
gr.addColorStop(1, "rgba(255,184,0,0)");
g.fillStyle = gr;
g.fillRect(0, 0, S, S);
}
export function makeMaterials() {
const std = (o) => new THREE.MeshStandardMaterial(o);
const shingleTex = canvasTex(256, (g, s) => drawShingles(g, s, "#2b2e32", 7), 6, 3);
const shingleWarmTex = canvasTex(256, (g, s) => drawShingles(g, s, "#352e26", 19), 6, 3);
const stormTex = canvasTex(512, (g, s) => drawStormShingles(g, s), 3, 1.6);
return {
shingle: std({ map: shingleTex, roughness: 0.92, metalness: 0.02 }),
shingleWarm: std({ map: shingleWarmTex, roughness: 0.92, metalness: 0.02 }),
shingleStorm: std({ map: stormTex, roughness: 0.95, metalness: 0.02 }),
sidingWhite: std({ map: canvasTex(256, (g, s) => drawClapboard(g, s, "#c9c6bd", "#8f8d85"), 3, 3), roughness: 0.85 }),
sidingBeige: std({ map: canvasTex(256, (g, s) => drawClapboard(g, s, "#b3a58c", "#7d7360"), 3, 3), roughness: 0.85 }),
sidingGray: std({ map: canvasTex(256, (g, s) => drawClapboard(g, s, "#9aa0a2", "#6a6f71"), 3, 3), roughness: 0.85 }),
trim: std({ color: 0xe8e6df, roughness: 0.6 }),
fascia: std({ color: 0xdad7cf, roughness: 0.65 }),
doorRed: std({ color: 0x7e2f26, roughness: 0.5 }),
doorBlue: std({ color: 0x2c3f4d, roughness: 0.5 }),
glass: std({ color: 0x0e1a22, roughness: 0.08, metalness: 0.85, envMapIntensity: 1.4 }),
garage: std({ map: canvasTex(256, drawGaragePanels, 1, 1), roughness: 0.6 }),
brick: std({ map: canvasTex(256, drawBrick, 1.4, 2.4), roughness: 0.95 }),
concrete: std({ map: canvasTex(256, (g, s) => drawConcrete(g, s), 2, 2), roughness: 0.95 }),
asphalt: std({ map: canvasTex(256, drawAsphalt, 2, 3), roughness: 0.98 }),
grass: std({
map: canvasTex(2048, drawGrassDisc),
transparent: true,
roughness: 1,
depthWrite: false,
}),
soil: std({ color: 0x2b2822, roughness: 1 }),
trunk: std({ color: 0x3c2f21, roughness: 1 }),
foliage: std({ color: 0x33431f, roughness: 1, flatShading: true }),
foliage2: std({ color: 0x415229, roughness: 1, flatShading: true }),
contact: new THREE.MeshBasicMaterial({
map: canvasTex(512, drawContact),
transparent: true,
depthWrite: false,
}),
road: std({ map: canvasTex(512, drawRoad), transparent: true, roughness: 0.98, depthWrite: false }),
tarp: std({ color: 0x27506e, roughness: 0.55, side: THREE.DoubleSide }),
porchRoof: std({ color: 0x3a3d40, roughness: 0.7, metalness: 0.25 }),
hlAmber: new THREE.MeshBasicMaterial({ color: 0xffb800, transparent: true, opacity: 0.95 }),
hlFace: new THREE.MeshBasicMaterial({ color: 0xffc233, transparent: true, opacity: 0.62, side: THREE.DoubleSide, depthWrite: false }),
impact: std({ color: 0xff5533, emissive: 0xd23c14, emissiveIntensity: 0.9, roughness: 0.6 }),
scan: new THREE.MeshBasicMaterial({
map: canvasTex(256, drawScanBand),
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
side: THREE.DoubleSide,
}),
};
}
/* alpha-fade note: the grass disc uses alphaMap + depthWrite:false so the lawn
dissolves radially into the page background instead of ending in a hard edge */