Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 9 min read

What got more real in 101 Indian monuments built from code

The first version of this site already had 101 Indian monuments in the browser, and not one of them was a downloaded model. Each building was a small function: boxes, cylinders, a lathe for the domes, and one shader that

What got more real in 101 Indian monuments built from code

The first version of this site already had 101 Indian monuments in the browser, and not one of them was a downloaded model. Each building was a small function: boxes, cylinders, a lathe for the domes, and one shader that painted stone. That story is in I Built 101 Indian Monuments in the Browser Using Only Code.

This is what happened after that. The masses were right. Up close, the parts were still a bit generic, and the ground around a building was often the wrong trees, the wrong water, or a lawn that ended in a hard edge. The work since then is a second pass: richer shared parts, pieces written for one monument, and a setting that belongs to that place.

The site is still plain files and three.js, with no build step.

Live: monuments-of-india-nu.vercel.app
Code: github.com/amananandrai/monuments-of-india

Taj Mahal model from the garden, white marble tomb, minarets and reflecting pool

Shared parts grew the pieces they were missing

Indian buildings repeat a vocabulary: onion domes, chhatris, jali screens, temple spires, fort gates. Those live in shared files, so a change to one part reaches every monument that uses it.

The plain versions read as the right silhouette and the wrong joinery. The second pass fills in the joins:

  • onionDome() now finishes a dome from about 1.5 m radius upward with a moulded neck at the springing and an inverted lotus at the crown. The Taj models its own crown and opts out.
  • chhatri() gives each column a base and a capital, the eave a drip edge, and the dome a low drum. A richer mughalChhatri() adds arches, a sloping eave and a ribbed lotus dome.
  • columnTrim() dresses a colonnade that used to be bare shafts: a plinth and moulding under each, a necking ring and an abacus at the head. It is on Agra Fort's Diwan-i-Am, the Padmanabhaswamy corridor, Mahakaleshwar's colonnade and the Secretariat loggias at Rashtrapati Bhavan.
  • Jali screens are no longer a square grid. The pierced-screen surface cuts the octagon-and-diamond lattice of Mughal and Rajput screens. One function serves both the cut-out and the shading, so the holes and the shadow cannot drift apart.

Onion dome with a moulded neck, an inverted lotus at the crown, and a finial

The screen itself is a few lines in the surface shader. A 22 cm grid, an octagon, and a small diamond between each four:

// Openings of a pierced stone screen: 1 in a hole, 0 on the stone.
float sJaliHole(vec2 uv) {
  vec2 p = abs(fract(uv / 0.22) - 0.5);
  float oct = step(max(p.x, p.y), 0.34) * step(p.x + p.y, 0.47);
  float dia = step(1.0 - p.x - p.y, 0.13);
  return max(oct, dia);
}

Up close, when the holes are large enough on screen not to shimmer, the shader discards the opening. The stone around it still casts the shadow. Far away the lattice stays a shading pattern, which is cheaper than modelling every hole on 101 buildings.

A window filled with an octagon-and-diamond jali screen

The same idea covers the rest of the ornament that should not cost a pile of triangles: rosette friezes, inlay, calligraphy, carved reliefs. The pattern lives in the shader. The mesh stays a wall.

Some buildings needed parts only they have

Shared parts get a monument into the right family. They do not make the Taj the Taj. Each monument now has a second file, js/details/<name>.js, whose addDetails adds the finer architecture and the setting. A few of those files exist because a photograph would not match a generic kit.

The Taj Mahal. Each face is a pishtaq: a deep pointed arch in a rectangular frame, roofed by a half-dome, with calligraphy in black marble and inlaid flowers in the spandrels. Smaller arched alcoves with pierced screens sit either side. The parapet carries leaf-shaped merlons, the drum a blind arcade, and the four roof chhatris have arches, an eave and a lotus cap. The minarets take three bracketed balconies.

Konark. The chariot plinth is wrapped in friezes. The Jagamohana walls stand on carved mouldings, with an image in a niche either side of each doorway. At the steps of the Natamandira, lions rear over elephants. The horses are sculpted, not blocked in as boxes.

Hawa Mahal. Each jharokha of the screen projects on a pair of small brackets under the sill, the white bands along the storeys are carved, and a lotus crowns each domed kiosk. Flowering bougainvillea sits at either end of the pavement. The lattice holes stay in the shader, so the stone is still solid and the screens still cast shadows.

Nageshwar's Shiva. The largest figures are one continuous skin, grown from a signed distance field and meshed in the browser, so a limb grows out of a body the way it does in a carving. The seated Shiva is the most specific of them. Matted locks, the crescent and the Ganga, the third eye, Vasuki round the neck, rudraksha, a tiger skin and the trishul are placed on that skin with snap(), so they neither float nor sink in.

The same split runs through the traditions, not only the famous four:

  • Nagara temples, Ram Mandir and the South Indian gopurams take doorways with depth: stepped frames, timber leaves standing open onto a lamp-lit sanctum, guardians, porches, and a real passage through a gopuram.
  • Mughal, Nawabi and Deccan fronts take an iwan with a half-dome and a door, and arcades of arched cells.
  • Fort gates are passages, with guard niches and leaves spiked against elephants.
  • Colonial, Gothic and Indo-Saracenic buildings take their own doors: paired-column porches, fanlights, a crocketed Gothic portal, sash windows.
  • Tibetan monasteries take painted doors, a pillared portico, a dharma wheel, victory banners and monks' houses with earth roofs.

India Gate, Charminar, the Taj, the Gateway of India and the Old Parliament House carry the most of this layer. The others take the carving of their own tradition: base mouldings and image niches on the temples, banded bastions and jharokhas on the forts, stacked iwans on the tombs.

A part that is only a silhouette is cheap to share and wrong up close. A part that exists for one building is how the model stops being "a Mughal tomb" and starts being this tomb.

The ground had to belong to the place

The first scenery was generous and often inaccurate. Mango and coconut turned up in the desert. Pools sat where the real ground is grass. A lawn ended like a tile.

js/details/realism.js is now one planter for 21 kinds of tree. One table, KINDS, holds the height, the crown, the leaf and the bark. An unknown kind throws, naming itself, so a monument cannot quietly borrow another tree's limbs. Only the banyan drops aerial roots.

Where What stands there now
Delhi's avenues Jamun
Bodh Gaya and Sarnath Peepal
Kashmir Chinar
Ladakh, by the Indus Poplar and willow
Tawang and Rumtek Fir
Ajanta, and the forests Teak and sal
The south Tamarind
Konark and the Shore Temple Casuarina behind the palms
Jaisalmer, Chittorgarh, the dry ridges Khejri, dhok, babul, ber

Planting is a list of spots. An avenue is a line; a grove is a scatter that stays out of the building:

// Twelve jamun down each side of a path.
g.add(trees(
  [...along(12, [-58, 14], [-58, 185]), ...along(12, [58, 14], [58, 185])],
  { kind: 'jamun', seed: 6 }
));

Every tree starts as a trunk and one crown, which is all a phone keeps. On a desktop, upgradeTrees() replants that monument's trees with limbs and separate leaf clumps for as long as the model stays inside its triangle budget: 650,000 on a desktop, 300,000 on a phone. Six specimen trees become full trees. A forest of 160 usually stays plain.

Dry-country trees: ber, babul, khejri and dhok, short trunks and low wide crowns

Water was corrected the same way, by taking it out where it had been invented and putting it where the site actually has it:

  • The Tungabhadra runs north of Hampi's Vittala compound, and the palms stand on its bank.
  • A stone-rimmed lotus pond lies south of the Mahabodhi railing.
  • The Shahi Baoli stands east of the Bara Imambara, a walled stepwell entered through an arched gate.
  • Ramappa's lake lies beyond the temple garden.
  • Two rows of houses line the streets outside Jagannath's outer wall at Puri, and the Grand Road stays open.
  • India Gate lost the canals that met its piers. Jallianwala Bagh's flame stands on the grass. Rameswaram's sea sits east of the temple, in the town, not against the outer wall. Maota Lake at Amber is a real reflective lake.

Paths and pavements are flagstones: courses 70 cm deep, stones 1.1 m long, every third course in half-length stones, the courses shifted so no joint runs straight through. Each stone has its own tone. Far off, the joints drop away and the floor stays slightly mottled.

float row = floor(uv.y / 0.7);
float wdt = mod(row, 3.0) < 0.5 ? 0.55 : 1.1;
float xo = uv.x + row * 0.37;

A wall no longer meets the grass in a hard line. When a monument is shown, it is drawn once from straight above into a height map (groundShade in js/surface.js). Upward faces darken where that blur stands well above them, which is the shade at the foot of a wall. Trees are left out of that pass, because they already cast shadows. It runs on phones too.

Ceremonial lawns stay mown: India Gate, the Taj, Victoria, the National War Memorial, both Parliaments. Elsewhere, desktops get grass tufts under about 0.35 m, kept 4 m clear of the monument and off any path.

There are still no people. Motor vehicles exist only on the Howrah Bridge and the Bandra–Worli Sea Link, so the monument stays the subject. Boats, houseboats, stalls, lamp posts, prayer flags and animals (Nandi, elephants, deer, yaks, camels) come from the same kit, instanced, so fifty trees are a few draw calls.

Close to a building, the camera casts a ray toward its target. If it has entered a solid it steps back out, and it keeps about 1.5 m off a wall in front of it.

Stone that catches the light

The shader still paints joints, veins, weathering and a slight tint per piece, from world position, so nothing has to be unwrapped. Over that, most stones now carry a CC0 photograph from Poly Haven: marble, sandstones, granite, laterite, fort rubble, plaster, concrete, weathered planks.

Each set has a normal map and a roughness map beside the colour. On a desktop the shader projects them the same way as the colour, so the stone catches the light as a rough surface. Phones keep the colour map. A set is fetched the first time a monument that uses it is opened, not all of them at startup.

Shadows follow the view. The shadow map used to cover a fixed box around the whole monument, so on a large site one texel was most of a metre. It now stays about three-quarters of the camera's distance from its target, and it changes in steps so the shadows do not crawl. Desktops start sharper and step down for that visit if frames come in slower than about 40 a second.

What I would still not claim

These are interpretations, not survey records. The goal is that a person who knows the building can see its plan, its material and its setting, and that a person who does not can tell Konark from a generic temple.

The useful rule from the second pass: put the repeated joinery in one shared part, put the thing that makes a monument itself in that monument's file, and plant the tree and the water that the site's own notes name. A new dome profile should not require a new texture unwrap, and a new tree should be one row in KINDS.

Adding another monument is still one module and one card in js/data.js. The card is smaller than it used to feel, because the dome, the door and the neem already exist.

Which building still looks least like itself? That is the next detail file.

This article was written with AI assistance from the project's source and is disclosed as fully autonomous: it was published without a human rewrite. three.js is MIT. Photo textures are CC0 from Poly Haven. Map data is public domain from Natural Earth.

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.