Spaces:
Running
Running
ahnobari
commited on
Commit
•
460c05d
1
Parent(s):
b2c81b6
init
Browse files- LInK/CAD.py +92 -0
- LInK/CurveUtils.py +21 -0
- LInK/Solver.py +174 -0
- LInK/Visulization.py +73 -0
- LInK/__init__.py +1 -0
- alpha_res.npy +0 -0
- alpha_z.npy +0 -0
- alphabet.npy +0 -0
- app.py +172 -0
- static/animation.html +23 -0
- static/csglib.js +481 -0
- static/csgworker.js +76 -0
- static/filler.html +0 -0
- static/imports.js +11 -0
- static/script.js +272 -0
- static/style.css +3 -0
- static/threecsg.js +219 -0
LInK/CAD.py
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
|
3 |
+
def get_3d_config(A,x0,nt,z_index):
|
4 |
+
|
5 |
+
A, x0, nt, z_index = np.array(A), np.array(x0), np.array(nt), np.array(z_index)
|
6 |
+
|
7 |
+
n_joints = (A.sum(-1)>0).sum()
|
8 |
+
A = A[:n_joints,:][:,:n_joints]
|
9 |
+
x0 = x0[:n_joints]
|
10 |
+
nt = nt[:n_joints]
|
11 |
+
n_links = int(A.sum()/2)
|
12 |
+
|
13 |
+
l1,l2 = np.where(np.triu(A))
|
14 |
+
|
15 |
+
linkages = []
|
16 |
+
|
17 |
+
max_len = 0
|
18 |
+
min_len = float(np.inf)
|
19 |
+
|
20 |
+
for j in range(n_links):
|
21 |
+
length= np.linalg.norm(x0[l1[j]]-x0[l2[j]])
|
22 |
+
if length>max_len:
|
23 |
+
max_len = float(length)
|
24 |
+
if length<min_len:
|
25 |
+
min_len = float(length)
|
26 |
+
|
27 |
+
scale_min = 0.25/min_len
|
28 |
+
scale_target = 1.0/max_len
|
29 |
+
scale = max(scale_min,scale_target)
|
30 |
+
|
31 |
+
x0 = x0*scale
|
32 |
+
|
33 |
+
for j in range(n_links):
|
34 |
+
length= np.linalg.norm(x0[l1[j]]-x0[l2[j]])
|
35 |
+
angle = np.arctan2(x0[l2[j]][1]-x0[l1[j]][1],x0[l2[j]][0]-x0[l1[j]][0])
|
36 |
+
linkages.append([length,0.1,0.05,0.03, angle, x0[l1[j]].tolist()+[float(z_index[j])*0.05]])
|
37 |
+
|
38 |
+
joints_max_z = np.zeros(x0.shape[0])
|
39 |
+
joints_min_z = np.zeros(x0.shape[0]) + np.inf
|
40 |
+
|
41 |
+
for i in range(n_links):
|
42 |
+
joints_max_z[l1[i]] = max(joints_max_z[l1[i]],z_index[i])
|
43 |
+
joints_max_z[l2[i]] = max(joints_max_z[l2[i]],z_index[i])
|
44 |
+
joints_min_z[l1[i]] = min(joints_min_z[l1[i]],z_index[i])
|
45 |
+
joints_min_z[l2[i]] = min(joints_min_z[l2[i]],z_index[i])
|
46 |
+
|
47 |
+
joints_max_z = joints_max_z*0.05
|
48 |
+
joints_min_z = joints_min_z*0.05
|
49 |
+
|
50 |
+
joints = []
|
51 |
+
for i in range(x0.shape[0]):
|
52 |
+
if nt[i] == 1:
|
53 |
+
for j in np.where(l1==i)[0]:
|
54 |
+
joints.append(x0[i].tolist()+[0.05,z_index[j]*0.05,1])
|
55 |
+
for j in np.where(l2==i)[0]:
|
56 |
+
joints.append(x0[i].tolist()+[0.05,z_index[j]*0.05,1])
|
57 |
+
else:
|
58 |
+
joints.append(x0[i].tolist()+[float(joints_max_z[i]-joints_min_z[i])+0.05,float(joints_min_z[i]+joints_max_z[i])/2,0])
|
59 |
+
|
60 |
+
return [linkages, joints], joints_max_z, scale
|
61 |
+
|
62 |
+
def get_animated_3d_config(A,x0,nt,z_index,sol, highlights = [-1]):
|
63 |
+
A, x0, nt, z_index, sol = np.array(A), np.array(x0), np.array(nt), np.array(z_index), np.array(sol)
|
64 |
+
configs = []
|
65 |
+
for i in range(sol.shape[1]):
|
66 |
+
c,z,s = get_3d_config(A,sol[:,i,:],nt,z_index)
|
67 |
+
configs.append(c)
|
68 |
+
|
69 |
+
if len(highlights) > 1:
|
70 |
+
highligh_curve = []
|
71 |
+
for i in highlights:
|
72 |
+
highligh_curve.append(np.pad(sol[i,:,:]*s,[[0,0],[0,1]],constant_values=z[i]+0.025))
|
73 |
+
highligh_curve = np.array(highligh_curve)
|
74 |
+
else:
|
75 |
+
highligh_curve = np.pad(sol[highlights[0],:,:]*s,[[0,0],[0,1]],constant_values=z[-1]+0.025)
|
76 |
+
|
77 |
+
return configs, highligh_curve.tolist()
|
78 |
+
|
79 |
+
def create_3d_html(A,x0,nt,z_index,sol, template_path='./static/animation.htm', save_path='./static/animated.html', highlights = [-1]):
|
80 |
+
|
81 |
+
res,hc = get_animated_3d_config(A,x0,nt,z_index,sol,highlights=highlights)
|
82 |
+
js_var = 'window.res = ' + str(res) + ';\n'
|
83 |
+
js_var += 'window.hc = ' + str(hc) + ';'
|
84 |
+
js_var += 'window.multi_high = ' + str(int(len(highlights)>1)) + ';'
|
85 |
+
|
86 |
+
with open(template_path, 'r') as file:
|
87 |
+
filedata = file.read()
|
88 |
+
|
89 |
+
filedata = filedata.replace('{res}',js_var)
|
90 |
+
|
91 |
+
with open(save_path, 'w') as file:
|
92 |
+
file.write(filedata)
|
LInK/CurveUtils.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
|
3 |
+
def uniformize(curves: torch.tensor, n: int = 200) -> torch.tensor:
|
4 |
+
with torch.no_grad():
|
5 |
+
l = torch.cumsum(torch.nn.functional.pad(torch.norm(curves[:,1:,:] - curves[:,:-1,:],dim=-1),[1,0,0,0]),-1)
|
6 |
+
l = l/l[:,-1].unsqueeze(-1)
|
7 |
+
|
8 |
+
sampling = torch.linspace(0,1,n).to(l.device).unsqueeze(0).tile([l.shape[0],1])
|
9 |
+
end_is = torch.searchsorted(l,sampling)[:,1:]
|
10 |
+
end_ids = end_is.unsqueeze(-1).tile([1,1,2])
|
11 |
+
|
12 |
+
l_end = torch.gather(l,1,end_is)
|
13 |
+
l_start = torch.gather(l,1,end_is-1)
|
14 |
+
ws = (l_end - sampling[:,1:])/(l_end-l_start)
|
15 |
+
|
16 |
+
end_gather = torch.gather(curves,1,end_ids)
|
17 |
+
start_gather = torch.gather(curves,1,end_ids-1)
|
18 |
+
|
19 |
+
uniform_curves = torch.cat([curves[:,0:1,:],(end_gather - (end_gather-start_gather)*ws.unsqueeze(-1))],1)
|
20 |
+
|
21 |
+
return uniform_curves
|
LInK/Solver.py
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
|
3 |
+
# Dyadic Solution Path Finder
|
4 |
+
def find_path(A, motor = [0,1], fixed_nodes=[0, 1]):
|
5 |
+
'''
|
6 |
+
This function finds the solution path of a dyadic mechanism.
|
7 |
+
|
8 |
+
Parameters:
|
9 |
+
A (np.array): Adjacency matrix of the mechanism.
|
10 |
+
motor (list): motor nodes.
|
11 |
+
fixed_nodes (list): List of fixed nodes.
|
12 |
+
|
13 |
+
Returns:
|
14 |
+
path (np.array): Solution path of the mechanism.
|
15 |
+
status (bool): True if the mechanism is dyadic and has a solution path, False otherwise.
|
16 |
+
'''
|
17 |
+
|
18 |
+
path = []
|
19 |
+
|
20 |
+
A,fixed_nodes,motor = np.array(A),np.array(fixed_nodes),np.array(motor)
|
21 |
+
|
22 |
+
unkowns = np.array(list(range(A.shape[0])))
|
23 |
+
knowns = np.concatenate([fixed_nodes,[motor[-1]]])
|
24 |
+
|
25 |
+
unkowns = unkowns[np.logical_not(np.isin(unkowns,knowns))]
|
26 |
+
|
27 |
+
counter = 0
|
28 |
+
while unkowns.shape[0] != 0:
|
29 |
+
|
30 |
+
if counter == unkowns.shape[0]:
|
31 |
+
# Non dyadic or DOF larger than 1
|
32 |
+
return [], False
|
33 |
+
n = unkowns[counter]
|
34 |
+
ne = np.where(A[n])[0]
|
35 |
+
|
36 |
+
kne = knowns[np.isin(knowns,ne)]
|
37 |
+
# print(kne.shape[0])
|
38 |
+
|
39 |
+
if kne.shape[0] == 2:
|
40 |
+
|
41 |
+
path.append([n,kne[0],kne[1]])
|
42 |
+
counter = 0
|
43 |
+
knowns = np.concatenate([knowns,[n]])
|
44 |
+
unkowns = unkowns[unkowns!=n]
|
45 |
+
elif kne.shape[0] > 2:
|
46 |
+
#redundant or overconstraint
|
47 |
+
return [], False
|
48 |
+
else:
|
49 |
+
counter += 1
|
50 |
+
|
51 |
+
return np.array(path), True
|
52 |
+
|
53 |
+
# Dyadic Mechanism Sorting
|
54 |
+
def get_order(A, motor = [0,1], fixed_nodes=[0, 1]):
|
55 |
+
'''
|
56 |
+
This function sorts the mechanism based on the solution path.
|
57 |
+
|
58 |
+
Parameters:
|
59 |
+
A (np.array): Adjacency matrix of the mechanism.
|
60 |
+
motor (list): motor nodes.
|
61 |
+
fixed_nodes (list): List of fixed nodes.
|
62 |
+
|
63 |
+
Returns:
|
64 |
+
joint order (np.array): Sorted order of the joints in a mechanism.
|
65 |
+
'''
|
66 |
+
path, status = find_path(A, motor, fixed_nodes)
|
67 |
+
fixed_nodes = np.array(fixed_nodes)
|
68 |
+
if status:
|
69 |
+
return np.concatenate([motor,fixed_nodes[fixed_nodes!=motor[0]],path[:,0]])
|
70 |
+
else:
|
71 |
+
raise Exception("Non Dyadic or Dof larger than 1")
|
72 |
+
|
73 |
+
def sort_mechanism(A, x0, motor = [0,1], fixed_nodes=[0, 1]):
|
74 |
+
'''
|
75 |
+
This function sorts the mechanism based on the solution path.
|
76 |
+
|
77 |
+
Parameters:
|
78 |
+
A (np.array): Adjacency matrix of the mechanism.
|
79 |
+
x0 (np.array): Initial positions of the joints.
|
80 |
+
motor (list): motor nodes.
|
81 |
+
fixed_nodes (list): List of fixed nodes.
|
82 |
+
|
83 |
+
Returns:
|
84 |
+
A_s (np.array): Sorted adjacency matrix of the mechanism.
|
85 |
+
x0 (np.array): Sorted initial positions of the joints.
|
86 |
+
motor (np.array): Motor nodes.
|
87 |
+
fixed_nodes (np.array): Fixed nodes.
|
88 |
+
ord (np.array): Sorted order of the joints in a mechanism.
|
89 |
+
'''
|
90 |
+
ord = get_order(A, motor, fixed_nodes)
|
91 |
+
|
92 |
+
n_t = np.zeros(A.shape[0])
|
93 |
+
n_t[fixed_nodes] = 1
|
94 |
+
|
95 |
+
A_s = A[ord,:][:,ord]
|
96 |
+
n_t_s = n_t[ord]
|
97 |
+
|
98 |
+
return A_s, x0[ord], np.array([0,1]), np.where(n_t_s)[0], ord
|
99 |
+
|
100 |
+
# Vectorized Dyadic Solver
|
101 |
+
def solve_rev_vectorized_batch_CPU(As,x0s,node_types,thetas):
|
102 |
+
|
103 |
+
Gs = np.square((np.expand_dims(x0s,1) - np.expand_dims(x0s,2))).sum(-1)
|
104 |
+
|
105 |
+
x = np.zeros([x0s.shape[0],x0s.shape[1],thetas.shape[0],2])
|
106 |
+
|
107 |
+
x = x + np.expand_dims(node_types * x0s,2)
|
108 |
+
|
109 |
+
m = x[:,0] + np.tile(np.expand_dims(np.swapaxes(np.concatenate([np.expand_dims(np.cos(thetas),0),np.expand_dims(np.sin(thetas),0)],0),0,1),0),[x0s.shape[0],1,1]) * np.expand_dims(np.expand_dims(np.sqrt(Gs[:,0,1]),-1),-1)
|
110 |
+
|
111 |
+
m = np.expand_dims(m,1)
|
112 |
+
m = np.pad(m,[[0,0],[1,x0s.shape[1]-2],[0,0],[0,0]],mode='constant')
|
113 |
+
x += m
|
114 |
+
|
115 |
+
for k in range(3,x0s.shape[1]):
|
116 |
+
|
117 |
+
inds = np.argsort(As[:,k,0:k])[:,-2:]
|
118 |
+
|
119 |
+
l_ijs = np.linalg.norm(x[np.arange(x0s.shape[0]),inds[:,0]] - x[np.arange(x0s.shape[0]),inds[:,1]], axis=-1)
|
120 |
+
|
121 |
+
gik = np.sqrt(np.expand_dims(Gs[np.arange(x0s.shape[0]),inds[:,0],np.ones(shape=[x0s.shape[0]],dtype=int)*k],-1))
|
122 |
+
gjk = np.sqrt(np.expand_dims(Gs[np.arange(x0s.shape[0]),inds[:,1],np.ones(shape=[x0s.shape[0]],dtype=int)*k],-1))
|
123 |
+
|
124 |
+
cosphis = (np.square(l_ijs) + np.square(gik) - np.square(gjk))/(2 * l_ijs * gik)
|
125 |
+
|
126 |
+
cosphis = np.where(np.tile(node_types[:,k],[1,thetas.shape[0]])==0.0,cosphis,np.zeros_like(cosphis))
|
127 |
+
|
128 |
+
x0i1 = x0s[np.arange(x0s.shape[0]),inds[:,0],np.ones(shape=[x0s.shape[0]]).astype(np.int32)]
|
129 |
+
x0i0 = x0s[np.arange(x0s.shape[0]),inds[:,0],np.zeros(shape=[x0s.shape[0]]).astype(np.int32)]
|
130 |
+
|
131 |
+
x0j1 = x0s[np.arange(x0s.shape[0]),inds[:,1],np.ones(shape=[x0s.shape[0]]).astype(np.int32)]
|
132 |
+
x0j0 = x0s[np.arange(x0s.shape[0]),inds[:,1],np.zeros(shape=[x0s.shape[0]]).astype(np.int32)]
|
133 |
+
|
134 |
+
x0k1 = x0s[:,k,1]
|
135 |
+
x0k0 = x0s[:,k,0]
|
136 |
+
|
137 |
+
s = np.expand_dims(np.sign((x0i1-x0k1)*(x0i0-x0j0) - (x0i1-x0j1)*(x0i0-x0k0)),-1)
|
138 |
+
|
139 |
+
|
140 |
+
phi = s * np.arccos(cosphis)
|
141 |
+
|
142 |
+
a = np.transpose(np.concatenate([np.expand_dims(np.cos(phi),0),np.expand_dims(-np.sin(phi),0)],0),axes=[1,2,0])
|
143 |
+
b = np.transpose(np.concatenate([np.expand_dims(np.sin(phi),0),np.expand_dims(np.cos(phi),0)],0),axes=[1,2,0])
|
144 |
+
|
145 |
+
R = np.einsum("ijk...->jki...", np.concatenate([np.expand_dims(a,0),np.expand_dims(b,0)],0))
|
146 |
+
|
147 |
+
xi = x[np.arange(x0s.shape[0]),inds[:,0]]
|
148 |
+
xj = x[np.arange(x0s.shape[0]),inds[:,1]]
|
149 |
+
|
150 |
+
scaled_ij = (xj-xi)/np.expand_dims(l_ijs,-1) * np.expand_dims(gik,-1)
|
151 |
+
|
152 |
+
x_k = np.squeeze(np.matmul(R, np.expand_dims(scaled_ij,-1))) + xi
|
153 |
+
x_k = np.where(np.tile(np.expand_dims(node_types[:,k],-1),[1,thetas.shape[0],2])==0.0,x_k,np.zeros_like(x_k))
|
154 |
+
|
155 |
+
x_k = np.expand_dims(x_k,1)
|
156 |
+
x_k = np.pad(x_k,[[0,0],[k,x0s.shape[1]-k-1],[0,0],[0,0]],mode='constant')
|
157 |
+
|
158 |
+
x += x_k
|
159 |
+
return x
|
160 |
+
|
161 |
+
# Solve a single mechanism
|
162 |
+
def solve_mechanism(A, x0 , motor = [0,1], fixed_nodes=[0, 1], thetas = np.linspace(0,2*np.pi,200)):
|
163 |
+
|
164 |
+
A,x0,motor,fixed_nodes,ord = sort_mechanism(A, x0, motor, fixed_nodes)
|
165 |
+
n_t = np.zeros([A.shape[0],1])
|
166 |
+
n_t[fixed_nodes] = 1
|
167 |
+
|
168 |
+
A = np.expand_dims(A,0)
|
169 |
+
x0 = np.expand_dims(x0,0)
|
170 |
+
n_t = np.expand_dims(n_t,0)
|
171 |
+
|
172 |
+
sol = solve_rev_vectorized_batch_CPU(A,x0,n_t,thetas)
|
173 |
+
|
174 |
+
return sol[0], ord
|
LInK/Visulization.py
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .Solver import solve_mechanism
|
2 |
+
import numpy as np
|
3 |
+
import matplotlib.pyplot as plt
|
4 |
+
from io import StringIO
|
5 |
+
import xml.etree.ElementTree as etree
|
6 |
+
from svgpath2mpl import parse_path
|
7 |
+
|
8 |
+
def draw_mechanism(A,x0,fixed_nodes=None,motor=[0,1],node_types=None, ax=None, highlight=None, solve=True, thetas = np.linspace(0,np.pi*2,200), def_alpha = 1.0, h_alfa =1.0, h_c = "#f15a24"):
|
9 |
+
|
10 |
+
if fixed_nodes is None and node_types is None:
|
11 |
+
raise ValueError("Either fixed_nodes or node_types should be provided")
|
12 |
+
|
13 |
+
if fixed_nodes is None:
|
14 |
+
fixed_nodes = np.where(node_types)[0]
|
15 |
+
|
16 |
+
def fetch_path():
|
17 |
+
# r = requests.get(svg_url)
|
18 |
+
root = etree.parse(StringIO('<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 620 338"><defs><style>.cls-1{fill:#1a1a1a;stroke:#1a1a1a;stroke-linecap:round;stroke-miterlimit:10;stroke-width:20px;}</style></defs><path class="cls-1" d="M45.5,358.5l70.71-70.71M46,287.5H644m-507.61,71,70.72-70.71M223,358.5l70.71-70.71m20.18,70.72,70.71-70.71m13.67,70.7,70.71-70.71m20.19,70.72,70.71-70.71m15.84,70.71,70.71-70.71M345,39.62A121.38,121.38,0,1,1,223.62,161,121.38,121.38,0,0,1,345,39.62Z" transform="translate(-35.5 -29.62)"/></svg>')).getroot()
|
19 |
+
view_box = root.attrib.get('viewBox')
|
20 |
+
if view_box is not None:
|
21 |
+
view_box = [int(x) for x in view_box.split()]
|
22 |
+
xlim = (view_box[0], view_box[0] + view_box[2])
|
23 |
+
ylim = (view_box[1] + view_box[3], view_box[1])
|
24 |
+
else:
|
25 |
+
xlim = (0, 500)
|
26 |
+
ylim = (500, 0)
|
27 |
+
path_elem = root.findall('.//{http://www.w3.org/2000/svg}path')[0]
|
28 |
+
return xlim, ylim, parse_path(path_elem.attrib['d'])
|
29 |
+
_,_,p = fetch_path()
|
30 |
+
p.vertices -= p.vertices.mean(axis=0)
|
31 |
+
p.vertices = (np.array([[np.cos(np.pi), -np.sin(np.pi)],[np.sin(np.pi), np.cos(np.pi)]])@p.vertices.T).T
|
32 |
+
|
33 |
+
if ax is None:
|
34 |
+
fig, ax = plt.subplots(figsize=(10,10))
|
35 |
+
|
36 |
+
A,x0,fixed_nodes,motor = np.array(A),np.array(x0),np.array(fixed_nodes),np.array(motor)
|
37 |
+
|
38 |
+
x = x0
|
39 |
+
|
40 |
+
N = A.shape[0]
|
41 |
+
for i in range(N):
|
42 |
+
if i in fixed_nodes:
|
43 |
+
if i == highlight:
|
44 |
+
ax.scatter(x[i,0],x[i,1],color=h_c,s=700,zorder=10,marker=p)
|
45 |
+
else:
|
46 |
+
ax.scatter(x[i,0],x[i,1],color="#1a1a1a",s=700,zorder=10,marker=p)
|
47 |
+
else:
|
48 |
+
if i == highlight:
|
49 |
+
ax.scatter(x[i,0],x[i,1],color=h_c,s=100,zorder=10,facecolors=h_c,alpha=0.7)
|
50 |
+
else:
|
51 |
+
ax.scatter(x[i,0],x[i,1],color="#1a1a1a",s=100,zorder=10,facecolors='#ffffff',alpha=0.7)
|
52 |
+
|
53 |
+
for j in range(i+1,N):
|
54 |
+
if A[i,j]:
|
55 |
+
if (motor[0] == i and motor[1] == j) or(motor[0] == j and motor[1] == i):
|
56 |
+
ax.plot([x[i,0],x[j,0]],[x[i,1],x[j,1]],color="#ffc800",linewidth=4.5)
|
57 |
+
else:
|
58 |
+
ax.plot([x[i,0],x[j,0]],[x[i,1],x[j,1]],color="#1a1a1a",linewidth=4.5,alpha=0.6)
|
59 |
+
|
60 |
+
if solve:
|
61 |
+
sol,ord = solve_mechanism(A,x0,motor,fixed_nodes,thetas)
|
62 |
+
x = sol
|
63 |
+
|
64 |
+
for i in range(A.shape[0]):
|
65 |
+
if not ord[i] in fixed_nodes:
|
66 |
+
if ord[i] == highlight:
|
67 |
+
ax.plot(x[i,:,0],x[i,:,1],'-',color=h_c,linewidth=4.5,alpha=h_alfa)
|
68 |
+
else:
|
69 |
+
ax.plot(x[i,:,0],x[i,:,1],'--',color="#0078a7",linewidth=1.5, alpha=def_alpha)
|
70 |
+
ax.axis('equal')
|
71 |
+
ax.axis('off')
|
72 |
+
|
73 |
+
return ax
|
LInK/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
'''Code Base For LInK Project'''
|
alpha_res.npy
ADDED
Binary file (473 kB). View file
|
|
alpha_z.npy
ADDED
Binary file (7.16 kB). View file
|
|
alphabet.npy
ADDED
Binary file (83.3 kB). View file
|
|
app.py
ADDED
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import uuid
|
3 |
+
import argparse
|
4 |
+
|
5 |
+
argparser = argparse.ArgumentParser()
|
6 |
+
argparser.add_argument("--port", type=int, default=1239, help="Port number for the local server")
|
7 |
+
argparser.add_argument("--cuda_device", type=str, default='0', help="Cuda devices to use. Default is 0")
|
8 |
+
argparser.add_argument("--static_folder", type=str, default='static', help="Folder to store static files")
|
9 |
+
args = argparser.parse_args()
|
10 |
+
|
11 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = ''
|
12 |
+
|
13 |
+
import gradio as gr
|
14 |
+
from pathlib import Path
|
15 |
+
|
16 |
+
import numpy as np
|
17 |
+
import torch
|
18 |
+
|
19 |
+
from LInK.CAD import create_3d_html
|
20 |
+
import numpy as np
|
21 |
+
from LInK.CurveUtils import uniformize
|
22 |
+
import torch
|
23 |
+
import matplotlib.pyplot as plt
|
24 |
+
|
25 |
+
from LInK.Solver import solve_rev_vectorized_batch_CPU
|
26 |
+
|
27 |
+
# turn off gradient computation
|
28 |
+
torch.set_grad_enabled(False)
|
29 |
+
|
30 |
+
results = np.load('alpha_res.npy',allow_pickle=True)
|
31 |
+
alphabet_test = np.load('alphabet.npy')
|
32 |
+
zs_ = np.load('alpha_z.npy',allow_pickle=True)
|
33 |
+
|
34 |
+
for i in range(zs_.shape[0]):
|
35 |
+
zs_[i] = zs_[i] - zs_[i].min()
|
36 |
+
|
37 |
+
curves__ = torch.tensor(alphabet_test).float()
|
38 |
+
curves__ = curves__ - curves__.mean(1).unsqueeze(1)
|
39 |
+
max_idx = torch.square(curves__).sum(-1).argmax(dim=1)
|
40 |
+
theta = torch.atan2(curves__[torch.arange(curves__.shape[0]),max_idx,1],curves__[torch.arange(curves__.shape[0]),max_idx,0]).numpy()
|
41 |
+
|
42 |
+
curves_ = []
|
43 |
+
for i in range(len(results)):
|
44 |
+
curves_.append(results[i][-1])
|
45 |
+
|
46 |
+
curves_ = torch.tensor(curves_).float()
|
47 |
+
curves_ = uniformize(curves_,200)
|
48 |
+
curves_ = curves_ - curves_.mean(1).unsqueeze(1)
|
49 |
+
max_idx = torch.square(curves_).sum(-1).argmax(dim=1)
|
50 |
+
theta2 = torch.atan2(curves_[torch.arange(curves_.shape[0]),max_idx,1],curves_[torch.arange(curves_.shape[0]),max_idx,0]).numpy()
|
51 |
+
|
52 |
+
alphas = []
|
53 |
+
letter_heights = []
|
54 |
+
letter_centers = []
|
55 |
+
letter_widths = []
|
56 |
+
|
57 |
+
for i in range(len(results)):
|
58 |
+
A, x0, node_type, start_theta, end_theta, tr = results[i][0]
|
59 |
+
alpha = theta[i] - theta2[i]
|
60 |
+
if i == 21:
|
61 |
+
alpha -= np.pi/2.5
|
62 |
+
if i == 7:
|
63 |
+
alpha -= np.pi/3
|
64 |
+
if i == 4:
|
65 |
+
alpha += np.pi/36
|
66 |
+
alphas.append(alpha)
|
67 |
+
R = np.array([[np.cos(alpha), -np.sin(alpha)],[np.sin(alpha), np.cos(alpha)]]).squeeze()
|
68 |
+
transformed_curve = (R@results[i][-1].T).T
|
69 |
+
CD,OD,_ = results[i][1]
|
70 |
+
sol = solve_rev_vectorized_batch_CPU(A[None],x0[None],node_type[None],np.linspace(start_theta,end_theta,2000))[0]
|
71 |
+
sol_curve = (R@sol[-1].T).T
|
72 |
+
|
73 |
+
n_left = len(results) - i
|
74 |
+
n_left_row = 10 - i%10
|
75 |
+
|
76 |
+
letter_heights.append(transformed_curve[:,1].max()-transformed_curve[:,1].min())
|
77 |
+
letter_widths.append(transformed_curve[:,0].max()-transformed_curve[:,0].min())
|
78 |
+
letter_centers.append([(transformed_curve[:,0].max() + transformed_curve[:,0].min())/2,(transformed_curve[:,1].max() + transformed_curve[:,1].min())/2])
|
79 |
+
|
80 |
+
alphas = np.array(alphas)
|
81 |
+
letter_heights = np.array(letter_heights)
|
82 |
+
letter_centers = np.array(letter_centers)
|
83 |
+
letter_widths = np.array(letter_widths)
|
84 |
+
|
85 |
+
alphabet_dict = {'A':0,'B':1,'C':2,'D':3,'E':4,'F':5,'G':6,'H':7,'I':8,'J':9,'K':10,'L':11,'M':12,'N':13,'O':14,'P':16,'Q':15,'R':17,'S':18,'T':19,'U':20,'V':21,'W':22,'X':23,'Y':24,'Z':25}
|
86 |
+
|
87 |
+
def create_mech(target_text):
|
88 |
+
target_text = target_text.replace(' ','').upper()
|
89 |
+
target_height = 1.
|
90 |
+
spacing = 0.2
|
91 |
+
letters = [alphabet_dict[l] for l in target_text]
|
92 |
+
|
93 |
+
translations = []
|
94 |
+
scaling = []
|
95 |
+
|
96 |
+
transformed_curves = []
|
97 |
+
|
98 |
+
mechs = []
|
99 |
+
|
100 |
+
total_size = 0
|
101 |
+
|
102 |
+
for i,l in enumerate(letters):
|
103 |
+
A, x0, node_type, start_theta, end_theta, tr = results[l][0]
|
104 |
+
alpha = alphas[l]
|
105 |
+
R = np.array([[np.cos(alpha), -np.sin(alpha)],[np.sin(alpha), np.cos(alpha)]]).squeeze()
|
106 |
+
transformed_curve = (R@results[l][-1].T).T
|
107 |
+
|
108 |
+
s = target_height/letter_heights[l]
|
109 |
+
scaling.append(s)
|
110 |
+
|
111 |
+
if i>0:
|
112 |
+
trans = [translations[-1][0] + letter_widths[letters[i-1]]/2 * scaling[i-1] + letter_widths[l]/2 * s + spacing , 0]
|
113 |
+
else:
|
114 |
+
trans = [letter_widths[l]/2 * s ,0]
|
115 |
+
|
116 |
+
translations.append(trans)
|
117 |
+
|
118 |
+
transformed_curves.append(s*(transformed_curve - letter_centers[l]) + trans)
|
119 |
+
|
120 |
+
mechs.append([A,s*((R@x0.T).T - letter_centers[l]) + trans,node_type,start_theta+alpha,end_theta+alpha])
|
121 |
+
total_size += A.shape[0]
|
122 |
+
|
123 |
+
A_all = np.zeros((total_size,total_size))
|
124 |
+
x0_all = np.zeros((total_size,2))
|
125 |
+
node_type_all = np.zeros((total_size,1))
|
126 |
+
|
127 |
+
current_count = 0
|
128 |
+
sols = []
|
129 |
+
highlights = []
|
130 |
+
zs = []
|
131 |
+
for i,m in enumerate(mechs):
|
132 |
+
A, x0, node_type, start_theta, end_theta = m
|
133 |
+
A_all[current_count:current_count+A.shape[0],current_count:current_count+A.shape[0]] = A
|
134 |
+
# x0_all[current_count:current_count+A.shape[0]] = x0
|
135 |
+
node_type_all[current_count:current_count+A.shape[0]] = node_type
|
136 |
+
|
137 |
+
if i ==0:
|
138 |
+
highlights.append(A.shape[0]-1)
|
139 |
+
else:
|
140 |
+
highlights.append(A.shape[0]+highlights[-1])
|
141 |
+
|
142 |
+
sol = solve_rev_vectorized_batch_CPU(A[None],x0[None],node_type[None],np.linspace(start_theta,end_theta,100))[0]
|
143 |
+
sols.append(sol.transpose(1,0,2))
|
144 |
+
|
145 |
+
x0_all[current_count:current_count+A.shape[0]] = sol[:,0,:]
|
146 |
+
current_count += A.shape[0]
|
147 |
+
z = zs_[letters[i]]
|
148 |
+
zs.append(z + zs[-1].max() + 1 if i>0 else z)
|
149 |
+
|
150 |
+
sols = np.concatenate(sols,axis=1)
|
151 |
+
zs = np.concatenate(zs)
|
152 |
+
|
153 |
+
uuid_ = str(uuid.uuid4())
|
154 |
+
|
155 |
+
create_3d_html(A_all, x0_all, node_type_all, zs, np.concatenate([sols.transpose(1,0,2),sols.transpose(1,0,2)[:,::-1,:]],1), template_path = f'./static/animation.html', save_path=f'./static/{uuid_}.html', highlights=highlights)
|
156 |
+
|
157 |
+
return gr.HTML(f'<iframe width="100%" height="800px" src="file=static/{uuid_}.html"></iframe>',label="3D Plot",elem_classes="plot3d")
|
158 |
+
|
159 |
+
gr.set_static_paths(paths=[Path(f'./{args.static_folder}')])
|
160 |
+
|
161 |
+
with gr.Blocks() as block:
|
162 |
+
with gr.Row():
|
163 |
+
with gr.Column():
|
164 |
+
text = gr.Textbox(label="Enter a word (spaces will be ignored)", value='DECODE')
|
165 |
+
btn = gr.Button(value="Create Mechanism", variant="primary")
|
166 |
+
|
167 |
+
plot_3d = gr.HTML('<iframe width="100%" height="800px" src="file=static/filler.html"></iframe>',label="3D Plot",elem_classes="plot3d")
|
168 |
+
|
169 |
+
event1 = btn.click(create_mech, inputs=[text], outputs=[plot_3d])
|
170 |
+
|
171 |
+
block.launch(server_port=args.port)
|
172 |
+
|
static/animation.html
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<html>
|
2 |
+
<head>
|
3 |
+
<title>Mechanism Animation</title>
|
4 |
+
<script type="importmap">
|
5 |
+
{
|
6 |
+
"imports": {
|
7 |
+
"three": "https://cdn.jsdelivr.net/npm/three@0.164.1/build/three.module.js",
|
8 |
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.164.1/examples/jsm/"
|
9 |
+
}
|
10 |
+
}
|
11 |
+
</script>
|
12 |
+
<!--Import style.css-->
|
13 |
+
<link rel="stylesheet" type="text/css" href="style.css">
|
14 |
+
|
15 |
+
</head>
|
16 |
+
<body>
|
17 |
+
<div id="container"></div>
|
18 |
+
<script>
|
19 |
+
{res}
|
20 |
+
</script>
|
21 |
+
<script src="script.js" type="module"></script>
|
22 |
+
</body>
|
23 |
+
</html>
|
static/csglib.js
ADDED
@@ -0,0 +1,481 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
// ## License
|
3 |
+
//
|
4 |
+
// Copyright (c) 2011 Evan Wallace (http://madebyevan.com/), under the MIT license.
|
5 |
+
// THREE.js rework by thrax
|
6 |
+
|
7 |
+
// # class CSG
|
8 |
+
// Holds a binary space partition tree representing a 3D solid. Two solids can
|
9 |
+
// be combined using the `union()`, `subtract()`, and `intersect()` methods.
|
10 |
+
|
11 |
+
|
12 |
+
class CSG {
|
13 |
+
constructor() {
|
14 |
+
this.polygons = [];
|
15 |
+
}
|
16 |
+
clone() {
|
17 |
+
let csg = new CSG();
|
18 |
+
csg.polygons = this.polygons.map(p=>p.clone())
|
19 |
+
return csg;
|
20 |
+
}
|
21 |
+
|
22 |
+
toPolygons() {
|
23 |
+
return this.polygons;
|
24 |
+
}
|
25 |
+
|
26 |
+
union(csg) {
|
27 |
+
let a = new Node(this.clone().polygons);
|
28 |
+
let b = new Node(csg.clone().polygons);
|
29 |
+
a.clipTo(b);
|
30 |
+
b.clipTo(a);
|
31 |
+
b.invert();
|
32 |
+
b.clipTo(a);
|
33 |
+
b.invert();
|
34 |
+
a.build(b.allPolygons());
|
35 |
+
return CSG.fromPolygons(a.allPolygons());
|
36 |
+
}
|
37 |
+
|
38 |
+
subtract(csg) {
|
39 |
+
let a = new Node(this.clone().polygons);
|
40 |
+
let b = new Node(csg.clone().polygons);
|
41 |
+
a.invert();
|
42 |
+
a.clipTo(b);
|
43 |
+
b.clipTo(a);
|
44 |
+
b.invert();
|
45 |
+
b.clipTo(a);
|
46 |
+
b.invert();
|
47 |
+
a.build(b.allPolygons());
|
48 |
+
a.invert();
|
49 |
+
return CSG.fromPolygons(a.allPolygons());
|
50 |
+
}
|
51 |
+
|
52 |
+
intersect(csg) {
|
53 |
+
let a = new Node(this.clone().polygons);
|
54 |
+
let b = new Node(csg.clone().polygons);
|
55 |
+
a.invert();
|
56 |
+
b.clipTo(a);
|
57 |
+
b.invert();
|
58 |
+
a.clipTo(b);
|
59 |
+
b.clipTo(a);
|
60 |
+
a.build(b.allPolygons());
|
61 |
+
a.invert();
|
62 |
+
return CSG.fromPolygons(a.allPolygons());
|
63 |
+
}
|
64 |
+
|
65 |
+
// Return a new CSG solid with solid and empty space switched. This solid is
|
66 |
+
// not modified.
|
67 |
+
inverse() {
|
68 |
+
let csg = this.clone();
|
69 |
+
csg.polygons.forEach(p=>p.flip());
|
70 |
+
return csg;
|
71 |
+
}
|
72 |
+
}
|
73 |
+
|
74 |
+
// Construct a CSG solid from a list of `Polygon` instances.
|
75 |
+
CSG.fromPolygons=function(polygons) {
|
76 |
+
let csg = new CSG();
|
77 |
+
csg.polygons = polygons;
|
78 |
+
return csg;
|
79 |
+
}
|
80 |
+
|
81 |
+
// # class Vector
|
82 |
+
|
83 |
+
// Represents a 3D vector.
|
84 |
+
//
|
85 |
+
// Example usage:
|
86 |
+
//
|
87 |
+
// new CSG.Vector(1, 2, 3);
|
88 |
+
|
89 |
+
|
90 |
+
|
91 |
+
class Vector {
|
92 |
+
constructor(x=0, y=0, z=0) {
|
93 |
+
this.x=x;
|
94 |
+
this.y=y;
|
95 |
+
this.z=z;
|
96 |
+
}
|
97 |
+
copy(v){
|
98 |
+
this.x=v.x;
|
99 |
+
this.y=v.y;
|
100 |
+
this.z=v.z;
|
101 |
+
return this
|
102 |
+
}
|
103 |
+
clone() {
|
104 |
+
return new Vector(this.x,this.y,this.z)
|
105 |
+
}
|
106 |
+
negate() {
|
107 |
+
this.x*=-1;
|
108 |
+
this.y*=-1;
|
109 |
+
this.z*=-1;
|
110 |
+
return this
|
111 |
+
}
|
112 |
+
add(a) {
|
113 |
+
this.x+=a.x
|
114 |
+
this.y+=a.y
|
115 |
+
this.z+=a.z
|
116 |
+
return this;
|
117 |
+
}
|
118 |
+
sub(a) {
|
119 |
+
this.x-=a.x
|
120 |
+
this.y-=a.y
|
121 |
+
this.z-=a.z
|
122 |
+
return this
|
123 |
+
}
|
124 |
+
times(a) {
|
125 |
+
this.x*=a
|
126 |
+
this.y*=a
|
127 |
+
this.z*=a
|
128 |
+
return this
|
129 |
+
}
|
130 |
+
dividedBy(a) {
|
131 |
+
this.x/=a
|
132 |
+
this.y/=a
|
133 |
+
this.z/=a
|
134 |
+
return this
|
135 |
+
}
|
136 |
+
lerp(a, t) {
|
137 |
+
return this.add(tv0.copy(a).sub(this).times(t))
|
138 |
+
}
|
139 |
+
unit() {
|
140 |
+
return this.dividedBy(this.length())
|
141 |
+
}
|
142 |
+
length(){
|
143 |
+
return Math.sqrt((this.x**2)+(this.y**2)+(this.z**2))
|
144 |
+
}
|
145 |
+
normalize(){
|
146 |
+
return this.unit()
|
147 |
+
}
|
148 |
+
cross(b) {
|
149 |
+
let a = this;
|
150 |
+
const ax = a.x, ay = a.y, az = a.z;
|
151 |
+
const bx = b.x, by = b.y, bz = b.z;
|
152 |
+
|
153 |
+
this.x = ay * bz - az * by;
|
154 |
+
this.y = az * bx - ax * bz;
|
155 |
+
this.z = ax * by - ay * bx;
|
156 |
+
|
157 |
+
return this;
|
158 |
+
}
|
159 |
+
dot(b){
|
160 |
+
return (this.x*b.x)+(this.y*b.y)+(this.z*b.z)
|
161 |
+
}
|
162 |
+
}
|
163 |
+
|
164 |
+
//Temporaries used to avoid internal allocation..
|
165 |
+
let tv0=new Vector()
|
166 |
+
let tv1=new Vector()
|
167 |
+
|
168 |
+
|
169 |
+
// # class Vertex
|
170 |
+
|
171 |
+
// Represents a vertex of a polygon. Use your own vertex class instead of this
|
172 |
+
// one to provide additional features like texture coordinates and vertex
|
173 |
+
// colors. Custom vertex classes need to provide a `pos` property and `clone()`,
|
174 |
+
// `flip()`, and `interpolate()` methods that behave analogous to the ones
|
175 |
+
// defined by `CSG.Vertex`. This class provides `normal` so convenience
|
176 |
+
// functions like `CSG.sphere()` can return a smooth vertex normal, but `normal`
|
177 |
+
// is not used anywhere else.
|
178 |
+
|
179 |
+
class Vertex {
|
180 |
+
|
181 |
+
constructor(pos, normal, uv, color) {
|
182 |
+
this.pos = new Vector().copy(pos);
|
183 |
+
this.normal = new Vector().copy(normal);
|
184 |
+
uv && (this.uv = new Vector().copy(uv)) && (this.uv.z=0);
|
185 |
+
color && (this.color = new Vector().copy(color));
|
186 |
+
}
|
187 |
+
|
188 |
+
clone() {
|
189 |
+
return new Vertex(this.pos,this.normal,this.uv,this.color);
|
190 |
+
}
|
191 |
+
|
192 |
+
// Invert all orientation-specific data (e.g. vertex normal). Called when the
|
193 |
+
// orientation of a polygon is flipped.
|
194 |
+
flip() {
|
195 |
+
this.normal.negate();
|
196 |
+
}
|
197 |
+
|
198 |
+
// Create a new vertex between this vertex and `other` by linearly
|
199 |
+
// interpolating all properties using a parameter of `t`. Subclasses should
|
200 |
+
// override this to interpolate additional properties.
|
201 |
+
interpolate(other, t) {
|
202 |
+
return new Vertex(this.pos.clone().lerp(other.pos, t),this.normal.clone().lerp(other.normal, t),this.uv&&other.uv&&this.uv.clone().lerp(other.uv, t), this.color&&other.color&&this.color.clone().lerp(other.color,t))
|
203 |
+
}
|
204 |
+
}
|
205 |
+
;
|
206 |
+
// # class Plane
|
207 |
+
|
208 |
+
// Represents a plane in 3D space.
|
209 |
+
|
210 |
+
class Plane {
|
211 |
+
constructor(normal, w) {
|
212 |
+
this.normal = normal;
|
213 |
+
this.w = w;
|
214 |
+
}
|
215 |
+
|
216 |
+
clone() {
|
217 |
+
return new Plane(this.normal.clone(),this.w);
|
218 |
+
}
|
219 |
+
|
220 |
+
flip() {
|
221 |
+
this.normal.negate();
|
222 |
+
this.w = -this.w;
|
223 |
+
}
|
224 |
+
|
225 |
+
// Split `polygon` by this plane if needed, then put the polygon or polygon
|
226 |
+
// fragments in the appropriate lists. Coplanar polygons go into either
|
227 |
+
// `coplanarFront` or `coplanarBack` depending on their orientation with
|
228 |
+
// respect to this plane. Polygons in front or in back of this plane go into
|
229 |
+
// either `front` or `back`.
|
230 |
+
splitPolygon(polygon, coplanarFront, coplanarBack, front, back) {
|
231 |
+
const COPLANAR = 0;
|
232 |
+
const FRONT = 1;
|
233 |
+
const BACK = 2;
|
234 |
+
const SPANNING = 3;
|
235 |
+
|
236 |
+
// Classify each point as well as the entire polygon into one of the above
|
237 |
+
// four classes.
|
238 |
+
let polygonType = 0;
|
239 |
+
let types = [];
|
240 |
+
for (let i = 0; i < polygon.vertices.length; i++) {
|
241 |
+
let t = this.normal.dot(polygon.vertices[i].pos) - this.w;
|
242 |
+
let type = (t < -Plane.EPSILON) ? BACK : (t > Plane.EPSILON) ? FRONT : COPLANAR;
|
243 |
+
polygonType |= type;
|
244 |
+
types.push(type);
|
245 |
+
}
|
246 |
+
|
247 |
+
// Put the polygon in the correct list, splitting it when necessary.
|
248 |
+
switch (polygonType) {
|
249 |
+
case COPLANAR:
|
250 |
+
(this.normal.dot(polygon.plane.normal) > 0 ? coplanarFront : coplanarBack).push(polygon);
|
251 |
+
break;
|
252 |
+
case FRONT:
|
253 |
+
front.push(polygon);
|
254 |
+
break;
|
255 |
+
case BACK:
|
256 |
+
back.push(polygon);
|
257 |
+
break;
|
258 |
+
case SPANNING:
|
259 |
+
let f = []
|
260 |
+
, b = [];
|
261 |
+
for (let i = 0; i < polygon.vertices.length; i++) {
|
262 |
+
let j = (i + 1) % polygon.vertices.length;
|
263 |
+
let ti = types[i]
|
264 |
+
, tj = types[j];
|
265 |
+
let vi = polygon.vertices[i]
|
266 |
+
, vj = polygon.vertices[j];
|
267 |
+
if (ti != BACK)
|
268 |
+
f.push(vi);
|
269 |
+
if (ti != FRONT)
|
270 |
+
b.push(ti != BACK ? vi.clone() : vi);
|
271 |
+
if ((ti | tj) == SPANNING) {
|
272 |
+
let t = (this.w - this.normal.dot(vi.pos)) / this.normal.dot(tv0.copy(vj.pos).sub(vi.pos));
|
273 |
+
let v = vi.interpolate(vj, t);
|
274 |
+
f.push(v);
|
275 |
+
b.push(v.clone());
|
276 |
+
}
|
277 |
+
}
|
278 |
+
if (f.length >= 3)
|
279 |
+
front.push(new Polygon(f,polygon.shared));
|
280 |
+
if (b.length >= 3)
|
281 |
+
back.push(new Polygon(b,polygon.shared));
|
282 |
+
break;
|
283 |
+
}
|
284 |
+
}
|
285 |
+
|
286 |
+
}
|
287 |
+
|
288 |
+
// `Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a
|
289 |
+
// point is on the plane.
|
290 |
+
Plane.EPSILON = 1e-5;
|
291 |
+
|
292 |
+
Plane.fromPoints = function(a, b, c) {
|
293 |
+
let n = tv0.copy(b).sub(a).cross(tv1.copy(c).sub(a)).normalize()
|
294 |
+
return new Plane(n.clone(),n.dot(a));
|
295 |
+
}
|
296 |
+
|
297 |
+
|
298 |
+
// # class Polygon
|
299 |
+
|
300 |
+
// Represents a convex polygon. The vertices used to initialize a polygon must
|
301 |
+
// be coplanar and form a convex loop. They do not have to be `Vertex`
|
302 |
+
// instances but they must behave similarly (duck typing can be used for
|
303 |
+
// customization).
|
304 |
+
//
|
305 |
+
// Each convex polygon has a `shared` property, which is shared between all
|
306 |
+
// polygons that are clones of each other or were split from the same polygon.
|
307 |
+
// This can be used to define per-polygon properties (such as surface color).
|
308 |
+
|
309 |
+
class Polygon {
|
310 |
+
constructor(vertices, shared) {
|
311 |
+
this.vertices = vertices;
|
312 |
+
this.shared = shared;
|
313 |
+
this.plane = Plane.fromPoints(vertices[0].pos, vertices[1].pos, vertices[2].pos);
|
314 |
+
}
|
315 |
+
clone() {
|
316 |
+
return new Polygon(this.vertices.map(v=>v.clone()),this.shared);
|
317 |
+
}
|
318 |
+
flip() {
|
319 |
+
this.vertices.reverse().forEach(v=>v.flip())
|
320 |
+
this.plane.flip();
|
321 |
+
}
|
322 |
+
}
|
323 |
+
|
324 |
+
// # class Node
|
325 |
+
|
326 |
+
// Holds a node in a BSP tree. A BSP tree is built from a collection of polygons
|
327 |
+
// by picking a polygon to split along. That polygon (and all other coplanar
|
328 |
+
// polygons) are added directly to that node and the other polygons are added to
|
329 |
+
// the front and/or back subtrees. This is not a leafy BSP tree since there is
|
330 |
+
// no distinction between internal and leaf nodes.
|
331 |
+
|
332 |
+
class Node {
|
333 |
+
constructor(polygons) {
|
334 |
+
this.plane = null;
|
335 |
+
this.front = null;
|
336 |
+
this.back = null;
|
337 |
+
this.polygons = [];
|
338 |
+
if (polygons)
|
339 |
+
this.build(polygons);
|
340 |
+
}
|
341 |
+
clone() {
|
342 |
+
let node = new Node();
|
343 |
+
node.plane = this.plane && this.plane.clone();
|
344 |
+
node.front = this.front && this.front.clone();
|
345 |
+
node.back = this.back && this.back.clone();
|
346 |
+
node.polygons = this.polygons.map(p=>p.clone());
|
347 |
+
return node;
|
348 |
+
}
|
349 |
+
|
350 |
+
// Convert solid space to empty space and empty space to solid space.
|
351 |
+
invert() {
|
352 |
+
for (let i = 0; i < this.polygons.length; i++)
|
353 |
+
this.polygons[i].flip();
|
354 |
+
|
355 |
+
this.plane && this.plane.flip();
|
356 |
+
this.front && this.front.invert();
|
357 |
+
this.back && this.back.invert();
|
358 |
+
let temp = this.front;
|
359 |
+
this.front = this.back;
|
360 |
+
this.back = temp;
|
361 |
+
}
|
362 |
+
|
363 |
+
// Recursively remove all polygons in `polygons` that are inside this BSP
|
364 |
+
// tree.
|
365 |
+
clipPolygons(polygons) {
|
366 |
+
if (!this.plane)
|
367 |
+
return polygons.slice();
|
368 |
+
let front = []
|
369 |
+
, back = [];
|
370 |
+
for (let i = 0; i < polygons.length; i++) {
|
371 |
+
this.plane.splitPolygon(polygons[i], front, back, front, back);
|
372 |
+
}
|
373 |
+
if (this.front)
|
374 |
+
front = this.front.clipPolygons(front);
|
375 |
+
if (this.back)
|
376 |
+
back = this.back.clipPolygons(back);
|
377 |
+
else
|
378 |
+
back = [];
|
379 |
+
//return front;
|
380 |
+
return front.concat(back);
|
381 |
+
}
|
382 |
+
|
383 |
+
// Remove all polygons in this BSP tree that are inside the other BSP tree
|
384 |
+
// `bsp`.
|
385 |
+
clipTo(bsp) {
|
386 |
+
this.polygons = bsp.clipPolygons(this.polygons);
|
387 |
+
if (this.front)
|
388 |
+
this.front.clipTo(bsp);
|
389 |
+
if (this.back)
|
390 |
+
this.back.clipTo(bsp);
|
391 |
+
}
|
392 |
+
|
393 |
+
// Return a list of all polygons in this BSP tree.
|
394 |
+
allPolygons() {
|
395 |
+
let polygons = this.polygons.slice();
|
396 |
+
if (this.front)
|
397 |
+
polygons = polygons.concat(this.front.allPolygons());
|
398 |
+
if (this.back)
|
399 |
+
polygons = polygons.concat(this.back.allPolygons());
|
400 |
+
return polygons;
|
401 |
+
}
|
402 |
+
|
403 |
+
// Build a BSP tree out of `polygons`. When called on an existing tree, the
|
404 |
+
// new polygons are filtered down to the bottom of the tree and become new
|
405 |
+
// nodes there. Each set of polygons is partitioned using the first polygon
|
406 |
+
// (no heuristic is used to pick a good split).
|
407 |
+
build(polygons) {
|
408 |
+
if (!polygons.length)
|
409 |
+
return;
|
410 |
+
if (!this.plane)
|
411 |
+
this.plane = polygons[0].plane.clone();
|
412 |
+
let front = []
|
413 |
+
, back = [];
|
414 |
+
for (let i = 0; i < polygons.length; i++) {
|
415 |
+
this.plane.splitPolygon(polygons[i], this.polygons, this.polygons, front, back);
|
416 |
+
}
|
417 |
+
if (front.length) {
|
418 |
+
if (!this.front)
|
419 |
+
this.front = new Node();
|
420 |
+
this.front.build(front);
|
421 |
+
}
|
422 |
+
if (back.length) {
|
423 |
+
if (!this.back)
|
424 |
+
this.back = new Node();
|
425 |
+
this.back.build(back);
|
426 |
+
}
|
427 |
+
}
|
428 |
+
}
|
429 |
+
|
430 |
+
// Inflate/deserialize a vanilla struct into a CSG structure webworker.
|
431 |
+
CSG.fromJSON=function(json){
|
432 |
+
return CSG.fromPolygons(json.polygons.map(p=>new Polygon(p.vertices.map(v=> new Vertex(v.pos,v.normal,v.uv)),p.shared)))
|
433 |
+
}
|
434 |
+
|
435 |
+
export {CSG,Vertex,Vector,Polygon,Plane}
|
436 |
+
|
437 |
+
|
438 |
+
|
439 |
+
// Return a new CSG solid representing space in either this solid or in the
|
440 |
+
// solid `csg`. Neither this solid nor the solid `csg` are modified.
|
441 |
+
//
|
442 |
+
// A.union(B)
|
443 |
+
//
|
444 |
+
// +-------+ +-------+
|
445 |
+
// | | | |
|
446 |
+
// | A | | |
|
447 |
+
// | +--+----+ = | +----+
|
448 |
+
// +----+--+ | +----+ |
|
449 |
+
// | B | | |
|
450 |
+
// | | | |
|
451 |
+
// +-------+ +-------+
|
452 |
+
//
|
453 |
+
// Return a new CSG solid representing space in this solid but not in the
|
454 |
+
// solid `csg`. Neither this solid nor the solid `csg` are modified.
|
455 |
+
//
|
456 |
+
// A.subtract(B)
|
457 |
+
//
|
458 |
+
// +-------+ +-------+
|
459 |
+
// | | | |
|
460 |
+
// | A | | |
|
461 |
+
// | +--+----+ = | +--+
|
462 |
+
// +----+--+ | +----+
|
463 |
+
// | B |
|
464 |
+
// | |
|
465 |
+
// +-------+
|
466 |
+
//
|
467 |
+
// Return a new CSG solid representing space both this solid and in the
|
468 |
+
// solid `csg`. Neither this solid nor the solid `csg` are modified.
|
469 |
+
//
|
470 |
+
// A.intersect(B)
|
471 |
+
//
|
472 |
+
// +-------+
|
473 |
+
// | |
|
474 |
+
// | A |
|
475 |
+
// | +--+----+ = +--+
|
476 |
+
// +----+--+ | +--+
|
477 |
+
// | B |
|
478 |
+
// | |
|
479 |
+
// +-------+
|
480 |
+
//
|
481 |
+
|
static/csgworker.js
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {CSG} from "./csglib.js"
|
2 |
+
|
3 |
+
|
4 |
+
let gWorkersStarted = false;
|
5 |
+
let gWorker;
|
6 |
+
let gWorkerUrl;
|
7 |
+
|
8 |
+
let taskId = 0;
|
9 |
+
let tasks={}
|
10 |
+
let spawnWorker=()=>{
|
11 |
+
const worker = new Worker(gWorkerUrl)
|
12 |
+
|
13 |
+
worker.onmessage = function(e) {
|
14 |
+
let rslt = JSON.parse(e.data)
|
15 |
+
let task = tasks[rslt.taskId]
|
16 |
+
delete tasks[rslt.taskId]
|
17 |
+
task.resolve(CSG.fromJSON(rslt.result))
|
18 |
+
//console.log('Message received from worker');
|
19 |
+
gWorker.busy = false;
|
20 |
+
}
|
21 |
+
return gWorker = {worker,busy:false};
|
22 |
+
}
|
23 |
+
|
24 |
+
let getWorker=()=>{
|
25 |
+
if(!gWorkersStarted){
|
26 |
+
gWorkersStarted = true;
|
27 |
+
return fetch('../csg-lib.js').then(function(response) {
|
28 |
+
return response.text().then(function(text) {
|
29 |
+
text = text.slice(0, text.lastIndexOf('export'));
|
30 |
+
const code = text + `
|
31 |
+
self.onmessage=(message)=>{
|
32 |
+
let task = JSON.parse(message.data)
|
33 |
+
//console.log("Got task:"+task.op+' '+task.taskId)
|
34 |
+
postMessage(JSON.stringify({
|
35 |
+
taskId:task.taskId,
|
36 |
+
result : CSG.fromJSON(task.a)[task.op](CSG.fromJSON(task.b))
|
37 |
+
}))
|
38 |
+
}
|
39 |
+
console.log('CSG worker started!')`
|
40 |
+
const blob = new Blob([code],{
|
41 |
+
type: 'application/javascript'
|
42 |
+
})
|
43 |
+
gWorkerUrl = URL.createObjectURL(blob);
|
44 |
+
|
45 |
+
|
46 |
+
}).then(()=>{
|
47 |
+
return spawnWorker()
|
48 |
+
})
|
49 |
+
});
|
50 |
+
}
|
51 |
+
if(gWorker && (!gWorker.busy)){
|
52 |
+
gWorker.busy = true;
|
53 |
+
|
54 |
+
return {then:(fn)=>{return fn(gWorker);}}
|
55 |
+
}
|
56 |
+
return{
|
57 |
+
then:function(){return this}
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
CSG.doAsync=(a,op,b)=>{
|
62 |
+
return getWorker().then((worker)=>{
|
63 |
+
let task={a,op,b,taskId}
|
64 |
+
tasks[taskId]=task;
|
65 |
+
taskId++;
|
66 |
+
task.result = new Promise((resolve,reject)=>{
|
67 |
+
task.resolve = resolve;
|
68 |
+
//console.log("posting to worker:")
|
69 |
+
worker.busy = true;
|
70 |
+
worker.worker.postMessage(JSON.stringify(task))
|
71 |
+
})
|
72 |
+
return task.result
|
73 |
+
})
|
74 |
+
}
|
75 |
+
|
76 |
+
export default {}
|
static/filler.html
ADDED
The diff for this file is too large to render.
See raw diff
|
|
static/imports.js
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as THREE from 'three';
|
2 |
+
|
3 |
+
import Stats from 'three/addons/libs/stats.module.js';
|
4 |
+
import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
|
5 |
+
|
6 |
+
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
7 |
+
import 'three/addons/controls/OrbitControls.js';
|
8 |
+
import { Line2 } from 'three/addons/lines/Line2.js';
|
9 |
+
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
10 |
+
import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
|
11 |
+
import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
|
static/script.js
ADDED
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as THREE from 'three';
|
2 |
+
|
3 |
+
import Stats from 'three/addons/libs/stats.module.js';
|
4 |
+
import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
|
5 |
+
|
6 |
+
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
7 |
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
8 |
+
import { Line2 } from 'three/addons/lines/Line2.js';
|
9 |
+
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
10 |
+
import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
|
11 |
+
import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
|
12 |
+
|
13 |
+
import {CSG} from "./threecsg.js"
|
14 |
+
|
15 |
+
var res = window.res;
|
16 |
+
var hc = window.hc;
|
17 |
+
var multi_high = window.multi_high;
|
18 |
+
var current_frame = 0;
|
19 |
+
var meshes = [];
|
20 |
+
var joint_meshes = [];
|
21 |
+
|
22 |
+
function make_linkage(x_length, y_length, z_length, hole_r) {
|
23 |
+
var link = new THREE.BoxGeometry( x_length, y_length, z_length );
|
24 |
+
var end_1 = new THREE.CylinderGeometry(y_length/2, y_length/2, z_length, 32);
|
25 |
+
var end_2 = new THREE.CylinderGeometry(y_length/2, y_length/2, z_length, 32);
|
26 |
+
var hole_1 = new THREE.CylinderGeometry(hole_r, hole_r, z_length, 32);
|
27 |
+
var hole_2 = new THREE.CylinderGeometry(hole_r, hole_r, z_length, 32);
|
28 |
+
end_1.rotateX(Math.PI/2).translate(-x_length/2, 0, 0);
|
29 |
+
end_2.rotateX(Math.PI/2).translate(x_length/2, 0, 0);
|
30 |
+
hole_1.rotateX(Math.PI/2).translate(-x_length/2, 0, 0);
|
31 |
+
hole_2.rotateX(Math.PI/2).translate(x_length/2, 0, 0);
|
32 |
+
|
33 |
+
var bsp_link = CSG.fromMesh(new THREE.Mesh(link));
|
34 |
+
var hole_1 = CSG.fromMesh(new THREE.Mesh(hole_1));
|
35 |
+
var hole_2 = CSG.fromMesh(new THREE.Mesh(hole_2));
|
36 |
+
var bsp_end_1 = CSG.fromMesh(new THREE.Mesh(end_1)).subtract(bsp_link).subtract(hole_1);
|
37 |
+
var bsp_end_2 = CSG.fromMesh(new THREE.Mesh(end_2)).subtract(bsp_link).subtract(hole_2);
|
38 |
+
bsp_link = bsp_link.subtract(hole_1).subtract(hole_2);
|
39 |
+
|
40 |
+
var full_link = CSG.toMesh(bsp_link.union(bsp_end_1).union(bsp_end_2));
|
41 |
+
|
42 |
+
return full_link;
|
43 |
+
}
|
44 |
+
|
45 |
+
function make_link(x_length, y_length, z_length, hole_r, Theta_z, translation) {
|
46 |
+
var full_link = make_linkage(x_length, y_length, z_length * 0.9, hole_r);
|
47 |
+
full_link.rotation.z = Theta_z;
|
48 |
+
full_link.position.set(translation[0] + x_length/2 * Math.cos(Theta_z), translation[1] + x_length/2 * Math.sin(Theta_z), translation[2]);
|
49 |
+
return full_link;
|
50 |
+
}
|
51 |
+
|
52 |
+
function make_joint(x, y, z, height, radius=0.03) {
|
53 |
+
var joint = new THREE.CylinderGeometry(radius, radius, height * 0.9, 32);
|
54 |
+
joint.rotateX(Math.PI/2);
|
55 |
+
joint = new THREE.Mesh(joint);
|
56 |
+
joint.position.set(x, y, z);
|
57 |
+
return joint;
|
58 |
+
}
|
59 |
+
|
60 |
+
(async function onLoad() {
|
61 |
+
var res = window.res;
|
62 |
+
var hc = window.hc;
|
63 |
+
var multi_high = window.multi_high;
|
64 |
+
// find the mean value of the res array
|
65 |
+
var max_x = 0;
|
66 |
+
var max_y = 0;
|
67 |
+
var min_x = Infinity;
|
68 |
+
var min_y = Infinity;
|
69 |
+
for(let i = 0; i < res.length; i++) {
|
70 |
+
for(let j = 0; j < res[i][1].length; j++) {
|
71 |
+
if (res[i][1][j][0] > max_x) {
|
72 |
+
max_x = res[i][1][j][0];
|
73 |
+
}
|
74 |
+
if (res[i][1][j][0] < min_x) {
|
75 |
+
min_x = res[i][1][j][0];
|
76 |
+
}
|
77 |
+
if (res[i][1][j][1] > max_y) {
|
78 |
+
max_y = res[i][1][j][1];
|
79 |
+
}
|
80 |
+
if (res[i][1][j][1] < min_y) {
|
81 |
+
min_y = res[i][1][j][1];
|
82 |
+
}
|
83 |
+
}
|
84 |
+
}
|
85 |
+
var mean_x = (max_x + min_x) / 2;
|
86 |
+
var mean_y = (max_y + min_y) / 2;
|
87 |
+
|
88 |
+
var container, camera, scene, renderer, controls, last_frame_time;
|
89 |
+
|
90 |
+
init();
|
91 |
+
animate();
|
92 |
+
controls.update();
|
93 |
+
function init() {
|
94 |
+
container = document.getElementById('container');
|
95 |
+
|
96 |
+
renderer = new THREE.WebGLRenderer({
|
97 |
+
antialias: true
|
98 |
+
});
|
99 |
+
renderer.setPixelRatio(window.devicePixelRatio);
|
100 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
101 |
+
renderer.shadowMap.enabled = true;
|
102 |
+
container.appendChild(renderer.domElement);
|
103 |
+
|
104 |
+
scene = new THREE.Scene();
|
105 |
+
scene.background = new THREE.Color(0xfafafa);
|
106 |
+
|
107 |
+
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
|
108 |
+
camera.position.set(mean_x,mean_y, 20);
|
109 |
+
scene.add(camera);
|
110 |
+
window.onresize = function() {
|
111 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
112 |
+
camera.aspect = window.innerWidth / window.innerHeight;
|
113 |
+
camera.updateProjectionMatrix();
|
114 |
+
}
|
115 |
+
|
116 |
+
var ambientLight = new THREE.AmbientLight(0xffffff, 2.0);
|
117 |
+
scene.add(ambientLight);
|
118 |
+
|
119 |
+
// var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
|
120 |
+
// directionalLight.position.x = 4;
|
121 |
+
// directionalLight.position.y = 1;
|
122 |
+
// directionalLight.position.z = -2;
|
123 |
+
// scene.add( directionalLight );
|
124 |
+
|
125 |
+
|
126 |
+
controls = new OrbitControls(camera, renderer.domElement);
|
127 |
+
controls.target.set(mean_x,mean_y,0);
|
128 |
+
|
129 |
+
// addGridHelper();
|
130 |
+
createModel(0);
|
131 |
+
last_frame_time = Date.now();
|
132 |
+
}
|
133 |
+
|
134 |
+
|
135 |
+
function createModel(j) {
|
136 |
+
var results = res[j];
|
137 |
+
//iterate through the results and create the links
|
138 |
+
meshes = []
|
139 |
+
for (let i = 0; i < results[0].length; i++) {
|
140 |
+
var link = results[0][i];
|
141 |
+
meshes.push(make_link(link[0], link[1], link[2], link[3], link[4], link[5]));
|
142 |
+
if (i == 0){
|
143 |
+
//yellow
|
144 |
+
meshes[i].material = new THREE.MeshStandardMaterial({ color: 0xff8c00, opacity: 0.5, transparent: true , side: THREE.DoubleSide, depthWrite: true});
|
145 |
+
}
|
146 |
+
else
|
147 |
+
meshes[i].material = new THREE.MeshStandardMaterial({ color: 0x3b3b3b, opacity: 0.5, transparent: true , side: THREE.DoubleSide, depthWrite: true});
|
148 |
+
scene.add(meshes[i]);
|
149 |
+
}
|
150 |
+
joint_meshes = []
|
151 |
+
for (let i = 0; i < results[1].length; i++) {
|
152 |
+
var joint = results[1][i];
|
153 |
+
joint_meshes.push(make_joint(joint[0], joint[1], joint[3], joint[2]));
|
154 |
+
if(joint[4] == 1)//brown
|
155 |
+
joint_meshes[i].material = new THREE.MeshStandardMaterial({ color: 0xff4c00, side: THREE.DoubleSide, depthWrite: false});
|
156 |
+
else
|
157 |
+
joint_meshes[i].material = new THREE.MeshStandardMaterial({ color: 0x121212, side: THREE.DoubleSide, depthWrite: false});
|
158 |
+
scene.add(joint_meshes[i]);
|
159 |
+
}
|
160 |
+
|
161 |
+
if (multi_high) {
|
162 |
+
var n = hc.length;
|
163 |
+
|
164 |
+
for (let j = 0; j < n; j++) {
|
165 |
+
var points = [];
|
166 |
+
var colors = [];
|
167 |
+
for (let i = 0; i <hc[j].length; i++) {
|
168 |
+
points.push(hc[j][i][0], hc[j][i][1], hc[j][i][2]);
|
169 |
+
}
|
170 |
+
|
171 |
+
var geometry = new LineGeometry();
|
172 |
+
geometry.setPositions( points );
|
173 |
+
var material = new LineMaterial( {
|
174 |
+
color: 0xff4c00,
|
175 |
+
linewidth: 0.002, // in world units with size attenuation, pixels otherwise
|
176 |
+
vertexColors: false,
|
177 |
+
|
178 |
+
//resolution: // to be set by renderer, eventually
|
179 |
+
dashed: false,
|
180 |
+
alphaToCoverage: false,
|
181 |
+
depthWrite: false,
|
182 |
+
depthTest: false,
|
183 |
+
transparent: true,
|
184 |
+
|
185 |
+
});
|
186 |
+
material.worldUnits = false;
|
187 |
+
const line = new Line2(geometry, material);
|
188 |
+
line.computeLineDistances();
|
189 |
+
line.scale.set( 1, 1, 1 );
|
190 |
+
line.renderOrder= 9999;
|
191 |
+
scene.add(line);
|
192 |
+
}
|
193 |
+
}
|
194 |
+
else{
|
195 |
+
var points = [];
|
196 |
+
var colors = [];
|
197 |
+
for (let i = 0; i <hc.length; i++) {
|
198 |
+
points.push(hc[i][0], hc[i][1], hc[i][2]);
|
199 |
+
}
|
200 |
+
|
201 |
+
var geometry = new LineGeometry();
|
202 |
+
geometry.setPositions( points );
|
203 |
+
var material = new LineMaterial( {
|
204 |
+
color: 0xff4c00,
|
205 |
+
linewidth: 0.002, // in world units with size attenuation, pixels otherwise
|
206 |
+
vertexColors: false,
|
207 |
+
|
208 |
+
//resolution: // to be set by renderer, eventually
|
209 |
+
dashed: false,
|
210 |
+
alphaToCoverage: false,
|
211 |
+
depthWrite: false,
|
212 |
+
depthTest: false,
|
213 |
+
transparent: true,
|
214 |
+
|
215 |
+
});
|
216 |
+
material.worldUnits = false;
|
217 |
+
const line = new Line2(geometry, material);
|
218 |
+
line.computeLineDistances();
|
219 |
+
line.scale.set( 1, 1, 1 );
|
220 |
+
line.renderOrder= 9999;
|
221 |
+
scene.add(line);
|
222 |
+
}
|
223 |
+
|
224 |
+
|
225 |
+
}
|
226 |
+
|
227 |
+
function addGridHelper() {
|
228 |
+
|
229 |
+
var helper = new THREE.GridHelper(10, 10);
|
230 |
+
helper.material.opacity = 0.25;
|
231 |
+
helper.material.transparent = true;
|
232 |
+
scene.add(helper);
|
233 |
+
|
234 |
+
var axis = new THREE.AxesHelper(100);
|
235 |
+
scene.add(axis);
|
236 |
+
}
|
237 |
+
|
238 |
+
function set_idx(){
|
239 |
+
if(current_frame < res.length-1) {
|
240 |
+
// createModel(current_frame);
|
241 |
+
current_frame += 1;
|
242 |
+
}
|
243 |
+
else {
|
244 |
+
current_frame = 0;
|
245 |
+
}
|
246 |
+
}
|
247 |
+
|
248 |
+
function animate() {
|
249 |
+
|
250 |
+
if (Date.now() - last_frame_time > 1000/30) {
|
251 |
+
set_idx();
|
252 |
+
last_frame_time = Date.now();
|
253 |
+
}
|
254 |
+
|
255 |
+
for (let i = 0; i < meshes.length; i++) {
|
256 |
+
meshes[i].rotation.z = res[current_frame][0][i][4];
|
257 |
+
meshes[i].position.set(res[current_frame][0][i][5][0] + res[current_frame][0][i][0]/2 * Math.cos(res[current_frame][0][i][4]), res[current_frame][0][i][5][1] + res[current_frame][0][i][0]/2 * Math.sin(res[current_frame][0][i][4]), res[current_frame][0][i][5][2]);
|
258 |
+
}
|
259 |
+
|
260 |
+
for (let i = 0; i < joint_meshes.length; i++) {
|
261 |
+
joint_meshes[i].position.set(res[current_frame][1][i][0], res[current_frame][1][i][1], res[current_frame][1][i][3]);
|
262 |
+
}
|
263 |
+
|
264 |
+
requestAnimationFrame(animate);
|
265 |
+
render();
|
266 |
+
}
|
267 |
+
|
268 |
+
function render() {
|
269 |
+
renderer.clearDepth();
|
270 |
+
renderer.render(scene, camera);
|
271 |
+
}
|
272 |
+
})();
|
static/style.css
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
body{
|
2 |
+
margin: 0;
|
3 |
+
}
|
static/threecsg.js
ADDED
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
//import*as THREE from "./lib/three.module.js";
|
3 |
+
|
4 |
+
import * as THREE from "three" //"https://threejs.org/build/three.module.js";
|
5 |
+
|
6 |
+
let { BufferGeometry, Vector3, Vector2} = THREE;
|
7 |
+
import {CSG, Vertex, Vector, Polygon} from "./csglib.js";
|
8 |
+
//import {Geometry} from "../three.js-dev/examples/jsm/deprecated/Geometry.js";
|
9 |
+
|
10 |
+
CSG.fromGeometry = function(geom,objectIndex) {
|
11 |
+
let polys = []
|
12 |
+
if (geom.isGeometry) {
|
13 |
+
let fs = geom.faces;
|
14 |
+
let vs = geom.vertices;
|
15 |
+
let fm = ['a', 'b', 'c']
|
16 |
+
for (let i = 0; i < fs.length; i++) {
|
17 |
+
let f = fs[i];
|
18 |
+
let vertices = []
|
19 |
+
for (let j = 0; j < 3; j++)
|
20 |
+
vertices.push(new Vertex(vs[f[fm[j]]],f.vertexNormals[j],geom.faceVertexUvs[0][i][j]))
|
21 |
+
polys.push(new Polygon(vertices, objectIndex))
|
22 |
+
}
|
23 |
+
} else if (geom.isBufferGeometry) {
|
24 |
+
let vertices, normals, uvs
|
25 |
+
let posattr = geom.attributes.position
|
26 |
+
let normalattr = geom.attributes.normal
|
27 |
+
let uvattr = geom.attributes.uv
|
28 |
+
let colorattr = geom.attributes.color
|
29 |
+
let index;
|
30 |
+
if (geom.index)
|
31 |
+
index = geom.index.array;
|
32 |
+
else {
|
33 |
+
index = new Array((posattr.array.length / posattr.itemSize) | 0);
|
34 |
+
for (let i = 0; i < index.length; i++)
|
35 |
+
index[i] = i
|
36 |
+
}
|
37 |
+
let triCount = (index.length / 3) | 0
|
38 |
+
polys = new Array(triCount)
|
39 |
+
for (let i = 0, pli = 0, l = index.length; i < l; i += 3,
|
40 |
+
pli++) {
|
41 |
+
let vertices = new Array(3)
|
42 |
+
for (let j = 0; j < 3; j++) {
|
43 |
+
let vi = index[i + j]
|
44 |
+
let vp = vi * 3;
|
45 |
+
let vt = vi * 2;
|
46 |
+
let x = posattr.array[vp]
|
47 |
+
let y = posattr.array[vp + 1]
|
48 |
+
let z = posattr.array[vp + 2]
|
49 |
+
let nx = normalattr.array[vp]
|
50 |
+
let ny = normalattr.array[vp + 1]
|
51 |
+
let nz = normalattr.array[vp + 2]
|
52 |
+
//let u = uvattr.array[vt]
|
53 |
+
//let v = uvattr.array[vt + 1]
|
54 |
+
vertices[j] = new Vertex({
|
55 |
+
x,
|
56 |
+
y,
|
57 |
+
z
|
58 |
+
},{
|
59 |
+
x: nx,
|
60 |
+
y: ny,
|
61 |
+
z: nz
|
62 |
+
},uvattr&&{
|
63 |
+
x: uvattr.array[vt],
|
64 |
+
y: uvattr.array[vt+1],
|
65 |
+
z: 0
|
66 |
+
},colorattr&&{x:colorattr.array[vt],y:colorattr.array[vt+1],z:colorattr.array[vt+2]});
|
67 |
+
}
|
68 |
+
polys[pli] = new Polygon(vertices,objectIndex)
|
69 |
+
}
|
70 |
+
} else
|
71 |
+
console.error("Unsupported CSG input type:" + geom.type)
|
72 |
+
return CSG.fromPolygons(polys)
|
73 |
+
}
|
74 |
+
|
75 |
+
let ttvv0 = new THREE.Vector3()
|
76 |
+
let tmpm3 = new THREE.Matrix3();
|
77 |
+
CSG.fromMesh = function(mesh,objectIndex) {
|
78 |
+
let csg = CSG.fromGeometry(mesh.geometry,objectIndex)
|
79 |
+
tmpm3.getNormalMatrix(mesh.matrix);
|
80 |
+
for (let i = 0; i < csg.polygons.length; i++) {
|
81 |
+
let p = csg.polygons[i]
|
82 |
+
for (let j = 0; j < p.vertices.length; j++) {
|
83 |
+
let v = p.vertices[j]
|
84 |
+
v.pos.copy(ttvv0.copy(v.pos).applyMatrix4(mesh.matrix));
|
85 |
+
v.normal.copy(ttvv0.copy(v.normal).applyMatrix3(tmpm3))
|
86 |
+
}
|
87 |
+
}
|
88 |
+
return csg;
|
89 |
+
}
|
90 |
+
|
91 |
+
let nbuf3=(ct)=>{
|
92 |
+
return{
|
93 |
+
top:0,
|
94 |
+
array:new Float32Array(ct),
|
95 |
+
write:function(v){(this.array[this.top++]=v.x);(this.array[this.top++]=v.y);(this.array[this.top++]=v.z);}
|
96 |
+
}
|
97 |
+
}
|
98 |
+
let nbuf2=(ct)=>{
|
99 |
+
return{
|
100 |
+
top:0,
|
101 |
+
array:new Float32Array(ct),
|
102 |
+
write:function(v){(this.array[this.top++]=v.x);(this.array[this.top++]=v.y)}
|
103 |
+
}
|
104 |
+
}
|
105 |
+
|
106 |
+
CSG.toGeometry = function(csg, buffered=true) {
|
107 |
+
let ps = csg.polygons;
|
108 |
+
let geom;
|
109 |
+
let g2;
|
110 |
+
if(!buffered) //Old geometry path...
|
111 |
+
{
|
112 |
+
geom = new Geometry();
|
113 |
+
let vs = geom.vertices;
|
114 |
+
let fvuv = geom.faceVertexUvs[0]
|
115 |
+
for (let i = 0; i < ps.length; i++) {
|
116 |
+
let p = ps[i]
|
117 |
+
let pvs = p.vertices;
|
118 |
+
let v0 = vs.length;
|
119 |
+
let pvlen = pvs.length
|
120 |
+
|
121 |
+
for (let j = 0; j < pvlen; j++)
|
122 |
+
vs.push(new THREE.Vector3().copy(pvs[j].pos))
|
123 |
+
|
124 |
+
for (let j = 3; j <= pvlen; j++) {
|
125 |
+
let fc = new THREE.Face3();
|
126 |
+
let fuv = []
|
127 |
+
fvuv.push(fuv)
|
128 |
+
let fnml = fc.vertexNormals;
|
129 |
+
fc.a = v0;
|
130 |
+
fc.b = v0 + j - 2;
|
131 |
+
fc.c = v0 + j - 1;
|
132 |
+
|
133 |
+
fnml.push(new THREE.Vector3().copy(pvs[0].normal))
|
134 |
+
fnml.push(new THREE.Vector3().copy(pvs[j - 2].normal))
|
135 |
+
fnml.push(new THREE.Vector3().copy(pvs[j - 1].normal))
|
136 |
+
fuv.push(new THREE.Vector3().copy(pvs[0].uv))
|
137 |
+
fuv.push(new THREE.Vector3().copy(pvs[j - 2].uv))
|
138 |
+
fuv.push(new THREE.Vector3().copy(pvs[j - 1].uv))
|
139 |
+
|
140 |
+
fc.normal = new THREE.Vector3().copy(p.plane.normal)
|
141 |
+
geom.faces.push(fc)
|
142 |
+
}
|
143 |
+
}
|
144 |
+
geom = new THREE.BufferGeometry().fromGeometry(geom)
|
145 |
+
geom.verticesNeedUpdate = geom.elementsNeedUpdate = geom.normalsNeedUpdate = true;
|
146 |
+
}else { //BufferGeometry path
|
147 |
+
let triCount = 0;
|
148 |
+
ps.forEach(p=>triCount += (p.vertices.length - 2))
|
149 |
+
geom = new THREE.BufferGeometry()
|
150 |
+
|
151 |
+
let vertices = nbuf3(triCount * 3 * 3)
|
152 |
+
let normals = nbuf3(triCount * 3 * 3)
|
153 |
+
let uvs; // = nbuf2(triCount * 2 * 3)
|
154 |
+
let colors;
|
155 |
+
let grps=[]
|
156 |
+
ps.forEach(p=>{
|
157 |
+
let pvs = p.vertices
|
158 |
+
let pvlen = pvs.length
|
159 |
+
if(p.shared!==undefined){
|
160 |
+
if(!grps[p.shared])grps[p.shared]=[]
|
161 |
+
}
|
162 |
+
if(pvlen){
|
163 |
+
if(pvs[0].color!==undefined){
|
164 |
+
if(!colors)colors = nbuf3(triCount*3*3);
|
165 |
+
}
|
166 |
+
if(pvs[0].uv!==undefined){
|
167 |
+
if(!uvs)uvs = nbuf2(triCount * 2 * 3)
|
168 |
+
}
|
169 |
+
}
|
170 |
+
for (let j = 3; j <= pvlen; j++) {
|
171 |
+
(p.shared!==undefined) && (grps[p.shared].push(vertices.top/3,(vertices.top/3)+1,(vertices.top/3)+2));
|
172 |
+
vertices.write(pvs[0].pos)
|
173 |
+
vertices.write(pvs[j-2].pos)
|
174 |
+
vertices.write(pvs[j-1].pos)
|
175 |
+
normals.write(pvs[0].normal)
|
176 |
+
normals.write(pvs[j-2].normal)
|
177 |
+
normals.write(pvs[j-1].normal);
|
178 |
+
uvs&&(pvs[0].uv)&&(uvs.write(pvs[0].uv)||uvs.write(pvs[j-2].uv)||uvs.write(pvs[j-1].uv));
|
179 |
+
colors&&(colors.write(pvs[0].color)||colors.write(pvs[j-2].color)||colors.write(pvs[j-1].color))
|
180 |
+
}
|
181 |
+
}
|
182 |
+
)
|
183 |
+
geom.setAttribute('position', new THREE.BufferAttribute(vertices.array,3));
|
184 |
+
geom.setAttribute('normal', new THREE.BufferAttribute(normals.array,3));
|
185 |
+
uvs && geom.setAttribute('uv', new THREE.BufferAttribute(uvs.array,2));
|
186 |
+
colors && geom.setAttribute('color', new THREE.BufferAttribute(colors.array,3));
|
187 |
+
if(grps.length){
|
188 |
+
let index = []
|
189 |
+
let gbase=0;
|
190 |
+
for(let gi=0;gi<grps.length;gi++){
|
191 |
+
geom.addGroup(gbase,grps[gi].length,gi)
|
192 |
+
gbase+=grps[gi].length
|
193 |
+
index=index.concat(grps[gi]);
|
194 |
+
}
|
195 |
+
geom.setIndex(index)
|
196 |
+
}
|
197 |
+
g2 = geom;
|
198 |
+
}
|
199 |
+
return geom;
|
200 |
+
}
|
201 |
+
|
202 |
+
CSG.toMesh = function(csg, toMatrix=new THREE.Matrix4(), toMaterial) {
|
203 |
+
let geom = CSG.toGeometry(csg);
|
204 |
+
let inv = new THREE.Matrix4().copy(toMatrix).invert();
|
205 |
+
geom.applyMatrix4(inv);
|
206 |
+
geom.computeBoundingSphere();
|
207 |
+
geom.computeBoundingBox();
|
208 |
+
let m = new THREE.Mesh(geom,toMaterial);
|
209 |
+
m.matrix.copy(toMatrix);
|
210 |
+
m.matrix.decompose(m.position, m.quaternion, m.scale)
|
211 |
+
m.rotation.setFromQuaternion(m.quaternion)
|
212 |
+
m.updateMatrixWorld();
|
213 |
+
m.castShadow = m.receiveShadow = true;
|
214 |
+
return m
|
215 |
+
}
|
216 |
+
|
217 |
+
import "./csgworker.js";
|
218 |
+
|
219 |
+
export {CSG}
|