File size: 2,673 Bytes
0dfa89f
 
 
 
 
 
 
 
 
 
 
 
7539b2e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0dfa89f
7407d55
7539b2e
 
 
 
 
 
 
 
 
7407d55
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import type {Asset,PlacedAsset,View} from './types.ts';
function hash(text:string){let h=2166136261;for(const c of text)h=Math.imul(h^c.charCodeAt(0),16777619);return (h>>>0)/4294967296;}
export function placeAssets(assets:Asset[]):PlacedAsset[]{
  let ring=0,start=0,count=1;
  return assets.map((asset,index)=>{
    while(index>=start+count){start+=count;ring++;count=ring*6;}
    const angle=(index-start)/count*Math.PI*2-Math.PI*0.8+(ring%2)*0.15;
    const radius=ring*410+(hash(asset.id)-0.5)*38;
    return {asset,x:ring?Math.cos(angle)*radius:0,y:ring?Math.sin(angle)*radius*0.9:0,size:300+hash(asset.id+'size')*52};
  });
}
export function visibleAssets(placed:PlacedAsset[],view:View,width:number,height:number,padding=200){return placed.filter(item=>{const x=item.x*view.k+view.x,y=item.y*view.k+view.y,half=item.size*view.k/2;return x+half>-padding&&x-half<width+padding&&y+half>-padding&&y-half<height+padding;});}

/** Keep the mounted set stable within its overscan window. Refresh before the
 * viewport reaches the preload edge, including when zooming out or resizing. */
export function createVisibilityWindow(placed:PlacedAsset[],width:number,height:number,padding=200){
  let safe:{left:number;top:number;right:number;bottom:number}|undefined;
  let items:PlacedAsset[]=[];
  return (view:View)=>{
    const left=-view.x/view.k,top=-view.y/view.k,right=(width-view.x)/view.k,bottom=(height-view.y)/view.k;
    if(!safe||left<safe.left||top<safe.top||right>safe.right||bottom>safe.bottom){
      const margin=padding/(2*view.k);
      safe={left:left-margin,top:top-margin,right:right+margin,bottom:bottom+margin};
      const next=visibleAssets(placed,view,width,height,padding);
      if(next.length!==items.length||next.some((item,i)=>item!==items[i]))items=next;
    }
    return items;
  };
}
export function homeView(width:number,height:number):View{return {x:width/2,y:height/2+15,k:width<700?0.66:1};}

/** Controls belong to the central half of the canvas, independently of zoom. */
export function selectionInFocus(x:number,y:number,width:number,height:number){
  return width>0&&height>0&&x>=width*.25&&x<=width*.75&&y>=height*.25&&y<=height*.75;
}

/** Match the exact thumbnail content box at every zoom. No minimum, cap or
 * responsive breakpoint may change the size when an object becomes interactive. */
export function viewerFrame(x:number,y:number,scale:number,width:number,height:number,objectSize:number){
  const dimension=Math.max(0,objectSize-28)*scale;
  const left=x-dimension/2,top=y-dimension/2;
  return {dimension,left,top,inView:width>0&&height>0&&dimension>0&&left<width&&left+dimension>0&&top<height&&top+dimension>0};
}