Skip to content

Commit b020ccb

Browse files
commit before I break
poly_mesh kinda working. translations are broken. doesn't work on all of the sample strucutres yet
1 parent 777bf81 commit b020ccb

8 files changed

Lines changed: 464 additions & 302 deletions

File tree

pipeline/build.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ function findProcessingFunction(filename) {
9494
* @returns {{ code: string }}
9595
*/
9696
function processHTML(code, filename) {
97-
// code = code.replaceAll(/<style>([^]+?)<\/style>/g, (_, css) => `<style>${processCSS(css, filename, true).code}</style>`);
9897
code = code.replaceAll(/<script type="(importmap|application\/ld\+json)">([^]+?)<\/script>/g, (_, scriptType, json) => `<script type="${scriptType}">${processJSON(json).code}</script>`);
9998
code = minifyHTML(code, {
10099
removeComments: true,

src/BlockGeoMaker.js

Lines changed: 243 additions & 133 deletions
Large diffs are not rendered by default.

src/HoloPrint.js

Lines changed: 78 additions & 130 deletions
Large diffs are not rendered by default.

src/PolyMeshMaker.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { JSONSet } from "./utils.js";
2+
3+
export default class PolyMeshMaker {
4+
/** @type {JSONSet<Vec3>} */
5+
#positions = new JSONSet();
6+
/** @type {JSONSet<Vec3>} */
7+
#normals = new JSONSet();
8+
/** @type {JSONSet<Vec2>} */
9+
#uvs = new JSONSet();
10+
/** @type {Array<PolyMeshFace>} */
11+
#polys = [];
12+
13+
/**
14+
* Adds templates faces to the poly mesh, with an optional position offset.
15+
* @param {Array<PolyMeshTemplateFaceWithUvs>} faces
16+
* @param {Vec3} [positionOffset]
17+
*/
18+
add(faces, positionOffset = [0, 0, 0]) {
19+
faces.forEach(face => {
20+
let newPoly = [];
21+
let normalI = this.#normals.addI(face["normal"]);
22+
for(let i = 0; i < 4; i++) {
23+
let vertex = face["vertices"][i];
24+
let pos = [vertex["pos"][0] + positionOffset[0], vertex["pos"][1] + positionOffset[1], vertex["pos"][2] + positionOffset[2]];
25+
let posI = this.#positions.addI(pos);
26+
let uvI = this.#uvs.addI(vertex["uv"]);
27+
newPoly.push([posI, normalI, uvI]);
28+
}
29+
this.#polys.push(newPoly);
30+
});
31+
}
32+
/** @returns {PolyMesh} */
33+
export() {
34+
return {
35+
"normalized_uvs": true,
36+
"positions": Array.from(this.#positions),
37+
"normals": Array.from(this.#normals),
38+
"uvs": Array.from(this.#uvs),
39+
"polys": this.#polys
40+
};
41+
}
42+
clear() {
43+
this.#positions.clear();
44+
this.#normals.clear();
45+
this.#uvs.clear();
46+
this.#polys = [];
47+
}
48+
}
49+
50+
/** @import { Vec3, Vec2, PolyMeshTemplateFaceWithUvs, PolyMeshFace, PolyMesh } from "./HoloPrint.js" */

src/PreviewRenderer.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default class PreviewRenderer extends AsyncFactory {
3636
cont;
3737
packName;
3838
textureAtlas;
39-
boneTemplatePalette;
39+
polyMeshTemplatePalette;
4040
structureSize;
4141
options = {
4242
showSkybox: true,
@@ -88,17 +88,17 @@ export default class PreviewRenderer extends AsyncFactory {
8888
* @param {import("./TextureAtlas.js").default} textureAtlas
8989
* @param {I32Vec3} structureSize
9090
* @param {Array<Block>} blockPalette
91-
* @param {Array<BoneTemplate>} boneTemplatePalette
91+
* @param {Array<import("./HoloPrint.js").PolyMeshTemplateFaceWithUvs>} polyMeshTemplatePalette
9292
* @param {[Int32Array, Int32Array]} blockIndices
9393
* @param {Partial<typeof this.options>} [options]
9494
*/
95-
constructor(cont, packName, textureAtlas, structureSize, blockPalette, boneTemplatePalette, blockIndices, options = {}) {
95+
constructor(cont, packName, textureAtlas, structureSize, blockPalette, polyMeshTemplatePalette, blockIndices, options = {}) {
9696
super();
9797
this.cont = cont;
9898
this.packName = packName;
9999
this.textureAtlas = textureAtlas;
100100
this.structureSize = structureSize;
101-
this.boneTemplatePalette = boneTemplatePalette;
101+
this.polyMeshTemplatePalette = polyMeshTemplatePalette;
102102
this.options = { ...this.options, ...options };
103103

104104
this.#can = document.createElement("canvas");
@@ -153,7 +153,7 @@ export default class PreviewRenderer extends AsyncFactory {
153153
let blockI = (x * this.structureSize[1] + y) * this.structureSize[2] + z;
154154
for(let layerI = 0; layerI < 2; layerI++) {
155155
let paletteI = blockIndices[layerI][blockI];
156-
if(!(paletteI in this.boneTemplatePalette)) {
156+
if(!(paletteI in this.polyMeshTemplatePalette)) {
157157
continue;
158158
}
159159
this.#blockPositions[paletteI] ??= [];
@@ -253,8 +253,8 @@ export default class PreviewRenderer extends AsyncFactory {
253253
// animator.play("spawn");
254254

255255
for(let i in this.#blockPositions) {
256-
let boneTemplate = this.boneTemplatePalette[i];
257-
let geo = this.#boneTemplateToGeo(boneTemplate);
256+
let boneTemplate = this.polyMeshTemplatePalette[i];
257+
let geo = this.#polyMeshTemplateToGeo(boneTemplate);
258258
let model = new Model(geo, imageUrl);
259259
let group = model.getGroup();
260260
group.rotation.set(0, pi, 0);
@@ -501,7 +501,7 @@ export default class PreviewRenderer extends AsyncFactory {
501501
* @param {BoneTemplate} boneTemplate
502502
* @returns {import("@bridge-editor/model-viewer").IGeoSchema}
503503
*/
504-
#boneTemplateToGeo(boneTemplate) {
504+
#polyMeshTemplateToGeo(boneTemplate) {
505505
return {
506506
"description": {
507507
"texture_width": this.textureAtlas.atlasWidth,

src/TextureAtlas.js

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ export default class TextureAtlas extends AsyncFactory {
1919
config;
2020
resourcePackStack;
2121

22-
/** When makeAtlas() is called, this will contain UV coordinates and sizes for texture references passed as input, as well as cropping information. */
23-
textures;
22+
/**
23+
* When makeAtlas() is called, this will contain UV coordinates and sizes for texture references passed as input, as well as cropping information.
24+
* @type {Array<{ uv: Vec2, uv_size: Vec2, crop?: Rectangle }>}
25+
*/
26+
uvs;
2427

2528
/**
2629
* Contains the actual texture atlas images: [textureName, imageBlob]
@@ -123,8 +126,7 @@ export default class TextureAtlas extends AsyncFactory {
123126
"tint_like_png": tintLikePng,
124127
"opacity": opacity,
125128
"uv": textureRef["uv"],
126-
"uv_size": textureRef["uv_size"],
127-
"croppable": textureRef["croppable"]
129+
"uv_size": textureRef["uv_size"]
128130
};
129131
allTextureFragments.add(textureFragment);
130132
textureImageIndices.push(allTextureFragments.indexOf(textureFragment));
@@ -146,7 +148,7 @@ export default class TextureAtlas extends AsyncFactory {
146148
let imageUvs = await this.#stitchTextureAtlas(imageFragments);
147149
console.log("Image UVs:", imageUvs);
148150

149-
this.textures = textureImageIndices.map(i => imageUvs[i]);
151+
this.uvs = textureImageIndices.map(i => imageUvs[i]);
150152
}
151153

152154
/**
@@ -306,7 +308,7 @@ export default class TextureAtlas extends AsyncFactory {
306308
return { imageData, imageIsTga, imageNotFound };
307309
}));
308310
let imageDataByTexturePath = new Map(allTexturePaths.map((texturePath, i) => [texturePath, allImageData[i]]));
309-
return await Promise.all(Array.from(textureFragments).map(async ({ texturePath, tint, tint_like_png: tintLikePng, opacity, uv: sourceUv, uv_size: uvSize, croppable }) => {
311+
return await Promise.all(Array.from(textureFragments).map(async ({ texturePath, tint, tint_like_png: tintLikePng, opacity, uv: sourceUv, uv_size: uvSize }) => {
310312
let { imageData, imageIsTga, imageNotFound } = imageDataByTexturePath.get(texturePath);
311313
if(imageNotFound) {
312314
sourceUv = [0, 0];
@@ -336,9 +338,8 @@ export default class TextureAtlas extends AsyncFactory {
336338
let sourceY = sourceUv[1] * imageH;
337339
let w = uvSize[0] * imageW;
338340
let h = uvSize[1] * imageH;
339-
let crop;
340-
// croppable = false;
341-
if(croppable) {
341+
let crop = null;
342+
if(true) {
342343
let old = { sourceX, sourceY, w, h };
343344
let extremePixels = this.#findMostExtremePixels(image, sourceX, sourceY, w, h);
344345
sourceX = extremePixels["minX"];
@@ -353,6 +354,8 @@ export default class TextureAtlas extends AsyncFactory {
353354
};
354355
if(crop["x"] != 0 || crop["y"] != 0 || crop["w"] != 1 || crop["h"] != 1) {
355356
console.debug(`Cropped part of image ${texturePath} to`, crop);
357+
} else {
358+
crop = null;
356359
}
357360
}
358361
let imageFragment = {
@@ -362,7 +365,7 @@ export default class TextureAtlas extends AsyncFactory {
362365
"w": w,
363366
"h": h
364367
};
365-
if(croppable) {
368+
if(crop) {
366369
imageFragment["crop"] = crop;
367370
}
368371
return imageFragment;
@@ -396,7 +399,7 @@ export default class TextureAtlas extends AsyncFactory {
396399
imageFragment["h"] = ceil(imageFragment["h"]);
397400
}
398401
});
399-
let imageFragments2 = imageFragments.map(imageFragment => ({ ... imageFragment }));
402+
let imageFragments2 = imageFragments.map(imageFragment => ({ ...imageFragment }));
400403
let packing1 = potpack(imageFragments);
401404
let packing2 = potpack(imageFragments2.sort((a, b) => b.h - a.h || b.w - a.w)); // width presort
402405
let packing = packing1.fill > packing2.fill? packing1 : packing2; // In my testing on 100 structures, 10 times no width presort was better, 17 times width presort was better, and the rest they were equal. On average, width presorting improved space efficiency by 0.1385%. Since potpack takes just a couple ms, it's best to look at both and take the better one.
@@ -432,10 +435,14 @@ export default class TextureAtlas extends AsyncFactory {
432435
return imageUv;
433436
});
434437

438+
ctx = can.getContext("2d");
439+
ctx.fillStyle = "#00f3";
440+
// ctx.fillRect(0, 0, can.width, can.height);
435441
if(this.config.TEXTURE_OUTLINE_WIDTH != 0) {
436442
can = TextureAtlas.addTextureOutlines(can, imageFragments, this.config);
437443
}
438444

445+
439446
if(this.config.MULTIPLE_OPACITIES) {
440447
let opacities = range(4, 10).map(x => x / 10); // lowest is 40% opacity. note that we do division after to avoid floating-point errors.
441448
this.imageBlobs = await Promise.all(opacities.map(async opacity => [`hologram_opacity_${opacity}`, await this.#setCanvasOpacity(can, opacity).convertToBlob()]));
@@ -461,6 +468,7 @@ export default class TextureAtlas extends AsyncFactory {
461468
let can = new OffscreenCanvas(imageW, imageH);
462469
let ctx = can.getContext("2d");
463470
ctx.drawImage(image, startX, startY, imageW, imageH, 0, 0, imageW, imageH);
471+
// console.log(imageW, imageH, can.width, can.height)
464472
let imageData = ctx.getImageData(0, 0, imageW, imageH).data;
465473

466474
let minX = imageW, minY = imageH, maxX = 0, maxY = 0;
@@ -599,18 +607,4 @@ export default class TextureAtlas extends AsyncFactory {
599607
}
600608
}
601609

602-
/**
603-
* @typedef {import("./HoloPrint.js").TextureReference} TextureReference
604-
*/
605-
/**
606-
* @typedef {import("./HoloPrint.js").TextureFragment} TextureFragment
607-
*/
608-
/**
609-
* @typedef {import("./HoloPrint.js").ImageFragment} ImageFragment
610-
*/
611-
/**
612-
* @typedef {import("./HoloPrint.js").HoloPrintConfig} HoloPrintConfig
613-
*/
614-
/**
615-
* @typedef {import("./HoloPrint.js").Vec3} Vec3
616-
*/
610+
/** @import { TextureReference, TextureFragment, ImageFragment, HoloPrintConfig, Vec3, Vec2, Rectangle } from "./HoloPrint.js" */

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import SimpleLogger from "./components/SimpleLogger.js";
1212
import LilGui from "./components/LilGui.js";
1313

1414
const IN_PRODUCTION = false;
15-
const ACTUAL_CONSOLE_LOG = false;
15+
const ACTUAL_CONSOLE_LOG = true;
1616

1717
const supabaseProjectUrl = "https://gnzyfffwvulwxbczqpgl.supabase.co";
1818
const supabaseApiKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImduenlmZmZ3dnVsd3hiY3pxcGdsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MjMwMjE3NzgsImV4cCI6MjAzODU5Nzc3OH0.AWMhFcP3PiMD3dMC_SeIVuPx128KVpgfkZ5qBStDuVw";

src/utils.js

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,30 @@ export function cosDeg(deg) {
168168
export function tanDeg(deg) {
169169
return tan(deg * pi / 180);
170170
}
171+
export function rotate([x, y], angle) {
172+
let c = cos(angle);
173+
let s = sin(angle);
174+
return [x * c - y * s, x * s + y * c];
175+
}
176+
export function rotateDeg(p, deg) {
177+
return rotate(p, deg * pi / 180);
178+
}
179+
/**
180+
* @param {Vec3} a
181+
* @param {Vec3} b
182+
* @returns {Vec3}
183+
*/
184+
export function addVec3(a, b) {
185+
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
186+
}
187+
/**
188+
* @param {Vec3} vec
189+
* @param {number} factor
190+
* @returns {Vec3}
191+
*/
192+
export function mulVec3(vec, factor) {
193+
return [vec[0] * factor, vec[1] * factor, vec[2] * factor];
194+
}
171195

172196
export function arrayMin(arr) {
173197
let min = Infinity;
@@ -713,20 +737,37 @@ function stringifyJsonBigIntSafe(value) {
713737
function parseJsonBigIntSafe(value) { // this function is unused but I'm keeping it here because it works well with the function above
714738
return JSON.parse(value, (_, x, context) => context && Number.isInteger(x) && !Number.isSafeInteger(x)? BigInt(context.source) : x);
715739
}
740+
/**
741+
* @template T
742+
* @extends {Set<T>}
743+
*/
716744
export class JSONSet extends Set {
745+
/** @type {Map<string, number>} */
746+
#indices = new Map();
717747
#actualValues = new Map();
718748
constructor(values) {
719749
super();
720750
values?.forEach(value => this.add(value));
721751
}
722-
indexOf(value) { // not part of sets normally! but they keep their order anyway so...
723-
let stringifiedValues = Array.from(super[Symbol.iterator]());
724-
return stringifiedValues.indexOf(this.#stringify(value));
752+
/** Not part of regular sets! Constant time indexing. */
753+
indexOf(value) {
754+
return this.#indices.get(this.#stringify(value));
755+
}
756+
addI(value) {
757+
let stringifiedValue = this.#stringify(value);
758+
super.add(stringifiedValue);
759+
if(!this.#actualValues.has(stringifiedValue)) {
760+
this.#actualValues.set(stringifiedValue, structuredClone(value));
761+
this.#indices.set(stringifiedValue, this.size - 1);
762+
return this.size - 1;
763+
}
764+
return this.#indices.get(stringifiedValue);
725765
}
726766
add(value) {
727767
let stringifiedValue = this.#stringify(value);
728768
if(!this.#actualValues.has(stringifiedValue)) {
729769
this.#actualValues.set(stringifiedValue, structuredClone(value));
770+
this.#indices.set(stringifiedValue, this.size);
730771
}
731772
return super.add(stringifiedValue);
732773
}
@@ -736,6 +777,11 @@ export class JSONSet extends Set {
736777
has(value) {
737778
return super.has(this.#stringify(value))
738779
}
780+
clear() {
781+
this.#indices.clear();
782+
this.#actualValues.clear();
783+
return super.clear();
784+
}
739785
[Symbol.iterator]() {
740786
return this.#actualValues.values();
741787
}
@@ -781,6 +827,21 @@ export class JSONMap extends Map { // very barebones
781827
return stringifyJsonBigIntSafe(value);
782828
}
783829
}
830+
export class JSONMultiSet {
831+
#freqs = new JSONMap();
832+
#indices = new Map();
833+
add(key) {
834+
let stringifiedKey = this.#stringify(key);
835+
this.#freqs.set(stringifiedKey, (this.#freqs.get(stringifiedKey) ?? 0) + 1);
836+
if(this.#indices.has(stringifiedKey)) {
837+
return this.#indices.get(stringifiedKey);
838+
}
839+
this.#indices.set(stringifiedKey, this.#indices.size);
840+
}
841+
#stringify(value) {
842+
return stringifyJsonBigIntSafe(value);
843+
}
844+
}
784845
export class CachingFetcher extends AsyncFactory {
785846
static URL_PREFIX = "https://cache/";
786847
static BAD_STATUS_CODES = [429];

0 commit comments

Comments
 (0)