bookcover-3d-web 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -0
- package/README_mockup.webp +0 -0
- package/README_sample.webp +0 -0
- package/dist/geometry.d.ts +12 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +390 -0
- package/dist/geometry.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +404 -0
- package/dist/index.js.map +1 -0
- package/dist/math.d.ts +15 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +92 -0
- package/dist/math.js.map +1 -0
- package/dist/photo.d.ts +6 -0
- package/dist/photo.d.ts.map +1 -0
- package/dist/photo.js +72 -0
- package/dist/photo.js.map +1 -0
- package/dist/svg.d.ts +12 -0
- package/dist/svg.d.ts.map +1 -0
- package/dist/svg.js +40 -0
- package/dist/svg.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/webgl.d.ts +15 -0
- package/dist/webgl.d.ts.map +1 -0
- package/dist/webgl.js +119 -0
- package/dist/webgl.js.map +1 -0
- package/package.json +18 -0
- package/src/backgrounds/coffee_table.jpg +0 -0
- package/src/backgrounds/table_side.jpg +0 -0
- package/src/backgrounds/table_with_book.jpg +0 -0
- package/src/backgrounds/table_with_laptop.jpg +0 -0
- package/src/backgrounds/wood.jpg +0 -0
- package/src/backgrounds_originals/coffee_table.jpg +0 -0
- package/src/backgrounds_originals/table_side.jpg +0 -0
- package/src/backgrounds_originals/table_with_book.jpg +0 -0
- package/src/backgrounds_originals/table_with_laptop.jpg +0 -0
- package/src/backgrounds_originals/wood.jpg +0 -0
- package/src/geometry.ts +513 -0
- package/src/index.ts +507 -0
- package/src/math.ts +84 -0
- package/src/photo.ts +77 -0
- package/src/svg.ts +46 -0
- package/src/types.ts +49 -0
- package/src/webgl.ts +131 -0
- package/tsconfig.json +19 -0
- package/tsconfig.tsbuildinfo +1 -0
package/dist/svg.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Parse dimensions from the root SVG element's width/height attributes.
|
|
2
|
+
* Values are in pt (Typst's SVG renderer uses pt as its coordinate system).
|
|
3
|
+
* Accepts both "Xpt" (node backend) and bare "X" (web backend) forms. */
|
|
4
|
+
export declare function parse_svg_size(svg: string): {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
};
|
|
8
|
+
/** Render an SVG string to an ImageBitmap via an HTMLImageElement.
|
|
9
|
+
* createImageBitmap(svgBlob) is unsupported in many browsers; the img→canvas
|
|
10
|
+
* route works universally. Must be called on the main thread. */
|
|
11
|
+
export declare function svg_to_bitmap(svg: string, w_pt: number, h_pt: number): Promise<ImageBitmap>;
|
|
12
|
+
//# sourceMappingURL=svg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../src/svg.ts"],"names":[],"mappings":"AAGA;;0EAE0E;AAC1E,wBAAgB,cAAc,CAAC,GAAG,EAAC,MAAM,GAAE;IAAC,KAAK,EAAC,MAAM,CAAC;IAAC,MAAM,EAAC,MAAM,CAAA;CAAC,CAMvE;AAED;;kEAEkE;AAClE,wBAAsB,aAAa,CAAC,GAAG,EAAC,MAAM,EAAE,IAAI,EAAC,MAAM,EAAE,IAAI,EAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAC,CA4B7F"}
|
package/dist/svg.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SVG parsing and rasterisation utilities
|
|
2
|
+
/** Parse dimensions from the root SVG element's width/height attributes.
|
|
3
|
+
* Values are in pt (Typst's SVG renderer uses pt as its coordinate system).
|
|
4
|
+
* Accepts both "Xpt" (node backend) and bare "X" (web backend) forms. */
|
|
5
|
+
export function parse_svg_size(svg) {
|
|
6
|
+
const w = svg.match(/width="([\d.]+)(?:pt)?"/);
|
|
7
|
+
const h = svg.match(/height="([\d.]+)(?:pt)?"/);
|
|
8
|
+
if (!w || !h)
|
|
9
|
+
throw new Error('[cover-3d] Could not parse SVG dimensions');
|
|
10
|
+
return { width: parseFloat(w[1]), height: parseFloat(h[1]) };
|
|
11
|
+
}
|
|
12
|
+
/** Render an SVG string to an ImageBitmap via an HTMLImageElement.
|
|
13
|
+
* createImageBitmap(svgBlob) is unsupported in many browsers; the img→canvas
|
|
14
|
+
* route works universally. Must be called on the main thread. */
|
|
15
|
+
export async function svg_to_bitmap(svg, w_pt, h_pt) {
|
|
16
|
+
const w = Math.max(1, Math.round(w_pt));
|
|
17
|
+
const h = Math.max(1, Math.round(h_pt));
|
|
18
|
+
// Strip out foreignObject elements (not rendered by image conversion)
|
|
19
|
+
let cleaned_svg = svg.replace(/<foreignObject[^>]*>[\s\S]*?<\/foreignObject>/g, '');
|
|
20
|
+
// Set explicit pixel dimensions so very narrow SVGs (e.g. thin spines) render at a usable size
|
|
21
|
+
cleaned_svg = cleaned_svg
|
|
22
|
+
.replace(/(<svg[^>]*?)width="[^"]*"/, `$1width="${w}"`)
|
|
23
|
+
.replace(/(<svg[^>]*?)height="[^"]*"/, `$1height="${h}"`);
|
|
24
|
+
const blob = new Blob([cleaned_svg], { type: 'image/svg+xml' });
|
|
25
|
+
const url = URL.createObjectURL(blob);
|
|
26
|
+
try {
|
|
27
|
+
// Load SVG as an image element — handles CSS vars, units, etc.
|
|
28
|
+
const img = new Image();
|
|
29
|
+
await new Promise((resolve, reject) => {
|
|
30
|
+
img.onload = () => resolve();
|
|
31
|
+
img.onerror = () => reject(new Error('[cover-3d] SVG failed to load as image'));
|
|
32
|
+
img.src = url;
|
|
33
|
+
});
|
|
34
|
+
return createImageBitmap(img);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
URL.revokeObjectURL(url);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=svg.js.map
|
package/dist/svg.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svg.js","sourceRoot":"","sources":["../src/svg.ts"],"names":[],"mappings":"AACA,0CAA0C;AAE1C;;0EAE0E;AAC1E,MAAM,UAAU,cAAc,CAAC,GAAU;IACrC,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAA;IAC9C,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAA;IAC/C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAA;IAChE,OAAO,EAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAC,CAAA;AAC9D,CAAC;AAED;;kEAEkE;AAClE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAU,EAAE,IAAW,EAAE,IAAW;IACpE,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IACvC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IAEvC,sEAAsE;IACtE,IAAI,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,gDAAgD,EAAE,EAAE,CAAC,CAAA;IAEnF,+FAA+F;IAC/F,WAAW,GAAG,WAAW;SACpB,OAAO,CAAC,2BAA2B,EAAE,YAAY,CAAC,GAAG,CAAC;SACtD,OAAO,CAAC,4BAA4B,EAAE,aAAa,CAAC,GAAG,CAAC,CAAA;IAE7D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,WAAW,CAAC,EAAE,EAAC,IAAI,EAAE,eAAe,EAAC,CAAC,CAAA;IAC7D,MAAM,GAAG,GAAI,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IACtC,IAAI,CAAC;QACD,+DAA+D;QAC/D,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,CAAA;QACvB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,GAAG,CAAC,MAAM,GAAI,GAAG,EAAE,CAAC,OAAO,EAAE,CAAA;YAC7B,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC,CAAA;YAC/E,GAAG,CAAC,GAAG,GAAG,GAAG,CAAA;QACjB,CAAC,CAAC,CAAA;QAEF,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAA;IACjC,CAAC;YACO,CAAC;QACL,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;IAC5B,CAAC;AACL,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** SVG strings for each face of the book cover */
|
|
2
|
+
export interface BookFaces {
|
|
3
|
+
front: string;
|
|
4
|
+
back: string;
|
|
5
|
+
spine?: string;
|
|
6
|
+
}
|
|
7
|
+
export type CoverType = 'paperback' | 'paperback_coil' | 'paperback_wire' | 'paperback_stitch' | 'hardcover' | 'hardcover_jacket';
|
|
8
|
+
export interface GenerateOptions {
|
|
9
|
+
cover_type?: CoverType;
|
|
10
|
+
azimuth?: number;
|
|
11
|
+
elevation?: number;
|
|
12
|
+
roll?: number;
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Options for compositing the book render onto a background photo */
|
|
17
|
+
export interface PhotoCompositeOptions {
|
|
18
|
+
azimuth?: number;
|
|
19
|
+
elevation?: number;
|
|
20
|
+
zoom?: number;
|
|
21
|
+
roll?: number;
|
|
22
|
+
book_scale?: number;
|
|
23
|
+
offset_x?: number;
|
|
24
|
+
offset_y?: number;
|
|
25
|
+
light_az?: number;
|
|
26
|
+
light_el?: number;
|
|
27
|
+
ambient?: number;
|
|
28
|
+
}
|
|
29
|
+
/** A named background photo — extends PhotoCompositeOptions so it can be passed directly */
|
|
30
|
+
export interface Background extends PhotoCompositeOptions {
|
|
31
|
+
id: string;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,kDAAkD;AAClD,MAAM,WAAW,SAAS;IACtB,KAAK,EAAC,MAAM,CAAA;IACZ,IAAI,EAAC,MAAM,CAAA;IACX,KAAK,CAAC,EAAC,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,kBAAkB,GAAG,WAAW,GAAG,kBAAkB,CAAA;AAEjI,MAAM,WAAW,eAAe;IAC5B,UAAU,CAAC,EAAC,SAAS,CAAA;IAErB,OAAO,CAAC,EAAC,MAAM,CAAA;IAEf,SAAS,CAAC,EAAC,MAAM,CAAA;IAEjB,IAAI,CAAC,EAAC,MAAM,CAAA;IACZ,KAAK,CAAC,EAAC,MAAM,CAAA;IACb,MAAM,CAAC,EAAC,MAAM,CAAA;CACjB;AAED,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IAElC,OAAO,CAAC,EAAC,MAAM,CAAA;IACf,SAAS,CAAC,EAAC,MAAM,CAAA;IACjB,IAAI,CAAC,EAAC,MAAM,CAAA;IAEZ,IAAI,CAAC,EAAC,MAAM,CAAA;IAEZ,UAAU,CAAC,EAAC,MAAM,CAAA;IAElB,QAAQ,CAAC,EAAC,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAC,MAAM,CAAA;IAGhB,QAAQ,CAAC,EAAC,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAC,MAAM,CAAA;IAEhB,OAAO,CAAC,EAAC,MAAM,CAAA;CAClB;AAED,4FAA4F;AAC5F,MAAM,WAAW,UAAW,SAAQ,qBAAqB;IACrD,EAAE,EAAC,MAAM,CAAA;CACZ"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,gDAAgD"}
|
package/dist/webgl.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const DEFAULT_LIGHT_AZ = 15;
|
|
2
|
+
export declare const DEFAULT_LIGHT_EL = 25;
|
|
3
|
+
export declare const DEFAULT_AMBIENT = 0.7;
|
|
4
|
+
export declare const VERT_SRC = "\nattribute vec3 a_pos;\nattribute vec2 a_uv;\nattribute vec3 a_normal;\nuniform mat4 u_mvp;\nuniform mat4 u_mv;\nuniform mat3 u_norm_mat;\nuniform vec3 u_light_dir;\nuniform float u_ambient;\nuniform float u_exposure;\nvarying vec2 v_uv;\nvarying float v_light;\nvarying float v_view_z;\nvoid main() {\n gl_Position = u_mvp * vec4(a_pos, 1.0);\n v_uv = a_uv;\n float diffuse = max(dot(normalize(u_norm_mat * a_normal), u_light_dir), 0.0);\n v_light = u_exposure * (u_ambient + (1.0 - u_ambient) * diffuse);\n // View-space Z: used in fragment shader to create depth-based gradient\n v_view_z = (u_mv * vec4(a_pos, 1.0)).z;\n}\n";
|
|
5
|
+
export declare const FRAG_SRC = "\nprecision mediump float;\nvarying vec2 v_uv;\nvarying float v_light;\nvarying float v_view_z;\nuniform sampler2D u_tex;\nuniform bool u_use_tex;\nuniform vec3 u_color;\nuniform float u_cam_z;\nvoid main() {\n vec3 base = u_use_tex ? texture2D(u_tex, v_uv).rgb : u_color;\n // View-angle gradient: a smooth ramp from bright (near side) to dark (far side)\n // across the whole face. rel_z > 0 = closer to camera, rel_z < 0 = farther away.\n // Negative shadow (near side) slightly brightens; positive shadow (far side) darkens.\n // Faces directly facing the camera have near-zero rel_z variance \u2192 no visible gradient.\n float rel_z = v_view_z - u_cam_z;\n float shadow = clamp(-rel_z * 0.6, -0.08, 0.15);\n gl_FragColor = vec4(base * v_light * (1.0 - shadow), 1.0);\n}\n";
|
|
6
|
+
/** Compile a single shader stage from GLSL source */
|
|
7
|
+
export declare function compile_shader(gl: WebGLRenderingContext, type: number, src: string): WebGLShader;
|
|
8
|
+
/** Link a vertex + fragment shader into a program */
|
|
9
|
+
export declare function build_program(gl: WebGLRenderingContext, vert: string, frag: string): WebGLProgram;
|
|
10
|
+
/** Create a procedural page-edge texture scaled to the book's spine width.
|
|
11
|
+
* spine_d is the normalised depth (spine_width / cover_height). */
|
|
12
|
+
export declare function create_page_edge_texture(gl: WebGLRenderingContext, spine_d: number): WebGLTexture;
|
|
13
|
+
/** Upload an ImageBitmap to a WebGL texture (no mipmaps for NPOT support) */
|
|
14
|
+
export declare function upload_texture(gl: WebGLRenderingContext, bitmap: ImageBitmap): WebGLTexture;
|
|
15
|
+
//# sourceMappingURL=webgl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webgl.d.ts","sourceRoot":"","sources":["../src/webgl.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,eAAe,MAAU,CAAA;AAEtC,eAAO,MAAM,QAAQ,2oBAqBpB,CAAA;AAED,eAAO,MAAM,QAAQ,syBAmBpB,CAAA;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,EAAE,EAAC,qBAAqB,EAAE,IAAI,EAAC,MAAM,EAAE,GAAG,EAAC,MAAM,GAAE,WAAW,CAO5F;AAED,qDAAqD;AACrD,wBAAgB,aAAa,CAAC,EAAE,EAAC,qBAAqB,EAAE,IAAI,EAAC,MAAM,EAAE,IAAI,EAAC,MAAM,GAAE,YAAY,CAQ7F;AAED;oEACoE;AACpE,wBAAgB,wBAAwB,CAAC,EAAE,EAAC,qBAAqB,EAAE,OAAO,EAAC,MAAM,GAAE,YAAY,CAqC9F;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,EAAE,EAAC,qBAAqB,EAAE,MAAM,EAAC,WAAW,GAAE,YAAY,CAaxF"}
|
package/dist/webgl.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// WebGL shader sources and low-level helpers
|
|
2
|
+
const AMBIENT = 0.7;
|
|
3
|
+
// Default light direction as azimuth/elevation degrees (see light_dir_vec3 in index.ts)
|
|
4
|
+
export const DEFAULT_LIGHT_AZ = 15;
|
|
5
|
+
export const DEFAULT_LIGHT_EL = 25;
|
|
6
|
+
export const DEFAULT_AMBIENT = AMBIENT;
|
|
7
|
+
export const VERT_SRC = `
|
|
8
|
+
attribute vec3 a_pos;
|
|
9
|
+
attribute vec2 a_uv;
|
|
10
|
+
attribute vec3 a_normal;
|
|
11
|
+
uniform mat4 u_mvp;
|
|
12
|
+
uniform mat4 u_mv;
|
|
13
|
+
uniform mat3 u_norm_mat;
|
|
14
|
+
uniform vec3 u_light_dir;
|
|
15
|
+
uniform float u_ambient;
|
|
16
|
+
uniform float u_exposure;
|
|
17
|
+
varying vec2 v_uv;
|
|
18
|
+
varying float v_light;
|
|
19
|
+
varying float v_view_z;
|
|
20
|
+
void main() {
|
|
21
|
+
gl_Position = u_mvp * vec4(a_pos, 1.0);
|
|
22
|
+
v_uv = a_uv;
|
|
23
|
+
float diffuse = max(dot(normalize(u_norm_mat * a_normal), u_light_dir), 0.0);
|
|
24
|
+
v_light = u_exposure * (u_ambient + (1.0 - u_ambient) * diffuse);
|
|
25
|
+
// View-space Z: used in fragment shader to create depth-based gradient
|
|
26
|
+
v_view_z = (u_mv * vec4(a_pos, 1.0)).z;
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
export const FRAG_SRC = `
|
|
30
|
+
precision mediump float;
|
|
31
|
+
varying vec2 v_uv;
|
|
32
|
+
varying float v_light;
|
|
33
|
+
varying float v_view_z;
|
|
34
|
+
uniform sampler2D u_tex;
|
|
35
|
+
uniform bool u_use_tex;
|
|
36
|
+
uniform vec3 u_color;
|
|
37
|
+
uniform float u_cam_z;
|
|
38
|
+
void main() {
|
|
39
|
+
vec3 base = u_use_tex ? texture2D(u_tex, v_uv).rgb : u_color;
|
|
40
|
+
// View-angle gradient: a smooth ramp from bright (near side) to dark (far side)
|
|
41
|
+
// across the whole face. rel_z > 0 = closer to camera, rel_z < 0 = farther away.
|
|
42
|
+
// Negative shadow (near side) slightly brightens; positive shadow (far side) darkens.
|
|
43
|
+
// Faces directly facing the camera have near-zero rel_z variance → no visible gradient.
|
|
44
|
+
float rel_z = v_view_z - u_cam_z;
|
|
45
|
+
float shadow = clamp(-rel_z * 0.6, -0.08, 0.15);
|
|
46
|
+
gl_FragColor = vec4(base * v_light * (1.0 - shadow), 1.0);
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
/** Compile a single shader stage from GLSL source */
|
|
50
|
+
export function compile_shader(gl, type, src) {
|
|
51
|
+
const shader = gl.createShader(type);
|
|
52
|
+
gl.shaderSource(shader, src);
|
|
53
|
+
gl.compileShader(shader);
|
|
54
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
|
|
55
|
+
throw new Error(`[cover-3d] Shader compile error: ${gl.getShaderInfoLog(shader)}`);
|
|
56
|
+
return shader;
|
|
57
|
+
}
|
|
58
|
+
/** Link a vertex + fragment shader into a program */
|
|
59
|
+
export function build_program(gl, vert, frag) {
|
|
60
|
+
const prog = gl.createProgram();
|
|
61
|
+
gl.attachShader(prog, compile_shader(gl, gl.VERTEX_SHADER, vert));
|
|
62
|
+
gl.attachShader(prog, compile_shader(gl, gl.FRAGMENT_SHADER, frag));
|
|
63
|
+
gl.linkProgram(prog);
|
|
64
|
+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS))
|
|
65
|
+
throw new Error(`[cover-3d] Program link error: ${gl.getProgramInfoLog(prog)}`);
|
|
66
|
+
return prog;
|
|
67
|
+
}
|
|
68
|
+
/** Create a procedural page-edge texture scaled to the book's spine width.
|
|
69
|
+
* spine_d is the normalised depth (spine_width / cover_height). */
|
|
70
|
+
export function create_page_edge_texture(gl, spine_d) {
|
|
71
|
+
// Derive stripe count proportional to spine thickness (600 lines per unit is visually calibrated)
|
|
72
|
+
const n_stripes = Math.max(6, Math.round(spine_d * 600));
|
|
73
|
+
// Pack into exactly 256px (power-of-2 required for mipmap generation).
|
|
74
|
+
// Each stripe: body + 2px separator; body scales with available space.
|
|
75
|
+
const px_per_stripe = Math.max(3, Math.floor(256 / n_stripes));
|
|
76
|
+
const actual_n = Math.floor(256 / px_per_stripe);
|
|
77
|
+
const body_h = px_per_stripe - 2;
|
|
78
|
+
// Pre-fill with cream so the unpainted tail of the texture blends seamlessly
|
|
79
|
+
const canvas = new OffscreenCanvas(4, 256);
|
|
80
|
+
const ctx = canvas.getContext('2d');
|
|
81
|
+
ctx.fillStyle = '#EDE5D4';
|
|
82
|
+
ctx.fillRect(0, 0, 4, 256);
|
|
83
|
+
// Draw separator lines; page body is already the pre-filled cream
|
|
84
|
+
for (let i = 0; i < actual_n; i++) {
|
|
85
|
+
ctx.fillStyle = '#847C6E';
|
|
86
|
+
ctx.fillRect(0, i * px_per_stripe + body_h, 4, 2);
|
|
87
|
+
}
|
|
88
|
+
// Mipmaps + LINEAR_MIPMAP_LINEAR: lines degrade smoothly at oblique angles rather
|
|
89
|
+
// than aliasing/flickering as the camera rotates. Requires power-of-2 dimensions (4×256).
|
|
90
|
+
const bitmap = canvas.transferToImageBitmap();
|
|
91
|
+
const tex = gl.createTexture();
|
|
92
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
93
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
94
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
|
|
95
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
96
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
97
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
98
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
99
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
100
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
101
|
+
bitmap.close();
|
|
102
|
+
return tex;
|
|
103
|
+
}
|
|
104
|
+
/** Upload an ImageBitmap to a WebGL texture (no mipmaps for NPOT support) */
|
|
105
|
+
export function upload_texture(gl, bitmap) {
|
|
106
|
+
const tex = gl.createTexture();
|
|
107
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
108
|
+
// Flip Y so UV (0,0) maps to the top-left of the source image
|
|
109
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
110
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
|
|
111
|
+
// Skip mipmap generation for NPOT textures (SVG dimensions may not be powers of 2)
|
|
112
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
113
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
114
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
115
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
116
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
117
|
+
return tex;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=webgl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webgl.js","sourceRoot":"","sources":["../src/webgl.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
{
|
|
3
|
+
"name": "bookcover-3d-web",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"license": "MIT-0",
|
|
6
|
+
"repository": {"type": "git", "url": "https://github.com/gracious-tech/bookcover.git", "directory": "3d"},
|
|
7
|
+
"homepage": "https://github.com/gracious-tech/bookcover/tree/main/3d#readme",
|
|
8
|
+
"bugs": "https://github.com/gracious-tech/bookcover/issues",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"dev": "tsc --watch"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
}
|
|
18
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|