Compare commits
8 commits
f2fb3894fb
...
f88e4e2416
Author | SHA1 | Date | |
---|---|---|---|
f88e4e2416 | |||
2be964acda | |||
3f44e38788 | |||
980dc249e9 | |||
4c0c9cedf8 | |||
4de2b97c2d | |||
215f8ed500 | |||
a47d2c2b57 |
5
.gitignore
vendored
|
@ -10,4 +10,7 @@
|
|||
/.cache
|
||||
|
||||
# Gdb
|
||||
/.gdb_history
|
||||
/.gdb_history
|
||||
|
||||
# Excluded assets
|
||||
/assets/excluded
|
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 354 B |
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 688 B After Width: | Height: | Size: 688 B |
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 774 B |
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 868 B |
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 836 B |
Before Width: | Height: | Size: 865 B After Width: | Height: | Size: 865 B |
BIN
assets/icons/editor-dark/actions/48/space-global.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/icons/editor-dark/actions/48/space-local.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
assets/icons/editor-dark/actions/48/surface-glue.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
assets/icons/editor-dark/actions/48/surface-inlets.png
Normal file
After Width: | Height: | Size: 757 B |
BIN
assets/icons/editor-dark/actions/48/surface-smooth.png
Normal file
After Width: | Height: | Size: 522 B |
BIN
assets/icons/editor-dark/actions/48/surface-studs.png
Normal file
After Width: | Height: | Size: 775 B |
BIN
assets/icons/editor-dark/actions/48/surface-universal.png
Normal file
After Width: | Height: | Size: 925 B |
BIN
assets/icons/editor-dark/actions/48/surface-weld.png
Normal file
After Width: | Height: | Size: 843 B |
8
assets/icons/editor-dark/index.theme
Normal file
|
@ -0,0 +1,8 @@
|
|||
[Icon Theme]
|
||||
Name=editor-dark
|
||||
Comment=icon theme (dark)
|
||||
|
||||
[actions/48]
|
||||
Size=48
|
||||
Context=Actions
|
||||
Type=Fixed
|
BIN
assets/icons/editor/actions/48/delete.png
Normal file
After Width: | Height: | Size: 715 B |
BIN
assets/icons/editor/actions/48/drag.png
Normal file
After Width: | Height: | Size: 354 B |
BIN
assets/icons/editor/actions/48/new.png
Normal file
After Width: | Height: | Size: 294 B |
BIN
assets/icons/editor/actions/48/open.png
Normal file
After Width: | Height: | Size: 688 B |
BIN
assets/icons/editor/actions/48/save.png
Normal file
After Width: | Height: | Size: 774 B |
BIN
assets/icons/editor/actions/48/snap05.png
Normal file
After Width: | Height: | Size: 868 B |
BIN
assets/icons/editor/actions/48/snap1.png
Normal file
After Width: | Height: | Size: 836 B |
BIN
assets/icons/editor/actions/48/snapoff.png
Normal file
After Width: | Height: | Size: 865 B |
BIN
assets/icons/editor/actions/48/space-global.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/icons/editor/actions/48/space-local.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
assets/icons/editor/actions/48/surface-glue.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/icons/editor/actions/48/surface-inlets.png
Normal file
After Width: | Height: | Size: 843 B |
BIN
assets/icons/editor/actions/48/surface-smooth.png
Normal file
After Width: | Height: | Size: 570 B |
BIN
assets/icons/editor/actions/48/surface-studs.png
Normal file
After Width: | Height: | Size: 835 B |
BIN
assets/icons/editor/actions/48/surface-universal.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
assets/icons/editor/actions/48/surface-weld.png
Normal file
After Width: | Height: | Size: 929 B |
8
assets/icons/editor/index.theme
Normal file
|
@ -0,0 +1,8 @@
|
|||
[Icon Theme]
|
||||
Name=editor
|
||||
Comment=icon theme
|
||||
|
||||
[actions/48]
|
||||
Size=48
|
||||
Context=Actions
|
||||
Type=Fixed
|
|
@ -39,8 +39,9 @@ const int FaceFront = 5;
|
|||
// I/O
|
||||
|
||||
in vec3 vPos;
|
||||
in vec3 lPos;
|
||||
in vec3 vNormal;
|
||||
in vec2 vTexCoords;
|
||||
in vec3 lNormal;
|
||||
flat in int vSurfaceZ;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
@ -54,12 +55,13 @@ uniform DirLight sunLight;
|
|||
uniform Material material;
|
||||
uniform sampler2DArray studs;
|
||||
uniform float transparency;
|
||||
uniform vec3 texScale;
|
||||
|
||||
// Functions
|
||||
|
||||
vec3 calculateDirectionalLight(DirLight light);
|
||||
vec3 calculatePointLight(PointLight light);
|
||||
|
||||
mat3 lookAlong(vec3 pos, vec3 forward, vec3 up);
|
||||
|
||||
// Main
|
||||
|
||||
|
@ -71,11 +73,28 @@ void main() {
|
|||
for (int i = 0; i < numPointLights; i++) {
|
||||
result += calculatePointLight(pointLights[i]);
|
||||
}
|
||||
|
||||
vec3 otherVec = abs(dot(lNormal, vec3(0, 1, 0))) > 0.99 ? vec3(0, 0, 1)
|
||||
: abs(dot(lNormal, vec3(0, 0, 1))) > 0.99 ? vec3(1, 0, 0)
|
||||
: vec3(0, 1, 0);
|
||||
// We use abs(lNormal) so opposing sides "cut" from the same side
|
||||
mat3 transform = transpose(inverse(lookAlong(vec3(0, 0, 0), abs(lNormal), otherVec)));
|
||||
|
||||
vec4 studPx = texture(studs, vec3(vTexCoords, vSurfaceZ));
|
||||
vec2 texCoords = vec2((transform * lPos) * (transform * texScale) / 2) - vec2(mod((transform * texScale) / 4, 1));
|
||||
|
||||
vec4 studPx = texture(studs, vec3(texCoords, vSurfaceZ));
|
||||
FragColor = vec4(mix(result, vec3(studPx), studPx.w), 1) * (1-transparency);
|
||||
}
|
||||
|
||||
mat3 lookAlong(vec3 pos, vec3 forward, vec3 up) {
|
||||
vec3 f = normalize(forward); // Forward/Look
|
||||
vec3 u = normalize(up); // Up
|
||||
vec3 s = normalize(cross(f, u)); // Right
|
||||
u = normalize(cross(s, f));
|
||||
|
||||
return mat3(s, u, f);
|
||||
}
|
||||
|
||||
vec3 calculateDirectionalLight(DirLight light) {
|
||||
// Calculate diffuse
|
||||
vec3 norm = normalize(vNormal);
|
||||
|
|
|
@ -18,7 +18,9 @@ const int SurfaceInlets = 4;
|
|||
const int SurfaceUniversal = 5;
|
||||
|
||||
out vec3 vPos;
|
||||
out vec3 lPos;
|
||||
out vec3 vNormal;
|
||||
out vec3 lNormal;
|
||||
out vec2 vTexCoords;
|
||||
flat out int vSurfaceZ;
|
||||
|
||||
|
@ -33,30 +35,16 @@ void main()
|
|||
{
|
||||
gl_Position = projection * view * model * vec4(aPos, 1.0);
|
||||
vPos = vec3(model * vec4(aPos, 1.0));
|
||||
lPos = aPos;
|
||||
vNormal = normalMatrix * aNormal;
|
||||
lNormal = aNormal;
|
||||
int vFace = aNormal == vec3(0,1,0) ? FaceTop :
|
||||
aNormal == vec3(0, -1, 0) ? FaceBottom :
|
||||
aNormal == vec3(1, 0, 0) ? FaceRight :
|
||||
aNormal == vec3(-1, 0, 0) ? FaceLeft :
|
||||
aNormal == vec3(0, 0, 1) ? FaceFront :
|
||||
aNormal == vec3(0, 0, -1) ? FaceBack : -1;
|
||||
aNormal == vec3(0, 0, -1) ? FaceFront :
|
||||
aNormal == vec3(0, 0, 1) ? FaceBack : -1;
|
||||
|
||||
vSurfaceZ = surfaces[vFace];
|
||||
// if (surfaces[vFace] > SurfaceUniversal) vSurfaceZ = 0;
|
||||
|
||||
switch (vFace) {
|
||||
case FaceTop:
|
||||
case FaceBottom:
|
||||
// vTexCoords = aTexCoords * vec2(texScale.x / 2, fract(surfaceOffset + texScale.z / 12));
|
||||
vTexCoords = aTexCoords * vec2(texScale.x, texScale.z) / 2;
|
||||
break;
|
||||
case FaceLeft:
|
||||
case FaceRight:
|
||||
vTexCoords = aTexCoords * vec2(texScale.y, texScale.z) / 2;
|
||||
break;
|
||||
case FaceFront:
|
||||
case FaceBack:
|
||||
vTexCoords = aTexCoords * vec2(texScale.x, texScale.y) / 2;
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
|
92
assets/src/glue.svg
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:export-filename="../icons/editor/weld.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
sodipodi:docname="glue.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="2.4375"
|
||||
inkscape:cy="10.25"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
showguides="true">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0.5"
|
||||
originy="0.5"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g3">
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1"
|
||||
width="10"
|
||||
height="10"
|
||||
x="2.5"
|
||||
y="2.5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,2.5 2,2"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,12.5 2,2"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2.5,12.5 2,2"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 4.8592944,7.0236254 c -1.213294,1.0424696 -1,1.7243022 -0.234375,1.7243022 -0.017005,3.5315634 2.2303514,1.8633974 3.234375,1 2.7742076,1.7680844 3.5903366,0.4332984 2,-1.248699 2.4102876,-1.0931787 0.4460256,-4.39491 -1,-2.751301 C 8.5061317,2.1238839 6.180487,5.3900806 6.5155444,4.5916776 4.7251975,3.6478829 4.0183403,5.4550835 4.8592944,7.0236254 Z"
|
||||
id="path18"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
103
assets/src/inlets.svg
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:export-filename="../icons/editor/studs.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
sodipodi:docname="inlets.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="9.3691648"
|
||||
inkscape:cy="12.772116"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0.5"
|
||||
originy="0.5"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g3">
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1"
|
||||
width="10"
|
||||
height="10"
|
||||
x="2.5"
|
||||
y="2.5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,2.5 2,2"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,12.5 2,2"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2.5,12.5 2,2"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3-3"
|
||||
transform="matrix(-0.5,0,0,-0.5,11.75,11.75)"
|
||||
style="stroke-width:2;stroke-dasharray:none" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 10.5,4.5 h -6 v 6 h 6 z"
|
||||
id="path4"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 6.5,6.5 -2,-2"
|
||||
id="path5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 6.5,10.5 v -4 h 4"
|
||||
id="path6" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
121
assets/src/localspace.svg
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16mm"
|
||||
height="16mm"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
sodipodi:docname="localspace.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="true"
|
||||
inkscape:zoom="5.701459"
|
||||
inkscape:cx="-3.3324803"
|
||||
inkscape:cy="14.908465"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid3"
|
||||
units="mm"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="0.99999994"
|
||||
spacingy="0.99999994"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="false"
|
||||
visible="true" />
|
||||
<inkscape:grid
|
||||
id="grid8"
|
||||
units="mm"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="0.99999997"
|
||||
spacingy="0.99999997"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g8"
|
||||
style="fill:none;fill-opacity:1;stroke:#3daee9;stroke-opacity:1"
|
||||
transform="translate(0.556875,-0.37125)">
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#3daee9;stroke-width:0.799999;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 1.9999999,11 5.9999998,12 8.788906,10.153086 4.7889062,9.1530855 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#3daee9;stroke-width:0.799999;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="m 1.9999999,11 v 3 l 3.9999999,1 v -3"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#3daee9;stroke-width:0.799999;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 5.9999998,15 8.788906,13.153086 v -3"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
</g>
|
||||
<g
|
||||
id="g7"
|
||||
transform="translate(-0.39999908,0.10000091)"
|
||||
style="stroke:#232629;stroke-opacity:1">
|
||||
<path
|
||||
id="path4"
|
||||
style="fill:none;stroke:#232629;stroke-width:0.799999;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="m 15,3.9999999 c 0,1.568079 -1.343147,2.9999997 -3,2.9999999 -1.656854,-1e-7 -3.0000007,-1.4319208 -3.0000007,-2.9999999 0,-1.5680791 1.3431467,-2.99999993 3.0000007,-2.99999993 1.656853,1.3e-7 3,1.43192093 3,2.99999993 z"
|
||||
sodipodi:nodetypes="sssss" />
|
||||
<path
|
||||
style="fill:none;stroke:#232629;stroke-width:0.4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 8.9999994,3.9999999 H 15"
|
||||
id="path5" />
|
||||
<path
|
||||
style="fill:none;stroke:#232629;stroke-width:0.420712;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 11.5,1 c 0,0 -0.709961,0.5 -0.709961,3.0000001 0,3.0000001 0.709961,2.7575037 0.709961,2.7575037"
|
||||
id="path6"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="fill:none;stroke:#232629;stroke-width:0.4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,1 c 0,0 0.738965,0.4999998 0.738965,2.9999999 0,2.5000001 -0.738966,2.7999998 -0.738966,2.7999998"
|
||||
id="path7"
|
||||
sodipodi:nodetypes="csc" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#3daee9;stroke-width:0.8;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1.8199217,1.5702338 0.016407,3.8000003 h 2.3695758"
|
||||
id="path8"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
86
assets/src/smooth.svg
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:export-filename="../icons/editor/studs.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
sodipodi:docname="smooth.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="32.252323"
|
||||
inkscape:cx="12.650252"
|
||||
inkscape:cy="8.4335011"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0.5"
|
||||
originy="0.5"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g3">
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1"
|
||||
width="10"
|
||||
height="10"
|
||||
x="2.5"
|
||||
y="2.5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,2.5 2,2"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,12.5 2,2"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2.5,12.5 2,2"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
113
assets/src/studs.svg
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:export-filename="studs.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
sodipodi:docname="studs.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="32.252323"
|
||||
inkscape:cx="12.650252"
|
||||
inkscape:cy="8.4024956"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0.5"
|
||||
originy="0.5"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g3">
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1"
|
||||
width="10"
|
||||
height="10"
|
||||
x="2.5"
|
||||
y="2.5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,2.5 2,2"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,12.5 2,2"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2.5,12.5 2,2"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3-3"
|
||||
transform="matrix(0.5,0,0,0.5,3.25,3.25)"
|
||||
style="stroke-width:2;stroke-dasharray:none">
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1-5"
|
||||
width="10"
|
||||
height="10"
|
||||
x="2.5"
|
||||
y="2.5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,2.5 2,2"
|
||||
id="path1-6"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,12.5 2,2"
|
||||
id="path2-2"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2.5,12.5 2,2"
|
||||
id="path3-9"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
154
assets/src/universal.svg
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:export-filename="../icons/editor/studs.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
sodipodi:docname="universal.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="45.254834"
|
||||
inkscape:cx="9.7448153"
|
||||
inkscape:cy="5.3033009"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
showguides="true">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="1"
|
||||
originy="1"
|
||||
spacingx="0.25"
|
||||
spacingy="0.25"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
<sodipodi:guide
|
||||
position="3.5,5.5"
|
||||
orientation="-0.70710678,-0.70710678"
|
||||
id="guide13"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="3.5,4.5"
|
||||
orientation="0.70710678,-0.70710678"
|
||||
id="guide14"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="11,12"
|
||||
orientation="-0.70710678,-0.70710678"
|
||||
id="guide15"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g3">
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1"
|
||||
width="10"
|
||||
height="10"
|
||||
x="2.5"
|
||||
y="2.5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,2.5 2,2"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,12.5 2,2"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2.5,12.5 2,2"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
<g
|
||||
id="g3-3"
|
||||
transform="matrix(0.5,0,0,0.5,3.25,3.25)"
|
||||
style="display:inline;stroke-width:2;stroke-dasharray:none">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="m 12.5,12.5 h -8 z"
|
||||
id="rect1-5"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 13.5,3.5 1,1"
|
||||
id="path1-6"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,12.5 2,2"
|
||||
id="path2-2"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 3.5,13.5 1,1"
|
||||
id="path3-9"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,12.5 v -8"
|
||||
id="path9" />
|
||||
</g>
|
||||
<g
|
||||
id="g6"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 10.5,4.5 h -6 v 6"
|
||||
id="path4"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 6.5,6.5 -2,-2"
|
||||
id="path5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 6.5,8.5 v -2 h 2"
|
||||
id="path6"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
</g>
|
||||
<path
|
||||
style="display:inline;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10.863301,4.1698442 4.25,10.75"
|
||||
id="path10"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.3 KiB |
102
assets/src/weld.svg
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:export-filename="../icons/editor/studs.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
sodipodi:docname="weld.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="45.611673"
|
||||
inkscape:cx="6.6211122"
|
||||
inkscape:cy="6.40187"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0.5"
|
||||
originy="0.5"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g3">
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1"
|
||||
width="10"
|
||||
height="10"
|
||||
x="2.5"
|
||||
y="2.5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,2.5 2,2"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,12.5 2,2"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2.5,12.5 2,2"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="m 4.5,4.5 2,2"
|
||||
id="path4" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="m 8.5,6.5 2,-2"
|
||||
id="path5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="m 8.5,8.5 2,2"
|
||||
id="path6" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="m 6.5,8.5 -2,2"
|
||||
id="path7" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
121
assets/src/worldspace.svg
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16mm"
|
||||
height="16mm"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
|
||||
sodipodi:docname="worldspace.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="true"
|
||||
inkscape:zoom="5.701459"
|
||||
inkscape:cx="-3.3324803"
|
||||
inkscape:cy="14.908465"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid3"
|
||||
units="mm"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="0.99999994"
|
||||
spacingy="0.99999994"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="false"
|
||||
visible="true" />
|
||||
<inkscape:grid
|
||||
id="grid8"
|
||||
units="mm"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="0.99999997"
|
||||
spacingy="0.99999997"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g8"
|
||||
style="fill:none;fill-opacity:1;stroke:#232629;stroke-opacity:1"
|
||||
transform="translate(0.556875,-0.37125)">
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#232629;stroke-width:0.799999;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 1.9999999,11 5.9999998,12 8.788906,10.153086 4.7889062,9.1530855 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#232629;stroke-width:0.799999;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="m 1.9999999,11 v 3 l 3.9999999,1 v -3"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#232629;stroke-width:0.799999;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 5.9999998,15 8.788906,13.153086 v -3"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
</g>
|
||||
<g
|
||||
id="g7"
|
||||
transform="translate(-0.39999908,0.10000091)"
|
||||
style="stroke:#3daee9;stroke-opacity:1">
|
||||
<path
|
||||
id="path4"
|
||||
style="fill:none;stroke:#3daee9;stroke-width:0.799999;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="m 15,3.9999999 c 0,1.568079 -1.343147,2.9999997 -3,2.9999999 -1.656854,-1e-7 -3.0000007,-1.4319208 -3.0000007,-2.9999999 0,-1.5680791 1.3431467,-2.99999993 3.0000007,-2.99999993 1.656853,1.3e-7 3,1.43192093 3,2.99999993 z"
|
||||
sodipodi:nodetypes="sssss" />
|
||||
<path
|
||||
style="fill:none;stroke:#3daee9;stroke-width:0.4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 8.9999994,3.9999999 H 15"
|
||||
id="path5" />
|
||||
<path
|
||||
style="fill:none;stroke:#3daee9;stroke-width:0.420712;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 11.5,1 c 0,0 -0.709961,0.5 -0.709961,3.0000001 0,3.0000001 0.709961,2.7575037 0.709961,2.7575037"
|
||||
id="path6"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="fill:none;stroke:#3daee9;stroke-width:0.4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.5,1 c 0,0 0.738965,0.4999998 0.738965,2.9999999 0,2.5000001 -0.738966,2.7999998 -0.738966,2.7999998"
|
||||
id="path7"
|
||||
sodipodi:nodetypes="csc" />
|
||||
</g>
|
||||
<path
|
||||
style="display:inline;fill:none;fill-opacity:1;stroke:#3daee9;stroke-width:0.799999;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1.4102681,2.1569419 0.9999999,2.9999999 1,-1 1,1 0.9999999,-2.9999999"
|
||||
id="path9"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
|
@ -45,12 +45,12 @@ int main() {
|
|||
glfwMakeContextCurrent(window);
|
||||
glewInit();
|
||||
|
||||
dataModel->Init();
|
||||
gDataModel->Init();
|
||||
simulationInit();
|
||||
renderInit(window, 1200, 900);
|
||||
|
||||
// Baseplate
|
||||
workspace()->AddChild(Part::New({
|
||||
gWorkspace()->AddChild(Part::New({
|
||||
.position = glm::vec3(0, -5, 0),
|
||||
.rotation = glm::vec3(0),
|
||||
.size = glm::vec3(512, 1.2, 512),
|
||||
|
@ -58,14 +58,14 @@ int main() {
|
|||
.anchored = true,
|
||||
}));
|
||||
|
||||
workspace()->AddChild(lastPart = Part::New({
|
||||
gWorkspace()->AddChild(lastPart = Part::New({
|
||||
.position = glm::vec3(0),
|
||||
.rotation = glm::vec3(0),
|
||||
.size = glm::vec3(4, 1.2, 2),
|
||||
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
||||
}));
|
||||
|
||||
for (InstanceRef inst : workspace()->GetChildren()) {
|
||||
for (InstanceRef inst : gWorkspace()->GetChildren()) {
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
syncPartPhysics(part);
|
||||
|
@ -158,7 +158,7 @@ void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
|
|||
|
||||
void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
|
||||
if (key == GLFW_KEY_F && action == GLFW_PRESS) {
|
||||
workspace()->AddChild(lastPart = Part::New({
|
||||
gWorkspace()->AddChild(lastPart = Part::New({
|
||||
.position = camera.cameraPos + camera.cameraFront * glm::vec3(3),
|
||||
.rotation = glm::vec3(0),
|
||||
.size = glm::vec3(1, 1, 1),
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
Camera camera(glm::vec3(0.0, 0.0, 3.0));
|
||||
//std::vector<Part> parts;
|
||||
std::shared_ptr<DataModel> dataModel = DataModel::New();
|
||||
std::shared_ptr<DataModel> gDataModel = DataModel::New();
|
||||
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
||||
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||
std::shared_ptr<Handles> editorToolHandles = Handles::New();
|
||||
|
|
|
@ -15,8 +15,8 @@ typedef std::function<void(std::vector<InstanceRefWeak> oldSelection, std::vecto
|
|||
// TEMPORARY COMMON DATA FOR VARIOUS INTERNAL COMPONENTS
|
||||
|
||||
extern Camera camera;
|
||||
extern std::shared_ptr<DataModel> dataModel;
|
||||
inline std::shared_ptr<Workspace> workspace() { return std::dynamic_pointer_cast<Workspace>(dataModel->services["Workspace"]); }
|
||||
extern std::shared_ptr<DataModel> gDataModel;
|
||||
inline std::shared_ptr<Workspace> gWorkspace() { return std::dynamic_pointer_cast<Workspace>(gDataModel->services["Workspace"]); }
|
||||
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
||||
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||
extern std::shared_ptr<Handles> editorToolHandles;
|
||||
|
|
|
@ -72,12 +72,51 @@ bool Instance::SetParent(std::optional<std::shared_ptr<Instance>> newParent) {
|
|||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<DataModel>> Instance::dataModel() {
|
||||
// TODO: This algorithm will defer calculations to every time the root data model
|
||||
// is accessed from any instance. This is inefficient as this can happen many times
|
||||
// a tick. A better option is to cache these values and only update them if the ancestry
|
||||
// changes, as that happens way less often.
|
||||
|
||||
std::optional<std::shared_ptr<Instance>> currentParent = GetParent();
|
||||
while (currentParent) {
|
||||
if (currentParent.value()->GetClass() == &DataModel::TYPE)
|
||||
return std::dynamic_pointer_cast<DataModel>(currentParent.value());
|
||||
|
||||
currentParent = currentParent.value()->GetParent();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Workspace>> Instance::workspace() {
|
||||
// See comment in above function
|
||||
std::optional<std::shared_ptr<Instance>> currentParent = GetParent();
|
||||
while (currentParent) {
|
||||
if (currentParent.value()->GetClass() == &DataModel::TYPE)
|
||||
return std::dynamic_pointer_cast<Workspace>(currentParent.value());
|
||||
|
||||
currentParent = currentParent.value()->GetParent();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Instance>> Instance::GetParent() {
|
||||
if (!parent.has_value()) return std::nullopt;
|
||||
if (parent.value().expired()) return std::nullopt;
|
||||
return parent.value().lock();
|
||||
}
|
||||
|
||||
static std::shared_ptr<Instance> DUMMY_INSTANCE;
|
||||
DescendantsIterator Instance::GetDescendantsStart() {
|
||||
return DescendantsIterator(GetChildren().size() > 0 ? GetChildren()[0] : DUMMY_INSTANCE);
|
||||
}
|
||||
|
||||
DescendantsIterator Instance::GetDescendantsEnd() {
|
||||
return DescendantsIterator(DUMMY_INSTANCE);
|
||||
}
|
||||
|
||||
bool Instance::IsParentLocked() {
|
||||
return this->parentLocked;
|
||||
}
|
||||
|
@ -201,4 +240,49 @@ InstanceRef Instance::Deserialize(pugi::xml_node* node) {
|
|||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
// DescendantsIterator
|
||||
|
||||
DescendantsIterator::DescendantsIterator(std::shared_ptr<Instance> current) : current(current), root(current == DUMMY_INSTANCE ? DUMMY_INSTANCE : current->GetParent()), siblingIndex { 0 } { }
|
||||
|
||||
DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
|
||||
// If the current item is dummy, an error has occurred, this is not supposed to happen.
|
||||
if (current == DUMMY_INSTANCE) {
|
||||
Logger::fatalError("Attempt to increment a descendant iterator past its end\n");
|
||||
panic();
|
||||
}
|
||||
|
||||
// If the current item has children, enter it
|
||||
if (current->GetChildren().size() > 0) {
|
||||
siblingIndex.push_back(0);
|
||||
current = current->GetChildren()[0];
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Otherwise, we move to the next sibling, if applicable.
|
||||
|
||||
// But not if one up is null or the root element
|
||||
if (!current->GetParent() || current == root) {
|
||||
current = DUMMY_INSTANCE;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// If we've hit the end of this item's children, move one up
|
||||
while (current->GetParent() && current->GetParent().value()->GetChildren().size() <= (siblingIndex.back() + 1)) {
|
||||
siblingIndex.pop_back();
|
||||
current = current->GetParent().value();
|
||||
|
||||
// But not if one up is null or the root element
|
||||
if (!current->GetParent() || current == root) {
|
||||
current = DUMMY_INSTANCE;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
// Now move to the next sibling
|
||||
siblingIndex.back()++;
|
||||
current = current->GetParent().value()->GetChildren()[siblingIndex.back()];
|
||||
|
||||
return *this;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
@ -19,6 +20,9 @@
|
|||
class Instance;
|
||||
typedef std::shared_ptr<Instance>(*InstanceConstructor)();
|
||||
|
||||
class DataModel;
|
||||
class Workspace;
|
||||
|
||||
// Struct describing information about an instance
|
||||
struct InstanceType {
|
||||
const InstanceType* super; // May be null
|
||||
|
@ -27,6 +31,8 @@ struct InstanceType {
|
|||
std::string explorerIcon = "";
|
||||
};
|
||||
|
||||
class DescendantsIterator;
|
||||
|
||||
// Base class for all instances in the data model
|
||||
// Note: enable_shared_from_this HAS to be public or else its field will not be populated
|
||||
// Maybe this could be replaced with a friendship? But that seems unnecessary.
|
||||
|
@ -48,6 +54,13 @@ protected:
|
|||
|
||||
virtual void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent);
|
||||
|
||||
// The root data model this object is a descendant of
|
||||
std::optional<std::shared_ptr<DataModel>> dataModel();
|
||||
// The root workspace this object is a descendant of
|
||||
// NOTE: This value is not necessarily present if dataModel is present
|
||||
// Objects under services other than workspace will NOT have this field set
|
||||
std::optional<std::shared_ptr<Workspace>> workspace();
|
||||
|
||||
template <typename T> inline std::shared_ptr<T> shared() { return std::dynamic_pointer_cast<T>(this->shared_from_this()); }
|
||||
public:
|
||||
const static InstanceType TYPE;
|
||||
|
@ -59,7 +72,9 @@ public:
|
|||
std::optional<std::shared_ptr<Instance>> GetParent();
|
||||
bool IsParentLocked();
|
||||
inline const std::vector<std::shared_ptr<Instance>> GetChildren() { return children; }
|
||||
|
||||
|
||||
DescendantsIterator GetDescendantsStart();
|
||||
DescendantsIterator GetDescendantsEnd();
|
||||
// Utility functions
|
||||
inline void AddChild(std::shared_ptr<Instance> object) { object->SetParent(this->shared_from_this()); }
|
||||
|
||||
|
@ -77,4 +92,28 @@ public:
|
|||
};
|
||||
|
||||
typedef std::shared_ptr<Instance> InstanceRef;
|
||||
typedef std::weak_ptr<Instance> InstanceRefWeak;
|
||||
typedef std::weak_ptr<Instance> InstanceRefWeak;
|
||||
|
||||
// https://gist.github.com/jeetsukumaran/307264
|
||||
class DescendantsIterator {
|
||||
public:
|
||||
typedef DescendantsIterator self_type;
|
||||
typedef std::shared_ptr<Instance> value_type;
|
||||
typedef std::shared_ptr<Instance>& reference;
|
||||
typedef std::shared_ptr<Instance> pointer;
|
||||
typedef std::forward_iterator_tag iterator_category;
|
||||
typedef int difference_type;
|
||||
|
||||
DescendantsIterator(std::shared_ptr<Instance> current);
|
||||
inline self_type operator++() { self_type i = *this; ++*this; return i; }
|
||||
inline std::shared_ptr<Instance> operator*() { return current; }
|
||||
inline std::shared_ptr<Instance> operator->() { return current; }
|
||||
inline bool operator==(const self_type& rhs) { return current == rhs.current; }
|
||||
inline bool operator!=(const self_type& rhs) { return current != rhs.current; }
|
||||
|
||||
self_type operator++(int _);
|
||||
private:
|
||||
std::optional<std::shared_ptr<Instance>> root;
|
||||
std::shared_ptr<Instance> current;
|
||||
std::vector<int> siblingIndex;
|
||||
};
|
|
@ -8,6 +8,7 @@
|
|||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "rendering/surface.h"
|
||||
#include <reactphysics3d/reactphysics3d.h>
|
||||
|
||||
namespace rp = reactphysics3d;
|
||||
|
@ -37,6 +38,13 @@ public:
|
|||
|
||||
bool anchored = false;
|
||||
rp::RigidBody* rigidBody = nullptr;
|
||||
|
||||
SurfaceType topSurface = SurfaceType::SurfaceStuds;
|
||||
SurfaceType bottomSurface = SurfaceType::SurfaceInlets;
|
||||
SurfaceType leftSurface = SurfaceType::SurfaceSmooth;
|
||||
SurfaceType rightSurface = SurfaceType::SurfaceSmooth;
|
||||
SurfaceType frontSurface = SurfaceType::SurfaceSmooth;
|
||||
SurfaceType backSurface = SurfaceType::SurfaceSmooth;
|
||||
|
||||
Part();
|
||||
Part(PartConstructParams params);
|
||||
|
|
|
@ -77,7 +77,8 @@ void physicsStep(float deltaTime) {
|
|||
|
||||
// Naive implementation. Parts are only considered so if they are just under Workspace
|
||||
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
|
||||
for (InstanceRef obj : workspace()->GetChildren()) {
|
||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||
InstanceRef obj = *it;
|
||||
if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(obj);
|
||||
const rp::Transform& transform = part->rigidBody->getTransform();
|
||||
|
|
|
@ -109,9 +109,6 @@ void renderParts() {
|
|||
// });
|
||||
studsTexture->activate(0);
|
||||
shader->set("studs", 0);
|
||||
// shader->set("surfaces[1]", SurfaceStuds);
|
||||
shader->set("surfaces[1]", SurfaceStuds);
|
||||
shader->set("surfaces[4]", SurfaceInlets);
|
||||
|
||||
// Pre-calculate the normal matrix for the shader
|
||||
|
||||
|
@ -120,7 +117,8 @@ void renderParts() {
|
|||
|
||||
// Sort by nearest
|
||||
std::map<float, std::shared_ptr<Part>> sorted;
|
||||
for (InstanceRef inst : workspace()->GetChildren()) {
|
||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||
InstanceRef inst = *it;
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
if (part->transparency > 0.00001) {
|
||||
|
@ -141,6 +139,13 @@ void renderParts() {
|
|||
shader->set("texScale", part->size);
|
||||
shader->set("transparency", part->transparency);
|
||||
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", part->rightSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", part->topSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", part->backSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", part->leftSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", part->bottomSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", part->frontSurface);
|
||||
|
||||
CUBE_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
|
||||
}
|
||||
|
@ -164,6 +169,13 @@ void renderParts() {
|
|||
shader->set("texScale", part->size);
|
||||
shader->set("transparency", part->transparency);
|
||||
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Right) + "]", part->rightSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Top) + "]", part->topSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Back) + "]", part->backSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Left) + "]", part->leftSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Bottom) + "]", part->bottomSurface);
|
||||
shader->set("surfaces[" + std::to_string(NormalId::Front) + "]", part->frontSurface);
|
||||
|
||||
CUBE_MESH->bind();
|
||||
glDrawArrays(GL_TRIANGLES, 0, CUBE_MESH->vertexCount);
|
||||
}
|
||||
|
@ -283,7 +295,7 @@ void renderAABB() {
|
|||
ghostShader->set("color", glm::vec3(1.f, 0.f, 0.f));
|
||||
|
||||
// Sort by nearest
|
||||
for (InstanceRef inst : workspace()->GetChildren()) {
|
||||
for (InstanceRef inst : gWorkspace()->GetChildren()) {
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
glm::mat4 model = Data::CFrame::IDENTITY + part->cframe.Position();
|
||||
|
|
23
core/src/rendering/surface.cpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#include "surface.h"
|
||||
#include "datatypes/vector.h"
|
||||
|
||||
static std::array<Data::Vector3, 6> FACE_NORMALS = {{
|
||||
{ 1, 0, 0 },
|
||||
{ 0, 1, 0 },
|
||||
{ 0, 0, 1 },
|
||||
{ -1, 0, 0 },
|
||||
{ 0, -1, 0 },
|
||||
{ 0, 0, -1 },
|
||||
}};
|
||||
|
||||
NormalId faceFromNormal(Data::Vector3 normal) {
|
||||
for (int face = 0; face < 6; face++) {
|
||||
if (normal.Dot(FACE_NORMALS[face]) > 0.99)
|
||||
return (NormalId)face;
|
||||
}
|
||||
return (NormalId)-1;
|
||||
}
|
||||
|
||||
Data::Vector3 normalFromFace(NormalId face) {
|
||||
return FACE_NORMALS[face];
|
||||
}
|
|
@ -1,5 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
enum NormalId {
|
||||
Right = 0,
|
||||
Top = 1,
|
||||
Back = 2,
|
||||
Left = 3,
|
||||
Bottom = 4,
|
||||
Front = 5
|
||||
};
|
||||
|
||||
enum SurfaceType {
|
||||
SurfaceSmooth = 0,
|
||||
SurfaceGlue = 1,
|
||||
|
@ -7,4 +16,8 @@ enum SurfaceType {
|
|||
SurfaceStuds = 3,
|
||||
SurfaceInlets = 4,
|
||||
SurfaceUniversal = 5,
|
||||
};
|
||||
};
|
||||
|
||||
namespace Data { class Vector3; }
|
||||
NormalId faceFromNormal(Data::Vector3);
|
||||
Data::Vector3 normalFromFace(NormalId);
|
|
@ -11,8 +11,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Multimedia LinguistTools)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Multimedia LinguistTools)
|
||||
|
||||
|
||||
set(TS_FILES editor_en_US.ts)
|
||||
|
@ -64,7 +64,7 @@ else()
|
|||
endif()
|
||||
|
||||
target_include_directories(editor PUBLIC "../core/src" "../include")
|
||||
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets)
|
||||
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Multimedia)
|
||||
|
||||
# Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so
|
||||
# we have to include it manually
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <optional>
|
||||
#include <reactphysics3d/collision/RaycastInfo.h>
|
||||
#include <vector>
|
||||
#include <QSound>
|
||||
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/vector.h"
|
||||
|
@ -37,6 +38,7 @@
|
|||
|
||||
#include "mainglwidget.h"
|
||||
#include "math_helper.h"
|
||||
#include "rendering/surface.h"
|
||||
|
||||
static Data::CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
|
||||
|
||||
|
@ -130,7 +132,7 @@ bool isMouseDragging = false;
|
|||
std::optional<std::weak_ptr<Part>> draggingObject;
|
||||
std::optional<HandleFace> draggingHandle;
|
||||
void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
||||
if (!isMouseDragging || !draggingObject) return;
|
||||
if (!isMouseDragging || !draggingObject || mainWindow()->selectedTool >= TOOL_SMOOTH) return;
|
||||
|
||||
QPoint position = evt->pos();
|
||||
|
||||
|
@ -153,11 +155,12 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
|||
localFrame = snapCFrame(localFrame);
|
||||
|
||||
// Snap to studs
|
||||
Data::Vector3 draggingPartSize = draggingObject->lock()->size;
|
||||
Data::Vector3 inverseSurfaceNormal = Data::Vector3::ONE - surfaceNormal.Abs();
|
||||
glm::vec3 inverseNormalPartSize = (Data::Vector3)(partSize + 1.f) * inverseSurfaceNormal / 2.f;
|
||||
glm::vec3 inverseNormalPartSize = (Data::Vector3)(partSize + glm::vec3(localFrame.Rotation() * draggingPartSize)) * inverseSurfaceNormal / 2.f;
|
||||
if (snappingFactor() > 0)
|
||||
localFrame = localFrame.Rotation() + glm::round(glm::vec3(localFrame.Position() * inverseSurfaceNormal - inverseNormalPartSize) / snappingFactor()) * snappingFactor() + inverseNormalPartSize
|
||||
+ localFrame.Position() * surfaceNormal.Abs();
|
||||
+ localFrame.Position() * surfaceNormal.Abs();
|
||||
|
||||
Data::CFrame newFrame = targetFrame * localFrame;
|
||||
|
||||
|
@ -214,12 +217,12 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
|||
// printf("Post-snap: (%f, %f, %f)\n", diff.x, diff.y, diff.z);
|
||||
|
||||
switch (mainWindow()->selectedTool) {
|
||||
case SelectedTool::MOVE: {
|
||||
case TOOL_MOVE: {
|
||||
// Add difference
|
||||
editorToolHandles->adornee->lock()->cframe = editorToolHandles->adornee->lock()->cframe + diff;
|
||||
} break;
|
||||
|
||||
case SelectedTool::SCALE: {
|
||||
case TOOL_SCALE: {
|
||||
// Find local difference
|
||||
glm::vec3 localDiff = frame.Inverse() * diff;
|
||||
// Find outwarwd difference
|
||||
|
@ -316,11 +319,11 @@ void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
|
|||
handleCursorChange(evt);
|
||||
|
||||
switch (mainWindow()->selectedTool) {
|
||||
case SelectedTool::MOVE:
|
||||
case SelectedTool::SCALE:
|
||||
case TOOL_MOVE:
|
||||
case TOOL_SCALE:
|
||||
handleLinearTransform(evt);
|
||||
break;
|
||||
case SelectedTool::ROTATE:
|
||||
case TOOL_ROTATE:
|
||||
handleRotationalTransform(evt);
|
||||
break;
|
||||
default:
|
||||
|
@ -356,6 +359,28 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
std::shared_ptr<Part> part = partFromBody(rayHit->body);
|
||||
if (part->name == "Baseplate") return;
|
||||
|
||||
// Handle surface tool
|
||||
if (mainWindow()->selectedTool >= TOOL_SMOOTH) {
|
||||
Data::Vector3 localNormal = part->cframe.Inverse().Rotation() * rayHit->worldNormal;
|
||||
NormalId face = faceFromNormal(localNormal);
|
||||
SurfaceType surface = SurfaceType(mainWindow()->selectedTool - TOOL_SMOOTH);
|
||||
|
||||
switch (face) {
|
||||
case Right: part->rightSurface = surface; break;
|
||||
case Top: part->topSurface = surface; break;
|
||||
case Back: part->backSurface = surface; break;
|
||||
case Left: part->leftSurface = surface; break;
|
||||
case Bottom: part->bottomSurface = surface; break;
|
||||
case Front: part->frontSurface = surface; break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
if (QFile::exists("./assets/excluded/electronicpingshort.wav"))
|
||||
QSound::play("./assets/excluded/electronicpingshort.wav");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//part.selected = true;
|
||||
isMouseDragging = true;
|
||||
draggingObject = part;
|
||||
|
@ -400,7 +425,7 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
|
|||
else if (evt->key() == Qt::Key_D) moveX = -1;
|
||||
|
||||
if (evt->key() == Qt::Key_F) {
|
||||
workspace()->AddChild(lastPart = Part::New({
|
||||
gWorkspace()->AddChild(lastPart = Part::New({
|
||||
.position = camera.cameraPos + camera.cameraFront * glm::vec3(3),
|
||||
.rotation = glm::vec3(0),
|
||||
.size = glm::vec3(1, 1, 1),
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <optional>
|
||||
#include <qcoreapplication.h>
|
||||
#include <qglobal.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <qicon.h>
|
||||
#include <qmessagebox.h>
|
||||
#include <qnamespace.h>
|
||||
|
@ -22,6 +23,7 @@
|
|||
#include <reactphysics3d/engine/PhysicsCommon.h>
|
||||
#include <reactphysics3d/engine/PhysicsWorld.h>
|
||||
#include <sstream>
|
||||
#include <QStyleHints>
|
||||
|
||||
#include "common.h"
|
||||
#include "editorcommon.h"
|
||||
|
@ -45,16 +47,37 @@ bool simulationPlaying = false;
|
|||
|
||||
bool worldSpaceTransforms = false;
|
||||
|
||||
inline bool isDarkMode() {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
const auto scheme = QGuiApplication::styleHints()->colorScheme();
|
||||
return scheme == Qt::ColorScheme::Dark;
|
||||
#else
|
||||
const QPalette defaultPalette;
|
||||
const auto text = defaultPalette.color(QPalette::WindowText);
|
||||
const auto window = defaultPalette.color(QPalette::Window);
|
||||
return text.lightness() > window.lightness();
|
||||
#endif // QT_VERSION
|
||||
}
|
||||
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, ui(new Ui::MainWindow)
|
||||
{
|
||||
dataModel->Init();
|
||||
gDataModel->Init();
|
||||
|
||||
ui->setupUi(this);
|
||||
timer.start(33, this);
|
||||
setMouseTracking(true);
|
||||
|
||||
// https://stackoverflow.com/a/78854851/16255372
|
||||
QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() + QStringList { "./assets/icons" });
|
||||
if (isDarkMode())
|
||||
QIcon::setFallbackThemeName("editor-dark");
|
||||
else
|
||||
QIcon::setFallbackThemeName("editor");
|
||||
|
||||
|
||||
setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
|
||||
setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
|
||||
|
||||
|
@ -86,12 +109,19 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
|
||||
ui->explorerView->buildContextMenu();
|
||||
|
||||
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::MOVE : SelectedTool::SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolScale, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::SCALE : SelectedTool::SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolRotate, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::ROTATE : SelectedTool::SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_MOVE : TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolScale, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_SCALE : TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolRotate, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_ROTATE : TOOL_SELECT; updateToolbars(); });
|
||||
|
||||
connect(ui->actionToolSmooth, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_SMOOTH : TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolGlue, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_GLUE : TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolWeld, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_WELD : TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolStuds, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_STUDS : TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolInlets, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_INLETS : TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolUniversal, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_UNIVERSAL : TOOL_SELECT; updateToolbars(); });
|
||||
ui->actionToolSelect->setChecked(true);
|
||||
selectedTool = SelectedTool::SELECT;
|
||||
selectedTool = TOOL_SELECT;
|
||||
|
||||
connect(ui->actionGridSnap1, &QAction::triggered, this, [&]() { snappingMode = GridSnappingMode::SNAP_1_STUD; updateToolbars(); });
|
||||
connect(ui->actionGridSnap05, &QAction::triggered, this, [&]() { snappingMode = GridSnappingMode::SNAP_05_STUDS; updateToolbars(); });
|
||||
|
@ -116,9 +146,11 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
worldSpaceTransforms = !worldSpaceTransforms;
|
||||
updateToolbars();
|
||||
if (worldSpaceTransforms) {
|
||||
ui->actionToggleSpace->setText("W");
|
||||
ui->actionToggleSpace->setText("World");
|
||||
ui->actionToggleSpace->setIcon(QIcon::fromTheme("space-global"));
|
||||
} else {
|
||||
ui->actionToggleSpace->setText("L");
|
||||
ui->actionToggleSpace->setText("Local");
|
||||
ui->actionToggleSpace->setIcon(QIcon::fromTheme("space-local"));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -136,11 +168,11 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
if (result == QMessageBox::Cancel) return;
|
||||
if (result == QMessageBox::Save) {
|
||||
std::optional<std::string> path;
|
||||
if (!dataModel->HasFile())
|
||||
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + dataModel->name));
|
||||
if (!gDataModel->HasFile())
|
||||
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name));
|
||||
if (!path || path == "") return;
|
||||
|
||||
dataModel->SaveToFile(path);
|
||||
gDataModel->SaveToFile(path);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -150,15 +182,15 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
// TL;DR: This stinks and I need to fix it.)
|
||||
ui->mainWidget->lastPart = Part::New();
|
||||
|
||||
dataModel = DataModel::New();
|
||||
dataModel->Init();
|
||||
ui->explorerView->updateRoot(dataModel);
|
||||
gDataModel = DataModel::New();
|
||||
gDataModel->Init();
|
||||
ui->explorerView->updateRoot(gDataModel);
|
||||
|
||||
// TODO: Remove this and use a proper fix. This *WILL* cause a leak and memory issues in the future
|
||||
simulationInit();
|
||||
|
||||
// Baseplate
|
||||
workspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(0, -5, 0),
|
||||
.rotation = glm::vec3(0),
|
||||
.size = glm::vec3(512, 1.2, 512),
|
||||
|
@ -171,25 +203,30 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
|
||||
connect(ui->actionSave, &QAction::triggered, this, [&]() {
|
||||
std::optional<std::string> path;
|
||||
if (!dataModel->HasFile())
|
||||
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + dataModel->name));
|
||||
if (!gDataModel->HasFile())
|
||||
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name));
|
||||
if (!path || path == "") return;
|
||||
|
||||
dataModel->SaveToFile(path);
|
||||
gDataModel->SaveToFile(path);
|
||||
});
|
||||
|
||||
connect(ui->actionSaveAs, &QAction::triggered, this, [&]() {
|
||||
std::optional<std::string> path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save as " + dataModel->name));
|
||||
std::optional<std::string> path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save as " + gDataModel->name));
|
||||
if (!path || path == "") return;
|
||||
|
||||
dataModel->SaveToFile(path);
|
||||
gDataModel->SaveToFile(path);
|
||||
});
|
||||
|
||||
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
|
||||
std::optional<std::string> path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptOpen);
|
||||
if (!path || path == "") return;
|
||||
|
||||
// // See TODO: Also remove this (the reaso
|
||||
// ui->mainWidget->lastPart = Part::New();
|
||||
|
||||
// simulationInit();
|
||||
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
|
||||
dataModel = newModel;
|
||||
gDataModel = newModel;
|
||||
ui->explorerView->updateRoot(newModel);
|
||||
});
|
||||
|
||||
|
@ -242,7 +279,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
|
||||
for (pugi::xml_node instNode : rootDoc.children()) {
|
||||
InstanceRef inst = Instance::Deserialize(&instNode);
|
||||
workspace()->AddChild(inst);
|
||||
gWorkspace()->AddChild(inst);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -322,7 +359,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
simulationInit();
|
||||
|
||||
// Baseplate
|
||||
workspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(0, -5, 0),
|
||||
.rotation = glm::vec3(0),
|
||||
.size = glm::vec3(512, 1.2, 512),
|
||||
|
@ -332,7 +369,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
ui->mainWidget->lastPart->name = "Baseplate";
|
||||
syncPartPhysics(ui->mainWidget->lastPart);
|
||||
|
||||
workspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(0),
|
||||
.rotation = glm::vec3(0.5, 2, 1),
|
||||
.size = glm::vec3(4, 1.2, 2),
|
||||
|
@ -354,11 +391,11 @@ void MainWindow::closeEvent(QCloseEvent* evt) {
|
|||
if (result == QMessageBox::Cancel) return evt->ignore();
|
||||
if (result == QMessageBox::Save) {
|
||||
std::optional<std::string> path;
|
||||
if (!dataModel->HasFile())
|
||||
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + dataModel->name));
|
||||
if (!gDataModel->HasFile())
|
||||
path = openFileDialog("Openblocks Level (*.obl)", ".obl", QFileDialog::AcceptSave, QString::fromStdString("Save " + gDataModel->name));
|
||||
if (!path || path == "") return evt->ignore();
|
||||
|
||||
dataModel->SaveToFile(path);
|
||||
gDataModel->SaveToFile(path);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -391,23 +428,30 @@ void MainWindow::timerEvent(QTimerEvent* evt) {
|
|||
}
|
||||
|
||||
void MainWindow::updateToolbars() {
|
||||
ui->actionToolSelect->setChecked(selectedTool == SelectedTool::SELECT);
|
||||
ui->actionToolMove->setChecked(selectedTool == SelectedTool::MOVE);
|
||||
ui->actionToolScale->setChecked(selectedTool == SelectedTool::SCALE);
|
||||
ui->actionToolRotate->setChecked(selectedTool == SelectedTool::ROTATE);
|
||||
ui->actionToolSelect->setChecked(selectedTool == TOOL_SELECT);
|
||||
ui->actionToolMove->setChecked(selectedTool == TOOL_MOVE);
|
||||
ui->actionToolScale->setChecked(selectedTool == TOOL_SCALE);
|
||||
ui->actionToolRotate->setChecked(selectedTool == TOOL_ROTATE);
|
||||
|
||||
ui->actionToolSmooth->setChecked(selectedTool == TOOL_SMOOTH);
|
||||
ui->actionToolGlue->setChecked(selectedTool == TOOL_GLUE);
|
||||
ui->actionToolWeld->setChecked(selectedTool == TOOL_WELD);
|
||||
ui->actionToolStuds->setChecked(selectedTool == TOOL_STUDS);
|
||||
ui->actionToolInlets->setChecked(selectedTool == TOOL_INLETS);
|
||||
ui->actionToolUniversal->setChecked(selectedTool == TOOL_UNIVERSAL);
|
||||
|
||||
ui->actionGridSnap1->setChecked(snappingMode == GridSnappingMode::SNAP_1_STUD);
|
||||
ui->actionGridSnap05->setChecked(snappingMode == GridSnappingMode::SNAP_05_STUDS);
|
||||
ui->actionGridSnapOff->setChecked(snappingMode == GridSnappingMode::SNAP_OFF);
|
||||
|
||||
editorToolHandles->worldMode = (selectedTool == SelectedTool::SCALE || selectedTool == SelectedTool::ROTATE) ? false : worldSpaceTransforms;
|
||||
editorToolHandles->nixAxes = selectedTool == SelectedTool::ROTATE;
|
||||
editorToolHandles->worldMode = (selectedTool == TOOL_SCALE || selectedTool == TOOL_ROTATE) ? false : worldSpaceTransforms;
|
||||
editorToolHandles->nixAxes = selectedTool == TOOL_ROTATE;
|
||||
|
||||
editorToolHandles->active = selectedTool != SelectedTool::SELECT;
|
||||
editorToolHandles->active = selectedTool > TOOL_SELECT && selectedTool < TOOL_SMOOTH;
|
||||
editorToolHandles->handlesType =
|
||||
selectedTool == SelectedTool::MOVE ? HandlesType::MoveHandles
|
||||
: selectedTool == SelectedTool::SCALE ? HandlesType::ScaleHandles
|
||||
: selectedTool == SelectedTool::ROTATE ? HandlesType::RotateHandles
|
||||
selectedTool == TOOL_MOVE ? HandlesType::MoveHandles
|
||||
: selectedTool == TOOL_SCALE ? HandlesType::ScaleHandles
|
||||
: selectedTool == TOOL_ROTATE ? HandlesType::RotateHandles
|
||||
: HandlesType::ScaleHandles;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,17 @@
|
|||
#include <qfiledialog.h>
|
||||
|
||||
enum SelectedTool {
|
||||
SELECT,
|
||||
MOVE,
|
||||
SCALE,
|
||||
ROTATE,
|
||||
TOOL_SELECT,
|
||||
TOOL_MOVE,
|
||||
TOOL_SCALE,
|
||||
TOOL_ROTATE,
|
||||
|
||||
TOOL_SMOOTH,
|
||||
TOOL_GLUE,
|
||||
TOOL_WELD,
|
||||
TOOL_STUDS,
|
||||
TOOL_INLETS,
|
||||
TOOL_UNIVERSAL,
|
||||
};
|
||||
|
||||
enum GridSnappingMode {
|
||||
|
|
|
@ -99,46 +99,6 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>toolBar</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionNew"/>
|
||||
<addaction name="actionOpen"/>
|
||||
<addaction name="actionSave"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionAddPart"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionToolSelect"/>
|
||||
<addaction name="actionToolMove"/>
|
||||
<addaction name="actionToolScale"/>
|
||||
<addaction name="actionToolRotate"/>
|
||||
<addaction name="actionToggleSpace"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionDelete"/>
|
||||
<addaction name="actionCopy"/>
|
||||
<addaction name="actionCut"/>
|
||||
<addaction name="actionPaste"/>
|
||||
<addaction name="actionPasteInto"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionGridSnap1"/>
|
||||
<addaction name="actionGridSnap05"/>
|
||||
<addaction name="actionGridSnapOff"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionToggleSimulation"/>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="outputWidget">
|
||||
<property name="windowTitle">
|
||||
<string>Output</string>
|
||||
|
@ -158,6 +118,95 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionNew"/>
|
||||
<addaction name="actionOpen"/>
|
||||
<addaction name="actionSave"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar_2">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar_2</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionToolSelect"/>
|
||||
<addaction name="actionToolMove"/>
|
||||
<addaction name="actionToolScale"/>
|
||||
<addaction name="actionToolRotate"/>
|
||||
<addaction name="actionToggleSpace"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar_3">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar_3</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionDelete"/>
|
||||
<addaction name="actionCopy"/>
|
||||
<addaction name="actionCut"/>
|
||||
<addaction name="actionPaste"/>
|
||||
<addaction name="actionPasteInto"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar_4">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar_4</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionGridSnap1"/>
|
||||
<addaction name="actionGridSnap05"/>
|
||||
<addaction name="actionGridSnapOff"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar_5">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar_5</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionToggleSimulation"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar_6">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar_6</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionToolSmooth"/>
|
||||
<addaction name="actionToolGlue"/>
|
||||
<addaction name="actionToolWeld"/>
|
||||
<addaction name="actionToolStuds"/>
|
||||
<addaction name="actionToolInlets"/>
|
||||
<addaction name="actionToolUniversal"/>
|
||||
</widget>
|
||||
<action name="actionAddPart">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
|
@ -300,8 +349,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>assets/icons/editor/snap1.png</normaloff>assets/icons/editor/snap1.png</iconset>
|
||||
<iconset theme="snap1"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1-Stud Snapping</string>
|
||||
|
@ -318,8 +366,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>assets/icons/editor/snap05.png</normaloff>assets/icons/editor/snap05.png</iconset>
|
||||
<iconset theme="snap05"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1/2-Stud Snapping</string>
|
||||
|
@ -336,8 +383,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>assets/icons/editor/snapoff.png</normaloff>assets/icons/editor/snapoff.png</iconset>
|
||||
<iconset theme="snapoff"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Grid Snapping</string>
|
||||
|
@ -468,8 +514,11 @@
|
|||
</property>
|
||||
</action>
|
||||
<action name="actionToggleSpace">
|
||||
<property name="icon">
|
||||
<iconset theme="space-local"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>L</string>
|
||||
<string>Local</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Switch between local and world space transformations</string>
|
||||
|
@ -495,6 +544,108 @@
|
|||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolStuds">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="surface-studs"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Studs</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Studs</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolInlets">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="surface-inlets"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Inlets</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Inlets</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolUniversal">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="surface-universal"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Universal</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Universal</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolSmooth">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="surface-smooth"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Smooth</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Smooth</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolWeld">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="surface-weld"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Weld</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Weld</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolGlue">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="surface-glue"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Glue</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Glue</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
ExplorerView::ExplorerView(QWidget* parent):
|
||||
QTreeView(parent),
|
||||
model(ExplorerModel(std::dynamic_pointer_cast<Instance>(dataModel))) {
|
||||
model(ExplorerModel(std::dynamic_pointer_cast<Instance>(gDataModel))) {
|
||||
|
||||
this->setModel(&model);
|
||||
// Disabling the root decoration will cause the expand/collapse chevrons to be hidden too, we don't want that
|
||||
|
@ -29,7 +29,7 @@ ExplorerView::ExplorerView(QWidget* parent):
|
|||
this->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
// Expand workspace
|
||||
this->expand(model.ObjectToIndex(workspace()));
|
||||
this->expand(model.ObjectToIndex(gWorkspace()));
|
||||
|
||||
connect(this, &QTreeView::customContextMenuRequested, this, [&](const QPoint& point) {
|
||||
QModelIndex index = this->indexAt(point);
|
||||
|
|