Add color support
This commit is contained in:
parent
32792f81a7
commit
5698dfb791
|
@ -15,10 +15,11 @@ precision highp float;
|
||||||
uniform sampler2D uStencilTexture;
|
uniform sampler2D uStencilTexture;
|
||||||
|
|
||||||
in vec2 vTexCoord;
|
in vec2 vTexCoord;
|
||||||
|
in vec4 vColor;
|
||||||
|
|
||||||
out vec4 oFragColor;
|
out vec4 oFragColor;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
float coverage = texture(uStencilTexture, vTexCoord).r;
|
float coverage = texture(uStencilTexture, vTexCoord).r;
|
||||||
oFragColor = vec4(vec3(1.0 - coverage), coverage);
|
oFragColor = vColor * coverage;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,10 @@ uniform vec2 uStencilTextureSize;
|
||||||
in vec2 aTessCoord;
|
in vec2 aTessCoord;
|
||||||
in vec2 aTileOrigin;
|
in vec2 aTileOrigin;
|
||||||
in uint aTileIndex;
|
in uint aTileIndex;
|
||||||
|
in vec4 aColor;
|
||||||
|
|
||||||
out vec2 vTexCoord;
|
out vec2 vTexCoord;
|
||||||
|
out vec4 vColor;
|
||||||
|
|
||||||
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
|
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
|
||||||
uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x);
|
uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x);
|
||||||
|
@ -32,5 +34,6 @@ void main() {
|
||||||
vec2 position = aTileOrigin + uTileSize * aTessCoord;
|
vec2 position = aTileOrigin + uTileSize * aTessCoord;
|
||||||
vec2 texCoord = computeTileOffset(aTileIndex, uStencilTextureSize.x) + aTessCoord * uTileSize;
|
vec2 texCoord = computeTileOffset(aTileIndex, uStencilTextureSize.x) + aTessCoord * uTileSize;
|
||||||
vTexCoord = texCoord / uStencilTextureSize;
|
vTexCoord = texCoord / uStencilTextureSize;
|
||||||
|
vColor = aColor;
|
||||||
gl_Position = vec4((position / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0), 0.0, 1.0);
|
gl_Position = vec4((position / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0), 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
|
||||||
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
|
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
|
||||||
},
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
|
||||||
|
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
|
||||||
|
},
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
||||||
|
@ -211,6 +216,14 @@
|
||||||
"wrappy": "1.0.2"
|
"wrappy": "1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"parse-color": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-e3SLlag/A/FqlPU15S1/PZRlhhk=",
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "0.5.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"path-parse": {
|
"path-parse": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/webgl2": "0.0.4",
|
"@types/webgl2": "0.0.4",
|
||||||
|
"parse-color": "^1.0.0",
|
||||||
"svgpath": "^2.2.1"
|
"svgpath": "^2.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,12 @@ import STENCIL_FRAGMENT_SHADER_SOURCE from "./stencil.fs.glsl";
|
||||||
import SVG from "../resources/svg/Ghostscript_Tiger.svg";
|
import SVG from "../resources/svg/Ghostscript_Tiger.svg";
|
||||||
|
|
||||||
const SVGPath: (path: string) => SVGPath = require('svgpath');
|
const SVGPath: (path: string) => SVGPath = require('svgpath');
|
||||||
|
const parseColor: (color: string) => any = require('parse-color');
|
||||||
|
|
||||||
const SVG_NS: string = "http://www.w3.org/2000/svg";
|
const SVG_NS: string = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
const TILE_SIZE: number = 16.0;
|
const TILE_SIZE: number = 16.0;
|
||||||
const STENCIL_FRAMEBUFFER_SIZE: number = TILE_SIZE * 256;
|
const STENCIL_FRAMEBUFFER_SIZE: number = TILE_SIZE * 64;
|
||||||
|
|
||||||
const GLOBAL_OFFSET: Point2D = {x: 200.0, y: 150.0};
|
const GLOBAL_OFFSET: Point2D = {x: 200.0, y: 150.0};
|
||||||
|
|
||||||
|
@ -37,10 +38,33 @@ interface SVGPath {
|
||||||
SVGPath;
|
SVGPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Point2D = {x: number, y: number};
|
interface Point2D {
|
||||||
type Size2D = {width: number, height: number};
|
x: number;
|
||||||
type Rect = {origin: Point2D, size: Size2D};
|
y: number;
|
||||||
type Vector3D = {x: number, y: number, z: number};
|
}
|
||||||
|
|
||||||
|
interface Size2D {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Rect {
|
||||||
|
origin: Point2D;
|
||||||
|
size: Size2D;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Vector3D {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Color {
|
||||||
|
r: number;
|
||||||
|
g: number;
|
||||||
|
b: number;
|
||||||
|
a: number;
|
||||||
|
}
|
||||||
|
|
||||||
type Edge = 'left' | 'top' | 'right' | 'bottom';
|
type Edge = 'left' | 'top' | 'right' | 'bottom';
|
||||||
|
|
||||||
|
@ -53,7 +77,7 @@ class App {
|
||||||
private stencilFramebuffer: WebGLFramebuffer;
|
private stencilFramebuffer: WebGLFramebuffer;
|
||||||
private coverProgram:
|
private coverProgram:
|
||||||
Program<'FramebufferSize' | 'TileSize' | 'StencilTexture' | 'StencilTextureSize',
|
Program<'FramebufferSize' | 'TileSize' | 'StencilTexture' | 'StencilTextureSize',
|
||||||
'TessCoord' | 'TileOrigin' | 'TileIndex'>;
|
'TessCoord' | 'TileOrigin' | 'TileIndex' | 'Color'>;
|
||||||
private stencilProgram: Program<'FramebufferSize' | 'TileSize', 'Position' | 'TileIndex'>;
|
private stencilProgram: Program<'FramebufferSize' | 'TileSize', 'Position' | 'TileIndex'>;
|
||||||
private quadVertexBuffer: WebGLBuffer;
|
private quadVertexBuffer: WebGLBuffer;
|
||||||
private stencilVertexPositionsBuffer: WebGLBuffer;
|
private stencilVertexPositionsBuffer: WebGLBuffer;
|
||||||
|
@ -105,7 +129,7 @@ class App {
|
||||||
'StencilTexture',
|
'StencilTexture',
|
||||||
'StencilTextureSize'
|
'StencilTextureSize'
|
||||||
],
|
],
|
||||||
['TessCoord', 'TileOrigin', 'TileIndex']);
|
['TessCoord', 'TileOrigin', 'TileIndex', 'Color']);
|
||||||
this.coverProgram = coverProgram;
|
this.coverProgram = coverProgram;
|
||||||
|
|
||||||
const stencilProgram = new Program(gl,
|
const stencilProgram = new Program(gl,
|
||||||
|
@ -154,13 +178,16 @@ class App {
|
||||||
0,
|
0,
|
||||||
0);
|
0);
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer);
|
||||||
gl.vertexAttribPointer(coverProgram.attributes.TileOrigin, 2, gl.SHORT, false, 6, 0);
|
gl.vertexAttribPointer(coverProgram.attributes.TileOrigin, 2, gl.SHORT, false, 10, 0);
|
||||||
gl.vertexAttribDivisor(coverProgram.attributes.TileOrigin, 1);
|
gl.vertexAttribDivisor(coverProgram.attributes.TileOrigin, 1);
|
||||||
gl.vertexAttribIPointer(coverProgram.attributes.TileIndex, 1, gl.UNSIGNED_SHORT, 6, 4);
|
gl.vertexAttribIPointer(coverProgram.attributes.TileIndex, 1, gl.UNSIGNED_SHORT, 10, 4);
|
||||||
gl.vertexAttribDivisor(coverProgram.attributes.TileIndex, 1);
|
gl.vertexAttribDivisor(coverProgram.attributes.TileIndex, 1);
|
||||||
|
gl.vertexAttribPointer(coverProgram.attributes.Color, 4, gl.UNSIGNED_BYTE, true, 10, 6);
|
||||||
|
gl.vertexAttribDivisor(coverProgram.attributes.Color, 1);
|
||||||
gl.enableVertexAttribArray(coverProgram.attributes.TessCoord);
|
gl.enableVertexAttribArray(coverProgram.attributes.TessCoord);
|
||||||
gl.enableVertexAttribArray(coverProgram.attributes.TileOrigin);
|
gl.enableVertexAttribArray(coverProgram.attributes.TileOrigin);
|
||||||
gl.enableVertexAttribArray(coverProgram.attributes.TileIndex);
|
gl.enableVertexAttribArray(coverProgram.attributes.TileIndex);
|
||||||
|
gl.enableVertexAttribArray(coverProgram.attributes.Color);
|
||||||
|
|
||||||
// TODO(pcwalton)
|
// TODO(pcwalton)
|
||||||
}
|
}
|
||||||
|
@ -168,14 +195,14 @@ class App {
|
||||||
run(): void {
|
run(): void {
|
||||||
const gl = this.gl, canvas = this.canvas;
|
const gl = this.gl, canvas = this.canvas;
|
||||||
|
|
||||||
const tiles = this.createTiles();
|
const scene = new Scene(this.svg);
|
||||||
console.log(tiles.length, "tiles");
|
console.log(scene.tiles.length, "tiles");
|
||||||
|
|
||||||
// Construct stencil VBOs.
|
// Construct stencil VBOs.
|
||||||
let primitives = 0;
|
let primitives = 0;
|
||||||
const stencilVertexPositions: number[] = [], stencilVertexTileIndices: number[] = [];
|
const stencilVertexPositions: number[] = [], stencilVertexTileIndices: number[] = [];
|
||||||
for (let tileIndex = 0; tileIndex < tiles.length; tileIndex++) {
|
for (let tileIndex = 0; tileIndex < scene.tiles.length; tileIndex++) {
|
||||||
const tile = tiles[tileIndex];
|
const tile = scene.tiles[tileIndex];
|
||||||
let lastPoint = {x: 0.0, y: 0.0};
|
let lastPoint = {x: 0.0, y: 0.0};
|
||||||
tile.path.iterate(segment => {
|
tile.path.iterate(segment => {
|
||||||
if (segment[0] === 'Z')
|
if (segment[0] === 'Z')
|
||||||
|
@ -223,11 +250,15 @@ class App {
|
||||||
gl.drawArrays(gl.LINES, 0, primitives * 2);
|
gl.drawArrays(gl.LINES, 0, primitives * 2);
|
||||||
|
|
||||||
// Populate the cover VBO.
|
// Populate the cover VBO.
|
||||||
const coverVertexBufferData = new Int16Array(tiles.length * 3);
|
const coverVertexBufferData = new Int16Array(scene.tiles.length * 5);
|
||||||
for (let tileIndex = 0; tileIndex < tiles.length; tileIndex++) {
|
for (let tileIndex = 0; tileIndex < scene.tiles.length; tileIndex++) {
|
||||||
coverVertexBufferData[tileIndex * 3 + 0] = Math.floor(tiles[tileIndex].origin.x);
|
const tile = scene.tiles[tileIndex];
|
||||||
coverVertexBufferData[tileIndex * 3 + 1] = Math.floor(tiles[tileIndex].origin.y);
|
const color = scene.pathColors[tile.pathIndex];
|
||||||
coverVertexBufferData[tileIndex * 3 + 2] = tileIndex;
|
coverVertexBufferData[tileIndex * 5 + 0] = Math.floor(tile.origin.x);
|
||||||
|
coverVertexBufferData[tileIndex * 5 + 1] = Math.floor(tile.origin.y);
|
||||||
|
coverVertexBufferData[tileIndex * 5 + 2] = tileIndex;
|
||||||
|
coverVertexBufferData[tileIndex * 5 + 3] = color.r | (color.g << 8);
|
||||||
|
coverVertexBufferData[tileIndex * 5 + 4] = color.b | (color.a << 8);
|
||||||
}
|
}
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, coverVertexBufferData, gl.DYNAMIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, coverVertexBufferData, gl.DYNAMIC_DRAW);
|
||||||
|
@ -255,47 +286,45 @@ class App {
|
||||||
gl.blendEquation(gl.FUNC_ADD);
|
gl.blendEquation(gl.FUNC_ADD);
|
||||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
gl.enable(gl.BLEND);
|
gl.enable(gl.BLEND);
|
||||||
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, tiles.length);
|
gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, scene.tiles.length);
|
||||||
gl.disable(gl.BLEND);
|
gl.disable(gl.BLEND);
|
||||||
|
|
||||||
/*
|
|
||||||
for (const tile of tiles) {
|
|
||||||
const newSVG = staticCast(document.createElementNS(SVG_NS, 'svg'), SVGElement);
|
|
||||||
newSVG.setAttribute('class', "tile");
|
|
||||||
newSVG.style.left = (GLOBAL_OFFSET.x + tile.origin.x) + "px";
|
|
||||||
newSVG.style.top = (GLOBAL_OFFSET.y + tile.origin.y) + "px";
|
|
||||||
newSVG.style.width = TILE_SIZE + "px";
|
|
||||||
newSVG.style.height = TILE_SIZE + "px";
|
|
||||||
|
|
||||||
const newPath = document.createElementNS(SVG_NS, 'path');
|
|
||||||
newPath.setAttribute('d',
|
|
||||||
tile.path
|
|
||||||
.translate(-tile.origin.x, -tile.origin.y)
|
|
||||||
.toString());
|
|
||||||
|
|
||||||
let color = "#";
|
|
||||||
for (let i = 0; i < 6; i++)
|
|
||||||
color += Math.floor(Math.random() * 16).toString(16);
|
|
||||||
newPath.setAttribute('fill', color);
|
|
||||||
|
|
||||||
newSVG.appendChild(newPath);
|
|
||||||
document.body.appendChild(newSVG);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private createTiles(): Tile[] {
|
class Scene {
|
||||||
const svgElement = unwrapNull(this.svg.documentElement).cloneNode(true);
|
tiles: Tile[];
|
||||||
|
pathColors: Color[];
|
||||||
|
|
||||||
|
constructor(svg: XMLDocument) {
|
||||||
|
const svgElement = unwrapNull(svg.documentElement).cloneNode(true);
|
||||||
document.body.appendChild(svgElement);
|
document.body.appendChild(svgElement);
|
||||||
|
|
||||||
const pathElements = Array.from(document.getElementsByTagName('path'));
|
const pathElements = Array.from(document.getElementsByTagName('path'));
|
||||||
const tiles: Tile[] = [];
|
const tiles: Tile[] = [], pathColors = [];
|
||||||
|
|
||||||
for (let pathElementIndex = 0;
|
for (let pathElementIndex = 0;
|
||||||
pathElementIndex < pathElements.length;
|
pathElementIndex < pathElements.length;
|
||||||
pathElementIndex++) {
|
pathElementIndex++) {
|
||||||
const pathElement = pathElements[pathElementIndex];
|
const pathElement = pathElements[pathElementIndex];
|
||||||
|
|
||||||
|
const style = window.getComputedStyle(pathElement);
|
||||||
|
let paint: string;
|
||||||
|
if (style.fill != null && style.fill !== 'none') {
|
||||||
|
paint = style.fill;
|
||||||
|
} else if (style.stroke != null && style.stroke !== 'none') {
|
||||||
|
paint = style.stroke;
|
||||||
|
} else {
|
||||||
|
pathColors.push({r: 0, g: 0, b: 0, a: 0});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const color = parseColor(paint).rgba;
|
||||||
|
pathColors.push({
|
||||||
|
r: color[0],
|
||||||
|
g: color[1],
|
||||||
|
b: color[2],
|
||||||
|
a: Math.round(color[3] * 255.),
|
||||||
|
});
|
||||||
|
|
||||||
let path =
|
let path =
|
||||||
SVGPath(unwrapNull(pathElement.getAttribute('d'))).translate(GLOBAL_OFFSET.x,
|
SVGPath(unwrapNull(pathElement.getAttribute('d'))).translate(GLOBAL_OFFSET.x,
|
||||||
GLOBAL_OFFSET.y);
|
GLOBAL_OFFSET.y);
|
||||||
|
@ -308,6 +337,7 @@ class App {
|
||||||
boundingRect.size.width,
|
boundingRect.size.width,
|
||||||
boundingRect.size.height);*/
|
boundingRect.size.height);*/
|
||||||
|
|
||||||
|
|
||||||
let y = boundingRect.origin.y - boundingRect.origin.y % TILE_SIZE;
|
let y = boundingRect.origin.y - boundingRect.origin.y % TILE_SIZE;
|
||||||
while (true) {
|
while (true) {
|
||||||
let x = boundingRect.origin.x - boundingRect.origin.x % TILE_SIZE;
|
let x = boundingRect.origin.x - boundingRect.origin.x % TILE_SIZE;
|
||||||
|
@ -334,7 +364,26 @@ class App {
|
||||||
|
|
||||||
document.body.removeChild(svgElement);
|
document.body.removeChild(svgElement);
|
||||||
|
|
||||||
return tiles;
|
this.tiles = tiles;
|
||||||
|
this.pathColors = pathColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boundingRectOfPath(path: SVGPath): Rect {
|
||||||
|
let minX: number | null = null, minY: number | null = null;
|
||||||
|
let maxX: number | null = null, maxY: number | null = null;
|
||||||
|
path.iterate(segment => {
|
||||||
|
for (let i = 1; i < segment.length; i += 2) {
|
||||||
|
const x = parseFloat(segment[i]), y = parseFloat(segment[i + 1]);
|
||||||
|
minX = minX == null ? x : Math.min(minX, x);
|
||||||
|
minY = minY == null ? y : Math.min(minY, y);
|
||||||
|
maxX = maxX == null ? x : Math.max(maxX, x);
|
||||||
|
maxY = maxY == null ? y : Math.max(maxY, y);
|
||||||
|
//console.log("x", x, "y", y, "maxX", maxX, "maxY", maxY, "segment", segment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (minX == null || minY == null || maxX == null || maxY == null)
|
||||||
|
return {origin: {x: 0, y: 0}, size: {width: 0, height: 0}};
|
||||||
|
return {origin: {x: minX, y: minY}, size: {width: maxX - minX, height: maxY - minY}};
|
||||||
}
|
}
|
||||||
|
|
||||||
private clipPathToRect(path: SVGPath, tileBounds: Rect): SVGPath {
|
private clipPathToRect(path: SVGPath, tileBounds: Rect): SVGPath {
|
||||||
|
@ -435,35 +484,10 @@ class App {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const intersection = this.cross(this.cross(start, end), edgeVector);
|
const intersection = cross(cross(start, end), edgeVector);
|
||||||
return {x: intersection.x / intersection.z, y: intersection.y / intersection.z};
|
return {x: intersection.x / intersection.z, y: intersection.y / intersection.z};
|
||||||
}
|
}
|
||||||
|
|
||||||
private boundingRectOfPath(path: SVGPath): Rect {
|
|
||||||
let minX: number | null = null, minY: number | null = null;
|
|
||||||
let maxX: number | null = null, maxY: number | null = null;
|
|
||||||
path.iterate(segment => {
|
|
||||||
for (let i = 1; i < segment.length; i += 2) {
|
|
||||||
const x = parseFloat(segment[i]), y = parseFloat(segment[i + 1]);
|
|
||||||
minX = minX == null ? x : Math.min(minX, x);
|
|
||||||
minY = minY == null ? y : Math.min(minY, y);
|
|
||||||
maxX = maxX == null ? x : Math.max(maxX, x);
|
|
||||||
maxY = maxY == null ? y : Math.max(maxY, y);
|
|
||||||
//console.log("x", x, "y", y, "maxX", maxX, "maxY", maxY, "segment", segment);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (minX == null || minY == null || maxX == null || maxY == null)
|
|
||||||
return {origin: {x: 0, y: 0}, size: {width: 0, height: 0}};
|
|
||||||
return {origin: {x: minX, y: minY}, size: {width: maxX - minX, height: maxY - minY}};
|
|
||||||
}
|
|
||||||
|
|
||||||
private cross(a: Vector3D, b: Vector3D): Vector3D {
|
|
||||||
return {
|
|
||||||
x: a.y*b.z - a.z*b.y,
|
|
||||||
y: a.z*b.x - a.x*b.z,
|
|
||||||
z: a.x*b.y - a.y*b.x,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tile {
|
class Tile {
|
||||||
|
@ -542,6 +566,14 @@ function canonicalizePath(path: SVGPath): SVGPath {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cross(a: Vector3D, b: Vector3D): Vector3D {
|
||||||
|
return {
|
||||||
|
x: a.y*b.z - a.z*b.y,
|
||||||
|
y: a.z*b.x - a.x*b.z,
|
||||||
|
z: a.x*b.y - a.y*b.x,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function main(): void {
|
function main(): void {
|
||||||
window.fetch(SVG).then(svg => {
|
window.fetch(SVG).then(svg => {
|
||||||
svg.text().then(svgText => {
|
svg.text().then(svgText => {
|
||||||
|
|
Loading…
Reference in New Issue