Contexte
Le port précédent (sessions 75-123) utilisait le pipeline d’éclairage JME
standard : AmbientLight + DirectionalLight + headLight (PointLight
attachée à la caméra) + un PointLight par zone très brillante. Cette
approche était fonctionnelle mais ne reproduisait pas les variations de
luminosité fines de l’Amiga (gradients de murs, flicker des lampes,
pulse, etc.) et coûtait du temps GPU avec Lighting.j3md + le mode
SinglePass à plusieurs lumières.
L’ASM original (cf. hires.s lignes 1455-1543 et hireswall.s
2120-2390) calcule la luminosité par sommet à partir de deux tables :
– pointBrights[zoneId][i] (40 entrées par zone) : un Lb signed
-20..+20 par point, plus un byte d’animation (phase + animIdx).
– Zone.brightness : Wb global de la zone (même format).
La formule runtime est :
Lb = (signed byte) lowByte(rawWord)
si animIdx != 0 et Lb >= 0 :
Lb += ((animTable[animIdx-1] - Lb) * phase) >> 4
LbScaled = (Lb * 397) >> 8 # ≈ 1.55 × Lb
Rb_point = LbScaled + (LbScaled>=0 ? 300 : -300)
Wb = (zone.brightness * 410) >> 8 # ≈ 1.6 × Wb
wallBright = abs(Rb_point) + Wb # = 1.6×|Lb| + Wb + 300
L’index wallBright adresse la palette de shading pré-calculée Amiga.
Décision
Option A retenue : tout en Unshaded.j3md + VertexColor=true, plus
aucune lumière JME. La luminosité est entièrement bakée dans les VertexBuffer.Color
des meshes, modulée à partir de la formule ASM via la nouvelle classe
com.ab3d2.world.BrightnessCalc. Les sprites/vectobj sont teintés par la
brightness de leur zone hôte (Material.setColor("Color", grayTint)).
Modifications
Nouveau fichier world/BrightnessCalc.java
– computePointRb(rawWord) / computePointRbAnimated(rawWord) : Rb_point ASM-fidèle.
– computeZoneWb(brightness) / computeZoneWbAnimated(brightness) : Wb zone.
– vertexIntensity(rbPoint, wb) : combine les 2 → intensité [0..1] pour le buffer Color.
– zoneOnlyIntensity(wb) : pour sols/plafonds/sprites (pas de Lb par-vertex).
– Constantes BASE=100, NORMALIZER=400, MIN_INTENSITY=0.10, AMBIENT_C=300
+ table ANIM_BRIGHT_TABLE (7 patterns + extensions, phase B).
tools/LevelJsonExporter.java
– Export du tableau pointIds[] par zone (jusqu’à 40 IDs de points), index
parallèle à pointBrights[zoneIdx][i]. Permet la lookup
globalPtId → pointBrightsIdx → rawWord côté builder de scène.
– pointBrights et zoneBorderPoints étaient déjà exportés depuis la session 118.
tools/LevelSceneBuilder.java
– Record ZD : ajout des champs int[] pointIds et int[] pointBrights.
– Record PolyData : ajout du champ float intensity.
– Méthode parsePointBrights(json) : parsing du tableau top-level
pointBrights[][] en Map<zoneId, int[40]>.
– Méthode parseZones() : utilise pointIds + pointBrights (fallback int[0]
pour anciens JSON).
– Méthodes wallVertexIntensity(globalPtId, zone, wbZone) et
findPointIndexInZone(globalPtId, zonePointIds[]) : lookup brightness par
vertex de mur.
– Boucle de murs (buildScene) : précalcule wallIL/wallIR pour le
segment complet (left/right point), puis lerp par sub-quad selon
segStart/segEnd. Payload étendu de 9 → 11 floats : [..., uOffset, iL, iR].
– buildWallGeo : ajoute le buffer Color (16 floats par quad) avec
gradient horizontal (BL/TL=iL, BR/TR=iR).
– buildHorizGeo : ajoute le buffer Color constant uniforme par polygone
(intensité issue de pd.intensity()).
– collectHoriz(...) : nouveau paramètre float intensity propagé dans
les PolyData créés.
– makeDoorSegGeo(...) : nouveau paramètre float intensity + écriture
d’un Color buffer constant 4 vertex.
– makePolyCapGeo(...) : nouveau paramètre float intensity (utilisé
pour le plafond mobile de zone-porte).
– Sol dynamique de lift et lift sides : ajout des buffers Color
constants (intensité = BrightnessCalc.zoneOnlyIntensity(zone.brightness)).
– Nouvel helper makeGeoWithColor(name, pos, uv, nor, col, idx) (variante
de makeGeo avec buffer Color).
– texMat() : passage Lighting.j3md → Unshaded.j3md + VertexColor=true,
paramètre texture ColorMap (au lieu de DiffuseMap).
– tryLoadSprite() : Unshaded.j3md, tinte le sprite par la zoneTint de
sa zone hôte (Material.setColor("Color", grayTint)).
– addItems(json, items, fb, objDefs, alienDefs, zones) : nouveau
paramètre Map<zoneId, ZD> pour calculer le zoneTint de chaque sprite
via BrightnessCalc.zoneOnlyIntensity(BrightnessCalc.computeZoneWb(...)).
– Lecture des dimensions de texture : m.getTextureParam("ColorMap") au
lieu de "DiffuseMap".
– Suppression complète des blocs AmbientLight et PointLight (plus aucune
lumière JME ajoutée à la rootNode). Le Node lights est conservé vide
pour la compatibilité descendante.
app/GameAppState.java
– Suppression des champs ambientLight, sunLight, headLight et des
constantes AMBIENT_COLOR, SUN_COLOR, SUN_DIRECTION, HEADLIGHT_COLOR,
HEADLIGHT_RANGE.
– Suppression des imports AmbientLight, DirectionalLight, PointLight.
– Suppression des appels setPreferredLightMode et setSinglePassLightBatchSize.
– Remplacement de upgradeMaterialsForLighting() par
forceUnshadedVertexColor(Node root) qui convertit les matériaux
Lighting.j3md orphelins (vectobj .j3o pré-construits avec
Lighting.j3md) en Unshaded.j3md + VertexColor=true. Sans cette
conversion, les vectobj apparaîtraient en silhouette noire car ils
attendent un AmbientLight qu’on a retiré.
– Suppression de ensureLightingParams(Material) (devenu inutile).
– Suppression des headLight.setPosition(...) dans update() et
positionCameraAtSpawn().
– Suppression des removeLight(...) dans cleanup().
– Suppression de la mention lights.getQuantity() du printf final
de buildScene (le Node lights est désormais toujours vide).
weapon/WeaponViewAppState.java (complément session 124)
– Suppression de l’AmbientLight (gris bleuté 0.9) et de la
DirectionalLight (fill blanc 0.6) attachées au weaponRoot.
– Suppression des imports AmbientLight et DirectionalLight.
– fixWeaponMaterials() : convertit désormais les matériaux Lighting.j3md
ET Unshaded legacy en un Unshaded.j3md propre. Avant la session 124,
ne traitait que le cas Unshaded (héritage session 92).
– upgradeVectObjMaterialInPlace() : produit maintenant un Unshaded.j3md
(au lieu de Lighting.j3md). Lit le ColorMap ET le DiffuseMap (cas
.j3o pré-construits en Lighting). Détecte VertexColor et
UseVertexColor pour compat avec les deux générations de .j3o.
– Justification : l’arme du joueur n’est jamais teintée par la luminosité
de la zone dans l’ASM original — elle est dessinée en pleine palette
nominale, comme un overlay foreground. Donc Unshaded sans tint convient.
tools/VectObjConverter.java (complément session 124)
– buildMaterial() : passage de Lighting.j3md (session 92) à
Unshaded.j3md. Texture sur ColorMap au lieu de DiffuseMap.
Boolean VertexColor=true (Unshaded) au lieu de UseVertexColor=true
(Lighting). Suppression de UseMaterialColors + Ambient/Diffuse.
– Cohérent avec le reste de la phase A : les .j3o vectobj générés par
./gradlew convertVectObj sortent maintenant directement en Unshaded,
ce qui élimine le besoin de la conversion runtime
forceUnshadedVertexColor() dans GameAppState pour les nouveaux .j3o.
La conversion runtime reste utile pour les .j3o pré-existants en
Lighting.j3md tant que convertVectObj n’a pas été relancé.
tools/LevelSceneBuilder.java (nettoyage final session 124)
– Suppression des méthodes orphelines makeGeo(...) (sans buffer Color)
et makeCapGeo(...) (cap horizontal de cube de porte). Plus aucun
appelant après le passage du builder en VertexColor — toutes les
géométries du builder utilisent maintenant makeGeoWithColor ou
construisent leur Mesh inline avec un buffer Color. JavaDoc de
makeGeoWithColor mise à jour pour signaler que c’est désormais l’unique
helper.
Flux complet (rendu d’un mur depuis le binaire ASM)
.lvl binaire Amiga
├─ Zone.pointBrights[40] : Lb signed -20..+20 (low byte) + anim (high byte)
├─ Zone.brightness : Wb signed (low byte) + anim (high byte)
└─ Zone.pointIds[40] : index → globalPointId
↓ LevelBinaryParser (déjà session 118)
↓ LevelJsonExporter (session 124 : ajoute pointIds[])
.json niveau
├─ pointBrights : [[w0..w39], ...]
└─ zones[].pointIds : [N0..NM]
↓ LevelSceneBuilder (session 124)
↓ pour chaque mur :
↓ wallIL = vertexIntensity(leftPt, zone, wbZone)
↓ wallIR = vertexIntensity(rightPt, zone, wbZone)
↓ pour chaque sub-quad (subdivisé par tile-width) :
↓ segIL = lerp(wallIL, wallIR, segStart)
↓ segIR = lerp(wallIL, wallIR, segEnd)
↓ col[BL]=col[TL]=segIL ; col[BR]=col[TR]=segIR
.j3o scène (Unshaded.j3md + VertexColor=true)
↓ rootNode.attachChild(levelScene)
↓ forceUnshadedVertexColor() : convertit les vectobj résiduels
↓ aucune lumière JME ajoutée
Écran : couleur finale = texture × vertex color
Vérifications attendues (après ./gradlew exportLevels buildScenes run)
| Aspect | Avant 124 | Après 124 |
|---|---|---|
| Murs zone très lumineuse (lampe) | uniforme sous AmbientLight + headLight | clair uniforme avec vertex colors proches de 1 |
| Murs zone sombre | léger headLight | sombre avec MIN_INTENSITY=0.10 (lisible) |
| Couloir adjacent à zone lumineuse | gradient via PointLight | gradient bakké côté Lb des points |
| Performance | Lighting.j3md + SinglePass + N PointLights | Unshaded.j3md, 1 draw call par texture |
| Dépendances scène | rootNode.addLight × N | aucune lumière (Node lights vide) |
Phase A vs B (à venir)
Cette session livre la Phase A : statique. La Phase B implémentera
AnimBrightnessSystem (port de Anim_BrightTable_vw lignes 252-269 de
newanims.s) qui modifiera dynamiquement les buffers Color des meshes
selon les 7 patterns d’animation (BrightPulse1-5, BrightFlicker1 lampe,
BrightFlicker2 flamme). Les vertex colors actuels capturent la valeur au
phase=0 de chaque pattern, donc tous les points animés démarrent à leur
valeur de base.
Les phases C (distance shading / fog) et D (flash explosions/tirs)
viendront ensuite.