@ifc-lite/renderer 1.15.2 → 1.16.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/dist/bvh.d.ts +1 -1
- package/dist/bvh.d.ts.map +1 -1
- package/dist/constants.d.ts +8 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +8 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +153 -30
- package/dist/index.js.map +1 -1
- package/dist/pipeline.d.ts +11 -1
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +49 -12
- package/dist/pipeline.js.map +1 -1
- package/dist/scene-geometry.d.ts +31 -0
- package/dist/scene-geometry.d.ts.map +1 -0
- package/dist/scene-geometry.js +137 -0
- package/dist/scene-geometry.js.map +1 -0
- package/dist/scene-raycaster.d.ts +65 -0
- package/dist/scene-raycaster.d.ts.map +1 -0
- package/dist/scene-raycaster.js +234 -0
- package/dist/scene-raycaster.js.map +1 -0
- package/dist/scene.d.ts +9 -39
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +15 -339
- package/dist/scene.js.map +1 -1
- package/dist/section-2d-overlay.d.ts +22 -7
- package/dist/section-2d-overlay.d.ts.map +1 -1
- package/dist/section-2d-overlay.js +219 -42
- package/dist/section-2d-overlay.js.map +1 -1
- package/dist/section-cap-style.d.ts +37 -0
- package/dist/section-cap-style.d.ts.map +1 -0
- package/dist/section-cap-style.js +38 -0
- package/dist/section-cap-style.js.map +1 -0
- package/dist/section-plane.d.ts +0 -3
- package/dist/section-plane.d.ts.map +1 -1
- package/dist/section-plane.js +33 -6
- package/dist/section-plane.js.map +1 -1
- package/dist/shaders/main.wgsl.d.ts +1 -1
- package/dist/shaders/main.wgsl.d.ts.map +1 -1
- package/dist/shaders/main.wgsl.js +7 -4
- package/dist/shaders/main.wgsl.js.map +1 -1
- package/dist/snap-detector.d.ts +1 -17
- package/dist/snap-detector.d.ts.map +1 -1
- package/dist/snap-detector.js +16 -213
- package/dist/snap-detector.js.map +1 -1
- package/dist/snap-geometry-cache.d.ts +24 -0
- package/dist/snap-geometry-cache.d.ts.map +1 -0
- package/dist/snap-geometry-cache.js +155 -0
- package/dist/snap-geometry-cache.js.map +1 -0
- package/dist/snap-geometry-utils.d.ts +27 -0
- package/dist/snap-geometry-utils.d.ts.map +1 -0
- package/dist/snap-geometry-utils.js +56 -0
- package/dist/snap-geometry-utils.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/section-plane.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Section plane renderer - renders a visible plane at the section cut location
|
|
6
|
+
*/
|
|
7
|
+
import { PIPELINE_CONSTANTS } from './constants.js';
|
|
4
8
|
export class SectionPlaneRenderer {
|
|
5
9
|
device;
|
|
6
10
|
bindGroupLayout = null; // Shared layout for both pipelines
|
|
@@ -56,8 +60,15 @@ export class SectionPlaneRenderer {
|
|
|
56
60
|
return output;
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
// Two outputs so the pipeline matches the main pass's two colour
|
|
64
|
+
// attachments. objectId is masked off at the pipeline level.
|
|
65
|
+
struct FragOut {
|
|
66
|
+
@location(0) color: vec4<f32>,
|
|
67
|
+
@location(1) objectId: vec4<f32>,
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
@fragment
|
|
60
|
-
fn fs_main(input: VertexOutput) ->
|
|
71
|
+
fn fs_main(input: VertexOutput) -> FragOut {
|
|
61
72
|
// Create fine grid pattern
|
|
62
73
|
let gridSize = 0.01; // Fine grid cells (100 divisions)
|
|
63
74
|
let lineWidth = 0.001; // Very thin lines
|
|
@@ -104,7 +115,10 @@ export class SectionPlaneRenderer {
|
|
|
104
115
|
// Clamp alpha
|
|
105
116
|
color.a = min(color.a, 0.5);
|
|
106
117
|
|
|
107
|
-
|
|
118
|
+
var out: FragOut;
|
|
119
|
+
out.color = color;
|
|
120
|
+
out.objectId = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
|
121
|
+
return out;
|
|
108
122
|
}
|
|
109
123
|
`,
|
|
110
124
|
});
|
|
@@ -127,7 +141,18 @@ export class SectionPlaneRenderer {
|
|
|
127
141
|
fragment: {
|
|
128
142
|
module: shaderModule,
|
|
129
143
|
entryPoint: 'fs_main',
|
|
130
|
-
|
|
144
|
+
// The main render pass has two colour attachments (main colour +
|
|
145
|
+
// the picker's objectId texture). WebGPU requires every pipeline
|
|
146
|
+
// used inside a pass to declare exactly the same target count and
|
|
147
|
+
// formats. The preview plane only paints into the main colour
|
|
148
|
+
// target — the objectId target is declared with writeMask 0 so
|
|
149
|
+
// cap-less picking IDs underneath are preserved. Without this,
|
|
150
|
+
// `setPipeline` raises "Incompatible color attachments at
|
|
151
|
+
// indices []: RenderPass uses formats [Bgra8Unorm, Rgba8Unorm]
|
|
152
|
+
// but RenderPipeline uses formats [Bgra8Unorm]" and the whole
|
|
153
|
+
// frame is dropped.
|
|
154
|
+
targets: [
|
|
155
|
+
{
|
|
131
156
|
format: this.format,
|
|
132
157
|
blend: {
|
|
133
158
|
color: {
|
|
@@ -141,7 +166,9 @@ export class SectionPlaneRenderer {
|
|
|
141
166
|
operation: 'add',
|
|
142
167
|
},
|
|
143
168
|
},
|
|
144
|
-
}
|
|
169
|
+
},
|
|
170
|
+
{ format: 'rgba8unorm', writeMask: 0 },
|
|
171
|
+
],
|
|
145
172
|
},
|
|
146
173
|
primitive: {
|
|
147
174
|
topology: 'triangle-list',
|
|
@@ -155,7 +182,7 @@ export class SectionPlaneRenderer {
|
|
|
155
182
|
this.previewPipeline = this.device.createRenderPipeline({
|
|
156
183
|
...pipelineBase,
|
|
157
184
|
depthStencil: {
|
|
158
|
-
format:
|
|
185
|
+
format: PIPELINE_CONSTANTS.DEPTH_FORMAT,
|
|
159
186
|
depthWriteEnabled: false,
|
|
160
187
|
depthCompare: 'greater', // Only draw where plane is behind geometry (empty space)
|
|
161
188
|
},
|
|
@@ -164,7 +191,7 @@ export class SectionPlaneRenderer {
|
|
|
164
191
|
this.cutPipeline = this.device.createRenderPipeline({
|
|
165
192
|
...pipelineBase,
|
|
166
193
|
depthStencil: {
|
|
167
|
-
format:
|
|
194
|
+
format: PIPELINE_CONSTANTS.DEPTH_FORMAT,
|
|
168
195
|
depthWriteEnabled: false,
|
|
169
196
|
depthCompare: 'always', // Always draw on top
|
|
170
197
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-plane.js","sourceRoot":"","sources":["../src/section-plane.ts"],"names":[],"mappings":"AAAA;;+DAE+D;
|
|
1
|
+
{"version":3,"file":"section-plane.js","sourceRoot":"","sources":["../src/section-plane.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* Features: PBR lighting, section plane clipping, selection highlight,
|
|
4
4
|
* glass fresnel, ACES tone mapping, screen-space edge enhancement.
|
|
5
5
|
*/
|
|
6
|
-
export declare const mainShaderSource = "\n struct Uniforms {\n viewProj: mat4x4<f32>,\n model: mat4x4<f32>,\n baseColor: vec4<f32>,\n metallicRoughness: vec2<f32>, // x = metallic, y = roughness\n _padding1: vec2<f32>,\n sectionPlane: vec4<f32>, // xyz = plane normal, w = plane distance\n flags: vec4<u32>, // x = isSelected, y = sectionEnabled, z = edgeEnabled, w = edgeIntensityMilli\n }\n @binding(0) @group(0) var<uniform> uniforms: Uniforms;\n\n struct VertexInput {\n @location(0) position: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) entityId: u32,\n }\n\n struct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPos: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) @interpolate(flat) entityId: u32,\n @location(3) viewPos: vec3<f32>, // For edge detection\n }\n\n @vertex\n fn vs_main(input: VertexInput, @builtin(instance_index) instanceIndex: u32) -> VertexOutput {\n var output: VertexOutput;\n let worldPos = uniforms.model * vec4<f32>(input.position, 1.0);\n output.position = uniforms.viewProj * worldPos;\n // Anti z-fighting: deterministic depth nudge per entity.\n // Knuth multiplicative hash spreads sequential IDs across 0-255\n // so coplanar faces from different entities always get distinct depths.\n // At 1e-6 per step the max world-space offset is <3mm at 10m \u2014 invisible.\n let zHash = (input.entityId * 2654435761u) & 255u;\n output.position.z *= 1.0 + f32(zHash) * 1e-6;\n output.worldPos = worldPos.xyz;\n output.normal = normalize((uniforms.model * vec4<f32>(input.normal, 0.0)).xyz);\n output.entityId = input.entityId;\n // Store view-space position for edge detection\n output.viewPos = (uniforms.viewProj * worldPos).xyz;\n return output;\n }\n\n // PBR helper functions\n fn fresnelSchlick(cosTheta: f32, F0: vec3<f32>) -> vec3<f32> {\n return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);\n }\n\n fn distributionGGX(NdotH: f32, roughness: f32) -> f32 {\n let a = roughness * roughness;\n let a2 = a * a;\n let NdotH2 = NdotH * NdotH;\n let num = a2;\n let denomBase = (NdotH2 * (a2 - 1.0) + 1.0);\n let denom = 3.14159265 * denomBase * denomBase;\n return num / max(denom, 0.0000001);\n }\n\n fn geometrySchlickGGX(NdotV: f32, roughness: f32) -> f32 {\n let r = (roughness + 1.0);\n let k = (r * r) / 8.0;\n let num = NdotV;\n let denom = NdotV * (1.0 - k) + k;\n return num / max(denom, 0.0000001);\n }\n\n fn geometrySmith(NdotV: f32, NdotL: f32, roughness: f32) -> f32 {\n let ggx2 = geometrySchlickGGX(NdotV, roughness);\n let ggx1 = geometrySchlickGGX(NdotL, roughness);\n return ggx1 * ggx2;\n }\n\n fn encodeId24(id: u32) -> vec4<f32> {\n let r = f32((id >> 16u) & 255u) / 255.0;\n let g = f32((id >> 8u) & 255u) / 255.0;\n let b = f32(id & 255u) / 255.0;\n return vec4<f32>(r, g, b, 1.0);\n }\n\n struct FragmentOutput {\n @location(0) color: vec4<f32>,\n @location(1) objectIdEncoded: vec4<f32>,\n }\n\n @fragment\n fn fs_main(input: VertexOutput) -> FragmentOutput {\n // Section plane clipping - discard fragments ABOVE the plane\n // For Down axis (normal +Y), keeps everything below cut height (look down into building)\n if (uniforms.flags.y == 1u) {\n let planeNormal = uniforms.sectionPlane.xyz;\n let planeDistance = uniforms.sectionPlane.w;\n let distToPlane = dot(input.worldPos, planeNormal) - planeDistance;\n if (distToPlane > 0.0) {\n discard;\n }\n }\n\n // Compute normal \u2014 with fallback for zero normals\n // dpdx/dpdy must be called outside non-uniform control flow (WGSL spec),\n // so we compute the flat normal unconditionally and select below.\n let faceN = cross(dpdx(input.worldPos), dpdy(input.worldPos));\n var N = input.normal;\n let nLen2 = dot(N, N);\n if (nLen2 < 0.0001) {\n // Fallback: use flat normal from screen-space derivatives\n let fLen2 = dot(faceN, faceN);\n N = select(vec3<f32>(0.0, 1.0, 0.0), faceN * inverseSqrt(fLen2), fLen2 > 1e-10);\n } else {\n N = N * inverseSqrt(nLen2);\n }\n\n // Enhanced lighting with multiple sources\n let sunLight = normalize(vec3<f32>(0.5, 1.0, 0.3)); // Main directional light\n let fillLight = normalize(vec3<f32>(-0.5, 0.3, -0.3)); // Fill light\n let rimLight = normalize(vec3<f32>(0.0, 0.2, -1.0)); // Rim light for edge definition\n\n // Hemisphere ambient - reduced for less washed-out look\n let skyColor = vec3<f32>(0.3, 0.35, 0.4); // Darker sky\n let groundColor = vec3<f32>(0.15, 0.1, 0.08); // Darker ground\n let hemisphereFactor = N.y * 0.5 + 0.5;\n let ambient = mix(groundColor, skyColor, hemisphereFactor) * 0.25;\n\n // Two-sided sun light so inner faces (I-beam channels) stay visible\n let NdotL = abs(dot(N, sunLight));\n let wrap = 0.3;\n let diffuseSun = max((NdotL + wrap) / (1.0 + wrap), 0.0) * 0.55;\n\n // Fill light - two-sided\n let NdotFill = abs(dot(N, fillLight));\n let diffuseFill = NdotFill * 0.15;\n\n // Rim light for edge definition\n let NdotRim = max(dot(N, rimLight), 0.0);\n let rim = pow(NdotRim, 4.0) * 0.15;\n\n var baseColor = uniforms.baseColor.rgb;\n\n // Detect if the color is close to white/gray (low saturation)\n let baseGray = dot(baseColor, vec3<f32>(0.299, 0.587, 0.114));\n let baseSaturation = length(baseColor - vec3<f32>(baseGray)) / max(baseGray, 0.001);\n let isWhiteish = 1.0 - smoothstep(0.0, 0.3, baseSaturation);\n\n // Darken whites/grays more to reduce washed-out appearance\n baseColor = mix(baseColor, baseColor * 0.7, isWhiteish * 0.4);\n\n // Combine all lighting\n var color = baseColor * (ambient + diffuseSun + diffuseFill + rim);\n\n // Selection highlight - add glow/fresnel effect\n if (uniforms.flags.x == 1u) {\n let V = normalize(-input.worldPos);\n let NdotV = max(dot(N, V), 0.0);\n let fresnel = pow(1.0 - NdotV, 2.0);\n let highlightColor = vec3<f32>(0.3, 0.6, 1.0);\n color = mix(color, highlightColor, fresnel * 0.5 + 0.2);\n }\n\n // Beautiful fresnel effect for transparent materials (glass)\n // Skip when selected \u2014 the glass shine and desaturation wash out the\n // blue highlight, making it appear white instead of blue.\n // Also force alpha to 1.0 for selected objects so the highlight is\n // fully opaque (the selection pipeline has no alpha blending).\n var finalAlpha = select(uniforms.baseColor.a, 1.0, uniforms.flags.x == 1u);\n if (finalAlpha < 0.99 && uniforms.flags.x != 1u) {\n // Calculate view direction for fresnel\n let V = normalize(-input.worldPos);\n let NdotV = max(dot(N, V), 0.0);\n\n // Enhanced fresnel effect - stronger at edges (grazing angles)\n // Using Schlick's approximation for realistic glass reflection\n let fresnelPower = 1.5; // Higher = softer edge reflections\n let fresnel = pow(1.0 - NdotV, fresnelPower);\n\n // Glass reflection tint (sky/environment reflection at edges)\n let reflectionTint = vec3<f32>(0.92, 0.96, 1.0); // Cool sky reflection\n let reflectionStrength = fresnel * 0.6; // Strong edge reflections\n\n // Mix in reflection tint at edges\n color = mix(color, color * reflectionTint, reflectionStrength);\n\n // Add realistic glass shine - brighter at edges where light reflects\n let glassShine = fresnel * 0.12;\n color += glassShine;\n\n // Slight desaturation at edges (glass reflects environment, not just color)\n let edgeDesaturation = fresnel * 0.25;\n let gray = dot(color, vec3<f32>(0.299, 0.587, 0.114));\n color = mix(color, vec3<f32>(gray), edgeDesaturation);\n\n // Make glass more transparent (reduce opacity by 30%)\n finalAlpha = finalAlpha * 0.7;\n }\n\n // Exposure adjustment - darken overall\n color *= 0.85;\n\n // Contrast enhancement\n color = (color - 0.5) * 1.15 + 0.5;\n color = max(color, vec3<f32>(0.0));\n\n // Saturation boost - stronger for colored surfaces, less for whites\n let gray = dot(color, vec3<f32>(0.299, 0.587, 0.114));\n let satBoost = mix(1.4, 1.1, isWhiteish); // More saturation for colored surfaces\n color = mix(vec3<f32>(gray), color, satBoost);\n\n // ACES filmic tone mapping\n let a = 2.51;\n let b = 0.03;\n let c = 2.43;\n let d = 0.59;\n let e = 0.14;\n color = clamp((color * (a * color + b)) / (color * (c * color + d) + e), vec3<f32>(0.0), vec3<f32>(1.0));\n\n // Subtle edge enhancement using screen-space derivatives\n let depthGradient = length(vec2<f32>(\n dpdx(input.viewPos.z),\n dpdy(input.viewPos.z)\n ));\n let normalGradient = length(vec2<f32>(\n length(dpdx(input.normal)),\n length(dpdy(input.normal))\n ));\n\n if (uniforms.flags.z == 1u) {\n // Threshold filters subtle normal discontinuities at internal\n // triangle edges between coplanar entities in the same batch.\n let edgeFactor = smoothstep(0.02, 0.12, depthGradient * 10.0 + normalGradient * 5.0);\n let edgeIntensity = f32(uniforms.flags.w) / 1000.0;\n let edgeDarkenStrength = clamp(0.25 * edgeIntensity, 0.0, 0.85);\n let edgeDarken = mix(1.0, 1.0 - edgeDarkenStrength, edgeFactor);\n color *= edgeDarken;\n }\n\n // Gamma correction\n color = pow(color, vec3<f32>(1.0 / 2.2));\n\n var out: FragmentOutput;\n out.color = vec4<f32>(color, finalAlpha);\n out.objectIdEncoded = encodeId24(input.entityId);\n return out;\n }\n ";
|
|
6
|
+
export declare const mainShaderSource = "\n struct Uniforms {\n viewProj: mat4x4<f32>,\n model: mat4x4<f32>,\n baseColor: vec4<f32>,\n metallicRoughness: vec2<f32>, // x = metallic, y = roughness\n _padding1: vec2<f32>,\n sectionPlane: vec4<f32>, // xyz = plane normal, w = plane distance\n flags: vec4<u32>, // x = isSelected, y = sectionEnabled, z = edgeEnabled, w = edgeIntensityMilli\n }\n @binding(0) @group(0) var<uniform> uniforms: Uniforms;\n\n struct VertexInput {\n @location(0) position: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) entityId: u32,\n }\n\n struct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPos: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) @interpolate(flat) entityId: u32,\n @location(3) viewPos: vec3<f32>, // For edge detection\n }\n\n @vertex\n fn vs_main(input: VertexInput, @builtin(instance_index) instanceIndex: u32) -> VertexOutput {\n var output: VertexOutput;\n let worldPos = uniforms.model * vec4<f32>(input.position, 1.0);\n output.position = uniforms.viewProj * worldPos;\n // Anti z-fighting: deterministic depth nudge per entity.\n // Knuth multiplicative hash spreads sequential IDs across 0-255\n // so coplanar faces from different entities always get distinct depths.\n // At 1e-6 per step the max world-space offset is <3mm at 10m \u2014 invisible.\n let zHash = (input.entityId * 2654435761u) & 255u;\n output.position.z *= 1.0 + f32(zHash) * 1e-6;\n output.worldPos = worldPos.xyz;\n output.normal = normalize((uniforms.model * vec4<f32>(input.normal, 0.0)).xyz);\n output.entityId = input.entityId;\n // Store view-space position for edge detection\n output.viewPos = (uniforms.viewProj * worldPos).xyz;\n return output;\n }\n\n // PBR helper functions\n fn fresnelSchlick(cosTheta: f32, F0: vec3<f32>) -> vec3<f32> {\n return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);\n }\n\n fn distributionGGX(NdotH: f32, roughness: f32) -> f32 {\n let a = roughness * roughness;\n let a2 = a * a;\n let NdotH2 = NdotH * NdotH;\n let num = a2;\n let denomBase = (NdotH2 * (a2 - 1.0) + 1.0);\n let denom = 3.14159265 * denomBase * denomBase;\n return num / max(denom, 0.0000001);\n }\n\n fn geometrySchlickGGX(NdotV: f32, roughness: f32) -> f32 {\n let r = (roughness + 1.0);\n let k = (r * r) / 8.0;\n let num = NdotV;\n let denom = NdotV * (1.0 - k) + k;\n return num / max(denom, 0.0000001);\n }\n\n fn geometrySmith(NdotV: f32, NdotL: f32, roughness: f32) -> f32 {\n let ggx2 = geometrySchlickGGX(NdotV, roughness);\n let ggx1 = geometrySchlickGGX(NdotL, roughness);\n return ggx1 * ggx2;\n }\n\n fn encodeId24(id: u32) -> vec4<f32> {\n let r = f32((id >> 16u) & 255u) / 255.0;\n let g = f32((id >> 8u) & 255u) / 255.0;\n let b = f32(id & 255u) / 255.0;\n return vec4<f32>(r, g, b, 1.0);\n }\n\n struct FragmentOutput {\n @location(0) color: vec4<f32>,\n @location(1) objectIdEncoded: vec4<f32>,\n }\n\n @fragment\n fn fs_main(input: VertexOutput) -> FragmentOutput {\n // Section plane clipping - discard fragments ABOVE the plane.\n // flags.y packs two bits: bit 0 = enabled, bit 1 = flipped.\n let sectionEnabled = (uniforms.flags.y & 1u) == 1u;\n if (sectionEnabled) {\n let planeNormal = uniforms.sectionPlane.xyz;\n let planeDistance = uniforms.sectionPlane.w;\n let flipped = (uniforms.flags.y & 2u) == 2u;\n let side = select(1.0, -1.0, flipped);\n let distToPlane = (dot(input.worldPos, planeNormal) - planeDistance) * side;\n if (distToPlane > 0.0) {\n discard;\n }\n }\n\n // Compute normal \u2014 with fallback for zero normals\n // dpdx/dpdy must be called outside non-uniform control flow (WGSL spec),\n // so we compute the flat normal unconditionally and select below.\n let faceN = cross(dpdx(input.worldPos), dpdy(input.worldPos));\n var N = input.normal;\n let nLen2 = dot(N, N);\n if (nLen2 < 0.0001) {\n // Fallback: use flat normal from screen-space derivatives\n let fLen2 = dot(faceN, faceN);\n N = select(vec3<f32>(0.0, 1.0, 0.0), faceN * inverseSqrt(fLen2), fLen2 > 1e-10);\n } else {\n N = N * inverseSqrt(nLen2);\n }\n\n // Enhanced lighting with multiple sources\n let sunLight = normalize(vec3<f32>(0.5, 1.0, 0.3)); // Main directional light\n let fillLight = normalize(vec3<f32>(-0.5, 0.3, -0.3)); // Fill light\n let rimLight = normalize(vec3<f32>(0.0, 0.2, -1.0)); // Rim light for edge definition\n\n // Hemisphere ambient - reduced for less washed-out look\n let skyColor = vec3<f32>(0.3, 0.35, 0.4); // Darker sky\n let groundColor = vec3<f32>(0.15, 0.1, 0.08); // Darker ground\n let hemisphereFactor = N.y * 0.5 + 0.5;\n let ambient = mix(groundColor, skyColor, hemisphereFactor) * 0.25;\n\n // Two-sided sun light so inner faces (I-beam channels) stay visible\n let NdotL = abs(dot(N, sunLight));\n let wrap = 0.3;\n let diffuseSun = max((NdotL + wrap) / (1.0 + wrap), 0.0) * 0.55;\n\n // Fill light - two-sided\n let NdotFill = abs(dot(N, fillLight));\n let diffuseFill = NdotFill * 0.15;\n\n // Rim light for edge definition\n let NdotRim = max(dot(N, rimLight), 0.0);\n let rim = pow(NdotRim, 4.0) * 0.15;\n\n var baseColor = uniforms.baseColor.rgb;\n\n // Detect if the color is close to white/gray (low saturation)\n let baseGray = dot(baseColor, vec3<f32>(0.299, 0.587, 0.114));\n let baseSaturation = length(baseColor - vec3<f32>(baseGray)) / max(baseGray, 0.001);\n let isWhiteish = 1.0 - smoothstep(0.0, 0.3, baseSaturation);\n\n // Darken whites/grays more to reduce washed-out appearance\n baseColor = mix(baseColor, baseColor * 0.7, isWhiteish * 0.4);\n\n // Combine all lighting\n var color = baseColor * (ambient + diffuseSun + diffuseFill + rim);\n\n // Selection highlight - add glow/fresnel effect\n if (uniforms.flags.x == 1u) {\n let V = normalize(-input.worldPos);\n let NdotV = max(dot(N, V), 0.0);\n let fresnel = pow(1.0 - NdotV, 2.0);\n let highlightColor = vec3<f32>(0.3, 0.6, 1.0);\n color = mix(color, highlightColor, fresnel * 0.5 + 0.2);\n }\n\n // Beautiful fresnel effect for transparent materials (glass)\n // Skip when selected \u2014 the glass shine and desaturation wash out the\n // blue highlight, making it appear white instead of blue.\n // Also force alpha to 1.0 for selected objects so the highlight is\n // fully opaque (the selection pipeline has no alpha blending).\n var finalAlpha = select(uniforms.baseColor.a, 1.0, uniforms.flags.x == 1u);\n if (finalAlpha < 0.99 && uniforms.flags.x != 1u) {\n // Calculate view direction for fresnel\n let V = normalize(-input.worldPos);\n let NdotV = max(dot(N, V), 0.0);\n\n // Enhanced fresnel effect - stronger at edges (grazing angles)\n // Using Schlick's approximation for realistic glass reflection\n let fresnelPower = 1.5; // Higher = softer edge reflections\n let fresnel = pow(1.0 - NdotV, fresnelPower);\n\n // Glass reflection tint (sky/environment reflection at edges)\n let reflectionTint = vec3<f32>(0.92, 0.96, 1.0); // Cool sky reflection\n let reflectionStrength = fresnel * 0.6; // Strong edge reflections\n\n // Mix in reflection tint at edges\n color = mix(color, color * reflectionTint, reflectionStrength);\n\n // Add realistic glass shine - brighter at edges where light reflects\n let glassShine = fresnel * 0.12;\n color += glassShine;\n\n // Slight desaturation at edges (glass reflects environment, not just color)\n let edgeDesaturation = fresnel * 0.25;\n let gray = dot(color, vec3<f32>(0.299, 0.587, 0.114));\n color = mix(color, vec3<f32>(gray), edgeDesaturation);\n\n // Make glass more transparent (reduce opacity by 30%)\n finalAlpha = finalAlpha * 0.7;\n }\n\n // Exposure adjustment - darken overall\n color *= 0.85;\n\n // Contrast enhancement\n color = (color - 0.5) * 1.15 + 0.5;\n color = max(color, vec3<f32>(0.0));\n\n // Saturation boost - stronger for colored surfaces, less for whites\n let gray = dot(color, vec3<f32>(0.299, 0.587, 0.114));\n let satBoost = mix(1.4, 1.1, isWhiteish); // More saturation for colored surfaces\n color = mix(vec3<f32>(gray), color, satBoost);\n\n // ACES filmic tone mapping\n let a = 2.51;\n let b = 0.03;\n let c = 2.43;\n let d = 0.59;\n let e = 0.14;\n color = clamp((color * (a * color + b)) / (color * (c * color + d) + e), vec3<f32>(0.0), vec3<f32>(1.0));\n\n // Subtle edge enhancement using screen-space derivatives\n let depthGradient = length(vec2<f32>(\n dpdx(input.viewPos.z),\n dpdy(input.viewPos.z)\n ));\n let normalGradient = length(vec2<f32>(\n length(dpdx(input.normal)),\n length(dpdy(input.normal))\n ));\n\n if (uniforms.flags.z == 1u) {\n // Threshold filters subtle normal discontinuities at internal\n // triangle edges between coplanar entities in the same batch.\n let edgeFactor = smoothstep(0.02, 0.12, depthGradient * 10.0 + normalGradient * 5.0);\n let edgeIntensity = f32(uniforms.flags.w) / 1000.0;\n let edgeDarkenStrength = clamp(0.25 * edgeIntensity, 0.0, 0.85);\n let edgeDarken = mix(1.0, 1.0 - edgeDarkenStrength, edgeFactor);\n color *= edgeDarken;\n }\n\n // Gamma correction\n color = pow(color, vec3<f32>(1.0 / 2.2));\n\n var out: FragmentOutput;\n out.color = vec4<f32>(color, finalAlpha);\n out.objectIdEncoded = encodeId24(input.entityId);\n return out;\n }\n ";
|
|
7
7
|
//# sourceMappingURL=main.wgsl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.wgsl.d.ts","sourceRoot":"","sources":["../../src/shaders/main.wgsl.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"main.wgsl.d.ts","sourceRoot":"","sources":["../../src/shaders/main.wgsl.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,mzVAsPtB,CAAC"}
|
|
@@ -94,12 +94,15 @@ export const mainShaderSource = `
|
|
|
94
94
|
|
|
95
95
|
@fragment
|
|
96
96
|
fn fs_main(input: VertexOutput) -> FragmentOutput {
|
|
97
|
-
// Section plane clipping - discard fragments ABOVE the plane
|
|
98
|
-
//
|
|
99
|
-
|
|
97
|
+
// Section plane clipping - discard fragments ABOVE the plane.
|
|
98
|
+
// flags.y packs two bits: bit 0 = enabled, bit 1 = flipped.
|
|
99
|
+
let sectionEnabled = (uniforms.flags.y & 1u) == 1u;
|
|
100
|
+
if (sectionEnabled) {
|
|
100
101
|
let planeNormal = uniforms.sectionPlane.xyz;
|
|
101
102
|
let planeDistance = uniforms.sectionPlane.w;
|
|
102
|
-
let
|
|
103
|
+
let flipped = (uniforms.flags.y & 2u) == 2u;
|
|
104
|
+
let side = select(1.0, -1.0, flipped);
|
|
105
|
+
let distToPlane = (dot(input.worldPos, planeNormal) - planeDistance) * side;
|
|
103
106
|
if (distToPlane > 0.0) {
|
|
104
107
|
discard;
|
|
105
108
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.wgsl.js","sourceRoot":"","sources":["../../src/shaders/main.wgsl.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG
|
|
1
|
+
{"version":3,"file":"main.wgsl.js","sourceRoot":"","sources":["../../src/shaders/main.wgsl.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsPzB,CAAC"}
|
package/dist/snap-detector.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MeshData } from '@ifc-lite/geometry';
|
|
2
|
-
import type { Ray, Vec3, Intersection } from './raycaster';
|
|
2
|
+
import type { Ray, Vec3, Intersection } from './raycaster.js';
|
|
3
3
|
export declare enum SnapType {
|
|
4
4
|
VERTEX = "vertex",
|
|
5
5
|
EDGE = "edge",
|
|
@@ -75,14 +75,6 @@ export declare class SnapDetector {
|
|
|
75
75
|
* Detect if position is at a corner (vertex with multiple edges)
|
|
76
76
|
*/
|
|
77
77
|
private detectCorner;
|
|
78
|
-
/**
|
|
79
|
-
* Get closest point on edge segment with parameter t (0-1)
|
|
80
|
-
*/
|
|
81
|
-
private closestPointOnEdgeWithT;
|
|
82
|
-
/**
|
|
83
|
-
* Check if two vectors are approximately equal
|
|
84
|
-
*/
|
|
85
|
-
private vecEquals;
|
|
86
78
|
/**
|
|
87
79
|
* Get or compute geometry cache for a mesh
|
|
88
80
|
*/
|
|
@@ -107,13 +99,5 @@ export declare class SnapDetector {
|
|
|
107
99
|
* Select best snap target based on confidence and priority
|
|
108
100
|
*/
|
|
109
101
|
private getBestSnapTarget;
|
|
110
|
-
/**
|
|
111
|
-
* Convert screen-space radius to world-space radius
|
|
112
|
-
*/
|
|
113
|
-
private screenToWorldRadius;
|
|
114
|
-
/**
|
|
115
|
-
* Vector utilities
|
|
116
|
-
*/
|
|
117
|
-
private distance;
|
|
118
102
|
}
|
|
119
103
|
//# sourceMappingURL=snap-detector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snap-detector.d.ts","sourceRoot":"","sources":["../src/snap-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"snap-detector.d.ts","sourceRoot":"","sources":["../src/snap-detector.ts"],"names":[],"mappings":"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"}
|
package/dist/snap-detector.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { Raycaster } from './raycaster';
|
|
1
|
+
import { Raycaster } from './raycaster.js';
|
|
2
|
+
import { distance, vecEquals, closestPointOnEdgeWithT, screenToWorldRadius } from './snap-geometry-utils.js';
|
|
3
|
+
import { buildGeometryCache } from './snap-geometry-cache.js';
|
|
2
4
|
export var SnapType;
|
|
3
5
|
(function (SnapType) {
|
|
4
6
|
SnapType["VERTEX"] = "vertex";
|
|
@@ -51,8 +53,8 @@ export class SnapDetector {
|
|
|
51
53
|
}
|
|
52
54
|
const targets = [];
|
|
53
55
|
// Calculate world-space snap radius based on screen-space radius and distance
|
|
54
|
-
const distanceToCamera =
|
|
55
|
-
const worldSnapRadius =
|
|
56
|
+
const distanceToCamera = distance(camera.position, intersection.point);
|
|
57
|
+
const worldSnapRadius = screenToWorldRadius(opts.screenSnapRadius, distanceToCamera, camera.fov, screenHeight);
|
|
56
58
|
// Only check the intersected mesh for snap targets (performance optimization)
|
|
57
59
|
// Checking all meshes was causing severe framerate drops with large models
|
|
58
60
|
const intersectedMesh = meshes[intersection.meshIndex];
|
|
@@ -94,8 +96,8 @@ export class SnapDetector {
|
|
|
94
96
|
},
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
|
-
const distanceToCamera =
|
|
98
|
-
const worldSnapRadius =
|
|
99
|
+
const distanceToCamera = distance(camera.position, intersection.point);
|
|
100
|
+
const worldSnapRadius = screenToWorldRadius(opts.screenSnapRadius, distanceToCamera, camera.fov, screenHeight);
|
|
99
101
|
const intersectedMesh = meshes[intersection.meshIndex];
|
|
100
102
|
if (!intersectedMesh) {
|
|
101
103
|
return {
|
|
@@ -165,7 +167,7 @@ export class SnapDetector {
|
|
|
165
167
|
// Find all nearby edges (filtered for visibility)
|
|
166
168
|
const nearbyEdges = [];
|
|
167
169
|
for (const edge of cache.edges) {
|
|
168
|
-
const result =
|
|
170
|
+
const result = closestPointOnEdgeWithT(intersection.point, edge.v0, edge.v1);
|
|
169
171
|
if (result.distance < edgeRadius) {
|
|
170
172
|
// Visibility check: edge should be on front-facing side
|
|
171
173
|
// Compute vector from intersection point to edge closest point
|
|
@@ -272,7 +274,7 @@ export class SnapDetector {
|
|
|
272
274
|
}
|
|
273
275
|
const { v0, v1 } = currentLock.edge;
|
|
274
276
|
// Project point onto the locked edge
|
|
275
|
-
const result =
|
|
277
|
+
const result = closestPointOnEdgeWithT(point, v0, v1);
|
|
276
278
|
// Calculate perpendicular distance (distance from point to edge line)
|
|
277
279
|
const perpDistance = result.distance;
|
|
278
280
|
// Calculate escape threshold based on lock strength
|
|
@@ -298,8 +300,8 @@ export class SnapDetector {
|
|
|
298
300
|
// Check for corner at current position
|
|
299
301
|
const cornerRadius = worldSnapRadius * MAGNETIC_CONFIG.EDGE_ATTRACTION_MULTIPLIER * MAGNETIC_CONFIG.CORNER_ATTRACTION_MULTIPLIER;
|
|
300
302
|
// Find the matching edge in cache to get proper index
|
|
301
|
-
let matchingEdge = cache.edges.find(e => (
|
|
302
|
-
(
|
|
303
|
+
let matchingEdge = cache.edges.find(e => (vecEquals(e.v0, v0) && vecEquals(e.v1, v1)) ||
|
|
304
|
+
(vecEquals(e.v0, v1) && vecEquals(e.v1, v0)));
|
|
303
305
|
const edgeForCorner = matchingEdge || { v0, v1, index: -1 };
|
|
304
306
|
const cornerInfo = this.detectCorner(edgeForCorner, edgeT, cache, cornerRadius, point);
|
|
305
307
|
// Calculate snap position (on the edge)
|
|
@@ -366,7 +368,7 @@ export class SnapDetector {
|
|
|
366
368
|
// Get valence from cache
|
|
367
369
|
const valence = cache.vertexValence.get(vertexKey) || 0;
|
|
368
370
|
// Also check distance to vertex
|
|
369
|
-
const distToVertex =
|
|
371
|
+
const distToVertex = distance(point, vertex);
|
|
370
372
|
const isCloseEnough = distToVertex < radius;
|
|
371
373
|
return {
|
|
372
374
|
isCorner: isCloseEnough && valence >= MAGNETIC_CONFIG.MIN_CORNER_VALENCE,
|
|
@@ -374,39 +376,6 @@ export class SnapDetector {
|
|
|
374
376
|
vertex,
|
|
375
377
|
};
|
|
376
378
|
}
|
|
377
|
-
/**
|
|
378
|
-
* Get closest point on edge segment with parameter t (0-1)
|
|
379
|
-
*/
|
|
380
|
-
closestPointOnEdgeWithT(point, v0, v1) {
|
|
381
|
-
const dx = v1.x - v0.x;
|
|
382
|
-
const dy = v1.y - v0.y;
|
|
383
|
-
const dz = v1.z - v0.z;
|
|
384
|
-
const lengthSq = dx * dx + dy * dy + dz * dz;
|
|
385
|
-
if (lengthSq < 0.0000001) {
|
|
386
|
-
// Degenerate edge
|
|
387
|
-
return { point: v0, distance: this.distance(point, v0), t: 0 };
|
|
388
|
-
}
|
|
389
|
-
// Project point onto line
|
|
390
|
-
const t = Math.max(0, Math.min(1, ((point.x - v0.x) * dx + (point.y - v0.y) * dy + (point.z - v0.z) * dz) / lengthSq));
|
|
391
|
-
const closest = {
|
|
392
|
-
x: v0.x + dx * t,
|
|
393
|
-
y: v0.y + dy * t,
|
|
394
|
-
z: v0.z + dz * t,
|
|
395
|
-
};
|
|
396
|
-
return {
|
|
397
|
-
point: closest,
|
|
398
|
-
distance: this.distance(point, closest),
|
|
399
|
-
t,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Check if two vectors are approximately equal
|
|
404
|
-
*/
|
|
405
|
-
vecEquals(a, b, epsilon = 0.0001) {
|
|
406
|
-
return (Math.abs(a.x - b.x) < epsilon &&
|
|
407
|
-
Math.abs(a.y - b.y) < epsilon &&
|
|
408
|
-
Math.abs(a.z - b.z) < epsilon);
|
|
409
|
-
}
|
|
410
379
|
/**
|
|
411
380
|
* Get or compute geometry cache for a mesh
|
|
412
381
|
*/
|
|
@@ -415,154 +384,7 @@ export class SnapDetector {
|
|
|
415
384
|
if (cached) {
|
|
416
385
|
return cached;
|
|
417
386
|
}
|
|
418
|
-
|
|
419
|
-
const positions = mesh.positions;
|
|
420
|
-
// Validate input
|
|
421
|
-
if (!positions || positions.length === 0) {
|
|
422
|
-
const emptyCache = {
|
|
423
|
-
vertices: [],
|
|
424
|
-
edges: [],
|
|
425
|
-
vertexValence: new Map(),
|
|
426
|
-
vertexEdges: new Map(),
|
|
427
|
-
};
|
|
428
|
-
this.geometryCache.set(mesh.expressId, emptyCache);
|
|
429
|
-
return emptyCache;
|
|
430
|
-
}
|
|
431
|
-
const vertexMap = new Map();
|
|
432
|
-
for (let i = 0; i < positions.length; i += 3) {
|
|
433
|
-
const vertex = {
|
|
434
|
-
x: positions[i],
|
|
435
|
-
y: positions[i + 1],
|
|
436
|
-
z: positions[i + 2],
|
|
437
|
-
};
|
|
438
|
-
// Skip invalid vertices
|
|
439
|
-
if (!isFinite(vertex.x) || !isFinite(vertex.y) || !isFinite(vertex.z)) {
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
// Use reduced precision for deduplication
|
|
443
|
-
const key = `${vertex.x.toFixed(4)}_${vertex.y.toFixed(4)}_${vertex.z.toFixed(4)}`;
|
|
444
|
-
vertexMap.set(key, vertex);
|
|
445
|
-
}
|
|
446
|
-
const vertices = Array.from(vertexMap.values());
|
|
447
|
-
// Compute and cache edges + vertex valence for corner detection
|
|
448
|
-
// Filter out internal triangulation edges (diagonals) - only keep real model edges
|
|
449
|
-
const edges = [];
|
|
450
|
-
const vertexValence = new Map();
|
|
451
|
-
const vertexEdges = new Map();
|
|
452
|
-
const indices = mesh.indices;
|
|
453
|
-
if (indices) {
|
|
454
|
-
// First pass: collect edges and their adjacent triangle normals
|
|
455
|
-
const edgeData = new Map();
|
|
456
|
-
// Helper to compute triangle normal
|
|
457
|
-
const computeTriangleNormal = (i) => {
|
|
458
|
-
const i0 = indices[i] * 3;
|
|
459
|
-
const i1 = indices[i + 1] * 3;
|
|
460
|
-
const i2 = indices[i + 2] * 3;
|
|
461
|
-
const ax = positions[i1] - positions[i0];
|
|
462
|
-
const ay = positions[i1 + 1] - positions[i0 + 1];
|
|
463
|
-
const az = positions[i1 + 2] - positions[i0 + 2];
|
|
464
|
-
const bx = positions[i2] - positions[i0];
|
|
465
|
-
const by = positions[i2 + 1] - positions[i0 + 1];
|
|
466
|
-
const bz = positions[i2 + 2] - positions[i0 + 2];
|
|
467
|
-
// Cross product
|
|
468
|
-
const nx = ay * bz - az * by;
|
|
469
|
-
const ny = az * bx - ax * bz;
|
|
470
|
-
const nz = ax * by - ay * bx;
|
|
471
|
-
// Normalize
|
|
472
|
-
const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
473
|
-
return len > 0 ? { x: nx / len, y: ny / len, z: nz / len } : { x: 0, y: 1, z: 0 };
|
|
474
|
-
};
|
|
475
|
-
for (let i = 0; i < indices.length; i += 3) {
|
|
476
|
-
const triNormal = computeTriangleNormal(i);
|
|
477
|
-
const triangleEdges = [
|
|
478
|
-
[indices[i], indices[i + 1]],
|
|
479
|
-
[indices[i + 1], indices[i + 2]],
|
|
480
|
-
[indices[i + 2], indices[i]],
|
|
481
|
-
];
|
|
482
|
-
for (const [idx0, idx1] of triangleEdges) {
|
|
483
|
-
const i0 = idx0 * 3;
|
|
484
|
-
const i1 = idx1 * 3;
|
|
485
|
-
const v0 = {
|
|
486
|
-
x: positions[i0],
|
|
487
|
-
y: positions[i0 + 1],
|
|
488
|
-
z: positions[i0 + 2],
|
|
489
|
-
};
|
|
490
|
-
const v1 = {
|
|
491
|
-
x: positions[i1],
|
|
492
|
-
y: positions[i1 + 1],
|
|
493
|
-
z: positions[i1 + 2],
|
|
494
|
-
};
|
|
495
|
-
// Create canonical edge key (smaller index first)
|
|
496
|
-
const key = idx0 < idx1 ? `${idx0}_${idx1}` : `${idx1}_${idx0}`;
|
|
497
|
-
if (!edgeData.has(key)) {
|
|
498
|
-
edgeData.set(key, { v0, v1, idx0, idx1, normals: [triNormal] });
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
const existing = edgeData.get(key);
|
|
502
|
-
if (existing) {
|
|
503
|
-
existing.normals.push(triNormal);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
// Second pass: filter to only real edges (boundary or crease edges)
|
|
509
|
-
// Skip internal triangulation edges (shared by coplanar triangles)
|
|
510
|
-
const COPLANAR_THRESHOLD = 0.98; // Dot product threshold for coplanar check
|
|
511
|
-
for (const [key, data] of edgeData) {
|
|
512
|
-
const { v0, v1, normals } = data;
|
|
513
|
-
// Boundary edge: only one triangle uses it - always a real edge
|
|
514
|
-
if (normals.length === 1) {
|
|
515
|
-
const edgeIndex = edges.length;
|
|
516
|
-
edges.push({ v0, v1, index: edgeIndex });
|
|
517
|
-
// Track vertex valence
|
|
518
|
-
const v0Key = `${v0.x.toFixed(4)}_${v0.y.toFixed(4)}_${v0.z.toFixed(4)}`;
|
|
519
|
-
const v1Key = `${v1.x.toFixed(4)}_${v1.y.toFixed(4)}_${v1.z.toFixed(4)}`;
|
|
520
|
-
vertexValence.set(v0Key, (vertexValence.get(v0Key) || 0) + 1);
|
|
521
|
-
vertexValence.set(v1Key, (vertexValence.get(v1Key) || 0) + 1);
|
|
522
|
-
if (!vertexEdges.has(v0Key))
|
|
523
|
-
vertexEdges.set(v0Key, []);
|
|
524
|
-
if (!vertexEdges.has(v1Key))
|
|
525
|
-
vertexEdges.set(v1Key, []);
|
|
526
|
-
const v0Edges = vertexEdges.get(v0Key);
|
|
527
|
-
const v1Edges = vertexEdges.get(v1Key);
|
|
528
|
-
if (v0Edges)
|
|
529
|
-
v0Edges.push(edgeIndex);
|
|
530
|
-
if (v1Edges)
|
|
531
|
-
v1Edges.push(edgeIndex);
|
|
532
|
-
continue;
|
|
533
|
-
}
|
|
534
|
-
// Shared edge: check if triangles are coplanar (internal triangulation edge)
|
|
535
|
-
if (normals.length >= 2) {
|
|
536
|
-
const n1 = normals[0];
|
|
537
|
-
const n2 = normals[1];
|
|
538
|
-
const dot = Math.abs(n1.x * n2.x + n1.y * n2.y + n1.z * n2.z);
|
|
539
|
-
// If normals are nearly parallel, triangles are coplanar - skip this edge
|
|
540
|
-
// (it's an internal triangulation diagonal, not a real model edge)
|
|
541
|
-
if (dot > COPLANAR_THRESHOLD) {
|
|
542
|
-
continue; // Skip internal edge
|
|
543
|
-
}
|
|
544
|
-
// Crease edge: triangles meet at an angle - this is a real edge
|
|
545
|
-
const edgeIndex = edges.length;
|
|
546
|
-
edges.push({ v0, v1, index: edgeIndex });
|
|
547
|
-
// Track vertex valence
|
|
548
|
-
const v0Key = `${v0.x.toFixed(4)}_${v0.y.toFixed(4)}_${v0.z.toFixed(4)}`;
|
|
549
|
-
const v1Key = `${v1.x.toFixed(4)}_${v1.y.toFixed(4)}_${v1.z.toFixed(4)}`;
|
|
550
|
-
vertexValence.set(v0Key, (vertexValence.get(v0Key) || 0) + 1);
|
|
551
|
-
vertexValence.set(v1Key, (vertexValence.get(v1Key) || 0) + 1);
|
|
552
|
-
if (!vertexEdges.has(v0Key))
|
|
553
|
-
vertexEdges.set(v0Key, []);
|
|
554
|
-
if (!vertexEdges.has(v1Key))
|
|
555
|
-
vertexEdges.set(v1Key, []);
|
|
556
|
-
const v0CreaseEdges = vertexEdges.get(v0Key);
|
|
557
|
-
const v1CreaseEdges = vertexEdges.get(v1Key);
|
|
558
|
-
if (v0CreaseEdges)
|
|
559
|
-
v0CreaseEdges.push(edgeIndex);
|
|
560
|
-
if (v1CreaseEdges)
|
|
561
|
-
v1CreaseEdges.push(edgeIndex);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
const cache = { vertices, edges, vertexValence, vertexEdges };
|
|
387
|
+
const cache = buildGeometryCache(mesh);
|
|
566
388
|
this.geometryCache.set(mesh.expressId, cache);
|
|
567
389
|
return cache;
|
|
568
390
|
}
|
|
@@ -574,7 +396,7 @@ export class SnapDetector {
|
|
|
574
396
|
const cache = this.getGeometryCache(mesh);
|
|
575
397
|
// Find vertices within radius - ONLY when VERY close for smooth edge sliding
|
|
576
398
|
for (const vertex of cache.vertices) {
|
|
577
|
-
const dist =
|
|
399
|
+
const dist = distance(vertex, point);
|
|
578
400
|
// Only snap to vertices when within 20% of snap radius (very tight) to avoid sticky behavior
|
|
579
401
|
if (dist < radius * 0.2) {
|
|
580
402
|
targets.push({
|
|
@@ -598,7 +420,7 @@ export class SnapDetector {
|
|
|
598
420
|
// Find edges near point using cached data
|
|
599
421
|
for (const edge of cache.edges) {
|
|
600
422
|
const closestPoint = this.raycaster.closestPointOnSegment(point, edge.v0, edge.v1);
|
|
601
|
-
const dist =
|
|
423
|
+
const dist = distance(closestPoint, point);
|
|
602
424
|
if (dist < edgeRadius) {
|
|
603
425
|
// Edge snap - ABSOLUTE HIGHEST priority for smooth sliding along edges
|
|
604
426
|
// Maximum confidence ensures edges ALWAYS win over vertices/faces
|
|
@@ -661,7 +483,7 @@ export class SnapDetector {
|
|
|
661
483
|
y: (v0.y + v1.y + v2.y) / 3,
|
|
662
484
|
z: (v0.z + v1.z + v2.z) / 3,
|
|
663
485
|
};
|
|
664
|
-
const dist =
|
|
486
|
+
const dist = distance(center, intersection.point);
|
|
665
487
|
if (dist < radius) {
|
|
666
488
|
targets.push({
|
|
667
489
|
type: SnapType.FACE_CENTER,
|
|
@@ -697,24 +519,5 @@ export class SnapDetector {
|
|
|
697
519
|
});
|
|
698
520
|
return targets[0];
|
|
699
521
|
}
|
|
700
|
-
/**
|
|
701
|
-
* Convert screen-space radius to world-space radius
|
|
702
|
-
*/
|
|
703
|
-
screenToWorldRadius(screenRadius, distance, fov, screenHeight) {
|
|
704
|
-
// Calculate world height at distance
|
|
705
|
-
const fovRadians = (fov * Math.PI) / 180;
|
|
706
|
-
const worldHeight = 2 * distance * Math.tan(fovRadians / 2);
|
|
707
|
-
// Convert screen pixels to world units
|
|
708
|
-
return (screenRadius / screenHeight) * worldHeight;
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* Vector utilities
|
|
712
|
-
*/
|
|
713
|
-
distance(a, b) {
|
|
714
|
-
const dx = a.x - b.x;
|
|
715
|
-
const dy = a.y - b.y;
|
|
716
|
-
const dz = a.z - b.z;
|
|
717
|
-
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
718
|
-
}
|
|
719
522
|
}
|
|
720
523
|
//# sourceMappingURL=snap-detector.js.map
|