agentleFS
wolffewolffe/three.js-skills/skills/geometry/greenhouse/SKILL.md

greenhouse

Skill4 starsChanged 16 days agoMITView on GitHub

Build a believable procedural space or sci-fi greenhouse in Three.js: a ribbed pressurised glazing dome with airlock, catwalk and nutrient core, plus tiered hydroponic grow rows with crops, trays and LED bars. Use for a lunar or Mars habitat, an orbital farm, a research biodome, or any interior where food is grown under lamps.

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 5 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈5

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use these words consistently in code, object names and prose:

    line 16
  2. Distinctive:

    Every rib lands on the tension ring.

    line 88
  3. Distinctive:

    No interior wall or mezzanine that would cut the pressure envelope.

    line 91
  4. Distinctive:

    Every row has a feed and a drain.

    line 116
  5. Very specific:

    Use crop: 'none' for a clean rack in a cutaway or for a row awaiting planting.

    line 118
  6. Distinctive:

    Run the branch along the aisle at the feed height, then a short bend into each row.

    line 179
  7. Very specific:

    Add one or two real lights for the pools on the floor and the core, not one per bar.

    line 195

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Greenhouse

Target: Three.js 0.187.0, WebGPU renderer (three/webgpu) and TSL node materials (three/tsl); WebGPU only, no WebGL fallback, no GLSL strings. Everything is generated by code; no model files or textures.

A greenhouse is a working closed-loop machine that happens to be full of plants, not a garden conservatory. Everything in it exists to keep a crop alive: the pressure envelope, the airlock, the drained floor, the nutrient core, the lamps, the rows, the service walkways. Build those first; the greenery is the result.

scripts/greenhouse.js registers two procedural models, "Pressurised greenhouse dome" (greenhouse-dome) and "Hydroponic grow row" (grow-row), and exports a bay layout and a whole-greenhouse helper. It is written against the procedural-models kernel shipped with the pipes skill (procedural-models.js); copy both files into the project together. Distribution pipework is not modelled here: use the pipes kit, and meet it at the feed points the layout returns.

Vocabulary

Use these words consistently in code, object names and prose:

TermMeaning
pressure envelopethe glazed shell that holds the atmosphere: a ribbed ellipsoid, not a conservatory roof
tension ringthe base ring beam and plinth that take the envelope's outward thrust
airlock / vestibulethe two-door pressure threshold on the door axis; the dome's front door
catwalkthe perimeter service walkway at mid-height, with grating, rails and a ladder
nutrient corethe central tank and ring main that receives drain water and returns solution
grow rowone freestanding tiered rack; the repeated unit of production
bay / ringan angular slice of the dome; rows sit tangentially in an inner and an outer ring
feedthe world point where a row's drip manifold meets a branch from pipes

When to use it

  • a pressurised agricultural dome on a moon, Mars, an asteroid or a station
  • a research biodome, a seed vault, a generation-ship farm, a terraforming camp
  • an interior where the grow rows are the subject: a hydroponics hall, a vertical farm, a biotech lab
  • a cutaway or diorama where the dome and the rows must read at the same scale

Not for: a domestic conservatory or a botanical garden (use architectural glazing and planting instead), a field or orchard (outdoor kits and card-grass), or a single potted plant. For the shell materials read the glass skill; for the lamps read lighting; for the pipes read pipes.

What ships

ExportWhat it is
greenhouseDomemodel definition, id greenhouse-dome
growRowmodel definition, id grow-row
registerGreenhouse()registers both; returns their ids
layoutRows(spec)rows in concentric bays; one entry per row with spec, matrix, feed, ring, bay, angle
createGreenhouse(spec, opts)dome plus every row as one THREE.Group; userData carries dome, rows, feeds, dispose()
greenhouseMaterials(def, opts)layer description to material; adds physical transmission glass and double-sided leaves
FLOOR_Ytop of the dome floor above the model origin, 0.55 m
import { registerModel, createModel, createField } from './procedural-models.js';
import { registerGreenhouse, createGreenhouse, layoutRows, greenhouseMaterials } from './greenhouse.js';

registerGreenhouse();
scene.add(createModel('greenhouse-dome', { diameter: 24, ribs: 24, bands: 6 }));
scene.add(createModel('grow-row', { length: 6, tiers: 4, crop: 'lettuce' }));

const whole = createGreenhouse({ diameter: 24, bays: 8, innerBays: 5 });
scene.add(whole);

The dome

Envelope

  • A ribbed ellipsoidal pressure envelope, not a hemisphere: rise is the crown height as a fraction of the radius, default 0.8. A 24 m dome rises 9.6 m. The shallow ellipse reads as a pressure vessel and leaves more usable height at the rim than a hemisphere.
  • Meridian ribs every 15° (ribs 12–32, default 24), in bands rings (3–8, default 6). The glazing is flat trapezoidal panels between the ribs and ring beams, inset a few centimetres, with a visible gasket gap. Flat panels are what a real prefabricated glazing system uses; a continuous glass skin reads as a bubble.
  • Tension ring at the base: a steel ring beam on a concrete plinth, with anchor nuts. The ring takes the outward thrust of the envelope, so it must be continuous and heavier than the ribs.
  • Crown compression ring and vent lantern: a ring at the top, a short drum and a mushroom cap. The lantern is the pressure relief and heat exhaust; it is also the silhouette that says "habitat" rather than "observatory".
  • Floor: a drained concrete slab 0.55 m above grade, with a perimeter drain channel and painted walkway lines. The slab is inset from the tension ring, leaving a service trough around the edge.
  • Panel glazing can be clear, frost or solar. Clear is nearly colourless; frost has a rough, milky transmission; solar is green-grey and absorbs heat. Use one choice for the whole envelope.

Openings and circulation

  • One airlock on the door axis, a vestibule about 2.75 m square by 2.55 m high, with an outer and an inner door, a porthole, a status lamp and steps down to grade. A pressure envelope has no plain door: the airlock is the threshold, and it should be the first thing a viewer understands.
  • The airlock is cut out of the lowest glazing bands, with a portal frame around the opening. Do not leave a glazing panel in front of the door.
  • A perimeter catwalk at mid-height (about 4.8 m on the default dome), 1.05 m wide, on brackets off the shell, with grating, edge beams, toe boards, two handrails at 0.56 m and 1.08 m, posts every 1.5–2.5 m, and a ladder from the floor. The catwalk is how the lamps, vents and glazing are serviced.
  • Aisles: the door axis is kept clear. layoutRows skips every bay within about half a bay of the door so the airlock opens onto a real aisle.
  • Mark the walkway and the drain with painted lines, not geometry: thin prisms a few millimetres above the slab are enough.

Core

A central nutrient core: a cylindrical tank about 2.2 m across, a domed lid, a level window, a control panel, a ring main around it and four risers. On top, a light mast with six high-bay fixtures aimed down at the inner beds. The core is the visual anchor under the crown; keep it below the catwalk so the dome reads as one volume.

Dome rules

  • The envelope is continuous: no gap between glazing and ring, no panel floating off a rib.
  • Every rib lands on the tension ring; every ring beam lands on a rib.
  • The airlock is on the ground, the catwalk is above head height, and neither blocks the other.
  • The floor is the working level inside the ring. The airlock floor meets it, and the steps go down to grade outside; do not recess the working floor below the door.
  • No interior wall or mezzanine that would cut the pressure envelope. If the programme needs rooms, put them in separate pressure vessels.

The grow row

A freestanding tiered hydroponic rack, 2–12 m long, 0.6–1.2 m wide, 2–6 tiers. Defaults: 6 m, 0.9 m, 4 tiers, 0.65 m pitch.

  • Frame: four corner posts on base plates, a crossbar at every tier and at the top, longitudinal shelf bars, and diagonal braces on the high-detail model. Painted steel, not aluminium extrusion.
  • Trays: a U-channel profile with a 24 mm wall and 130 mm depth, one per tier, with a substrate slab 55 mm thick. The tray is a gutter, not a box: it has an open top and a lip.
  • Crop: lettuce, herbs, strawberry, seedling or none. Plants are built from folded leaf ribbons, a stem and (strawberry, high detail) small fruit. Spacing is by crop:

    CropAlong rowAcross rowCanopy height
    Lettuce0.24 m0.24 m0.20 m
    Herbs0.18 m0.17 m0.17 m
    Strawberry0.26 m0.24 m0.16 m
    Seedlings0.12 m0.11 m0.08 m
  • Light: one or two LED bars above each tray, not one per plant. The bar sits 0.17 m below the next tray and 0.58 m above the top canopy; a pink-magenta diffuser with a dark housing. The lamp is emissive geometry, not a real light per bar.
  • Water: a drip manifold at one end, a branch and a distribution line along each tray, emitters every 0.32 m at high detail, an isolation valve on the first branch, and a tank and pump under the bottom shelf (or a floor stub when tank is off). The manifold rises from the floor to above the top tray.
  • Clearance: the tank fits under the first tier with the pump on top; the lowest tray is at 0.42 m so the tank and the floor can be cleaned. Nothing hangs below the base plates.

Row rules

  • The lamp is above the canopy and below the tray above: a plant must never grow into a fixture. If the crop is taller, raise the tier pitch, do not shrink the lamp gap.
  • Trays overhang the frame by a few centimetres at most; a tray that overhangs the aisle is a head injury.
  • Every row has a feed and a drain. A row that ends in mid-air is a diagram, not a machine.
  • Rows are serviced from the aisles, so put the manifold and tank at the aisle end, not against a wall.
  • Use crop: 'none' for a clean rack in a cutaway or for a row awaiting planting; empty trays still need substrate and lamps.

Layout

layoutRows places rows tangentially in two concentric rings:

  • inner ring at 0.35 R, innerBays bays (default 5), tiers - 1 tiers
  • outer ring at 0.68 R, bays bays (default 8), full tiers
  • rowsPerBay rows per bay (default 2) at rowPitch centres (default 1.15 m): a double-row bed serviced from both sides
  • row length = clamp(chord × 0.78, 2.2, 10), rounded to 0.5 m, where chord = 2 r sin(π / bays)
  • the bay containing the door is skipped, and crops alternate through CROP_ORDER when crop: 'mixed'

The default 24 m dome gives 22 rows. Each row's matrix is a world transform with local +X along the row, +Y up and +Z radial; feed is the world point at the row's drip manifold, at FLOOR_Y + 0.21, ready to meet a branch.

const rows = layoutRows({ diameter: 24, bays: 8, innerBays: 5, crop: 'mixed' });
for (const row of rows) {
  const m = createModel('grow-row', { ...row.spec, detail: 'high' }, { materials: greenhouseMaterials });
  m.position.setFromMatrixPosition(row.matrix);
  m.quaternion.setFromRotationMatrix(row.matrix);
  scene.add(m);
}

For many identical rows, build one createField('grow-row', spec, matrices) per crop and set distances: [45, 160]; call field.update(camera) whenever the camera moves. The field takes one spec for every instance, so group rows by crop and tier count rather than trying to vary them per instance.

Reuse the pipes kit

The dome ships only short service pipes around the core. The mains and branches belong to the pipes skill: that kit already models flanged modules, supports, valves, lagging and end bends, and the pipeRun recipe in that skill stretches a whole number of modules along a line.

import * as THREE from 'three/webgpu';
import { registerModel, createField } from './procedural-models.js';
import { pipeRack } from './pipe-rack.js';
import { layoutRows } from './greenhouse.js';

registerModel(pipeRack);
const rows = layoutRows({ diameter: 24 });

// The pipes module runs along local +Y and stands off along local +Z.
// Row local X is tangential, row local Z is radial; point +Y radially at the
// feed and +Z up, then lower the origin by the stand-off so the pipe centre
// lands at feed height.
const feedMatrices = rows.map(({ matrix, feed }) => new THREE.Matrix4()
  .makeBasis(
    new THREE.Vector3().setFromMatrixColumn(matrix, 0),
    new THREE.Vector3().setFromMatrixColumn(matrix, 2),
    new THREE.Vector3(0, 1, 0),
  )
  .setPosition(feed.x, feed.y - 0.3, feed.z));

const branches = createField('pipes', {
  pipes: 1, diameter: 0.15, length: 3, spacing: 1.8, stand: 0.3,
  supports: 'none', flanges: false, valve: false, bend: 'none', color: '#6C8E93',
}, feedMatrices);
scene.add(branches);
branches.update(camera);

Rules for the join:

  • Run the branch along the aisle at the feed height, then a short bend into each row. Do not run a main across a row or through the airlock aisle.
  • A 3 m pipe module is the shortest the pipes kit builds. Use it as the visible branch, place the feed at one end, and hide the last few centimetres behind the manifold.
  • Mains follow the catwalk edge or the service trough, never diagonally across the floor.
  • One colour per service everywhere: water, nutrient solution, drain, air, power. The dome's Service pipes layer and the pipes kit must agree.

Materials and lighting

greenhouseMaterials(def) extends the kernel's createMaterial:

  • { glass: true, color, rough, transmission, ior, thickness, attenuation?, attenuationDistance?, frost? } → MeshPhysicalNodeMaterial with transmission, thickness and DoubleSide. Keep transparent: false: transmission is an opaque pass in three, and setting transparent reintroduces sorting artefacts.
  • { leaf: true, color, ... } → the normal painted material with side: DoubleSide. Leaves need double-sided shading or half the canopy disappears.
  • Everything else passes through to the kernel (paint, metal, glow, coat).

Lighting:

  • The LED diffusers and the high-bay lenses are emissive geometry (glow around 2.2). They read at any distance and cost nothing.
  • Add one or two real lights for the pools on the floor and the core, not one per bar. A dome with 40 point lights will not render in a reasonable frame; see lighting for clustered or baked alternatives.
  • Glass needs something to reflect: a sky, a starfield, an environment map. Without it, transmission reads as grey plastic.
  • Shadows: the dome shell and the rows cast; the glazing is usually better as a shadow caster only at high detail, or not at all. The catwalk grating should not cast a solid slab shadow.

Performance

Measured on the defaults, one model at each level:

Modelhighmediumlow
greenhouse-dome (24 m)~107k tris~36k~27k
grow-row (6 m, 4 tiers, lettuce)~47k~15k~12k
  • High detail is for a hero shot or a cutaway; medium is the working level for a scene; low is for rows seen from across the dome.
  • The dome is one draw call per material layer (15 layers), the row one per layer (12). Instancing rows with createField is the only way to get hundreds.
  • createGreenhouse shares one material cache across the dome and all rows. userData.dispose() frees the geometries and the materials; call it when the scene is torn down.
  • Glass panels at high detail are separate quads; at low detail one lathe replaces them. Do not add more glazing subdivisions than the rib count can explain.

Pitfalls

  • The model must sit on y = 0. The dome's plinth and the row's base plates are the lowest geometry. If you edit a part, check the lowest vertex before shipping.
  • The row's origin is the floor, not the first tray. Place rows at FLOOR_Y + 0.01 in the dome, or on a slab of your own.
  • Do not pass layout parameters to the dome model. createGreenhouse filters them for you; if you call createModel('greenhouse-dome', spec) yourself, pass only the dome parameters. Unknown keys warn.
  • Do not scale the model non-uniformly. The glazing thickness, leaf normals and pipe radii all assume metres. Scale uniformly or rebuild with different parameters.
  • Do not clone hundreds of rows. clone() shares geometry but each clone is still a group of meshes; use createField.
  • Do not add a flat green sheet as a crop. The leaves are the crop; a green plane reads as AstroTurf.
  • Do not put a real light in every fixture, and do not make the glass transparent: true.

API

greenhouse-dome

ParameterRangeDefaultMeaning
diameter12–48 m24tension-ring diameter
rise0.55–1.0 R0.8crown height / radius
ribs12–32, step 424meridian ribs
bands3–86glazing bands
glazingclear / frost / solarclearpanel material
airlocktoggleonvestibule, doors, steps, porthole
catwalktoggleonring walkway, rails, ladder
coretoggleonnutrient core, ring main, light mast
colorcolour#C6CBC4frame paint
detailhigh / medium / lowhighlevel of detail

grow-row

ParameterRangeDefaultMeaning
length2–12 m6rack length
tiers2–64tray levels
width0.6–1.2 m0.9tray width
pitch0.45–0.9 m0.65tier spacing
croplettuce / herb / strawberry / seedling / nonelettuceplanting
lightstoggleonLED bars
tanktoggleontank and pump under the bottom shelf
colorcolour#D8DAD3frame paint
leafColorcolour#4F8A3Ccrop colour
detailhigh / medium / lowhighlevel of detail

layoutRows(spec)

Accepts diameter, bays, innerBays, rowsPerBay, tiers, crop, rowPitch, color, leafColor. Returns [{ spec, matrix, feed, ring, bay, angle }]; spec is ready for grow-row, feed is a THREE.Vector3 in world space.

createGreenhouse(spec, opts)

spec accepts the layout keys plus the dome keys (rise, ribs, bands, glazing, airlock, catwalk, core). opts accepts detail and anything createModel accepts. Returns a group with userData.dome, userData.rows, userData.feeds and userData.dispose().

Research notes, precedents and the measurements behind these choices are in references/space-greenhouse-systems.md.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.