Search code examples
csssvgtile

SVG Texture Tiled Plane


Im trying to tile a plane in SVG, but unfortunatley i cant use css3 perspective as its buggy in safari. I tried skewX with a fixed perspective tile. It kind of works, but i think the maths is wrong? Has anyone experience with maths of tiling a plane in CSS?

I was hoping the mirror tiles would stay aligned but they don't seem to line up after 5 columns.

:root {
  --baseWidth: 97;
  --baseFactor: calc(5/8);
}

#sRows use:nth-child(even) {
  --f1: #ff0;
  --f2: #000;
  --col: var(--f1);
}

#sRows use:nth-child(odd) {
  --f1: #000;
  --f2: #ff0;
  --col: var(--f1);
}

#mCol use:nth-child(even) {
  --f: var(--f1);
}

#mCol use:nth-child(odd) {
  --f: var(--f2);
}

#mCol use {
  --x: calc(var(--baseWidth) * var(--i));
  --s: calc(89 - calc(30 / var(--i)));
}

#mCol use:first-child {
  --s: calc(91 - calc(30 / var(--i)));
}

#mirrorLeft {
  transform: skewX(calc( var(--s) * -1deg)) translate(calc(var(--x) * -1px));
}

#mirrorRight {
  transform: skewX(calc( var(--s) * 1deg)) translate(calc(var(--x) * 1px));
}

#sRows use {
  --rx: calc(240 - calc(204 * pow(var(--baseFactor), calc( var(--ri) + 1))));
  --ry: calc(88 + calc( 90 * pow( var(--baseFactor), var(--ri))));
  --rz: calc( var(--baseWidth) * pow(var(--baseFactor), var(--ri)));
  transform: translate(calc(var(--rx)*1px), calc( var(--ry) * 1px)) scale(calc(var(--rz)*1.66%));
}
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="180">
   <defs>
      <symbol id="tileTemplate" overflow="hidden" >
         <path style="fill:var(--f)" d="M 31.5 0 H 127.5 L 160 35 H 0 Z" opacity=".5" />
         <?ignore
         <g >
            <path style="fill:var(--f)" d="M 31.5 0 H 128.5 L 160 35 H 0 Z" opacity=".25"/>
            <g opacity=".5" >
               <path d="m 79 23.3471 l -0.208 11.433 m 30.182 -20.888 l 3.75 8.6 m -64.112 -8.6 l -4.063 8.6 m 100.9347 1.3838 l 9.1319 9.852 M 130 6.5311 l 6.003 7.031 M 79 6.5311 L 78.873 13.5621 m 24.23 -13.135 l 2.435 5.585 M 54.971 0.4271 L 52.333 6.0121" style="stroke:#fff;stroke-width:2"/>
               <path d="m 147.571,22.984 10.177,11 M 81,22.984 81.208,34.417 m 30.18,-20.888 4.063,8.6 m -64.425,-8.6 -3.75,8.6 m 85.327,-15.961 6.003,7.031 M 81,6.168 l 0.127,7.031 m 23.902,-13.135 2.638,5.585 M 56.897,0.064 54.462,5.649" style="stroke:#000000;stroke-width:2" />
               <path d="m 0.665,33.984 h 158.67 M 11.44,22.554 H 148.56 M 20.063,13.2 H 140.189 M 26.6,5.907 h 106.88" style="stroke:#000000;stroke-width:2"/>
            </g>
         </g>
         ?>
      </symbol>
      <symbol id="tRow" overflow="visible">
         <use href="#tileTemplate" style="--f:var(--col)" />
         <use href="#mCol" />
      </symbol> 
      <symbol id="mSkew" overflow="visible" >
         <use id="mirrorRight" href="#tileTemplate"   />
         <use id="mirrorLeft"  href="#tileTemplate"  />
      </symbol>
      <symbol id="mCol" overflow="visible" >
         <use href="#mSkew" style="--i:1"  />
         <use href="#mSkew" style="--i:2 " />
         <use href="#mSkew" style="--i:3 " />
         <use href="#mSkew" style="--i:4 " />
         <use href="#mSkew" style="--i:5 " />
         <use href="#mSkew" style="--i:6 " />
         <use href="#mSkew" style="--i:7 " />
         <use href="#mSkew" style="--i:8 " />
         <use href="#mSkew" style="--i:9 " />
         <use href="#mSkew" style="--i:10" />
         <use href="#mSkew" style="--i:11" />
         <use href="#mSkew" style="--i:12" />
         <use href="#mSkew" style="--i:13" />
         <use href="#mSkew" style="--i:14" />
         <use href="#mSkew" style="--i:15" />
         <use href="#mSkew" style="--i:16" />
         <use href="#mSkew" style="--i:17" />
         <use href="#mSkew" style="--i:18" />
         <use href="#mSkew" style="--i:19" />
         <use href="#mSkew" style="--i:20" />
         <use href="#mSkew" style="--i:21" />
         <use href="#mSkew" style="--i:22" />
         <use href="#mSkew" style="--i:23" />
         <use href="#mSkew" style="--i:24" />
         <use href="#mSkew" style="--i:25" />
         <use href="#mSkew" style="--i:26" />
         <use href="#mSkew" style="--i:27" />
         <use href="#mSkew" style="--i:28" />
         <use href="#mSkew" style="--i:29" />
         <use href="#mSkew" style="--i:30" />
         <use href="#mSkew" style="--i:31" />
         <use href="#mSkew" style="--i:32" />
      </symbol>  
   </defs>
   <g id="sRows" class="scaleRows" >
      <use href="#tRow" style="--ri:1;"/>
      <use href="#tRow" style="--ri:2;" />
      <use href="#tRow" style="--ri:3;" />
      <use href="#tRow" style="--ri:4;" />
      <use href="#tRow" style="--ri:5;" />
      <use href="#tRow" style="--ri:6;" />
      <use href="#tRow" style="--ri:7;" />
      <use href="#tRow" style="--ri:8;" />
   </g>
   <g transform="translate(240)" display="inline" opacity=".5" >
      <path d="M 0,0 -80,180 M 0,0 80,180" style="stroke:#888;stroke-width:0.5"/>
      <path d="M -240,145 H 240" style="stroke:#0f0;stroke-width:.5;stroke-dasharray:1, 1" />
      <path d="M 0,90 -80,180 M 0,90 80,180" style="stroke:#00f;stroke-width:.5;stroke-dasharray:1, 1"  />
   </g>
</svg>


Solution

  • To make your computations as easy as possible, your focal point should be at [0, 0] of the user coordinate system. Then, after you draw a base tile, two characteristic numbers are needed:

    --width: x distance between the two lower corners / y value of the lower corners
    --height: y value of the upper corners / y value of the lower corners
    

    For my example, I've choosen them to be 0.2 and 0.9, respectively.

    Then, the skewed tiles of each row can be computed as

    transform: skewX(atan(var(--width) * [0, 1, 2,...]));
    

    and the scaling of each row as

    transform: scale(pow(var(--height), [0, 1, 2,...]));
    

    Instead of explicite mirroring, you can just add negative numbers to continue in the opposite direction from the base tile. This is both true for horizontal and vertical tiling.

    :root {
      --width: 0.2;
      --height: 0.9;
    }
    
    use[href="#tile"] {
      fill: none;
      transform: skewX(atan(var(--width, 0) * var(--x)));
    }
    
    use[href="#row"] {
      transform: scale(pow(var(--height), var(--y)));
    }
    
    use[href="#row"]:nth-child(even) {
      --even: red;
      --odd: darkgreen;
    }
    
    use[href="#row"]:nth-child(odd) {
      --even: darkgreen;
      --odd: red;
    }
    
    use[href="#tile"]:nth-child(even) {
      stroke: var(--even);
    }
    
    use[href="#tile"]:nth-child(odd) {
      stroke: var(--odd);
    }
    
    use#base {
      stroke: black;
    }
    <svg viewBox="-100 50 200 100">
      <defs>
        <path id="tile" d="M -9.4,90 H 8.6 M -9.25,92.4 H 8.75 L 9,94.8 M -9.5,94.8 H 9.5 M -9.75,97.4 H 9.25 L 9.5,100 M -4.5,90 -4.625,92.4 M -4.75,94.8 -4.875,97.4 M 4.5,90 4.625,92.5 M 4.75,94.8 4.875,97.4 M 0,92.5 V 94.8 M 0,97.4 V 99.5"/>
        <g id="row">
          <use href="#tile" style="--x:-10"/>
          <use href="#tile" style="--x:-9"/>
          <use href="#tile" style="--x:-8"/>
          <use href="#tile" style="--x:-7"/>
          <use href="#tile" style="--x:-6"/>
          <use href="#tile" style="--x:-5"/>
          <use href="#tile" style="--x:-4"/>
          <use href="#tile" style="--x:-3"/>
          <use href="#tile" style="--x:-2"/>
          <use href="#tile" style="--x:-1"/>
          <use href="#tile" style="--x:0"/>
          <use href="#tile" style="--x:1"/>
          <use href="#tile" style="--x:2"/>
          <use href="#tile" style="--x:3"/>
          <use href="#tile" style="--x:4"/>
          <use href="#tile" style="--x:5"/>
          <use href="#tile" style="--x:6"/>
          <use href="#tile" style="--x:7"/>
          <use href="#tile" style="--x:8"/>
          <use href="#tile" style="--x:9"/>
          <use href="#tile" style="--x:10"/>
        </g>
      </defs>
      <use href="#row" style="--y:-4"/>
      <use href="#row" style="--y:-3"/>
      <use href="#row" style="--y:-2"/>
      <use href="#row" style="--y:-1"/>
      <use href="#row" style="--y:0"/>
      <use href="#row" style="--y:1"/>
      <use href="#row" style="--y:2"/>
      <use href="#row" style="--y:3"/>
      <use href="#row" style="--y:4"/>
      <use href="#row" style="--y:5"/>
      <use href="#row" style="--y:6"/>
      <use href="#row" style="--y:7"/>
      <use href="#row" style="--y:8"/>
      <!-- repeat the base tile just to mark its position -->
      <use id="base" href="#tile"/>
    </svg>