Runtime assets
Models, textures and animations prepared for use in a scene or application.
45,958 bytes (0.05 MB)
Download runtime assetsA heavy stone and moss woodland golem with separate red eyes and readable native attack phases.
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. Optional extensions declared by this file: KHR_materials_emissive_strength. Importer support varies; see the Blender and Unity guide. 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.
45,958 bytes (0.05 MB)
Download runtime assetsModels plus Kiln source, editing metadata, materials and included revisions, so you can reopen and continue editing.
236,355 bytes (0.24 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: Idle, Lumber, Telegraph, Strike, Recover.
const meta={name:"Nightman / hunched stone golem"};
function shapeGeo(center,rings,n=7){
const P=[],UV=[],UVscale=Math.max(1,2*Math.max(...rings.map(r=>Math.max(r[1],r[2]))));const pts=rings.map((r,j)=>Array.from({length:n},(_,i)=>{const a=i*2*Math.PI/n;return [center[0]+(r[3]||0)+Math.cos(a)*r[1],center[1]+r[0],center[2]+Math.sin(a)*r[2]];}));
function tri(a,b,c){P.push(...a,...b,...c);const normal=new THREE.Vector3(...b).sub(new THREE.Vector3(...a)).cross(new THREE.Vector3(...c).sub(new THREE.Vector3(...a)));const abs=[Math.abs(normal.x),Math.abs(normal.y),Math.abs(normal.z)],d=abs.indexOf(Math.max(...abs));const uAxis=d===0?2:0,vAxis=d===1?2:1;for(const p of [a,b,c])UV.push(.5+(p[uAxis]-center[uAxis])/UVscale,.5+(p[vAxis]-center[vAxis])/UVscale);}
for(let j=0;j<rings.length-1;j++)for(let i=0;i<n;i++){const k=(i+1)%n;tri(pts[j][i],pts[j+1][i],pts[j+1][k]);tri(pts[j][i],pts[j+1][k],pts[j][k]);}
const low=[center[0]+(rings[0][3]||0),center[1]+rings[0][0],center[2]],r=rings[rings.length-1],high=[center[0]+(r[3]||0),center[1]+r[0],center[2]];
for(let i=0;i<n;i++){const k=(i+1)%n;tri(low,pts[0][i],pts[0][k]);tri(high,pts[pts.length-1][k],pts[pts.length-1][i]);}return meshGeo({positions:P,uvs:UV});
}
let pendingSkins=[];
function skinChunk(name,geometry,material,region,bones,boneIndex,root){pendingSkins.push({name,geometry,material,region,bones,boneIndex,root});}
function flushStoneSkins(root){const groups={};for(const part of pendingSkins){const key=part.region+(part.name==='SeparateHead'?'.SeparateHead':'');if(!groups[key])groups[key]=[];groups[key].push(part);}for(const key of Object.keys(groups)){const parts=groups[key],P=[],N=[],UV=[],indices=[],weights=[];for(const part of parts){const g=part.geometry,p=g.getAttribute('position'),n=g.getAttribute('normal'),uv=g.getAttribute('uv');if(g.index)throw new Error('Stone consolidation expects authored triangle corners');for(let i=0;i<p.count;i++){P.push(p.getX(i),p.getY(i),p.getZ(i));N.push(n.getX(i),n.getY(i),n.getZ(i));UV.push(uv.getX(i),uv.getY(i));indices.push(part.boneIndex,0,0,0);weights.push(1,0,0,0);}}const source=new THREE.BufferGeometry();source.setAttribute('position',new THREE.Float32BufferAttribute(P,3));source.setAttribute('normal',new THREE.Float32BufferAttribute(N,3));source.setAttribute('uv',new THREE.Float32BufferAttribute(UV,2));const first=parts[0],g=remapUVToAtlas(source,first.material.map,first.region,{inputOrigin:'bottom-left',mode:'texel-center'});createSkinnedPart(key==='head.SeparateHead'?'SeparateHead':'Stone_'+key,g,first.material,{parent:root,bones:first.bones,indices,weights});}pendingSkins=[];}
function brokenRidgeGeo(center,rings,n,seed){const g=shapeGeo(center,rings,n),p=g.getAttribute('position');for(let i=0;i<p.count;i++){const x=p.getX(i)-center[0],z=p.getZ(i)-center[2],a=Math.atan2(z,x),y=p.getY(i)-center[1],factor=1+.14*Math.sin(a*3+seed)+.045*Math.cos(y*13+a);p.setX(i,center[0]+x*factor);p.setZ(i,center[2]+z*factor);}g.computeVertexNormals();return g;}
function limbGeo(a,b,r0,r1){const A=new THREE.Vector3(...a),B=new THREE.Vector3(...b),L=A.distanceTo(B),axis=B.clone().sub(A).normalize(),u=new THREE.Vector3(0,1,0).cross(axis).normalize(),v=axis.clone().cross(u).normalize(),P=[],UV=[],N=6;
function at(t,ang,r){return A.clone().lerp(B,t).addScaledVector(u,Math.cos(ang)*r).addScaledVector(v,Math.sin(ang)*r);}
function tri(p,q,r,uv){P.push(...p.toArray(),...q.toArray(),...r.toArray());UV.push(...uv);}
for(let i=0;i<N;i++){const circumference=2*Math.PI*(r0+r1)/2,x=i/N*circumference,y=(i+1)/N*circumference,a=i*2*Math.PI/N,b=(i+1)*2*Math.PI/N,p=at(0,a,r0),q=at(0,b,r0),s=at(1,a,r1),t=at(1,b,r1);tri(p,q,t,[x,0,y,0,y,Math.min(L,.98)]);tri(p,t,s,[x,0,y,Math.min(L,.98),x,Math.min(L,.98)]);tri(A,q,p,[.5,.5,.5+Math.cos(b)*r0,.5+Math.sin(b)*r0,.5+Math.cos(a)*r0,.5+Math.sin(a)*r0]);tri(B,s,t,[.5,.5,.5+Math.cos(a)*r1,.5+Math.sin(a)*r1,.5+Math.cos(b)*r1,.5+Math.sin(b)*r1]);}return meshGeo({positions:P,uvs:UV});}
async function build(){pendingSkins=[];const root=createRoot('Nightman');const stone=await compilePortableMaterialSpecV3({"schemaVersion":3,"model":"pbrMetallicRoughness","name":"Forest nightman-atlas","baseColor":16777215,"roughness":1,"metalness":0,"emissiveIntensity":1,"alphaMode":"opaque","alphaCutoff":0.5,"doubleSided":false,"textures":{"baseColor":{"kind":"resource","resourceId":"kiln.library.7b711fed2b077855239949f953a4c3e0a42f834fa46f1741b58c5aea2fbce8e1.base-color","sampler":{"minFilter":"nearest","magFilter":"nearest","wrapS":"clamp-to-edge","wrapT":"clamp-to-edge"}}}});
const spine=createBoneChain('NightmanSpine',[{role:'pelvis',offset:[0,1.05,0]},{role:'chest',offset:[-.1,.5,0]},{role:'neck',offset:[.24,.36,0]},{role:'head',offset:[.13,.12,0]}],{parent:root});const all=[...spine.nodes],limbs=[];
for(const [side,s]of [['L',-1],['R',1]]){const arm=createBoneChain('NightmanArm'+side,[{role:'arm.'+side.toLowerCase()+'.shoulder',offset:[.1,.23,s*.49]},{role:'arm.'+side.toLowerCase()+'.elbow',offset:[0,-.61,s*.19]},{role:'arm.'+side.toLowerCase()+'.wrist',offset:[0,-.58,s*.02]}],{parent:spine.nodes[1]});const leg=createBoneChain('NightmanLeg'+side,[{role:'leg.'+side.toLowerCase()+'.hip',offset:[0,-.08,s*.25]},{role:'leg.'+side.toLowerCase()+'.knee',offset:[.12,-.45,s*.02]},{role:'leg.'+side.toLowerCase()+'.ankle',offset:[-.16,-.38,s*.01],endEffector:true,contact:false}],{parent:spine.root});limbs.push({side,s,arm,leg,armIndex:all.length,legIndex:all.length+3});all.push(...arm.nodes,...leg.nodes);}
spine.nodes.forEach((b,i)=>b.name=['Nightman_Pelvis','Nightman_Chest','Nightman_Neck','Joint_Nightman_head'][i]);for(const l of limbs){l.arm.nodes.forEach((b,i)=>b.name='Nightman_'+l.side+'_'+['Shoulder','Elbow','Wrist'][i]);l.leg.nodes.forEach((b,i)=>b.name='Nightman_'+l.side+'_'+['Hip','Knee','Ankle'][i]);}
skinChunk('Pelvis',shapeGeo([0,1.01,0],[[-.19,.21,.31],[0,.29,.39],[.20,.25,.37]],8),stone,'body',all,0,root);
skinChunk('HunchedChest',shapeGeo([-.10,1.48,0],[[-.36,.25,.36],[0,.37,.46],[.28,.43,.57,-.08],[.48,.31,.46,-.09]],8),stone,'body',all,1,root);
skinChunk('UpperBackHump',shapeGeo([-.35,1.78,0],[[-.17,.22,.32],[.11,.27,.40],[.22,.17,.30]],7),stone,'shoulders',all,1,root);skinChunk('BackCragLeft',shapeGeo([-.49,1.87,-.20],[[-.12,.13,.15],[.10,.19,.17,-.05],[.25,.09,.10,-.06]],6),stone,'shoulders',all,1,root);skinChunk('BackCragRight',shapeGeo([-.44,1.87,.24],[[-.13,.14,.14],[.09,.17,.16],[.18,.09,.10,-.04]],5),stone,'shoulders',all,1,root);skinChunk('ShoulderCragLeft',shapeGeo([-.04,2.00,-.59],[[-.13,.18,.20],[.04,.19,.21],[.23,.09,.12,-.05]],5),stone,'shoulders',all,limbs[0].armIndex,root);skinChunk('ShoulderCragRight',shapeGeo([-.14,1.90,.54],[[-.10,.15,.18],[.13,.20,.22],[.20,.07,.10,.035]],6),stone,'shoulders',all,limbs[1].armIndex,root);skinChunk('HeavyNeck',shapeGeo([.15,1.87,0],[[-.13,.19,.19],[.14,.20,.20]],7),stone,'head',all,2,root);
const headGeo=shapeGeo([.28,2.07,0],[[-.22,.15,.19],[0,.27,.225],[.27,.21,.20]],8);const hp=headGeo.getAttribute('position');for(let i=0;i<hp.count;i++)if(hp.getX(i)<.31&&hp.getZ(i)<-.12){hp.setZ(i,hp.getZ(i)-.045);hp.setY(i,hp.getY(i)-.022);}for(let i=0;i<hp.count;i++)if(hp.getY(i)>2.25){const a=Math.atan2(hp.getZ(i),hp.getX(i)-.28);hp.setY(i,hp.getY(i)+.055*Math.sin(a*2+.8)+.030*Math.sin(a*5+1.1));}headGeo.computeVertexNormals();skinChunk('SeparateHead',headGeo,stone,'head',all,3,root);
skinChunk('BrowRidge',shapeGeo([.46,2.18,0],[[-.045,.145,.245],[.045,.12,.23]],7),stone,'head',all,3,root);
for(const l of limbs){const z=l.s;
skinChunk('ShoulderPlate_'+l.side,shapeGeo([-.035,1.85,z*.51],[[-.16,.26,.25],[.08,.29,.29],[.18,.21,.22]],7),stone,'shoulders',all,l.armIndex,root);
skinChunk('UpperArm_'+l.side,brokenRidgeGeo([0,1.48,z*.59],[[-.33,.17,.18,.035],[.02,.26,.23,-.02],[.12,.24,.20,.07],[.29,.23,.25]],7,z*1.7),stone,'body',all,l.armIndex,root);
skinChunk('ElbowBridge_'+l.side,shapeGeo([0,1.18,z*.68],[[-.11,.155,.14],[.10,.165,.16]],6),stone,'body',all,l.armIndex+1,root);
skinChunk('Forearm_'+l.side,brokenRidgeGeo([0,.86,z*.70],[[-.32,.17,.16],[-.06,.23,.21,.03],[.02,.30,.24,.06],[.13,.205,.18,-.025],[.31,.19,.18]],6,z*.8),stone,'hands',all,l.armIndex+1,root);
skinChunk('HeavyHand_'+l.side,shapeGeo([0,.47,z*.70],[[-.20,.22,.24],[.06,.29,.28],[.21,.18,.18]],7),stone,'hands',all,l.armIndex+2,root);
for(let i=0;i<3;i++)skinChunk('StoneFinger_'+l.side+'_'+i,limbGeo([.11+(i%2)*.035,.33,z*.70+(i-1)*.105],[.22+(i%2)*.035,[.085,.055,.110][i],z*.70+(i-1)*.11],.055,.010),stone,'hands',all,l.armIndex+2,root);
skinChunk('Thigh_'+l.side,shapeGeo([.03,.78,z*.26],[[-.25,.20,.21],[0,.23,.24],[.23,.24,.22]],7),stone,'body',all,l.legIndex,root);
skinChunk('Knee_'+l.side,shapeGeo([.13,.53,z*.27],[[-.11,.19,.19],[.12,.20,.19]],6),stone,'body',all,l.legIndex+1,root);
skinChunk('Shin_'+l.side,shapeGeo([.04,.34,z*.28],[[-.20,.15,.16],[.08,.20,.19],[.22,.18,.18]],7),stone,'body',all,l.legIndex+1,root);
skinChunk('GroundedFoot_'+l.side,brokenRidgeGeo([.14,.08,z*.28],[[-.08,.28,.20],[.02,.23,.19,.05],[.14,.13,.13,-.12]],6,z*.9),stone,'hands',all,l.legIndex+2,root);
const contact=createPivot('Nightman_'+l.side+'_FootContact',[.18,-.14,0],l.leg.end);contact.name='Nightman_'+l.side+'_FootContact';}
const eye=await compilePortableMaterialSpecV3({schemaVersion:3,model:'pbrMetallicRoughness',name:'Nightman red eye stone',baseColor:0xb51f24,roughness:.8,metalness:0,emissive:0xb51f24,emissiveIntensity:.8});
function headFaceX(z,y){const t=Math.max(0,Math.min(1,(y-2.07)/.27)),rx=.27+(.21-.27)*t,rz=.225+(.20-.225)*t;return .28+rx-.41421356237*rx/rz*Math.abs(z);}
const recess=gameMaterial(0x26302c,{roughness:1});
for(const [side,sign]of [['Left',-1],['Right',1]]){const inner=[[.053,2.125],[.185,2.125],[.170,2.025],[.065,2.025]],outer=[[.025,2.151],[.216,2.145],[.193,2.006],[.040,2.002]],P=[],I=[];
for(let i=0;i<4;i++){const [z,y]=outer[i];P.push(headFaceX(z,y)+.016-.27,y-2.03,sign*z);}for(let i=0;i<4;i++){const [z,y]=inner[i];P.push(headFaceX(z,y)+.002-.27,y-2.03,sign*z);}for(let i=0;i<4;i++){const k=(i+1)%4;I.push(i,k,4+k,i,4+k,4+i);}I.push(4,5,6,4,6,7);if(sign<0)for(let i=0;i<I.length;i+=3){const t=I[i+1];I[i+1]=I[i+2];I[i+2]=t;}const socket=createPart('RecessedEyeSocket'+side,meshGeo({positions:P,indices:I}),recess,{parent:spine.end});markOpenShell(socket,'Intended recessed eye frame mounted to head face');
const points=[[.065,2.10],[.172,2.105],[.150,2.055],[.077,2.047]],E=[];for(const [z,y]of points)E.push(headFaceX(z,y)+.009-.27,y-2.03,sign*z);const g=meshGeo({positions:E,indices:sign>0?[0,1,2,0,2,3]:[0,2,1,0,3,2]});const e=createPart('Eye'+side,g,eye,{parent:spine.end});markOpenShell(e,'Intentional opaque/emissive eye surface inside mounted socket');}
flushStoneSkins(root);const strike=createPivot('NightmanStrike',[.92,1.0,0],root);strike.name='NightmanStrike';return root;}
function animate(root){
function rot(name,keys){return rotationTrack(name,keys.map(k=>({time:k[0],rotation:[k[1]||0,k[2]||0,k[3]||0]})));}
const idle=[rot('Nightman_Chest',[[0,0,0,0],[1.6,1,0,-1],[3.2,0,0,0]]),rot('Joint_Nightman_head',[[0,0,-3,0],[1.6,0,3,0],[3.2,0,-3,0]])];
const lumber=[rot('Nightman_Chest',[[0,0,-2,-2],[.6,0,2,1],[1.2,0,-2,-2]])];for(const side of ['L','R']){const s=side==='L'?1:-1;lumber.push(rot('Nightman_'+side+'_Hip',[[0,0,0,s*12],[.6,0,0,-s*12],[1.2,0,0,s*12]]));lumber.push(rot('Nightman_'+side+'_Shoulder',[[0,0,0,-s*7],[.6,0,0,s*7],[1.2,0,0,-s*7]]));}
lumber.push(positionTrack('Nightman_Pelvis',[{"time":0.0,"position":[0,1.040278976,0]},{"time":0.025,"position":[0,1.042482922,0]},{"time":0.05,"position":[0,1.044436362,0]},{"time":0.075,"position":[0,1.046138701,0]},{"time":0.1,"position":[0,1.047589421,0]},{"time":0.125,"position":[0,1.04878808,0]},{"time":0.15,"position":[0,1.049734312,0]},{"time":0.175,"position":[0,1.050427829,0]},{"time":0.2,"position":[0,1.050868421,0]},{"time":0.225,"position":[0,1.051055952,0]},{"time":0.25,"position":[0,1.050990366,0]},{"time":0.275,"position":[0,1.050671683,0]},{"time":0.3,"position":[0,1.0501,0]},{"time":0.325,"position":[0,1.050671683,0]},{"time":0.35,"position":[0,1.050990366,0]},{"time":0.375,"position":[0,1.051055952,0]},{"time":0.4,"position":[0,1.050868421,0]},{"time":0.425,"position":[0,1.050427829,0]},{"time":0.45,"position":[0,1.049734312,0]},{"time":0.475,"position":[0,1.04878808,0]},{"time":0.5,"position":[0,1.047589421,0]},{"time":0.525,"position":[0,1.046138701,0]},{"time":0.55,"position":[0,1.044436362,0]},{"time":0.575,"position":[0,1.042482922,0]},{"time":0.6,"position":[0,1.040278976,0]},{"time":0.625,"position":[0,1.042482922,0]},{"time":0.65,"position":[0,1.044436362,0]},{"time":0.675,"position":[0,1.046138701,0]},{"time":0.7,"position":[0,1.047589421,0]},{"time":0.725,"position":[0,1.04878808,0]},{"time":0.75,"position":[0,1.049734312,0]},{"time":0.775,"position":[0,1.050427829,0]},{"time":0.8,"position":[0,1.050868421,0]},{"time":0.825,"position":[0,1.051055952,0]},{"time":0.85,"position":[0,1.050990366,0]},{"time":0.875,"position":[0,1.050671683,0]},{"time":0.9,"position":[0,1.0501,0]},{"time":0.925,"position":[0,1.050671683,0]},{"time":0.95,"position":[0,1.050990366,0]},{"time":0.975,"position":[0,1.051055952,0]},{"time":1.0,"position":[0,1.050868421,0]},{"time":1.025,"position":[0,1.050427829,0]},{"time":1.05,"position":[0,1.049734312,0]},{"time":1.075,"position":[0,1.04878808,0]},{"time":1.1,"position":[0,1.047589421,0]},{"time":1.125,"position":[0,1.046138701,0]},{"time":1.15,"position":[0,1.044436362,0]},{"time":1.175,"position":[0,1.042482922,0]},{"time":1.2,"position":[0,1.040278976,0]}]));for(const side of ['L','R']){const s=side==='L'?1:-1;lumber.push(rot('Nightman_'+side+'_Ankle',[[0,0,0,-s*12],[.6,0,0,s*12],[1.2,0,0,-s*12]]));}const tele=[rot('Nightman_Chest',[[0,0,0,0],[.45,0,0,8],[.8,0,0,8]])],strike=[rot('Nightman_Chest',[[0,0,0,8],[.18,0,0,-12],[.34,0,0,-20],[.5,0,0,-20]])],recover=[rot('Nightman_Chest',[[0,0,0,-20],[.45,0,0,-6],[.9,0,0,0]])];
for(const side of ['L','R']){const inward=side==='L'?-15:15;tele.push(rot('Nightman_'+side+'_Shoulder',[[0,0,0,0],[.55,0,inward,94],[.8,0,inward,94]]));tele.push(rot('Nightman_'+side+'_Elbow',[[0,0,0,0],[.55,0,0,-28],[.8,0,0,-28]]));strike.push(rot('Nightman_'+side+'_Shoulder',[[0,0,inward,94],[.18,0,inward,80],[.34,0,inward,60],[.5,0,inward,60]]));strike.push(rot('Nightman_'+side+'_Elbow',[[0,0,0,-28],[.18,0,0,-8],[.34,0,0,-3],[.5,0,0,-3]]));recover.push(rot('Nightman_'+side+'_Shoulder',[[0,0,inward,60],[.9,0,0,0]]));recover.push(rot('Nightman_'+side+'_Elbow',[[0,0,0,-3],[.9,0,0,0]]));}
return [createClip('Idle',3.2,idle,{loop:true}),createClip('Lumber',1.2,lumber,{loop:true}),createClip('Telegraph',.8,tele,{loop:false}),createClip('Strike',.5,strike,{loop:false}),createClip('Recover',.9,recover,{loop:false})];}Scroll code horizontally
Asset content follows the supplied CC0-1.0 licence record. Kiln and scene software retain their own licences.