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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import * as THREE from 'three';
import {RoundedBoxGeometry} from 'three/addons/geometries/RoundedBoxGeometry.js';
const navigationRoutes=new WeakMap();
export function rallyNavigation(road,race){
let route=navigationRoutes.get(road);
if(!route){const xs=road.points.map(p=>p.x),zs=road.points.map(p=>p.z),minX=Math.min(...xs),minZ=Math.min(...zs),span=Math.max(Math.max(...xs)-minX,Math.max(...zs)-minZ,1),centerX=(Math.max(...xs)+minX)/2,centerZ=(Math.max(...zs)+minZ)/2;
const project=p=>({x:.5+(p.x-centerX)/span,y:.5+(p.z-centerZ)/span});route={project,path:road.points.map(project)};navigationRoutes.set(road,route);
}
return {path:route.path,markers:[race.player,...race.opponents].map((p,i)=>({...route.project(p),number:i+1,player:i===0,gap:p.travel-race.player.travel,finished:Boolean(p.finished)}))};
}
// Original, unbranded long-hood GT silhouette. X is forward; dimensions are metres.
export function buildRallyCar({high,paint,mesh,mat,parent}){
const rubber=mat('#101419');rubber.roughness=.96;rubber.metalness=0;
Expand All @@ -19,14 +27,14 @@ export function buildRallyCar({high,paint,mesh,mat,parent}){
const body=new THREE.BufferGeometry();body.setAttribute('position',new THREE.Float32BufferAttribute(pos,3));body.setIndex(indices);body.computeVertexNormals();mesh(body,paint,0,0,0,parent);
// Muscular wheel haunches and a short fastback rear are present in both quality modes.
for(const x of [-1.28,1.32])for(const z of [-.82,.82]){const f=mesh(new THREE.SphereGeometry(.6,high?24:10,high?14:6),paint,x,.59,z,parent);f.scale.set(1,.54,.36);}
const canopy=new THREE.Group();parent.add(canopy);
const glassSections=[[-1.55,.70,.78],[-.78,.66,1.22],[-.12,.64,1.25],[.68,.73,.82]];
const canopy=new THREE.Group();parent.add(canopy);const glassPanes=[];
const glassSections=[[-1.55,.76,.78],[-1.02,.73,1.55],[-.21,.73,1.58],[.78,.79,.80]];
for(let i=0;i<glassSections.length-1;i++){const [x0,w0,h0]=glassSections[i],[x1,w1,h1]=glassSections[i+1],v=[];
for(const [x,w,h] of [[x0,w0,h0],[x1,w1,h1]])v.push(x,h,-w,x,h,w,x,.79,-w,x,.79,w);
const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(v,3));g.setIndex([0,1,4,1,5,4,0,4,2,2,4,6,1,3,5,3,7,5]);g.computeVertexNormals();const pane=mesh(g,glass,0,0,0,canopy);pane.material.side=THREE.DoubleSide;
const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(v,3));g.setIndex([0,1,4,1,5,4,0,4,2,2,4,6,1,3,5,3,7,5]);g.computeVertexNormals();const pane=mesh(g,glass,0,0,0,canopy);pane.material.side=THREE.DoubleSide;glassPanes.push(pane);
}
mesh(round(.76,.055,1.28,.025),paint,-.43,1.26,0,canopy);
for(const z of [-1,1]){rod([.66,.8,z*.74],[-.12,1.25,z*.65],.027,paint,canopy);rod([-.8,1.22,z*.66],[-1.57,.79,z*.7],.032,paint,canopy);rod([-.78,.8,z*.77],[-.78,1.23,z*.66],.018,trim,canopy);mesh(round(.27,.12,.18,.05),paint,.38,.96,z*.98,parent);}
const roof=mesh(round(.88,.065,1.49,.035),paint,-.62,1.585,0,canopy);roof.name='solid-gt-roof';
for(const z of [-1,1]){rod([.78,.8,z*.79],[-.21,1.58,z*.73],.026,paint,canopy);rod([-1.02,1.55,z*.73],[-1.57,.79,z*.76],.032,paint,canopy);rod([-1.02,.8,z*.79],[-1.02,1.55,z*.73],.018,trim,canopy);mesh(round(.27,.12,.18,.05),paint,.38,.96,z*.98,parent);}
mesh(round(.12,.27,1.42,.06),trim,2.24,.46,0,parent);
mesh(round(.09,.19,1.12,.035),rubber,2.32,.52,0,parent);
if(high)for(let j=0;j<13;j++)mesh(round(.025,.15,.023,.008),alloy,2.375,.53,(j-6)*.075,parent);
Expand All @@ -53,9 +61,9 @@ export function buildRallyCar({high,paint,mesh,mat,parent}){
const centerDisplay=mesh(round(.018,.16,.28,.018),display,-.011,.98,-.17,interior);
let dashboardTexture=null,dashboardCanvas=null;
if(typeof document!=='undefined'){
dashboardCanvas=document.createElement('canvas');dashboardCanvas.width=512;dashboardCanvas.height=256;dashboardTexture=new THREE.CanvasTexture(dashboardCanvas);dashboardTexture.colorSpace=THREE.SRGBColorSpace;const m=screen.clone();m.color.set('#ffffff');m.emissive.set('#ffffff');m.map=dashboardTexture;m.emissiveMap=dashboardTexture;centerDisplay.material=m;
dashboardCanvas=document.createElement('canvas');dashboardCanvas.width=512;dashboardCanvas.height=384;dashboardTexture=new THREE.CanvasTexture(dashboardCanvas);dashboardTexture.colorSpace=THREE.SRGBColorSpace;const m=screen.clone();m.color.set('#ffffff');m.emissive.set('#ffffff');m.map=dashboardTexture;m.emissiveMap=dashboardTexture;centerDisplay.material=m;
// Face the driver (-X); a real screen shows speed, gear, RPM and shift indication.
centerDisplay.geometry.dispose();centerDisplay.geometry=new THREE.PlaneGeometry(.27,.145);centerDisplay.rotation.y=-Math.PI/2;centerDisplay.position.x=-.023;
centerDisplay.geometry.dispose();centerDisplay.geometry=new THREE.PlaneGeometry(.34,.255);centerDisplay.rotation.y=-Math.PI/2;centerDisplay.position.set(-.024,.98,-.17);
}
for(const z of [-.61,-.39,.64]){
const vent=mesh(new THREE.TorusGeometry(.05,.008,6,high?32:12),alloy,-.022,.88,z,interior);vent.rotation.y=Math.PI/2;
Expand All @@ -78,17 +86,22 @@ export function buildRallyCar({high,paint,mesh,mat,parent}){
function hand(group){const palm=mesh(new THREE.SphereGeometry(.038,12,8),glove,0,0,0,group);palm.scale.set(.9,1.2,.65);for(let i=0;i<4;i++)mesh(round(.013,.041,.016,.006),glove,.021-i*.015,.025,-.015,group);mesh(round(.075,.045,.048,.018),suit,-.075,-.01,0,group);}
hand(leftHand);hand(rightHand);
const cockpitFrame=new THREE.Group();interior.add(cockpitFrame);
for(const z of [-1,1]){rod([.65,.86,z*.74],[-.12,1.25,z*.65],.026,leather,cockpitFrame);mesh(round(.68,.035,.045,.01),leather,-.45,1.23,z*.63,cockpitFrame);}
for(const z of [-1,1]){rod([.78,.86,z*.79],[-.21,1.56,z*.73],.020,leather,cockpitFrame);mesh(round(.86,.035,.045,.01),leather,-.62,1.545,z*.71,cockpitFrame);}
// Interior rear-view mirror, roof liner and sun visors, without hiding the road.
mesh(round(.50,.025,1.25,.015),leather,-.86,1.245,0,cockpitFrame);mesh(round(.09,.065,.21,.012),alloy,.26,1.18,0,cockpitFrame);
for(const z of [-.36,.36])mesh(round(.15,.016,.25,.006),leather,-.03,1.22,z,cockpitFrame);
function animate(state,dt){
mesh(round(.88,.025,1.45,.015),leather,-.62,1.545,0,cockpitFrame);mesh(round(.07,.055,.19,.012),alloy,.13,1.45,0,cockpitFrame);
for(const z of [-.40,.40])mesh(round(.15,.016,.25,.006),leather,-.25,1.52,z,cockpitFrame);
function setCockpit(active){canopy.visible=true;roof.visible=true;interior.visible=active;for(const pane of glassPanes)pane.visible=!active;}
function animate(state,dt,navigation){
steering.rotation.z=-(state.steer||0)*.85;
needles[0].rotation.x=2.35-Math.min(1,(state.rpm??900)/7500)*4.7;needles[1].rotation.x=2.35-Math.min(1,Math.abs(state.speed)/60)*4.7;
const phase=state.shift>0?Math.sin(Math.PI*(1-state.shift/.32)):0;shifter.rotation.z=phase*.22;
leftHand.position.set(-.21,1.025,.515);rightHand.position.set(-.21+phase*.01,1.025-phase*.055,.225-phase*.21);
leftHand.rotation.x=-(state.steer||0)*.5;rightHand.rotation.x=-(state.steer||0)*.5*(1-phase);
if(dashboardCanvas){const c=dashboardCanvas.getContext('2d');c.fillStyle='#07131d';c.fillRect(0,0,512,256);c.fillStyle='#b2eef1';c.font='bold 90px sans-serif';c.fillText(state.gear===-1?'R':String(state.gear??1),24,108);c.font='bold 52px sans-serif';c.fillText(String(Math.round(Math.abs(state.speed)*3.6)).padStart(3,'0'),170,97);c.font='22px sans-serif';c.fillText('km/h',178,129);c.fillText(`${Math.round(state.rpm??900)} RPM`,24,193);c.fillStyle=phase>0?'#ffb955':'#66c9c9';c.fillRect(24,216,Math.min(464,(state.rpm??900)/7500*464),9);dashboardTexture.needsUpdate=true;}
if(dashboardCanvas){const c=dashboardCanvas.getContext('2d');c.fillStyle='#07131d';c.fillRect(0,0,512,384);c.fillStyle='#b2eef1';c.font='bold 23px sans-serif';c.fillText('LIVE TRACK · YOU 1',18,28);
if(navigation){const size=202,ox=144,oy=40;c.strokeStyle='#64778b';c.lineWidth=9;c.lineJoin='round';c.beginPath();navigation.path.forEach((p,i)=>c[i?'lineTo':'moveTo'](ox+p.x*size,oy+p.y*size));c.closePath();c.stroke();
for(const p of navigation.markers){c.beginPath();c.fillStyle=p.player?'#ffffff':['','#ffffff','#3da5bd','#f3bd47','#536ed0','#78a755'][p.number];c.arc(ox+p.x*size,oy+p.y*size,p.player?9:7,0,Math.PI*2);c.fill();c.font='bold 16px sans-serif';c.fillText(String(p.number),ox+p.x*size+10,oy+p.y*size-6);}
const behind=navigation.markers.filter(p=>!p.player&&p.gap<0&&!p.finished).sort((a,b)=>b.gap-a.gap)[0];c.fillStyle='#b2eef1';c.font='20px sans-serif';c.fillText(behind?`BEHIND: ${behind.number} · ${Math.round(-behind.gap)} m`:'BEHIND: clear',18,274);
}c.fillStyle='#b2eef1';c.font='bold 46px sans-serif';c.fillText(state.gear===-1?'R':String(state.gear??1),20,328);c.font='bold 32px sans-serif';c.fillText(`${Math.round(Math.abs(state.speed)*3.6)} km/h`,94,320);c.font='18px sans-serif';c.fillText(`${Math.round(state.rpm??900)} RPM`,326,320);c.fillStyle=phase>0?'#ffb955':'#66c9c9';c.fillRect(18,355,Math.min(476,(state.rpm??900)/7500*476),8);dashboardTexture.needsUpdate=true;}
}
const wheels=[];for(const x of [-1.28,1.32])for(const z of [-.96,.96]){const axle=new THREE.Group();axle.position.set(x,.37,z);parent.add(axle);const spin=new THREE.Group();axle.add(spin);
const tire=mesh(new THREE.TorusGeometry(.27,.105,high?12:6,high?32:12),rubber,0,0,0,spin);tire.scale.z=1.30;
Expand All @@ -98,5 +111,5 @@ export function buildRallyCar({high,paint,mesh,mat,parent}){
mesh(round(.075,.16,.045,.015),tail,.12,.04,-Math.sign(z)*.09,axle);
}wheels.push({steering:axle,spin,front:x>0});
}
return {parent,wheels,tail,canopy,interior,steering,body,shifter,needles,animate,dashboardTexture};
return {parent,wheels,tail,canopy,roof,glassPanes,interior,steering,body,shifter,needles,animate,setCockpit,dashboardTexture};
}
6 changes: 6 additions & 0 deletions local/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# BeeBright releases

## 2.5 bugfix

- Solid GT roof stays visible in both cockpit and chase cameras.
- Taller, wider windshield, slimmer interior pillars and a wider cockpit field of view.
- Live dashboard navigation shows the complete circuit, all five cars, and the nearest car behind with its distance. Speed, gear and RPM remain visible.

## 2.5 racing beta

- Tracks widened to 18–20 metres, with faster rivals and stable, committed overtaking instead of rapid side-to-side lane changes.
Expand Down
2 changes: 1 addition & 1 deletion local/release.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"BeeBright","version":"2.5 racing beta"}
{"name":"BeeBright","version":"2.5 bugfix"}
Loading