Skip to content
Kiln1.3Get Kiln

Collectible shell pair using the shared equipment palette.

Cabin in the Woods: PSX Horror pack
Collectible shell pair using the shared equipment palette.

Drag to orbit after opening. The model starts stationary.

For your engine. The download is a standard glTF 2.0 binary (GLB) with PBR metallic-roughness materials. glTF stores materials, not lighting or tone mapping, so your engine decides how they read. This 3D view tone-maps with Review Neutral, the Khronos PBR Neutral construction with a smaller glare offset (0.015 instead of 0.04), at exposure 0.9. Its Tone mapping control also shows ACES and Linear, for comparison.

Triangles
224
Meshes
6
Materials
2
Dimensions (m)
0.06 × 0.06 × 0.02

Full Kiln assets (remixable)

Models plus Kiln source, editing metadata, materials and included revisions, so you can reopen and continue editing.

40,504 bytes (0.04 MB)

Download full kiln assets (remixable)

Runtime includes the GLB and metadata sidecar. The editable ZIP contains this selected revision and its complete material closure. Parent revision identifiers are retained; earlier authoring history is deliberately not distributed in this initial pack.

Import this ZIP into Kiln’s Library, or run node kiln.mjs import asset.zip in an authoring workspace. The full model, exact source, editing metadata, preview and material resources are included so you can reopen and remix it.

Use an importing Kiln build that supports the included material schemas. Use Kiln 1.2.3 or later.

Collection / asset / revision
project / a_679acc21bed649dcb48b93b5b4a6798a / r_a46643a06a8d403baba730724211f696
Source SHA-256
2d0ddeff433076b6786b25d8b1f8c3f500067eb45b7416c364b20ae42ba5a950
Shotgun shell pair: exact saved source
const meta={name:'Caretaker equipment',description:'Original low-poly rural equipment; shared weathered wood and iron plus original shell, brass, rubber and honed-edge pixel regions.'};
const ITEM='shells',WOOD_SPEC={"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Fresh split wood","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.dec8945f78a3dca6d5031b36094a0583f02b39b0e3978ed2c34418010f14fbd5.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"repeat","wrapT":"repeat"}}}},IRON_SPEC={"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Worn iron","baseColor":16777215,"roughness":0.85,"metalness":0.25,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.1870a95ffec8e00bc9daac56a32bc537547e002305e373c4ff7a97f098119fbe.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"repeat","wrapT":"repeat"}}}},DETAIL_SPEC={"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Original tool edge, shell and brass detail pixels","roughness":0.7,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.3d181e24dfe2a2c6b475e9460dd1f32b0b31be32f0149f577ed4ad0679b8049f.base-color","sampler":{"minFilter":"nearest","magFilter":"nearest","wrapS":"clamp-to-edge","wrapT":"clamp-to-edge"}}}};
async function build(){
  const root=createRoot(ITEM==='axe'?'CaretakerAxe':ITEM==='shotgun'?'CaretakerShotgun':'TwoShellPickup');
  const wood=await compilePortableMaterialSpecV3(WOOD_SPEC),iron=await compilePortableMaterialSpecV3(IRON_SPEC),detail=await compilePortableMaterialSpecV3(DETAIL_SPEC),polished=detail.clone(),brass=detail.clone();polished.name='Honed steel edge';polished.metalness=.8;polished.roughness=.45;brass.name='Brass cartridge head';brass.metalness=.75;brass.roughness=.5;
  const anchors={};function anchor(name,position,parent=root){const node=new THREE.Object3D();node.name=name;node.position.fromArray(position);node.userData.nightmanAnchor={role:name,version:1,forward:'+X',up:'+Y'};parent.add(node);anchors[name]=node;return node;}
  function geom(p,uv,ix){let g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(p,3));g.setAttribute('uv',new THREE.Float32BufferAttribute(uv,2));g.setIndex(ix);g=g.toNonIndexed();g.computeVertexNormals();return g;}
  function loftX(stations,n=8){const p=[],uv=[],ix=[],stride=n+1;for(let j=0;j<stations.length;j++){const s=stations[j];for(let i=0;i<=n;i++){const a=i/n*Math.PI*2;p.push(s.x,s.y+s.ry*Math.cos(a),(s.z??0)+s.rz*Math.sin(a));uv.push(i/n,j/(stations.length-1));}}for(let j=0;j<stations.length-1;j++)for(let i=0;i<n;i++){const a=j*stride+i,b=a+stride;ix.push(a,a+1,b,a+1,b+1,b);}for(const [j,end]of[[0,false],[stations.length-1,true]]){const s=stations[j],c=p.length/3;p.push(s.x,s.y,s.z??0);uv.push(.5,.5);for(let i=0;i<n;i++){const a=j*stride+i;ix.push(c,end?a:a+1,end?a+1:a);}}return geom(p,uv,ix);}
  function loftY(stations,n=8){const p=[],uv=[],ix=[],stride=n+1;for(let j=0;j<stations.length;j++){const s=stations[j];for(let i=0;i<=n;i++){const a=i/n*Math.PI*2;p.push(s.x+s.rx*Math.cos(a),s.y,(s.z??0)+s.rz*Math.sin(a));uv.push(i/n,1-j/(stations.length-1));}}for(let j=0;j<stations.length-1;j++)for(let i=0;i<n;i++){const a=j*stride+i,b=a+stride;ix.push(a,b,a+1,a+1,b,b+1);}for(const [j,end]of[[0,false],[stations.length-1,true]]){const s=stations[j],c=p.length/3;p.push(s.x,s.y,s.z??0);uv.push(.5,.5);for(let i=0;i<n;i++){const a=j*stride+i;ix.push(c,end?a+1:a,end?a:a+1);}}return geom(p,uv,ix);}
  function mapWorld(g,axis='X',density=2){const p=g.getAttribute('position'),uv=g.getAttribute('uv');for(let i=0;i<p.count;i++)uv.setXY(i,p.getZ(i)*density+.5,(axis==='X'?p.getX(i):p.getY(i))*density+.5);return g;}
  function part(name,g,m,parent=root,position){return createPart(name,g,m,{parent,position:position??[0,0,0]});}
  function atlas(name,g,region,m=detail,parent=root,position){return part(name,remapUVToAtlas(g,detail.map,region,{inputOrigin:'top-left',mode:'texel-center'}),m,parent,position);}
  function polygonPlate(points,thickness){const p=[],uv=[],ix=[];for(const z of [-thickness/2,thickness/2])for(const [x,y]of points){p.push(x,y,z);uv.push((x+.08)/.26,(y-.40)/.14);}const n=points.length;for(let i=1;i<n-1;i++)ix.push(0,i+1,i,n,n+i,n+i+1);for(let i=0;i<n;i++){const next=(i+1)%n;ix.push(i,next,n+i,next,n+next,n+i);}for(let i=0;i<ix.length;i+=3){const swap=ix[i+1];ix[i+1]=ix[i+2];ix[i+2]=swap;}return geom(p,uv,ix);}
  function ringX(x0,x1,y,z,outer,inner,n=10){const p=[],uv=[],ix=[];for(const x of[x0,x1])for(const r of[outer,inner])for(let i=0;i<=n;i++){const a=i/n*Math.PI*2;p.push(x,y+r*Math.cos(a),z+r*Math.sin(a));uv.push(i/n,x===x0?0:1);}const stride=n+1;for(let i=0;i<n;i++){const a=i,b=i+2*stride,c=i+stride,d=i+3*stride;ix.push(a,a+1,b,a+1,b+1,b,c,d,c+1,c+1,d,d+1,a,c,a+1,a+1,c,c+1,b,b+1,d,b+1,d+1,d);}return geom(p,uv,ix);}
  function shell(name,parent,position=[0,0,0]){const group=new THREE.Object3D();group.name=name;group.position.fromArray(position);parent.add(group);atlas(name+'Hull',loftX([{x:.009,y:0,ry:.0118,rz:.0118},{x:.063,y:0,ry:.0118,rz:.0118}],10),'shell',detail,group);atlas(name+'Head',loftX([{x:0,y:0,ry:.0128,rz:.0128},{x:.009,y:0,ry:.0128,rz:.0128}],10),'brass',brass,group);atlas(name+'Primer',loftX([{x:-.0008,y:0,ry:.0038,rz:.0038},{x:0,y:0,ry:.0038,rz:.0038}],8),'brass',brass,group);anchor(name+'_base',[0,0,0],group);return group;}
  if(ITEM==='axe'){
    part('AshHaft',mapWorld(loftY([{x:-.008,y:-.13,rx:.024,rz:.019},{x:-.003,y:-.09,rx:.023,rz:.018},{x:.006,y:.10,rx:.020,rz:.016},{x:.012,y:.28,rx:.019,rz:.014},{x:0,y:.49,rx:.017,rz:.013}]),'Y'),wood);
    const outline=[[-.058,.432],[-.078,.463],[-.057,.515],[.06,.532],[.135,.515],[.166,.467],[.137,.421],[.05,.415]];part('ForgedAxeHead',mapWorld(polygonPlate(outline,.035),'Y'),iron);
    const bevel=geom([.137,.422,-.018,.167,.467,-.004,.135,.515,-.018,.137,.422,.018,.167,.467,.004,.135,.515,.018,.167,.467,-.004,.167,.467,.004,.135,.515,.018,.135,.515,-.018,.137,.422,-.018,.137,.422,.018],[0,1,1,.5,0,0,0,1,1,.5,0,0,1,.5,1,.5,0,0,0,0,0,1,0,1],[0,1,2,3,5,4,6,8,7,6,9,8,10,11,7,10,7,6]);atlas('HonedBlade',bevel,'edge',polished);
    part('HeadEyeCollar',mapWorld(loftY([{x:0,y:.41,rx:.025,rz:.024},{x:0,y:.436,rx:.025,rz:.024}]),'Y'),iron);anchor('grip_dominant',[0,0,0]);anchor('grip_support',[.01,.16,0]);anchor('blade_contact',[.164,.467,0]);anchor('haft_end',[-.008,-.13,0]);root.userData.nightman={version:1,item:ITEM,axis:'+X edge-facing, +Y shaft',intendedLength:.662,contacts:{blade_contact:'Cut-plane center, not a damage or collision solver'}};
  }else if(ITEM==='shotgun'){
    part('WalnutStock',mapWorld(loftX([{x:-.305,y:-.02,ry:.072,rz:.035},{x:-.23,y:-.005,ry:.060,rz:.031},{x:-.15,y:.018,ry:.036,rz:.025},{x:-.08,y:.044,ry:.029,rz:.022},{x:-.02,y:.05,ry:.04,rz:.031}],8),'X'),wood);
    atlas('ButtPad',loftX([{x:-.318,y:-.02,ry:.073,rz:.037},{x:-.305,y:-.02,ry:.073,rz:.037}],8),'rubber');part('Receiver',mapWorld(loftX([{x:-.025,y:.069,ry:.038,rz:.037},{x:.115,y:.073,ry:.035,rz:.037}],4),'X'),iron);
    // A true open trigger guard, not a solid slab across the finger space.
    const p=[],uv=[],ix=[],n=12;for(const z of[-.007,.007])for(const inner of[false,true])for(let i=0;i<n;i++){const a=i/n*Math.PI*2;p.push(.018+(inner?.027:.035)*Math.cos(a),.011+(inner?.025:.033)*Math.sin(a),z);uv.push(i/n,inner?1:0);}for(let i=0;i<n;i++){const a=i,b=(i+1)%n;ix.push(a,b,n+a,b,n+b,n+a,2*n+a,3*n+a,2*n+b,2*n+b,3*n+a,3*n+b,a,2*n+a,b,b,2*n+a,2*n+b,n+a,n+b,3*n+a,n+b,3*n+b,3*n+a);}part('TriggerGuard',mapWorld(geom(p,uv,ix),'X'),iron);part('Trigger',mapWorld(loftY([{x:.025,y:-.005,rx:.004,rz:.004},{x:.017,y:.027,rx:.004,rz:.004}],4),'Y'),iron);
    const breech=createPivot('Breech',[.095,.038,0],root);anchor('breech_hinge',[0,0,0],breech);
    for(const side of[-1,1]){const suffix=side<0?'left':'right';part('Barrel_'+suffix,mapWorld(ringX(.025,.595,.047,side*.016,.0145,.0095,10),'X'),iron,breech);anchor('chamber_'+suffix,[.025,.047,side*.016],breech);shell('LoadedShell_'+suffix,breech,[.027,.047,side*.016]);}
    part('ForeEnd',mapWorld(loftX([{x:.06,y:.010,ry:.025,rz:.040},{x:.17,y:.010,ry:.033,rz:.043},{x:.33,y:.014,ry:.028,rz:.040}],8),'X'),wood,breech);
    part('BarrelRib',mapWorld(loftX([{x:.028,y:.064,ry:.0025,rz:.007},{x:.585,y:.064,ry:.0025,rz:.007}],4),'X'),iron,breech);atlas('FrontSight',loftX([{x:.57,y:.07,ry:.004,rz:.003},{x:.58,y:.07,ry:.004,rz:.003}],4),'brass',brass,breech);
    anchor('grip_dominant',[0,0,0]);anchor('grip_support',[.28,.035,0]);anchor('muzzle',[.595,.047,0],breech);anchor('stock_contact',[-.31,-.02,0]);root.userData.nightman={version:1,item:ITEM,axes:'+X muzzle, +Y up',intendedLength:1.008,clipTargets:{breech:breech.name,leftShell:'LoadedShell_left',rightShell:'LoadedShell_right'},events:{Fire:[{phase:.05,event:'muzzle_flash'}],Reload:[{phase:.28,event:'extract_shells'},{phase:.68,event:'insert_shells'}]}};
  }else{for(const side of[-1,1]){const group=shell(side<0?'PickupShellLeft':'PickupShellRight',root,[side*.018,.0008,0]);group.rotation.z=Math.PI/2;}anchor('pickup_center',[0,.042,0]);root.userData.nightman={version:1,item:'two-shell-pickup',count:2,reuse:'Exact shotgun shell geometry and material family'};}
  return root;
}
function animate(root){if(ITEM!=='shotgun')return [];const targets=root.userData.nightman.clipTargets;
  return [createClip('Reload',2.4,[rotationTrack(targets.breech,[{time:0,rotation:[0,0,0]},{time:.55,rotation:[0,0,-32]},{time:1.85,rotation:[0,0,-32]},{time:2.4,rotation:[0,0,0]}],'EASE_IN_OUT'),positionTrack(targets.leftShell,[{time:0,position:[.027,.047,-.016]},{time:.8,position:[-.065,.047,-.016]},{time:1.15,position:[-.085,.047,-.016]},{time:1.65,position:[.027,.047,-.016]},{time:2.4,position:[.027,.047,-.016]}],'LINEAR'),positionTrack(targets.rightShell,[{time:0,position:[.027,.047,.016]},{time:.8,position:[-.065,.047,.016]},{time:1.15,position:[-.085,.047,.016]},{time:1.65,position:[.027,.047,.016]},{time:2.4,position:[.027,.047,.016]}],'LINEAR')],{loop:false})];
}

Asset content follows the supplied CC0-1.0 licence record. Kiln and scene software retain their own licences.