Runtime assets
Models, textures and animations prepared for use in a scene or application.
10,481 bytes (0.01 MB)
Download runtime assetsOriginal modular cabin collection. 428 triangles; named assembly and interaction interfaces.
Cabin in the Woods: PSX Horror pack
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.
Models, textures and animations prepared for use in a scene or application.
10,481 bytes (0.01 MB)
Download runtime assetsModels plus Kiln source, editing metadata, materials and included revisions, so you can reopen and continue editing.
61,294 bytes (0.06 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.
Native clips: DoorOpen.
const meta={name:'Nightman interior door',role:"prop"};
const MATERIALS={"tx01":{"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Weathered cabin timber","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.87c9fe1286e32442ed5e29c42b5c05cf88ac5b509260b95ce755bcbd0e3cb8ea.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"repeat","wrapT":"repeat"}}}},"tx02":{"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Warm interior boards","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.d86eb2087a7bd4edbe734e603bc6b8180220e751bc0e5244d80c696b566dfc26.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"repeat","wrapT":"repeat"}}}},"tx03":{"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"}}}},"tx04":{"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Cut endgrain","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.2dadd9f8f02157fcab7add4c237598d8358b2c65babbd07830c653791bad128d.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"repeat","wrapT":"repeat"}}}},"tx08":{"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"}}}},"tx06":{"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Dark wooden shingles","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.f20b5106a73407a176c1f38ddda6062c1a78c33e306535631e2a187620d9f26f.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"repeat","wrapT":"repeat"}}}},"tx07":{"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Worn hearth masonry","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.428f19533b55f6125797dc44914d1ee8d09c78b8d24a36841e25f72f3d435fab.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"repeat","wrapT":"repeat"}}}},"tx09":{"schemaVersion":3,"model":"pbrMetallicRoughness","name":"CRT olive enamel","baseColor":16777215,"roughness":0.85,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.c93242eb798477b337836583041d305076ffe53b051abc78ba592be6803105c5.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"repeat","wrapT":"repeat"}}}},"tx11":{"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Original CRT quiet signal","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.620c01887b01c13a660d05ccdddbaf91aaf0bcc35a133a6ae1078adee9a402a0.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"clamp-to-edge","wrapT":"clamp-to-edge"}}}},"tx34":{"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Mounted deer warm hide","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.14df6cc21c5a44c1da1792d17fccce6ee8f02b650b43e4e0aeff624a268074b2.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"clamp-to-edge","wrapT":"clamp-to-edge"}}}},"tx36":{"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Original framed woodland","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.845366f6ec1ef3545d3dfda14fab634ff189255b307f1b69bdea618efaa323ac.base-color","sampler":{"minFilter":"nearest-mipmap-linear","magFilter":"nearest","wrapS":"clamp-to-edge","wrapT":"clamp-to-edge"}}}}};
// Original beveled timber section: grain follows each member's long Z axis.
function timberGeo(length,width,height,repeat=1,warp=0,chip=0){
const bevel=Math.min(width,height)*.16,profile=[[-width/2+bevel,-height/2],[width/2-bevel,-height/2],[width/2,-height/2+bevel],[width/2,height/2-bevel],[width/2-bevel,height/2],[-width/2+bevel,height/2],[-width/2,height/2-bevel],[-width/2,-height/2+bevel]];
const positions=[],uv=[],indices=[],groups=[];let count=0;
function face(points,coords,material){const base=count;for(let i=0;i<points.length;i++){positions.push(...points[i]);uv.push(...coords[i]);count++;}for(let j=1;j<points.length-1;j++)indices.push(base,base+j,base+j+1);groups.push({start:indices.length-(points.length-2)*3,count:(points.length-2)*3,materialIndex:material});}
const z0=-length/2,z1=length/2;const start=i=>z0+((i===0||i===1)?chip:0),finish=i=>z1-((i===4||i===5)?chip*1.3:0);
for(let i=0;i<8;i++){const a=profile[i],b=profile[(i+1)%8],v=Math.hypot(b[0]-a[0],b[1]-a[1])/repeat;face([[...a,start(i)],[...b,start((i+1)%8)],[b[0]+warp,b[1],finish((i+1)%8)],[a[0]+warp,a[1],finish(i)]],[[0,0],[0,v],[length/repeat,v],[length/repeat,0]],0);}
face(profile.map((p,i)=>[...p,start(i)]).reverse(),profile.map(p=>[(p[0]+width/2)/.5,(p[1]+height/2)/.5]).reverse(),1);
face(profile.map((p,i)=>[p[0]+warp,p[1],finish(i)]),profile.map(p=>[(p[0]+width/2)/.5,(p[1]+height/2)/.5]),1);
const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(positions,3));g.setAttribute('uv',new THREE.Float32BufferAttribute(uv,2));g.setIndex(indices);g.computeVertexNormals();g.addGroup(0,48,0);g.addGroup(48,36,1);return g;
}
function timber(parent,name,L,w,h,wood,end,pos,rotation=[0,0,0],repeat=1,warp=0){const g=timberGeo(L,w,h,repeat,warp,name.includes("Plank")||name.includes("Board")?.025:0);const u=g.getAttribute("uv");let seed=17;for(let k=0;k<name.length;k++)seed=(Math.imul(seed,31)+name.charCodeAt(k))>>>0;const phaseU=(seed&31)/32,phaseV=((seed>>>5)&31)/32;for(let j=0;j<Math.min(u.count,32);j++)u.setXY(j,u.getX(j)+phaseU,u.getY(j)+phaseV);const mesh=new THREE.Mesh(g,[wood,end]);mesh.name=name;mesh.position.set(...pos);mesh.rotation.set(...rotation.map(v=>v*Math.PI/180));parent.add(mesh);return mesh;}
function socket(parent,name,xyz){const s=new THREE.Object3D();s.name=name;s.position.set(...xyz);parent.add(s);return s;}
async function surfaces(){const out={};for(const key of Object.keys(MATERIALS))out[key]=await compilePortableMaterialSpecV3(MATERIALS[key]);out.dark=gameMaterial(0x28382F);out.rope=gameMaterial(0x8C8056);out.antler=gameMaterial(0xB6AC86);return out;}
function box(parent,name,size,material,pos){return createPart(name,boxGeo(...size),material,{parent,position:pos});}
function group(parent,name,pos=[0,0,0]){const g=new THREE.Group();g.name=name;g.position.set(...pos);parent.add(g);return g;}
function timberBetween(parent,name,a,b,width,height,wood,end){const p=new THREE.Vector3(...a),q=new THREE.Vector3(...b),d=q.clone().sub(p),m=timber(parent,name,d.length(),width,height,wood,end,p.clone().add(q).multiplyScalar(.5).toArray());m.quaternion.setFromUnitVectors(new THREE.Vector3(0,0,1),d.normalize());return m;}
function polygonPrism(profile,L){const n=profile.length,positions=[],uv=[],idx=[];let count=0;function face(points,coords){const b=count;points.forEach((p,i)=>{positions.push(...p);uv.push(...coords[i]);count++;});for(let j=1;j<points.length-1;j++)idx.push(b,b+j,b+j+1);}
for(let i=0;i<n;i++){const a=profile[i],b=profile[(i+1)%n];face([[...a,-L/2],[...b,-L/2],[...b,L/2],[...a,L/2]],[[0,0],[0,.15],[L,.15],[L,0]]);}const side=idx.length;
face(profile.map(p=>[...p,-L/2]).reverse(),profile.map(p=>[p[0]*2+.5,p[1]*2+.5]).reverse());face(profile.map(p=>[...p,L/2]),profile.map(p=>[p[0]*2+.5,p[1]*2+.5]));const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(positions,3));g.setAttribute('uv',new THREE.Float32BufferAttribute(uv,2));g.setIndex(idx);g.computeVertexNormals();g.addGroup(0,side,0);g.addGroup(side,idx.length-side,1);return g;}
function log(parent,name,L,r,m,pos,rot=[0,0,0]){const shape=[[0,-r],[r*.8,-r*.5],[r*.95,r*.15],[r*.55,r*.65],[-r*.25,r*.75],[-r*.75,r*.25],[-r*.7,-r*.4]];const mesh=new THREE.Mesh(polygonPrism(shape,L),[m.tx03,m.tx04]);mesh.name=name;mesh.position.set(...pos);mesh.rotation.set(...rot.map(x=>x*Math.PI/180));parent.add(mesh);return mesh;}
function pin(parent,name,pos,m){return createPart(name,cylinderGeo(.014,.014,.014,6),m.tx08,{parent,position:pos,rotation:[0,0,-90]});}
function consolidate(parent,name){parent.updateMatrixWorld(true);const inverse=parent.matrixWorld.clone().invert(),byMaterial=new Map(),old=[];parent.traverse(o=>{if(o.isMesh&&o.parent===parent){old.push(o);const mats=Array.isArray(o.material)?o.material:[o.material];const g=o.geometry,index=g.getIndex(),p=g.getAttribute('position'),u=g.getAttribute('uv'),n=g.getAttribute('normal'),matrix=inverse.clone().multiply(o.matrixWorld),normalMatrix=new THREE.Matrix3().getNormalMatrix(matrix);for(const gr of g.groups.length?g.groups:[{start:0,count:index?index.count:p.count,materialIndex:0}]){const mat=mats[gr.materialIndex||0];let entry=byMaterial.get(mat);if(!entry){entry={p:[],u:[],n:[]};byMaterial.set(mat,entry);}for(let k=gr.start;k<gr.start+gr.count;k++){const i=index?index.getX(k):k;entry.p.push(...new THREE.Vector3(p.getX(i),p.getY(i),p.getZ(i)).applyMatrix4(matrix).toArray());entry.n.push(...new THREE.Vector3(n.getX(i),n.getY(i),n.getZ(i)).applyMatrix3(normalMatrix).normalize().toArray());entry.u.push(u?u.getX(i):0,u?u.getY(i):0);}}}});for(const o of old){parent.remove(o);const marker=socket(parent,o.name,o.position.toArray());marker.userData.originalStaticMember=true;}let j=0;for(const [material,e]of byMaterial){const g=new THREE.BufferGeometry();g.setAttribute('position',new THREE.Float32BufferAttribute(e.p,3));g.setAttribute('normal',new THREE.Float32BufferAttribute(e.n,3));g.setAttribute('uv',new THREE.Float32BufferAttribute(e.u,2));const mesh=new THREE.Mesh(g,material);mesh.name=name+'_Surface_'+j++;parent.add(mesh);}}
function metricBox(parent,name,size,material,pos,repeat=2){const g=boxGeo(...size).clone(),p=g.getAttribute('position'),n=g.getAttribute('normal'),uv=g.getAttribute('uv');for(let i=0;i<p.count;i++){const x=p.getX(i),y=p.getY(i),z=p.getZ(i);if(Math.abs(n.getX(i))>.5)uv.setXY(i,(z+size[2]/2)/repeat,(y+size[1]/2)/repeat);else if(Math.abs(n.getY(i))>.5)uv.setXY(i,(x+size[0]/2)/repeat,(z+size[2]/2)/repeat);else uv.setXY(i,(x+size[0]/2)/repeat,(y+size[1]/2)/repeat);}return createPart(name,g,material,{parent,position:pos});}
function wall(parent,name,L,openings,m,pos=[0,0,0],rotationY=0,height=2.8){const root=group(parent,name,pos);root.rotation.y=rotationY*Math.PI/180;const edges=[0,height];for(let y=.2;y<height-.001;y+=.2)edges.push(y);for(const o of openings)edges.push(o.y0,o.y1);const ys=[...new Set(edges)].sort((a,b)=>a-b);let seq=0;
for(let i=0;i<ys.length-1;i++){const lo=ys[i],hi=ys[i+1];if(hi-lo<.004)continue;const mid=(lo+hi)/2,cut=openings.filter(o=>mid>=o.y0&&mid<o.y1).map(o=>[o.z0,o.z1]).sort((a,b)=>a[0]-b[0]);let z=-L/2;const spans=[];for(const c of cut){if(c[0]>z)spans.push([z,c[0]]);z=Math.max(z,c[1]);}if(z<L/2)spans.push([z,L/2]);for(const s of spans){const len=s[1]-s[0];if(len<.01)continue;timber(root,name+'_Outer_'+seq,len,.095,hi-lo-.003,m.tx01,m.tx04,[.0525,mid,(s[0]+s[1])/2],[0,0,0],2);timber(root,name+'_Inner_'+seq,len,.095,hi-lo-.003,m.tx02,m.tx04,[-.0525,mid,(s[0]+s[1])/2],[0,0,0],2);seq++;}}
consolidate(root,name);root.userData.nightmanWall={length:L,height,thickness:.2,openings};return root;}
function floor(parent,name,width,depth,m,pos=[0,0,0]){const root=group(parent,name,pos),count=Math.round(width/.2),pitch=width/count;for(let i=0;i<count;i++)timber(root,name+'_Board_'+i,depth,pitch-.004,.15,m.tx02,m.tx04,[0,-.075,-width/2+(i+.5)*pitch],[0,90,0],2);consolidate(root,name);return root;}
function roof(parent,name,m,side){const root=group(parent,name);const profile=side<0?[[0,4.08],[4.2,2.82],[4.2,2.74],[0,4.0]]:[[-4.2,2.82],[0,4.08],[0,4.0],[-4.2,2.74]];const mesh=new THREE.Mesh(polygonPrism(profile,6.4),[m.tx06,m.tx06]);mesh.name=name+'_Shingles';mesh.rotation.y=Math.PI/2;root.add(mesh);for(const x of [-3.2,3.2])timberBetween(root,name+'_GableTrim_'+x,[x,4.05,0],[x,2.79,side*4.2],.09,.12,m.tx01,m.tx04);return root;}
function hearth(parent,m){const root=group(parent,'HearthBody');metricBox(root,'Hearth_Base',[.8,.14,1.25],m.tx07,[.02,.07,0]);for(const z of [-.46,.46])metricBox(root,'Hearth_Jamb_'+z,[.62,.92,.25],m.tx07,[0,.6,z]);metricBox(root,'Hearth_Back',[.12,.95,.68],m.tx07,[-.25,.64,0]);metricBox(root,'Hearth_Lintel',[.68,.23,1.18],m.tx07,[0,1.13,0]);timber(root,'Hearth_Mantel',1.34,.82,.12,m.tx01,m.tx04,[.05,1.31,0],[0,0,0],2);metricBox(root,'Hearth_Chimney',[.5,1.2,.63],m.tx07,[-.11,1.92,0]);for(const z of [-.18,0,.18])log(root,'Fuel_Log_'+z,.48,.055,m,[.13,.22,z],[0,90,0]);socket(root,'hearth_root',[0,0,0]);socket(root,'fuel_slot',[.1,.19,0]);socket(root,'fire_socket',[.1,.32,0]);socket(root,'player_interact',[1,0,0]);return root;}
async function build(){const m=await surfaces();const root=createRoot('InteriorDoor');const frame=group(root,'DoorFrame');for(const z of [-.53,.53])timber(frame,'Jamb_'+z,2.2,.12,.06,m.tx01,m.tx04,[0,1.1,z],[90,0,0],2);timber(frame,'Header',1.12,.12,.10,m.tx01,m.tx04,[0,2.25,0],[0,0,0],2);const leaf=group(root,'Door_Hinge',[0,.02,-.5]);for(let i=0;i<6;i++)timber(leaf,'Leaf_Plank_'+i,2.16,.04,.157,m.tx01,m.tx04,[0,1.08,.02+(i+.5)*.16],[90,0,0],2);for(const y of [.38,1.72]){box(leaf,'HingeStrap_'+y,[.02,.1,.28],m.tx08,[.035,y,.12]);createPart('HingePin_'+y,cylinderGeo(.015,.015,.14,6),m.tx08,{parent:leaf,position:[.017,y,0]});}timberBetween(leaf,'Inside_Brace',[-.04,.31,.13],[-.04,1.83,.84],.04,.11,m.tx02,m.tx04);box(leaf,'LatchPlate',[.025,.16,.07],m.tx08,[.035,1.04,.85]);const handle=new THREE.Mesh(new THREE.TorusGeometry(.045,.009,4,8),m.tx08);handle.name='DoorHandle';handle.rotation.y=Math.PI/2;handle.position.set(.05,1.04,.85);leaf.add(handle);socket(root,'door_mount',[0,0,0]);socket(root,'door_passage',[0,1.1,0]);socket(leaf,'door_handle',[.05,1.04,.85]);root.userData.nightmanInterface={clearWidth:1.0,clearHeight:2.2,hinge:'Door_Hinge',inwardRotationY:[0,-95],leafCollision:{center:[0,1.08,.5],size:[.04,2.16,.96]}};return root;}
function animate(){const q0=new THREE.Quaternion(),q1=new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0),-95*Math.PI/180);return[createClip('DoorOpen',1,[new THREE.QuaternionKeyframeTrack('Door_Hinge.quaternion',[0,1],[...q0.toArray(),...q1.toArray()])],{loop:false})];}Scroll code horizontally
Asset content follows the supplied CC0-1.0 licence record. Kiln and scene software retain their own licences.